Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
688a1df
fix(peer): validate signed node origin against node and admin pins fo…
blackkcold Oct 19, 2026
3c6dd83
fix(peer): keep LAN peer discovery alive in foreground service indepe…
blackkcold Oct 19, 2026
77b5993
fix(peer): serialize NSD resolution and retain discovered LAN peers a…
blackkcold Oct 19, 2026
df2830b
fix(peer): bind LAN listener after permission grant without restartin…
blackkcold Oct 19, 2026
065aec4
fix(android): request LAN permission independently of default SMS rol…
blackkcold Oct 19, 2026
eca5bda
feat(peer-core): add non-escalating bidirectional v2 grant policy wit…
blackkcold Oct 19, 2026
9452a9b
test(peer-core): cover directional v2 grants and fail-closed legacy b…
blackkcold Oct 19, 2026
856dd77
feat(peer): versioned reciprocal pairing with signed directional gran…
blackkcold Oct 19, 2026
aa60340
fix(peer-store): freeze signed v2 directional grants against silent p…
blackkcold Oct 19, 2026
8b51c14
feat(peer-rpc): authorize reciprocal v2 RPC on both Android endpoints
blackkcold Oct 19, 2026
c792043
feat(peer-lan): accept authenticated reciprocal v2 connections withou…
blackkcold Oct 19, 2026
425f84b
feat(android): explicit reciprocal SMS grants and unified peer manage…
blackkcold Oct 19, 2026
a347663
test(android): exercise authenticated v2 pairing transcript and non-r…
blackkcold Oct 19, 2026
18c7acd
feat(peer-core): isolate 72-hour offline E2EE envelope from short-liv…
blackkcold Oct 19, 2026
b99b838
test(peer-core): verify offline E2EE TTL, replay binding and domain i…
blackkcold Oct 19, 2026
73d9ef5
feat(android): add bounded durable ciphertext-only Relay Lite mailbox…
blackkcold Oct 19, 2026
b8bbefd
feat(peer-rpc): expose scoped Relay Lite ciphertext put/pull/ack behi…
blackkcold Oct 19, 2026
df7e9d6
fix(relay-lite): purge ciphertext addressed to or sent by revoked peers
blackkcold Oct 19, 2026
819afb1
feat(transport): route explicitly configured Relay Lite as independen…
blackkcold Oct 19, 2026
19eac97
feat(android): encapsulate authorized offline Peer RPC in selected Re…
blackkcold Oct 19, 2026
f979dd0
feat(gateway): use explicit Android Relay Lite as last encrypted fall…
blackkcold Oct 19, 2026
429c2cc
fix(peer): Relay Lite queue acceptance must not finalize pending targ…
blackkcold Oct 19, 2026
0d2b5e3
feat(android): resume offline peer requests and results via authentic…
blackkcold Oct 19, 2026
806406e
fix(relay-lite): return durable asynchronous response instead of send…
blackkcold Oct 19, 2026
786d78f
feat(peer-service): schedule bounded Relay Lite recovery only for opt…
blackkcold Oct 19, 2026
48903d0
fix(lan): discover reciprocal v2 peers and reject stale NSD callbacks
blackkcold Oct 20, 2026
37f81be
feat(android-ui): expose explicit Relay Lite host, peer consent, inbo…
blackkcold Oct 20, 2026
6e06052
fix(relay-lite): persist immutable E2EE outbox for idempotent resend …
blackkcold Oct 20, 2026
23bdda1
fix(relay-lite): retransmit durable ciphertext not freshly encrypted …
blackkcold Oct 20, 2026
5994b44
fix(relay-lite): close durable outbound record after authenticated re…
blackkcold Oct 20, 2026
80a7023
test(android): verify durable Relay Lite ACK scope and encrypted outb…
blackkcold Oct 20, 2026
91a962c
fix(peer-rpc): support bounded SMS pages for offline encrypted transport
blackkcold Oct 20, 2026
416ed41
fix(relay-lite): bound offline SMS history replies to fit durable cip…
blackkcold Oct 20, 2026
ca1e9f0
fix(android): retain async Relay Lite request IDs until responses arr…
blackkcold Oct 20, 2026
45ab7c6
fix(peer-events): add size-bounded incremental events pages for Relay…
blackkcold Oct 20, 2026
bf5ab00
fix(peer-rpc): pass offline events pagination budgets to storage
blackkcold Oct 20, 2026
c03a3b8
fix(relay-lite): keep offline events responses inside encrypted frame…
blackkcold Oct 20, 2026
2663dbc
fix(android-ui): show directional v2 grants and explicit Relay Lite r…
blackkcold Oct 20, 2026
4ad92cd
fix(webrtc): dispatch reciprocal v2 RPC in both directions, retain v1…
blackkcold Oct 20, 2026
67074b0
perf(android): back off idle Relay Lite mailbox polling while preserv…
blackkcold Oct 20, 2026
cc91533
fix(ci): require all 15 Android P2P instrumented tests before preview…
blackkcold Oct 20, 2026
babf7d9
test(ci): update positive and failure export fixtures for 15 real ins…
blackkcold Oct 20, 2026
918f716
test(ci): align rejected failing instrumentation fixture with 15-test…
blackkcold Oct 20, 2026
5b45e07
docs(p2p): reflect 15 emulator tests and scoped Relay Lite sandbox co…
blackkcold Oct 20, 2026
dc72920
docs(ci): remove obsolete PR 84 warning from tested preview summary
blackkcold Oct 20, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/p2p-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,5 @@ jobs:
echo "Artifact: [simhub-p2p-tested-package](${TEST_APK_URL})"
echo "Package includes APK, SHA-256 and Android-runner test evidence."
echo "Public signaling is retired; the preview build carries the fake test description only."
echo "PR #84 remains draft and must not be merged without manual approval."
echo "Dev experimental preview: real-device and public-network acceptance remain separate release gates."
} >> "$GITHUB_STEP_SUMMARY"
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ public final class PeerInstrumentedTest {
PeerPairingManager manager=new PeerPairingManager(c);String offerQr=manager.offer("Sandbox Node","testSmsReceiptSurvivesRouteSwitchAndExpiry".equals(testName.getMethodName()),new JSONArray());
JSONObject qr=new JSONObject(new String(PeerCrypto.unb64(offerQr.substring(12)),java.nio.charset.StandardCharsets.UTF_8));String offerBody=qr.getString("body");
JSONObject offer=new JSONObject(new String(PeerCrypto.unb64(offerBody),java.nio.charset.StandardCharsets.UTF_8));PeerCrypto.verify(offer.getString("publicKey"),offerBody,qr.getString("signature"));
JSONObject response=new JSONObject().put("v",1).put("kind","response").put("offer",offerBody).put("offerHash",PeerCrypto.digest(PeerCrypto.utf8(offerBody))).put("publicKey",pub).put("ephemeral",epub).put("name","Sandbox Controller");
JSONObject response=new JSONObject().put("v",offer.getInt("v")).put("kind","response").put("reversePermissions","").put("endpoints",new JSONArray()).put("offer",offerBody).put("offerHash",PeerCrypto.digest(PeerCrypto.utf8(offerBody))).put("publicKey",pub).put("ephemeral",epub).put("name","Sandbox Controller");
String reply=PeerCrypto.b64(PeerCrypto.utf8(response.toString()));String replyQr="simhub-peer:"+PeerCrypto.b64(PeerCrypto.utf8(new JSONObject().put("body",reply).put("signature",PeerCrypto.sign(identity.getPrivate(),reply)).toString()));
String transcript=PeerCrypto.pairTranscript(offer.getString("sid"),offer.getLong("expires"),offer.getString("publicKey"),offer.getString("ephemeral"),pub,epub,offer.getString("permissions"));
String transcript=PeerCrypto.pairTranscript(offer.getString("sid"),offer.getLong("expires"),offer.getString("publicKey"),offer.getString("ephemeral"),pub,epub,offer.getString("permissions")+(offer.optInt("v")==2?"|":""));
root=PeerCrypto.derive(PeerCrypto.dh(ephemeral.getPrivate(),PeerCrypto.publicKey(offer.getString("ephemeral"))),PeerCrypto.utf8(PeerCrypto.digest(PeerCrypto.utf8(transcript))),"content-root");
assertEquals(controllerId,manager.accept(replyQr));assertTrue(Arrays.equals(root,store.key(controllerId)));
assertEquals(2,store.peer(controllerId).getInt("protocolVersion"));
assertFalse(PeerGrantPolicy.allows(store.peer(controllerId),"sms.page",false));
try{manager.accept(replyQr);fail("replayed QR must fail");}catch(SecurityException expected){}
}
@After public void tearDown()throws Exception{PeerCenterConnector.closePeer(controllerId);server.close();store.revoke(controllerId);Arrays.fill(root,(byte)0);}
Expand All @@ -49,6 +51,44 @@ private JSONObject rpc(String id,JSONObject req)throws Exception{
try{DirectPeerTransport.write(socket.getOutputStream(),PeerProtocol.seal(session,controllerId,to,id,expires,req));return PeerProtocol.open(session,to,controllerId,DirectPeerTransport.read(socket.getInputStream()),System.currentTimeMillis()/1000);}finally{Arrays.fill(session,(byte)0);}
}
}
@Test public void testRelayLiteCiphertextQueueAndScopedAcknowledgement()throws Exception{
Context c=InstrumentationRegistry.getInstrumentation().getTargetContext();
RelayLiteMailbox box=RelayLiteMailbox.get(c);
box.setEnabled(true);
try{
long now=System.currentTimeMillis()/1000;
String id=UUID.randomUUID().toString();
JSONObject inner=PeerOfflineProtocol.seal(root,controllerId,store.myId(),id,now,
now+600,new JSONObject().put("action","device.state").put("marker","sensitive-sandbox-data"));
JSONObject accepted=box.enqueue(controllerId,store.myId(),inner);
assertEquals("accepted",accepted.getString("state"));
assertFalse(accepted.getBoolean("duplicate"));
assertTrue(box.enqueue(controllerId,store.myId(),inner).getBoolean("duplicate"));
JSONObject messages=box.pull(store.myId(),4);
assertEquals(1,messages.getJSONArray("items").length());
JSONObject first=messages.getJSONArray("items").getJSONObject(0);
assertFalse(first.toString().contains("sensitive-sandbox-data"));
assertEquals("sensitive-sandbox-data",PeerOfflineProtocol.open(root,controllerId,
store.myId(),first.getJSONObject("frame"),now).getString("marker"));
assertFalse(box.ack(controllerId,controllerId,id));
assertTrue(box.ack(store.myId(),controllerId,id));
assertEquals(0,box.pull(store.myId(),4).getJSONArray("items").length());
}finally{box.setEnabled(false);}
}

@Test public void testRelayLiteOutboundIdempotenceAfterRetry()throws Exception{
JSONObject body=new JSONObject().put("action","device.state");
long now=System.currentTimeMillis()/1000,expires=now+600;
String id=UUID.randomUUID().toString();
JSONObject first=store.liteOutbound(controllerId,id,body,expires);
JSONObject retry=store.liteOutbound(controllerId,id,body,expires);
assertEquals(first.toString(),retry.toString());
try{
store.liteOutbound(controllerId,id,new JSONObject().put("action","sms.send"),expires);
fail("mutated request reused a logical id");
}catch(SecurityException expected){}
store.clearLiteOutbound(controllerId,id);
}
@Test public void testAuthenticatedDirectStateAndRevocation()throws Exception{
assertEquals("succeeded",rpc(new JSONObject().put("action","device.state")).getString("state"));
store.revoke(controllerId);try{rpc(new JSONObject().put("action","device.state"));fail("revoked peer accepted");}catch(java.io.IOException expected){}
Expand Down
Loading
Loading