summaryrefslogtreecommitdiffstats
path: root/channels/rdpecam
diff options
context:
space:
mode:
Diffstat (limited to 'channels/rdpecam')
-rw-r--r--channels/rdpecam/CMakeLists.txt22
-rw-r--r--channels/rdpecam/ChannelOptions.cmake12
-rw-r--r--channels/rdpecam/server/CMakeLists.txt29
-rw-r--r--channels/rdpecam/server/camera_device_enumerator_main.c611
-rw-r--r--channels/rdpecam/server/camera_device_main.c966
5 files changed, 1640 insertions, 0 deletions
diff --git a/channels/rdpecam/CMakeLists.txt b/channels/rdpecam/CMakeLists.txt
new file mode 100644
index 0000000..63ed410
--- /dev/null
+++ b/channels/rdpecam/CMakeLists.txt
@@ -0,0 +1,22 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
+#
+# 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("rdpecam")
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/rdpecam/ChannelOptions.cmake b/channels/rdpecam/ChannelOptions.cmake
new file mode 100644
index 0000000..7528d11
--- /dev/null
+++ b/channels/rdpecam/ChannelOptions.cmake
@@ -0,0 +1,12 @@
+
+set(OPTION_DEFAULT ON)
+set(OPTION_CLIENT_DEFAULT OFF)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "rdpecam" TYPE "dynamic"
+ DESCRIPTION "Video Capture Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPECAM]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/rdpecam/server/CMakeLists.txt b/channels/rdpecam/server/CMakeLists.txt
new file mode 100644
index 0000000..2f82428
--- /dev/null
+++ b/channels/rdpecam/server/CMakeLists.txt
@@ -0,0 +1,29 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
+#
+# 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_server("rdpecam")
+
+set(${MODULE_PREFIX}_SRCS
+ camera_device_enumerator_main.c
+ camera_device_main.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
diff --git a/channels/rdpecam/server/camera_device_enumerator_main.c b/channels/rdpecam/server/camera_device_enumerator_main.c
new file mode 100644
index 0000000..27523b0
--- /dev/null
+++ b/channels/rdpecam/server/camera_device_enumerator_main.c
@@ -0,0 +1,611 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Capture Virtual Channel Extension
+ *
+ * Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
+ *
+ * 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 <freerdp/freerdp.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/server/rdpecam-enumerator.h>
+
+#define TAG CHANNELS_TAG("rdpecam-enumerator.server")
+
+typedef enum
+{
+ ENUMERATOR_INITIAL,
+ ENUMERATOR_OPENED,
+} eEnumeratorChannelState;
+
+typedef struct
+{
+ CamDevEnumServerContext context;
+
+ HANDLE stopEvent;
+
+ HANDLE thread;
+ void* enumerator_channel;
+
+ DWORD SessionId;
+
+ BOOL isOpened;
+ BOOL externalThread;
+
+ /* Channel state */
+ eEnumeratorChannelState state;
+
+ wStream* buffer;
+} enumerator_server;
+
+static UINT enumerator_server_initialize(CamDevEnumServerContext* context, BOOL externalThread)
+{
+ UINT error = CHANNEL_RC_OK;
+ enumerator_server* enumerator = (enumerator_server*)context;
+
+ WINPR_ASSERT(enumerator);
+
+ if (enumerator->isOpened)
+ {
+ WLog_WARN(TAG, "Application error: Camera Device Enumerator channel already initialized, "
+ "calling in this state is not possible!");
+ return ERROR_INVALID_STATE;
+ }
+
+ enumerator->externalThread = externalThread;
+
+ return error;
+}
+
+static UINT enumerator_server_open_channel(enumerator_server* enumerator)
+{
+ CamDevEnumServerContext* context = &enumerator->context;
+ DWORD Error = ERROR_SUCCESS;
+ HANDLE hEvent = NULL;
+ DWORD BytesReturned = 0;
+ PULONG pSessionId = NULL;
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ WINPR_ASSERT(enumerator);
+
+ if (WTSQuerySessionInformationA(enumerator->context.vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSQuerySessionInformationA failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ enumerator->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ hEvent = WTSVirtualChannelManagerGetEventHandle(enumerator->context.vcm);
+
+ if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ enumerator->enumerator_channel = WTSVirtualChannelOpenEx(
+ enumerator->SessionId, RDPECAM_CONTROL_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC);
+ if (!enumerator->enumerator_channel)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ channelId = WTSChannelGetIdByHandle(enumerator->enumerator_channel);
+
+ IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
+ if (!status)
+ {
+ WLog_ERR(TAG, "context->ChannelIdAssigned failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return Error;
+}
+
+static UINT enumerator_server_handle_select_version_request(CamDevEnumServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_SELECT_VERSION_REQUEST pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ IFCALLRET(context->SelectVersionRequest, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->SelectVersionRequest failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT enumerator_server_recv_device_added_notification(CamDevEnumServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_DEVICE_ADDED_NOTIFICATION pdu;
+ UINT error = CHANNEL_RC_OK;
+ size_t remaining_length = 0;
+ WCHAR* channel_name_start = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ /*
+ * RequiredLength 4:
+ *
+ * Nullterminator DeviceName (2),
+ * VirtualChannelName (>= 1),
+ * Nullterminator VirtualChannelName (1)
+ */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_NO_DATA;
+
+ pdu.DeviceName = Stream_Pointer(s);
+
+ remaining_length = Stream_GetRemainingLength(s);
+ channel_name_start = Stream_Pointer(s);
+
+ /* Search for null terminator of DeviceName */
+ size_t i = 0;
+ for (; i < remaining_length; i += sizeof(WCHAR), ++channel_name_start)
+ {
+ if (*channel_name_start == L'\0')
+ break;
+ }
+
+ if (*channel_name_start != L'\0')
+ {
+ WLog_ERR(TAG, "enumerator_server_recv_device_added_notification: Invalid DeviceName!");
+ return ERROR_INVALID_DATA;
+ }
+
+ pdu.VirtualChannelName = (char*)++channel_name_start;
+ ++i;
+
+ if (i >= remaining_length || *pdu.VirtualChannelName == '\0')
+ {
+ WLog_ERR(TAG,
+ "enumerator_server_recv_device_added_notification: Invalid VirtualChannelName!");
+ return ERROR_INVALID_DATA;
+ }
+
+ char* tmp = pdu.VirtualChannelName;
+ for (; i < remaining_length; ++i, ++tmp)
+ {
+ if (*tmp == '\0')
+ break;
+ }
+
+ if (*tmp != '\0')
+ {
+ WLog_ERR(TAG,
+ "enumerator_server_recv_device_added_notification: Invalid VirtualChannelName!");
+ return ERROR_INVALID_DATA;
+ }
+
+ IFCALLRET(context->DeviceAddedNotification, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->DeviceAddedNotification failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT enumerator_server_recv_device_removed_notification(CamDevEnumServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_DEVICE_REMOVED_NOTIFICATION pdu;
+ UINT error = CHANNEL_RC_OK;
+ size_t remaining_length = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
+ return ERROR_NO_DATA;
+
+ pdu.VirtualChannelName = Stream_Pointer(s);
+
+ remaining_length = Stream_GetRemainingLength(s);
+ char* tmp = pdu.VirtualChannelName + 1;
+
+ for (size_t i = 1; i < remaining_length; ++i, ++tmp)
+ {
+ if (*tmp == '\0')
+ break;
+ }
+
+ if (*tmp != '\0')
+ {
+ WLog_ERR(TAG,
+ "enumerator_server_recv_device_removed_notification: Invalid VirtualChannelName!");
+ return ERROR_INVALID_DATA;
+ }
+
+ IFCALLRET(context->DeviceRemovedNotification, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->DeviceRemovedNotification failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT enumerator_process_message(enumerator_server* enumerator)
+{
+ BOOL rc = 0;
+ UINT error = ERROR_INTERNAL_ERROR;
+ ULONG BytesReturned = 0;
+ CAM_SHARED_MSG_HEADER header = { 0 };
+ wStream* s = NULL;
+
+ WINPR_ASSERT(enumerator);
+ WINPR_ASSERT(enumerator->enumerator_channel);
+
+ s = enumerator->buffer;
+ WINPR_ASSERT(s);
+
+ Stream_SetPosition(s, 0);
+ rc = WTSVirtualChannelRead(enumerator->enumerator_channel, 0, NULL, 0, &BytesReturned);
+ if (!rc)
+ goto out;
+
+ if (BytesReturned < 1)
+ {
+ error = CHANNEL_RC_OK;
+ goto out;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (WTSVirtualChannelRead(enumerator->enumerator_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ goto out;
+ }
+
+ Stream_SetLength(s, BytesReturned);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, CAM_HEADER_SIZE))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, header.Version);
+ Stream_Read_UINT8(s, header.MessageId);
+
+ switch (header.MessageId)
+ {
+ case CAM_MSG_ID_SelectVersionRequest:
+ error =
+ enumerator_server_handle_select_version_request(&enumerator->context, s, &header);
+ break;
+ case CAM_MSG_ID_DeviceAddedNotification:
+ error =
+ enumerator_server_recv_device_added_notification(&enumerator->context, s, &header);
+ break;
+ case CAM_MSG_ID_DeviceRemovedNotification:
+ error = enumerator_server_recv_device_removed_notification(&enumerator->context, s,
+ &header);
+ break;
+ default:
+ WLog_ERR(TAG, "enumerator_process_message: unknown or invalid MessageId %" PRIu8 "",
+ header.MessageId);
+ break;
+ }
+
+out:
+ if (error)
+ WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+static UINT enumerator_server_context_poll_int(CamDevEnumServerContext* context)
+{
+ enumerator_server* enumerator = (enumerator_server*)context;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(enumerator);
+
+ switch (enumerator->state)
+ {
+ case ENUMERATOR_INITIAL:
+ error = enumerator_server_open_channel(enumerator);
+ if (error)
+ WLog_ERR(TAG, "enumerator_server_open_channel failed with error %" PRIu32 "!",
+ error);
+ else
+ enumerator->state = ENUMERATOR_OPENED;
+ break;
+ case ENUMERATOR_OPENED:
+ error = enumerator_process_message(enumerator);
+ break;
+ }
+
+ return error;
+}
+
+static HANDLE enumerator_server_get_channel_handle(enumerator_server* enumerator)
+{
+ void* buffer = NULL;
+ DWORD BytesReturned = 0;
+ HANDLE ChannelEvent = NULL;
+
+ WINPR_ASSERT(enumerator);
+
+ if (WTSVirtualChannelQuery(enumerator->enumerator_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+
+ return ChannelEvent;
+}
+
+static DWORD WINAPI enumerator_server_thread_func(LPVOID arg)
+{
+ DWORD nCount = 0;
+ HANDLE events[2] = { 0 };
+ enumerator_server* enumerator = (enumerator_server*)arg;
+ UINT error = CHANNEL_RC_OK;
+ DWORD status = 0;
+
+ WINPR_ASSERT(enumerator);
+
+ nCount = 0;
+ events[nCount++] = enumerator->stopEvent;
+
+ while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0))
+ {
+ switch (enumerator->state)
+ {
+ case ENUMERATOR_INITIAL:
+ error = enumerator_server_context_poll_int(&enumerator->context);
+ if (error == CHANNEL_RC_OK)
+ {
+ events[1] = enumerator_server_get_channel_handle(enumerator);
+ nCount = 2;
+ }
+ break;
+ case ENUMERATOR_OPENED:
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_OBJECT_0 + 1:
+ case WAIT_TIMEOUT:
+ error = enumerator_server_context_poll_int(&enumerator->context);
+ break;
+
+ case WAIT_FAILED:
+ default:
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+ break;
+ }
+ }
+
+ WTSVirtualChannelClose(enumerator->enumerator_channel);
+ enumerator->enumerator_channel = NULL;
+
+ if (error && enumerator->context.rdpcontext)
+ setChannelError(enumerator->context.rdpcontext, error,
+ "enumerator_server_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+static UINT enumerator_server_open(CamDevEnumServerContext* context)
+{
+ enumerator_server* enumerator = (enumerator_server*)context;
+
+ WINPR_ASSERT(enumerator);
+
+ if (!enumerator->externalThread && (enumerator->thread == NULL))
+ {
+ enumerator->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!enumerator->stopEvent)
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ enumerator->thread =
+ CreateThread(NULL, 0, enumerator_server_thread_func, enumerator, 0, NULL);
+ if (!enumerator->thread)
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(enumerator->stopEvent);
+ enumerator->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ enumerator->isOpened = TRUE;
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT enumerator_server_close(CamDevEnumServerContext* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ enumerator_server* enumerator = (enumerator_server*)context;
+
+ WINPR_ASSERT(enumerator);
+
+ if (!enumerator->externalThread && enumerator->thread)
+ {
+ SetEvent(enumerator->stopEvent);
+
+ if (WaitForSingleObject(enumerator->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(enumerator->thread);
+ CloseHandle(enumerator->stopEvent);
+ enumerator->thread = NULL;
+ enumerator->stopEvent = NULL;
+ }
+ if (enumerator->externalThread)
+ {
+ if (enumerator->state != ENUMERATOR_INITIAL)
+ {
+ WTSVirtualChannelClose(enumerator->enumerator_channel);
+ enumerator->enumerator_channel = NULL;
+ enumerator->state = ENUMERATOR_INITIAL;
+ }
+ }
+ enumerator->isOpened = FALSE;
+
+ return error;
+}
+
+static UINT enumerator_server_context_poll(CamDevEnumServerContext* context)
+{
+ enumerator_server* enumerator = (enumerator_server*)context;
+
+ WINPR_ASSERT(enumerator);
+
+ if (!enumerator->externalThread)
+ return ERROR_INTERNAL_ERROR;
+
+ return enumerator_server_context_poll_int(context);
+}
+
+static BOOL enumerator_server_context_handle(CamDevEnumServerContext* context, HANDLE* handle)
+{
+ enumerator_server* enumerator = (enumerator_server*)context;
+
+ WINPR_ASSERT(enumerator);
+ WINPR_ASSERT(handle);
+
+ if (!enumerator->externalThread)
+ return FALSE;
+ if (enumerator->state == ENUMERATOR_INITIAL)
+ return FALSE;
+
+ *handle = enumerator_server_get_channel_handle(enumerator);
+
+ return TRUE;
+}
+
+static UINT enumerator_server_packet_send(CamDevEnumServerContext* context, wStream* s)
+{
+ enumerator_server* enumerator = (enumerator_server*)context;
+ UINT error = CHANNEL_RC_OK;
+ ULONG written = 0;
+
+ if (!WTSVirtualChannelWrite(enumerator->enumerator_channel, (PCHAR)Stream_Buffer(s),
+ Stream_GetPosition(s), &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ if (written < Stream_GetPosition(s))
+ {
+ WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written,
+ Stream_GetPosition(s));
+ }
+
+out:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+static UINT enumerator_send_select_version_response_pdu(
+ CamDevEnumServerContext* context, const CAM_SELECT_VERSION_RESPONSE* selectVersionResponse)
+{
+ wStream* s = NULL;
+
+ s = Stream_New(NULL, CAM_HEADER_SIZE);
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ Stream_Write_UINT8(s, selectVersionResponse->Header.Version);
+ Stream_Write_UINT8(s, selectVersionResponse->Header.MessageId);
+
+ return enumerator_server_packet_send(context, s);
+}
+
+CamDevEnumServerContext* cam_dev_enum_server_context_new(HANDLE vcm)
+{
+ enumerator_server* enumerator = (enumerator_server*)calloc(1, sizeof(enumerator_server));
+
+ if (!enumerator)
+ return NULL;
+
+ enumerator->context.vcm = vcm;
+ enumerator->context.Initialize = enumerator_server_initialize;
+ enumerator->context.Open = enumerator_server_open;
+ enumerator->context.Close = enumerator_server_close;
+ enumerator->context.Poll = enumerator_server_context_poll;
+ enumerator->context.ChannelHandle = enumerator_server_context_handle;
+
+ enumerator->context.SelectVersionResponse = enumerator_send_select_version_response_pdu;
+
+ enumerator->buffer = Stream_New(NULL, 4096);
+ if (!enumerator->buffer)
+ goto fail;
+
+ return &enumerator->context;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ cam_dev_enum_server_context_free(&enumerator->context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void cam_dev_enum_server_context_free(CamDevEnumServerContext* context)
+{
+ enumerator_server* enumerator = (enumerator_server*)context;
+
+ if (enumerator)
+ {
+ enumerator_server_close(context);
+ Stream_Free(enumerator->buffer, TRUE);
+ }
+
+ free(enumerator);
+}
diff --git a/channels/rdpecam/server/camera_device_main.c b/channels/rdpecam/server/camera_device_main.c
new file mode 100644
index 0000000..ce0774c
--- /dev/null
+++ b/channels/rdpecam/server/camera_device_main.c
@@ -0,0 +1,966 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Video Capture Virtual Channel Extension
+ *
+ * Copyright 2022 Pascal Nowack <Pascal.Nowack@gmx.de>
+ *
+ * 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 <freerdp/freerdp.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/server/rdpecam.h>
+
+#define TAG CHANNELS_TAG("rdpecam.server")
+
+typedef enum
+{
+ CAMERA_DEVICE_INITIAL,
+ CAMERA_DEVICE_OPENED,
+} eCameraDeviceChannelState;
+
+typedef struct
+{
+ CameraDeviceServerContext context;
+
+ HANDLE stopEvent;
+
+ HANDLE thread;
+ void* device_channel;
+
+ DWORD SessionId;
+
+ BOOL isOpened;
+ BOOL externalThread;
+
+ /* Channel state */
+ eCameraDeviceChannelState state;
+
+ wStream* buffer;
+} device_server;
+
+static UINT device_server_initialize(CameraDeviceServerContext* context, BOOL externalThread)
+{
+ UINT error = CHANNEL_RC_OK;
+ device_server* device = (device_server*)context;
+
+ WINPR_ASSERT(device);
+
+ if (device->isOpened)
+ {
+ WLog_WARN(TAG, "Application error: Camera channel already initialized, "
+ "calling in this state is not possible!");
+ return ERROR_INVALID_STATE;
+ }
+
+ device->externalThread = externalThread;
+
+ return error;
+}
+
+static UINT device_server_open_channel(device_server* device)
+{
+ CameraDeviceServerContext* context = &device->context;
+ DWORD Error = ERROR_SUCCESS;
+ HANDLE hEvent = NULL;
+ DWORD BytesReturned = 0;
+ PULONG pSessionId = NULL;
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ WINPR_ASSERT(device);
+
+ if (WTSQuerySessionInformationA(device->context.vcm, WTS_CURRENT_SESSION, WTSSessionId,
+ (LPSTR*)&pSessionId, &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSQuerySessionInformationA failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ device->SessionId = (DWORD)*pSessionId;
+ WTSFreeMemory(pSessionId);
+ hEvent = WTSVirtualChannelManagerGetEventHandle(device->context.vcm);
+
+ if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ device->device_channel = WTSVirtualChannelOpenEx(device->SessionId, context->virtualChannelName,
+ WTS_CHANNEL_OPTION_DYNAMIC);
+ if (!device->device_channel)
+ {
+ Error = GetLastError();
+ WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error);
+ return Error;
+ }
+
+ channelId = WTSChannelGetIdByHandle(device->device_channel);
+
+ IFCALLRET(context->ChannelIdAssigned, status, context, channelId);
+ if (!status)
+ {
+ WLog_ERR(TAG, "context->ChannelIdAssigned failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return Error;
+}
+
+static UINT device_server_handle_success_response(CameraDeviceServerContext* context, wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_SUCCESS_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ IFCALLRET(context->SuccessResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->SuccessResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_server_recv_error_response(CameraDeviceServerContext* context, wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_ERROR_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT32(s, pdu.ErrorCode);
+
+ IFCALLRET(context->ErrorResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->ErrorResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_server_recv_stream_list_response(CameraDeviceServerContext* context, wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_STREAM_LIST_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 5))
+ return ERROR_NO_DATA;
+
+ pdu.N_Descriptions = MIN(Stream_GetRemainingLength(s) / 5, 255);
+
+ for (BYTE i = 0; i < pdu.N_Descriptions; ++i)
+ {
+ CAM_STREAM_DESCRIPTION* StreamDescription = &pdu.StreamDescriptions[i];
+
+ Stream_Read_UINT16(s, StreamDescription->FrameSourceTypes);
+ Stream_Read_UINT8(s, StreamDescription->StreamCategory);
+ Stream_Read_UINT8(s, StreamDescription->Selected);
+ Stream_Read_UINT8(s, StreamDescription->CanBeShared);
+ }
+
+ IFCALLRET(context->StreamListResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->StreamListResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_server_recv_media_type_list_response(CameraDeviceServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_MEDIA_TYPE_LIST_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 26))
+ return ERROR_NO_DATA;
+
+ pdu.N_Descriptions = Stream_GetRemainingLength(s) / 26;
+
+ pdu.MediaTypeDescriptions = calloc(pdu.N_Descriptions, sizeof(CAM_MEDIA_TYPE_DESCRIPTION));
+ if (!pdu.MediaTypeDescriptions)
+ {
+ WLog_ERR(TAG, "Failed to allocate %zu CAM_MEDIA_TYPE_DESCRIPTION structs",
+ pdu.N_Descriptions);
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (size_t i = 0; i < pdu.N_Descriptions; ++i)
+ {
+ CAM_MEDIA_TYPE_DESCRIPTION* MediaTypeDescriptions = &pdu.MediaTypeDescriptions[i];
+
+ Stream_Read_UINT8(s, MediaTypeDescriptions->Format);
+ Stream_Read_UINT32(s, MediaTypeDescriptions->Width);
+ Stream_Read_UINT32(s, MediaTypeDescriptions->Height);
+ Stream_Read_UINT32(s, MediaTypeDescriptions->FrameRateNumerator);
+ Stream_Read_UINT32(s, MediaTypeDescriptions->FrameRateDenominator);
+ Stream_Read_UINT32(s, MediaTypeDescriptions->PixelAspectRatioNumerator);
+ Stream_Read_UINT32(s, MediaTypeDescriptions->PixelAspectRatioDenominator);
+ Stream_Read_UINT8(s, MediaTypeDescriptions->Flags);
+ }
+
+ IFCALLRET(context->MediaTypeListResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->MediaTypeListResponse failed with error %" PRIu32 "", error);
+
+ free(pdu.MediaTypeDescriptions);
+
+ return error;
+}
+
+static UINT device_server_recv_current_media_type_response(CameraDeviceServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_CURRENT_MEDIA_TYPE_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 26))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, pdu.MediaTypeDescription.Format);
+ Stream_Read_UINT32(s, pdu.MediaTypeDescription.Width);
+ Stream_Read_UINT32(s, pdu.MediaTypeDescription.Height);
+ Stream_Read_UINT32(s, pdu.MediaTypeDescription.FrameRateNumerator);
+ Stream_Read_UINT32(s, pdu.MediaTypeDescription.FrameRateDenominator);
+ Stream_Read_UINT32(s, pdu.MediaTypeDescription.PixelAspectRatioNumerator);
+ Stream_Read_UINT32(s, pdu.MediaTypeDescription.PixelAspectRatioDenominator);
+ Stream_Read_UINT8(s, pdu.MediaTypeDescription.Flags);
+
+ IFCALLRET(context->CurrentMediaTypeResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->CurrentMediaTypeResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_server_recv_sample_response(CameraDeviceServerContext* context, wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_SAMPLE_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, pdu.StreamIndex);
+
+ pdu.SampleSize = Stream_GetRemainingLength(s);
+ pdu.Sample = Stream_Pointer(s);
+
+ IFCALLRET(context->SampleResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->SampleResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_server_recv_sample_error_response(CameraDeviceServerContext* context, wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_SAMPLE_ERROR_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 5))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, pdu.StreamIndex);
+ Stream_Read_UINT32(s, pdu.ErrorCode);
+
+ IFCALLRET(context->SampleErrorResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->SampleErrorResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_server_recv_property_list_response(CameraDeviceServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_PROPERTY_LIST_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ pdu.N_Properties = Stream_GetRemainingLength(s) / 19;
+
+ if (pdu.N_Properties > 0)
+ {
+ pdu.Properties = calloc(pdu.N_Properties, sizeof(CAM_PROPERTY_DESCRIPTION));
+ if (!pdu.Properties)
+ {
+ WLog_ERR(TAG, "Failed to allocate %zu CAM_PROPERTY_DESCRIPTION structs",
+ pdu.N_Properties);
+ return ERROR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (size_t i = 0; i < pdu.N_Properties; ++i)
+ {
+ Stream_Read_UINT8(s, pdu.Properties[i].PropertySet);
+ Stream_Read_UINT8(s, pdu.Properties[i].PropertyId);
+ Stream_Read_UINT8(s, pdu.Properties[i].Capabilities);
+ Stream_Read_INT32(s, pdu.Properties[i].MinValue);
+ Stream_Read_INT32(s, pdu.Properties[i].MaxValue);
+ Stream_Read_INT32(s, pdu.Properties[i].Step);
+ Stream_Read_INT32(s, pdu.Properties[i].DefaultValue);
+ }
+ }
+
+ IFCALLRET(context->PropertyListResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->PropertyListResponse failed with error %" PRIu32 "", error);
+
+ free(pdu.Properties);
+
+ return error;
+}
+
+static UINT device_server_recv_property_value_response(CameraDeviceServerContext* context,
+ wStream* s,
+ const CAM_SHARED_MSG_HEADER* header)
+{
+ CAM_PROPERTY_VALUE_RESPONSE pdu = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ pdu.Header = *header;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 5))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, pdu.PropertyValue.Mode);
+ Stream_Read_INT32(s, pdu.PropertyValue.Value);
+
+ IFCALLRET(context->PropertyValueResponse, error, context, &pdu);
+ if (error)
+ WLog_ERR(TAG, "context->PropertyValueResponse failed with error %" PRIu32 "", error);
+
+ return error;
+}
+
+static UINT device_process_message(device_server* device)
+{
+ BOOL rc = 0;
+ UINT error = ERROR_INTERNAL_ERROR;
+ ULONG BytesReturned = 0;
+ CAM_SHARED_MSG_HEADER header = { 0 };
+ wStream* s = NULL;
+
+ WINPR_ASSERT(device);
+ WINPR_ASSERT(device->device_channel);
+
+ s = device->buffer;
+ WINPR_ASSERT(s);
+
+ Stream_SetPosition(s, 0);
+ rc = WTSVirtualChannelRead(device->device_channel, 0, NULL, 0, &BytesReturned);
+ if (!rc)
+ goto out;
+
+ if (BytesReturned < 1)
+ {
+ error = CHANNEL_RC_OK;
+ goto out;
+ }
+
+ if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
+ {
+ WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto out;
+ }
+
+ if (WTSVirtualChannelRead(device->device_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ goto out;
+ }
+
+ Stream_SetLength(s, BytesReturned);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, CAM_HEADER_SIZE))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT8(s, header.Version);
+ Stream_Read_UINT8(s, header.MessageId);
+
+ switch (header.MessageId)
+ {
+ case CAM_MSG_ID_SuccessResponse:
+ error = device_server_handle_success_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_ErrorResponse:
+ error = device_server_recv_error_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_StreamListResponse:
+ error = device_server_recv_stream_list_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_MediaTypeListResponse:
+ error = device_server_recv_media_type_list_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_CurrentMediaTypeResponse:
+ error = device_server_recv_current_media_type_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_SampleResponse:
+ error = device_server_recv_sample_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_SampleErrorResponse:
+ error = device_server_recv_sample_error_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_PropertyListResponse:
+ error = device_server_recv_property_list_response(&device->context, s, &header);
+ break;
+ case CAM_MSG_ID_PropertyValueResponse:
+ error = device_server_recv_property_value_response(&device->context, s, &header);
+ break;
+ default:
+ WLog_ERR(TAG, "device_process_message: unknown or invalid MessageId %" PRIu8 "",
+ header.MessageId);
+ break;
+ }
+
+out:
+ if (error)
+ WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+static UINT device_server_context_poll_int(CameraDeviceServerContext* context)
+{
+ device_server* device = (device_server*)context;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(device);
+
+ switch (device->state)
+ {
+ case CAMERA_DEVICE_INITIAL:
+ error = device_server_open_channel(device);
+ if (error)
+ WLog_ERR(TAG, "device_server_open_channel failed with error %" PRIu32 "!", error);
+ else
+ device->state = CAMERA_DEVICE_OPENED;
+ break;
+ case CAMERA_DEVICE_OPENED:
+ error = device_process_message(device);
+ break;
+ }
+
+ return error;
+}
+
+static HANDLE device_server_get_channel_handle(device_server* device)
+{
+ void* buffer = NULL;
+ DWORD BytesReturned = 0;
+ HANDLE ChannelEvent = NULL;
+
+ WINPR_ASSERT(device);
+
+ if (WTSVirtualChannelQuery(device->device_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+
+ return ChannelEvent;
+}
+
+static DWORD WINAPI device_server_thread_func(LPVOID arg)
+{
+ DWORD nCount = 0;
+ HANDLE events[2] = { 0 };
+ device_server* device = (device_server*)arg;
+ UINT error = CHANNEL_RC_OK;
+ DWORD status = 0;
+
+ WINPR_ASSERT(device);
+
+ nCount = 0;
+ events[nCount++] = device->stopEvent;
+
+ while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0))
+ {
+ switch (device->state)
+ {
+ case CAMERA_DEVICE_INITIAL:
+ error = device_server_context_poll_int(&device->context);
+ if (error == CHANNEL_RC_OK)
+ {
+ events[1] = device_server_get_channel_handle(device);
+ nCount = 2;
+ }
+ break;
+ case CAMERA_DEVICE_OPENED:
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_OBJECT_0:
+ break;
+ case WAIT_OBJECT_0 + 1:
+ case WAIT_TIMEOUT:
+ error = device_server_context_poll_int(&device->context);
+ break;
+
+ case WAIT_FAILED:
+ default:
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+ break;
+ }
+ }
+
+ WTSVirtualChannelClose(device->device_channel);
+ device->device_channel = NULL;
+
+ if (error && device->context.rdpcontext)
+ setChannelError(device->context.rdpcontext, error,
+ "device_server_thread_func reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+static UINT device_server_open(CameraDeviceServerContext* context)
+{
+ device_server* device = (device_server*)context;
+
+ WINPR_ASSERT(device);
+
+ if (!device->externalThread && (device->thread == NULL))
+ {
+ device->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!device->stopEvent)
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ device->thread = CreateThread(NULL, 0, device_server_thread_func, device, 0, NULL);
+ if (!device->thread)
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(device->stopEvent);
+ device->stopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+ }
+ device->isOpened = TRUE;
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT device_server_close(CameraDeviceServerContext* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ device_server* device = (device_server*)context;
+
+ WINPR_ASSERT(device);
+
+ if (!device->externalThread && device->thread)
+ {
+ SetEvent(device->stopEvent);
+
+ if (WaitForSingleObject(device->thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(device->thread);
+ CloseHandle(device->stopEvent);
+ device->thread = NULL;
+ device->stopEvent = NULL;
+ }
+ if (device->externalThread)
+ {
+ if (device->state != CAMERA_DEVICE_INITIAL)
+ {
+ WTSVirtualChannelClose(device->device_channel);
+ device->device_channel = NULL;
+ device->state = CAMERA_DEVICE_INITIAL;
+ }
+ }
+ device->isOpened = FALSE;
+
+ return error;
+}
+
+static UINT device_server_context_poll(CameraDeviceServerContext* context)
+{
+ device_server* device = (device_server*)context;
+
+ WINPR_ASSERT(device);
+
+ if (!device->externalThread)
+ return ERROR_INTERNAL_ERROR;
+
+ return device_server_context_poll_int(context);
+}
+
+static BOOL device_server_context_handle(CameraDeviceServerContext* context, HANDLE* handle)
+{
+ device_server* device = (device_server*)context;
+
+ WINPR_ASSERT(device);
+ WINPR_ASSERT(handle);
+
+ if (!device->externalThread)
+ return FALSE;
+ if (device->state == CAMERA_DEVICE_INITIAL)
+ return FALSE;
+
+ *handle = device_server_get_channel_handle(device);
+
+ return TRUE;
+}
+
+static wStream* device_server_packet_new(size_t size, BYTE version, BYTE messageId)
+{
+ wStream* s = NULL;
+
+ /* Allocate what we need plus header bytes */
+ s = Stream_New(NULL, size + CAM_HEADER_SIZE);
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return NULL;
+ }
+
+ Stream_Write_UINT8(s, version);
+ Stream_Write_UINT8(s, messageId);
+
+ return s;
+}
+
+static UINT device_server_packet_send(CameraDeviceServerContext* context, wStream* s)
+{
+ device_server* device = (device_server*)context;
+ UINT error = CHANNEL_RC_OK;
+ ULONG written = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(s);
+
+ if (!WTSVirtualChannelWrite(device->device_channel, (PCHAR)Stream_Buffer(s),
+ Stream_GetPosition(s), &written))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
+ error = ERROR_INTERNAL_ERROR;
+ goto out;
+ }
+
+ if (written < Stream_GetPosition(s))
+ {
+ WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written,
+ Stream_GetPosition(s));
+ }
+
+out:
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+static UINT device_server_write_and_send_header(CameraDeviceServerContext* context, BYTE messageId)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+
+ s = device_server_packet_new(0, context->protocolVersion, messageId);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ return device_server_packet_send(context, s);
+}
+
+static UINT
+device_send_activate_device_request_pdu(CameraDeviceServerContext* context,
+ const CAM_ACTIVATE_DEVICE_REQUEST* activateDeviceRequest)
+{
+ WINPR_ASSERT(context);
+
+ return device_server_write_and_send_header(context, CAM_MSG_ID_ActivateDeviceRequest);
+}
+
+static UINT device_send_deactivate_device_request_pdu(
+ CameraDeviceServerContext* context,
+ const CAM_DEACTIVATE_DEVICE_REQUEST* deactivateDeviceRequest)
+{
+ WINPR_ASSERT(context);
+
+ return device_server_write_and_send_header(context, CAM_MSG_ID_DeactivateDeviceRequest);
+}
+
+static UINT device_send_stream_list_request_pdu(CameraDeviceServerContext* context,
+ const CAM_STREAM_LIST_REQUEST* streamListRequest)
+{
+ WINPR_ASSERT(context);
+
+ return device_server_write_and_send_header(context, CAM_MSG_ID_StreamListRequest);
+}
+
+static UINT
+device_send_media_type_list_request_pdu(CameraDeviceServerContext* context,
+ const CAM_MEDIA_TYPE_LIST_REQUEST* mediaTypeListRequest)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(mediaTypeListRequest);
+
+ s = device_server_packet_new(1, context->protocolVersion, CAM_MSG_ID_MediaTypeListRequest);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT8(s, mediaTypeListRequest->StreamIndex);
+
+ return device_server_packet_send(context, s);
+}
+
+static UINT device_send_current_media_type_request_pdu(
+ CameraDeviceServerContext* context,
+ const CAM_CURRENT_MEDIA_TYPE_REQUEST* currentMediaTypeRequest)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(currentMediaTypeRequest);
+
+ s = device_server_packet_new(1, context->protocolVersion, CAM_MSG_ID_CurrentMediaTypeRequest);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT8(s, currentMediaTypeRequest->StreamIndex);
+
+ return device_server_packet_send(context, s);
+}
+
+static UINT
+device_send_start_streams_request_pdu(CameraDeviceServerContext* context,
+ const CAM_START_STREAMS_REQUEST* startStreamsRequest)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(startStreamsRequest);
+
+ s = device_server_packet_new(startStreamsRequest->N_Infos * 27ul, context->protocolVersion,
+ CAM_MSG_ID_StartStreamsRequest);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ for (size_t i = 0; i < startStreamsRequest->N_Infos; ++i)
+ {
+ const CAM_START_STREAM_INFO* info = &startStreamsRequest->StartStreamsInfo[i];
+ const CAM_MEDIA_TYPE_DESCRIPTION* description = &info->MediaTypeDescription;
+
+ Stream_Write_UINT8(s, info->StreamIndex);
+
+ Stream_Write_UINT8(s, description->Format);
+ Stream_Write_UINT32(s, description->Width);
+ Stream_Write_UINT32(s, description->Height);
+ Stream_Write_UINT32(s, description->FrameRateNumerator);
+ Stream_Write_UINT32(s, description->FrameRateDenominator);
+ Stream_Write_UINT32(s, description->PixelAspectRatioNumerator);
+ Stream_Write_UINT32(s, description->PixelAspectRatioDenominator);
+ Stream_Write_UINT8(s, description->Flags);
+ }
+
+ return device_server_packet_send(context, s);
+}
+
+static UINT device_send_stop_streams_request_pdu(CameraDeviceServerContext* context,
+ const CAM_STOP_STREAMS_REQUEST* stopStreamsRequest)
+{
+ WINPR_ASSERT(context);
+
+ return device_server_write_and_send_header(context, CAM_MSG_ID_StopStreamsRequest);
+}
+
+static UINT device_send_sample_request_pdu(CameraDeviceServerContext* context,
+ const CAM_SAMPLE_REQUEST* sampleRequest)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(sampleRequest);
+
+ s = device_server_packet_new(1, context->protocolVersion, CAM_MSG_ID_SampleRequest);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT8(s, sampleRequest->StreamIndex);
+
+ return device_server_packet_send(context, s);
+}
+
+static UINT
+device_send_property_list_request_pdu(CameraDeviceServerContext* context,
+ const CAM_PROPERTY_LIST_REQUEST* propertyListRequest)
+{
+ WINPR_ASSERT(context);
+
+ return device_server_write_and_send_header(context, CAM_MSG_ID_PropertyListRequest);
+}
+
+static UINT
+device_send_property_value_request_pdu(CameraDeviceServerContext* context,
+ const CAM_PROPERTY_VALUE_REQUEST* propertyValueRequest)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(propertyValueRequest);
+
+ s = device_server_packet_new(2, context->protocolVersion, CAM_MSG_ID_PropertyValueRequest);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT8(s, propertyValueRequest->PropertySet);
+ Stream_Write_UINT8(s, propertyValueRequest->PropertyId);
+
+ return device_server_packet_send(context, s);
+}
+
+static UINT device_send_set_property_value_request_pdu(
+ CameraDeviceServerContext* context,
+ const CAM_SET_PROPERTY_VALUE_REQUEST* setPropertyValueRequest)
+{
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(setPropertyValueRequest);
+
+ s = device_server_packet_new(2 + 5, context->protocolVersion,
+ CAM_MSG_ID_SetPropertyValueRequest);
+ if (!s)
+ return ERROR_NOT_ENOUGH_MEMORY;
+
+ Stream_Write_UINT8(s, setPropertyValueRequest->PropertySet);
+ Stream_Write_UINT8(s, setPropertyValueRequest->PropertyId);
+
+ Stream_Write_UINT8(s, setPropertyValueRequest->PropertyValue.Mode);
+ Stream_Write_INT32(s, setPropertyValueRequest->PropertyValue.Value);
+
+ return device_server_packet_send(context, s);
+}
+
+CameraDeviceServerContext* camera_device_server_context_new(HANDLE vcm)
+{
+ device_server* device = (device_server*)calloc(1, sizeof(device_server));
+
+ if (!device)
+ return NULL;
+
+ device->context.vcm = vcm;
+ device->context.Initialize = device_server_initialize;
+ device->context.Open = device_server_open;
+ device->context.Close = device_server_close;
+ device->context.Poll = device_server_context_poll;
+ device->context.ChannelHandle = device_server_context_handle;
+
+ device->context.ActivateDeviceRequest = device_send_activate_device_request_pdu;
+ device->context.DeactivateDeviceRequest = device_send_deactivate_device_request_pdu;
+
+ device->context.StreamListRequest = device_send_stream_list_request_pdu;
+ device->context.MediaTypeListRequest = device_send_media_type_list_request_pdu;
+ device->context.CurrentMediaTypeRequest = device_send_current_media_type_request_pdu;
+
+ device->context.StartStreamsRequest = device_send_start_streams_request_pdu;
+ device->context.StopStreamsRequest = device_send_stop_streams_request_pdu;
+ device->context.SampleRequest = device_send_sample_request_pdu;
+
+ device->context.PropertyListRequest = device_send_property_list_request_pdu;
+ device->context.PropertyValueRequest = device_send_property_value_request_pdu;
+ device->context.SetPropertyValueRequest = device_send_set_property_value_request_pdu;
+
+ device->buffer = Stream_New(NULL, 4096);
+ if (!device->buffer)
+ goto fail;
+
+ return &device->context;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ camera_device_server_context_free(&device->context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void camera_device_server_context_free(CameraDeviceServerContext* context)
+{
+ device_server* device = (device_server*)context;
+
+ if (device)
+ {
+ device_server_close(context);
+ Stream_Free(device->buffer, TRUE);
+ }
+
+ free(context->virtualChannelName);
+
+ free(device);
+}