diff --git a/cunit/CMakeLists.txt b/cunit/CMakeLists.txt index 44d11ba0c..757416e02 100644 --- a/cunit/CMakeLists.txt +++ b/cunit/CMakeLists.txt @@ -21,6 +21,7 @@ include_directories(${CUNIT_INCLUDE_DIRS}) include_directories(.) include_directories(../include) +include_directories(../libfreerdp-core) include_directories(../libfreerdp-gdi) add_executable(test_freerdp @@ -30,11 +31,14 @@ add_executable(test_freerdp test_libgdi.h test_stream.c test_stream.h + test_transport.c + test_transport.h test_freerdp.c test_freerdp.h) target_link_libraries(test_freerdp ${CUNIT_LIBRARIES}) +target_link_libraries(test_freerdp freerdp-core) target_link_libraries(test_freerdp freerdp-gdi) target_link_libraries(test_freerdp freerdp-asn1) target_link_libraries(test_freerdp freerdp-utils) diff --git a/cunit/test_freerdp.c b/cunit/test_freerdp.c index ecc5aa81d..23ba54c3d 100644 --- a/cunit/test_freerdp.c +++ b/cunit/test_freerdp.c @@ -22,6 +22,7 @@ #include "test_color.h" #include "test_libgdi.h" #include "test_stream.h" +#include "test_transport.h" #include "test_freerdp.h" void dump_data(unsigned char * p, int len, int width, char* name) @@ -63,6 +64,7 @@ int main(int argc, char* argv[]) add_color_suite(); add_libgdi_suite(); add_stream_suite(); + add_transport_suite(); } else { @@ -80,6 +82,10 @@ int main(int argc, char* argv[]) { add_stream_suite(); } + else if (strcmp("transport", argv[*pindex]) == 0) + { + add_transport_suite(); + } *pindex = *pindex + 1; } diff --git a/cunit/test_transport.c b/cunit/test_transport.c new file mode 100644 index 000000000..1d3ec9ed4 --- /dev/null +++ b/cunit/test_transport.c @@ -0,0 +1,99 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * Transport 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 "tpkt.h" +#include "transport.h" +#include "test_transport.h" + +static const char test_server[] = "192.168.0.1"; +static const uint8 test_x224_req[] = +{ + "\x03\x00\x00\x2C\x27\xE0\x00\x00\x00\x00\x00\x43\x6F\x6F\x6B\x69" + "\x65\x3A\x20\x6D\x73\x74\x73\x68\x61\x73\x68\x3D\x65\x6C\x74\x6F" + "\x6e\x73\x0D\x0A\x01\x00\x08\x00\x00\x00\x00\x00" +}; + +int init_transport_suite(void) +{ + return 0; +} + +int clean_transport_suite(void) +{ + return 0; +} + +int add_transport_suite(void) +{ + add_test_suite(transport); + + add_test_function(transport); + + return 0; +} + +static int test_finished = 0; + +static int +packet_received(STREAM * stream, void * callback_data) +{ + uint16 len; + + len = tpkt_read_header(stream); + CU_ASSERT(len == 19); + freerdp_hexdump(stream->buffer, len); + test_finished = 1; +} + +void test_transport(void) +{ + rdpTransport * transport; + STREAM * stream; + int r; + + transport = transport_new(); + transport->recv_callback = packet_received; + transport->recv_callback_data = NULL; + + r = transport_connect(transport, test_server, 3389); + CU_ASSERT(r == 0); + + stream = stream_new(sizeof(test_x224_req)); + stream_write_buffer(stream, test_x224_req, sizeof(test_x224_req)); + r = transport_send(transport, stream); + CU_ASSERT(r == 0); + + while (!test_finished) + { + transport_check_fds(transport); + sleep(1); + } + + r = transport_disconnect(transport); + CU_ASSERT(r == 0); + + transport_free(transport); +} diff --git a/cunit/test_transport.h b/cunit/test_transport.h new file mode 100644 index 000000000..5c5488e0c --- /dev/null +++ b/cunit/test_transport.h @@ -0,0 +1,26 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * Transport 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_transport_suite(void); +int clean_transport_suite(void); +int add_transport_suite(void); + +void test_transport(void); diff --git a/include/freerdp/utils/stream.h b/include/freerdp/utils/stream.h index 167f9b027..3c8376661 100644 --- a/include/freerdp/utils/stream.h +++ b/include/freerdp/utils/stream.h @@ -45,6 +45,8 @@ stream_extend(STREAM * stream); #define stream_get_pos(_s) (_s->ptr - _s->buffer) #define stream_set_pos(_s,_m) _s->ptr = _s->buffer + (_m) #define stream_seek(_s,_offset) _s->ptr += (_offset) +#define stream_get_head(_s) _s->buffer +#define stream_get_tail(_s) _s->ptr #define stream_read_uint8(_s, _v) do { _v = *_s->ptr++; } while (0) #define stream_read_uint16(_s, _v) do { _v = \ @@ -87,6 +89,10 @@ stream_extend(STREAM * stream); *_s->ptr++ = ((_v) >> 40) & 0xFF; \ *_s->ptr++ = ((_v) >> 48) & 0xFF; \ *_s->ptr++ = ((_v) >> 56) & 0xFF; } while (0) +#define stream_write_buffer(_s, _b, _n) do { \ + memcpy(_s->ptr, (_b), (_n)); \ + _s->ptr += (_n); \ + } while (0) #define stream_peek_uint8(_s, _v) do { _v = *_s->ptr; } while (0) #define stream_peek_uint16(_s, _v) do { _v = \ @@ -110,5 +116,20 @@ stream_extend(STREAM * stream); (((uint64)(*(_s->ptr + 7))) << 56); \ } while (0) +#define stream_read_uint16_be(_s, _v) do { _v = \ + (((uint16)(*_s->ptr)) << 8) + \ + (uint16)(*(_s->ptr + 1)); \ + _s->ptr += 2; } while (0) + +#define stream_write_uint16_be(_s, _v) do { \ + *_s->ptr++ = ((_v) >> 8) & 0xFF; \ + *_s->ptr++ = (_v) & 0xFF; } while (0) + +#define stream_copy(_dst, _src, _n) do { \ + memcpy(_dst->ptr, _src->ptr, _n); \ + _dst->ptr += _n; \ + _src->ptr += _n; \ + } while (0) + #endif /* __STREAM_UTILS_H */ diff --git a/libfreerdp-core/CMakeLists.txt b/libfreerdp-core/CMakeLists.txt index 96492be1b..9f25c5df3 100644 --- a/libfreerdp-core/CMakeLists.txt +++ b/libfreerdp-core/CMakeLists.txt @@ -34,6 +34,8 @@ set(LIBFREERDP_CORE_SRCS tpdu.h tpkt.c tpkt.h + transport.c + transport.h ) add_library(freerdp-core SHARED ${LIBFREERDP_CORE_SRCS}) diff --git a/libfreerdp-core/tpkt.c b/libfreerdp-core/tpkt.c index ad6bf61fd..0bc5814ff 100644 --- a/libfreerdp-core/tpkt.c +++ b/libfreerdp-core/tpkt.c @@ -64,7 +64,7 @@ tpkt_read_header(STREAM* s) if (version == 3) { stream_seek(s, 2); - stream_read_uint16(s, length); + stream_read_uint16_be(s, length); } else { @@ -80,5 +80,5 @@ tpkt_write_header(STREAM* s, int length) { stream_write_uint8(s, 3); /* version */ stream_write_uint8(s, 8); /* reserved */ - stream_write_uint16(s, length); /* length */ + stream_write_uint16_be(s, length); /* length */ } diff --git a/libfreerdp-core/transport.c b/libfreerdp-core/transport.c new file mode 100644 index 000000000..62d9900ad --- /dev/null +++ b/libfreerdp-core/transport.c @@ -0,0 +1,285 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * Network Transport Layer + * + * 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 + +#include "tpkt.h" +#include "transport.h" + +#define BUFFER_SIZE 16384 + +rdpTransport * +transport_new(void) +{ + rdpTransport * transport; + + transport = (rdpTransport *) xmalloc(sizeof(rdpTransport)); + memset(transport, 0, sizeof(rdpTransport)); + + transport->sockfd = -1; + + /* a small 0.1ms delay when transport is blocking. */ + transport->ts.tv_sec = 0; + transport->ts.tv_nsec = 100000; + + /* receive buffer for non-blocking read. */ + transport->recv_buffer = stream_new(BUFFER_SIZE); + + return transport; +} + +void +transport_free(rdpTransport * transport) +{ + stream_free(transport->recv_buffer); + xfree(transport); +} + +static int +transport_connect_sockfd(rdpTransport * transport, const char * server, int port) +{ + struct addrinfo hints = { 0 }; + struct addrinfo * res, * ai; + int r; + char servname[10]; + int sockfd = -1; + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + snprintf(servname, sizeof(servname), "%d", port); + r = getaddrinfo(server, servname, &hints, &res); + if (r != 0) + { + printf("transport_connect: getaddrinfo (%s)\n", gai_strerror(r)); + return -1; + } + + for (ai = res; ai; ai = ai->ai_next) + { + sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sockfd < 0) + continue; + + r = connect(sockfd, ai->ai_addr, ai->ai_addrlen); + if (r == 0) + { + printf("connected to %s:%s\n", server, servname); + break; + } + + sockfd = -1; + } + freeaddrinfo(res); + + if (sockfd == -1) + { + printf("unable to connect to %s:%s\n", server, servname); + return -1; + } + + transport->sockfd = sockfd; + + return 0; +} + +static int +transport_configure_sockfd(rdpTransport * transport) +{ + int flags; + + flags = fcntl(transport->sockfd, F_GETFL); + if (flags == -1) + { + printf("transport_configure_sockfd: fcntl failed.\n"); + return -1; + } + fcntl(transport->sockfd, F_SETFL, flags | O_NONBLOCK); + + return 0; +} + +int +transport_connect(rdpTransport * transport, const char * server, int port) +{ + int r; + + r = transport_connect_sockfd(transport, server, port); + if (r != 0) + return r; + + r = transport_configure_sockfd(transport); + if (r != 0) + return r; + + return 0; +} + +int +transport_disconnect(rdpTransport * transport) +{ + if (transport->sockfd != -1) + { + close(transport->sockfd); + transport->sockfd = -1; + } + return 0; +} + +int +transport_start_tls(rdpTransport * transport) +{ + return 0; +} + +static int +transport_delay(rdpTransport * transport) +{ + nanosleep(&transport->ts, NULL); + return 0; +} + +static int +transport_send_tls(rdpTransport * transport, STREAM * stream) +{ + return 0; +} + +static int +transport_send_tcp(rdpTransport * transport, STREAM * stream) +{ + uint8 * head; + uint8 * tail; + int r; + + head = stream_get_head(stream); + tail = stream_get_tail(stream); + while (head < tail) + { + r = send(transport->sockfd, head, tail - head, MSG_NOSIGNAL); + if (r < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + if (transport_delay(transport) != 0) + return -1; + continue; + } + printf("transport_send_tcp: send (%d)\n", errno); + return -1; + } + head += r; + } + return 0; +} + +int +transport_send(rdpTransport * transport, STREAM * stream) +{ + if (transport->tls) + return transport_send_tls(transport, stream); + else + return transport_send_tcp(transport, stream); +} + +static int +transport_recv_tls(rdpTransport * transport) +{ + return 0; +} + +static int +transport_recv_tcp(rdpTransport * transport) +{ + int r; + + stream_check_capacity(transport->recv_buffer, BUFFER_SIZE); + + r = recv(transport->sockfd, transport->recv_buffer->ptr, BUFFER_SIZE, 0); + if (r == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return 0; + printf("transport_recv_tcp: recv failed (%d).\n", errno); + return -1; + } + stream_seek(transport->recv_buffer, r); + + return r; +} + +int +transport_check_fds(rdpTransport * transport) +{ + int r; + int pos; + uint16 len; + STREAM * received; + + if (transport->tls) + r = transport_recv_tls(transport); + else + r = transport_recv_tcp(transport); + + if (r <= 0) + return r; + + pos = stream_get_pos(transport->recv_buffer); + /* Ensure the TPKT header is available. */ + if (pos <= 4) + return 0; + + stream_set_pos(transport->recv_buffer, 0); + len = tpkt_read_header(transport->recv_buffer); + if (len == 0) + { + printf("transport_check_fds: protocol error, not a TPKT header.\n"); + return -1; + } + if (pos < len) + return 0; /* Packet is not yet completely received. */ + + /* A complete packet has been received. In case there are trailing data + * for the next packet, we copy it to the new receive buffer. + */ + received = transport->recv_buffer; + transport->recv_buffer = stream_new(BUFFER_SIZE); + if (pos > len) + { + stream_set_pos(received, len); + stream_check_capacity(transport->recv_buffer, pos - len); + stream_copy(transport->recv_buffer, received, pos - len); + } + + stream_set_pos(received, 0); + r = transport->recv_callback(received, transport->recv_callback_data); + stream_free(received); + + return r; +} diff --git a/libfreerdp-core/transport.h b/libfreerdp-core/transport.h new file mode 100644 index 000000000..316f6d912 --- /dev/null +++ b/libfreerdp-core/transport.h @@ -0,0 +1,54 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * Network Transport Layer + * + * 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. + */ + +#ifndef __TRANSPORT_H +#define __TRANSPORT_H + +#include + +typedef int (* PacketReceivedCallback) (STREAM * stream, void * callback_data); + +struct rdp_transport +{ + int sockfd; + struct crypto_tls * tls; + struct timespec ts; + STREAM * recv_buffer; + + PacketReceivedCallback recv_callback; + void * recv_callback_data; +}; +typedef struct rdp_transport rdpTransport; + +rdpTransport * +transport_new(void); +void +transport_free(rdpTransport * transport); +int +transport_connect(rdpTransport * transport, const char * server, int port); +int +transport_disconnect(rdpTransport * transport); +int +transport_start_tls(rdpTransport * transport); +int +transport_send(rdpTransport * transport, STREAM * stream); +int +transport_check_fds(rdpTransport * transport); + +#endif