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**
- _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:
- 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)

View file

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

View file

@ -727,7 +727,7 @@ impl StorageManager {
record_key: TypedRecordKey,
subkey: ValueSubkey,
data: Vec<u8>,
writer: Option<KeyPair>,
options: Option<SetDHTValueOptions>,
) -> VeilidAPIResult<Option<ValueData>> {
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
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
let Some(writer) = opt_writer else {
@ -781,9 +785,13 @@ impl StorageManager {
};
// 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
apibail_generic!("failed schema validation");
apibail_generic!(format!(
"failed schema validation: {}:{}",
record_key, subkey
));
}
// Sign the new value data with the writer
@ -821,10 +829,19 @@ impl StorageManager {
};
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() );
// 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);
} else {
apibail_try_again!("offline, try again later");
}
};
// Drop the lock for network access
@ -847,12 +864,17 @@ impl StorageManager {
Err(e) => {
// Failed to write, try again later
let mut inner = self.inner.lock().await;
if allow_offline == AllowOffline(true) {
Self::add_offline_subkey_write_inner(
&mut inner,
record_key,
subkey,
safety_selection,
);
} else {
apibail_try_again!("offline, try again later");
}
// Remove from active subkey writes
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());
// Validate with schema
if !ctx.schema.check_subkey_value_data(
if ctx.schema.check_subkey_value_data(
descriptor.owner(),
subkey,
value.value_data(),
) {
).is_err() {
// Validation failed, ignore this value and pretend we never saw this node
return Ok(FanoutCallOutput{peer_info_list: vec![], disposition: FanoutCallDisposition::Invalid});
}
@ -474,7 +474,10 @@ impl StorageManager {
};
// 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
return Ok(NetworkResult::invalid_message("failed schema validation"));
}

View file

@ -1179,11 +1179,10 @@ impl StorageManager {
let schema = descriptor.schema()?;
// Validate with schema
if !schema.check_subkey_value_data(
descriptor.owner(),
first_subkey,
value.value_data(),
) {
if schema
.check_subkey_value_data(descriptor.owner(), first_subkey, value.value_data())
.is_err()
{
// Validation failed, ignore this value
// Move to the next node
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
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;
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();
}
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
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_set_get_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;
}

View file

@ -1669,7 +1669,15 @@ impl VeilidAPI {
// Do a record set
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
{
Err(e) => {

View file

@ -435,15 +435,15 @@ impl RoutingContext {
key: TypedRecordKey,
subkey: ValueSubkey,
data: Vec<u8>,
writer: Option<KeyPair>,
options: Option<SetDHTValueOptions>,
) -> VeilidAPIResult<Option<ValueData>> {
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)?;
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.

View file

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

View file

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

View file

@ -64,13 +64,12 @@ impl DHTSchema {
}
/// Check a subkey value data against the schema
#[must_use]
pub fn check_subkey_value_data(
&self,
owner: &PublicKey,
subkey: ValueSubkey,
value_data: &ValueData,
) -> bool {
) -> VeilidAPIResult<()> {
match self {
DHTSchema::DFLT(d) => d.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
#[must_use]
pub fn check_subkey_value_data(
&self,
owner: &PublicKey,
subkey: ValueSubkey,
value_data: &ValueData,
) -> bool {
) -> VeilidAPIResult<()> {
let mut cur_subkey = subkey as usize;
let max_value_len = usize::min(
@ -127,14 +126,22 @@ impl DHTSchemaSMPL {
if value_data.writer() == owner {
// Ensure value size is within additional limit
if value_data.data_size() <= max_value_len {
return true;
return Ok(());
}
// Value too big
return false;
apibail_invalid_argument!(
"value too big",
"data",
format!("{:?}", value_data.data())
);
}
// Wrong writer
return false;
apibail_invalid_argument!(
"wrong writer",
"writer",
format!("{:?}", value_data.writer())
);
}
cur_subkey -= self.o_cnt as usize;
@ -146,20 +153,28 @@ impl DHTSchemaSMPL {
if value_data.writer() == &m.m_key {
// Ensure value size is in allowed range
if value_data.data_size() <= max_value_len {
return true;
return Ok(());
}
// Value too big
return false;
apibail_invalid_argument!(
"value too big",
"data",
format!("{:?}", value_data.data())
);
}
// Wrong writer
return false;
apibail_invalid_argument!(
"wrong writer",
"writer",
format!("{:?}", value_data.writer())
);
}
cur_subkey -= m.m_cnt as usize;
}
// Subkey out of range
false
apibail_invalid_argument!("subkey out of range", "subkey", subkey);
}
/// 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
// exists online at seq 0
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!.data, equals(vb));
expect(vdtemp.seq, equals(0));
@ -252,7 +253,8 @@ Future<void> testOpenWriterDHTValue() async {
// Should update the second time to seq 1
vdtemp = await rc.setDHTValue(key, 0, va,
writer: KeyPair(key: owner, secret: secret));
options:
SetDHTValueOptions(writer: KeyPair(key: owner, secret: secret)));
expect(vdtemp, isNull);
// Clean up

View file

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

View file

@ -273,6 +273,25 @@ enum DHTReportScope {
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
@ -300,7 +319,7 @@ abstract class VeilidRoutingContext {
Future<ValueData?> getDHTValue(TypedKey key, int subkey,
{bool forceRefresh = false});
Future<ValueData?> setDHTValue(TypedKey key, int subkey, Uint8List data,
{KeyPair? writer});
{SetDHTValueOptions? options});
Future<bool> watchDHTValues(TypedKey key,
{List<ValueSubkeyRange>? subkeys, Timestamp? expiration, int? count});
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

View file

@ -128,3 +128,15 @@ Map<String, dynamic> _$DHTRecordReportToJson(_DHTRecordReport instance) =>
'local_seqs': instance.localSeqs,
'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
sealed class VeilidConfigTLS with _$VeilidConfigTLS {
const factory VeilidConfigTLS({
@ -370,6 +382,7 @@ sealed class VeilidConfigNetwork with _$VeilidConfigNetwork {
required VeilidConfigTLS tls,
required VeilidConfigApplication application,
required VeilidConfigProtocol protocol,
required VeilidConfigPrivacy privacy,
String? networkKeyPassword,
}) = _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
mixin _$VeilidConfigTLS implements DiagnosticableTreeMixin {
String get certificatePath;
@ -5922,6 +6085,7 @@ mixin _$VeilidConfigNetwork implements DiagnosticableTreeMixin {
VeilidConfigTLS get tls;
VeilidConfigApplication get application;
VeilidConfigProtocol get protocol;
VeilidConfigPrivacy get privacy;
String? get networkKeyPassword;
/// Create a copy of VeilidConfigNetwork
@ -5965,6 +6129,7 @@ mixin _$VeilidConfigNetwork implements DiagnosticableTreeMixin {
..add(DiagnosticsProperty('tls', tls))
..add(DiagnosticsProperty('application', application))
..add(DiagnosticsProperty('protocol', protocol))
..add(DiagnosticsProperty('privacy', privacy))
..add(DiagnosticsProperty('networkKeyPassword', networkKeyPassword));
}
@ -6012,6 +6177,7 @@ mixin _$VeilidConfigNetwork implements DiagnosticableTreeMixin {
other.application == application) &&
(identical(other.protocol, protocol) ||
other.protocol == protocol) &&
(identical(other.privacy, privacy) || other.privacy == privacy) &&
(identical(other.networkKeyPassword, networkKeyPassword) ||
other.networkKeyPassword == networkKeyPassword));
}
@ -6038,12 +6204,13 @@ mixin _$VeilidConfigNetwork implements DiagnosticableTreeMixin {
tls,
application,
protocol,
privacy,
networkKeyPassword
]);
@override
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,
VeilidConfigApplication application,
VeilidConfigProtocol protocol,
VeilidConfigPrivacy privacy,
String? networkKeyPassword});
$VeilidConfigRoutingTableCopyWith<$Res> get routingTable;
@ -6080,6 +6248,7 @@ abstract mixin class $VeilidConfigNetworkCopyWith<$Res> {
$VeilidConfigTLSCopyWith<$Res> get tls;
$VeilidConfigApplicationCopyWith<$Res> get application;
$VeilidConfigProtocolCopyWith<$Res> get protocol;
$VeilidConfigPrivacyCopyWith<$Res> get privacy;
}
/// @nodoc
@ -6113,6 +6282,7 @@ class _$VeilidConfigNetworkCopyWithImpl<$Res>
Object? tls = null,
Object? application = null,
Object? protocol = null,
Object? privacy = null,
Object? networkKeyPassword = freezed,
}) {
return _then(_self.copyWith(
@ -6188,6 +6358,10 @@ class _$VeilidConfigNetworkCopyWithImpl<$Res>
? _self.protocol
: protocol // ignore: cast_nullable_to_non_nullable
as VeilidConfigProtocol,
privacy: null == privacy
? _self.privacy
: privacy // ignore: cast_nullable_to_non_nullable
as VeilidConfigPrivacy,
networkKeyPassword: freezed == networkKeyPassword
? _self.networkKeyPassword
: networkKeyPassword // ignore: cast_nullable_to_non_nullable
@ -6254,6 +6428,16 @@ class _$VeilidConfigNetworkCopyWithImpl<$Res>
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
@ -6280,6 +6464,7 @@ class _VeilidConfigNetwork
required this.tls,
required this.application,
required this.protocol,
required this.privacy,
this.networkKeyPassword});
factory _VeilidConfigNetwork.fromJson(Map<String, dynamic> json) =>
_$VeilidConfigNetworkFromJson(json);
@ -6321,6 +6506,8 @@ class _VeilidConfigNetwork
@override
final VeilidConfigProtocol protocol;
@override
final VeilidConfigPrivacy privacy;
@override
final String? networkKeyPassword;
/// Create a copy of VeilidConfigNetwork
@ -6369,6 +6556,7 @@ class _VeilidConfigNetwork
..add(DiagnosticsProperty('tls', tls))
..add(DiagnosticsProperty('application', application))
..add(DiagnosticsProperty('protocol', protocol))
..add(DiagnosticsProperty('privacy', privacy))
..add(DiagnosticsProperty('networkKeyPassword', networkKeyPassword));
}
@ -6416,6 +6604,7 @@ class _VeilidConfigNetwork
other.application == application) &&
(identical(other.protocol, protocol) ||
other.protocol == protocol) &&
(identical(other.privacy, privacy) || other.privacy == privacy) &&
(identical(other.networkKeyPassword, networkKeyPassword) ||
other.networkKeyPassword == networkKeyPassword));
}
@ -6442,12 +6631,13 @@ class _VeilidConfigNetwork
tls,
application,
protocol,
privacy,
networkKeyPassword
]);
@override
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,
VeilidConfigApplication application,
VeilidConfigProtocol protocol,
VeilidConfigPrivacy privacy,
String? networkKeyPassword});
@override
@ -6492,6 +6683,8 @@ abstract mixin class _$VeilidConfigNetworkCopyWith<$Res>
$VeilidConfigApplicationCopyWith<$Res> get application;
@override
$VeilidConfigProtocolCopyWith<$Res> get protocol;
@override
$VeilidConfigPrivacyCopyWith<$Res> get privacy;
}
/// @nodoc
@ -6525,6 +6718,7 @@ class __$VeilidConfigNetworkCopyWithImpl<$Res>
Object? tls = null,
Object? application = null,
Object? protocol = null,
Object? privacy = null,
Object? networkKeyPassword = freezed,
}) {
return _then(_VeilidConfigNetwork(
@ -6600,6 +6794,10 @@ class __$VeilidConfigNetworkCopyWithImpl<$Res>
? _self.protocol
: protocol // ignore: cast_nullable_to_non_nullable
as VeilidConfigProtocol,
privacy: null == privacy
? _self.privacy
: privacy // ignore: cast_nullable_to_non_nullable
as VeilidConfigPrivacy,
networkKeyPassword: freezed == networkKeyPassword
? _self.networkKeyPassword
: networkKeyPassword // ignore: cast_nullable_to_non_nullable
@ -6666,6 +6864,16 @@ class __$VeilidConfigNetworkCopyWithImpl<$Res>
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

View file

@ -314,6 +314,17 @@ Map<String, dynamic> _$VeilidConfigProtocolToJson(
'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(
certificatePath: json['certificate_path'] as String,
@ -472,6 +483,7 @@ _VeilidConfigNetwork _$VeilidConfigNetworkFromJson(Map<String, dynamic> json) =>
tls: VeilidConfigTLS.fromJson(json['tls']),
application: VeilidConfigApplication.fromJson(json['application']),
protocol: VeilidConfigProtocol.fromJson(json['protocol']),
privacy: VeilidConfigPrivacy.fromJson(json['privacy']),
networkKeyPassword: json['network_key_password'] as String?,
);
@ -499,6 +511,7 @@ Map<String, dynamic> _$VeilidConfigNetworkToJson(
'tls': instance.tls.toJson(),
'application': instance.application.toJson(),
'protocol': instance.protocol.toJson(),
'privacy': instance.privacy.toJson(),
'network_key_password': instance.networkKeyPassword,
};

View file

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

View file

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

View file

@ -793,14 +793,14 @@ pub extern "C" fn routing_context_set_dht_value(
key: FfiStr,
subkey: u32,
data: FfiStr,
writer: FfiStr,
options: FfiStr,
) {
let key: veilid_core::TypedRecordKey =
veilid_core::deserialize_opt_json(key.into_opt_string()).unwrap();
let data: Vec<u8> = data_encoding::BASE64URL_NOPAD
.decode(data.into_opt_string().unwrap().as_bytes())
.unwrap();
let writer: Option<veilid_core::KeyPair> = writer
let options: Option<veilid_core::SetDHTValueOptions> = options
.into_opt_string()
.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 res = routing_context
.set_dht_value(key, subkey, data, writer)
.set_dht_value(key, subkey, data, options)
.await?;
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
# 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.data == vb
assert vdtemp.seq == 0
assert vdtemp.writer == owner
# 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
# Clean up

View file

@ -84,7 +84,7 @@ class RoutingContext(ABC):
@abstractmethod
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]:
pass

View file

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

View file

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

View file

@ -461,6 +461,16 @@
"key": {
"type": "string"
},
"options": {
"anyOf": [
{
"$ref": "#/definitions/SetDHTValueOptions"
},
{
"type": "null"
}
]
},
"rc_op": {
"type": "string",
"enum": [
@ -471,12 +481,6 @@
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"writer": {
"type": [
"string",
"null"
]
}
}
},
@ -1656,6 +1660,9 @@
}
},
"definitions": {
"AllowOffline": {
"type": "boolean"
},
"DHTReportScope": {
"description": "DHT Record Report Scope",
"oneOf": [
@ -1852,6 +1859,28 @@
"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": {
"type": "string",
"enum": [

View file

@ -431,6 +431,27 @@ class DHTRecordReport:
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
class ValueData:
seq: ValueSeqNum

View file

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

View file

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

View file

@ -593,7 +593,7 @@ pub fn routing_context_set_dht_value(
key: String,
subkey: u32,
data: String,
writer: Option<String>,
options: Option<String>,
) -> Promise {
wrap_api_future_json(async move {
let key: veilid_core::TypedRecordKey =
@ -601,15 +601,16 @@ pub fn routing_context_set_dht_value(
let data: Vec<u8> = data_encoding::BASE64URL_NOPAD
.decode(data.as_bytes())
.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,
};
let routing_context = get_routing_context(id, "routing_context_set_dht_value")?;
let res = routing_context
.set_dht_value(key, subkey, data, writer)
.set_dht_value(key, subkey, data, options)
.await?;
APIResult::Ok(res)
})

View file

@ -322,17 +322,14 @@ impl VeilidRoutingContext {
key: String,
subkey: u32,
data: Box<[u8]>,
writer: Option<String>,
options: Option<SetDHTValueOptions>,
) -> APIResult<Option<ValueData>> {
let key = TypedRecordKey::from_str(&key)?;
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 res = routing_context
.set_dht_value(key, subkey, data, writer)
.set_dht_value(key, subkey, data, options)
.await?;
APIResult::Ok(res)
}

View file

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

View file

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