aboutsummaryrefslogtreecommitdiff
path: root/cast/sender
diff options
context:
space:
mode:
authorbtolsch <btolsch@chromium.org>2019-09-18 13:50:32 -0700
committerCommit Bot <commit-bot@chromium.org>2019-09-18 21:00:03 +0000
commit26491c18de210836015403b7882b465c4552f81d (patch)
tree22b3af72544051e5f840a520399190bf475bf8e9 /cast/sender
parent17bbaa79f495bbd4e13983926413e11d92aec839 (diff)
downloadopenscreen-26491c18de210836015403b7882b465c4552f81d.tar.gz
Add framing for CastMessage
This change imports cast_framer.cc from Chromium's //components/cast_channel. Bug: openscreen:59 Change-Id: I189b6d1a243f439c8e8585afdb94f4bdd3d62251 Reviewed-on: https://chromium-review.googlesource.com/c/openscreen/+/1775425 Commit-Queue: Brandon Tolsch <btolsch@chromium.org> Reviewed-by: Max Yakimakha <yakimakha@chromium.org> Reviewed-by: Ryan Keane <rwkeane@google.com>
Diffstat (limited to 'cast/sender')
-rw-r--r--cast/sender/channel/BUILD.gn11
-rw-r--r--cast/sender/channel/cast_auth_util.h3
-rw-r--r--cast/sender/channel/cast_framer.cc94
-rw-r--r--cast/sender/channel/cast_framer.h59
-rw-r--r--cast/sender/channel/cast_framer_unittest.cc179
5 files changed, 343 insertions, 3 deletions
diff --git a/cast/sender/channel/BUILD.gn b/cast/sender/channel/BUILD.gn
index c383b6fa..13b061c1 100644
--- a/cast/sender/channel/BUILD.gn
+++ b/cast/sender/channel/BUILD.gn
@@ -6,24 +6,33 @@ source_set("channel") {
sources = [
"cast_auth_util.cc",
"cast_auth_util.h",
+ "cast_framer.cc",
+ "cast_framer.h",
]
deps = [
- "../../../platform",
+ "../../../util",
"proto",
]
+
+ public_deps = [
+ "../../../platform",
+ "../../../third_party/abseil",
+ ]
}
source_set("unittests") {
testonly = true
sources = [
"cast_auth_util_unittest.cc",
+ "cast_framer_unittest.cc",
]
deps = [
":channel",
"../../../platform",
"../../../third_party/googletest:gtest",
+ "../../../util",
"../../common/certificate/proto:unittest_proto",
"proto",
]
diff --git a/cast/sender/channel/cast_auth_util.h b/cast/sender/channel/cast_auth_util.h
index 35a9a704..b4a81e0b 100644
--- a/cast/sender/channel/cast_auth_util.h
+++ b/cast/sender/channel/cast_auth_util.h
@@ -27,8 +27,7 @@ namespace channel {
class AuthResponse;
class CastMessage;
-template <typename T>
-using ErrorOr = openscreen::ErrorOr<T>;
+using openscreen::ErrorOr;
using CastDeviceCertPolicy = certificate::CastDeviceCertPolicy;
class AuthContext {
diff --git a/cast/sender/channel/cast_framer.cc b/cast/sender/channel/cast_framer.cc
new file mode 100644
index 00000000..b5a8acb1
--- /dev/null
+++ b/cast/sender/channel/cast_framer.cc
@@ -0,0 +1,94 @@
+// Copyright 2019 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 "cast/sender/channel/cast_framer.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <limits>
+
+#include "cast/sender/channel/proto/cast_channel.pb.h"
+#include "platform/api/logging.h"
+#include "util/big_endian.h"
+#include "util/std_util.h"
+
+namespace cast {
+namespace channel {
+
+using ChannelError = openscreen::Error::Code;
+
+namespace {
+
+static constexpr size_t kHeaderSize = sizeof(uint32_t);
+
+// Cast specifies a max message body size of 64 KiB.
+static constexpr size_t kMaxBodySize = 65536;
+
+} // namespace
+
+MessageFramer::MessageFramer(absl::Span<uint8_t> input_buffer)
+ : input_buffer_(input_buffer) {}
+
+MessageFramer::~MessageFramer() = default;
+
+// static
+ErrorOr<std::string> MessageFramer::Serialize(const CastMessage& message) {
+ const size_t message_size = message.ByteSizeLong();
+ if (message_size > kMaxBodySize || message_size == 0) {
+ return ChannelError::kCastV2InvalidMessage;
+ }
+ std::string out(message_size + kHeaderSize, 0);
+ openscreen::WriteBigEndian<uint32_t>(message_size, openscreen::data(out));
+ if (!message.SerializeToArray(&out[kHeaderSize], message_size)) {
+ return ChannelError::kCastV2InvalidMessage;
+ }
+ return out;
+}
+
+ErrorOr<size_t> MessageFramer::BytesRequested() const {
+ if (message_bytes_received_ < kHeaderSize) {
+ return kHeaderSize - message_bytes_received_;
+ }
+
+ const uint32_t message_size =
+ openscreen::ReadBigEndian<uint32_t>(input_buffer_.data());
+ if (message_size > kMaxBodySize) {
+ return ChannelError::kCastV2InvalidMessage;
+ }
+ return (kHeaderSize + message_size) - message_bytes_received_;
+}
+
+ErrorOr<CastMessage> MessageFramer::TryDeserialize(size_t byte_count) {
+ message_bytes_received_ += byte_count;
+ if (message_bytes_received_ > input_buffer_.size()) {
+ return ChannelError::kCastV2InvalidMessage;
+ }
+
+ if (message_bytes_received_ < kHeaderSize) {
+ return ChannelError::kInsufficientBuffer;
+ }
+
+ const uint32_t message_size =
+ openscreen::ReadBigEndian<uint32_t>(input_buffer_.data());
+ if (message_size > kMaxBodySize) {
+ return ChannelError::kCastV2InvalidMessage;
+ }
+
+ if (message_bytes_received_ < (kHeaderSize + message_size)) {
+ return ChannelError::kInsufficientBuffer;
+ }
+
+ CastMessage parsed_message;
+ if (!parsed_message.ParseFromArray(input_buffer_.data() + kHeaderSize,
+ message_size)) {
+ return ChannelError::kCastV2InvalidMessage;
+ }
+
+ message_bytes_received_ = 0;
+ return parsed_message;
+}
+
+} // namespace channel
+} // namespace cast
diff --git a/cast/sender/channel/cast_framer.h b/cast/sender/channel/cast_framer.h
new file mode 100644
index 00000000..8fbabfd3
--- /dev/null
+++ b/cast/sender/channel/cast_framer.h
@@ -0,0 +1,59 @@
+// Copyright 2019 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 CAST_SENDER_CHANNEL_CAST_FRAMER_H_
+#define CAST_SENDER_CHANNEL_CAST_FRAMER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "absl/types/span.h"
+#include "platform/base/error.h"
+
+namespace cast {
+namespace channel {
+
+class CastMessage;
+
+using openscreen::ErrorOr;
+
+// Class for constructing and parsing CastMessage packet data.
+class MessageFramer {
+ public:
+ // Serializes |message_proto| into |message_data|.
+ // Returns true if the message was serialized successfully, false otherwise.
+ static ErrorOr<std::string> Serialize(const CastMessage& message);
+
+ explicit MessageFramer(absl::Span<uint8_t> input_buffer);
+ ~MessageFramer();
+
+ // The number of bytes required from the next |input_buffer| passed to
+ // TryDeserialize to complete the CastMessage being read. Returns zero if
+ // there has been a parsing error.
+ ErrorOr<size_t> BytesRequested() const;
+
+ // Reads bytes from |input_buffer_| and returns a new CastMessage if one is
+ // fully read.
+ //
+ // |byte_count| Number of additional bytes available in |input_buffer_|.
+ // Returns a pointer to a parsed CastMessage if a message was received in its
+ // entirety, empty unique_ptr if parsing was successful but didn't produce a
+ // complete message, and an error otherwise.
+ ErrorOr<CastMessage> TryDeserialize(size_t byte_count);
+
+ private:
+ // Total size of the message received so far in bytes (head + body).
+ size_t message_bytes_received_ = 0;
+
+ // Data buffer wherein the caller should place message data for ingest.
+ absl::Span<uint8_t> input_buffer_;
+};
+
+} // namespace channel
+} // namespace cast
+
+#endif // CAST_SENDER_CHANNEL_CAST_FRAMER_H_
diff --git a/cast/sender/channel/cast_framer_unittest.cc b/cast/sender/channel/cast_framer_unittest.cc
new file mode 100644
index 00000000..0cd10c71
--- /dev/null
+++ b/cast/sender/channel/cast_framer_unittest.cc
@@ -0,0 +1,179 @@
+// Copyright 2019 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 "cast/sender/channel/cast_framer.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <string>
+
+#include "cast/sender/channel/proto/cast_channel.pb.h"
+#include "gtest/gtest.h"
+#include "util/big_endian.h"
+#include "util/std_util.h"
+
+namespace cast {
+namespace channel {
+
+using ChannelError = openscreen::Error::Code;
+
+namespace {
+
+static constexpr size_t kHeaderSize = sizeof(uint32_t);
+
+// Cast specifies a max message body size of 64 KiB.
+static constexpr size_t kMaxBodySize = 65536;
+
+} // namespace
+
+class CastFramerTest : public testing::Test {
+ public:
+ CastFramerTest()
+ : buffer_(kHeaderSize + kMaxBodySize),
+ framer_(absl::Span<uint8_t>(&buffer_[0], buffer_.size())) {}
+
+ void SetUp() override {
+ cast_message_.set_protocol_version(CastMessage::CASTV2_1_0);
+ cast_message_.set_source_id("source");
+ cast_message_.set_destination_id("destination");
+ cast_message_.set_namespace_("namespace");
+ cast_message_.set_payload_type(CastMessage::STRING);
+ cast_message_.set_payload_utf8("payload");
+ ErrorOr<std::string> result = MessageFramer::Serialize(cast_message_);
+ ASSERT_TRUE(result.is_value());
+ cast_message_str_ = std::move(result.value());
+ }
+
+ void WriteToBuffer(const std::string& data) {
+ memcpy(&buffer_[0], data.data(), data.size());
+ }
+
+ protected:
+ CastMessage cast_message_;
+ std::string cast_message_str_;
+ std::vector<uint8_t> buffer_;
+ MessageFramer framer_;
+};
+
+TEST_F(CastFramerTest, TestMessageFramerCompleteMessage) {
+ WriteToBuffer(cast_message_str_);
+
+ // Receive 1 byte of the header, framer demands 3 more bytes.
+ EXPECT_EQ(4u, framer_.BytesRequested().value());
+ ErrorOr<CastMessage> result = framer_.TryDeserialize(1);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ChannelError::kInsufficientBuffer, result.error().code());
+ EXPECT_EQ(3u, framer_.BytesRequested().value());
+
+ // TryDeserialize remaining 3, expect that the framer has moved on to
+ // requesting the body contents.
+ result = framer_.TryDeserialize(3);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ChannelError::kInsufficientBuffer, result.error().code());
+ EXPECT_EQ(cast_message_str_.size() - kHeaderSize,
+ framer_.BytesRequested().value());
+
+ // Remainder of packet sent over the wire.
+ result = framer_.TryDeserialize(framer_.BytesRequested().value());
+ ASSERT_TRUE(result);
+ const CastMessage& message = result.value();
+ EXPECT_EQ(message.SerializeAsString(), cast_message_.SerializeAsString());
+ EXPECT_EQ(4u, framer_.BytesRequested().value());
+}
+
+TEST_F(CastFramerTest, BigEndianMessageHeader) {
+ WriteToBuffer(cast_message_str_);
+
+ EXPECT_EQ(4u, framer_.BytesRequested().value());
+ ErrorOr<CastMessage> result = framer_.TryDeserialize(4);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ChannelError::kInsufficientBuffer, result.error().code());
+
+ const uint32_t expected_size =
+ openscreen::ReadBigEndian<uint32_t>(openscreen::data(cast_message_str_));
+ EXPECT_EQ(expected_size, framer_.BytesRequested().value());
+}
+
+TEST_F(CastFramerTest, TestSerializeErrorMessageTooLarge) {
+ CastMessage big_message;
+ big_message.CopyFrom(cast_message_);
+ std::string payload;
+ payload.append(kMaxBodySize + 1, 'x');
+ big_message.set_payload_utf8(payload);
+ EXPECT_FALSE(MessageFramer::Serialize(big_message));
+}
+
+TEST_F(CastFramerTest, TestCompleteMessageAtOnce) {
+ WriteToBuffer(cast_message_str_);
+
+ ErrorOr<CastMessage> result =
+ framer_.TryDeserialize(cast_message_str_.size());
+ ASSERT_TRUE(result);
+ const CastMessage& message = result.value();
+ EXPECT_EQ(message.SerializeAsString(), cast_message_.SerializeAsString());
+ EXPECT_EQ(4u, framer_.BytesRequested().value());
+}
+
+TEST_F(CastFramerTest, TestTryDeserializeIllegalLargeMessage) {
+ std::string mangled_cast_message = cast_message_str_;
+ mangled_cast_message[0] = 88;
+ mangled_cast_message[1] = 88;
+ mangled_cast_message[2] = 88;
+ mangled_cast_message[3] = 88;
+ WriteToBuffer(mangled_cast_message);
+
+ EXPECT_EQ(4u, framer_.BytesRequested().value());
+ ErrorOr<CastMessage> result = framer_.TryDeserialize(4);
+ ASSERT_FALSE(result);
+ EXPECT_EQ(ChannelError::kCastV2InvalidMessage, result.error().code());
+ ErrorOr<size_t> bytes_requested = framer_.BytesRequested();
+ ASSERT_FALSE(bytes_requested);
+ EXPECT_EQ(ChannelError::kCastV2InvalidMessage,
+ bytes_requested.error().code());
+}
+
+TEST_F(CastFramerTest, TestTryDeserializeIllegalLargeMessage2) {
+ std::string mangled_cast_message = cast_message_str_;
+ // Header indicates body size is 0x00010001 = 65537
+ mangled_cast_message[0] = 0;
+ mangled_cast_message[1] = 0x1;
+ mangled_cast_message[2] = 0;
+ mangled_cast_message[3] = 0x1;
+ WriteToBuffer(mangled_cast_message);
+
+ EXPECT_EQ(4u, framer_.BytesRequested().value());
+ ErrorOr<CastMessage> result = framer_.TryDeserialize(4);
+ ASSERT_FALSE(result);
+ EXPECT_EQ(ChannelError::kCastV2InvalidMessage, result.error().code());
+ ErrorOr<size_t> bytes_requested = framer_.BytesRequested();
+ ASSERT_FALSE(bytes_requested);
+ EXPECT_EQ(ChannelError::kCastV2InvalidMessage,
+ bytes_requested.error().code());
+}
+
+TEST_F(CastFramerTest, TestUnparsableBodyProto) {
+ // Message header is OK, but the body is replaced with "x"es.
+ std::string mangled_cast_message = cast_message_str_;
+ for (size_t i = kHeaderSize; i < mangled_cast_message.size(); ++i) {
+ std::fill(mangled_cast_message.begin() + kHeaderSize,
+ mangled_cast_message.end(), 'x');
+ }
+ WriteToBuffer(mangled_cast_message);
+
+ // Send header.
+ EXPECT_EQ(4u, framer_.BytesRequested().value());
+ ErrorOr<CastMessage> result = framer_.TryDeserialize(4);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ChannelError::kInsufficientBuffer, result.error().code());
+ EXPECT_EQ(cast_message_str_.size() - 4, framer_.BytesRequested().value());
+
+ // Send body, expect an error.
+ result = framer_.TryDeserialize(framer_.BytesRequested().value());
+ ASSERT_FALSE(result);
+ EXPECT_EQ(ChannelError::kCastV2InvalidMessage, result.error().code());
+}
+
+} // namespace channel
+} // namespace cast