1.. _module-pw_transfer: 2 3=========== 4pw_transfer 5=========== 6.. pigweed-module:: 7 :name: pw_transfer 8 9``pw_transfer`` is a reliable data transfer protocol which runs on top of 10Pigweed RPC. 11 12----- 13Usage 14----- 15 16C++ 17=== 18 19Transfer thread 20--------------- 21To run transfers as either a client or server (or both), a dedicated thread is 22required. The transfer thread is used to process all transfer-related events 23safely. The same transfer thread can be shared by a transfer client and service 24running on the same system. 25 26.. note:: 27 28 All user-defined transfer callbacks (i.e. the virtual interface of a 29 ``Handler`` or completion function in a transfer client) will be 30 invoked from the transfer thread's context. 31 32In order to operate, a transfer thread requires two buffers: 33 34- The first is a *chunk buffer*. This is used to stage transfer packets received 35 by the RPC system to be processed by the transfer thread. It must be large 36 enough to store the largest possible chunk the system supports. 37 38- The second is an *encode buffer*. This is used by the transfer thread to 39 encode outgoing RPC packets. It is necessarily larger than the chunk buffer. 40 Typically, this is sized to the system's maximum transmission unit at the 41 transport layer. 42 43A transfer thread is created by instantiating a ``pw::transfer::Thread``. This 44class derives from ``pw::thread::ThreadCore``, allowing it to directly be used 45when creating a system thread. Refer to :ref:`module-pw_thread-thread-creation` 46for additional information. 47 48**Example thread configuration** 49 50.. code-block:: cpp 51 52 #include "pw_transfer/transfer_thread.h" 53 54 namespace { 55 56 // The maximum number of concurrent transfers the thread should support as 57 // either a client or a server. These can be set to 0 (if only using one or 58 // the other). 59 constexpr size_t kMaxConcurrentClientTransfers = 5; 60 constexpr size_t kMaxConcurrentServerTransfers = 3; 61 62 // The maximum payload size that can be transmitted by the system's 63 // transport stack. This would typically be defined within some transport 64 // header. 65 constexpr size_t kMaxTransmissionUnit = 512; 66 67 // The maximum amount of data that should be sent within a single transfer 68 // packet. By necessity, this should be less than the max transmission unit. 69 // 70 // pw_transfer requires some additional per-packet overhead, so the actual 71 // amount of data it sends may be lower than this. 72 constexpr size_t kMaxTransferChunkSizeBytes = 480; 73 74 // Buffers for storing and encoding chunks (see documentation above). 75 std::array<std::byte, kMaxTransferChunkSizeBytes> chunk_buffer; 76 std::array<std::byte, kMaxTransmissionUnit> encode_buffer; 77 78 pw::transfer::Thread<kMaxConcurrentClientTransfers, 79 kMaxConcurrentServerTransfers> 80 transfer_thread(chunk_buffer, encode_buffer); 81 82 } // namespace 83 84 // pw::transfer::TransferThread is the generic, non-templated version of the 85 // Thread class. A Thread can implicitly convert to a TransferThread. 86 pw::transfer::TransferThread& GetSystemTransferThread() { 87 return transfer_thread; 88 } 89 90.. _pw_transfer-transfer-server: 91 92Transfer server 93--------------- 94``pw_transfer`` provides an RPC service for running transfers through an RPC 95server. 96 97To know how to read data from or write data to device, a ``Handler`` interface 98is defined (``pw_transfer/public/pw_transfer/handler.h``). Transfer handlers 99represent a transferable resource, wrapping a stream reader and/or writer with 100initialization and completion code. Custom transfer handler implementations 101should derive from ``ReadOnlyHandler``, ``WriteOnlyHandler``, or 102``ReadWriteHandler`` as appropriate and override Prepare and Finalize methods 103if necessary. 104 105A transfer handler should be implemented and instantiated for each unique 106resource that can be transferred to or from a device. Each instantiated handler 107must have a globally-unique integer ID used to identify the resource. 108 109Handlers are registered with the transfer service. This may be done during 110system initialization (for static resources), or dynamically at runtime to 111support ephemeral transfer resources. A boolean is returned with registration/ 112unregistration to indicate success or failure. 113 114**Example transfer handler implementation** 115 116.. code-block:: cpp 117 118 #include "pw_stream/memory_stream.h" 119 #include "pw_transfer/transfer.h" 120 121 // A simple transfer handler which reads data from an in-memory buffer. 122 class SimpleBufferReadHandler : public pw::transfer::ReadOnlyHandler { 123 public: 124 SimpleReadTransfer(uint32_t resource_id, pw::ConstByteSpan data) 125 : ReadOnlyHandler(resource_id), reader_(data) { 126 set_reader(reader_); 127 } 128 129 private: 130 pw::stream::MemoryReader reader_; 131 }; 132 133Handlers may optionally implement a `GetStatus` method, which allows clients to 134query the status of a resource with a handler registered. The application layer 135above transfer can choose how to fill and interpret this information. The status 136information is `readable_offset`, `writeable_offset`, `read_checksum`, and 137`write_checksum`. 138 139**Example GetStatus implementation** 140 141.. code-block:: cpp 142 143 Status GetStatus(uint64_t& readable_offset, 144 uint64_t& writeable_offset, 145 uint64_t& read_checksum, 146 uint64_t& write_checksum) { 147 readable_offset = resource.get_size(); 148 writeable_offset = resource.get_writeable_offset(); 149 read_checksum = resource.get_crc(); 150 write_checksum = resource.calculate_crc(0, writeable_offset); 151 152 return pw::OkStatus(); 153 } 154 155The transfer service is instantiated with a reference to the system's transfer 156thread and registered with the system's RPC server. 157 158**Example transfer service initialization** 159 160.. code-block:: cpp 161 162 #include "pw_transfer/transfer.h" 163 164 namespace { 165 166 // In a write transfer, the maximum number of bytes to receive at one time 167 // (potentially across multiple chunks), unless specified otherwise by the 168 // transfer handler's stream::Writer. Should be set reasonably high; the 169 // transfer will attempt to determine an optimal window size based on the 170 // link. 171 constexpr size_t kDefaultMaxBytesToReceive = 16384; 172 173 pw::transfer::TransferService transfer_service( 174 GetSystemTransferThread(), kDefaultMaxBytesToReceive); 175 176 // Instantiate a handler for the data to be transferred. The resource ID will 177 // be used by the transfer client and server to identify the handler. 178 constexpr uint32_t kMagicBufferResourceId = 1; 179 char magic_buffer_to_transfer[256] = { /* ... */ }; 180 SimpleBufferReadHandler magic_buffer_handler( 181 kMagicBufferResourceId, magic_buffer_to_transfer); 182 183 } // namespace 184 185 void InitTransferService() { 186 // Register the handler with the transfer service, then the transfer service 187 // with an RPC server. 188 bool success = transfer_service.RegisterHandler(magic_buffer_handler); 189 GetSystemRpcServer().RegisterService(transfer_service); 190 } 191 192Transfer client 193--------------- 194``pw_transfer`` provides a transfer client capable of running transfers through 195an RPC client. 196 197.. note:: 198 199 Currently, a transfer client is only capable of running transfers on a single 200 RPC channel. This may be expanded in the future. 201 202The transfer client provides the following APIs for managing data transfers: 203 204.. cpp:function:: Result<pw::Transfer::Client::Handle> pw::transfer::Client::Read(uint32_t resource_id, pw::stream::Writer& output, CompletionFunc&& on_completion, pw::transfer::ProtocolVersion version = kDefaultProtocolVersion, pw::chrono::SystemClock::duration timeout = cfg::kDefaultClientTimeout, pw::chrono::SystemClock::duration initial_chunk_timeout = cfg::kDefaultInitialChunkTimeout, uint32_t initial_offset = 0u) 205 206 Reads data from a transfer server to the specified ``pw::stream::Writer``. 207 Invokes the provided callback function with the overall status of the 208 transfer. 209 210 Due to the asynchronous nature of transfer operations, this function will only 211 return a non-OK status if it is called with bad arguments. Otherwise, it will 212 return OK and errors will be reported through the completion callback. 213 214 For using the offset parameter, please see :ref:`pw_transfer-nonzero-transfers`. 215 216.. cpp:function:: Result<pw::Transfer::Client::Handle> pw::transfer::Client::Write(uint32_t resource_id, pw::stream::Reader& input, CompletionFunc&& on_completion, pw::transfer::ProtocolVersion version = kDefaultProtocolVersion, pw::chrono::SystemClock::duration timeout = cfg::kDefaultClientTimeout, pw::chrono::SystemClock::duration initial_chunk_timeout = cfg::kDefaultInitialChunkTimeout, uint32_t initial_offset = 0u) 217 218 Writes data from a source ``pw::stream::Reader`` to a transfer server. 219 Invokes the provided callback function with the overall status of the 220 transfer. 221 222 Due to the asynchronous nature of transfer operations, this function will only 223 return a non-OK status if it is called with bad arguments. Otherwise, it will 224 return OK and errors will be reported through the completion callback. 225 226 For using the offset parameter, please see :ref:`pw_transfer-nonzero-transfers`. 227 228Transfer handles 229^^^^^^^^^^^^^^^^ 230Each transfer session initiated by a client returns a ``Handle`` object which 231is used to manage the transfer. These handles support the following operations: 232 233.. cpp:function:: pw::Transfer::Client::Handle::Cancel() 234 235 Terminates the ongoing transfer. 236 237.. cpp:function:: pw::Transfer::Client::Handle::SetTransferSize(size_t size_bytes) 238 239 In a write transfer, indicates the total size of the transfer resource. 240 241**Example client setup** 242 243.. code-block:: cpp 244 245 #include "pw_transfer/client.h" 246 247 namespace { 248 249 // RPC channel on which transfers should be run. 250 constexpr uint32_t kChannelId = 42; 251 252 // In a read transfer, the maximum number of bytes to receive at one time 253 // (potentially across multiple chunks), unless specified otherwise by the 254 // transfer's stream. Should be set reasonably high; the transfer will 255 // attempt to determine an optimal window size based on the link. 256 constexpr size_t kDefaultMaxBytesToReceive = 16384; 257 258 pw::transfer::Client transfer_client(GetSystemRpcClient(), 259 kChannelId, 260 GetSystemTransferThread(), 261 kDefaultMaxBytesToReceive); 262 263 } // namespace 264 265 Status ReadMagicBufferSync(pw::ByteSpan sink) { 266 pw::stream::Writer writer(sink); 267 268 struct { 269 pw::sync::ThreadNotification notification; 270 pw::Status status; 271 } transfer_state; 272 273 Result<pw::transfer::Client::Handle> handle = transfer_client.Read( 274 kMagicBufferResourceId, 275 writer, 276 [&transfer_state](pw::Status status) { 277 transfer_state.status = status; 278 transfer_state.notification.release(); 279 }); 280 if (!handle.ok()) { 281 return handle.status(); 282 } 283 284 // Block until the transfer completes. 285 transfer_state.notification.acquire(); 286 return transfer_state.status; 287 } 288 289Specifying Resource Sizes 290------------------------- 291Transfer data is sent and received through the ``pw::Stream`` interface, which 292does not have a concept of overall stream size. Users of transfers that are 293fixed-size may optionally indicate this to the transfer client and server, 294which will be shared with the transfer peer to enable features such as progress 295reporting. 296 297The transfer size can only be set on the transmitting side of the transfer; 298that is, the client in a ``Write`` transfer or the server in a ``Read`` 299transfer. 300 301If the specified resource size is smaller than the available transferrable data, 302only a slice of the data up to the resource size will be transferred. If the 303specified size is equal to or larger than the data size, all of the data will 304be sent. 305 306**Setting a transfer size from a transmitting client** 307 308.. code-block:: c++ 309 310 Result<pw::transfer::Client::Handle> handle = client.Write(...); 311 if (handle.ok()) { 312 handle->SetTransferSize(kMyResourceSize); 313 } 314 315**Setting a transfer size on a server resource** 316 317 The ``TransferHandler`` interface allows overriding its ``ResourceSize`` 318 function to return the size of its transfer resource. 319 320.. code-block:: c++ 321 322 class MyResourceHandler : public pw::transfer::ReadOnlyHandler { 323 public: 324 Status PrepareRead() final; 325 326 virtual size_t ResourceSize() const final { 327 return kMyResourceSize; 328 } 329 330 }; 331 332Atomic File Transfer Handler 333---------------------------- 334Transfers are handled using the generic `Handler` interface. A specialized 335`Handler`, `AtomicFileTransferHandler` is available to handle file transfers 336with atomic semantics. It guarantees that the target file of the transfer is 337always in a correct state. A temporary file is written to prior to updating the 338target file. If any transfer failure occurs, the transfer is aborted and the 339target file is either not created or not updated. 340 341.. _module-pw_transfer-config: 342 343Module Configuration Options 344---------------------------- 345The following configurations can be adjusted via compile-time configuration of 346this module, see the 347:ref:`module documentation <module-structure-compile-time-configuration>` for 348more details. 349 350.. c:macro:: PW_TRANSFER_DEFAULT_MAX_CLIENT_RETRIES 351 352 The default maximum number of times a transfer client should retry sending a 353 chunk when no response is received. Can later be configured per-transfer when 354 starting one. 355 356.. c:macro:: PW_TRANSFER_DEFAULT_MAX_SERVER_RETRIES 357 358 The default maximum number of times a transfer server should retry sending a 359 chunk when no response is received. 360 361 In typical setups, retries are driven by the client, and timeouts on the 362 server are used only to clean up resources, so this defaults to 0. 363 364.. c:macro:: PW_TRANSFER_DEFAULT_MAX_LIFETIME_RETRIES 365 366 The default maximum number of times a transfer should retry sending any chunk 367 over the course of its entire lifetime. 368 369 This number should be high, particularly if long-running transfers are 370 expected. Its purpose is to prevent transfers from getting stuck in an 371 infinite loop. 372 373.. c:macro:: PW_TRANSFER_DEFAULT_CLIENT_TIMEOUT_MS 374 375 The default amount of time, in milliseconds, to wait for a chunk to arrive 376 in a transfer client before retrying. This can later be configured 377 per-transfer. 378 379.. c:macro:: PW_TRANSFER_DEFAULT_SERVER_TIMEOUT_MS 380 381 The default amount of time, in milliseconds, to wait for a chunk to arrive 382 on the server before retrying. This can later be configured per-transfer. 383 384.. c:macro:: PW_TRANSFER_DEFAULT_INITIAL_TIMEOUT_MS 385 386 The default amount of time, in milliseconds, to wait for an initial server 387 response to a transfer before retrying. This can later be configured 388 per-transfer. 389 390 This is set separately to PW_TRANSFER_DEFAULT_TIMEOUT_MS as transfers may 391 require additional time for resource initialization (e.g. erasing a flash 392 region before writing to it). 393 394.. c:macro:: PW_TRANSFER_DEFAULT_EXTEND_WINDOW_DIVISOR 395 396 The fractional position within a window at which a receive transfer should 397 extend its window size to minimize the amount of time the transmitter 398 spends blocked. 399 400 For example, a divisor of 2 will extend the window when half of the 401 requested data has been received, a divisor of three will extend at a third 402 of the window, and so on. 403 404.. c:macro:: PW_TRANSFER_LOG_DEFAULT_CHUNKS_BEFORE_RATE_LIMIT 405 406 Number of chunks to send repetitive logs at full rate before reducing to 407 rate_limit. Retransmit parameter chunks will restart at this chunk count 408 limit. 409 Default is first 10 parameter logs will be sent, then reduced to one log 410 every ``PW_TRANSFER_RATE_PERIOD_MS`` 411 412.. c:macro:: PW_TRANSFER_LOG_DEFAULT_RATE_PERIOD_MS 413 414 The minimum time between repetative logs after the rate limit has been 415 applied (after CHUNKS_BEFORE_RATE_LIMIT parameter chunks). 416 Default is to reduce repetative logs to once every 10 seconds after 417 CHUNKS_BEFORE_RATE_LIMIT parameter chunks have been sent. 418 419.. c:macro:: PW_TRANSFER_CONFIG_LOG_LEVEL 420 421 Configurable log level for the entire transfer module. 422 423.. c:macro:: PW_TRANSFER_CONFIG_DEBUG_CHUNKS 424 425 Turns on logging of individual non-data or non-parameter chunks. Default is 426 false, to disable logging. 427 428.. c:macro:: PW_TRANSFER_CONFIG_DEBUG_DATA_CHUNKS 429 430 Turns on logging of individual data and parameter chunks. Default is false to 431 disable logging. These chunks are moderated (rate-limited) by the same 432 ``PW_TRANSFER_RATE_PERIOD_MS`` as other repetitive logs. 433 434.. c:macro:: PW_TRANSFER_EVENT_PROCESSING_TIMEOUT_MS 435 436 Maximum time to wait for a transfer event to be processed before dropping 437 further queued events. In systems which can perform long-running operations 438 to process transfer data, this can be used to prevent threads from blocking 439 for extended periods. A value of 0 results in indefinite blocking. 440 441.. _pw_transfer-nonzero-transfers: 442 443Non-zero Starting Offset Transfers 444---------------------------------- 445``pw_transfer`` provides for transfers which read from or 446write to a server resource starting from a point after the beginning. 447Handling of read/write/erase boundaries of the resource storage backend must 448be handled by the user through the transfer handler interfaces of `GetStatus` 449and `PrepareRead/Write(uint32_t offset)`. 450 451A resource can be read or written from a non-zero starting offset simply by 452having the transfer client calling `read()` or `write()` with an offset 453parameter. The offset gets included in the starting handshake. 454 455.. note:: 456 The data or stream passed to `read()` or `write()` will be used as-is. I.e. 457 no seeking will be applied; the user is expected to seek to the desired 458 location. 459 460On the server side, the offset is accepted, and passed to the transfer 461handler's `Prepare(uint32_t)` method. This method must be implemented 462specifically by the handler in order to support the offset transfer. The 463transfer handler confirms that the start offset is valid for the read/write 464operation, and the server responds with the offset to confirm the non-zero 465transfer operation. Older server sw will ignore the offset, so the clients 466check that the server has accepted the non-zero offset during the handshake, so 467users may elect to catch such errors. Clients return `Status.UNIMPLEMENTED` in 468such cases. 469 470Due to the need to seek streams by the handler to support the non-zero offset, 471it is recommended to return `Status.RESOURCE_EXHAUSTED` if a seek is requested 472past the end of the stream. 473 474See the :ref:`transfer handler <pw_transfer-transfer-server>` documentation for 475further information about configuring resources for non-zero transfers and the 476interface documentation in 477``pw/transfer/public/pw_transfer/handler.h`` 478 479Python 480====== 481.. automodule:: pw_transfer 482 :members: ProgressStats, ProtocolVersion, Manager, Error 483 484**Example** 485 486.. code-block:: python 487 488 import pw_transfer 489 490 # Initialize a Pigweed RPC client; see pw_rpc docs for more info. 491 rpc_client = CustomRpcClient() 492 rpcs = rpc_client.channel(1).rpcs 493 494 transfer_service = rpcs.pw.transfer.Transfer 495 transfer_manager = pw_transfer.Manager(transfer_service) 496 497 try: 498 # Read the transfer resource with ID 3 from the server. 499 data = transfer_manager.read(3) 500 except pw_transfer.Error as err: 501 print('Failed to read:', err.status) 502 503 try: 504 # Send some data to the server. The transfer manager does not have to be 505 # reinitialized. 506 transfer_manager.write(2, b'hello, world') 507 except pw_transfer.Error as err: 508 print('Failed to write:', err.status) 509 510Typescript 511========== 512Provides a simple interface for transferring bulk data over pw_rpc. 513 514**Example** 515 516.. code-block:: typescript 517 518 import { pw_transfer } from 'pigweedjs'; 519 const { Manager } from pw_transfer; 520 521 const client = new CustomRpcClient(); 522 service = client.channel()!.service('pw.transfer.Transfer')!; 523 524 const manager = new Manager(service, DEFAULT_TIMEOUT_S); 525 526 manager.read(3, (stats: ProgressStats) => { 527 console.log(`Progress Update: ${stats}`); 528 }).then((data: Uint8Array) => { 529 console.log(`Completed read: ${data}`); 530 }).catch(error => { 531 console.log(`Failed to read: ${error.status}`); 532 }); 533 534 manager.write(2, textEncoder.encode('hello world')) 535 .catch(error => { 536 console.log(`Failed to read: ${error.status}`); 537 }); 538 539Java 540==== 541pw_transfer provides a Java client. The transfer client returns a 542`ListenableFuture <https://guava.dev/releases/21.0/api/docs/com/google/common/util/concurrent/ListenableFuture>`_ 543to represent the results of a read or write transfer. 544 545.. code-block:: java 546 547 import dev.pigweed.pw_transfer.TransferClient; 548 549 public class TheClass { 550 public void DoTransfer(MethodClient transferReadMethodClient, 551 MethodClient transferWriteMethodClient) { 552 // Create a new transfer client. 553 TransferClient client = new TransferClient( 554 transferReadMethodClient, 555 transferWriteMethodClient, 556 TransferTimeoutSettings.builder() 557 .setTimeoutMillis(TRANSFER_TIMEOUT_MS) 558 .setMaxRetries(MAX_RETRIES) 559 .build()); 560 561 // Start a read transfer. 562 ListenableFuture<byte[]> readTransfer = client.read(123); 563 564 // Start a write transfer. 565 ListenableFuture<Void> writeTransfer = client.write(123, dataToWrite); 566 567 // Get the data from the read transfer. 568 byte[] readData = readTransfer.get(); 569 570 // Wait for the write transfer to complete. 571 writeTransfer.get(); 572 } 573 } 574 575-------- 576Protocol 577-------- 578 579Chunks 580====== 581Transfers run as a series of *chunks* exchanged over an RPC stream. Chunks can 582contain transferable data, metadata, and control parameters. Each chunk has an 583associated type, which determines what information it holds and the semantics of 584its fields. 585 586The chunk is a protobuf message, whose definition can be found 587:ref:`here <module-pw_transfer-proto-definition>`. 588 589Resources and sessions 590====================== 591Transfers are run for a specific *resource* --- a stream of data which can be 592read from or written to. Resources have a system-specific integral identifier 593defined by the implementers of the server-side transfer node. 594 595The series of chunks exchanged in an individual transfer operation for a 596resource constitute a transfer *session*. The session runs from its opening 597chunk until either a terminating chunk is received or the transfer times out. 598Sessions are assigned IDs by the client that starts them, which are unique over 599the RPC channel between the client and server, allowing the server to identify 600transfers across multiple clients. 601 602Reliability 603=========== 604``pw_transfer`` attempts to be a reliable data transfer protocol. 605 606As Pigweed RPC is considered an unreliable communications system, 607``pw_transfer`` implements its own mechanisms for reliability. These include 608timeouts, data retransmissions, and handshakes. 609 610.. note:: 611 612 A transfer can only be reliable if its underlying data stream is seekable. 613 A non-seekable stream could prematurely terminate a transfer following a 614 packet drop. 615 616At present, ``pw_transfer`` requires in-order data transmission. If packets are 617received out-of-order, the receiver will request that the transmitter re-send 618data from the last received position. 619 620Opening handshake 621================= 622Transfers begin with a three-way handshake, whose purpose is to identify the 623resource being transferred, assign a session ID, and synchronize the protocol 624version to use. 625 626A read or write transfer for a resource is initiated by a transfer client. The 627client sends the ID of the resource to the server alongside a unique session ID 628in a ``START`` chunk, indicating that it wishes to begin a new transfer. This 629chunk additionally encodes the protocol version which the client is configured 630to use. 631 632Upon receiving a ``START`` chunk, the transfer server checks whether the 633requested resource is available. If so, it prepares the resource for the 634operation, which typically involves opening a data stream, alongside any 635additional user-specified setup. The server accepts the client's session ID, 636then responds to the client with a ``START_ACK`` chunk containing the resource, 637session, and configured protocol version for the transfer. 638 639.. _module-pw_transfer-windowing: 640 641Windowing 642========= 643Throughout a transfer, the receiver maintains a window of how much data it can 644receive at a given time. This window is a multiple of the maximum size of a 645single data chunk, and is adjusted dynamically in response to the ongoing status 646of the transfer. 647 648pw_transfer uses a congestion control algorithm similar to that of TCP 649`(RFC 5681 §3.1) <https://datatracker.ietf.org/doc/html/rfc5681#section-3.1>`_, 650adapted to pw_transfer's mode of operation that tunes parameters per window. 651 652Once a portion of a window has successfully been received, it is acknowledged by 653the receiver and the window size is extended. Transfers begin in a "slow start" 654phase, during which the window is doubled on each ACK. This continues until the 655transfer detects a packet loss or times out. Once this occurs, the window size 656is halved and the transfer enters a "congestion avoidance" phase for the 657remainder of its run. During this phase, successful ACKs increase the window 658size by a single chunk, whereas packet loss continues to half it. 659 660Transfer completion 661=================== 662Either side of a transfer can terminate the operation at any time by sending a 663``COMPLETION`` chunk containing the final status of the transfer. When a 664``COMPLETION`` chunk is sent, the terminator of the transfer performs local 665cleanup, then waits for its peer to acknowledge the completion. 666 667Upon receving a ``COMPLETION`` chunk, the transfer peer cancels any pending 668operations, runs its set of cleanups, and responds with a ``COMPLETION_ACK``, 669fully ending the session from the peer's side. 670 671The terminator's session remains active waiting for a ``COMPLETION_ACK``. If not 672received after a timeout, it re-sends its ``COMPLETION`` chunk. The session ends 673either following receipt of the acknowledgement or if a maximum number of 674retries is hit. 675 676.. _module-pw_transfer-proto-definition: 677 678Server to client transfer (read) 679================================ 680.. image:: https://storage.googleapis.com/pigweed-media/pw_transfer/read.svg 681 682Client to server transfer (write) 683================================= 684.. image:: https://storage.googleapis.com/pigweed-media/pw_transfer/write.svg 685 686Protocol buffer definition 687========================== 688.. literalinclude:: transfer.proto 689 :language: protobuf 690 :lines: 14- 691 692Errors 693====== 694 695Protocol errors 696--------------- 697The following table describes the meaning of each status code when sent by the 698sender or the receiver (see `Transfer roles`_). 699 700.. cpp:namespace-push:: pw::stream 701 702+-------------------------+-------------------------+-------------------------+ 703| Status | Sent by sender | Sent by receiver | 704+=========================+=========================+=========================+ 705| ``OK`` | (not sent) | All data was received | 706| | | and handled | 707| | | successfully. | 708+-------------------------+-------------------------+-------------------------+ 709| ``ABORTED`` | The service aborted the transfer because the | 710| | client restarted it. This status is passed to the | 711| | transfer handler, but not sent to the client | 712| | because it restarted the transfer. | 713+-------------------------+---------------------------------------------------+ 714| ``CANCELLED`` | The client cancelled the transfer. | 715+-------------------------+-------------------------+-------------------------+ 716| ``DATA_LOSS`` | Failed to read the data | Failed to write the | 717| | to send. The | received data. The | 718| | :cpp:class:`Reader` | :cpp:class:`Writer` | 719| | returned an error. | returned an error. | 720+-------------------------+-------------------------+-------------------------+ 721| ``FAILED_PRECONDITION`` | Received chunk for transfer that is not active. | 722+-------------------------+-------------------------+-------------------------+ 723| ``INVALID_ARGUMENT`` | Received a malformed packet. | 724+-------------------------+-------------------------+-------------------------+ 725| ``INTERNAL`` | An assumption of the protocol was violated. | 726| | Encountering ``INTERNAL`` indicates that there is | 727| | a bug in the service or client implementation. | 728+-------------------------+-------------------------+-------------------------+ 729| ``PERMISSION_DENIED`` | The transfer does not support the requested | 730| | operation (either reading or writing). | 731+-------------------------+-------------------------+-------------------------+ 732| ``RESOURCE_EXHAUSTED`` | The receiver requested | Storage is full. | 733| | zero bytes, indicating | | 734| | their storage is full, | | 735| | but there is still data | | 736| | to send. | | 737+-------------------------+-------------------------+-------------------------+ 738| ``UNAVAILABLE`` | The service is busy with other transfers and | 739| | cannot begin a new transfer at this time. | 740+-------------------------+-------------------------+-------------------------+ 741| ``UNIMPLEMENTED`` | Out-of-order chunk was | (not sent) | 742| | requested, but seeking | | 743| | is not supported. | | 744+-------------------------+-------------------------+-------------------------+ 745 746.. cpp:namespace-pop:: 747 748 749Transfer roles 750============== 751Every transfer has two participants: the sender and the receiver. The sender 752transmits data to the receiver. The receiver controls how the data is 753transferred and sends the final status when the transfer is complete. 754 755In read transfers, the client is the receiver and the service is the sender. In 756write transfers, the client is the sender and the service is the receiver. 757 758Sender flow 759----------- 760.. mermaid:: 761 762 graph TD 763 start([Client initiates<br>transfer]) -->data_request 764 data_request[Receive transfer<br>parameters]-->send_chunk 765 766 send_chunk[Send chunk]-->sent_all 767 768 sent_all{Sent final<br>chunk?} -->|yes|wait 769 sent_all-->|no|sent_requested 770 771 sent_requested{Sent all<br>pending?}-->|yes|data_request 772 sent_requested-->|no|send_chunk 773 774 wait[Wait for receiver]-->is_done 775 776 is_done{Received<br>final chunk?}-->|yes|done 777 is_done-->|no|data_request 778 779 done([Transfer complete]) 780 781Receiver flow 782------------- 783.. mermaid:: 784 785 graph TD 786 start([Client initiates<br>transfer]) -->request_bytes 787 request_bytes[Set transfer<br>parameters]-->wait 788 789 wait[Wait for chunk]-->received_chunk 790 791 received_chunk{Received<br>chunk by<br>deadline?}-->|no|request_bytes 792 received_chunk-->|yes|check_chunk 793 794 check_chunk{Correct<br>offset?} -->|yes|process_chunk 795 check_chunk --> |no|request_bytes 796 797 process_chunk[Process chunk]-->final_chunk 798 799 final_chunk{Final<br>chunk?}-->|yes|signal_completion 800 final_chunk{Final<br>chunk?}-->|no|received_requested 801 802 received_requested{Received all<br>pending?}-->|yes|request_bytes 803 received_requested-->|no|wait 804 805 signal_completion[Signal completion]-->done 806 807 done([Transfer complete]) 808 809Legacy protocol 810=============== 811``pw_transfer`` was initially released into production prior to several of the 812reliability improvements of its modern protocol. As a result of this, transfer 813implementations support a "legacy" protocol mode, in which transfers run without 814utilizing these features. 815 816The primary differences between the legacy and modern protocols are listed 817below. 818 819- There is no distinction between a transfer resource and session --- a single 820 ``transfer_id`` field represents both. Only one transfer for a given resource 821 can run at a time, and it is not possible to determine where one transfer for 822 a resource ends and the next begins. 823- The legacy protocol has no opening handshake phase. The client initiates with 824 a transfer ID and starting transfer parameters (during a read), and the data 825 transfer phase begins immediately. 826- The legacy protocol has no terminating handshake phase. When either end 827 completes a transfer by sending a status chunk, it does not wait for the peer 828 to acknowledge. Resources used by the transfer are immediately freed, and 829 there is no guarantee that the peer is notified of completion. 830 831Transfer clients request the latest transfer protocol version by default, but 832may be configured to request the legacy protocol. Transfer server and client 833implementations detect if their transfer peer is running the legacy protocol and 834automatically switch to it if required, even if they requested a newer protocol 835version. It is **strongly** unadvised to use the legacy protocol in new code. 836 837.. _module-pw_transfer-integration-tests: 838 839----------------- 840Integration tests 841----------------- 842The ``pw_transfer`` module has a set of integration tests that verify the 843correctness of implementations in different languages. 844`Test source code <https://cs.pigweed.dev/pigweed/+/main:pw_transfer/integration_test/>`_. 845 846To run the tests on your machine, run 847 848.. code-block:: bash 849 850 $ bazel test \ 851 pw_transfer/integration_test:cross_language_small_test \ 852 pw_transfer/integration_test:cross_language_medium_test 853 854.. note:: There is a large test that tests transfers that are megabytes in size. 855 These are not run automatically, but can be run manually via the 856 ``pw_transfer/integration_test:cross_language_large_test`` test. These are 857 VERY slow, but exist for manual validation of real-world use cases. 858 859The integration tests permit injection of client/server/proxy binaries to use 860when running the tests. This allows manual testing of older versions of 861pw_transfer against newer versions. 862 863.. code-block:: bash 864 865 # Test a newer version of pw_transfer against an old C++ client that was 866 # backed up to another directory. 867 $ bazel run pw_transfer/integration_test:cross_language_medium_test -- \ 868 --cpp-client-binary ../old_pw_transfer_version/cpp_client 869 870Backwards compatibility tests 871============================= 872``pw_transfer`` includes a `suite of backwards-compatibility tests 873<https://cs.pigweed.dev/pigweed/+/main:pw_transfer/integration_test/legacy_binaries_test.py>`_ 874that are intended to continuously validate a degree of backwards-compatibility 875with older pw_transfer servers and clients. This is done by retrieving older 876binaries hosted in CIPD and running tests between the older client/server 877binaries and the latest binaries. 878 879The CIPD package contents can be created with this command: 880 881.. code-block::bash 882 883 $ bazel build pw_transfer/integration_test:server \ 884 pw_transfer/integration_test:cpp_client 885 $ mkdir pw_transfer_test_binaries 886 $ cp bazel-bin/pw_transfer/integration_test/server \ 887 pw_transfer_test_binaries 888 $ cp bazel-bin/pw_transfer/integration_test/cpp_client \ 889 pw_transfer_test_binaries 890 891To update the CIPD package itself, follow the `internal documentation for 892updating a CIPD package <http://go/pigweed-cipd#installing-packages-into-cipd>`_. 893 894CI/CQ integration 895================= 896`Current status of the test in CI <https://ci.chromium.org/ui/p/pigweed/builders/luci.pigweed.pigweed.ci/pigweed-linux-bzl-integration>`_. 897 898By default, these tests are not run in CQ (on presubmit) because they are too 899slow. However, you can request that the tests be run in presubmit on your 900change by adding to following line to the commit message footer: 901 902.. code-block:: 903 904 Cq-Include-Trybots: luci.pigweed.try:pigweed-linux-bzl-integration 905 906.. _module-pw_transfer-parallel-tests: 907 908Running the tests many times 909============================ 910Because the tests bind to network ports, you cannot run more than one instance 911of each test in parallel. However, you might want to do so, e.g. to debug 912flakes. This section describes a manual process that makes this possible. 913 914Linux 915----- 916On Linux, you can add the ``"block-network"`` tag to the tests (`example 917<https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/181297>`_). This 918enables network isolation for the tests, allowing you to run them in parallel 919via, 920 921.. code-block:: 922 923 bazel test --runs_per_test=10 //pw_transfer/integration_tests/... 924 925MacOS 926----- 927Network isolation is not supported on MacOS because the OS doesn't support 928network virtualization (`gh#2669 929<https://github.com/bazelbuild/bazel/issues/2669>`_). The best you can do is to 930tag the tests ``"exclusive"``. This allows you to use ``--runs_per_test``, but 931will force each test to run by itself, with no parallelism. 932 933Why is this manual? 934------------------- 935Ideally, we would apply either the ``"block-network"`` or ``"exclusive"`` tag 936to the tests depending on the OS. But this is not supported, `gh#2971 937<https://github.com/bazelbuild/bazel/issues/2971>`_. 938 939We don't want to tag the tests ``"exclusive"`` by default because that will 940prevent *different* tests from running in parallel, significantly slowing them 941down. 942 943.. toctree:: 944 :hidden: 945 946 API reference <api> 947