Apply rustfmt to entire workspace
Runs cargo fmt --all; all 573 tests still passing, clippy still clean. No logic changes — formatting only. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
3d524e2d63
commit
1c107fe58a
@@ -20,23 +20,27 @@ use tempfile::NamedTempFile;
|
||||
// Layout constants (must match CLAWONION_SPEC.md §2 + §3)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
const HEADER_SIZE: usize = 128;
|
||||
const REV_ENTRY_SIZE: usize = 112;
|
||||
const HEADER_SIZE: usize = 128;
|
||||
const REV_ENTRY_SIZE: usize = 112;
|
||||
const BRANCH_ENTRY_SIZE: usize = 40;
|
||||
const PAGE_ENTRY_SIZE: usize = 32;
|
||||
const PAGE_SIZE: u32 = 4096;
|
||||
const PAGE_ENTRY_SIZE: usize = 32;
|
||||
const PAGE_SIZE: u32 = 4096;
|
||||
|
||||
const INDEX_OFFSET: usize = HEADER_SIZE; // 128
|
||||
const BRANCH_OFFSET: usize = INDEX_OFFSET + REV_ENTRY_SIZE; // 240
|
||||
const PT_OFFSET: usize = BRANCH_OFFSET + BRANCH_ENTRY_SIZE; // 280
|
||||
const PD_OFFSET: usize = PT_OFFSET + PAGE_ENTRY_SIZE; // 312
|
||||
const INDEX_OFFSET: usize = HEADER_SIZE; // 128
|
||||
const BRANCH_OFFSET: usize = INDEX_OFFSET + REV_ENTRY_SIZE; // 240
|
||||
const PT_OFFSET: usize = BRANCH_OFFSET + BRANCH_ENTRY_SIZE; // 280
|
||||
const PD_OFFSET: usize = PT_OFFSET + PAGE_ENTRY_SIZE; // 312
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn le64(v: u64) -> [u8; 8] { v.to_le_bytes() }
|
||||
fn le32(v: u32) -> [u8; 4] { v.to_le_bytes() }
|
||||
fn le64(v: u64) -> [u8; 8] {
|
||||
v.to_le_bytes()
|
||||
}
|
||||
fn le32(v: u32) -> [u8; 4] {
|
||||
v.to_le_bytes()
|
||||
}
|
||||
|
||||
/// Build the expected BLAKE3 for one 4096-byte page of 0xAB at h5_offset = 0.
|
||||
///
|
||||
@@ -82,7 +86,8 @@ fn golden_minimal_onion_byte_layout() {
|
||||
let ann_heap: &[u8] = &[0x00, 0x04, 0x00, 0x00, 0x00, b'm', b'a', b'i', b'n'];
|
||||
let expected_total = PD_OFFSET + PAGE_SIZE as usize + ann_heap.len(); // 4417
|
||||
assert_eq!(
|
||||
bytes.len(), expected_total,
|
||||
bytes.len(),
|
||||
expected_total,
|
||||
"total file size mismatch (got {}, expected {expected_total})",
|
||||
bytes.len()
|
||||
);
|
||||
@@ -127,10 +132,17 @@ fn golden_minimal_onion_byte_layout() {
|
||||
// [64..72] created_at — f64, time-dependent; skip exact value check,
|
||||
// but assert it's non-zero (a real Unix timestamp).
|
||||
let created_at = f64::from_le_bytes(bytes[64..72].try_into().unwrap());
|
||||
assert!(created_at > 0.0, "created_at should be a positive Unix timestamp");
|
||||
assert!(
|
||||
created_at > 0.0,
|
||||
"created_at should be a positive Unix timestamp"
|
||||
);
|
||||
|
||||
// [72..128] reserved — 56 bytes, must all be zero
|
||||
assert_eq!(&bytes[72..128], &[0u8; 56], "reserved header bytes must be zero");
|
||||
assert_eq!(
|
||||
&bytes[72..128],
|
||||
&[0u8; 56],
|
||||
"reserved header bytes must be zero"
|
||||
);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// §3.2 RevisionEntry — 112 bytes @ offset 128
|
||||
@@ -139,45 +151,69 @@ fn golden_minimal_onion_byte_layout() {
|
||||
let re = INDEX_OFFSET; // 128
|
||||
|
||||
// [re+0..re+8] revision = 0
|
||||
assert_eq!(&bytes[re..re+8], &le64(0), "revision number");
|
||||
assert_eq!(&bytes[re..re + 8], &le64(0), "revision number");
|
||||
|
||||
// [re+8..re+12] branch_id = 0 (main)
|
||||
assert_eq!(&bytes[re+8..re+12], &le32(0), "branch_id");
|
||||
assert_eq!(&bytes[re + 8..re + 12], &le32(0), "branch_id");
|
||||
|
||||
// [re+12..re+16] _pad_bi — must be zero
|
||||
assert_eq!(&bytes[re+12..re+16], &[0u8; 4], "_pad_bi must be zero");
|
||||
assert_eq!(&bytes[re + 12..re + 16], &[0u8; 4], "_pad_bi must be zero");
|
||||
|
||||
// [re+16..re+24] parent_rev = u64::MAX (root has no parent)
|
||||
assert_eq!(&bytes[re+16..re+24], &le64(u64::MAX), "parent_rev (NO_PARENT sentinel)");
|
||||
assert_eq!(
|
||||
&bytes[re + 16..re + 24],
|
||||
&le64(u64::MAX),
|
||||
"parent_rev (NO_PARENT sentinel)"
|
||||
);
|
||||
|
||||
// [re+24..re+28] page_count = 1
|
||||
assert_eq!(&bytes[re+24..re+28], &le32(1), "page_count");
|
||||
assert_eq!(&bytes[re + 24..re + 28], &le32(1), "page_count");
|
||||
|
||||
// [re+28..re+32] _pad_pc — must be zero
|
||||
assert_eq!(&bytes[re+28..re+32], &[0u8; 4], "_pad_pc must be zero");
|
||||
assert_eq!(&bytes[re + 28..re + 32], &[0u8; 4], "_pad_pc must be zero");
|
||||
|
||||
// [re+32..re+40] page_table_off = 280 (PT_OFFSET)
|
||||
assert_eq!(&bytes[re+32..re+40], &le64(PT_OFFSET as u64), "page_table_off");
|
||||
assert_eq!(
|
||||
&bytes[re + 32..re + 40],
|
||||
&le64(PT_OFFSET as u64),
|
||||
"page_table_off"
|
||||
);
|
||||
|
||||
// [re+40..re+48] timestamp — f64, time-dependent; assert positive
|
||||
let ts = f64::from_le_bytes(bytes[re+40..re+48].try_into().unwrap());
|
||||
let ts = f64::from_le_bytes(bytes[re + 40..re + 48].try_into().unwrap());
|
||||
assert!(ts > 0.0, "revision timestamp should be positive");
|
||||
|
||||
// [re+48..re+80] blake3 — verify against spec §5 algorithm
|
||||
let computed_blake3 = expected_blake3(&page_bytes);
|
||||
assert_eq!(&bytes[re+48..re+80], &computed_blake3, "BLAKE3 hash mismatch");
|
||||
assert_eq!(
|
||||
&bytes[re + 48..re + 80],
|
||||
&computed_blake3,
|
||||
"BLAKE3 hash mismatch"
|
||||
);
|
||||
|
||||
// [re+80..re+96] session_uuid — 16 bytes UUIDv7; must be non-zero
|
||||
assert_ne!(&bytes[re+80..re+96], &[0u8; 16], "session_uuid must not be all zeros");
|
||||
assert_ne!(
|
||||
&bytes[re + 80..re + 96],
|
||||
&[0u8; 16],
|
||||
"session_uuid must not be all zeros"
|
||||
);
|
||||
|
||||
// [re+96..re+104] annotation_off = 0 (no annotation)
|
||||
assert_eq!(&bytes[re+96..re+104], &le64(0), "annotation_off must be 0 (no annotation)");
|
||||
assert_eq!(
|
||||
&bytes[re + 96..re + 104],
|
||||
&le64(0),
|
||||
"annotation_off must be 0 (no annotation)"
|
||||
);
|
||||
|
||||
// [re+104] flags = 0 (not a snapshot)
|
||||
assert_eq!(bytes[re+104], 0x00, "revision flags");
|
||||
assert_eq!(bytes[re + 104], 0x00, "revision flags");
|
||||
|
||||
// [re+105..re+112] _pad_flags — must be zero
|
||||
assert_eq!(&bytes[re+105..re+112], &[0u8; 7], "_pad_flags must be zero");
|
||||
assert_eq!(
|
||||
&bytes[re + 105..re + 112],
|
||||
&[0u8; 7],
|
||||
"_pad_flags must be zero"
|
||||
);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// §3.3 BranchEntry — 40 bytes @ offset 240
|
||||
@@ -186,22 +222,34 @@ fn golden_minimal_onion_byte_layout() {
|
||||
let be = BRANCH_OFFSET; // 240
|
||||
|
||||
// [be+0..be+4] id = 0 (main)
|
||||
assert_eq!(&bytes[be..be+4], &le32(0), "branch id");
|
||||
assert_eq!(&bytes[be..be + 4], &le32(0), "branch id");
|
||||
|
||||
// [be+4..be+8] _pad_id — must be zero
|
||||
assert_eq!(&bytes[be+4..be+8], &[0u8; 4], "_pad_id must be zero");
|
||||
assert_eq!(&bytes[be + 4..be + 8], &[0u8; 4], "_pad_id must be zero");
|
||||
|
||||
// [be+8..be+16] name_off = 1 (heap-relative; first string after reserved null)
|
||||
assert_eq!(&bytes[be+8..be+16], &le64(1), "name_off for main branch");
|
||||
assert_eq!(
|
||||
&bytes[be + 8..be + 16],
|
||||
&le64(1),
|
||||
"name_off for main branch"
|
||||
);
|
||||
|
||||
// [be+16..be+24] head_rev = 0 (updated after commit)
|
||||
assert_eq!(&bytes[be+16..be+24], &le64(0), "head_rev after first commit");
|
||||
assert_eq!(
|
||||
&bytes[be + 16..be + 24],
|
||||
&le64(0),
|
||||
"head_rev after first commit"
|
||||
);
|
||||
|
||||
// [be+24..be+32] fork_rev = u64::MAX (main is not forked)
|
||||
assert_eq!(&bytes[be+24..be+32], &le64(u64::MAX), "fork_rev (NO_PARENT for main)");
|
||||
assert_eq!(
|
||||
&bytes[be + 24..be + 32],
|
||||
&le64(u64::MAX),
|
||||
"fork_rev (NO_PARENT for main)"
|
||||
);
|
||||
|
||||
// [be+32..be+40] created_at — f64, time-dependent; assert positive
|
||||
let branch_ts = f64::from_le_bytes(bytes[be+32..be+40].try_into().unwrap());
|
||||
let branch_ts = f64::from_le_bytes(bytes[be + 32..be + 40].try_into().unwrap());
|
||||
assert!(branch_ts > 0.0, "branch created_at should be positive");
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
@@ -211,36 +259,51 @@ fn golden_minimal_onion_byte_layout() {
|
||||
let pt = PT_OFFSET; // 280
|
||||
|
||||
// [pt+0..pt+8] h5_offset = 0
|
||||
assert_eq!(&bytes[pt..pt+8], &le64(0), "h5_offset");
|
||||
assert_eq!(&bytes[pt..pt + 8], &le64(0), "h5_offset");
|
||||
|
||||
// [pt+8..pt+16] data_offset = 312 (PD_OFFSET, absolute file position)
|
||||
assert_eq!(&bytes[pt+8..pt+16], &le64(PD_OFFSET as u64), "data_offset (absolute)");
|
||||
assert_eq!(
|
||||
&bytes[pt + 8..pt + 16],
|
||||
&le64(PD_OFFSET as u64),
|
||||
"data_offset (absolute)"
|
||||
);
|
||||
|
||||
// [pt+16..pt+20] orig_size = 4096
|
||||
assert_eq!(&bytes[pt+16..pt+20], &le32(4096), "orig_size");
|
||||
assert_eq!(&bytes[pt + 16..pt + 20], &le32(4096), "orig_size");
|
||||
|
||||
// [pt+20..pt+24] data_size = 4096 (codec=None → no compression)
|
||||
assert_eq!(&bytes[pt+20..pt+24], &le32(4096), "data_size (codec=None, uncompressed)");
|
||||
assert_eq!(
|
||||
&bytes[pt + 20..pt + 24],
|
||||
&le32(4096),
|
||||
"data_size (codec=None, uncompressed)"
|
||||
);
|
||||
|
||||
// [pt+24] codec = 0 (None)
|
||||
assert_eq!(bytes[pt+24], 0x00, "codec = None (0)");
|
||||
assert_eq!(bytes[pt + 24], 0x00, "codec = None (0)");
|
||||
|
||||
// [pt+25..pt+32] _pad — must be zero
|
||||
assert_eq!(&bytes[pt+25..pt+32], &[0u8; 7], "_pad must be zero");
|
||||
assert_eq!(&bytes[pt + 25..pt + 32], &[0u8; 7], "_pad must be zero");
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// PageData — 4096 bytes @ offset 312
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
assert_eq!(&bytes[PD_OFFSET..PD_OFFSET + 4096], page_bytes.as_slice(),
|
||||
"page data must equal the uncompressed original bytes");
|
||||
assert_eq!(
|
||||
&bytes[PD_OFFSET..PD_OFFSET + 4096],
|
||||
page_bytes.as_slice(),
|
||||
"page data must equal the uncompressed original bytes"
|
||||
);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// §3.5 AnnotationHeap — tail of file
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Last 9 bytes: [0x00 reserved] [u32 LE length=4] ["main"]
|
||||
assert_eq!(&bytes[bytes.len()-9..], ann_heap, "annotation heap content");
|
||||
assert_eq!(
|
||||
&bytes[bytes.len() - 9..],
|
||||
ann_heap,
|
||||
"annotation heap content"
|
||||
);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// §7 Reconstruction — verify round-trip
|
||||
@@ -270,10 +333,10 @@ fn golden_minimal_onion_byte_layout() {
|
||||
|
||||
#[test]
|
||||
fn byte_exact_format_freeze() {
|
||||
let re = INDEX_OFFSET; // 128 — RevisionEntry start
|
||||
let re = INDEX_OFFSET; // 128 — RevisionEntry start
|
||||
let be = BRANCH_OFFSET; // 240 — BranchEntry start
|
||||
let pt = PT_OFFSET; // 280 — PageTableEntry start
|
||||
let pd = PD_OFFSET; // 312 — PageData start
|
||||
let pt = PT_OFFSET; // 280 — PageTableEntry start
|
||||
let pd = PD_OFFSET; // 312 — PageData start
|
||||
|
||||
// ── Build the actual serialised file ────────────────────────────────────
|
||||
|
||||
@@ -298,71 +361,75 @@ fn byte_exact_format_freeze() {
|
||||
let mut expected = vec![0u8; total];
|
||||
|
||||
// §3.1 OnionHeader (128 bytes)
|
||||
expected[0..9].copy_from_slice(b"CLAWONION"); // magic
|
||||
expected[9] = 0x01; // format_version
|
||||
expected[0..9].copy_from_slice(b"CLAWONION"); // magic
|
||||
expected[9] = 0x01; // format_version
|
||||
// [10..16] = 0 (_pad_align)
|
||||
expected[16..24].copy_from_slice(&le64(0x07)); // feature_flags
|
||||
expected[24..28].copy_from_slice(&le32(4096)); // page_size
|
||||
// [28..32] = 0 (_pad_ps)
|
||||
expected[32..40].copy_from_slice(&le64(1)); // revision_count = 1
|
||||
expected[40..44].copy_from_slice(&le32(1)); // branch_count = 1
|
||||
expected[32..40].copy_from_slice(&le64(1)); // revision_count = 1
|
||||
expected[40..44].copy_from_slice(&le32(1)); // branch_count = 1
|
||||
// [44..48] = 0 (_pad_bc)
|
||||
expected[48..56].copy_from_slice(&le64(128)); // index_offset
|
||||
expected[56..64].copy_from_slice(&le64(240)); // branch_offset
|
||||
expected[48..56].copy_from_slice(&le64(128)); // index_offset
|
||||
expected[56..64].copy_from_slice(&le64(240)); // branch_offset
|
||||
// [64..72] = 0 (created_at — zeroed, dynamic)
|
||||
// [72..128] = 0 (reserved)
|
||||
|
||||
// §3.2 RevisionEntry (112 bytes @ 128)
|
||||
expected[re..re+8] .copy_from_slice(&le64(0)); // revision = 0
|
||||
expected[re+8..re+12].copy_from_slice(&le32(0)); // branch_id = 0
|
||||
expected[re..re + 8].copy_from_slice(&le64(0)); // revision = 0
|
||||
expected[re + 8..re + 12].copy_from_slice(&le32(0)); // branch_id = 0
|
||||
// [re+12..re+16] = 0 (_pad_bi)
|
||||
expected[re+16..re+24].copy_from_slice(&le64(u64::MAX)); // parent_rev = NO_PARENT
|
||||
expected[re+24..re+28].copy_from_slice(&le32(1)); // page_count = 1
|
||||
expected[re + 16..re + 24].copy_from_slice(&le64(u64::MAX)); // parent_rev = NO_PARENT
|
||||
expected[re + 24..re + 28].copy_from_slice(&le32(1)); // page_count = 1
|
||||
// [re+28..re+32] = 0 (_pad_pc)
|
||||
expected[re+32..re+40].copy_from_slice(&le64(pt as u64)); // page_table_off
|
||||
expected[re + 32..re + 40].copy_from_slice(&le64(pt as u64)); // page_table_off
|
||||
// [re+40..re+48] = 0 (timestamp — zeroed, dynamic)
|
||||
expected[re+48..re+80].copy_from_slice(&expected_blake3(&page_bytes)); // blake3
|
||||
expected[re + 48..re + 80].copy_from_slice(&expected_blake3(&page_bytes)); // blake3
|
||||
// [re+80..re+96] = 0 (session_uuid — zeroed, dynamic)
|
||||
// [re+96..re+104] = 0 (annotation_off = 0, no annotation)
|
||||
// [re+104] = 0 (flags = 0, not a snapshot)
|
||||
// [re+105..re+112] = 0 (_pad_flags)
|
||||
|
||||
// §3.3 BranchEntry (40 bytes @ 240)
|
||||
expected[be..be+4] .copy_from_slice(&le32(0)); // id = 0 (main)
|
||||
expected[be..be + 4].copy_from_slice(&le32(0)); // id = 0 (main)
|
||||
// [be+4..be+8] = 0 (_pad_id)
|
||||
expected[be+8..be+16].copy_from_slice(&le64(1)); // name_off = 1 (heap offset of "main")
|
||||
expected[be+16..be+24].copy_from_slice(&le64(0)); // head_rev = 0
|
||||
expected[be+24..be+32].copy_from_slice(&le64(u64::MAX)); // fork_rev = NO_PARENT
|
||||
expected[be + 8..be + 16].copy_from_slice(&le64(1)); // name_off = 1 (heap offset of "main")
|
||||
expected[be + 16..be + 24].copy_from_slice(&le64(0)); // head_rev = 0
|
||||
expected[be + 24..be + 32].copy_from_slice(&le64(u64::MAX)); // fork_rev = NO_PARENT
|
||||
// [be+32..be+40] = 0 (created_at — zeroed, dynamic)
|
||||
|
||||
// §3.4 PageTableEntry (32 bytes @ 280)
|
||||
expected[pt..pt+8] .copy_from_slice(&le64(0)); // h5_offset = 0
|
||||
expected[pt+8..pt+16].copy_from_slice(&le64(pd as u64)); // data_offset
|
||||
expected[pt+16..pt+20].copy_from_slice(&le32(4096)); // orig_size
|
||||
expected[pt+20..pt+24].copy_from_slice(&le32(4096)); // data_size (no compression)
|
||||
expected[pt..pt + 8].copy_from_slice(&le64(0)); // h5_offset = 0
|
||||
expected[pt + 8..pt + 16].copy_from_slice(&le64(pd as u64)); // data_offset
|
||||
expected[pt + 16..pt + 20].copy_from_slice(&le32(4096)); // orig_size
|
||||
expected[pt + 20..pt + 24].copy_from_slice(&le32(4096)); // data_size (no compression)
|
||||
// [pt+24] = 0 (codec = None)
|
||||
// [pt+25..pt+32] = 0 (_pad)
|
||||
|
||||
// PageData (4096 bytes @ 312)
|
||||
expected[pd..pd+4096].fill(0xAB);
|
||||
expected[pd..pd + 4096].fill(0xAB);
|
||||
|
||||
// §3.5 AnnotationHeap (9 bytes at end)
|
||||
expected[pd+4096..].copy_from_slice(ann_heap);
|
||||
expected[pd + 4096..].copy_from_slice(ann_heap);
|
||||
|
||||
// ── Zero dynamic fields in both buffers ──────────────────────────────────
|
||||
// OnionHeader.created_at
|
||||
actual[64..72].fill(0);
|
||||
// RevisionEntry.timestamp
|
||||
actual[re+40..re+48].fill(0);
|
||||
actual[re + 40..re + 48].fill(0);
|
||||
// RevisionEntry.session_uuid
|
||||
actual[re+80..re+96].fill(0);
|
||||
actual[re + 80..re + 96].fill(0);
|
||||
// BranchEntry.created_at
|
||||
actual[be+32..be+40].fill(0);
|
||||
actual[be + 32..be + 40].fill(0);
|
||||
|
||||
// ── Compare byte-for-byte ────────────────────────────────────────────────
|
||||
assert_eq!(actual.len(), expected.len(),
|
||||
assert_eq!(
|
||||
actual.len(),
|
||||
expected.len(),
|
||||
"total file size mismatch: actual={} expected={}",
|
||||
actual.len(), expected.len());
|
||||
actual.len(),
|
||||
expected.len()
|
||||
);
|
||||
|
||||
// Find the first differing byte for a useful failure message.
|
||||
if actual != expected {
|
||||
@@ -387,13 +454,29 @@ fn byte_exact_format_freeze() {
|
||||
|
||||
#[test]
|
||||
fn struct_sizes_match_spec() {
|
||||
use clawhdf5_onion::format::{BranchEntry, OnionHeader, PageTableEntry, RevisionEntry};
|
||||
use std::mem::size_of;
|
||||
use clawhdf5_onion::format::{OnionHeader, RevisionEntry, BranchEntry, PageTableEntry};
|
||||
|
||||
assert_eq!(size_of::<OnionHeader>(), 128, "OnionHeader must be 128 bytes (§3.1)");
|
||||
assert_eq!(size_of::<RevisionEntry>(), 112, "RevisionEntry must be 112 bytes (§3.2)");
|
||||
assert_eq!(size_of::<BranchEntry>(), 40, "BranchEntry must be 40 bytes (§3.3)");
|
||||
assert_eq!(size_of::<PageTableEntry>(), 32, "PageTableEntry must be 32 bytes (§3.4)");
|
||||
assert_eq!(
|
||||
size_of::<OnionHeader>(),
|
||||
128,
|
||||
"OnionHeader must be 128 bytes (§3.1)"
|
||||
);
|
||||
assert_eq!(
|
||||
size_of::<RevisionEntry>(),
|
||||
112,
|
||||
"RevisionEntry must be 112 bytes (§3.2)"
|
||||
);
|
||||
assert_eq!(
|
||||
size_of::<BranchEntry>(),
|
||||
40,
|
||||
"BranchEntry must be 40 bytes (§3.3)"
|
||||
);
|
||||
assert_eq!(
|
||||
size_of::<PageTableEntry>(),
|
||||
32,
|
||||
"PageTableEntry must be 32 bytes (§3.4)"
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user