xref: /aosp_15_r20/external/pigweed/pw_transfer/docs.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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