Merge branch 'safety-by-default' into 'main'

Safety by default

See merge request veilid/veilid!235
This commit is contained in:
Christien Rioux 2023-11-06 01:40:02 +00:00
commit 765ecee450
24 changed files with 1560 additions and 1619 deletions

View File

@ -372,7 +372,51 @@ impl ConnectionManager {
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
/// Callbacks
/// Asynchronous Event Processor
async fn process_connection_manager_event(
&self,
event: ConnectionManagerEvent,
allow_accept: bool,
) {
match event {
ConnectionManagerEvent::Accepted(prot_conn) => {
if !allow_accept {
return;
}
// Async lock on the remote address for atomicity per remote
let _lock_guard = self
.arc
.address_lock_table
.lock_tag(prot_conn.flow().remote_address().socket_addr())
.await;
let mut inner = self.arc.inner.lock();
match &mut *inner {
Some(inner) => {
// Register the connection
// We don't care if this fails, since nobody here asked for the inbound connection.
// If it does, we just drop the connection
let _ = self.on_new_protocol_network_connection(inner, prot_conn);
}
None => {
// If this somehow happens, we're shutting down
}
};
}
ConnectionManagerEvent::Dead(mut conn) => {
let _lock_guard = self
.arc
.address_lock_table
.lock_tag(conn.flow().remote_address().socket_addr())
.await;
conn.close();
conn.await;
}
}
}
#[instrument(level = "trace", skip_all)]
async fn async_processor(
@ -382,40 +426,11 @@ impl ConnectionManager {
) {
// Process async commands
while let Ok(Ok(event)) = receiver.recv_async().timeout_at(stop_token.clone()).await {
match event {
ConnectionManagerEvent::Accepted(prot_conn) => {
// Async lock on the remote address for atomicity per remote
let _lock_guard = self
.arc
.address_lock_table
.lock_tag(prot_conn.flow().remote_address().socket_addr())
.await;
let mut inner = self.arc.inner.lock();
match &mut *inner {
Some(inner) => {
// Register the connection
// We don't care if this fails, since nobody here asked for the inbound connection.
// If it does, we just drop the connection
let _ = self.on_new_protocol_network_connection(inner, prot_conn);
}
None => {
// If this somehow happens, we're shutting down
}
};
}
ConnectionManagerEvent::Dead(mut conn) => {
let _lock_guard = self
.arc
.address_lock_table
.lock_tag(conn.flow().remote_address().socket_addr())
.await;
conn.close();
conn.await;
}
}
self.process_connection_manager_event(event, true).await;
}
// Ensure receiver is drained completely
for event in receiver.drain() {
self.process_connection_manager_event(event, false).await;
}
}

View File

@ -11,35 +11,55 @@ lazy_static! {
}
pub async fn test_get_dht_value_unopened(api: VeilidAPI) {
let rc = api.routing_context();
let rc = api
.routing_context()
.unwrap()
.with_safety(SafetySelection::Unsafe(Sequencing::EnsureOrdered))
.unwrap();
let result = rc.get_dht_value(*BOGUS_KEY, 0, false).await;
assert_err!(result);
}
pub async fn test_open_dht_record_nonexistent_no_writer(api: VeilidAPI) {
let rc = api.routing_context();
let rc = api
.routing_context()
.unwrap()
.with_safety(SafetySelection::Unsafe(Sequencing::EnsureOrdered))
.unwrap();
let result = rc.get_dht_value(*BOGUS_KEY, 0, false).await;
assert_err!(result);
}
pub async fn test_close_dht_record_nonexistent(api: VeilidAPI) {
let rc = api.routing_context();
let rc = api
.routing_context()
.unwrap()
.with_safety(SafetySelection::Unsafe(Sequencing::EnsureOrdered))
.unwrap();
let result = rc.close_dht_record(*BOGUS_KEY).await;
assert_err!(result);
}
pub async fn test_delete_dht_record_nonexistent(api: VeilidAPI) {
let rc = api.routing_context();
let rc = api
.routing_context()
.unwrap()
.with_safety(SafetySelection::Unsafe(Sequencing::EnsureOrdered))
.unwrap();
let result = rc.delete_dht_record(*BOGUS_KEY).await;
assert_err!(result);
}
pub async fn test_create_delete_dht_record_simple(api: VeilidAPI) {
let rc = api.routing_context();
let rc = api
.routing_context()
.unwrap()
.with_safety(SafetySelection::Unsafe(Sequencing::EnsureOrdered))
.unwrap();
let rec = rc
.create_dht_record(
@ -55,7 +75,11 @@ pub async fn test_create_delete_dht_record_simple(api: VeilidAPI) {
}
pub async fn test_get_dht_value_nonexistent(api: VeilidAPI) {
let rc = api.routing_context();
let rc = api
.routing_context()
.unwrap()
.with_safety(SafetySelection::Unsafe(Sequencing::EnsureOrdered))
.unwrap();
let rec = rc
.create_dht_record(
@ -73,7 +97,11 @@ pub async fn test_get_dht_value_nonexistent(api: VeilidAPI) {
}
pub async fn test_set_get_dht_value(api: VeilidAPI) {
let rc = api.routing_context();
let rc = api
.routing_context()
.unwrap()
.with_safety(SafetySelection::Unsafe(Sequencing::EnsureOrdered))
.unwrap();
let rec = rc
.create_dht_record(
@ -124,7 +152,11 @@ pub async fn test_set_get_dht_value(api: VeilidAPI) {
}
pub async fn test_open_writer_dht_value(api: VeilidAPI) {
let rc = api.routing_context();
let rc = api
.routing_context()
.unwrap()
.with_safety(SafetySelection::Unsafe(Sequencing::EnsureOrdered))
.unwrap();
let rec = rc
.create_dht_record(

View File

@ -189,8 +189,8 @@ impl VeilidAPI {
// Routing Context
/// Get a new `RoutingContext` object to use to send messages over the Veilid network.
pub fn routing_context(&self) -> RoutingContext {
RoutingContext::new(self.clone())
pub fn routing_context(&self) -> VeilidAPIResult<RoutingContext> {
RoutingContext::try_new(self.clone())
}
/// Parse a string into a target object that can be used in a [RoutingContext]
@ -234,8 +234,8 @@ impl VeilidAPI {
pub async fn new_private_route(&self) -> VeilidAPIResult<(RouteId, Vec<u8>)> {
self.new_custom_private_route(
&VALID_CRYPTO_KINDS,
Stability::default(),
Sequencing::default(),
Stability::Reliable,
Sequencing::PreferOrdered,
)
.await
}

View File

@ -1394,10 +1394,10 @@ impl VeilidAPI {
)
.ok();
// Get routing context with optional privacy
let rc = self.routing_context();
// Get routing context with optional safety
let rc = self.routing_context()?;
let rc = if let Some(ss) = ss {
match rc.with_custom_privacy(ss) {
match rc.with_safety(ss) {
Err(e) => return Ok(format!("Can't use safety selection: {}", e)),
Ok(v) => v,
}
@ -1453,9 +1453,9 @@ impl VeilidAPI {
};
// Get routing context with optional privacy
let rc = self.routing_context();
let rc = self.routing_context()?;
let rc = if let Some(ss) = ss {
match rc.with_custom_privacy(ss) {
match rc.with_safety(ss) {
Err(e) => return Ok(format!("Can't use safety selection: {}", e)),
Ok(v) => v,
}
@ -1514,9 +1514,9 @@ impl VeilidAPI {
let data = get_debug_argument_at(&args, 4, "debug_record_set", "data", get_data)?;
// Get routing context with optional privacy
let rc = self.routing_context();
let rc = self.routing_context()?;
let rc = if let Some(ss) = ss {
match rc.with_custom_privacy(ss) {
match rc.with_safety(ss) {
Err(e) => return Ok(format!("Can't use safety selection: {}", e)),
Ok(v) => v,
}
@ -1560,7 +1560,7 @@ impl VeilidAPI {
let key = get_debug_argument_at(&args, 1, "debug_record_delete", "key", get_typed_key)?;
// Do a record delete
let rc = self.routing_context();
let rc = self.routing_context()?;
match rc.delete_dht_record(key).await {
Err(e) => return Ok(format!("Can't delete DHT record: {}", e)),
Ok(v) => v,

View File

@ -173,7 +173,8 @@ pub enum ResponseOp {
},
// Routing Context
NewRoutingContext {
value: u32,
#[serde(flatten)]
result: ApiResult<u32>,
},
RoutingContext(Box<RoutingContextResponse>),
// TableDb

View File

@ -235,20 +235,22 @@ impl JsonRequestProcessor {
self.release_routing_context(rcr.rc_id);
RoutingContextResponseOp::Release {}
}
RoutingContextRequestOp::WithPrivacy => RoutingContextResponseOp::WithPrivacy {
result: to_json_api_result(
routing_context
.clone()
.with_privacy()
.map(|new_rc| self.add_routing_context(new_rc)),
),
},
RoutingContextRequestOp::WithCustomPrivacy { safety_selection } => {
RoutingContextResponseOp::WithCustomPrivacy {
RoutingContextRequestOp::WithDefaultSafety => {
RoutingContextResponseOp::WithDefaultSafety {
result: to_json_api_result(
routing_context
.clone()
.with_custom_privacy(safety_selection)
.with_default_safety()
.map(|new_rc| self.add_routing_context(new_rc)),
),
}
}
RoutingContextRequestOp::WithSafety { safety_selection } => {
RoutingContextResponseOp::WithSafety {
result: to_json_api_result(
routing_context
.clone()
.with_safety(safety_selection)
.map(|new_rc| self.add_routing_context(new_rc)),
),
}
@ -259,6 +261,9 @@ impl JsonRequestProcessor {
.add_routing_context(routing_context.clone().with_sequencing(sequencing)),
}
}
RoutingContextRequestOp::Safety => RoutingContextResponseOp::Safety {
value: routing_context.safety(),
},
RoutingContextRequestOp::AppCall { target, message } => {
RoutingContextResponseOp::AppCall {
result: to_json_api_result_with_vec_u8(
@ -597,7 +602,11 @@ impl JsonRequestProcessor {
result: to_json_api_result(self.api.app_call_reply(call_id, message).await),
},
RequestOp::NewRoutingContext => ResponseOp::NewRoutingContext {
value: self.add_routing_context(self.api.routing_context()),
result: to_json_api_result(
self.api
.routing_context()
.map(|rc| self.add_routing_context(rc)),
),
},
RequestOp::RoutingContext(rcr) => {
let routing_context = match self.lookup_routing_context(id, rcr.rc_id) {

View File

@ -18,13 +18,14 @@ pub struct RoutingContextResponse {
#[serde(tag = "rc_op")]
pub enum RoutingContextRequestOp {
Release,
WithPrivacy,
WithCustomPrivacy {
WithDefaultSafety,
WithSafety {
safety_selection: SafetySelection,
},
WithSequencing {
sequencing: Sequencing,
},
Safety,
AppCall {
target: String,
#[serde(with = "as_human_base64")]
@ -88,17 +89,20 @@ pub enum RoutingContextRequestOp {
pub enum RoutingContextResponseOp {
InvalidId,
Release,
WithPrivacy {
WithDefaultSafety {
#[serde(flatten)]
result: ApiResult<u32>,
},
WithCustomPrivacy {
WithSafety {
#[serde(flatten)]
result: ApiResult<u32>,
},
WithSequencing {
value: u32,
},
Safety {
value: SafetySelection,
},
AppCall {
#[serde(flatten)]
#[schemars(with = "ApiResult<String>")]

View File

@ -21,9 +21,9 @@ pub struct RoutingContextUnlockedInner {
/// Routing contexts are the way you specify the communication preferences for Veilid.
///
/// By default routing contexts are 'direct' from node to node, offering no privacy. To enable sender
/// privacy, use [RoutingContext::with_privacy()]. To enable receiver privacy, you should send to a private route RouteId that you have
/// imported, rather than directly to a NodeId.
/// By default routing contexts have 'safety routing' enabled which offers sender privacy.
/// privacy. To disable this and send RPC operations straight from the node use [RoutingContext::with_safety()] with a [SafetySelection::Unsafe] parameter.
/// To enable receiver privacy, you should send to a private route RouteId that you have imported, rather than directly to a NodeId.
///
#[derive(Clone)]
pub struct RoutingContext {
@ -36,39 +36,48 @@ pub struct RoutingContext {
impl RoutingContext {
////////////////////////////////////////////////////////////////
pub(super) fn new(api: VeilidAPI) -> Self {
Self {
pub(super) fn try_new(api: VeilidAPI) -> VeilidAPIResult<Self> {
let config = api.config()?;
let c = config.get();
Ok(Self {
api,
inner: Arc::new(Mutex::new(RoutingContextInner {})),
unlocked_inner: Arc::new(RoutingContextUnlockedInner {
safety_selection: SafetySelection::Unsafe(Sequencing::default()),
safety_selection: SafetySelection::Safe(SafetySpec {
preferred_route: None,
hop_count: c.network.rpc.default_route_hop_count as usize,
stability: Stability::default(),
sequencing: Sequencing::PreferOrdered,
}),
}),
}
})
}
/// Turn on sender privacy, enabling the use of safety routes.
/// Turn on sender privacy, enabling the use of safety routes. This is the default and
/// calling this function is only necessary if you have previously disable safety or used other parameters.
///
/// Default values for hop count, stability and sequencing preferences are used.
///
/// * Hop count default is dependent on config, but is set to 1 extra hop.
/// * Stability default is to choose 'low latency' routes, preferring them over long-term reliability.
/// * Sequencing default is to have no preference for ordered vs unordered message delivery
/// * Sequencing default is to prefer ordered before unordered message delivery
///
/// To modify these defaults, use [RoutingContext::with_custom_privacy()].
pub fn with_privacy(self) -> VeilidAPIResult<Self> {
/// To customize the safety selection in use, use [RoutingContext::with_safety()].
pub fn with_default_safety(self) -> VeilidAPIResult<Self> {
let config = self.api.config()?;
let c = config.get();
self.with_custom_privacy(SafetySelection::Safe(SafetySpec {
self.with_safety(SafetySelection::Safe(SafetySpec {
preferred_route: None,
hop_count: c.network.rpc.default_route_hop_count as usize,
stability: Stability::default(),
sequencing: Sequencing::default(),
sequencing: Sequencing::PreferOrdered,
}))
}
/// Turn on privacy using a custom [SafetySelection]
pub fn with_custom_privacy(self, safety_selection: SafetySelection) -> VeilidAPIResult<Self> {
/// Use a custom [SafetySelection]. Can be used to disable safety via [SafetySelection::Unsafe]
pub fn with_safety(self, safety_selection: SafetySelection) -> VeilidAPIResult<Self> {
Ok(Self {
api: self.api.clone(),
inner: Arc::new(Mutex::new(RoutingContextInner {})),
@ -95,6 +104,11 @@ impl RoutingContext {
}
}
/// Get the safety selection in use on this routing context
pub fn safety(&self) -> SafetySelection {
self.unlocked_inner.safety_selection
}
fn sequencing(&self) -> Sequencing {
match self.unlocked_inner.safety_selection {
SafetySelection::Unsafe(sequencing) => sequencing,

View File

@ -37,7 +37,12 @@ impl Default for Stability {
#[derive(
Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
#[cfg_attr(target_arch = "wasm32", derive(Tsify), tsify(from_wasm_abi, namespace))]
#[cfg_attr(
target_arch = "wasm32",
derive(Tsify),
tsify(from_wasm_abi, into_wasm_abi, namespace)
)]
pub enum SafetySelection {
/// Don't use a safety route, only specify the sequencing preference
Unsafe(Sequencing),

View File

@ -239,9 +239,10 @@ abstract class VeilidRoutingContext {
void close();
// Modifiers
VeilidRoutingContext withPrivacy();
VeilidRoutingContext withCustomPrivacy(SafetySelection safetySelection);
VeilidRoutingContext withDefaultSafety();
VeilidRoutingContext withSafety(SafetySelection safetySelection);
VeilidRoutingContext withSequencing(Sequencing sequencing);
Future<SafetySelection> safety();
// App call/message
Future<Uint8List> appCall(String target, Uint8List request);

View File

@ -45,12 +45,15 @@ typedef _DetachDart = void Function(int);
typedef _RoutingContextDart = void Function(int);
// fn release_routing_context(id: u32)
typedef _ReleaseRoutingContextDart = int Function(int);
// fn routing_context_with_privacy(id: u32) -> u32
typedef _RoutingContextWithPrivacyDart = int Function(int);
// fn routing_context_with_custom_privacy(id: u32, stability: FfiStr)
typedef _RoutingContextWithCustomPrivacyDart = int Function(int, Pointer<Utf8>);
// fn routing_context_with_default_safety(id: u32) -> u32
typedef _RoutingContextWithDefaultSafetyDart = int Function(int);
// fn routing_context_with_safety(id: u32, stability: FfiStr)
typedef _RoutingContextWithSafetyDart = int Function(int, Pointer<Utf8>);
// fn routing_context_with_sequencing(id: u32, sequencing: FfiStr)
typedef _RoutingContextWithSequencingDart = int Function(int, Pointer<Utf8>);
// fn routing_context_safety(port: i64,
// id: u32)
typedef _RoutingContextSafetyDart = void Function(int, int);
// fn routing_context_app_call(port: i64,
// id: u32, target: FfiStr, request: FfiStr)
typedef _RoutingContextAppCallDart = void Function(
@ -525,16 +528,16 @@ class VeilidRoutingContextFFI extends VeilidRoutingContext {
}
@override
VeilidRoutingContextFFI withPrivacy() {
VeilidRoutingContextFFI withDefaultSafety() {
_ctx.ensureValid();
final newId = _ctx.ffi._routingContextWithPrivacy(_ctx.id!);
final newId = _ctx.ffi._routingContextWithDefaultSafety(_ctx.id!);
return VeilidRoutingContextFFI._(_Ctx(newId, _ctx.ffi));
}
@override
VeilidRoutingContextFFI withCustomPrivacy(SafetySelection safetySelection) {
VeilidRoutingContextFFI withSafety(SafetySelection safetySelection) {
_ctx.ensureValid();
final newId = _ctx.ffi._routingContextWithCustomPrivacy(
final newId = _ctx.ffi._routingContextWithSafety(
_ctx.id!, jsonEncode(safetySelection).toNativeUtf8());
return VeilidRoutingContextFFI._(_Ctx(newId, _ctx.ffi));
}
@ -547,6 +550,17 @@ class VeilidRoutingContextFFI extends VeilidRoutingContext {
return VeilidRoutingContextFFI._(_Ctx(newId, _ctx.ffi));
}
@override
Future<SafetySelection> safety() async {
_ctx.ensureValid();
final recvPort = ReceivePort('routing_context_safety');
final sendPort = recvPort.sendPort;
_ctx.ffi._routingContextSafety(sendPort.nativePort, _ctx.id!);
final out = await processFutureJson<SafetySelection>(
SafetySelection.fromJson, recvPort.first);
return out;
}
@override
Future<Uint8List> appCall(String target, Uint8List request) async {
_ctx.ensureValid();
@ -1175,17 +1189,19 @@ class VeilidFFI extends Veilid {
'routing_context'),
_releaseRoutingContext = dylib.lookupFunction<Int32 Function(Uint32),
_ReleaseRoutingContextDart>('release_routing_context'),
_routingContextWithPrivacy = dylib.lookupFunction<
Uint32 Function(Uint32),
_RoutingContextWithPrivacyDart>('routing_context_with_privacy'),
_routingContextWithCustomPrivacy = dylib.lookupFunction<
Uint32 Function(Uint32, Pointer<Utf8>),
_RoutingContextWithCustomPrivacyDart>(
'routing_context_with_custom_privacy'),
_routingContextWithDefaultSafety = dylib.lookupFunction<
Uint32 Function(Uint32), _RoutingContextWithDefaultSafetyDart>(
'routing_context_with_default_safety'),
_routingContextWithSafety = dylib.lookupFunction<
Uint32 Function(Uint32, Pointer<Utf8>),
_RoutingContextWithSafetyDart>('routing_context_with_safety'),
_routingContextWithSequencing = dylib.lookupFunction<
Uint32 Function(Uint32, Pointer<Utf8>),
_RoutingContextWithSequencingDart>(
'routing_context_with_sequencing'),
_routingContextSafety = dylib.lookupFunction<
Void Function(Int64, Uint32),
_RoutingContextSafetyDart>('routing_context_safety'),
_routingContextAppCall = dylib.lookupFunction<
Void Function(Int64, Uint32, Pointer<Utf8>, Pointer<Utf8>),
_RoutingContextAppCallDart>('routing_context_app_call'),
@ -1383,9 +1399,10 @@ class VeilidFFI extends Veilid {
final _RoutingContextDart _routingContext;
final _ReleaseRoutingContextDart _releaseRoutingContext;
final _RoutingContextWithPrivacyDart _routingContextWithPrivacy;
final _RoutingContextWithCustomPrivacyDart _routingContextWithCustomPrivacy;
final _RoutingContextWithDefaultSafetyDart _routingContextWithDefaultSafety;
final _RoutingContextWithSafetyDart _routingContextWithSafety;
final _RoutingContextWithSequencingDart _routingContextWithSequencing;
final _RoutingContextSafetyDart _routingContextSafety;
final _RoutingContextAppCallDart _routingContextAppCall;
final _RoutingContextAppMessageDart _routingContextAppMessage;
final _RoutingContextCreateDHTRecordDart _routingContextCreateDHTRecord;

View File

@ -68,20 +68,18 @@ class VeilidRoutingContextJS extends VeilidRoutingContext {
}
@override
VeilidRoutingContextJS withPrivacy() {
VeilidRoutingContextJS withDefaultSafety() {
final id = _ctx.requireId();
final int newId =
js_util.callMethod(wasm, 'routing_context_with_privacy', [id]);
js_util.callMethod(wasm, 'routing_context_with_default_safety', [id]);
return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js));
}
@override
VeilidRoutingContextJS withCustomPrivacy(SafetySelection safetySelection) {
VeilidRoutingContextJS withSafety(SafetySelection safetySelection) {
final id = _ctx.requireId();
final newId = js_util.callMethod<int>(
wasm,
'routing_context_with_custom_privacy',
[id, jsonEncode(safetySelection)]);
wasm, 'routing_context_with_safety', [id, jsonEncode(safetySelection)]);
return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js));
}
@ -94,6 +92,15 @@ class VeilidRoutingContextJS extends VeilidRoutingContext {
return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js));
}
@override
Future<SafetySelection> safety() async {
final id = _ctx.requireId();
return SafetySelection.fromJson(jsonDecode(await _wrapApiPromise(
js_util.callMethod(wasm, 'routing_context_safety', [
id,
]))));
}
@override
Future<Uint8List> appCall(String target, Uint8List request) async {
final id = _ctx.requireId();

View File

@ -407,7 +407,7 @@ fn add_routing_context(
pub extern "C" fn routing_context(port: i64) {
DartIsolateWrapper::new(port).spawn_result(async move {
let veilid_api = get_veilid_api().await?;
let routing_context = veilid_api.routing_context();
let routing_context = veilid_api.routing_context()?;
let mut rc = ROUTING_CONTEXTS.lock();
let new_id = add_routing_context(&mut rc, routing_context);
APIResult::Ok(new_id)
@ -424,12 +424,12 @@ pub extern "C" fn release_routing_context(id: u32) -> i32 {
}
#[no_mangle]
pub extern "C" fn routing_context_with_privacy(id: u32) -> u32 {
pub extern "C" fn routing_context_with_default_safety(id: u32) -> u32 {
let mut rc = ROUTING_CONTEXTS.lock();
let Some(routing_context) = rc.get(&id) else {
return 0;
};
let Ok(routing_context) = routing_context.clone().with_privacy() else {
let Ok(routing_context) = routing_context.clone().with_default_safety() else {
return 0;
};
@ -437,7 +437,7 @@ pub extern "C" fn routing_context_with_privacy(id: u32) -> u32 {
}
#[no_mangle]
pub extern "C" fn routing_context_with_custom_privacy(id: u32, safety_selection: FfiStr) -> u32 {
pub extern "C" fn routing_context_with_safety(id: u32, safety_selection: FfiStr) -> u32 {
let safety_selection: veilid_core::SafetySelection =
veilid_core::deserialize_opt_json(safety_selection.into_opt_string()).unwrap();
@ -445,10 +445,7 @@ pub extern "C" fn routing_context_with_custom_privacy(id: u32, safety_selection:
let Some(routing_context) = rc.get(&id) else {
return 0;
};
let Ok(routing_context) = routing_context
.clone()
.with_custom_privacy(safety_selection)
else {
let Ok(routing_context) = routing_context.clone().with_safety(safety_selection) else {
return 0;
};
@ -469,6 +466,23 @@ pub extern "C" fn routing_context_with_sequencing(id: u32, sequencing: FfiStr) -
add_routing_context(&mut rc, routing_context)
}
#[no_mangle]
pub extern "C" fn routing_context_safety(port: i64, id: u32) {
DartIsolateWrapper::new(port).spawn_result_json(async move {
let routing_context = {
let rc = ROUTING_CONTEXTS.lock();
let Some(routing_context) = rc.get(&id) else {
return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument(
"routing_context_app_call",
"id",
id,
));
};
routing_context.clone()
};
APIResult::Ok(routing_context.safety())
});
}
#[no_mangle]
pub extern "C" fn routing_context_app_call(port: i64, id: u32, target: FfiStr, request: FfiStr) {
let target_string: String = target.into_opt_string().unwrap();

View File

@ -22,7 +22,7 @@ async def test_routing_contexts(api_connection: veilid.VeilidAPI):
rc = await api_connection.new_routing_context()
async with rc:
rcp = await rc.with_privacy(release=False)
rcp = await rc.with_default_safety(release=False)
async with rcp:
pass
@ -32,15 +32,15 @@ async def test_routing_contexts(api_connection: veilid.VeilidAPI):
async with rc:
pass
rc = await (await api_connection.new_routing_context()).with_custom_privacy(
rc = await (await api_connection.new_routing_context()).with_safety(
veilid.SafetySelection.safe(
veilid.SafetySpec(None, 2, veilid.Stability.RELIABLE, veilid.Sequencing.ENSURE_ORDERED)
veilid.SafetySpec(None, 2, veilid.Stability.LOW_LATENCY, veilid.Sequencing.NO_PREFERENCE)
)
)
await rc.release()
rc = await (await api_connection.new_routing_context()).with_custom_privacy(
veilid.SafetySelection.unsafe(veilid.Sequencing.ENSURE_ORDERED)
rc = await (await api_connection.new_routing_context()).with_safety(
veilid.SafetySelection.unsafe(veilid.Sequencing.PREFER_ORDERED)
)
await rc.release()
@ -65,7 +65,7 @@ async def test_routing_context_app_message_loopback():
await api.debug("purge routes")
# make a routing context that uses a safety route
rc = await (await api.new_routing_context()).with_privacy()
rc = await api.new_routing_context()
async with rc:
# make a new local private route
prl, blob = await api.new_private_route()
@ -104,10 +104,8 @@ async def test_routing_context_app_call_loopback():
# purge routes to ensure we start fresh
await api.debug("purge routes")
# make a routing context that uses a safety route
rc = await (await (await api.new_routing_context()).with_privacy()).with_sequencing(
veilid.Sequencing.ENSURE_ORDERED
)
# make a routing context
rc = await api.new_routing_context()
async with rc:
# make a new local private route
prl, blob = await api.new_private_route()
@ -160,15 +158,10 @@ async def test_routing_context_app_message_loopback_big_packets():
await api.debug("purge routes")
# make a routing context that uses a safety route
rc = await (await (await api.new_routing_context()).with_privacy()).with_sequencing(
veilid.Sequencing.ENSURE_ORDERED
)
rc = await api.new_routing_context()
async with rc:
# make a new local private route
prl, blob = await api.new_custom_private_route(
[veilid.CryptoKind.CRYPTO_KIND_VLD0],
veilid.Stability.RELIABLE,
veilid.Sequencing.ENSURE_ORDERED)
prl, blob = await api.new_private_route()
# import it as a remote route as well so we can send to it
prr = await api.import_remote_private_route(blob)
@ -225,7 +218,7 @@ async def test_routing_context_app_call_loopback_big_packets():
app_call_task = asyncio.create_task(app_call_queue_task_handler(api), name="app call task")
# make a routing context that uses a safety route
rc = await (await (await api.new_routing_context()).with_privacy()).with_sequencing(
rc = await (await api.new_routing_context()).with_sequencing(
veilid.Sequencing.ENSURE_ORDERED
)
async with rc:
@ -269,8 +262,6 @@ async def test_routing_context_app_message_loopback_bandwidth():
await api.debug("purge routes")
# make a routing context that uses a safety route
# rc = await (await (await api.new_routing_context()).with_privacy()).with_sequencing(veilid.Sequencing.ENSURE_ORDERED)
# rc = await (await api.new_routing_context()).with_privacy()
rc = await api.new_routing_context()
async with rc:
# make a new local private route

View File

@ -22,11 +22,11 @@ class RoutingContext(ABC):
pass
@abstractmethod
async def with_privacy(self, release=True) -> Self:
async def with_default_safety(self, release=True) -> Self:
pass
@abstractmethod
async def with_custom_privacy(
async def with_safety(
self, safety_selection: types.SafetySelection, release=True
) -> Self:
pass
@ -35,6 +35,10 @@ class RoutingContext(ABC):
async def with_sequencing(self, sequencing: types.Sequencing, release=True) -> Self:
pass
@abstractmethod
async def safety(self) -> types.SafetySelection:
pass
@abstractmethod
async def app_call(self, target: types.TypedKey | types.RouteId, request: bytes) -> bytes:
pass

View File

@ -446,26 +446,26 @@ class _JsonRoutingContext(RoutingContext):
)
self.done = True
async def with_privacy(self, release=True) -> Self:
async def with_default_safety(self, release=True) -> Self:
new_rc_id = raise_api_result(
await self.api.send_ndjson_request(
Operation.ROUTING_CONTEXT,
validate=validate_rc_op,
rc_id=self.rc_id,
rc_op=RoutingContextOperation.WITH_PRIVACY,
rc_op=RoutingContextOperation.WITH_DEFAULT_SAFETY,
)
)
if release:
await self.release()
return self.__class__(self.api, new_rc_id)
async def with_custom_privacy(self, safety_selection: SafetySelection, release=True) -> Self:
async def with_safety(self, safety_selection: SafetySelection, release=True) -> Self:
new_rc_id = raise_api_result(
await self.api.send_ndjson_request(
Operation.ROUTING_CONTEXT,
validate=validate_rc_op,
rc_id=self.rc_id,
rc_op=RoutingContextOperation.WITH_CUSTOM_PRIVACY,
rc_op=RoutingContextOperation.WITH_SAFETY,
safety_selection=safety_selection,
)
)
@ -487,6 +487,19 @@ class _JsonRoutingContext(RoutingContext):
await self.release()
return self.__class__(self.api, new_rc_id)
async def safety(
self
) -> SafetySelection:
return SafetySelection.from_json(
raise_api_result(
await self.api.send_ndjson_request(
Operation.ROUTING_CONTEXT,
validate=validate_rc_op,
rc_id=self.rc_id,
rc_op=RoutingContextOperation.SAFETY,
)
)
)
async def app_call(self, target: TypedKey | RouteId, message: bytes) -> bytes:
return urlsafe_b64decode_no_pad(
raise_api_result(

View File

@ -33,9 +33,10 @@ class Operation(StrEnum):
class RoutingContextOperation(StrEnum):
INVALID_ID = "InvalidId"
RELEASE = "Release"
WITH_PRIVACY = "WithPrivacy"
WITH_CUSTOM_PRIVACY = "WithCustomPrivacy"
WITH_DEFAULT_SAFETY = "WithDefaultSafety"
WITH_SAFETY = "WithSafety"
WITH_SEQUENCING = "WithSequencing"
SAFETY = "Safety"
APP_CALL = "AppCall"
APP_MESSAGE = "AppMessage"
CREATE_DHT_RECORD = "CreateDhtRecord"

View File

@ -349,9 +349,34 @@
},
{
"type": "object",
"anyOf": [
{
"type": "object",
"required": [
"value"
],
"properties": {
"value": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
},
{
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"$ref": "#/definitions/VeilidAPIError"
}
}
}
],
"required": [
"op",
"value"
"op"
],
"properties": {
"op": {
@ -359,11 +384,6 @@
"enum": [
"NewRoutingContext"
]
},
"value": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
},
@ -433,7 +453,7 @@
"rc_op": {
"type": "string",
"enum": [
"WithPrivacy"
"WithDefaultSafety"
]
}
}
@ -473,7 +493,7 @@
"rc_op": {
"type": "string",
"enum": [
"WithCustomPrivacy"
"WithSafety"
]
}
}
@ -498,6 +518,24 @@
}
}
},
{
"type": "object",
"required": [
"rc_op",
"value"
],
"properties": {
"rc_op": {
"type": "string",
"enum": [
"Safety"
]
},
"value": {
"$ref": "#/definitions/SafetySelection"
}
}
},
{
"type": "object",
"anyOf": [
@ -2865,6 +2903,92 @@
}
}
},
"SafetySelection": {
"description": "The choice of safety route to include in compiled routes",
"oneOf": [
{
"description": "Don't use a safety route, only specify the sequencing preference",
"type": "object",
"required": [
"Unsafe"
],
"properties": {
"Unsafe": {
"$ref": "#/definitions/Sequencing"
}
},
"additionalProperties": false
},
{
"description": "Use a safety route and parameters specified by a SafetySpec",
"type": "object",
"required": [
"Safe"
],
"properties": {
"Safe": {
"$ref": "#/definitions/SafetySpec"
}
},
"additionalProperties": false
}
]
},
"SafetySpec": {
"description": "Options for safety routes (sender privacy)",
"type": "object",
"required": [
"hop_count",
"sequencing",
"stability"
],
"properties": {
"hop_count": {
"description": "must be greater than 0",
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"preferred_route": {
"description": "preferred safety route set id if it still exists",
"type": [
"string",
"null"
]
},
"sequencing": {
"description": "prefer connection-oriented sequenced protocols",
"allOf": [
{
"$ref": "#/definitions/Sequencing"
}
]
},
"stability": {
"description": "prefer reliability over speed",
"allOf": [
{
"$ref": "#/definitions/Stability"
}
]
}
}
},
"Sequencing": {
"type": "string",
"enum": [
"NoPreference",
"PreferOrdered",
"EnsureOrdered"
]
},
"Stability": {
"type": "string",
"enum": [
"LowLatency",
"Reliable"
]
},
"TransferStats": {
"type": "object",
"required": [

View File

@ -215,7 +215,7 @@
"rc_op": {
"type": "string",
"enum": [
"WithPrivacy"
"WithDefaultSafety"
]
}
}
@ -230,7 +230,7 @@
"rc_op": {
"type": "string",
"enum": [
"WithCustomPrivacy"
"WithSafety"
]
},
"safety_selection": {
@ -256,6 +256,20 @@
}
}
},
{
"type": "object",
"required": [
"rc_op"
],
"properties": {
"rc_op": {
"type": "string",
"enum": [
"Safety"
]
}
}
},
{
"type": "object",
"required": [

View File

@ -328,7 +328,7 @@ fn add_routing_context(routing_context: veilid_core::RoutingContext) -> u32 {
pub fn routing_context() -> Promise {
wrap_api_future_plain(async move {
let veilid_api = get_veilid_api()?;
let routing_context = veilid_api.routing_context();
let routing_context = veilid_api.routing_context()?;
let new_id = add_routing_context(routing_context);
APIResult::Ok(new_id)
})
@ -344,7 +344,7 @@ pub fn release_routing_context(id: u32) -> i32 {
}
#[wasm_bindgen()]
pub fn routing_context_with_privacy(id: u32) -> u32 {
pub fn routing_context_with_default_safety(id: u32) -> u32 {
let routing_context = {
let rc = (*ROUTING_CONTEXTS).borrow();
let Some(routing_context) = rc.get(&id) else {
@ -352,14 +352,14 @@ pub fn routing_context_with_privacy(id: u32) -> u32 {
};
routing_context.clone()
};
let Ok(routing_context) = routing_context.with_privacy() else {
let Ok(routing_context) = routing_context.with_default_safety() else {
return 0;
};
add_routing_context(routing_context)
}
#[wasm_bindgen()]
pub fn routing_context_with_custom_privacy(id: u32, safety_selection: String) -> u32 {
pub fn routing_context_with_safety(id: u32, safety_selection: String) -> u32 {
let safety_selection: veilid_core::SafetySelection =
veilid_core::deserialize_json(&safety_selection).unwrap();
@ -370,7 +370,7 @@ pub fn routing_context_with_custom_privacy(id: u32, safety_selection: String) ->
};
routing_context.clone()
};
let Ok(routing_context) = routing_context.with_custom_privacy(safety_selection) else {
let Ok(routing_context) = routing_context.with_safety(safety_selection) else {
return 0;
};
add_routing_context(routing_context)
@ -391,6 +391,26 @@ pub fn routing_context_with_sequencing(id: u32, sequencing: String) -> u32 {
add_routing_context(routing_context)
}
#[wasm_bindgen()]
pub fn routing_context_safety(id: u32) -> Promise {
wrap_api_future_json(async move {
let routing_context = {
let rc = (*ROUTING_CONTEXTS).borrow();
let Some(routing_context) = rc.get(&id) else {
return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument(
"routing_context_safety",
"id",
id,
));
};
routing_context.clone()
};
let safety_selection = routing_context.safety();
APIResult::Ok(safety_selection)
})
}
#[wasm_bindgen()]
pub fn routing_context_app_call(id: u32, target_string: String, request: String) -> Promise {
let request: Vec<u8> = data_encoding::BASE64URL_NOPAD

View File

@ -13,7 +13,7 @@ impl VeilidRoutingContext {
pub fn new() -> APIResult<VeilidRoutingContext> {
let veilid_api = get_veilid_api()?;
APIResult::Ok(VeilidRoutingContext {
inner_routing_context: veilid_api.routing_context(),
inner_routing_context: veilid_api.routing_context()?,
})
}
@ -105,30 +105,30 @@ impl VeilidRoutingContext {
APIResult::Ok(self.inner_routing_context.clone())
}
/// Turn on sender privacy, enabling the use of safety routes.
/// Turn on sender privacy, enabling the use of safety routes. This is the default and
/// calling this function is only necessary if you have previously disable safety or used other parameters.
/// Returns a new instance of VeilidRoutingContext - does not mutate.
///
/// Default values for hop count, stability and sequencing preferences are used.
///
/// Hop count default is dependent on config, but is set to 1 extra hop.
/// Stability default is to choose 'low latency' routes, preferring them over long-term reliability.
/// Sequencing default is to have no preference for ordered vs unordered message delivery
pub fn withPrivacy(&self) -> APIResult<VeilidRoutingContext> {
/// * Hop count default is dependent on config, but is set to 1 extra hop.
/// * Stability default is to choose 'low latency' routes, preferring them over long-term reliability.
/// * Sequencing default is to have no preference for ordered vs unordered message delivery
///
/// To customize the safety selection in use, use [VeilidRoutingContext::withSafety].
pub fn withDefaultSafety(&self) -> APIResult<VeilidRoutingContext> {
let routing_context = self.getRoutingContext()?;
APIResult::Ok(VeilidRoutingContext {
inner_routing_context: routing_context.with_privacy()?,
inner_routing_context: routing_context.with_default_safety()?,
})
}
/// Turn on privacy using a custom `SafetySelection`.
/// Use a custom [SafetySelection]. Can be used to disable safety via [SafetySelection::Unsafe]
/// Returns a new instance of VeilidRoutingContext - does not mutate.
pub fn withCustomPrivacy(
&self,
safety_selection: SafetySelection,
) -> APIResult<VeilidRoutingContext> {
pub fn withSafety(&self, safety_selection: SafetySelection) -> APIResult<VeilidRoutingContext> {
let routing_context = self.getRoutingContext()?;
APIResult::Ok(VeilidRoutingContext {
inner_routing_context: routing_context.with_custom_privacy(safety_selection)?,
inner_routing_context: routing_context.with_safety(safety_selection)?,
})
}
@ -141,6 +141,14 @@ impl VeilidRoutingContext {
})
}
/// Get the safety selection in use on this routing context
/// @returns the SafetySelection currently in use if successful.
pub fn safety(&self) -> APIResult<SafetySelection> {
let routing_context = self.getRoutingContext()?;
let safety_selection = routing_context.safety();
APIResult::Ok(safety_selection)
}
/// App-level unidirectional message that does not expect any value to be returned.
///
/// Veilid apps may use this for arbitrary message passing.

File diff suppressed because it is too large Load Diff

View File

@ -46,15 +46,15 @@ describe('VeilidRoutingContext', () => {
routingContext.free();
});
it('should create with privacy', async () => {
const routingContext = VeilidRoutingContext.create().withPrivacy();
it('should create with default safety', async () => {
const routingContext = VeilidRoutingContext.create().withDefaultSafety();
expect(routingContext instanceof VeilidRoutingContext).toBe(true);
routingContext.free();
});
it('should create with custom privacy', async () => {
const routingContext = VeilidRoutingContext.create().withCustomPrivacy({
it('should create with safety', async () => {
const routingContext = VeilidRoutingContext.create().withSafety({
Safe: {
hop_count: 2,
sequencing: 'EnsureOrdered',
@ -80,7 +80,6 @@ describe('VeilidRoutingContext', () => {
before('create routing context', () => {
routingContext = VeilidRoutingContext.create()
.withPrivacy()
.withSequencing('EnsureOrdered');
});

View File

@ -16,7 +16,7 @@ wasm-pack build $WASM_PACK_FLAGS --target bundler --weak-refs
cd tests
npm install
original_tmpdir=$TMPDIR
mkdir --parents ~/tmp
mkdir -p ~/tmp
export TMPDIR=~/tmp
npm run test:headless
export TMPDIR=$original_tmpdir