diff options
author | btolsch <btolsch@chromium.org> | 2019-09-18 13:50:32 -0700 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2019-09-18 21:00:03 +0000 |
commit | 26491c18de210836015403b7882b465c4552f81d (patch) | |
tree | 22b3af72544051e5f840a520399190bf475bf8e9 /cast/sender | |
parent | 17bbaa79f495bbd4e13983926413e11d92aec839 (diff) | |
download | openscreen-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.gn | 11 | ||||
-rw-r--r-- | cast/sender/channel/cast_auth_util.h | 3 | ||||
-rw-r--r-- | cast/sender/channel/cast_framer.cc | 94 | ||||
-rw-r--r-- | cast/sender/channel/cast_framer.h | 59 | ||||
-rw-r--r-- | cast/sender/channel/cast_framer_unittest.cc | 179 |
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 |