allow_offline flag for set_dht_value

This commit is contained in:
Brandon Vandegrift 2025-06-13 21:57:19 -04:00
parent eda436f264
commit b7d725ab12
36 changed files with 751 additions and 98 deletions

View file

@ -1,5 +1,11 @@
**UNRELEASED** **UNRELEASED**
- _BREAKING API CHANGES_:
- set_dht_value now accepts a new flag called `allow_offline`, which defaults to `true`.
- The previous `writer: Option<KeyPair>` argument position is now `options: Option<SetDHTValueOptions>`
- This will only be a breaking change for anyone utilizing the previous `writer` argument.
- `writer` is now a member of `SetDHTValueOptions`, alongside the new `allow_offline` property.
- veilid-core: - veilid-core:
- Add private route example - Add private route example
- Add `require_inbound_relay` option in VeilidConfig. Default is false, but if enabled, forces OutboundOnly/InboundRelay mode. Can be used as an extra layer of IP address obscurity for some threat models. (@neequ57) - Add `require_inbound_relay` option in VeilidConfig. Default is false, but if enabled, forces OutboundOnly/InboundRelay mode. Can be used as an extra layer of IP address obscurity for some threat models. (@neequ57)

View file

@ -145,11 +145,11 @@ impl StorageManager {
}; };
// Validate with schema // Validate with schema
if !schema.check_subkey_value_data( if schema.check_subkey_value_data(
descriptor.owner(), descriptor.owner(),
subkey, subkey,
value.value_data(), value.value_data(),
) { ).is_err() {
// Validation failed, ignore this value // Validation failed, ignore this value
// Move to the next node // Move to the next node
return Ok(FanoutCallOutput{peer_info_list: vec![], disposition: FanoutCallDisposition::Invalid}); return Ok(FanoutCallOutput{peer_info_list: vec![], disposition: FanoutCallDisposition::Invalid});

View file

@ -727,7 +727,7 @@ impl StorageManager {
record_key: TypedRecordKey, record_key: TypedRecordKey,
subkey: ValueSubkey, subkey: ValueSubkey,
data: Vec<u8>, data: Vec<u8>,
writer: Option<KeyPair>, options: Option<SetDHTValueOptions>,
) -> VeilidAPIResult<Option<ValueData>> { ) -> VeilidAPIResult<Option<ValueData>> {
let mut inner = self.inner.lock().await; let mut inner = self.inner.lock().await;
@ -748,7 +748,11 @@ impl StorageManager {
}; };
// Use the specified writer, or if not specified, the default writer when the record was opened // Use the specified writer, or if not specified, the default writer when the record was opened
let opt_writer = writer.or(opt_writer); let opt_writer = options.as_ref().and_then(|o| o.writer).or(opt_writer);
let allow_offline = options
.unwrap_or_default()
.allow_offline
.unwrap_or_default();
// If we don't have a writer then we can't write // If we don't have a writer then we can't write
let Some(writer) = opt_writer else { let Some(writer) = opt_writer else {
@ -781,9 +785,13 @@ impl StorageManager {
}; };
// Validate with schema // Validate with schema
if !schema.check_subkey_value_data(descriptor.owner(), subkey, &value_data) { if let Err(e) = schema.check_subkey_value_data(descriptor.owner(), subkey, &value_data) {
veilid_log!(self debug "schema validation error: {}", e);
// Validation failed, ignore this value // Validation failed, ignore this value
apibail_generic!("failed schema validation"); apibail_generic!(format!(
"failed schema validation: {}:{}",
record_key, subkey
));
} }
// Sign the new value data with the writer // Sign the new value data with the writer
@ -821,10 +829,19 @@ impl StorageManager {
}; };
if already_writing || !self.dht_is_online() { if already_writing || !self.dht_is_online() {
if allow_offline == AllowOffline(true) {
veilid_log!(self debug "Writing subkey offline: {}:{} len={}", record_key, subkey, signed_value_data.value_data().data().len() ); veilid_log!(self debug "Writing subkey offline: {}:{} len={}", record_key, subkey, signed_value_data.value_data().data().len() );
// Add to offline writes to flush // Add to offline writes to flush
Self::add_offline_subkey_write_inner(&mut inner, record_key, subkey, safety_selection); Self::add_offline_subkey_write_inner(
&mut inner,
record_key,
subkey,
safety_selection,
);
return Ok(None); return Ok(None);
} else {
apibail_try_again!("offline, try again later");
}
}; };
// Drop the lock for network access // Drop the lock for network access
@ -847,12 +864,17 @@ impl StorageManager {
Err(e) => { Err(e) => {
// Failed to write, try again later // Failed to write, try again later
let mut inner = self.inner.lock().await; let mut inner = self.inner.lock().await;
if allow_offline == AllowOffline(true) {
Self::add_offline_subkey_write_inner( Self::add_offline_subkey_write_inner(
&mut inner, &mut inner,
record_key, record_key,
subkey, subkey,
safety_selection, safety_selection,
); );
} else {
apibail_try_again!("offline, try again later");
}
// Remove from active subkey writes // Remove from active subkey writes
let asw = inner.active_subkey_writes.get_mut(&record_key).unwrap(); let asw = inner.active_subkey_writes.get_mut(&record_key).unwrap();

View file

@ -142,11 +142,11 @@ impl StorageManager {
veilid_log!(registry debug "SetValue got value back: len={} seq={}", value.value_data().data().len(), value.value_data().seq()); veilid_log!(registry debug "SetValue got value back: len={} seq={}", value.value_data().data().len(), value.value_data().seq());
// Validate with schema // Validate with schema
if !ctx.schema.check_subkey_value_data( if ctx.schema.check_subkey_value_data(
descriptor.owner(), descriptor.owner(),
subkey, subkey,
value.value_data(), value.value_data(),
) { ).is_err() {
// Validation failed, ignore this value and pretend we never saw this node // Validation failed, ignore this value and pretend we never saw this node
return Ok(FanoutCallOutput{peer_info_list: vec![], disposition: FanoutCallDisposition::Invalid}); return Ok(FanoutCallOutput{peer_info_list: vec![], disposition: FanoutCallDisposition::Invalid});
} }
@ -474,7 +474,10 @@ impl StorageManager {
}; };
// Validate new value with schema // Validate new value with schema
if !schema.check_subkey_value_data(actual_descriptor.owner(), subkey, value.value_data()) { if schema
.check_subkey_value_data(actual_descriptor.owner(), subkey, value.value_data())
.is_err()
{
// Validation failed, ignore this value // Validation failed, ignore this value
return Ok(NetworkResult::invalid_message("failed schema validation")); return Ok(NetworkResult::invalid_message("failed schema validation"));
} }

View file

@ -1179,11 +1179,10 @@ impl StorageManager {
let schema = descriptor.schema()?; let schema = descriptor.schema()?;
// Validate with schema // Validate with schema
if !schema.check_subkey_value_data( if schema
descriptor.owner(), .check_subkey_value_data(descriptor.owner(), first_subkey, value.value_data())
first_subkey, .is_err()
value.value_data(), {
) {
// Validation failed, ignore this value // Validation failed, ignore this value
// Move to the next node // Move to the next node
return Ok(NetworkResult::invalid_message(format!( return Ok(NetworkResult::invalid_message(format!(

View file

@ -319,7 +319,15 @@ pub async fn test_open_writer_dht_value(api: VeilidAPI) {
// Verify subkey 0 can be set because we have overridden with the correct writer // Verify subkey 0 can be set because we have overridden with the correct writer
let set_dht_test_value_0_result = rc let set_dht_test_value_0_result = rc
.set_dht_value(key, 0, test_value_1.clone(), Some(keypair)) .set_dht_value(
key,
0,
test_value_1.clone(),
Some(SetDHTValueOptions {
writer: Some(keypair),
allow_offline: None,
}),
)
.await; .await;
assert!(set_dht_test_value_0_result.is_ok()); assert!(set_dht_test_value_0_result.is_ok());
@ -327,6 +335,69 @@ pub async fn test_open_writer_dht_value(api: VeilidAPI) {
rc.delete_dht_record(key).await.unwrap(); rc.delete_dht_record(key).await.unwrap();
} }
pub async fn test_set_dht_value_allow_offline(api: VeilidAPI) {
let rc = api.routing_context().unwrap();
// Create a DHT record
let rec = rc
.create_dht_record(DHTSchema::dflt(1).unwrap(), None, Some(CRYPTO_KIND_VLD0))
.await
.unwrap();
let dht_key = *rec.key();
let test_value = String::from("Test offline value").as_bytes().to_vec();
// Test 1: Default behavior (options = None) should allow offline writes
let set_result = rc.set_dht_value(dht_key, 0, test_value.clone(), None).await;
assert!(set_result.is_ok());
// Test 2: Default behavior (allow_offline = None) should allow offline writes
let set_result = rc
.set_dht_value(
dht_key,
0,
test_value.clone(),
Some(SetDHTValueOptions {
writer: None,
allow_offline: None,
}),
)
.await;
assert!(set_result.is_ok());
// Test 3: Explicitly allow offline writes
let set_result = rc
.set_dht_value(
dht_key,
1,
test_value.clone(),
Some(SetDHTValueOptions {
writer: None,
allow_offline: Some(AllowOffline(true)),
}),
)
.await;
assert!(set_result.is_ok());
// Test 4: Disallow offline writes
let set_result = rc
.set_dht_value(
dht_key,
2,
test_value.clone(),
Some(SetDHTValueOptions {
writer: None,
allow_offline: Some(AllowOffline(false)),
}),
)
.await;
assert!(set_result.is_err());
assert!(set_result.unwrap_err().to_string().contains("offline"));
rc.close_dht_record(dht_key).await.unwrap();
rc.delete_dht_record(dht_key).await.unwrap();
}
// Network-related code to make sure veilid node is connetected to other peers // Network-related code to make sure veilid node is connetected to other peers
async fn wait_for_public_internet_ready(api: &VeilidAPI) { async fn wait_for_public_internet_ready(api: &VeilidAPI) {
@ -364,6 +435,7 @@ pub async fn test_all() {
test_create_dht_record_with_owner(api.clone()).await; test_create_dht_record_with_owner(api.clone()).await;
test_set_get_dht_value(api.clone()).await; test_set_get_dht_value(api.clone()).await;
test_open_writer_dht_value(api.clone()).await; test_open_writer_dht_value(api.clone()).await;
test_set_dht_value_allow_offline(api.clone()).await;
api.shutdown().await; api.shutdown().await;
} }

View file

@ -1669,7 +1669,15 @@ impl VeilidAPI {
// Do a record set // Do a record set
let value = match rc let value = match rc
.set_dht_value(key, subkey as ValueSubkey, data, writer) .set_dht_value(
key,
subkey as ValueSubkey,
data,
Some(SetDHTValueOptions {
writer,
allow_offline: None,
}),
)
.await .await
{ {
Err(e) => { Err(e) => {

View file

@ -435,15 +435,15 @@ impl RoutingContext {
key: TypedRecordKey, key: TypedRecordKey,
subkey: ValueSubkey, subkey: ValueSubkey,
data: Vec<u8>, data: Vec<u8>,
writer: Option<KeyPair>, options: Option<SetDHTValueOptions>,
) -> VeilidAPIResult<Option<ValueData>> { ) -> VeilidAPIResult<Option<ValueData>> {
veilid_log!(self debug veilid_log!(self debug
"RoutingContext::set_dht_value(self: {:?}, key: {:?}, subkey: {:?}, data: len={}, writer: {:?})", self, key, subkey, data.len(), writer); "RoutingContext::set_dht_value(self: {:?}, key: {:?}, subkey: {:?}, data: len={}, options: {:?})", self, key, subkey, data.len(), options);
Crypto::validate_crypto_kind(key.kind)?; Crypto::validate_crypto_kind(key.kind)?;
let storage_manager = self.api.core_context()?.storage_manager(); let storage_manager = self.api.core_context()?.storage_manager();
Box::pin(storage_manager.set_value(key, subkey, data, writer)).await Box::pin(storage_manager.set_value(key, subkey, data, options)).await
} }
/// Add or update a watch to a DHT value that informs the user via an VeilidUpdate::ValueChange callback when the record has subkeys change. /// Add or update a watch to a DHT value that informs the user via an VeilidUpdate::ValueChange callback when the record has subkeys change.

View file

@ -1,6 +1,7 @@
mod dht_record_descriptor; mod dht_record_descriptor;
mod dht_record_report; mod dht_record_report;
mod schema; mod schema;
mod set_dht_value_options;
mod value_data; mod value_data;
mod value_subkey_range_set; mod value_subkey_range_set;
@ -9,6 +10,7 @@ use super::*;
pub use dht_record_descriptor::*; pub use dht_record_descriptor::*;
pub use dht_record_report::*; pub use dht_record_report::*;
pub use schema::*; pub use schema::*;
pub use set_dht_value_options::*;
pub use value_data::*; pub use value_data::*;
pub use value_subkey_range_set::*; pub use value_subkey_range_set::*;

View file

@ -62,13 +62,12 @@ impl DHTSchemaDFLT {
} }
/// Check a subkey value data against the schema /// Check a subkey value data against the schema
#[must_use]
pub fn check_subkey_value_data( pub fn check_subkey_value_data(
&self, &self,
owner: &PublicKey, owner: &PublicKey,
subkey: ValueSubkey, subkey: ValueSubkey,
value_data: &ValueData, value_data: &ValueData,
) -> bool { ) -> VeilidAPIResult<()> {
let subkey = subkey as usize; let subkey = subkey as usize;
// Check if subkey is in owner range // Check if subkey is in owner range
@ -80,19 +79,27 @@ impl DHTSchemaDFLT {
// Ensure value size is within additional limit // Ensure value size is within additional limit
if value_data.data_size() <= max_value_len { if value_data.data_size() <= max_value_len {
return true; return Ok(());
} }
// Value too big // Value too big
return false; apibail_invalid_argument!(
"value too big",
"data",
format!("{:?}", value_data.data())
);
} }
// Wrong writer // Wrong writer
return false; apibail_invalid_argument!(
"wrong writer",
"writer",
format!("{:?}", value_data.writer())
);
} }
// Subkey out of range // Subkey out of range
false apibail_invalid_argument!("subkey out of range", "subkey", subkey);
} }
/// Check if a key is a schema member /// Check if a key is a schema member

View file

@ -64,13 +64,12 @@ impl DHTSchema {
} }
/// Check a subkey value data against the schema /// Check a subkey value data against the schema
#[must_use]
pub fn check_subkey_value_data( pub fn check_subkey_value_data(
&self, &self,
owner: &PublicKey, owner: &PublicKey,
subkey: ValueSubkey, subkey: ValueSubkey,
value_data: &ValueData, value_data: &ValueData,
) -> bool { ) -> VeilidAPIResult<()> {
match self { match self {
DHTSchema::DFLT(d) => d.check_subkey_value_data(owner, subkey, value_data), DHTSchema::DFLT(d) => d.check_subkey_value_data(owner, subkey, value_data),
DHTSchema::SMPL(s) => s.check_subkey_value_data(owner, subkey, value_data), DHTSchema::SMPL(s) => s.check_subkey_value_data(owner, subkey, value_data),

View file

@ -107,13 +107,12 @@ impl DHTSchemaSMPL {
} }
/// Check a subkey value data against the schema /// Check a subkey value data against the schema
#[must_use]
pub fn check_subkey_value_data( pub fn check_subkey_value_data(
&self, &self,
owner: &PublicKey, owner: &PublicKey,
subkey: ValueSubkey, subkey: ValueSubkey,
value_data: &ValueData, value_data: &ValueData,
) -> bool { ) -> VeilidAPIResult<()> {
let mut cur_subkey = subkey as usize; let mut cur_subkey = subkey as usize;
let max_value_len = usize::min( let max_value_len = usize::min(
@ -127,14 +126,22 @@ impl DHTSchemaSMPL {
if value_data.writer() == owner { if value_data.writer() == owner {
// Ensure value size is within additional limit // Ensure value size is within additional limit
if value_data.data_size() <= max_value_len { if value_data.data_size() <= max_value_len {
return true; return Ok(());
} }
// Value too big // Value too big
return false; apibail_invalid_argument!(
"value too big",
"data",
format!("{:?}", value_data.data())
);
} }
// Wrong writer // Wrong writer
return false; apibail_invalid_argument!(
"wrong writer",
"writer",
format!("{:?}", value_data.writer())
);
} }
cur_subkey -= self.o_cnt as usize; cur_subkey -= self.o_cnt as usize;
@ -146,20 +153,28 @@ impl DHTSchemaSMPL {
if value_data.writer() == &m.m_key { if value_data.writer() == &m.m_key {
// Ensure value size is in allowed range // Ensure value size is in allowed range
if value_data.data_size() <= max_value_len { if value_data.data_size() <= max_value_len {
return true; return Ok(());
} }
// Value too big // Value too big
return false; apibail_invalid_argument!(
"value too big",
"data",
format!("{:?}", value_data.data())
);
} }
// Wrong writer // Wrong writer
return false; apibail_invalid_argument!(
"wrong writer",
"writer",
format!("{:?}", value_data.writer())
);
} }
cur_subkey -= m.m_cnt as usize; cur_subkey -= m.m_cnt as usize;
} }
// Subkey out of range // Subkey out of range
false apibail_invalid_argument!("subkey out of range", "subkey", subkey);
} }
/// Check if a key is a schema member /// Check if a key is a schema member

View file

@ -0,0 +1,40 @@
use crate::{Deserialize, JsonSchema, KeyPair, Serialize};
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
use crate::Tsify;
#[derive(Debug, JsonSchema, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
derive(Tsify),
tsify(from_wasm_abi, into_wasm_abi)
)]
pub struct AllowOffline(pub bool);
impl Default for AllowOffline {
fn default() -> Self {
Self(true)
}
}
#[derive(Debug, JsonSchema, Serialize, Deserialize, Clone)]
#[cfg_attr(
all(target_arch = "wasm32", target_os = "unknown"),
derive(Tsify),
tsify(from_wasm_abi, into_wasm_abi)
)]
pub struct SetDHTValueOptions {
#[schemars(with = "Option<String>")]
pub writer: Option<KeyPair>,
/// Defaults to true. If false, the value will not be written if the node is offline,
/// and a TryAgain error will be returned.
pub allow_offline: Option<AllowOffline>,
}
impl Default for SetDHTValueOptions {
fn default() -> Self {
Self {
writer: None,
allow_offline: Some(AllowOffline(true)),
}
}
}

View file

@ -244,7 +244,8 @@ Future<void> testOpenWriterDHTValue() async {
// Should have prior sequence number as its returned value because it // Should have prior sequence number as its returned value because it
// exists online at seq 0 // exists online at seq 0
vdtemp = await rc.setDHTValue(key, 0, va, vdtemp = await rc.setDHTValue(key, 0, va,
writer: KeyPair(key: owner, secret: secret)); options:
SetDHTValueOptions(writer: KeyPair(key: owner, secret: secret)));
expect(vdtemp, isNotNull); expect(vdtemp, isNotNull);
expect(vdtemp!.data, equals(vb)); expect(vdtemp!.data, equals(vb));
expect(vdtemp.seq, equals(0)); expect(vdtemp.seq, equals(0));
@ -252,7 +253,8 @@ Future<void> testOpenWriterDHTValue() async {
// Should update the second time to seq 1 // Should update the second time to seq 1
vdtemp = await rc.setDHTValue(key, 0, va, vdtemp = await rc.setDHTValue(key, 0, va,
writer: KeyPair(key: owner, secret: secret)); options:
SetDHTValueOptions(writer: KeyPair(key: owner, secret: secret)));
expect(vdtemp, isNull); expect(vdtemp, isNull);
// Clean up // Clean up

View file

@ -13,10 +13,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: async name: async
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.12.0" version: "2.13.0"
async_tools: async_tools:
dependency: transitive dependency: transitive
description: description:
@ -101,10 +101,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.2" version: "1.3.3"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
@ -195,10 +195,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.8" version: "10.0.9"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
@ -450,7 +450,7 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "0.4.4" version: "0.4.7"
veilid_test: veilid_test:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -462,18 +462,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.3.1" version: "15.0.0"
webdriver: webdriver:
dependency: transitive dependency: transitive
description: description:
name: webdriver name: webdriver
sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.4" version: "3.1.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View file

@ -273,6 +273,25 @@ enum DHTReportScope {
String toJson() => name.toPascalCase(); String toJson() => name.toPascalCase();
} }
/// SetDHTValueOptions
@freezed
sealed class SetDHTValueOptions with _$SetDHTValueOptions {
const factory SetDHTValueOptions({
KeyPair? writer,
bool? allowOffline,
}) = _SetDHTValueOptions;
factory SetDHTValueOptions.fromJson(dynamic json) =>
_$SetDHTValueOptionsFromJson(json as Map<String, dynamic>);
@override
Map<String, dynamic> toJson() => {
'writer': writer,
'allow_offline': allowOffline,
};
}
////////////////////////////////////// //////////////////////////////////////
/// VeilidRoutingContext /// VeilidRoutingContext
@ -300,7 +319,7 @@ abstract class VeilidRoutingContext {
Future<ValueData?> getDHTValue(TypedKey key, int subkey, Future<ValueData?> getDHTValue(TypedKey key, int subkey,
{bool forceRefresh = false}); {bool forceRefresh = false});
Future<ValueData?> setDHTValue(TypedKey key, int subkey, Uint8List data, Future<ValueData?> setDHTValue(TypedKey key, int subkey, Uint8List data,
{KeyPair? writer}); {SetDHTValueOptions? options});
Future<bool> watchDHTValues(TypedKey key, Future<bool> watchDHTValues(TypedKey key,
{List<ValueSubkeyRange>? subkeys, Timestamp? expiration, int? count}); {List<ValueSubkeyRange>? subkeys, Timestamp? expiration, int? count});
Future<bool> cancelDHTWatch(TypedKey key, {List<ValueSubkeyRange>? subkeys}); Future<bool> cancelDHTWatch(TypedKey key, {List<ValueSubkeyRange>? subkeys});

View file

@ -1453,4 +1453,165 @@ class __$DHTRecordReportCopyWithImpl<$Res>
} }
} }
/// @nodoc
mixin _$SetDHTValueOptions {
KeyPair? get writer;
bool? get allowOffline;
/// Create a copy of SetDHTValueOptions
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SetDHTValueOptionsCopyWith<SetDHTValueOptions> get copyWith =>
_$SetDHTValueOptionsCopyWithImpl<SetDHTValueOptions>(
this as SetDHTValueOptions, _$identity);
/// Serializes this SetDHTValueOptions to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is SetDHTValueOptions &&
(identical(other.writer, writer) || other.writer == writer) &&
(identical(other.allowOffline, allowOffline) ||
other.allowOffline == allowOffline));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, writer, allowOffline);
@override
String toString() {
return 'SetDHTValueOptions(writer: $writer, allowOffline: $allowOffline)';
}
}
/// @nodoc
abstract mixin class $SetDHTValueOptionsCopyWith<$Res> {
factory $SetDHTValueOptionsCopyWith(
SetDHTValueOptions value, $Res Function(SetDHTValueOptions) _then) =
_$SetDHTValueOptionsCopyWithImpl;
@useResult
$Res call({KeyPair? writer, bool? allowOffline});
}
/// @nodoc
class _$SetDHTValueOptionsCopyWithImpl<$Res>
implements $SetDHTValueOptionsCopyWith<$Res> {
_$SetDHTValueOptionsCopyWithImpl(this._self, this._then);
final SetDHTValueOptions _self;
final $Res Function(SetDHTValueOptions) _then;
/// Create a copy of SetDHTValueOptions
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? writer = freezed,
Object? allowOffline = freezed,
}) {
return _then(_self.copyWith(
writer: freezed == writer
? _self.writer
: writer // ignore: cast_nullable_to_non_nullable
as KeyPair?,
allowOffline: freezed == allowOffline
? _self.allowOffline
: allowOffline // ignore: cast_nullable_to_non_nullable
as bool?,
));
}
}
/// @nodoc
@JsonSerializable()
class _SetDHTValueOptions implements SetDHTValueOptions {
const _SetDHTValueOptions({this.writer, this.allowOffline});
factory _SetDHTValueOptions.fromJson(Map<String, dynamic> json) =>
_$SetDHTValueOptionsFromJson(json);
@override
final KeyPair? writer;
@override
final bool? allowOffline;
/// Create a copy of SetDHTValueOptions
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SetDHTValueOptionsCopyWith<_SetDHTValueOptions> get copyWith =>
__$SetDHTValueOptionsCopyWithImpl<_SetDHTValueOptions>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SetDHTValueOptionsToJson(
this,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _SetDHTValueOptions &&
(identical(other.writer, writer) || other.writer == writer) &&
(identical(other.allowOffline, allowOffline) ||
other.allowOffline == allowOffline));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, writer, allowOffline);
@override
String toString() {
return 'SetDHTValueOptions(writer: $writer, allowOffline: $allowOffline)';
}
}
/// @nodoc
abstract mixin class _$SetDHTValueOptionsCopyWith<$Res>
implements $SetDHTValueOptionsCopyWith<$Res> {
factory _$SetDHTValueOptionsCopyWith(
_SetDHTValueOptions value, $Res Function(_SetDHTValueOptions) _then) =
__$SetDHTValueOptionsCopyWithImpl;
@override
@useResult
$Res call({KeyPair? writer, bool? allowOffline});
}
/// @nodoc
class __$SetDHTValueOptionsCopyWithImpl<$Res>
implements _$SetDHTValueOptionsCopyWith<$Res> {
__$SetDHTValueOptionsCopyWithImpl(this._self, this._then);
final _SetDHTValueOptions _self;
final $Res Function(_SetDHTValueOptions) _then;
/// Create a copy of SetDHTValueOptions
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? writer = freezed,
Object? allowOffline = freezed,
}) {
return _then(_SetDHTValueOptions(
writer: freezed == writer
? _self.writer
: writer // ignore: cast_nullable_to_non_nullable
as KeyPair?,
allowOffline: freezed == allowOffline
? _self.allowOffline
: allowOffline // ignore: cast_nullable_to_non_nullable
as bool?,
));
}
}
// dart format on // dart format on

View file

@ -128,3 +128,15 @@ Map<String, dynamic> _$DHTRecordReportToJson(_DHTRecordReport instance) =>
'local_seqs': instance.localSeqs, 'local_seqs': instance.localSeqs,
'network_seqs': instance.networkSeqs, 'network_seqs': instance.networkSeqs,
}; };
_SetDHTValueOptions _$SetDHTValueOptionsFromJson(Map<String, dynamic> json) =>
_SetDHTValueOptions(
writer: json['writer'] == null ? null : KeyPair.fromJson(json['writer']),
allowOffline: json['allow_offline'] as bool?,
);
Map<String, dynamic> _$SetDHTValueOptionsToJson(_SetDHTValueOptions instance) =>
<String, dynamic>{
'writer': instance.writer?.toJson(),
'allow_offline': instance.allowOffline,
};

View file

@ -266,6 +266,18 @@ sealed class VeilidConfigProtocol with _$VeilidConfigProtocol {
//////////// ////////////
@freezed
sealed class VeilidConfigPrivacy with _$VeilidConfigPrivacy {
const factory VeilidConfigPrivacy({
required bool requireInboundRelay,
}) = _VeilidConfigPrivacy;
factory VeilidConfigPrivacy.fromJson(dynamic json) =>
_$VeilidConfigPrivacyFromJson(json as Map<String, dynamic>);
}
////////////
@freezed @freezed
sealed class VeilidConfigTLS with _$VeilidConfigTLS { sealed class VeilidConfigTLS with _$VeilidConfigTLS {
const factory VeilidConfigTLS({ const factory VeilidConfigTLS({
@ -370,6 +382,7 @@ sealed class VeilidConfigNetwork with _$VeilidConfigNetwork {
required VeilidConfigTLS tls, required VeilidConfigTLS tls,
required VeilidConfigApplication application, required VeilidConfigApplication application,
required VeilidConfigProtocol protocol, required VeilidConfigProtocol protocol,
required VeilidConfigPrivacy privacy,
String? networkKeyPassword, String? networkKeyPassword,
}) = _VeilidConfigNetwork; }) = _VeilidConfigNetwork;

View file

@ -4296,6 +4296,169 @@ class __$VeilidConfigProtocolCopyWithImpl<$Res>
} }
} }
/// @nodoc
mixin _$VeilidConfigPrivacy implements DiagnosticableTreeMixin {
bool get requireInboundRelay;
/// Create a copy of VeilidConfigPrivacy
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$VeilidConfigPrivacyCopyWith<VeilidConfigPrivacy> get copyWith =>
_$VeilidConfigPrivacyCopyWithImpl<VeilidConfigPrivacy>(
this as VeilidConfigPrivacy, _$identity);
/// Serializes this VeilidConfigPrivacy to a JSON map.
Map<String, dynamic> toJson();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'VeilidConfigPrivacy'))
..add(DiagnosticsProperty('requireInboundRelay', requireInboundRelay));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is VeilidConfigPrivacy &&
(identical(other.requireInboundRelay, requireInboundRelay) ||
other.requireInboundRelay == requireInboundRelay));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, requireInboundRelay);
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'VeilidConfigPrivacy(requireInboundRelay: $requireInboundRelay)';
}
}
/// @nodoc
abstract mixin class $VeilidConfigPrivacyCopyWith<$Res> {
factory $VeilidConfigPrivacyCopyWith(
VeilidConfigPrivacy value, $Res Function(VeilidConfigPrivacy) _then) =
_$VeilidConfigPrivacyCopyWithImpl;
@useResult
$Res call({bool requireInboundRelay});
}
/// @nodoc
class _$VeilidConfigPrivacyCopyWithImpl<$Res>
implements $VeilidConfigPrivacyCopyWith<$Res> {
_$VeilidConfigPrivacyCopyWithImpl(this._self, this._then);
final VeilidConfigPrivacy _self;
final $Res Function(VeilidConfigPrivacy) _then;
/// Create a copy of VeilidConfigPrivacy
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? requireInboundRelay = null,
}) {
return _then(_self.copyWith(
requireInboundRelay: null == requireInboundRelay
? _self.requireInboundRelay
: requireInboundRelay // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
@JsonSerializable()
class _VeilidConfigPrivacy
with DiagnosticableTreeMixin
implements VeilidConfigPrivacy {
const _VeilidConfigPrivacy({required this.requireInboundRelay});
factory _VeilidConfigPrivacy.fromJson(Map<String, dynamic> json) =>
_$VeilidConfigPrivacyFromJson(json);
@override
final bool requireInboundRelay;
/// Create a copy of VeilidConfigPrivacy
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$VeilidConfigPrivacyCopyWith<_VeilidConfigPrivacy> get copyWith =>
__$VeilidConfigPrivacyCopyWithImpl<_VeilidConfigPrivacy>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$VeilidConfigPrivacyToJson(
this,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'VeilidConfigPrivacy'))
..add(DiagnosticsProperty('requireInboundRelay', requireInboundRelay));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _VeilidConfigPrivacy &&
(identical(other.requireInboundRelay, requireInboundRelay) ||
other.requireInboundRelay == requireInboundRelay));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, requireInboundRelay);
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'VeilidConfigPrivacy(requireInboundRelay: $requireInboundRelay)';
}
}
/// @nodoc
abstract mixin class _$VeilidConfigPrivacyCopyWith<$Res>
implements $VeilidConfigPrivacyCopyWith<$Res> {
factory _$VeilidConfigPrivacyCopyWith(_VeilidConfigPrivacy value,
$Res Function(_VeilidConfigPrivacy) _then) =
__$VeilidConfigPrivacyCopyWithImpl;
@override
@useResult
$Res call({bool requireInboundRelay});
}
/// @nodoc
class __$VeilidConfigPrivacyCopyWithImpl<$Res>
implements _$VeilidConfigPrivacyCopyWith<$Res> {
__$VeilidConfigPrivacyCopyWithImpl(this._self, this._then);
final _VeilidConfigPrivacy _self;
final $Res Function(_VeilidConfigPrivacy) _then;
/// Create a copy of VeilidConfigPrivacy
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? requireInboundRelay = null,
}) {
return _then(_VeilidConfigPrivacy(
requireInboundRelay: null == requireInboundRelay
? _self.requireInboundRelay
: requireInboundRelay // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc /// @nodoc
mixin _$VeilidConfigTLS implements DiagnosticableTreeMixin { mixin _$VeilidConfigTLS implements DiagnosticableTreeMixin {
String get certificatePath; String get certificatePath;
@ -5922,6 +6085,7 @@ mixin _$VeilidConfigNetwork implements DiagnosticableTreeMixin {
VeilidConfigTLS get tls; VeilidConfigTLS get tls;
VeilidConfigApplication get application; VeilidConfigApplication get application;
VeilidConfigProtocol get protocol; VeilidConfigProtocol get protocol;
VeilidConfigPrivacy get privacy;
String? get networkKeyPassword; String? get networkKeyPassword;
/// Create a copy of VeilidConfigNetwork /// Create a copy of VeilidConfigNetwork
@ -5965,6 +6129,7 @@ mixin _$VeilidConfigNetwork implements DiagnosticableTreeMixin {
..add(DiagnosticsProperty('tls', tls)) ..add(DiagnosticsProperty('tls', tls))
..add(DiagnosticsProperty('application', application)) ..add(DiagnosticsProperty('application', application))
..add(DiagnosticsProperty('protocol', protocol)) ..add(DiagnosticsProperty('protocol', protocol))
..add(DiagnosticsProperty('privacy', privacy))
..add(DiagnosticsProperty('networkKeyPassword', networkKeyPassword)); ..add(DiagnosticsProperty('networkKeyPassword', networkKeyPassword));
} }
@ -6012,6 +6177,7 @@ mixin _$VeilidConfigNetwork implements DiagnosticableTreeMixin {
other.application == application) && other.application == application) &&
(identical(other.protocol, protocol) || (identical(other.protocol, protocol) ||
other.protocol == protocol) && other.protocol == protocol) &&
(identical(other.privacy, privacy) || other.privacy == privacy) &&
(identical(other.networkKeyPassword, networkKeyPassword) || (identical(other.networkKeyPassword, networkKeyPassword) ||
other.networkKeyPassword == networkKeyPassword)); other.networkKeyPassword == networkKeyPassword));
} }
@ -6038,12 +6204,13 @@ mixin _$VeilidConfigNetwork implements DiagnosticableTreeMixin {
tls, tls,
application, application,
protocol, protocol,
privacy,
networkKeyPassword networkKeyPassword
]); ]);
@override @override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'VeilidConfigNetwork(connectionInitialTimeoutMs: $connectionInitialTimeoutMs, connectionInactivityTimeoutMs: $connectionInactivityTimeoutMs, maxConnectionsPerIp4: $maxConnectionsPerIp4, maxConnectionsPerIp6Prefix: $maxConnectionsPerIp6Prefix, maxConnectionsPerIp6PrefixSize: $maxConnectionsPerIp6PrefixSize, maxConnectionFrequencyPerMin: $maxConnectionFrequencyPerMin, clientAllowlistTimeoutMs: $clientAllowlistTimeoutMs, reverseConnectionReceiptTimeMs: $reverseConnectionReceiptTimeMs, holePunchReceiptTimeMs: $holePunchReceiptTimeMs, routingTable: $routingTable, rpc: $rpc, dht: $dht, upnp: $upnp, detectAddressChanges: $detectAddressChanges, restrictedNatRetries: $restrictedNatRetries, tls: $tls, application: $application, protocol: $protocol, networkKeyPassword: $networkKeyPassword)'; return 'VeilidConfigNetwork(connectionInitialTimeoutMs: $connectionInitialTimeoutMs, connectionInactivityTimeoutMs: $connectionInactivityTimeoutMs, maxConnectionsPerIp4: $maxConnectionsPerIp4, maxConnectionsPerIp6Prefix: $maxConnectionsPerIp6Prefix, maxConnectionsPerIp6PrefixSize: $maxConnectionsPerIp6PrefixSize, maxConnectionFrequencyPerMin: $maxConnectionFrequencyPerMin, clientAllowlistTimeoutMs: $clientAllowlistTimeoutMs, reverseConnectionReceiptTimeMs: $reverseConnectionReceiptTimeMs, holePunchReceiptTimeMs: $holePunchReceiptTimeMs, routingTable: $routingTable, rpc: $rpc, dht: $dht, upnp: $upnp, detectAddressChanges: $detectAddressChanges, restrictedNatRetries: $restrictedNatRetries, tls: $tls, application: $application, protocol: $protocol, privacy: $privacy, networkKeyPassword: $networkKeyPassword)';
} }
} }
@ -6072,6 +6239,7 @@ abstract mixin class $VeilidConfigNetworkCopyWith<$Res> {
VeilidConfigTLS tls, VeilidConfigTLS tls,
VeilidConfigApplication application, VeilidConfigApplication application,
VeilidConfigProtocol protocol, VeilidConfigProtocol protocol,
VeilidConfigPrivacy privacy,
String? networkKeyPassword}); String? networkKeyPassword});
$VeilidConfigRoutingTableCopyWith<$Res> get routingTable; $VeilidConfigRoutingTableCopyWith<$Res> get routingTable;
@ -6080,6 +6248,7 @@ abstract mixin class $VeilidConfigNetworkCopyWith<$Res> {
$VeilidConfigTLSCopyWith<$Res> get tls; $VeilidConfigTLSCopyWith<$Res> get tls;
$VeilidConfigApplicationCopyWith<$Res> get application; $VeilidConfigApplicationCopyWith<$Res> get application;
$VeilidConfigProtocolCopyWith<$Res> get protocol; $VeilidConfigProtocolCopyWith<$Res> get protocol;
$VeilidConfigPrivacyCopyWith<$Res> get privacy;
} }
/// @nodoc /// @nodoc
@ -6113,6 +6282,7 @@ class _$VeilidConfigNetworkCopyWithImpl<$Res>
Object? tls = null, Object? tls = null,
Object? application = null, Object? application = null,
Object? protocol = null, Object? protocol = null,
Object? privacy = null,
Object? networkKeyPassword = freezed, Object? networkKeyPassword = freezed,
}) { }) {
return _then(_self.copyWith( return _then(_self.copyWith(
@ -6188,6 +6358,10 @@ class _$VeilidConfigNetworkCopyWithImpl<$Res>
? _self.protocol ? _self.protocol
: protocol // ignore: cast_nullable_to_non_nullable : protocol // ignore: cast_nullable_to_non_nullable
as VeilidConfigProtocol, as VeilidConfigProtocol,
privacy: null == privacy
? _self.privacy
: privacy // ignore: cast_nullable_to_non_nullable
as VeilidConfigPrivacy,
networkKeyPassword: freezed == networkKeyPassword networkKeyPassword: freezed == networkKeyPassword
? _self.networkKeyPassword ? _self.networkKeyPassword
: networkKeyPassword // ignore: cast_nullable_to_non_nullable : networkKeyPassword // ignore: cast_nullable_to_non_nullable
@ -6254,6 +6428,16 @@ class _$VeilidConfigNetworkCopyWithImpl<$Res>
return _then(_self.copyWith(protocol: value)); return _then(_self.copyWith(protocol: value));
}); });
} }
/// Create a copy of VeilidConfigNetwork
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$VeilidConfigPrivacyCopyWith<$Res> get privacy {
return $VeilidConfigPrivacyCopyWith<$Res>(_self.privacy, (value) {
return _then(_self.copyWith(privacy: value));
});
}
} }
/// @nodoc /// @nodoc
@ -6280,6 +6464,7 @@ class _VeilidConfigNetwork
required this.tls, required this.tls,
required this.application, required this.application,
required this.protocol, required this.protocol,
required this.privacy,
this.networkKeyPassword}); this.networkKeyPassword});
factory _VeilidConfigNetwork.fromJson(Map<String, dynamic> json) => factory _VeilidConfigNetwork.fromJson(Map<String, dynamic> json) =>
_$VeilidConfigNetworkFromJson(json); _$VeilidConfigNetworkFromJson(json);
@ -6321,6 +6506,8 @@ class _VeilidConfigNetwork
@override @override
final VeilidConfigProtocol protocol; final VeilidConfigProtocol protocol;
@override @override
final VeilidConfigPrivacy privacy;
@override
final String? networkKeyPassword; final String? networkKeyPassword;
/// Create a copy of VeilidConfigNetwork /// Create a copy of VeilidConfigNetwork
@ -6369,6 +6556,7 @@ class _VeilidConfigNetwork
..add(DiagnosticsProperty('tls', tls)) ..add(DiagnosticsProperty('tls', tls))
..add(DiagnosticsProperty('application', application)) ..add(DiagnosticsProperty('application', application))
..add(DiagnosticsProperty('protocol', protocol)) ..add(DiagnosticsProperty('protocol', protocol))
..add(DiagnosticsProperty('privacy', privacy))
..add(DiagnosticsProperty('networkKeyPassword', networkKeyPassword)); ..add(DiagnosticsProperty('networkKeyPassword', networkKeyPassword));
} }
@ -6416,6 +6604,7 @@ class _VeilidConfigNetwork
other.application == application) && other.application == application) &&
(identical(other.protocol, protocol) || (identical(other.protocol, protocol) ||
other.protocol == protocol) && other.protocol == protocol) &&
(identical(other.privacy, privacy) || other.privacy == privacy) &&
(identical(other.networkKeyPassword, networkKeyPassword) || (identical(other.networkKeyPassword, networkKeyPassword) ||
other.networkKeyPassword == networkKeyPassword)); other.networkKeyPassword == networkKeyPassword));
} }
@ -6442,12 +6631,13 @@ class _VeilidConfigNetwork
tls, tls,
application, application,
protocol, protocol,
privacy,
networkKeyPassword networkKeyPassword
]); ]);
@override @override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'VeilidConfigNetwork(connectionInitialTimeoutMs: $connectionInitialTimeoutMs, connectionInactivityTimeoutMs: $connectionInactivityTimeoutMs, maxConnectionsPerIp4: $maxConnectionsPerIp4, maxConnectionsPerIp6Prefix: $maxConnectionsPerIp6Prefix, maxConnectionsPerIp6PrefixSize: $maxConnectionsPerIp6PrefixSize, maxConnectionFrequencyPerMin: $maxConnectionFrequencyPerMin, clientAllowlistTimeoutMs: $clientAllowlistTimeoutMs, reverseConnectionReceiptTimeMs: $reverseConnectionReceiptTimeMs, holePunchReceiptTimeMs: $holePunchReceiptTimeMs, routingTable: $routingTable, rpc: $rpc, dht: $dht, upnp: $upnp, detectAddressChanges: $detectAddressChanges, restrictedNatRetries: $restrictedNatRetries, tls: $tls, application: $application, protocol: $protocol, networkKeyPassword: $networkKeyPassword)'; return 'VeilidConfigNetwork(connectionInitialTimeoutMs: $connectionInitialTimeoutMs, connectionInactivityTimeoutMs: $connectionInactivityTimeoutMs, maxConnectionsPerIp4: $maxConnectionsPerIp4, maxConnectionsPerIp6Prefix: $maxConnectionsPerIp6Prefix, maxConnectionsPerIp6PrefixSize: $maxConnectionsPerIp6PrefixSize, maxConnectionFrequencyPerMin: $maxConnectionFrequencyPerMin, clientAllowlistTimeoutMs: $clientAllowlistTimeoutMs, reverseConnectionReceiptTimeMs: $reverseConnectionReceiptTimeMs, holePunchReceiptTimeMs: $holePunchReceiptTimeMs, routingTable: $routingTable, rpc: $rpc, dht: $dht, upnp: $upnp, detectAddressChanges: $detectAddressChanges, restrictedNatRetries: $restrictedNatRetries, tls: $tls, application: $application, protocol: $protocol, privacy: $privacy, networkKeyPassword: $networkKeyPassword)';
} }
} }
@ -6478,6 +6668,7 @@ abstract mixin class _$VeilidConfigNetworkCopyWith<$Res>
VeilidConfigTLS tls, VeilidConfigTLS tls,
VeilidConfigApplication application, VeilidConfigApplication application,
VeilidConfigProtocol protocol, VeilidConfigProtocol protocol,
VeilidConfigPrivacy privacy,
String? networkKeyPassword}); String? networkKeyPassword});
@override @override
@ -6492,6 +6683,8 @@ abstract mixin class _$VeilidConfigNetworkCopyWith<$Res>
$VeilidConfigApplicationCopyWith<$Res> get application; $VeilidConfigApplicationCopyWith<$Res> get application;
@override @override
$VeilidConfigProtocolCopyWith<$Res> get protocol; $VeilidConfigProtocolCopyWith<$Res> get protocol;
@override
$VeilidConfigPrivacyCopyWith<$Res> get privacy;
} }
/// @nodoc /// @nodoc
@ -6525,6 +6718,7 @@ class __$VeilidConfigNetworkCopyWithImpl<$Res>
Object? tls = null, Object? tls = null,
Object? application = null, Object? application = null,
Object? protocol = null, Object? protocol = null,
Object? privacy = null,
Object? networkKeyPassword = freezed, Object? networkKeyPassword = freezed,
}) { }) {
return _then(_VeilidConfigNetwork( return _then(_VeilidConfigNetwork(
@ -6600,6 +6794,10 @@ class __$VeilidConfigNetworkCopyWithImpl<$Res>
? _self.protocol ? _self.protocol
: protocol // ignore: cast_nullable_to_non_nullable : protocol // ignore: cast_nullable_to_non_nullable
as VeilidConfigProtocol, as VeilidConfigProtocol,
privacy: null == privacy
? _self.privacy
: privacy // ignore: cast_nullable_to_non_nullable
as VeilidConfigPrivacy,
networkKeyPassword: freezed == networkKeyPassword networkKeyPassword: freezed == networkKeyPassword
? _self.networkKeyPassword ? _self.networkKeyPassword
: networkKeyPassword // ignore: cast_nullable_to_non_nullable : networkKeyPassword // ignore: cast_nullable_to_non_nullable
@ -6666,6 +6864,16 @@ class __$VeilidConfigNetworkCopyWithImpl<$Res>
return _then(_self.copyWith(protocol: value)); return _then(_self.copyWith(protocol: value));
}); });
} }
/// Create a copy of VeilidConfigNetwork
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$VeilidConfigPrivacyCopyWith<$Res> get privacy {
return $VeilidConfigPrivacyCopyWith<$Res>(_self.privacy, (value) {
return _then(_self.copyWith(privacy: value));
});
}
} }
/// @nodoc /// @nodoc

View file

@ -314,6 +314,17 @@ Map<String, dynamic> _$VeilidConfigProtocolToJson(
'wss': instance.wss.toJson(), 'wss': instance.wss.toJson(),
}; };
_VeilidConfigPrivacy _$VeilidConfigPrivacyFromJson(Map<String, dynamic> json) =>
_VeilidConfigPrivacy(
requireInboundRelay: json['require_inbound_relay'] as bool,
);
Map<String, dynamic> _$VeilidConfigPrivacyToJson(
_VeilidConfigPrivacy instance) =>
<String, dynamic>{
'require_inbound_relay': instance.requireInboundRelay,
};
_VeilidConfigTLS _$VeilidConfigTLSFromJson(Map<String, dynamic> json) => _VeilidConfigTLS _$VeilidConfigTLSFromJson(Map<String, dynamic> json) =>
_VeilidConfigTLS( _VeilidConfigTLS(
certificatePath: json['certificate_path'] as String, certificatePath: json['certificate_path'] as String,
@ -472,6 +483,7 @@ _VeilidConfigNetwork _$VeilidConfigNetworkFromJson(Map<String, dynamic> json) =>
tls: VeilidConfigTLS.fromJson(json['tls']), tls: VeilidConfigTLS.fromJson(json['tls']),
application: VeilidConfigApplication.fromJson(json['application']), application: VeilidConfigApplication.fromJson(json['application']),
protocol: VeilidConfigProtocol.fromJson(json['protocol']), protocol: VeilidConfigProtocol.fromJson(json['protocol']),
privacy: VeilidConfigPrivacy.fromJson(json['privacy']),
networkKeyPassword: json['network_key_password'] as String?, networkKeyPassword: json['network_key_password'] as String?,
); );
@ -499,6 +511,7 @@ Map<String, dynamic> _$VeilidConfigNetworkToJson(
'tls': instance.tls.toJson(), 'tls': instance.tls.toJson(),
'application': instance.application.toJson(), 'application': instance.application.toJson(),
'protocol': instance.protocol.toJson(), 'protocol': instance.protocol.toJson(),
'privacy': instance.privacy.toJson(),
'network_key_password': instance.networkKeyPassword, 'network_key_password': instance.networkKeyPassword,
}; };

View file

@ -696,17 +696,17 @@ class VeilidRoutingContextFFI extends VeilidRoutingContext {
@override @override
Future<ValueData?> setDHTValue(TypedKey key, int subkey, Uint8List data, Future<ValueData?> setDHTValue(TypedKey key, int subkey, Uint8List data,
{KeyPair? writer}) async { {SetDHTValueOptions? options}) async {
_ctx.ensureValid(); _ctx.ensureValid();
final nativeKey = jsonEncode(key).toNativeUtf8(); final nativeKey = jsonEncode(key).toNativeUtf8();
final nativeData = base64UrlNoPadEncode(data).toNativeUtf8(); final nativeData = base64UrlNoPadEncode(data).toNativeUtf8();
final nativeWriter = final nativeOptions =
writer != null ? jsonEncode(writer).toNativeUtf8() : nullptr; options != null ? jsonEncode(options).toNativeUtf8() : nullptr;
final recvPort = ReceivePort('routing_context_set_dht_value'); final recvPort = ReceivePort('routing_context_set_dht_value');
final sendPort = recvPort.sendPort; final sendPort = recvPort.sendPort;
_ctx.ffi._routingContextSetDHTValue(sendPort.nativePort, _ctx.id!, _ctx.ffi._routingContextSetDHTValue(sendPort.nativePort, _ctx.id!,
nativeKey, subkey, nativeData, nativeWriter); nativeKey, subkey, nativeData, nativeOptions);
final valueData = final valueData =
await processFutureOptJson(ValueData.fromJson, recvPort.first); await processFutureOptJson(ValueData.fromJson, recvPort.first);
return valueData; return valueData;

View file

@ -193,7 +193,7 @@ class VeilidRoutingContextJS extends VeilidRoutingContext {
@override @override
Future<ValueData?> setDHTValue(TypedKey key, int subkey, Uint8List data, Future<ValueData?> setDHTValue(TypedKey key, int subkey, Uint8List data,
{KeyPair? writer}) async { {SetDHTValueOptions? options}) async {
final id = _ctx.requireId(); final id = _ctx.requireId();
final opt = await _wrapApiPromise<String?>( final opt = await _wrapApiPromise<String?>(
js_util.callMethod(wasm, 'routing_context_set_dht_value', [ js_util.callMethod(wasm, 'routing_context_set_dht_value', [
@ -201,7 +201,7 @@ class VeilidRoutingContextJS extends VeilidRoutingContext {
jsonEncode(key), jsonEncode(key),
subkey, subkey,
base64UrlNoPadEncode(data), base64UrlNoPadEncode(data),
if (writer != null) jsonEncode(writer) else null if (options != null) jsonEncode(options) else null
])); ]));
if (opt == null) { if (opt == null) {
return null; return null;

View file

@ -793,14 +793,14 @@ pub extern "C" fn routing_context_set_dht_value(
key: FfiStr, key: FfiStr,
subkey: u32, subkey: u32,
data: FfiStr, data: FfiStr,
writer: FfiStr, options: FfiStr,
) { ) {
let key: veilid_core::TypedRecordKey = let key: veilid_core::TypedRecordKey =
veilid_core::deserialize_opt_json(key.into_opt_string()).unwrap(); veilid_core::deserialize_opt_json(key.into_opt_string()).unwrap();
let data: Vec<u8> = data_encoding::BASE64URL_NOPAD let data: Vec<u8> = data_encoding::BASE64URL_NOPAD
.decode(data.into_opt_string().unwrap().as_bytes()) .decode(data.into_opt_string().unwrap().as_bytes())
.unwrap(); .unwrap();
let writer: Option<veilid_core::KeyPair> = writer let options: Option<veilid_core::SetDHTValueOptions> = options
.into_opt_string() .into_opt_string()
.map(|s| veilid_core::deserialize_json(&s).unwrap()); .map(|s| veilid_core::deserialize_json(&s).unwrap());
@ -809,7 +809,7 @@ pub extern "C" fn routing_context_set_dht_value(
let routing_context = get_routing_context(id, "routing_context_set_dht_value")?; let routing_context = get_routing_context(id, "routing_context_set_dht_value")?;
let res = routing_context let res = routing_context
.set_dht_value(key, subkey, data, writer) .set_dht_value(key, subkey, data, options)
.await?; .await?;
APIResult::Ok(res) APIResult::Ok(res)
} }

View file

@ -238,14 +238,14 @@ async def test_open_writer_dht_value(api_connection: veilid.VeilidAPI):
# Verify subkey 0 can be set because override with the right writer # Verify subkey 0 can be set because override with the right writer
# Should have prior sequence number as its returned value because it exists online at seq 0 # Should have prior sequence number as its returned value because it exists online at seq 0
vdtemp = await rc.set_dht_value(key, ValueSubkey(0), va, veilid.KeyPair.from_parts(owner, secret)) vdtemp = await rc.set_dht_value(key, ValueSubkey(0), va, veilid.SetDHTValueOptions(veilid.KeyPair.from_parts(owner, secret)))
assert vdtemp is not None assert vdtemp is not None
assert vdtemp.data == vb assert vdtemp.data == vb
assert vdtemp.seq == 0 assert vdtemp.seq == 0
assert vdtemp.writer == owner assert vdtemp.writer == owner
# Should update the second time to seq 1 # Should update the second time to seq 1
vdtemp = await rc.set_dht_value(key, ValueSubkey(0), va, veilid.KeyPair.from_parts(owner, secret)) vdtemp = await rc.set_dht_value(key, ValueSubkey(0), va, veilid.SetDHTValueOptions(veilid.KeyPair.from_parts(owner, secret)))
assert vdtemp is None assert vdtemp is None
# Clean up # Clean up

View file

@ -84,7 +84,7 @@ class RoutingContext(ABC):
@abstractmethod @abstractmethod
async def set_dht_value( async def set_dht_value(
self, key: types.TypedKey, subkey: types.ValueSubkey, data: bytes, writer: Optional[types.KeyPair] = None self, key: types.TypedKey, subkey: types.ValueSubkey, data: bytes, options: Optional[types.SetDHTValueOptions] = None
) -> Optional[types.ValueData]: ) -> Optional[types.ValueData]:
pass pass

View file

@ -36,6 +36,7 @@ from .types import (
SafetySelection, SafetySelection,
SecretKey, SecretKey,
Sequencing, Sequencing,
SetDHTValueOptions,
SharedSecret, SharedSecret,
Signature, Signature,
Stability, Stability,
@ -721,12 +722,12 @@ class _JsonRoutingContext(RoutingContext):
return None if ret is None else ValueData.from_json(ret) return None if ret is None else ValueData.from_json(ret)
async def set_dht_value( async def set_dht_value(
self, key: TypedKey, subkey: ValueSubkey, data: bytes, writer: Optional[KeyPair] = None self, key: TypedKey, subkey: ValueSubkey, data: bytes, options: Optional[SetDHTValueOptions] = None
) -> Optional[ValueData]: ) -> Optional[ValueData]:
assert isinstance(key, TypedKey) assert isinstance(key, TypedKey)
assert isinstance(subkey, ValueSubkey) assert isinstance(subkey, ValueSubkey)
assert isinstance(data, bytes) assert isinstance(data, bytes)
assert writer is None or isinstance(writer, KeyPair) assert options is None or isinstance(options, SetDHTValueOptions)
ret = raise_api_result( ret = raise_api_result(
await self.api.send_ndjson_request( await self.api.send_ndjson_request(
@ -737,7 +738,7 @@ class _JsonRoutingContext(RoutingContext):
key=key, key=key,
subkey=subkey, subkey=subkey,
data=data, data=data,
writer=writer, options=options,
) )
) )
return None if ret is None else ValueData.from_json(ret) return None if ret is None else ValueData.from_json(ret)

View file

@ -3944,6 +3944,7 @@
] ]
}, },
"VeilidCapability": { "VeilidCapability": {
"description": "A four-character code",
"type": "array", "type": "array",
"items": { "items": {
"type": "integer", "type": "integer",

View file

@ -461,6 +461,16 @@
"key": { "key": {
"type": "string" "type": "string"
}, },
"options": {
"anyOf": [
{
"$ref": "#/definitions/SetDHTValueOptions"
},
{
"type": "null"
}
]
},
"rc_op": { "rc_op": {
"type": "string", "type": "string",
"enum": [ "enum": [
@ -471,12 +481,6 @@
"type": "integer", "type": "integer",
"format": "uint32", "format": "uint32",
"minimum": 0.0 "minimum": 0.0
},
"writer": {
"type": [
"string",
"null"
]
} }
} }
}, },
@ -1656,6 +1660,9 @@
} }
}, },
"definitions": { "definitions": {
"AllowOffline": {
"type": "boolean"
},
"DHTReportScope": { "DHTReportScope": {
"description": "DHT Record Report Scope", "description": "DHT Record Report Scope",
"oneOf": [ "oneOf": [
@ -1852,6 +1859,28 @@
"EnsureOrdered" "EnsureOrdered"
] ]
}, },
"SetDHTValueOptions": {
"type": "object",
"properties": {
"allow_offline": {
"description": "Defaults to true. If false, the value will not be written if the node is offline, and a TryAgain error will be returned.",
"anyOf": [
{
"$ref": "#/definitions/AllowOffline"
},
{
"type": "null"
}
]
},
"writer": {
"type": [
"string",
"null"
]
}
}
},
"Stability": { "Stability": {
"type": "string", "type": "string",
"enum": [ "enum": [

View file

@ -431,6 +431,27 @@ class DHTRecordReport:
return self.__dict__ return self.__dict__
class SetDHTValueOptions:
writer: Optional[KeyPair]
allow_offline: Optional[bool]
def __init__(self, writer: Optional[KeyPair], allow_offline: Optional[bool] = None):
self.writer = writer
self.allow_offline = allow_offline
def __repr__(self) -> str:
return f"<{self.__class__.__name__}(writer={self.writer!r}, allow_offline={self.allow_offline!r})>"
@classmethod
def from_json(cls, j: dict) -> Self:
return cls(
KeyPair(j["writer"]) if "writer" in j else None,
j["allow_offline"] if "allow_offline" in j else None,
)
def to_json(self) -> dict:
return self.__dict__
@total_ordering @total_ordering
class ValueData: class ValueData:
seq: ValueSeqNum seq: ValueSeqNum

View file

@ -324,11 +324,11 @@ impl JsonRequestProcessor {
key, key,
subkey, subkey,
data, data,
writer, options,
} => RoutingContextResponseOp::SetDhtValue { } => RoutingContextResponseOp::SetDhtValue {
result: to_json_api_result( result: to_json_api_result(
routing_context routing_context
.set_dht_value(key, subkey, data, writer) .set_dht_value(key, subkey, data, options)
.await, .await,
), ),
}, },

View file

@ -72,8 +72,7 @@ pub enum RoutingContextRequestOp {
#[serde(with = "as_human_base64")] #[serde(with = "as_human_base64")]
#[schemars(with = "String")] #[schemars(with = "String")]
data: Vec<u8>, data: Vec<u8>,
#[schemars(with = "Option<String>")] options: Option<SetDHTValueOptions>,
writer: Option<KeyPair>,
}, },
WatchDhtValues { WatchDhtValues {
#[schemars(with = "String")] #[schemars(with = "String")]

View file

@ -593,7 +593,7 @@ pub fn routing_context_set_dht_value(
key: String, key: String,
subkey: u32, subkey: u32,
data: String, data: String,
writer: Option<String>, options: Option<String>,
) -> Promise { ) -> Promise {
wrap_api_future_json(async move { wrap_api_future_json(async move {
let key: veilid_core::TypedRecordKey = let key: veilid_core::TypedRecordKey =
@ -601,15 +601,16 @@ pub fn routing_context_set_dht_value(
let data: Vec<u8> = data_encoding::BASE64URL_NOPAD let data: Vec<u8> = data_encoding::BASE64URL_NOPAD
.decode(data.as_bytes()) .decode(data.as_bytes())
.map_err(VeilidAPIError::generic)?; .map_err(VeilidAPIError::generic)?;
let writer: Option<veilid_core::KeyPair> = match writer {
Some(s) => veilid_core::deserialize_json(&s).map_err(VeilidAPIError::generic)?, let options: Option<veilid_core::SetDHTValueOptions> = match options {
Some(s) => Some(veilid_core::deserialize_json(&s).map_err(VeilidAPIError::generic)?),
None => None, None => None,
}; };
let routing_context = get_routing_context(id, "routing_context_set_dht_value")?; let routing_context = get_routing_context(id, "routing_context_set_dht_value")?;
let res = routing_context let res = routing_context
.set_dht_value(key, subkey, data, writer) .set_dht_value(key, subkey, data, options)
.await?; .await?;
APIResult::Ok(res) APIResult::Ok(res)
}) })

View file

@ -322,17 +322,14 @@ impl VeilidRoutingContext {
key: String, key: String,
subkey: u32, subkey: u32,
data: Box<[u8]>, data: Box<[u8]>,
writer: Option<String>, options: Option<SetDHTValueOptions>,
) -> APIResult<Option<ValueData>> { ) -> APIResult<Option<ValueData>> {
let key = TypedRecordKey::from_str(&key)?; let key = TypedRecordKey::from_str(&key)?;
let data = data.into_vec(); let data = data.into_vec();
let writer = writer
.map(|writer| KeyPair::from_str(&writer))
.map_or(APIResult::Ok(None), |r| r.map(Some))?;
let routing_context = self.getRoutingContext()?; let routing_context = self.getRoutingContext()?;
let res = routing_context let res = routing_context
.set_dht_value(key, subkey, data, writer) .set_dht_value(key, subkey, data, options)
.await?; .await?;
APIResult::Ok(res) APIResult::Ok(res)
} }

View file

@ -21,7 +21,7 @@
}, },
"../pkg": { "../pkg": {
"name": "veilid-wasm", "name": "veilid-wasm",
"version": "0.4.6", "version": "0.4.7",
"dev": true, "dev": true,
"license": "MPL-2.0" "license": "MPL-2.0"
}, },

View file

@ -225,7 +225,10 @@ describe('VeilidRoutingContext', () => {
dhtRecord.key, dhtRecord.key,
0, 0,
textEncoder.encode(`${data}👋`), textEncoder.encode(`${data}👋`),
`${dhtRecord.owner}:${dhtRecord.owner_secret}` {
writer: `${dhtRecord.owner}:${dhtRecord.owner_secret}`,
allow_offline: undefined
}
); );
expect(setValueRes).toBeUndefined(); expect(setValueRes).toBeUndefined();
}); });