• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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