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