Skip to content

Commit 7be8711

Browse files
authored
Handler & SubductionBuilder (#77)
1 parent e64a02d commit 7be8711

File tree

38 files changed

+2894
-2006
lines changed

38 files changed

+2894
-2006
lines changed

nix/home-manager-module.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ in {
99
options.services.subduction = {
1010
package = lib.mkOption {
1111
type = lib.types.package;
12-
default = self.packages.${pkgs.system}.subduction_cli;
12+
default = self.packages.${pkgs.stdenv.hostPlatform.system}.subduction_cli;
1313
defaultText = lib.literalExpression "pkgs.subduction_cli";
1414
description = "The Subduction CLI package to use.";
1515
};

nix/nixos-module.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ in {
99
options.services.subduction = {
1010
package = lib.mkOption {
1111
type = lib.types.package;
12-
default = self.packages.${pkgs.system}.subduction_cli;
12+
default = self.packages.${pkgs.stdenv.hostPlatform.system}.subduction_cli;
1313
defaultText = lib.literalExpression "pkgs.subduction_cli";
1414
description = "The Subduction CLI package to use.";
1515
};

sedimentree_core/src/codec/decode.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,40 @@ use super::{
55
schema::Schema,
66
};
77

8-
/// Decode a type from its canonical binary representation.
8+
/// Decode a type from its complete wire representation.
99
///
10-
/// Types implementing this trait can be parsed from received bytes.
11-
pub trait Decode: Schema + Sized {
10+
/// This is the generic decoding trait for any type that can be
11+
/// deserialized from bytes, including both message envelopes
12+
/// and signed payloads.
13+
pub trait Decode: Sized {
1214
/// Minimum valid encoded size (for early rejection).
13-
///
14-
/// This is the size of the full signed message (schema + issuer + fields + signature).
1515
const MIN_SIZE: usize;
1616

17+
/// Decode from complete wire bytes.
18+
///
19+
/// The buffer should contain the full encoded representation
20+
/// of the type, including any framing or headers.
21+
///
22+
/// # Errors
23+
///
24+
/// Returns [`DecodeError`] if the buffer is malformed, too short,
25+
/// contains invalid values, or fails validation.
26+
fn try_decode(buf: &[u8]) -> Result<Self, DecodeError>;
27+
}
28+
29+
/// Decode the fields portion of a signed payload.
30+
///
31+
/// This trait is for types that live inside [`Signed<T>`] envelopes.
32+
/// The `buf` passed to [`try_decode_fields`](DecodeFields::try_decode_fields)
33+
/// contains only the type-specific fields (after schema + issuer,
34+
/// before signature), not the full wire message.
35+
///
36+
/// [`Signed<T>`]: https://docs.rs/subduction_crypto/latest/subduction_crypto/signed/struct.Signed.html
37+
pub trait DecodeFields: Schema + Sized {
38+
/// Minimum valid size of the full signed message
39+
/// (schema + issuer + fields + signature), for early rejection.
40+
const MIN_SIGNED_SIZE: usize;
41+
1742
/// Decode type-specific fields from the buffer.
1843
///
1944
/// `buf` contains only the fields portion (after schema + issuer,

sedimentree_core/src/fragment.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use id::FragmentId;
1111
use crate::{
1212
blob::{Blob, BlobMeta, has_meta::HasBlobMeta},
1313
codec::{
14-
decode::{self, Decode},
14+
decode::{self, DecodeFields},
1515
encode::{self, EncodeFields},
1616
error::{Bijou64Error, BufferTooShort, DecodeError, ReadingType},
1717
schema::{self, Schema},
@@ -335,8 +335,8 @@ impl EncodeFields for Fragment {
335335
}
336336
}
337337

338-
impl Decode for Fragment {
339-
const MIN_SIZE: usize = CODEC_MIN_SIZE;
338+
impl DecodeFields for Fragment {
339+
const MIN_SIGNED_SIZE: usize = CODEC_MIN_SIZE;
340340

341341
fn try_decode_fields(buf: &[u8]) -> Result<Self, DecodeError> {
342342
if buf.len() < CODEC_MIN_FIELDS_SIZE {
@@ -472,7 +472,7 @@ mod tests {
472472
#[test]
473473
fn min_size_is_correct() {
474474
// Schema(4) + IssuerVK(32) + SedimentreeId(32) + Head(32) + BlobDigest(32) + BndryCnt(1) + CkptCnt(2) + BlobSize(bijou64 min=1) + Signature(64)
475-
assert_eq!(Fragment::MIN_SIZE, 200);
475+
assert_eq!(Fragment::MIN_SIGNED_SIZE, 200);
476476
}
477477
}
478478
use alloc::collections::BTreeSet;
@@ -691,7 +691,7 @@ mod tests {
691691
mod proptests {
692692
use crate::{
693693
codec::{
694-
decode::Decode,
694+
decode::DecodeFields,
695695
encode::{Encode, EncodeFields},
696696
},
697697
commit::CountLeadingZeroBytes,

sedimentree_core/src/loose_commit.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use id::CommitId;
99
use crate::{
1010
blob::{Blob, BlobMeta, has_meta::HasBlobMeta},
1111
codec::{
12-
decode::{self, Decode},
12+
decode::{self, DecodeFields},
1313
encode::{self, EncodeFields},
1414
error::{Bijou64Error, BufferTooShort, DecodeError, ReadingType},
1515
schema::{self, Schema},
@@ -132,8 +132,8 @@ impl EncodeFields for LooseCommit {
132132
}
133133
}
134134

135-
impl Decode for LooseCommit {
136-
const MIN_SIZE: usize = CODEC_MIN_SIZE;
135+
impl DecodeFields for LooseCommit {
136+
const MIN_SIGNED_SIZE: usize = CODEC_MIN_SIZE;
137137

138138
fn try_decode_fields(buf: &[u8]) -> Result<Self, DecodeError> {
139139
if buf.len() < CODEC_MIN_FIELDS_SIZE {
@@ -233,7 +233,7 @@ mod tests {
233233
#[test]
234234
fn codec_min_size_is_correct() {
235235
// Schema(4) + IssuerVK(32) + SedimentreeId(32) + BlobDigest(32) + ParentCnt(1) + BlobSize(bijou64 min=1) + Signature(64)
236-
assert_eq!(LooseCommit::MIN_SIZE, 166);
236+
assert_eq!(LooseCommit::MIN_SIGNED_SIZE, 166);
237237
}
238238
}
239239

sedimentree_wasm/src/storage.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use futures::future::LocalBoxFuture;
1111
use js_sys::{Promise, Uint8Array};
1212
use sedimentree_core::{
1313
blob::Blob,
14-
codec::{decode::Decode, encode::Encode, error::DecodeError},
14+
codec::{decode::DecodeFields, encode::Encode, error::DecodeError},
1515
crypto::digest::Digest,
1616
fragment::Fragment,
1717
id::{BadSedimentreeId, SedimentreeId},
@@ -154,7 +154,7 @@ impl core::fmt::Debug for JsStorage {
154154
/// Parse a JS array of digests.
155155
///
156156
/// Returns an error if any element cannot be cast to a `Digest`.
157-
fn parse_digest_array<T: Encode + Decode>(
157+
fn parse_digest_array<T: Encode + DecodeFields>(
158158
js_value: &JsValue,
159159
) -> Result<Set<Digest<T>>, JsStorageError> {
160160
let array = js_sys::Array::from(js_value);

subduction_cli/src/server.rs

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ use std::{net::SocketAddr, path::PathBuf, time::Duration};
77

88
use eyre::Result;
99
use iroh::EndpointAddr;
10-
use sedimentree_core::{
11-
commit::CountLeadingZeroBytes, id::SedimentreeId, sedimentree::Sedimentree,
12-
};
10+
use sedimentree_core::commit::CountLeadingZeroBytes;
1311
use sedimentree_fs_storage::FsStorage;
1412
use subduction_core::{
1513
connection::{
@@ -18,9 +16,8 @@ use subduction_core::{
1816
},
1917
peer::id::PeerId,
2018
policy::open::OpenPolicy,
21-
sharded_map::ShardedMap,
2219
storage::metrics::{MetricsStorage, RefreshMetrics},
23-
subduction::{Subduction, pending_blob_requests::DEFAULT_MAX_PENDING_BLOB_REQUESTS},
20+
subduction::{Subduction, builder::SubductionBuilder},
2421
timestamp::TimestampSeconds,
2522
};
2623
use subduction_crypto::{nonce::Nonce, signer::memory::MemorySigner};
@@ -203,20 +200,18 @@ pub(crate) async fn run(args: ServerArgs, token: CancellationToken) -> Result<()
203200
let discovery_id = Some(DiscoveryId::new(service_name.as_bytes()));
204201
let discovery_audience: Option<Audience> = discovery_id.map(Audience::discover_id);
205202

206-
let sedimentrees: ShardedMap<SedimentreeId, Sedimentree> = ShardedMap::new();
203+
// Build the Subduction instance with all defaults
204+
let mut builder = SubductionBuilder::new()
205+
.signer(signer.clone())
206+
.storage(storage, Arc::new(OpenPolicy))
207+
.spawner(TokioSpawn);
207208

208-
// Create the unified Subduction instance (parameterized over UnifiedTransport)
209-
let (subduction, listener_fut, manager_fut): (CliSubduction, _, _) = Subduction::new(
210-
discovery_id,
211-
signer.clone(),
212-
storage,
213-
OpenPolicy,
214-
NonceCache::default(),
215-
CountLeadingZeroBytes,
216-
sedimentrees,
217-
TokioSpawn,
218-
DEFAULT_MAX_PENDING_BLOB_REQUESTS,
219-
);
209+
if let Some(id) = discovery_id {
210+
builder = builder.discovery_id(id);
211+
}
212+
213+
let (subduction, _handler, listener_fut, manager_fut): (CliSubduction, _, _, _) =
214+
builder.build();
220215

221216
let server_peer_id = subduction.peer_id();
222217

subduction_cli/tests/iroh_e2e.rs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,10 @@ use std::{
3131
use future_form::Sendable;
3232
use sedimentree_core::{blob::Blob, commit::CountLeadingZeroBytes, id::SedimentreeId};
3333
use subduction_core::{
34-
connection::{nonce_cache::NonceCache, test_utils::TokioSpawn},
34+
connection::test_utils::TokioSpawn,
3535
policy::open::OpenPolicy,
36-
sharded_map::ShardedMap,
3736
storage::memory::MemoryStorage,
38-
subduction::{Subduction, pending_blob_requests::DEFAULT_MAX_PENDING_BLOB_REQUESTS},
37+
subduction::{Subduction, builder::SubductionBuilder},
3938
timestamp::TimestampSeconds,
4039
};
4140
use subduction_crypto::signer::memory::MemorySigner;
@@ -188,17 +187,12 @@ async fn wait_for_http(base_url: &str) {
188187
async fn connect_to_server(base_url: &str, client_seed: u8, service_name: &str) -> TestSubduction {
189188
let client_signer = signer(client_seed);
190189

191-
let (subduction, listener_fut, manager_fut): (TestSubduction, _, _) = Subduction::new(
192-
None,
193-
client_signer.clone(),
194-
MemoryStorage::default(),
195-
OpenPolicy,
196-
NonceCache::default(),
197-
CountLeadingZeroBytes,
198-
ShardedMap::new(),
199-
TokioSpawn,
200-
DEFAULT_MAX_PENDING_BLOB_REQUESTS,
201-
);
190+
let (subduction, _handler, listener_fut, manager_fut): (TestSubduction, _, _, _) =
191+
SubductionBuilder::new()
192+
.signer(client_signer.clone())
193+
.storage(MemoryStorage::default(), Arc::new(OpenPolicy))
194+
.spawner(TokioSpawn)
195+
.build::<Sendable, HttpLongPollConnection<FuturesTimerTimeout>>();
202196

203197
tokio::spawn(listener_fut);
204198
tokio::spawn(manager_fut);

subduction_core/src/connection/handshake.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ use crate::{connection::nonce_cache::NonceCache, peer::id::PeerId, timestamp::Ti
6969
use sedimentree_core::{
7070
codec::{
7171
decode,
72-
decode::Decode,
72+
decode::DecodeFields,
7373
encode,
7474
encode::EncodeFields,
7575
error::{DecodeError, InvalidEnumTag},
@@ -309,8 +309,8 @@ impl EncodeFields for Challenge {
309309
}
310310
}
311311

312-
impl Decode for Challenge {
313-
const MIN_SIZE: usize = CHALLENGE_MIN_SIZE;
312+
impl DecodeFields for Challenge {
313+
const MIN_SIGNED_SIZE: usize = CHALLENGE_MIN_SIZE;
314314

315315
fn try_decode_fields(buf: &[u8]) -> Result<Self, DecodeError> {
316316
if buf.len() < CHALLENGE_FIELDS_SIZE {
@@ -381,8 +381,8 @@ impl EncodeFields for Response {
381381
}
382382
}
383383

384-
impl Decode for Response {
385-
const MIN_SIZE: usize = RESPONSE_MIN_SIZE;
384+
impl DecodeFields for Response {
385+
const MIN_SIGNED_SIZE: usize = RESPONSE_MIN_SIZE;
386386

387387
fn try_decode_fields(buf: &[u8]) -> Result<Self, DecodeError> {
388388
if buf.len() < RESPONSE_FIELDS_SIZE {

0 commit comments

Comments
 (0)