1 // Copyright 2022 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_transfer/internal/chunk.h"
16
17 #include "pw_assert/check.h"
18 #include "pw_protobuf/decoder.h"
19 #include "pw_protobuf/serialized_size.h"
20 #include "pw_status/try.h"
21
22 namespace pw::transfer::internal {
23
24 namespace ProtoChunk = transfer::pwpb::Chunk;
25
ExtractIdentifier(ConstByteSpan message)26 Result<Chunk::Identifier> Chunk::ExtractIdentifier(ConstByteSpan message) {
27 protobuf::Decoder decoder(message);
28
29 uint32_t session_id = 0;
30 uint32_t resource_id = 0;
31
32 while (decoder.Next().ok()) {
33 ProtoChunk::Fields field =
34 static_cast<ProtoChunk::Fields>(decoder.FieldNumber());
35
36 if (field == ProtoChunk::Fields::kTransferId) {
37 // Interpret a legacy transfer_id field as a session ID if an explicit
38 // session_id field has not already been seen.
39 if (session_id == 0) {
40 PW_TRY(decoder.ReadUint32(&session_id));
41 }
42 } else if (field == ProtoChunk::Fields::kSessionId) {
43 // A session_id field always takes precedence over transfer_id.
44 PW_TRY(decoder.ReadUint32(&session_id));
45 } else if (field == ProtoChunk::Fields::kResourceId) {
46 PW_TRY(decoder.ReadUint32(&resource_id));
47 }
48 }
49
50 // Always prioritize a resource_id if one is set. Resource IDs should only be
51 // set in cases where the transfer session ID has not yet been negotiated.
52 if (resource_id != 0) {
53 return Identifier::Resource(resource_id);
54 }
55
56 if (session_id != 0) {
57 return Identifier::Session(session_id);
58 }
59
60 return Status::DataLoss();
61 }
62
Parse(ConstByteSpan message)63 Result<Chunk> Chunk::Parse(ConstByteSpan message) {
64 protobuf::Decoder decoder(message);
65 Status status;
66 uint32_t value;
67
68 Chunk chunk;
69
70 // Determine the protocol version of the chunk depending on field presence in
71 // the serialized message.
72 chunk.protocol_version_ = ProtocolVersion::kUnknown;
73
74 // Some older versions of the protocol set the deprecated pending_bytes field
75 // in their chunks. The newer transfer handling code does not process this
76 // field, instead working only in terms of window_end_offset. If pending_bytes
77 // is encountered in the serialized message, save its value, then calculate
78 // window_end_offset from it once parsing is complete.
79 uint32_t pending_bytes = 0;
80
81 while ((status = decoder.Next()).ok()) {
82 ProtoChunk::Fields field =
83 static_cast<ProtoChunk::Fields>(decoder.FieldNumber());
84
85 switch (field) {
86 case ProtoChunk::Fields::kTransferId:
87 // transfer_id is a legacy field. session_id will always take precedence
88 // over it, so it should only be read if session_id has not yet been
89 // encountered.
90 if (chunk.session_id_ == 0) {
91 PW_TRY(decoder.ReadUint32(&chunk.session_id_));
92 }
93 break;
94
95 case ProtoChunk::Fields::kSessionId:
96 // The existence of a session_id field indicates that a newer protocol
97 // is running. Update the deduced protocol unless it was explicitly
98 // specified.
99 if (chunk.protocol_version_ == ProtocolVersion::kUnknown) {
100 chunk.protocol_version_ = ProtocolVersion::kVersionTwo;
101 }
102
103 PW_TRY(decoder.ReadUint32(&chunk.session_id_));
104 break;
105
106 case ProtoChunk::Fields::kPendingBytes:
107 PW_TRY(decoder.ReadUint32(&pending_bytes));
108 break;
109
110 case ProtoChunk::Fields::kMaxChunkSizeBytes:
111 PW_TRY(decoder.ReadUint32(&value));
112 chunk.set_max_chunk_size_bytes(value);
113 break;
114
115 case ProtoChunk::Fields::kMinDelayMicroseconds:
116 PW_TRY(decoder.ReadUint32(&value));
117 chunk.set_min_delay_microseconds(value);
118 break;
119
120 case ProtoChunk::Fields::kOffset:
121 PW_TRY(decoder.ReadUint32(&chunk.offset_));
122 break;
123
124 case ProtoChunk::Fields::kData:
125 PW_TRY(decoder.ReadBytes(&chunk.payload_));
126 break;
127
128 case ProtoChunk::Fields::kRemainingBytes: {
129 uint64_t remaining_bytes;
130 PW_TRY(decoder.ReadUint64(&remaining_bytes));
131 chunk.set_remaining_bytes(remaining_bytes);
132 break;
133 }
134
135 case ProtoChunk::Fields::kStatus:
136 PW_TRY(decoder.ReadUint32(&value));
137 chunk.set_status(static_cast<Status::Code>(value));
138 break;
139
140 case ProtoChunk::Fields::kWindowEndOffset:
141 PW_TRY(decoder.ReadUint32(&chunk.window_end_offset_));
142 break;
143
144 case ProtoChunk::Fields::kType: {
145 uint32_t type;
146 PW_TRY(decoder.ReadUint32(&type));
147 chunk.type_ = static_cast<Chunk::Type>(type);
148 break;
149 }
150
151 case ProtoChunk::Fields::kResourceId:
152 PW_TRY(decoder.ReadUint32(&value));
153 chunk.set_resource_id(value);
154 break;
155
156 case ProtoChunk::Fields::kProtocolVersion:
157 // The protocol_version field is added as part of the initial handshake
158 // starting from version 2. If provided, it should override any deduced
159 // protocol version.
160 PW_TRY(decoder.ReadUint32(&value));
161 if (!ValidProtocolVersion(value)) {
162 return Status::DataLoss();
163 }
164 chunk.protocol_version_ = static_cast<ProtocolVersion>(value);
165 break;
166
167 // Silently ignore any unrecognized fields.
168 }
169 }
170
171 if (chunk.protocol_version_ == ProtocolVersion::kUnknown) {
172 // If no fields in the chunk specified its protocol version, assume it is a
173 // legacy chunk.
174 chunk.protocol_version_ = ProtocolVersion::kLegacy;
175 }
176
177 if (pending_bytes != 0) {
178 // Compute window_end_offset if it isn't explicitly provided (in older
179 // protocol versions).
180 chunk.set_window_end_offset(chunk.offset() + pending_bytes);
181 }
182
183 if (status.ok() || status.IsOutOfRange()) {
184 return chunk;
185 }
186
187 return status;
188 }
189
Encode(ByteSpan buffer) const190 Result<ConstByteSpan> Chunk::Encode(ByteSpan buffer) const {
191 PW_CHECK(protocol_version_ != ProtocolVersion::kUnknown,
192 "Cannot encode a transfer chunk with an unknown protocol version");
193
194 ProtoChunk::MemoryEncoder encoder(buffer);
195
196 // Write the payload first to avoid clobbering it if it shares the same buffer
197 // as the encode buffer.
198 if (has_payload()) {
199 encoder.WriteData(payload_).IgnoreError();
200 }
201
202 if (protocol_version_ >= ProtocolVersion::kVersionTwo) {
203 if (session_id_ != 0) {
204 encoder.WriteSessionId(session_id_).IgnoreError();
205 }
206
207 if (resource_id_.has_value()) {
208 encoder.WriteResourceId(resource_id_.value()).IgnoreError();
209 }
210 }
211
212 // During the initial handshake, the chunk's configured protocol version is
213 // explicitly serialized to the wire.
214 if (IsInitialHandshakeChunk()) {
215 encoder.WriteProtocolVersion(static_cast<uint32_t>(protocol_version_))
216 .IgnoreError();
217 }
218
219 if (type_.has_value()) {
220 encoder.WriteType(static_cast<ProtoChunk::Type>(type_.value()))
221 .IgnoreError();
222 }
223
224 if (window_end_offset_ != 0) {
225 encoder.WriteWindowEndOffset(window_end_offset_).IgnoreError();
226 }
227
228 // Encode additional fields from the legacy protocol.
229 if (ShouldEncodeLegacyFields()) {
230 // The legacy protocol uses the transfer_id field instead of session_id or
231 // resource_id.
232 if (resource_id_.has_value()) {
233 encoder.WriteTransferId(resource_id_.value()).IgnoreError();
234 } else {
235 encoder.WriteTransferId(session_id_).IgnoreError();
236 }
237
238 // In the legacy protocol, the pending_bytes field must be set alongside
239 // window_end_offset, as some transfer implementations require it.
240 if (window_end_offset_ != 0) {
241 encoder.WritePendingBytes(window_end_offset_ - offset_).IgnoreError();
242 }
243 }
244
245 if (max_chunk_size_bytes_.has_value()) {
246 encoder.WriteMaxChunkSizeBytes(max_chunk_size_bytes_.value()).IgnoreError();
247 }
248 if (min_delay_microseconds_.has_value()) {
249 encoder.WriteMinDelayMicroseconds(min_delay_microseconds_.value())
250 .IgnoreError();
251 }
252
253 if (offset_ != 0) {
254 encoder.WriteOffset(offset_).IgnoreError();
255 }
256
257 if (remaining_bytes_.has_value()) {
258 encoder.WriteRemainingBytes(remaining_bytes_.value()).IgnoreError();
259 }
260
261 if (status_.has_value()) {
262 encoder.WriteStatus(status_.value().code()).IgnoreError();
263 }
264
265 PW_TRY(encoder.status());
266 return ConstByteSpan(encoder);
267 }
268
EncodedSize() const269 size_t Chunk::EncodedSize() const {
270 size_t size = 0;
271
272 if (session_id_ != 0) {
273 if (protocol_version_ >= ProtocolVersion::kVersionTwo) {
274 size += protobuf::SizeOfVarintField(ProtoChunk::Fields::kSessionId,
275 session_id_);
276 }
277
278 if (ShouldEncodeLegacyFields()) {
279 if (resource_id_.has_value()) {
280 size += protobuf::SizeOfVarintField(ProtoChunk::Fields::kTransferId,
281 resource_id_.value());
282 } else {
283 size += protobuf::SizeOfVarintField(ProtoChunk::Fields::kTransferId,
284 session_id_);
285 }
286 }
287 }
288
289 if (IsInitialHandshakeChunk()) {
290 size +=
291 protobuf::SizeOfVarintField(ProtoChunk::Fields::kProtocolVersion,
292 static_cast<uint32_t>(protocol_version_));
293 }
294
295 if (protocol_version_ >= ProtocolVersion::kVersionTwo) {
296 if (resource_id_.has_value()) {
297 size += protobuf::SizeOfVarintField(ProtoChunk::Fields::kResourceId,
298 resource_id_.value());
299 }
300 }
301
302 if (offset_ != 0) {
303 size += protobuf::SizeOfVarintField(ProtoChunk::Fields::kOffset, offset_);
304 }
305
306 if (window_end_offset_ != 0) {
307 size += protobuf::SizeOfVarintField(ProtoChunk::Fields::kWindowEndOffset,
308 window_end_offset_);
309
310 if (ShouldEncodeLegacyFields()) {
311 size += protobuf::SizeOfVarintField(ProtoChunk::Fields::kPendingBytes,
312 window_end_offset_ - offset_);
313 }
314 }
315
316 if (type_.has_value()) {
317 size += protobuf::SizeOfVarintField(ProtoChunk::Fields::kType,
318 static_cast<uint32_t>(type_.value()));
319 }
320
321 if (has_payload()) {
322 size += protobuf::SizeOfDelimitedField(ProtoChunk::Fields::kData,
323 payload_.size());
324 }
325
326 if (max_chunk_size_bytes_.has_value()) {
327 size += protobuf::SizeOfVarintField(ProtoChunk::Fields::kMaxChunkSizeBytes,
328 max_chunk_size_bytes_.value());
329 }
330
331 if (min_delay_microseconds_.has_value()) {
332 size +=
333 protobuf::SizeOfVarintField(ProtoChunk::Fields::kMinDelayMicroseconds,
334 min_delay_microseconds_.value());
335 }
336
337 if (remaining_bytes_.has_value()) {
338 size += protobuf::SizeOfVarintField(ProtoChunk::Fields::kRemainingBytes,
339 remaining_bytes_.value());
340 }
341
342 if (status_.has_value()) {
343 size += protobuf::SizeOfVarintField(ProtoChunk::Fields::kStatus,
344 status_.value().code());
345 }
346
347 return size;
348 }
349
350 } // namespace pw::transfer::internal
351