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