1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/proximity_auth/wire_message.h"
9 #include "base/json/json_reader.h"
10 #include "base/json/json_writer.h"
11 #include "base/macros.h"
12 #include "base/values.h"
13 #include "components/proximity_auth/cryptauth/base64url.h"
14 #include "components/proximity_auth/logging/logging.h"
16 // The wire messages have a simple format:
17 // [ message version ] [ body length ] [ JSON body ]
18 // 1 byte 2 bytes body length
19 // The JSON body contains two fields: an optional permit_id field and a required
22 namespace proximity_auth
{
25 // The length of the message header, in bytes.
26 const size_t kHeaderLength
= 3;
28 // The protocol version of the message format.
29 const int kMessageFormatVersionThree
= 3;
31 const char kPayloadKey
[] = "payload";
32 const char kPermitIdKey
[] = "permit_id";
34 // Parses the |serialized_message|'s header. Returns |true| iff the message has
35 // a valid header, is complete, and is well-formed according to the header. Sets
36 // |is_incomplete_message| to true iff the message does not have enough data to
37 // parse the header, or if the message length encoded in the message header
38 // exceeds the size of the |serialized_message|.
39 bool ParseHeader(const std::string
& serialized_message
,
40 bool* is_incomplete_message
) {
41 *is_incomplete_message
= false;
42 if (serialized_message
.size() < kHeaderLength
) {
43 *is_incomplete_message
= true;
47 static_assert(kHeaderLength
> 2, "kHeaderLength too small");
48 size_t version
= serialized_message
[0];
49 if (version
!= kMessageFormatVersionThree
) {
50 PA_LOG(WARNING
) << "Error: Invalid message version. Got " << version
51 << ", expected " << kMessageFormatVersionThree
;
55 uint16_t expected_body_length
=
56 (static_cast<uint8_t>(serialized_message
[1]) << 8) |
57 (static_cast<uint8_t>(serialized_message
[2]) << 0);
58 size_t expected_message_length
= kHeaderLength
+ expected_body_length
;
59 if (serialized_message
.size() < expected_message_length
) {
60 *is_incomplete_message
= true;
63 if (serialized_message
.size() != expected_message_length
) {
64 PA_LOG(WARNING
) << "Error: Invalid message length. Got "
65 << serialized_message
.size() << ", expected "
66 << expected_message_length
;
75 WireMessage::~WireMessage() {
79 scoped_ptr
<WireMessage
> WireMessage::Deserialize(
80 const std::string
& serialized_message
,
81 bool* is_incomplete_message
) {
82 if (!ParseHeader(serialized_message
, is_incomplete_message
))
83 return scoped_ptr
<WireMessage
>();
85 scoped_ptr
<base::Value
> body_value
=
86 base::JSONReader::Read(serialized_message
.substr(kHeaderLength
));
87 if (!body_value
|| !body_value
->IsType(base::Value::TYPE_DICTIONARY
)) {
88 PA_LOG(WARNING
) << "Error: Unable to parse message as JSON.";
89 return scoped_ptr
<WireMessage
>();
92 base::DictionaryValue
* body
;
93 bool success
= body_value
->GetAsDictionary(&body
);
96 // The permit ID is optional. In the Easy Unlock protocol, only the first
97 // message includes this field.
98 std::string permit_id
;
99 body
->GetString(kPermitIdKey
, &permit_id
);
101 std::string payload_base64
;
102 if (!body
->GetString(kPayloadKey
, &payload_base64
) ||
103 payload_base64
.empty()) {
104 PA_LOG(WARNING
) << "Error: Missing payload.";
105 return scoped_ptr
<WireMessage
>();
109 if (!Base64UrlDecode(payload_base64
, &payload
)) {
110 PA_LOG(WARNING
) << "Error: Invalid base64 encoding for payload.";
111 return scoped_ptr
<WireMessage
>();
114 return make_scoped_ptr(new WireMessage(payload
, permit_id
));
117 std::string
WireMessage::Serialize() const {
118 if (payload_
.empty()) {
119 PA_LOG(ERROR
) << "Failed to serialize empty wire message.";
120 return std::string();
123 // Create JSON body containing permit id and payload.
124 base::DictionaryValue body
;
125 if (!permit_id_
.empty())
126 body
.SetString(kPermitIdKey
, permit_id_
);
128 std::string base64_payload
;
129 Base64UrlEncode(payload_
, &base64_payload
);
130 body
.SetString(kPayloadKey
, base64_payload
);
132 std::string json_body
;
133 if (!base::JSONWriter::Write(body
, &json_body
)) {
134 PA_LOG(ERROR
) << "Failed to convert WireMessage body to JSON: " << body
;
135 return std::string();
138 // Create header containing version and payload size.
139 size_t body_size
= json_body
.size();
140 if (body_size
> std::numeric_limits
<uint16_t>::max()) {
141 PA_LOG(ERROR
) << "Can not create WireMessage because body size exceeds "
142 << "16-bit unsigned integer: " << body_size
;
143 return std::string();
147 static_cast<uint8_t>(kMessageFormatVersionThree
),
148 static_cast<uint8_t>((body_size
>> 8) & 0xFF),
149 static_cast<uint8_t>(body_size
& 0xFF),
151 static_assert(sizeof(header
) == kHeaderLength
, "Malformed header.");
153 std::string
header_string(kHeaderLength
, 0);
154 std::memcpy(&header_string
[0], header
, kHeaderLength
);
155 return header_string
+ json_body
;
158 WireMessage::WireMessage(const std::string
& payload
)
159 : WireMessage(payload
, std::string()) {}
161 WireMessage::WireMessage(const std::string
& payload
,
162 const std::string
& permit_id
)
163 : payload_(payload
), permit_id_(permit_id
) {}
165 } // namespace proximity_auth