LocalSend Onboarding LabChapter V — Send Session: Outgoing Transfer Orchestration0/6Contents
Chapter V

Send Session: Outgoing Transfer Orchestration

This module traces a single outgoing transfer from SendNotifier.startSession() — which registers device info and negotiates prepare-upload with the target, including PIN retry — through the per-file isolate dispatch that streams bytes via HttpUploadService.postStream(). It highlights the two distinct Rust bridges the Dart app leans on: the FRB-generated RsHttpClient for control-plane calls, and the rhttp package's Rust client for the actual cert-pinned HTTPS byte stream. Understanding this hand-off is key to seeing where pure-Dart orchestration ends and the native TLS/cert-pinning core takes over.


From SendNotifier to the Rust HTTP core
SendNotifier.startSession()…ovider/network/send_provider.dart
RsHttpClient (FRB bridge)app/rust/src/api/http.rs
Upload isolate handler…isolate/child/upload_isolate.dart
core::LsHttpClientV2core/src/http/client/v2.rs
RhttpWrapper.postStream()app/lib/util/rhttp.dart
rhttp native client (TLS)app/lib/util/rhttp.dart
Figure I · Click a node to see what it does, where it lives in the code, and its labeled connections — the annotation opens beside it.
walkthrough

Handing bytes off to the native HTTP client

common/lib/src/task/upload/http_upload.dart · lines 1642
lines 16–26

upload() takes the raw byte stream plus everything needed to address the request: the target device, the remote session/file identifiers, the per-file upload token, and progress/cancel hooks passed down from the isolate.

lines 27–36

It calls _client.postStream() — the CustomHttpClient abstraction — building the /upload URL via ApiRoute and passing sessionId/fileId/token as query params plus Content-Length/Content-Type headers.

lines 37–42

The actual byte stream, progress callback, and cancel token are handed straight through — this is the exact point where pure-Dart orchestration hands off to whatever concrete client implements postStream (RhttpWrapper, backed by Rust).

  Future<void> upload({    required Stream<List<int>> stream,    required int contentLength,    required String contentType,    required Device target,    required String? remoteSessionId,    required String fileId,    required String token,    required void Function(double) onSendProgress,    required CustomCancelToken cancelToken,  }) async {    await _client.postStream(      uri: ApiRoute.upload.target(target),      query: {        if (remoteSessionId != null) 'sessionId': remoteSessionId,        'fileId': fileId,        'token': token,      },      headers: {        'Content-Length': contentLength.toString(),        'Content-Type': contentType,      },      stream: stream,      onSendProgress: onSendProgress,      cancelToken: cancelToken,    );  }
step 1 of 3
RhttpClient createRhttpClient(Duration timeout, StoredSecurityContext securityContext, {Interceptor? interceptor}) {  return RhttpClient.createSync(    settings: ClientSettings(      timeoutSettings: TimeoutSettings(        timeout: timeout,      ),      tlsSettings: TlsSettings(        verifyCertificates: false,        clientCertificate: ClientCertificate(          certificate: securityContext.certificate,          privateKey: securityContext.privateKey,        ),      ),    ),    interceptors: interceptor != null ? [interceptor] : [],  );}
app/lib/util/rhttp.dart · lines 7086
Figure II · createRhttpClient() disables rhttp's default certificate verification and instead attaches the device's own self-signed client certificate — LocalSend does its own pinning check rather than trusting a CA, which is why sending works between devices with no shared certificate authority.
Figure III · state machineSendSessionState.status during a transfer
prepare-upload accepted, fileMap non-empty204 No Content, or receiver selected no files403 Forbidden from prepare-upload409 Conflict from prepare-upload429 Too Many Requests from prepare-uploaduser dismisses the PIN dialog (pin == null)_finish(): no file has FileStatus.failed_finish(): at least one file has FileStatus.failedwaitingsendingfinisheddeclinedrecipientBusytooManyAttemptscanceledBySenderfinishedWithErrors
Click a state above.
checkpoint

Check your understanding

Answered in place — nothing is graded, everything is explained. 0 / 3 passed

In SendNotifier.startSession(), what happens when client.prepareUpload() throws a status-401 error?

Which call is actually responsible for streaming a file's bytes to the receiver over HTTPS?

Why does createRhttpClient() set verifyCertificates: false while still attaching a client certificate?