Commit Graph
13 Commits
Author SHA1 Message Date
70229f0be1 Originate v2 source routes and wire fragmentIdFilter targeted resync (#1378)
* Originate v2 source routes and wire fragmentIdFilter targeted resync

Part A — source-route origination policy:
- Gate route application (BLESourceRouteOriginationPolicy): only packets we
  author, directed at a single peer, with TTL headroom, whose recipient is
  not directly connected. Relays no longer attach routes to (and re-sign)
  packets they merely forward.
- Version-gate paths: MeshTopologyTracker records the highest protocol
  version observed per peer; BFS routes require every intermediate hop and
  the recipient to be v2-observed, capped at 4 intermediate hops.
- Degrade on failure: BLESourceRouteFailureCache marks a routed send that
  sees no inbound traffic from the recipient within 10s as failed and floods
  for 60s before retrying routes.

Part B — REQUEST_SYNC fragmentIdFilter (TLV 0x06):
- Requester: BLEFragmentAssemblyBuffer reports stalled broadcast
  reassemblies (no new fragment for 5s, retried at most every 10s); the
  maintenance pass sends a types=fragment REQUEST_SYNC naming the stalled
  8-byte fragment stream IDs to each connected peer.
- Responder: GossipSyncManager restricts the fragment diff to exactly the
  named streams, bypassing the since-cursor while the GCS filter still
  excludes pieces the requester holds; RSR/TTL-0/rate-limit semantics
  unchanged and REQUEST_SYNC stays link-local.
- Bounds: at most 60 IDs per request (60*17-1 = 1019 bytes <= the 1024-byte
  decoder cap); oversized 0x06 values are ignored, not fatal.

Docs: SOURCE_ROUTING.md gains the iOS origination policy (§8);
REQUEST_SYNC_MANAGER.md documents 0x05/0x06 as implemented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Fix stall-clock refresh on duplicates and overflow suppression in fragment resync

Two fixes to stalledBroadcastFragmentIDs bookkeeping in
BLEFragmentAssemblyBuffer:

- Duplicate fragments no longer reset the stall clock. Fragment packets
  bypass the packet deduplicator, so relayed duplicates of an
  already-held index arriving every few seconds kept lastFragmentAt
  fresh and suppressed the targeted REQUEST_SYNC indefinitely. Now
  lastFragmentAt only updates when the index is new (actual progress).

- Only the streams that will actually be encoded on the wire are
  rate-limited. Previously every stalled candidate got
  lastResyncRequestAt set, but encodeFragmentIdFilter serializes at most
  RequestSyncPacket.maxFragmentIdFilterCount (60) IDs, so overflow
  streams were suppressed for retryAfter without ever being requested.
  Selection now caps at that shared constant, oldest stall first, so
  overflow stays eligible and rotates fairly on the next pass.

Tests: duplicates arriving periodically still trigger the stall report;
70 stalled streams yield the 60 oldest on the first pass and the
remaining 10 on the next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 14:42:53 +02:00
c2a5668569 Sync cleanups: normalize SyncTypeFlags, single announce-ID path, TODO (#1373)
Follow-ups deferred from the REQUEST_SYNC review (#1371):

- SyncTypeFlags.init(rawValue:) now masks to the union of bits that map to a
  known message type (derived from the bit↔type table, so it tracks new
  types automatically). Phantom bits from a truncated/garbled flags field —
  or a type a newer peer added — no longer live in the set as membership no
  contains() matches yet toData() re-serializes.

- GossipSyncManager stored each latest announce as (hex-id string, packet)
  and diffed announces against the stored string while every other type
  recomputed the ID via PacketIdUtil. Collapsed the store to just the packet
  and recompute the ID everywhere, removing the latent dual-path divergence.

- Documented the REQUEST_SYNC TLV table and marked fragmentIdFilter (0x06)
  with a TODO(v2): it's parsed/re-serialized but never populated or honored
  (reserved for incremental fragment sync) — finish or drop, not silent dead
  surface.

Adds SyncTypeFlags phantom-bit/round-trip tests and a GossipSyncManager test
that an announce already in the requester's filter is suppressed (guards the
recompute path). Full suite: 1034 tests pass.

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 21:04:14 +02:00
7341696280 Expand store-and-forward: open couriers, spray-and-wait, persistent outbox, 6h public history (#1372)
Store-and-forward previously delivered to an out-of-range peer only if a
mutual favorite happened to be connected at send time and later met the
recipient directly, and everything except courier envelopes died with the
app process. This closes those gaps end to end:

- Persist the MessageRouter outbox to disk, sealed with a ChaChaPoly key
  held only in the Keychain (no plaintext at rest); queued private
  messages now survive an app kill and flush on next launch.
- Deposit retry: queued messages are re-deposited whenever a new eligible
  courier connects, tracked per message so the same courier is never
  double-burned, until 3 distinct couriers carry it or it expires.
- Tiered open couriering: signature-verified strangers can now carry mail
  (2 envelopes/depositor into a 20-slot pool) alongside mutual favorites
  (5 each); overflow evicts verified-tier mail before favorites'.
- Spray-and-wait: envelopes carry a copy budget (4, capped 8, new TLV,
  wire-compatible with old clients); couriers split half their remaining
  budget with each newly encountered courier so mail diffuses through a
  moving crowd.
- Remote handover: a verified relayed announce now floods a copy toward
  the multi-hop recipient (directed-relay treatment, 10-min per-envelope
  cooldown) while the carried original stays put for a direct encounter.
- Public history: gossip-sync window for whole public messages widened
  from 15 min to 6 h, matched on the receive-acceptance side, and the
  message store persists to disk so devices bridge partitions and
  restarts ("town crier").
- Privacy-safe local delivery counters (bare tallies, log-only) so the
  store-and-forward stack is measurable on-device.
- Panic wipe now also clears the sealed outbox, gossip archive, and
  counters.
- Rewrite WHITEPAPER.md to describe the app as implemented (Noise XX/X,
  actual flood control, courier system, gossip sync, Nostr path); the old
  document described a bloom filter, three fragment types, and a
  MessageRetryService that don't exist.

1037 macOS tests pass (17 new); iOS builds.

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 19:33:16 +02:00
295f855b6f Harden REQUEST_SYNC and stop gossip-sync re-send loops (#1371)
* Harden REQUEST_SYNC and stop gossip-sync re-send loops

Two fixes from an end-to-end review of the sync path:

Efficiency: the GCS filter (400B, p=7) covers ~355 packet IDs, but stores
hold up to 1000 messages + 600 fragments + 200 files. Once a mesh
accumulates more than the filter can cover, responders re-sent the entire
older tail to every requester every round — ~120KB per pair per 30s during
file transfers, dropped by dedup after the airtime was already burned.
Requesters now stamp the dormant sinceTimestamp TLV with the oldest
timestamp their filter covers, and responders skip older packets (announces
exempt: they carry the signing keys needed to verify everything else).
Periodic sync also sends one request per type schedule instead of a union
filter, so fragment floods can't crowd messages out of the filter budget.

Security: a ~40-byte unsigned REQUEST_SYNC with an empty filter could elicit
a full store replay (~900KB) — an unauthenticated >10,000x amplification
vector, repeatable in a tight loop and relayable with crafted TTL to fan the
drain out of every reachable node. Requests now require ttl == 0, a valid
signature from the claimed sender's announced signing key, and a matching
link binding; REQUEST_SYNC is never relayed regardless of TTL; and responses
are rate-limited per peer (8 per 30s sliding window, ~3x the legitimate
cadence).

Cross-platform: verified against bitchat-android — it signs REQUEST_SYNC and
sends SYNC_TTL_HOPS = 0, so both gates hold; it neither sends nor honors
sinceTimestamp yet, so mixed pairs keep today's behavior with no regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Address Codex review: enforce no-relay on route path, exact since-cursor

Two P2 findings from Codex on the REQUEST_SYNC hardening:

- Route-forwarding bypass: handleRequestSync's early return for a rejected
  (nonzero-TTL / unsigned) request still fell through to
  forwardAlongRouteIfNeeded, which relays any routed packet with ttl > 1
  regardless of type. The no-relay invariant was only enforced on the flood
  path. BLERouteForwardingPolicy now suppresses REQUEST_SYNC outright, so a
  crafted request with a route and TTL headroom can't be forwarded to the
  next hop either.

- Inexact since-cursor: GCSFilter.buildFilter trimmed by hash order when the
  encoding overflowed the byte budget, so the cursor (computed from the
  untrimmed prefix) could claim coverage of timestamps whose packets were
  dropped from the filter — re-sending exactly those every round. buildFilter
  now trims from the input tail (oldest, since candidates are newest-first)
  and reports includedCount; the cursor is derived from that, so the covered
  set is always a contiguous newest-prefix and the cursor is exact.

Adds GCSFilter includedCount coverage (full vs trimmed), a route-forwarding
test for REQUEST_SYNC, and makes the truncated-cursor test robust to trim
variance. Full suite: 1029 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 19:17:26 +02:00
ab0da61533 [codex] Refactor BLE transport event handling (#1266)
* Refactor BLE transport event handling

* Make image output paths unique

* Keep queued Nostr read receipts alive

* Allow self-authored RSR ingress replies

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
2026-05-31 13:58:27 +02:00
IslamandGitHub 4cfcefcda6 BitFoundation module to centralize shared components (#1089)
* Run local packages’ tests as well on CI

* BitFoundation module to centralize shared components
2026-04-14 14:10:03 -05:00
3fc64f6168 feat: Implement Request Sync Manager (V2 Sync) (#965)
* feat: Implement Request Sync Manager (V2 Sync)

- Add RequestSyncManager to track and attribute sync requests
- Update BitchatPacket and BinaryProtocol to support IS_RSR flag (0x10)
- Update RequestSyncPacket with new TLV fields (sinceTimestamp, fragmentIdFilter)
- Update GossipSyncManager to use unicast sync requests and mark responses as RSR
- Update BLEService to enforce timestamp validation for normal packets and exempt valid RSRs
- Add documentation for the new sync manager mechanism

* fix: Resolve compilation errors in V2 Sync implementation

- Remove duplicate restartGossipManager in BLEService
- Add missing TransportConfig constants for sync
- Add 'sync' log category to BitLogger
- Add missing BitLogger import in GossipSyncManager

* fix: Update tests for V2 Sync changes

- Add requestSyncManager parameter to GossipSyncManager init in tests
- Implement getConnectedPeers stub in RecordingDelegate
- Remove unused variable warning in SubscriptionRateLimitTests

---------

Co-authored-by: a1denvalu3 <>
Co-authored-by: jack <212554440+jackjackbits@users.noreply.github.com>
2026-01-17 07:43:02 -10:00
jack da3fcd5a21 Remove dead code and stabilize tests 2026-01-15 09:46:35 -10:00
b6ce4fae43 Add type-aware REQUEST_SYNC sync rounds (#853)
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
2025-10-22 12:24:29 +02:00
3d914dcf46 Convert the remaining tests to Swift Testing (#781)
* SwiftTesting: NoiseProtocolTests + BinaryProtocolPaddingTests

* SwiftTesting: `NotificationStreamAssemblerTests`

* SwiftTesting: `NostrProtocolTests`

* SwiftTesting: `BinaryProtocolTests`

* SwiftTesting: `PeerIDTests`

* SwiftTesting: `BLEServiceTests`

* SwiftTesting: `CommandProcessorTests`

* SwiftTesting: `GCSFilterTests`

* SwiftTesting: `GeohashBookmarksStoreTests`

* Remove `peerID` test constants

* Remove PeerID + String interop from tests

* Refactor IntegrationTests to extract state management

* Refactor global state management of MockBLEService

* NoiseProtocolSwiftTests: `actor` -> `struct`

* Remove measurement tests w/ no benchmark

* `NoiseProtocolSwiftTests` -> `NoiseProtocolTests`

* SwiftTesting: `LocationChannelsTests`

* SwiftTesting: `GossipSyncManagerTests`

* SwiftTesting: `LocationNotesManagerTests`

* Global `sleep` function for tests

* SwiftTesting: `IntegrationTests`

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
2025-10-15 01:04:01 +02:00
6588861e34 Align gossip sync stale cleanup with Android client (#798)
* Align DM sheet toolbar with people list

* Gate stale gossip announcements

* Remove stale peer messages during gossip cleanup

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
2025-10-14 13:54:36 +02:00
IslamandGitHub 2673d28686 PeerID 12/n: GossipSyncManager (#751)
* Noise types use PeerID

* Fix tests

* Extract `NoiseSessionManager` into a separate file

* Extract `NoiseSessionState` into a separate file

* Remove `failed` state from `NoiseSessionState`

* Extract `NoiseSessionError` into a separate file

* PeerID 12/n: `GossipSyncManager`
2025-10-05 15:53:57 +02:00
3a94b57341 Fix BLE stream crashes and gossip sync races (#663)
* Handle long BLE packets safely

* Keep BLE stream aligned after partial drops

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
2025-09-23 14:06:13 +02:00