Upgrade to Rust 1.88

This commit is contained in:
marc-signal
2026-01-21 15:39:02 -05:00
committed by GitHub
parent 40955292f9
commit f5ab0fe616
28 changed files with 130 additions and 154 deletions

View File

@@ -40,7 +40,7 @@ resolver = "2" # so that our dev-dependency features don't leak into products
version = "0.86.14"
authors = ["Signal Messenger LLC"]
license = "AGPL-3.0-only"
rust-version = "1.85"
rust-version = "1.88"
[workspace.lints.clippy]
# Prefer TryFrom between integers unless truncation is desired.

View File

@@ -1,3 +1,5 @@
v0.86.14
- TypeScript: Fix accidentally-lax typing for the non-deprecated overload of `Net.setRemoteConfig`.
- Upgrade MSRV to 1.88

View File

@@ -8,7 +8,7 @@ name = "attest"
version = "0.1.0"
authors.workspace = true
license.workspace = true
rust-version = "1.85"
rust-version = "1.88"
edition = "2024"
[lints]

View File

@@ -93,16 +93,15 @@ pub fn verify_remote_attestation(
let attestation = attest(evidence_bytes, endorsement_bytes, current_time)?;
// 4. Verify the status of the Intel® SGX TCB described in the chain.
if let TcbStanding::SWHardeningNeeded { advisory_ids } = attestation.tcb_standing {
if advisory_ids
if let TcbStanding::SWHardeningNeeded { advisory_ids } = attestation.tcb_standing
&& advisory_ids
.iter()
.any(|id| !acceptable_sw_advisories.contains(&id.as_str()))
{
return Err(Error::new(format!(
"TCB contains unmitigated unaccepted advisory ids: {advisory_ids:?}"
))
.into());
}
{
return Err(Error::new(format!(
"TCB contains unmitigated unaccepted advisory ids: {advisory_ids:?}"
))
.into());
}
// 5. Verify the enclave measurements in the Quote reflect an enclave identity expected.

View File

@@ -106,10 +106,12 @@ impl TryFrom<&[u8]> for SgxEndorsements {
let (offsets, data) = src.split_at(offsets_required_size);
// TODO: Use as_chunks instead at MSRV 1.88.
let (offsets, offsets_remainder) = offsets.as_chunks::<{ std::mem::size_of::<u32>() }>();
// offsets_required_size is a multiple of std::mem::size_of::<u32>
assert!(offsets_remainder.is_empty());
let offsets = offsets
.chunks_exact(4)
.map(|d| u32::from_le_bytes(d.try_into().expect("correct size")) as usize)
.iter()
.map(|d| u32::from_le_bytes(*d) as usize)
.collect::<Vec<usize>>();
validate_offsets(&offsets, data)?;

View File

@@ -72,14 +72,11 @@ pub fn ValidatingMac_Initialize(
}
let incremental = Incremental::new(hmac, chunk_size as usize);
const MAC_SIZE: usize = <Digest as OutputSizeUser>::OutputSize::USIZE;
// TODO: When we reach an MSRV of 1.88, we can use as_chunks instead.
let macs = digests.chunks_exact(MAC_SIZE);
if !macs.remainder().is_empty() {
let (macs, macs_remainder) = digests.as_chunks::<MAC_SIZE>();
if !macs_remainder.is_empty() {
return None;
}
Some(ValidatingMac(Some(incremental.validating(macs.map(
|chunk| <&[u8; MAC_SIZE]>::try_from(chunk).expect("split into correct size already"),
)))))
Some(ValidatingMac(Some(incremental.validating(macs.iter()))))
}
#[bridge_fn]

View File

@@ -1073,7 +1073,11 @@ fn GroupSendEndorsementsResponse_IssueDeterministic(
key_pair: &[u8],
randomness: &[u8; RANDOMNESS_LEN],
) -> Vec<u8> {
assert!(concatenated_group_member_ciphertexts.len() % UUID_CIPHERTEXT_LEN == 0);
assert!(
concatenated_group_member_ciphertexts
.len()
.is_multiple_of(UUID_CIPHERTEXT_LEN)
);
let user_id_ciphertexts = concatenated_group_member_ciphertexts
.chunks_exact(UUID_CIPHERTEXT_LEN)
.map(|serialized| {
@@ -1142,7 +1146,11 @@ fn GroupSendEndorsementsResponse_ReceiveAndCombineWithCiphertexts(
let response = zkgroup::deserialize::<GroupSendEndorsementsResponse>(response_bytes)
.expect("should have been parsed previously");
assert!(concatenated_group_member_ciphertexts.len() % UUID_CIPHERTEXT_LEN == 0);
assert!(
concatenated_group_member_ciphertexts
.len()
.is_multiple_of(UUID_CIPHERTEXT_LEN)
);
let local_user_index = concatenated_group_member_ciphertexts
.chunks_exact(UUID_CIPHERTEXT_LEN)
.position(|serialized| serialized == local_user_ciphertext)

View File

@@ -1063,21 +1063,15 @@ impl<A: ResultTypeInfo, B: ResultTypeInfo> ResultTypeInfo for (A, B) {
}
impl<A: ResultTypeInfo, B: ResultTypeInfo> ResultTypeInfo for Option<(A, B)>
// We can simplify this when our MSRV is 1.88, when pointers become Default.
// Meanwhile, we'll rely on the fact that every C type is zeroable.
where
A::ResultType: zerocopy::FromZeros,
B::ResultType: zerocopy::FromZeros,
A::ResultType: Default,
B::ResultType: Default,
{
type ResultType = OptionalPairOf<A::ResultType, B::ResultType>;
fn convert_into(self) -> SignalFfiResult<Self::ResultType> {
let Some(value) = self else {
return Ok(OptionalPairOf {
present: false,
first: zerocopy::FromZeros::new_zeroed(),
second: zerocopy::FromZeros::new_zeroed(),
});
return Ok(OptionalPairOf::default());
};
Ok(OptionalPairOf {
present: true,

View File

@@ -27,7 +27,9 @@ impl HsmEnclaveClient {
return Err(hsm_enclave::Error::InvalidPublicKeyError);
}
if trusted_code_hashes.is_empty()
|| trusted_code_hashes.len() % hsm_enclave::CODE_HASH_SIZE != 0
|| !trusted_code_hashes
.len()
.is_multiple_of(hsm_enclave::CODE_HASH_SIZE)
{
return Err(hsm_enclave::Error::InvalidCodeHashError);
}

View File

@@ -9,7 +9,6 @@ use std::sync::Arc;
use libsignal_protocol::{ServiceId, ServiceIdFixedWidthBinaryBytes};
use rayon::iter::ParallelIterator as _;
use rayon::slice::ParallelSlice as _;
use crate::*;
@@ -39,11 +38,8 @@ impl<'a> ServiceIdSequence<'a> {
Self(input)
}
fn parse_single_chunk(chunk: &[u8]) -> ServiceId {
ServiceId::parse_from_service_id_fixed_width_binary(
chunk.try_into().expect("correctly split"),
)
.expect(concat!(
fn parse_single_chunk(chunk: &ServiceIdFixedWidthBinaryBytes) -> ServiceId {
ServiceId::parse_from_service_id_fixed_width_binary(chunk).expect(concat!(
"input should be a concatenated list of Service-Id-FixedWidthBinary, ",
"but one ServiceId was invalid"
))
@@ -51,25 +47,29 @@ impl<'a> ServiceIdSequence<'a> {
}
impl<'a> IntoIterator for ServiceIdSequence<'a> {
type IntoIter = std::iter::Map<std::slice::ChunksExact<'a, u8>, fn(&[u8]) -> ServiceId>;
type IntoIter = std::iter::Map<
std::slice::Iter<'a, [u8; 17]>,
for<'b> fn(&'b ServiceIdFixedWidthBinaryBytes) -> ServiceId,
>;
type Item = ServiceId;
fn into_iter(self) -> Self::IntoIter {
// TODO: Use as_chunks when we reach MSRV 1.88.
self.0
.chunks_exact(Self::SERVICE_ID_FIXED_WIDTH_BINARY_LEN)
.map(Self::parse_single_chunk)
self.0.as_chunks().0.iter().map(Self::parse_single_chunk)
}
}
impl<'a> rayon::iter::IntoParallelIterator for ServiceIdSequence<'a> {
type Iter = rayon::iter::Map<rayon::slice::ChunksExact<'a, u8>, fn(&[u8]) -> ServiceId>;
type Iter = rayon::iter::Map<
rayon::slice::Iter<'a, ServiceIdFixedWidthBinaryBytes>,
for<'b> fn(&'b ServiceIdFixedWidthBinaryBytes) -> ServiceId,
>;
type Item = ServiceId;
fn into_par_iter(self) -> Self::Iter {
// TODO: Use as_chunks when we reach MSRV 1.88.
self.0
.par_chunks_exact(Self::SERVICE_ID_FIXED_WIDTH_BINARY_LEN)
.as_chunks()
.0
.into_par_iter()
.map(Self::parse_single_chunk)
}
}

View File

@@ -2,7 +2,7 @@
name = "libsignal-net"
version = "0.1.0"
edition = "2024"
rust-version = "1.85"
rust-version = "1.88"
authors.workspace = true
license.workspace = true

View File

@@ -2,7 +2,7 @@
name = "libsignal-net-infra"
version = "0.1.0"
edition = "2024"
rust-version = "1.85"
rust-version = "1.88"
authors.workspace = true
license.workspace = true

View File

@@ -104,12 +104,11 @@ impl<S> From<HandshakeError<S>> for FailedHandshakeReason {
// If we specifically have an *SSL* error, check if it's an *X509* error underneath.
// (But not X509VerifyError::INVALID_CALL, which means we're not in the right state to
// query for verification errors.)
if code == boring_signal::ssl::ErrorCode::SSL {
if let Some(cert_error_code) = value.ssl().and_then(|ssl| ssl.verify_result().err()) {
if cert_error_code != boring_signal::x509::X509VerifyError::INVALID_CALL {
return Self::Cert(cert_error_code);
}
}
if code == boring_signal::ssl::ErrorCode::SSL
&& let Some(cert_error_code) = value.ssl().and_then(|ssl| ssl.verify_result().err())
&& cert_error_code != boring_signal::x509::X509VerifyError::INVALID_CALL
{
return Self::Cert(cert_error_code);
}
Self::OtherBoring(code)

View File

@@ -143,30 +143,10 @@ pub trait RouteProvider {
/// The routes must be produced in the order in which connection attempts
/// should be made. The iterator is allowed to borrow from `self` as an
/// optimization.
///
/// Why is `context` a `&` instead of a `&mut`? Because as of Jan 2025,
/// there's no way to prevent the lifetime in the type of `context` from
/// being captured in the opaque return type. That's important because there
/// are some implementations of this trait where it's necessary to combine
/// the output of two different comprising providers. If `context` was a
/// `&mut` the first call's exclusive borrow for its entire lifetime would
/// prevent the second call from being able to use the same `context`.
///
/// There are two potential ways we could work around this:
///
/// 1. Use the new precise-capture syntax introduced in Rust 1.82, and
/// stabilized for use in traits in Rust 1.87.
///
/// 2. Introduce a named associated type that only captures `'s`, not `'c`.
/// This works now, but would require all returned iterator types to be
/// named. That would prevent us from using `Iterator::map` and other
/// combinators, or require any uses be `Box`ed and those tradeoffs
/// aren't (currently) worth the imprecision.
// TODO: when our MSRV >= 1.87, use precise captures and make context &mut.
fn routes<'s>(
fn routes<'s, C: RouteProviderContext>(
&'s self,
context: &impl RouteProviderContext,
) -> impl Iterator<Item = Self::Route> + 's;
context: &mut C,
) -> impl Iterator<Item = Self::Route> + use<'s, C, Self>;
}
/// Context parameter passed to [`RouteProvider::routes`].
@@ -175,7 +155,7 @@ pub trait RouteProvider {
/// implementer can use to make decisions about what routes to emit.
pub trait RouteProviderContext {
/// Returns a uniformly random [`usize`].
fn random_usize(&self) -> usize;
fn random_usize(&mut self) -> usize;
}
/// A hostname in a route that can later be resolved to IP addresses.
@@ -586,13 +566,13 @@ fn pull_next_route_delay<F>(connects_in_progress: &FuturesUnordered<F>) -> Durat
PER_CONNECTION_WAIT_DURATION * connections_factor
}
impl<R: RouteProvider> RouteProvider for &R {
impl<'a, R: RouteProvider> RouteProvider for &'a R {
type Route = R::Route;
fn routes<'s>(
fn routes<'s, C: RouteProviderContext>(
&'s self,
context: &impl RouteProviderContext,
) -> impl Iterator<Item = Self::Route> + 's {
context: &mut C,
) -> impl Iterator<Item = Self::Route> + use<'s, 'a, C, R> {
R::routes(self, context)
}
}
@@ -646,10 +626,10 @@ pub mod testutils {
impl<R: Clone> RouteProvider for Vec<R> {
type Route = R;
fn routes<'s>(
fn routes<'s, C: RouteProviderContext>(
&'s self,
_context: &impl RouteProviderContext,
) -> impl Iterator<Item = Self::Route> + 's {
_context: &mut C,
) -> impl Iterator<Item = Self::Route> + use<'s, R, C> {
self.iter().cloned()
}
}
@@ -673,7 +653,7 @@ pub mod testutils {
}
impl RouteProviderContext for FakeContext {
fn random_usize(&self) -> usize {
fn random_usize(&mut self) -> usize {
UniformUsize::sample_single_inclusive(0, usize::MAX, &mut self.rng.borrow_mut())
.expect("non-empty range")
}
@@ -775,7 +755,7 @@ mod test {
},
};
let routes = RouteProvider::routes(&provider, &FakeContext::new()).collect_vec();
let routes = RouteProvider::routes(&provider, &mut FakeContext::new()).collect_vec();
let expected_routes = vec![
WebSocketRoute {
@@ -896,7 +876,7 @@ mod test {
inner: direct_provider,
};
let routes = provider.routes(&FakeContext::new()).collect_vec();
let routes = provider.routes(&mut FakeContext::new()).collect_vec();
assert_eq!(
routes,
@@ -958,7 +938,7 @@ mod test {
inner: direct_provider,
};
let routes = provider.routes(&FakeContext::new()).collect_vec();
let routes = provider.routes(&mut FakeContext::new()).collect_vec();
let expected_routes = vec![
TlsRoute {

View File

@@ -113,10 +113,10 @@ impl DomainFrontRouteProvider {
impl RouteProvider for DomainFrontRouteProvider {
type Route = HttpsTlsRoute<TlsRoute<TcpRoute<UnresolvedHost>>>;
fn routes<'s>(
fn routes<'s, C: RouteProviderContext>(
&'s self,
context: &impl RouteProviderContext,
) -> impl Iterator<Item = Self::Route> + 's {
context: &mut C,
) -> impl Iterator<Item = Self::Route> + use<'s, C> {
let Self {
fronts,
http_version,
@@ -174,10 +174,10 @@ where
F: RouteProvider<Route = HttpsTlsRoute<P::Route>>,
{
type Route = HttpsTlsRoute<P::Route>;
fn routes<'s>(
fn routes<'s, C: RouteProviderContext>(
&'s self,
context: &impl RouteProviderContext,
) -> impl Iterator<Item = Self::Route> + 's {
context: &mut C,
) -> impl Iterator<Item = Self::Route> + use<'s, C, F, P> {
let Self {
direct_host_header,
direct_http_version,
@@ -274,7 +274,7 @@ mod test {
},
};
let routes = provider.routes(&FakeContext::new()).collect_vec();
let routes = provider.routes(&mut FakeContext::new()).collect_vec();
assert_eq!(
routes,

View File

@@ -45,10 +45,10 @@ pub struct Map<R, F>(R, F);
impl<R: RouteProvider, F: Fn(R::Route) -> T, T> RouteProvider for Map<R, F> {
type Route = T;
fn routes<'s>(
fn routes<'s, C: RouteProviderContext>(
&'s self,
context: &impl RouteProviderContext,
) -> impl Iterator<Item = Self::Route> + 's {
context: &mut C,
) -> impl Iterator<Item = Self::Route> + use<'s, C, R, F, T> {
self.0.routes(context).map(&self.1)
}
}
@@ -59,10 +59,10 @@ pub struct Filter<R, F>(R, F);
impl<R: RouteProvider, F: Fn(&R::Route) -> bool> RouteProvider for Filter<R, F> {
type Route = R::Route;
fn routes<'s>(
fn routes<'s, C: RouteProviderContext>(
&'s self,
context: &impl RouteProviderContext,
) -> impl Iterator<Item = Self::Route> + 's {
context: &mut C,
) -> impl Iterator<Item = Self::Route> + use<'s, C, R, F> {
self.0.routes(context).filter(&self.1)
}
}
@@ -75,10 +75,10 @@ pub struct EmptyProvider<R> {
impl<R> RouteProvider for EmptyProvider<R> {
type Route = R;
fn routes<'s>(
fn routes<'s, C: RouteProviderContext>(
&'s self,
_context: &impl RouteProviderContext,
) -> impl Iterator<Item = Self::Route> + 's {
_context: &mut C,
) -> impl Iterator<Item = Self::Route> + use<'s, C, R> {
std::iter::empty()
}
}

View File

@@ -307,10 +307,10 @@ where
{
type Route = R;
fn routes<'s>(
fn routes<'s, C: RouteProviderContext>(
&'s self,
context: &impl RouteProviderContext,
) -> impl Iterator<Item = Self::Route> + 's {
context: &mut C,
) -> impl Iterator<Item = Self::Route> + use<'s, C, D, R> {
let Self { inner, mode } = self;
let original_routes = inner.routes(context);
match mode {

View File

@@ -70,10 +70,10 @@ impl DirectTcpRouteProvider {
impl RouteProvider for DirectTcpRouteProvider {
type Route = TcpRoute<UnresolvedHost>;
fn routes<'s>(
fn routes<'s, C: RouteProviderContext>(
&'s self,
_context: &impl RouteProviderContext,
) -> impl Iterator<Item = Self::Route> + 's {
_context: &mut C,
) -> impl Iterator<Item = Self::Route> + use<'s, C> {
let Self {
dns_hostname,
port,

View File

@@ -64,10 +64,10 @@ pub(crate) trait SetAlpn {
impl<P: RouteProvider> RouteProvider for TlsRouteProvider<P> {
type Route = TlsRoute<P::Route>;
fn routes<'s>(
fn routes<'s, C: RouteProviderContext>(
&'s self,
context: &impl RouteProviderContext,
) -> impl Iterator<Item = Self::Route> + 's {
context: &mut C,
) -> impl Iterator<Item = Self::Route> + use<'s, C, P> {
let Self {
sni,
certs,

View File

@@ -44,10 +44,10 @@ impl<P> WebSocketProvider<P> {
impl<P: RouteProvider> RouteProvider for WebSocketProvider<P> {
type Route = WebSocketRoute<P::Route>;
fn routes<'s>(
fn routes<'s, C: RouteProviderContext>(
&'s self,
context: &impl RouteProviderContext,
) -> impl Iterator<Item = Self::Route> + 's {
context: &mut C,
) -> impl Iterator<Item = Self::Route> + use<'s, C, P> {
self.inner.routes(context).map(|route| WebSocketRoute {
inner: route,
fragment: self.fragment.clone(),

View File

@@ -142,20 +142,17 @@ impl TryFrom<ClientResponse> for LookupResponse {
debug_permits_used,
} = response;
if e164_pni_aci_triples.len() % LookupResponseEntry::SERIALIZED_LEN != 0 {
let (record_chunks, record_remainder) =
e164_pni_aci_triples.as_chunks::<{ LookupResponseEntry::SERIALIZED_LEN }>();
if !record_remainder.is_empty() {
return Err(CdsiProtocolError::InvalidNumberOfBytes {
actual_length: e164_pni_aci_triples.len(),
});
}
// TODO: Use as_chunks when we reach MSRV 1.88.
let records = e164_pni_aci_triples
.chunks(LookupResponseEntry::SERIALIZED_LEN)
.flat_map(|record| {
LookupResponseEntry::try_parse_from(
record.try_into().expect("chunk size is correct"),
)
})
let records = record_chunks
.iter()
.flat_map(LookupResponseEntry::try_parse_from)
.collect();
Ok(Self {

View File

@@ -441,10 +441,10 @@ impl Responder {
pub fn send_response(self, status: StatusCode) -> Result<(), SendError> {
let Self { id, tx } = self;
if let Some(tx) = tx.upgrade() {
if let Ok(()) = tx.send(OutgoingResponse { id, status }) {
return Ok(());
}
if let Some(tx) = tx.upgrade()
&& let Ok(()) = tx.send(OutgoingResponse { id, status })
{
return Ok(());
}
Err(SendError::Disconnected(DisconnectedReason::SocketClosed {

View File

@@ -506,13 +506,13 @@ impl<TC> ConnectionResources<'_, TC> {
post_route_change_connect_timeout,
transport_connector,
attempts_record,
route_provider_context,
mut route_provider_context,
} = connect_state
.lock()
.expect("not poisoned")
.prepare_snapshot();
let routes = routes.routes(&route_provider_context).collect_vec();
let routes = routes.routes(&mut route_provider_context).collect_vec();
log::info!(
"[{log_tag}] starting connection attempt with {} routes",
@@ -649,7 +649,7 @@ where
post_route_change_connect_timeout,
transport_connector,
attempts_record,
route_provider_context,
mut route_provider_context,
} = connect_state
.lock()
.expect("not poisoned")
@@ -660,7 +660,7 @@ where
should: true,
inner: r,
})
.routes(&route_provider_context)
.routes(&mut route_provider_context)
.collect_vec();
log::info!(
@@ -777,7 +777,7 @@ where
struct RouteProviderContextImpl(UnwrapErr<OsRng>);
impl RouteProviderContext for RouteProviderContextImpl {
fn random_usize(&self) -> usize {
fn random_usize(&mut self) -> usize {
// OsRng is zero-sized, so we're not losing random values by copying it.
let mut owned_rng: UnwrapErr<OsRng> = self.0;
assert_eq_size_val!(owned_rng, ());

View File

@@ -251,10 +251,10 @@ impl From<WebSocketServiceConnectError> for Error {
response,
received_at: _,
} => {
if response.status() == http::StatusCode::TOO_MANY_REQUESTS {
if let Some(retry_later) = extract_retry_later(response.headers()) {
return Self::RateLimited(retry_later);
}
if response.status() == http::StatusCode::TOO_MANY_REQUESTS
&& let Some(retry_later) = extract_retry_later(response.headers())
{
return Self::RateLimited(retry_later);
}
Self::WebSocket(WebSocketError::Http(response))
}

View File

@@ -962,7 +962,7 @@ mod test {
},
override_nagle_algorithm,
);
let routes = route_provider.routes(&FakeContext::new()).collect_vec();
let routes = route_provider.routes(&mut FakeContext::new()).collect_vec();
let expected_direct_route = HttpsTlsRoute {
fragment: HttpRouteFragment {

View File

@@ -9,7 +9,7 @@ version = "0.7.0"
authors.workspace = true
license.workspace = true
edition = "2024"
rust-version = "1.85"
rust-version = "1.88"
[lints]
workspace = true

View File

@@ -19,15 +19,13 @@ impl Proof {
/// Returns `None` if the input is invalid. This does not run in constant
/// time!
pub fn from_slice(bytes: &[u8]) -> Option<Self> {
// TODO: use as_chunks instead once we reach MSRV 1.88.
let chunks = bytes.chunks_exact(32);
if !chunks.remainder().is_empty() {
let (chunks, chunks_remainder) = bytes.as_chunks::<32>();
if !chunks_remainder.is_empty() {
return None;
}
let mut array_chunks = chunks.map(|chunk| {
let chunk = chunk.try_into().expect("chunk size is exact");
Option::from(Scalar::from_canonical_bytes(chunk))
});
let mut array_chunks = chunks
.iter()
.map(|chunk| Option::from(Scalar::from_canonical_bytes(*chunk)));
let challenge = array_chunks.next()??;
if array_chunks.len() > 256 {

View File

@@ -204,13 +204,11 @@ impl Statement {
sho2.absorb_and_ratchet(message); // M
let blinding_scalar_bytes = sho2.squeeze_and_ratchet(g1.len() * 64);
// TODO: use as_chunks once we reach MSRV 1.88.
let nonce: G1 = blinding_scalar_bytes
.chunks_exact(64)
.map(|chunk| {
let chunk = chunk.try_into().expect("correct width");
Scalar::from_bytes_mod_order_wide(chunk)
})
.as_chunks::<64>()
.0
.iter()
.map(Scalar::from_bytes_mod_order_wide)
.collect();
// Commitment from nonce by applying homomorphism F: commitment = F(nonce)