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