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/client | |
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 'channels/rdpgfx/client')
-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 |
5 files changed, 2842 insertions, 0 deletions
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 */ |