diff options
Diffstat (limited to '')
-rw-r--r-- | channels/location/CMakeLists.txt | 4 | ||||
-rw-r--r-- | channels/location/ChannelOptions.cmake | 3 | ||||
-rw-r--r-- | channels/location/client/CMakeLists.txt | 30 | ||||
-rw-r--r-- | channels/location/client/location_main.c | 473 |
4 files changed, 509 insertions, 1 deletions
diff --git a/channels/location/CMakeLists.txt b/channels/location/CMakeLists.txt index 5e77fcb..fbf920c 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 11f5e30..acffc26 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 0000000..3c5a05a --- /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 <anovak@thincast.com> +# 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 0000000..281070f --- /dev/null +++ b/channels/location/client/location_main.c @@ -0,0 +1,473 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Location Virtual Channel Extension + * + * Copyright 2024 Armin Novak <anovak@thincast.com> + * 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 <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <float.h> +#include <math.h> + +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/stream.h> + +#include <freerdp/client/channels.h> +#include <freerdp/channels/log.h> +#include <freerdp/channels/location.h> +#include <freerdp/client/location.h> +#include <freerdp/utils/encoded_types.h> + +#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 - 6ull); +} + +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[64] = { 0 }; + char sbuffer[64] = { 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 (pdu->source) + WLog_DBG(TAG, + "latitude=%lf, logitude=%lf, altitude=%" PRId32 + ", speed=%lf, heading=%lf, haccuracy=%lf, source=%" PRIu8, + pdu->latitude, pdu->longitude, pdu->altitude, pdu->speed, pdu->heading, + pdu->horizontalAccuracy, *pdu->source); + else + WLog_DBG(TAG, "latitude=%lf, logitude=%lf, altitude=%" PRId32, pdu->latitude, + pdu->longitude, pdu->altitude); + + if (!location_write_header(s, PDUTYPE_BASE_LOCATION3D, pdu->source ? 25 : 12)) + 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 ERROR_INTERNAL_ERROR; + + 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 ERROR_INTERNAL_ERROR; + + 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 (ext) + WLog_DBG(TAG, "latitude=%lf, logitude=%lf, speed=%lf, heading=%lf", pdu->latitudeDelta, + pdu->longitudeDelta, pdu->speedDelta, pdu->headingDelta); + else + WLog_DBG(TAG, "latitude=%lf, logitude=%lf", pdu->latitudeDelta, pdu->longitudeDelta); + + if (!location_write_header(s, PDUTYPE_LOCATION2D_DELTA, ext ? 16 : 8)) + return ERROR_OUTOFMEMORY; + + if (!freerdp_write_four_byte_float(s, pdu->latitudeDelta) || + !freerdp_write_four_byte_float(s, pdu->longitudeDelta)) + return ERROR_INTERNAL_ERROR; + + if (ext) + { + if (!freerdp_write_four_byte_float(s, *pdu->speedDelta) || + !freerdp_write_four_byte_float(s, *pdu->headingDelta)) + return ERROR_INTERNAL_ERROR; + } + + 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 (ext) + WLog_DBG(TAG, "latitude=%lf, logitude=%lf, altitude=%" PRId32 ", speed=%lf, heading=%lf", + pdu->latitudeDelta, pdu->longitudeDelta, pdu->altitudeDelta, pdu->speedDelta, + pdu->headingDelta); + else + WLog_DBG(TAG, "latitude=%lf, logitude=%lf, altitude=%" PRId32, pdu->latitudeDelta, + pdu->longitudeDelta, pdu->altitudeDelta); + + if (!location_write_header(s, PDUTYPE_LOCATION3D_DELTA, ext ? 20 : 12)) + 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 ERROR_INTERNAL_ERROR; + + if (ext) + { + if (!freerdp_write_four_byte_float(s, *pdu->speedDelta) || + !freerdp_write_four_byte_float(s, *pdu->headingDelta)) + return ERROR_INTERNAL_ERROR; + } + + 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); +} |