From 161617c4a473c0e35284b99bc30bb554fd994c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Mon, 30 May 2022 16:32:23 -0400 Subject: [PATCH] Implement RDP persistent bitmap cache --- channels/rdpgfx/client/rdpgfx_main.c | 452 ++++++++++++++---- channels/rdpgfx/client/rdpgfx_main.h | 3 + channels/rdpgfx/server/rdpgfx_main.c | 21 +- client/common/cmdline.c | 11 + client/common/cmdline.h | 4 + include/freerdp/cache/bitmap.h | 2 + include/freerdp/cache/persistent.h | 103 ++++ include/freerdp/channels/rdpgfx.h | 6 +- include/freerdp/client/rdpgfx.h | 8 + include/freerdp/graphics.h | 3 +- include/freerdp/settings.h | 4 +- libfreerdp/cache/CMakeLists.txt | 1 + libfreerdp/cache/bitmap.c | 105 +++- libfreerdp/cache/persistent.c | 342 +++++++++++++ libfreerdp/common/settings_getters.c | 9 + libfreerdp/common/settings_str.c | 1 + libfreerdp/core/activation.c | 166 ++++++- libfreerdp/core/activation.h | 19 + libfreerdp/core/capabilities.c | 7 + .../core/test/settings_property_lists.h | 1 + libfreerdp/gdi/gfx.c | 110 ++++- 21 files changed, 1230 insertions(+), 148 deletions(-) create mode 100644 include/freerdp/cache/persistent.h create mode 100644 libfreerdp/cache/persistent.c diff --git a/channels/rdpgfx/client/rdpgfx_main.c b/channels/rdpgfx/client/rdpgfx_main.c index 7ede62537..2222ee438 100644 --- a/channels/rdpgfx/client/rdpgfx_main.c +++ b/channels/rdpgfx/client/rdpgfx_main.c @@ -481,76 +481,6 @@ fail: 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) -{ - UINT16 index; - UINT error = CHANNEL_RC_OK; - wStream* s; - RDPGFX_PLUGIN* gfx; - RDPGFX_CHANNEL_CALLBACK* callback; - RDPGFX_HEADER header; - RDPGFX_CACHE_ENTRY_METADATA* cacheEntries; - - if (!context || !pdu) - return ERROR_BAD_ARGUMENTS; - - gfx = (RDPGFX_PLUGIN*)context->handle; - - if (!gfx || !gfx->listener_callback) - return ERROR_BAD_CONFIGURATION; - - callback = gfx->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 (index = 0; index < pdu->cacheEntriesCount; index++) - { - cacheEntries = &(pdu->cacheEntries[index]); - Stream_Write_UINT64(s, cacheEntries->cacheKey); /* cacheKey (8 bytes) */ - Stream_Write_UINT32(s, cacheEntries->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 * @@ -668,6 +598,340 @@ static UINT rdpgfx_recv_evict_cache_entry_pdu(RDPGFX_CHANNEL_CALLBACK* callback, return error; } +/** + * Load cache import offer from file (offline replay) + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT rdpgfx_load_cache_import_offer(RDPGFX_PLUGIN* gfx, RDPGFX_CACHE_IMPORT_OFFER_PDU* offer) +{ + int idx, count; + UINT error = CHANNEL_RC_OK; + PERSISTENT_CACHE_ENTRY entry; + rdpPersistentCache* persistent = NULL; + rdpSettings* settings = gfx->settings; + + offer->cacheEntriesCount = 0; + + if (!settings->BitmapCachePersistEnabled) + return CHANNEL_RC_OK; + + if (!settings->BitmapCachePersistFile) + return CHANNEL_RC_OK; + + persistent = persistent_cache_new(); + + if (!persistent) + return CHANNEL_RC_NO_MEMORY; + + if (persistent_cache_open(persistent, settings->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 (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) +{ + int idx; + UINT error = CHANNEL_RC_OK; + PERSISTENT_CACHE_ENTRY cacheEntry; + rdpPersistentCache* persistent = NULL; + rdpSettings* settings = gfx->settings; + RdpgfxClientContext* context = (RdpgfxClientContext*)gfx->iface.pInterface; + + WINPR_ASSERT(context); + + if (!settings->BitmapCachePersistEnabled) + return CHANNEL_RC_OK; + + if (!settings->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, settings->BitmapCachePersistFile, TRUE, 3) < 1) { + error = CHANNEL_RC_INITIALIZATION_ERROR; + goto fail; + } + + for (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) +{ + UINT16 index; + UINT error = CHANNEL_RC_OK; + wStream* s; + RDPGFX_HEADER header; + RDPGFX_CHANNEL_CALLBACK* callback; + RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle; + + if (!context || !pdu) + return ERROR_BAD_ARGUMENTS; + + gfx = (RDPGFX_PLUGIN*)context->handle; + + if (!gfx || !gfx->listener_callback) + return ERROR_BAD_CONFIGURATION; + + callback = gfx->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 (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 idx, count; + UINT error = CHANNEL_RC_OK; + PERSISTENT_CACHE_ENTRY entry; + RDPGFX_CACHE_IMPORT_OFFER_PDU offer = { 0 }; + rdpPersistentCache* persistent = NULL; + RdpgfxClientContext* context = (RdpgfxClientContext*)gfx->iface.pInterface; + rdpSettings* settings = gfx->settings; + + if (!settings->BitmapCachePersistEnabled) + return CHANNEL_RC_OK; + + if (!settings->BitmapCachePersistFile) + return CHANNEL_RC_OK; + + persistent = persistent_cache_new(); + + if (!persistent) + return CHANNEL_RC_NO_MEMORY; + + if (persistent_cache_open(persistent, settings->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.cacheEntriesCount = (UINT16) count; + + WLog_DBG(TAG, "Sending Cache Import Offer: %d", count); + + for (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); + + 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; + } + } + + return error; +fail: + persistent_cache_free(persistent); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpgfx_load_cache_import_reply(RDPGFX_PLUGIN* gfx, RDPGFX_CACHE_IMPORT_REPLY_PDU* reply) +{ + int idx; + int count; + UINT16 cacheSlot; + UINT error = CHANNEL_RC_OK; + PERSISTENT_CACHE_ENTRY entry; + rdpPersistentCache* persistent = NULL; + rdpSettings* settings = gfx->settings; + RdpgfxClientContext* context = (RdpgfxClientContext*)gfx->iface.pInterface; + + if (!settings->BitmapCachePersistEnabled) + return CHANNEL_RC_OK; + + if (!settings->BitmapCachePersistFile) + return CHANNEL_RC_OK; + + persistent = persistent_cache_new(); + + if (!persistent) + return CHANNEL_RC_NO_MEMORY; + + if (persistent_cache_open(persistent, settings->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 (idx = 0; idx < count; idx++) + { + if (persistent_cache_read_entry(persistent, &entry) < 1) { + error = ERROR_INVALID_DATA; + goto fail; + } + + 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 * @@ -675,7 +939,7 @@ static UINT rdpgfx_recv_evict_cache_entry_pdu(RDPGFX_CHANNEL_CALLBACK* callback, */ static UINT rdpgfx_recv_cache_import_reply_pdu(RDPGFX_CHANNEL_CALLBACK* callback, wStream* s) { - UINT16 index; + UINT16 idx; RDPGFX_CACHE_IMPORT_REPLY_PDU pdu; RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; RdpgfxClientContext* context = (RdpgfxClientContext*)gfx->iface.pInterface; @@ -689,22 +953,25 @@ static UINT rdpgfx_recv_cache_import_reply_pdu(RDPGFX_CHANNEL_CALLBACK* callback if (!Stream_CheckAndLogRequiredLength(TAG, s, 2ull * pdu.importedEntriesCount)) return ERROR_INVALID_DATA; - pdu.cacheSlots = (UINT16*)calloc(pdu.importedEntriesCount, sizeof(UINT16)); + if (pdu.importedEntriesCount > RDPGFX_CACHE_ENTRY_MAX_COUNT) + return ERROR_INVALID_DATA; - if (!pdu.cacheSlots) + for (idx = 0; idx < pdu.importedEntriesCount; idx++) { - WLog_Print(gfx->log, WLOG_ERROR, "calloc failed!"); - return CHANNEL_RC_NO_MEMORY; - } - - for (index = 0; index < pdu.importedEntriesCount; index++) - { - Stream_Read_UINT16(s, pdu.cacheSlots[index]); /* cacheSlot (2 bytes) */ + 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); @@ -714,7 +981,6 @@ static UINT rdpgfx_recv_cache_import_reply_pdu(RDPGFX_CHANNEL_CALLBACK* callback "context->CacheImportReply failed with error %" PRIu32 "", error); } - free(pdu.cacheSlots); return error; } @@ -1026,6 +1292,7 @@ static UINT rdpgfx_recv_wire_to_surface_2_pdu(RDPGFX_CHANNEL_CALLBACK* callback, 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 @@ -1643,6 +1910,10 @@ static UINT rdpgfx_recv_pdu(RDPGFX_CHANNEL_CALLBACK* callback, wStream* 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: @@ -1680,7 +1951,8 @@ static UINT rdpgfx_recv_pdu(RDPGFX_CHANNEL_CALLBACK* callback, wStream* s) { WLog_Print(gfx->log, WLOG_ERROR, "Error while processing GFX cmdId: %s (0x%04" PRIX16 ")", rdpgfx_get_cmd_id_string(header.cmdId), header.cmdId); - return error; + Stream_SetPosition(s, (beg + header.pduLength)); + return 0; } end = Stream_GetPosition(s); @@ -1710,7 +1982,8 @@ static UINT rdpgfx_on_data_received(IWTSVirtualChannelCallback* pChannelCallback RDPGFX_CHANNEL_CALLBACK* callback = (RDPGFX_CHANNEL_CALLBACK*)pChannelCallback; RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; UINT error = CHANNEL_RC_OK; - status = zgfx_decompress(gfx->zgfx, Stream_Pointer(data), Stream_GetRemainingLength(data), + status = zgfx_decompress(gfx->zgfx, Stream_Pointer(data), + (UINT32) Stream_GetRemainingLength(data), &pDstData, &DstSize, 0); if (status < 0) @@ -1777,11 +2050,20 @@ static UINT rdpgfx_on_open(IWTSVirtualChannelCallback* pChannelCallback) */ static UINT rdpgfx_on_close(IWTSVirtualChannelCallback* pChannelCallback) { + UINT error = CHANNEL_RC_OK; RDPGFX_CHANNEL_CALLBACK* callback = (RDPGFX_CHANNEL_CALLBACK*)pChannelCallback; RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)callback->plugin; RdpgfxClientContext* context = (RdpgfxClientContext*)gfx->iface.pInterface; 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); @@ -1936,7 +2218,7 @@ static UINT rdpgfx_get_surface_ids(RdpgfxClientContext* context, UINT16** ppSurf for (index = 0; index < count; index++) { - pSurfaceIds[index] = pKeys[index] - 1; + pSurfaceIds[index] = (UINT16) (pKeys[index] - 1); } free(pKeys); @@ -1963,7 +2245,7 @@ static void* rdpgfx_get_surface_data(RdpgfxClientContext* context, UINT16 surfac static UINT rdpgfx_set_cache_slot_data(RdpgfxClientContext* context, UINT16 cacheSlot, void* pData) { RDPGFX_PLUGIN* gfx = (RDPGFX_PLUGIN*)context->handle; - + /* Microsoft uses 1-based indexing for the egfx bitmap cache ! */ if (cacheSlot == 0 || cacheSlot > gfx->MaxCacheSlots) { diff --git a/channels/rdpgfx/client/rdpgfx_main.h b/channels/rdpgfx/client/rdpgfx_main.h index b6477cb69..887bb1452 100644 --- a/channels/rdpgfx/client/rdpgfx_main.h +++ b/channels/rdpgfx/client/rdpgfx_main.h @@ -30,6 +30,7 @@ #include #include #include +#include #include typedef struct @@ -78,6 +79,8 @@ typedef struct UINT16 MaxCacheSlots; void* CacheSlots[25600]; + rdpPersistentCache* persistent; + rdpContext* rdpcontext; wLog* log; diff --git a/channels/rdpgfx/server/rdpgfx_main.c b/channels/rdpgfx/server/rdpgfx_main.c index b07f82ae3..e2695c2e0 100644 --- a/channels/rdpgfx/server/rdpgfx_main.c +++ b/channels/rdpgfx/server/rdpgfx_main.c @@ -1125,7 +1125,7 @@ static UINT rdpgfx_recv_cache_import_offer_pdu(RdpgfxServerContext* context, wSt { UINT16 index; RDPGFX_CACHE_IMPORT_OFFER_PDU pdu = { 0 }; - RDPGFX_CACHE_ENTRY_METADATA* cacheEntries; + RDPGFX_CACHE_ENTRY_METADATA* cacheEntry; UINT error = CHANNEL_RC_OK; if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) @@ -1144,23 +1144,11 @@ static UINT rdpgfx_recv_cache_import_offer_pdu(RdpgfxServerContext* context, wSt if (!Stream_CheckAndLogRequiredLength(TAG, s, 12ull * pdu.cacheEntriesCount)) return ERROR_INVALID_DATA; - if (pdu.cacheEntriesCount > 0) - { - pdu.cacheEntries = (RDPGFX_CACHE_ENTRY_METADATA*)calloc( - pdu.cacheEntriesCount, sizeof(RDPGFX_CACHE_ENTRY_METADATA)); - - if (!pdu.cacheEntries) - { - WLog_ERR(TAG, "calloc failed!"); - return CHANNEL_RC_NO_MEMORY; - } - } - for (index = 0; index < pdu.cacheEntriesCount; index++) { - cacheEntries = &(pdu.cacheEntries[index]); - Stream_Read_UINT64(s, cacheEntries->cacheKey); /* cacheKey (8 bytes) */ - Stream_Read_UINT32(s, cacheEntries->bitmapLength); /* bitmapLength (4 bytes) */ + cacheEntry = &(pdu.cacheEntries[index]); + Stream_Read_UINT64(s, cacheEntry->cacheKey); /* cacheKey (8 bytes) */ + Stream_Read_UINT32(s, cacheEntry->bitmapLength); /* bitmapLength (4 bytes) */ } if (context) @@ -1171,7 +1159,6 @@ static UINT rdpgfx_recv_cache_import_offer_pdu(RdpgfxServerContext* context, wSt WLog_ERR(TAG, "context->CacheImportOffer failed with error %" PRIu32 "", error); } - free(pdu.cacheEntries); return error; } diff --git a/client/common/cmdline.c b/client/common/cmdline.c index 590ea73e1..6ba482682 100644 --- a/client/common/cmdline.c +++ b/client/common/cmdline.c @@ -3082,6 +3082,17 @@ int freerdp_client_settings_parse_command_line_arguments(rdpSettings* settings, { settings->BitmapCacheEnabled = enable; } + CommandLineSwitchCase(arg, "persist-cache") + { + settings->BitmapCachePersistEnabled = enable; + } + CommandLineSwitchCase(arg, "persist-cache-file") + { + if (!freerdp_settings_set_string(settings, FreeRDP_BitmapCachePersistFile, arg->Value)) + return COMMAND_LINE_ERROR_MEMORY; + + settings->BitmapCachePersistEnabled = TRUE; + } CommandLineSwitchCase(arg, "offscreen-cache") { settings->OffscreenSupportLevel = (UINT32)enable; diff --git a/client/common/cmdline.h b/client/common/cmdline.h index 2498cfdea..d53d2a5c1 100644 --- a/client/common/cmdline.h +++ b/client/common/cmdline.h @@ -66,6 +66,10 @@ static const COMMAND_LINE_ARGUMENT_A global_cmd_args[] = { "Automatic reconnection maximum retries, 0 for unlimited [0,1000]" }, { "bitmap-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, "bitmap cache" }, + { "persist-cache", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueFalse, NULL, -1, NULL, + "persistent bitmap cache" }, + { "persist-cache-file", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, + "persistent bitmap cache file" }, { "bpp", COMMAND_LINE_VALUE_REQUIRED, "", "16", NULL, -1, NULL, "Session bpp (color depth)" }, { "buildconfig", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_BUILDCONFIG, NULL, NULL, NULL, -1, diff --git a/include/freerdp/cache/bitmap.h b/include/freerdp/cache/bitmap.h index d2be4330a..641c8a770 100644 --- a/include/freerdp/cache/bitmap.h +++ b/include/freerdp/cache/bitmap.h @@ -24,6 +24,7 @@ #include #include #include +#include #include @@ -49,6 +50,7 @@ typedef struct /* internal */ rdpContext* context; + rdpPersistentCache* persistent; } rdpBitmapCache; #ifdef __cplusplus diff --git a/include/freerdp/cache/persistent.h b/include/freerdp/cache/persistent.h new file mode 100644 index 000000000..e1cdbde4e --- /dev/null +++ b/include/freerdp/cache/persistent.h @@ -0,0 +1,103 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Persistent Bitmap Cache + * + * Copyright 2016 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_PERSISTENT_CACHE_H +#define FREERDP_PERSISTENT_CACHE_H + +#include +#include +#include +#include + +#include +#include + +typedef struct rdp_persistent_cache rdpPersistentCache; + +#pragma pack(push, 1) + +/* 12 bytes */ + +struct _PERSISTENT_CACHE_HEADER_V3 +{ + BYTE sig[8]; + UINT32 flags; /* 0x00000003, 0x00000006 */ +}; +typedef struct _PERSISTENT_CACHE_HEADER_V3 PERSISTENT_CACHE_HEADER_V3; + +/* 12 bytes */ + +struct _PERSISTENT_CACHE_ENTRY_V3 +{ + UINT64 key64; + UINT16 width; + UINT16 height; +}; +typedef struct _PERSISTENT_CACHE_ENTRY_V3 PERSISTENT_CACHE_ENTRY_V3; + +/* 20 bytes */ + +struct _PERSISTENT_CACHE_ENTRY_V2 +{ + UINT64 key64; + UINT16 width; + UINT16 height; + UINT32 size; + UINT32 flags; /* 0x00000011 */ +}; +typedef struct _PERSISTENT_CACHE_ENTRY_V2 PERSISTENT_CACHE_ENTRY_V2; + +#pragma pack(pop) + +struct _PERSISTENT_CACHE_ENTRY +{ + UINT64 key64; + UINT16 width; + UINT16 height; + UINT32 size; + UINT32 flags; + BYTE* data; +}; +typedef struct _PERSISTENT_CACHE_ENTRY PERSISTENT_CACHE_ENTRY; + +#ifdef __cplusplus +extern "C" +{ +#endif + + FREERDP_API int persistent_cache_get_version(rdpPersistentCache* persistent); + FREERDP_API int persistent_cache_get_count(rdpPersistentCache* persistent); + + FREERDP_API int persistent_cache_read_entry(rdpPersistentCache* persistent, + PERSISTENT_CACHE_ENTRY* entry); + FREERDP_API int persistent_cache_write_entry(rdpPersistentCache* persistent, + const PERSISTENT_CACHE_ENTRY* entry); + + FREERDP_API int persistent_cache_open(rdpPersistentCache* persistent, const char* filename, + BOOL write, UINT32 version); + FREERDP_API int persistent_cache_close(rdpPersistentCache* persistent); + + FREERDP_API rdpPersistentCache* persistent_cache_new(void); + FREERDP_API void persistent_cache_free(rdpPersistentCache* persistent); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_PERSISTENT_CACHE_H */ diff --git a/include/freerdp/channels/rdpgfx.h b/include/freerdp/channels/rdpgfx.h index c179642c7..df22e44e7 100644 --- a/include/freerdp/channels/rdpgfx.h +++ b/include/freerdp/channels/rdpgfx.h @@ -309,16 +309,18 @@ typedef struct UINT32 bitmapLength; } RDPGFX_CACHE_ENTRY_METADATA; +#define RDPGFX_CACHE_ENTRY_MAX_COUNT 5462 + typedef struct { UINT16 cacheEntriesCount; - RDPGFX_CACHE_ENTRY_METADATA* cacheEntries; + RDPGFX_CACHE_ENTRY_METADATA cacheEntries[RDPGFX_CACHE_ENTRY_MAX_COUNT]; } RDPGFX_CACHE_IMPORT_OFFER_PDU; typedef struct { UINT16 importedEntriesCount; - UINT16* cacheSlots; + UINT16 cacheSlots[RDPGFX_CACHE_ENTRY_MAX_COUNT]; } RDPGFX_CACHE_IMPORT_REPLY_PDU; typedef struct diff --git a/include/freerdp/client/rdpgfx.h b/include/freerdp/client/rdpgfx.h index 8561a3e2b..4f06add74 100644 --- a/include/freerdp/client/rdpgfx.h +++ b/include/freerdp/client/rdpgfx.h @@ -26,6 +26,8 @@ #include #include +#include + /** * Client Interface */ @@ -60,6 +62,10 @@ typedef UINT (*pcRdpgfxCacheImportReply)(RdpgfxClientContext* context, const RDPGFX_CACHE_IMPORT_REPLY_PDU* cacheImportReply); typedef UINT (*pcRdpgfxEvictCacheEntry)(RdpgfxClientContext* context, const RDPGFX_EVICT_CACHE_ENTRY_PDU* evictCacheEntry); +typedef UINT (*pcRdpgfxImportCacheEntry)(RdpgfxClientContext* context, UINT16 cacheSlot, + PERSISTENT_CACHE_ENTRY* importCacheEntry); +typedef UINT (*pcRdpgfxExportCacheEntry)(RdpgfxClientContext* context, UINT16 cacheSlot, + PERSISTENT_CACHE_ENTRY* importCacheEntry); typedef UINT (*pcRdpgfxMapSurfaceToOutput)(RdpgfxClientContext* context, const RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU* surfaceToOutput); typedef UINT (*pcRdpgfxMapSurfaceToScaledOutput)( @@ -116,6 +122,8 @@ struct s_rdpgfx_client_context pcRdpgfxCacheToSurface CacheToSurface; pcRdpgfxCacheImportOffer CacheImportOffer; pcRdpgfxCacheImportReply CacheImportReply; + pcRdpgfxImportCacheEntry ImportCacheEntry; + pcRdpgfxExportCacheEntry ExportCacheEntry; pcRdpgfxEvictCacheEntry EvictCacheEntry; pcRdpgfxMapSurfaceToOutput MapSurfaceToOutput; pcRdpgfxMapSurfaceToScaledOutput MapSurfaceToScaledOutput; diff --git a/include/freerdp/graphics.h b/include/freerdp/graphics.h index 8883cc00d..e72202923 100644 --- a/include/freerdp/graphics.h +++ b/include/freerdp/graphics.h @@ -65,7 +65,8 @@ extern "C" UINT32 flags; /* 23 */ UINT32 length; /* 24 */ BYTE* data; /* 25 */ - UINT32 paddingB[32 - 26]; /* 26 */ + UINT64 key64; /* 26 */ + UINT32 paddingB[32 - 27]; /* 27 */ BOOL compressed; /* 32 */ BOOL ephemeral; /* 33 */ diff --git a/include/freerdp/settings.h b/include/freerdp/settings.h index b81e2f7d3..28edb047b 100644 --- a/include/freerdp/settings.h +++ b/include/freerdp/settings.h @@ -803,6 +803,7 @@ typedef struct #define FreeRDP_BitmapCachePersistEnabled (2500) #define FreeRDP_BitmapCacheV2NumCells (2501) #define FreeRDP_BitmapCacheV2CellInfo (2502) +#define FreeRDP_BitmapCachePersistFile (2503) #define FreeRDP_ColorPointerFlag (2560) #define FreeRDP_PointerCacheSize (2561) #define FreeRDP_KeyboardRemappingList (2622) @@ -1374,7 +1375,8 @@ struct rdp_settings ALIGN64 BOOL BitmapCachePersistEnabled; /* 2500 */ ALIGN64 UINT32 BitmapCacheV2NumCells; /* 2501 */ ALIGN64 BITMAP_CACHE_V2_CELL_INFO* BitmapCacheV2CellInfo; /* 2502 */ - UINT64 padding2560[2560 - 2503]; /* 2503 */ + ALIGN64 char* BitmapCachePersistFile; /* 2503 */ + UINT64 padding2560[2560 - 2504]; /* 2504 */ /* Pointer Capabilities */ ALIGN64 BOOL ColorPointerFlag; /* 2560 */ diff --git a/libfreerdp/cache/CMakeLists.txt b/libfreerdp/cache/CMakeLists.txt index 28f47a07e..5cea23ae3 100644 --- a/libfreerdp/cache/CMakeLists.txt +++ b/libfreerdp/cache/CMakeLists.txt @@ -25,6 +25,7 @@ freerdp_module_add( pointer.h bitmap.c bitmap.h + persistent.c nine_grid.c offscreen.c palette.c diff --git a/libfreerdp/cache/bitmap.c b/libfreerdp/cache/bitmap.c index e6a7bfffb..060dbe5b0 100644 --- a/libfreerdp/cache/bitmap.c +++ b/libfreerdp/cache/bitmap.c @@ -142,6 +142,8 @@ static BOOL update_gdi_cache_bitmap_v2(rdpContext* context, CACHE_BITMAP_V2_ORDE return FALSE; const UINT32 ColorDepth = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth); + bitmap->key64 = ((UINT64)cacheBitmapV2->key1 | (((UINT64)cacheBitmapV2->key2) << 32)); + if (!cacheBitmapV2->bitmapBpp) cacheBitmapV2->bitmapBpp = ColorDepth; @@ -184,6 +186,8 @@ static BOOL update_gdi_cache_bitmap_v3(rdpContext* context, CACHE_BITMAP_V3_ORDE return FALSE; const UINT32 ColorDepth = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth); + bitmap->key64 = ((UINT64)cacheBitmapV3->key1 | (((UINT64)cacheBitmapV3->key2) << 32)); + if (!cacheBitmapV3->bpp) cacheBitmapV3->bpp = ColorDepth; @@ -278,6 +282,67 @@ void bitmap_cache_register_callbacks(rdpUpdate* update) } } +int bitmap_cache_save_persistent(rdpBitmapCache* bitmapCache) +{ + int status; + UINT32 i, j; + UINT32 version; + rdpPersistentCache* persistent; + rdpContext* context = bitmapCache->context; + rdpSettings* settings = context->settings; + + version = settings->BitmapCacheVersion; + + if (version != 2) + return 0; /* persistent bitmap cache already saved in egfx channel */ + + if (!settings->BitmapCachePersistEnabled) + return 0; + + if (!settings->BitmapCachePersistFile) + return 0; + + persistent = persistent_cache_new(); + + if (!persistent) + return -1; + + status = persistent_cache_open(persistent, settings->BitmapCachePersistFile, TRUE, version); + + if (status < 1) + goto end; + + for (i = 0; i < bitmapCache->maxCells; i++) + { + for (j = 0; j < bitmapCache->cells[i].number + 1; j++) + { + PERSISTENT_CACHE_ENTRY cacheEntry; + rdpBitmap* bitmap = bitmapCache->cells[i].entries[j]; + + if (!bitmap || !bitmap->key64) + continue; + + cacheEntry.key64 = bitmap->key64; + cacheEntry.width = bitmap->width; + cacheEntry.height = bitmap->height; + cacheEntry.size = (UINT32)(bitmap->width * bitmap->height * 4); + cacheEntry.flags = 0; + cacheEntry.data = bitmap->data; + + if (persistent_cache_write_entry(persistent, &cacheEntry) < 1) { + status = -1; + goto end; + } + } + } + + status = 1; + +end: + persistent_cache_free(persistent); + return status; +} + rdpBitmapCache* bitmap_cache_new(rdpContext* context) { UINT32 i; @@ -326,27 +391,33 @@ fail: void bitmap_cache_free(rdpBitmapCache* bitmapCache) { - if (bitmapCache) - { - UINT32 i; - for (i = 0; i < bitmapCache->maxCells; i++) - { - UINT32 j; - BITMAP_V2_CELL* cell = &bitmapCache->cells[i]; - if (!cell->entries) - continue; - for (j = 0; j < cell->number + 1; j++) - { - rdpBitmap* bitmap = cell->entries[j]; - Bitmap_Free(bitmapCache->context, bitmap); - } + if (!bitmapCache) + return; - free(bitmapCache->cells[i].entries); + bitmap_cache_save_persistent(bitmapCache); + + UINT32 i; + for (i = 0; i < bitmapCache->maxCells; i++) + { + UINT32 j; + BITMAP_V2_CELL* cell = &bitmapCache->cells[i]; + + if (!cell->entries) + continue; + + for (j = 0; j < cell->number + 1; j++) + { + rdpBitmap* bitmap = cell->entries[j]; + Bitmap_Free(bitmapCache->context, bitmap); } - free(bitmapCache->cells); - free(bitmapCache); + free(bitmapCache->cells[i].entries); } + + free(bitmapCache->cells); + persistent_cache_free(bitmapCache->persistent); + + free(bitmapCache); } static void free_bitmap_data(BITMAP_DATA* data, size_t count) diff --git a/libfreerdp/cache/persistent.c b/libfreerdp/cache/persistent.c new file mode 100644 index 000000000..989273d2e --- /dev/null +++ b/libfreerdp/cache/persistent.c @@ -0,0 +1,342 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Persistent Bitmap Cache + * + * Copyright 2016 Marc-Andre Moreau + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include +#include + +#include +#include + +#define TAG FREERDP_TAG("cache.persistent") + +struct rdp_persistent_cache +{ + FILE* fp; + BOOL write; + UINT32 version; + int count; + char* filename; + BYTE* bmpData; + UINT32 bmpSize; +}; + +int persistent_cache_get_version(rdpPersistentCache* persistent) +{ + return persistent->version; +} + +int persistent_cache_get_count(rdpPersistentCache* persistent) +{ + return persistent->count; +} + +int persistent_cache_read_entry_v2(rdpPersistentCache* persistent, PERSISTENT_CACHE_ENTRY* entry) +{ + PERSISTENT_CACHE_ENTRY_V2 entry2; + + if (fread((void*)&entry2, 1, sizeof(PERSISTENT_CACHE_ENTRY_V2), persistent->fp) != sizeof(PERSISTENT_CACHE_ENTRY_V2)) + return -1; + + entry->key64 = entry2.key64; + entry->width = entry2.width; + entry->height = entry2.height; + entry->size = entry2.width * entry2.height * 4; + entry->flags = entry2.flags; + + entry->data = persistent->bmpData; + + if (fread((void*)entry->data, 1, 0x4000, persistent->fp) != 0x4000) + return -1; + + return 1; +} + +int persistent_cache_write_entry_v2(rdpPersistentCache* persistent, const PERSISTENT_CACHE_ENTRY* entry) +{ + int padding; + PERSISTENT_CACHE_ENTRY_V2 entry2; + + entry2.key64 = entry->key64; + entry2.width = entry->width; + entry2.height = entry->height; + entry2.size = entry->size; + entry2.flags = entry->flags; + + if (!entry2.flags) + entry2.flags = 0x00000011; + + if (fwrite((void*)&entry2, 1, sizeof(PERSISTENT_CACHE_ENTRY_V2), persistent->fp) != sizeof(PERSISTENT_CACHE_ENTRY_V2)) + return -1; + + if (fwrite((void*)entry->data, 1, entry->size, persistent->fp) != entry->size) + return -1; + + if (0x4000 > entry->size) + { + padding = 0x4000 - entry->size; + + if (fwrite((void*)persistent->bmpData, 1, padding, persistent->fp) != padding) + return -1; + } + + persistent->count++; + + return 1; +} + +int persistent_cache_read_v2(rdpPersistentCache* persistent) +{ + PERSISTENT_CACHE_ENTRY_V2 entry; + + while (1) + { + if (fread((void*)&entry, 1, sizeof(PERSISTENT_CACHE_ENTRY_V2), persistent->fp) != sizeof(PERSISTENT_CACHE_ENTRY_V2)) + break; + + if (fseek(persistent->fp, 0x4000, SEEK_CUR) != 0) + break; + + persistent->count++; + } + + return 1; +} + +int persistent_cache_read_entry_v3(rdpPersistentCache* persistent, PERSISTENT_CACHE_ENTRY* entry) +{ + PERSISTENT_CACHE_ENTRY_V3 entry3; + + if (fread((void*)&entry3, 1, sizeof(PERSISTENT_CACHE_ENTRY_V3), persistent->fp) != sizeof(PERSISTENT_CACHE_ENTRY_V3)) + return -1; + + entry->key64 = entry3.key64; + entry->width = entry3.width; + entry->height = entry3.height; + entry->size = entry3.width * entry3.height * 4; + entry->flags = 0; + + if (entry->size > persistent->bmpSize) + { + persistent->bmpSize = entry->size; + BYTE* bmpData = (BYTE*)realloc(persistent->bmpData, persistent->bmpSize); + + if (!bmpData) { + free(persistent->bmpData); + persistent->bmpData = NULL; + persistent->bmpSize = 0; + return -1; + } + + persistent->bmpData = bmpData; + } + + entry->data = persistent->bmpData; + + if (fread((void*)entry->data, 1, entry->size, persistent->fp) != entry->size) + return -1; + + return 1; +} + +int persistent_cache_write_entry_v3(rdpPersistentCache* persistent, const PERSISTENT_CACHE_ENTRY* entry) +{ + PERSISTENT_CACHE_ENTRY_V3 entry3; + + entry3.key64 = entry->key64; + entry3.width = entry->width; + entry3.height = entry->height; + + if (fwrite((void*)&entry3, 1, sizeof(PERSISTENT_CACHE_ENTRY_V3), persistent->fp) != sizeof(PERSISTENT_CACHE_ENTRY_V3)) + return -1; + + if (fwrite((void*)entry->data, 1, entry->size, persistent->fp) != entry->size) + return -1; + + persistent->count++; + + return 1; +} + +int persistent_cache_read_v3(rdpPersistentCache* persistent) +{ + PERSISTENT_CACHE_ENTRY_V3 entry; + + while (1) + { + if (fread((void*)&entry, 1, sizeof(PERSISTENT_CACHE_ENTRY_V3), persistent->fp) != sizeof(PERSISTENT_CACHE_ENTRY_V3)) + break; + + if (fseek(persistent->fp, (entry.width * entry.height * 4), SEEK_CUR) != 0) + break; + + persistent->count++; + } + + return 1; +} + +int persistent_cache_read_entry(rdpPersistentCache* persistent, PERSISTENT_CACHE_ENTRY* entry) +{ + if (persistent->version == 3) + return persistent_cache_read_entry_v3(persistent, entry); + else if (persistent->version == 2) + return persistent_cache_read_entry_v2(persistent, entry); + + return -1; +} + +int persistent_cache_write_entry(rdpPersistentCache* persistent, const PERSISTENT_CACHE_ENTRY* entry) +{ + if (persistent->version == 3) + return persistent_cache_write_entry_v3(persistent, entry); + else if (persistent->version == 2) + return persistent_cache_write_entry_v2(persistent, entry); + + return -1; +} + +int persistent_cache_open_read(rdpPersistentCache* persistent) +{ + BYTE sig[8]; + int status = 1; + PERSISTENT_CACHE_HEADER_V3 header; + + persistent->fp = fopen(persistent->filename, "rb"); + + if (!persistent->fp) + return -1; + + if (fread(sig, 1, 8, persistent->fp) != 8) + return -1; + + if (!strncmp((const char*) sig, "RDP8bmp", 8)) + persistent->version = 3; + else + persistent->version = 2; + + fseek(persistent->fp, 0, SEEK_SET); + + if (persistent->version == 3) + { + if (fread(&header, 1, sizeof(PERSISTENT_CACHE_HEADER_V3), persistent->fp) != sizeof(PERSISTENT_CACHE_HEADER_V3)) + return -1; + + status = persistent_cache_read_v3(persistent); + + fseek(persistent->fp, sizeof(PERSISTENT_CACHE_HEADER_V3), SEEK_SET); + } + else + { + status = persistent_cache_read_v2(persistent); + + fseek(persistent->fp, 0, SEEK_SET); + } + + return status; +} + +int persistent_cache_open_write(rdpPersistentCache* persistent) +{ + PERSISTENT_CACHE_HEADER_V3 header; + + persistent->fp = fopen(persistent->filename, "w+b"); + + if (!persistent->fp) + return -1; + + if (persistent->version == 3) + { + strncpy((char*) header.sig, "RDP8bmp", 8); + header.flags = 0x00000006; + + if (fwrite(&header, 1, sizeof(PERSISTENT_CACHE_HEADER_V3), persistent->fp) != sizeof(PERSISTENT_CACHE_HEADER_V3)) + return -1; + } + + ZeroMemory(persistent->bmpData, persistent->bmpSize); + + return 1; +} + +int persistent_cache_open(rdpPersistentCache* persistent, const char* filename, BOOL write, + UINT32 version) +{ + persistent->write = write; + + persistent->filename = _strdup(filename); + + if (!persistent->filename) + return -1; + + if (persistent->write) + { + persistent->version = version; + return persistent_cache_open_write(persistent); + } + + return persistent_cache_open_read(persistent); +} + +int persistent_cache_close(rdpPersistentCache* persistent) +{ + if (persistent->fp) + { + fclose(persistent->fp); + persistent->fp = NULL; + } + + return 1; +} + +rdpPersistentCache* persistent_cache_new(void) +{ + rdpPersistentCache* persistent; + + persistent = (rdpPersistentCache*)calloc(1, sizeof(rdpPersistentCache)); + + if (!persistent) + return NULL; + + persistent->bmpSize = 0x4000; + persistent->bmpData = (BYTE*)malloc(persistent->bmpSize); + + if (!persistent->bmpData) + return NULL; + + return persistent; +} + +void persistent_cache_free(rdpPersistentCache* persistent) +{ + if (!persistent) + return; + + persistent_cache_close(persistent); + + free(persistent->filename); + + free(persistent->bmpData); + + free(persistent); +} \ No newline at end of file diff --git a/libfreerdp/common/settings_getters.c b/libfreerdp/common/settings_getters.c index e218f02c2..644a32b51 100644 --- a/libfreerdp/common/settings_getters.c +++ b/libfreerdp/common/settings_getters.c @@ -2361,6 +2361,9 @@ const char* freerdp_settings_get_string(const rdpSettings* settings, size_t id) case FreeRDP_AuthenticationServiceClass: return settings->AuthenticationServiceClass; + case FreeRDP_BitmapCachePersistFile: + return settings->BitmapCachePersistFile; + case FreeRDP_CardName: return settings->CardName; @@ -2622,6 +2625,9 @@ char* freerdp_settings_get_string_writable(rdpSettings* settings, size_t id) case FreeRDP_AuthenticationServiceClass: return settings->AuthenticationServiceClass; + case FreeRDP_BitmapCachePersistFile: + return settings->BitmapCachePersistFile; + case FreeRDP_CardName: return settings->CardName; @@ -2893,6 +2899,9 @@ BOOL freerdp_settings_set_string_(rdpSettings* settings, size_t id, const char* case FreeRDP_AuthenticationServiceClass: return update_string(&settings->AuthenticationServiceClass, cnv.cc, len, cleanup); + case FreeRDP_BitmapCachePersistFile: + return update_string(&settings->BitmapCachePersistFile, cnv.cc, len, cleanup); + case FreeRDP_CardName: return update_string(&settings->CardName, cnv.cc, len, cleanup); diff --git a/libfreerdp/common/settings_str.c b/libfreerdp/common/settings_str.c index 6e50eb53b..5d7ff4aa4 100644 --- a/libfreerdp/common/settings_str.c +++ b/libfreerdp/common/settings_str.c @@ -316,6 +316,7 @@ static const struct settings_str_entry settings_map[] = { { FreeRDP_AlternateShell, 7, "FreeRDP_AlternateShell" }, { FreeRDP_AssistanceFile, 7, "FreeRDP_AssistanceFile" }, { FreeRDP_AuthenticationServiceClass, 7, "FreeRDP_AuthenticationServiceClass" }, + { FreeRDP_BitmapCachePersistFile, 7, "FreeRDP_BitmapCachePersistFile" }, { FreeRDP_CardName, 7, "FreeRDP_CardName" }, { FreeRDP_CertificateAcceptedFingerprints, 7, "FreeRDP_CertificateAcceptedFingerprints" }, { FreeRDP_CertificateContent, 7, "FreeRDP_CertificateContent" }, diff --git a/libfreerdp/core/activation.c b/libfreerdp/core/activation.c index b34f29549..5588b932b 100644 --- a/libfreerdp/core/activation.c +++ b/libfreerdp/core/activation.c @@ -236,53 +236,173 @@ BOOL rdp_send_client_control_pdu(rdpRdp* rdp, UINT16 action) return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_CONTROL, rdp->mcs->userId); } -static BOOL rdp_write_persistent_list_entry(wStream* s, UINT32 key1, UINT32 key2) +static BOOL rdp_write_client_persistent_key_list_pdu(wStream* s, RDP_BITMAP_PERSISTENT_INFO* info) { - WINPR_ASSERT(s); + int index; + UINT32 key1; + UINT32 key2; - if (Stream_GetRemainingCapacity(s) < 8) - return FALSE; - Stream_Write_UINT32(s, key1); /* key1 (4 bytes) */ - Stream_Write_UINT32(s, key2); /* key2 (4 bytes) */ - return TRUE; -} - -static BOOL rdp_write_client_persistent_key_list_pdu(wStream* s, const rdpSettings* settings) -{ WINPR_ASSERT(s); - WINPR_ASSERT(settings); + WINPR_ASSERT(info); if (Stream_GetRemainingCapacity(s) < 24) return FALSE; - Stream_Write_UINT16(s, 0); /* numEntriesCache0 (2 bytes) */ - Stream_Write_UINT16(s, 0); /* numEntriesCache1 (2 bytes) */ - Stream_Write_UINT16(s, 0); /* numEntriesCache2 (2 bytes) */ - Stream_Write_UINT16(s, 0); /* numEntriesCache3 (2 bytes) */ - Stream_Write_UINT16(s, 0); /* numEntriesCache4 (2 bytes) */ - Stream_Write_UINT16(s, 0); /* totalEntriesCache0 (2 bytes) */ - Stream_Write_UINT16(s, 0); /* totalEntriesCache1 (2 bytes) */ - Stream_Write_UINT16(s, 0); /* totalEntriesCache2 (2 bytes) */ - Stream_Write_UINT16(s, 0); /* totalEntriesCache3 (2 bytes) */ - Stream_Write_UINT16(s, 0); /* totalEntriesCache4 (2 bytes) */ + + Stream_Write_UINT16(s, info->numEntriesCache0); /* numEntriesCache0 (2 bytes) */ + Stream_Write_UINT16(s, info->numEntriesCache1); /* numEntriesCache1 (2 bytes) */ + Stream_Write_UINT16(s, info->numEntriesCache2); /* numEntriesCache2 (2 bytes) */ + Stream_Write_UINT16(s, info->numEntriesCache3); /* numEntriesCache3 (2 bytes) */ + Stream_Write_UINT16(s, info->numEntriesCache4); /* numEntriesCache4 (2 bytes) */ + Stream_Write_UINT16(s, info->totalEntriesCache0); /* totalEntriesCache0 (2 bytes) */ + Stream_Write_UINT16(s, info->totalEntriesCache1); /* totalEntriesCache1 (2 bytes) */ + Stream_Write_UINT16(s, info->totalEntriesCache2); /* totalEntriesCache2 (2 bytes) */ + Stream_Write_UINT16(s, info->totalEntriesCache3); /* totalEntriesCache3 (2 bytes) */ + Stream_Write_UINT16(s, info->totalEntriesCache4); /* totalEntriesCache4 (2 bytes) */ Stream_Write_UINT8(s, PERSIST_FIRST_PDU | PERSIST_LAST_PDU); /* bBitMask (1 byte) */ Stream_Write_UINT8(s, 0); /* pad1 (1 byte) */ Stream_Write_UINT16(s, 0); /* pad3 (2 bytes) */ /* entries */ + + Stream_EnsureRemainingCapacity(s, info->keyCount * 8); + + for (index = 0; index < info->keyCount; index++) + { + key1 = (UINT32)info->keyList[index]; + key2 = (UINT32)(info->keyList[index] >> 32); + Stream_Write_UINT32(s, key1); + Stream_Write_UINT32(s, key2); + } + return TRUE; } +UINT32 rdp_load_persistent_key_list(rdpRdp* rdp, UINT64** pKeyList) +{ + int index; + int count; + int status; + UINT32 keyCount; + UINT64* keyList = NULL; + rdpPersistentCache* persistent; + PERSISTENT_CACHE_ENTRY cacheEntry; + rdpSettings* settings = rdp->settings; + + *pKeyList = NULL; + + if (!settings->BitmapCachePersistEnabled) + return 0; + + if (!settings->BitmapCachePersistFile) + return 0; + + persistent = persistent_cache_new(); + + if (!persistent) + return 0; + + status = persistent_cache_open(persistent, settings->BitmapCachePersistFile, FALSE, 0); + + if (status < 1) + goto error; + + count = persistent_cache_get_count(persistent); + + keyCount = (UINT32)count; + keyList = (UINT64*)malloc(keyCount * sizeof(UINT64)); + + if (!keyList) + goto error; + + for (index = 0; index < count; index++) + { + if (persistent_cache_read_entry(persistent, &cacheEntry) < 1) + continue; + + keyList[index] = cacheEntry.key64; + } + + *pKeyList = keyList; + + persistent_cache_free(persistent); + return keyCount; +error: + persistent_cache_free(persistent); + free(keyList); + return 0; +} + BOOL rdp_send_client_persistent_key_list_pdu(rdpRdp* rdp) { + UINT32 keyCount; + UINT32 keyMaxFrag = 2042; + UINT64* keyList = NULL; + RDP_BITMAP_PERSISTENT_INFO info; + rdpSettings* settings = rdp->settings; + + keyCount = rdp_load_persistent_key_list(rdp, &keyList); + + WLog_DBG(TAG, "Persistent Key List: TotalKeyCount: %d MaxKeyFrag: %d", keyCount, keyMaxFrag); + + // MS-RDPBCGR recommends sending no more than 169 entries at once. + // In practice, sending more than 2042 entries at once triggers an error. + // It should be possible to advertise the entire client bitmap cache + // by sending multiple persistent key list PDUs, but the current code + // only bothers sending a single, smaller list of entries instead. + + if (keyCount > keyMaxFrag) + keyCount = keyMaxFrag; + + info.totalEntriesCache0 = settings->BitmapCacheV2CellInfo[0].numEntries; + info.totalEntriesCache1 = settings->BitmapCacheV2CellInfo[1].numEntries; + info.totalEntriesCache2 = settings->BitmapCacheV2CellInfo[2].numEntries; + info.totalEntriesCache3 = settings->BitmapCacheV2CellInfo[3].numEntries; + info.totalEntriesCache4 = settings->BitmapCacheV2CellInfo[4].numEntries; + + info.numEntriesCache0 = MIN(keyCount, info.totalEntriesCache0); + keyCount -= info.numEntriesCache0; + info.numEntriesCache1 = MIN(keyCount, info.totalEntriesCache1); + keyCount -= info.numEntriesCache1; + info.numEntriesCache2 = MIN(keyCount, info.totalEntriesCache2); + keyCount -= info.numEntriesCache2; + info.numEntriesCache3 = MIN(keyCount, info.totalEntriesCache3); + keyCount -= info.numEntriesCache3; + info.numEntriesCache4 = MIN(keyCount, info.totalEntriesCache4); + keyCount -= info.numEntriesCache4; + + info.totalEntriesCache0 = info.numEntriesCache0; + info.totalEntriesCache1 = info.numEntriesCache1; + info.totalEntriesCache2 = info.numEntriesCache2; + info.totalEntriesCache3 = info.numEntriesCache3; + info.totalEntriesCache4 = info.numEntriesCache4; + + keyCount = info.totalEntriesCache0 + info.totalEntriesCache1 + info.totalEntriesCache2 + + info.totalEntriesCache3 + info.totalEntriesCache4; + + info.keyCount = keyCount; + info.keyList = keyList; + + WLog_DBG(TAG, "persistentKeyList count: %d", info.keyCount); + + WLog_DBG(TAG, "numEntriesCache: 0: %d 1: %d 2: %d 3: %d 4: %d", + info.numEntriesCache0, info.numEntriesCache1, info.numEntriesCache2, info.numEntriesCache3, info.numEntriesCache4); + + WLog_DBG(TAG, "totalEntriesCache: 0: %d 1: %d 2: %d 3: %d 4: %d", + info.totalEntriesCache0, info.totalEntriesCache1, info.totalEntriesCache2, info.totalEntriesCache3, info.totalEntriesCache4); + wStream* s = rdp_data_pdu_init(rdp); + if (!s) return FALSE; - if (!rdp_write_client_persistent_key_list_pdu(s, rdp->settings)) + + if (!rdp_write_client_persistent_key_list_pdu(s, &info)) { Stream_Free(s, TRUE); return FALSE; } WINPR_ASSERT(rdp->mcs); + free(keyList); + return rdp_send_data_pdu(rdp, s, DATA_PDU_TYPE_BITMAP_CACHE_PERSISTENT_LIST, rdp->mcs->userId); } diff --git a/libfreerdp/core/activation.h b/libfreerdp/core/activation.h index f9bb67a38..8aac1467f 100644 --- a/libfreerdp/core/activation.h +++ b/libfreerdp/core/activation.h @@ -24,6 +24,7 @@ #include #include +#include #define SYNCMSGTYPE_SYNC 0x0001 @@ -32,6 +33,24 @@ #define CTRLACTION_DETACH 0x0003 #define CTRLACTION_COOPERATE 0x0004 +struct _RDP_BITMAP_PERSISTENT_INFO +{ + UINT16 numEntriesCache0; + UINT16 numEntriesCache1; + UINT16 numEntriesCache2; + UINT16 numEntriesCache3; + UINT16 numEntriesCache4; + UINT16 totalEntriesCache0; + UINT16 totalEntriesCache1; + UINT16 totalEntriesCache2; + UINT16 totalEntriesCache3; + UINT16 totalEntriesCache4; + UINT32 keyCount; + UINT64* keyList; +}; +typedef struct _RDP_BITMAP_PERSISTENT_INFO RDP_BITMAP_PERSISTENT_INFO; + + #define PERSIST_FIRST_PDU 0x01 #define PERSIST_LAST_PDU 0x02 diff --git a/libfreerdp/core/capabilities.c b/libfreerdp/core/capabilities.c index 54b1eaaa2..164a959af 100644 --- a/libfreerdp/core/capabilities.c +++ b/libfreerdp/core/capabilities.c @@ -1866,7 +1866,14 @@ static BOOL rdp_write_bitmap_cache_v2_capability_set(wStream* s, const rdpSettin cacheFlags = ALLOW_CACHE_WAITING_LIST_FLAG; if (settings->BitmapCachePersistEnabled) + { cacheFlags |= PERSISTENT_KEYS_EXPECTED_FLAG; + settings->BitmapCacheV2CellInfo[0].persistent = 1; + settings->BitmapCacheV2CellInfo[1].persistent = 1; + settings->BitmapCacheV2CellInfo[2].persistent = 1; + settings->BitmapCacheV2CellInfo[3].persistent = 1; + settings->BitmapCacheV2CellInfo[4].persistent = 1; + } Stream_Write_UINT16(s, cacheFlags); /* cacheFlags (2 bytes) */ Stream_Write_UINT8(s, 0); /* pad2 (1 byte) */ diff --git a/libfreerdp/core/test/settings_property_lists.h b/libfreerdp/core/test/settings_property_lists.h index 8cc50d8bc..fd827c3b6 100644 --- a/libfreerdp/core/test/settings_property_lists.h +++ b/libfreerdp/core/test/settings_property_lists.h @@ -325,6 +325,7 @@ static const size_t string_list_indices[] = { FreeRDP_AlternateShell, FreeRDP_AssistanceFile, FreeRDP_AuthenticationServiceClass, + FreeRDP_BitmapCachePersistFile, FreeRDP_CardName, FreeRDP_CertificateAcceptedFingerprints, FreeRDP_CertificateContent, diff --git a/libfreerdp/gdi/gfx.c b/libfreerdp/gdi/gfx.c index b874d5baf..f77bf2526 100644 --- a/libfreerdp/gdi/gfx.c +++ b/libfreerdp/gdi/gfx.c @@ -1349,6 +1349,7 @@ static UINT gdi_SurfaceToCache(RdpgfxClientContext* context, if (!cacheEntry) goto fail; + cacheEntry->cacheKey = surfaceToCache->cacheKey; cacheEntry->width = (UINT32)(rect->right - rect->left); cacheEntry->height = (UINT32)(rect->bottom - rect->top); cacheEntry->format = surface->format; @@ -1445,9 +1446,112 @@ fail: static UINT gdi_CacheImportReply(RdpgfxClientContext* context, const RDPGFX_CACHE_IMPORT_REPLY_PDU* cacheImportReply) { - return CHANNEL_RC_OK; + UINT16 index; + UINT16 count; + const UINT16* slots; + gdiGfxCacheEntry* cacheEntry; + UINT error = CHANNEL_RC_OK; + + slots = cacheImportReply->cacheSlots; + count = cacheImportReply->importedEntriesCount; + + for (index = 0; index < count; index++) + { + UINT16 cacheSlot = slots[index]; + + if (cacheSlot == 0) + continue; + + cacheEntry = (gdiGfxCacheEntry*)context->GetCacheSlotData(context, cacheSlot); + + if (cacheEntry) + continue; + + cacheEntry = (gdiGfxCacheEntry*)calloc(1, sizeof(gdiGfxCacheEntry)); + + if (!cacheEntry) + return ERROR_INTERNAL_ERROR; + + cacheEntry->width = 0; + cacheEntry->height = 0; + cacheEntry->format = PIXEL_FORMAT_BGRX32; + cacheEntry->scanline = (cacheEntry->width + (cacheEntry->width % 4)) * 4; + cacheEntry->data = NULL; + + error = context->SetCacheSlotData(context, cacheSlot, (void*)cacheEntry); + + if (error) { + WLog_ERR(TAG, "CacheImportReply: SetCacheSlotData failed with error %" PRIu32 "", error); + break; + } + } + + return error; } +UINT gdi_ImportCacheEntry(RdpgfxClientContext* context, UINT16 cacheSlot, + PERSISTENT_CACHE_ENTRY* importCacheEntry) +{ + UINT error; + gdiGfxCacheEntry* cacheEntry; + + if (cacheSlot == 0) + return CHANNEL_RC_OK; + + cacheEntry = (gdiGfxCacheEntry*)calloc(1, sizeof(gdiGfxCacheEntry)); + + if (!cacheEntry) + return ERROR_INTERNAL_ERROR; + + cacheEntry->cacheKey = importCacheEntry->key64; + cacheEntry->width = (UINT32)importCacheEntry->width; + cacheEntry->height = (UINT32)importCacheEntry->height; + cacheEntry->format = PIXEL_FORMAT_BGRX32; + cacheEntry->scanline = (cacheEntry->width + (cacheEntry->width % 4)) * 4; + cacheEntry->data = (BYTE*)calloc(cacheEntry->height, cacheEntry->scanline); + + if (!cacheEntry->data) + { + free(cacheEntry); + return ERROR_INTERNAL_ERROR; + } + + if (!freerdp_image_copy(cacheEntry->data, cacheEntry->format, cacheEntry->scanline, 0, 0, + cacheEntry->width, cacheEntry->height, importCacheEntry->data, + PIXEL_FORMAT_BGRX32, 0, 0, 0, NULL, FREERDP_FLIP_NONE)) { + return ERROR_INTERNAL_ERROR; + } + + error = context->SetCacheSlotData(context, cacheSlot, (void*)cacheEntry); + + if (error) + WLog_ERR(TAG, "ImportCacheEntry: SetCacheSlotData failed with error %" PRIu32 "", error); + + return error; +} + +UINT gdi_ExportCacheEntry(RdpgfxClientContext* context, UINT16 cacheSlot, + PERSISTENT_CACHE_ENTRY* exportCacheEntry) +{ + gdiGfxCacheEntry* cacheEntry; + + cacheEntry = (gdiGfxCacheEntry*)context->GetCacheSlotData(context, cacheSlot); + + if (cacheEntry) + { + exportCacheEntry->key64 = cacheEntry->cacheKey; + exportCacheEntry->width = cacheEntry->width; + exportCacheEntry->height = cacheEntry->height; + exportCacheEntry->size = cacheEntry->width * cacheEntry->height * 4; + exportCacheEntry->flags = 0; + exportCacheEntry->data = cacheEntry->data; + return CHANNEL_RC_OK; + } + + return ERROR_NOT_FOUND; +} + + /** * Function description * @@ -1465,9 +1569,9 @@ static UINT gdi_EvictCacheEntry(RdpgfxClientContext* context, { free(cacheEntry->data); free(cacheEntry); - rc = context->SetCacheSlotData(context, evictCacheEntry->cacheSlot, NULL); } + rc = context->SetCacheSlotData(context, evictCacheEntry->cacheSlot, NULL); LeaveCriticalSection(&context->mux); return rc; } @@ -1621,6 +1725,8 @@ BOOL gdi_graphics_pipeline_init_ex(rdpGdi* gdi, RdpgfxClientContext* gfx, gfx->SurfaceToCache = gdi_SurfaceToCache; gfx->CacheToSurface = gdi_CacheToSurface; gfx->CacheImportReply = gdi_CacheImportReply; + gfx->ImportCacheEntry = gdi_ImportCacheEntry; + gfx->ExportCacheEntry = gdi_ExportCacheEntry; gfx->EvictCacheEntry = gdi_EvictCacheEntry; gfx->MapSurfaceToOutput = gdi_MapSurfaceToOutput; gfx->MapSurfaceToWindow = gdi_MapSurfaceToWindow;