diff --git a/channels/CMakeLists.txt b/channels/CMakeLists.txt index cac9ecb25..cad803767 100644 --- a/channels/CMakeLists.txt +++ b/channels/CMakeLists.txt @@ -18,5 +18,6 @@ # limitations under the License. add_subdirectory(cliprdr) +add_subdirectory(drdynvc) add_subdirectory(rdpdbg) diff --git a/channels/drdynvc/CMakeLists.txt b/channels/drdynvc/CMakeLists.txt new file mode 100644 index 000000000..a21e4fa50 --- /dev/null +++ b/channels/drdynvc/CMakeLists.txt @@ -0,0 +1,33 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(DRDYNVC_SRCS + drdynvc_main.c + drdynvc_main.h + drdynvc_types.h + dvcman.c + dvcman.h +) + +add_library(drdynvc SHARED ${DRDYNVC_SRCS}) +set_target_properties(drdynvc PROPERTIES PREFIX "") + +target_link_libraries(drdynvc freerdp-utils) + +install(TARGETS drdynvc DESTINATION ${FREERDP_PLUGIN_PATH}) diff --git a/channels/drdynvc/drdynvc_main.c b/channels/drdynvc/drdynvc_main.c new file mode 100644 index 000000000..c1b3a6061 --- /dev/null +++ b/channels/drdynvc/drdynvc_main.c @@ -0,0 +1,356 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Dynamic Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * + * 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 "dvcman.h" +#include "drdynvc_types.h" +#include "drdynvc_main.h" + +#define CREATE_REQUEST_PDU 0x01 +#define DATA_FIRST_PDU 0x02 +#define DATA_PDU 0x03 +#define CLOSE_REQUEST_PDU 0x04 +#define CAPABILITY_REQUEST_PDU 0x05 + +struct drdynvc_plugin +{ + rdpSvcPlugin plugin; + + int version; + int PriorityCharge0; + int PriorityCharge1; + int PriorityCharge2; + int PriorityCharge3; + + IWTSVirtualChannelManager* channel_mgr; +}; + +static int drdynvc_write_variable_uint(STREAM* stream, uint32 val) +{ + int cb; + + if (val <= 0xFF) + { + cb = 0; + stream_write_uint8(stream, val); + } + else if (val <= 0xFFFF) + { + cb = 1; + stream_write_uint16(stream, val); + } + else + { + cb = 3; + stream_write_uint32(stream, val); + } + return cb; +} + +int drdynvc_write_data(drdynvcPlugin* drdynvc, uint32 ChannelId, char* data, uint32 data_size) +{ + STREAM* data_out; + uint32 pos; + uint32 t; + uint32 cbChId; + uint32 cbLen; + uint32 chunk_len; + int error; + + DEBUG_DVC("ChannelId=%d size=%d", ChannelId, data_size); + + data_out = stream_new(CHANNEL_CHUNK_LENGTH); + stream_set_pos(data_out, 1); + cbChId = drdynvc_write_variable_uint(data_out, ChannelId); + + if (data_size <= CHANNEL_CHUNK_LENGTH - pos) + { + pos = stream_get_pos(data_out); + stream_set_pos(data_out, 0); + stream_write_uint8(data_out, 0x30 | cbChId); + stream_set_pos(data_out, pos); + stream_write(data_out, data, data_size); + error = svc_plugin_send((rdpSvcPlugin*)drdynvc, data_out); + } + else + { + /* Fragment the data */ + cbLen = drdynvc_write_variable_uint(data_out, data_size); + pos = stream_get_pos(data_out); + stream_set_pos(data_out, 0); + stream_write_uint8(data_out, 0x20 | cbChId | (cbLen << 2)); + stream_set_pos(data_out, pos); + chunk_len = CHANNEL_CHUNK_LENGTH - pos; + stream_write(data_out, data, chunk_len); + data += chunk_len; + data_size -= chunk_len; + error = svc_plugin_send((rdpSvcPlugin*)drdynvc, data_out); + + while (error == CHANNEL_RC_OK && data_size > 0) + { + data_out = stream_new(CHANNEL_CHUNK_LENGTH); + stream_set_pos(data_out, 1); + cbChId = drdynvc_write_variable_uint(data_out, ChannelId); + + pos = stream_get_pos(data_out); + stream_set_pos(data_out, 0); + stream_write_uint8(data_out, 0x30 | cbChId); + stream_set_pos(data_out, pos); + + chunk_len = data_size; + if (chunk_len > CHANNEL_CHUNK_LENGTH - pos) + chunk_len = CHANNEL_CHUNK_LENGTH - pos; + stream_write(data_out, data, chunk_len); + data += chunk_len; + data_size -= chunk_len; + error = svc_plugin_send((rdpSvcPlugin*)drdynvc, data_out); + } + } + if (error != CHANNEL_RC_OK) + { + DEBUG_WARN("VirtualChannelWrite failed %d", error); + return 1; + } + return 0; +} + +int drdynvc_push_event(drdynvcPlugin* drdynvc, FRDP_EVENT* event) +{ + int error; + + error = svc_plugin_send_event((rdpSvcPlugin*)drdynvc, event); + if (error != CHANNEL_RC_OK) + { + DEBUG_WARN("pVirtualChannelEventPush failed %d", error); + return 1; + } + return 0; +} + +static int drdynvc_process_capability_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* data_in) +{ + STREAM* data_out; + int error; + + DEBUG_DVC("Sp=%d cbChId=%d", Sp, cbChId); + stream_seek(data_in, 1); /* pad */ + stream_read_uint16(data_in, drdynvc->version); + if (drdynvc->version == 2) + { + stream_read_uint16(data_in, drdynvc->PriorityCharge0); + stream_read_uint16(data_in, drdynvc->PriorityCharge1); + stream_read_uint16(data_in, drdynvc->PriorityCharge2); + stream_read_uint16(data_in, drdynvc->PriorityCharge3); + } + data_out = stream_new(4); + stream_write_uint16(data_out, 0x0050); /* Cmd+Sp+cbChId+Pad. Note: MSTSC sends 0x005c */ + stream_write_uint16(data_out, drdynvc->version); + error = svc_plugin_send((rdpSvcPlugin*)drdynvc, data_out); + if (error != CHANNEL_RC_OK) + { + DEBUG_WARN("VirtualChannelWrite failed %d", error); + return 1; + } + return 0; +} + +static uint32 drdynvc_read_variable_uint(STREAM* stream, int cbLen) +{ + uint32 val; + + switch (cbLen) + { + case 0: + stream_read_uint8(stream, val); + break; + case 1: + stream_read_uint16(stream, val); + break; + default: + stream_read_uint32(stream, val); + break; + } + return val; +} + +static int drdynvc_process_create_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* data_in) +{ + STREAM* data_out; + int pos; + int error; + uint32 ChannelId; + + ChannelId = drdynvc_read_variable_uint(data_in, cbChId); + pos = stream_get_pos(data_in); + DEBUG_DVC("ChannelId=%d ChannelName=%s", ChannelId, stream_get_tail(data_in)); + + error = dvcman_create_channel(drdynvc->channel_mgr, ChannelId, stream_get_tail(data_in)); + + data_out = stream_new(pos + 4); + stream_write_uint8(data_out, 0x10 | cbChId); + stream_set_pos(data_in, 1); + stream_copy(data_out, data_in, pos - 1); + + if (error == 0) + { + DEBUG_DVC("channel created"); + stream_write_uint32(data_out, 0); + } + else + { + DEBUG_DVC("no listener"); + stream_write_uint32(data_out, (uint32)(-1)); + } + + error = svc_plugin_send((rdpSvcPlugin*)drdynvc, data_out); + if (error != CHANNEL_RC_OK) + { + DEBUG_WARN("VirtualChannelWrite failed %d", error); + return 1; + } + return 0; +} + +static int drdynvc_process_data_first(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* data_in, int in_length) +{ + int pos; + uint32 ChannelId; + uint32 Length; + int error; + + ChannelId = drdynvc_read_variable_uint(data_in, cbChId); + Length = drdynvc_read_variable_uint(data_in, Sp); + pos = stream_get_pos(data_in); + DEBUG_DVC("ChannelId=%d Length=%d", ChannelId, Length); + + error = dvcman_receive_channel_data_first(drdynvc->channel_mgr, ChannelId, Length); + if (error) + return error; + + return dvcman_receive_channel_data(drdynvc->channel_mgr, ChannelId, + stream_get_tail(data_in), in_length - pos); +} + +static int drdynvc_process_data(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* data_in, int in_length) +{ + int pos; + uint32 ChannelId; + + ChannelId = drdynvc_read_variable_uint(data_in, cbChId); + pos = stream_get_pos(data_in); + DEBUG_DVC("ChannelId=%d", ChannelId); + + return dvcman_receive_channel_data(drdynvc->channel_mgr, ChannelId, + stream_get_tail(data_in), in_length - pos); +} + +static int drdynvc_process_close_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, STREAM* data_in) +{ + int pos; + uint32 ChannelId; + + ChannelId = drdynvc_read_variable_uint(data_in, cbChId); + DEBUG_DVC("ChannelId=%d", ChannelId); + dvcman_close_channel(drdynvc->channel_mgr, ChannelId); + + return 0; +} + +static void drdynvc_process_receive(rdpSvcPlugin* plugin, STREAM* data_in) +{ + drdynvcPlugin* drdynvc = (drdynvcPlugin*)plugin; + int in_length; + int value; + int Cmd; + int Sp; + int cbChId; + + in_length = stream_get_length(data_in); + stream_set_pos(data_in, 0); + + stream_read_uint8(data_in, value); + Cmd = (value & 0xf0) >> 4; + Sp = (value & 0x0c) >> 2; + cbChId = (value & 0x03) >> 0; + DEBUG_DVC("in_length=%d Cmd=0x%x", in_length, Cmd); + + switch (Cmd) + { + case CAPABILITY_REQUEST_PDU: + drdynvc_process_capability_request(drdynvc, Sp, cbChId, data_in); + break; + case CREATE_REQUEST_PDU: + drdynvc_process_create_request(drdynvc, Sp, cbChId, data_in); + break; + case DATA_FIRST_PDU: + drdynvc_process_data_first(drdynvc, Sp, cbChId, data_in, in_length); + break; + case DATA_PDU: + drdynvc_process_data(drdynvc, Sp, cbChId, data_in, in_length); + break; + case CLOSE_REQUEST_PDU: + drdynvc_process_close_request(drdynvc, Sp, cbChId, data_in); + break; + default: + DEBUG_WARN("unknown drdynvc cmd 0x%x", Cmd); + break; + } + + stream_free(data_in); +} + +static void drdynvc_process_connect(rdpSvcPlugin* plugin) +{ + drdynvcPlugin* drdynvc = (drdynvcPlugin*)plugin; + + DEBUG_DVC("connecting"); + + drdynvc->channel_mgr = dvcman_new(drdynvc); + dvcman_load_plugin(drdynvc->channel_mgr, svc_plugin_get_data(plugin)); + dvcman_init(drdynvc->channel_mgr); +} + +static void drdynvc_process_event(rdpSvcPlugin* plugin, FRDP_EVENT* event) +{ + freerdp_event_free(event); +} + +static void drdynvc_process_terminate(rdpSvcPlugin* plugin) +{ + drdynvcPlugin* drdynvc = (drdynvcPlugin*)plugin; + + DEBUG_DVC("terminating"); + + if (drdynvc->channel_mgr != NULL) + dvcman_free(drdynvc->channel_mgr); + xfree(drdynvc); +} + +DEFINE_SVC_PLUGIN(drdynvc, "drdynvc", + CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP | + CHANNEL_OPTION_COMPRESS_RDP) diff --git a/channels/drdynvc/drdynvc_main.h b/channels/drdynvc/drdynvc_main.h new file mode 100644 index 000000000..8c24c5a33 --- /dev/null +++ b/channels/drdynvc/drdynvc_main.h @@ -0,0 +1,30 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Dynamic Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * + * 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 __DRDYNVC_MAIN_H +#define __DRDYNVC_MAIN_H + +#include + +typedef struct drdynvc_plugin drdynvcPlugin; + +int drdynvc_write_data(drdynvcPlugin* plugin, uint32 ChannelId, char* data, uint32 data_size); +int drdynvc_push_event(drdynvcPlugin* plugin, FRDP_EVENT* event); + +#endif diff --git a/channels/drdynvc/drdynvc_types.h b/channels/drdynvc/drdynvc_types.h new file mode 100644 index 000000000..f62fee7f5 --- /dev/null +++ b/channels/drdynvc/drdynvc_types.h @@ -0,0 +1,32 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Dynamic Virtual Channel + * + * Copyright 2010-2011 Vic Lee + * + * 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 __DRDYNVC_TYPES_H +#define __DRDYNVC_TYPES_H + +#include "config.h" +#include + +#ifdef WITH_DEBUG_DVC +#define DEBUG_DVC(fmt, ...) DEBUG_CLASS(DVC, fmt, ## __VA_ARGS__) +#else +#define DEBUG_DVC(fmt, ...) DEBUG_NULL(fmt, ## __VA_ARGS__) +#endif + +#endif diff --git a/channels/drdynvc/dvcman.c b/channels/drdynvc/dvcman.c new file mode 100644 index 000000000..7de22a482 --- /dev/null +++ b/channels/drdynvc/dvcman.c @@ -0,0 +1,430 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Dynamic Virtual Channel Manager + * + * Copyright 2010-2011 Vic Lee + * + * 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 "drdynvc_types.h" +#include "dvcman.h" + +#define MAX_PLUGINS 10 + +typedef struct _DVCMAN DVCMAN; +struct _DVCMAN +{ + IWTSVirtualChannelManager iface; + + drdynvcPlugin* drdynvc; + + const char* plugin_names[MAX_PLUGINS]; + IWTSPlugin* plugins[MAX_PLUGINS]; + int num_plugins; + + IWTSListener* listeners[MAX_PLUGINS]; + int num_listeners; + + struct dvcman_channel_list* channels; +}; + +typedef struct _DVCMAN_LISTENER DVCMAN_LISTENER; +struct _DVCMAN_LISTENER +{ + IWTSListener iface; + + DVCMAN* dvcman; + char* channel_name; + uint32 flags; + IWTSListenerCallback* listener_callback; +}; + +typedef struct _DVCMAN_ENTRY_POINTS DVCMAN_ENTRY_POINTS; +struct _DVCMAN_ENTRY_POINTS +{ + IDRDYNVC_ENTRY_POINTS iface; + + DVCMAN* dvcman; + FRDP_PLUGIN_DATA* plugin_data; +}; + +typedef struct _DVCMAN_CHANNEL DVCMAN_CHANNEL; +struct _DVCMAN_CHANNEL +{ + IWTSVirtualChannel iface; + + DVCMAN* dvcman; + DVCMAN_CHANNEL* next; + uint32 channel_id; + IWTSVirtualChannelCallback* channel_callback; + + STREAM* dvc_data; +}; + +struct dvcman_channel_list_item +{ + DVCMAN_CHANNEL channel; +}; + +DEFINE_LIST_TYPE(dvcman_channel_list, dvcman_channel_list_item) + +static int dvcman_get_configuration(IWTSListener* pListener, + void** ppPropertyBag) +{ + *ppPropertyBag = NULL; + return 1; +} + +static int dvcman_create_listener(IWTSVirtualChannelManager* pChannelMgr, + const char* pszChannelName, + uint32 ulFlags, + IWTSListenerCallback* pListenerCallback, + IWTSListener** ppListener) +{ + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + DVCMAN_LISTENER* listener; + + if (dvcman->num_listeners < MAX_PLUGINS) + { + DEBUG_DVC("%d.%s.", dvcman->num_listeners, pszChannelName); + listener = xnew(DVCMAN_LISTENER); + listener->iface.GetConfiguration = dvcman_get_configuration; + listener->dvcman = dvcman; + listener->channel_name = xstrdup(pszChannelName); + listener->flags = ulFlags; + listener->listener_callback = pListenerCallback; + + if (ppListener) + *ppListener = (IWTSListener*)listener; + dvcman->listeners[dvcman->num_listeners++] = (IWTSListener*)listener; + return 0; + } + else + { + DEBUG_WARN("Maximum DVC listener number reached."); + return 1; + } +} + +static int dvcman_push_event(IWTSVirtualChannelManager* pChannelMgr, + FRDP_EVENT* pEvent) +{ + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + int error; + + error = drdynvc_push_event(dvcman->drdynvc, pEvent); + if (error == 0) + { + DEBUG_DVC("event_type %d pushed.", pEvent->event_type); + } + else + { + DEBUG_WARN("event_type %d push failed.", pEvent->event_type); + } + return error; +} + +static int dvcman_register_plugin(IDRDYNVC_ENTRY_POINTS* pEntryPoints, + const char* name, IWTSPlugin* pPlugin) +{ + DVCMAN* dvcman = ((DVCMAN_ENTRY_POINTS*)pEntryPoints)->dvcman; + + if (dvcman->num_plugins < MAX_PLUGINS) + { + DEBUG_DVC("num_plugins %d", dvcman->num_plugins); + dvcman->plugin_names[dvcman->num_plugins] = name; + dvcman->plugins[dvcman->num_plugins++] = pPlugin; + return 0; + } + else + { + DEBUG_WARN("Maximum DVC plugin number reached."); + return 1; + } +} + +IWTSPlugin* dvcman_get_plugin(IDRDYNVC_ENTRY_POINTS* pEntryPoints, + const char* name) +{ + DVCMAN* dvcman = ((DVCMAN_ENTRY_POINTS*)pEntryPoints)->dvcman; + int i; + + for (i = 0; i < dvcman->num_plugins; i++) + { + if (dvcman->plugin_names[i] == name || + strcmp(dvcman->plugin_names[i], name) == 0) + { + return dvcman->plugins[i]; + } + } + return NULL; +} + +FRDP_PLUGIN_DATA* dvcman_get_plugin_data(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + return ((DVCMAN_ENTRY_POINTS*)pEntryPoints)->plugin_data; +} + +IWTSVirtualChannelManager* dvcman_new(drdynvcPlugin* plugin) +{ + DVCMAN* dvcman; + + dvcman = xnew(DVCMAN); + dvcman->iface.CreateListener = dvcman_create_listener; + dvcman->iface.PushEvent = dvcman_push_event; + dvcman->drdynvc = plugin; + dvcman->channels = dvcman_channel_list_new(); + + return (IWTSVirtualChannelManager*)dvcman; +} + +int dvcman_load_plugin(IWTSVirtualChannelManager* pChannelMgr, FRDP_PLUGIN_DATA* data) +{ + DVCMAN_ENTRY_POINTS entryPoints; + PDVC_PLUGIN_ENTRY pDVCPluginEntry = NULL; + + if (data != NULL) + { + pDVCPluginEntry = freerdp_load_plugin((char*)data->data[0], "DVCPluginEntry"); + + if(pDVCPluginEntry != NULL) + { + entryPoints.iface.RegisterPlugin = dvcman_register_plugin; + entryPoints.iface.GetPlugin = dvcman_get_plugin; + entryPoints.iface.GetPluginData = dvcman_get_plugin_data; + entryPoints.dvcman = (DVCMAN*)pChannelMgr; + entryPoints.plugin_data = data; + pDVCPluginEntry((IDRDYNVC_ENTRY_POINTS*)&entryPoints); + } + } + + return 0; +} + +void dvcman_free(IWTSVirtualChannelManager* pChannelMgr) +{ + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + int i; + IWTSPlugin* pPlugin; + DVCMAN_LISTENER* listener; + + dvcman_channel_list_free(dvcman->channels); + for (i = 0; i < dvcman->num_listeners; i++) + { + listener = (DVCMAN_LISTENER*)dvcman->listeners[i]; + xfree(listener->channel_name); + xfree(listener); + } + for (i = 0; i < dvcman->num_plugins; i++) + { + pPlugin = dvcman->plugins[i]; + if (pPlugin->Terminated) + pPlugin->Terminated(pPlugin); + } + xfree(dvcman); +} + +int dvcman_init(IWTSVirtualChannelManager* pChannelMgr) +{ + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + int i; + IWTSPlugin* pPlugin; + + for (i = 0; i < dvcman->num_plugins; i++) + { + pPlugin = dvcman->plugins[i]; + if (pPlugin->Initialize) + pPlugin->Initialize(pPlugin, pChannelMgr); + } + return 0; +} + +static int dvcman_write_channel(IWTSVirtualChannel* pChannel, + uint32 cbSize, + char* pBuffer, + void* pReserved) +{ + DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*)pChannel; + + return drdynvc_write_data(channel->dvcman->drdynvc, channel->channel_id, pBuffer, cbSize); +} + +static void dvcman_channel_list_item_free(struct dvcman_channel_list_item* item) +{ + DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*)item; + + if (channel->channel_callback) + channel->channel_callback->OnClose(channel->channel_callback); + xfree(item); +} + +static int dvcman_close_channel_iface(IWTSVirtualChannel* pChannel) +{ + DVCMAN_CHANNEL* channel = (DVCMAN_CHANNEL*)pChannel; + DVCMAN* dvcman = channel->dvcman; + + DEBUG_DVC("id=%d", channel->channel_id); + + if (dvcman_channel_list_remove(dvcman->channels, (struct dvcman_channel_list_item*)channel) == NULL) + DEBUG_WARN("channel not found"); + dvcman_channel_list_item_free((struct dvcman_channel_list_item*)channel); + + return 1; +} + +int dvcman_create_channel(IWTSVirtualChannelManager* pChannelMgr, uint32 ChannelId, const char* ChannelName) +{ + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + int i; + DVCMAN_LISTENER* listener; + DVCMAN_CHANNEL* channel; + struct dvcman_channel_list_item* item; + int bAccept; + IWTSVirtualChannelCallback* pCallback; + + for (i = 0; i < dvcman->num_listeners; i++) + { + listener = (DVCMAN_LISTENER*)dvcman->listeners[i]; + if (strcmp(listener->channel_name, ChannelName) == 0) + { + item = dvcman_channel_list_item_new(); + channel = (DVCMAN_CHANNEL*)item; + channel->iface.Write = dvcman_write_channel; + channel->iface.Close = dvcman_close_channel_iface; + channel->dvcman = dvcman; + channel->channel_id = ChannelId; + + bAccept = 1; + pCallback = NULL; + if (listener->listener_callback->OnNewChannelConnection(listener->listener_callback, + (IWTSVirtualChannel*)channel, NULL, &bAccept, &pCallback) == 0 && bAccept == 1) + { + DEBUG_PRINT("DVC", "listener %s created new channel %d", + listener->channel_name, channel->channel_id); + channel->channel_callback = pCallback; + dvcman_channel_list_add(dvcman->channels, item); + return 0; + } + else + { + DEBUG_WARN("channel rejected by plugin"); + dvcman_channel_list_item_free(item); + return 1; + } + } + } + return 1; +} + +static DVCMAN_CHANNEL* dvcman_find_channel_by_id(IWTSVirtualChannelManager* pChannelMgr, uint32 ChannelId) +{ + DVCMAN* dvcman = (DVCMAN*)pChannelMgr; + struct dvcman_channel_list_item* curr; + + for (curr = dvcman->channels->head; curr; curr = dvcman_channel_list_item_next(curr)) + { + if (((DVCMAN_CHANNEL*)curr)->channel_id == ChannelId) + { + return (DVCMAN_CHANNEL*)curr; + } + } + return NULL; +} + +int dvcman_close_channel(IWTSVirtualChannelManager* pChannelMgr, uint32 ChannelId) +{ + DVCMAN_CHANNEL* channel; + IWTSVirtualChannel* ichannel; + + channel = dvcman_find_channel_by_id(pChannelMgr, ChannelId); + if (channel == NULL) + { + DEBUG_WARN("ChannelId %d not found!", ChannelId); + return 1; + } + if (channel->dvc_data) + { + stream_free(channel->dvc_data); + channel->dvc_data = NULL; + } + DEBUG_PRINT("DVC", "dvcman_close_channel: channel %d closed", ChannelId); + ichannel = (IWTSVirtualChannel*)channel; + ichannel->Close(ichannel); + return 0; +} + +int dvcman_receive_channel_data_first(IWTSVirtualChannelManager* pChannelMgr, uint32 ChannelId, uint32 length) +{ + DVCMAN_CHANNEL* channel; + + channel = dvcman_find_channel_by_id(pChannelMgr, ChannelId); + if (channel == NULL) + { + DEBUG_WARN("ChannelId %d not found!", ChannelId); + return 1; + } + if (channel->dvc_data) + stream_free(channel->dvc_data); + channel->dvc_data = stream_new(length); + + return 0; +} + +int dvcman_receive_channel_data(IWTSVirtualChannelManager* pChannelMgr, uint32 ChannelId, uint8* data, uint32 data_size) +{ + DVCMAN_CHANNEL* channel; + int error = 0; + + channel = dvcman_find_channel_by_id(pChannelMgr, ChannelId); + if (channel == NULL) + { + DEBUG_WARN("ChannelId %d not found!", ChannelId); + return 1; + } + + if (channel->dvc_data) + { + /* Fragmented data */ + if (stream_get_length(channel->dvc_data) + data_size > stream_get_size(channel->dvc_data)) + { + DEBUG_WARN("data exceeding declared length!"); + stream_free(channel->dvc_data); + channel->dvc_data = NULL; + return 1; + } + stream_write(channel->dvc_data, data, data_size); + if (stream_get_length(channel->dvc_data) >= stream_get_size(channel->dvc_data)) + { + error = channel->channel_callback->OnDataReceived(channel->channel_callback, + stream_get_size(channel->dvc_data), stream_get_data(channel->dvc_data)); + stream_free(channel->dvc_data); + channel->dvc_data = NULL; + } + } + else + { + error = channel->channel_callback->OnDataReceived(channel->channel_callback, + data_size, data); + } + + return error; +} diff --git a/channels/drdynvc/dvcman.h b/channels/drdynvc/dvcman.h new file mode 100644 index 000000000..8e8997abd --- /dev/null +++ b/channels/drdynvc/dvcman.h @@ -0,0 +1,35 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Dynamic Virtual Channel Manager + * + * Copyright 2010-2011 Vic Lee + * + * 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 __DVCMAN_H +#define __DVCMAN_H + +#include +#include "drdynvc_main.h" + +IWTSVirtualChannelManager* dvcman_new(drdynvcPlugin* plugin); +int dvcman_load_plugin(IWTSVirtualChannelManager* pChannelMgr, FRDP_PLUGIN_DATA* data); +void dvcman_free(IWTSVirtualChannelManager* pChannelMgr); +int dvcman_init(IWTSVirtualChannelManager* pChannelMgr); +int dvcman_create_channel(IWTSVirtualChannelManager* pChannelMgr, uint32 ChannelId, const char* ChannelName); +int dvcman_close_channel(IWTSVirtualChannelManager* pChannelMgr, uint32 ChannelId); +int dvcman_receive_channel_data_first(IWTSVirtualChannelManager* pChannelMgr, uint32 ChannelId, uint32 length); +int dvcman_receive_channel_data(IWTSVirtualChannelManager* pChannelMgr, uint32 ChannelId, uint8* data, uint32 data_size); + +#endif diff --git a/cmake/ConfigOptions.cmake b/cmake/ConfigOptions.cmake index 9c681f2d3..44b0624ec 100644 --- a/cmake/ConfigOptions.cmake +++ b/cmake/ConfigOptions.cmake @@ -1,3 +1,4 @@ option(WITH_DEBUG_TRANSPORT "Print transport debug message." OFF) option(WITH_DEBUG_CHANMAN "Print channel manager debug message." OFF) option(WITH_DEBUG_SVC "Print static virtual channel debug message." OFF) +option(WITH_DEBUG_DVC "Print dynamic virtual channel debug message." OFF) diff --git a/config.h.in b/config.h.in index 7baeabd19..a91b3f59d 100644 --- a/config.h.in +++ b/config.h.in @@ -15,5 +15,6 @@ #cmakedefine WITH_DEBUG_TRANSPORT #cmakedefine WITH_DEBUG_CHANMAN #cmakedefine WITH_DEBUG_SVC +#cmakedefine WITH_DEBUG_DVC #endif diff --git a/cunit/CMakeLists.txt b/cunit/CMakeLists.txt index 0cb11d727..c8416834c 100644 --- a/cunit/CMakeLists.txt +++ b/cunit/CMakeLists.txt @@ -51,6 +51,8 @@ add_executable(test_freerdp test_chanman.h test_cliprdr.c test_cliprdr.h + test_drdynvc.c + test_drdynvc.h test_freerdp.c test_freerdp.h) diff --git a/cunit/test_drdynvc.c b/cunit/test_drdynvc.c new file mode 100644 index 000000000..9eec39ac3 --- /dev/null +++ b/cunit/test_drdynvc.c @@ -0,0 +1,97 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * Dynamic Virtual Channel Unit Tests + * + * Copyright 2011 Vic Lee + * + * 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 "test_drdynvc.h" + +int init_drdynvc_suite(void) +{ + freerdp_chanman_global_init(); + return 0; +} + +int clean_drdynvc_suite(void) +{ + freerdp_chanman_global_uninit(); + return 0; +} + +int add_drdynvc_suite(void) +{ + add_test_suite(drdynvc); + + add_test_function(drdynvc); + + return 0; +} + +static const uint8 test_capability_request_data[] = +{ + "\x58\x00\x02\x00\x33\x33\x11\x11\x3D\x0A\xA7\x04" +}; + +static int data_received = 0; + +static int test_rdp_channel_data(rdpInst* inst, int chan_id, char* data, int data_size) +{ + printf("chan_id %d data_size %d\n", chan_id, data_size); + freerdp_hexdump(data, data_size); + data_received = 1; +} + +void test_drdynvc(void) +{ + rdpChanMan* chan_man; + rdpSettings settings = { 0 }; + rdpInst inst = { 0 }; + int i; + + settings.hostname = "testhost"; + inst.settings = &settings; + inst.rdp_channel_data = test_rdp_channel_data; + + chan_man = freerdp_chanman_new(); + + freerdp_chanman_load_plugin(chan_man, &settings, "../channels/drdynvc/drdynvc.so", NULL); + freerdp_chanman_pre_connect(chan_man, &inst); + freerdp_chanman_post_connect(chan_man, &inst); + + /* server sends capability request PDU */ + freerdp_chanman_data(&inst, 0, (char*)test_capability_request_data, sizeof(test_capability_request_data) - 1, + CHANNEL_FLAG_FIRST | CHANNEL_FLAG_LAST, sizeof(test_capability_request_data) - 1); + + /* drdynvc sends capability response PDU to server */ + data_received = 0; + while (!data_received) + { + freerdp_chanman_check_fds(chan_man, &inst); + } + + freerdp_chanman_close(chan_man, &inst); + freerdp_chanman_free(chan_man); +} diff --git a/cunit/test_drdynvc.h b/cunit/test_drdynvc.h new file mode 100644 index 000000000..67e075461 --- /dev/null +++ b/cunit/test_drdynvc.h @@ -0,0 +1,26 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * Dynamic Virtual Channel Unit Tests + * + * Copyright 2011 Vic Lee + * + * 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 "test_freerdp.h" + +int init_drdynvc_suite(void); +int clean_drdynvc_suite(void); +int add_drdynvc_suite(void); + +void test_drdynvc(void); diff --git a/cunit/test_freerdp.c b/cunit/test_freerdp.c index f6141f05c..e579a51a9 100644 --- a/cunit/test_freerdp.c +++ b/cunit/test_freerdp.c @@ -33,6 +33,7 @@ #include "test_transport.h" #include "test_chanman.h" #include "test_cliprdr.h" +#include "test_drdynvc.h" #include "test_freerdp.h" void dump_data(unsigned char * p, int len, int width, char* name) @@ -127,6 +128,7 @@ int main(int argc, char* argv[]) add_transport_suite(); add_chanman_suite(); add_cliprdr_suite(); + add_drdynvc_suite(); } else { @@ -172,6 +174,10 @@ int main(int argc, char* argv[]) { add_cliprdr_suite(); } + else if (strcmp("drdynvc", argv[*pindex]) == 0) + { + add_drdynvc_suite(); + } else if (strcmp("per", argv[*pindex]) == 0) { add_per_suite(); diff --git a/cunit/test_list.c b/cunit/test_list.c index 13942917a..c0b375c84 100644 --- a/cunit/test_list.c +++ b/cunit/test_list.c @@ -62,6 +62,8 @@ void test_list(void) { struct my_list* list; struct my_list_item* item; + struct my_list_item* item1; + struct my_list_item* item2; int i; list = my_list_new(); @@ -81,5 +83,16 @@ void test_list(void) /*printf("%d %d\n", item->a, item->b);*/ } + item1 = my_list_item_new(); + my_list_add(list, item1); + item2 = my_list_item_new(); + my_list_add(list, item2); + + CU_ASSERT(my_list_remove(list, item1) == item1); + my_list_item_free(item1); + CU_ASSERT(my_list_remove(list, item2) == item2); + CU_ASSERT(my_list_remove(list, item2) == NULL); + my_list_item_free(item2); + my_list_free(list); } diff --git a/include/freerdp/constants.h b/include/freerdp/constants.h index 8fa5f5592..05abbf565 100644 --- a/include/freerdp/constants.h +++ b/include/freerdp/constants.h @@ -99,4 +99,9 @@ enum FRDP_CB_FORMAT CB_FORMAT_GIF = 0xD013 }; +/** + * Virtual Channel Constants + */ +#define CHANNEL_CHUNK_LENGTH 1600 + #endif diff --git a/include/freerdp/dvc.h b/include/freerdp/dvc.h new file mode 100644 index 000000000..fcb5373b8 --- /dev/null +++ b/include/freerdp/dvc.h @@ -0,0 +1,149 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Dynamic Virtual Channel Interface + * + * Copyright 2010-2011 Vic Lee + * + * 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. + */ + +/** + * DVC Plugin API: See the original MS DVC Client API: + * http://msdn.microsoft.com/en-us/library/bb540880%28v=VS.85%29.aspx + * + * The FreeRDP DVC Plugin API is a simulation of the MS DVC Client API in C. + * The main difference is that every interface method must take an instance + * pointer as the first parameter. + */ + +/** + * Implemented by DRDYNVC: + * o IWTSVirtualChannelManager + * o IWTSListener + * o IWTSVirtualChannel + * + * Implemented by DVC plugin: + * o IWTSPlugin + * o IWTSListenerCallback + * o IWTSVirtualChannelCallback + * + * A basic DVC plugin implementation: + * 1. DVCPluginEntry: + * The plugin entry point, which creates and initializes a new IWTSPlugin + * instance + * 2. IWTSPlugin.Initialize: + * Call IWTSVirtualChannelManager.CreateListener with a newly created + * IWTSListenerCallback instance + * 3. IWTSListenerCallback.OnNewChannelConnection: + * Create IWTSVirtualChannelCallback instance if the new channel is accepted + */ + +#ifndef __FREERDP_DVC_H +#define __FREERDP_DVC_H + +#include + +typedef struct _IWTSVirtualChannelManager IWTSVirtualChannelManager; +typedef struct _IWTSListener IWTSListener; +typedef struct _IWTSVirtualChannel IWTSVirtualChannel; + +typedef struct _IWTSPlugin IWTSPlugin; +typedef struct _IWTSListenerCallback IWTSListenerCallback; +typedef struct _IWTSVirtualChannelCallback IWTSVirtualChannelCallback; + +struct _IWTSListener +{ + /* Retrieves the listener-specific configuration. */ + int (*GetConfiguration) (IWTSListener* pListener, + void** ppPropertyBag); +}; + +struct _IWTSVirtualChannel +{ + /* Starts a write request on the channel. */ + int (*Write) (IWTSVirtualChannel* pChannel, + uint32 cbSize, + char* pBuffer, + void* pReserved); + /* Closes the channel. */ + int (*Close) (IWTSVirtualChannel* pChannel); +}; + +struct _IWTSVirtualChannelManager +{ + /* Returns an instance of a listener object that listens on a specific + endpoint, or creates a static channel. */ + int (*CreateListener) (IWTSVirtualChannelManager* pChannelMgr, + const char* pszChannelName, + uint32 ulFlags, + IWTSListenerCallback* pListenerCallback, + IWTSListener** ppListener); + /* Push a virtual channel event. + This is a FreeRDP extension to standard MS API. */ + int (*PushEvent) (IWTSVirtualChannelManager* pChannelMgr, + FRDP_EVENT* pEvent); +}; + +struct _IWTSPlugin +{ + /* Used for the first call that is made from the client to the plug-in. */ + int (*Initialize) (IWTSPlugin* pPlugin, + IWTSVirtualChannelManager* pChannelMgr); + /* Notifies the plug-in that the Remote Desktop Connection (RDC) client + has successfully connected to the Remote Desktop Session Host (RD + Session Host) server. */ + int (*Connected) (IWTSPlugin* pPlugin); + /* Notifies the plug-in that the Remote Desktop Connection (RDC) client + has disconnected from the RD Session Host server. */ + int (*Disconnected) (IWTSPlugin* pPlugin, + uint32 dwDisconnectCode); + /* Notifies the plug-in that the Remote Desktop Connection (RDC) client + has terminated. */ + int (*Terminated) (IWTSPlugin* pPlugin); +}; + +struct _IWTSListenerCallback +{ + /* Accepts or denies a connection request for an incoming connection to + the associated listener. */ + int (*OnNewChannelConnection) (IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, + char* Data, + int* pbAccept, + IWTSVirtualChannelCallback** ppCallback); +}; + +struct _IWTSVirtualChannelCallback +{ + /* Notifies the user about data that is being received. */ + int (*OnDataReceived) (IWTSVirtualChannelCallback* pChannelCallback, + uint32 cbSize, + char* pBuffer); + /* Notifies the user that the channel has been closed. */ + int (*OnClose) (IWTSVirtualChannelCallback* pChannelCallback); +}; + +/* The DVC Plugin entry points */ +typedef struct _IDRDYNVC_ENTRY_POINTS IDRDYNVC_ENTRY_POINTS; +struct _IDRDYNVC_ENTRY_POINTS +{ + int (*RegisterPlugin) (IDRDYNVC_ENTRY_POINTS* pEntryPoints, + const char* name, IWTSPlugin* pPlugin); + IWTSPlugin* (*GetPlugin) (IDRDYNVC_ENTRY_POINTS* pEntryPoints, + const char* name); + FRDP_PLUGIN_DATA* (*GetPluginData) (IDRDYNVC_ENTRY_POINTS* pEntryPoints); +}; + +typedef int (*PDVC_PLUGIN_ENTRY) (IDRDYNVC_ENTRY_POINTS*); + +#endif diff --git a/include/freerdp/utils/list.h b/include/freerdp/utils/list.h index c08f4566d..cdc33666f 100644 --- a/include/freerdp/utils/list.h +++ b/include/freerdp/utils/list.h @@ -94,6 +94,32 @@ static struct _item_type* _list_type##_dequeue(struct _list_type* list) \ return item; \ } \ \ +static void _list_type##_add(struct _list_type* list, struct _item_type* item) \ +{ \ + _list_type##_enqueue(list, item); \ +} \ +\ +static struct _item_type* _list_type##_remove(struct _list_type* list, struct _item_type* item) \ +{ \ + struct _item_type* prev; \ + struct _item_type* curr; \ +\ + for (prev = NULL, curr = (struct _item_type*)list->head; curr; prev = curr, curr = ((struct _item_type##_full*)curr)->next) \ + { \ + if (curr == item) \ + { \ + if (prev) \ + ((struct _item_type##_full*)prev)->next = ((struct _item_type##_full*)curr)->next; \ + if (list->head == item) \ + list->head = ((struct _item_type##_full*)curr)->next; \ + if (list->tail == item) \ + list->tail = prev; \ + return item; \ + } \ + } \ + return NULL; \ +} \ +\ void _list_type##_free(struct _list_type* list) \ { \ struct _item_type* item; \ diff --git a/include/freerdp/utils/svc_plugin.h b/include/freerdp/utils/svc_plugin.h index 17023d2ea..4de2f892d 100644 --- a/include/freerdp/utils/svc_plugin.h +++ b/include/freerdp/utils/svc_plugin.h @@ -47,6 +47,8 @@ void svc_plugin_init(rdpSvcPlugin* plugin, CHANNEL_ENTRY_POINTS* pEntryPoints); int svc_plugin_send(rdpSvcPlugin* plugin, STREAM* data_out); int svc_plugin_send_event(rdpSvcPlugin* plugin, FRDP_EVENT* event); +#define svc_plugin_get_data(_p) (FRDP_PLUGIN_DATA*)(((rdpSvcPlugin*)_p)->channel_entry_points.pExtendedData) + #ifdef WITH_DEBUG_SVC #define DEBUG_SVC(fmt, ...) DEBUG_CLASS(SVC, fmt, ## __VA_ARGS__) #else