diff options
author | isherman <isherman@chromium.org> | 2014-09-15 17:21:03 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-16 00:22:57 +0000 |
commit | 3ca088e1bf653a570216e7c092657acfc36cdb4a (patch) | |
tree | 51c65d1de4771c9d241a77bae62905e2075d9ae9 | |
parent | 4c02231133fdd769143debb286620505a3456ba0 (diff) | |
download | chromium_src-3ca088e1bf653a570216e7c092657acfc36cdb4a.zip chromium_src-3ca088e1bf653a570216e7c092657acfc36cdb4a.tar.gz chromium_src-3ca088e1bf653a570216e7c092657acfc36cdb4a.tar.bz2 |
[EasyUnlock] Port PermitMessage class to native code.
BUG=412882
TEST=components_unittests
R=tengs@chromium.org
Review URL: https://codereview.chromium.org/562763004
Cr-Commit-Position: refs/heads/master@{#294939}
-rw-r--r-- | components/components_tests.gyp | 1 | ||||
-rw-r--r-- | components/proximity_auth/BUILD.gn | 1 | ||||
-rw-r--r-- | components/proximity_auth/connection.cc | 31 | ||||
-rw-r--r-- | components/proximity_auth/connection.h | 12 | ||||
-rw-r--r-- | components/proximity_auth/connection_unittest.cc | 30 | ||||
-rw-r--r-- | components/proximity_auth/wire_message.cc | 111 | ||||
-rw-r--r-- | components/proximity_auth/wire_message.h | 24 | ||||
-rw-r--r-- | components/proximity_auth/wire_message_unittest.cc | 169 |
8 files changed, 327 insertions, 52 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 837921e..e4204d8 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -180,6 +180,7 @@ 'precache/core/precache_url_table_unittest.cc', 'proximity_auth/connection_unittest.cc', 'proximity_auth/proximity_auth_system_unittest.cc', + 'proximity_auth/wire_message_unittest.cc', 'query_parser/query_parser_unittest.cc', 'query_parser/snippet_unittest.cc', 'rappor/bloom_filter_unittest.cc', diff --git a/components/proximity_auth/BUILD.gn b/components/proximity_auth/BUILD.gn index 1f25402..01d4efc2 100644 --- a/components/proximity_auth/BUILD.gn +++ b/components/proximity_auth/BUILD.gn @@ -24,6 +24,7 @@ source_set("unit_tests") { sources = [ "connection_unittest.cc", "proximity_auth_system_unittest.cc", + "wire_message_unittest.cc", ] deps = [ diff --git a/components/proximity_auth/connection.cc b/components/proximity_auth/connection.cc index 5a35e4d..65f6dd6 100644 --- a/components/proximity_auth/connection.cc +++ b/components/proximity_auth/connection.cc @@ -77,25 +77,26 @@ void Connection::OnBytesReceived(const std::string& bytes) { } received_bytes_ += bytes; - if (HasReceivedCompleteMessage()) { - scoped_ptr<WireMessage> message = DeserializeWireMessage(); - if (message) { - FOR_EACH_OBSERVER( - ConnectionObserver, observers_, OnMessageReceived(*this, *message)); - } - - // Whether the message was parsed successfully or not, clear the - // |received_bytes_| buffer. - received_bytes_.clear(); + + bool is_incomplete_message; + scoped_ptr<WireMessage> message = + DeserializeWireMessage(&is_incomplete_message); + if (is_incomplete_message) + return; + + if (message) { + FOR_EACH_OBSERVER( + ConnectionObserver, observers_, OnMessageReceived(*this, *message)); } -} -bool Connection::HasReceivedCompleteMessage() { - return WireMessage::IsCompleteMessage(received_bytes_); + // Whether the message was parsed successfully or not, clear the + // |received_bytes_| buffer. + received_bytes_.clear(); } -scoped_ptr<WireMessage> Connection::DeserializeWireMessage() { - return WireMessage::Deserialize(received_bytes_); +scoped_ptr<WireMessage> Connection::DeserializeWireMessage( + bool* is_incomplete_message) { + return WireMessage::Deserialize(received_bytes_, is_incomplete_message); } } // namespace proximity_auth diff --git a/components/proximity_auth/connection.h b/components/proximity_auth/connection.h index b904f84..b0275da 100644 --- a/components/proximity_auth/connection.h +++ b/components/proximity_auth/connection.h @@ -75,13 +75,13 @@ class Connection { // in progress. virtual void SendMessageImpl(scoped_ptr<WireMessage> message) = 0; - // Returns |true| iff the |received_bytes_| are long enough to contain a - // complete wire message. Exposed for testing. - virtual bool HasReceivedCompleteMessage(); - // Deserializes the |recieved_bytes_| and returns the resulting WireMessage, - // or NULL if the message is malformed. Exposed for testing. - virtual scoped_ptr<WireMessage> DeserializeWireMessage(); + // or NULL if the message is malformed. Sets |is_incomplete_message| to true + // if the |serialized_message| does not have enough data to parse the header, + // or if the message length encoded in the message header exceeds the size of + // the |serialized_message|. Exposed for testing. + virtual scoped_ptr<WireMessage> DeserializeWireMessage( + bool* is_incomplete_message); private: // The remote device corresponding to this connection. diff --git a/components/proximity_auth/connection_unittest.cc b/components/proximity_auth/connection_unittest.cc index 2f9fbdb..2e3678d 100644 --- a/components/proximity_auth/connection_unittest.cc +++ b/components/proximity_auth/connection_unittest.cc @@ -10,8 +10,10 @@ #include "testing/gtest/include/gtest/gtest.h" using testing::_; +using testing::DoAll; using testing::NiceMock; using testing::Return; +using testing::SetArgPointee; using testing::StrictMock; namespace proximity_auth { @@ -27,8 +29,8 @@ class MockConnection : public Connection { MOCK_METHOD0(Disconnect, void()); MOCK_METHOD0(CancelConnectionAttempt, void()); MOCK_METHOD1(SendMessageImplProxy, void(WireMessage* message)); - MOCK_METHOD0(HasReceivedCompleteMessage, bool()); - MOCK_METHOD0(DeserializeWireMessageProxy, WireMessage*()); + MOCK_METHOD1(DeserializeWireMessageProxy, + WireMessage*(bool* is_incomplete_message)); // Gmock only supports copyable types, so create simple wrapper methods for // ease of mocking. @@ -36,8 +38,9 @@ class MockConnection : public Connection { SendMessageImplProxy(message.get()); } - virtual scoped_ptr<WireMessage> DeserializeWireMessage() OVERRIDE { - return make_scoped_ptr(DeserializeWireMessageProxy()); + virtual scoped_ptr<WireMessage> DeserializeWireMessage( + bool* is_incomplete_message) OVERRIDE { + return make_scoped_ptr(DeserializeWireMessageProxy(is_incomplete_message)); } using Connection::status; @@ -72,7 +75,7 @@ class MockConnectionObserver : public ConnectionObserver { // Unlike WireMessage, offers a public constructor. class TestWireMessage : public WireMessage { public: - TestWireMessage() {} + TestWireMessage() : WireMessage(std::string(), std::string()) {} virtual ~TestWireMessage() {} private: @@ -191,9 +194,9 @@ TEST(ProximityAuthConnectionTest, StrictMock<MockConnectionObserver> observer; connection.AddObserver(&observer); - ON_CALL(connection, HasReceivedCompleteMessage()).WillByDefault(Return(true)); - ON_CALL(connection, DeserializeWireMessageProxy()) - .WillByDefault(Return(new TestWireMessage)); + ON_CALL(connection, DeserializeWireMessageProxy(_)) + .WillByDefault(DoAll(SetArgPointee<0>(false), + Return(new TestWireMessage))); EXPECT_CALL(observer, OnMessageReceived(Ref(connection), _)); connection.OnBytesReceived(std::string()); } @@ -218,8 +221,9 @@ TEST(ProximityAuthConnectionTest, StrictMock<MockConnectionObserver> observer; connection.AddObserver(&observer); - ON_CALL(connection, HasReceivedCompleteMessage()) - .WillByDefault(Return(false)); + ON_CALL(connection, DeserializeWireMessageProxy(_)) + .WillByDefault(DoAll(SetArgPointee<0>(true), + Return(static_cast<WireMessage*>(NULL)))); EXPECT_CALL(observer, OnMessageReceived(_, _)).Times(0); connection.OnBytesReceived(std::string()); } @@ -232,9 +236,9 @@ TEST(ProximityAuthConnectionTest, StrictMock<MockConnectionObserver> observer; connection.AddObserver(&observer); - ON_CALL(connection, HasReceivedCompleteMessage()).WillByDefault(Return(true)); - ON_CALL(connection, DeserializeWireMessageProxy()) - .WillByDefault(Return(static_cast<WireMessage*>(NULL))); + ON_CALL(connection, DeserializeWireMessageProxy(_)) + .WillByDefault(DoAll(SetArgPointee<0>(false), + Return(static_cast<WireMessage*>(NULL)))); EXPECT_CALL(observer, OnMessageReceived(_, _)).Times(0); connection.OnBytesReceived(std::string()); } diff --git a/components/proximity_auth/wire_message.cc b/components/proximity_auth/wire_message.cc index b6b5770..678014d 100644 --- a/components/proximity_auth/wire_message.cc +++ b/components/proximity_auth/wire_message.cc @@ -4,26 +4,117 @@ #include "components/proximity_auth/wire_message.h" +#include "base/base64.h" +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/values.h" + +// The wire messages have a simple format: +// [ message version ] [ body length ] [ JSON body ] +// 1 byte 2 bytes body length +// The JSON body contains two fields: an optional permit_id field and a required +// data field. + namespace proximity_auth { +namespace { -WireMessage::~WireMessage() { +// The length of the message header, in bytes. +const size_t kHeaderLength = 3; + +// The protocol version of the message format. +const int kExpectedMessageFormatVersion = 3; + +const char kPayloadKey[] = "payload"; +const char kPermitIdKey[] = "permit_id"; + +// Parses the |serialized_message|'s header. Returns |true| iff the message has +// a valid header, is complete, and is well-formed according to the header. Sets +// |is_incomplete_message| to true iff the message does not have enough data to +// parse the header, or if the message length encoded in the message header +// exceeds the size of the |serialized_message|. +bool ParseHeader(const std::string& serialized_message, + bool* is_incomplete_message) { + *is_incomplete_message = false; + if (serialized_message.size() < kHeaderLength) { + *is_incomplete_message = true; + return false; + } + + COMPILE_ASSERT(kHeaderLength > 2, header_length_too_small); + size_t version = serialized_message[0]; + if (version != kExpectedMessageFormatVersion) { + VLOG(1) << "Error: Invalid message version. Got " << version + << ", expected " << kExpectedMessageFormatVersion; + return false; + } + + size_t expected_body_length = + (static_cast<size_t>(serialized_message[1]) << 8) | + (static_cast<size_t>(serialized_message[2]) << 0); + size_t expected_message_length = kHeaderLength + expected_body_length; + if (serialized_message.size() < expected_message_length) { + *is_incomplete_message = true; + return false; + } + if (serialized_message.size() != expected_message_length) { + VLOG(1) << "Error: Invalid message length. Got " + << serialized_message.size() << ", expected " + << expected_message_length; + return false; + } + + return true; } -// static -bool WireMessage::IsCompleteMessage(const std::string& serialized_message) { - // TODO(isherman): Implement. - return false; +} // namespace + +WireMessage::~WireMessage() { } // static scoped_ptr<WireMessage> WireMessage::Deserialize( - const std::string& serialized_message) { - // TODO(isherman): Implement. - return scoped_ptr<WireMessage>(); + const std::string& serialized_message, + bool* is_incomplete_message) { + if (!ParseHeader(serialized_message, is_incomplete_message)) + return scoped_ptr<WireMessage>(); + + scoped_ptr<base::Value> body_value( + base::JSONReader::Read(serialized_message.substr(kHeaderLength))); + if (!body_value || !body_value->IsType(base::Value::TYPE_DICTIONARY)) { + VLOG(1) << "Error: Unable to parse message as JSON."; + return scoped_ptr<WireMessage>(); + } + + base::DictionaryValue* body; + bool success = body_value->GetAsDictionary(&body); + DCHECK(success); + + // The permit ID is optional. In the Easy Unlock protocol, only the first + // message includes this field. + std::string permit_id; + body->GetString(kPermitIdKey, &permit_id); + + std::string payload_base64; + if (!body->GetString(kPayloadKey, &payload_base64) || + payload_base64.empty()) { + VLOG(1) << "Error: Missing payload."; + return scoped_ptr<WireMessage>(); + } + + std::string payload; + if (!base::Base64Decode(payload_base64, &payload)) { + VLOG(1) << "Error: Invalid base64 encoding for payload."; + return scoped_ptr<WireMessage>(); + } + + return scoped_ptr<WireMessage>(new WireMessage(permit_id, payload)); } -WireMessage::WireMessage() { - // TODO(isherman): Implement. +WireMessage::WireMessage(const std::string& permit_id, + const std::string& payload) + : permit_id_(permit_id), + payload_(payload) { } } // namespace proximity_auth diff --git a/components/proximity_auth/wire_message.h b/components/proximity_auth/wire_message.h index db5af37..3610762 100644 --- a/components/proximity_auth/wire_message.h +++ b/components/proximity_auth/wire_message.h @@ -16,21 +16,29 @@ class WireMessage { public: virtual ~WireMessage(); - // Returns |true| iff the size of |message_bytes| is at least equal to the - // message length encoded in the message header. Returns false if the message - // header is not available. - static bool IsCompleteMessage(const std::string& message_bytes); - // Returns the deserialized message from |serialized_message|, or NULL if the - // message is malformed. + // message is malformed. Sets |is_incomplete_message| to true if the message + // does not have enough data to parse the header, or if the message length + // encoded in the message header exceeds the size of the |serialized_message|. static scoped_ptr<WireMessage> Deserialize( - const std::string& serialized_message); + const std::string& serialized_message, + bool* is_incomplete_message); + + const std::string& permit_id() const { return permit_id_; } + const std::string& payload() const { return payload_; } protected: // Visible for tests. - WireMessage(); + WireMessage(const std::string& permit_id, const std::string& payload); private: + // Identifier of the permit being used. + // TODO(isherman): Describe in a bit more detail. + const std::string permit_id_; + + // The message payload. + const std::string payload_; + DISALLOW_COPY_AND_ASSIGN(WireMessage); }; diff --git a/components/proximity_auth/wire_message_unittest.cc b/components/proximity_auth/wire_message_unittest.cc new file mode 100644 index 0000000..afa05ef --- /dev/null +++ b/components/proximity_auth/wire_message_unittest.cc @@ -0,0 +1,169 @@ +// Copyright 2014 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 "components/proximity_auth/wire_message.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace proximity_auth { + +TEST(ProximityAuthWireMessage, Deserialize_EmptyMessage) { + bool is_incomplete; + scoped_ptr<WireMessage> message = + WireMessage::Deserialize(std::string(), &is_incomplete); + EXPECT_TRUE(is_incomplete); + EXPECT_FALSE(message); +} + +TEST(ProximityAuthWireMessage, Deserialize_IncompleteHeader) { + bool is_incomplete; + scoped_ptr<WireMessage> message = + WireMessage::Deserialize("\3", &is_incomplete); + EXPECT_TRUE(is_incomplete); + EXPECT_FALSE(message); +} + +TEST(ProximityAuthWireMessage, Deserialize_UnexpectedMessageFormatVersion) { + bool is_incomplete; + // Version 2 is below the minimum supported version. + scoped_ptr<WireMessage> message = + WireMessage::Deserialize("\2\1\1", &is_incomplete); + EXPECT_FALSE(is_incomplete); + EXPECT_FALSE(message); +} + +TEST(ProximityAuthWireMessage, Deserialize_BodyOfSizeZero) { + bool is_incomplete; + scoped_ptr<WireMessage> message = + WireMessage::Deserialize(std::string("\3\0\0", 3), &is_incomplete); + EXPECT_FALSE(is_incomplete); + EXPECT_FALSE(message); +} + +TEST(ProximityAuthWireMessage, Deserialize_IncompleteBody) { + bool is_incomplete; + scoped_ptr<WireMessage> message = + WireMessage::Deserialize(std::string("\3\0\5", 3), &is_incomplete); + EXPECT_TRUE(is_incomplete); + EXPECT_FALSE(message); +} + +TEST(ProximityAuthWireMessage, Deserialize_BodyLongerThanSpecifiedInHeader) { + bool is_incomplete; + scoped_ptr<WireMessage> message = WireMessage::Deserialize( + std::string("\3\0\5", 3) + "123456", &is_incomplete); + EXPECT_FALSE(is_incomplete); + EXPECT_FALSE(message); +} + +TEST(ProximityAuthWireMessage, Deserialize_BodyIsNotValidJSON) { + bool is_incomplete; + scoped_ptr<WireMessage> message = WireMessage::Deserialize( + std::string("\3\0\5", 3) + "12345", &is_incomplete); + EXPECT_FALSE(is_incomplete); + EXPECT_FALSE(message); +} + +TEST(ProximityAuthWireMessage, Deserialize_BodyIsNotADictionary) { + bool is_incomplete; + std::string header("\3\0\x29", 3); + std::string bytes = + header + "[{\"permit_id\": \"Hi!\", \"payload\": \"YQ==\"}]"; + scoped_ptr<WireMessage> message = + WireMessage::Deserialize(bytes, &is_incomplete); + EXPECT_FALSE(is_incomplete); + EXPECT_FALSE(message); +} + +// The permit ID is optional. +TEST(ProximityAuthWireMessage, Deserialize_BodyLacksPermitId) { + bool is_incomplete; + std::string header("\3\0\x13", 3); + std::string bytes = header + "{\"payload\": \"YQ==\"}"; + scoped_ptr<WireMessage> message = + WireMessage::Deserialize(bytes, &is_incomplete); + EXPECT_FALSE(is_incomplete); + EXPECT_TRUE(message); + EXPECT_EQ(std::string(), message->permit_id()); + EXPECT_EQ("a", message->payload()); +} + +TEST(ProximityAuthWireMessage, Deserialize_BodyLacksPayload) { + bool is_incomplete; + std::string header("\3\0\x14", 3); + std::string bytes = header + "{\"permit_id\": \"Hi!\"}"; + scoped_ptr<WireMessage> message = + WireMessage::Deserialize(bytes, &is_incomplete); + EXPECT_FALSE(is_incomplete); + EXPECT_FALSE(message); +} + +// The permit ID is optional. +TEST(ProximityAuthWireMessage, Deserialize_BodyHasEmptyPermitId) { + bool is_incomplete; + std::string header("\3\0\x24", 3); + std::string bytes = + header + "{\"permit_id\": \"\", \"payload\": \"YQ==\"}"; + scoped_ptr<WireMessage> message = + WireMessage::Deserialize(bytes, &is_incomplete); + EXPECT_FALSE(is_incomplete); + EXPECT_TRUE(message); + EXPECT_EQ(std::string(), message->permit_id()); + EXPECT_EQ("a", message->payload()); +} + +TEST(ProximityAuthWireMessage, Deserialize_BodyHasEmptyPayload) { + bool is_incomplete; + std::string header("\3\0\x23", 3); + std::string bytes = + header + "{\"permit_id\": \"Hi!\", \"payload\": \"\"}"; + scoped_ptr<WireMessage> message = + WireMessage::Deserialize(bytes, &is_incomplete); + EXPECT_FALSE(is_incomplete); + EXPECT_FALSE(message); +} + +TEST(ProximityAuthWireMessage, Deserialize_PayloadIsNotBase64Encoded) { + bool is_incomplete; + std::string header("\3\0\x2A", 3); + std::string bytes = + header + "{\"permit_id\": \"Hi!\", \"payload\": \"garbage\"}"; + scoped_ptr<WireMessage> message = + WireMessage::Deserialize(bytes, &is_incomplete); + EXPECT_FALSE(is_incomplete); + EXPECT_FALSE(message); +} + +TEST(ProximityAuthWireMessage, Deserialize_ValidMessage) { + bool is_incomplete; + std::string header("\3\0\x27", 3); + std::string bytes = + header + "{\"permit_id\": \"Hi!\", \"payload\": \"YQ==\"}"; + scoped_ptr<WireMessage> message = + WireMessage::Deserialize(bytes, &is_incomplete); + EXPECT_FALSE(is_incomplete); + EXPECT_TRUE(message); + EXPECT_EQ("Hi!", message->permit_id()); + EXPECT_EQ("a", message->payload()); +} + +TEST(ProximityAuthWireMessage, Deserialize_ValidMessageWithExtraUnknownFields) { + bool is_incomplete; + std::string header("\3\0\x46", 3); + std::string bytes = + header + + "{" + " \"permit_id\": \"Hi!\"," + " \"payload\": \"YQ==\"," + " \"unexpected\": \"surprise!\"" + "}"; + scoped_ptr<WireMessage> message = + WireMessage::Deserialize(bytes, &is_incomplete); + EXPECT_FALSE(is_incomplete); + EXPECT_TRUE(message); + EXPECT_EQ("Hi!", message->permit_id()); + EXPECT_EQ("a", message->payload()); +} + +} // namespace proximity_auth |