1use crate::{ffi, mem};
7
8pub fn base64_encode(data: &[u8]) -> String {
10 let (data_ptr, data_len) = mem::host_arg_bytes(data);
11 let result = unsafe { ffi::encoding_base64_encode(data_ptr, data_len) };
12 unsafe { mem::read_packed_string(result) }.unwrap_or_default()
14}
15
16pub fn base64_decode(input: &str) -> Option<Vec<u8>> {
20 let (data_ptr, data_len) = mem::host_arg_str(input);
21 let result = unsafe { ffi::encoding_base64_decode(data_ptr, data_len) };
22 unsafe { mem::read_packed_bytes(result) }
24}
25
26pub fn hex_encode(data: &[u8]) -> String {
28 let (data_ptr, data_len) = mem::host_arg_bytes(data);
29 let result = unsafe { ffi::encoding_hex_encode(data_ptr, data_len) };
30 unsafe { mem::read_packed_string(result) }.unwrap_or_default()
32}
33
34pub fn hex_decode(input: &str) -> Option<Vec<u8>> {
38 let (data_ptr, data_len) = mem::host_arg_str(input);
39 let result = unsafe { ffi::encoding_hex_decode(data_ptr, data_len) };
40 unsafe { mem::read_packed_bytes(result) }
42}
43
44pub fn url_encode(input: &str) -> String {
47 let (data_ptr, data_len) = mem::host_arg_str(input);
48 let result = unsafe { ffi::encoding_url_encode(data_ptr, data_len) };
49 unsafe { mem::read_packed_string(result) }.unwrap_or_default()
51}
52
53pub fn url_decode(input: &str) -> Option<String> {
57 let (data_ptr, data_len) = mem::host_arg_str(input);
58 let result = unsafe { ffi::encoding_url_decode(data_ptr, data_len) };
59 unsafe { mem::read_packed_string(result) }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use crate::ffi::test_host;
67
68 #[test]
69 fn base64_encode_passes_input_and_returns_response() {
70 test_host::reset();
71 test_host::with_mock(|m| {
72 m.encoding_base64_encode_response = Some("aGVsbG8=".into());
73 });
74 assert_eq!(base64_encode(b"hello"), "aGVsbG8=");
75 assert_eq!(
76 test_host::read_mock(|m| m.last_encoding_base64_encode_input.clone()),
77 Some(b"hello".to_vec())
78 );
79 }
80
81 #[test]
82 fn base64_encode_empty_when_host_silent() {
83 test_host::reset();
84 assert_eq!(base64_encode(b"x"), "");
85 }
86
87 #[test]
88 fn base64_decode_returns_bytes() {
89 test_host::reset();
90 test_host::with_mock(|m| {
91 m.encoding_base64_decode_response = Some(b"hello".to_vec());
92 });
93 assert_eq!(base64_decode("aGVsbG8="), Some(b"hello".to_vec()));
94 }
95
96 #[test]
97 fn base64_decode_none_on_failure() {
98 test_host::reset();
99 assert!(base64_decode("not base64!").is_none());
100 }
101
102 #[test]
103 fn hex_encode_round_trip_via_mock() {
104 test_host::reset();
105 test_host::with_mock(|m| {
106 m.encoding_hex_encode_response = Some("deadbeef".into());
107 m.encoding_hex_decode_response = Some(vec![0xde, 0xad, 0xbe, 0xef]);
108 });
109 let encoded = hex_encode(&[0xde, 0xad, 0xbe, 0xef]);
110 assert_eq!(encoded, "deadbeef");
111 let decoded = hex_decode(&encoded).unwrap();
112 assert_eq!(decoded, vec![0xde, 0xad, 0xbe, 0xef]);
113 }
114
115 #[test]
116 fn hex_decode_none_on_failure() {
117 test_host::reset();
118 assert!(hex_decode("zz").is_none());
119 }
120
121 #[test]
122 fn url_encode_passes_input() {
123 test_host::reset();
124 test_host::with_mock(|m| {
125 m.encoding_url_encode_response = Some("hello%20world".into());
126 });
127 assert_eq!(url_encode("hello world"), "hello%20world");
128 assert_eq!(
129 test_host::read_mock(|m| m.last_encoding_url_encode_input.clone()),
130 Some("hello world".into())
131 );
132 }
133
134 #[test]
135 fn url_decode_returns_some() {
136 test_host::reset();
137 test_host::with_mock(|m| {
138 m.encoding_url_decode_response = Some("hello world".into());
139 });
140 assert_eq!(url_decode("hello%20world").as_deref(), Some("hello world"));
141 }
142
143 #[test]
144 fn url_decode_none_on_failure() {
145 test_host::reset();
146 assert!(url_decode("%ZZ").is_none());
147 }
148}