1mod install;
14
15pub use install::{declare, install};
16pub(crate) use install::{declare_types, install_constants};
17
18use base64::Engine as _;
19use base64::alphabet;
20use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig, NO_PAD, PAD};
21use wasmtime::{ArrayRef, Caller, Rooted, StructRef, StructRefPre, Val};
22
23use crate::runtime::StoreData;
24use crate::runtime::fuel;
25use crate::runtime::host::{
26 fatal_host_error, host_boxed_number_vtable, read_uint8, read_uint8_array_arg,
27 read_uint8_array_range, uint8_array_backing, write_submilli_uint8array_struct,
28};
29use crate::runtime::intrinsic_types::intrinsic_types;
30use crate::runtime::number::{format_number_js, to_uint8};
31use crate::runtime::prelude::array::{ElementCallback, merge_sort};
32use crate::runtime::prelude::closure::Closure;
33use crate::runtime::prelude::iterator::{IterKind, IteratorSource, as_struct, make_index_iterator};
34use crate::runtime::prelude::keep::KeptValue;
35use crate::runtime::prelude::vtable::{read_object_entries, read_string_units};
36
37pub(crate) fn read_bytes(
46 caller: &mut Caller<'_, StoreData>,
47 val: &Val,
48 name: &str,
49) -> wasmtime::Result<Vec<u8>> {
50 read_uint8_array_arg(caller, val, name)
51}
52
53fn build(caller: &mut Caller<'_, StoreData>, bytes: &[u8]) -> wasmtime::Result<Val> {
55 let st = write_submilli_uint8array_struct(caller, bytes)?;
56 Ok(Val::AnyRef(Some(st.to_anyref())))
57}
58
59fn store_bytes(
63 caller: &mut Caller<'_, StoreData>,
64 receiver: &Val,
65 bytes: &[u8],
66) -> wasmtime::Result<()> {
67 let raw = backing(caller, receiver)?;
68 fuel::charge(&mut *caller, fuel::COPY, bytes.len() as u64)?;
69 raw.write_i8(&mut *caller, 0, bytes)
70}
71
72fn backing(
74 caller: &mut Caller<'_, StoreData>,
75 receiver: &Val,
76) -> wasmtime::Result<Rooted<ArrayRef>> {
77 let st = as_struct(caller, receiver, "Uint8Array mutate receiver")?;
78 match st.field(&mut *caller, 1)? {
79 Val::AnyRef(Some(arr)) => arr.unwrap_array(&mut *caller),
80 other => Err(wasmtime::Error::msg(format!(
81 "Uint8Array: malformed $rawUint8Array backing {other:?}"
82 ))),
83 }
84}
85
86fn box_byte(caller: &mut Caller<'_, StoreData>, b: u8) -> wasmtime::Result<Val> {
88 let boxed = intrinsic_types(&mut *caller)?.boxed_number.clone();
89 let vtable = host_boxed_number_vtable(caller)?;
90 let pre = StructRefPre::new(&mut *caller, boxed);
91 let st = StructRef::new(
92 &mut *caller,
93 &pre,
94 &[vtable, Val::F64(f64::from(b).to_bits())],
95 )?;
96 Ok(Val::AnyRef(Some(st.to_anyref())))
97}
98
99fn unbox_byte(caller: &mut Caller<'_, StoreData>, v: &Val, name: &str) -> wasmtime::Result<u8> {
101 let Val::AnyRef(Some(any)) = v else {
102 return Err(wasmtime::Error::msg(format!(
103 "{name} callback returned {v:?}, expected a number"
104 )));
105 };
106 let st = any
107 .as_struct(&mut *caller)?
108 .ok_or_else(|| wasmtime::Error::msg(format!("{name} callback result is not a number")))?;
109 match st.field(&mut *caller, 1)? {
110 Val::F64(bits) => Ok(to_uint8(f64::from_bits(bits))),
111 other => Err(wasmtime::Error::msg(format!(
112 "{name} callback result field was {other:?}, not f64"
113 ))),
114 }
115}
116
117fn trunc_sat(x: f64) -> i32 {
122 x as i32
123}
124
125fn norm_clamp(x: f64, len: i32) -> i32 {
126 let mut i = trunc_sat(x);
127 if i < 0 {
128 i += len;
129 }
130 i.clamp(0, len)
131}
132
133fn at_index(x: f64, len: i32) -> Option<usize> {
134 let mut i = trunc_sat(x);
135 if i < 0 {
136 i += len;
137 }
138 if i < 0 || i >= len {
139 None
140 } else {
141 Some(i as usize)
142 }
143}
144
145fn fwd_from(x: f64, len: i32) -> i32 {
146 let mut fi = trunc_sat(x);
147 if fi < 0 {
148 fi += len;
149 }
150 fi.max(0)
151}
152
153fn last_from(x: f64, len: i32) -> Option<i32> {
154 let mut fi = trunc_sat(x);
155 if fi < 0 {
156 fi += len;
157 }
158 if fi < 0 {
159 return None;
160 }
161 Some(fi.min(len - 1))
162}
163
164fn length(caller: &mut Caller<'_, StoreData>, receiver: &Val, name: &str) -> wasmtime::Result<f64> {
170 let arr = uint8_array_backing(caller, receiver, name)?;
171 Ok(f64::from(arr.len(&mut *caller)?))
172}
173
174fn at(
176 caller: &mut Caller<'_, StoreData>,
177 receiver: &Val,
178 index: f64,
179 name: &str,
180) -> wasmtime::Result<Val> {
181 let arr = uint8_array_backing(caller, receiver, name)?;
182 let len = arr.len(&mut *caller)?;
183 match at_index(index, len as i32) {
184 Some(i) => {
185 let byte = read_uint8(caller, arr, i)?;
186 box_byte(caller, byte)
187 }
188 None => super::undefined::value(caller),
189 }
190}
191
192fn byte_step(
195 caller: &mut Caller<'_, StoreData>,
196 payload: &Val,
197 pos: i32,
198) -> wasmtime::Result<Option<(Val, Val)>> {
199 let arr = uint8_array_backing(caller, payload, "Uint8Array iterator")?;
200 let Ok(index) = usize::try_from(pos) else {
201 return Ok(None);
202 };
203 if index >= arr.len(&mut *caller)? as usize {
204 return Ok(None);
205 }
206 let byte = read_uint8(caller, arr, index)?;
207 let key = box_byte_index(caller, index)?;
208 Ok(Some((key, box_byte(caller, byte)?)))
209}
210
211fn box_byte_index(caller: &mut Caller<'_, StoreData>, index: usize) -> wasmtime::Result<Val> {
212 let st = crate::runtime::host::write_boxed_number_struct(caller, index as f64)?;
213 Ok(Val::AnyRef(Some(st.to_anyref())))
214}
215
216pub(crate) fn values(caller: &mut Caller<'_, StoreData>, receiver: &Val) -> wasmtime::Result<Val> {
218 iterate(caller, receiver, IterKind::Values)
219}
220
221fn iterate(
223 caller: &mut Caller<'_, StoreData>,
224 receiver: &Val,
225 kind: IterKind,
226) -> wasmtime::Result<Val> {
227 make_index_iterator(caller, IteratorSource::Bytes, *receiver, kind, byte_step)
228}
229
230fn slice(
232 caller: &mut Caller<'_, StoreData>,
233 receiver: &Val,
234 start: f64,
235 end: f64,
236 name: &str,
237) -> wasmtime::Result<Vec<u8>> {
238 let arr = uint8_array_backing(caller, receiver, name)?;
239 let len = arr.len(&mut *caller)? as i32;
240 let si = norm_clamp(start, len);
241 let count = (norm_clamp(end, len) - si).max(0) as usize;
242 read_uint8_array_range(caller, arr, si as usize, count, name)
243}
244
245fn with(bytes: &[u8], index: f64, value: f64) -> Option<Vec<u8>> {
248 let i = at_index(index, bytes.len() as i32)?;
249 let mut out = bytes.to_vec();
250 out[i] = to_uint8(value);
251 Some(out)
252}
253
254fn searched_byte(target: f64) -> Option<u8> {
257 (target.fract() == 0.0 && (0.0..=255.0).contains(&target)).then_some(target as u8)
258}
259
260fn index_of(bytes: &[u8], target: f64, from: f64) -> f64 {
261 let len = bytes.len() as i32;
262 let Some(target) = searched_byte(target) else {
263 return -1.0;
264 };
265 let mut i = fwd_from(from, len);
266 while i < len {
267 if bytes[i as usize] == target {
268 return f64::from(i);
269 }
270 i += 1;
271 }
272 -1.0
273}
274
275fn last_index_of(bytes: &[u8], target: f64, from: f64) -> f64 {
276 let len = bytes.len() as i32;
277 let Some(target) = searched_byte(target) else {
278 return -1.0;
279 };
280 let Some(mut i) = last_from(from, len) else {
281 return -1.0;
282 };
283 while i >= 0 {
284 if bytes[i as usize] == target {
285 return f64::from(i);
286 }
287 i -= 1;
288 }
289 -1.0
290}
291
292fn includes(bytes: &[u8], target: f64, from: f64) -> bool {
293 index_of(bytes, target, from) >= 0.0
294}
295
296pub(crate) fn join(bytes: &[u8], sep: &[u16]) -> Vec<u16> {
299 let mut out: Vec<u16> = Vec::new();
300 for (i, &b) in bytes.iter().enumerate() {
301 if i > 0 {
302 out.extend_from_slice(sep);
303 }
304 out.extend(format_number_js(f64::from(b)).encode_utf16());
305 }
306 out
307}
308
309fn equals(a: &[u8], b: &[u8]) -> bool {
310 a == b
311}
312
313fn reverse(
318 caller: &mut Caller<'_, StoreData>,
319 receiver: &Val,
320 mut bytes: Vec<u8>,
321) -> wasmtime::Result<Val> {
322 bytes.reverse();
323 store_bytes(caller, receiver, &bytes)?;
324 Ok(*receiver)
325}
326
327fn fill(
328 caller: &mut Caller<'_, StoreData>,
329 receiver: &Val,
330 value: f64,
331 start: f64,
332 end: f64,
333) -> wasmtime::Result<Val> {
334 let raw = backing(caller, receiver)?;
335 let len = i32::try_from(raw.len(&mut *caller)?).map_err(fatal_host_error)?;
336 let start = norm_clamp(start, len) as u32;
337 let end = norm_clamp(end, len) as u32;
338 let count = end.saturating_sub(start);
339 fuel::charge(&mut *caller, fuel::COPY, u64::from(count))?;
340 let chunk = [to_uint8(value); 4096];
341 let mut offset = start;
342 while offset < end {
343 let count = (end - offset).min(chunk.len() as u32);
344 let bytes = chunk
345 .get(..count as usize)
346 .ok_or_else(|| fatal_host_error("invalid fill chunk"))?;
347 raw.write_i8(&mut *caller, offset, bytes)?;
348 offset += count;
349 }
350 Ok(*receiver)
351}
352
353fn copy_within(
354 caller: &mut Caller<'_, StoreData>,
355 receiver: &Val,
356 target: f64,
357 start: f64,
358 end: f64,
359) -> wasmtime::Result<Val> {
360 let raw = backing(caller, receiver)?;
361 let len = i32::try_from(raw.len(&mut *caller)?).map_err(fatal_host_error)?;
362 let target = norm_clamp(target, len) as usize;
363 let start = norm_clamp(start, len) as usize;
364 let end = norm_clamp(end, len) as usize;
365 let count = end.saturating_sub(start).min(len as usize - target);
366 let bytes = read_uint8_array_range(caller, raw, start, count, "Uint8Array#copyWithin")?;
368 fuel::charge(&mut *caller, fuel::COPY, count as u64)?;
369 raw.write_i8(&mut *caller, target as u32, &bytes)?;
370 Ok(*receiver)
371}
372
373fn set(
375 caller: &mut Caller<'_, StoreData>,
376 receiver: &Val,
377 source: &Val,
378 offset: f64,
379) -> wasmtime::Result<()> {
380 let target = backing(caller, receiver)?;
381 let source = backing(caller, source)?;
382 let target_len = target.len(&mut *caller)?;
383 let source_len = source.len(&mut *caller)?;
384 let offset = trunc_sat(offset);
385 if offset < 0
386 || (offset as u32)
387 .checked_add(source_len)
388 .is_none_or(|end| end > target_len)
389 {
390 return Err(crate::runtime::host::range_error("offset is out of bounds"));
391 }
392 let bytes = read_uint8_array_range(
393 caller,
394 source,
395 0,
396 source_len as usize,
397 "Uint8Array#set source",
398 )?;
399 fuel::charge(&mut *caller, fuel::COPY, u64::from(source_len))?;
400 target.write_i8(&mut *caller, offset as u32, &bytes)
401}
402
403fn allocate(caller: &mut Caller<'_, StoreData>, len: usize) -> wasmtime::Result<Val> {
404 use wasmtime::ArrayRefPre;
405 let intr = intrinsic_types(&mut *caller)?;
406 fuel::charge(&mut *caller, fuel::COPY, len as u64)?;
407 let len = u32::try_from(len).map_err(fatal_host_error)?;
408 let pre = ArrayRefPre::new(&mut *caller, intr.raw_uint8_array.clone());
409 let raw = ArrayRef::new_zeroed_i8(&mut *caller, &pre, len)?;
410 let vtable = crate::runtime::host::host_uint8_array_vtable(caller)?;
411 let pre = StructRefPre::new(&mut *caller, intr.uint8_array.clone());
412 let value = StructRef::new(
413 &mut *caller,
414 &pre,
415 &[vtable, Val::AnyRef(Some(raw.to_anyref()))],
416 )?;
417 Ok(Val::AnyRef(Some(value.to_anyref())))
418}
419
420async fn sort_bytes(
421 caller: &mut Caller<'_, StoreData>,
422 bytes: &mut Vec<u8>,
423 cmp: Option<&Closure>,
424) -> wasmtime::Result<()> {
425 let Some(cmp) = cmp else {
426 bytes.sort_unstable();
427 return Ok(());
428 };
429 if bytes.len() < 2 {
430 return Ok(());
431 }
432 let mut boxes = Vec::with_capacity(256);
435 for byte in 0..=u8::MAX {
436 boxes.push(box_byte(caller, byte)?);
437 }
438 merge_sort(caller, bytes, cmp, |_, byte| {
439 boxes
440 .get(usize::from(byte))
441 .copied()
442 .ok_or_else(|| fatal_host_error("Uint8Array#sort: byte has no box"))
443 })
444 .await
445}
446
447async fn sort(
448 caller: &mut Caller<'_, StoreData>,
449 receiver: &Val,
450 mut bytes: Vec<u8>,
451 cmp: Option<Closure>,
452) -> wasmtime::Result<Val> {
453 sort_bytes(caller, &mut bytes, cmp.as_ref()).await?;
454 store_bytes(caller, receiver, &bytes)?;
455 Ok(*receiver)
456}
457
458fn to_reversed(mut bytes: Vec<u8>) -> Vec<u8> {
463 bytes.reverse();
464 bytes
465}
466
467async fn to_sorted(
468 caller: &mut Caller<'_, StoreData>,
469 mut bytes: Vec<u8>,
470 cmp: Option<Closure>,
471) -> wasmtime::Result<Vec<u8>> {
472 sort_bytes(caller, &mut bytes, cmp.as_ref()).await?;
473 Ok(bytes)
474}
475
476async fn for_each(
481 caller: &mut Caller<'_, StoreData>,
482 array: Val,
483 bytes: Vec<u8>,
484 f: &Closure,
485) -> wasmtime::Result<()> {
486 let f = ElementCallback::new(caller, f, Some(array))?;
487 for (index, b) in bytes.into_iter().enumerate() {
488 let boxed = box_byte(caller, b)?;
489 f.call(caller, None, boxed, index).await?;
490 }
491 Ok(())
492}
493
494async fn map(
495 caller: &mut Caller<'_, StoreData>,
496 array: Val,
497 bytes: Vec<u8>,
498 f: &Closure,
499) -> wasmtime::Result<Vec<u8>> {
500 let f = ElementCallback::new(caller, f, Some(array))?;
501 let mut out = Vec::with_capacity(bytes.len());
502 for (index, b) in bytes.into_iter().enumerate() {
503 let boxed = box_byte(caller, b)?;
504 let r = f.call(caller, None, boxed, index).await?;
505 out.push(unbox_byte(caller, &r, "Uint8Array#map")?);
506 }
507 Ok(out)
508}
509
510async fn filter(
511 caller: &mut Caller<'_, StoreData>,
512 array: Val,
513 bytes: Vec<u8>,
514 pred: &Closure,
515) -> wasmtime::Result<Vec<u8>> {
516 let pred = ElementCallback::new(caller, pred, Some(array))?;
517 let mut out = Vec::new();
518 for (index, b) in bytes.into_iter().enumerate() {
519 let boxed = box_byte(caller, b)?;
520 if pred.test(caller, boxed, index).await? {
521 out.push(b);
522 }
523 }
524 Ok(out)
525}
526
527async fn reduce(
528 caller: &mut Caller<'_, StoreData>,
529 array: Val,
530 bytes: Vec<u8>,
531 f: &Closure,
532 mut acc: Val,
533 reverse: bool,
534) -> wasmtime::Result<Val> {
535 let f = ElementCallback::new(caller, f, Some(array))?;
536 let order: Vec<usize> = if reverse {
537 (0..bytes.len()).rev().collect()
538 } else {
539 (0..bytes.len()).collect()
540 };
541 let kept_acc = KeptValue::new(caller)?;
543 for i in order {
544 let boxed = box_byte(caller, bytes[i])?;
545 acc = f.call(caller, Some(acc), boxed, i).await?;
546 kept_acc.set(caller, acc)?;
547 }
548 Ok(acc)
549}
550
551async fn some(
552 caller: &mut Caller<'_, StoreData>,
553 array: Val,
554 bytes: Vec<u8>,
555 pred: &Closure,
556) -> wasmtime::Result<bool> {
557 let pred = ElementCallback::new(caller, pred, Some(array))?;
558 for (index, b) in bytes.into_iter().enumerate() {
559 let boxed = box_byte(caller, b)?;
560 if pred.test(caller, boxed, index).await? {
561 return Ok(true);
562 }
563 }
564 Ok(false)
565}
566
567async fn every(
568 caller: &mut Caller<'_, StoreData>,
569 array: Val,
570 bytes: Vec<u8>,
571 pred: &Closure,
572) -> wasmtime::Result<bool> {
573 let pred = ElementCallback::new(caller, pred, Some(array))?;
574 for (index, b) in bytes.into_iter().enumerate() {
575 let boxed = box_byte(caller, b)?;
576 if !pred.test(caller, boxed, index).await? {
577 return Ok(false);
578 }
579 }
580 Ok(true)
581}
582
583async fn find_match(
585 caller: &mut Caller<'_, StoreData>,
586 array: Val,
587 bytes: &[u8],
588 pred: &Closure,
589 reverse: bool,
590) -> wasmtime::Result<Option<usize>> {
591 let pred = ElementCallback::new(caller, pred, Some(array))?;
592 let order: Vec<usize> = if reverse {
593 (0..bytes.len()).rev().collect()
594 } else {
595 (0..bytes.len()).collect()
596 };
597 for i in order {
598 let boxed = box_byte(caller, bytes[i])?;
599 if pred.test(caller, boxed, i).await? {
600 return Ok(Some(i));
601 }
602 }
603 Ok(None)
604}
605
606pub(crate) fn to_base64_standard(bytes: &[u8]) -> String {
612 encode_base64(bytes, false, false)
613}
614
615fn encode_base64(bytes: &[u8], url_safe: bool, omit_padding: bool) -> String {
616 let alpha = if url_safe {
617 &alphabet::URL_SAFE
618 } else {
619 &alphabet::STANDARD
620 };
621 let config: GeneralPurposeConfig = if omit_padding { NO_PAD } else { PAD };
622 GeneralPurpose::new(alpha, config).encode(bytes)
623}
624
625fn decode_base64(s: &str, url_safe: bool) -> wasmtime::Result<Vec<u8>> {
627 let alpha = if url_safe {
628 &alphabet::URL_SAFE
629 } else {
630 &alphabet::STANDARD
631 };
632 let config = if s.as_bytes().last() == Some(&b'=') {
633 PAD
634 } else {
635 NO_PAD
636 };
637 GeneralPurpose::new(alpha, config)
638 .decode(s.as_bytes())
639 .map_err(|e| crate::runtime::host::syntax_error(format!("Uint8Array.fromBase64: {e}")))
640}
641
642fn to_hex(bytes: &[u8]) -> String {
643 let mut out = String::with_capacity(bytes.len() * 2);
644 for &b in bytes {
645 out.push(hex_digit(b >> 4));
646 out.push(hex_digit(b & 0xf));
647 }
648 out
649}
650
651fn hex_digit(nibble: u8) -> char {
652 char::from(if nibble < 10 {
653 b'0' + nibble
654 } else {
655 b'a' + (nibble - 10)
656 })
657}
658
659fn from_hex(units: &[u16]) -> wasmtime::Result<Vec<u8>> {
662 if !units.len().is_multiple_of(2) {
663 return Err(crate::runtime::host::syntax_error(
664 "Uint8Array.fromHex: string must have an even number of characters",
665 ));
666 }
667 let mut out = Vec::with_capacity(units.len() / 2);
668 for pair in units.as_chunks::<2>().0 {
669 let hi = hex_nibble(pair[0])?;
670 let lo = hex_nibble(pair[1])?;
671 out.push((hi << 4) | lo);
672 }
673 Ok(out)
674}
675
676fn hex_nibble(c: u16) -> wasmtime::Result<u8> {
677 match c {
678 0x30..=0x39 => Ok((c - 0x30) as u8),
679 0x61..=0x66 => Ok((c - 0x61 + 10) as u8),
680 0x41..=0x46 => Ok((c - 0x41 + 10) as u8),
681 _ => Err(crate::runtime::host::syntax_error(
682 "Uint8Array.fromHex: invalid hex digit",
683 )),
684 }
685}
686
687fn read_base64_options(
690 caller: &mut Caller<'_, StoreData>,
691 opt: &Val,
692) -> wasmtime::Result<(bool, bool)> {
693 if super::undefined::is_undefined(caller, opt)? {
694 return Ok((false, false));
695 }
696 let (mut url_safe, mut omit_padding) = (false, false);
697 for (name, value) in read_object_entries(caller, opt, "Base64Options")? {
698 if super::undefined::is_undefined(caller, &value)? {
701 continue;
702 }
703 match String::from_utf16_lossy(&name).as_str() {
704 "alphabet" => {
705 let alphabet = read_string_units(caller, &value, "Base64Options.alphabet")?;
706 url_safe = String::from_utf16_lossy(&alphabet) == "base64url";
707 }
708 "omitPadding" => omit_padding = read_boolean(caller, &value)?,
709 _ => {}
710 }
711 }
712 Ok((url_safe, omit_padding))
713}
714
715fn read_boolean(caller: &mut Caller<'_, StoreData>, val: &Val) -> wasmtime::Result<bool> {
716 let st = as_struct(caller, val, "Base64Options.omitPadding")?;
717 match st.field(&mut *caller, 1)? {
718 Val::I32(b) => Ok(b != 0),
719 other => wasmtime::bail!("Base64Options.omitPadding: field 1 is {other:?}, not i32"),
720 }
721}
722
723pub(crate) fn alloc_len(n: f64) -> wasmtime::Result<usize> {
736 if n.trunc() < 0.0 {
739 return Err(crate::runtime::host::range_error(format!(
740 "invalid Uint8Array length {} — a length cannot be negative",
741 format_number_js(n)
742 )));
743 }
744 if n > MAX_ALLOC_LEN as f64 {
745 return Err(crate::runtime::host::range_error(format!(
746 "invalid Uint8Array length {} — the maximum is {MAX_ALLOC_LEN}",
747 format_number_js(n)
748 )));
749 }
750 Ok(n as u32 as usize)
751}
752
753pub(crate) const MAX_ALLOC_LEN: usize = 1 << 30;
757
758fn read_number_array(
760 caller: &mut Caller<'_, StoreData>,
761 val: &Val,
762 name: &str,
763) -> wasmtime::Result<Vec<u8>> {
764 let elements = crate::runtime::prelude::array::read_array(caller, val, name)?;
765 let mut bytes = Vec::with_capacity(elements.len());
766 for e in &elements {
767 bytes.push(unbox_byte(caller, e, name)?);
768 }
769 Ok(bytes)
770}
771
772#[cfg(test)]
773mod tests {
774 use super::*;
775
776 fn units(s: &str) -> Vec<u16> {
777 s.encode_utf16().collect()
778 }
779
780 #[test]
781 fn base64_round_trips_both_alphabets() {
782 let bytes = [0xfb_u8, 0xff, 0x00, 0x10, 0x41];
783 for url_safe in [false, true] {
784 let padded = encode_base64(&bytes, url_safe, false);
785 assert_eq!(decode_base64(&padded, url_safe).unwrap(), bytes);
786 let unpadded = encode_base64(&bytes, url_safe, true);
788 assert_eq!(decode_base64(&unpadded, url_safe).unwrap(), bytes);
789 }
790 assert!(encode_base64(&[0xfb, 0xff], true, false).contains('-'));
792 assert!(encode_base64(&[0xfb, 0xff], false, false).contains('+'));
793 }
794
795 #[test]
796 fn base64_padding_selection_preserves_previous_acceptance() {
797 let alphabet = b"AQf=_-";
800 for length in 0..=5_u32 {
801 for mut code in 0..alphabet.len().pow(length) {
802 let mut input = Vec::new();
803 for _ in 0..length {
804 input.push(alphabet[code % alphabet.len()]);
805 code /= alphabet.len();
806 }
807 let text = std::str::from_utf8(&input).unwrap();
808 for url_safe in [false, true] {
809 let alpha = if url_safe {
810 &alphabet::URL_SAFE
811 } else {
812 &alphabet::STANDARD
813 };
814 let old = GeneralPurpose::new(alpha, PAD)
815 .decode(&input)
816 .or_else(|_| GeneralPurpose::new(alpha, NO_PAD).decode(&input));
817 let new = decode_base64(text, url_safe);
818 assert_eq!(new.ok(), old.ok(), "{text:?}, url_safe={url_safe}");
819 }
820 }
821 }
822 }
823
824 #[test]
825 fn hex_round_trips_and_rejects_bad_input() {
826 let bytes = [0x00_u8, 0x0f, 0xa0, 0xff];
827 assert_eq!(to_hex(&bytes), "000fa0ff");
828 assert_eq!(from_hex(&units("000fA0Ff")).unwrap(), bytes);
829 assert!(
830 from_hex(&units("0"))
831 .unwrap_err()
832 .to_string()
833 .contains("even number of characters")
834 );
835 assert!(
836 from_hex(&units("0g"))
837 .unwrap_err()
838 .to_string()
839 .contains("invalid hex digit")
840 );
841 }
842}