diff --git a/lib/native/client.dart b/lib/native/client.dart index 6685b74..bdbf589 100644 --- a/lib/native/client.dart +++ b/lib/native/client.dart @@ -112,14 +112,14 @@ class FumiNative { Future rotations() => _io((ffi) => ffi.rotations(_s)); - Future pinServer(String host, String keyB32) => - _io((ffi) => ffi.pinServer(_s, host, keyB32)); + Future pinServer(String host, String keyB32, int port) => + _io((ffi) => ffi.pinServer(_s, host, keyB32, port)); - Future serverPin(String host) => - _io((ffi) => ffi.serverPin(_s, host)); + Future serverPin(String host, int port) => + _io((ffi) => ffi.serverPin(_s, host, port)); - Future unpinServer(String host) => - _io((ffi) => ffi.unpinServer(_s, host)); + Future unpinServer(String host, int port) => + _io((ffi) => ffi.unpinServer(_s, host, port)); Future> pins() => _io((ffi) => ffi.pins(_s)); diff --git a/lib/native/ffi.dart b/lib/native/ffi.dart index 32a7d69..016943b 100644 --- a/lib/native/ffi.dart +++ b/lib/native/ffi.dart @@ -109,15 +109,15 @@ class SmolFfi { Void Function(Pointer), void Function(Pointer)>("smol_flag_free"); - late final int Function(Pointer, Pointer, Pointer) + late final int Function(Pointer, Pointer, Pointer, int) _pinServer = _lib.lookupFunction< - Int32 Function(Pointer, Pointer, Pointer), - int Function( - Pointer, Pointer, Pointer)>("smol_pin_server"); - late final Pointer Function(Pointer, Pointer) + Int32 Function(Pointer, Pointer, Pointer, Uint16), + int Function(Pointer, Pointer, Pointer, + int)>("smol_pin_server"); + late final Pointer Function(Pointer, Pointer, int) _serverPin = _lib.lookupFunction< - Pointer Function(Pointer, Pointer), - Pointer Function(Pointer, Pointer)>( + Pointer Function(Pointer, Pointer, Uint16), + Pointer Function(Pointer, Pointer, int)>( "smol_server_pin"); late final Pointer Function(Pointer) _storeAccount = _lib .lookupFunction Function(Pointer), @@ -226,9 +226,11 @@ class SmolFfi { Pointer, Pointer, Pointer, Int32), int Function(Pointer, Pointer, Pointer, int)>("smol_contact_save"); - late final int Function(Pointer, Pointer) _unpinServer = _lib - .lookupFunction, Pointer), - int Function(Pointer, Pointer)>("smol_unpin_server"); + late final int Function(Pointer, Pointer, int) _unpinServer = + _lib.lookupFunction< + Int32 Function(Pointer, Pointer, Uint16), + int Function( + Pointer, Pointer, int)>("smol_unpin_server"); late final Pointer Function(Pointer) _pins = _lib.lookupFunction< Pointer Function(Pointer), Pointer Function(Pointer)>("smol_pins"); @@ -334,12 +336,14 @@ class SmolFfi { void freeFlag(int flag) => _flagFree(_h(flag)); - void pinServer(int store, String host, String keyB32) { - if (_pinServer(_h(store), _text(host), _text(keyB32)) != smolOk) _fail(); + void pinServer(int store, String host, String keyB32, int port) { + if (_pinServer(_h(store), _text(host), _text(keyB32), port) != smolOk) { + _fail(); + } } - void unpinServer(int store, String host) { - if (_unpinServer(_h(store), _text(host)) != smolOk) _fail(); + void unpinServer(int store, String host, int port) { + if (_unpinServer(_h(store), _text(host), port) != smolOk) _fail(); } /// Every pin: [{host, key}] with keys as base32. @@ -355,8 +359,8 @@ class SmolFfi { _h(store), _text(address), _text(keyB32), verified ? 1 : 0); /// Null when nothing is pinned. - String? serverPin(int store, String host) { - final text = take(_serverPin(_h(store), _text(host))); + String? serverPin(int store, String host, int port) { + final text = take(_serverPin(_h(store), _text(host), port)); return text.isEmpty ? null : text; } diff --git a/lib/presentation/screens/onboarding_screen.dart b/lib/presentation/screens/onboarding_screen.dart index 75e62dc..cbfea57 100644 --- a/lib/presentation/screens/onboarding_screen.dart +++ b/lib/presentation/screens/onboarding_screen.dart @@ -161,7 +161,7 @@ class _OnboardingScreenState extends ConsumerState { // "already pinned, e.g. by a previous attempt or a preset server". final serverKey = serverKeyController.text.trim(); if (serverKey.isNotEmpty) { - client.pinServer(address.host, serverKey); + client.pinServer(address.host, serverKey, address.port); } if (recall && restoreIntent) { await client.restoreAndRecall(seedController.text, address.short); @@ -210,7 +210,7 @@ class _OnboardingScreenState extends ConsumerState { image: AssetImage("assets/images/bug_logo.png"), fit: BoxFit.contain), ), - Text("kirakira", + Text("キラキラ", style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 40), Text(title, style: Theme.of(context).textTheme.titleMedium), diff --git a/lib/presentation/screens/settings_screen.dart b/lib/presentation/screens/settings_screen.dart index a9b5746..70a0f6b 100644 --- a/lib/presentation/screens/settings_screen.dart +++ b/lib/presentation/screens/settings_screen.dart @@ -12,6 +12,7 @@ import "package:smol_mail/data/providers/providers.dart"; import "package:smol_mail/presentation/routes/app_router.gr.dart"; import "package:smol_mail/presentation/theme/app_colors.dart"; import "package:smol_mail/shared/utils/snackbar.dart"; +import "package:smol_mail/smol/address.dart"; import "package:smol_mail/smol/errors.dart"; import "package:smol_mail/smol/ui.dart"; @@ -320,7 +321,7 @@ class _SettingsScreenState extends ConsumerState { trailing: IconButton( icon: const Icon(Icons.delete_outline, size: 18), onPressed: () { - store.unpinServer(host); + store.unpinServer(host, defaultPort); ref.read(revisionProvider.notifier).bump(); setState(() {}); }, @@ -350,9 +351,8 @@ class _SettingsScreenState extends ConsumerState { TextButton( onPressed: () { try { - ref - .read(clientProvider) - .pinServer(hostController.text, keyController.text); + ref.read(clientProvider).pinServer( + hostController.text, keyController.text, defaultPort); keyController.clear(); setState(() {}); } on SmolError catch (err) { diff --git a/lib/smol/client.dart b/lib/smol/client.dart index 1ea6a7a..750435b 100644 --- a/lib/smol/client.dart +++ b/lib/smol/client.dart @@ -144,7 +144,7 @@ class SmolClient { // registration and fetching demand it anyway (sec 4), so restoring is // stopped at the pin step rather than letting the account bind to a // server whose key nobody verified. - if (store.serverPin(addr.host) == null) { + if (store.serverPin(addr.host, addr.port) == null) { throw SmolError("no pinned key for ${addr.host}. Obtain it from the " "operator through a trusted channel, then pin it in settings."); } @@ -157,7 +157,8 @@ class SmolClient { } } - void pinServer(String host, String keyB32) => store.pinServer(host, keyB32); + void pinServer(String host, String keyB32, int port) => + store.pinServer(host, keyB32, port); Future registerAccount(String addressText, {String token = ""}) async { final addr = parseAddress(addressText); diff --git a/lib/smol/config.dart b/lib/smol/config.dart index aae3fee..c879c72 100644 --- a/lib/smol/config.dart +++ b/lib/smol/config.dart @@ -9,6 +9,7 @@ // removable in settings like any other, and never overwrites a host the user // (or a previous install) already pinned. +import "package:smol_mail/smol/address.dart"; import "package:smol_mail/smol/store.dart"; const _presetHost = String.fromEnvironment("SMOL_PRESET_SERVER"); @@ -16,7 +17,7 @@ const _presetKey = String.fromEnvironment("SMOL_PRESET_SERVER_KEY"); void applyPresetServer(SmolStore store) { if (_presetHost.isEmpty || _presetKey.isEmpty) return; - if (store.serverPin(_presetHost) != null) return; + if (store.serverPin(_presetHost, defaultPort) != null) return; // A malformed preset leaves the host unpinned rather than blocking boot. - store.pinServer(_presetHost, _presetKey); + store.pinServer(_presetHost, _presetKey, defaultPort); } diff --git a/lib/smol/store.dart b/lib/smol/store.dart index ef4a414..09ea825 100644 --- a/lib/smol/store.dart +++ b/lib/smol/store.dart @@ -291,10 +291,11 @@ class SmolStore { // --- pins ------------------------------------------------------------------- /// Pins a server's static key (§4): sync, because it is one local write. - void pinServer(String host, String keyB32) => - _ffi.pinServer(_native.store!, host, keyB32); + void pinServer(String host, String keyB32, int port) => + _ffi.pinServer(_native.store!, host, keyB32, port); - String? serverPin(String host) => _ffi.serverPin(_native.store!, host); + String? serverPin(String host, int port) => + _ffi.serverPin(_native.store!, host, port); List<(String, String)> allPins() { final rows = _ffi.pins(_native.store!); @@ -304,7 +305,8 @@ class SmolStore { ]; } - Future unpinServer(String host) async => _native.unpinServer(host); + Future unpinServer(String host, int port) async => + _native.unpinServer(host, port); // --- backups ---------------------------------------------------------------- diff --git a/native/Cargo.lock b/native/Cargo.lock index 209a64d..9ae3a6b 100644 --- a/native/Cargo.lock +++ b/native/Cargo.lock @@ -271,7 +271,7 @@ checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" [[package]] name = "fumi-core" version = "0.1.0" -source = "git+ssh://git@code.randogoth.com:2222/randogoth/fumi.git?rev=2d65d66#2d65d66012fa40121e4dc3d8d15db9ffc486569a" +source = "git+ssh://git@code.randogoth.com:2222/randogoth/fumi.git?rev=1468f3c#1468f3cbc0a89a0cb073f46a985911c0327885d1" dependencies = [ "chacha20poly1305", "data-encoding", diff --git a/native/Cargo.toml b/native/Cargo.toml index adb0e95..52a28c3 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" crate-type = ["lib", "cdylib"] [dependencies] -fumi-core = { git = "ssh://git@code.randogoth.com:2222/randogoth/fumi.git", rev = "2d65d66" } +fumi-core = { git = "ssh://git@code.randogoth.com:2222/randogoth/fumi.git", rev = "1468f3c" } rand_core = { version = "0.6", features = ["getrandom"] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/native/src/ffi.rs b/native/src/ffi.rs index f16f3c8..8b036e5 100644 --- a/native/src/ffi.rs +++ b/native/src/ffi.rs @@ -126,22 +126,29 @@ struct ErrorJson { /// The raw byte behind an UNKNOWN_STATUS (code 11). #[serde(skip_serializing_if = "Option::is_none")] status: Option, + #[serde(skip_serializing_if = "Option::is_none")] + port: Option, } impl ErrorJson { fn of(error: &Error) -> ErrorJson { let message = error.to_string(); - let (host, pinned, presented, address, known, offered, status) = match error { + let (host, pinned, presented, address, known, offered, status, port) = match error { Error::UnknownStatus(status, _) => { - (None, None, None, None, None, None, Some(*status)) + (None, None, None, None, None, None, Some(*status), None) + } + Error::NotPinned { host, port } => { + (Some(host.clone()), None, None, None, None, None, None, Some(*port)) + } + Error::Unreachable { host, .. } => { + (Some(host.clone()), None, None, None, None, None, None, None) } - Error::NotPinned { host } => (Some(host.clone()), None, None, None, None, None, None), - Error::Unreachable { host, .. } => (Some(host.clone()), None, None, None, None, None, None), Error::HandshakeRefused { host, .. } => { - (Some(host.clone()), None, None, None, None, None, None) + (Some(host.clone()), None, None, None, None, None, None, None) } Error::PinMismatch { host, + port, pinned, presented, } => ( @@ -152,6 +159,7 @@ impl ErrorJson { None, None, None, + Some(*port), ), Error::KeyChanged { address, @@ -165,8 +173,9 @@ impl ErrorJson { Some(b32(known)), Some(b32(offered)), None, + None, ), - _ => (None, None, None, None, None, None, None), + _ => (None, None, None, None, None, None, None, None), }; ErrorJson { code: code_of(error), @@ -178,6 +187,7 @@ impl ErrorJson { known, offered, status, + port, } } } @@ -383,23 +393,24 @@ pub extern "C" fn smol_pin_server( store: *mut Store, host: *const c_char, key_b32: *const c_char, + port: u16, ) -> i32 { status(|| { let store = unsafe { store.as_ref().ok_or_else(|| Error::Other("store handle is null".into()))? }; let key: [u8; KEY_LEN] = unb32(text(key_b32)?)? .try_into() .map_err(|_| Error::Other("server key must decode to 32 bytes".into()))?; - store.pin_server(text(host)?, &key) + store.pin_server(text(host)?, port, &key) }) } /// The pinned key for a host, base32. Empty string: none pinned. Null: error. #[no_mangle] -pub extern "C" fn smol_server_pin(store: *mut Store, host: *const c_char) -> *mut c_char { +pub extern "C" fn smol_server_pin(store: *mut Store, host: *const c_char, port: u16) -> *mut c_char { match guarded(|| { let store = unsafe { store.as_ref().ok_or_else(|| Error::Other("store handle is null".into()))? }; Ok(store - .server_pin(text(host)?)? + .server_pin(text(host)?, port)? .map(|key| b32(&key)) .unwrap_or_default()) }) { @@ -426,12 +437,13 @@ pub extern "C" fn smol_contact_save( }) } -/// Removes a pin: the next session against that host is trust on first use. +/// Removes a pin: the next session against that host and port is trust on +/// first use. #[no_mangle] -pub extern "C" fn smol_unpin_server(store: *mut Store, host: *const c_char) -> i32 { +pub extern "C" fn smol_unpin_server(store: *mut Store, host: *const c_char, port: u16) -> i32 { status(|| { let store = unsafe { store.as_ref().ok_or_else(|| Error::Other("store handle is null".into()))? }; - store.unpin_server(text(host)?) + store.unpin_server(text(host)?, port) }) } @@ -1178,6 +1190,7 @@ mod tests { let json: serde_json::Value = serde_json::from_str(&slot).unwrap(); assert_eq!(json["code"], NOT_PINNED); assert_eq!(json["host"], "127.0.0.1"); + assert_eq!(json["port"], 19619); // Strings returned on success must be freed without double-free. assert_eq!(take(smol_store_account(store)), ""); @@ -1193,7 +1206,8 @@ mod tests { fn mail_and_contacts_render_as_json() { let store = smol_store_memory(); let s = unsafe { &*store }; - s.pin_server("example.org", &[1u8; KEY_LEN]).unwrap(); + s.pin_server("example.org", fumi_core::address::DEFAULT_PORT, &[1u8; KEY_LEN]) + .unwrap(); s.save_contact("alice@example.org", &[2u8; KEY_LEN], true).unwrap(); s.save_history("alice@example.org", &[9u8; KEY_LEN], 500).unwrap(); s.store_inbox(&[3u8; ID_LEN], b"sealed", 7, false, true).unwrap(); diff --git a/native/src/lib.rs b/native/src/lib.rs index b04be2b..2008ce7 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -86,8 +86,8 @@ mod tests { let alice_addr = Address::parse(&format!("a{run}@127.0.0.1:19619")).unwrap(); let bob_addr = Address::parse(&format!("b{run}@127.0.0.1:19619")).unwrap(); - alice_store.pin_server("127.0.0.1", &server).unwrap(); - bob_store.pin_server("127.0.0.1", &server).unwrap(); + alice_store.pin_server("127.0.0.1", 19619, &server).unwrap(); + bob_store.pin_server("127.0.0.1", 19619, &server).unwrap(); register(&alice_store, &alice_addr, &alice, None, 5).unwrap(); register(&bob_store, &bob_addr, &bob, None, 5).unwrap(); @@ -168,8 +168,8 @@ mod tests { let run = fumi_core::crypto::b32(&suffix); let alice_addr = Address::parse(&format!("a{run}@127.0.0.1:19619")).unwrap(); let bob_addr = Address::parse(&format!("b{run}@127.0.0.1:19619")).unwrap(); - alice_store.pin_server("127.0.0.1", &server).unwrap(); - bob_store.pin_server("127.0.0.1", &server).unwrap(); + alice_store.pin_server("127.0.0.1", 19619, &server).unwrap(); + bob_store.pin_server("127.0.0.1", 19619, &server).unwrap(); register(&alice_store, &alice_addr, &alice, None, 5).unwrap(); register(&bob_store, &bob_addr, &bob, None, 5).unwrap(); @@ -272,7 +272,7 @@ mod tests { fumi_core::address::Address::parse(&format!("u{run}@{host}:19619")) .unwrap() .with_dial("127.0.0.1"); - store.pin_server(&host, &server).unwrap(); + store.pin_server(&host, 19619, &server).unwrap(); register(&store, &addr, &account, None, 5).unwrap(); assert_eq!(store.account().unwrap().unwrap().short(), format!("u{run}@{host}:19619")); @@ -296,7 +296,10 @@ mod tests { let unpinned = Store::open_in_memory().unwrap(); match connect(&unpinned, &addr, true, 5) { - Err(Error::NotPinned { host }) => assert_eq!(host, "127.0.0.1"), + Err(Error::NotPinned { host, port }) => { + assert_eq!(host, "127.0.0.1"); + assert_eq!(port, 19619); + } Err(err) => panic!("expected NotPinned, got {err}"), Ok(_) => panic!("expected NotPinned, connected"), } @@ -304,7 +307,7 @@ mod tests { let mut wrong = [0u8; KEY_LEN]; OsRng.fill_bytes(&mut wrong); let mismatched = Store::open_in_memory().unwrap(); - mismatched.pin_server("127.0.0.1", &wrong).unwrap(); + mismatched.pin_server("127.0.0.1", 19619, &wrong).unwrap(); match connect(&mismatched, &addr, true, 5) { Err(Error::PinMismatch { .. }) => {} Err(err) => panic!("expected PinMismatch, got {err}"), @@ -348,8 +351,8 @@ mod tests { let run = fumi_core::crypto::b32(&suffix); let carol_addr = Address::parse(&format!("c{run}@127.0.0.1:19619")).unwrap(); let alice_addr = Address::parse(&format!("d{run}@127.0.0.1:19619")).unwrap(); - carol_store.pin_server("127.0.0.1", &server).unwrap(); - alice_store.pin_server("127.0.0.1", &server).unwrap(); + carol_store.pin_server("127.0.0.1", 19619, &server).unwrap(); + alice_store.pin_server("127.0.0.1", 19619, &server).unwrap(); register(&carol_store, &carol_addr, &carol, None, 5).unwrap(); register(&alice_store, &alice_addr, &alice, None, 5).unwrap(); diff --git a/test/e2e_test.dart b/test/e2e_test.dart index 2c67ea3..9a83dbd 100644 --- a/test/e2e_test.dart +++ b/test/e2e_test.dart @@ -60,7 +60,7 @@ void main() { final me = client.identity!; final user = "e2e${hex(randomBytes(4))}"; final address = parseAddress("$user@$host"); - store.pinServer(host, serverKey); + store.pinServer(host, serverKey, port); await client.registerAccount(address.short); expect(client.accountAddress()!.short, address.short); @@ -129,7 +129,7 @@ void main() { secondStore.restoreMaster(store.master()!, 0); await expectLater( secondDevice.recallAccount(address.short), throwsA(isA())); - secondStore.pinServer(host, serverKey); + secondStore.pinServer(host, serverKey, port); await expectLater( secondDevice.registerAccount(address.short), throwsA(isA())); final bound = await secondDevice.recallAccount(address.short); @@ -157,7 +157,7 @@ void main() { await client.createIdentity(); final user = "e2ekeep${hex(randomBytes(4))}"; final address = parseAddress("$user@$host"); - store.pinServer(host, serverKey); + store.pinServer(host, serverKey, port); await client.registerAccount(address.short); await store.setLeaveOnServer(true); diff --git a/test/interop_fumi_test.dart b/test/interop_fumi_test.dart index c44f0f6..43d5d4e 100644 --- a/test/interop_fumi_test.dart +++ b/test/interop_fumi_test.dart @@ -13,6 +13,7 @@ import "dart:typed_data"; import "package:flutter_test/flutter_test.dart"; import "package:hive_flutter/hive_flutter.dart"; +import "package:smol_mail/smol/address.dart"; import "package:smol_mail/smol/store.dart"; const fumiBinary = "../fumi/target/release/fumi"; @@ -38,7 +39,7 @@ void main() { stateBox: "interop-state", readBox: "interop-read"); mine.restoreMaster(master, 0); - mine.pinServer("example.org", pinKey); + mine.pinServer("example.org", pinKey, defaultPort); // One contact, bound to the current key; rotation history is written by // the trust engine on a validated rotation, not by a plain re-save. await mine.saveContact("alice@example.org", pinKey, verified: true); @@ -72,6 +73,6 @@ void main() { // The binding survived both directions of the round trip. final contact = restored.contact("alice@example.org")!; expect(contact.key, pinKey); - expect(restored.serverPin("example.org"), pinKey); + expect(restored.serverPin("example.org", defaultPort), pinKey); }); } diff --git a/test/native_binding_test.dart b/test/native_binding_test.dart index 3a36ebc..82750b5 100644 --- a/test/native_binding_test.dart +++ b/test/native_binding_test.dart @@ -11,6 +11,7 @@ import "package:flutter_test/flutter_test.dart"; import "package:smol_mail/native/client.dart"; import "package:smol_mail/native/ffi.dart"; +import "package:smol_mail/smol/address.dart"; const spikeServerKey = "wukbhdiwboarbg4pujxstbqi3caveunrruaopbwxr226ga3xv5ga"; const spikeServer = "127.0.0.1"; @@ -39,7 +40,9 @@ void main() async { expect(await client.accountAddress(), isNull); // A pinned but dead host fails by code, never by prose: Unreachable (7). - await client.pinServer("127.0.0.1", spikeServerKey); + // The pin is scoped to the dead port itself — a default-port pin would + // not cover it, and the register would fail NotPinned instead. + await client.pinServer("127.0.0.1", spikeServerKey, 19629); client.setMaster(Uint8List.fromList(List.filled(32, 9))); try { await client.register("nobody@127.0.0.1:19629"); @@ -63,7 +66,7 @@ void main() async { final mine = FumiNative("${dir.path}/mine.db"); await mine.open(); mine.setMaster(master); - await mine.pinServer("example.org", spikeServerKey); + await mine.pinServer("example.org", spikeServerKey, defaultPort); await mine.importContact( "smol://alice@example.org/ayb6y3mwr3cfkcmcaqbn3cgfi6xsrsoqkalykw5s7oqmwqcpnmsq"); @@ -76,7 +79,7 @@ void main() async { as Map; expect(summary["contactsAdded"], 1); expect(summary["pinsAdded"], 1); - expect(await restored.serverPin("example.org"), spikeServerKey); + expect(await restored.serverPin("example.org", defaultPort), spikeServerKey); await mine.close(); await restored.close(); @@ -98,14 +101,14 @@ void main() async { final alice = FumiNative("${dir.path}/alice.db"); await alice.open(); alice.setMaster(aliceMaster); - await alice.pinServer(spikeServer, spikeServerKey); + await alice.pinServer(spikeServer, spikeServerKey, spikePort); await alice.register("a$run@$spikeServer:$spikePort"); expect(await alice.accountAddress(), "a$run@$spikeServer:$spikePort"); final bob = FumiNative("${dir.path}/bob.db"); await bob.open(); bob.setMaster(bobMaster); - await bob.pinServer(spikeServer, spikeServerKey); + await bob.pinServer(spikeServer, spikeServerKey, spikePort); await bob.register("b$run@$spikeServer:$spikePort"); final sent = await alice.send("b$run@$spikeServer:$spikePort", @@ -147,7 +150,7 @@ void main() async { final alice = FumiNative("${dir.path}/alice.db"); await alice.open(); alice.setMaster(aliceMaster); - await alice.pinServer(spikeServer, spikeServerKey); + await alice.pinServer(spikeServer, spikeServerKey, spikePort); await alice.register("a$run@$spikeServer:$spikePort"); final bob = FumiNative("${dir.path}/bob.db"); @@ -156,7 +159,7 @@ void main() async { // The onboarding screen zeroes its own master reference on dispose; // whatever holds the master after that must not share that buffer. bobMaster.fillRange(0, 32, 0); - await bob.pinServer(spikeServer, spikeServerKey); + await bob.pinServer(spikeServer, spikeServerKey, spikePort); await bob.register("b$run@$spikeServer:$spikePort"); await alice.send("b$run@$spikeServer:$spikePort", diff --git a/test/recall_flow_test.dart b/test/recall_flow_test.dart index 3ddfa97..4f18021 100644 --- a/test/recall_flow_test.dart +++ b/test/recall_flow_test.dart @@ -36,7 +36,7 @@ class StubClient extends SmolClient { @override Future restoreAndRecall(String masterHex, String addressText) async { final addr = parseAddress(addressText); - if (store.serverPin(addr.host) == null) { + if (store.serverPin(addr.host, addr.port) == null) { throw SmolError("no pinned key for ${addr.host}"); } store.restoreMaster(unhex(masterHex.trim()), 0); diff --git a/test/store_test.dart b/test/store_test.dart index 5e296af..c31163b 100644 --- a/test/store_test.dart +++ b/test/store_test.dart @@ -8,6 +8,7 @@ import "dart:typed_data"; import "package:flutter_test/flutter_test.dart"; import "package:hive_flutter/hive_flutter.dart"; +import "package:smol_mail/smol/address.dart"; import "package:smol_mail/smol/store.dart"; Uint8List randomBytes(int n) => @@ -66,11 +67,11 @@ void main() { expect(store.allPins(), isEmpty); // A syntactically valid key: the system server's, a real 52-char form. const key = "lm2gqd7e5q67xq3isc5hx6jfj2q7a7xvq4l7trctxipudujovmgq"; - store.pinServer("example.org", key); - expect(store.serverPin("example.org"), key); + store.pinServer("example.org", key, defaultPort); + expect(store.serverPin("example.org", defaultPort), key); expect(store.allPins().length, 1); - await store.unpinServer("example.org"); - expect(store.serverPin("example.org"), isNull); + await store.unpinServer("example.org", defaultPort); + expect(store.serverPin("example.org", defaultPort), isNull); }); test("wipe clears every secret and mark, overwriting the master", () async { @@ -80,7 +81,7 @@ void main() { // The store's own copy, read back before the wipe. final stored = store.master()!; store.pinServer("example.org", - "lm2gqd7e5q67xq3isc5hx6jfj2q7a7xvq4l7trctxipudujovmgq"); + "lm2gqd7e5q67xq3isc5hx6jfj2q7a7xvq4l7trctxipudujovmgq", defaultPort); store.markRead("cd" * 32); await store.wipe(); @@ -93,7 +94,7 @@ void main() { expect(master.every((b) => b == 0), isFalse); expect(store.master(), isNull); expect(store.identity(), isNull); - expect(store.serverPin("example.org"), isNull); + expect(store.serverPin("example.org", defaultPort), isNull); expect(store.isRead("cd" * 32), isFalse); }); }