summaryrefslogtreecommitdiffstats
path: root/remoting/base
diff options
context:
space:
mode:
authorgarykac@google.com <garykac@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-07 19:58:23 +0000
committergarykac@google.com <garykac@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-07 19:58:23 +0000
commitcb3b1f93130040a150e7fcc57cd4d5a75569685a (patch)
treeff93665e3c1478c61663d1107cd42dc25b31448d /remoting/base
parentb0110e822ac2b2db56d1b1542aad06da573cd544 (diff)
downloadchromium_src-cb3b1f93130040a150e7fcc57cd4d5a75569685a.zip
chromium_src-cb3b1f93130040a150e7fcc57cd4d5a75569685a.tar.gz
chromium_src-cb3b1f93130040a150e7fcc57cd4d5a75569685a.tar.bz2
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
Diffstat (limited to 'remoting/base')
-rw-r--r--remoting/base/constants.cc11
-rw-r--r--remoting/base/constants.h16
-rw-r--r--remoting/base/mock_objects.h30
-rw-r--r--remoting/base/multiple_array_input_stream.cc96
-rw-r--r--remoting/base/multiple_array_input_stream.h49
-rw-r--r--remoting/base/multiple_array_input_stream_unittest.cc94
-rw-r--r--remoting/base/protocol/chromotocol.gyp79
-rw-r--r--remoting/base/protocol/chromotocol.proto153
-rw-r--r--remoting/base/protocol_decoder.cc149
-rw-r--r--remoting/base/protocol_decoder.h72
-rw-r--r--remoting/base/protocol_decoder_unittest.cc113
-rw-r--r--remoting/base/protocol_util.cc27
-rw-r--r--remoting/base/protocol_util.h24
13 files changed, 913 insertions, 0 deletions
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 <string>
+
+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<media::DataBuffer> data,
+ ClientMessageList* messages));
+ MOCK_METHOD2(ParseHostMessages,
+ void(scoped_refptr<media::DataBuffer> 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 <functional>
+
+#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<const uint8*> buffers_;
+ scoped_array<int> 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 <string>
+
+#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<uint8*>(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<char> 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<MultipleArrayInputStream>* 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<const uint8*>(data), size);
+ data += size;
+ }
+ stream->reset(mstream);
+}
+
+TEST(MultipleArrayInputStreamTest, BasicOperations) {
+ scoped_ptr<MultipleArrayInputStream> 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<media::DataBuffer> data,
+ ClientMessageList* messages) {
+ ParseMessages<chromotocol_pb::ClientMessage>(data, messages);
+}
+
+void ProtocolDecoder::ParseHostMessages(scoped_refptr<media::DataBuffer> data,
+ HostMessageList* messages) {
+ ParseMessages<chromotocol_pb::HostMessage>(data, messages);
+}
+
+template <typename T>
+void ProtocolDecoder::ParseMessages(scoped_refptr<media::DataBuffer> data,
+ std::vector<T*>* 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<T>(&message)) {
+ messages->push_back(message);
+ }
+}
+
+template <typename T>
+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<const uint8*> buffer_pointers;
+ std::deque<int> buffer_sizes;
+ while (next_payload_ > 0 && !data_list_.empty()) {
+ scoped_refptr<media::DataBuffer> buffer = data_list_.front();
+ int read_bytes = std::min(
+ static_cast<int>(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<media::DataBuffer> 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<const char*>(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 <deque>
+#include <vector>
+
+#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<chromotocol_pb::HostMessage*> HostMessageList;
+typedef std::vector<chromotocol_pb::ClientMessage*> 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<media::DataBuffer> 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<media::DataBuffer> data,
+ HostMessageList* messages);
+
+ private:
+ // A private method used to parse data received from network into protocol
+ // buffers.
+ template <typename T>
+ void ParseMessages(scoped_refptr<media::DataBuffer> data,
+ std::vector<T*>* messages);
+
+ // Parse one message from |data_list_|. Return true if sucessful.
+ template <typename T>
+ 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<scoped_refptr<media::DataBuffer> > 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 <string>
+
+#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<media::DataBuffer> encoded_msg;
+ encoded_msg = SerializeAndFrameMessage(msg);
+ buffer->append(reinterpret_cast<const char*>(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<uint8> 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<media::DataBuffer> 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<media::DataBuffer> 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<media::DataBuffer> 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<media::DataBuffer> SerializeAndFrameMessage(
+ const google::protobuf::MessageLite& msg);
+
+} // namespace remoting
+
+#endif // REMOTING_BASE_PROTOCOL_UTIL_H_