Skip to main content

vyre_primitives/decode/
base64.rs

1//! Base64 decode primitive body.
2
3use std::error::Error as StdError;
4use std::fmt;
5use std::sync::{Arc, OnceLock};
6
7use vyre_foundation::ir::model::expr::{GeneratorRef, Ident};
8use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
9
10/// Canonical op id for base64 decode.
11pub const BASE64_DECODE_OP_ID: &str = "vyre-primitives::decode::base64_decode";
12/// Base64 padding byte.
13pub const PAD: u32 = b'=' as u32;
14/// Invalid table entry sentinel.
15pub const INVALID: u32 = 0xFF;
16/// Number of words in the standard decode lookup table.
17pub const BASE64_DECODE_TABLE_WORDS: u32 = 256;
18/// Canonical base64 decode workgroup size.
19pub const BASE64_WORKGROUP_SIZE: [u32; 3] = [64, 1, 1];
20
21static STANDARD_DECODE_TABLE: OnceLock<[u32; 256]> = OnceLock::new();
22
23/// CPU-reference base64 decode failure.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum Base64DecodeReferenceError {
26    /// Base64 input must be padded to full 4-byte quads.
27    InvalidLength {
28        /// Input byte length.
29        len: usize,
30    },
31    /// Decoded fixed-capacity word count overflowed host `usize`.
32    CapacityOverflow {
33        /// Number of four-byte quads.
34        blocks: usize,
35    },
36    /// Decoded fixed-capacity word count cannot fit the public u32 length ABI.
37    DecodedLengthOverflow {
38        /// Decoded capacity in u32 slots.
39        decoded_words: usize,
40    },
41    /// Host output staging reservation failed.
42    Allocation {
43        /// Requested u32 slots.
44        requested: usize,
45        /// Allocator detail.
46        source: String,
47    },
48}
49
50impl fmt::Display for Base64DecodeReferenceError {
51    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            Self::InvalidLength { len } => write!(
54                formatter,
55                "base64 reference input length {len} is not a multiple of 4. Fix: pad with '=' or reject the payload before decode."
56            ),
57            Self::CapacityOverflow { blocks } => write!(
58                formatter,
59                "base64 reference decoded capacity overflowed for {blocks} input quads. Fix: shard the payload before CPU/GPU parity decode."
60            ),
61            Self::DecodedLengthOverflow { decoded_words } => write!(
62                formatter,
63                "base64 reference decoded capacity {decoded_words} cannot fit u32. Fix: shard the payload before dispatch."
64            ),
65            Self::Allocation { requested, source } => write!(
66                formatter,
67                "base64 reference could not reserve {requested} decoded u32 slots: {source}. Fix: shard the payload before CPU/GPU parity decode."
68            ),
69        }
70    }
71}
72
73impl StdError for Base64DecodeReferenceError {}
74
75fn blocks_for_len(input_len: u32) -> u32 {
76    input_len / 4
77}
78
79/// Return the standard base64 decode table (RFC 4648) by value.
80#[must_use]
81pub fn standard_decode_table() -> [u32; 256] {
82    *standard_decode_table_ref()
83}
84
85/// Process-wide standard base64 decode table (RFC 4648).
86///
87/// The table is immutable after construction. Dispatch setup and CPU oracles
88/// should use this reference when they do not need an owned copy.
89#[must_use]
90pub fn standard_decode_table_ref() -> &'static [u32; 256] {
91    STANDARD_DECODE_TABLE.get_or_init(build_standard_decode_table)
92}
93
94fn build_standard_decode_table() -> [u32; 256] {
95    let mut table = [INVALID; 256];
96    for byte in b'A'..=b'Z' {
97        table[usize::from(byte)] = u32::from(byte - b'A');
98    }
99    for byte in b'a'..=b'z' {
100        table[usize::from(byte)] = u32::from(byte - b'a' + 26);
101    }
102    for byte in b'0'..=b'9' {
103        table[usize::from(byte)] = u32::from(byte - b'0' + 52);
104    }
105    table[usize::from(b'+')] = 62;
106    table[usize::from(b'/')] = 63;
107    table[usize::from(b'=')] = 0;
108    table
109}
110
111/// Decoded capacity for a padded base64 input.
112#[must_use]
113pub fn decoded_capacity(input_len: u32) -> u32 {
114    blocks_for_len(input_len) * 3
115}
116
117/// CPU oracle for the standard RFC 4648 decode table used by the primitive.
118///
119/// The output mirrors the GPU contract: one decoded byte per `u32` slot, with
120/// padded bytes left as zero in the fixed decoded capacity. Invalid input
121/// characters are clamped to zero, matching [`base64_decode_body`].
122#[must_use]
123#[cfg(any(test, feature = "cpu-parity"))]
124pub fn decode_standard_packed_reference(input: &[u8]) -> (Vec<u32>, u32) {
125    match try_decode_standard_packed_reference(input) {
126        Ok(decoded) => decoded,
127        // A decode oracle that returns empty on failure makes the GPU-vs-CPU
128        // assertion pass on empty==empty, silently masking a divergence
129        // (Law 10 / Law 6). Fail loud; callers use the try_ variant.
130        Err(error) => panic!("vyre-primitives base64 decode reference failed: {error}"),
131    }
132}
133
134/// CPU oracle for the standard RFC 4648 decode table into caller-owned storage.
135///
136/// Returns the decoded logical byte length while `out` holds the fixed-capacity
137/// GPU ABI representation: one decoded byte per `u32` slot, including zeroed
138/// padding slots.
139#[cfg(any(test, feature = "cpu-parity"))]
140pub fn decode_standard_packed_reference_into(input: &[u8], out: &mut Vec<u32>) -> u32 {
141    match try_decode_standard_packed_reference_into(input, out) {
142        Ok(decoded_len) => decoded_len,
143        // Clearing and returning 0 on failure silently masks a parity
144        // divergence (Law 10 / Law 6). Fail loud; callers use the try_ variant.
145        Err(error) => panic!("vyre-primitives base64 decode reference failed: {error}"),
146    }
147}
148
149/// Fallible CPU oracle for the standard RFC 4648 decode table.
150///
151/// This variant is suitable for fuzzing and hostile-input parity tests because
152/// malformed lengths and output staging failures are reported as typed errors
153/// instead of panics.
154#[cfg(any(test, feature = "cpu-parity"))]
155pub fn try_decode_standard_packed_reference(
156    input: &[u8],
157) -> Result<(Vec<u32>, u32), Base64DecodeReferenceError> {
158    let mut out = Vec::new();
159    let decoded_len = try_decode_standard_packed_reference_into(input, &mut out)?;
160    Ok((out, decoded_len))
161}
162
163/// Fallible CPU oracle for the standard RFC 4648 decode table into caller-owned storage.
164///
165/// On validation or reservation failure, the caller-owned output buffer is left
166/// unchanged so fuzzers can assert transactional decode behavior.
167#[cfg(any(test, feature = "cpu-parity"))]
168pub fn try_decode_standard_packed_reference_into(
169    input: &[u8],
170    out: &mut Vec<u32>,
171) -> Result<u32, Base64DecodeReferenceError> {
172    if input.len() % 4 != 0 {
173        return Err(Base64DecodeReferenceError::InvalidLength { len: input.len() });
174    }
175    let table = standard_decode_table_ref();
176    let blocks = input.len() / 4;
177    let decoded_words = blocks
178        .checked_mul(3)
179        .ok_or(Base64DecodeReferenceError::CapacityOverflow { blocks })?;
180    vyre_foundation::allocation::reserve_exact_cleared(out, decoded_words).map_err(|source| {
181        Base64DecodeReferenceError::Allocation {
182            requested: decoded_words,
183            source: source.to_string(),
184        }
185    })?;
186    out.resize(decoded_words, 0);
187    for block in 0..blocks {
188        let base = block * 4;
189        let vals = [
190            table[usize::from(input[base])],
191            table[usize::from(input[base + 1])],
192            table[usize::from(input[base + 2])],
193            table[usize::from(input[base + 3])],
194        ]
195        .map(|value| if value == INVALID { 0 } else { value });
196        let out_base = block * 3;
197        out[out_base] = (vals[0] << 2) | (vals[1] >> 4);
198        if input[base + 2] != b'=' {
199            out[out_base + 1] = ((vals[1] & 0x0F) << 4) | (vals[2] >> 2);
200        }
201        if input[base + 3] != b'=' {
202            out[out_base + 2] = ((vals[2] & 0x03) << 6) | vals[3];
203        }
204    }
205    let mut decoded_len = u32::try_from(out.len()).map_err(|_| {
206        Base64DecodeReferenceError::DecodedLengthOverflow {
207            decoded_words: out.len(),
208        }
209    })?;
210    if input.len() >= 2 {
211        if input[input.len() - 1] == b'=' {
212            decoded_len = decoded_len.saturating_sub(1);
213        }
214        if input[input.len() - 2] == b'=' {
215            decoded_len = decoded_len.saturating_sub(1);
216        }
217    }
218    Ok(decoded_len)
219}
220
221fn clamp_lookup(name: &str, table: &str) -> Vec<Node> {
222    let raw = format!("{name}_raw");
223    let value = format!("{name}_v");
224    vec![
225        // Masked 256-table lookup via the canonical ONE-PLACE helper: a >255 input
226        // element (the input buffer is U32 and unvalidated) folds to `c & 0xFF`
227        // instead of reading past the 256-entry decode table (a raw OOB read is UB
228        // on CUDA). Transparent for valid bytes; an out-of-range value lands on a
229        // non-base64 slot (INVALID → clamped to 0 below). Using the shared helper
230        // is what keeps this mask from being forgotten again.
231        Node::let_bind(
232            raw.as_str(),
233            crate::ir_safe::byte_table_lookup(table, Expr::var(name)),
234        ),
235        Node::let_bind(
236            value.as_str(),
237            Expr::select(
238                Expr::eq(Expr::var(raw.as_str()), Expr::u32(INVALID)),
239                Expr::u32(0),
240                Expr::var(raw.as_str()),
241            ),
242        ),
243    ]
244}
245
246/// Build the reusable base64 decode body.
247#[must_use]
248pub fn base64_decode_body(
249    input: &str,
250    table: &str,
251    output: &str,
252    decoded_len_buffer: &str,
253    input_len: u32,
254) -> Vec<Node> {
255    if input_len % 4 != 0 {
256        return vec![Node::trap(
257            Expr::u32(input_len),
258            "Fix: base64_decode requires input_len to be a multiple of 4; pad with '=' or reject the truncated payload upstream",
259        )];
260    }
261    let decoded_len = decoded_capacity(input_len);
262    let mut body = vec![Node::let_bind("j", Expr::InvocationId { axis: 0 })];
263    if input_len >= 2 {
264        body.push(Node::if_then(
265            Expr::eq(Expr::var("j"), Expr::u32(0)),
266            vec![
267                Node::let_bind(
268                    "tail_pad_1",
269                    Expr::select(
270                        Expr::eq(Expr::load(input, Expr::u32(input_len - 1)), Expr::u32(PAD)),
271                        Expr::u32(1),
272                        Expr::u32(0),
273                    ),
274                ),
275                Node::let_bind(
276                    "tail_pad_2",
277                    Expr::select(
278                        Expr::eq(Expr::load(input, Expr::u32(input_len - 2)), Expr::u32(PAD)),
279                        Expr::u32(1),
280                        Expr::u32(0),
281                    ),
282                ),
283                Node::store(
284                    decoded_len_buffer,
285                    Expr::u32(0),
286                    Expr::sub(
287                        Expr::sub(Expr::u32(decoded_len), Expr::var("tail_pad_1")),
288                        Expr::var("tail_pad_2"),
289                    ),
290                ),
291            ],
292        ));
293    } else {
294        body.push(Node::if_then(
295            Expr::eq(Expr::var("j"), Expr::u32(0)),
296            vec![Node::store(decoded_len_buffer, Expr::u32(0), Expr::u32(0))],
297        ));
298    }
299    body.push(Node::if_then(
300        Expr::lt(Expr::var("j"), Expr::u32(decoded_len)),
301        {
302            let mut per_byte = vec![
303                Node::let_bind("quad", Expr::div(Expr::var("j"), Expr::u32(3))),
304                Node::let_bind("in_base", Expr::mul(Expr::var("quad"), Expr::u32(4))),
305                Node::let_bind(
306                    "pos",
307                    Expr::sub(Expr::var("j"), Expr::mul(Expr::var("quad"), Expr::u32(3))),
308                ),
309                Node::let_bind("c0", Expr::load(input, Expr::var("in_base"))),
310                Node::let_bind(
311                    "c1",
312                    Expr::load(input, Expr::add(Expr::var("in_base"), Expr::u32(1))),
313                ),
314                Node::let_bind(
315                    "c2",
316                    Expr::load(input, Expr::add(Expr::var("in_base"), Expr::u32(2))),
317                ),
318                Node::let_bind(
319                    "c3",
320                    Expr::load(input, Expr::add(Expr::var("in_base"), Expr::u32(3))),
321                ),
322                Node::let_bind("pad2", Expr::eq(Expr::var("c2"), Expr::u32(PAD))),
323                Node::let_bind("pad1", Expr::eq(Expr::var("c3"), Expr::u32(PAD))),
324            ];
325            per_byte.extend(clamp_lookup("c0", table));
326            per_byte.extend(clamp_lookup("c1", table));
327            per_byte.extend(clamp_lookup("c2", table));
328            per_byte.extend(clamp_lookup("c3", table));
329            per_byte.extend([
330                Node::let_bind(
331                    "b0",
332                    Expr::bitor(
333                        Expr::shl(Expr::var("c0_v"), Expr::u32(2)),
334                        Expr::shr(Expr::var("c1_v"), Expr::u32(4)),
335                    ),
336                ),
337                Node::let_bind(
338                    "b1",
339                    Expr::bitor(
340                        Expr::shl(
341                            Expr::bitand(Expr::var("c1_v"), Expr::u32(0x0F)),
342                            Expr::u32(4),
343                        ),
344                        Expr::shr(Expr::var("c2_v"), Expr::u32(2)),
345                    ),
346                ),
347                Node::let_bind(
348                    "b2",
349                    Expr::bitor(
350                        Expr::shl(
351                            Expr::bitand(Expr::var("c2_v"), Expr::u32(0x03)),
352                            Expr::u32(6),
353                        ),
354                        Expr::var("c3_v"),
355                    ),
356                ),
357                Node::if_then(
358                    Expr::eq(Expr::var("pos"), Expr::u32(0)),
359                    vec![Node::store(output, Expr::var("j"), Expr::var("b0"))],
360                ),
361                Node::if_then(
362                    Expr::eq(Expr::var("pos"), Expr::u32(1)),
363                    vec![Node::if_then(
364                        Expr::eq(Expr::var("pad2"), Expr::bool(false)),
365                        vec![Node::store(output, Expr::var("j"), Expr::var("b1"))],
366                    )],
367                ),
368                Node::if_then(
369                    Expr::eq(Expr::var("pos"), Expr::u32(2)),
370                    vec![Node::if_then(
371                        Expr::eq(Expr::var("pad1"), Expr::bool(false)),
372                        vec![Node::store(output, Expr::var("j"), Expr::var("b2"))],
373                    )],
374                ),
375            ]);
376            per_byte
377        },
378    ));
379    body
380}
381
382/// Wrap the base64 decode body as a child of `parent_op_id`.
383#[must_use]
384pub fn base64_decode_child(
385    parent_op_id: &str,
386    input: &str,
387    table: &str,
388    output: &str,
389    decoded_len_buffer: &str,
390    input_len: u32,
391) -> Node {
392    Node::Region {
393        generator: Ident::from(BASE64_DECODE_OP_ID),
394        source_region: Some(GeneratorRef {
395            name: parent_op_id.to_string(),
396        }),
397        body: Arc::new(base64_decode_body(
398            input,
399            table,
400            output,
401            decoded_len_buffer,
402            input_len,
403        )),
404    }
405}
406
407/// Standalone base64 decode program for primitive-level conformance.
408#[must_use]
409pub fn base64_decode(
410    input: &str,
411    table: &str,
412    output: &str,
413    decoded_len_buffer: &str,
414    input_len: u32,
415) -> Program {
416    Program::wrapped(
417        vec![
418            BufferDecl::storage(input, 0, BufferAccess::ReadOnly, DataType::U32)
419                .with_count(input_len),
420            BufferDecl::storage(table, 1, BufferAccess::ReadOnly, DataType::U32)
421                .with_count(BASE64_DECODE_TABLE_WORDS),
422            BufferDecl::output(output, 2, DataType::U32).with_count(decoded_capacity(input_len)),
423            BufferDecl::read_write(decoded_len_buffer, 3, DataType::U32).with_count(1),
424        ],
425        BASE64_WORKGROUP_SIZE,
426        vec![Node::Region {
427            generator: Ident::from(BASE64_DECODE_OP_ID),
428            source_region: None,
429            body: Arc::new(base64_decode_body(
430                input,
431                table,
432                output,
433                decoded_len_buffer,
434                input_len,
435            )),
436        }],
437    )
438}
439
440#[cfg(feature = "inventory-registry")]
441inventory::submit! {
442    vyre_foundation::operation::OperationRegistration::primitive(
443        BASE64_DECODE_OP_ID,
444        || base64_decode("input", "table", "output", "decoded_len", 4),
445        Some(|| vec![vec![
446            crate::wire::pack_u32_slice(&[u32::from(b'T'), u32::from(b'W'), u32::from(b'F'), u32::from(b'u')]),
447            crate::wire::pack_u32_slice(standard_decode_table_ref()),
448            vec![0; 12],
449            vec![0; 4],
450        ]]),
451        Some(|| vec![vec![
452            crate::wire::pack_u32_slice(&[u32::from(b'M'), u32::from(b'a'), u32::from(b'n')]),
453            crate::wire::pack_u32_slice(&[3]),
454        ]]),
455    )
456}
457
458// ---------------------------------------------------------------------------
459// CPU reference implementation
460// ---------------------------------------------------------------------------
461
462/// Build the standard base64 decode table (RFC 4648).
463#[cfg(any(test, feature = "cpu-parity"))]
464pub fn cpu_base64_table() -> [u32; 256] {
465    standard_decode_table()
466}
467
468/// CPU reference: decode a base64-encoded byte slice (standard alphabet,
469/// `=`-padded, length must be a multiple of 4). Returns decoded bytes.
470#[must_use]
471#[cfg(any(test, feature = "cpu-parity"))]
472pub fn cpu_base64_decode(input: &[u8]) -> Vec<u8> {
473    let (words, decoded_len) = decode_standard_packed_reference(input);
474    let decoded_len = usize::try_from(decoded_len).unwrap_or(words.len());
475    words
476        .into_iter()
477        .take(decoded_len)
478        .map(|word| (word & 0xFF) as u8)
479        .collect()
480}
481
482#[cfg(test)]
483mod tests {
484    use super::*;
485
486    #[test]
487    fn decode_man() {
488        assert_eq!(cpu_base64_decode(b"TWFu"), b"Man");
489    }
490
491    #[test]
492    fn cpu_table_is_the_standard_primitive_table() {
493        assert_eq!(cpu_base64_table(), standard_decode_table());
494        assert_eq!(standard_decode_table()[b'/' as usize], 63);
495        assert_eq!(standard_decode_table()[b'*' as usize], INVALID);
496    }
497
498    #[test]
499    fn standard_decode_table_ref_matches_value_api_and_reuses_allocation() {
500        let first = standard_decode_table_ref();
501        let second = standard_decode_table_ref();
502        assert!(
503            std::ptr::eq(first, second),
504            "Fix: base64 decode setup must reuse the immutable primitive table instead of rebuilding it per dispatch."
505        );
506        assert_eq!(*first, standard_decode_table());
507    }
508
509    #[test]
510    fn try_decode_reference_rejects_unaligned_input_without_panic() {
511        let err = try_decode_standard_packed_reference(b"abc")
512            .expect_err("unaligned base64 input must be rejected");
513        assert_eq!(err, Base64DecodeReferenceError::InvalidLength { len: 3 });
514    }
515
516    #[test]
517    fn try_decode_reference_matches_infallible_wrapper() {
518        let fallible = try_decode_standard_packed_reference(b"Zm9vYmFy")
519            .expect("Fix: unit-test oracle precondition - valid base64 must decode");
520        let infallible = decode_standard_packed_reference(b"Zm9vYmFy");
521        assert_eq!(fallible, infallible);
522        assert_eq!(fallible.1, 6);
523    }
524
525    #[test]
526    fn try_decode_reference_into_reuses_output_and_clears_stale_tail() {
527        let mut out = Vec::with_capacity(16);
528        out.extend_from_slice(&[u32::MAX; 16]);
529        let ptr = out.as_ptr();
530
531        let decoded_len = try_decode_standard_packed_reference_into(b"TWE=", &mut out)
532            .expect("Fix: unit-test oracle precondition - valid padded base64 must decode into caller-owned storage");
533
534        assert_eq!(decoded_len, 2);
535        assert_eq!(out, vec![u32::from(b'M'), u32::from(b'a'), 0]);
536        assert_eq!(out.as_ptr(), ptr);
537    }
538
539    #[test]
540    fn try_decode_reference_into_is_transactional_on_invalid_length() {
541        let mut out = vec![0x1234_5678, 0x9abc_def0];
542        let before = out.clone();
543
544        let err = try_decode_standard_packed_reference_into(b"abc", &mut out)
545            .expect_err("unaligned base64 input must be rejected");
546
547        assert_eq!(err, Base64DecodeReferenceError::InvalidLength { len: 3 });
548        assert_eq!(out, before);
549    }
550
551    // The infallible base64 oracle wrappers must FAIL LOUD on malformed input,
552    // not silently return empty. An empty result would let a GPU-vs-CPU parity
553    // assertion pass on empty==empty and hide a real divergence (Law 10 / Law 6).
554    // Each wrapper gets its own #[should_panic] behavioral proof; callers that
555    // need to tolerate bad input use the try_ fallible variants instead.
556    #[test]
557    #[should_panic(expected = "vyre-primitives base64 decode reference failed")]
558    fn decode_reference_fails_loud_on_invalid_length() {
559        let _ = decode_standard_packed_reference(b"abc");
560    }
561
562    #[test]
563    #[should_panic(expected = "vyre-primitives base64 decode reference failed")]
564    fn decode_reference_into_fails_loud_on_invalid_length() {
565        let mut out = vec![1, 2, 3];
566        let _ = decode_standard_packed_reference_into(b"abc", &mut out);
567    }
568
569    #[test]
570    #[should_panic(expected = "vyre-primitives base64 decode reference failed")]
571    fn cpu_base64_decode_fails_loud_on_invalid_length() {
572        let _ = cpu_base64_decode(b"abc");
573    }
574
575    #[test]
576    fn decode_padded_1() {
577        assert_eq!(cpu_base64_decode(b"TWE="), b"Ma");
578    }
579
580    #[test]
581    fn decode_padded_2() {
582        assert_eq!(cpu_base64_decode(b"TQ=="), b"M");
583    }
584
585    #[test]
586    fn decode_empty() {
587        assert_eq!(cpu_base64_decode(b""), b"");
588    }
589
590    #[test]
591    fn decode_hello_world() {
592        assert_eq!(cpu_base64_decode(b"SGVsbG8gV29ybGQ="), b"Hello World");
593    }
594
595    #[test]
596    fn decode_roundtrip_rfc4648_vectors() {
597        // RFC 4648 test vectors
598        assert_eq!(cpu_base64_decode(b"Zg=="), b"f");
599        assert_eq!(cpu_base64_decode(b"Zm8="), b"fo");
600        assert_eq!(cpu_base64_decode(b"Zm9v"), b"foo");
601        assert_eq!(cpu_base64_decode(b"Zm9vYg=="), b"foob");
602        assert_eq!(cpu_base64_decode(b"Zm9vYmE="), b"fooba");
603        assert_eq!(cpu_base64_decode(b"Zm9vYmFy"), b"foobar");
604    }
605
606    #[test]
607    fn table_index_is_masked_so_high_bit_input_cannot_read_out_of_bounds() {
608        use vyre_reference::value::Value;
609        // "TWFu" decodes to "Man". The U32 input buffer can carry a >255 element
610        // (it is unvalidated); here the first char is `0x100 | 'T'`. The `& 0xFF`
611        // index mask must fold it back to 'T' so the decode is IDENTICAL to the
612        // clean input, and must never read past the 256-entry decode table (a raw
613        // OOB read is UB on CUDA). This is a regression LOCK: the OLD unmasked
614        // `load(table, c)` OOB-indexes the table (zero-filled to 0 by the reference
615        // interpreter), decoding a wrong first byte instead of 'M'.
616        let input_len = 4u32;
617        let dirty = [
618            0x0100u32 | u32::from(b'T'),
619            u32::from(b'W'),
620            u32::from(b'F'),
621            u32::from(b'u'),
622        ];
623        let program = base64_decode("input", "table", "output", "decoded_len", input_len);
624        let inputs = vec![
625            Value::from(crate::wire::pack_u32_slice(&dirty)),
626            Value::from(crate::wire::pack_u32_slice(standard_decode_table_ref())),
627            Value::from(vec![0u8; decoded_capacity(input_len) as usize * 4]),
628            Value::from(crate::wire::pack_u32_slice(&[0])),
629        ];
630        let outputs = vyre_reference::reference_eval(&program, &inputs)
631            .expect("Fix: base64 decode with a >255 input element must not fault the interpreter");
632        // Two outputs (output + decoded_len): locate each by name via the
633        // interpreter's own output ABI, never by fixed position.
634        let out_idx = vyre_reference::output_index(&program, "output")
635            .expect("Fix: base64 output buffer must be a reference output");
636        let len_idx = vyre_reference::output_index(&program, "decoded_len")
637            .expect("Fix: base64 decoded_len buffer must be a reference output");
638        let words = crate::wire::decode_u32_le_bytes_all(&outputs[out_idx].to_bytes());
639        let decoded_len =
640            crate::wire::decode_u32_le_bytes_all(&outputs[len_idx].to_bytes())[0] as usize;
641        let bytes: Vec<u8> = words
642            .into_iter()
643            .take(decoded_len)
644            .map(|word| (word & 0xFF) as u8)
645            .collect();
646        assert_eq!(
647            bytes, b"Man",
648            "Fix: masked table index must decode the high-bit-dirty input identically to the clean 'TWFu'"
649        );
650    }
651}