diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /channels/rdpgfx | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | channels/rdpgfx/CMakeLists.txt | 26 | ||||
-rw-r--r-- | channels/rdpgfx/ChannelOptions.cmake | 13 | ||||
-rw-r--r-- | channels/rdpgfx/client/CMakeLists.txt | 35 | ||||
-rw-r--r-- | channels/rdpgfx/client/rdpgfx_codec.c | 294 | ||||
-rw-r--r-- | channels/rdpgfx/client/rdpgfx_codec.h | 35 | ||||
-rw-r--r-- | channels/rdpgfx/client/rdpgfx_main.c | 2417 | ||||
-rw-r--r-- | channels/rdpgfx/client/rdpgfx_main.h | 61 | ||||
-rw-r--r-- | channels/rdpgfx/rdpgfx_common.c | 197 | ||||
-rw-r--r-- | channels/rdpgfx/rdpgfx_common.h | 54 | ||||
-rw-r--r-- | channels/rdpgfx/server/CMakeLists.txt | 33 | ||||
-rw-r--r-- | channels/rdpgfx/server/rdpgfx_main.c | 1863 | ||||
-rw-r--r-- | channels/rdpgfx/server/rdpgfx_main.h | 42 |
12 files changed, 5070 insertions, 0 deletions
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 */ |