Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,18 @@ jobs:
uses: actions/cache@v4
with:
path: .build
key: spm-${{ runner.os }}-${{ hashFiles('Package.swift') }}
restore-keys: spm-${{ runner.os }}-
# 'stormo' salt: pre-rename caches embed the old /PeerMesh/ workspace
# path in their PCHs; restoring one poisons every clang module build.
key: spm-stormo-${{ runner.os }}-${{ hashFiles('Package.swift') }}
restore-keys: spm-stormo-${{ runner.os }}-
- name: Run tests with coverage
run: swift test --enable-code-coverage
env:
QUIC_DEBUG: "1"
QUIC_DEBUG_LOG: /tmp/quic-debug.log
# Same-machine runs need p2p off (failure mode 10). Peer-to-peer
# Wi-Fi coverage is tier 3 (hardware lab) by design.
PEERMESH_NO_P2P: "1"
STORMO_NO_P2P: "1"
- name: Diagnostics on failure (QUIC log + crash reports)
if: failure()
run: |
Expand All @@ -70,7 +72,7 @@ jobs:
run: ./Scripts/e2e-cli.sh
env:
QUIC_DEBUG: "1"
PEERMESH_NO_P2P: "1"
STORMO_NO_P2P: "1"

# Same test bundle per Apple destination. iOS Simulator runs on PRs (the
# availability-floor check); Mac Catalyst only on pushes to main.
Expand Down Expand Up @@ -106,7 +108,7 @@ jobs:
# xcbeautify streams progress live — a hung xcodebuild shows where
# it stalled instead of buffering silently until the timeout.
xcodebuild test \
-scheme PeerMesh-Package \
-scheme Stormo-Package \
-destination '${{ steps.dest.outputs.destination }}' \
| xcbeautify --renderer github-actions

Expand All @@ -122,7 +124,7 @@ jobs:
run: |
set -o pipefail
xcodebuild test \
-scheme PeerMesh-Package \
-scheme Stormo-Package \
-destination 'platform=macOS,variant=Mac Catalyst' \
| xcbeautify --renderer github-actions

Expand All @@ -145,7 +147,7 @@ jobs:
- name: Regenerate and diff
run: |
/tmp/flatc-bin/flatc --swift -o /tmp/generated Schemas/*.fbs
diff -r /tmp/generated Sources/PeerMeshProtocol/Generated \
diff -r /tmp/generated Sources/StormoProtocol/Generated \
|| { echo '::error::Generated sources drifted from schemas (DD-5 rule 4)'; exit 1; }

# TODO(S-6): nightly workflow_dispatch job — stream-churn benchmark and 32-peer
Expand Down
30 changes: 15 additions & 15 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CLAUDE.md — PeerMesh contributor guide (AI & human)
# CLAUDE.md — Stormo contributor guide (AI & human)

PeerMesh is an open-source replacement for Apple's deprecated
Stormo is an open-source replacement for Apple's deprecated
MultipeerConnectivity, built on Network.framework + QUIC. The codebase is
split by the sans-I/O discipline (DD-6): a pure, deterministic protocol engine
that decides, and thin async drivers that move bytes. Read
Expand All @@ -12,29 +12,29 @@ the hard-won platform-findings log and the source of the failure modes below.

| Module | Kind | What lives here |
|---|---|---|
| **PeerMeshProtocol** | library (sans-I/O) | `ProtocolEngine` state machine, `Signal` model + `SignalCodec` (FlatBuffers verifier), `PeerID`/`LibP2PIdentity`, `Delivery`/`Recipients`/`PeerMeshError`, `Locked`. Only dependency: FlatBuffers. |
| **PeerMesh** | library (runtime) | `PeerSession` actor (effect executor), `QUICTransport` + QUIC driver, Security (identity, certificate, keychain stores, `TrustEvaluator`), public event/config types. Re-exports PeerMeshProtocol so apps write only `import PeerMesh`. |
| **StormoProtocol** | library (sans-I/O) | `ProtocolEngine` state machine, `Signal` model + `SignalCodec` (FlatBuffers verifier), `PeerID`/`LibP2PIdentity`, `Delivery`/`Recipients`/`StormoError`, `Locked`. Only dependency: FlatBuffers. |
| **Stormo** | library (runtime) | `PeerSession` actor (effect executor), `QUICTransport` + QUIC driver, Security (identity, certificate, keychain stores, `TrustEvaluator`), public event/config types. Re-exports StormoProtocol so apps write only `import Stormo`. |
| **MPCCompat** | library | Near-drop-in `MCSession`/`MCPeerID`/advertiser/browser analogs over `PeerSession` (FR-24). |
| **PeerMeshUI** | library | SwiftUI peer picker + invitation consent (FR-23). **Experimental preview, API unstable.** |
| **PeerMeshTestKit** | library | `InMemoryTransport` (+ mesh sim) and `ReorderingTransport` — CI without radios (QA-8). |
| **PeerMeshCLI** (`peermesh-cli`) | executable | Diagnostic advertise/browse/host/join over real Bonjour+QUIC between processes. |
| **StormoUI** | library | SwiftUI peer picker + invitation consent (FR-23). **Experimental preview, API unstable.** |
| **StormoTestKit** | library | `InMemoryTransport` (+ mesh sim) and `ReorderingTransport` — CI without radios (QA-8). |
| **StormoCLI** (`stormo-cli`) | executable | Diagnostic advertise/browse/host/join over real Bonjour+QUIC between processes. |

## Build & test

- `swift test` — tiers 1 & 2 (engine + loopback QUIC), fast macOS loop. 54 tests today.
- `./Scripts/e2e-cli.sh` — cross-process Bonjour+QUIC exchange (host + joiner as separate processes). Caught the FIN and inbound-retention bugs.
- `xcodebuild test -scheme PeerMesh-Package -destination '<dest>'` for other Apple targets (same suite, N destinations):
- `xcodebuild test -scheme Stormo-Package -destination '<dest>'` for other Apple targets (same suite, N destinations):
- iOS simulator: `-destination 'platform=iOS Simulator,id=<UDID>'` (resolve a UDID via `xcrun simctl list devices available`).
- Mac Catalyst: `-destination 'platform=macOS,variant=Mac Catalyst'`.

### Environment flags
- `QUIC_DEBUG=1` — enable the QUIC driver's diagnostic log to stdout.
- `QUIC_DEBUG_LOG=<path>` — also append that log to a file (survives sandboxing).
- `PEERMESH_NO_P2P=1` — disable `includePeerToPeer`. Required for **in-process** entitled E2E tests: `includePeerToPeer` breaks same-machine self-dials (failure mode 10). Never set it for real cross-device runs.
- `STORMO_NO_P2P=1` — disable `includePeerToPeer`. Required for **in-process** entitled E2E tests: `includePeerToPeer` breaks same-machine self-dials (failure mode 10). Never set it for real cross-device runs.

### FlatBuffers regeneration (DD-5 rule 4)
```
flatc --swift -o Sources/PeerMeshProtocol/Generated Schemas/*.fbs
flatc --swift -o Sources/StormoProtocol/Generated Schemas/*.fbs
```
Generated sources are committed and CI fails on drift. **Exact-version pin
rule:** the `flatc` in `flake.nix` MUST equal the `google/flatbuffers` runtime
Expand All @@ -43,18 +43,18 @@ the runtime that reads it are one unit. Bump both together.

## Hard architectural rules

- **PeerMeshProtocol stays sans-I/O.** No sockets, no clocks, no async, no
- **StormoProtocol stays sans-I/O.** No sockets, no clocks, no async, no
Foundation I/O. Its only dependency is FlatBuffers. Time enters the engine as
an `Input`, never from a clock. (`Locked`/`NSLock` is a synchronization
primitive, not I/O, and is allowed.)
- **Engine changes land with tier-1 tests first** (`Tests/PeerMeshProtocolTests`).
- **Engine changes land with tier-1 tests first** (`Tests/StormoProtocolTests`).
The engine is `handle(Input) -> [Effect]`, pure and deterministic — assert on
the effect list.
- **FlatBuffers schema evolution:** field ids are append-only; never renumber,
retype, or remove (only `(deprecated)`); no new `(required)` after 1.0;
enum/union values append-only with `UNKNOWN = 0`. Every inbound buffer goes
through the verifier (`getCheckedRoot`, hard caps). Generated code is
committed. Do NOT hand-edit `Schemas/` or `Sources/PeerMeshProtocol/Generated/`.
committed. Do NOT hand-edit `Schemas/` or `Sources/StormoProtocol/Generated/`.
- **Platform floor: iOS 15 / macOS 12** (QUIC floor). No `Duration` (use
`TimeInterval`). RFC 9221 datagrams are iOS 16/macOS 13, below the floor — see
failure mode 1 and the `StreamKind.Datagram` channel mapping.
Expand Down Expand Up @@ -106,7 +106,7 @@ code that guards them without understanding why it exists.
interface assertion MUST be validated in the mesh-hardware spike before
radio release.
10. **`includePeerToPeer` breaks same-machine self-dials.** Use
`PEERMESH_NO_P2P=1` for in-process tests; CI sets it for every
`STORMO_NO_P2P=1` for in-process tests; CI sets it for every
same-machine job. Note `ProcessInfo.environment` caches at first access;
the driver reads this flag via `getenv` so a test's `setenv` actually
lands.
Expand Down Expand Up @@ -153,7 +153,7 @@ In-code TODOs reference these by name: `// TODO(ledger-name): one line`.
- **churn-benchmark** — S-6 formal stream-churn benchmark (nightly CI stub in `ci.yml`).
- **pairing-code** — `.pairingCode` transcript binding (S-4, DD-2).
- **compat-nsstream-bridge** — `MPCCompat.startStream` `NSStream` bridge over `PeerByteStream`.
- **ui-completion** — PeerMeshUI beyond the current skeleton.
- **ui-completion** — StormoUI beyond the current skeleton.
- **mesh-join** — join via endpoint exchange for gossiped roster members (roster names peers we haven't discovered).
- **send-ack** — send-acknowledgement API (`PeerSession.send` returns before transport handoff).
- **mesh-hardware** — S-2 mesh-ceiling and S-5 backgrounding hardware spikes.
Expand Down
42 changes: 21 additions & 21 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import PackageDescription

let package = Package(
name: "PeerMesh",
name: "Stormo",
platforms: [
.iOS(.v15),
.macOS(.v12),
Expand All @@ -12,21 +12,21 @@ let package = Package(
products: [
// Sans-I/O protocol engine: deterministic state machine + signal model,
// fully testable without any transport (DD-6).
.library(name: "PeerMeshProtocol", targets: ["PeerMeshProtocol"]),
.library(name: "StormoProtocol", targets: ["StormoProtocol"]),
// Modern core: discovery, sessions, messaging, streams, resources over QUIC (DD-1).
.library(name: "PeerMesh", targets: ["PeerMesh"]),
.library(name: "Stormo", targets: ["Stormo"]),
// Near-drop-in migration surface for MultipeerConnectivity codebases (FR-24).
.library(name: "MPCCompat", targets: ["MPCCompat"]),
// SwiftUI peer picker and invitation consent components (FR-23).
.library(name: "PeerMeshUI", targets: ["PeerMeshUI"]),
.library(name: "StormoUI", targets: ["StormoUI"]),
// In-memory transport and mesh simulation for CI without radios (QA-8).
.library(name: "PeerMeshTestKit", targets: ["PeerMeshTestKit"]),
.library(name: "StormoTestKit", targets: ["StormoTestKit"]),
// Diagnostic CLI: advertise/browse/chat between real processes over
// Bonjour + QUIC (the production discovery path).
// Named "peermesh-cli", not "peermesh": a product differing from the
// PeerMesh library only by case collides on case-insensitive APFS when
// Named "stormo-cli", not "Stormo": a product differing from the
// Stormo library only by case collides on case-insensitive APFS when
// xcodebuild materializes package products as modules.
.executable(name: "peermesh-cli", targets: ["PeerMeshCLI"]),
.executable(name: "stormo-cli", targets: ["StormoCLI"]),
],
dependencies: [
// Signaling plane serialization (DD-5). Pinned EXACTLY to match the
Expand All @@ -35,42 +35,42 @@ let package = Package(
// doesn't import). Bump both together (DD-5 rule 4).
.package(url: "https://github.com/google/flatbuffers.git", exact: "25.2.10"),
// Pure-Swift X.509 for self-signed identity certificates (DD-2). Used by
// the PeerMesh target only — PeerMeshProtocol stays FlatBuffers-only.
// the Stormo target only — StormoProtocol stays FlatBuffers-only.
.package(url: "https://github.com/apple/swift-certificates.git", from: "1.0.0"),
],
targets: [
// Sans-I/O (DD-6): the ONLY dependency is FlatBuffers (pure CPU).
// No Network.framework, no clocks, no async — keep it that way.
.target(
name: "PeerMeshProtocol",
name: "StormoProtocol",
dependencies: [
.product(name: "FlatBuffers", package: "flatbuffers")
]
),
// Runtime shell: executes engine Effects against real transports.
.target(
name: "PeerMesh",
name: "Stormo",
dependencies: [
"PeerMeshProtocol",
"StormoProtocol",
.product(name: "X509", package: "swift-certificates"),
// The QUIC driver serializes the DD-7 `StreamHeader` prologue of
// every dedicated stream (the generated FlatBuffers type lives in
// PeerMeshProtocol). Moving bytes onto the wire is driver work
// StormoProtocol). Moving bytes onto the wire is driver work
// (DD-6); no protocol decisions live here.
.product(name: "FlatBuffers", package: "flatbuffers"),
]
),
.target(name: "MPCCompat", dependencies: ["PeerMesh"]),
.target(name: "PeerMeshUI", dependencies: ["PeerMesh"]),
.target(name: "PeerMeshTestKit", dependencies: ["PeerMesh"]),
.executableTarget(name: "PeerMeshCLI", dependencies: ["PeerMesh"]),
.target(name: "MPCCompat", dependencies: ["Stormo"]),
.target(name: "StormoUI", dependencies: ["Stormo"]),
.target(name: "StormoTestKit", dependencies: ["Stormo"]),
.executableTarget(name: "StormoCLI", dependencies: ["Stormo"]),
// Tier 1 (DD-6): engine tests — no transport, no radios, deterministic.
.testTarget(name: "PeerMeshProtocolTests", dependencies: ["PeerMeshProtocol"]),
.testTarget(name: "StormoProtocolTests", dependencies: ["StormoProtocol"]),
.testTarget(
name: "PeerMeshTests",
dependencies: ["PeerMesh", "PeerMeshTestKit"]
name: "StormoTests",
dependencies: ["Stormo", "StormoTestKit"]
),
.testTarget(name: "MPCCompatTests", dependencies: ["MPCCompat", "PeerMeshTestKit"]),
.testTarget(name: "MPCCompatTests", dependencies: ["MPCCompat", "StormoTestKit"]),
],
swiftLanguageModes: [.v6]
)
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# PeerMesh
# Stormo

**The open-source replacement for Apple's deprecated MultipeerConnectivity
framework** — peer-to-peer sessions over Network.framework + QUIC.
Expand All @@ -10,7 +10,7 @@ camera app streaming live preview at 33 fps device-to-device with no
infrastructure.

```swift
import PeerMesh
import Stormo

let session = PeerSession(name: "Dario's iPhone", service: "_myapp._udp")
try await session.startAdvertising()
Expand All @@ -35,7 +35,7 @@ typealias file makes it a near-drop-in swap — see
## Install

```swift
.package(url: "https://github.com/security-union/PeerMesh.git", from: "1.0.0")
.package(url: "https://github.com/security-union/Stormo.git", from: "1.0.0")
```

Platforms: iOS 15+ · macOS 12+ · tvOS 15+ · visionOS 1+. Apps need
Expand All @@ -49,29 +49,29 @@ The recipe below is exactly how [Remote Shutter](https://github.com/security-uni
(a shipping App Store camera app) migrated off MPC — the app diff was a few
imports, one new file, and its peer-ID cache.

**1. Add the package** to your app target, linking the `PeerMesh` and
**1. Add the package** to your app target, linking the `Stormo` and
`MPCCompat` products:

```swift
.package(url: "https://github.com/security-union/PeerMesh.git", from: "1.0.0")
.package(url: "https://github.com/security-union/Stormo.git", from: "1.0.0")
```

**2. Check your `Info.plist`.** Same requirements as MPC:
`NSLocalNetworkUsageDescription`, and your service type under
`NSBonjourServices` — PeerMesh uses the `_yourservice._udp` variant (Apple's
`NSBonjourServices` — Stormo uses the `_yourservice._udp` variant (Apple's
guidance for MPC apps was to declare both `._tcp` and `._udp`, so most apps
already have it).

**3. Add one app-local typealias file** — this is the key to a thin diff. Your
app keeps MPC's type names; the implementations come from MPCCompat. PeerMesh
app keeps MPC's type names; the implementations come from MPCCompat. Stormo
deliberately does not publish `MC`-prefixed names, so the mapping lives in
*your* app:

```swift
// MultipeerCompatAliases.swift — the entire MPC → PeerMesh mapping.
// MultipeerCompatAliases.swift — the entire MPC → Stormo mapping.

import MPCCompat
import PeerMesh
import Stormo

public typealias MCPeerID = PeerID
public typealias MCSession = MultipeerSession
Expand Down
4 changes: 2 additions & 2 deletions Schemas/common.fbs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Shared wire scalars (DD-5), included by signal.fbs and stream_header.fbs.
//
// Codegen (flatc version pinned in flake.nix, matched exactly by Package.swift):
// flatc --swift -o Sources/PeerMeshProtocol/Generated Schemas/*.fbs
// flatc --swift -o Sources/StormoProtocol/Generated Schemas/*.fbs

namespace PeerMesh.Wire;
namespace Stormo.Wire;

/// A 128-bit transfer identifier: the two 8-byte halves of a `uuid_t`, in
/// memory order. A fixed-size struct — no vector length word, and the
Expand Down
8 changes: 4 additions & 4 deletions Schemas/peer_hello.fbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// PeerMesh transport-level identity bootstrap (PeerHello).
// Stormo transport-level identity bootstrap (PeerHello).
//
// The first frame in each direction on the control stream, sent immediately
// after the QUIC/mTLS handshake. TLS authenticates the peer's KEY; PeerHello
Expand All @@ -10,15 +10,15 @@
// this table — it is how incompatible peers discover the mismatch and tell
// the user to upgrade, instead of dying in an opaque handshake failure.
// Breaking changes to this file are forbidden, forever: append-only field
// ids with no pre-1.0 exceptions. The QUIC ALPN ("peermesh/1") is frozen
// ids with no pre-1.0 exceptions. The QUIC ALPN ("Stormo/1") is frozen
// for the same reason — bumping it would fail the handshake before hello.
//
// Codegen (flatc version pinned in flake.nix, matched exactly by Package.swift):
// flatc --swift -o Sources/PeerMeshProtocol/Generated Schemas/*.fbs
// flatc --swift -o Sources/StormoProtocol/Generated Schemas/*.fbs

include "signal.fbs";

namespace PeerMesh.Wire;
namespace Stormo.Wire;

table PeerHello {
/// The sender's identity. key_hash must match the TLS certificate's key
Expand Down
6 changes: 3 additions & 3 deletions Schemas/signal.fbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// PeerMesh signaling schema (DD-5: disciplined FlatBuffers).
// Stormo signaling schema (DD-5: disciplined FlatBuffers).
//
// DISCIPLINE (normative — see design doc DD-5; enforced by CI):
// * Field ids are append-only. Never renumber, retype, or remove a field —
Expand All @@ -12,11 +12,11 @@
// (64 KB control-message size) — see SignalCodec.
//
// Codegen (flatc version pinned in flake.nix):
// flatc --swift -o Sources/PeerMesh/Signaling/Generated Schemas/signal.fbs
// flatc --swift -o Sources/StormoProtocol/Generated Schemas/*.fbs

include "common.fbs";

namespace PeerMesh.Wire;
namespace Stormo.Wire;

/// A session member, as carried in rosters (FR-13).
table PeerInfo {
Expand Down
6 changes: 3 additions & 3 deletions Schemas/stream_header.fbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// PeerMesh data-plane stream header (DD-7, incl. the message-channel
// Stormo data-plane stream header (DD-7, incl. the message-channel
// amendment).
//
// On the message channel (stream tag 0x02) every message is a size-prefixed
Expand All @@ -11,11 +11,11 @@
// append-only enum values, verifier + caps on every inbound read.
//
// Codegen (flatc version pinned in flake.nix, matched exactly by Package.swift):
// flatc --swift -o Sources/PeerMeshProtocol/Generated Schemas/*.fbs
// flatc --swift -o Sources/StormoProtocol/Generated Schemas/*.fbs

include "common.fbs";

namespace PeerMesh.Wire;
namespace Stormo.Wire;

enum StreamKind: ubyte {
Unknown = 0,
Expand Down
Loading
Loading