low_latency_sync 0.1.1 copy "low_latency_sync: ^0.1.1" to clipboard
low_latency_sync: ^0.1.1 copied to clipboard

QUIC datagram transport primitives for low latency Flutter applications.

low_latency_sync #

Flutter plugin surface for low-latency QUIC datagram transport on Android and iOS.

low_latency_sync exposes small, binary-first transport primitives for Flutter apps that need to exchange latency-sensitive state as Uint8List payloads. The package API is application-agnostic: it does not impose a protocol, serialization format, tick rate, or domain model.

Capabilities #

  • The public Dart API targets QUIC datagrams, not raw UDP.
  • Android and iOS plugin entry points are available.
  • Client and server endpoints expose a shared QuicEndpoint API.
  • Incoming payloads are exposed as Stream<QuicDatagram>.
  • Outgoing payloads are sent as raw Uint8List datagrams.
  • Server datagram sends require a QUIC connectionId so replies target an established client connection.

Native Android and iOS QUIC backends are still pending. Until they are integrated, platform calls can report QUIC_NOT_IMPLEMENTED.

Why QUIC #

QUIC provides encrypted transport, stream multiplexing, connection migration, and avoids TCP head-of-line blocking at the transport layer. QUIC datagrams add unreliable datagram delivery for latency-sensitive data where stale packets should be dropped instead of retransmitted.

Installation #

Add the package to your Flutter app:

dependencies:
  low_latency_sync: ^0.1.1

Then import the public API:

import 'package:low_latency_sync/low_latency_sync.dart';

Building from source #

The Dart and Flutter side builds with the standard Flutter toolchain:

flutter pub get
flutter analyze
flutter test

The native QUIC backend targets Cloudflare quiche. quiche is written in Rust, so native builds require extra tools in addition to Flutter.

Native credential modes #

Native builds support two credential modes:

Mode Use case Private key in app binary
external Apps that connect to a backend-owned QUIC server no
embedded Autonomous/offline apps without backend infrastructure yes

external is the default. In this mode the app can act as a QUIC client and no certificate or private key is compiled into the native library. Calling LowLatencyQuicServer.listen() requires embedded mode because the app must present a server certificate during the QUIC/TLS handshake.

Enable embedded only when the application must run without a backend:

  • LLS_QUIC_CREDENTIAL_MODE=embedded
  • LLS_QUIC_CERT_PEM_FILE pointing to a local PEM certificate file.
  • LLS_QUIC_KEY_PEM_FILE pointing to the matching local PEM private key file.

For local development, keep them outside version control, for example under certs/:

$env:LLS_QUIC_CREDENTIAL_MODE = "embedded"
$env:LLS_QUIC_CERT_PEM_FILE = "$PWD\certs\dev-cert.pem"
$env:LLS_QUIC_KEY_PEM_FILE = "$PWD\certs\dev-key.pem"

In embedded mode the private key is present in the compiled binary and should be treated as extractable. For backend-backed production apps, keep the server private key on the backend and ship the app in external mode.

The example app includes a committed self-signed development certificate under example/certs/ to demonstrate embedded mode:

cd example
$env:LLS_QUIC_CREDENTIAL_MODE = "embedded"
$env:LLS_QUIC_CERT_PEM_FILE = "$PWD\certs\example-dev-cert.pem"
$env:LLS_QUIC_KEY_PEM_FILE = "$PWD\certs\example-dev-key.pem"
flutter run

Those example credentials are public, intentionally non-secret, and only useful to demonstrate the build flow. Do not reuse them in real applications.

Android native requirements #

  • Flutter SDK with Android support.
  • Android SDK and Android NDK.
  • Java 17.
  • CMake.
  • Rust toolchain: rustup and cargo.
  • Android Rust targets:
    • aarch64-linux-android
    • armv7-linux-androideabi
    • x86_64-linux-android
  • ANDROID_NDK_HOME or ANDROID_NDK_ROOT pointing to the installed NDK.

Prepare the Android quiche checkout and Rust targets:

.\tool\build_quiche_android.ps1

The Android Gradle configuration currently packages arm64-v8a.

During the Android native build, CMake reads LLS_QUIC_CREDENTIAL_MODE. In embedded mode it reads the PEM files and generates a build-local C header. In external mode it generates an empty credential header.

iOS native requirements #

iOS native builds require macOS:

  • Flutter SDK with iOS support.
  • Xcode and command line tools.
  • Rust toolchain: rustup and cargo.
  • iOS Rust targets:
    • aarch64-apple-ios
    • aarch64-apple-ios-sim
    • x86_64-apple-ios

Prepare the iOS quiche checkout and Rust targets:

./tool/build_quiche_ios.sh

During CocoaPods preparation, the podspec reads LLS_QUIC_CREDENTIAL_MODE. In embedded mode it reads the PEM files and generates native/generated/lls_quic_build_credentials.h. In external mode it generates an empty credential header.

Native artifacts #

The expected native artifact layout is defined in native/quiche/artifacts.json.

Check that native quiche artifacts are present:

.\tool\check_quiche_artifacts.ps1

The quiche preparation scripts intentionally stop before producing final release artifacts until the exact native build commands are pinned and reviewed.

Usage #

Connect a client #

import 'dart:typed_data';

import 'package:low_latency_sync/low_latency_sync.dart';

final client = LowLatencyQuicClient();

Future<void> startClient() async {
  await client.connect(
    host: '192.168.1.10',
    port: 4433,
    serverName: 'localhost',
    alpn: 'low-latency-sync/1',
  );

  client.datagrams.listen((datagram) {
    final Uint8List bytes = datagram.bytes;
    // Decode your application payload here.
  });

  await client.sendDatagram(Uint8List.fromList(<int>[1, 2, 3, 4]));
}

Future<void> stopClient() => client.close();

Listen as a server #

import 'dart:typed_data';

import 'package:low_latency_sync/low_latency_sync.dart';

final server = LowLatencyQuicServer();

Future<void> startServer() async {
  await server.listen(
    port: 4433,
    certificateIdentity: 'development',
    alpn: 'low-latency-sync/1',
  );

  server.datagrams.listen((datagram) async {
    final Uint8List bytes = datagram.bytes;
    final String connectionId = datagram.connectionId;

    // Decode your application payload here.

    await server.sendDatagram(
      bytes,
      connectionId: connectionId,
    );
  });
}

Future<void> stopServer() => server.close();

On the server side, connectionId is required when sending a datagram. It selects the established client connection that should receive the payload.

Shared endpoint shape #

Both LowLatencyQuicClient and LowLatencyQuicServer implement QuicEndpoint:

abstract interface class QuicEndpoint {
  Stream<QuicDatagram> get datagrams;

  Future<void> sendDatagram(Uint8List bytes, {String? connectionId});

  Future<void> close();
}

Received datagrams expose:

final class QuicDatagram {
  final String connectionId;
  final Uint8List bytes;
  final String remoteHost;
  final int remotePort;
}

Packet header helper #

The package includes a tiny binary header helper for applications that need a fixed packet prefix:

final payload = Uint8List(PacketHeader.size + 4);
final data = ByteData.sublistView(payload);

PacketHeader.write(
  data,
  0x01, // application packet type
  42, // sequence
  DateTime.now().microsecondsSinceEpoch,
);

final type = PacketHeader.type(payload);
final sequence = PacketHeader.sequence(payload);
final timestampMicros = PacketHeader.timestampMicros(payload);

Header layout:

Offset Size Field Endian
0 1 byte packet type n/a
1 4 bytes sequence little-endian
5 4 bytes timestamp in microseconds little-endian

Error handling #

Platform errors are surfaced as QuicException:

try {
  await client.connect(
    host: '192.168.1.10',
    port: 4433,
    serverName: 'localhost',
  );
} on QuicException catch (error) {
  if (error.code == QuicErrorCode.notImplemented) {
    // Native backend is not available yet.
  }
}

Known error codes:

  • QUIC_NOT_IMPLEMENTED
  • QUIC_INVALID_ARGUMENT
  • QUIC_HANDSHAKE_FAILED
  • QUIC_CONNECTION_CLOSED
  • QUIC_DATAGRAM_UNSUPPORTED
  • QUIC_NATIVE_FAILURE

Package boundary #

  • lib/ stays generic and only exposes QUIC transport primitives over Uint8List.
  • example/ owns the particle simulation, multitouch handling, and application-specific binary packet types.
  • docs/ADR-0001-quic-transport.md records the QUIC transport decision.
  • docs/ADR-0002-quic-backend.md records the first native backend decision.
  • docs/native-channel-contract.md defines the Flutter/native channel contract.
  • docs/native-abi-contract.md defines the package-owned native ABI mapping.
  • docs/native-engine-integration.md defines where the concrete Android/iOS QUIC engine plugs in.
  • docs/quiche-build.md records the native quiche artifact build plan.
  • docs/quality-gates.md records local and CI verification gates.
  • docs/device-smoke-tests.md records Android/iOS runtime smoke tests.

Native implementation targets #

The next implementation step is selecting and integrating one native QUIC stack for both Android and iOS. Candidate families:

  • Rust core with mobile bindings, for example Quinn or quiche.
  • C/C++ core with FFI/JNI/Swift bridge, for example MsQuic or quiche C API.
  • Platform APIs only if they expose the needed QUIC datagram behavior consistently on both Android and iOS.

Raw UDP is intentionally not used because it does not provide QUIC-level encryption, connection semantics, congestion behavior, or resilience.

Development #

Run static analysis:

flutter analyze

Run tests:

flutter test

Run the example app:

cd example
flutter run

Limitations #

  • Native Android and iOS QUIC backends are still pending.
  • The package transports raw binary datagrams only; application-level serialization belongs in the host app.
  • Delivery is designed for latency-sensitive datagrams where stale state may be discarded by the application protocol.
1
likes
0
points
178
downloads

Publisher

verified publisherkavacode.com

Weekly Downloads

QUIC datagram transport primitives for low latency Flutter applications.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on low_latency_sync

Packages that implement low_latency_sync