safety by default

This commit is contained in:
Christien Rioux 2023-11-05 18:38:05 -05:00
parent 88389a1b78
commit ee375ad430
22 changed files with 1406 additions and 1593 deletions

View File

@ -372,18 +372,18 @@ impl ConnectionManager {
} }
/////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////
/// Callbacks /// Asynchronous Event Processor
#[instrument(level = "trace", skip_all)] async fn process_connection_manager_event(
async fn async_processor( &self,
self, event: ConnectionManagerEvent,
stop_token: StopToken, allow_accept: bool,
receiver: flume::Receiver<ConnectionManagerEvent>,
) { ) {
// Process async commands
while let Ok(Ok(event)) = receiver.recv_async().timeout_at(stop_token.clone()).await {
match event { match event {
ConnectionManagerEvent::Accepted(prot_conn) => { ConnectionManagerEvent::Accepted(prot_conn) => {
if !allow_accept {
return;
}
// Async lock on the remote address for atomicity per remote // Async lock on the remote address for atomicity per remote
let _lock_guard = self let _lock_guard = self
.arc .arc
@ -417,6 +417,21 @@ impl ConnectionManager {
} }
} }
} }
#[instrument(level = "trace", skip_all)]
async fn async_processor(
self,
stop_token: StopToken,
receiver: flume::Receiver<ConnectionManagerEvent>,
) {
// Process async commands
while let Ok(Ok(event)) = receiver.recv_async().timeout_at(stop_token.clone()).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;
}
} }
// Called by low-level network when any connection-oriented protocol connection appears // Called by low-level network when any connection-oriented protocol connection appears

View File

@ -11,35 +11,55 @@ lazy_static! {
} }
pub async fn test_get_dht_value_unopened(api: VeilidAPI) { 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; let result = rc.get_dht_value(*BOGUS_KEY, 0, false).await;
assert_err!(result); assert_err!(result);
} }
pub async fn test_open_dht_record_nonexistent_no_writer(api: VeilidAPI) { 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; let result = rc.get_dht_value(*BOGUS_KEY, 0, false).await;
assert_err!(result); assert_err!(result);
} }
pub async fn test_close_dht_record_nonexistent(api: VeilidAPI) { 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; let result = rc.close_dht_record(*BOGUS_KEY).await;
assert_err!(result); assert_err!(result);
} }
pub async fn test_delete_dht_record_nonexistent(api: VeilidAPI) { 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; let result = rc.delete_dht_record(*BOGUS_KEY).await;
assert_err!(result); assert_err!(result);
} }
pub async fn test_create_delete_dht_record_simple(api: VeilidAPI) { 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 let rec = rc
.create_dht_record( .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) { 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 let rec = rc
.create_dht_record( .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) { 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 let rec = rc
.create_dht_record( .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) { 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 let rec = rc
.create_dht_record( .create_dht_record(

View File

@ -189,8 +189,8 @@ impl VeilidAPI {
// Routing Context // Routing Context
/// Get a new `RoutingContext` object to use to send messages over the Veilid network. /// Get a new `RoutingContext` object to use to send messages over the Veilid network.
pub fn routing_context(&self) -> RoutingContext { pub fn routing_context(&self) -> VeilidAPIResult<RoutingContext> {
RoutingContext::new(self.clone()) RoutingContext::try_new(self.clone())
} }
/// Parse a string into a target object that can be used in a [RoutingContext] /// Parse a string into a target object that can be used in a [RoutingContext]

View File

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

View File

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

View File

@ -235,20 +235,22 @@ impl JsonRequestProcessor {
self.release_routing_context(rcr.rc_id); self.release_routing_context(rcr.rc_id);
RoutingContextResponseOp::Release {} RoutingContextResponseOp::Release {}
} }
RoutingContextRequestOp::WithPrivacy => RoutingContextResponseOp::WithPrivacy { RoutingContextRequestOp::WithDefaultSafety => {
RoutingContextResponseOp::WithDefaultSafety {
result: to_json_api_result( result: to_json_api_result(
routing_context routing_context
.clone() .clone()
.with_privacy() .with_default_safety()
.map(|new_rc| self.add_routing_context(new_rc)), .map(|new_rc| self.add_routing_context(new_rc)),
), ),
}, }
RoutingContextRequestOp::WithCustomPrivacy { safety_selection } => { }
RoutingContextResponseOp::WithCustomPrivacy { RoutingContextRequestOp::WithSafety { safety_selection } => {
RoutingContextResponseOp::WithSafety {
result: to_json_api_result( result: to_json_api_result(
routing_context routing_context
.clone() .clone()
.with_custom_privacy(safety_selection) .with_safety(safety_selection)
.map(|new_rc| self.add_routing_context(new_rc)), .map(|new_rc| self.add_routing_context(new_rc)),
), ),
} }
@ -259,6 +261,9 @@ impl JsonRequestProcessor {
.add_routing_context(routing_context.clone().with_sequencing(sequencing)), .add_routing_context(routing_context.clone().with_sequencing(sequencing)),
} }
} }
RoutingContextRequestOp::Safety => RoutingContextResponseOp::Safety {
value: routing_context.safety(),
},
RoutingContextRequestOp::AppCall { target, message } => { RoutingContextRequestOp::AppCall { target, message } => {
RoutingContextResponseOp::AppCall { RoutingContextResponseOp::AppCall {
result: to_json_api_result_with_vec_u8( 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), result: to_json_api_result(self.api.app_call_reply(call_id, message).await),
}, },
RequestOp::NewRoutingContext => ResponseOp::NewRoutingContext { 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) => { RequestOp::RoutingContext(rcr) => {
let routing_context = match self.lookup_routing_context(id, rcr.rc_id) { 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")] #[serde(tag = "rc_op")]
pub enum RoutingContextRequestOp { pub enum RoutingContextRequestOp {
Release, Release,
WithPrivacy, WithDefaultSafety,
WithCustomPrivacy { WithSafety {
safety_selection: SafetySelection, safety_selection: SafetySelection,
}, },
WithSequencing { WithSequencing {
sequencing: Sequencing, sequencing: Sequencing,
}, },
Safety,
AppCall { AppCall {
target: String, target: String,
#[serde(with = "as_human_base64")] #[serde(with = "as_human_base64")]
@ -88,17 +89,20 @@ pub enum RoutingContextRequestOp {
pub enum RoutingContextResponseOp { pub enum RoutingContextResponseOp {
InvalidId, InvalidId,
Release, Release,
WithPrivacy { WithDefaultSafety {
#[serde(flatten)] #[serde(flatten)]
result: ApiResult<u32>, result: ApiResult<u32>,
}, },
WithCustomPrivacy { WithSafety {
#[serde(flatten)] #[serde(flatten)]
result: ApiResult<u32>, result: ApiResult<u32>,
}, },
WithSequencing { WithSequencing {
value: u32, value: u32,
}, },
Safety {
value: SafetySelection,
},
AppCall { AppCall {
#[serde(flatten)] #[serde(flatten)]
#[schemars(with = "ApiResult<String>")] #[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. /// 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 /// By default routing contexts have 'safety routing' enabled which offers sender privacy.
/// privacy, use [RoutingContext::with_privacy()]. To enable receiver privacy, you should send to a private route RouteId that you have /// privacy. To disable this and send RPC operations straight from the node use [RoutingContext::with_safety()] with a [SafetySelection::Unsafe] parameter.
/// imported, rather than directly to a NodeId. /// To enable receiver privacy, you should send to a private route RouteId that you have imported, rather than directly to a NodeId.
/// ///
#[derive(Clone)] #[derive(Clone)]
pub struct RoutingContext { pub struct RoutingContext {
@ -36,17 +36,26 @@ pub struct RoutingContext {
impl RoutingContext { impl RoutingContext {
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
pub(super) fn new(api: VeilidAPI) -> Self { pub(super) fn try_new(api: VeilidAPI) -> VeilidAPIResult<Self> {
Self { let config = api.config()?;
let c = config.get();
Ok(Self {
api, api,
inner: Arc::new(Mutex::new(RoutingContextInner {})), inner: Arc::new(Mutex::new(RoutingContextInner {})),
unlocked_inner: Arc::new(RoutingContextUnlockedInner { 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::default(),
}), }),
} }),
})
} }
/// 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. /// Default values for hop count, stability and sequencing preferences are used.
/// ///
@ -54,12 +63,12 @@ impl RoutingContext {
/// * Stability default is to choose 'low latency' routes, preferring them over long-term reliability. /// * 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 have no preference for ordered vs unordered message delivery
/// ///
/// To modify these defaults, use [RoutingContext::with_custom_privacy()]. /// To customize the safety selection in use, use [RoutingContext::with_safety()].
pub fn with_privacy(self) -> VeilidAPIResult<Self> { pub fn with_default_safety(self) -> VeilidAPIResult<Self> {
let config = self.api.config()?; let config = self.api.config()?;
let c = config.get(); let c = config.get();
self.with_custom_privacy(SafetySelection::Safe(SafetySpec { self.with_safety(SafetySelection::Safe(SafetySpec {
preferred_route: None, preferred_route: None,
hop_count: c.network.rpc.default_route_hop_count as usize, hop_count: c.network.rpc.default_route_hop_count as usize,
stability: Stability::default(), stability: Stability::default(),
@ -67,8 +76,8 @@ impl RoutingContext {
})) }))
} }
/// Turn on privacy using a custom [SafetySelection] /// Use a custom [SafetySelection]. Can be used to disable safety via [SafetySelection::Unsafe]
pub fn with_custom_privacy(self, safety_selection: SafetySelection) -> VeilidAPIResult<Self> { pub fn with_safety(self, safety_selection: SafetySelection) -> VeilidAPIResult<Self> {
Ok(Self { Ok(Self {
api: self.api.clone(), api: self.api.clone(),
inner: Arc::new(Mutex::new(RoutingContextInner {})), 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 { fn sequencing(&self) -> Sequencing {
match self.unlocked_inner.safety_selection { match self.unlocked_inner.safety_selection {
SafetySelection::Unsafe(sequencing) => sequencing, SafetySelection::Unsafe(sequencing) => sequencing,

View File

@ -37,7 +37,12 @@ impl Default for Stability {
#[derive( #[derive(
Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema, 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 { pub enum SafetySelection {
/// Don't use a safety route, only specify the sequencing preference /// Don't use a safety route, only specify the sequencing preference
Unsafe(Sequencing), Unsafe(Sequencing),

View File

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

View File

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

View File

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

View File

@ -407,7 +407,7 @@ fn add_routing_context(
pub extern "C" fn routing_context(port: i64) { pub extern "C" fn routing_context(port: i64) {
DartIsolateWrapper::new(port).spawn_result(async move { DartIsolateWrapper::new(port).spawn_result(async move {
let veilid_api = get_veilid_api().await?; 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 mut rc = ROUTING_CONTEXTS.lock();
let new_id = add_routing_context(&mut rc, routing_context); let new_id = add_routing_context(&mut rc, routing_context);
APIResult::Ok(new_id) APIResult::Ok(new_id)
@ -424,12 +424,12 @@ pub extern "C" fn release_routing_context(id: u32) -> i32 {
} }
#[no_mangle] #[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 mut rc = ROUTING_CONTEXTS.lock();
let Some(routing_context) = rc.get(&id) else { let Some(routing_context) = rc.get(&id) else {
return 0; 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; return 0;
}; };
@ -437,7 +437,7 @@ pub extern "C" fn routing_context_with_privacy(id: u32) -> u32 {
} }
#[no_mangle] #[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 = let safety_selection: veilid_core::SafetySelection =
veilid_core::deserialize_opt_json(safety_selection.into_opt_string()).unwrap(); 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 { let Some(routing_context) = rc.get(&id) else {
return 0; return 0;
}; };
let Ok(routing_context) = routing_context let Ok(routing_context) = routing_context.clone().with_safety(safety_selection) else {
.clone()
.with_custom_privacy(safety_selection)
else {
return 0; 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) 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] #[no_mangle]
pub extern "C" fn routing_context_app_call(port: i64, id: u32, target: FfiStr, request: FfiStr) { 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(); 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() rc = await api_connection.new_routing_context()
async with rc: async with rc:
rcp = await rc.with_privacy(release=False) rcp = await rc.with_default_safety(release=False)
async with rcp: async with rcp:
pass pass
@ -32,15 +32,15 @@ async def test_routing_contexts(api_connection: veilid.VeilidAPI):
async with rc: async with rc:
pass 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.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() await rc.release()
rc = await (await api_connection.new_routing_context()).with_custom_privacy( rc = await (await api_connection.new_routing_context()).with_safety(
veilid.SafetySelection.unsafe(veilid.Sequencing.ENSURE_ORDERED) veilid.SafetySelection.unsafe(veilid.Sequencing.PREFER_ORDERED)
) )
await rc.release() await rc.release()
@ -65,7 +65,7 @@ async def test_routing_context_app_message_loopback():
await api.debug("purge routes") await api.debug("purge routes")
# make a routing context that uses a safety route # 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: async with rc:
# make a new local private route # make a new local private route
prl, blob = await api.new_private_route() prl, blob = await api.new_private_route()
@ -105,7 +105,7 @@ async def test_routing_context_app_call_loopback():
await api.debug("purge routes") await api.debug("purge routes")
# make a routing context that uses a safety route # 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 veilid.Sequencing.ENSURE_ORDERED
) )
async with rc: async with rc:
@ -160,7 +160,7 @@ async def test_routing_context_app_message_loopback_big_packets():
await api.debug("purge routes") await api.debug("purge routes")
# make a routing context that uses a safety route # 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 veilid.Sequencing.ENSURE_ORDERED
) )
async with rc: async with rc:
@ -225,7 +225,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") 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 # 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 veilid.Sequencing.ENSURE_ORDERED
) )
async with rc: async with rc:
@ -269,8 +269,8 @@ async def test_routing_context_app_message_loopback_bandwidth():
await api.debug("purge routes") await api.debug("purge routes")
# make a routing context that uses a safety route # 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_sequencing(veilid.Sequencing.ENSURE_ORDERED)
# rc = await (await api.new_routing_context()).with_privacy() # rc = await api.new_routing_context()
rc = await api.new_routing_context() rc = await api.new_routing_context()
async with rc: async with rc:
# make a new local private route # make a new local private route

View File

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

View File

@ -446,26 +446,26 @@ class _JsonRoutingContext(RoutingContext):
) )
self.done = True 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( new_rc_id = raise_api_result(
await self.api.send_ndjson_request( await self.api.send_ndjson_request(
Operation.ROUTING_CONTEXT, Operation.ROUTING_CONTEXT,
validate=validate_rc_op, validate=validate_rc_op,
rc_id=self.rc_id, rc_id=self.rc_id,
rc_op=RoutingContextOperation.WITH_PRIVACY, rc_op=RoutingContextOperation.WITH_DEFAULT_SAFETY,
) )
) )
if release: if release:
await self.release() await self.release()
return self.__class__(self.api, new_rc_id) 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( new_rc_id = raise_api_result(
await self.api.send_ndjson_request( await self.api.send_ndjson_request(
Operation.ROUTING_CONTEXT, Operation.ROUTING_CONTEXT,
validate=validate_rc_op, validate=validate_rc_op,
rc_id=self.rc_id, rc_id=self.rc_id,
rc_op=RoutingContextOperation.WITH_CUSTOM_PRIVACY, rc_op=RoutingContextOperation.WITH_SAFETY,
safety_selection=safety_selection, safety_selection=safety_selection,
) )
) )
@ -487,6 +487,19 @@ class _JsonRoutingContext(RoutingContext):
await self.release() await self.release()
return self.__class__(self.api, new_rc_id) 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: async def app_call(self, target: TypedKey | RouteId, message: bytes) -> bytes:
return urlsafe_b64decode_no_pad( return urlsafe_b64decode_no_pad(
raise_api_result( raise_api_result(

View File

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

View File

@ -328,7 +328,7 @@ fn add_routing_context(routing_context: veilid_core::RoutingContext) -> u32 {
pub fn routing_context() -> Promise { pub fn routing_context() -> Promise {
wrap_api_future_plain(async move { wrap_api_future_plain(async move {
let veilid_api = get_veilid_api()?; 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); let new_id = add_routing_context(routing_context);
APIResult::Ok(new_id) APIResult::Ok(new_id)
}) })
@ -344,7 +344,7 @@ pub fn release_routing_context(id: u32) -> i32 {
} }
#[wasm_bindgen()] #[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 routing_context = {
let rc = (*ROUTING_CONTEXTS).borrow(); let rc = (*ROUTING_CONTEXTS).borrow();
let Some(routing_context) = rc.get(&id) else { let Some(routing_context) = rc.get(&id) else {
@ -352,14 +352,14 @@ pub fn routing_context_with_privacy(id: u32) -> u32 {
}; };
routing_context.clone() routing_context.clone()
}; };
let Ok(routing_context) = routing_context.with_privacy() else { let Ok(routing_context) = routing_context.with_default_safety() else {
return 0; return 0;
}; };
add_routing_context(routing_context) add_routing_context(routing_context)
} }
#[wasm_bindgen()] #[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 = let safety_selection: veilid_core::SafetySelection =
veilid_core::deserialize_json(&safety_selection).unwrap(); 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() 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; return 0;
}; };
add_routing_context(routing_context) 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) 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()] #[wasm_bindgen()]
pub fn routing_context_app_call(id: u32, target_string: String, request: String) -> Promise { pub fn routing_context_app_call(id: u32, target_string: String, request: String) -> Promise {
let request: Vec<u8> = data_encoding::BASE64URL_NOPAD let request: Vec<u8> = data_encoding::BASE64URL_NOPAD

View File

@ -13,7 +13,7 @@ impl VeilidRoutingContext {
pub fn new() -> APIResult<VeilidRoutingContext> { pub fn new() -> APIResult<VeilidRoutingContext> {
let veilid_api = get_veilid_api()?; let veilid_api = get_veilid_api()?;
APIResult::Ok(VeilidRoutingContext { 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()) 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. /// Returns a new instance of VeilidRoutingContext - does not mutate.
/// ///
/// Default values for hop count, stability and sequencing preferences are used. /// 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. /// * 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. /// * 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 have no preference for ordered vs unordered message delivery
pub fn withPrivacy(&self) -> APIResult<VeilidRoutingContext> { ///
/// To customize the safety selection in use, use [VeilidRoutingContext::withSafety].
pub fn withDefaultSafety(&self) -> APIResult<VeilidRoutingContext> {
let routing_context = self.getRoutingContext()?; let routing_context = self.getRoutingContext()?;
APIResult::Ok(VeilidRoutingContext { 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. /// Returns a new instance of VeilidRoutingContext - does not mutate.
pub fn withCustomPrivacy( pub fn withSafety(&self, safety_selection: SafetySelection) -> APIResult<VeilidRoutingContext> {
&self,
safety_selection: SafetySelection,
) -> APIResult<VeilidRoutingContext> {
let routing_context = self.getRoutingContext()?; let routing_context = self.getRoutingContext()?;
APIResult::Ok(VeilidRoutingContext { 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. /// App-level unidirectional message that does not expect any value to be returned.
/// ///
/// Veilid apps may use this for arbitrary message passing. /// 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(); routingContext.free();
}); });
it('should create with privacy', async () => { it('should create with default safety', async () => {
const routingContext = VeilidRoutingContext.create().withPrivacy(); const routingContext = VeilidRoutingContext.create().withDefaultSafety();
expect(routingContext instanceof VeilidRoutingContext).toBe(true); expect(routingContext instanceof VeilidRoutingContext).toBe(true);
routingContext.free(); routingContext.free();
}); });
it('should create with custom privacy', async () => { it('should create with safety', async () => {
const routingContext = VeilidRoutingContext.create().withCustomPrivacy({ const routingContext = VeilidRoutingContext.create().withSafety({
Safe: { Safe: {
hop_count: 2, hop_count: 2,
sequencing: 'EnsureOrdered', sequencing: 'EnsureOrdered',
@ -80,7 +80,6 @@ describe('VeilidRoutingContext', () => {
before('create routing context', () => { before('create routing context', () => {
routingContext = VeilidRoutingContext.create() routingContext = VeilidRoutingContext.create()
.withPrivacy()
.withSequencing('EnsureOrdered'); .withSequencing('EnsureOrdered');
}); });

View File

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