Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
42a7a72
fix 1 off-by-2 and 2 off-by-1 errors in comment
tweedegolf-marc Aug 5, 2025
03c8661
introduce a cesr const helper function
tweedegolf-marc Aug 5, 2025
ba9952c
add version encoding/decoding to envelope
tweedegolf-marc Aug 5, 2025
a1e98ae
change VID code (this makes a test failing due to breaking a signature)
tweedegolf-marc Aug 5, 2025
49fb629
change signature of detected_tsp_header_and_confidentiality
tweedegolf-marc Aug 14, 2025
f58d937
move sender/receiver to earlier in the envelope
tweedegolf-marc Aug 14, 2025
9c2acba
slight refactor of msgtype handling in anticipation of introducing th…
tweedegolf-marc Aug 18, 2025
afe43f3
use the new type codes
tweedegolf-marc Aug 18, 2025
53c3712
add more convenience
tweedegolf-marc Aug 18, 2025
67116c1
remove old version check
tweedegolf-marc Aug 18, 2025
091878a
decouple the presence of the sender with the -Z code
tweedegolf-marc Aug 18, 2025
ca780b0
change routed message encoding to use 'nested' message encoding
tweedegolf-marc Aug 18, 2025
49dddb6
always require a hop list
tweedegolf-marc Aug 18, 2025
9d5a2d4
add a convenience function
tweedegolf-marc Aug 18, 2025
c1a44ad
add X3RR
tweedegolf-marc Aug 18, 2025
6068577
esthetic touchups
tweedegolf-marc Aug 18, 2025
20eb481
merge XRFI for referral and initiation
tweedegolf-marc Aug 18, 2025
987e3fd
derive crypto from CESR
tweedegolf-marc Sep 24, 2025
d01e3e7
there is no provision for large data
tweedegolf-marc Sep 25, 2025
7d9a430
add large counts
tweedegolf-marc Sep 25, 2025
6bd83b4
fix nacl test
tweedegolf-marc Sep 25, 2025
11521a4
fix fuzzing error
tweedegolf-marc Sep 25, 2025
67d6d9c
remove dead code
tweedegolf-marc Sep 25, 2025
fc937b4
add placeholder for signature in NewIdentifierProposal
tweedegolf-marc Sep 25, 2025
0d36ee8
replace old codes and remove old defs
tweedegolf-marc Sep 25, 2025
8a0bbdd
fix nacl test
tweedegolf-marc Sep 26, 2025
b0755ee
Fix intermediary "temporary" routine
tweedegolf-marc Sep 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/fuzz_targets/payload_encode_decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ fuzz_target!(|data: cesr::fuzzing::Wrapper| {
cesr::Payload::RoutedMessage(route, _) => assert!(route.is_empty()),
_ => todo!(),
},
Err(cesr::error::EncodeError::InvalidVid) => {}
_ => todo!(),
}
});
46 changes: 46 additions & 0 deletions tsp_sdk/src/cesr/consts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
macro_rules! cesr {
($value: expr) => {
$crate::cesr::consts::cesr_int($value) as _
};
}

pub(super) use cesr;

/// A function for more easy encoding of CESR constants
pub const fn cesr_int(x: &str) -> u64 {
let x = x.as_bytes();
let mut acc = 0;
let mut i = 0;
while i < x.len() {
let ch = x[i];
acc = acc << 6
| match ch {
ch if ch.is_ascii_uppercase() => ch - b'A',
ch if ch.is_ascii_lowercase() => ch - b'a' + 26,
ch if ch.is_ascii_digit() => ch - b'0' + 52,
b'-' => 62,
b'_' => 63,
_ => panic!("not a base64url character"),
} as u64;
i += 1;
}

acc
}

/// A function for giving the contents of "TSP_TYPECODE" fields
pub const fn cesr_data<const N: usize>(x: &str) -> [u8; N] {
let val = cesr_int(x);
assert!(val < (1u64 << (8 * N)));
// we canot use 'try_into().unwrap()' here
let src = u64::to_be_bytes(val);
let start = src.len() - N;
let mut result = [0; N];
let mut i = start;
while i < src.len() {
result[i - start] = src[i];
i += 1
}

result
}
62 changes: 25 additions & 37 deletions tsp_sdk/src/cesr/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ pub fn decode_variable_data_index(
}
}

#[allow(dead_code)]
pub fn decode_variable_data<'a>(identifier: u32, stream: &mut &'a [u8]) -> Option<&'a [u8]> {
let range = decode_variable_data_index(identifier, stream, &mut 0)?;
let slice = &stream[range.start..range.end];
Expand All @@ -113,6 +114,20 @@ pub fn decode_variable_data_mut(
Some((slice, stream))
}

pub fn opt_decode_variable_data_mut(
identifier: u32,
stream: &mut [u8],
) -> (Option<&[u8]>, &mut [u8]) {
let Some(range) = decode_variable_data_index(identifier, stream, &mut 0) else {
return (None, stream);
};

let (prefix, stream) = stream.split_at_mut(range.end);
let slice = &mut prefix[range.start..];

(Some(slice), stream)
}

/// Decode indexed data with a known identifier
#[allow(dead_code)]
pub fn decode_indexed_data<'a, const N: usize>(
Expand Down Expand Up @@ -153,22 +168,29 @@ pub fn decode_indexed_data<'a, const N: usize>(
}

/// Decode a frame with known identifier and size
pub fn decode_count(identifier: u16, stream: &mut &[u8]) -> Option<u16> {
pub fn decode_count(identifier: u16, stream: &mut &[u8]) -> Option<u32> {
let word = extract_triplet(stream.get(0..=2)?.try_into().unwrap());
let index = word & mask(12);

let expected = (DASH << 18) | (bits(identifier, 6) << 12) | bits(index, 12);
let expected_long =
(DASH << 18) | D0 << 12 | (bits(identifier, 6) << 6) | bits(index & 0x3F, 6);
if word == expected {
*stream = &stream[3..];

Some(index as u16)
Some((index as u16).into())
} else if word == expected_long {
let next = extract_triplet(stream.get(3..=5)?.try_into().unwrap());
*stream = &stream[6..];

Some(index << 24 | next)
} else {
None
}
}

/// Decode a frame with known identifier and size
pub fn decode_count_mut(identifier: u16, stream: &mut [u8]) -> Option<(u16, &mut [u8])> {
pub fn decode_count_mut(identifier: u16, stream: &mut [u8]) -> Option<(u32, &mut [u8])> {
let mut const_stream: &[u8] = stream;
let count = decode_count(identifier, &mut const_stream)?;
let offset = stream.len() - const_stream.len();
Expand All @@ -193,37 +215,3 @@ pub fn decode_genus(

Some(())
}

/// Decode variable size data (intended size >=50mb) that doesn't fit in known CESR encodings.
///
/// Since CESR has no native type for this, we use the convention that "big data"
/// Is announced by encoding the size as a 64bit integer (prefix "N"), which is then
/// followed by the raw data padded in the usual sense for CESR (pre-padding)
/// This is a temporary encoding, depending on how CESR will address this in the future.
pub fn decode_large_blob_index(stream: &[u8]) -> Option<std::ops::Range<usize>> {
let selector = b'N' - b'A';
let size = u64::from_be_bytes(*decode_fixed_data(
selector as u32,
&mut <_>::clone(&stream),
)?) as usize;
let padded_size = size.next_multiple_of(3);
let lead_bytes = padded_size - size;

let start = 9usize.checked_add(lead_bytes)?;
let end = 9usize.checked_add(padded_size)?;

let range = start..end;
// make sure the range is valid before returning it
stream.get(range.clone())?;

Some(range)
}

#[cfg(test)]
pub fn decode_large_blob<'a>(stream: &mut &'a [u8]) -> Option<&'a [u8]> {
let range = decode_large_blob_index(stream)?;
let slice = &stream[range.start..range.end];
*stream = &stream[range.end..];

Some(slice)
}
37 changes: 17 additions & 20 deletions tsp_sdk/src/cesr/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,24 @@ pub fn encode_variable_data(
}

/// Encode a frame with known identifier and count code
pub fn encode_count(identifier: u16, count: u16, stream: &mut impl for<'a> Extend<&'a u8>) {
let word = (DASH << 18) | (bits(identifier, 6) << 12) | bits(count, 12);
pub fn encode_count(
identifier: u16,
count: impl Into<usize>,
stream: &mut impl for<'a> Extend<&'a u8>,
) {
let count: usize = count.into();
let count: u32 = count.try_into().unwrap();
if count < 4096 {
let word = (DASH << 18) | (bits(identifier, 6) << 12) | bits(count, 12);

stream.extend(&u32::to_be_bytes(word)[1..]);
} else {
let word1 = (DASH << 18) | (D0 << 12) | (bits(identifier, 6) << 6) | bits(count >> 24, 6);
let word2 = bits(count, 24);

stream.extend(&u32::to_be_bytes(word)[1..]);
stream.extend(&u32::to_be_bytes(word1)[1..]);
stream.extend(&u32::to_be_bytes(word2)[1..]);
}
}

/// Encode a genus with known identifier and version
Expand All @@ -88,20 +102,3 @@ pub fn encode_genus(
stream.extend(&u32::to_be_bytes(word1)[1..]);
stream.extend(&u32::to_be_bytes(word2)[1..]);
}

/// Encode variable size data (intended size >=50mb) that doesn't fit in known CESR encodings.
///
/// Since CESR has no native type for this, we use the convention that "big data"
/// Is announced by encoding the size as a 64bit integer (prefix "N"), which is then
/// followed by the raw data padded in the usual sense for CESR (pre-padding)
/// This is a temporary encoding, depending on how CESR will address this in the future.
pub fn encode_large_blob(payload: &[u8], stream: &mut impl for<'a> Extend<&'a u8>) {
let size = payload.len() as u64;
let padded_size = size.next_multiple_of(3);
let lead_bytes = padded_size - size;

let selector = (b'N' - b'A') as u32;
encode_fixed_data(selector, &u64::to_be_bytes(size), stream);
stream.extend(&<[u8; 2]>::default()[0..lead_bytes as usize]);
stream.extend(payload);
}
5 changes: 5 additions & 0 deletions tsp_sdk/src/cesr/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
pub enum EncodeError {
ExcessiveFieldSize,
MissingHops,
MissingReceiver,
InvalidVid,
}

/// An error type to indicate something went wrong with decoding
Expand All @@ -16,6 +18,9 @@ pub enum DecodeError {
VersionMismatch,
InvalidCryptoType,
InvalidSignatureType,
MissingHops,
UnknownCrypto,
InvalidCrypto,
}

impl std::fmt::Display for EncodeError {
Expand Down
30 changes: 6 additions & 24 deletions tsp_sdk/src/cesr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod error;
mod packet;
use base64ct::{Base64UrlUnpadded, Encoding};
use error::DecodeError;
mod consts;
pub use packet::*;

#[cfg(feature = "cesr-t")]
Expand Down Expand Up @@ -58,8 +59,7 @@ mod selector {

/// (Temporary) interface to get Sender/Receiver VIDs information from a CESR-encoded message
pub fn get_sender_receiver(message: &[u8]) -> Result<(&[u8], Option<&[u8]>), error::DecodeError> {
let mut stream = message;
let (sender, receiver, _, _) = decode_sender_receiver(&mut stream)?;
let (sender, receiver, _, _) = decode_sender_receiver(message)?;

Ok((sender, receiver))
}
Expand Down Expand Up @@ -100,8 +100,10 @@ impl EnvelopeType<'_> {
}
}

//TODO: simplify the source of sender/receiver
pub fn probe(stream: &mut [u8]) -> Result<EnvelopeType<'_>, error::DecodeError> {
let (_, crypto_type, _) = detected_tsp_header_size_and_confidentiality(&mut (stream as &[u8]))?;
let (_sender, _receiver, crypto_type, _) =
detected_tsp_header_size_and_confidentiality(stream, &mut 0)?;

let envelope = decode_envelope(stream)?
.into_opened()
Expand Down Expand Up @@ -184,7 +186,7 @@ mod test {
&mut data,
); // 1 lead byte
encode_variable_data(42, b"I always speak the truth. Not the whole truth, because there's no way, to say it all.", &mut data); // 2 lead bytes
encode_count(7, 2, &mut data);
encode_count(7, 2usize, &mut data);
encode_indexed_data(5, 57, b"DON'T PANIC!", &mut data); // 0 lead bytes
encode_indexed_data(5, 0, b"SECRET KEY", &mut data); // 2 lead bytes

Expand Down Expand Up @@ -420,24 +422,4 @@ ACTD7NDX93ZGTkZBBuSeSGsAQ7u0hngpNTZTK_Um7rUZGnLRNJvo5oOnnC1J2iBQHuxoq8PyjdT3BHS2
assert_eq!(decode_indexed_data::<64>(0, slice).unwrap().0, 1);
assert_eq!(decode_indexed_data::<64>(0, slice).unwrap().0, 2);
}

#[test]
fn blob_encode_decode() {
let mut data = vec![];
encode_large_blob(b"Where there is power, there is resistance.", &mut data); // 0 lead bytes
encode_large_blob(b"TrustSpanP!", &mut data); // 1 lead byte
encode_large_blob(b"I always speak the truth. Not the whole truth, because there's no way, to say it all.", &mut data); // 2 lead bytes
encode_variable_data(5, b"TrustSpanP!", &mut data); // not a blob
let mut input = &data[..];
assert_eq!(
decode_large_blob(&mut input).unwrap(),
b"Where there is power, there is resistance."
);
assert_eq!(decode_large_blob(&mut input).unwrap(), b"TrustSpanP!");
assert_eq!(
decode_large_blob(&mut input).unwrap(),
b"I always speak the truth. Not the whole truth, because there's no way, to say it all."
);
assert!(decode_large_blob(&mut input).is_none());
}
}
Loading