From cb3b1f93130040a150e7fcc57cd4d5a75569685a Mon Sep 17 00:00:00 2001 From: "garykac@google.com" Date: Mon, 7 Jun 2010 19:58:23 +0000 Subject: Copy the (early prototype of) remoting in Chrome into the public tree. At the moment, this is a semi-functional demo. BUG=none TEST=build/run all unittests on linux Review URL: http://codereview.chromium.org/2690003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@49087 0039d316-1c4b-4281-b951-d872f2087c98 --- remoting/base/constants.cc | 11 ++ remoting/base/constants.h | 16 +++ remoting/base/mock_objects.h | 30 ++++ remoting/base/multiple_array_input_stream.cc | 96 +++++++++++++ remoting/base/multiple_array_input_stream.h | 49 +++++++ .../base/multiple_array_input_stream_unittest.cc | 94 +++++++++++++ remoting/base/protocol/chromotocol.gyp | 79 +++++++++++ remoting/base/protocol/chromotocol.proto | 153 +++++++++++++++++++++ remoting/base/protocol_decoder.cc | 149 ++++++++++++++++++++ remoting/base/protocol_decoder.h | 72 ++++++++++ remoting/base/protocol_decoder_unittest.cc | 113 +++++++++++++++ remoting/base/protocol_util.cc | 27 ++++ remoting/base/protocol_util.h | 24 ++++ 13 files changed, 913 insertions(+) create mode 100644 remoting/base/constants.cc create mode 100644 remoting/base/constants.h create mode 100644 remoting/base/mock_objects.h create mode 100644 remoting/base/multiple_array_input_stream.cc create mode 100644 remoting/base/multiple_array_input_stream.h create mode 100644 remoting/base/multiple_array_input_stream_unittest.cc create mode 100644 remoting/base/protocol/chromotocol.gyp create mode 100644 remoting/base/protocol/chromotocol.proto create mode 100644 remoting/base/protocol_decoder.cc create mode 100644 remoting/base/protocol_decoder.h create mode 100644 remoting/base/protocol_decoder_unittest.cc create mode 100644 remoting/base/protocol_util.cc create mode 100644 remoting/base/protocol_util.h (limited to 'remoting/base') diff --git a/remoting/base/constants.cc b/remoting/base/constants.cc new file mode 100644 index 0000000..7d42110 --- /dev/null +++ b/remoting/base/constants.cc @@ -0,0 +1,11 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/base/constants.h" + +namespace remoting { + +const std::string kChromotingBotJid("chromoting@bot.talk.google.com"); + +} // namespace remoting diff --git a/remoting/base/constants.h b/remoting/base/constants.h new file mode 100644 index 0000000..190559e --- /dev/null +++ b/remoting/base/constants.h @@ -0,0 +1,16 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_BASE_CONSTANTS_H +#define REMOTING_BASE_CONSTANTS_H + +#include + +namespace remoting { + +extern const std::string kChromotingBotJid; + +} // namespace remoting + +#endif // REMOTING_BASE_CONSTANTS_H diff --git a/remoting/base/mock_objects.h b/remoting/base/mock_objects.h new file mode 100644 index 0000000..a61830e --- /dev/null +++ b/remoting/base/mock_objects.h @@ -0,0 +1,30 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_BASE_MOCK_OBJECTS_H_ +#define REMOTING_BASE_MOCK_OBJECTS_H_ + +#include "remoting/base/protocol_decoder.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace remoting { + +class MockProtocolDecoder : public ProtocolDecoder { + public: + MockProtocolDecoder() {} + + MOCK_METHOD2(ParseClientMessages, + void(scoped_refptr data, + ClientMessageList* messages)); + MOCK_METHOD2(ParseHostMessages, + void(scoped_refptr data, + HostMessageList* messages)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockProtocolDecoder); +}; + +} // namespace remoting + +#endif // REMOTING_BASE_MOCK_OBJECTS_H_ diff --git a/remoting/base/multiple_array_input_stream.cc b/remoting/base/multiple_array_input_stream.cc new file mode 100644 index 0000000..e5b8f94 --- /dev/null +++ b/remoting/base/multiple_array_input_stream.cc @@ -0,0 +1,96 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/logging.h" +#include "remoting/base/multiple_array_input_stream.h" + +namespace remoting { + +MultipleArrayInputStream::MultipleArrayInputStream(int count) + : buffer_count_(count), + current_buffer_(0), + current_buffer_offset_(0), + position_(0), + last_returned_size_(0) { + DCHECK_GT(buffer_count_, 0); + buffers_.reset(new const uint8*[buffer_count_]); + buffer_sizes_.reset(new int[buffer_count_]); +} + +MultipleArrayInputStream::~MultipleArrayInputStream() { +} + +bool MultipleArrayInputStream::Next(const void** data, int* size) { + if (current_buffer_ < buffer_count_) { + // Also reply with that is remaining in the current buffer. + last_returned_size_ = + buffer_sizes_[current_buffer_] - current_buffer_offset_; + *data = buffers_[current_buffer_] + current_buffer_offset_; + *size = last_returned_size_; + + // After reading the current buffer then advance to the next buffer. + current_buffer_offset_ = 0; + ++current_buffer_; + position_ += last_returned_size_; + return true; + } + + // We've reached the end of the stream. So reset |last_returned_size_| + // to zero to prevent any backup request. + // This is the same as in ArrayInputStream. + // See google/protobuf/io/zero_copy_stream_impl_lite.cc. + last_returned_size_ = 0; + return false; +} + +void MultipleArrayInputStream::BackUp(int count) { + DCHECK_LE(count, last_returned_size_); + DCHECK_EQ(0, current_buffer_offset_); + DCHECK_GT(current_buffer_, 0); + + // Rewind one buffer. + --current_buffer_; + current_buffer_offset_ = buffer_sizes_[current_buffer_] - count; + position_ -= count; + DCHECK_GE(current_buffer_offset_, 0); + DCHECK_GE(position_, 0); +} + +bool MultipleArrayInputStream::Skip(int count) { + DCHECK_GE(count, 0); + last_returned_size_ = 0; + + while (count && current_buffer_ < buffer_count_) { + int read = std::min( + count, + buffer_sizes_[current_buffer_] - current_buffer_offset_); + + // Advance the current buffer offset and position. + current_buffer_offset_ += read; + position_ += read; + count -= read; + + // If the current buffer is fully read, then advance to the next buffer. + if (current_buffer_offset_ == buffer_sizes_[current_buffer_]) { + ++current_buffer_; + current_buffer_offset_ = 0; + } + } + return count == 0; +} + +int64 MultipleArrayInputStream::ByteCount() const { + return position_; +} + +void MultipleArrayInputStream::SetBuffer( + int n, const uint8* buffer, int size) { + CHECK(n < buffer_count_); + buffers_[n] = buffer; + buffer_sizes_[n] = size; +} + +} // namespace remoting diff --git a/remoting/base/multiple_array_input_stream.h b/remoting/base/multiple_array_input_stream.h new file mode 100644 index 0000000..a1b5f01 --- /dev/null +++ b/remoting/base/multiple_array_input_stream.h @@ -0,0 +1,49 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_BASE_MULTIPLE_ARRAY_INPUT_STREAM_H_ +#define REMOTING_BASE_MULTIPLE_ARRAY_INPUT_STREAM_H_ + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "google/protobuf/io/zero_copy_stream.h" + +namespace remoting { + +// A MultipleArrayInputStream provides a ZeroCopyInputStream with multiple +// backing arrays. +class MultipleArrayInputStream : + public google::protobuf::io::ZeroCopyInputStream { + public: + // Construct a MultipleArrayInputStream with |count| backing arrays. + // TODO(hclam): Consider adding block size to see if it has a performance + // gain. + explicit MultipleArrayInputStream(int count); + virtual ~MultipleArrayInputStream(); + + virtual bool Next(const void** data, int* size); + virtual void BackUp(int count); + virtual bool Skip(int count); + virtual int64 ByteCount() const; + + // Set the n-th buffer to be |buffer|. + void SetBuffer(int n, const uint8* buffer, int size); + + private: + scoped_array buffers_; + scoped_array buffer_sizes_; + + const int buffer_count_; + + int current_buffer_; + int current_buffer_offset_; + int position_; + int last_returned_size_; + + DISALLOW_COPY_AND_ASSIGN(MultipleArrayInputStream); +}; + +} // namespace remoting + +#endif // REMOTING_BASE_MULTIPLE_ARRAY_INPUT_STREAM_H_ diff --git a/remoting/base/multiple_array_input_stream_unittest.cc b/remoting/base/multiple_array_input_stream_unittest.cc new file mode 100644 index 0000000..810596c --- /dev/null +++ b/remoting/base/multiple_array_input_stream_unittest.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "remoting/base/multiple_array_input_stream.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace remoting { + +static int ReadFromInput(MultipleArrayInputStream* input, + void* data, int size) { + uint8* out = reinterpret_cast(data); + int out_size = size; + + const void* in; + int in_size = 0; + + while (true) { + if (!input->Next(&in, &in_size)) { + return size - out_size; + } + EXPECT_GT(in_size, -1); + + if (out_size <= in_size) { + memcpy(out, in, out_size); + if (in_size > out_size) { + input->BackUp(in_size - out_size); + } + return size; // Copied all of it. + } + + memcpy(out, in, in_size); + out += in_size; + out_size -= in_size; + } +} + +static void ReadString(MultipleArrayInputStream* input, + const std::string& str) { + scoped_array buffer(new char[str.size() + 1]); + buffer[str.size()] = '\0'; + EXPECT_EQ(ReadFromInput(input, buffer.get(), str.size()), str.size()); + EXPECT_STREQ(str.c_str(), buffer.get()); +} + +// Construct and prepare data in the |output_stream|. +static void PrepareData(scoped_ptr* stream) { + static const std::string kTestData = + "Hello world!" + "This is testing" + "MultipleArrayInputStream" + "for Chromoting"; + + // Determine how many segments to split kTestData. We split the data in + // 1 character, 2 characters, 1 character, 2 characters ... + int segments = (kTestData.length() / 3) * 2; + int remaining_chars = kTestData.length() % 3; + if (remaining_chars) { + if (remaining_chars == 1) + ++segments; + else + segments += 2; + } + + MultipleArrayInputStream* mstream = new MultipleArrayInputStream(segments); + const char* data = kTestData.c_str(); + for (int i = 0; i < segments; ++i) { + int size = i % 2 == 0 ? 1 : 2; + mstream->SetBuffer(i, reinterpret_cast(data), size); + data += size; + } + stream->reset(mstream); +} + +TEST(MultipleArrayInputStreamTest, BasicOperations) { + scoped_ptr stream; + PrepareData(&stream); + + ReadString(stream.get(), "Hello world!"); + ReadString(stream.get(), "This "); + ReadString(stream.get(), "is test"); + EXPECT_TRUE(stream->Skip(3)); + ReadString(stream.get(), "MultipleArrayInput"); + EXPECT_TRUE(stream->Skip(6)); + ReadString(stream.get(), "f"); + ReadString(stream.get(), "o"); + ReadString(stream.get(), "r"); + ReadString(stream.get(), " "); + ReadString(stream.get(), "Chromoting"); +} + +} // namespace remoting diff --git a/remoting/base/protocol/chromotocol.gyp b/remoting/base/protocol/chromotocol.gyp new file mode 100644 index 0000000..bdcb845 --- /dev/null +++ b/remoting/base/protocol/chromotocol.gyp @@ -0,0 +1,79 @@ +# Copyright (c) 2010 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'variables': { + 'chromium_code': 1, + 'out_dir': '<(SHARED_INTERMEDIATE_DIR)/protoc_out/remoting/base/protocol', + }, + 'targets': [ + { + # Protobuf compiler / generate rule for chromoting.proto. + 'target_name': 'chromotocol_proto', + 'type': 'none', + 'sources': [ + 'chromotocol.proto', + ], + 'rules': [ + { + 'rule_name': 'genproto', + 'extension': 'proto', + 'inputs': [ + '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)protoc<(EXECUTABLE_SUFFIX)', + ], + 'outputs': [ + '<(PRODUCT_DIR)/pyproto/chromotocol_pb/<(RULE_INPUT_ROOT)_pb2.py', + '<(out_dir)/<(RULE_INPUT_ROOT).pb.cc', + '<(out_dir)/<(RULE_INPUT_ROOT).pb.h', + ], + 'action': [ + '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)protoc<(EXECUTABLE_SUFFIX)', + '--proto_path=.', + './<(RULE_INPUT_ROOT)<(RULE_INPUT_EXT)', + '--cpp_out=<(out_dir)', + '--python_out=<(PRODUCT_DIR)/pyproto/chromotocol_pb', + ], + 'message': 'Generating C++ and Python code from <(RULE_INPUT_PATH)', + }, + ], + 'dependencies': [ + '../../../third_party/protobuf2/protobuf.gyp:protoc#host', + ], + # This target exports a hard dependency because it generates header + # files. + 'hard_dependency': 1, + }, + + { + 'target_name': 'chromotocol_proto_lib', + 'type': '<(library)', + 'export_dependent_settings': [ + '../../../third_party/protobuf2/protobuf.gyp:protobuf_lite', + 'chromotocol_proto', + ], + 'dependencies': [ + '../../../third_party/protobuf2/protobuf.gyp:protobuf_lite', + 'chromotocol_proto', + ], + # This target exports a hard dependency because depedents require + # chromotocol_proto to compile. + 'hard_dependency': 1, + 'direct_dependent_settings': { + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/protoc_out', + ], + }, + 'sources': [ + '<(out_dir)/chromotocol.pb.cc', + '<(out_dir)/chromotocol.pb.h', + ], + }, + ], +} + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: diff --git a/remoting/base/protocol/chromotocol.proto b/remoting/base/protocol/chromotocol.proto new file mode 100644 index 0000000..a7907f9 --- /dev/null +++ b/remoting/base/protocol/chromotocol.proto @@ -0,0 +1,153 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Protocol for communication between chromoting client and host. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package chromotocol_pb; + +// A message that gets sent to the client after the client is connected to the +// host. It contains information that the client needs to know about the host. +// NEXT ID: 3 +message InitClientMessage { + required int32 width = 1; + required int32 height = 2; +} + +// A message to denote the beginning of an update stream. It will be followed +// by 0 or more PartialUpdateStream messages and then a EndUpdateStream message. +// NEXT ID: 1 +message BeginUpdateStreamMessage { +} + +// A message to denote the end of an update stream. +// NEXT ID: 1 +message EndUpdateStreamMessage { +} + +// Identifies how the image was encoded. +enum UpdateStreamEncoding { + EncodingNone = 0; + EncodingZlib = 1; +} + +// Identifies the pixel format. +// Note that this list should match exactly the same as +// media::VideoFrame::Format in media/base/video_frame.h. +enum PixelFormat { + PixelFormatInvalid = 0; + PixelFormatRgb555 = 1; + PixelFormatRgb565 = 2; + PixelFormatRgb24 = 3; + PixelFormatRgb32 = 4; + PixelFormatRgba = 5; + PixelFormatYv12 = 6; + PixelFormatYv16 = 7; + PixelFormatEmpty = 8; + PixelFormatAscii = 9; +} + +// A message with info about the update stream. +// NEXT ID: 6 +message UpdateStreamPacketHeader { + // X,Y coordinates (in screen pixels) for origin of this update. + required int32 x = 1; + required int32 y = 2; + + // Width, height (in screen pixels) for this update. + required int32 width = 3; + required int32 height = 4; + + // The encoding used for this image update. + optional UpdateStreamEncoding encoding = 5 [default=EncodingNone]; + + // The pixel format of this image. + optional PixelFormat pixel_format = 6 [default=PixelFormatRgb24]; +} + +// A message to denote a partial update stream. +// NEXT ID: 3 +message UpdateStreamPacketMessage { + // TODO(garykac): Make this required and fix unit tests. + optional UpdateStreamPacketHeader header = 2; + optional bytes data = 1; +} + +// Defines the message that is sent from the host to the client. +// Only one of these messages should be present. +// NEXT ID: 5 +message HostMessage { + optional InitClientMessage init_client= 1; + optional BeginUpdateStreamMessage begin_update_stream = 2; + optional EndUpdateStreamMessage end_update_stream = 3; + optional UpdateStreamPacketMessage update_stream_packet = 4; +} + +// Defines a keyboard event. +// NEXT ID: 3 +message KeyEvent { + // The POSIX key code. + required int32 key = 1; + required bool pressed = 2; +} + +// Sets the position of the mouse cursor. +// The coordinate value is between [0 .. 1] which is relative to the +// dimension of the screen area. +// NEXT ID: 3 +message MouseSetPositionEvent { + required float x = 1; + required float y = 2; +} + +// Adjust the position of the mouse cursor by an offset. +// NEXT ID: 3 +message MouseMoveEvent { + required int32 offset_x = 1; + required int32 offset_y = 2; +} + +// Motion of the mouse wheel. +// NEXT ID: 3 +message MouseWheelEvent { + required int32 offset_x = 1; + required int32 offset_y = 2; +} + +// Mouse button is pressed down. +// NEXT ID: 2 +message MouseDownEvent { + enum Button { + LEFT = 0; + MIDDLE = 1; + RIGHT = 2; + } + required Button button = 1; +} + +// Mouse button is released. +// NEXT ID: 2 +message MouseUpEvent { + enum Button { + LEFT = 0; + MIDDLE = 1; + RIGHT = 2; + } + required Button button = 1; +} + +// Defines the message that is sent from the client to the host. +// Only one of these messages should be present. +// NEXT ID: 7 +message ClientMessage { + optional KeyEvent key_event = 1; + optional MouseSetPositionEvent mouse_set_position_event = 2; + optional MouseMoveEvent mouse_move_event = 3; + optional MouseWheelEvent mouse_wheel_event = 4; + optional MouseDownEvent mouse_down_event = 5; + optional MouseUpEvent mouse_up_event = 6; +} diff --git a/remoting/base/protocol_decoder.cc b/remoting/base/protocol_decoder.cc new file mode 100644 index 0000000..1334b20 --- /dev/null +++ b/remoting/base/protocol_decoder.cc @@ -0,0 +1,149 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/base/protocol_decoder.h" + +#include "remoting/base/multiple_array_input_stream.h" +#include "talk/base/byteorder.h" + +namespace remoting { + +ProtocolDecoder::ProtocolDecoder() + : last_read_position_(0), + available_bytes_(0), + next_payload_(0), + next_payload_known_(false) { +} + +void ProtocolDecoder::ParseClientMessages(scoped_refptr data, + ClientMessageList* messages) { + ParseMessages(data, messages); +} + +void ProtocolDecoder::ParseHostMessages(scoped_refptr data, + HostMessageList* messages) { + ParseMessages(data, messages); +} + +template +void ProtocolDecoder::ParseMessages(scoped_refptr data, + std::vector* messages) { + // If this is the first data in the processing queue, then set the + // last read position to 0. + if (data_list_.empty()) + last_read_position_ = 0; + + // First enqueue the data received. + data_list_.push_back(data); + available_bytes_ += data->GetDataSize(); + + // Then try to parse one message until we can't parse anymore. + T* message; + while (ParseOneMessage(&message)) { + messages->push_back(message); + } +} + +template +bool ProtocolDecoder::ParseOneMessage(T** message) { + // Determine the payload size. If we already know it, then skip this + // part. + // We have the value set to -1 for checking later. + int next_payload = -1; + if (!next_payload_known_ && GetPayloadSize(&next_payload)) { + DCHECK_NE(-1, next_payload); + next_payload_ = next_payload; + next_payload_known_ = true; + } + + // If the next payload size is still not known or we don't have enough + // data for parsing then exit. + if (!next_payload_known_ || available_bytes_ < next_payload_) + return false; + next_payload_known_ = false; + + // Extract data from |data_list_| used to form a full protocol buffer. + DataList buffers; + std::deque buffer_pointers; + std::deque buffer_sizes; + while (next_payload_ > 0 && !data_list_.empty()) { + scoped_refptr buffer = data_list_.front(); + int read_bytes = std::min( + static_cast(buffer->GetDataSize()) - last_read_position_, + next_payload_); + + buffers.push_back(buffer); + buffer_pointers.push_back(buffer->GetData() + last_read_position_); + buffer_sizes.push_back(read_bytes); + + // Adjust counters. + last_read_position_ += read_bytes; + next_payload_ -= read_bytes; + available_bytes_ -= read_bytes; + + // If the front buffer is fully read, remove it from the queue. + if (buffer->GetDataSize() == last_read_position_) { + data_list_.pop_front(); + last_read_position_ = 0; + } + } + DCHECK_EQ(0, next_payload_); + DCHECK_EQ(buffers.size(), buffer_pointers.size()); + DCHECK_EQ(buffers.size(), buffer_sizes.size()); + + // Create a MultipleArrayInputStream for parsing. + MultipleArrayInputStream stream(buffers.size()); + for (size_t i = 0; i < buffers.size(); ++i) { + stream.SetBuffer(i, buffer_pointers[i], buffer_sizes[i]); + } + + // And finally it is parsing. + *message = new T(); + bool ret = (*message)->ParseFromZeroCopyStream(&stream); + if (!ret) + delete *message; + return ret; +} + +bool ProtocolDecoder::GetPayloadSize(int* size) { + // The header has a size of 4 bytes. + const int kHeaderSize = sizeof(int32); + + if (available_bytes_ < kHeaderSize) + return false; + + std::string header; + while (header.length() < kHeaderSize && !data_list_.empty()) { + scoped_refptr buffer = data_list_.front(); + + // Find out how many bytes we need and how many bytes are available in this + // buffer. + int needed_bytes = kHeaderSize - header.length(); + int available_bytes = buffer->GetDataSize() - last_read_position_; + + // Then append the required bytes into the header and advance the last + // read position. + int read_bytes = std::min(needed_bytes, available_bytes); + header.append( + reinterpret_cast(buffer->GetData()) + last_read_position_, + read_bytes); + last_read_position_ += read_bytes; + available_bytes_ -= read_bytes; + + // If the buffer is depleted then remove it from the queue. + if (last_read_position_ == buffer->GetDataSize()) { + last_read_position_ = 0; + data_list_.pop_front(); + } + } + + if (header.length() == kHeaderSize) { + *size = talk_base::GetBE32(header.c_str()); + return true; + } + NOTREACHED() << "Unable to extract payload size"; + return false; +} + +} // namespace remoting diff --git a/remoting/base/protocol_decoder.h b/remoting/base/protocol_decoder.h new file mode 100644 index 0000000..9350e51 --- /dev/null +++ b/remoting/base/protocol_decoder.h @@ -0,0 +1,72 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_BASE_PROTOCOL_DECODER_H_ +#define REMOTING_BASE_PROTOCOL_DECODER_H_ + +#include +#include + +#include "base/ref_counted.h" +#include "google/protobuf/message_lite.h" +#include "media/base/data_buffer.h" +#include "remoting/base/protocol/chromotocol.pb.h" + +namespace remoting { + +typedef std::vector HostMessageList; +typedef std::vector ClientMessageList; + +// A protocol decoder is used to decode data transmitted in the chromoting +// network. +// TODO(hclam): Defines the interface and implement methods. +class ProtocolDecoder { + public: + ProtocolDecoder(); + + virtual ~ProtocolDecoder() {} + + // Parse data received from network into ClientMessages. Ownership of |data| + // is passed to this object and output is written to |messages|. + virtual void ParseClientMessages(scoped_refptr data, + ClientMessageList* messages); + + // Parse data received from network into HostMessages. Ownership of |data| + // is passed to this object and output is written to |messages|. + virtual void ParseHostMessages(scoped_refptr data, + HostMessageList* messages); + + private: + // A private method used to parse data received from network into protocol + // buffers. + template + void ParseMessages(scoped_refptr data, + std::vector* messages); + + // Parse one message from |data_list_|. Return true if sucessful. + template + bool ParseOneMessage(T** messages); + + // A utility method to read payload size of the protocol buffer from the + // data list. Return false if we don't have enough data. + bool GetPayloadSize(int* size); + + typedef std::deque > DataList; + DataList data_list_; + int last_read_position_; + + // Count the number of bytes in |data_list_| not read. + int available_bytes_; + + // Stores the size of the next payload if known. + int next_payload_; + + // True if the size of the next payload is known. After one payload is read, + // this is reset to false. + bool next_payload_known_; +}; + +} // namespace remoting + +#endif // REMOTING_BASE_PROTOCOL_DECODER_H_ diff --git a/remoting/base/protocol_decoder_unittest.cc b/remoting/base/protocol_decoder_unittest.cc new file mode 100644 index 0000000..900f7eb --- /dev/null +++ b/remoting/base/protocol_decoder_unittest.cc @@ -0,0 +1,113 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/scoped_ptr.h" +#include "media/base/data_buffer.h" +#include "remoting/base/protocol_decoder.h" +#include "remoting/base/protocol_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace remoting { + +static const int kWidth = 640; +static const int kHeight = 480; +static const std::string kTestData = "Chromoting rockz"; + +static void AppendMessage(const chromotocol_pb::HostMessage& msg, + std::string* buffer) { + // Contains one encoded message. + scoped_refptr encoded_msg; + encoded_msg = SerializeAndFrameMessage(msg); + buffer->append(reinterpret_cast(encoded_msg->GetData()), + encoded_msg->GetDataSize()); +} + +// Construct and prepare data in the |output_stream|. +static void PrepareData(uint8** buffer, int* size) { + // Contains all encoded messages. + std::string encoded_data; + + // The first message is InitClient. + chromotocol_pb::HostMessage msg; + msg.mutable_init_client()->set_width(kWidth); + msg.mutable_init_client()->set_height(kHeight); + AppendMessage(msg, &encoded_data); + msg.Clear(); + + // Then append 10 update sequences to the data. + for (int i = 0; i < 10; ++i) { + msg.mutable_begin_update_stream(); + AppendMessage(msg, &encoded_data); + msg.Clear(); + + msg.mutable_update_stream_packet()->set_data(kTestData); + AppendMessage(msg, &encoded_data); + msg.Clear(); + + msg.mutable_end_update_stream(); + AppendMessage(msg, &encoded_data); + msg.Clear(); + } + + *size = encoded_data.length(); + *buffer = new uint8[*size]; + memcpy(*buffer, encoded_data.c_str(), *size); +} + +TEST(ProtocolDecoderTest, BasicOperations) { + // Prepare encoded data for testing. + int size; + uint8* test_data; + PrepareData(&test_data, &size); + scoped_array memory_deleter(test_data); + + // Then simulate using ProtocolDecoder to decode variable + // size of encoded data. + // The first thing to do is to generate a variable size of data. This is done + // by iterating the following array for read sizes. + const int kReadSizes[] = {1, 2, 3, 1}; + + ProtocolDecoder decoder; + + // Then feed the protocol decoder using the above generated data and the + // read pattern. + HostMessageList message_list; + for (int i = 0; i < size;) { + // First generate the amount to feed the decoder. + int read = std::min(size - i, kReadSizes[i % arraysize(kReadSizes)]); + + // And then prepare a DataBuffer for feeding it. + scoped_refptr buffer = new media::DataBuffer(read); + memcpy(buffer->GetWritableData(), test_data + i, read); + buffer->SetDataSize(read); + decoder.ParseHostMessages(buffer, &message_list); + i += read; + } + + // Then verify the decoded messages. + EXPECT_EQ(31u, message_list.size()); + ASSERT_TRUE(message_list.size() > 0); + EXPECT_TRUE(message_list[0]->has_init_client()); + delete message_list[0]; + + for (size_t i = 1; i < message_list.size(); ++i) { + int type = (i - 1) % 3; + if (type == 0) { + // Begin update stream. + EXPECT_TRUE(message_list[i]->has_begin_update_stream()); + } else if (type == 1) { + // Partial update stream. + EXPECT_TRUE(message_list[i]->has_update_stream_packet()); + EXPECT_EQ(kTestData, message_list[i]->update_stream_packet().data()); + } else if (type == 2) { + // End update stream. + EXPECT_TRUE(message_list[i]->has_end_update_stream()); + } + delete message_list[i]; + } +} + +} // namespace remoting diff --git a/remoting/base/protocol_util.cc b/remoting/base/protocol_util.cc new file mode 100644 index 0000000..3ac738c5 --- /dev/null +++ b/remoting/base/protocol_util.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/base/protocol_util.h" + +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "talk/base/byteorder.h" + +namespace remoting { + +scoped_refptr SerializeAndFrameMessage( + const google::protobuf::MessageLite& msg) { + + // Create a buffer with 4 extra bytes. This is used as prefix to write an + // int32 of the serialized message size for framing. + const int kExtraBytes = sizeof(int32); + int size = msg.ByteSize() + kExtraBytes; + scoped_refptr buffer = new media::DataBuffer(size); + talk_base::SetBE32(buffer->GetWritableData(), msg.GetCachedSize()); + msg.SerializeWithCachedSizesToArray(buffer->GetWritableData() + kExtraBytes); + buffer->SetDataSize(size); + return buffer; +} + +} // namespace remoting diff --git a/remoting/base/protocol_util.h b/remoting/base/protocol_util.h new file mode 100644 index 0000000..16781eb --- /dev/null +++ b/remoting/base/protocol_util.h @@ -0,0 +1,24 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_BASE_PROTOCOL_UTIL_H_ +#define REMOTING_BASE_PROTOCOL_UTIL_H_ + +#include "google/protobuf/message_lite.h" +#include "media/base/data_buffer.h" + +// This file defines utility methods used for encoding and decoding the protocol +// used in Chromoting. +namespace remoting { + +// Serialize the Protocol Buffer message and provide sufficient framing for +// sending it over the wire. +// This will provide sufficient prefix and suffix for the receiver side to +// decode the message. +scoped_refptr SerializeAndFrameMessage( + const google::protobuf::MessageLite& msg); + +} // namespace remoting + +#endif // REMOTING_BASE_PROTOCOL_UTIL_H_ -- cgit v1.1