Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ zlib-rs = { version = "0.6.5", default-features = false, features = ["std", "rus

[features]
debug-snapshots = ["wacore/debug-snapshots"]
# Typed interop with the decoded legacy SessionRecord v1 model, for migrating
# externally produced auth state. Off by default so native clients carry no
# migration surface.
legacy-session-interop = ["wacore/legacy-session-interop"]
# Generation-scoped extension lifecycle. Kept opt-in so ordinary clients do not
# retain lifecycle state or branches when no extension host is present.
client-lifecycle = []
Expand Down
2 changes: 2 additions & 0 deletions wacore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ ignored = ["getrandom"]
default = ["simd"]
simd = ["wacore-appstate/simd"]
debug-snapshots = []
# Typed interop with the decoded libsignal SessionRecord v1 model.
legacy-session-interop = ["wacore-libsignal/legacy-session-interop"]
# Expose the InMemoryBackend fault-injection / call-count hooks used by the e2e
# suite. Off by default so normal builds carry no extra fields or per-call
# bookkeeping. Enabled only from test crates.
Expand Down
89 changes: 67 additions & 22 deletions wacore/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,8 @@ fn is_option_type(ty: &syn::Type) -> bool {
// 3. int (enum has #[wire(kind = "int")])
// Unit variants + optional #[wire_fallback] tuple with i32. Each variant
// has #[wire = NUM].
// Emits: code(), From<i32>, Serialize (as i32), Deserialize (from i32).
// Emits: code(), Serialize (as i32), Deserialize (from i32), and either
// From<i32> with a fallback or strict TryFrom<i32> without one.
//
// The wire string/number lives exactly once per variant, in the #[wire = ...]
// attribute. Everything else is derived.
Expand Down Expand Up @@ -1158,21 +1159,12 @@ fn expand_wire_enum_int(
}
}

let Some(fb) = fallback else {
return syn::Error::new_spanned(
name,
"int-mode WireEnum requires a #[wire_fallback] variant like Unknown(i32)",
)
.to_compile_error();
};
let fb_ident = &fb.ident;

let code_arms: Vec<_> = infos
.iter()
.filter(|i| !i.is_fallback)
.map(|i| {
let id = &i.ident;
let VariantWire::Int(n) = i.wire.as_ref().unwrap() else {
let Some(VariantWire::Int(n)) = i.wire.as_ref() else {
unreachable!()
};
let lit = proc_macro2::Literal::i32_suffixed(*n);
Expand All @@ -1185,33 +1177,86 @@ fn expand_wire_enum_int(
.filter(|i| !i.is_fallback)
.map(|i| {
let id = &i.ident;
let VariantWire::Int(n) = i.wire.as_ref().unwrap() else {
let Some(VariantWire::Int(n)) = i.wire.as_ref() else {
unreachable!()
};
let lit = proc_macro2::Literal::i32_suffixed(*n);
quote! { #lit => #name::#id }
})
.collect();

let strict_from_arms: Vec<_> = infos
.iter()
.filter(|i| !i.is_fallback)
.map(|i| {
let id = &i.ident;
let Some(VariantWire::Int(n)) = i.wire.as_ref() else {
unreachable!()
};
let lit = proc_macro2::Literal::i32_suffixed(*n);
quote! { #lit => ::core::result::Result::Ok(#name::#id) }
})
.collect();
Comment thread
coderabbitai[bot] marked this conversation as resolved.

let conversion = if let Some(fallback) = fallback {
let fallback_ident = &fallback.ident;
quote! {
impl ::core::convert::From<i32> for #name {
fn from(code: i32) -> Self {
match code {
#(#from_arms,)*
other => #name::#fallback_ident(other),
}
}
}
}
} else {
quote! {
impl ::core::convert::TryFrom<i32> for #name {
type Error = i32;

fn try_from(code: i32) -> ::core::result::Result<Self, Self::Error> {
match code {
#(#strict_from_arms,)*
other => ::core::result::Result::Err(other),
}
}
}
}
};

let fallback_code_arm = fallback.map(|fallback| {
let fallback_ident = &fallback.ident;
quote! { #name::#fallback_ident(n) => *n, }
});

let deserialize = if fallback.is_some() {
quote! {
::core::result::Result::Ok(<Self as ::core::convert::From<i32>>::from(n))
}
} else {
quote! {
<Self as ::core::convert::TryFrom<i32>>::try_from(n).map_err(|unknown| {
<D::Error as ::serde::de::Error>::custom(::core::format_args!(
"unknown numeric wire code {unknown} for {}",
::core::stringify!(#name),
))
})
}
};

quote! {
impl #name {
/// Numeric wire code for this variant (single source of truth).
pub fn code(&self) -> i32 {
match self {
#(#code_arms,)*
#name::#fb_ident(n) => *n,
#fallback_code_arm
}
}
}

impl ::core::convert::From<i32> for #name {
fn from(code: i32) -> Self {
match code {
#(#from_arms,)*
other => #name::#fb_ident(other),
}
}
}
#conversion

impl ::serde::Serialize for #name {
fn serialize<S: ::serde::Serializer>(
Expand All @@ -1227,7 +1272,7 @@ fn expand_wire_enum_int(
deserializer: D,
) -> ::core::result::Result<Self, D::Error> {
let n = <i32 as ::serde::Deserialize>::deserialize(deserializer)?;
::core::result::Result::Ok(<Self as ::core::convert::From<i32>>::from(n))
#deserialize
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions wacore/libsignal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ license = "MIT"
repository = "https://github.com/jlucaso1/whatsapp-rust"
description = "Signal Protocol implementation for the WhatsApp platform"

[features]
# Typed interop with the decoded libsignal SessionRecord v1 model. Off by
# default so native consumers carry no migration surface.
legacy-session-interop = []

[dependencies]
aes = { workspace = true }
arrayref = "0.3.9"
Expand All @@ -33,6 +38,7 @@ sha2 = { workspace = true }
subtle = { workspace = true }
thiserror = { workspace = true }
uuid = { workspace = true }
wacore-derive = { workspace = true }
waproto = { workspace = true }
x25519-dalek = { version = "3.0.0-rc.1", features = ["static_secrets"] }

Expand Down
Loading
Loading