From 8c298780e373aef7897a16cb360ba71c7cdcb190 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 24 Nov 2025 10:52:43 +0000 Subject: [PATCH 1/7] Add fns BlockRng::remaining_results, reconstruct --- src/block.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/block.rs b/src/block.rs index 0889bcd8..319908c1 100644 --- a/src/block.rs +++ b/src/block.rs @@ -168,6 +168,27 @@ impl> BlockRng< results: [W::default(); N], } } + + /// Reconstruct from a core and a remaining-results buffer. + /// + /// This may be used to deserialize using a `core` and the output of + /// [`Self::remaining_results`]. + /// + /// Returns `None` if `remaining_results` is too long. + pub fn reconstruct(core: G, remaining_results: &[W]) -> Option { + let mut results = [W::default(); N]; + if remaining_results.len() <= N { + let index = N - remaining_results.len(); + results[index..].copy_from_slice(remaining_results); + Some(BlockRng { + results, + index, + core, + }) + } else { + None + } + } } impl> BlockRng { @@ -197,6 +218,15 @@ impl> BlockRng { self.index = index; } + /// Access the unused part of the results buffer + /// + /// This is a low-level interface intended for serialization. + /// Results are not marked as consumed. + #[inline] + pub fn remaining_results(&self) -> &[W] { + &self.results[self.index..] + } + /// Generate the next word (e.g. `u32`) #[inline] pub fn next_word(&mut self) -> W { From f4ef7acddef92a323d67dfa8f946ec587248617d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 10 Dec 2025 10:27:46 +0000 Subject: [PATCH 2/7] BlockRng: require W: Word in most impls --- src/block.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/block.rs b/src/block.rs index 319908c1..01e7cb88 100644 --- a/src/block.rs +++ b/src/block.rs @@ -142,7 +142,10 @@ pub struct BlockRng { } // Custom Debug implementation that does not expose the contents of `results`. -impl fmt::Debug for BlockRng { +impl fmt::Debug for BlockRng +where + G: Generator + fmt::Debug, +{ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("BlockRng") .field("core", &self.core) @@ -157,7 +160,7 @@ impl Drop for BlockRng { } } -impl> BlockRng { +impl> BlockRng { /// Create a new `BlockRng` from an existing RNG implementing /// `Generator`. Results will be generated on first use. #[inline] @@ -191,7 +194,7 @@ impl> BlockRng< } } -impl> BlockRng { +impl> BlockRng { /// Get the index into the result buffer. /// /// If this is equal to or larger than the size of the result buffer then From 65b0d45d0cb85bef0cc056163f5fd692a7b6fd0f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 10 Dec 2025 10:00:09 +0000 Subject: [PATCH 3/7] BlockRng: use index, set_index methods --- src/block.rs | 62 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/src/block.rs b/src/block.rs index 01e7cb88..aaeeb0dd 100644 --- a/src/block.rs +++ b/src/block.rs @@ -149,7 +149,7 @@ where fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("BlockRng") .field("core", &self.core) - .field("index", &self.index) + .field("index", &self.index()) .finish() } } @@ -205,6 +205,12 @@ impl> BlockRng { self.index } + #[inline(always)] + fn set_index(&mut self, index: usize) { + debug_assert!(0 < index && index <= N); + self.index = index; + } + /// Reset the number of available results. /// This will force a new set of results to be generated on next use. #[inline] @@ -218,7 +224,7 @@ impl> BlockRng { pub fn generate_and_set(&mut self, index: usize) { assert!(index < N); self.core.generate(&mut self.results); - self.index = index; + self.set_index(index); } /// Access the unused part of the results buffer @@ -227,18 +233,21 @@ impl> BlockRng { /// Results are not marked as consumed. #[inline] pub fn remaining_results(&self) -> &[W] { - &self.results[self.index..] + let index = self.index(); + &self.results[index..] } /// Generate the next word (e.g. `u32`) #[inline] pub fn next_word(&mut self) -> W { - if self.index >= N { - self.generate_and_set(0); + let mut index = self.index(); + if index >= N { + self.core.generate(&mut self.results); + index = 0; } - let value = self.results[self.index].clone(); - self.index += 1; + let value = self.results[index].clone(); + self.set_index(index + 1); value } } @@ -247,25 +256,24 @@ impl> BlockRng { /// Generate a `u64` from two `u32` words #[inline] pub fn next_u64_from_u32(&mut self) -> u64 { - let read_u64 = |results: &[u32], index| { - let data = &results[index..=index + 1]; - (u64::from(data[1]) << 32) | u64::from(data[0]) - }; - - let index = self.index; + let index = self.index(); + let (lo, hi); if index < N - 1 { - self.index += 2; - // Read an u64 from the current index - read_u64(&self.results, index) + lo = self.results[index]; + hi = self.results[index + 1]; + self.set_index(index + 2); } else if index >= N { - self.generate_and_set(2); - read_u64(&self.results, 0) + self.core.generate(&mut self.results); + lo = self.results[0]; + hi = self.results[1]; + self.set_index(2); } else { - let x = u64::from(self.results[N - 1]); - self.generate_and_set(1); - let y = u64::from(self.results[0]); - (y << 32) | x + lo = self.results[N - 1]; + self.core.generate(&mut self.results); + hi = self.results[0]; + self.set_index(1); } + (u64::from(hi) << 32) | u64::from(lo) } } @@ -275,13 +283,15 @@ impl> BlockRng { pub fn fill_bytes(&mut self, dest: &mut [u8]) { let mut read_len = 0; while read_len < dest.len() { - if self.index >= N { - self.generate_and_set(0); + let mut index = self.index(); + if index >= N { + self.core.generate(&mut self.results); + index = 0; } let (consumed_u32, filled_u8) = - fill_via_chunks(&self.results[self.index..], &mut dest[read_len..]); + fill_via_chunks(&self.results[index..], &mut dest[read_len..]); - self.index += consumed_u32; + self.set_index(index + consumed_u32); read_len += filled_u8; } } From 30c3c60e149e42587cf93c58d4c5d8eff8ee02d0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 10 Dec 2025 10:42:14 +0000 Subject: [PATCH 4/7] Let BlockRng::generate_and_set(0) reset without generation --- src/block.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/block.rs b/src/block.rs index aaeeb0dd..d6669af9 100644 --- a/src/block.rs +++ b/src/block.rs @@ -218,10 +218,20 @@ impl> BlockRng { self.index = N; } - /// Generate a new set of results immediately, setting the index to the - /// given value. + /// Updates the index and buffer contents + /// + /// If `index == 0`, this marks the buffer as "empty", causing generation on + /// next use. + /// + /// If `index > 0`, this generates a new block immediately then sets the + /// index. #[inline] pub fn generate_and_set(&mut self, index: usize) { + if index == 0 { + self.set_index(N); + return; + } + assert!(index < N); self.core.generate(&mut self.results); self.set_index(index); @@ -229,6 +239,10 @@ impl> BlockRng { /// Access the unused part of the results buffer /// + /// The length of the returned slice is guaranteed to be less than the + /// length of `::Output` (i.e. less than `N` where + /// `Output = [W; N]`). + /// /// This is a low-level interface intended for serialization. /// Results are not marked as consumed. #[inline] From b247e7bcb6866fb34039421b602ece5a931fc73b Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 10 Dec 2025 10:56:41 +0000 Subject: [PATCH 5/7] Add fns word::Sealed::from_usize, into_usize --- src/le.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/le.rs b/src/le.rs index b8508ff1..9c1730d1 100644 --- a/src/le.rs +++ b/src/le.rs @@ -74,21 +74,41 @@ pub fn fill_bytes_via_next(rng: &mut R, dest: &mut [u8]) { mod word { pub trait Sealed: Copy { type Bytes: Sized + AsRef<[u8]>; + fn to_le_bytes(self) -> Self::Bytes; + + fn from_usize(val: usize) -> Self; + fn into_usize(self) -> usize; } + impl Sealed for u32 { type Bytes = [u8; 4]; fn to_le_bytes(self) -> Self::Bytes { Self::to_le_bytes(self) } + + fn from_usize(val: usize) -> Self { + val.try_into().unwrap() + } + fn into_usize(self) -> usize { + self.try_into().unwrap() + } } + impl Sealed for u64 { type Bytes = [u8; 8]; fn to_le_bytes(self) -> Self::Bytes { Self::to_le_bytes(self) } + + fn from_usize(val: usize) -> Self { + val.try_into().unwrap() + } + fn into_usize(self) -> usize { + self.try_into().unwrap() + } } } From e46516a1afa5408b7c7a1acde3ea7d94018f886f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 10 Dec 2025 10:59:18 +0000 Subject: [PATCH 6/7] BlockRng: store index in results[0] --- src/block.rs | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/block.rs b/src/block.rs index d6669af9..6859e80a 100644 --- a/src/block.rs +++ b/src/block.rs @@ -136,7 +136,6 @@ pub trait CryptoGenerator: Generator {} #[derive(Clone)] pub struct BlockRng { results: G::Output, - index: usize, /// The *core* part of the RNG, implementing the `generate` function. pub core: G, } @@ -165,11 +164,9 @@ impl> BlockRng< /// `Generator`. Results will be generated on first use. #[inline] pub fn new(core: G) -> BlockRng { - BlockRng { - core, - index: N, - results: [W::default(); N], - } + let mut results = [W::default(); N]; + results[0] = W::from_usize(N); + BlockRng { core, results } } /// Reconstruct from a core and a remaining-results buffer. @@ -180,14 +177,11 @@ impl> BlockRng< /// Returns `None` if `remaining_results` is too long. pub fn reconstruct(core: G, remaining_results: &[W]) -> Option { let mut results = [W::default(); N]; - if remaining_results.len() <= N { + if remaining_results.len() < N { let index = N - remaining_results.len(); results[index..].copy_from_slice(remaining_results); - Some(BlockRng { - results, - index, - core, - }) + results[0] = W::from_usize(index); + Some(BlockRng { results, core }) } else { None } @@ -202,20 +196,20 @@ impl> BlockRng { /// results. #[inline(always)] pub fn index(&self) -> usize { - self.index + self.results[0].into_usize() } #[inline(always)] fn set_index(&mut self, index: usize) { debug_assert!(0 < index && index <= N); - self.index = index; + self.results[0] = W::from_usize(index); } /// Reset the number of available results. /// This will force a new set of results to be generated on next use. #[inline] pub fn reset(&mut self) { - self.index = N; + self.set_index(N); } /// Updates the index and buffer contents From cb72fa6acf481eda2f956587740d756a92cede76 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 10 Dec 2025 11:12:24 +0000 Subject: [PATCH 7/7] CHANGELOG and Clippy suggestion --- CHANGELOG.md | 2 ++ src/block.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf8a3714..ebf3a794 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove `BlockRng64` ([#34]) - Remove impl of `RngCore` for `BlockRng`, making the latter more generic ([#34]) - Add trait `le::Word` ([#34]) +- Add fn `BlockRng::reconstruct` and fn `BlockRng::remaining_results` ([#36]) ### Other - Changed repository from [rust-random/rand] to [rust-random/core]. @@ -36,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#28]: https://github.com/rust-random/rand-core/pull/28 [#34]: https://github.com/rust-random/rand-core/pull/34 [#35]: https://github.com/rust-random/rand-core/pull/35 +[#36]: https://github.com/rust-random/rand-core/pull/36 [rust-random/rand]: https://github.com/rust-random/rand [rust-random/core]: https://github.com/rust-random/core diff --git a/src/block.rs b/src/block.rs index 6859e80a..8b502603 100644 --- a/src/block.rs +++ b/src/block.rs @@ -254,7 +254,7 @@ impl> BlockRng { index = 0; } - let value = self.results[index].clone(); + let value = self.results[index]; self.set_index(index + 1); value }