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