diff --git a/veilid-cli/src/command_processor.rs b/veilid-cli/src/command_processor.rs index cab0aaa2..5bd05bd7 100644 --- a/veilid-cli/src/command_processor.rs +++ b/veilid-cli/src/command_processor.rs @@ -187,8 +187,14 @@ reply - reply to an AppCall not handled directly by the server let ui = self.ui_sender(); spawn_detached_local(async move { match capi.server_debug(rest.unwrap_or_default()).await { - Ok(output) => ui.display_string_dialog("Debug Output", output, callback), - Err(e) => ui.display_string_dialog("Debug Error", e.to_string(), callback), + Ok(output) => { + ui.add_node_event(Level::Debug, output); + ui.send_callback(callback); + } + Err(e) => { + ui.add_node_event(Level::Error, e.to_string()); + ui.send_callback(callback); + } } }); Ok(()) diff --git a/veilid-cli/src/ui.rs b/veilid-cli/src/ui.rs index 5d7e1e4d..177407ee 100644 --- a/veilid-cli/src/ui.rs +++ b/veilid-cli/src/ui.rs @@ -14,8 +14,8 @@ use cursive::CursiveRunnable; use cursive_flexi_logger_view::{CursiveLogWriter, FlexiLoggerView}; // use cursive_multiplex::*; use std::collections::{HashMap, VecDeque}; +use std::io::Write; use thiserror::Error; - ////////////////////////////////////////////////////////////// /// struct Dirty { @@ -454,20 +454,52 @@ impl UI { Self::command_processor(s).start_connection(); } + fn copy_to_clipboard>(s: &mut Cursive, text: S) { + if let Ok(mut clipboard) = arboard::Clipboard::new() { + // X11/Wayland/other system copy + if clipboard.set_text(text.as_ref()).is_ok() { + let color = *Self::inner_mut(s).log_colors.get(&Level::Info).unwrap(); + cursive_flexi_logger_view::push_to_log(StyledString::styled( + format!(">> Copied: {}", text.as_ref()), + color, + )); + } else { + let color = *Self::inner_mut(s).log_colors.get(&Level::Warn).unwrap(); + cursive_flexi_logger_view::push_to_log(StyledString::styled( + format!(">> Could not copy to clipboard"), + color, + )); + } + } else { + // OSC52 clipboard copy for terminals + if std::io::stdout() + .write_all( + format!( + "\x1B]52;c;{}\x07", + data_encoding::BASE64.encode(text.as_ref().as_bytes()), + ) + .as_bytes(), + ) + .is_ok() + { + if std::io::stdout().flush().is_ok() { + let color = *Self::inner_mut(s).log_colors.get(&Level::Info).unwrap(); + cursive_flexi_logger_view::push_to_log(StyledString::styled( + format!(">> Copied: {}", text.as_ref()), + color, + )); + } + } + } + } + fn on_submit_peers_table_view(s: &mut Cursive, _row: usize, index: usize) { let peers_table_view = UI::peers(s); let node_id = peers_table_view .borrow_item(index) .map(|j| j["node_ids"][0].to_string()); if let Some(node_id) = node_id { - let mut clipboard = arboard::Clipboard::new().unwrap(); - clipboard.set_text(node_id.clone()).unwrap(); - - let color = *Self::inner_mut(s).log_colors.get(&Level::Info).unwrap(); - cursive_flexi_logger_view::push_to_log(StyledString::styled( - format!(">> NodeId Copied: {}", node_id), - color, - )); + Self::copy_to_clipboard(s, node_id); } } @@ -967,10 +999,18 @@ impl UISender { pub fn set_config(&mut self, config: &json::JsonValue) { let mut inner = self.inner.lock(); - inner - .ui_state - .node_id - .set(config["network"]["routing_table"]["node_id"].to_string()); + let node_ids = &config["network"]["routing_table"]["node_id"]; + + let mut node_id_str = String::new(); + for l in 0..node_ids.len() { + let nid = &node_ids[l]; + if !node_id_str.is_empty() { + node_id_str.push_str(" "); + } + node_id_str.push_str(nid.to_string().as_ref()); + } + + inner.ui_state.node_id.set(node_id_str); } pub fn set_connection_state(&mut self, state: ConnectionState) { { diff --git a/veilid-core/src/crypto/byte_array_types.rs b/veilid-core/src/crypto/byte_array_types.rs index 8a1f7a01..5590e467 100644 --- a/veilid-core/src/crypto/byte_array_types.rs +++ b/veilid-core/src/crypto/byte_array_types.rs @@ -130,6 +130,7 @@ macro_rules! byte_array_type { Self { bytes } } + // Big endian bit ordering pub fn bit(&self, index: usize) -> bool { assert!(index < ($size * 8)); let bi = index / 8; @@ -152,6 +153,7 @@ macro_rules! byte_array_type { None } + // Big endian nibble ordering pub fn nibble(&self, index: usize) -> u8 { assert!(index < ($size * 2)); let bi = index / 2; diff --git a/veilid-core/src/crypto/tests/test_types.rs b/veilid-core/src/crypto/tests/test_types.rs index 72813ea0..3a544186 100644 --- a/veilid-core/src/crypto/tests/test_types.rs +++ b/veilid-core/src/crypto/tests/test_types.rs @@ -353,10 +353,40 @@ async fn test_operations(vcrypto: CryptoSystemVersion) { assert_eq!(d4.first_nonzero_bit(), Some(0)); } +pub async fn test_crypto_key_ordering() { + let k1 = CryptoKey::new([ + 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + ]); + let k2 = CryptoKey::new([ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + ]); + let k3 = CryptoKey::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 128, + ]); + let k4 = CryptoKey::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, + ]); + let k5 = CryptoKey::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + ]); + + assert!(k2 < k1); + assert!(k3 < k2); + assert!(k4 < k3); + assert!(k5 < k4); +} + pub async fn test_all() { let api = crypto_tests_startup().await; let crypto = api.crypto().unwrap(); + test_crypto_key_ordering().await; + // Test versions for v in VALID_CRYPTO_KINDS { let vcrypto = crypto.get(v).unwrap(); diff --git a/veilid-core/src/routing_table/find_peers.rs b/veilid-core/src/routing_table/find_peers.rs index 54a45796..2373e9be 100644 --- a/veilid-core/src/routing_table/find_peers.rs +++ b/veilid-core/src/routing_table/find_peers.rs @@ -51,6 +51,7 @@ impl RoutingTable { return NetworkResult::invalid_message("unsupported cryptosystem"); }; let own_distance = vcrypto.distance(&own_node_id.value, &key.value); + let vcrypto2 = vcrypto.clone(); let filter = Box::new( move |rti: &RoutingTableInner, opt_entry: Option>| { @@ -98,6 +99,46 @@ impl RoutingTable { }, ); + // xxx test + // Validate peers returned are, in fact, closer to the key than the node we sent this to + let valid = match Self::verify_peers_closer(vcrypto2, own_node_id, key, &closest_nodes) { + Ok(v) => v, + Err(e) => { + panic!("missing cryptosystem in peers node ids: {}", e); + } + }; + if !valid { + panic!("non-closer peers returned"); + } + NetworkResult::value(closest_nodes) } + + /// Determine if set of peers is closer to key_near than key_far + pub(crate) fn verify_peers_closer( + vcrypto: CryptoSystemVersion, + key_far: TypedKey, + key_near: TypedKey, + peers: &[PeerInfo], + ) -> EyreResult { + let kind = vcrypto.kind(); + + if key_far.kind != kind || key_near.kind != kind { + bail!("keys all need the same cryptosystem"); + } + + let mut closer = true; + for peer in peers { + let Some(key_peer) = peer.node_ids().get(kind) else { + bail!("peers need to have a key with the same cryptosystem"); + }; + let d_near = vcrypto.distance(&key_near.value, &key_peer.value); + let d_far = vcrypto.distance(&key_far.value, &key_peer.value); + if d_far < d_near { + closer = false; + } + } + + Ok(closer) + } } diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index eb57b02f..f9b41323 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -404,37 +404,6 @@ impl RPCProcessor { routing_table.signed_node_info_is_valid_in_routing_domain(routing_domain, &signed_node_info) } - /// Determine if set of peers is closer to key_near than key_far - fn verify_peers_closer( - &self, - vcrypto: CryptoSystemVersion, - key_far: TypedKey, - key_near: TypedKey, - peers: &[PeerInfo], - ) -> Result { - let kind = vcrypto.kind(); - - if key_far.kind != kind || key_near.kind != kind { - return Err(RPCError::internal("keys all need the same cryptosystem")); - } - - let mut closer = true; - for peer in peers { - let Some(key_peer) = peer.node_ids().get(kind) else { - return Err(RPCError::invalid_format( - "peers need to have a key with the same cryptosystem", - )); - }; - let d_near = vcrypto.distance(&key_near.value, &key_peer.value); - let d_far = vcrypto.distance(&key_far.value, &key_peer.value); - if d_far < d_near { - closer = false; - } - } - - Ok(closer) - } - ////////////////////////////////////////////////////////////////////// /// Search the DHT for a single node closest to a key and add it to the routing table and return the node reference diff --git a/veilid-core/src/rpc_processor/rpc_get_value.rs b/veilid-core/src/rpc_processor/rpc_get_value.rs index 352eb192..cd82f067 100644 --- a/veilid-core/src/rpc_processor/rpc_get_value.rs +++ b/veilid-core/src/rpc_processor/rpc_get_value.rs @@ -76,15 +76,13 @@ impl RPCProcessor { let (value, peers, descriptor) = get_value_a.destructure(); // Validate peers returned are, in fact, closer to the key than the node we sent this to - let valid = match self.verify_peers_closer(vcrypto, target_node_id, key, &peers) { + let valid = match RoutingTable::verify_peers_closer(vcrypto, target_node_id, key, &peers) { Ok(v) => v, Err(e) => { - if matches!(e, RPCError::Internal(_)) { - return Err(e); - } - return Ok(NetworkResult::invalid_message( - "missing cryptosystem in peers node ids", - )); + return Ok(NetworkResult::invalid_message(format!( + "missing cryptosystem in peers node ids: {}", + e + ))); } }; if !valid { diff --git a/veilid-core/src/rpc_processor/rpc_set_value.rs b/veilid-core/src/rpc_processor/rpc_set_value.rs index a12ae922..e36852af 100644 --- a/veilid-core/src/rpc_processor/rpc_set_value.rs +++ b/veilid-core/src/rpc_processor/rpc_set_value.rs @@ -83,15 +83,13 @@ impl RPCProcessor { let (set, value, peers) = set_value_a.destructure(); // Validate peers returned are, in fact, closer to the key than the node we sent this to - let valid = match self.verify_peers_closer(vcrypto, target_node_id, key, &peers) { + let valid = match RoutingTable::verify_peers_closer(vcrypto, target_node_id, key, &peers) { Ok(v) => v, Err(e) => { - if matches!(e, RPCError::Internal(_)) { - return Err(e); - } - return Ok(NetworkResult::invalid_message( - "missing cryptosystem in peers node ids", - )); + return Ok(NetworkResult::invalid_message(format!( + "missing cryptosystem in peers node ids: {}", + e + ))); } }; if !valid { diff --git a/veilid-core/src/storage_manager/mod.rs b/veilid-core/src/storage_manager/mod.rs index dbef0bc8..56a906bc 100644 --- a/veilid-core/src/storage_manager/mod.rs +++ b/veilid-core/src/storage_manager/mod.rs @@ -341,7 +341,6 @@ impl StorageManager { } else { ValueData::new(data, writer.key) }; - let seq = value_data.seq(); // Validate with schema if !schema.check_subkey_value_data(descriptor.owner(), subkey, &value_data) { @@ -374,7 +373,6 @@ impl StorageManager { drop(inner); // Use the safety selection we opened the record with - let final_signed_value_data = self .outbound_set_value( rpc_processor, @@ -386,13 +384,12 @@ impl StorageManager { ) .await?; - // If we got a new value back then write it to the opened record - if final_signed_value_data.value_data().seq() != seq { - let mut inner = self.lock().await?; - inner - .handle_set_local_value(key, subkey, final_signed_value_data.clone()) - .await?; - } + // Whatever record we got back, store it locally, might be newer than the one we asked to save + let mut inner = self.lock().await?; + inner + .handle_set_local_value(key, subkey, final_signed_value_data.clone()) + .await?; + Ok(Some(final_signed_value_data.into_value_data())) } diff --git a/veilid-python/tests/test_dht.py b/veilid-python/tests/test_dht.py index 630a6d12..af17bc64 100644 --- a/veilid-python/tests/test_dht.py +++ b/veilid-python/tests/test_dht.py @@ -67,6 +67,9 @@ async def test_set_get_dht_value(api_connection: veilid.VeilidAPI): vd2 = await rc.get_dht_value(rec.key, 0, False) assert vd2 != None + print("vd: {}", vd.__dict__) + print("vd2: {}", vd2.__dict__) + assert vd == vd2 await rc.close_dht_record(rec.key) diff --git a/veilid-python/veilid/types.py b/veilid-python/veilid/types.py index d1766fab..7116550e 100644 --- a/veilid-python/veilid/types.py +++ b/veilid-python/veilid/types.py @@ -2,6 +2,7 @@ import base64 import json from enum import StrEnum from typing import Any, Optional, Self, Tuple +from functools import total_ordering #################################################################### @@ -323,6 +324,7 @@ class DHTRecordDescriptor: return self.__dict__ +# @total_ordering class ValueData: seq: ValueSeqNum data: bytes @@ -333,6 +335,21 @@ class ValueData: self.data = data self.writer = writer + # def __lt__(self, other): + # return self.data < other.data + + # def __eq__(self, other): + # return self.cgpa == other.cgpa + + # def __le__(self, other): + # return self.cgpa<= other.cgpa + + # def __ge__(self, other): + # return self.cgpa>= other.cgpa + + # def __ne__(self, other): + # return self.cgpa != other.cgpa + @classmethod def from_json(cls, j: dict) -> Self: return cls(