1#[repr(u8)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[non_exhaustive]
22pub enum NackReason {
23 BadMagic = 0x01,
25 VersionMismatch = 0x02,
28 BadHeaderCrc = 0x03,
30 PayloadTooLarge = 0x04,
32 BadPayloadCrc = 0x05,
34 InternalError = 0x06,
36 EmptyPayload = 0x07,
41 UnknownMessage = 0x08,
48 ReservedFlagsSet = 0x09,
54}
55
56impl NackReason {
57 #[must_use]
70 pub fn as_metric_label(&self) -> &'static str {
71 match self {
72 NackReason::BadMagic => "bad_magic",
73 NackReason::VersionMismatch => "version_mismatch",
74 NackReason::BadHeaderCrc => "bad_header_crc",
75 NackReason::PayloadTooLarge => "payload_too_large",
76 NackReason::BadPayloadCrc => "bad_payload_crc",
77 NackReason::InternalError => "internal_error",
78 NackReason::EmptyPayload => "empty_payload",
79 NackReason::UnknownMessage => "unknown_message",
80 NackReason::ReservedFlagsSet => "reserved_flags_set",
81 }
82 }
83
84 #[must_use]
97 pub fn from_metric_label(s: &str) -> Option<NackReason> {
98 match s {
99 "bad_magic" => Some(NackReason::BadMagic),
100 "version_mismatch" => Some(NackReason::VersionMismatch),
101 "bad_header_crc" => Some(NackReason::BadHeaderCrc),
102 "payload_too_large" => Some(NackReason::PayloadTooLarge),
103 "bad_payload_crc" => Some(NackReason::BadPayloadCrc),
104 "internal_error" => Some(NackReason::InternalError),
105 "empty_payload" => Some(NackReason::EmptyPayload),
106 "unknown_message" => Some(NackReason::UnknownMessage),
107 "reserved_flags_set" => Some(NackReason::ReservedFlagsSet),
108 _ => None,
109 }
110 }
111}
112
113impl From<NackReason> for u8 {
114 fn from(reason: NackReason) -> u8 {
116 reason as u8
117 }
118}
119
120impl std::fmt::Display for NackReason {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 let msg = match self {
123 NackReason::BadMagic => "bad frame magic",
124 NackReason::VersionMismatch => "wire protocol version mismatch",
125 NackReason::BadHeaderCrc => "header CRC mismatch",
126 NackReason::PayloadTooLarge => "payload exceeds the daemon's cap",
127 NackReason::BadPayloadCrc => "payload CRC mismatch",
128 NackReason::InternalError => "daemon internal error (transient)",
129 NackReason::EmptyPayload => "empty payload (not representable)",
130 NackReason::UnknownMessage => "unknown message type or durability byte",
131 NackReason::ReservedFlagsSet => "reserved flags byte was non-zero",
132 };
133 write!(f, "{msg}")
134 }
135}
136
137impl std::error::Error for NackReason {}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub struct UnknownNackReason(pub u8);
146
147impl std::fmt::Display for UnknownNackReason {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 write!(f, "unknown nack reason byte: {:#04x}", self.0)
150 }
151}
152
153impl std::error::Error for UnknownNackReason {}
154
155impl TryFrom<u8> for NackReason {
156 type Error = UnknownNackReason;
157
158 fn try_from(value: u8) -> Result<Self, Self::Error> {
159 match value {
160 0x01 => Ok(NackReason::BadMagic),
161 0x02 => Ok(NackReason::VersionMismatch),
162 0x03 => Ok(NackReason::BadHeaderCrc),
163 0x04 => Ok(NackReason::PayloadTooLarge),
164 0x05 => Ok(NackReason::BadPayloadCrc),
165 0x06 => Ok(NackReason::InternalError),
166 0x07 => Ok(NackReason::EmptyPayload),
167 0x08 => Ok(NackReason::UnknownMessage),
168 0x09 => Ok(NackReason::ReservedFlagsSet),
169 v => Err(UnknownNackReason(v)),
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use crate::version::WIRE_VERSION;
178
179 #[test]
180 fn try_from_accepts_all_known_reasons() {
181 assert_eq!(NackReason::try_from(0x01).unwrap(), NackReason::BadMagic);
182 assert_eq!(
183 NackReason::try_from(0x02).unwrap(),
184 NackReason::VersionMismatch
185 );
186 assert_eq!(
187 NackReason::try_from(0x03).unwrap(),
188 NackReason::BadHeaderCrc
189 );
190 assert_eq!(
191 NackReason::try_from(0x04).unwrap(),
192 NackReason::PayloadTooLarge
193 );
194 assert_eq!(
195 NackReason::try_from(0x05).unwrap(),
196 NackReason::BadPayloadCrc
197 );
198 assert_eq!(
199 NackReason::try_from(0x06).unwrap(),
200 NackReason::InternalError
201 );
202 assert_eq!(
203 NackReason::try_from(0x07).unwrap(),
204 NackReason::EmptyPayload
205 );
206 assert_eq!(
207 NackReason::try_from(0x08).unwrap(),
208 NackReason::UnknownMessage
209 );
210 assert_eq!(
211 NackReason::try_from(0x09).unwrap(),
212 NackReason::ReservedFlagsSet
213 );
214 }
215
216 #[test]
217 fn from_for_u8_is_inverse_of_try_from_and_display_is_nonempty() {
218 for byte in 0x01u8..=0x09 {
219 let r = NackReason::try_from(byte).unwrap();
220 assert_eq!(u8::from(r), byte);
221 assert!(!r.to_string().is_empty());
222 }
223 let _: &dyn std::error::Error = &NackReason::BadMagic;
225 }
226
227 #[test]
228 fn try_from_returns_unknown_for_unrecognised_byte() {
229 let err = NackReason::try_from(0x0A).unwrap_err();
231 assert_eq!(err.0, 0x0A);
232 let err = NackReason::try_from(0x00).unwrap_err();
233 assert_eq!(err.0, 0x00);
234 let err = NackReason::try_from(0xff).unwrap_err();
235 assert_eq!(err.0, 0xff);
236 }
237
238 #[test]
239 fn repr_values_match_wire() {
240 assert_eq!(NackReason::BadMagic as u8, 0x01);
241 assert_eq!(NackReason::VersionMismatch as u8, 0x02);
242 assert_eq!(NackReason::BadHeaderCrc as u8, 0x03);
243 assert_eq!(NackReason::PayloadTooLarge as u8, 0x04);
244 assert_eq!(NackReason::BadPayloadCrc as u8, 0x05);
245 assert_eq!(NackReason::InternalError as u8, 0x06);
246 assert_eq!(NackReason::EmptyPayload as u8, 0x07);
247 assert_eq!(NackReason::UnknownMessage as u8, 0x08);
248 assert_eq!(NackReason::ReservedFlagsSet as u8, 0x09);
249 }
250
251 #[test]
252 fn as_metric_label_pins_every_variant() {
253 assert_eq!(NackReason::BadMagic.as_metric_label(), "bad_magic");
258 assert_eq!(
259 NackReason::VersionMismatch.as_metric_label(),
260 "version_mismatch"
261 );
262 assert_eq!(NackReason::BadHeaderCrc.as_metric_label(), "bad_header_crc");
263 assert_eq!(
264 NackReason::PayloadTooLarge.as_metric_label(),
265 "payload_too_large"
266 );
267 assert_eq!(
268 NackReason::BadPayloadCrc.as_metric_label(),
269 "bad_payload_crc"
270 );
271 assert_eq!(
272 NackReason::InternalError.as_metric_label(),
273 "internal_error"
274 );
275 assert_eq!(NackReason::EmptyPayload.as_metric_label(), "empty_payload");
276 assert_eq!(
277 NackReason::UnknownMessage.as_metric_label(),
278 "unknown_message"
279 );
280 assert_eq!(
281 NackReason::ReservedFlagsSet.as_metric_label(),
282 "reserved_flags_set"
283 );
284 }
285
286 #[test]
287 fn metric_label_round_trips_for_every_variant() {
288 for byte in 0x01u8..=0x09 {
289 let r = NackReason::try_from(byte).unwrap();
290 assert_eq!(
291 NackReason::from_metric_label(r.as_metric_label()),
292 Some(r),
293 "metric label for {r:?} did not round-trip"
294 );
295 }
296 assert_eq!(NackReason::from_metric_label("not_a_reason"), None);
298 assert_eq!(NackReason::from_metric_label(""), None);
299 }
300
301 #[test]
304 fn version_mismatch_nack_payload_encodes_daemon_version() {
305 let payload = [NackReason::VersionMismatch as u8, WIRE_VERSION];
306 assert_eq!(payload, [0x02, 0x01]);
307 }
308}