1use alloc::format;
22use core::cmp::Ordering;
23use num_traits::bounds::{LowerBounded, UpperBounded};
24use numerated::{Bound, Numerated, interval::Interval, iterators::IntervalIterator};
25use scale_decode::DecodeAsType;
26use scale_encode::EncodeAsType;
27use scale_info::{
28 TypeInfo,
29 scale::{Decode, Encode},
30};
31
32pub use numerated::{self, num_traits};
33
34const WASM_PAGE_SIZE: u32 = 64 * 1024;
36
37const GEAR_PAGE_SIZE: u32 = 16 * 1024;
42
43const _: () = assert!(WASM_PAGE_SIZE.is_multiple_of(GEAR_PAGE_SIZE));
44
45#[derive(
49 Clone,
50 Copy,
51 Debug,
52 Decode,
53 DecodeAsType,
54 Encode,
55 EncodeAsType,
56 PartialEq,
57 Eq,
58 PartialOrd,
59 Ord,
60 Hash,
61 TypeInfo,
62 Default,
63 derive_more::Into,
64)]
65pub struct PagesAmount<const SIZE: u32>(u32);
66
67impl<const SIZE: u32> PagesAmount<SIZE> {
68 pub const SIZE: u32 = SIZE;
72
73 pub const UPPER: Self = Self(u32::MAX / SIZE + 1 / if SIZE.is_power_of_two() { 1 } else { 0 });
77
78 #[cfg(test)]
80 pub fn add(&self, other: Self) -> Option<Self> {
81 self.0
82 .checked_add(other.0)
83 .and_then(|r| (r <= Self::UPPER.0).then_some(Self(r)))
84 }
85
86 pub fn to_page_number(&self) -> Option<Page<SIZE>> {
89 self.unbound()
90 }
91
92 pub fn to_pages_amount<const S: u32>(&self) -> PagesAmount<S> {
94 let raw = if Self::SIZE > S {
95 (Self::SIZE / S) * self.0
96 } else {
97 self.0 / (S / Self::SIZE)
98 };
99 PagesAmount(raw)
100 }
101
102 pub fn offset(&self) -> u64 {
107 self.0 as u64 * SIZE as u64
108 }
109}
110
111impl<const SIZE: u32> From<Page<SIZE>> for PagesAmount<SIZE> {
112 fn from(value: Page<SIZE>) -> Self {
113 Self(value.0)
114 }
115}
116
117impl<const SIZE: u32> From<Option<Page<SIZE>>> for PagesAmount<SIZE> {
118 fn from(value: Option<Page<SIZE>>) -> Self {
119 value.map(|page| page.into()).unwrap_or(Self::UPPER)
120 }
121}
122
123impl<const SIZE: u32> Bound<Page<SIZE>> for PagesAmount<SIZE> {
124 fn unbound(self) -> Option<Page<SIZE>> {
125 match self.cmp(&Self::UPPER) {
126 Ordering::Greater => {
127 let err_msg = format!(
129 "PagesAmount::unbound: PageBound must be always less or equal than UPPER. \
130 Page bound - {:?}, UPPER - {:?}",
131 self,
132 Self::UPPER
133 );
134
135 log::error!("{err_msg}");
136 unreachable!("{err_msg}")
137 }
138 Ordering::Equal => None,
139 Ordering::Less => Some(Page(self.0)),
140 }
141 }
142}
143
144#[derive(Debug, Clone, derive_more::Display)]
146#[display("Tries to make pages amount from {_0}, which must be less or equal to {_1}")]
147pub struct PagesAmountError(u32, u32);
148
149impl<const SIZE: u32> TryFrom<u32> for PagesAmount<SIZE> {
150 type Error = PagesAmountError;
151
152 fn try_from(raw: u32) -> Result<Self, Self::Error> {
153 if raw > Self::UPPER.0 {
154 Err(PagesAmountError(raw, Self::UPPER.0))
155 } else {
156 Ok(Self(raw))
157 }
158 }
159}
160
161impl<const SIZE: u32> PartialEq<Page<SIZE>> for PagesAmount<SIZE> {
162 fn eq(&self, other: &Page<SIZE>) -> bool {
163 self.0 == other.0
164 }
165}
166
167impl<const SIZE: u32> PartialOrd<Page<SIZE>> for PagesAmount<SIZE> {
168 fn partial_cmp(&self, other: &Page<SIZE>) -> Option<Ordering> {
169 self.0.partial_cmp(&other.0)
170 }
171}
172
173impl<const SIZE: u32> PartialEq<PagesAmount<SIZE>> for Page<SIZE> {
174 fn eq(&self, other: &PagesAmount<SIZE>) -> bool {
175 self.0 == other.0
176 }
177}
178
179impl<const SIZE: u32> PartialOrd<PagesAmount<SIZE>> for Page<SIZE> {
180 fn partial_cmp(&self, other: &PagesAmount<SIZE>) -> Option<Ordering> {
181 self.0.partial_cmp(&other.0)
182 }
183}
184
185#[derive(
190 Clone,
191 Copy,
192 Debug,
193 Decode,
194 DecodeAsType,
195 Encode,
196 EncodeAsType,
197 PartialEq,
198 Eq,
199 PartialOrd,
200 Ord,
201 Hash,
202 TypeInfo,
203 Default,
204 derive_more::Into,
205)]
206#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
207pub struct Page<const SIZE: u32>(u32);
208
209impl<const SIZE: u32> Page<SIZE> {
210 pub const SIZE: u32 = SIZE;
212
213 #[allow(clippy::erasing_op)]
217 pub const UPPER: Self = Self(u32::MAX / SIZE + 0 / if SIZE.is_power_of_two() { 1 } else { 0 });
218
219 pub fn inc(&self) -> PagesAmount<SIZE> {
221 PagesAmount(self.0 + 1)
222 }
223
224 pub fn from_offset(offset: u32) -> Self {
226 Self(offset / Self::SIZE)
227 }
228
229 pub fn offset(&self) -> u32 {
231 self.0 * Self::SIZE
232 }
233
234 pub fn end_offset(&self) -> u32 {
236 self.0 * Self::SIZE + (Self::SIZE - 1)
237 }
238
239 pub fn to_page<const S1: u32>(self) -> Page<S1> {
241 Page::from_offset(self.offset())
242 }
243
244 pub fn to_iter<const S1: u32>(self) -> IntervalIterator<Page<S1>> {
257 let start = Page::<S1>::from_offset(self.offset());
258 let end = Page::<S1>::from_offset(self.end_offset());
259 unsafe { Interval::new_unchecked(start, end).iter() }
261 }
262}
263
264#[derive(Debug, Clone, derive_more::Display)]
266#[display("Tries to make page from {_0}, which must be less or equal to {_1}")]
267pub struct PageError(u32, u32);
268
269impl<const SIZE: u32> TryFrom<u32> for Page<SIZE> {
270 type Error = PageError;
271
272 fn try_from(raw: u32) -> Result<Self, Self::Error> {
273 if raw > Self::UPPER.0 {
274 Err(PageError(raw, Self::UPPER.0))
275 } else {
276 Ok(Self(raw))
277 }
278 }
279}
280
281impl<const SIZE: u32> Numerated for Page<SIZE> {
282 type Distance = u32;
283 type Bound = PagesAmount<SIZE>;
284
285 fn add_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
286 self.0
287 .checked_add(num)
288 .and_then(|sum| sum.enclosed_by(&self.0, &other.0).then_some(Self(sum)))
289 }
290
291 fn sub_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
292 self.0
293 .checked_sub(num)
294 .and_then(|sub| sub.enclosed_by(&self.0, &other.0).then_some(Self(sub)))
295 }
296
297 fn distance(self, other: Self) -> Self::Distance {
298 self.0.abs_diff(other.0)
299 }
300}
301
302impl<const SIZE: u32> LowerBounded for Page<SIZE> {
303 fn min_value() -> Self {
304 Self(0)
305 }
306}
307
308impl<const SIZE: u32> UpperBounded for Page<SIZE> {
309 fn max_value() -> Self {
310 Self::UPPER
311 }
312}
313
314pub type WasmPage = Page<WASM_PAGE_SIZE>;
316pub type GearPage = Page<GEAR_PAGE_SIZE>;
318pub type WasmPagesAmount = PagesAmount<WASM_PAGE_SIZE>;
320pub type GearPagesAmount = PagesAmount<GEAR_PAGE_SIZE>;
322
323impl From<u16> for WasmPagesAmount {
324 fn from(value: u16) -> Self {
325 const { assert!(WASM_PAGE_SIZE <= 0x10_000) };
326 Self(value as u32)
327 }
328}
329
330impl From<u16> for WasmPage {
331 fn from(value: u16) -> Self {
332 const { assert!(WASM_PAGE_SIZE <= 0x10_000) };
333 Self(value as u32)
334 }
335}
336
337impl From<u16> for GearPagesAmount {
338 fn from(value: u16) -> Self {
339 const { assert!(GEAR_PAGE_SIZE <= 0x10_000) };
340 Self(value as u32)
341 }
342}
343
344impl From<u16> for GearPage {
345 fn from(value: u16) -> Self {
346 const { assert!(GEAR_PAGE_SIZE <= 0x10_000) };
347 Self(value as u32)
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354 use alloc::{vec, vec::Vec};
355
356 #[test]
357 fn test_page_inc() {
358 assert_eq!(WasmPage::from(10).inc(), WasmPagesAmount::from(11));
359 assert_eq!(WasmPage::UPPER.inc(), WasmPagesAmount::UPPER);
360 }
361
362 #[test]
363 fn test_page_from_offset() {
364 assert_eq!(WasmPage::from_offset(WASM_PAGE_SIZE - 1), WasmPage::from(0));
365 assert_eq!(WasmPage::from_offset(WASM_PAGE_SIZE), WasmPage::from(1));
366 assert_eq!(WasmPage::from_offset(WASM_PAGE_SIZE + 1), WasmPage::from(1));
367 }
368
369 #[test]
370 fn test_page_offset() {
371 assert_eq!(WasmPage::from(80).offset(), 80 * WASM_PAGE_SIZE);
372 }
373
374 #[test]
375 fn test_page_end_offset() {
376 assert_eq!(
377 WasmPage::from(80).end_offset(),
378 80 * WASM_PAGE_SIZE + (WASM_PAGE_SIZE - 1)
379 );
380 }
381
382 #[test]
383 fn test_page_to_page() {
384 assert_eq!(
385 WasmPage::from(80).to_page::<GEAR_PAGE_SIZE>(),
386 GearPage::from(80 * 4)
387 );
388 }
389
390 #[test]
391 fn test_page_to_iter() {
392 assert_eq!(
393 WasmPage::from(5).to_iter().collect::<Vec<GearPage>>(),
394 vec![
395 GearPage::from(20),
396 GearPage::from(21),
397 GearPage::from(22),
398 GearPage::from(23)
399 ]
400 );
401 }
402
403 #[test]
404 fn test_pages_amount_add() {
405 let a = WasmPagesAmount::from(10);
406 let b = WasmPagesAmount::from(20);
407 assert_eq!(a.add(b), Some(WasmPagesAmount::from(30)));
408 assert_eq!(a.add(WasmPagesAmount::UPPER), None);
409 }
410
411 #[test]
412 fn test_pages_amount_to_page_number() {
413 assert_eq!(
414 WasmPagesAmount::from(10).to_page_number(),
415 Some(WasmPage::from(10))
416 );
417 assert_eq!(WasmPagesAmount::UPPER.to_page_number(), None);
418 }
419
420 #[test]
421 fn test_pages_amount_to_pages_amount() {
422 assert_eq!(
423 WasmPagesAmount::from(10).to_pages_amount::<GEAR_PAGE_SIZE>(),
424 GearPagesAmount::from(40)
425 );
426 assert_eq!(
427 GearPagesAmount::from(40).to_pages_amount::<WASM_PAGE_SIZE>(),
428 WasmPagesAmount::from(10)
429 );
430 }
431
432 #[test]
433 fn test_pages_amount_offset() {
434 assert_eq!(
435 WasmPagesAmount::from(10).offset(),
436 10 * WASM_PAGE_SIZE as u64
437 );
438 assert_eq!(WasmPagesAmount::UPPER.offset(), u32::MAX as u64 + 1);
439 }
440}
441
442#[cfg(test)]
443mod property_tests {
444 use super::*;
445 use numerated::mock::{self, IntervalAction};
446 use proptest::{
447 prelude::{Arbitrary, any},
448 proptest,
449 strategy::{BoxedStrategy, Strategy},
450 test_runner::Config as ProptestConfig,
451 };
452
453 impl<const S: u32> Arbitrary for Page<S> {
454 type Parameters = ();
455 type Strategy = BoxedStrategy<Page<S>>;
456
457 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
458 (0..=Page::<S>::UPPER.0).prop_map(Page).boxed()
459 }
460 }
461
462 proptest! {
463 #![proptest_config(ProptestConfig::with_cases(1024))]
464
465 #[test]
466 fn gear_page_numerated(x in any::<GearPage>(), y in any::<GearPage>()) {
467 mock::test_numerated(x, y);
468 }
469
470 #[test]
471 fn gear_page_interval(action in any::<IntervalAction<GearPage>>()) {
472 mock::test_interval(action);
473 }
474
475 #[test]
476 fn wasm_page_numerated(x in any::<WasmPage>(), y in any::<WasmPage>()) {
477 mock::test_numerated(x, y);
478 }
479
480 #[test]
481 fn wasm_page_interval(action in any::<IntervalAction<WasmPage>>()) {
482 mock::test_interval(action);
483 }
484 }
485
486 proptest! {
487 #![proptest_config(ProptestConfig::with_cases(64))]
488
489 #[test]
490 fn gear_page_tree((initial, actions) in mock::tree_actions::<GearPage>(0..128, 2..8)) {
491 mock::test_tree(initial, actions);
492 }
493
494 #[test]
495 fn wasm_page_tree((initial, actions) in mock::tree_actions::<WasmPage>(0..128, 10..20)) {
496 mock::test_tree(initial, actions);
497 }
498 }
499}