xref: /aosp_15_r20/external/pigweed/pw_transfer/public/pw_transfer/internal/chunk.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2023 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 #pragma once
15 
16 #include <optional>
17 
18 #include "pw_bytes/span.h"
19 #include "pw_chrono/system_clock.h"
20 #include "pw_result/result.h"
21 #include "pw_transfer/internal/protocol.h"
22 #include "pw_transfer/transfer.pwpb.h"
23 
24 namespace pw::transfer::internal {
25 
26 class Chunk {
27  public:
28   using Type = transfer::pwpb::Chunk::Type;
29 
30   class Identifier {
31    public:
is_session()32     constexpr bool is_session() const { return type_ == kSession; }
is_desired_session()33     constexpr bool is_desired_session() const { return type_ == kDesired; }
is_legacy()34     constexpr bool is_legacy() const { return type_ == kLegacy; }
35 
value()36     constexpr uint32_t value() const { return value_; }
37 
38    private:
39     friend class Chunk;
40 
Session(uint32_t value)41     static constexpr Identifier Session(uint32_t value) {
42       return Identifier(kSession, value);
43     }
Desired(uint32_t value)44     static constexpr Identifier Desired(uint32_t value) {
45       return Identifier(kDesired, value);
46     }
Legacy(uint32_t value)47     static constexpr Identifier Legacy(uint32_t value) {
48       return Identifier(kLegacy, value);
49     }
50 
51     enum IdType {
52       kSession,
53       kDesired,
54       kLegacy,
55     };
56 
Identifier(IdType type,uint32_t value)57     constexpr Identifier(IdType type, uint32_t value)
58         : type_(type), value_(value) {}
59 
60     IdType type_;
61     uint32_t value_;
62   };
63 
64   // Partially decodes a transfer chunk to find its transfer context identifier.
65   // Depending on the protocol version and type of chunk, this may be one of
66   // several proto fields.
67   static Result<Identifier> ExtractIdentifier(ConstByteSpan message);
68 
69   // Constructs a new chunk with the given transfer protocol version. All fields
70   // are initialized to their zero values.
Chunk(ProtocolVersion version,Type type)71   constexpr Chunk(ProtocolVersion version, Type type)
72       : Chunk(version, std::optional<Type>(type)) {}
73 
74   // Parses a chunk from a serialized protobuf message.
75   static Result<Chunk> Parse(ConstByteSpan message);
76 
77   // Creates a terminating status chunk within a transfer.
Final(ProtocolVersion version,uint32_t session_id,Status status)78   static Chunk Final(ProtocolVersion version,
79                      uint32_t session_id,
80                      Status status) {
81     return Chunk(version, Type::kCompletion)
82         .set_session_id(session_id)
83         .set_status(status);
84   }
85 
86   // Encodes the chunk to the specified buffer, returning a span of the
87   // serialized data on success.
88   Result<ConstByteSpan> Encode(ByteSpan buffer) const;
89 
90   // Returns the size of the serialized chunk based on the fields currently set
91   // within the chunk object.
92   size_t EncodedSize() const;
93 
94   void LogChunk(bool received,
95                 pw::chrono::SystemClock::duration rate_limit =
96                     chrono::SystemClock::duration::zero()) const;
97 
set_session_id(uint32_t session_id)98   constexpr Chunk& set_session_id(uint32_t session_id) {
99     session_id_ = session_id;
100     return *this;
101   }
102 
set_desired_session_id(uint32_t session_id)103   constexpr Chunk& set_desired_session_id(uint32_t session_id) {
104     desired_session_id_ = session_id;
105     return *this;
106   }
107 
set_resource_id(uint32_t resource_id)108   constexpr Chunk& set_resource_id(uint32_t resource_id) {
109     resource_id_ = resource_id;
110     return *this;
111   }
112 
set_protocol_version(ProtocolVersion version)113   constexpr Chunk& set_protocol_version(ProtocolVersion version) {
114     protocol_version_ = version;
115     return *this;
116   }
117 
set_window_end_offset(uint32_t window_end_offset)118   constexpr Chunk& set_window_end_offset(uint32_t window_end_offset) {
119     window_end_offset_ = window_end_offset;
120     return *this;
121   }
122 
set_max_chunk_size_bytes(uint32_t max_chunk_size_bytes)123   constexpr Chunk& set_max_chunk_size_bytes(uint32_t max_chunk_size_bytes) {
124     max_chunk_size_bytes_ = max_chunk_size_bytes;
125     return *this;
126   }
127 
set_min_delay_microseconds(uint32_t min_delay_microseconds)128   constexpr Chunk& set_min_delay_microseconds(uint32_t min_delay_microseconds) {
129     min_delay_microseconds_ = min_delay_microseconds;
130     return *this;
131   }
132 
set_offset(uint32_t offset)133   constexpr Chunk& set_offset(uint32_t offset) {
134     offset_ = offset;
135     return *this;
136   }
137 
set_payload(ConstByteSpan payload)138   constexpr Chunk& set_payload(ConstByteSpan payload) {
139     payload_ = payload;
140     return *this;
141   }
142 
set_remaining_bytes(uint64_t remaining_bytes)143   constexpr Chunk& set_remaining_bytes(uint64_t remaining_bytes) {
144     remaining_bytes_ = remaining_bytes;
145     return *this;
146   }
147 
set_initial_offset(uint32_t offset)148   constexpr Chunk& set_initial_offset(uint32_t offset) {
149     initial_offset_ = offset;
150     return *this;
151   }
152 
153   // TODO(frolv): For some reason, the compiler complains if this setter is
154   // marked constexpr. Leaving it off for now, but this should be investigated
155   // and fixed.
set_status(Status status)156   Chunk& set_status(Status status) {
157     status_ = status;
158     return *this;
159   }
160 
session_id()161   constexpr uint32_t session_id() const {
162     if (desired_session_id_.has_value()) {
163       return desired_session_id_.value();
164     }
165     return session_id_;
166   }
167 
desired_session_id()168   constexpr std::optional<uint32_t> desired_session_id() const {
169     return desired_session_id_;
170   }
171 
resource_id()172   constexpr std::optional<uint32_t> resource_id() const {
173     if (is_legacy()) {
174       // In the legacy protocol, resource_id and session_id are the same (i.e.
175       // transfer_id).
176       return session_id_;
177     }
178 
179     return resource_id_;
180   }
181 
window_end_offset()182   constexpr uint32_t window_end_offset() const { return window_end_offset_; }
offset()183   constexpr uint32_t offset() const { return offset_; }
status()184   constexpr std::optional<Status> status() const { return status_; }
185 
has_payload()186   constexpr bool has_payload() const { return !payload_.empty(); }
payload()187   constexpr ConstByteSpan payload() const { return payload_; }
188 
max_chunk_size_bytes()189   constexpr std::optional<uint32_t> max_chunk_size_bytes() const {
190     return max_chunk_size_bytes_;
191   }
min_delay_microseconds()192   constexpr std::optional<uint32_t> min_delay_microseconds() const {
193     return min_delay_microseconds_;
194   }
remaining_bytes()195   constexpr std::optional<uint64_t> remaining_bytes() const {
196     return remaining_bytes_;
197   }
198 
protocol_version()199   constexpr ProtocolVersion protocol_version() const {
200     return protocol_version_;
201   }
202 
is_legacy()203   constexpr bool is_legacy() const {
204     return protocol_version_ == ProtocolVersion::kLegacy;
205   }
206 
type()207   constexpr Type type() const {
208     // Legacy protocol chunks may not have a type, but newer versions always
209     // will. Try to deduce the type of a legacy chunk without one set.
210     if (!is_legacy() || type_.has_value()) {
211       return type_.value();
212     }
213 
214     // The type-less legacy transfer protocol doesn't support handshakes or
215     // continuation parameters. Therefore, there are only three possible chunk
216     // types: start, data, and retransmit.
217     if (IsInitialChunk()) {
218       return Type::kStart;
219     }
220 
221     if (has_payload()) {
222       return Type::kData;
223     }
224 
225     return Type::kParametersRetransmit;
226   }
227 
initial_offset()228   constexpr uint32_t initial_offset() const { return initial_offset_; }
229 
230   // Returns true if this parameters chunk is requesting that the transmitter
231   // transmit from its set offset instead of simply ACKing.
RequestsTransmissionFromOffset()232   constexpr bool RequestsTransmissionFromOffset() const {
233     if (is_legacy() && !type_.has_value()) {
234       return true;
235     }
236 
237     return type_.value() == Type::kParametersRetransmit ||
238            type_.value() == Type::kStartAckConfirmation ||
239            type_.value() == Type::kStart;
240   }
241 
IsInitialChunk()242   constexpr bool IsInitialChunk() const {
243     if (protocol_version_ >= ProtocolVersion::kVersionTwo) {
244       return type_ == Type::kStart;
245     }
246 
247     // In legacy versions of the transfer protocol, the chunk type is not always
248     // set. Infer that a chunk is initial if it has an offset of 0 and no data
249     // or status.
250     return type_ == Type::kStart ||
251            (offset_ == 0 && !has_payload() && !status_.has_value());
252   }
253 
IsTerminatingChunk()254   constexpr bool IsTerminatingChunk() const {
255     return type_ == Type::kCompletion || (is_legacy() && status_.has_value());
256   }
257 
258   // The final chunk from the transmitter sets remaining_bytes to 0 in both Read
259   // and Write transfers.
IsFinalTransmitChunk()260   constexpr bool IsFinalTransmitChunk() const { return remaining_bytes_ == 0u; }
261 
262   // Returns true if this chunk is part of an initial transfer handshake.
IsInitialHandshakeChunk()263   constexpr bool IsInitialHandshakeChunk() const {
264     return type_ == Type::kStart || type_ == Type::kStartAck ||
265            type_ == Type::kStartAckConfirmation;
266   }
267 
268  private:
Chunk(ProtocolVersion version,std::optional<Type> type)269   constexpr Chunk(ProtocolVersion version, std::optional<Type> type)
270       : session_id_(0),
271         desired_session_id_(std::nullopt),
272         resource_id_(std::nullopt),
273         window_end_offset_(0),
274         max_chunk_size_bytes_(std::nullopt),
275         min_delay_microseconds_(std::nullopt),
276         offset_(0),
277         initial_offset_(0),
278         payload_({}),
279         remaining_bytes_(std::nullopt),
280         status_(std::nullopt),
281         type_(type),
282         protocol_version_(version) {}
283 
Chunk()284   constexpr Chunk() : Chunk(ProtocolVersion::kUnknown, std::nullopt) {}
285 
286   // Returns true if this chunk should write legacy protocol fields to the
287   // serialized message.
288   //
289   // The first chunk of a transfer (type TRANSFER_START) is a special case: as
290   // we do not yet know what version of the protocol the other end is speaking,
291   // every legacy field must be encoded alongside newer ones to ensure that the
292   // chunk is processable. Following a response, the common protocol version
293   // will be determined and fields omitted as necessary.
ShouldEncodeLegacyFields()294   constexpr bool ShouldEncodeLegacyFields() const {
295     return is_legacy() || type_ == Type::kStart;
296   }
297 
298   uint32_t session_id_;
299   std::optional<uint32_t> desired_session_id_;
300   std::optional<uint32_t> resource_id_;
301   uint32_t window_end_offset_;
302   std::optional<uint32_t> max_chunk_size_bytes_;
303   std::optional<uint32_t> min_delay_microseconds_;
304   uint32_t offset_;
305   uint32_t initial_offset_;
306   ConstByteSpan payload_;
307   std::optional<uint64_t> remaining_bytes_;
308   std::optional<Status> status_;
309   std::optional<Type> type_;
310   ProtocolVersion protocol_version_;
311 };
312 
313 }  // namespace pw::transfer::internal
314