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