From 50dbe13863ce0952d9cb8db2525545d19a7602cd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 20:35:38 +0000 Subject: [PATCH 1/2] refactor(deps): drop redundant crates and unused dependency features Nine crates leave the dependency graph, each replaced by something the workspace already links or by plain core: - typed-builder: bon is already a hard dependency of wacore and covers the one struct pair that used it (wacore/src/iq/groups.rs). - flate2: the only runtime use was inflating the MLow table blobs, which wacore-binary's pooled zlib-rs inflater already does. It stays as a dev-dependency for the compression side of tests. - bytemuck: the three casts in lthash were transmutes between [u8; 16] and [u16; 8] wrapped in a manual big-endian swap. from_le_bytes per lane states the wire endianness once and drops both. - arrayref: split_first_chunk carries the window length in the type. - displaydoc: thiserror was already derived alongside it on every enum, and #[error(...)] is the same format string. - derive_more: the From/Into/TryFrom derives covered newtypes and two repr enums; the hand-written impls are shorter than the attributes were. - uuid: only reachable through Aci/Pni/ServiceId, which are Signal-app identity types with no WhatsApp meaning and no use anywhere in the tree. - iana-time-zone and its two platform backends: chrono's `clock` feature exists to resolve the local timezone, and only Utc is ever used. Feature trims that keep the same crates but compile less of them: hashbrown's `equivalent` and aes-gcm's `bytes` were both enabled and unused. Also removes three dev-dependency entries that restated a normal dependency verbatim, and points waproto's dev serde_json at the workspace pin. BREAKING CHANGE: wacore_libsignal no longer exports Aci, Pni, ServiceId, ServiceIdKind, ServiceIdFixedWidthBinaryBytes or WrongKindOfServiceIdError. The repr-based TryFrom impls on CiphertextMessageType and IdentityChange now fail with wacore_libsignal::core::UnknownDiscriminant instead of derive_more::TryFromReprError. --- Cargo.lock | 98 ----------- Cargo.toml | 10 +- storages/chat-store/Cargo.toml | 3 +- storages/sqlite-storage/Cargo.toml | 1 - wacore/Cargo.toml | 5 +- wacore/appstate/Cargo.toml | 1 - wacore/appstate/src/lthash.rs | 28 ++- wacore/libsignal/Cargo.toml | 4 - wacore/libsignal/src/core/address.rs | 163 ++---------------- wacore/libsignal/src/core/curve.rs | 19 +- wacore/libsignal/src/core/mod.rs | 16 +- wacore/libsignal/src/crypto/aes_cbc.rs | 14 +- wacore/libsignal/src/crypto/error.rs | 14 +- wacore/libsignal/src/crypto/provider.rs | 8 +- wacore/libsignal/src/protocol/error.rs | 65 ++++--- wacore/libsignal/src/protocol/identity_key.rs | 26 +-- wacore/libsignal/src/protocol/mod.rs | 5 +- wacore/libsignal/src/protocol/protocol.rs | 17 +- wacore/libsignal/src/protocol/ratchet/keys.rs | 32 ++-- wacore/libsignal/src/protocol/state/prekey.rs | 18 +- .../src/protocol/state/signed_prekey.rs | 18 +- .../libsignal/src/protocol/storage/traits.rs | 17 +- wacore/noise/Cargo.toml | 1 - wacore/src/iq/groups.rs | 27 ++- wacore/src/voip/mlow/smpl_tables_blob.rs | 13 +- waproto/Cargo.toml | 2 +- 26 files changed, 223 insertions(+), 402 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb436b9eb..8943d7156 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,7 +33,6 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" dependencies = [ - "bytes", "crypto-common 0.2.2", "inout 0.2.2", ] @@ -119,15 +118,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anstyle" version = "1.0.14" @@ -158,12 +148,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - [[package]] name = "asn1-rs" version = "0.6.2" @@ -495,12 +479,6 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" -[[package]] -name = "bytemuck" -version = "1.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" - [[package]] name = "byteorder" version = "1.5.0" @@ -593,10 +571,8 @@ version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ - "iana-time-zone", "num-traits", "serde", - "windows-link", ] [[package]] @@ -1101,27 +1077,6 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.119", -] - [[package]] name = "dhat" version = "0.3.3" @@ -1705,9 +1660,6 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" -dependencies = [ - "equivalent", -] [[package]] name = "heck" @@ -1869,30 +1821,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -3794,26 +3722,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "typed-builder" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda" -dependencies = [ - "typed-builder-macro", -] - -[[package]] -name = "typed-builder-macro" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "typenum" version = "1.20.1" @@ -3964,7 +3872,6 @@ dependencies = [ "thiserror 2.0.19", "tokio", "tracing", - "typed-builder", "wacore-appstate", "wacore-binary", "wacore-derive", @@ -3981,7 +3888,6 @@ version = "0.6.0" dependencies = [ "anyhow", "buffa", - "bytemuck", "codspeed-divan-compat", "hex", "hkdf 0.13.0", @@ -4030,7 +3936,6 @@ name = "wacore-libsignal" version = "0.6.0" dependencies = [ "aes 0.9.2", - "arrayref", "async-lock", "async-trait", "buffa", @@ -4040,8 +3945,6 @@ dependencies = [ "codspeed-divan-compat", "ctr 0.10.1", "curve25519-dalek 5.0.0", - "derive_more", - "displaydoc", "futures", "ghash 0.6.0", "hex", @@ -4055,7 +3958,6 @@ dependencies = [ "sha2 0.11.0", "subtle", "thiserror 2.0.19", - "uuid", "wacore-derive", "waproto", "x25519-dalek 3.0.0", diff --git a/Cargo.toml b/Cargo.toml index 8c81b4458..0f2d89131 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ unused_qualifications = "warn" [workspace.dependencies] aes = "0.9.2" -aes-gcm = { version = "0.11.0", default-features = false, features = ["aes", "alloc", "bytes"] } +aes-gcm = { version = "0.11.0", default-features = false, features = ["aes", "alloc"] } anyhow = { version = "1.0", default-features = false } async-channel = { version = "2.5.0", default-features = false, features = ["std"] } async-lock = { version = "3", default-features = false } @@ -85,7 +85,6 @@ bon = { version = "3.9.3", default-features = false, features = ["std"] } buffa = { version = "0.9.1", default-features = false, features = ["json"] } buffa-build = { version = "0.9.1", default-features = false } buffa-descriptor = { version = "0.9.1", default-features = false } -bytemuck = { version = "1.25", default-features = false } bytes = { version = "1.12", default-features = false } cbc = { version = "0.2", features = ["alloc"] } chrono = { version = "0.4", default-features = false } @@ -99,7 +98,7 @@ event-listener = { version = "5", default-features = false } flate2 = { version = "1.1.9", default-features = false, features = ["zlib-rs"] } futures = { version = "0.3", default-features = false, features = ["alloc", "async-await"] } getrandom = { version = "0.4", default-features = false } -hashbrown = { version = "0.17.1", default-features = false, features = ["equivalent"] } +hashbrown = { version = "0.17.1", default-features = false } heck = "0.5" hex = { version = "0.4", default-features = false, features = ["alloc"] } hkdf = { version = "0.13.0", default-features = false } @@ -207,7 +206,9 @@ base64 = { workspace = true } bon = { workspace = true, optional = true } buffa = { workspace = true } bytes = { workspace = true } -chrono = { workspace = true, features = ["clock"] } +# `now` rather than `clock`: only `Utc::now()` is used, and `clock` exists to +# resolve the local timezone (pulling iana-time-zone and its per-OS backends). +chrono = { workspace = true, features = ["now"] } event-listener = { workspace = true } futures = { workspace = true, features = ["std"] } smallvec = { workspace = true } @@ -252,7 +253,6 @@ getrandom = { workspace = true, features = ["wasm_js"] } [dev-dependencies] aes = { workspace = true } -async-trait = { workspace = true } cbc = { workspace = true, features = ["block-padding"] } env_logger = { workspace = true } # `total_listeners()` (how tests observe that a waiter parked) is std-gated. diff --git a/storages/chat-store/Cargo.toml b/storages/chat-store/Cargo.toml index 40dabb391..aa70df4a2 100644 --- a/storages/chat-store/Cargo.toml +++ b/storages/chat-store/Cargo.toml @@ -17,7 +17,8 @@ search = [] [dependencies] buffa = { workspace = true } -chrono = { workspace = true, features = ["std", "clock"] } +# `now` implies `std` and skips the local-timezone backends; only `Utc` is used. +chrono = { workspace = true, features = ["now"] } diesel = { workspace = true } diesel_migrations = { workspace = true } log = { workspace = true } diff --git a/storages/sqlite-storage/Cargo.toml b/storages/sqlite-storage/Cargo.toml index fce12dea7..4d5128776 100644 --- a/storages/sqlite-storage/Cargo.toml +++ b/storages/sqlite-storage/Cargo.toml @@ -38,7 +38,6 @@ sha2 = { workspace = true } [dev-dependencies] portable-atomic = { workspace = true } -tokio = { workspace = true, features = ["sync", "rt", "time", "macros"] } [lints] workspace = true diff --git a/wacore/Cargo.toml b/wacore/Cargo.toml index f3406495e..750e578d5 100644 --- a/wacore/Cargo.toml +++ b/wacore/Cargo.toml @@ -62,7 +62,6 @@ compact_str = { workspace = true } ctr = { workspace = true } dhat = { version = "0.3", optional = true } event-listener = { workspace = true } -flate2 = { workspace = true } futures = { workspace = true, features = ["std"] } getrandom = { workspace = true, optional = true } hashbrown = { workspace = true } @@ -85,7 +84,6 @@ smoothutf8 = { workspace = true } subtle = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true, optional = true } -typed-builder = "0.23" wacore-appstate = { workspace = true } wacore-binary = { workspace = true, features = ["serde"] } zeroize = { workspace = true } @@ -101,6 +99,9 @@ sha2 = { workspace = true } [dev-dependencies] divan = { workspace = true } +# Compression only: the runtime inflates through `wacore_binary::zlib_pool` +# (zlib-rs directly), so flate2 stays out of the release dependency set. +flate2 = { workspace = true } futures = { workspace = true, features = ["executor", "thread-pool"] } tokio = { workspace = true, features = ["macros", "rt", "time"] } diff --git a/wacore/appstate/Cargo.toml b/wacore/appstate/Cargo.toml index b693da5af..8915fe9bc 100644 --- a/wacore/appstate/Cargo.toml +++ b/wacore/appstate/Cargo.toml @@ -18,7 +18,6 @@ simd = ["wacore-binary/simd"] [dependencies] anyhow = { workspace = true } buffa = { workspace = true } -bytemuck = { workspace = true } hex = { workspace = true } hkdf = { workspace = true } hmac = { workspace = true } diff --git a/wacore/appstate/src/lthash.rs b/wacore/appstate/src/lthash.rs index 9f525fdb7..bfe4ac14b 100644 --- a/wacore/appstate/src/lthash.rs +++ b/wacore/appstate/src/lthash.rs @@ -78,16 +78,15 @@ fn perform_pointwise_with_overflow(base: &mut [u8], input: &[u8], subtract: bool let (input_chunks, input_rem) = input_remaining.as_chunks::<16>(); for (base_chunk, input_chunk) in base_chunks.iter_mut().zip(input_chunks) { - let mut base_arr: [u16; 8] = bytemuck::cast(*base_chunk); - let mut input_arr: [u16; 8] = bytemuck::cast(*input_chunk); - if cfg!(target_endian = "big") { - for v in &mut base_arr { - *v = v.swap_bytes(); - } - for v in &mut input_arr { - *v = v.swap_bytes(); - } - } + // `from_le_bytes` per lane states the wire endianness directly, so + // the same code is correct on either host; on little-endian it + // lowers to the plain 16-byte load a transmute would have emitted. + let base_arr: [u16; 8] = core::array::from_fn(|i| { + u16::from_le_bytes([base_chunk[2 * i], base_chunk[2 * i + 1]]) + }); + let input_arr: [u16; 8] = core::array::from_fn(|i| { + u16::from_le_bytes([input_chunk[2 * i], input_chunk[2 * i + 1]]) + }); let base_simd = u16x8::from_array(base_arr); let input_simd = u16x8::from_array(input_arr); @@ -97,13 +96,10 @@ fn perform_pointwise_with_overflow(base: &mut [u8], input: &[u8], subtract: bool base_simd + input_simd }; - let mut out = result_simd.to_array(); - if cfg!(target_endian = "big") { - for v in &mut out { - *v = v.swap_bytes(); - } + let out = result_simd.to_array(); + for (base_pair, lane) in base_chunk.as_chunks_mut::<2>().0.iter_mut().zip(out) { + *base_pair = lane.to_le_bytes(); } - *base_chunk = bytemuck::cast(out); } base_remaining = base_rem; diff --git a/wacore/libsignal/Cargo.toml b/wacore/libsignal/Cargo.toml index 483970d3c..77d2c7afd 100644 --- a/wacore/libsignal/Cargo.toml +++ b/wacore/libsignal/Cargo.toml @@ -15,7 +15,6 @@ legacy-session-interop = [] [dependencies] aes = { workspace = true } -arrayref = "0.3.9" async-lock = { workspace = true } async-trait = { workspace = true } buffa = { workspace = true } @@ -24,8 +23,6 @@ cbc = { workspace = true } chrono = { workspace = true, features = ["now"] } ctr = { workspace = true } curve25519-dalek = "5.0.0" -derive_more = { version = "2.1.1", features = ["from", "into", "try_from"] } -displaydoc = "0.2.7" ghash = "0.6.0" hex = { workspace = true } hkdf = { workspace = true } @@ -38,7 +35,6 @@ sha1 = { workspace = true } 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", features = ["static_secrets"] } diff --git a/wacore/libsignal/src/core/address.rs b/wacore/libsignal/src/core/address.rs index c9978d895..5a7520934 100644 --- a/wacore/libsignal/src/core/address.rs +++ b/wacore/libsignal/src/core/address.rs @@ -1,168 +1,27 @@ use std::fmt; use std::hash::{Hash, Hasher}; -use uuid::Uuid; - -#[derive(Clone, Copy, Hash, PartialEq, Eq, derive_more::TryFrom)] -#[try_from(repr)] -#[repr(u8)] -pub enum ServiceIdKind { - Aci, - Pni, -} - -impl From for u8 { - fn from(value: ServiceIdKind) -> Self { - value as u8 - } -} - -impl fmt::Display for ServiceIdKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ServiceIdKind::Aci => f.write_str("ACI"), - ServiceIdKind::Pni => f.write_str("PNI"), - } - } -} - -impl fmt::Debug for ServiceIdKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct WrongKindOfServiceIdError { - pub expected: ServiceIdKind, - pub actual: ServiceIdKind, -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct SpecificServiceId(Uuid); - -impl SpecificServiceId { - #[inline] - pub const fn from_uuid_bytes(bytes: [u8; 16]) -> Self { - Self::from_uuid(Uuid::from_bytes(bytes)) - } - - #[inline] - const fn from_uuid(uuid: Uuid) -> Self { - Self(uuid) - } -} - -impl Hash for SpecificServiceId { - fn hash(&self, state: &mut H) { - state.write(self.0.as_bytes()); - } -} - -impl From for SpecificServiceId { - #[inline] - fn from(value: Uuid) -> Self { - Self::from_uuid(value) - } -} - -impl From> for Uuid { - #[inline] - fn from(value: SpecificServiceId) -> Self { - value.0 - } -} - -impl fmt::Debug for SpecificServiceId -where - ServiceId: From, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - ServiceId::from(*self).fmt(f) - } -} - -pub type Aci = SpecificServiceId<{ ServiceIdKind::Aci as u8 }>; - -pub type Pni = SpecificServiceId<{ ServiceIdKind::Pni as u8 }>; - -pub type ServiceIdFixedWidthBinaryBytes = [u8; 17]; - -#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, derive_more::From)] -pub enum ServiceId { - Aci(Aci), - Pni(Pni), -} - -impl ServiceId { - #[inline] - pub fn kind(&self) -> ServiceIdKind { - match self { - ServiceId::Aci(_) => ServiceIdKind::Aci, - ServiceId::Pni(_) => ServiceIdKind::Pni, - } - } +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] +pub struct DeviceId(u32); +impl DeviceId { #[inline] - pub fn raw_uuid(self) -> Uuid { - match self { - ServiceId::Aci(aci) => aci.into(), - ServiceId::Pni(pni) => pni.into(), - } - } -} - -impl fmt::Debug for ServiceId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "<{}:{}>", self.kind(), self.raw_uuid()) + pub const fn new(id: u32) -> Self { + Self(id) } } -impl TryFrom for SpecificServiceId { - type Error = WrongKindOfServiceIdError; - +impl From for DeviceId { #[inline] - fn try_from(value: ServiceId) -> Result { - if u8::from(value.kind()) == KIND { - Ok(value.raw_uuid().into()) - } else { - Err(WrongKindOfServiceIdError { - expected: KIND - .try_into() - .expect("invalid kind, not covered in ServiceIdKind"), - actual: value.kind(), - }) - } - } -} - -impl PartialEq for SpecificServiceId -where - ServiceId: From>, -{ - fn eq(&self, other: &ServiceId) -> bool { - ServiceId::from(*self) == *other - } -} - -impl PartialEq> for ServiceId -where - ServiceId: From>, -{ - fn eq(&self, other: &SpecificServiceId) -> bool { - *self == ServiceId::from(*other) + fn from(id: u32) -> Self { + Self(id) } } -#[derive( - Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord, derive_more::From, derive_more::Into, -)] -pub struct DeviceId(u32); - -impl DeviceId { +impl From for u32 { #[inline] - pub const fn new(id: u32) -> Self { - Self(id) + fn from(id: DeviceId) -> Self { + id.0 } } diff --git a/wacore/libsignal/src/core/curve.rs b/wacore/libsignal/src/core/curve.rs index ffe931e0f..a619dd1ac 100644 --- a/wacore/libsignal/src/core/curve.rs +++ b/wacore/libsignal/src/core/curve.rs @@ -33,18 +33,16 @@ impl KeyType { } } -#[derive(Debug, displaydoc::Display)] +#[derive(Debug, thiserror::Error)] pub enum CurveError { - /// no key type identifier + #[error("no key type identifier")] NoKeyTypeIdentifier, - /// bad key type <{0:#04x}> + #[error("bad key type <{0:#04x}>")] BadKeyType(u8), - /// bad key length <{1}> for key with type <{0}> + #[error("bad key length <{1}> for key with type <{0}>")] BadKeyLength(KeyType, usize), } -impl std::error::Error for CurveError {} - impl TryFrom for KeyType { type Error = CurveError; @@ -61,11 +59,18 @@ enum PublicKeyData { DjbPublicKey([u8; curve25519::PUBLIC_KEY_LENGTH]), } -#[derive(Clone, Copy, Eq, derive_more::From)] +#[derive(Clone, Copy, Eq)] pub struct PublicKey { key: PublicKeyData, } +impl From for PublicKey { + #[inline] + fn from(key: PublicKeyData) -> Self { + Self { key } + } +} + impl PublicKey { /// Length of a raw Curve25519 public key, without its type prefix. pub const RAW_KEY_LEN: usize = curve25519::PUBLIC_KEY_LENGTH; diff --git a/wacore/libsignal/src/core/mod.rs b/wacore/libsignal/src/core/mod.rs index 48a531fe8..dc22ca8a8 100644 --- a/wacore/libsignal/src/core/mod.rs +++ b/wacore/libsignal/src/core/mod.rs @@ -7,7 +7,15 @@ mod address; // Not exporting the members because they have overly-generic names. pub mod curve; -pub use address::{ - Aci, AddressBuf, DeviceId, Pni, ProtocolAddress, ServiceId, ServiceIdFixedWidthBinaryBytes, - ServiceIdKind, WrongKindOfServiceIdError, -}; +pub use address::{AddressBuf, DeviceId, ProtocolAddress}; + +/// A wire byte that names no variant of the enum it was converted into. +/// +/// The `repr`-based `TryFrom` impls in this crate are hand-written rather than +/// derived, so their error type lives here instead of being a third-party one +/// leaked through the public API. +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +#[error("no variant with discriminant {value}")] +pub struct UnknownDiscriminant { + pub value: u8, +} diff --git a/wacore/libsignal/src/crypto/aes_cbc.rs b/wacore/libsignal/src/crypto/aes_cbc.rs index a2d7b6fce..857aee969 100644 --- a/wacore/libsignal/src/crypto/aes_cbc.rs +++ b/wacore/libsignal/src/crypto/aes_cbc.rs @@ -7,19 +7,21 @@ use std::result::Result; use crate::crypto::provider::provider; -#[derive(Debug, displaydoc::Display, thiserror::Error)] +#[derive(Debug, thiserror::Error)] pub enum EncryptionError { - /// The key or IV is the wrong length. + #[error("The key or IV is the wrong length.")] BadKeyOrIv, - /// Padding error during encryption. + #[error("Padding error during encryption.")] BadPadding, } -#[derive(Debug, displaydoc::Display, thiserror::Error)] +#[derive(Debug, thiserror::Error)] pub enum DecryptionError { - /// The key or IV is the wrong length. + #[error("The key or IV is the wrong length.")] BadKeyOrIv, - /// These cases should not be distinguished; message corruption can cause either problem. + #[error( + "These cases should not be distinguished; message corruption can cause either problem." + )] BadCiphertext(&'static str), } diff --git a/wacore/libsignal/src/crypto/error.rs b/wacore/libsignal/src/crypto/error.rs index 3566c9a4d..f9194bc23 100644 --- a/wacore/libsignal/src/crypto/error.rs +++ b/wacore/libsignal/src/crypto/error.rs @@ -3,19 +3,19 @@ // SPDX-License-Identifier: AGPL-3.0-only // -#[derive(displaydoc::Display, thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug)] pub enum Error { - /// "unknown {0} algorithm {1}" + #[error("\"unknown {0} algorithm {1}\"")] UnknownAlgorithm(&'static str, String), - /// invalid key size + #[error("invalid key size")] InvalidKeySize, - /// invalid nonce size + #[error("invalid nonce size")] InvalidNonceSize, - /// invalid input size + #[error("invalid input size")] InvalidInputSize, - /// invalid authentication tag + #[error("invalid authentication tag")] InvalidTag, - /// output buffer too small: required {required} bytes, provided {provided} + #[error("output buffer too small: required {required} bytes, provided {provided}")] OutputBufferTooSmall { required: usize, provided: usize }, } diff --git a/wacore/libsignal/src/crypto/provider.rs b/wacore/libsignal/src/crypto/provider.rs index f5038a40a..62a2cdf6b 100644 --- a/wacore/libsignal/src/crypto/provider.rs +++ b/wacore/libsignal/src/crypto/provider.rs @@ -83,13 +83,13 @@ impl GcmInPlaceBuffer for BytesMut { } } -#[derive(Debug, displaydoc::Display, thiserror::Error)] +#[derive(Debug, thiserror::Error)] pub enum CryptoProviderError { - /// bad key/iv/nonce size or malformed input + #[error("bad key/iv/nonce size or malformed input")] BadInput, - /// authentication tag verification failed + #[error("authentication tag verification failed")] AuthFailed, - /// provider backend reported failure + #[error("provider backend reported failure")] BackendFailed, } diff --git a/wacore/libsignal/src/protocol/error.rs b/wacore/libsignal/src/protocol/error.rs index 460afc2e0..d6768c832 100644 --- a/wacore/libsignal/src/protocol/error.rs +++ b/wacore/libsignal/src/protocol/error.rs @@ -12,94 +12,93 @@ use crate::{ }, protocol::CiphertextMessageType, }; -use displaydoc::Display; use thiserror::Error; pub type Result = std::result::Result; -#[derive(Debug, Display, Error)] +#[derive(Debug, Error)] pub enum SignalProtocolError { - /// invalid argument: {0} + #[error("invalid argument: {0}")] InvalidArgument(String), - /// invalid state for call to {0} to succeed: {1} + #[error("invalid state for call to {0} to succeed: {1}")] InvalidState(&'static str, String), - /// backend store error in {0} + #[error("backend store error in {0}")] BackendError( &'static str, #[source] Box, ), - /// protobuf encoding was invalid + #[error("protobuf encoding was invalid")] InvalidProtobufEncoding, - /// ciphertext serialized bytes were too short <{0}> + #[error("ciphertext serialized bytes were too short <{0}>")] CiphertextMessageTooShort(usize), - /// ciphertext version was too old <{0}> + #[error("ciphertext version was too old <{0}>")] LegacyCiphertextVersion(u8), - /// ciphertext version was unrecognized <{0}> + #[error("ciphertext version was unrecognized <{0}>")] UnrecognizedCiphertextVersion(u8), - /// unrecognized message version <{0}> + #[error("unrecognized message version <{0}>")] UnrecognizedMessageVersion(u32), - /// fingerprint version number mismatch them {0} us {1} + #[error("fingerprint version number mismatch them {0} us {1}")] FingerprintVersionMismatch(u32, u32), - /// fingerprint parsing error + #[error("fingerprint parsing error")] FingerprintParsingError, - /// no key type identifier + #[error("no key type identifier")] NoKeyTypeIdentifier, - /// bad key type <{0:#04x}> + #[error("bad key type <{0:#04x}>")] BadKeyType(u8), - /// bad key length <{1}> for key with type <{0}> + #[error("bad key length <{1}> for key with type <{0}>")] BadKeyLength(KeyType, usize), - /// invalid signature detected + #[error("invalid signature detected")] SignatureValidationFailed, - /// untrusted identity for address {0} + #[error("untrusted identity for address {0}")] UntrustedIdentity(ProtocolAddress), - /// invalid prekey identifier + #[error("invalid prekey identifier")] InvalidPreKeyId, - /// invalid signed prekey identifier + #[error("invalid signed prekey identifier")] InvalidSignedPreKeyId, - /// invalid MAC key length <{0}> + #[error("invalid MAC key length <{0}>")] InvalidMacKeyLength(usize), - /// no sender key state: {0} + #[error("no sender key state: {0}")] NoSenderKeyState(String), - /// session with {0} not found + #[error("session with {0} not found")] SessionNotFound(ProtocolAddress), - /// invalid session: {0} + #[error("invalid session: {0}")] InvalidSessionStructure(&'static str), - /// invalid sender key session + #[error("invalid sender key session")] InvalidSenderKeySession, - /// session for {0} has invalid registration ID {1:X} + #[error("session for {0} has invalid registration ID {1:X}")] InvalidRegistrationId(ProtocolAddress, u32), - /// message with old counter {0} / {1} + #[error("message with old counter {0} / {1}")] DuplicatedMessage(u32, u32), - /// invalid {0:?} message: {1} + #[error("invalid {0:?} message: {1}")] InvalidMessage(CiphertextMessageType, &'static str), - /// MAC verification failed for {0:?} message + #[error("MAC verification failed for {0:?} message")] BadMac(CiphertextMessageType), - /// error while invoking an ffi callback: {0} + #[error("error while invoking an ffi callback: {0}")] FfiBindingError(String), - /// error in method call '{0}': {1} + #[error("error in method call '{0}': {1}")] ApplicationCallbackError( &'static str, #[source] Box, ), - /// invalid sealed sender message: {0} + #[error("invalid sealed sender message: {0}")] InvalidSealedSenderMessage(String), - /// unknown sealed sender message version {0} + #[error("unknown sealed sender message version {0}")] UnknownSealedSenderVersion(u8), - /// self send of a sealed sender message + #[error("self send of a sealed sender message")] SealedSenderSelfSend, } diff --git a/wacore/libsignal/src/protocol/identity_key.rs b/wacore/libsignal/src/protocol/identity_key.rs index d8ec48857..2c6959d88 100644 --- a/wacore/libsignal/src/protocol/identity_key.rs +++ b/wacore/libsignal/src/protocol/identity_key.rs @@ -18,23 +18,27 @@ use crate::protocol::{ /// /// Wrapper for [`PublicKey`]. #[derive( - Debug, - PartialOrd, - Ord, - PartialEq, - Eq, - Clone, - Copy, - derive_more::From, - derive_more::Into, - serde::Serialize, - serde::Deserialize, + Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize, )] #[serde(transparent)] pub struct IdentityKey { public_key: PublicKey, } +impl From for IdentityKey { + #[inline] + fn from(public_key: PublicKey) -> Self { + Self { public_key } + } +} + +impl From for PublicKey { + #[inline] + fn from(identity: IdentityKey) -> Self { + identity.public_key + } +} + impl IdentityKey { /// Initialize a public-facing identity from a public key. pub fn new(public_key: PublicKey) -> Self { diff --git a/wacore/libsignal/src/protocol/mod.rs b/wacore/libsignal/src/protocol/mod.rs index 6c44a5b35..133659784 100644 --- a/wacore/libsignal/src/protocol/mod.rs +++ b/wacore/libsignal/src/protocol/mod.rs @@ -37,10 +37,7 @@ mod storage; mod stores; mod timestamp; pub use crate::core::curve::{CurveError, KeyPair, PreparedVerifyingKey, PrivateKey, PublicKey}; -pub use crate::core::{ - Aci, AddressBuf, DeviceId, Pni, ProtocolAddress, ServiceId, ServiceIdFixedWidthBinaryBytes, - ServiceIdKind, -}; +pub use crate::core::{AddressBuf, DeviceId, ProtocolAddress}; pub use crate::protocol::protocol::SENDERKEY_MESSAGE_CURRENT_VERSION; pub use crate::protocol::sender_keys::InvalidSenderKeySessionError; pub use crate::store::sender_key_name::SenderKeyName; diff --git a/wacore/libsignal/src/protocol/protocol.rs b/wacore/libsignal/src/protocol/protocol.rs index 5c3f7c8a1..87abacad6 100644 --- a/wacore/libsignal/src/protocol/protocol.rs +++ b/wacore/libsignal/src/protocol/protocol.rs @@ -221,9 +221,8 @@ pub enum CiphertextMessage { PlaintextContent(PlaintextContent), } -#[derive(Copy, Clone, Eq, PartialEq, Debug, derive_more::TryFrom)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[repr(u8)] -#[try_from(repr)] pub enum CiphertextMessageType { Whisper = 2, PreKey = 3, @@ -231,6 +230,20 @@ pub enum CiphertextMessageType { Plaintext = 8, } +impl TryFrom for CiphertextMessageType { + type Error = crate::core::UnknownDiscriminant; + + fn try_from(value: u8) -> std::result::Result { + match value { + 2 => Ok(Self::Whisper), + 3 => Ok(Self::PreKey), + 7 => Ok(Self::SenderKey), + 8 => Ok(Self::Plaintext), + _ => Err(crate::core::UnknownDiscriminant { value }), + } + } +} + impl CiphertextMessage { pub fn message_type(&self) -> CiphertextMessageType { match self { diff --git a/wacore/libsignal/src/protocol/ratchet/keys.rs b/wacore/libsignal/src/protocol/ratchet/keys.rs index 9ee8b72a2..405f9e872 100644 --- a/wacore/libsignal/src/protocol/ratchet/keys.rs +++ b/wacore/libsignal/src/protocol/ratchet/keys.rs @@ -6,8 +6,6 @@ use std::fmt; use std::sync::LazyLock; -use arrayref::array_ref; - use hmac::{Hmac, HmacReset, KeyInit, Mac}; use sha2::Sha256; @@ -156,10 +154,17 @@ impl MessageKeys { } } + // `split_first_chunk` carries the window lengths in the type, so the + // 32/32/16 split of the 80-byte OKM is checked at compile time and the + // `Option` folds away against the fixed-size array. + let (cipher_key, rest) = okm.split_first_chunk::<32>().expect("80-byte OKM"); + let (mac_key, rest) = rest.split_first_chunk::<32>().expect("80-byte OKM"); + let (iv, _) = rest.split_first_chunk::<16>().expect("80-byte OKM"); + MessageKeys { - cipher_key: *array_ref![okm, 0, 32], - mac_key: *array_ref![okm, 32, 32], - iv: *array_ref![okm, 64, 16], + cipher_key: *cipher_key, + mac_key: *mac_key, + iv: *iv, counter, } } @@ -284,12 +289,15 @@ impl RootKey { .expand(b"WhisperRatchet", &mut derived_secret_bytes) .expect("valid output length"); + let (root_key, chain_key) = derived_secret_bytes + .split_first_chunk::<32>() + .expect("64-byte OKM"); + let (chain_key, _) = chain_key.split_first_chunk::<32>().expect("64-byte OKM"); + Ok(( - RootKey { - key: *array_ref![derived_secret_bytes, 0, 32], - }, + RootKey { key: *root_key }, ChainKey { - key: *array_ref![derived_secret_bytes, 32, 32], + key: *chain_key, index: 0, }, )) @@ -326,9 +334,9 @@ mod tests { .expand(b"WhisperMessageKeys", &mut okm) .expect("valid output length"); - assert_eq!(keys.cipher_key(), array_ref![okm, 0, 32]); - assert_eq!(keys.mac_key(), array_ref![okm, 32, 32]); - assert_eq!(keys.iv(), array_ref![okm, 64, 16]); + assert_eq!(&keys.cipher_key()[..], &okm[0..32]); + assert_eq!(&keys.mac_key()[..], &okm[32..64]); + assert_eq!(&keys.iv()[..], &okm[64..80]); assert_eq!(keys.counter(), i as u32); } } diff --git a/wacore/libsignal/src/protocol/state/prekey.rs b/wacore/libsignal/src/protocol/state/prekey.rs index fafd9eedf..75eaa67fa 100644 --- a/wacore/libsignal/src/protocol/state/prekey.rs +++ b/wacore/libsignal/src/protocol/state/prekey.rs @@ -10,11 +10,23 @@ use crate::protocol::{ }; /// A unique identifier selecting among this client's known pre-keys. -#[derive( - Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, derive_more::From, derive_more::Into, -)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct PreKeyId(u32); +impl From for PreKeyId { + #[inline] + fn from(id: u32) -> Self { + Self(id) + } +} + +impl From for u32 { + #[inline] + fn from(id: PreKeyId) -> Self { + id.0 + } +} + impl fmt::Display for PreKeyId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) diff --git a/wacore/libsignal/src/protocol/state/signed_prekey.rs b/wacore/libsignal/src/protocol/state/signed_prekey.rs index 1284ad43b..d2275c4b3 100644 --- a/wacore/libsignal/src/protocol/state/signed_prekey.rs +++ b/wacore/libsignal/src/protocol/state/signed_prekey.rs @@ -12,11 +12,23 @@ use crate::protocol::{ }; /// A unique identifier selecting among this client's known signed pre-keys. -#[derive( - Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, derive_more::From, derive_more::Into, -)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct SignedPreKeyId(u32); +impl From for SignedPreKeyId { + #[inline] + fn from(id: u32) -> Self { + Self(id) + } +} + +impl From for u32 { + #[inline] + fn from(id: SignedPreKeyId) -> Self { + id.0 + } +} + impl fmt::Display for SignedPreKeyId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) diff --git a/wacore/libsignal/src/protocol/storage/traits.rs b/wacore/libsignal/src/protocol/storage/traits.rs index e4cd3b9d4..af999569e 100644 --- a/wacore/libsignal/src/protocol/storage/traits.rs +++ b/wacore/libsignal/src/protocol/storage/traits.rs @@ -27,9 +27,8 @@ pub enum Direction { } /// The result of saving a new identity key for a protocol address. -#[derive(Copy, Clone, Debug, Eq, PartialEq, derive_more::TryFrom)] -#[repr(C)] -#[try_from(repr)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u8)] pub enum IdentityChange { /// The protocol address didn't have an identity key or had the same key. NewOrUnchanged, @@ -37,6 +36,18 @@ pub enum IdentityChange { ReplacedExisting, } +impl TryFrom for IdentityChange { + type Error = crate::core::UnknownDiscriminant; + + fn try_from(value: u8) -> std::result::Result { + match value { + 0 => Ok(Self::NewOrUnchanged), + 1 => Ok(Self::ReplacedExisting), + _ => Err(crate::core::UnknownDiscriminant { value }), + } + } +} + #[cfg(not(target_arch = "wasm32"))] pub trait ThreadSafe: Send + Sync {} #[cfg(not(target_arch = "wasm32"))] diff --git a/wacore/noise/Cargo.toml b/wacore/noise/Cargo.toml index ebdcd9b74..54ead65e9 100644 --- a/wacore/noise/Cargo.toml +++ b/wacore/noise/Cargo.toml @@ -35,7 +35,6 @@ waproto = { workspace = true } [dev-dependencies] divan = { workspace = true } -waproto = { workspace = true } [[bench]] name = "noise_benchmark" diff --git a/wacore/src/iq/groups.rs b/wacore/src/iq/groups.rs index 042ceb648..34f91f1d9 100644 --- a/wacore/src/iq/groups.rs +++ b/wacore/src/iq/groups.rs @@ -5,7 +5,6 @@ use crate::protocol::ProtocolNode; use crate::request::InfoQuery; use anyhow::{Result, anyhow}; use std::num::NonZeroU32; -use typed_builder::TypedBuilder; use wacore_binary::builder::NodeBuilder; use wacore_binary::{CompactString, Jid, Server}; use wacore_binary::{Node, NodeContent, NodeRef}; @@ -224,13 +223,10 @@ crate::define_validated_string! { pub struct GroupDescription(max_len = GROUP_DESCRIPTION_MAX_LENGTH, name = "Group description") } /// Options for a participant when creating a group. -#[derive(Debug, Clone, TypedBuilder)] -#[builder(build_method(into))] +#[derive(Debug, Clone, bon::Builder)] pub struct GroupParticipantOptions { pub jid: Jid, - #[builder(default, setter(strip_option))] pub phone_number: Option, - #[builder(default, setter(strip_option))] pub privacy: Option>, } @@ -259,20 +255,23 @@ impl GroupParticipantOptions { } /// Options for creating a new group. -#[derive(Debug, Clone, TypedBuilder)] -#[builder(build_method(into))] +// `member_link_mode` and the three that follow default to `Some(..)` rather +// than `None`, which bon spells only on a member marked `required` (its +// `Option` shorthand hardcodes a `None` default). `with` restores the +// bare-value setter that shorthand would otherwise have provided. +#[derive(Debug, Clone, bon::Builder)] pub struct GroupCreateOptions { - #[builder(setter(into))] + #[builder(into)] pub subject: String, #[builder(default)] pub participants: Vec, - #[builder(default = Some(MemberLinkMode::AdminLink), setter(strip_option))] + #[builder(required, default = Some(MemberLinkMode::AdminLink), with = |v: MemberLinkMode| Some(v))] pub member_link_mode: Option, - #[builder(default = Some(MemberAddMode::AllMemberAdd), setter(strip_option))] + #[builder(required, default = Some(MemberAddMode::AllMemberAdd), with = |v: MemberAddMode| Some(v))] pub member_add_mode: Option, - #[builder(default = Some(MembershipApprovalMode::Off), setter(strip_option))] + #[builder(required, default = Some(MembershipApprovalMode::Off), with = |v: MembershipApprovalMode| Some(v))] pub membership_approval_mode: Option, - #[builder(default = Some(0), setter(strip_option))] + #[builder(required, default = Some(0), with = |v: u32| Some(v))] pub ephemeral_expiration: Option, /// Create as a community (parent group). Emits `` in the create stanza. #[builder(default)] @@ -291,12 +290,12 @@ pub struct GroupCreateOptions { pub create_general_chat: bool, /// Parent community to link this subgroup to. Atomic alternative to /// creating then linking; mutually exclusive with `is_parent`. - #[builder(default, setter(strip_option, into))] + #[builder(into)] pub linked_parent: Option, /// Inline description carried on the create stanza; avoids a follow-up /// SetGroupDescription IQ. Validation (length cap) goes through /// [`GroupDescription`] so both create paths share the same contract. - #[builder(default, setter(strip_option, into))] + #[builder(into)] pub description: Option, } diff --git a/wacore/src/voip/mlow/smpl_tables_blob.rs b/wacore/src/voip/mlow/smpl_tables_blob.rs index ab04cd085..1c273e1b0 100644 --- a/wacore/src/voip/mlow/smpl_tables_blob.rs +++ b/wacore/src/voip/mlow/smpl_tables_blob.rs @@ -7,8 +7,6 @@ //! this module. The load path is byte-identical to deserializing the JSON, so the codec output does //! not change. -use std::io::Read; - /// buffa-generated types for the table schemas (`tables.proto`), produced at /// build time into `OUT_DIR` (see `wacore/build.rs`). The `.bin` blobs decode /// against these by field number, so the format is unchanged from the prior @@ -30,13 +28,14 @@ pub(crate) mod tables { #[cfg(test)] const GEN_ZLIB_LEVEL: u32 = 9; +/// Largest table blob we will inflate. The committed `.bin` set tops out well +/// under this; the cap only exists because the pooled inflater requires one. +const MAX_TABLE_BYTES: u64 = 4 * 1024 * 1024; + /// Inflate a zlib blob (the runtime load path for the table `.bin`). fn inflate(compressed: &[u8]) -> Vec { - let mut dec = flate2::read::ZlibDecoder::new(compressed); - let mut out = Vec::new(); - dec.read_to_end(&mut out) - .expect("mlow table blob must zlib-inflate"); - out + wacore_binary::zlib_pool::decompress_zlib_pooled(compressed, MAX_TABLE_BYTES) + .expect("mlow table blob must zlib-inflate") } /// Load a protobuf table from its embedded zlib blob. diff --git a/waproto/Cargo.toml b/waproto/Cargo.toml index 55bdae7be..b59e7000b 100644 --- a/waproto/Cargo.toml +++ b/waproto/Cargo.toml @@ -39,7 +39,7 @@ heck = { workspace = true } sha2 = { workspace = true } [dev-dependencies] -serde_json = "1" +serde_json = { workspace = true, features = ["std"] } [lints] workspace = true From 79c680bb81144d5226e5e0c7d072eb2b86edc10d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 20:53:04 +0000 Subject: [PATCH 2/2] fix(deps): keep the surfaces the removed derives had guaranteed Three shapes changed by accident in the previous commit rather than by decision: - typed-builder's build_method(into) made build() finish into anything the options convert into. bon's finisher is renamed to finish() and build() is hand-written generic over From, so a downstream impl From for its own request type still compiles. The reflexive From covers the plain case. - IdentityChange is repr(C) again. Its layout is a public guarantee for FFI, and the hand-written conversion never needed the repr narrowed to u8. - Because repr(C) leaves the discriminant type unspecified, IdentityChange's TryFrom takes isize, matching what the repr-driven derive fell back to. UnknownDiscriminant is generic over the discriminant type to carry both it and CiphertextMessageType's u8. --- wacore/libsignal/src/core/mod.rs | 4 ++-- wacore/libsignal/src/protocol/protocol.rs | 2 +- .../libsignal/src/protocol/storage/traits.rs | 11 +++++++---- wacore/src/iq/groups.rs | 18 ++++++++++++++++++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/wacore/libsignal/src/core/mod.rs b/wacore/libsignal/src/core/mod.rs index dc22ca8a8..2521c66b2 100644 --- a/wacore/libsignal/src/core/mod.rs +++ b/wacore/libsignal/src/core/mod.rs @@ -16,6 +16,6 @@ pub use address::{AddressBuf, DeviceId, ProtocolAddress}; /// leaked through the public API. #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] #[error("no variant with discriminant {value}")] -pub struct UnknownDiscriminant { - pub value: u8, +pub struct UnknownDiscriminant { + pub value: T, } diff --git a/wacore/libsignal/src/protocol/protocol.rs b/wacore/libsignal/src/protocol/protocol.rs index 87abacad6..19525d1ef 100644 --- a/wacore/libsignal/src/protocol/protocol.rs +++ b/wacore/libsignal/src/protocol/protocol.rs @@ -231,7 +231,7 @@ pub enum CiphertextMessageType { } impl TryFrom for CiphertextMessageType { - type Error = crate::core::UnknownDiscriminant; + type Error = crate::core::UnknownDiscriminant; fn try_from(value: u8) -> std::result::Result { match value { diff --git a/wacore/libsignal/src/protocol/storage/traits.rs b/wacore/libsignal/src/protocol/storage/traits.rs index af999569e..ddf84cd38 100644 --- a/wacore/libsignal/src/protocol/storage/traits.rs +++ b/wacore/libsignal/src/protocol/storage/traits.rs @@ -28,7 +28,7 @@ pub enum Direction { /// The result of saving a new identity key for a protocol address. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[repr(u8)] +#[repr(C)] pub enum IdentityChange { /// The protocol address didn't have an identity key or had the same key. NewOrUnchanged, @@ -36,10 +36,13 @@ pub enum IdentityChange { ReplacedExisting, } -impl TryFrom for IdentityChange { - type Error = crate::core::UnknownDiscriminant; +// `isize` rather than the narrower `u8` the variants fit in: `repr(C)` leaves +// the discriminant type unspecified, so this mirrors what a repr-driven derive +// falls back to and keeps the conversion callable with the same argument. +impl TryFrom for IdentityChange { + type Error = crate::core::UnknownDiscriminant; - fn try_from(value: u8) -> std::result::Result { + fn try_from(value: isize) -> std::result::Result { match value { 0 => Ok(Self::NewOrUnchanged), 1 => Ok(Self::ReplacedExisting), diff --git a/wacore/src/iq/groups.rs b/wacore/src/iq/groups.rs index 34f91f1d9..f3a5f879b 100644 --- a/wacore/src/iq/groups.rs +++ b/wacore/src/iq/groups.rs @@ -224,12 +224,22 @@ crate::define_validated_string! { } /// Options for a participant when creating a group. #[derive(Debug, Clone, bon::Builder)] +#[builder(finish_fn = finish)] pub struct GroupParticipantOptions { pub jid: Jid, pub phone_number: Option, pub privacy: Option>, } +/// `build()` finishes into anything the options convert into, not just `Self`. +/// The reflexive `From` covers the plain case, so a caller that already has +/// `impl From for T` keeps compiling. +impl GroupParticipantOptionsBuilder { + pub fn build>(self) -> T { + self.finish().into() + } +} + impl GroupParticipantOptions { pub fn new(jid: Jid) -> Self { Self { @@ -260,6 +270,7 @@ impl GroupParticipantOptions { // `Option` shorthand hardcodes a `None` default). `with` restores the // bare-value setter that shorthand would otherwise have provided. #[derive(Debug, Clone, bon::Builder)] +#[builder(finish_fn = finish)] pub struct GroupCreateOptions { #[builder(into)] pub subject: String, @@ -299,6 +310,13 @@ pub struct GroupCreateOptions { pub description: Option, } +/// See [`GroupParticipantOptionsBuilder::build`] for why this is generic. +impl GroupCreateOptionsBuilder { + pub fn build>(self) -> T { + self.finish().into() + } +} + impl GroupCreateOptions { /// Create new options with just a subject (for backwards compatibility). pub fn new(subject: impl Into) -> Self {