1.. _module-pw_transfer: 2 3=========== 4pw_transfer 5=========== 6``pw_transfer`` is a reliable data transfer protocol which runs on top of 7Pigweed RPC. 8 9.. attention:: 10 11 ``pw_transfer`` is under construction and so is its documentation. 12 13----- 14Usage 15----- 16 17C++ 18=== 19 20Transfer thread 21--------------- 22To run transfers as either a client or server (or both), a dedicated thread is 23required. The transfer thread is used to process all transfer-related events 24safely. The same transfer thread can be shared by a transfer client and service 25running on the same system. 26 27.. note:: 28 29 All user-defined transfer callbacks (i.e. the virtual interface of a 30 ``Handler`` or completion function in a transfer client) will be 31 invoked from the transfer thread's context. 32 33In order to operate, a transfer thread requires two buffers: 34 35- The first is a *chunk buffer*. This is used to stage transfer packets received 36 by the RPC system to be processed by the transfer thread. It must be large 37 enough to store the largest possible chunk the system supports. 38 39- The second is an *encode buffer*. This is used by the transfer thread to 40 encode outgoing RPC packets. It is necessarily larger than the chunk buffer. 41 Typically, this is sized to the system's maximum transmission unit at the 42 transport layer. 43 44A transfer thread is created by instantiating a ``pw::transfer::Thread``. This 45class derives from ``pw::thread::ThreadCore``, allowing it to directly be used 46when creating a system thread. Refer to :ref:`module-pw_thread-thread-creation` 47for additional information. 48 49**Example thread configuration** 50 51.. code-block:: cpp 52 53 #include "pw_transfer/transfer_thread.h" 54 55 namespace { 56 57 // The maximum number of concurrent transfers the thread should support as 58 // either a client or a server. These can be set to 0 (if only using one or 59 // the other). 60 constexpr size_t kMaxConcurrentClientTransfers = 5; 61 constexpr size_t kMaxConcurrentServerTransfers = 3; 62 63 // The maximum payload size that can be transmitted by the system's 64 // transport stack. This would typically be defined within some transport 65 // header. 66 constexpr size_t kMaxTransmissionUnit = 512; 67 68 // The maximum amount of data that should be sent within a single transfer 69 // packet. By necessity, this should be less than the max transmission unit. 70 // 71 // pw_transfer requires some additional per-packet overhead, so the actual 72 // amount of data it sends may be lower than this. 73 constexpr size_t kMaxTransferChunkSizeBytes = 480; 74 75 // Buffers for storing and encoding chunks (see documentation above). 76 std::array<std::byte, kMaxTransferChunkSizeBytes> chunk_buffer; 77 std::array<std::byte, kMaxTransmissionUnit> encode_buffer; 78 79 pw::transfer::Thread<kMaxConcurrentClientTransfers, 80 kMaxConcurrentServerTransfers> 81 transfer_thread(chunk_buffer, encode_buffer); 82 83 } // namespace 84 85 // pw::transfer::TransferThread is the generic, non-templated version of the 86 // Thread class. A Thread can implicitly convert to a TransferThread. 87 pw::transfer::TransferThread& GetSystemTransferThread() { 88 return transfer_thread; 89 } 90 91.. _pw_transfer-transfer-server: 92 93Transfer server 94--------------- 95``pw_transfer`` provides an RPC service for running transfers through an RPC 96server. 97 98To know how to read data from or write data to device, a ``Handler`` interface 99is defined (``pw_transfer/public/pw_transfer/handler.h``). Transfer handlers 100represent a transferable resource, wrapping a stream reader and/or writer with 101initialization and completion code. Custom transfer handler implementations 102should derive from ``ReadOnlyHandler``, ``WriteOnlyHandler``, or 103``ReadWriteHandler`` as appropriate and override Prepare and Finalize methods 104if necessary. 105 106A transfer handler should be implemented and instantiated for each unique 107resource that can be transferred to or from a device. Each instantiated handler 108must have a globally-unique integer ID used to identify the resource. 109 110Handlers are registered with the transfer service. This may be done during 111system initialization (for static resources), or dynamically at runtime to 112support ephemeral transfer resources. 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 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.. _pw_transfer-nonzero-transfers: 435 436Non-zero Starting Offset Transfers 437---------------------------------- 438``pw_transfer`` provides for transfers which read from or 439write to a server resource starting from a point after the beginning. 440Handling of read/write/erase boundaries of the resource storage backend must 441be handled by the user through the transfer handler interfaces of `GetStatus` 442and `PrepareRead/Write(uint32_t offset)`. 443 444A resource can be read or written from a non-zero starting offset simply by 445having the transfer client calling `read()` or `write()` with an offset 446parameter. The offset gets included in the starting handshake. 447 448.. note:: 449 The data or stream passed to `read()` or `write()` will be used as-is. I.e. 450 no seeking will be applied; the user is expected to seek to the desired 451 location. 452 453On the server side, the offset is accepted, and passed to the transfer 454handler's `Prepare(uint32_t)` method. This method must be implemented 455specifically by the handler in order to support the offset transfer. The 456transfer handler confirms that the start offset is valid for the read/write 457operation, and the server responds with the offset to confirm the non-zero 458transfer operation. Older server sw will ignore the offset, so the clients 459check that the server has accepted the non-zero offset during the handshake, so 460users may elect to catch such errors. Clients return `Status.UNIMPLEMENTED` in 461such cases. 462 463Due to the need to seek streams by the handler to support the non-zero offset, 464it is recommended to return `Status.RESOURCE_EXHAUSTED` if a seek is requested 465past the end of the stream. 466 467See the :ref:`transfer handler <pw_transfer-transfer-server>` documentation for 468further information about configuring resources for non-zero transfers and the 469interface documentation in 470``pw/transfer/public/pw_transfer/handler.h`` 471 472Python 473====== 474.. automodule:: pw_transfer 475 :members: ProgressStats, ProtocolVersion, Manager, Error 476 477**Example** 478 479.. code-block:: python 480 481 import pw_transfer 482 483 # Initialize a Pigweed RPC client; see pw_rpc docs for more info. 484 rpc_client = CustomRpcClient() 485 rpcs = rpc_client.channel(1).rpcs 486 487 transfer_service = rpcs.pw.transfer.Transfer 488 transfer_manager = pw_transfer.Manager(transfer_service) 489 490 try: 491 # Read the transfer resource with ID 3 from the server. 492 data = transfer_manager.read(3) 493 except pw_transfer.Error as err: 494 print('Failed to read:', err.status) 495 496 try: 497 # Send some data to the server. The transfer manager does not have to be 498 # reinitialized. 499 transfer_manager.write(2, b'hello, world') 500 except pw_transfer.Error as err: 501 print('Failed to write:', err.status) 502 503Typescript 504========== 505Provides a simple interface for transferring bulk data over pw_rpc. 506 507**Example** 508 509.. code-block:: typescript 510 511 import { pw_transfer } from 'pigweedjs'; 512 const { Manager } from pw_transfer; 513 514 const client = new CustomRpcClient(); 515 service = client.channel()!.service('pw.transfer.Transfer')!; 516 517 const manager = new Manager(service, DEFAULT_TIMEOUT_S); 518 519 manager.read(3, (stats: ProgressStats) => { 520 console.log(`Progress Update: ${stats}`); 521 }).then((data: Uint8Array) => { 522 console.log(`Completed read: ${data}`); 523 }).catch(error => { 524 console.log(`Failed to read: ${error.status}`); 525 }); 526 527 manager.write(2, textEncoder.encode('hello world')) 528 .catch(error => { 529 console.log(`Failed to read: ${error.status}`); 530 }); 531 532Java 533==== 534pw_transfer provides a Java client. The transfer client returns a 535`ListenableFuture <https://guava.dev/releases/21.0/api/docs/com/google/common/util/concurrent/ListenableFuture>`_ 536to represent the results of a read or write transfer. 537 538.. code-block:: java 539 540 import dev.pigweed.pw_transfer.TransferClient; 541 542 public class TheClass { 543 public void DoTransfer(MethodClient transferReadMethodClient, 544 MethodClient transferWriteMethodClient) { 545 // Create a new transfer client. 546 TransferClient client = new TransferClient( 547 transferReadMethodClient, 548 transferWriteMethodClient, 549 TransferTimeoutSettings.builder() 550 .setTimeoutMillis(TRANSFER_TIMEOUT_MS) 551 .setMaxRetries(MAX_RETRIES) 552 .build()); 553 554 // Start a read transfer. 555 ListenableFuture<byte[]> readTransfer = client.read(123); 556 557 // Start a write transfer. 558 ListenableFuture<Void> writeTransfer = client.write(123, dataToWrite); 559 560 // Get the data from the read transfer. 561 byte[] readData = readTransfer.get(); 562 563 // Wait for the write transfer to complete. 564 writeTransfer.get(); 565 } 566 } 567 568-------- 569Protocol 570-------- 571 572Chunks 573====== 574Transfers run as a series of *chunks* exchanged over an RPC stream. Chunks can 575contain transferable data, metadata, and control parameters. Each chunk has an 576associated type, which determines what information it holds and the semantics of 577its fields. 578 579The chunk is a protobuf message, whose definition can be found 580:ref:`here <module-pw_transfer-proto-definition>`. 581 582Resources and sessions 583====================== 584Transfers are run for a specific *resource* --- a stream of data which can be 585read from or written to. Resources have a system-specific integral identifier 586defined by the implementers of the server-side transfer node. 587 588The series of chunks exchanged in an individual transfer operation for a 589resource constitute a transfer *session*. The session runs from its opening 590chunk until either a terminating chunk is received or the transfer times out. 591Sessions are assigned IDs by the client that starts them, which are unique over 592the RPC channel between the client and server, allowing the server to identify 593transfers across multiple clients. 594 595Reliability 596=========== 597``pw_transfer`` attempts to be a reliable data transfer protocol. 598 599As Pigweed RPC is considered an unreliable communications system, 600``pw_transfer`` implements its own mechanisms for reliability. These include 601timeouts, data retransmissions, and handshakes. 602 603.. note:: 604 605 A transfer can only be reliable if its underlying data stream is seekable. 606 A non-seekable stream could prematurely terminate a transfer following a 607 packet drop. 608 609Opening handshake 610================= 611Transfers begin with a three-way handshake, whose purpose is to identify the 612resource being transferred, assign a session ID, and synchronize the protocol 613version to use. 614 615A read or write transfer for a resource is initiated by a transfer client. The 616client sends the ID of the resource to the server alongside a unique session ID 617in a ``START`` chunk, indicating that it wishes to begin a new transfer. This 618chunk additionally encodes the protocol version which the client is configured 619to use. 620 621Upon receiving a ``START`` chunk, the transfer server checks whether the 622requested resource is available. If so, it prepares the resource for the 623operation, which typically involves opening a data stream, alongside any 624additional user-specified setup. The server accepts the client's session ID, 625then responds to the client with a ``START_ACK`` chunk containing the resource, 626session, and configured protocol version for the transfer. 627 628.. _module-pw_transfer-windowing: 629 630Windowing 631========= 632Throughout a transfer, the receiver maintains a window of how much data it can 633receive at a given time. This window is a multiple of the maximum size of a 634single data chunk, and is adjusted dynamically in response to the ongoing status 635of the transfer. 636 637pw_transfer uses a congestion control algorithm similar to that of TCP 638`(RFC 5681 §3.1) <https://datatracker.ietf.org/doc/html/rfc5681#section-3.1>`_, 639adapted to pw_transfer's mode of operation that tunes parameters per window. 640 641Once a portion of a window has successfully been received, it is acknowledged by 642the reciever and the window size is extended. Transfers begin in a "slow start" 643phase, during which the window is doubled on each ACK. This continues until the 644transfer detects a packet loss. Once this occurs, the window size is halved and 645the transfer enters a "congestion avoidance" phase for the remainder of its run. 646During this phase, successful ACKs increase the window size by a single chunk, 647whereas packet loss continues to half it. 648 649Transfer completion 650=================== 651Either side of a transfer can terminate the operation at any time by sending a 652``COMPLETION`` chunk containing the final status of the transfer. When a 653``COMPLETION`` chunk is sent, the terminator of the transfer performs local 654cleanup, then waits for its peer to acknowledge the completion. 655 656Upon receving a ``COMPLETION`` chunk, the transfer peer cancels any pending 657operations, runs its set of cleanups, and responds with a ``COMPLETION_ACK``, 658fully ending the session from the peer's side. 659 660The terminator's session remains active waiting for a ``COMPLETION_ACK``. If not 661received after a timeout, it re-sends its ``COMPLETION`` chunk. The session ends 662either following receipt of the acknowledgement or if a maximum number of 663retries is hit. 664 665.. _module-pw_transfer-proto-definition: 666 667Server to client transfer (read) 668================================ 669.. image:: read.svg 670 671Client to server transfer (write) 672================================= 673.. image:: write.svg 674 675Protocol buffer definition 676========================== 677.. literalinclude:: transfer.proto 678 :language: protobuf 679 :lines: 14- 680 681Errors 682====== 683 684Protocol errors 685--------------- 686The following table describes the meaning of each status code when sent by the 687sender or the receiver (see `Transfer roles`_). 688 689.. cpp:namespace-push:: pw::stream 690 691+-------------------------+-------------------------+-------------------------+ 692| Status | Sent by sender | Sent by receiver | 693+=========================+=========================+=========================+ 694| ``OK`` | (not sent) | All data was received | 695| | | and handled | 696| | | successfully. | 697+-------------------------+-------------------------+-------------------------+ 698| ``ABORTED`` | The service aborted the transfer because the | 699| | client restarted it. This status is passed to the | 700| | transfer handler, but not sent to the client | 701| | because it restarted the transfer. | 702+-------------------------+---------------------------------------------------+ 703| ``CANCELLED`` | The client cancelled the transfer. | 704+-------------------------+-------------------------+-------------------------+ 705| ``DATA_LOSS`` | Failed to read the data | Failed to write the | 706| | to send. The | received data. The | 707| | :cpp:class:`Reader` | :cpp:class:`Writer` | 708| | returned an error. | returned an error. | 709+-------------------------+-------------------------+-------------------------+ 710| ``FAILED_PRECONDITION`` | Received chunk for transfer that is not active. | 711+-------------------------+-------------------------+-------------------------+ 712| ``INVALID_ARGUMENT`` | Received a malformed packet. | 713+-------------------------+-------------------------+-------------------------+ 714| ``INTERNAL`` | An assumption of the protocol was violated. | 715| | Encountering ``INTERNAL`` indicates that there is | 716| | a bug in the service or client implementation. | 717+-------------------------+-------------------------+-------------------------+ 718| ``PERMISSION_DENIED`` | The transfer does not support the requested | 719| | operation (either reading or writing). | 720+-------------------------+-------------------------+-------------------------+ 721| ``RESOURCE_EXHAUSTED`` | The receiver requested | Storage is full. | 722| | zero bytes, indicating | | 723| | their storage is full, | | 724| | but there is still data | | 725| | to send. | | 726+-------------------------+-------------------------+-------------------------+ 727| ``UNAVAILABLE`` | The service is busy with other transfers and | 728| | cannot begin a new transfer at this time. | 729+-------------------------+-------------------------+-------------------------+ 730| ``UNIMPLEMENTED`` | Out-of-order chunk was | (not sent) | 731| | requested, but seeking | | 732| | is not supported. | | 733+-------------------------+-------------------------+-------------------------+ 734 735.. cpp:namespace-pop:: 736 737 738Transfer roles 739============== 740Every transfer has two participants: the sender and the receiver. The sender 741transmits data to the receiver. The receiver controls how the data is 742transferred and sends the final status when the transfer is complete. 743 744In read transfers, the client is the receiver and the service is the sender. In 745write transfers, the client is the sender and the service is the receiver. 746 747Sender flow 748----------- 749.. mermaid:: 750 751 graph TD 752 start([Client initiates<br>transfer]) -->data_request 753 data_request[Receive transfer<br>parameters]-->send_chunk 754 755 send_chunk[Send chunk]-->sent_all 756 757 sent_all{Sent final<br>chunk?} -->|yes|wait 758 sent_all-->|no|sent_requested 759 760 sent_requested{Sent all<br>pending?}-->|yes|data_request 761 sent_requested-->|no|send_chunk 762 763 wait[Wait for receiver]-->is_done 764 765 is_done{Received<br>final chunk?}-->|yes|done 766 is_done-->|no|data_request 767 768 done([Transfer complete]) 769 770Receiver flow 771------------- 772.. mermaid:: 773 774 graph TD 775 start([Client initiates<br>transfer]) -->request_bytes 776 request_bytes[Set transfer<br>parameters]-->wait 777 778 wait[Wait for chunk]-->received_chunk 779 780 received_chunk{Received<br>chunk by<br>deadline?}-->|no|request_bytes 781 received_chunk-->|yes|check_chunk 782 783 check_chunk{Correct<br>offset?} -->|yes|process_chunk 784 check_chunk --> |no|request_bytes 785 786 process_chunk[Process chunk]-->final_chunk 787 788 final_chunk{Final<br>chunk?}-->|yes|signal_completion 789 final_chunk{Final<br>chunk?}-->|no|received_requested 790 791 received_requested{Received all<br>pending?}-->|yes|request_bytes 792 received_requested-->|no|wait 793 794 signal_completion[Signal completion]-->done 795 796 done([Transfer complete]) 797 798Legacy protocol 799=============== 800``pw_transfer`` was initially released into production prior to several of the 801reliability improvements of its modern protocol. As a result of this, transfer 802implementations support a "legacy" protocol mode, in which transfers run without 803utilizing these features. 804 805The primary differences between the legacy and modern protocols are listed 806below. 807 808- There is no distinction between a transfer resource and session --- a single 809 ``transfer_id`` field represents both. Only one transfer for a given resource 810 can run at a time, and it is not possible to determine where one transfer for 811 a resource ends and the next begins. 812- The legacy protocol has no opening handshake phase. The client initiates with 813 a transfer ID and starting transfer parameters (during a read), and the data 814 transfer phase begins immediately. 815- The legacy protocol has no terminating handshake phase. When either end 816 completes a transfer by sending a status chunk, it does not wait for the peer 817 to acknowledge. Resources used by the transfer are immediately freed, and 818 there is no guarantee that the peer is notified of completion. 819 820Transfer clients request the latest transfer protocol version by default, but 821may be configured to request the legacy protocol. Transfer server and client 822implementations detect if their transfer peer is running the legacy protocol and 823automatically switch to it if required, even if they requested a newer protocol 824version. It is **strongly** unadvised to use the legacy protocol in new code. 825 826.. _module-pw_transfer-integration-tests: 827 828----------------- 829Integration tests 830----------------- 831The ``pw_transfer`` module has a set of integration tests that verify the 832correctness of implementations in different languages. 833`Test source code <https://cs.pigweed.dev/pigweed/+/main:pw_transfer/integration_test/>`_. 834 835To run the tests on your machine, run 836 837.. code-block:: bash 838 839 $ bazel test \ 840 pw_transfer/integration_test:cross_language_small_test \ 841 pw_transfer/integration_test:cross_language_medium_test 842 843.. note:: There is a large test that tests transfers that are megabytes in size. 844 These are not run automatically, but can be run manually via the 845 ``pw_transfer/integration_test:cross_language_large_test`` test. These are 846 VERY slow, but exist for manual validation of real-world use cases. 847 848The integration tests permit injection of client/server/proxy binaries to use 849when running the tests. This allows manual testing of older versions of 850pw_transfer against newer versions. 851 852.. code-block:: bash 853 854 # Test a newer version of pw_transfer against an old C++ client that was 855 # backed up to another directory. 856 $ bazel run pw_transfer/integration_test:cross_language_medium_test -- \ 857 --cpp-client-binary ../old_pw_transfer_version/cpp_client 858 859Backwards compatibility tests 860============================= 861``pw_transfer`` includes a `suite of backwards-compatibility tests 862<https://cs.pigweed.dev/pigweed/+/main:pw_transfer/integration_test/legacy_binaries_test.py>`_ 863that are intended to continuously validate a degree of backwards-compatibility 864with older pw_transfer servers and clients. This is done by retrieving older 865binaries hosted in CIPD and running tests between the older client/server 866binaries and the latest binaries. 867 868The CIPD package contents can be created with this command: 869 870.. code-block::bash 871 872 $ bazel build pw_transfer/integration_test:server \ 873 pw_transfer/integration_test:cpp_client 874 $ mkdir pw_transfer_test_binaries 875 $ cp bazel-bin/pw_transfer/integration_test/server \ 876 pw_transfer_test_binaries 877 $ cp bazel-bin/pw_transfer/integration_test/cpp_client \ 878 pw_transfer_test_binaries 879 880To update the CIPD package itself, follow the `internal documentation for 881updating a CIPD package <go/pigweed-cipd#installing-packages-into-cipd>`_. 882 883CI/CQ integration 884================= 885`Current status of the test in CI <https://ci.chromium.org/ui/p/pigweed/builders/luci.pigweed.pigweed.ci/pigweed-linux-bzl-integration>`_. 886 887By default, these tests are not run in CQ (on presubmit) because they are too 888slow. However, you can request that the tests be run in presubmit on your 889change by adding to following line to the commit message footer: 890 891.. code-block:: 892 893 Cq-Include-Trybots: luci.pigweed.try:pigweed-linux-bzl-integration 894 895.. _module-pw_transfer-parallel-tests: 896 897Running the tests many times 898============================ 899Because the tests bind to network ports, you cannot run more than one instance 900of each test in parallel. However, you might want to do so, e.g. to debug 901flakes. This section describes a manual process that makes this possible. 902 903Linux 904----- 905On Linux, you can add the ``"block-network"`` tag to the tests (`example 906<https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/181297>`_). This 907enables network isolation for the tests, allowing you to run them in parallel 908via, 909 910.. code-block:: 911 912 bazel test --runs_per_test=10 //pw_transfer/integration_tests/... 913 914MacOS 915----- 916Network isolation is not supported on MacOS because the OS doesn't support 917network virtualization (`gh#2669 918<https://github.com/bazelbuild/bazel/issues/2669>`_). The best you can do is to 919tag the tests ``"exclusive"``. This allows you to use ``--runs_per_test``, but 920will force each test to run by itself, with no parallelism. 921 922Why is this manual? 923------------------- 924Ideally, we would apply either the ``"block-network"`` or ``"exclusive"`` tag 925to the tests depending on the OS. But this is not supported, `gh#2971 926<https://github.com/bazelbuild/bazel/issues/2971>`_. 927 928We don't want to tag the tests ``"exclusive"`` by default because that will 929prevent *different* tests from running in parallel, significantly slowing them 930down. 931 932.. toctree:: 933 :hidden: 934 935 API reference <api> 936