1use serde_json::Value as JsonValue;
8use std::fmt;
9
10pub use reddb_types::encoding::base64_encode as base64_std;
11
12use super::{BuildError, Frame, FrameBuilder, MessageKind, MAX_KNOWN_MINOR_VERSION};
13
14pub const SUPPORTED_METHODS: &[&str] = &["bearer", "anonymous", "scram-sha-256", "oauth-jwt"];
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Hello {
19 pub versions: Vec<u8>,
20 pub auth_methods: Vec<String>,
21 pub features: u32,
22 pub client_name: Option<String>,
23}
24
25impl Hello {
26 pub fn to_payload(&self) -> Vec<u8> {
27 build_hello_payload(
28 &self.versions,
29 self.auth_methods.iter().map(String::as_str),
30 self.features,
31 self.client_name.as_deref(),
32 )
33 }
34
35 pub fn from_payload(bytes: &[u8]) -> Result<Self, String> {
36 let v: JsonValue =
37 serde_json::from_slice(bytes).map_err(|e| format!("Hello: invalid JSON: {e}"))?;
38 let obj = match v {
39 JsonValue::Object(o) => o,
40 _ => return Err("Hello: payload must be a JSON object".into()),
41 };
42 let versions: Vec<u8> = obj
43 .get("versions")
44 .and_then(|v| v.as_array())
45 .map(|arr| {
46 arr.iter()
47 .filter_map(|n| n.as_u64().map(|u| u as u8))
48 .collect()
49 })
50 .unwrap_or_default();
51 let auth_methods: Vec<String> = obj
52 .get("auth_methods")
53 .and_then(|v| v.as_array())
54 .map(|arr| {
55 arr.iter()
56 .filter_map(|s| s.as_str().map(String::from))
57 .collect()
58 })
59 .unwrap_or_default();
60 let features = obj
61 .get("features")
62 .and_then(|v| v.as_u64())
63 .map(|u| u as u32)
64 .unwrap_or(0);
65 let client_name = obj
66 .get("client_name")
67 .and_then(|v| v.as_str())
68 .map(String::from);
69 if versions.is_empty() {
70 return Err("Hello: versions[] is empty".into());
71 }
72 if auth_methods.is_empty() {
73 return Err("Hello: auth_methods[] is empty".into());
74 }
75 Ok(Self {
76 versions,
77 auth_methods,
78 features,
79 client_name,
80 })
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct HelloAck {
86 pub version: u8,
87 pub auth: String,
88 pub features: u32,
89 pub server: Option<String>,
90 pub topology: Option<String>,
91}
92
93impl HelloAck {
94 pub fn from_payload(bytes: &[u8]) -> Result<Self, String> {
95 let obj = object_from_payload("HelloAck", bytes)?;
96 let version = required_u8(&obj, "HelloAck", "version")?;
97 let auth = required_string(&obj, "HelloAck", "auth")?;
98 let features = optional_u32(&obj, "features").unwrap_or(0);
99 let server = optional_string(&obj, "server");
100 let topology = optional_string(&obj, "topology");
101 Ok(Self {
102 version,
103 auth,
104 features,
105 server,
106 topology,
107 })
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct AuthOk {
113 pub session_id: String,
114 pub username: Option<String>,
115 pub role: Option<String>,
116 pub features: u32,
117 pub server_signature: Option<String>,
118}
119
120impl AuthOk {
121 pub fn from_payload(bytes: &[u8]) -> Result<Self, String> {
122 let obj = object_from_payload("AuthOk", bytes)?;
123 let session_id = required_string(&obj, "AuthOk", "session_id")?;
124 let username = optional_string(&obj, "username");
125 let role = optional_string(&obj, "role");
126 let features = optional_u32(&obj, "features").unwrap_or(0);
127 let server_signature = optional_string(&obj, "v");
128 Ok(Self {
129 session_id,
130 username,
131 role,
132 features,
133 server_signature,
134 })
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct AuthFail {
140 pub reason: String,
141}
142
143impl AuthFail {
144 pub fn from_payload(bytes: &[u8]) -> Result<Self, String> {
145 let obj = object_from_payload("AuthFail", bytes)?;
146 Ok(Self {
147 reason: required_string(&obj, "AuthFail", "reason")?,
148 })
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct AuthResponseKindError {
154 pub expected: &'static str,
155 pub actual: MessageKind,
156}
157
158impl fmt::Display for AuthResponseKindError {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 write!(f, "expected {}", self.expected)
161 }
162}
163
164impl std::error::Error for AuthResponseKindError {}
165
166pub fn build_hello_payload<'a, I>(
167 versions: &[u8],
168 auth_methods: I,
169 features: u32,
170 client_name: Option<&str>,
171) -> Vec<u8>
172where
173 I: IntoIterator<Item = &'a str>,
174{
175 let mut obj = serde_json::Map::new();
176 obj.insert(
177 "versions".to_string(),
178 JsonValue::Array(
179 versions
180 .iter()
181 .map(|version| JsonValue::Number((*version).into()))
182 .collect(),
183 ),
184 );
185 obj.insert(
186 "auth_methods".to_string(),
187 JsonValue::Array(
188 auth_methods
189 .into_iter()
190 .map(|method| JsonValue::String(method.to_string()))
191 .collect(),
192 ),
193 );
194 obj.insert("features".to_string(), JsonValue::Number(features.into()));
195 if let Some(name) = client_name {
196 obj.insert(
197 "client_name".to_string(),
198 JsonValue::String(name.to_string()),
199 );
200 }
201 serde_json::to_vec(&JsonValue::Object(obj)).unwrap_or_default()
202}
203
204pub fn build_client_hello_payload<'a, I>(
205 auth_methods: I,
206 features: u32,
207 client_name: Option<&str>,
208) -> Vec<u8>
209where
210 I: IntoIterator<Item = &'a str>,
211{
212 build_hello_payload(
213 &[MAX_KNOWN_MINOR_VERSION],
214 auth_methods,
215 features,
216 client_name,
217 )
218}
219
220pub fn build_client_hello_frame<'a, I>(
221 correlation_id: u64,
222 auth_methods: I,
223 features: u32,
224 client_name: Option<&str>,
225) -> Result<Frame, BuildError>
226where
227 I: IntoIterator<Item = &'a str>,
228{
229 FrameBuilder::request(correlation_id)
230 .kind(MessageKind::Hello)
231 .payload(build_client_hello_payload(
232 auth_methods,
233 features,
234 client_name,
235 ))
236 .build()
237}
238
239pub fn choose_hello_minor_version(client_versions: &[u8]) -> Option<u8> {
240 client_versions
241 .iter()
242 .copied()
243 .filter(|version| *version > 0 && *version <= MAX_KNOWN_MINOR_VERSION)
244 .max()
245}
246
247pub fn build_hello_ack(
248 chosen_version: u8,
249 chosen_auth: &str,
250 server_features: u32,
251 topology: Option<&crate::topology::Topology>,
252) -> Vec<u8> {
253 let mut obj = serde_json::Map::new();
254 obj.insert(
255 "version".to_string(),
256 JsonValue::Number(chosen_version.into()),
257 );
258 obj.insert(
259 "auth".to_string(),
260 JsonValue::String(chosen_auth.to_string()),
261 );
262 obj.insert(
263 "features".to_string(),
264 JsonValue::Number(server_features.into()),
265 );
266 obj.insert(
267 "server".to_string(),
268 JsonValue::String(format!("reddb/{}", env!("CARGO_PKG_VERSION"))),
269 );
270 if let Some(topo) = topology {
271 obj.insert(
272 "topology".to_string(),
273 JsonValue::String(crate::topology::encode_topology_for_hello_ack(topo)),
274 );
275 }
276 serde_json::to_vec(&JsonValue::Object(obj)).unwrap_or_default()
277}
278
279pub fn build_hello_ack_frame(
280 correlation_id: u64,
281 chosen_version: u8,
282 chosen_auth: &str,
283 server_features: u32,
284 topology: Option<&crate::topology::Topology>,
285) -> Result<Frame, BuildError> {
286 FrameBuilder::reply_to(correlation_id)
287 .kind(MessageKind::HelloAck)
288 .payload(build_hello_ack(
289 chosen_version,
290 chosen_auth,
291 server_features,
292 topology,
293 ))
294 .build()
295}
296
297pub fn build_auth_response_anonymous_payload() -> Vec<u8> {
298 Vec::new()
299}
300
301pub fn build_auth_response_bearer_payload(token: &str) -> Vec<u8> {
302 let mut obj = serde_json::Map::new();
303 obj.insert("token".to_string(), JsonValue::String(token.to_string()));
304 serde_json::to_vec(&JsonValue::Object(obj)).unwrap_or_default()
305}
306
307pub fn parse_auth_response_bearer_token(payload: &[u8]) -> Result<String, String> {
308 let obj = object_from_payload("AuthResponse", payload)?;
309 required_string(&obj, "AuthResponse", "token")
310}
311
312pub fn build_auth_response_oauth_jwt_payload(jwt: &str) -> Vec<u8> {
313 let mut obj = serde_json::Map::new();
314 obj.insert("jwt".to_string(), JsonValue::String(jwt.to_string()));
315 serde_json::to_vec(&JsonValue::Object(obj)).unwrap_or_default()
316}
317
318pub fn build_auth_response_frame(
319 correlation_id: u64,
320 payload: Vec<u8>,
321) -> Result<Frame, BuildError> {
322 FrameBuilder::request(correlation_id)
323 .kind(MessageKind::AuthResponse)
324 .payload(payload)
325 .build()
326}
327
328pub fn parse_auth_response_oauth_jwt(payload: &[u8]) -> Result<String, String> {
329 let obj = object_from_payload("AuthResponse", payload)?;
330 required_string(&obj, "AuthResponse", "jwt")
331}
332
333pub fn expect_auth_response_payload<'a>(
334 kind: MessageKind,
335 payload: &'a [u8],
336 expected: &'static str,
337) -> Result<&'a [u8], AuthResponseKindError> {
338 if kind == MessageKind::AuthResponse {
339 Ok(payload)
340 } else {
341 Err(AuthResponseKindError {
342 expected,
343 actual: kind,
344 })
345 }
346}
347
348pub fn build_auth_ok_payload(
349 session_id: &str,
350 username: &str,
351 role: &str,
352 server_features: u32,
353) -> Vec<u8> {
354 let mut obj = serde_json::Map::new();
355 obj.insert(
356 "session_id".to_string(),
357 JsonValue::String(session_id.to_string()),
358 );
359 obj.insert(
360 "username".to_string(),
361 JsonValue::String(username.to_string()),
362 );
363 obj.insert("role".to_string(), JsonValue::String(role.to_string()));
364 obj.insert(
365 "features".to_string(),
366 JsonValue::Number(server_features.into()),
367 );
368 serde_json::to_vec(&JsonValue::Object(obj)).unwrap_or_default()
369}
370
371pub fn build_auth_ok_frame_from_payload(
372 correlation_id: u64,
373 payload: Vec<u8>,
374) -> Result<Frame, BuildError> {
375 FrameBuilder::reply_to(correlation_id)
376 .kind(MessageKind::AuthOk)
377 .payload(payload)
378 .build()
379}
380
381pub fn build_auth_fail_frame(correlation_id: u64, reason: &str) -> Result<Frame, BuildError> {
382 FrameBuilder::reply_to(correlation_id)
383 .kind(MessageKind::AuthFail)
384 .payload(build_auth_fail_payload(reason))
385 .build()
386}
387
388pub fn build_scram_auth_ok_payload(
389 session_id: &str,
390 username: &str,
391 role: &str,
392 server_features: u32,
393 server_signature: &[u8],
394) -> Vec<u8> {
395 let mut obj = serde_json::Map::new();
396 obj.insert(
397 "session_id".to_string(),
398 JsonValue::String(session_id.to_string()),
399 );
400 obj.insert(
401 "username".to_string(),
402 JsonValue::String(username.to_string()),
403 );
404 obj.insert("role".to_string(), JsonValue::String(role.to_string()));
405 obj.insert(
406 "features".to_string(),
407 JsonValue::Number(server_features.into()),
408 );
409 obj.insert(
410 "v".to_string(),
411 JsonValue::String(base64_std(server_signature)),
412 );
413 serde_json::to_vec(&JsonValue::Object(obj)).unwrap_or_default()
414}
415
416pub fn build_auth_fail_payload(reason: &str) -> Vec<u8> {
417 let mut obj = serde_json::Map::new();
418 obj.insert("reason".to_string(), JsonValue::String(reason.to_string()));
419 serde_json::to_vec(&JsonValue::Object(obj)).unwrap_or_default()
420}
421
422pub fn parse_scram_client_first(payload: &[u8]) -> Result<(String, String, String), String> {
427 let s = std::str::from_utf8(payload).map_err(|_| "client-first not UTF-8".to_string())?;
428 let bare = s
429 .strip_prefix("n,,")
430 .ok_or_else(|| "client-first must start with 'n,,' (no channel binding)".to_string())?;
431 let mut user = None;
432 let mut nonce = None;
433 for part in bare.split(',') {
434 if let Some(v) = part.strip_prefix("n=") {
435 user = Some(v.to_string());
436 } else if let Some(v) = part.strip_prefix("r=") {
437 nonce = Some(v.to_string());
438 }
439 }
440 let user = user.ok_or_else(|| "missing n=<user>".to_string())?;
441 let nonce = nonce.ok_or_else(|| "missing r=<nonce>".to_string())?;
442 Ok((user, nonce, bare.to_string()))
443}
444
445pub fn build_scram_server_first(
449 client_nonce: &str,
450 server_nonce: &str,
451 salt: &[u8],
452 iter: u32,
453) -> String {
454 format!(
455 "r={client_nonce}{server_nonce},s={},i={iter}",
456 base64_std(salt)
457 )
458}
459
460pub fn parse_scram_client_final(payload: &[u8]) -> Result<(String, Vec<u8>, String), String> {
464 let s = std::str::from_utf8(payload).map_err(|_| "client-final not UTF-8".to_string())?;
465 let mut channel_binding = None;
466 let mut nonce = None;
467 let mut proof_b64 = None;
468 for part in s.split(',') {
469 if let Some(v) = part.strip_prefix("c=") {
470 channel_binding = Some(v.to_string());
471 } else if let Some(v) = part.strip_prefix("r=") {
472 nonce = Some(v.to_string());
473 } else if let Some(v) = part.strip_prefix("p=") {
474 proof_b64 = Some(v.to_string());
475 }
476 }
477 let channel_binding =
478 channel_binding.ok_or_else(|| "missing c=<channel-binding>".to_string())?;
479 let nonce = nonce.ok_or_else(|| "missing r=<nonce>".to_string())?;
480 let proof_b64 = proof_b64.ok_or_else(|| "missing p=<proof>".to_string())?;
481 let proof = base64_std_decode(&proof_b64)
482 .ok_or_else(|| "client proof is not valid base64".to_string())?;
483 if channel_binding != "biws" {
484 return Err(format!(
485 "channel binding must be 'biws' (n,,), got '{channel_binding}'"
486 ));
487 }
488 let no_proof = format!("c={channel_binding},r={nonce}");
489 Ok((nonce, proof, no_proof))
490}
491
492pub fn base64_std_decode(input: &str) -> Option<Vec<u8>> {
493 let trimmed = input.trim_end_matches('=');
494 let mut out = Vec::with_capacity(trimmed.len() * 3 / 4);
495 let mut buf = 0u32;
496 let mut bits = 0u8;
497 for ch in trimmed.bytes() {
498 let v: u32 = match ch {
499 b'A'..=b'Z' => (ch - b'A') as u32,
500 b'a'..=b'z' => (ch - b'a' + 26) as u32,
501 b'0'..=b'9' => (ch - b'0' + 52) as u32,
502 b'+' => 62,
503 b'/' => 63,
504 _ => return None,
505 };
506 buf = (buf << 6) | v;
507 bits += 6;
508 if bits >= 8 {
509 bits -= 8;
510 out.push(((buf >> bits) & 0xFF) as u8);
511 }
512 }
513 Some(out)
514}
515
516fn object_from_payload(
517 name: &str,
518 bytes: &[u8],
519) -> Result<serde_json::Map<String, JsonValue>, String> {
520 let v: JsonValue =
521 serde_json::from_slice(bytes).map_err(|e| format!("{name}: invalid JSON: {e}"))?;
522 match v {
523 JsonValue::Object(o) => Ok(o),
524 _ => Err(format!("{name}: payload must be a JSON object")),
525 }
526}
527
528fn required_string(
529 obj: &serde_json::Map<String, JsonValue>,
530 name: &str,
531 field: &str,
532) -> Result<String, String> {
533 obj.get(field)
534 .and_then(JsonValue::as_str)
535 .map(String::from)
536 .ok_or_else(|| format!("{name}: missing {field} string"))
537}
538
539fn optional_string(obj: &serde_json::Map<String, JsonValue>, field: &str) -> Option<String> {
540 obj.get(field).and_then(JsonValue::as_str).map(String::from)
541}
542
543fn required_u8(
544 obj: &serde_json::Map<String, JsonValue>,
545 name: &str,
546 field: &str,
547) -> Result<u8, String> {
548 let n = obj
549 .get(field)
550 .and_then(JsonValue::as_u64)
551 .ok_or_else(|| format!("{name}: missing {field} number"))?;
552 u8::try_from(n).map_err(|_| format!("{name}: {field} out of range for u8"))
553}
554
555fn optional_u32(obj: &serde_json::Map<String, JsonValue>, field: &str) -> Option<u32> {
556 obj.get(field)
557 .and_then(JsonValue::as_u64)
558 .and_then(|n| u32::try_from(n).ok())
559}
560
561#[cfg(test)]
562mod tests {
563 use super::*;
564 use crate::topology::{Endpoint, ReplicaInfo, Topology};
565
566 #[test]
567 fn hello_parses_client_payload() {
568 let payload =
569 br#"{"versions":[1],"auth_methods":["bearer"],"features":1,"client_name":"x"}"#;
570 let hello = Hello::from_payload(payload).unwrap();
571 assert_eq!(hello.versions, vec![1]);
572 assert_eq!(hello.auth_methods, vec!["bearer"]);
573 assert_eq!(hello.features, 1);
574 assert_eq!(hello.client_name.as_deref(), Some("x"));
575 }
576
577 #[test]
578 fn hello_builds_client_payload() {
579 let bytes = build_hello_payload(&[1], ["anonymous", "bearer"], 7, Some("client"));
580 let hello = Hello::from_payload(&bytes).unwrap();
581 assert_eq!(hello.versions, vec![1]);
582 assert_eq!(hello.auth_methods, vec!["anonymous", "bearer"]);
583 assert_eq!(hello.features, 7);
584 assert_eq!(hello.client_name.as_deref(), Some("client"));
585 }
586
587 #[test]
588 fn client_hello_payload_uses_current_minor_version() {
589 let bytes = build_client_hello_payload(["anonymous"], 0, Some("client"));
590 let hello = Hello::from_payload(&bytes).unwrap();
591 assert_eq!(hello.versions, vec![MAX_KNOWN_MINOR_VERSION]);
592 assert_eq!(hello.auth_methods, vec!["anonymous"]);
593 assert_eq!(hello.client_name.as_deref(), Some("client"));
594 }
595
596 #[test]
597 fn hello_minor_version_negotiation_picks_highest_supported_nonzero_version() {
598 assert_eq!(
599 choose_hello_minor_version(&[0, MAX_KNOWN_MINOR_VERSION]),
600 Some(MAX_KNOWN_MINOR_VERSION)
601 );
602 assert_eq!(
603 choose_hello_minor_version(&[
604 MAX_KNOWN_MINOR_VERSION.saturating_add(1),
605 MAX_KNOWN_MINOR_VERSION,
606 1,
607 ]),
608 Some(MAX_KNOWN_MINOR_VERSION)
609 );
610 assert_eq!(choose_hello_minor_version(&[]), None);
611 assert_eq!(choose_hello_minor_version(&[0]), None);
612 assert_eq!(
613 choose_hello_minor_version(&[MAX_KNOWN_MINOR_VERSION.saturating_add(1)]),
614 None
615 );
616 }
617
618 #[test]
619 fn hello_requires_versions_and_auth_methods() {
620 assert!(Hello::from_payload(br#"{"auth_methods":["bearer"]}"#).is_err());
621 assert!(Hello::from_payload(br#"{"versions":[1]}"#).is_err());
622 }
623
624 #[test]
625 fn hello_ack_can_embed_topology() {
626 let topology = Topology {
627 epoch: 7,
628 primary: Endpoint {
629 addr: "127.0.0.1:5050".to_string(),
630 region: "local".to_string(),
631 },
632 replicas: vec![ReplicaInfo {
633 addr: "127.0.0.1:5051".to_string(),
634 region: "local".to_string(),
635 healthy: true,
636 lag_ms: 3,
637 last_applied_lsn: 9,
638 rebootstrapping: false,
639 }],
640 };
641 let bytes = build_hello_ack(1, "bearer", 0, Some(&topology));
642 let json: JsonValue = serde_json::from_slice(&bytes).unwrap();
643 assert_eq!(json["version"], 1);
644 assert!(json["topology"].as_str().is_some());
645 let ack = HelloAck::from_payload(&bytes).unwrap();
646 assert_eq!(ack.version, 1);
647 assert_eq!(ack.auth, "bearer");
648 assert_eq!(ack.features, 0);
649 assert!(ack.topology.is_some());
650 }
651
652 #[test]
653 fn auth_response_builders_are_pinned() {
654 assert!(build_auth_response_anonymous_payload().is_empty());
655
656 let bearer: JsonValue =
657 serde_json::from_slice(&build_auth_response_bearer_payload("token")).unwrap();
658 assert_eq!(bearer["token"], "token");
659
660 let oauth: JsonValue =
661 serde_json::from_slice(&build_auth_response_oauth_jwt_payload("jwt")).unwrap();
662 assert_eq!(oauth["jwt"], "jwt");
663 }
664
665 #[test]
666 fn auth_response_kind_expectation_is_pinned() {
667 assert_eq!(
668 expect_auth_response_payload(MessageKind::AuthResponse, b"proof", "AuthResponse")
669 .unwrap(),
670 b"proof"
671 );
672
673 let err =
674 expect_auth_response_payload(MessageKind::Hello, b"{}", "AuthResponse").unwrap_err();
675 assert_eq!(err.actual, MessageKind::Hello);
676 assert_eq!(err.to_string(), "expected AuthResponse");
677 }
678
679 #[test]
680 fn auth_ok_and_fail_parse_payloads() {
681 let ok = AuthOk::from_payload(&build_auth_ok_payload("s1", "alice", "admin", 3)).unwrap();
682 assert_eq!(ok.session_id, "s1");
683 assert_eq!(ok.username.as_deref(), Some("alice"));
684 assert_eq!(ok.role.as_deref(), Some("admin"));
685 assert_eq!(ok.features, 3);
686 assert_eq!(ok.server_signature.as_deref(), None);
687
688 let scram_ok = AuthOk::from_payload(&build_scram_auth_ok_payload(
689 "s1", "alice", "admin", 3, b"sig",
690 ))
691 .unwrap();
692 assert_eq!(scram_ok.server_signature.as_deref(), Some("c2ln"));
693
694 let fail = AuthFail::from_payload(&build_auth_fail_payload("nope")).unwrap();
695 assert_eq!(fail.reason, "nope");
696 }
697
698 #[test]
699 fn handshake_frame_builders_pin_message_kinds() {
700 let hello_ack = build_hello_ack_frame(7, 1, "anonymous", 3, None).unwrap();
701 assert_eq!(hello_ack.kind, MessageKind::HelloAck);
702 assert_eq!(hello_ack.correlation_id, 7);
703 assert_eq!(
704 HelloAck::from_payload(&hello_ack.payload).unwrap().auth,
705 "anonymous"
706 );
707
708 let auth_ok =
709 build_auth_ok_frame_from_payload(8, build_auth_ok_payload("s1", "alice", "admin", 3))
710 .unwrap();
711 assert_eq!(auth_ok.kind, MessageKind::AuthOk);
712 assert_eq!(auth_ok.correlation_id, 8);
713 assert_eq!(
714 AuthOk::from_payload(&auth_ok.payload)
715 .unwrap()
716 .username
717 .as_deref(),
718 Some("alice")
719 );
720
721 let auth_fail = build_auth_fail_frame(9, "nope").unwrap();
722 assert_eq!(auth_fail.kind, MessageKind::AuthFail);
723 assert_eq!(auth_fail.correlation_id, 9);
724 assert_eq!(
725 AuthFail::from_payload(&auth_fail.payload).unwrap().reason,
726 "nope"
727 );
728 }
729
730 #[test]
731 fn auth_response_parsers_are_pinned() {
732 assert_eq!(
733 parse_auth_response_bearer_token(&build_auth_response_bearer_payload("token")).unwrap(),
734 "token"
735 );
736 assert_eq!(
737 parse_auth_response_oauth_jwt(&build_auth_response_oauth_jwt_payload("jwt")).unwrap(),
738 "jwt"
739 );
740 assert!(parse_auth_response_bearer_token(br#"{"jwt":"x"}"#).is_err());
741 }
742
743 #[test]
744 fn scram_wire_messages_round_trip() {
745 let (user, nonce, bare) = parse_scram_client_first(b"n,,n=alice,r=client").unwrap();
746 assert_eq!(user, "alice");
747 assert_eq!(nonce, "client");
748 assert_eq!(bare, "n=alice,r=client");
749
750 let server_first = build_scram_server_first("client", "server", b"salt", 4096);
751 assert_eq!(server_first, "r=clientserver,s=c2FsdA==,i=4096");
752
753 let proof = base64_std(b"proof");
754 let final_msg = format!("c=biws,r=clientserver,p={proof}");
755 let (combined, decoded_proof, without_proof) =
756 parse_scram_client_final(final_msg.as_bytes()).unwrap();
757 assert_eq!(combined, "clientserver");
758 assert_eq!(decoded_proof, b"proof");
759 assert_eq!(without_proof, "c=biws,r=clientserver");
760 }
761}