1use core::fmt;
2
3pub const PREPARED_CANDIDATE_FORMAT_VERSION: u16 = 1;
4pub const PREPARED_CANDIDATE_ALGORITHM_VERSION: u16 = 2;
5pub const PREPARED_CANDIDATE_HEADER_LEN: usize = 16;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct PreparedCandidateHeader {
9 pub format_version: u16,
10 pub algorithm_version: u16,
11 pub fingerprint: u64,
12 pub text_len: u32,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum EncodeError {
17 BufferTooSmall { required: usize },
18 TextTooLong { len: usize },
19}
20
21impl fmt::Display for EncodeError {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 match self {
24 Self::BufferTooSmall { required } => {
25 write!(
26 f,
27 "buffer too small for prepared candidate archive: need {required} bytes"
28 )
29 }
30 Self::TextTooLong { len } => {
31 write!(
32 f,
33 "prepared candidate text length {len} exceeds u32 archive header"
34 )
35 }
36 }
37 }
38}
39
40impl std::error::Error for EncodeError {}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum DecodeError {
44 Truncated { required: usize, actual: usize },
45 UnsupportedFormatVersion { expected: u16, actual: u16 },
46 UnsupportedAlgorithmVersion { expected: u16, actual: u16 },
47 FingerprintMismatch { expected: u64, actual: u64 },
48 PlatformLengthOverflow { text_len: u32 },
49 InvalidUtf8,
50}
51
52impl fmt::Display for DecodeError {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 match self {
55 Self::Truncated { required, actual } => {
56 write!(
57 f,
58 "prepared candidate archive truncated: need {required} bytes, got {actual}"
59 )
60 }
61 Self::UnsupportedFormatVersion { expected, actual } => {
62 write!(
63 f,
64 "unsupported prepared candidate format version: expected {expected}, got {actual}"
65 )
66 }
67 Self::UnsupportedAlgorithmVersion { expected, actual } => {
68 write!(
69 f,
70 "unsupported prepared candidate algorithm version: expected {expected}, got {actual}"
71 )
72 }
73 Self::FingerprintMismatch { expected, actual } => {
74 write!(
75 f,
76 "prepared candidate fingerprint mismatch: expected {expected}, got {actual}"
77 )
78 }
79 Self::PlatformLengthOverflow { text_len } => {
80 write!(
81 f,
82 "prepared candidate archive length {text_len} does not fit on this platform"
83 )
84 }
85 Self::InvalidUtf8 => write!(f, "prepared candidate archive contains invalid utf-8"),
86 }
87 }
88}
89
90impl std::error::Error for DecodeError {}
91
92pub(crate) fn checked_archive_text_len(len: usize) -> Result<u32, EncodeError> {
93 u32::try_from(len).map_err(|_| EncodeError::TextTooLong { len })
94}
95
96pub(crate) fn decode_header(bytes: &[u8]) -> Result<PreparedCandidateHeader, DecodeError> {
97 if bytes.len() < PREPARED_CANDIDATE_HEADER_LEN {
98 return Err(DecodeError::Truncated {
99 required: PREPARED_CANDIDATE_HEADER_LEN,
100 actual: bytes.len(),
101 });
102 }
103
104 let format_version = u16::from_le_bytes([bytes[0], bytes[1]]);
105 let algorithm_version = u16::from_le_bytes([bytes[2], bytes[3]]);
106 let fingerprint = u64::from_le_bytes([
107 bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11],
108 ]);
109 let text_len = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
110 Ok(PreparedCandidateHeader {
111 format_version,
112 algorithm_version,
113 fingerprint,
114 text_len,
115 })
116}
117
118pub(crate) fn encode_candidate(
119 text: &str,
120 fingerprint: u64,
121 out: &mut [u8],
122) -> Result<usize, EncodeError> {
123 let required = encoded_len(text.len());
124 if out.len() < required {
125 return Err(EncodeError::BufferTooSmall { required });
126 }
127
128 let header = PreparedCandidateHeader {
129 format_version: PREPARED_CANDIDATE_FORMAT_VERSION,
130 algorithm_version: PREPARED_CANDIDATE_ALGORITHM_VERSION,
131 fingerprint,
132 text_len: checked_archive_text_len(text.len())?,
133 };
134 out[0..2].copy_from_slice(&header.format_version.to_le_bytes());
135 out[2..4].copy_from_slice(&header.algorithm_version.to_le_bytes());
136 out[4..12].copy_from_slice(&header.fingerprint.to_le_bytes());
137 out[12..16].copy_from_slice(&header.text_len.to_le_bytes());
138 out[16..required].copy_from_slice(text.as_bytes());
139 Ok(required)
140}
141
142pub(crate) fn encoded_len(text_len: usize) -> usize {
143 PREPARED_CANDIDATE_HEADER_LEN + text_len
144}
145
146pub(crate) fn decode_candidate_text(
147 bytes: &[u8],
148) -> Result<(&str, PreparedCandidateHeader), DecodeError> {
149 let header = decode_header(bytes)?;
150 if header.format_version != PREPARED_CANDIDATE_FORMAT_VERSION {
151 return Err(DecodeError::UnsupportedFormatVersion {
152 expected: PREPARED_CANDIDATE_FORMAT_VERSION,
153 actual: header.format_version,
154 });
155 }
156 if header.algorithm_version != PREPARED_CANDIDATE_ALGORITHM_VERSION {
157 return Err(DecodeError::UnsupportedAlgorithmVersion {
158 expected: PREPARED_CANDIDATE_ALGORITHM_VERSION,
159 actual: header.algorithm_version,
160 });
161 }
162
163 let text_len =
164 usize::try_from(header.text_len).map_err(|_| DecodeError::PlatformLengthOverflow {
165 text_len: header.text_len,
166 })?;
167 let required = PREPARED_CANDIDATE_HEADER_LEN + text_len;
168 if bytes.len() < required {
169 return Err(DecodeError::Truncated {
170 required,
171 actual: bytes.len(),
172 });
173 }
174
175 let text_bytes = &bytes[PREPARED_CANDIDATE_HEADER_LEN..required];
176 let text = core::str::from_utf8(text_bytes).map_err(|_| DecodeError::InvalidUtf8)?;
177 Ok((text, header))
178}