From b4a071170d395da06a01b8bfa6ca74e001d7329c Mon Sep 17 00:00:00 2001 From: John Smith Date: Wed, 19 Apr 2023 10:47:09 -0400 Subject: [PATCH] refactor api to clean up internals --- veilid-cli/src/peers_table_view.rs | 6 +- veilid-core/proto/veilid.capnp | 42 +- veilid-core/src/crypto/mod.rs | 2 - veilid-core/src/crypto/value.rs | 0 veilid-core/src/intf/native/system.rs | 2 +- veilid-core/src/intf/wasm/system.rs | 2 +- veilid-core/src/network_manager/mod.rs | 4 +- veilid-core/src/network_manager/native/mod.rs | 2 +- veilid-core/src/network_manager/tests/mod.rs | 2 + .../tests/test_signed_node_info.rs | 167 + .../src/network_manager/types/address.rs | 130 + .../src/network_manager/types/address_type.rs | 22 + .../types/connection_descriptor.rs | 80 + .../network_manager/types/dial_info/mod.rs | 522 ++++ .../network_manager/types/dial_info/tcp.rs | 21 + .../network_manager/types/dial_info/udp.rs | 21 + .../src/network_manager/types/dial_info/ws.rs | 22 + .../network_manager/types/dial_info/wss.rs | 22 + .../network_manager/types/dial_info_class.rs | 50 + .../network_manager/types/dial_info_filter.rs | 86 + .../types/low_level_protocol_type.rs | 31 + veilid-core/src/network_manager/types/mod.rs | 31 + .../network_manager/types/network_class.rs | 37 + .../src/network_manager/types/peer_address.rs | 66 + .../network_manager/types/protocol_type.rs | 104 + .../src/network_manager/types/signal_info.rs | 22 + .../network_manager/types/socket_address.rs | 77 + veilid-core/src/routing_table/mod.rs | 5 +- .../routing_table/types/dial_info_detail.rs | 43 + .../src/routing_table/types/direction.rs | 22 + veilid-core/src/routing_table/types/mod.rs | 25 + .../src/routing_table/types/node_info.rs | 125 + .../src/routing_table/types/node_status.rs | 66 + .../src/routing_table/types/peer_info.rs | 18 + .../src/routing_table/types/routing_domain.rs | 32 + .../types/signed_direct_node_info.rs | 86 + .../routing_table/types/signed_node_info.rs | 89 + .../types/signed_relayed_node_info.rs | 106 + veilid-core/src/rpc_processor/coders/mod.rs | 8 +- .../rpc_processor/coders/signed_value_data.rs | 31 + .../coders/signed_value_descriptor.rs | 26 + .../src/rpc_processor/coders/value_data.rs | 18 - .../src/rpc_processor/coders/value_detail.rs | 20 + veilid-core/src/rpc_processor/mod.rs | 6 +- veilid-core/src/storage_manager/mod.rs | 51 +- .../{value_record.rs => record.rs} | 74 +- .../src/storage_manager/record_data.rs | 33 + .../src/storage_manager/record_store.rs | 13 +- .../src/storage_manager/signed_value_data.rs | 92 + .../signed_value_descriptor.rs | 78 + .../src/storage_manager/value_detail.rs | 43 + veilid-core/src/supplier_table.rs | 0 .../src/tests/common/test_veilid_core.rs | 162 - veilid-core/src/tests/native/mod.rs | 11 + veilid-core/src/veilid_api/debug.rs | 1 + veilid-core/src/veilid_api/mod.rs | 4 +- veilid-core/src/veilid_api/types.rs | 2752 ----------------- .../src/veilid_api/{ => types}/aligned_u64.rs | 14 + .../src/veilid_api/types/app_message_call.rs | 32 + .../types/dht/dht_record_descriptor.rs | 35 + veilid-core/src/veilid_api/types/dht/mod.rs | 16 + .../src/veilid_api/types/dht/schema/dflt.rs | 61 + .../src/veilid_api/types/dht/schema/mod.rs | 84 + .../src/veilid_api/types/dht/schema/smpl.rs | 114 + .../src/veilid_api/types/dht/value_data.rs | 64 + veilid-core/src/veilid_api/types/fourcc.rs | 52 + veilid-core/src/veilid_api/types/mod.rs | 21 + veilid-core/src/veilid_api/types/safety.rs | 125 + veilid-core/src/veilid_api/types/stats.rs | 113 + veilid-core/src/veilid_api/types/tunnel.rs | 83 + .../src/veilid_api/types/veilid_log.rs | 88 + .../src/veilid_api/types/veilid_state.rs | 144 + veilid-core/src/watcher_table.rs | 0 veilid-core/tests/web.rs | 6 + 74 files changed, 3638 insertions(+), 3027 deletions(-) delete mode 100644 veilid-core/src/crypto/value.rs create mode 100644 veilid-core/src/network_manager/tests/test_signed_node_info.rs create mode 100644 veilid-core/src/network_manager/types/address.rs create mode 100644 veilid-core/src/network_manager/types/address_type.rs create mode 100644 veilid-core/src/network_manager/types/connection_descriptor.rs create mode 100644 veilid-core/src/network_manager/types/dial_info/mod.rs create mode 100644 veilid-core/src/network_manager/types/dial_info/tcp.rs create mode 100644 veilid-core/src/network_manager/types/dial_info/udp.rs create mode 100644 veilid-core/src/network_manager/types/dial_info/ws.rs create mode 100644 veilid-core/src/network_manager/types/dial_info/wss.rs create mode 100644 veilid-core/src/network_manager/types/dial_info_class.rs create mode 100644 veilid-core/src/network_manager/types/dial_info_filter.rs create mode 100644 veilid-core/src/network_manager/types/low_level_protocol_type.rs create mode 100644 veilid-core/src/network_manager/types/mod.rs create mode 100644 veilid-core/src/network_manager/types/network_class.rs create mode 100644 veilid-core/src/network_manager/types/peer_address.rs create mode 100644 veilid-core/src/network_manager/types/protocol_type.rs create mode 100644 veilid-core/src/network_manager/types/signal_info.rs create mode 100644 veilid-core/src/network_manager/types/socket_address.rs create mode 100644 veilid-core/src/routing_table/types/dial_info_detail.rs create mode 100644 veilid-core/src/routing_table/types/direction.rs create mode 100644 veilid-core/src/routing_table/types/mod.rs create mode 100644 veilid-core/src/routing_table/types/node_info.rs create mode 100644 veilid-core/src/routing_table/types/node_status.rs create mode 100644 veilid-core/src/routing_table/types/peer_info.rs create mode 100644 veilid-core/src/routing_table/types/routing_domain.rs create mode 100644 veilid-core/src/routing_table/types/signed_direct_node_info.rs create mode 100644 veilid-core/src/routing_table/types/signed_node_info.rs create mode 100644 veilid-core/src/routing_table/types/signed_relayed_node_info.rs create mode 100644 veilid-core/src/rpc_processor/coders/signed_value_data.rs create mode 100644 veilid-core/src/rpc_processor/coders/signed_value_descriptor.rs delete mode 100644 veilid-core/src/rpc_processor/coders/value_data.rs create mode 100644 veilid-core/src/rpc_processor/coders/value_detail.rs rename veilid-core/src/storage_manager/{value_record.rs => record.rs} (50%) create mode 100644 veilid-core/src/storage_manager/record_data.rs create mode 100644 veilid-core/src/storage_manager/signed_value_data.rs create mode 100644 veilid-core/src/storage_manager/signed_value_descriptor.rs create mode 100644 veilid-core/src/storage_manager/value_detail.rs delete mode 100644 veilid-core/src/supplier_table.rs delete mode 100644 veilid-core/src/veilid_api/types.rs rename veilid-core/src/veilid_api/{ => types}/aligned_u64.rs (85%) create mode 100644 veilid-core/src/veilid_api/types/app_message_call.rs create mode 100644 veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs create mode 100644 veilid-core/src/veilid_api/types/dht/mod.rs create mode 100644 veilid-core/src/veilid_api/types/dht/schema/dflt.rs create mode 100644 veilid-core/src/veilid_api/types/dht/schema/mod.rs create mode 100644 veilid-core/src/veilid_api/types/dht/schema/smpl.rs create mode 100644 veilid-core/src/veilid_api/types/dht/value_data.rs create mode 100644 veilid-core/src/veilid_api/types/fourcc.rs create mode 100644 veilid-core/src/veilid_api/types/mod.rs create mode 100644 veilid-core/src/veilid_api/types/safety.rs create mode 100644 veilid-core/src/veilid_api/types/stats.rs create mode 100644 veilid-core/src/veilid_api/types/tunnel.rs create mode 100644 veilid-core/src/veilid_api/types/veilid_log.rs create mode 100644 veilid-core/src/veilid_api/types/veilid_state.rs delete mode 100644 veilid-core/src/watcher_table.rs diff --git a/veilid-cli/src/peers_table_view.rs b/veilid-cli/src/peers_table_view.rs index 41992584..e81a7e86 100644 --- a/veilid-cli/src/peers_table_view.rs +++ b/veilid-cli/src/peers_table_view.rs @@ -55,11 +55,7 @@ impl TableViewItem for PeerTableData { .first() .cloned() .unwrap_or_else(|| "???".to_owned()), - PeerTableColumn::Address => format!( - "{:?}:{}", - self.peer_address.protocol_type(), - self.peer_address.to_socket_addr() - ), + PeerTableColumn::Address => self.peer_address.clone(), PeerTableColumn::LatencyAvg => format!( "{}", self.peer_stats diff --git a/veilid-core/proto/veilid.capnp b/veilid-core/proto/veilid.capnp index e9352711..fc42116b 100644 --- a/veilid-core/proto/veilid.capnp +++ b/veilid-core/proto/veilid.capnp @@ -311,17 +311,16 @@ struct OperationAppMessage @0x9baf542d81b411f5 { message @0 :Data; # opaque message to application } -struct SubkeyRange { +struct SubkeyRange @0xf592dac0a4d0171c { start @0 :Subkey; # the start of a subkey range end @1 :Subkey; # the end of a subkey range } - -struct ValueData @0xb4b7416f169f2a3d { + +struct SignedValueData @0xb4b7416f169f2a3d { seq @0 :ValueSeqNum; # sequence number of value data @1 :Data; # value or subvalue contents - owner @2 :PublicKey; # the public key of the owner - writer @3 :PublicKey; # the public key of the writer - signature @4 :Signature; # signature of data at this subkey, using the writer key (which may be the same as the owner key) + writer @2 :PublicKey; # the public key of the writer + signature @3 :Signature; # signature of data at this subkey, using the writer key (which may be the same as the owner key) # signature covers: # * ownerKey # * subkey @@ -329,23 +328,32 @@ struct ValueData @0xb4b7416f169f2a3d { # * data # signature does not need to cover schema because schema is validated upon every set # so the data either fits, or it doesn't. - schema @5 :Data; # (optional) the schema in use - # If not set and seqnum == 0, uses the default schema. - # If not set and If seqnum != 0, the schema must have been set prior and no other schema may be used, but this field may be eliminated to save space - # Changing this after key creation is not supported as it would change the dht key - # Schema data is signed by ownerKey and is verified both by set and get operations } +struct ValueDetail @0xe88607fa8982d67f { + signedValueData @0 :SignedValueData; # One value data with signature + descriptor @1 :SignedValueDescriptor; # (optional) must provide if seq is 0, and may be present from valueget if wantSchema is specified + # If not set and If seqnum != 0, the schema must have been set prior and no other schema may be used, but this field may be eliminated to save space +} + +struct SignedValueDescriptor @0xe7911cd3f9e1b0e7 { + owner @0 :PublicKey; # the public key of the owner + schemaData @1 :Data; # the schema data + # Changing this after key creation is not supported as it would change the dht key + signature @2 :Signature; # Schema data is signed by ownerKey and is verified both by set and get operations +} + + struct OperationGetValueQ @0xf88a5b6da5eda5d0 { - key @0 :TypedKey; # the location of the value + key @0 :TypedKey; # DHT Key = Hash(ownerKeyKind) of: [ ownerKeyValue, schema ] subkey @1 :Subkey; # the index of the subkey - wantSchema @2 :Bool; # whether or not to include the schema for the key + wantDescriptor @2 :Bool; # whether or not to include the descriptor for the key } struct OperationGetValueA @0xd896bb46f2e0249f { union { - data @0 :ValueData; # the value if successful + value @0 :ValueDetail; # the value if successful peers @1 :List(PeerInfo); # returned 'closer peer' information if not successful } } @@ -353,13 +361,13 @@ struct OperationGetValueA @0xd896bb46f2e0249f { struct OperationSetValueQ @0xbac06191ff8bdbc5 { key @0 :TypedKey; # DHT Key = Hash(ownerKeyKind) of: [ ownerKeyValue, schema ] subkey @1 :Subkey; # the index of the subkey - value @2 :ValueData; # value or subvalue contents (older or equal seq number gets dropped) + value @2 :ValueDetail; # value or subvalue contents (older or equal seq number gets dropped) } struct OperationSetValueA @0x9378d0732dc95be2 { union { schemaError @0 :Void; # Either the schema is not available at the node, or the data does not match the schema that is there - data @1 :ValueData; # the new value if successful, may be a different value than what was set if the seq number was lower or equal + value @1 :ValueDetail; # the new value if successful, may be a different value than what was set if the seq number was lower or equal peers @2 :List(PeerInfo); # returned 'closer peer' information if this node is refusing to store the key } } @@ -381,7 +389,7 @@ struct OperationValueChanged @0xd1c59ebdd8cc1bf6 { key @0 :TypedKey; # key for value that changed subkeys @1 :List(SubkeyRange); # subkey range that changed (up to 512 ranges at a time) count @2 :UInt32; # remaining changes left (0 means watch has expired) - value @3 :ValueData; # first value that changed (the rest can be gotten with getvalue) + value @3 :ValueDetail; # first value that changed (the rest can be gotten with getvalue) } struct OperationSupplyBlockQ @0xadbf4c542d749971 { diff --git a/veilid-core/src/crypto/mod.rs b/veilid-core/src/crypto/mod.rs index ec32c677..f273562c 100644 --- a/veilid-core/src/crypto/mod.rs +++ b/veilid-core/src/crypto/mod.rs @@ -4,7 +4,6 @@ mod dh_cache; mod envelope; mod receipt; mod types; -mod value; pub mod crypto_system; #[cfg(feature = "enable-crypto-none")] @@ -20,7 +19,6 @@ pub use dh_cache::*; pub use envelope::*; pub use receipt::*; pub use types::*; -pub use value::*; #[cfg(feature = "enable-crypto-none")] pub use none::*; diff --git a/veilid-core/src/crypto/value.rs b/veilid-core/src/crypto/value.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/veilid-core/src/intf/native/system.rs b/veilid-core/src/intf/native/system.rs index 5491bff4..9855c256 100644 --- a/veilid-core/src/intf/native/system.rs +++ b/veilid-core/src/intf/native/system.rs @@ -2,7 +2,7 @@ use crate::*; -pub async fn get_outbound_relay_peer() -> Option { +pub async fn get_outbound_relay_peer() -> Option { panic!("Native Veilid should never require an outbound relay"); } diff --git a/veilid-core/src/intf/wasm/system.rs b/veilid-core/src/intf/wasm/system.rs index 95e4e544..de8e158a 100644 --- a/veilid-core/src/intf/wasm/system.rs +++ b/veilid-core/src/intf/wasm/system.rs @@ -2,7 +2,7 @@ use crate::*; //use js_sys::*; -pub async fn get_outbound_relay_peer() -> Option { +pub async fn get_outbound_relay_peer() -> Option { // unimplemented! None } diff --git a/veilid-core/src/network_manager/mod.rs b/veilid-core/src/network_manager/mod.rs index a01b5bdd..d02bdea3 100644 --- a/veilid-core/src/network_manager/mod.rs +++ b/veilid-core/src/network_manager/mod.rs @@ -11,6 +11,7 @@ mod connection_manager; mod connection_table; mod network_connection; mod tasks; +mod types; pub mod tests; @@ -18,6 +19,7 @@ pub mod tests; pub use connection_manager::*; pub use network_connection::*; +pub use types::*; //////////////////////////////////////////////////////////////////////////////////////// use connection_handle::*; @@ -1552,7 +1554,7 @@ impl NetworkManager { let peer_stats = nr.peer_stats(); let peer = PeerTableData { node_ids: nr.node_ids().iter().map(|x| x.to_string()).collect(), - peer_address: v.last_connection.remote(), + peer_address: v.last_connection.remote().to_string(), peer_stats, }; out.push(peer); diff --git a/veilid-core/src/network_manager/native/mod.rs b/veilid-core/src/network_manager/native/mod.rs index c38f799b..319a68c5 100644 --- a/veilid-core/src/network_manager/native/mod.rs +++ b/veilid-core/src/network_manager/native/mod.rs @@ -645,7 +645,7 @@ impl Network { log_net!(debug "enable address {:?} as ipv4", addr); inner.enable_ipv4 = true; } else if addr.is_ipv6() { - let address = crate::Address::from_ip_addr(addr); + let address = Address::from_ip_addr(addr); if address.is_global() { log_net!(debug "enable address {:?} as ipv6 global", address); inner.enable_ipv6_global = true; diff --git a/veilid-core/src/network_manager/tests/mod.rs b/veilid-core/src/network_manager/tests/mod.rs index f0c7391d..64b2b67f 100644 --- a/veilid-core/src/network_manager/tests/mod.rs +++ b/veilid-core/src/network_manager/tests/mod.rs @@ -1,2 +1,4 @@ pub mod test_connection_table; +pub mod test_signed_node_info; + use super::*; diff --git a/veilid-core/src/network_manager/tests/test_signed_node_info.rs b/veilid-core/src/network_manager/tests/test_signed_node_info.rs new file mode 100644 index 00000000..ff1db400 --- /dev/null +++ b/veilid-core/src/network_manager/tests/test_signed_node_info.rs @@ -0,0 +1,167 @@ +use super::*; +use crate::tests::common::test_veilid_config::*; + +pub async fn test_signed_node_info() { + info!("--- test_signed_node_info ---"); + + let (update_callback, config_callback) = setup_veilid_core(); + let api = api_startup(update_callback, config_callback) + .await + .expect("startup failed"); + + let crypto = api.crypto().unwrap(); + for ck in VALID_CRYPTO_KINDS { + let vcrypto = crypto.get(ck).unwrap(); + + // Test direct + let node_info = NodeInfo { + network_class: NetworkClass::InboundCapable, + outbound_protocols: ProtocolTypeSet::all(), + address_types: AddressTypeSet::all(), + envelope_support: VALID_ENVELOPE_VERSIONS.to_vec(), + crypto_support: VALID_CRYPTO_KINDS.to_vec(), + dial_info_detail_list: vec![DialInfoDetail { + class: DialInfoClass::Mapped, + dial_info: DialInfo::udp(SocketAddress::default()), + }], + }; + + // Test correct validation + let keypair = vcrypto.generate_keypair(); + let sni = SignedDirectNodeInfo::make_signatures( + crypto.clone(), + vec![TypedKeyPair::new(ck, keypair)], + node_info.clone(), + ) + .unwrap(); + let mut tks: TypedKeySet = TypedKey::new(ck, keypair.key).into(); + let oldtkslen = tks.len(); + let _ = SignedDirectNodeInfo::new( + crypto.clone(), + &mut tks, + node_info.clone(), + sni.timestamp, + sni.signatures.clone(), + ) + .unwrap(); + assert_eq!(tks.len(), oldtkslen); + assert_eq!(tks.len(), sni.signatures.len()); + + // Test incorrect validation + let keypair1 = vcrypto.generate_keypair(); + let mut tks1: TypedKeySet = TypedKey::new(ck, keypair1.key).into(); + let oldtks1len = tks1.len(); + let _ = SignedDirectNodeInfo::new( + crypto.clone(), + &mut tks1, + node_info.clone(), + sni.timestamp, + sni.signatures.clone(), + ) + .unwrap_err(); + assert_eq!(tks1.len(), oldtks1len); + assert_eq!(tks1.len(), sni.signatures.len()); + + // Test unsupported cryptosystem validation + let fake_crypto_kind: CryptoKind = FourCC::from([0, 1, 2, 3]); + let mut tksfake: TypedKeySet = TypedKey::new(fake_crypto_kind, PublicKey::default()).into(); + let mut sigsfake = sni.signatures.clone(); + sigsfake.push(TypedSignature::new(fake_crypto_kind, Signature::default())); + tksfake.add(TypedKey::new(ck, keypair.key)); + let sdnifake = SignedDirectNodeInfo::new( + crypto.clone(), + &mut tksfake, + node_info.clone(), + sni.timestamp, + sigsfake.clone(), + ) + .unwrap(); + assert_eq!(tksfake.len(), 1); + assert_eq!(sdnifake.signatures.len(), sigsfake.len()); + + // Test relayed + let node_info2 = NodeInfo { + network_class: NetworkClass::OutboundOnly, + outbound_protocols: ProtocolTypeSet::all(), + address_types: AddressTypeSet::all(), + envelope_support: VALID_ENVELOPE_VERSIONS.to_vec(), + crypto_support: VALID_CRYPTO_KINDS.to_vec(), + dial_info_detail_list: vec![DialInfoDetail { + class: DialInfoClass::Blocked, + dial_info: DialInfo::udp(SocketAddress::default()), + }], + }; + + // Test correct validation + let keypair2 = vcrypto.generate_keypair(); + let mut tks2: TypedKeySet = TypedKey::new(ck, keypair2.key).into(); + let oldtks2len = tks2.len(); + + let sni2 = SignedRelayedNodeInfo::make_signatures( + crypto.clone(), + vec![TypedKeyPair::new(ck, keypair2)], + node_info2.clone(), + tks.clone(), + sni.clone(), + ) + .unwrap(); + let _ = SignedRelayedNodeInfo::new( + crypto.clone(), + &mut tks2, + node_info2.clone(), + tks.clone(), + sni.clone(), + sni2.timestamp, + sni2.signatures.clone(), + ) + .unwrap(); + + assert_eq!(tks2.len(), oldtks2len); + assert_eq!(tks2.len(), sni2.signatures.len()); + + // Test incorrect validation + let keypair3 = vcrypto.generate_keypair(); + let mut tks3: TypedKeySet = TypedKey::new(ck, keypair3.key).into(); + let oldtks3len = tks3.len(); + + let _ = SignedRelayedNodeInfo::new( + crypto.clone(), + &mut tks3, + node_info2.clone(), + tks.clone(), + sni.clone(), + sni2.timestamp, + sni2.signatures.clone(), + ) + .unwrap_err(); + + assert_eq!(tks3.len(), oldtks3len); + assert_eq!(tks3.len(), sni2.signatures.len()); + + // Test unsupported cryptosystem validation + let fake_crypto_kind: CryptoKind = FourCC::from([0, 1, 2, 3]); + let mut tksfake3: TypedKeySet = + TypedKey::new(fake_crypto_kind, PublicKey::default()).into(); + let mut sigsfake3 = sni2.signatures.clone(); + sigsfake3.push(TypedSignature::new(fake_crypto_kind, Signature::default())); + tksfake3.add(TypedKey::new(ck, keypair2.key)); + let srnifake = SignedRelayedNodeInfo::new( + crypto.clone(), + &mut tksfake3, + node_info2.clone(), + tks.clone(), + sni.clone(), + sni2.timestamp, + sigsfake3.clone(), + ) + .unwrap(); + assert_eq!(tksfake3.len(), 1); + assert_eq!(srnifake.signatures.len(), sigsfake3.len()); + } + + api.shutdown().await; +} + +pub async fn test_all() { + test_signed_node_info().await; +} diff --git a/veilid-core/src/network_manager/types/address.rs b/veilid-core/src/network_manager/types/address.rs new file mode 100644 index 00000000..c343c8da --- /dev/null +++ b/veilid-core/src/network_manager/types/address.rs @@ -0,0 +1,130 @@ +use super::*; + +#[derive( + Copy, + Clone, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum Address { + IPV4(Ipv4Addr), + IPV6(Ipv6Addr), +} + +impl Default for Address { + fn default() -> Self { + Address::IPV4(Ipv4Addr::new(0, 0, 0, 0)) + } +} + +impl Address { + pub fn from_socket_addr(sa: SocketAddr) -> Address { + match sa { + SocketAddr::V4(v4) => Address::IPV4(*v4.ip()), + SocketAddr::V6(v6) => Address::IPV6(*v6.ip()), + } + } + pub fn from_ip_addr(addr: IpAddr) -> Address { + match addr { + IpAddr::V4(v4) => Address::IPV4(v4), + IpAddr::V6(v6) => Address::IPV6(v6), + } + } + pub fn address_type(&self) -> AddressType { + match self { + Address::IPV4(_) => AddressType::IPV4, + Address::IPV6(_) => AddressType::IPV6, + } + } + pub fn address_string(&self) -> String { + match self { + Address::IPV4(v4) => v4.to_string(), + Address::IPV6(v6) => v6.to_string(), + } + } + pub fn address_string_with_port(&self, port: u16) -> String { + match self { + Address::IPV4(v4) => format!("{}:{}", v4, port), + Address::IPV6(v6) => format!("[{}]:{}", v6, port), + } + } + pub fn is_unspecified(&self) -> bool { + match self { + Address::IPV4(v4) => ipv4addr_is_unspecified(v4), + Address::IPV6(v6) => ipv6addr_is_unspecified(v6), + } + } + pub fn is_global(&self) -> bool { + match self { + Address::IPV4(v4) => ipv4addr_is_global(v4) && !ipv4addr_is_multicast(v4), + Address::IPV6(v6) => ipv6addr_is_unicast_global(v6), + } + } + pub fn is_local(&self) -> bool { + match self { + Address::IPV4(v4) => { + ipv4addr_is_private(v4) + || ipv4addr_is_link_local(v4) + || ipv4addr_is_ietf_protocol_assignment(v4) + } + Address::IPV6(v6) => { + ipv6addr_is_unicast_site_local(v6) + || ipv6addr_is_unicast_link_local(v6) + || ipv6addr_is_unique_local(v6) + } + } + } + pub fn to_ip_addr(&self) -> IpAddr { + match self { + Self::IPV4(a) => IpAddr::V4(*a), + Self::IPV6(a) => IpAddr::V6(*a), + } + } + pub fn to_socket_addr(&self, port: u16) -> SocketAddr { + SocketAddr::new(self.to_ip_addr(), port) + } + pub fn to_canonical(&self) -> Address { + match self { + Address::IPV4(v4) => Address::IPV4(*v4), + Address::IPV6(v6) => match v6.to_ipv4() { + Some(v4) => Address::IPV4(v4), + None => Address::IPV6(*v6), + }, + } + } +} + +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Address::IPV4(v4) => write!(f, "{}", v4), + Address::IPV6(v6) => write!(f, "{}", v6), + } + } +} + +impl FromStr for Address { + type Err = VeilidAPIError; + fn from_str(host: &str) -> Result { + if let Ok(addr) = Ipv4Addr::from_str(host) { + Ok(Address::IPV4(addr)) + } else if let Ok(addr) = Ipv6Addr::from_str(host) { + Ok(Address::IPV6(addr)) + } else { + Err(VeilidAPIError::parse_error( + "Address::from_str failed", + host, + )) + } + } +} diff --git a/veilid-core/src/network_manager/types/address_type.rs b/veilid-core/src/network_manager/types/address_type.rs new file mode 100644 index 00000000..5193654e --- /dev/null +++ b/veilid-core/src/network_manager/types/address_type.rs @@ -0,0 +1,22 @@ +use super::*; + +#[allow(clippy::derive_hash_xor_eq)] +#[derive( + Debug, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + EnumSetType, +)] +#[enumset(repr = "u8")] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum AddressType { + IPV4, + IPV6, +} +pub type AddressTypeSet = EnumSet; diff --git a/veilid-core/src/network_manager/types/connection_descriptor.rs b/veilid-core/src/network_manager/types/connection_descriptor.rs new file mode 100644 index 00000000..1046838f --- /dev/null +++ b/veilid-core/src/network_manager/types/connection_descriptor.rs @@ -0,0 +1,80 @@ +use super::*; + +/// Represents the 5-tuple of an established connection +/// Not used to specify connections to create, that is reserved for DialInfo +/// +/// ConnectionDescriptors should never be from unspecified local addresses for connection oriented protocols +/// If the medium does not allow local addresses, None should have been used or 'new_no_local' +/// If we are specifying only a port, then the socket's 'local_address()' should have been used, since an +/// established connection is always from a real address to another real address. +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct ConnectionDescriptor { + remote: PeerAddress, + local: Option, +} + +impl ConnectionDescriptor { + pub fn new(remote: PeerAddress, local: SocketAddress) -> Self { + assert!( + !remote.protocol_type().is_connection_oriented() || !local.address().is_unspecified() + ); + + Self { + remote, + local: Some(local), + } + } + pub fn new_no_local(remote: PeerAddress) -> Self { + Self { + remote, + local: None, + } + } + pub fn remote(&self) -> PeerAddress { + self.remote + } + pub fn remote_address(&self) -> &SocketAddress { + self.remote.socket_address() + } + pub fn local(&self) -> Option { + self.local + } + pub fn protocol_type(&self) -> ProtocolType { + self.remote.protocol_type() + } + pub fn address_type(&self) -> AddressType { + self.remote.address_type() + } + pub fn make_dial_info_filter(&self) -> DialInfoFilter { + DialInfoFilter::all() + .with_protocol_type(self.protocol_type()) + .with_address_type(self.address_type()) + } +} + +impl MatchesDialInfoFilter for ConnectionDescriptor { + fn matches_filter(&self, filter: &DialInfoFilter) -> bool { + if !filter.protocol_type_set.contains(self.protocol_type()) { + return false; + } + if !filter.address_type_set.contains(self.address_type()) { + return false; + } + true + } +} diff --git a/veilid-core/src/network_manager/types/dial_info/mod.rs b/veilid-core/src/network_manager/types/dial_info/mod.rs new file mode 100644 index 00000000..4d6a8428 --- /dev/null +++ b/veilid-core/src/network_manager/types/dial_info/mod.rs @@ -0,0 +1,522 @@ +mod tcp; +mod udp; +mod ws; +mod wss; + +use super::*; + +pub use tcp::*; +pub use udp::*; +pub use ws::*; +pub use wss::*; + +// Keep member order appropriate for sorting < preference +// Must match ProtocolType order +#[derive( + Clone, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +#[serde(tag = "kind")] +pub enum DialInfo { + UDP(DialInfoUDP), + TCP(DialInfoTCP), + WS(DialInfoWS), + WSS(DialInfoWSS), +} +impl Default for DialInfo { + fn default() -> Self { + DialInfo::UDP(DialInfoUDP::default()) + } +} + +impl fmt::Display for DialInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + DialInfo::UDP(di) => write!(f, "udp|{}", di.socket_address), + DialInfo::TCP(di) => write!(f, "tcp|{}", di.socket_address), + DialInfo::WS(di) => { + let url = format!("ws://{}", di.request); + let split_url = SplitUrl::from_str(&url).unwrap(); + match split_url.host { + SplitUrlHost::Hostname(_) => { + write!(f, "ws|{}|{}", di.socket_address.to_ip_addr(), di.request) + } + SplitUrlHost::IpAddr(a) => { + if di.socket_address.to_ip_addr() == a { + write!(f, "ws|{}", di.request) + } else { + panic!("resolved address does not match url: {}", di.request); + } + } + } + } + DialInfo::WSS(di) => { + let url = format!("wss://{}", di.request); + let split_url = SplitUrl::from_str(&url).unwrap(); + match split_url.host { + SplitUrlHost::Hostname(_) => { + write!(f, "wss|{}|{}", di.socket_address.to_ip_addr(), di.request) + } + SplitUrlHost::IpAddr(_) => { + panic!( + "secure websockets can not use ip address in request: {}", + di.request + ); + } + } + } + } + } +} + +impl FromStr for DialInfo { + type Err = VeilidAPIError; + fn from_str(s: &str) -> Result { + let (proto, rest) = s.split_once('|').ok_or_else(|| { + VeilidAPIError::parse_error("DialInfo::from_str missing protocol '|' separator", s) + })?; + match proto { + "udp" => { + let socket_address = SocketAddress::from_str(rest)?; + Ok(DialInfo::udp(socket_address)) + } + "tcp" => { + let socket_address = SocketAddress::from_str(rest)?; + Ok(DialInfo::tcp(socket_address)) + } + "ws" => { + let url = format!("ws://{}", rest); + let split_url = SplitUrl::from_str(&url).map_err(|e| { + VeilidAPIError::parse_error(format!("unable to split WS url: {}", e), &url) + })?; + if split_url.scheme != "ws" || !url.starts_with("ws://") { + apibail_parse_error!("incorrect scheme for WS dialinfo", url); + } + let url_port = split_url.port.unwrap_or(80u16); + + match rest.split_once('|') { + Some((sa, rest)) => { + let address = Address::from_str(sa)?; + + DialInfo::try_ws( + SocketAddress::new(address, url_port), + format!("ws://{}", rest), + ) + } + None => { + let address = Address::from_str(&split_url.host.to_string())?; + DialInfo::try_ws( + SocketAddress::new(address, url_port), + format!("ws://{}", rest), + ) + } + } + } + "wss" => { + let url = format!("wss://{}", rest); + let split_url = SplitUrl::from_str(&url).map_err(|e| { + VeilidAPIError::parse_error(format!("unable to split WSS url: {}", e), &url) + })?; + if split_url.scheme != "wss" || !url.starts_with("wss://") { + apibail_parse_error!("incorrect scheme for WSS dialinfo", url); + } + let url_port = split_url.port.unwrap_or(443u16); + + let (a, rest) = rest.split_once('|').ok_or_else(|| { + VeilidAPIError::parse_error( + "DialInfo::from_str missing socket address '|' separator", + s, + ) + })?; + + let address = Address::from_str(a)?; + DialInfo::try_wss( + SocketAddress::new(address, url_port), + format!("wss://{}", rest), + ) + } + _ => Err(VeilidAPIError::parse_error( + "DialInfo::from_str has invalid scheme", + s, + )), + } + } +} + +impl DialInfo { + pub fn udp_from_socketaddr(socket_addr: SocketAddr) -> Self { + Self::UDP(DialInfoUDP { + socket_address: SocketAddress::from_socket_addr(socket_addr).to_canonical(), + }) + } + pub fn tcp_from_socketaddr(socket_addr: SocketAddr) -> Self { + Self::TCP(DialInfoTCP { + socket_address: SocketAddress::from_socket_addr(socket_addr).to_canonical(), + }) + } + pub fn udp(socket_address: SocketAddress) -> Self { + Self::UDP(DialInfoUDP { + socket_address: socket_address.to_canonical(), + }) + } + pub fn tcp(socket_address: SocketAddress) -> Self { + Self::TCP(DialInfoTCP { + socket_address: socket_address.to_canonical(), + }) + } + pub fn try_ws(socket_address: SocketAddress, url: String) -> Result { + let split_url = SplitUrl::from_str(&url).map_err(|e| { + VeilidAPIError::parse_error(format!("unable to split WS url: {}", e), &url) + })?; + if split_url.scheme != "ws" || !url.starts_with("ws://") { + apibail_parse_error!("incorrect scheme for WS dialinfo", url); + } + let url_port = split_url.port.unwrap_or(80u16); + if url_port != socket_address.port() { + apibail_parse_error!("socket address port doesn't match url port", url); + } + if let SplitUrlHost::IpAddr(a) = split_url.host { + if socket_address.to_ip_addr() != a { + apibail_parse_error!( + format!("request address does not match socket address: {}", a), + socket_address + ); + } + } + Ok(Self::WS(DialInfoWS { + socket_address: socket_address.to_canonical(), + request: url[5..].to_string(), + })) + } + pub fn try_wss(socket_address: SocketAddress, url: String) -> Result { + let split_url = SplitUrl::from_str(&url).map_err(|e| { + VeilidAPIError::parse_error(format!("unable to split WSS url: {}", e), &url) + })?; + if split_url.scheme != "wss" || !url.starts_with("wss://") { + apibail_parse_error!("incorrect scheme for WSS dialinfo", url); + } + let url_port = split_url.port.unwrap_or(443u16); + if url_port != socket_address.port() { + apibail_parse_error!("socket address port doesn't match url port", url); + } + if !matches!(split_url.host, SplitUrlHost::Hostname(_)) { + apibail_parse_error!( + "WSS url can not use address format, only hostname format", + url + ); + } + Ok(Self::WSS(DialInfoWSS { + socket_address: socket_address.to_canonical(), + request: url[6..].to_string(), + })) + } + pub fn protocol_type(&self) -> ProtocolType { + match self { + Self::UDP(_) => ProtocolType::UDP, + Self::TCP(_) => ProtocolType::TCP, + Self::WS(_) => ProtocolType::WS, + Self::WSS(_) => ProtocolType::WSS, + } + } + pub fn address_type(&self) -> AddressType { + self.socket_address().address_type() + } + pub fn address(&self) -> Address { + match self { + Self::UDP(di) => di.socket_address.address(), + Self::TCP(di) => di.socket_address.address(), + Self::WS(di) => di.socket_address.address(), + Self::WSS(di) => di.socket_address.address(), + } + } + pub fn set_address(&mut self, address: Address) { + match self { + Self::UDP(di) => di.socket_address.set_address(address), + Self::TCP(di) => di.socket_address.set_address(address), + Self::WS(di) => di.socket_address.set_address(address), + Self::WSS(di) => di.socket_address.set_address(address), + } + } + pub fn socket_address(&self) -> SocketAddress { + match self { + Self::UDP(di) => di.socket_address, + Self::TCP(di) => di.socket_address, + Self::WS(di) => di.socket_address, + Self::WSS(di) => di.socket_address, + } + } + pub fn to_ip_addr(&self) -> IpAddr { + match self { + Self::UDP(di) => di.socket_address.to_ip_addr(), + Self::TCP(di) => di.socket_address.to_ip_addr(), + Self::WS(di) => di.socket_address.to_ip_addr(), + Self::WSS(di) => di.socket_address.to_ip_addr(), + } + } + pub fn port(&self) -> u16 { + match self { + Self::UDP(di) => di.socket_address.port(), + Self::TCP(di) => di.socket_address.port(), + Self::WS(di) => di.socket_address.port(), + Self::WSS(di) => di.socket_address.port(), + } + } + pub fn set_port(&mut self, port: u16) { + match self { + Self::UDP(di) => di.socket_address.set_port(port), + Self::TCP(di) => di.socket_address.set_port(port), + Self::WS(di) => di.socket_address.set_port(port), + Self::WSS(di) => di.socket_address.set_port(port), + } + } + pub fn to_socket_addr(&self) -> SocketAddr { + match self { + Self::UDP(di) => di.socket_address.to_socket_addr(), + Self::TCP(di) => di.socket_address.to_socket_addr(), + Self::WS(di) => di.socket_address.to_socket_addr(), + Self::WSS(di) => di.socket_address.to_socket_addr(), + } + } + pub fn to_peer_address(&self) -> PeerAddress { + match self { + Self::UDP(di) => PeerAddress::new(di.socket_address, ProtocolType::UDP), + Self::TCP(di) => PeerAddress::new(di.socket_address, ProtocolType::TCP), + Self::WS(di) => PeerAddress::new(di.socket_address, ProtocolType::WS), + Self::WSS(di) => PeerAddress::new(di.socket_address, ProtocolType::WSS), + } + } + pub fn request(&self) -> Option { + match self { + Self::UDP(_) => None, + Self::TCP(_) => None, + Self::WS(di) => Some(format!("ws://{}", di.request)), + Self::WSS(di) => Some(format!("wss://{}", di.request)), + } + } + pub fn is_valid(&self) -> bool { + let socket_address = self.socket_address(); + let address = socket_address.address(); + let port = socket_address.port(); + (address.is_global() || address.is_local()) && port > 0 + } + + pub fn make_filter(&self) -> DialInfoFilter { + DialInfoFilter { + protocol_type_set: ProtocolTypeSet::only(self.protocol_type()), + address_type_set: AddressTypeSet::only(self.address_type()), + } + } + + pub fn try_vec_from_short, H: AsRef>( + short: S, + hostname: H, + ) -> Result, VeilidAPIError> { + let short = short.as_ref(); + let hostname = hostname.as_ref(); + + if short.len() < 2 { + apibail_parse_error!("invalid short url length", short); + } + let url = match &short[0..1] { + "U" => { + format!("udp://{}:{}", hostname, &short[1..]) + } + "T" => { + format!("tcp://{}:{}", hostname, &short[1..]) + } + "W" => { + format!("ws://{}:{}", hostname, &short[1..]) + } + "S" => { + format!("wss://{}:{}", hostname, &short[1..]) + } + _ => { + apibail_parse_error!("invalid short url type", short); + } + }; + Self::try_vec_from_url(url) + } + + pub fn try_vec_from_url>(url: S) -> Result, VeilidAPIError> { + let url = url.as_ref(); + let split_url = SplitUrl::from_str(url) + .map_err(|e| VeilidAPIError::parse_error(format!("unable to split url: {}", e), url))?; + + let port = match split_url.scheme.as_str() { + "udp" | "tcp" => split_url + .port + .ok_or_else(|| VeilidAPIError::parse_error("Missing port in udp url", url))?, + "ws" => split_url.port.unwrap_or(80u16), + "wss" => split_url.port.unwrap_or(443u16), + _ => { + apibail_parse_error!("Invalid dial info url scheme", split_url.scheme); + } + }; + + let socket_addrs = { + // Resolve if possible, WASM doesn't support resolution and doesn't need it to connect to the dialinfo + // This will not be used on signed dialinfo, only for bootstrapping, so we don't need to worry about + // the '0.0.0.0' address being propagated across the routing table + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), port)] + } else { + match split_url.host { + SplitUrlHost::Hostname(_) => split_url + .host_port(port) + .to_socket_addrs() + .map_err(|_| VeilidAPIError::parse_error("couldn't resolve hostname in url", url))? + .collect(), + SplitUrlHost::IpAddr(a) => vec![SocketAddr::new(a, port)], + } + } + } + }; + + let mut out = Vec::new(); + for sa in socket_addrs { + out.push(match split_url.scheme.as_str() { + "udp" => Self::udp_from_socketaddr(sa), + "tcp" => Self::tcp_from_socketaddr(sa), + "ws" => Self::try_ws( + SocketAddress::from_socket_addr(sa).to_canonical(), + url.to_string(), + )?, + "wss" => Self::try_wss( + SocketAddress::from_socket_addr(sa).to_canonical(), + url.to_string(), + )?, + _ => { + unreachable!("Invalid dial info url scheme") + } + }); + } + Ok(out) + } + + pub async fn to_short(&self) -> (String, String) { + match self { + DialInfo::UDP(di) => ( + format!("U{}", di.socket_address.port()), + intf::ptr_lookup(di.socket_address.to_ip_addr()) + .await + .unwrap_or_else(|_| di.socket_address.to_string()), + ), + DialInfo::TCP(di) => ( + format!("T{}", di.socket_address.port()), + intf::ptr_lookup(di.socket_address.to_ip_addr()) + .await + .unwrap_or_else(|_| di.socket_address.to_string()), + ), + DialInfo::WS(di) => { + let mut split_url = SplitUrl::from_str(&format!("ws://{}", di.request)).unwrap(); + if let SplitUrlHost::IpAddr(a) = split_url.host { + if let Ok(host) = intf::ptr_lookup(a).await { + split_url.host = SplitUrlHost::Hostname(host); + } + } + ( + format!( + "W{}{}", + split_url.port.unwrap_or(80), + split_url + .path + .map(|p| format!("/{}", p)) + .unwrap_or_default() + ), + split_url.host.to_string(), + ) + } + DialInfo::WSS(di) => { + let mut split_url = SplitUrl::from_str(&format!("wss://{}", di.request)).unwrap(); + if let SplitUrlHost::IpAddr(a) = split_url.host { + if let Ok(host) = intf::ptr_lookup(a).await { + split_url.host = SplitUrlHost::Hostname(host); + } + } + ( + format!( + "S{}{}", + split_url.port.unwrap_or(443), + split_url + .path + .map(|p| format!("/{}", p)) + .unwrap_or_default() + ), + split_url.host.to_string(), + ) + } + } + } + pub async fn to_url(&self) -> String { + match self { + DialInfo::UDP(di) => intf::ptr_lookup(di.socket_address.to_ip_addr()) + .await + .map(|h| format!("udp://{}:{}", h, di.socket_address.port())) + .unwrap_or_else(|_| format!("udp://{}", di.socket_address)), + DialInfo::TCP(di) => intf::ptr_lookup(di.socket_address.to_ip_addr()) + .await + .map(|h| format!("tcp://{}:{}", h, di.socket_address.port())) + .unwrap_or_else(|_| format!("tcp://{}", di.socket_address)), + DialInfo::WS(di) => { + let mut split_url = SplitUrl::from_str(&format!("ws://{}", di.request)).unwrap(); + if let SplitUrlHost::IpAddr(a) = split_url.host { + if let Ok(host) = intf::ptr_lookup(a).await { + split_url.host = SplitUrlHost::Hostname(host); + } + } + split_url.to_string() + } + DialInfo::WSS(di) => { + let mut split_url = SplitUrl::from_str(&format!("wss://{}", di.request)).unwrap(); + if let SplitUrlHost::IpAddr(a) = split_url.host { + if let Ok(host) = intf::ptr_lookup(a).await { + split_url.host = SplitUrlHost::Hostname(host); + } + } + split_url.to_string() + } + } + } + + pub fn ordered_sequencing_sort(a: &DialInfo, b: &DialInfo) -> core::cmp::Ordering { + let ca = a.protocol_type().sort_order(Sequencing::EnsureOrdered); + let cb = b.protocol_type().sort_order(Sequencing::EnsureOrdered); + if ca < cb { + return core::cmp::Ordering::Less; + } + if ca > cb { + return core::cmp::Ordering::Greater; + } + match (a, b) { + (DialInfo::UDP(a), DialInfo::UDP(b)) => a.cmp(b), + (DialInfo::TCP(a), DialInfo::TCP(b)) => a.cmp(b), + (DialInfo::WS(a), DialInfo::WS(b)) => a.cmp(b), + (DialInfo::WSS(a), DialInfo::WSS(b)) => a.cmp(b), + _ => unreachable!(), + } + } +} + +impl MatchesDialInfoFilter for DialInfo { + fn matches_filter(&self, filter: &DialInfoFilter) -> bool { + if !filter.protocol_type_set.contains(self.protocol_type()) { + return false; + } + if !filter.address_type_set.contains(self.address_type()) { + return false; + } + true + } +} diff --git a/veilid-core/src/network_manager/types/dial_info/tcp.rs b/veilid-core/src/network_manager/types/dial_info/tcp.rs new file mode 100644 index 00000000..0f93273d --- /dev/null +++ b/veilid-core/src/network_manager/types/dial_info/tcp.rs @@ -0,0 +1,21 @@ +use super::*; + +#[derive( + Clone, + Default, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DialInfoTCP { + pub socket_address: SocketAddress, +} \ No newline at end of file diff --git a/veilid-core/src/network_manager/types/dial_info/udp.rs b/veilid-core/src/network_manager/types/dial_info/udp.rs new file mode 100644 index 00000000..d799e116 --- /dev/null +++ b/veilid-core/src/network_manager/types/dial_info/udp.rs @@ -0,0 +1,21 @@ +use super::*; + +#[derive( + Clone, + Default, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DialInfoUDP { + pub socket_address: SocketAddress, +} diff --git a/veilid-core/src/network_manager/types/dial_info/ws.rs b/veilid-core/src/network_manager/types/dial_info/ws.rs new file mode 100644 index 00000000..18e2a37c --- /dev/null +++ b/veilid-core/src/network_manager/types/dial_info/ws.rs @@ -0,0 +1,22 @@ +use super::*; + +#[derive( + Clone, + Default, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DialInfoWS { + pub socket_address: SocketAddress, + pub request: String, +} \ No newline at end of file diff --git a/veilid-core/src/network_manager/types/dial_info/wss.rs b/veilid-core/src/network_manager/types/dial_info/wss.rs new file mode 100644 index 00000000..e999430d --- /dev/null +++ b/veilid-core/src/network_manager/types/dial_info/wss.rs @@ -0,0 +1,22 @@ +use super::*; + +#[derive( + Clone, + Default, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DialInfoWSS { + pub socket_address: SocketAddress, + pub request: String, +} diff --git a/veilid-core/src/network_manager/types/dial_info_class.rs b/veilid-core/src/network_manager/types/dial_info_class.rs new file mode 100644 index 00000000..f3f91376 --- /dev/null +++ b/veilid-core/src/network_manager/types/dial_info_class.rs @@ -0,0 +1,50 @@ +use super::*; + +// Keep member order appropriate for sorting < preference +#[derive( + Copy, + Clone, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum DialInfoClass { + Direct = 0, // D = Directly reachable with public IP and no firewall, with statically configured port + Mapped = 1, // M = Directly reachable with via portmap behind any NAT or firewalled with dynamically negotiated port + FullConeNAT = 2, // F = Directly reachable device without portmap behind full-cone NAT + Blocked = 3, // B = Inbound blocked at firewall but may hole punch with public address + AddressRestrictedNAT = 4, // A = Device without portmap behind address-only restricted NAT + PortRestrictedNAT = 5, // P = Device without portmap behind address-and-port restricted NAT +} + +impl DialInfoClass { + // Is a signal required to do an inbound hole-punch? + pub fn requires_signal(&self) -> bool { + matches!( + self, + Self::Blocked | Self::AddressRestrictedNAT | Self::PortRestrictedNAT + ) + } + + // Does a relay node need to be allocated for this dial info? + // For full cone NAT, the relay itself may not be used but the keepalive sent to it + // is required to keep the NAT mapping valid in the router state table + pub fn requires_relay(&self) -> bool { + matches!( + self, + Self::FullConeNAT + | Self::Blocked + | Self::AddressRestrictedNAT + | Self::PortRestrictedNAT + ) + } +} diff --git a/veilid-core/src/network_manager/types/dial_info_filter.rs b/veilid-core/src/network_manager/types/dial_info_filter.rs new file mode 100644 index 00000000..c3635957 --- /dev/null +++ b/veilid-core/src/network_manager/types/dial_info_filter.rs @@ -0,0 +1,86 @@ +use super::*; + +#[derive( + Copy, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DialInfoFilter { + #[with(RkyvEnumSet)] + pub protocol_type_set: ProtocolTypeSet, + #[with(RkyvEnumSet)] + pub address_type_set: AddressTypeSet, +} + +impl Default for DialInfoFilter { + fn default() -> Self { + Self { + protocol_type_set: ProtocolTypeSet::all(), + address_type_set: AddressTypeSet::all(), + } + } +} + +impl DialInfoFilter { + pub fn all() -> Self { + Self { + protocol_type_set: ProtocolTypeSet::all(), + address_type_set: AddressTypeSet::all(), + } + } + pub fn with_protocol_type(mut self, protocol_type: ProtocolType) -> Self { + self.protocol_type_set = ProtocolTypeSet::only(protocol_type); + self + } + pub fn with_protocol_type_set(mut self, protocol_set: ProtocolTypeSet) -> Self { + self.protocol_type_set = protocol_set; + self + } + pub fn with_address_type(mut self, address_type: AddressType) -> Self { + self.address_type_set = AddressTypeSet::only(address_type); + self + } + pub fn with_address_type_set(mut self, address_set: AddressTypeSet) -> Self { + self.address_type_set = address_set; + self + } + pub fn filtered(mut self, other_dif: &DialInfoFilter) -> Self { + self.protocol_type_set &= other_dif.protocol_type_set; + self.address_type_set &= other_dif.address_type_set; + self + } + pub fn is_dead(&self) -> bool { + self.protocol_type_set.is_empty() || self.address_type_set.is_empty() + } +} + +impl fmt::Debug for DialInfoFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let mut out = String::new(); + if self.protocol_type_set != ProtocolTypeSet::all() { + out += &format!("+{:?}", self.protocol_type_set); + } else { + out += "*"; + } + if self.address_type_set != AddressTypeSet::all() { + out += &format!("+{:?}", self.address_type_set); + } else { + out += "*"; + } + write!(f, "[{}]", out) + } +} + +pub trait MatchesDialInfoFilter { + fn matches_filter(&self, filter: &DialInfoFilter) -> bool; +} + diff --git a/veilid-core/src/network_manager/types/low_level_protocol_type.rs b/veilid-core/src/network_manager/types/low_level_protocol_type.rs new file mode 100644 index 00000000..3ae2df7b --- /dev/null +++ b/veilid-core/src/network_manager/types/low_level_protocol_type.rs @@ -0,0 +1,31 @@ +use super::*; + +// Keep member order appropriate for sorting < preference +// Must match DialInfo order +#[allow(clippy::derive_hash_xor_eq)] +#[derive( + Debug, + PartialOrd, + Ord, + Hash, + EnumSetType, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[enumset(repr = "u8")] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum LowLevelProtocolType { + UDP, + TCP, +} + +impl LowLevelProtocolType { + pub fn is_connection_oriented(&self) -> bool { + matches!(self, LowLevelProtocolType::TCP) + } +} +pub type LowLevelProtocolTypeSet = EnumSet; + diff --git a/veilid-core/src/network_manager/types/mod.rs b/veilid-core/src/network_manager/types/mod.rs new file mode 100644 index 00000000..e5b494f8 --- /dev/null +++ b/veilid-core/src/network_manager/types/mod.rs @@ -0,0 +1,31 @@ +mod address; +mod address_type; +mod connection_descriptor; +mod dial_info; +mod dial_info_class; +mod dial_info_filter; +mod low_level_protocol_type; +mod network_class; +mod peer_address; +mod protocol_type; +mod signal_info; +mod socket_address; + +use super::*; + +pub use address::*; +pub use address_type::*; +pub use connection_descriptor::*; +pub use dial_info::*; +pub use dial_info_class::*; +pub use dial_info_filter::*; +pub use low_level_protocol_type::*; +pub use network_class::*; +pub use peer_address::*; +pub use protocol_type::*; +pub use signal_info::*; +pub use socket_address::*; + +use enumset::*; +use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; +use serde::*; diff --git a/veilid-core/src/network_manager/types/network_class.rs b/veilid-core/src/network_manager/types/network_class.rs new file mode 100644 index 00000000..828edac7 --- /dev/null +++ b/veilid-core/src/network_manager/types/network_class.rs @@ -0,0 +1,37 @@ +use super::*; + +#[derive( + Copy, + Clone, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum NetworkClass { + InboundCapable = 0, // I = Inbound capable without relay, may require signal + OutboundOnly = 1, // O = Outbound only, inbound relay required except with reverse connect signal + WebApp = 2, // W = PWA, outbound relay is required in most cases + Invalid = 3, // X = Invalid network class, we don't know how to reach this node +} + +impl Default for NetworkClass { + fn default() -> Self { + Self::Invalid + } +} + +impl NetworkClass { + // Should an outbound relay be kept available? + pub fn outbound_wants_relay(&self) -> bool { + matches!(self, Self::WebApp) + } +} diff --git a/veilid-core/src/network_manager/types/peer_address.rs b/veilid-core/src/network_manager/types/peer_address.rs new file mode 100644 index 00000000..2ca52329 --- /dev/null +++ b/veilid-core/src/network_manager/types/peer_address.rs @@ -0,0 +1,66 @@ +use super::*; + +#[derive( + Copy, + Clone, + Debug, + PartialEq, + PartialOrd, + Eq, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct PeerAddress { + protocol_type: ProtocolType, + #[serde(with = "json_as_string")] + socket_address: SocketAddress, +} + +impl PeerAddress { + pub fn new(socket_address: SocketAddress, protocol_type: ProtocolType) -> Self { + Self { + socket_address: socket_address.to_canonical(), + protocol_type, + } + } + + pub fn socket_address(&self) -> &SocketAddress { + &self.socket_address + } + + pub fn protocol_type(&self) -> ProtocolType { + self.protocol_type + } + + pub fn to_socket_addr(&self) -> SocketAddr { + self.socket_address.to_socket_addr() + } + + pub fn address_type(&self) -> AddressType { + self.socket_address.address_type() + } +} + +impl fmt::Display for PeerAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.protocol_type, self.socket_address) + } +} + +impl FromStr for PeerAddress { + type Err = VeilidAPIError; + fn from_str(s: &str) -> Result { + let Some((first, second)) = s.split_once(':') else { + return Err(VeilidAPIError::parse_error("PeerAddress is missing a colon: {}", s)); + }; + let protocol_type = ProtocolType::from_str(first)?; + let socket_address = SocketAddress::from_str(second)?; + Ok(PeerAddress::new(socket_address, protocol_type)) + } +} diff --git a/veilid-core/src/network_manager/types/protocol_type.rs b/veilid-core/src/network_manager/types/protocol_type.rs new file mode 100644 index 00000000..d0ca4c99 --- /dev/null +++ b/veilid-core/src/network_manager/types/protocol_type.rs @@ -0,0 +1,104 @@ +use super::*; + +// Keep member order appropriate for sorting < preference +// Must match DialInfo order +#[allow(clippy::derive_hash_xor_eq)] +#[derive( + Debug, + PartialOrd, + Ord, + Hash, + EnumSetType, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[enumset(repr = "u8")] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum ProtocolType { + UDP, + TCP, + WS, + WSS, +} + +impl ProtocolType { + pub fn is_connection_oriented(&self) -> bool { + matches!( + self, + ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS + ) + } + pub fn low_level_protocol_type(&self) -> LowLevelProtocolType { + match self { + ProtocolType::UDP => LowLevelProtocolType::UDP, + ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS => LowLevelProtocolType::TCP, + } + } + pub fn sort_order(&self, sequencing: Sequencing) -> usize { + match self { + ProtocolType::UDP => { + if sequencing != Sequencing::NoPreference { + 3 + } else { + 0 + } + } + ProtocolType::TCP => { + if sequencing != Sequencing::NoPreference { + 0 + } else { + 1 + } + } + ProtocolType::WS => { + if sequencing != Sequencing::NoPreference { + 1 + } else { + 2 + } + } + ProtocolType::WSS => { + if sequencing != Sequencing::NoPreference { + 2 + } else { + 3 + } + } + } + } + pub fn all_ordered_set() -> ProtocolTypeSet { + ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS + } +} + +impl fmt::Display for ProtocolType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ProtocolType::UDP => write!(f, "UDP"), + ProtocolType::TCP => write!(f, "TCP"), + ProtocolType::WS => write!(f, "WS"), + ProtocolType::WSS => write!(f, "WSS"), + } + } +} + +impl FromStr for ProtocolType { + type Err = VeilidAPIError; + fn from_str(s: &str) -> Result { + match s.to_ascii_uppercase().as_str() { + "UDP" => Ok(ProtocolType::UDP), + "TCP" => Ok(ProtocolType::TCP), + "WS" => Ok(ProtocolType::WS), + "WSS" => Ok(ProtocolType::WSS), + _ => Err(VeilidAPIError::parse_error( + "ProtocolType::from_str failed", + s, + )), + } + } +} + +pub type ProtocolTypeSet = EnumSet; diff --git a/veilid-core/src/network_manager/types/signal_info.rs b/veilid-core/src/network_manager/types/signal_info.rs new file mode 100644 index 00000000..f81aef5c --- /dev/null +++ b/veilid-core/src/network_manager/types/signal_info.rs @@ -0,0 +1,22 @@ +use super::*; + +/// Parameter for Signal operation +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum SignalInfo { + /// UDP Hole Punch Request + HolePunch { + /// /// Receipt to be returned after the hole punch + receipt: Vec, + /// Sender's peer info + peer_info: PeerInfo, + }, + /// Reverse Connection Request + ReverseConnect { + /// Receipt to be returned by the reverse connection + receipt: Vec, + /// Sender's peer info + peer_info: PeerInfo, + }, + // XXX: WebRTC +} diff --git a/veilid-core/src/network_manager/types/socket_address.rs b/veilid-core/src/network_manager/types/socket_address.rs new file mode 100644 index 00000000..bbd21c8a --- /dev/null +++ b/veilid-core/src/network_manager/types/socket_address.rs @@ -0,0 +1,77 @@ +use super::*; + +#[derive( + Copy, + Default, + Clone, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct SocketAddress { + address: Address, + port: u16, +} + +impl SocketAddress { + pub fn new(address: Address, port: u16) -> Self { + Self { address, port } + } + pub fn from_socket_addr(sa: SocketAddr) -> SocketAddress { + Self { + address: Address::from_socket_addr(sa), + port: sa.port(), + } + } + pub fn address(&self) -> Address { + self.address + } + pub fn set_address(&mut self, address: Address) { + self.address = address; + } + pub fn address_type(&self) -> AddressType { + self.address.address_type() + } + pub fn port(&self) -> u16 { + self.port + } + pub fn set_port(&mut self, port: u16) { + self.port = port + } + pub fn to_canonical(&self) -> SocketAddress { + SocketAddress { + address: self.address.to_canonical(), + port: self.port, + } + } + pub fn to_ip_addr(&self) -> IpAddr { + self.address.to_ip_addr() + } + pub fn to_socket_addr(&self) -> SocketAddr { + self.address.to_socket_addr(self.port) + } +} + +impl fmt::Display for SocketAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{}", self.to_socket_addr()) + } +} + +impl FromStr for SocketAddress { + type Err = VeilidAPIError; + fn from_str(s: &str) -> Result { + let sa = SocketAddr::from_str(s) + .map_err(|e| VeilidAPIError::parse_error("Failed to parse SocketAddress", e))?; + Ok(SocketAddress::from_socket_addr(sa)) + } +} diff --git a/veilid-core/src/routing_table/mod.rs b/veilid-core/src/routing_table/mod.rs index 01381d0e..2c4a3c79 100644 --- a/veilid-core/src/routing_table/mod.rs +++ b/veilid-core/src/routing_table/mod.rs @@ -10,6 +10,7 @@ mod routing_domains; mod routing_table_inner; mod stats_accounting; mod tasks; +mod types; use crate::*; @@ -17,9 +18,10 @@ use crate::crypto::*; use crate::network_manager::*; use crate::rpc_processor::*; use bucket::*; +use hashlink::LruCache; + pub use bucket_entry::*; pub use debug::*; -use hashlink::LruCache; pub use node_ref::*; pub use node_ref_filter::*; pub use privacy::*; @@ -28,6 +30,7 @@ pub use routing_domain_editor::*; pub use routing_domains::*; pub use routing_table_inner::*; pub use stats_accounting::*; +pub use types::*; ////////////////////////////////////////////////////////////////////////// diff --git a/veilid-core/src/routing_table/types/dial_info_detail.rs b/veilid-core/src/routing_table/types/dial_info_detail.rs new file mode 100644 index 00000000..22adf233 --- /dev/null +++ b/veilid-core/src/routing_table/types/dial_info_detail.rs @@ -0,0 +1,43 @@ +use super::*; + +// Keep member order appropriate for sorting < preference +#[derive( + Debug, + Clone, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DialInfoDetail { + pub class: DialInfoClass, + pub dial_info: DialInfo, +} + +impl MatchesDialInfoFilter for DialInfoDetail { + fn matches_filter(&self, filter: &DialInfoFilter) -> bool { + self.dial_info.matches_filter(filter) + } +} + +impl DialInfoDetail { + pub fn ordered_sequencing_sort(a: &DialInfoDetail, b: &DialInfoDetail) -> core::cmp::Ordering { + if a.class < b.class { + return core::cmp::Ordering::Less; + } + if a.class > b.class { + return core::cmp::Ordering::Greater; + } + DialInfo::ordered_sequencing_sort(&a.dial_info, &b.dial_info) + } + pub const NO_SORT: std::option::Option< + for<'r, 's> fn(&'r DialInfoDetail, &'s DialInfoDetail) -> std::cmp::Ordering, + > = None:: core::cmp::Ordering>; +} diff --git a/veilid-core/src/routing_table/types/direction.rs b/veilid-core/src/routing_table/types/direction.rs new file mode 100644 index 00000000..98f50182 --- /dev/null +++ b/veilid-core/src/routing_table/types/direction.rs @@ -0,0 +1,22 @@ +use super::*; + +#[allow(clippy::derive_hash_xor_eq)] +#[derive( + Debug, + PartialOrd, + Ord, + Hash, + EnumSetType, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[enumset(repr = "u8")] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum Direction { + Inbound, + Outbound, +} +pub type DirectionSet = EnumSet; diff --git a/veilid-core/src/routing_table/types/mod.rs b/veilid-core/src/routing_table/types/mod.rs new file mode 100644 index 00000000..f86299bd --- /dev/null +++ b/veilid-core/src/routing_table/types/mod.rs @@ -0,0 +1,25 @@ +mod dial_info_detail; +mod direction; +mod node_info; +mod node_status; +mod peer_info; +mod routing_domain; +mod signed_direct_node_info; +mod signed_node_info; +mod signed_relayed_node_info; + +use super::*; + +pub use dial_info_detail::*; +pub use direction::*; +pub use node_info::*; +pub use node_status::*; +pub use peer_info::*; +pub use routing_domain::*; +pub use signed_direct_node_info::*; +pub use signed_node_info::*; +pub use signed_relayed_node_info::*; + +use enumset::*; +use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; +use serde::*; diff --git a/veilid-core/src/routing_table/types/node_info.rs b/veilid-core/src/routing_table/types/node_info.rs new file mode 100644 index 00000000..ecbd709b --- /dev/null +++ b/veilid-core/src/routing_table/types/node_info.rs @@ -0,0 +1,125 @@ +use super::*; + +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct NodeInfo { + pub network_class: NetworkClass, + #[with(RkyvEnumSet)] + pub outbound_protocols: ProtocolTypeSet, + #[with(RkyvEnumSet)] + pub address_types: AddressTypeSet, + pub envelope_support: Vec, + pub crypto_support: Vec, + pub dial_info_detail_list: Vec, +} + +impl NodeInfo { + pub fn first_filtered_dial_info_detail( + &self, + sort: Option, + filter: F, + ) -> Option + where + S: Fn(&DialInfoDetail, &DialInfoDetail) -> std::cmp::Ordering, + F: Fn(&DialInfoDetail) -> bool, + { + if let Some(sort) = sort { + let mut dids = self.dial_info_detail_list.clone(); + dids.sort_by(sort); + for did in dids { + if filter(&did) { + return Some(did); + } + } + } else { + for did in &self.dial_info_detail_list { + if filter(did) { + return Some(did.clone()); + } + } + }; + None + } + + pub fn all_filtered_dial_info_details( + &self, + sort: Option, + filter: F, + ) -> Vec + where + S: Fn(&DialInfoDetail, &DialInfoDetail) -> std::cmp::Ordering, + F: Fn(&DialInfoDetail) -> bool, + { + let mut dial_info_detail_list = Vec::new(); + + if let Some(sort) = sort { + let mut dids = self.dial_info_detail_list.clone(); + dids.sort_by(sort); + for did in dids { + if filter(&did) { + dial_info_detail_list.push(did); + } + } + } else { + for did in &self.dial_info_detail_list { + if filter(did) { + dial_info_detail_list.push(did.clone()); + } + } + }; + dial_info_detail_list + } + + /// Does this node has some dial info + pub fn has_dial_info(&self) -> bool { + !self.dial_info_detail_list.is_empty() + } + + /// Is some relay required either for signal or inbound relay or outbound relay? + pub fn requires_relay(&self) -> bool { + match self.network_class { + NetworkClass::InboundCapable => { + for did in &self.dial_info_detail_list { + if did.class.requires_relay() { + return true; + } + } + } + NetworkClass::OutboundOnly => { + return true; + } + NetworkClass::WebApp => { + return true; + } + NetworkClass::Invalid => {} + } + false + } + + /// Can this node assist with signalling? Yes but only if it doesn't require signalling, itself. + pub fn can_signal(&self) -> bool { + // Must be inbound capable + if !matches!(self.network_class, NetworkClass::InboundCapable) { + return false; + } + // Do any of our dial info require signalling? if so, we can't offer signalling + for did in &self.dial_info_detail_list { + if did.class.requires_signal() { + return false; + } + } + true + } + + /// Can this node relay be an inbound relay? + pub fn can_inbound_relay(&self) -> bool { + // For now this is the same + self.can_signal() + } + + /// Is this node capable of validating dial info + pub fn can_validate_dial_info(&self) -> bool { + // For now this is the same + self.can_signal() + } +} diff --git a/veilid-core/src/routing_table/types/node_status.rs b/veilid-core/src/routing_table/types/node_status.rs new file mode 100644 index 00000000..11c388d0 --- /dev/null +++ b/veilid-core/src/routing_table/types/node_status.rs @@ -0,0 +1,66 @@ +use super::*; + +/// RoutingDomain-specific status for each node +/// is returned by the StatusA call + +/// PublicInternet RoutingDomain Status +#[derive( + Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct PublicInternetNodeStatus { + pub will_route: bool, + pub will_tunnel: bool, + pub will_signal: bool, + pub will_relay: bool, + pub will_validate_dial_info: bool, +} + +#[derive( + Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct LocalNetworkNodeStatus { + pub will_relay: bool, + pub will_validate_dial_info: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum NodeStatus { + PublicInternet(PublicInternetNodeStatus), + LocalNetwork(LocalNetworkNodeStatus), +} + +impl NodeStatus { + pub fn will_route(&self) -> bool { + match self { + NodeStatus::PublicInternet(pi) => pi.will_route, + NodeStatus::LocalNetwork(_) => false, + } + } + pub fn will_tunnel(&self) -> bool { + match self { + NodeStatus::PublicInternet(pi) => pi.will_tunnel, + NodeStatus::LocalNetwork(_) => false, + } + } + pub fn will_signal(&self) -> bool { + match self { + NodeStatus::PublicInternet(pi) => pi.will_signal, + NodeStatus::LocalNetwork(_) => false, + } + } + pub fn will_relay(&self) -> bool { + match self { + NodeStatus::PublicInternet(pi) => pi.will_relay, + NodeStatus::LocalNetwork(ln) => ln.will_relay, + } + } + pub fn will_validate_dial_info(&self) -> bool { + match self { + NodeStatus::PublicInternet(pi) => pi.will_validate_dial_info, + NodeStatus::LocalNetwork(ln) => ln.will_validate_dial_info, + } + } +} diff --git a/veilid-core/src/routing_table/types/peer_info.rs b/veilid-core/src/routing_table/types/peer_info.rs new file mode 100644 index 00000000..0e7e3558 --- /dev/null +++ b/veilid-core/src/routing_table/types/peer_info.rs @@ -0,0 +1,18 @@ +use super::*; + +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct PeerInfo { + pub node_ids: TypedKeySet, + pub signed_node_info: SignedNodeInfo, +} + +impl PeerInfo { + pub fn new(node_ids: TypedKeySet, signed_node_info: SignedNodeInfo) -> Self { + assert!(node_ids.len() > 0 && node_ids.len() <= MAX_CRYPTO_KINDS); + Self { + node_ids, + signed_node_info, + } + } +} diff --git a/veilid-core/src/routing_table/types/routing_domain.rs b/veilid-core/src/routing_table/types/routing_domain.rs new file mode 100644 index 00000000..e1982a08 --- /dev/null +++ b/veilid-core/src/routing_table/types/routing_domain.rs @@ -0,0 +1,32 @@ +use super::*; + +// Routing domain here is listed in order of preference, keep in order +#[allow(clippy::derive_hash_xor_eq)] +#[derive( + Debug, + Ord, + PartialOrd, + Hash, + EnumSetType, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[enumset(repr = "u8")] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum RoutingDomain { + LocalNetwork = 0, + PublicInternet = 1, +} +impl RoutingDomain { + pub const fn count() -> usize { + 2 + } + pub const fn all() -> [RoutingDomain; RoutingDomain::count()] { + // Routing domain here is listed in order of preference, keep in order + [RoutingDomain::LocalNetwork, RoutingDomain::PublicInternet] + } +} +pub type RoutingDomainSet = EnumSet; diff --git a/veilid-core/src/routing_table/types/signed_direct_node_info.rs b/veilid-core/src/routing_table/types/signed_direct_node_info.rs new file mode 100644 index 00000000..c04652c7 --- /dev/null +++ b/veilid-core/src/routing_table/types/signed_direct_node_info.rs @@ -0,0 +1,86 @@ +use super::*; + +/// Signed NodeInfo that can be passed around amongst peers and verifiable +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct SignedDirectNodeInfo { + pub node_info: NodeInfo, + pub timestamp: Timestamp, + pub signatures: Vec, +} +impl SignedDirectNodeInfo { + /// Returns a new SignedDirectNodeInfo that has its signatures validated. + /// On success, this will modify the node_ids set to only include node_ids whose signatures validate. + /// All signatures are stored however, as this can be passed to other nodes that may be able to validate those signatures. + pub fn new( + crypto: Crypto, + node_ids: &mut TypedKeySet, + node_info: NodeInfo, + timestamp: Timestamp, + typed_signatures: Vec, + ) -> Result { + let node_info_bytes = Self::make_signature_bytes(&node_info, timestamp)?; + + // Verify the signatures that we can + let validated_node_ids = + crypto.verify_signatures(node_ids, &node_info_bytes, &typed_signatures)?; + *node_ids = validated_node_ids; + if node_ids.len() == 0 { + apibail_generic!("no valid node ids in direct node info"); + } + + Ok(Self { + node_info, + timestamp, + signatures: typed_signatures, + }) + } + + pub fn make_signatures( + crypto: Crypto, + typed_key_pairs: Vec, + node_info: NodeInfo, + ) -> Result { + let timestamp = get_aligned_timestamp(); + let node_info_bytes = Self::make_signature_bytes(&node_info, timestamp)?; + let typed_signatures = + crypto.generate_signatures(&node_info_bytes, &typed_key_pairs, |kp, s| { + TypedSignature::new(kp.kind, s) + })?; + Ok(Self { + node_info, + timestamp, + signatures: typed_signatures, + }) + } + + fn make_signature_bytes( + node_info: &NodeInfo, + timestamp: Timestamp, + ) -> Result, VeilidAPIError> { + let mut node_info_bytes = Vec::new(); + + // Add nodeinfo to signature + let mut ni_msg = ::capnp::message::Builder::new_default(); + let mut ni_builder = ni_msg.init_root::(); + encode_node_info(node_info, &mut ni_builder).map_err(VeilidAPIError::internal)?; + node_info_bytes.append(&mut builder_to_vec(ni_msg).map_err(VeilidAPIError::internal)?); + + // Add timestamp to signature + node_info_bytes.append(&mut timestamp.as_u64().to_le_bytes().to_vec()); + + Ok(node_info_bytes) + } + + pub fn with_no_signature(node_info: NodeInfo) -> Self { + Self { + node_info, + timestamp: get_aligned_timestamp(), + signatures: Vec::new(), + } + } + + pub fn has_any_signature(&self) -> bool { + !self.signatures.is_empty() + } +} diff --git a/veilid-core/src/routing_table/types/signed_node_info.rs b/veilid-core/src/routing_table/types/signed_node_info.rs new file mode 100644 index 00000000..2137bb27 --- /dev/null +++ b/veilid-core/src/routing_table/types/signed_node_info.rs @@ -0,0 +1,89 @@ +use super::*; + +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum SignedNodeInfo { + Direct(SignedDirectNodeInfo), + Relayed(SignedRelayedNodeInfo), +} + +impl SignedNodeInfo { + pub fn has_any_signature(&self) -> bool { + match self { + SignedNodeInfo::Direct(d) => d.has_any_signature(), + SignedNodeInfo::Relayed(r) => r.has_any_signature(), + } + } + + pub fn timestamp(&self) -> Timestamp { + match self { + SignedNodeInfo::Direct(d) => d.timestamp, + SignedNodeInfo::Relayed(r) => r.timestamp, + } + } + pub fn node_info(&self) -> &NodeInfo { + match self { + SignedNodeInfo::Direct(d) => &d.node_info, + SignedNodeInfo::Relayed(r) => &r.node_info, + } + } + pub fn relay_ids(&self) -> TypedKeySet { + match self { + SignedNodeInfo::Direct(_) => TypedKeySet::new(), + SignedNodeInfo::Relayed(r) => r.relay_ids.clone(), + } + } + pub fn relay_info(&self) -> Option<&NodeInfo> { + match self { + SignedNodeInfo::Direct(_) => None, + SignedNodeInfo::Relayed(r) => Some(&r.relay_info.node_info), + } + } + pub fn relay_peer_info(&self) -> Option { + match self { + SignedNodeInfo::Direct(_) => None, + SignedNodeInfo::Relayed(r) => Some(PeerInfo::new( + r.relay_ids.clone(), + SignedNodeInfo::Direct(r.relay_info.clone()), + )), + } + } + pub fn has_any_dial_info(&self) -> bool { + self.node_info().has_dial_info() + || self + .relay_info() + .map(|relay_ni| relay_ni.has_dial_info()) + .unwrap_or_default() + } + + pub fn has_sequencing_matched_dial_info(&self, sequencing: Sequencing) -> bool { + // Check our dial info + for did in &self.node_info().dial_info_detail_list { + match sequencing { + Sequencing::NoPreference | Sequencing::PreferOrdered => return true, + Sequencing::EnsureOrdered => { + if did.dial_info.protocol_type().is_connection_oriented() { + return true; + } + } + } + } + // Check our relay if we have one + return self + .relay_info() + .map(|relay_ni| { + for did in &relay_ni.dial_info_detail_list { + match sequencing { + Sequencing::NoPreference | Sequencing::PreferOrdered => return true, + Sequencing::EnsureOrdered => { + if did.dial_info.protocol_type().is_connection_oriented() { + return true; + } + } + } + } + false + }) + .unwrap_or_default(); + } +} diff --git a/veilid-core/src/routing_table/types/signed_relayed_node_info.rs b/veilid-core/src/routing_table/types/signed_relayed_node_info.rs new file mode 100644 index 00000000..4e716eba --- /dev/null +++ b/veilid-core/src/routing_table/types/signed_relayed_node_info.rs @@ -0,0 +1,106 @@ +use super::*; + +/// Signed NodeInfo with a relay that can be passed around amongst peers and verifiable +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct SignedRelayedNodeInfo { + pub node_info: NodeInfo, + pub relay_ids: TypedKeySet, + pub relay_info: SignedDirectNodeInfo, + pub timestamp: Timestamp, + pub signatures: Vec, +} + +impl SignedRelayedNodeInfo { + /// Returns a new SignedRelayedNodeInfo that has its signatures validated. + /// On success, this will modify the node_ids set to only include node_ids whose signatures validate. + /// All signatures are stored however, as this can be passed to other nodes that may be able to validate those signatures. + pub fn new( + crypto: Crypto, + node_ids: &mut TypedKeySet, + node_info: NodeInfo, + relay_ids: TypedKeySet, + relay_info: SignedDirectNodeInfo, + timestamp: Timestamp, + typed_signatures: Vec, + ) -> Result { + let node_info_bytes = + Self::make_signature_bytes(&node_info, &relay_ids, &relay_info, timestamp)?; + let validated_node_ids = + crypto.verify_signatures(node_ids, &node_info_bytes, &typed_signatures)?; + *node_ids = validated_node_ids; + if node_ids.len() == 0 { + apibail_generic!("no valid node ids in relayed node info"); + } + + Ok(Self { + node_info, + relay_ids, + relay_info, + timestamp, + signatures: typed_signatures, + }) + } + + pub fn make_signatures( + crypto: Crypto, + typed_key_pairs: Vec, + node_info: NodeInfo, + relay_ids: TypedKeySet, + relay_info: SignedDirectNodeInfo, + ) -> Result { + let timestamp = get_aligned_timestamp(); + let node_info_bytes = + Self::make_signature_bytes(&node_info, &relay_ids, &relay_info, timestamp)?; + let typed_signatures = + crypto.generate_signatures(&node_info_bytes, &typed_key_pairs, |kp, s| { + TypedSignature::new(kp.kind, s) + })?; + Ok(Self { + node_info, + relay_ids, + relay_info, + timestamp, + signatures: typed_signatures, + }) + } + + fn make_signature_bytes( + node_info: &NodeInfo, + relay_ids: &[TypedKey], + relay_info: &SignedDirectNodeInfo, + timestamp: Timestamp, + ) -> Result, VeilidAPIError> { + let mut sig_bytes = Vec::new(); + + // Add nodeinfo to signature + let mut ni_msg = ::capnp::message::Builder::new_default(); + let mut ni_builder = ni_msg.init_root::(); + encode_node_info(node_info, &mut ni_builder).map_err(VeilidAPIError::internal)?; + sig_bytes.append(&mut builder_to_vec(ni_msg).map_err(VeilidAPIError::internal)?); + + // Add relay ids to signature + for relay_id in relay_ids { + let mut rid_msg = ::capnp::message::Builder::new_default(); + let mut rid_builder = rid_msg.init_root::(); + encode_typed_key(relay_id, &mut rid_builder); + sig_bytes.append(&mut builder_to_vec(rid_msg).map_err(VeilidAPIError::internal)?); + } + + // Add relay info to signature + let mut ri_msg = ::capnp::message::Builder::new_default(); + let mut ri_builder = ri_msg.init_root::(); + encode_signed_direct_node_info(relay_info, &mut ri_builder) + .map_err(VeilidAPIError::internal)?; + sig_bytes.append(&mut builder_to_vec(ri_msg).map_err(VeilidAPIError::internal)?); + + // Add timestamp to signature + sig_bytes.append(&mut timestamp.as_u64().to_le_bytes().to_vec()); + + Ok(sig_bytes) + } + + pub fn has_any_signature(&self) -> bool { + !self.signatures.is_empty() + } +} diff --git a/veilid-core/src/rpc_processor/coders/mod.rs b/veilid-core/src/rpc_processor/coders/mod.rs index 8c8ce160..8ede58ce 100644 --- a/veilid-core/src/rpc_processor/coders/mod.rs +++ b/veilid-core/src/rpc_processor/coders/mod.rs @@ -19,11 +19,13 @@ mod signature512; mod signed_direct_node_info; mod signed_node_info; mod signed_relayed_node_info; +mod signed_value_data; +mod signed_value_descriptor; mod socket_address; mod tunnel; mod typed_key; mod typed_signature; -mod value_data; +mod value_detail; pub use address::*; pub use address_type_set::*; @@ -46,10 +48,12 @@ pub use signature512::*; pub use signed_direct_node_info::*; pub use signed_node_info::*; pub use signed_relayed_node_info::*; +pub use signed_value_data::*; +pub use signed_value_descriptor::*; pub use socket_address::*; pub use tunnel::*; pub use typed_key::*; pub use typed_signature::*; -pub use value_data::*; +pub use value_detail::*; use super::*; diff --git a/veilid-core/src/rpc_processor/coders/signed_value_data.rs b/veilid-core/src/rpc_processor/coders/signed_value_data.rs new file mode 100644 index 00000000..6f65b304 --- /dev/null +++ b/veilid-core/src/rpc_processor/coders/signed_value_data.rs @@ -0,0 +1,31 @@ +use super::*; +use crate::storage_manager::*; + +pub fn encode_signed_value_data( + signed_value_data: &SignedValueData, + builder: &mut veilid_capnp::signed_value_data::Builder, +) -> Result<(), RPCError> { + builder.set_seq(signed_value_data.value_data().seq()); + builder.set_data(signed_value_data.value_data().data()); + let mut wb = builder.reborrow().init_writer(); + encode_key256(signed_value_data.value_data().writer(), &mut wb); + let mut sb = builder.reborrow().init_signature(); + encode_signature512(signed_value_data.signature(), &mut sb); + Ok(()) +} + +pub fn decode_signed_value_data( + reader: &veilid_capnp::signed_value_data::Reader, +) -> Result { + let seq = reader.get_seq(); + let data = reader.get_data().map_err(RPCError::protocol)?.to_vec(); + let wr = reader.get_writer().map_err(RPCError::protocol)?; + let writer = decode_key256(&wr); + let sr = reader.get_signature().map_err(RPCError::protocol)?; + let signature = decode_signature512(&sr); + + Ok(SignedValueData { + value_data: ValueData { seq, data, writer }, + signature, + }) +} diff --git a/veilid-core/src/rpc_processor/coders/signed_value_descriptor.rs b/veilid-core/src/rpc_processor/coders/signed_value_descriptor.rs new file mode 100644 index 00000000..22be6b5a --- /dev/null +++ b/veilid-core/src/rpc_processor/coders/signed_value_descriptor.rs @@ -0,0 +1,26 @@ +use super::*; +use crate::storage_manager::SignedValueDescriptor; + +pub fn encode_signed_value_descriptor( + signed_value_descriptor: &SignedValueDescriptor, + builder: &mut veilid_capnp::signed_value_descriptor::Builder, +) -> Result<(), RPCError> { + let mut ob = builder.reborrow().init_owner(); + encode_key256(signed_value_descriptor.owner(), &mut ob); + builder.set_data(signed_value_descriptor.data()); + let mut sb = builder.reborrow().init_signature(); + encode_signature512(signed_value_descriptor.signature(), &mut sb); + Ok(()) +} + +pub fn decode_signed_value_descriptor( + reader: &veilid_capnp::signed_value_descriptor::Reader, + vcrypto: CryptoSystemVersion, +) -> Result { + let or = reader.get_owner().map_err(RPCError::protocol)?; + let owner = decode_key256(&or); + let data = reader.get_data().map_err(RPCError::protocol)?.to_vec(); + let sr = reader.get_signature().map_err(RPCError::protocol)?; + let signature = decode_signature512(&sr); + Ok(SignedValueDescriptor::new(owner, data, signature, vcrypto).map_err(RPCError::protocol)?) +} diff --git a/veilid-core/src/rpc_processor/coders/value_data.rs b/veilid-core/src/rpc_processor/coders/value_data.rs deleted file mode 100644 index 70cbf0a4..00000000 --- a/veilid-core/src/rpc_processor/coders/value_data.rs +++ /dev/null @@ -1,18 +0,0 @@ -use super::*; - -pub fn encode_value_data( - value_data: &ValueData, - builder: &mut veilid_capnp::value_data::Builder, -) -> Result<(), RPCError> { - builder.set_data(&value_data.data); - builder.set_schema(u32::from_be_bytes(value_data.schema.0)); - builder.set_seq(value_data.seq); - Ok(()) -} - -pub fn decode_value_data(reader: &veilid_capnp::value_data::Reader) -> Result { - let data = reader.get_data().map_err(RPCError::protocol)?.to_vec(); - let seq = reader.get_seq(); - let schema = FourCC::from(reader.get_schema().to_be_bytes()); - Ok(ValueData { data, schema, seq }) -} diff --git a/veilid-core/src/rpc_processor/coders/value_detail.rs b/veilid-core/src/rpc_processor/coders/value_detail.rs new file mode 100644 index 00000000..c853861c --- /dev/null +++ b/veilid-core/src/rpc_processor/coders/value_detail.rs @@ -0,0 +1,20 @@ +use super::*; +use crate::storage_manager::ValueDetail; + +pub fn encode_value_detail( + value_detail: &ValueDetail, + builder: &mut veilid_capnp::value_detail::Builder, +) -> Result<(), RPCError> { + let mut svdb = builder.reborrow().init_signed_value_data(); + + Ok(()) +} + +pub fn decode_value_detail( + reader: &veilid_capnp::value_detail::Reader, +) -> Result { + Ok(ValueDetail { + signed_value_data, + descriptor, + }) +} diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index c31fc2cc..a1d11478 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -387,7 +387,7 @@ impl RPCProcessor { _node_id: PublicKey, _count: u32, _fanout: u32, - _timeout: Option, + _timeout: TimestampDuration, ) -> Result, RPCError> { //let routing_table = self.routing_table(); @@ -402,7 +402,7 @@ impl RPCProcessor { _node_id: PublicKey, _count: u32, _fanout: u32, - _timeout: Option, + _timeout: TimestampDuration, ) -> Result, RPCError> { // xxx return closest nodes after the timeout Err(RPCError::unimplemented("search_dht_multi_key")).map_err(logthru_rpc!(error)) @@ -433,7 +433,7 @@ impl RPCProcessor { ( c.network.dht.resolve_node_count, c.network.dht.resolve_node_fanout, - c.network.dht.resolve_node_timeout_ms.map(ms_to_us), + TimestampDuration::from(ms_to_us(c.network.dht.resolve_node_timeout_ms)), ) }; diff --git a/veilid-core/src/storage_manager/mod.rs b/veilid-core/src/storage_manager/mod.rs index e4ba58ed..695d8503 100644 --- a/veilid-core/src/storage_manager/mod.rs +++ b/veilid-core/src/storage_manager/mod.rs @@ -1,12 +1,21 @@ mod keys; +mod record; +mod record_data; mod record_store; mod record_store_limits; -mod value_record; +mod signed_value_data; +mod signed_value_descriptor; +mod value_detail; use keys::*; +use record::*; +use record_data::*; use record_store::*; use record_store_limits::*; -use value_record::*; + +pub use signed_value_data::*; +pub use signed_value_descriptor::*; +pub use value_detail::*; use super::*; use crate::rpc_processor::*; @@ -150,12 +159,28 @@ impl StorageManager { debug!("finished storage manager shutdown"); } - async fn new_local_record(&self, key: TypedKey, record: Record) -> Result<(), VeilidAPIError> { + /// # DHT Key = Hash(ownerKeyKind) of: [ ownerKeyValue, schema ] + fn get_key(&self, vcrypto: CryptoSystemVersion, record: &Record) -> TypedKey { + let compiled = record.descriptor().schema_data(); + let mut hash_data = Vec::::with_capacity(PUBLIC_KEY_LENGTH + 4 + compiled.len()); + hash_data.extend_from_slice(&vcrypto.kind().0); + hash_data.extend_from_slice(&record.owner().bytes); + hash_data.extend_from_slice(compiled); + let hash = vcrypto.generate_hash(&hash_data); + TypedKey::new(vcrypto.kind(), hash) + } + + async fn new_local_record( + &self, + vcrypto: CryptoSystemVersion, + record: Record, + ) -> Result<(), VeilidAPIError> { // add value record to record store let mut inner = self.inner.lock(); let Some(local_record_store) = inner.local_record_store.as_mut() else { apibail_generic!("not initialized"); }; + let key = self.get_key(vcrypto.clone(), &record); local_record_store.new_record(key, record).await } @@ -170,15 +195,25 @@ impl StorageManager { apibail_generic!("unsupported cryptosystem"); }; + // Compile the dht schema + let schema_data = schema.compile(); + // New values require a new owner key - let keypair = vcrypto.generate_keypair(); - let key = TypedKey::new(kind, keypair.key); - let secret = keypair.secret; + let owner = vcrypto.generate_keypair(); + + // Make a signed value descriptor for this dht value + let signed_value_descriptor = SignedValueDescriptor::new(owner.key, ) // Add new local value record let cur_ts = get_aligned_timestamp(); - let record = Record::new(cur_ts, Some(secret), schema, safety_selection); - self.new_local_record(key, record) + let record = Record::new( + cur_ts, + owner.key, + Some(owner.secret), + schema, + safety_selection, + ); + self.new_local_record(vcrypto.clone(), record) .await .map_err(VeilidAPIError::internal)?; diff --git a/veilid-core/src/storage_manager/value_record.rs b/veilid-core/src/storage_manager/record.rs similarity index 50% rename from veilid-core/src/storage_manager/value_record.rs rename to veilid-core/src/storage_manager/record.rs index 0ffd44d3..b80a8d47 100644 --- a/veilid-core/src/storage_manager/value_record.rs +++ b/veilid-core/src/storage_manager/record.rs @@ -3,46 +3,15 @@ use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as use serde::*; #[derive( - Clone, - Debug, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct RecordData { - pub value_data: ValueData, - pub signature: Signature, -} - -impl RecordData { - pub fn total_size(&self) -> usize { - mem::size_of::() + self.value_data.data().len() - } -} - -#[derive( - Clone, - Debug, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, + Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct Record { last_touched_ts: Timestamp, - secret: Option, - schema: DHTSchema, + descriptor: SignedValueDescriptor, + subkey_count: usize, + + owner_secret: Option, safety_selection: SafetySelection, record_data_size: usize, } @@ -50,21 +19,31 @@ pub struct Record { impl Record { pub fn new( cur_ts: Timestamp, - secret: Option, - schema: DHTSchema, + descriptor: SignedValueDescriptor, + owner_secret: Option, safety_selection: SafetySelection, - ) -> Self { - Self { + ) -> Result { + let schema = descriptor.schema()?; + let subkey_count = schema.subkey_count(); + Ok(Self { last_touched_ts: cur_ts, - secret, - schema, + descriptor, + subkey_count, + owner_secret, safety_selection, record_data_size: 0, - } + }) + } + + pub fn descriptor(&self) -> &SignedValueDescriptor { + &self.descriptor + } + pub fn owner(&self) -> &PublicKey { + self.descriptor.owner() } pub fn subkey_count(&self) -> usize { - self.schema.subkey_count() + self.subkey_count } pub fn touch(&mut self, cur_ts: Timestamp) { @@ -83,7 +62,12 @@ impl Record { self.record_data_size } + pub fn schema(&self) -> DHTSchema { + // unwrap is safe here because descriptor is immutable and set in new() + self.descriptor.schema().unwrap() + } + pub fn total_size(&self) -> usize { - mem::size_of::() + self.schema.data_size() + self.record_data_size + mem::size_of::() + self.descriptor.total_size() + self.record_data_size } } diff --git a/veilid-core/src/storage_manager/record_data.rs b/veilid-core/src/storage_manager/record_data.rs new file mode 100644 index 00000000..e439ed67 --- /dev/null +++ b/veilid-core/src/storage_manager/record_data.rs @@ -0,0 +1,33 @@ +use super::*; +use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; +use serde::*; + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct RecordData { + signed_value_data: SignedValueData, +} + +impl RecordData { + pub fn new(signed_value_data: SignedValueData) -> Self { + Self { signed_value_data } + } + pub fn signed_value_data(&self) -> &SignedValueData { + &self.signed_value_data + } + pub fn total_size(&self) -> usize { + mem::size_of::() + self.signed_value_data.value_data().data().len() + } +} diff --git a/veilid-core/src/storage_manager/record_store.rs b/veilid-core/src/storage_manager/record_store.rs index b5cb4ae4..6bbb125d 100644 --- a/veilid-core/src/storage_manager/record_store.rs +++ b/veilid-core/src/storage_manager/record_store.rs @@ -286,7 +286,7 @@ impl RecordStore { &mut self, key: TypedKey, subkey: ValueSubkey, - ) -> Result, VeilidAPIError> { + ) -> Result, VeilidAPIError> { // record from index let rtk = RecordTableKey { key }; let subkey_count = { @@ -314,7 +314,7 @@ impl RecordStore { // If subkey exists in subkey cache, use that let stk = SubkeyTableKey { key, subkey }; if let Some(record_data) = self.subkey_cache.get_mut(&stk) { - let out = record_data.clone(); + let out = record_data.signed_value_data().clone(); return Ok(Some(out)); } @@ -323,7 +323,7 @@ impl RecordStore { .load_rkyv::(0, &stk.bytes()) .map_err(VeilidAPIError::internal)? { - let out = record_data.clone(); + let out = record_data.signed_value_data().clone(); // Add to cache, do nothing with lru out self.add_to_subkey_cache(stk, record_data); @@ -338,10 +338,10 @@ impl RecordStore { &mut self, key: TypedKey, subkey: ValueSubkey, - record_data: RecordData, + signed_value_data: SignedValueData, ) -> Result<(), VeilidAPIError> { // Check size limit for data - if record_data.value_data.data().len() > self.limits.max_subkey_size { + if signed_value_data.value_data().data().len() > self.limits.max_subkey_size { return Err(VeilidAPIError::generic("record subkey too large")); } @@ -388,6 +388,9 @@ impl RecordStore { } } + // Make new record data + let record_data = RecordData::new(signed_value_data); + // Check new total record size let new_record_data_size = record_data.total_size(); let new_total_size = total_size + new_record_data_size - prior_record_data_size; diff --git a/veilid-core/src/storage_manager/signed_value_data.rs b/veilid-core/src/storage_manager/signed_value_data.rs new file mode 100644 index 00000000..5ca2acba --- /dev/null +++ b/veilid-core/src/storage_manager/signed_value_data.rs @@ -0,0 +1,92 @@ +use super::*; +use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; +use serde::*; + +///////////////////////////////////////////////////////////////////////////////////////////////////// +/// + +#[derive( + Clone, + Debug, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct SignedValueData { + value_data: ValueData, + signature: Signature, +} +impl SignedValueData { + pub fn new( + value_data: ValueData, + owner: PublicKey, + subkey: ValueSubkey, + signature: Signature, + vcrypto: CryptoSystemVersion, + ) -> Result { + let node_info_bytes = Self::make_signature_bytes(&value_data, &owner, subkey)?; + + // validate signature + vcrypto.verify(&value_data.writer(), &node_info_bytes, &signature)?; + Ok(Self { + value_data, + signature, + }) + } + + pub fn make_signature( + value_data: ValueData, + owner: PublicKey, + subkey: ValueSubkey, + vcrypto: CryptoSystemVersion, + writer_secret: SecretKey, + ) -> Result { + let node_info_bytes = Self::make_signature_bytes(&value_data, &owner, subkey)?; + + // create signature + let signature = vcrypto.sign(&value_data.writer(), &writer_secret, &node_info_bytes)?; + Ok(Self { + value_data, + signature, + }) + } + + pub fn value_data(&self) -> &ValueData { + &self.value_data + } + + pub fn signature(&self) -> &Signature { + &self.signature + } + + pub fn total_size(&self) -> usize { + (mem::size_of::() - mem::size_of::()) + self.value_data.total_size() + } + + fn make_signature_bytes( + value_data: &ValueData, + owner: &PublicKey, + subkey: ValueSubkey, + ) -> Result, VeilidAPIError> { + let mut node_info_bytes = + Vec::with_capacity(PUBLIC_KEY_LENGTH + 4 + 4 + value_data.data().len()); + + // Add owner to signature + node_info_bytes.extend_from_slice(&owner.bytes); + // Add subkey to signature + node_info_bytes.extend_from_slice(&subkey.to_le_bytes()); + // Add sequence number to signature + node_info_bytes.extend_from_slice(&value_data.seq().to_le_bytes()); + // Add data to signature + node_info_bytes.extend_from_slice(value_data.data()); + + Ok(node_info_bytes) + } +} diff --git a/veilid-core/src/storage_manager/signed_value_descriptor.rs b/veilid-core/src/storage_manager/signed_value_descriptor.rs new file mode 100644 index 00000000..84442bc4 --- /dev/null +++ b/veilid-core/src/storage_manager/signed_value_descriptor.rs @@ -0,0 +1,78 @@ +use super::*; +use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; +use serde::*; + +///////////////////////////////////////////////////////////////////////////////////////////////////// +/// + +#[derive( + Clone, + Debug, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct SignedValueDescriptor { + owner: PublicKey, + schema_data: Vec, + signature: Signature, +} +impl SignedValueDescriptor { + pub fn new( + owner: PublicKey, + schema_data: Vec, + signature: Signature, + vcrypto: CryptoSystemVersion, + ) -> Result { + // validate signature + vcrypto.verify(&owner, &schema_data, &signature)?; + + Ok(Self { + owner, + schema_data, + signature, + }) + } + + pub fn owner(&self) -> &PublicKey { + &self.owner + } + + pub fn schema_data(&self) -> &[u8] { + &self.schema_data + } + + pub fn schema(&self) -> Result { + DHTSchema::try_from(self.schema_data.as_slice()) + } + + pub fn signature(&self) -> &Signature { + &self.signature + } + + pub fn make_signature( + owner: PublicKey, + schema_data: Vec, + vcrypto: CryptoSystemVersion, + owner_secret: SecretKey, + ) -> Result { + // create signature + let signature = vcrypto.sign(&owner, &owner_secret, &schema_data)?; + Ok(Self { + owner, + schema_data, + signature, + }) + } + + pub fn total_size(&self) -> usize { + mem::size_of::() + self.schema_data.len() + } +} diff --git a/veilid-core/src/storage_manager/value_detail.rs b/veilid-core/src/storage_manager/value_detail.rs new file mode 100644 index 00000000..85135f10 --- /dev/null +++ b/veilid-core/src/storage_manager/value_detail.rs @@ -0,0 +1,43 @@ +use super::*; +use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; +use serde::*; + +///////////////////////////////////////////////////////////////////////////////////////////////////// +/// + +#[derive( + Clone, + Debug, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct ValueDetail { + signed_value_data: SignedValueData, + descriptor: Option, +} + +impl ValueDetail { + pub fn new( + signed_value_data: SignedValueData, + descriptor: Option, + ) -> Self { + Self { + signed_value_data, + descriptor, + } + } + pub fn signed_value_data(&self) -> &SignedValueData { + &self.signed_value_data + } + pub fn descriptor(&self) -> Option<&SignedValueDescriptor> { + self.descriptor.as_ref() + } +} diff --git a/veilid-core/src/supplier_table.rs b/veilid-core/src/supplier_table.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/veilid-core/src/tests/common/test_veilid_core.rs b/veilid-core/src/tests/common/test_veilid_core.rs index eab1dde3..e1f20f3e 100644 --- a/veilid-core/src/tests/common/test_veilid_core.rs +++ b/veilid-core/src/tests/common/test_veilid_core.rs @@ -42,169 +42,7 @@ pub async fn test_attach_detach() { api.shutdown().await; } -pub async fn test_signed_node_info() { - info!("--- test_signed_node_info ---"); - - let (update_callback, config_callback) = setup_veilid_core(); - let api = api_startup(update_callback, config_callback) - .await - .expect("startup failed"); - - let crypto = api.crypto().unwrap(); - for ck in VALID_CRYPTO_KINDS { - let vcrypto = crypto.get(ck).unwrap(); - - // Test direct - let node_info = NodeInfo { - network_class: NetworkClass::InboundCapable, - outbound_protocols: ProtocolTypeSet::all(), - address_types: AddressTypeSet::all(), - envelope_support: VALID_ENVELOPE_VERSIONS.to_vec(), - crypto_support: VALID_CRYPTO_KINDS.to_vec(), - dial_info_detail_list: vec![DialInfoDetail { - class: DialInfoClass::Mapped, - dial_info: DialInfo::udp(SocketAddress::default()), - }], - }; - - // Test correct validation - let keypair = vcrypto.generate_keypair(); - let sni = SignedDirectNodeInfo::make_signatures( - crypto.clone(), - vec![TypedKeyPair::new(ck, keypair)], - node_info.clone(), - ) - .unwrap(); - let mut tks: TypedKeySet = TypedKey::new(ck, keypair.key).into(); - let oldtkslen = tks.len(); - let _ = SignedDirectNodeInfo::new( - crypto.clone(), - &mut tks, - node_info.clone(), - sni.timestamp, - sni.signatures.clone(), - ) - .unwrap(); - assert_eq!(tks.len(), oldtkslen); - assert_eq!(tks.len(), sni.signatures.len()); - - // Test incorrect validation - let keypair1 = vcrypto.generate_keypair(); - let mut tks1: TypedKeySet = TypedKey::new(ck, keypair1.key).into(); - let oldtks1len = tks1.len(); - let _ = SignedDirectNodeInfo::new( - crypto.clone(), - &mut tks1, - node_info.clone(), - sni.timestamp, - sni.signatures.clone(), - ) - .unwrap_err(); - assert_eq!(tks1.len(), oldtks1len); - assert_eq!(tks1.len(), sni.signatures.len()); - - // Test unsupported cryptosystem validation - let fake_crypto_kind: CryptoKind = FourCC::from([0, 1, 2, 3]); - let mut tksfake: TypedKeySet = TypedKey::new(fake_crypto_kind, PublicKey::default()).into(); - let mut sigsfake = sni.signatures.clone(); - sigsfake.push(TypedSignature::new(fake_crypto_kind, Signature::default())); - tksfake.add(TypedKey::new(ck, keypair.key)); - let sdnifake = SignedDirectNodeInfo::new( - crypto.clone(), - &mut tksfake, - node_info.clone(), - sni.timestamp, - sigsfake.clone(), - ) - .unwrap(); - assert_eq!(tksfake.len(), 1); - assert_eq!(sdnifake.signatures.len(), sigsfake.len()); - - // Test relayed - let node_info2 = NodeInfo { - network_class: NetworkClass::OutboundOnly, - outbound_protocols: ProtocolTypeSet::all(), - address_types: AddressTypeSet::all(), - envelope_support: VALID_ENVELOPE_VERSIONS.to_vec(), - crypto_support: VALID_CRYPTO_KINDS.to_vec(), - dial_info_detail_list: vec![DialInfoDetail { - class: DialInfoClass::Blocked, - dial_info: DialInfo::udp(SocketAddress::default()), - }], - }; - - // Test correct validation - let keypair2 = vcrypto.generate_keypair(); - let mut tks2: TypedKeySet = TypedKey::new(ck, keypair2.key).into(); - let oldtks2len = tks2.len(); - - let sni2 = SignedRelayedNodeInfo::make_signatures( - crypto.clone(), - vec![TypedKeyPair::new(ck, keypair2)], - node_info2.clone(), - tks.clone(), - sni.clone(), - ) - .unwrap(); - let _ = SignedRelayedNodeInfo::new( - crypto.clone(), - &mut tks2, - node_info2.clone(), - tks.clone(), - sni.clone(), - sni2.timestamp, - sni2.signatures.clone(), - ) - .unwrap(); - - assert_eq!(tks2.len(), oldtks2len); - assert_eq!(tks2.len(), sni2.signatures.len()); - - // Test incorrect validation - let keypair3 = vcrypto.generate_keypair(); - let mut tks3: TypedKeySet = TypedKey::new(ck, keypair3.key).into(); - let oldtks3len = tks3.len(); - - let _ = SignedRelayedNodeInfo::new( - crypto.clone(), - &mut tks3, - node_info2.clone(), - tks.clone(), - sni.clone(), - sni2.timestamp, - sni2.signatures.clone(), - ) - .unwrap_err(); - - assert_eq!(tks3.len(), oldtks3len); - assert_eq!(tks3.len(), sni2.signatures.len()); - - // Test unsupported cryptosystem validation - let fake_crypto_kind: CryptoKind = FourCC::from([0, 1, 2, 3]); - let mut tksfake3: TypedKeySet = - TypedKey::new(fake_crypto_kind, PublicKey::default()).into(); - let mut sigsfake3 = sni2.signatures.clone(); - sigsfake3.push(TypedSignature::new(fake_crypto_kind, Signature::default())); - tksfake3.add(TypedKey::new(ck, keypair2.key)); - let srnifake = SignedRelayedNodeInfo::new( - crypto.clone(), - &mut tksfake3, - node_info2.clone(), - tks.clone(), - sni.clone(), - sni2.timestamp, - sigsfake3.clone(), - ) - .unwrap(); - assert_eq!(tksfake3.len(), 1); - assert_eq!(srnifake.signatures.len(), sigsfake3.len()); - } - - api.shutdown().await; -} - pub async fn test_all() { test_startup_shutdown().await; test_attach_detach().await; - test_signed_node_info().await; } diff --git a/veilid-core/src/tests/native/mod.rs b/veilid-core/src/tests/native/mod.rs index ccb7d420..3dde78c7 100644 --- a/veilid-core/src/tests/native/mod.rs +++ b/veilid-core/src/tests/native/mod.rs @@ -19,6 +19,8 @@ pub async fn run_all_tests() { test_veilid_config::test_all().await; info!("TEST: test_connection_table"); test_connection_table::test_all().await; + info!("TEST: test_signed_node_info"); + test_signed_node_info::test_all().await; info!("TEST: test_table_store"); test_table_store::test_all().await; info!("TEST: test_protected_store"); @@ -116,6 +118,15 @@ cfg_if! { }) } + #[test] + #[serial] + fn run_test_signed_node_info() { + setup(); + block_on(async { + test_signed_node_info::test_all().await; + }) + } + #[test] #[serial] fn run_test_table_store() { diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index c744fa3b..2b7c3c49 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -3,6 +3,7 @@ use super::*; use data_encoding::BASE64URL_NOPAD; +use network_manager::*; use routing_table::*; #[derive(Default, Debug)] diff --git a/veilid-core/src/veilid_api/mod.rs b/veilid-core/src/veilid_api/mod.rs index 78c43f3e..766b89f4 100644 --- a/veilid-core/src/veilid_api/mod.rs +++ b/veilid-core/src/veilid_api/mod.rs @@ -1,6 +1,5 @@ #![allow(dead_code)] -mod aligned_u64; mod api; mod debug; mod error; @@ -8,7 +7,6 @@ mod routing_context; mod serialize_helpers; mod types; -pub use aligned_u64::*; pub use api::*; pub use debug::*; pub use error::*; @@ -31,7 +29,7 @@ use core::fmt; use core_context::{api_shutdown, VeilidCoreContext}; use enumset::*; use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; -use routing_table::{RouteSpecStore, RoutingTable}; +use routing_table::{Direction, RouteSpecStore, RoutingTable}; use rpc_processor::*; use serde::*; use storage_manager::StorageManager; diff --git a/veilid-core/src/veilid_api/types.rs b/veilid-core/src/veilid_api/types.rs deleted file mode 100644 index 2124d46e..00000000 --- a/veilid-core/src/veilid_api/types.rs +++ /dev/null @@ -1,2752 +0,0 @@ -use super::*; - -///////////////////////////////////////////////////////////////////////////////////////////////////// - -/// Microseconds since epoch -pub type Timestamp = AlignedU64; -pub fn get_aligned_timestamp() -> Timestamp { - get_timestamp().into() -} -/// Microseconds duration -pub type TimestampDuration = AlignedU64; -/// Request/Response matching id -pub type OperationId = AlignedU64; -/// Number of bytes -pub type ByteCount = AlignedU64; -/// Tunnel identifier -pub type TunnelId = AlignedU64; -/// Value subkey -pub type ValueSubkey = u32; -/// Value subkey range -pub type ValueSubkeyRange = (u32, u32); -/// Value sequence number -pub type ValueSeqNum = u32; - -/// FOURCC code -#[derive( - Copy, - Default, - Clone, - Hash, - PartialOrd, - Ord, - PartialEq, - Eq, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes, PartialOrd, Ord, PartialEq, Eq, Hash))] -pub struct FourCC(pub [u8; 4]); - -impl From<[u8; 4]> for FourCC { - fn from(b: [u8; 4]) -> Self { - Self(b) - } -} -impl TryFrom<&[u8]> for FourCC { - type Error = VeilidAPIError; - fn try_from(b: &[u8]) -> Result { - Ok(Self(b.try_into().map_err(VeilidAPIError::generic)?)) - } -} - -impl fmt::Display for FourCC { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{}", String::from_utf8_lossy(&self.0)) - } -} -impl fmt::Debug for FourCC { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{}", String::from_utf8_lossy(&self.0)) - } -} - -impl FromStr for FourCC { - type Err = VeilidAPIError; - fn from_str(s: &str) -> Result { - Ok(Self( - s.as_bytes().try_into().map_err(VeilidAPIError::generic)?, - )) - } -} - -/// Log level for VeilidCore -#[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Copy, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum VeilidLogLevel { - Error = 1, - Warn, - Info, - Debug, - Trace, -} - -impl VeilidLogLevel { - pub fn from_tracing_level(level: tracing::Level) -> VeilidLogLevel { - match level { - tracing::Level::ERROR => VeilidLogLevel::Error, - tracing::Level::WARN => VeilidLogLevel::Warn, - tracing::Level::INFO => VeilidLogLevel::Info, - tracing::Level::DEBUG => VeilidLogLevel::Debug, - tracing::Level::TRACE => VeilidLogLevel::Trace, - } - } - pub fn from_log_level(level: log::Level) -> VeilidLogLevel { - match level { - log::Level::Error => VeilidLogLevel::Error, - log::Level::Warn => VeilidLogLevel::Warn, - log::Level::Info => VeilidLogLevel::Info, - log::Level::Debug => VeilidLogLevel::Debug, - log::Level::Trace => VeilidLogLevel::Trace, - } - } - pub fn to_tracing_level(&self) -> tracing::Level { - match self { - Self::Error => tracing::Level::ERROR, - Self::Warn => tracing::Level::WARN, - Self::Info => tracing::Level::INFO, - Self::Debug => tracing::Level::DEBUG, - Self::Trace => tracing::Level::TRACE, - } - } - pub fn to_log_level(&self) -> log::Level { - match self { - Self::Error => log::Level::Error, - Self::Warn => log::Level::Warn, - Self::Info => log::Level::Info, - Self::Debug => log::Level::Debug, - Self::Trace => log::Level::Trace, - } - } -} - -impl fmt::Display for VeilidLogLevel { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let text = match self { - Self::Error => "ERROR", - Self::Warn => "WARN", - Self::Info => "INFO", - Self::Debug => "DEBUG", - Self::Trace => "TRACE", - }; - write!(f, "{}", text) - } -} - -/// A VeilidCore log message with optional backtrace -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidLog { - pub log_level: VeilidLogLevel, - pub message: String, - pub backtrace: Option, -} - -/// Direct statement blob passed to hosting application for processing -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidAppMessage { - /// Some(sender) if the message was sent directly, None if received via a private/safety route - #[serde(with = "opt_json_as_string")] - pub sender: Option, - /// The content of the message to deliver to the application - #[serde(with = "json_as_base64")] - pub message: Vec, -} - -/// Direct question blob passed to hosting application for processing to send an eventual AppReply -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidAppCall { - /// Some(sender) if the request was sent directly, None if received via a private/safety route - #[serde(with = "opt_json_as_string")] - pub sender: Option, - /// The content of the request to deliver to the application - #[serde(with = "json_as_base64")] - pub message: Vec, - /// The id to reply to - #[serde(with = "json_as_string")] - pub id: OperationId, -} - -/// Attachment abstraction for network 'signal strength' -#[derive( - Debug, - PartialEq, - Eq, - Clone, - Copy, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum AttachmentState { - Detached, - Attaching, - AttachedWeak, - AttachedGood, - AttachedStrong, - FullyAttached, - OverAttached, - Detaching, -} - -impl fmt::Display for AttachmentState { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let out = match self { - AttachmentState::Attaching => "attaching".to_owned(), - AttachmentState::AttachedWeak => "attached_weak".to_owned(), - AttachmentState::AttachedGood => "attached_good".to_owned(), - AttachmentState::AttachedStrong => "attached_strong".to_owned(), - AttachmentState::FullyAttached => "fully_attached".to_owned(), - AttachmentState::OverAttached => "over_attached".to_owned(), - AttachmentState::Detaching => "detaching".to_owned(), - AttachmentState::Detached => "detached".to_owned(), - }; - write!(f, "{}", out) - } -} - -impl TryFrom for AttachmentState { - type Error = (); - - fn try_from(s: String) -> Result { - Ok(match s.as_str() { - "attaching" => AttachmentState::Attaching, - "attached_weak" => AttachmentState::AttachedWeak, - "attached_good" => AttachmentState::AttachedGood, - "attached_strong" => AttachmentState::AttachedStrong, - "fully_attached" => AttachmentState::FullyAttached, - "over_attached" => AttachmentState::OverAttached, - "detaching" => AttachmentState::Detaching, - "detached" => AttachmentState::Detached, - _ => return Err(()), - }) - } -} - -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidStateAttachment { - pub state: AttachmentState, - pub public_internet_ready: bool, - pub local_network_ready: bool, -} - -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct PeerTableData { - pub node_ids: Vec, - pub peer_address: PeerAddress, - pub peer_stats: PeerStats, -} - -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidStateNetwork { - pub started: bool, - #[serde(with = "json_as_string")] - pub bps_down: ByteCount, - #[serde(with = "json_as_string")] - pub bps_up: ByteCount, - pub peers: Vec, -} - -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidStateRoute { - pub dead_routes: Vec, - pub dead_remote_routes: Vec, -} - -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidStateConfig { - pub config: VeilidConfigInner, -} - -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidValueChange { - key: TypedKey, - subkeys: Vec, - count: u32, - value: ValueData, -} - -#[derive(Debug, Clone, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(u8), derive(CheckBytes))] -#[serde(tag = "kind")] -pub enum VeilidUpdate { - Log(VeilidLog), - AppMessage(VeilidAppMessage), - AppCall(VeilidAppCall), - Attachment(VeilidStateAttachment), - Network(VeilidStateNetwork), - Config(VeilidStateConfig), - Route(VeilidStateRoute), - ValueChange(VeilidValueChange), - Shutdown, -} - -#[derive(Debug, Clone, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidState { - pub attachment: VeilidStateAttachment, - pub network: VeilidStateNetwork, - pub config: VeilidStateConfig, -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -/// - -#[derive( - Clone, - Debug, - Default, - PartialOrd, - PartialEq, - Eq, - Ord, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct ValueData { - seq: ValueSeqNum, - data: Vec, - writer: PublicKey, -} -impl ValueData { - pub const MAX_LEN: usize = 32768; - - pub fn new(data: Vec, writer: PublicKey) -> Self { - assert!(data.len() <= Self::MAX_LEN); - Self { - seq: 0, - data, - writer, - } - } - pub fn new_with_seq(seq: ValueSeqNum, data: Vec, writer: PublicKey) -> Self { - assert!(data.len() <= Self::MAX_LEN); - Self { seq, data, writer } - } - - pub fn seq(&self) -> ValueSeqNum { - self.seq - } - - pub fn writer(&self) -> PublicKey { - self.writer - } - - pub fn data(&self) -> &[u8] { - &self.data - } - - pub fn with_data_mut(&mut self, f: F) -> R - where - F: FnOnce(&mut Vec) -> R, - { - let out = f(&mut self.data); - assert!(self.data.len() <= Self::MAX_LEN); - self.seq += 1; - out - } -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// - -// Keep member order appropriate for sorting < preference -#[derive( - Copy, - Clone, - Debug, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum DialInfoClass { - Direct = 0, // D = Directly reachable with public IP and no firewall, with statically configured port - Mapped = 1, // M = Directly reachable with via portmap behind any NAT or firewalled with dynamically negotiated port - FullConeNAT = 2, // F = Directly reachable device without portmap behind full-cone NAT - Blocked = 3, // B = Inbound blocked at firewall but may hole punch with public address - AddressRestrictedNAT = 4, // A = Device without portmap behind address-only restricted NAT - PortRestrictedNAT = 5, // P = Device without portmap behind address-and-port restricted NAT -} - -impl DialInfoClass { - // Is a signal required to do an inbound hole-punch? - pub fn requires_signal(&self) -> bool { - matches!( - self, - Self::Blocked | Self::AddressRestrictedNAT | Self::PortRestrictedNAT - ) - } - - // Does a relay node need to be allocated for this dial info? - // For full cone NAT, the relay itself may not be used but the keepalive sent to it - // is required to keep the NAT mapping valid in the router state table - pub fn requires_relay(&self) -> bool { - matches!( - self, - Self::FullConeNAT - | Self::Blocked - | Self::AddressRestrictedNAT - | Self::PortRestrictedNAT - ) - } -} - -// Ordering here matters, >= is used to check strength of sequencing requirement -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum Sequencing { - NoPreference, - PreferOrdered, - EnsureOrdered, -} - -impl Default for Sequencing { - fn default() -> Self { - Self::NoPreference - } -} - -// Ordering here matters, >= is used to check strength of stability requirement -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum Stability { - LowLatency, - Reliable, -} - -impl Default for Stability { - fn default() -> Self { - Self::LowLatency - } -} - -/// The choice of safety route to include in compiled routes -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum SafetySelection { - /// Don't use a safety route, only specify the sequencing preference - Unsafe(Sequencing), - /// Use a safety route and parameters specified by a SafetySpec - Safe(SafetySpec), -} - -impl SafetySelection { - pub fn get_sequencing(&self) -> Sequencing { - match self { - SafetySelection::Unsafe(seq) => *seq, - SafetySelection::Safe(ss) => ss.sequencing, - } - } -} - -impl Default for SafetySelection { - fn default() -> Self { - Self::Unsafe(Sequencing::NoPreference) - } -} - -/// Options for safety routes (sender privacy) -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct SafetySpec { - /// preferred safety route set id if it still exists - pub preferred_route: Option, - /// must be greater than 0 - pub hop_count: usize, - /// prefer reliability over speed - pub stability: Stability, - /// prefer connection-oriented sequenced protocols - pub sequencing: Sequencing, -} - -// Keep member order appropriate for sorting < preference -#[derive( - Debug, - Clone, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DialInfoDetail { - pub class: DialInfoClass, - pub dial_info: DialInfo, -} - -impl MatchesDialInfoFilter for DialInfoDetail { - fn matches_filter(&self, filter: &DialInfoFilter) -> bool { - self.dial_info.matches_filter(filter) - } -} - -impl DialInfoDetail { - pub fn ordered_sequencing_sort(a: &DialInfoDetail, b: &DialInfoDetail) -> core::cmp::Ordering { - if a.class < b.class { - return core::cmp::Ordering::Less; - } - if a.class > b.class { - return core::cmp::Ordering::Greater; - } - DialInfo::ordered_sequencing_sort(&a.dial_info, &b.dial_info) - } - pub const NO_SORT: std::option::Option< - for<'r, 's> fn( - &'r veilid_api::DialInfoDetail, - &'s veilid_api::DialInfoDetail, - ) -> std::cmp::Ordering, - > = None:: core::cmp::Ordering>; -} - -#[derive( - Copy, - Clone, - Debug, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum NetworkClass { - InboundCapable = 0, // I = Inbound capable without relay, may require signal - OutboundOnly = 1, // O = Outbound only, inbound relay required except with reverse connect signal - WebApp = 2, // W = PWA, outbound relay is required in most cases - Invalid = 3, // X = Invalid network class, we don't know how to reach this node -} - -impl Default for NetworkClass { - fn default() -> Self { - Self::Invalid - } -} - -impl NetworkClass { - // Should an outbound relay be kept available? - pub fn outbound_wants_relay(&self) -> bool { - matches!(self, Self::WebApp) - } -} - -/// RoutingDomain-specific status for each node -/// is returned by the StatusA call - -/// PublicInternet RoutingDomain Status -#[derive( - Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct PublicInternetNodeStatus { - pub will_route: bool, - pub will_tunnel: bool, - pub will_signal: bool, - pub will_relay: bool, - pub will_validate_dial_info: bool, -} - -#[derive( - Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct LocalNetworkNodeStatus { - pub will_relay: bool, - pub will_validate_dial_info: bool, -} - -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum NodeStatus { - PublicInternet(PublicInternetNodeStatus), - LocalNetwork(LocalNetworkNodeStatus), -} - -impl NodeStatus { - pub fn will_route(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_route, - NodeStatus::LocalNetwork(_) => false, - } - } - pub fn will_tunnel(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_tunnel, - NodeStatus::LocalNetwork(_) => false, - } - } - pub fn will_signal(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_signal, - NodeStatus::LocalNetwork(_) => false, - } - } - pub fn will_relay(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_relay, - NodeStatus::LocalNetwork(ln) => ln.will_relay, - } - } - pub fn will_validate_dial_info(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_validate_dial_info, - NodeStatus::LocalNetwork(ln) => ln.will_validate_dial_info, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct NodeInfo { - pub network_class: NetworkClass, - #[with(RkyvEnumSet)] - pub outbound_protocols: ProtocolTypeSet, - #[with(RkyvEnumSet)] - pub address_types: AddressTypeSet, - pub envelope_support: Vec, - pub crypto_support: Vec, - pub dial_info_detail_list: Vec, -} - -impl NodeInfo { - pub fn first_filtered_dial_info_detail( - &self, - sort: Option, - filter: F, - ) -> Option - where - S: Fn(&DialInfoDetail, &DialInfoDetail) -> std::cmp::Ordering, - F: Fn(&DialInfoDetail) -> bool, - { - if let Some(sort) = sort { - let mut dids = self.dial_info_detail_list.clone(); - dids.sort_by(sort); - for did in dids { - if filter(&did) { - return Some(did); - } - } - } else { - for did in &self.dial_info_detail_list { - if filter(did) { - return Some(did.clone()); - } - } - }; - None - } - - pub fn all_filtered_dial_info_details( - &self, - sort: Option, - filter: F, - ) -> Vec - where - S: Fn(&DialInfoDetail, &DialInfoDetail) -> std::cmp::Ordering, - F: Fn(&DialInfoDetail) -> bool, - { - let mut dial_info_detail_list = Vec::new(); - - if let Some(sort) = sort { - let mut dids = self.dial_info_detail_list.clone(); - dids.sort_by(sort); - for did in dids { - if filter(&did) { - dial_info_detail_list.push(did); - } - } - } else { - for did in &self.dial_info_detail_list { - if filter(did) { - dial_info_detail_list.push(did.clone()); - } - } - }; - dial_info_detail_list - } - - /// Does this node has some dial info - pub fn has_dial_info(&self) -> bool { - !self.dial_info_detail_list.is_empty() - } - - /// Is some relay required either for signal or inbound relay or outbound relay? - pub fn requires_relay(&self) -> bool { - match self.network_class { - NetworkClass::InboundCapable => { - for did in &self.dial_info_detail_list { - if did.class.requires_relay() { - return true; - } - } - } - NetworkClass::OutboundOnly => { - return true; - } - NetworkClass::WebApp => { - return true; - } - NetworkClass::Invalid => {} - } - false - } - - /// Can this node assist with signalling? Yes but only if it doesn't require signalling, itself. - pub fn can_signal(&self) -> bool { - // Must be inbound capable - if !matches!(self.network_class, NetworkClass::InboundCapable) { - return false; - } - // Do any of our dial info require signalling? if so, we can't offer signalling - for did in &self.dial_info_detail_list { - if did.class.requires_signal() { - return false; - } - } - true - } - - /// Can this node relay be an inbound relay? - pub fn can_inbound_relay(&self) -> bool { - // For now this is the same - self.can_signal() - } - - /// Is this node capable of validating dial info - pub fn can_validate_dial_info(&self) -> bool { - // For now this is the same - self.can_signal() - } -} - -#[allow(clippy::derive_hash_xor_eq)] -#[derive( - Debug, - PartialOrd, - Ord, - Hash, - EnumSetType, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[enumset(repr = "u8")] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum Direction { - Inbound, - Outbound, -} -pub type DirectionSet = EnumSet; - -// Keep member order appropriate for sorting < preference -// Must match DialInfo order -#[allow(clippy::derive_hash_xor_eq)] -#[derive( - Debug, - PartialOrd, - Ord, - Hash, - EnumSetType, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[enumset(repr = "u8")] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum LowLevelProtocolType { - UDP, - TCP, -} - -impl LowLevelProtocolType { - pub fn is_connection_oriented(&self) -> bool { - matches!(self, LowLevelProtocolType::TCP) - } -} -pub type LowLevelProtocolTypeSet = EnumSet; - -// Keep member order appropriate for sorting < preference -// Must match DialInfo order -#[allow(clippy::derive_hash_xor_eq)] -#[derive( - Debug, - PartialOrd, - Ord, - Hash, - EnumSetType, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[enumset(repr = "u8")] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum ProtocolType { - UDP, - TCP, - WS, - WSS, -} - -impl ProtocolType { - pub fn is_connection_oriented(&self) -> bool { - matches!( - self, - ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS - ) - } - pub fn low_level_protocol_type(&self) -> LowLevelProtocolType { - match self { - ProtocolType::UDP => LowLevelProtocolType::UDP, - ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS => LowLevelProtocolType::TCP, - } - } - pub fn sort_order(&self, sequencing: Sequencing) -> usize { - match self { - ProtocolType::UDP => { - if sequencing != Sequencing::NoPreference { - 3 - } else { - 0 - } - } - ProtocolType::TCP => { - if sequencing != Sequencing::NoPreference { - 0 - } else { - 1 - } - } - ProtocolType::WS => { - if sequencing != Sequencing::NoPreference { - 1 - } else { - 2 - } - } - ProtocolType::WSS => { - if sequencing != Sequencing::NoPreference { - 2 - } else { - 3 - } - } - } - } - pub fn all_ordered_set() -> ProtocolTypeSet { - ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS - } -} - -pub type ProtocolTypeSet = EnumSet; - -#[allow(clippy::derive_hash_xor_eq)] -#[derive( - Debug, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, - EnumSetType, -)] -#[enumset(repr = "u8")] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum AddressType { - IPV4, - IPV6, -} -pub type AddressTypeSet = EnumSet; - -// Routing domain here is listed in order of preference, keep in order -#[allow(clippy::derive_hash_xor_eq)] -#[derive( - Debug, - Ord, - PartialOrd, - Hash, - EnumSetType, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[enumset(repr = "u8")] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum RoutingDomain { - LocalNetwork = 0, - PublicInternet = 1, -} -impl RoutingDomain { - pub const fn count() -> usize { - 2 - } - pub const fn all() -> [RoutingDomain; RoutingDomain::count()] { - // Routing domain here is listed in order of preference, keep in order - [RoutingDomain::LocalNetwork, RoutingDomain::PublicInternet] - } -} -pub type RoutingDomainSet = EnumSet; - -#[derive( - Copy, - Clone, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum Address { - IPV4(Ipv4Addr), - IPV6(Ipv6Addr), -} - -impl Default for Address { - fn default() -> Self { - Address::IPV4(Ipv4Addr::new(0, 0, 0, 0)) - } -} - -impl Address { - pub fn from_socket_addr(sa: SocketAddr) -> Address { - match sa { - SocketAddr::V4(v4) => Address::IPV4(*v4.ip()), - SocketAddr::V6(v6) => Address::IPV6(*v6.ip()), - } - } - pub fn from_ip_addr(addr: IpAddr) -> Address { - match addr { - IpAddr::V4(v4) => Address::IPV4(v4), - IpAddr::V6(v6) => Address::IPV6(v6), - } - } - pub fn address_type(&self) -> AddressType { - match self { - Address::IPV4(_) => AddressType::IPV4, - Address::IPV6(_) => AddressType::IPV6, - } - } - pub fn address_string(&self) -> String { - match self { - Address::IPV4(v4) => v4.to_string(), - Address::IPV6(v6) => v6.to_string(), - } - } - pub fn address_string_with_port(&self, port: u16) -> String { - match self { - Address::IPV4(v4) => format!("{}:{}", v4, port), - Address::IPV6(v6) => format!("[{}]:{}", v6, port), - } - } - pub fn is_unspecified(&self) -> bool { - match self { - Address::IPV4(v4) => ipv4addr_is_unspecified(v4), - Address::IPV6(v6) => ipv6addr_is_unspecified(v6), - } - } - pub fn is_global(&self) -> bool { - match self { - Address::IPV4(v4) => ipv4addr_is_global(v4) && !ipv4addr_is_multicast(v4), - Address::IPV6(v6) => ipv6addr_is_unicast_global(v6), - } - } - pub fn is_local(&self) -> bool { - match self { - Address::IPV4(v4) => { - ipv4addr_is_private(v4) - || ipv4addr_is_link_local(v4) - || ipv4addr_is_ietf_protocol_assignment(v4) - } - Address::IPV6(v6) => { - ipv6addr_is_unicast_site_local(v6) - || ipv6addr_is_unicast_link_local(v6) - || ipv6addr_is_unique_local(v6) - } - } - } - pub fn to_ip_addr(&self) -> IpAddr { - match self { - Self::IPV4(a) => IpAddr::V4(*a), - Self::IPV6(a) => IpAddr::V6(*a), - } - } - pub fn to_socket_addr(&self, port: u16) -> SocketAddr { - SocketAddr::new(self.to_ip_addr(), port) - } - pub fn to_canonical(&self) -> Address { - match self { - Address::IPV4(v4) => Address::IPV4(*v4), - Address::IPV6(v6) => match v6.to_ipv4() { - Some(v4) => Address::IPV4(v4), - None => Address::IPV6(*v6), - }, - } - } -} - -impl fmt::Display for Address { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Address::IPV4(v4) => write!(f, "{}", v4), - Address::IPV6(v6) => write!(f, "{}", v6), - } - } -} - -impl FromStr for Address { - type Err = VeilidAPIError; - fn from_str(host: &str) -> Result { - if let Ok(addr) = Ipv4Addr::from_str(host) { - Ok(Address::IPV4(addr)) - } else if let Ok(addr) = Ipv6Addr::from_str(host) { - Ok(Address::IPV6(addr)) - } else { - Err(VeilidAPIError::parse_error( - "Address::from_str failed", - host, - )) - } - } -} - -#[derive( - Copy, - Default, - Clone, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct SocketAddress { - address: Address, - port: u16, -} - -impl SocketAddress { - pub fn new(address: Address, port: u16) -> Self { - Self { address, port } - } - pub fn from_socket_addr(sa: SocketAddr) -> SocketAddress { - Self { - address: Address::from_socket_addr(sa), - port: sa.port(), - } - } - pub fn address(&self) -> Address { - self.address - } - pub fn address_type(&self) -> AddressType { - self.address.address_type() - } - pub fn port(&self) -> u16 { - self.port - } - pub fn to_canonical(&self) -> SocketAddress { - SocketAddress { - address: self.address.to_canonical(), - port: self.port, - } - } - pub fn to_ip_addr(&self) -> IpAddr { - self.address.to_ip_addr() - } - pub fn to_socket_addr(&self) -> SocketAddr { - self.address.to_socket_addr(self.port) - } -} - -impl fmt::Display for SocketAddress { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{}", self.to_socket_addr()) - } -} - -impl FromStr for SocketAddress { - type Err = VeilidAPIError; - fn from_str(s: &str) -> Result { - let sa = SocketAddr::from_str(s) - .map_err(|e| VeilidAPIError::parse_error("Failed to parse SocketAddress", e))?; - Ok(SocketAddress::from_socket_addr(sa)) - } -} - -////////////////////////////////////////////////////////////////// - -#[derive( - Copy, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DialInfoFilter { - #[with(RkyvEnumSet)] - pub protocol_type_set: ProtocolTypeSet, - #[with(RkyvEnumSet)] - pub address_type_set: AddressTypeSet, -} - -impl Default for DialInfoFilter { - fn default() -> Self { - Self { - protocol_type_set: ProtocolTypeSet::all(), - address_type_set: AddressTypeSet::all(), - } - } -} - -impl DialInfoFilter { - pub fn all() -> Self { - Self { - protocol_type_set: ProtocolTypeSet::all(), - address_type_set: AddressTypeSet::all(), - } - } - pub fn with_protocol_type(mut self, protocol_type: ProtocolType) -> Self { - self.protocol_type_set = ProtocolTypeSet::only(protocol_type); - self - } - pub fn with_protocol_type_set(mut self, protocol_set: ProtocolTypeSet) -> Self { - self.protocol_type_set = protocol_set; - self - } - pub fn with_address_type(mut self, address_type: AddressType) -> Self { - self.address_type_set = AddressTypeSet::only(address_type); - self - } - pub fn with_address_type_set(mut self, address_set: AddressTypeSet) -> Self { - self.address_type_set = address_set; - self - } - pub fn filtered(mut self, other_dif: &DialInfoFilter) -> Self { - self.protocol_type_set &= other_dif.protocol_type_set; - self.address_type_set &= other_dif.address_type_set; - self - } - pub fn is_dead(&self) -> bool { - self.protocol_type_set.is_empty() || self.address_type_set.is_empty() - } -} - -impl fmt::Debug for DialInfoFilter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let mut out = String::new(); - if self.protocol_type_set != ProtocolTypeSet::all() { - out += &format!("+{:?}", self.protocol_type_set); - } else { - out += "*"; - } - if self.address_type_set != AddressTypeSet::all() { - out += &format!("+{:?}", self.address_type_set); - } else { - out += "*"; - } - write!(f, "[{}]", out) - } -} - -pub trait MatchesDialInfoFilter { - fn matches_filter(&self, filter: &DialInfoFilter) -> bool; -} - -#[derive( - Clone, - Default, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DialInfoUDP { - pub socket_address: SocketAddress, -} - -#[derive( - Clone, - Default, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DialInfoTCP { - pub socket_address: SocketAddress, -} - -#[derive( - Clone, - Default, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DialInfoWS { - pub socket_address: SocketAddress, - pub request: String, -} - -#[derive( - Clone, - Default, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DialInfoWSS { - pub socket_address: SocketAddress, - pub request: String, -} - -// Keep member order appropriate for sorting < preference -// Must match ProtocolType order -#[derive( - Clone, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -#[serde(tag = "kind")] -pub enum DialInfo { - UDP(DialInfoUDP), - TCP(DialInfoTCP), - WS(DialInfoWS), - WSS(DialInfoWSS), -} -impl Default for DialInfo { - fn default() -> Self { - DialInfo::UDP(DialInfoUDP::default()) - } -} - -impl fmt::Display for DialInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - match self { - DialInfo::UDP(di) => write!(f, "udp|{}", di.socket_address), - DialInfo::TCP(di) => write!(f, "tcp|{}", di.socket_address), - DialInfo::WS(di) => { - let url = format!("ws://{}", di.request); - let split_url = SplitUrl::from_str(&url).unwrap(); - match split_url.host { - SplitUrlHost::Hostname(_) => { - write!(f, "ws|{}|{}", di.socket_address.to_ip_addr(), di.request) - } - SplitUrlHost::IpAddr(a) => { - if di.socket_address.to_ip_addr() == a { - write!(f, "ws|{}", di.request) - } else { - panic!("resolved address does not match url: {}", di.request); - } - } - } - } - DialInfo::WSS(di) => { - let url = format!("wss://{}", di.request); - let split_url = SplitUrl::from_str(&url).unwrap(); - match split_url.host { - SplitUrlHost::Hostname(_) => { - write!(f, "wss|{}|{}", di.socket_address.to_ip_addr(), di.request) - } - SplitUrlHost::IpAddr(_) => { - panic!( - "secure websockets can not use ip address in request: {}", - di.request - ); - } - } - } - } - } -} - -impl FromStr for DialInfo { - type Err = VeilidAPIError; - fn from_str(s: &str) -> Result { - let (proto, rest) = s.split_once('|').ok_or_else(|| { - VeilidAPIError::parse_error("DialInfo::from_str missing protocol '|' separator", s) - })?; - match proto { - "udp" => { - let socket_address = SocketAddress::from_str(rest)?; - Ok(DialInfo::udp(socket_address)) - } - "tcp" => { - let socket_address = SocketAddress::from_str(rest)?; - Ok(DialInfo::tcp(socket_address)) - } - "ws" => { - let url = format!("ws://{}", rest); - let split_url = SplitUrl::from_str(&url).map_err(|e| { - VeilidAPIError::parse_error(format!("unable to split WS url: {}", e), &url) - })?; - if split_url.scheme != "ws" || !url.starts_with("ws://") { - apibail_parse_error!("incorrect scheme for WS dialinfo", url); - } - let url_port = split_url.port.unwrap_or(80u16); - - match rest.split_once('|') { - Some((sa, rest)) => { - let address = Address::from_str(sa)?; - - DialInfo::try_ws( - SocketAddress::new(address, url_port), - format!("ws://{}", rest), - ) - } - None => { - let address = Address::from_str(&split_url.host.to_string())?; - DialInfo::try_ws( - SocketAddress::new(address, url_port), - format!("ws://{}", rest), - ) - } - } - } - "wss" => { - let url = format!("wss://{}", rest); - let split_url = SplitUrl::from_str(&url).map_err(|e| { - VeilidAPIError::parse_error(format!("unable to split WSS url: {}", e), &url) - })?; - if split_url.scheme != "wss" || !url.starts_with("wss://") { - apibail_parse_error!("incorrect scheme for WSS dialinfo", url); - } - let url_port = split_url.port.unwrap_or(443u16); - - let (a, rest) = rest.split_once('|').ok_or_else(|| { - VeilidAPIError::parse_error( - "DialInfo::from_str missing socket address '|' separator", - s, - ) - })?; - - let address = Address::from_str(a)?; - DialInfo::try_wss( - SocketAddress::new(address, url_port), - format!("wss://{}", rest), - ) - } - _ => Err(VeilidAPIError::parse_error( - "DialInfo::from_str has invalid scheme", - s, - )), - } - } -} - -impl DialInfo { - pub fn udp_from_socketaddr(socket_addr: SocketAddr) -> Self { - Self::UDP(DialInfoUDP { - socket_address: SocketAddress::from_socket_addr(socket_addr).to_canonical(), - }) - } - pub fn tcp_from_socketaddr(socket_addr: SocketAddr) -> Self { - Self::TCP(DialInfoTCP { - socket_address: SocketAddress::from_socket_addr(socket_addr).to_canonical(), - }) - } - pub fn udp(socket_address: SocketAddress) -> Self { - Self::UDP(DialInfoUDP { - socket_address: socket_address.to_canonical(), - }) - } - pub fn tcp(socket_address: SocketAddress) -> Self { - Self::TCP(DialInfoTCP { - socket_address: socket_address.to_canonical(), - }) - } - pub fn try_ws(socket_address: SocketAddress, url: String) -> Result { - let split_url = SplitUrl::from_str(&url).map_err(|e| { - VeilidAPIError::parse_error(format!("unable to split WS url: {}", e), &url) - })?; - if split_url.scheme != "ws" || !url.starts_with("ws://") { - apibail_parse_error!("incorrect scheme for WS dialinfo", url); - } - let url_port = split_url.port.unwrap_or(80u16); - if url_port != socket_address.port() { - apibail_parse_error!("socket address port doesn't match url port", url); - } - if let SplitUrlHost::IpAddr(a) = split_url.host { - if socket_address.to_ip_addr() != a { - apibail_parse_error!( - format!("request address does not match socket address: {}", a), - socket_address - ); - } - } - Ok(Self::WS(DialInfoWS { - socket_address: socket_address.to_canonical(), - request: url[5..].to_string(), - })) - } - pub fn try_wss(socket_address: SocketAddress, url: String) -> Result { - let split_url = SplitUrl::from_str(&url).map_err(|e| { - VeilidAPIError::parse_error(format!("unable to split WSS url: {}", e), &url) - })?; - if split_url.scheme != "wss" || !url.starts_with("wss://") { - apibail_parse_error!("incorrect scheme for WSS dialinfo", url); - } - let url_port = split_url.port.unwrap_or(443u16); - if url_port != socket_address.port() { - apibail_parse_error!("socket address port doesn't match url port", url); - } - if !matches!(split_url.host, SplitUrlHost::Hostname(_)) { - apibail_parse_error!( - "WSS url can not use address format, only hostname format", - url - ); - } - Ok(Self::WSS(DialInfoWSS { - socket_address: socket_address.to_canonical(), - request: url[6..].to_string(), - })) - } - pub fn protocol_type(&self) -> ProtocolType { - match self { - Self::UDP(_) => ProtocolType::UDP, - Self::TCP(_) => ProtocolType::TCP, - Self::WS(_) => ProtocolType::WS, - Self::WSS(_) => ProtocolType::WSS, - } - } - pub fn address_type(&self) -> AddressType { - self.socket_address().address_type() - } - pub fn address(&self) -> Address { - match self { - Self::UDP(di) => di.socket_address.address, - Self::TCP(di) => di.socket_address.address, - Self::WS(di) => di.socket_address.address, - Self::WSS(di) => di.socket_address.address, - } - } - pub fn socket_address(&self) -> SocketAddress { - match self { - Self::UDP(di) => di.socket_address, - Self::TCP(di) => di.socket_address, - Self::WS(di) => di.socket_address, - Self::WSS(di) => di.socket_address, - } - } - pub fn to_ip_addr(&self) -> IpAddr { - match self { - Self::UDP(di) => di.socket_address.to_ip_addr(), - Self::TCP(di) => di.socket_address.to_ip_addr(), - Self::WS(di) => di.socket_address.to_ip_addr(), - Self::WSS(di) => di.socket_address.to_ip_addr(), - } - } - pub fn port(&self) -> u16 { - match self { - Self::UDP(di) => di.socket_address.port, - Self::TCP(di) => di.socket_address.port, - Self::WS(di) => di.socket_address.port, - Self::WSS(di) => di.socket_address.port, - } - } - pub fn set_port(&mut self, port: u16) { - match self { - Self::UDP(di) => di.socket_address.port = port, - Self::TCP(di) => di.socket_address.port = port, - Self::WS(di) => di.socket_address.port = port, - Self::WSS(di) => di.socket_address.port = port, - } - } - pub fn to_socket_addr(&self) -> SocketAddr { - match self { - Self::UDP(di) => di.socket_address.to_socket_addr(), - Self::TCP(di) => di.socket_address.to_socket_addr(), - Self::WS(di) => di.socket_address.to_socket_addr(), - Self::WSS(di) => di.socket_address.to_socket_addr(), - } - } - pub fn to_peer_address(&self) -> PeerAddress { - match self { - Self::UDP(di) => PeerAddress::new(di.socket_address, ProtocolType::UDP), - Self::TCP(di) => PeerAddress::new(di.socket_address, ProtocolType::TCP), - Self::WS(di) => PeerAddress::new(di.socket_address, ProtocolType::WS), - Self::WSS(di) => PeerAddress::new(di.socket_address, ProtocolType::WSS), - } - } - pub fn request(&self) -> Option { - match self { - Self::UDP(_) => None, - Self::TCP(_) => None, - Self::WS(di) => Some(format!("ws://{}", di.request)), - Self::WSS(di) => Some(format!("wss://{}", di.request)), - } - } - pub fn is_valid(&self) -> bool { - let socket_address = self.socket_address(); - let address = socket_address.address(); - let port = socket_address.port(); - (address.is_global() || address.is_local()) && port > 0 - } - - pub fn make_filter(&self) -> DialInfoFilter { - DialInfoFilter { - protocol_type_set: ProtocolTypeSet::only(self.protocol_type()), - address_type_set: AddressTypeSet::only(self.address_type()), - } - } - - pub fn try_vec_from_short, H: AsRef>( - short: S, - hostname: H, - ) -> Result, VeilidAPIError> { - let short = short.as_ref(); - let hostname = hostname.as_ref(); - - if short.len() < 2 { - apibail_parse_error!("invalid short url length", short); - } - let url = match &short[0..1] { - "U" => { - format!("udp://{}:{}", hostname, &short[1..]) - } - "T" => { - format!("tcp://{}:{}", hostname, &short[1..]) - } - "W" => { - format!("ws://{}:{}", hostname, &short[1..]) - } - "S" => { - format!("wss://{}:{}", hostname, &short[1..]) - } - _ => { - apibail_parse_error!("invalid short url type", short); - } - }; - Self::try_vec_from_url(url) - } - - pub fn try_vec_from_url>(url: S) -> Result, VeilidAPIError> { - let url = url.as_ref(); - let split_url = SplitUrl::from_str(url) - .map_err(|e| VeilidAPIError::parse_error(format!("unable to split url: {}", e), url))?; - - let port = match split_url.scheme.as_str() { - "udp" | "tcp" => split_url - .port - .ok_or_else(|| VeilidAPIError::parse_error("Missing port in udp url", url))?, - "ws" => split_url.port.unwrap_or(80u16), - "wss" => split_url.port.unwrap_or(443u16), - _ => { - apibail_parse_error!("Invalid dial info url scheme", split_url.scheme); - } - }; - - let socket_addrs = { - // Resolve if possible, WASM doesn't support resolution and doesn't need it to connect to the dialinfo - // This will not be used on signed dialinfo, only for bootstrapping, so we don't need to worry about - // the '0.0.0.0' address being propagated across the routing table - cfg_if::cfg_if! { - if #[cfg(target_arch = "wasm32")] { - vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), port)] - } else { - match split_url.host { - SplitUrlHost::Hostname(_) => split_url - .host_port(port) - .to_socket_addrs() - .map_err(|_| VeilidAPIError::parse_error("couldn't resolve hostname in url", url))? - .collect(), - SplitUrlHost::IpAddr(a) => vec![SocketAddr::new(a, port)], - } - } - } - }; - - let mut out = Vec::new(); - for sa in socket_addrs { - out.push(match split_url.scheme.as_str() { - "udp" => Self::udp_from_socketaddr(sa), - "tcp" => Self::tcp_from_socketaddr(sa), - "ws" => Self::try_ws( - SocketAddress::from_socket_addr(sa).to_canonical(), - url.to_string(), - )?, - "wss" => Self::try_wss( - SocketAddress::from_socket_addr(sa).to_canonical(), - url.to_string(), - )?, - _ => { - unreachable!("Invalid dial info url scheme") - } - }); - } - Ok(out) - } - - pub async fn to_short(&self) -> (String, String) { - match self { - DialInfo::UDP(di) => ( - format!("U{}", di.socket_address.port()), - intf::ptr_lookup(di.socket_address.to_ip_addr()) - .await - .unwrap_or_else(|_| di.socket_address.to_string()), - ), - DialInfo::TCP(di) => ( - format!("T{}", di.socket_address.port()), - intf::ptr_lookup(di.socket_address.to_ip_addr()) - .await - .unwrap_or_else(|_| di.socket_address.to_string()), - ), - DialInfo::WS(di) => { - let mut split_url = SplitUrl::from_str(&format!("ws://{}", di.request)).unwrap(); - if let SplitUrlHost::IpAddr(a) = split_url.host { - if let Ok(host) = intf::ptr_lookup(a).await { - split_url.host = SplitUrlHost::Hostname(host); - } - } - ( - format!( - "W{}{}", - split_url.port.unwrap_or(80), - split_url - .path - .map(|p| format!("/{}", p)) - .unwrap_or_default() - ), - split_url.host.to_string(), - ) - } - DialInfo::WSS(di) => { - let mut split_url = SplitUrl::from_str(&format!("wss://{}", di.request)).unwrap(); - if let SplitUrlHost::IpAddr(a) = split_url.host { - if let Ok(host) = intf::ptr_lookup(a).await { - split_url.host = SplitUrlHost::Hostname(host); - } - } - ( - format!( - "S{}{}", - split_url.port.unwrap_or(443), - split_url - .path - .map(|p| format!("/{}", p)) - .unwrap_or_default() - ), - split_url.host.to_string(), - ) - } - } - } - pub async fn to_url(&self) -> String { - match self { - DialInfo::UDP(di) => intf::ptr_lookup(di.socket_address.to_ip_addr()) - .await - .map(|h| format!("udp://{}:{}", h, di.socket_address.port())) - .unwrap_or_else(|_| format!("udp://{}", di.socket_address)), - DialInfo::TCP(di) => intf::ptr_lookup(di.socket_address.to_ip_addr()) - .await - .map(|h| format!("tcp://{}:{}", h, di.socket_address.port())) - .unwrap_or_else(|_| format!("tcp://{}", di.socket_address)), - DialInfo::WS(di) => { - let mut split_url = SplitUrl::from_str(&format!("ws://{}", di.request)).unwrap(); - if let SplitUrlHost::IpAddr(a) = split_url.host { - if let Ok(host) = intf::ptr_lookup(a).await { - split_url.host = SplitUrlHost::Hostname(host); - } - } - split_url.to_string() - } - DialInfo::WSS(di) => { - let mut split_url = SplitUrl::from_str(&format!("wss://{}", di.request)).unwrap(); - if let SplitUrlHost::IpAddr(a) = split_url.host { - if let Ok(host) = intf::ptr_lookup(a).await { - split_url.host = SplitUrlHost::Hostname(host); - } - } - split_url.to_string() - } - } - } - - pub fn ordered_sequencing_sort(a: &DialInfo, b: &DialInfo) -> core::cmp::Ordering { - let ca = a.protocol_type().sort_order(Sequencing::EnsureOrdered); - let cb = b.protocol_type().sort_order(Sequencing::EnsureOrdered); - if ca < cb { - return core::cmp::Ordering::Less; - } - if ca > cb { - return core::cmp::Ordering::Greater; - } - match (a, b) { - (DialInfo::UDP(a), DialInfo::UDP(b)) => a.cmp(b), - (DialInfo::TCP(a), DialInfo::TCP(b)) => a.cmp(b), - (DialInfo::WS(a), DialInfo::WS(b)) => a.cmp(b), - (DialInfo::WSS(a), DialInfo::WSS(b)) => a.cmp(b), - _ => unreachable!(), - } - } -} - -impl MatchesDialInfoFilter for DialInfo { - fn matches_filter(&self, filter: &DialInfoFilter) -> bool { - if !filter.protocol_type_set.contains(self.protocol_type()) { - return false; - } - if !filter.address_type_set.contains(self.address_type()) { - return false; - } - true - } -} - -////////////////////////////////////////////////////////////////////////// - -/// Signed NodeInfo that can be passed around amongst peers and verifiable -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct SignedDirectNodeInfo { - pub node_info: NodeInfo, - pub timestamp: Timestamp, - pub signatures: Vec, -} -impl SignedDirectNodeInfo { - /// Returns a new SignedDirectNodeInfo that has its signatures validated. - /// On success, this will modify the node_ids set to only include node_ids whose signatures validate. - /// All signatures are stored however, as this can be passed to other nodes that may be able to validate those signatures. - pub fn new( - crypto: Crypto, - node_ids: &mut TypedKeySet, - node_info: NodeInfo, - timestamp: Timestamp, - typed_signatures: Vec, - ) -> Result { - let node_info_bytes = Self::make_signature_bytes(&node_info, timestamp)?; - - // Verify the signatures that we can - let validated_node_ids = - crypto.verify_signatures(node_ids, &node_info_bytes, &typed_signatures)?; - *node_ids = validated_node_ids; - if node_ids.len() == 0 { - apibail_generic!("no valid node ids in direct node info"); - } - - Ok(Self { - node_info, - timestamp, - signatures: typed_signatures, - }) - } - - pub fn make_signatures( - crypto: Crypto, - typed_key_pairs: Vec, - node_info: NodeInfo, - ) -> Result { - let timestamp = get_aligned_timestamp(); - let node_info_bytes = Self::make_signature_bytes(&node_info, timestamp)?; - let typed_signatures = - crypto.generate_signatures(&node_info_bytes, &typed_key_pairs, |kp, s| { - TypedSignature::new(kp.kind, s) - })?; - Ok(Self { - node_info, - timestamp, - signatures: typed_signatures, - }) - } - - fn make_signature_bytes( - node_info: &NodeInfo, - timestamp: Timestamp, - ) -> Result, VeilidAPIError> { - let mut node_info_bytes = Vec::new(); - - // Add nodeinfo to signature - let mut ni_msg = ::capnp::message::Builder::new_default(); - let mut ni_builder = ni_msg.init_root::(); - encode_node_info(node_info, &mut ni_builder).map_err(VeilidAPIError::internal)?; - node_info_bytes.append(&mut builder_to_vec(ni_msg).map_err(VeilidAPIError::internal)?); - - // Add timestamp to signature - node_info_bytes.append(&mut timestamp.as_u64().to_le_bytes().to_vec()); - - Ok(node_info_bytes) - } - - pub fn with_no_signature(node_info: NodeInfo) -> Self { - Self { - node_info, - timestamp: get_aligned_timestamp(), - signatures: Vec::new(), - } - } - - pub fn has_any_signature(&self) -> bool { - !self.signatures.is_empty() - } -} - -/// Signed NodeInfo with a relay that can be passed around amongst peers and verifiable -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct SignedRelayedNodeInfo { - pub node_info: NodeInfo, - pub relay_ids: TypedKeySet, - pub relay_info: SignedDirectNodeInfo, - pub timestamp: Timestamp, - pub signatures: Vec, -} - -impl SignedRelayedNodeInfo { - /// Returns a new SignedRelayedNodeInfo that has its signatures validated. - /// On success, this will modify the node_ids set to only include node_ids whose signatures validate. - /// All signatures are stored however, as this can be passed to other nodes that may be able to validate those signatures. - pub fn new( - crypto: Crypto, - node_ids: &mut TypedKeySet, - node_info: NodeInfo, - relay_ids: TypedKeySet, - relay_info: SignedDirectNodeInfo, - timestamp: Timestamp, - typed_signatures: Vec, - ) -> Result { - let node_info_bytes = - Self::make_signature_bytes(&node_info, &relay_ids, &relay_info, timestamp)?; - let validated_node_ids = - crypto.verify_signatures(node_ids, &node_info_bytes, &typed_signatures)?; - *node_ids = validated_node_ids; - if node_ids.len() == 0 { - apibail_generic!("no valid node ids in relayed node info"); - } - - Ok(Self { - node_info, - relay_ids, - relay_info, - timestamp, - signatures: typed_signatures, - }) - } - - pub fn make_signatures( - crypto: Crypto, - typed_key_pairs: Vec, - node_info: NodeInfo, - relay_ids: TypedKeySet, - relay_info: SignedDirectNodeInfo, - ) -> Result { - let timestamp = get_aligned_timestamp(); - let node_info_bytes = - Self::make_signature_bytes(&node_info, &relay_ids, &relay_info, timestamp)?; - let typed_signatures = - crypto.generate_signatures(&node_info_bytes, &typed_key_pairs, |kp, s| { - TypedSignature::new(kp.kind, s) - })?; - Ok(Self { - node_info, - relay_ids, - relay_info, - timestamp, - signatures: typed_signatures, - }) - } - - fn make_signature_bytes( - node_info: &NodeInfo, - relay_ids: &[TypedKey], - relay_info: &SignedDirectNodeInfo, - timestamp: Timestamp, - ) -> Result, VeilidAPIError> { - let mut sig_bytes = Vec::new(); - - // Add nodeinfo to signature - let mut ni_msg = ::capnp::message::Builder::new_default(); - let mut ni_builder = ni_msg.init_root::(); - encode_node_info(node_info, &mut ni_builder).map_err(VeilidAPIError::internal)?; - sig_bytes.append(&mut builder_to_vec(ni_msg).map_err(VeilidAPIError::internal)?); - - // Add relay ids to signature - for relay_id in relay_ids { - let mut rid_msg = ::capnp::message::Builder::new_default(); - let mut rid_builder = rid_msg.init_root::(); - encode_typed_key(relay_id, &mut rid_builder); - sig_bytes.append(&mut builder_to_vec(rid_msg).map_err(VeilidAPIError::internal)?); - } - - // Add relay info to signature - let mut ri_msg = ::capnp::message::Builder::new_default(); - let mut ri_builder = ri_msg.init_root::(); - encode_signed_direct_node_info(relay_info, &mut ri_builder) - .map_err(VeilidAPIError::internal)?; - sig_bytes.append(&mut builder_to_vec(ri_msg).map_err(VeilidAPIError::internal)?); - - // Add timestamp to signature - sig_bytes.append(&mut timestamp.as_u64().to_le_bytes().to_vec()); - - Ok(sig_bytes) - } - - pub fn has_any_signature(&self) -> bool { - !self.signatures.is_empty() - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum SignedNodeInfo { - Direct(SignedDirectNodeInfo), - Relayed(SignedRelayedNodeInfo), -} - -impl SignedNodeInfo { - pub fn has_any_signature(&self) -> bool { - match self { - SignedNodeInfo::Direct(d) => d.has_any_signature(), - SignedNodeInfo::Relayed(r) => r.has_any_signature(), - } - } - - pub fn timestamp(&self) -> Timestamp { - match self { - SignedNodeInfo::Direct(d) => d.timestamp, - SignedNodeInfo::Relayed(r) => r.timestamp, - } - } - pub fn node_info(&self) -> &NodeInfo { - match self { - SignedNodeInfo::Direct(d) => &d.node_info, - SignedNodeInfo::Relayed(r) => &r.node_info, - } - } - pub fn relay_ids(&self) -> TypedKeySet { - match self { - SignedNodeInfo::Direct(_) => TypedKeySet::new(), - SignedNodeInfo::Relayed(r) => r.relay_ids.clone(), - } - } - pub fn relay_info(&self) -> Option<&NodeInfo> { - match self { - SignedNodeInfo::Direct(_) => None, - SignedNodeInfo::Relayed(r) => Some(&r.relay_info.node_info), - } - } - pub fn relay_peer_info(&self) -> Option { - match self { - SignedNodeInfo::Direct(_) => None, - SignedNodeInfo::Relayed(r) => Some(PeerInfo::new( - r.relay_ids.clone(), - SignedNodeInfo::Direct(r.relay_info.clone()), - )), - } - } - pub fn has_any_dial_info(&self) -> bool { - self.node_info().has_dial_info() - || self - .relay_info() - .map(|relay_ni| relay_ni.has_dial_info()) - .unwrap_or_default() - } - - pub fn has_sequencing_matched_dial_info(&self, sequencing: Sequencing) -> bool { - // Check our dial info - for did in &self.node_info().dial_info_detail_list { - match sequencing { - Sequencing::NoPreference | Sequencing::PreferOrdered => return true, - Sequencing::EnsureOrdered => { - if did.dial_info.protocol_type().is_connection_oriented() { - return true; - } - } - } - } - // Check our relay if we have one - return self - .relay_info() - .map(|relay_ni| { - for did in &relay_ni.dial_info_detail_list { - match sequencing { - Sequencing::NoPreference | Sequencing::PreferOrdered => return true, - Sequencing::EnsureOrdered => { - if did.dial_info.protocol_type().is_connection_oriented() { - return true; - } - } - } - } - false - }) - .unwrap_or_default(); - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct PeerInfo { - pub node_ids: TypedKeySet, - pub signed_node_info: SignedNodeInfo, -} - -impl PeerInfo { - pub fn new(node_ids: TypedKeySet, signed_node_info: SignedNodeInfo) -> Self { - assert!(node_ids.len() > 0 && node_ids.len() <= MAX_CRYPTO_KINDS); - Self { - node_ids, - signed_node_info, - } - } -} - -#[derive( - Copy, - Clone, - Debug, - PartialEq, - PartialOrd, - Eq, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct PeerAddress { - protocol_type: ProtocolType, - #[serde(with = "json_as_string")] - socket_address: SocketAddress, -} - -impl PeerAddress { - pub fn new(socket_address: SocketAddress, protocol_type: ProtocolType) -> Self { - Self { - socket_address: socket_address.to_canonical(), - protocol_type, - } - } - - pub fn socket_address(&self) -> &SocketAddress { - &self.socket_address - } - - pub fn protocol_type(&self) -> ProtocolType { - self.protocol_type - } - - pub fn to_socket_addr(&self) -> SocketAddr { - self.socket_address.to_socket_addr() - } - - pub fn address_type(&self) -> AddressType { - self.socket_address.address_type() - } -} - -/// Represents the 5-tuple of an established connection -/// Not used to specify connections to create, that is reserved for DialInfo -/// -/// ConnectionDescriptors should never be from unspecified local addresses for connection oriented protocols -/// If the medium does not allow local addresses, None should have been used or 'new_no_local' -/// If we are specifying only a port, then the socket's 'local_address()' should have been used, since an -/// established connection is always from a real address to another real address. -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct ConnectionDescriptor { - remote: PeerAddress, - local: Option, -} - -impl ConnectionDescriptor { - pub fn new(remote: PeerAddress, local: SocketAddress) -> Self { - assert!( - !remote.protocol_type().is_connection_oriented() || !local.address().is_unspecified() - ); - - Self { - remote, - local: Some(local), - } - } - pub fn new_no_local(remote: PeerAddress) -> Self { - Self { - remote, - local: None, - } - } - pub fn remote(&self) -> PeerAddress { - self.remote - } - pub fn remote_address(&self) -> &SocketAddress { - self.remote.socket_address() - } - pub fn local(&self) -> Option { - self.local - } - pub fn protocol_type(&self) -> ProtocolType { - self.remote.protocol_type - } - pub fn address_type(&self) -> AddressType { - self.remote.address_type() - } - pub fn make_dial_info_filter(&self) -> DialInfoFilter { - DialInfoFilter::all() - .with_protocol_type(self.protocol_type()) - .with_address_type(self.address_type()) - } -} - -impl MatchesDialInfoFilter for ConnectionDescriptor { - fn matches_filter(&self, filter: &DialInfoFilter) -> bool { - if !filter.protocol_type_set.contains(self.protocol_type()) { - return false; - } - if !filter.address_type_set.contains(self.address_type()) { - return false; - } - true - } -} - -////////////////////////////////////////////////////////////////////////// - -#[derive( - Clone, - Debug, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct LatencyStats { - #[serde(with = "json_as_string")] - pub fastest: TimestampDuration, // fastest latency in the ROLLING_LATENCIES_SIZE last latencies - #[serde(with = "json_as_string")] - pub average: TimestampDuration, // average latency over the ROLLING_LATENCIES_SIZE last latencies - #[serde(with = "json_as_string")] - pub slowest: TimestampDuration, // slowest latency in the ROLLING_LATENCIES_SIZE last latencies -} - -#[derive( - Clone, - Debug, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct TransferStats { - #[serde(with = "json_as_string")] - pub total: ByteCount, // total amount transferred ever - #[serde(with = "json_as_string")] - pub maximum: ByteCount, // maximum rate over the ROLLING_TRANSFERS_SIZE last amounts - #[serde(with = "json_as_string")] - pub average: ByteCount, // average rate over the ROLLING_TRANSFERS_SIZE last amounts - #[serde(with = "json_as_string")] - pub minimum: ByteCount, // minimum rate over the ROLLING_TRANSFERS_SIZE last amounts -} - -#[derive( - Clone, - Debug, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct TransferStatsDownUp { - pub down: TransferStats, - pub up: TransferStats, -} - -#[derive( - Clone, - Debug, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct RPCStats { - pub messages_sent: u32, // number of rpcs that have been sent in the total_time range - pub messages_rcvd: u32, // number of rpcs that have been received in the total_time range - pub questions_in_flight: u32, // number of questions issued that have yet to be answered - #[serde(with = "opt_json_as_string")] - pub last_question_ts: Option, // when the peer was last questioned (either successfully or not) and we wanted an answer - #[serde(with = "opt_json_as_string")] - pub last_seen_ts: Option, // when the peer was last seen for any reason, including when we first attempted to reach out to it - #[serde(with = "opt_json_as_string")] - pub first_consecutive_seen_ts: Option, // the timestamp of the first consecutive proof-of-life for this node (an answer or received question) - pub recent_lost_answers: u32, // number of answers that have been lost since we lost reliability - pub failed_to_send: u32, // number of messages that have failed to send since we last successfully sent one -} - -#[derive( - Clone, - Debug, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct PeerStats { - #[serde(with = "json_as_string")] - pub time_added: Timestamp, // when the peer was added to the routing table - pub rpc_stats: RPCStats, // information about RPCs - pub latency: Option, // latencies for communications with the peer - pub transfer: TransferStatsDownUp, // Stats for communications with the peer -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// - -/// Parameter for Signal operation -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum SignalInfo { - /// UDP Hole Punch Request - HolePunch { - /// /// Receipt to be returned after the hole punch - receipt: Vec, - /// Sender's peer info - peer_info: PeerInfo, - }, - /// Reverse Connection Request - ReverseConnect { - /// Receipt to be returned by the reverse connection - receipt: Vec, - /// Sender's peer info - peer_info: PeerInfo, - }, - // XXX: WebRTC -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// - -/// Default DHT Schema (DFLT) -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DHTSchemaDFLT { - /// Owner subkey count - pub o_cnt: u16, -} - -impl DHTSchemaDFLT { - const FCC: [u8; 4] = *b"DFLT"; - const FIXED_SIZE: usize = 6; - - /// Build the data representation of the schema - pub fn compile(&self) -> Vec { - let mut out = Vec::::with_capacity(Self::FIXED_SIZE); - // kind - out.extend_from_slice(&Self::FCC); - // o_cnt - out.extend_from_slice(&self.o_cnt.to_le_bytes()); - out - } - - /// Get the number of subkeys this schema allocates - pub fn subkey_count(&self) -> usize { - self.o_cnt as usize - } - /// Get the data size of this schema beyond the size of the structure itself - pub fn data_size(&self) -> usize { - 0 - } -} - -impl TryFrom<&[u8]> for DHTSchemaDFLT { - type Error = VeilidAPIError; - fn try_from(b: &[u8]) -> Result { - if b.len() != Self::FIXED_SIZE { - apibail_generic!("invalid size"); - } - if &b[0..4] != &Self::FCC { - apibail_generic!("wrong fourcc"); - } - - let o_cnt = u16::from_le_bytes(b[4..6].try_into().map_err(VeilidAPIError::internal)?); - - Ok(Self { o_cnt }) - } -} - -/// Simple DHT Schema (SMPL) Member -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DHTSchemaSMPLMember { - /// Member key - pub m_key: PublicKey, - /// Member subkey countanyway, - pub m_cnt: u16, -} - -/// Simple DHT Schema (SMPL) -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DHTSchemaSMPL { - /// Owner subkey count - pub o_cnt: u16, - /// Members - pub members: Vec, -} - -impl DHTSchemaSMPL { - const FCC: [u8; 4] = *b"SMPL"; - const FIXED_SIZE: usize = 6; - - /// Build the data representation of the schema - pub fn compile(&self) -> Vec { - let mut out = Vec::::with_capacity( - Self::FIXED_SIZE + (self.members.len() * (PUBLIC_KEY_LENGTH + 2)), - ); - // kind - out.extend_from_slice(&Self::FCC); - // o_cnt - out.extend_from_slice(&self.o_cnt.to_le_bytes()); - // members - for m in self.members { - // m_key - out.extend_from_slice(&m.m_key.bytes); - // m_cnt - out.extend_from_slice(&m.m_cnt.to_le_bytes()); - } - out - } - - /// Get the number of subkeys this schema allocates - pub fn subkey_count(&self) -> usize { - self.members - .iter() - .fold(self.o_cnt as usize, |acc, x| acc + (x.m_cnt as usize)) - } - - /// Get the data size of this schema beyond the size of the structure itself - pub fn data_size(&self) -> usize { - self.members.len() * mem::size_of::() - } -} - -impl TryFrom<&[u8]> for DHTSchemaSMPL { - type Error = VeilidAPIError; - fn try_from(b: &[u8]) -> Result { - if b.len() != Self::FIXED_SIZE { - apibail_generic!("invalid size"); - } - if &b[0..4] != &Self::FCC { - apibail_generic!("wrong fourcc"); - } - if (b.len() - Self::FIXED_SIZE) % (PUBLIC_KEY_LENGTH + 2) != 0 { - apibail_generic!("invalid member length"); - } - - let o_cnt = u16::from_le_bytes(b[4..6].try_into().map_err(VeilidAPIError::internal)?); - - let members_len = (b.len() - Self::FIXED_SIZE) / (PUBLIC_KEY_LENGTH + 2); - let mut members: Vec = Vec::with_capacity(members_len); - for n in 0..members_len { - let mstart = Self::FIXED_SIZE + n * (PUBLIC_KEY_LENGTH + 2); - let m_key = PublicKey::try_from(&b[mstart..mstart + PUBLIC_KEY_LENGTH]) - .map_err(VeilidAPIError::internal)?; - let m_cnt = u16::from_le_bytes( - b[mstart + PUBLIC_KEY_LENGTH..mstart + PUBLIC_KEY_LENGTH + 2] - .try_into() - .map_err(VeilidAPIError::internal)?, - ); - members.push(DHTSchemaSMPLMember { m_key, m_cnt }); - } - - Ok(Self { o_cnt, members }) - } -} - -/// Enum over all the supported DHT Schemas -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -#[serde(tag = "kind")] -pub enum DHTSchema { - DFLT(DHTSchemaDFLT), - SMPL(DHTSchemaSMPL), -} - -impl DHTSchema { - pub fn dflt(o_cnt: u16) -> DHTSchema { - DHTSchema::DFLT(DHTSchemaDFLT { o_cnt }) - } - pub fn smpl(o_cnt: u16, members: Vec) -> DHTSchema { - DHTSchema::SMPL(DHTSchemaSMPL { o_cnt, members }) - } - - /// Build the data representation of the schema - pub fn compile(&self) -> Vec { - match self { - DHTSchema::DFLT(d) => d.compile(), - DHTSchema::SMPL(s) => s.compile(), - } - } - - /// Get the number of subkeys this schema allocates - pub fn subkey_count(&self) -> usize { - match self { - DHTSchema::DFLT(d) => d.subkey_count(), - DHTSchema::SMPL(s) => s.subkey_count(), - } - } - - /// Get the data size of this schema beyond the size of the structure itself - pub fn data_size(&self) -> usize { - match self { - DHTSchema::DFLT(d) => d.data_size(), - DHTSchema::SMPL(s) => s.data_size(), - } - } -} - -impl Default for DHTSchema { - fn default() -> Self { - Self::dflt(1) - } -} - -impl TryFrom<&[u8]> for DHTSchema { - type Error = VeilidAPIError; - fn try_from(b: &[u8]) -> Result { - if b.len() < 4 { - apibail_generic!("invalid size"); - } - let fcc: [u8; 4] = b[0..4].try_into().unwrap(); - match fcc { - DHTSchemaDFLT::FCC => Ok(DHTSchema::DFLT(DHTSchemaDFLT::try_from(b)?)), - DHTSchemaSMPL::FCC => Ok(DHTSchema::SMPL(DHTSchemaSMPL::try_from(b)?)), - _ => { - apibail_generic!("unknown fourcc"); - } - } - } -} - -/// DHT Record Descriptor -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DHTRecordDescriptor { - pub owner: PublicKey, - pub schema: DHTSchema, -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -#[derive( - Copy, - Clone, - Debug, - PartialOrd, - PartialEq, - Eq, - Ord, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum TunnelMode { - Raw, - Turn, -} - -#[derive( - Copy, - Clone, - Debug, - PartialOrd, - PartialEq, - Eq, - Ord, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum TunnelError { - BadId, // Tunnel ID was rejected - NoEndpoint, // Endpoint was unreachable - RejectedMode, // Endpoint couldn't provide mode - NoCapacity, // Endpoint is full -} - -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct TunnelEndpoint { - pub mode: TunnelMode, - pub description: String, // XXX: TODO -} - -impl Default for TunnelEndpoint { - fn default() -> Self { - Self { - mode: TunnelMode::Raw, - description: "".to_string(), - } - } -} - -#[derive( - Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct FullTunnel { - pub id: TunnelId, - pub timeout: TimestampDuration, - pub local: TunnelEndpoint, - pub remote: TunnelEndpoint, -} - -#[derive( - Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct PartialTunnel { - pub id: TunnelId, - pub timeout: TimestampDuration, - pub local: TunnelEndpoint, -} diff --git a/veilid-core/src/veilid_api/aligned_u64.rs b/veilid-core/src/veilid_api/types/aligned_u64.rs similarity index 85% rename from veilid-core/src/veilid_api/aligned_u64.rs rename to veilid-core/src/veilid_api/types/aligned_u64.rs index 31c161aa..6a64b86d 100644 --- a/veilid-core/src/veilid_api/aligned_u64.rs +++ b/veilid-core/src/veilid_api/types/aligned_u64.rs @@ -120,3 +120,17 @@ impl AlignedU64 { Self(self.0.saturating_sub(rhs.0)) } } + +///////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Microseconds since epoch +pub type Timestamp = AlignedU64; +pub fn get_aligned_timestamp() -> Timestamp { + get_timestamp().into() +} +/// Microseconds duration +pub type TimestampDuration = AlignedU64; +/// Request/Response matching id +pub type OperationId = AlignedU64; +/// Number of bytes +pub type ByteCount = AlignedU64; diff --git a/veilid-core/src/veilid_api/types/app_message_call.rs b/veilid-core/src/veilid_api/types/app_message_call.rs new file mode 100644 index 00000000..f3c24f3a --- /dev/null +++ b/veilid-core/src/veilid_api/types/app_message_call.rs @@ -0,0 +1,32 @@ +use super::*; + +/// Direct statement blob passed to hosting application for processing +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidAppMessage { + /// Some(sender) if the message was sent directly, None if received via a private/safety route + #[serde(with = "opt_json_as_string")] + pub sender: Option, + /// The content of the message to deliver to the application + #[serde(with = "json_as_base64")] + pub message: Vec, +} + +/// Direct question blob passed to hosting application for processing to send an eventual AppReply +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidAppCall { + /// Some(sender) if the request was sent directly, None if received via a private/safety route + #[serde(with = "opt_json_as_string")] + pub sender: Option, + /// The content of the request to deliver to the application + #[serde(with = "json_as_base64")] + pub message: Vec, + /// The id to reply to + #[serde(with = "json_as_string")] + pub id: OperationId, +} diff --git a/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs b/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs new file mode 100644 index 00000000..ad2fac30 --- /dev/null +++ b/veilid-core/src/veilid_api/types/dht/dht_record_descriptor.rs @@ -0,0 +1,35 @@ +use super::*; + +/// DHT Record Descriptor +#[derive( + Debug, + Clone, + PartialOrd, + Ord, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DHTRecordDescriptor { + owner: PublicKey, + schema: DHTSchema, +} + +impl DHTRecordDescriptor { + pub fn new(owner: PublicKey, schema: DHTSchema) -> Self { + Self { owner, schema } + } + + pub fn owner(&self) -> PublicKey { + self.owner + } + + pub fn schema(&self) -> DHTSchema { + self.schema + } +} diff --git a/veilid-core/src/veilid_api/types/dht/mod.rs b/veilid-core/src/veilid_api/types/dht/mod.rs new file mode 100644 index 00000000..55ca7803 --- /dev/null +++ b/veilid-core/src/veilid_api/types/dht/mod.rs @@ -0,0 +1,16 @@ +mod dht_record_descriptor; +mod schema; +mod value_data; + +use super::*; + +pub use dht_record_descriptor::*; +pub use schema::*; +pub use value_data::*; + +/// Value subkey +pub type ValueSubkey = u32; +/// Value subkey range +pub type ValueSubkeyRange = (u32, u32); +/// Value sequence number +pub type ValueSeqNum = u32; diff --git a/veilid-core/src/veilid_api/types/dht/schema/dflt.rs b/veilid-core/src/veilid_api/types/dht/schema/dflt.rs new file mode 100644 index 00000000..332ecd90 --- /dev/null +++ b/veilid-core/src/veilid_api/types/dht/schema/dflt.rs @@ -0,0 +1,61 @@ +use super::*; + +/// Default DHT Schema (DFLT) +#[derive( + Debug, + Clone, + PartialEq, + Eq, + Ord, + PartialOrd, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DHTSchemaDFLT { + /// Owner subkey count + pub o_cnt: u16, +} + +impl DHTSchemaDFLT { + pub const FCC: [u8; 4] = *b"DFLT"; + pub const FIXED_SIZE: usize = 6; + + /// Build the data representation of the schema + pub fn compile(&self) -> Vec { + let mut out = Vec::::with_capacity(Self::FIXED_SIZE); + // kind + out.extend_from_slice(&Self::FCC); + // o_cnt + out.extend_from_slice(&self.o_cnt.to_le_bytes()); + out + } + + /// Get the number of subkeys this schema allocates + pub fn subkey_count(&self) -> usize { + self.o_cnt as usize + } + /// Get the data size of this schema beyond the size of the structure itself + pub fn data_size(&self) -> usize { + 0 + } +} + +impl TryFrom<&[u8]> for DHTSchemaDFLT { + type Error = VeilidAPIError; + fn try_from(b: &[u8]) -> Result { + if b.len() != Self::FIXED_SIZE { + apibail_generic!("invalid size"); + } + if &b[0..4] != &Self::FCC { + apibail_generic!("wrong fourcc"); + } + + let o_cnt = u16::from_le_bytes(b[4..6].try_into().map_err(VeilidAPIError::internal)?); + + Ok(Self { o_cnt }) + } +} diff --git a/veilid-core/src/veilid_api/types/dht/schema/mod.rs b/veilid-core/src/veilid_api/types/dht/schema/mod.rs new file mode 100644 index 00000000..160f1422 --- /dev/null +++ b/veilid-core/src/veilid_api/types/dht/schema/mod.rs @@ -0,0 +1,84 @@ +mod dflt; +mod smpl; + +use super::*; + +pub use dflt::*; +pub use smpl::*; + +/// Enum over all the supported DHT Schemas +#[derive( + Debug, + Clone, + PartialEq, + Eq, + Ord, + PartialOrd, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +#[serde(tag = "kind")] +pub enum DHTSchema { + DFLT(DHTSchemaDFLT), + SMPL(DHTSchemaSMPL), +} + +impl DHTSchema { + pub fn dflt(o_cnt: u16) -> DHTSchema { + DHTSchema::DFLT(DHTSchemaDFLT { o_cnt }) + } + pub fn smpl(o_cnt: u16, members: Vec) -> DHTSchema { + DHTSchema::SMPL(DHTSchemaSMPL { o_cnt, members }) + } + + /// Build the data representation of the schema + pub fn compile(&self) -> Vec { + match self { + DHTSchema::DFLT(d) => d.compile(), + DHTSchema::SMPL(s) => s.compile(), + } + } + + /// Get the number of subkeys this schema allocates + pub fn subkey_count(&self) -> usize { + match self { + DHTSchema::DFLT(d) => d.subkey_count(), + DHTSchema::SMPL(s) => s.subkey_count(), + } + } + + /// Get the data size of this schema beyond the size of the structure itself + pub fn data_size(&self) -> usize { + match self { + DHTSchema::DFLT(d) => d.data_size(), + DHTSchema::SMPL(s) => s.data_size(), + } + } +} + +impl Default for DHTSchema { + fn default() -> Self { + Self::dflt(1) + } +} + +impl TryFrom<&[u8]> for DHTSchema { + type Error = VeilidAPIError; + fn try_from(b: &[u8]) -> Result { + if b.len() < 4 { + apibail_generic!("invalid size"); + } + let fcc: [u8; 4] = b[0..4].try_into().unwrap(); + match fcc { + DHTSchemaDFLT::FCC => Ok(DHTSchema::DFLT(DHTSchemaDFLT::try_from(b)?)), + DHTSchemaSMPL::FCC => Ok(DHTSchema::SMPL(DHTSchemaSMPL::try_from(b)?)), + _ => { + apibail_generic!("unknown fourcc"); + } + } + } +} diff --git a/veilid-core/src/veilid_api/types/dht/schema/smpl.rs b/veilid-core/src/veilid_api/types/dht/schema/smpl.rs new file mode 100644 index 00000000..6ae08a26 --- /dev/null +++ b/veilid-core/src/veilid_api/types/dht/schema/smpl.rs @@ -0,0 +1,114 @@ +use super::*; + +/// Simple DHT Schema (SMPL) Member +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DHTSchemaSMPLMember { + /// Member key + pub m_key: PublicKey, + /// Member subkey countanyway, + pub m_cnt: u16, +} + +/// Simple DHT Schema (SMPL) +#[derive( + Debug, + Clone, + PartialEq, + Eq, + Ord, + PartialOrd, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DHTSchemaSMPL { + /// Owner subkey count + pub o_cnt: u16, + /// Members + pub members: Vec, +} + +impl DHTSchemaSMPL { + pub const FCC: [u8; 4] = *b"SMPL"; + pub const FIXED_SIZE: usize = 6; + + /// Build the data representation of the schema + pub fn compile(&self) -> Vec { + let mut out = Vec::::with_capacity( + Self::FIXED_SIZE + (self.members.len() * (PUBLIC_KEY_LENGTH + 2)), + ); + // kind + out.extend_from_slice(&Self::FCC); + // o_cnt + out.extend_from_slice(&self.o_cnt.to_le_bytes()); + // members + for m in self.members { + // m_key + out.extend_from_slice(&m.m_key.bytes); + // m_cnt + out.extend_from_slice(&m.m_cnt.to_le_bytes()); + } + out + } + + /// Get the number of subkeys this schema allocates + pub fn subkey_count(&self) -> usize { + self.members + .iter() + .fold(self.o_cnt as usize, |acc, x| acc + (x.m_cnt as usize)) + } + + /// Get the data size of this schema beyond the size of the structure itself + pub fn data_size(&self) -> usize { + self.members.len() * mem::size_of::() + } +} + +impl TryFrom<&[u8]> for DHTSchemaSMPL { + type Error = VeilidAPIError; + fn try_from(b: &[u8]) -> Result { + if b.len() != Self::FIXED_SIZE { + apibail_generic!("invalid size"); + } + if &b[0..4] != &Self::FCC { + apibail_generic!("wrong fourcc"); + } + if (b.len() - Self::FIXED_SIZE) % (PUBLIC_KEY_LENGTH + 2) != 0 { + apibail_generic!("invalid member length"); + } + + let o_cnt = u16::from_le_bytes(b[4..6].try_into().map_err(VeilidAPIError::internal)?); + + let members_len = (b.len() - Self::FIXED_SIZE) / (PUBLIC_KEY_LENGTH + 2); + let mut members: Vec = Vec::with_capacity(members_len); + for n in 0..members_len { + let mstart = Self::FIXED_SIZE + n * (PUBLIC_KEY_LENGTH + 2); + let m_key = PublicKey::try_from(&b[mstart..mstart + PUBLIC_KEY_LENGTH]) + .map_err(VeilidAPIError::internal)?; + let m_cnt = u16::from_le_bytes( + b[mstart + PUBLIC_KEY_LENGTH..mstart + PUBLIC_KEY_LENGTH + 2] + .try_into() + .map_err(VeilidAPIError::internal)?, + ); + members.push(DHTSchemaSMPLMember { m_key, m_cnt }); + } + + Ok(Self { o_cnt, members }) + } +} diff --git a/veilid-core/src/veilid_api/types/dht/value_data.rs b/veilid-core/src/veilid_api/types/dht/value_data.rs new file mode 100644 index 00000000..0cb34143 --- /dev/null +++ b/veilid-core/src/veilid_api/types/dht/value_data.rs @@ -0,0 +1,64 @@ +use super::*; + +#[derive( + Clone, + Debug, + Default, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct ValueData { + seq: ValueSeqNum, + data: Vec, + writer: PublicKey, +} +impl ValueData { + pub const MAX_LEN: usize = 32768; + + pub fn new(data: Vec, writer: PublicKey) -> Self { + assert!(data.len() <= Self::MAX_LEN); + Self { + seq: 0, + data, + writer, + } + } + pub fn new_with_seq(seq: ValueSeqNum, data: Vec, writer: PublicKey) -> Self { + assert!(data.len() <= Self::MAX_LEN); + Self { seq, data, writer } + } + + pub fn seq(&self) -> ValueSeqNum { + self.seq + } + + pub fn writer(&self) -> &PublicKey { + &self.writer + } + + pub fn data(&self) -> &[u8] { + &self.data + } + + pub fn with_data_mut(&mut self, f: F) -> R + where + F: FnOnce(&mut Vec) -> R, + { + let out = f(&mut self.data); + assert!(self.data.len() <= Self::MAX_LEN); + self.seq += 1; + out + } + + pub fn total_size(&self) -> usize { + mem::size_of::() + self.data.len() + } +} diff --git a/veilid-core/src/veilid_api/types/fourcc.rs b/veilid-core/src/veilid_api/types/fourcc.rs new file mode 100644 index 00000000..c634b796 --- /dev/null +++ b/veilid-core/src/veilid_api/types/fourcc.rs @@ -0,0 +1,52 @@ +use super::*; + +/// FOURCC code +#[derive( + Copy, + Default, + Clone, + Hash, + PartialOrd, + Ord, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes, PartialOrd, Ord, PartialEq, Eq, Hash))] +pub struct FourCC(pub [u8; 4]); + +impl From<[u8; 4]> for FourCC { + fn from(b: [u8; 4]) -> Self { + Self(b) + } +} +impl TryFrom<&[u8]> for FourCC { + type Error = VeilidAPIError; + fn try_from(b: &[u8]) -> Result { + Ok(Self(b.try_into().map_err(VeilidAPIError::generic)?)) + } +} + +impl fmt::Display for FourCC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{}", String::from_utf8_lossy(&self.0)) + } +} +impl fmt::Debug for FourCC { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{}", String::from_utf8_lossy(&self.0)) + } +} + +impl FromStr for FourCC { + type Err = VeilidAPIError; + fn from_str(s: &str) -> Result { + Ok(Self( + s.as_bytes().try_into().map_err(VeilidAPIError::generic)?, + )) + } +} diff --git a/veilid-core/src/veilid_api/types/mod.rs b/veilid-core/src/veilid_api/types/mod.rs new file mode 100644 index 00000000..6dea3db5 --- /dev/null +++ b/veilid-core/src/veilid_api/types/mod.rs @@ -0,0 +1,21 @@ +mod aligned_u64; +mod app_message_call; +mod dht; +mod fourcc; +mod safety; +mod stats; +mod tunnel; +mod veilid_log; +mod veilid_state; + +use super::*; + +pub use aligned_u64::*; +pub use app_message_call::*; +pub use dht::*; +pub use fourcc::*; +pub use safety::*; +pub use stats::*; +pub use tunnel::*; +pub use veilid_log::*; +pub use veilid_state::*; diff --git a/veilid-core/src/veilid_api/types/safety.rs b/veilid-core/src/veilid_api/types/safety.rs new file mode 100644 index 00000000..27cb46ba --- /dev/null +++ b/veilid-core/src/veilid_api/types/safety.rs @@ -0,0 +1,125 @@ +use super::*; + +// Ordering here matters, >= is used to check strength of sequencing requirement +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum Sequencing { + NoPreference, + PreferOrdered, + EnsureOrdered, +} + +impl Default for Sequencing { + fn default() -> Self { + Self::NoPreference + } +} + +// Ordering here matters, >= is used to check strength of stability requirement +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum Stability { + LowLatency, + Reliable, +} + +impl Default for Stability { + fn default() -> Self { + Self::LowLatency + } +} + +/// The choice of safety route to include in compiled routes +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum SafetySelection { + /// Don't use a safety route, only specify the sequencing preference + Unsafe(Sequencing), + /// Use a safety route and parameters specified by a SafetySpec + Safe(SafetySpec), +} + +impl SafetySelection { + pub fn get_sequencing(&self) -> Sequencing { + match self { + SafetySelection::Unsafe(seq) => *seq, + SafetySelection::Safe(ss) => ss.sequencing, + } + } +} + +impl Default for SafetySelection { + fn default() -> Self { + Self::Unsafe(Sequencing::NoPreference) + } +} + +/// Options for safety routes (sender privacy) +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct SafetySpec { + /// preferred safety route set id if it still exists + pub preferred_route: Option, + /// must be greater than 0 + pub hop_count: usize, + /// prefer reliability over speed + pub stability: Stability, + /// prefer connection-oriented sequenced protocols + pub sequencing: Sequencing, +} diff --git a/veilid-core/src/veilid_api/types/stats.rs b/veilid-core/src/veilid_api/types/stats.rs new file mode 100644 index 00000000..8ea79e11 --- /dev/null +++ b/veilid-core/src/veilid_api/types/stats.rs @@ -0,0 +1,113 @@ +use super::*; + +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct LatencyStats { + #[serde(with = "json_as_string")] + pub fastest: TimestampDuration, // fastest latency in the ROLLING_LATENCIES_SIZE last latencies + #[serde(with = "json_as_string")] + pub average: TimestampDuration, // average latency over the ROLLING_LATENCIES_SIZE last latencies + #[serde(with = "json_as_string")] + pub slowest: TimestampDuration, // slowest latency in the ROLLING_LATENCIES_SIZE last latencies +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct TransferStats { + #[serde(with = "json_as_string")] + pub total: ByteCount, // total amount transferred ever + #[serde(with = "json_as_string")] + pub maximum: ByteCount, // maximum rate over the ROLLING_TRANSFERS_SIZE last amounts + #[serde(with = "json_as_string")] + pub average: ByteCount, // average rate over the ROLLING_TRANSFERS_SIZE last amounts + #[serde(with = "json_as_string")] + pub minimum: ByteCount, // minimum rate over the ROLLING_TRANSFERS_SIZE last amounts +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct TransferStatsDownUp { + pub down: TransferStats, + pub up: TransferStats, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct RPCStats { + pub messages_sent: u32, // number of rpcs that have been sent in the total_time range + pub messages_rcvd: u32, // number of rpcs that have been received in the total_time range + pub questions_in_flight: u32, // number of questions issued that have yet to be answered + #[serde(with = "opt_json_as_string")] + pub last_question_ts: Option, // when the peer was last questioned (either successfully or not) and we wanted an answer + #[serde(with = "opt_json_as_string")] + pub last_seen_ts: Option, // when the peer was last seen for any reason, including when we first attempted to reach out to it + #[serde(with = "opt_json_as_string")] + pub first_consecutive_seen_ts: Option, // the timestamp of the first consecutive proof-of-life for this node (an answer or received question) + pub recent_lost_answers: u32, // number of answers that have been lost since we lost reliability + pub failed_to_send: u32, // number of messages that have failed to send since we last successfully sent one +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct PeerStats { + #[serde(with = "json_as_string")] + pub time_added: Timestamp, // when the peer was added to the routing table + pub rpc_stats: RPCStats, // information about RPCs + pub latency: Option, // latencies for communications with the peer + pub transfer: TransferStatsDownUp, // Stats for communications with the peer +} diff --git a/veilid-core/src/veilid_api/types/tunnel.rs b/veilid-core/src/veilid_api/types/tunnel.rs new file mode 100644 index 00000000..968c7695 --- /dev/null +++ b/veilid-core/src/veilid_api/types/tunnel.rs @@ -0,0 +1,83 @@ +use super::*; + +/// Tunnel identifier +pub type TunnelId = AlignedU64; + +#[derive( + Copy, + Clone, + Debug, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum TunnelMode { + Raw, + Turn, +} + +#[derive( + Copy, + Clone, + Debug, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum TunnelError { + BadId, // Tunnel ID was rejected + NoEndpoint, // Endpoint was unreachable + RejectedMode, // Endpoint couldn't provide mode + NoCapacity, // Endpoint is full +} + +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct TunnelEndpoint { + pub mode: TunnelMode, + pub description: String, // XXX: TODO +} + +impl Default for TunnelEndpoint { + fn default() -> Self { + Self { + mode: TunnelMode::Raw, + description: "".to_string(), + } + } +} + +#[derive( + Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct FullTunnel { + pub id: TunnelId, + pub timeout: TimestampDuration, + pub local: TunnelEndpoint, + pub remote: TunnelEndpoint, +} + +#[derive( + Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct PartialTunnel { + pub id: TunnelId, + pub timeout: TimestampDuration, + pub local: TunnelEndpoint, +} diff --git a/veilid-core/src/veilid_api/types/veilid_log.rs b/veilid-core/src/veilid_api/types/veilid_log.rs new file mode 100644 index 00000000..5eab945c --- /dev/null +++ b/veilid-core/src/veilid_api/types/veilid_log.rs @@ -0,0 +1,88 @@ +use super::*; + +/// Log level for VeilidCore +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Copy, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum VeilidLogLevel { + Error = 1, + Warn, + Info, + Debug, + Trace, +} + +impl VeilidLogLevel { + pub fn from_tracing_level(level: tracing::Level) -> VeilidLogLevel { + match level { + tracing::Level::ERROR => VeilidLogLevel::Error, + tracing::Level::WARN => VeilidLogLevel::Warn, + tracing::Level::INFO => VeilidLogLevel::Info, + tracing::Level::DEBUG => VeilidLogLevel::Debug, + tracing::Level::TRACE => VeilidLogLevel::Trace, + } + } + pub fn from_log_level(level: log::Level) -> VeilidLogLevel { + match level { + log::Level::Error => VeilidLogLevel::Error, + log::Level::Warn => VeilidLogLevel::Warn, + log::Level::Info => VeilidLogLevel::Info, + log::Level::Debug => VeilidLogLevel::Debug, + log::Level::Trace => VeilidLogLevel::Trace, + } + } + pub fn to_tracing_level(&self) -> tracing::Level { + match self { + Self::Error => tracing::Level::ERROR, + Self::Warn => tracing::Level::WARN, + Self::Info => tracing::Level::INFO, + Self::Debug => tracing::Level::DEBUG, + Self::Trace => tracing::Level::TRACE, + } + } + pub fn to_log_level(&self) -> log::Level { + match self { + Self::Error => log::Level::Error, + Self::Warn => log::Level::Warn, + Self::Info => log::Level::Info, + Self::Debug => log::Level::Debug, + Self::Trace => log::Level::Trace, + } + } +} + +impl fmt::Display for VeilidLogLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let text = match self { + Self::Error => "ERROR", + Self::Warn => "WARN", + Self::Info => "INFO", + Self::Debug => "DEBUG", + Self::Trace => "TRACE", + }; + write!(f, "{}", text) + } +} + +/// A VeilidCore log message with optional backtrace +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidLog { + pub log_level: VeilidLogLevel, + pub message: String, + pub backtrace: Option, +} diff --git a/veilid-core/src/veilid_api/types/veilid_state.rs b/veilid-core/src/veilid_api/types/veilid_state.rs new file mode 100644 index 00000000..d2bb50b2 --- /dev/null +++ b/veilid-core/src/veilid_api/types/veilid_state.rs @@ -0,0 +1,144 @@ +use super::*; + +/// Attachment abstraction for network 'signal strength' +#[derive( + Debug, + PartialEq, + Eq, + Clone, + Copy, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum AttachmentState { + Detached, + Attaching, + AttachedWeak, + AttachedGood, + AttachedStrong, + FullyAttached, + OverAttached, + Detaching, +} + +impl fmt::Display for AttachmentState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let out = match self { + AttachmentState::Attaching => "attaching".to_owned(), + AttachmentState::AttachedWeak => "attached_weak".to_owned(), + AttachmentState::AttachedGood => "attached_good".to_owned(), + AttachmentState::AttachedStrong => "attached_strong".to_owned(), + AttachmentState::FullyAttached => "fully_attached".to_owned(), + AttachmentState::OverAttached => "over_attached".to_owned(), + AttachmentState::Detaching => "detaching".to_owned(), + AttachmentState::Detached => "detached".to_owned(), + }; + write!(f, "{}", out) + } +} + +impl TryFrom for AttachmentState { + type Error = (); + + fn try_from(s: String) -> Result { + Ok(match s.as_str() { + "attaching" => AttachmentState::Attaching, + "attached_weak" => AttachmentState::AttachedWeak, + "attached_good" => AttachmentState::AttachedGood, + "attached_strong" => AttachmentState::AttachedStrong, + "fully_attached" => AttachmentState::FullyAttached, + "over_attached" => AttachmentState::OverAttached, + "detaching" => AttachmentState::Detaching, + "detached" => AttachmentState::Detached, + _ => return Err(()), + }) + } +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidStateAttachment { + pub state: AttachmentState, + pub public_internet_ready: bool, + pub local_network_ready: bool, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct PeerTableData { + pub node_ids: Vec, + pub peer_address: String, + pub peer_stats: PeerStats, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidStateNetwork { + pub started: bool, + #[serde(with = "json_as_string")] + pub bps_down: ByteCount, + #[serde(with = "json_as_string")] + pub bps_up: ByteCount, + pub peers: Vec, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidStateRoute { + pub dead_routes: Vec, + pub dead_remote_routes: Vec, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidStateConfig { + pub config: VeilidConfigInner, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidValueChange { + key: TypedKey, + subkeys: Vec, + count: u32, + value: ValueData, +} + +#[derive(Debug, Clone, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(u8), derive(CheckBytes))] +#[serde(tag = "kind")] +pub enum VeilidUpdate { + Log(VeilidLog), + AppMessage(VeilidAppMessage), + AppCall(VeilidAppCall), + Attachment(VeilidStateAttachment), + Network(VeilidStateNetwork), + Config(VeilidStateConfig), + Route(VeilidStateRoute), + ValueChange(VeilidValueChange), + Shutdown, +} + +#[derive(Debug, Clone, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidState { + pub attachment: VeilidStateAttachment, + pub network: VeilidStateNetwork, + pub config: VeilidStateConfig, +} diff --git a/veilid-core/src/watcher_table.rs b/veilid-core/src/watcher_table.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/veilid-core/tests/web.rs b/veilid-core/tests/web.rs index 5024dfd8..82fb397d 100644 --- a/veilid-core/tests/web.rs +++ b/veilid-core/tests/web.rs @@ -60,6 +60,12 @@ async fn run_test_connection_table() { test_connection_table::test_all().await; } +#[wasm_bindgen_test] +async fn run_test_signed_node_info() { + setup(); + test_signed_node_info::test_all().await; +} + #[wasm_bindgen_test] async fn exec_test_table_store() { setup();