Implement RDP persistent bitmap cache

This commit is contained in:
Marc-André Moreau 2022-05-30 16:32:23 -04:00 committed by David Fort
parent 9d627e0df2
commit 161617c4a4
21 changed files with 1230 additions and 148 deletions

View File

@ -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)
{

View File

@ -30,6 +30,7 @@
#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
@ -78,6 +79,8 @@ typedef struct
UINT16 MaxCacheSlots;
void* CacheSlots[25600];
rdpPersistentCache* persistent;
rdpContext* rdpcontext;
wLog* log;

View File

@ -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;
}

View File

@ -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;

View File

@ -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, "<filename>", NULL, NULL, -1, NULL,
"persistent bitmap cache file" },
{ "bpp", COMMAND_LINE_VALUE_REQUIRED, "<depth>", "16", NULL, -1, NULL,
"Session bpp (color depth)" },
{ "buildconfig", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_BUILDCONFIG, NULL, NULL, NULL, -1,

View File

@ -24,6 +24,7 @@
#include <freerdp/types.h>
#include <freerdp/update.h>
#include <freerdp/freerdp.h>
#include <freerdp/cache/persistent.h>
#include <winpr/stream.h>
@ -49,6 +50,7 @@ typedef struct
/* internal */
rdpContext* context;
rdpPersistentCache* persistent;
} rdpBitmapCache;
#ifdef __cplusplus

103
include/freerdp/cache/persistent.h vendored Normal file
View File

@ -0,0 +1,103 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Persistent Bitmap Cache
*
* Copyright 2016 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_PERSISTENT_CACHE_H
#define FREERDP_PERSISTENT_CACHE_H
#include <freerdp/api.h>
#include <freerdp/types.h>
#include <freerdp/update.h>
#include <freerdp/freerdp.h>
#include <winpr/crt.h>
#include <winpr/stream.h>
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 */

View File

@ -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

View File

@ -26,6 +26,8 @@
#include <freerdp/channels/rdpgfx.h>
#include <freerdp/utils/profiler.h>
#include <freerdp/cache/persistent.h>
/**
* 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;

View File

@ -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 */

View File

@ -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 */

View File

@ -25,6 +25,7 @@ freerdp_module_add(
pointer.h
bitmap.c
bitmap.h
persistent.c
nine_grid.c
offscreen.c
palette.c

View File

@ -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)

342
libfreerdp/cache/persistent.c vendored Normal file
View File

@ -0,0 +1,342 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Persistent Bitmap Cache
*
* Copyright 2016 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <freerdp/config.h>
#include <winpr/crt.h>
#include <winpr/stream.h>
#include <freerdp/freerdp.h>
#include <freerdp/constants.h>
#include <freerdp/log.h>
#include <freerdp/cache/persistent.h>
#define TAG FREERDP_TAG("cache.persistent")
struct rdp_persistent_cache
{
FILE* fp;
BOOL write;
UINT32 version;
int count;
char* filename;
BYTE* bmpData;
UINT32 bmpSize;
};
int persistent_cache_get_version(rdpPersistentCache* persistent)
{
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);
}

View File

@ -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);

View File

@ -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" },

View File

@ -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);
}

View File

@ -24,6 +24,7 @@
#include <freerdp/api.h>
#include <freerdp/settings.h>
#include <freerdp/cache/persistent.h>
#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

View File

@ -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) */

View File

@ -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,

View File

@ -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;