From f764b0a8549970fa053a3e8c114d3c2dd30f0b71 Mon Sep 17 00:00:00 2001 From: akallabeth Date: Mon, 4 Mar 2024 08:45:22 +0100 Subject: [PATCH] [channels,location] implement client channel * provide client side callbacks to implement platform specific location backend glue code --- channels/location/CMakeLists.txt | 4 + channels/location/ChannelOptions.cmake | 3 +- channels/location/client/CMakeLists.txt | 30 ++ channels/location/client/location_main.c | 448 +++++++++++++++++++++++ client/common/file.c | 5 +- include/freerdp/channels/location.h | 1 + include/freerdp/client/location.h | 97 +++++ 7 files changed, 584 insertions(+), 4 deletions(-) create mode 100644 channels/location/client/CMakeLists.txt create mode 100644 channels/location/client/location_main.c create mode 100644 include/freerdp/client/location.h diff --git a/channels/location/CMakeLists.txt b/channels/location/CMakeLists.txt index 5e77fcb33..fbf920cf1 100644 --- a/channels/location/CMakeLists.txt +++ b/channels/location/CMakeLists.txt @@ -17,6 +17,10 @@ define_channel("location") +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + if(WITH_SERVER_CHANNELS) add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) endif() diff --git a/channels/location/ChannelOptions.cmake b/channels/location/ChannelOptions.cmake index 11f5e3001..acffc2609 100644 --- a/channels/location/ChannelOptions.cmake +++ b/channels/location/ChannelOptions.cmake @@ -1,6 +1,6 @@ set(OPTION_DEFAULT ON) -set(OPTION_CLIENT_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) set(OPTION_SERVER_DEFAULT ON) define_channel_options(NAME "location" TYPE "dynamic" @@ -8,5 +8,6 @@ define_channel_options(NAME "location" TYPE "dynamic" SPECIFICATIONS "[MS-RDPEL]" DEFAULT ${OPTION_DEFAULT}) +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) define_channel_server_options(${OPTION_SERVER_DEFAULT}) diff --git a/channels/location/client/CMakeLists.txt b/channels/location/client/CMakeLists.txt new file mode 100644 index 000000000..3c5a05aae --- /dev/null +++ b/channels/location/client/CMakeLists.txt @@ -0,0 +1,30 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2024 Armin Novak +# Copyright 2024 Thincast Technologies GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("location") + +set(${MODULE_PREFIX}_SRCS + location_main.c +) + +set(${MODULE_PREFIX}_LIBS + winpr +) +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") diff --git a/channels/location/client/location_main.c b/channels/location/client/location_main.c new file mode 100644 index 000000000..03e0f95c1 --- /dev/null +++ b/channels/location/client/location_main.c @@ -0,0 +1,448 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Location Virtual Channel Extension + * + * Copyright 2024 Armin Novak + * Copyright 2024 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("location.client") + +/* implement [MS-RDPEL] + * https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpel/4397a0af-c821-4b75-9068-476fb579c327 + */ +typedef struct +{ + GENERIC_DYNVC_PLUGIN baseDynPlugin; + LocationClientContext context; +} LOCATION_PLUGIN; + +typedef struct +{ + GENERIC_CHANNEL_CALLBACK baseCb; + UINT32 serverVersion; + UINT32 clientVersion; + UINT32 serverFlags; + UINT32 clientFlags; +} LOCATION_CALLBACK; + +static BOOL location_read_header(wLog* log, wStream* s, UINT16* ppduType, UINT32* ppduLength) +{ + WINPR_ASSERT(log); + WINPR_ASSERT(s); + WINPR_ASSERT(ppduType); + WINPR_ASSERT(ppduLength); + + if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 6)) + return FALSE; + Stream_Read_UINT16(s, *ppduType); + Stream_Read_UINT32(s, *ppduLength); + if (*ppduLength < 6) + { + WLog_Print(log, WLOG_ERROR, + "RDPLOCATION_HEADER::pduLengh=%" PRIu16 " < sizeof(RDPLOCATION_HEADER)[6]", + *ppduLength); + return FALSE; + } + return Stream_CheckAndLogRequiredLengthWLog(log, s, *ppduLength); +} + +static BOOL location_write_header(wStream* s, UINT16 pduType, UINT32 pduLength) +{ + if (!Stream_EnsureRemainingCapacity(s, 6)) + return FALSE; + Stream_Write_UINT16(s, pduType); + Stream_Write_UINT32(s, pduLength + 6); + return Stream_EnsureRemainingCapacity(s, pduLength); +} + +static BOOL location_read_server_ready_pdu(LOCATION_CALLBACK* callback, wStream* s, UINT16 pduSize) +{ + if (pduSize < 6 + 4) + return FALSE; // Short message + + Stream_Read_UINT32(s, callback->serverVersion); + if (pduSize >= 6 + 4 + 4) + Stream_Read_UINT32(s, callback->serverFlags); + return TRUE; +} + +static UINT location_channel_send(IWTSVirtualChannel* channel, wStream* s) +{ + const size_t len = Stream_GetPosition(s); + Stream_SetPosition(s, 2); + Stream_Write_UINT32(s, len); + + WINPR_ASSERT(channel); + WINPR_ASSERT(channel->Write); + return channel->Write(channel, len, Stream_Buffer(s), NULL); +} + +static UINT location_send_client_ready_pdu(const LOCATION_CALLBACK* callback) +{ + wStream sbuffer = { 0 }; + char buffer[32] = { 0 }; + wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer)); + WINPR_ASSERT(s); + + if (!location_write_header(s, PDUTYPE_CLIENT_READY, 8)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT32(s, callback->clientVersion); + Stream_Write_UINT32(s, callback->clientFlags); + return location_channel_send(callback->baseCb.channel, s); +} + +static const char* location_version_str(UINT32 version, char* buffer, size_t size) +{ + const char* str = NULL; + switch (version) + { + case RDPLOCATION_PROTOCOL_VERSION_100: + str = "RDPLOCATION_PROTOCOL_VERSION_100"; + break; + case RDPLOCATION_PROTOCOL_VERSION_200: + str = "RDPLOCATION_PROTOCOL_VERSION_200"; + break; + default: + str = "RDPLOCATION_PROTOCOL_VERSION_UNKNOWN"; + break; + } + + _snprintf(buffer, size, "%s [0x%08" PRIx32 "]", str, version); + return buffer; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT location_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + LOCATION_CALLBACK* callback = (LOCATION_CALLBACK*)pChannelCallback; + + WINPR_ASSERT(callback); + + LOCATION_PLUGIN* plugin = (LOCATION_PLUGIN*)callback->baseCb.plugin; + WINPR_ASSERT(plugin); + + UINT16 pduType = 0; + UINT32 pduLength = 0; + if (!location_read_header(plugin->baseDynPlugin.log, data, &pduType, &pduLength)) + return ERROR_INVALID_DATA; + + switch (pduType) + { + case PDUTYPE_SERVER_READY: + if (!location_read_server_ready_pdu(callback, data, pduLength)) + return ERROR_INVALID_DATA; + + switch (callback->serverVersion) + { + case RDPLOCATION_PROTOCOL_VERSION_200: + callback->clientVersion = RDPLOCATION_PROTOCOL_VERSION_200; + break; + case RDPLOCATION_PROTOCOL_VERSION_100: + callback->clientVersion = RDPLOCATION_PROTOCOL_VERSION_100; + break; + default: + callback->clientVersion = RDPLOCATION_PROTOCOL_VERSION_100; + if (callback->serverVersion > RDPLOCATION_PROTOCOL_VERSION_200) + callback->clientVersion = RDPLOCATION_PROTOCOL_VERSION_200; + break; + } + + char cbuffer[32] = { 0 }; + char sbuffer[32] = { 0 }; + WLog_Print(plugin->baseDynPlugin.log, WLOG_DEBUG, + "Server version %s, client version %s", + location_version_str(callback->serverVersion, sbuffer, sizeof(sbuffer)), + location_version_str(callback->clientVersion, cbuffer, sizeof(cbuffer))); + + if (!plugin->context.LocationStart) + { + WLog_Print(plugin->baseDynPlugin.log, WLOG_WARN, + "LocationStart=NULL, no location data will be sent"); + return CHANNEL_RC_OK; + } + const UINT res = + plugin->context.LocationStart(&plugin->context, callback->clientVersion, 0); + if (res != CHANNEL_RC_OK) + return res; + return location_send_client_ready_pdu(callback); + default: + WLog_WARN(TAG, "invalid pduType=%s"); + return ERROR_INVALID_DATA; + } +} + +static UINT location_send_base_location3d(IWTSVirtualChannel* channel, + const RDPLOCATION_BASE_LOCATION3D_PDU* pdu) +{ + wStream sbuffer = { 0 }; + char buffer[32] = { 0 }; + wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer)); + WINPR_ASSERT(s); + WINPR_ASSERT(channel); + WINPR_ASSERT(pdu); + + if (location_write_header(s, PDUTYPE_BASE_LOCATION3D, 32)) + return ERROR_OUTOFMEMORY; + + if (!freerdp_write_four_byte_float(s, pdu->latitude) || + !freerdp_write_four_byte_float(s, pdu->longitude) || + !freerdp_write_four_byte_signed_integer(s, pdu->altitude)) + return FALSE; + + if (pdu->source) + { + if (!freerdp_write_four_byte_float(s, *pdu->speed) || + !freerdp_write_four_byte_float(s, *pdu->heading) || + !freerdp_write_four_byte_float(s, *pdu->horizontalAccuracy)) + return FALSE; + + Stream_Write_UINT8(s, *pdu->source); + } + + return location_channel_send(channel, s); +} + +static UINT location_send_location2d_delta(IWTSVirtualChannel* channel, + const RDPLOCATION_LOCATION2D_DELTA_PDU* pdu) +{ + wStream sbuffer = { 0 }; + char buffer[32] = { 0 }; + wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer)); + WINPR_ASSERT(s); + + WINPR_ASSERT(channel); + WINPR_ASSERT(pdu); + + const BOOL ext = pdu->speedDelta && pdu->headingDelta; + + if (location_write_header(s, PDUTYPE_LOCATION2D_DELTA, 32)) + return ERROR_OUTOFMEMORY; + + if (!freerdp_write_four_byte_float(s, pdu->latitudeDelta) || + !freerdp_write_four_byte_float(s, pdu->longitudeDelta)) + return FALSE; + + if (ext) + { + if (!freerdp_write_four_byte_float(s, *pdu->speedDelta) || + !freerdp_write_four_byte_float(s, *pdu->headingDelta)) + return FALSE; + } + + return location_channel_send(channel, s); +} + +static UINT location_send_location3d_delta(IWTSVirtualChannel* channel, + const RDPLOCATION_LOCATION3D_DELTA_PDU* pdu) +{ + wStream sbuffer = { 0 }; + char buffer[32] = { 0 }; + wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer)); + WINPR_ASSERT(s); + + WINPR_ASSERT(channel); + WINPR_ASSERT(pdu); + + const BOOL ext = pdu->speedDelta && pdu->headingDelta; + if (location_write_header(s, PDUTYPE_LOCATION3D_DELTA, 32)) + return ERROR_OUTOFMEMORY; + + if (!freerdp_write_four_byte_float(s, pdu->latitudeDelta) || + !freerdp_write_four_byte_float(s, pdu->longitudeDelta) || + !freerdp_write_four_byte_signed_integer(s, pdu->altitudeDelta)) + return FALSE; + + if (ext) + { + if (!freerdp_write_four_byte_float(s, *pdu->speedDelta) || + !freerdp_write_four_byte_float(s, *pdu->headingDelta)) + return FALSE; + } + + return location_channel_send(channel, s); +} + +static UINT location_send(LocationClientContext* context, LOCATION_PDUTYPE type, size_t count, ...) +{ + WINPR_ASSERT(context); + + LOCATION_PLUGIN* loc = context->handle; + WINPR_ASSERT(loc); + + GENERIC_LISTENER_CALLBACK* cb = loc->baseDynPlugin.listener_callback; + WINPR_ASSERT(cb); + + IWTSVirtualChannel* channel = cb->channel; + WINPR_ASSERT(channel); + + LOCATION_CALLBACK* callback = (LOCATION_CALLBACK*)loc->baseDynPlugin.channel_callbacks; + WINPR_ASSERT(callback); + + UINT32 res = ERROR_INTERNAL_ERROR; + va_list ap; + va_start(ap, count); + switch (type) + { + case PDUTYPE_BASE_LOCATION3D: + if ((count != 3) && (count != 7)) + res = ERROR_INVALID_PARAMETER; + else + { + RDPLOCATION_BASE_LOCATION3D_PDU pdu = { 0 }; + LOCATIONSOURCE source = LOCATIONSOURCE_IP; + double speed = FP_NAN; + double heading = FP_NAN; + double horizontalAccuracy = FP_NAN; + pdu.latitude = va_arg(ap, double); + pdu.longitude = va_arg(ap, double); + pdu.altitude = va_arg(ap, INT32); + if ((count > 3) && (callback->clientVersion >= RDPLOCATION_PROTOCOL_VERSION_200)) + { + speed = va_arg(ap, double); + heading = va_arg(ap, double); + horizontalAccuracy = va_arg(ap, double); + source = va_arg(ap, int); + pdu.speed = &speed; + pdu.heading = &heading; + pdu.horizontalAccuracy = &horizontalAccuracy; + pdu.source = &source; + } + res = location_send_base_location3d(channel, &pdu); + } + break; + case PDUTYPE_LOCATION2D_DELTA: + if ((count != 2) && (count != 4)) + res = ERROR_INVALID_PARAMETER; + else + { + RDPLOCATION_LOCATION2D_DELTA_PDU pdu = { 0 }; + + pdu.latitudeDelta = va_arg(ap, double); + pdu.longitudeDelta = va_arg(ap, double); + + double speedDelta = FP_NAN; + double headingDelta = FP_NAN; + if ((count > 2) && (callback->clientVersion >= RDPLOCATION_PROTOCOL_VERSION_200)) + { + speedDelta = va_arg(ap, double); + headingDelta = va_arg(ap, double); + pdu.speedDelta = &speedDelta; + pdu.headingDelta = &headingDelta; + } + res = location_send_location2d_delta(channel, &pdu); + } + break; + case PDUTYPE_LOCATION3D_DELTA: + if ((count != 3) && (count != 5)) + res = ERROR_INVALID_PARAMETER; + else + { + RDPLOCATION_LOCATION3D_DELTA_PDU pdu = { 0 }; + double speedDelta = FP_NAN; + double headingDelta = FP_NAN; + + pdu.latitudeDelta = va_arg(ap, double); + pdu.longitudeDelta = va_arg(ap, double); + pdu.altitudeDelta = va_arg(ap, INT32); + if ((count > 3) && (callback->clientVersion >= RDPLOCATION_PROTOCOL_VERSION_200)) + { + speedDelta = va_arg(ap, double); + headingDelta = va_arg(ap, double); + pdu.speedDelta = &speedDelta; + pdu.headingDelta = &headingDelta; + } + res = location_send_location3d_delta(channel, &pdu); + } + break; + default: + res = ERROR_INVALID_PARAMETER; + break; + } + va_end(ap); + return res; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT location_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + UINT res = CHANNEL_RC_OK; + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + + if (callback) + { + LOCATION_PLUGIN* plugin = (LOCATION_PLUGIN*)callback->plugin; + WINPR_ASSERT(plugin); + + res = IFCALLRESULT(CHANNEL_RC_OK, plugin->context.LocationStop, &plugin->context); + } + free(callback); + + return res; +} + +static UINT location_init(GENERIC_DYNVC_PLUGIN* plugin, rdpContext* context, rdpSettings* settings) +{ + LOCATION_PLUGIN* loc = (LOCATION_PLUGIN*)plugin; + + WINPR_ASSERT(loc); + + loc->context.LocationSend = location_send; + loc->context.handle = loc; + plugin->iface.pInterface = &loc->context; + return CHANNEL_RC_OK; +} + +static const IWTSVirtualChannelCallback location_callbacks = { location_on_data_received, + NULL, /* Open */ + location_on_close, NULL }; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT location_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, LOCATION_DVC_CHANNEL_NAME, + sizeof(LOCATION_PLUGIN), sizeof(LOCATION_CALLBACK), + &location_callbacks, location_init, NULL); +} diff --git a/client/common/file.c b/client/common/file.c index 760c62e9a..feb37967f 100644 --- a/client/common/file.c +++ b/client/common/file.c @@ -1309,7 +1309,7 @@ BOOL freerdp_client_populate_rdp_file_from_settings(rdpFile* file, const rdpSett file->RedirectComPorts = (freerdp_settings_get_bool(settings, FreeRDP_RedirectSerialPorts) || freerdp_settings_get_bool(settings, FreeRDP_RedirectParallelPorts)); file->RedirectLocation = - freerdp_dynamic_channel_collection_find(settings, LOCATION_DVC_CHANNEL_NAME) ? TRUE : FALSE; + freerdp_dynamic_channel_collection_find(settings, LOCATION_CHANNEL_NAME) ? TRUE : FALSE; if (!FILE_POPULATE_STRING(&file->DrivesToRedirect, settings, FreeRDP_DrivesToRedirect) || !FILE_POPULATE_STRING(&file->PreconnectionBlob, settings, FreeRDP_PreconnectionBlob) || !FILE_POPULATE_STRING(&file->KdcProxyName, settings, FreeRDP_KerberosKdcUrl)) @@ -2299,8 +2299,7 @@ BOOL freerdp_client_populate_settings_from_rdp_file(const rdpFile* file, rdpSett if (~file->RedirectLocation) { size_t count = 0; - char** str = - CommandLineParseCommaSeparatedValuesEx(LOCATION_DVC_CHANNEL_NAME, NULL, &count); + char** str = CommandLineParseCommaSeparatedValuesEx(LOCATION_CHANNEL_NAME, NULL, &count); const BOOL rc = freerdp_client_add_dynamic_channel(settings, count, str); free(str); if (!rc) diff --git a/include/freerdp/channels/location.h b/include/freerdp/channels/location.h index 5b91ea465..45682b074 100644 --- a/include/freerdp/channels/location.h +++ b/include/freerdp/channels/location.h @@ -24,6 +24,7 @@ #include #include +#define LOCATION_CHANNEL_NAME "location" #define LOCATION_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::Location" #ifdef __cplusplus diff --git a/include/freerdp/client/location.h b/include/freerdp/client/location.h new file mode 100644 index 000000000..67db65531 --- /dev/null +++ b/include/freerdp/client/location.h @@ -0,0 +1,97 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Location Virtual Channel Extension + * + * Copyright 2024 Armin Novak + * Copyright 2024 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_LOCATION_CLIENT_LOCATION_H +#define FREERDP_CHANNEL_LOCATION_CLIENT_LOCATION_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct s_location_client_context LocationClientContext; + + typedef UINT (*pcLocationStart)(LocationClientContext* context, UINT32 version, UINT32 flags); + typedef UINT (*pcLocationStop)(LocationClientContext* context); + typedef UINT (*pcLocationSend)(LocationClientContext* context, LOCATION_PDUTYPE type, + size_t count, ...); + + struct s_location_client_context + { + void* handle; + void* custom; + + /**! \brief initialize location services on client + * + * \param context The client context to operate on + * \param version The location channel version (determines which features are available. + * \param flags The location channel flags. + * + * \return \b CHANNEL_RC_OK for success, an appropriate error otherwise. + */ + pcLocationStart LocationStart; + + /**! \brief stop location services on client + * + * \param context The client context to operate on + * + * \return \b CHANNEL_RC_OK for success, an appropriate error otherwise. + */ + pcLocationStop LocationStop; + + /**! \brief Send a location update. + * + * This function sends location updates to a server. + * The following parameter formats are supported: + * + * \param type one of the following: + * PDUTYPE_BASE_LOCATION3D : count = 3 | 7 + * latitude : double, required + * longitude : double, required + * altitude : INT32, required + * speed : double, optional + * heading : double, optional + * horizontalAccuracy : double, optional + * source : int, optional + * PDUTYPE_LOCATION2D_DELTA : count = 2 | 4 + * latitudeDelta : double, required + * longitudeDelta : double, required + * speedDelta : double, optional + * headingDelta : double, optional + * PDUTYPE_LOCATION3D_DELTA : count = 3 | 5 + * latitudeDelta : double, required + * longitudeDelta : double, required + * altitudeDelta : INT32, optional + * speedDelta : double, optional + * headingDelta : double, optional + * \param count the number of variable arguments following + * + * return \b CHANNEL_RC_OK for success, an appropriate error otherwise. + */ + pcLocationSend LocationSend; + }; + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CHANNEL_LOCATION_CLIENT_LOCATION_H */