1pub mod meta {
6 include!("generated/meta.rs");
7}
8#[allow(clippy::large_enum_variant)]
9pub mod client {
10 include!("generated/client.rs");
11}
12
13pub const CLIENT_PROTOCOL_VERSION: u32 = 1;
14pub const CLIENT_CAPABILITY_QUERY: u64 = 1;
15pub const CLIENT_CAPABILITY_SCAN: u64 = 2;
16pub const CLIENT_CAPABILITIES: u64 = CLIENT_CAPABILITY_QUERY | CLIENT_CAPABILITY_SCAN;
17pub const MAX_FRAME_BYTES: usize = 16 * 1024 * 1024;
18pub const MAX_BATCH_ITEMS: usize = 4096;
19pub const MAX_HANDSHAKE_BYTES: usize = 4096;
20
21pub fn client_auth_proof(
24 secret: &[u8],
25 dialer: bool,
26 client: &client::Hello,
27 server: &client::Hello,
28) -> Vec<u8> {
29 use hmac::{KeyInit, Mac};
30 let mut mac =
31 hmac::Hmac::<sha2::Sha256>::new_from_slice(secret).expect("HMAC accepts any key size");
32 mac.update(b"actordb-client-proof-v1\0");
33 mac.update(if dialer { b"client" } else { b"server" });
34 for hello in [client, server] {
35 mac.update(&(hello.identity.len() as u64).to_be_bytes());
36 mac.update(hello.identity.as_bytes());
37 mac.update(&hello.min_version.to_be_bytes());
38 mac.update(&hello.max_version.to_be_bytes());
39 mac.update(&hello.capabilities.to_be_bytes());
40 mac.update(&(hello.challenge.len() as u64).to_be_bytes());
41 mac.update(&hello.challenge);
42 }
43 mac.finalize().into_bytes().to_vec()
44}
45
46pub fn verify_client_auth(
47 secret: Option<&[u8]>,
48 dialer: bool,
49 client: &client::Hello,
50 server: &client::Hello,
51 proof: &[u8],
52) -> bool {
53 match secret {
54 None => proof.is_empty(),
55 Some(secret) => {
56 use hmac::{KeyInit, Mac};
58 let expected = client_auth_proof(secret, dialer, client, server);
59 let mut expected_mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(secret)
60 .expect("HMAC accepts any key size");
61 expected_mac.update(&expected);
62 let tag = expected_mac.finalize().into_bytes();
63 let mut supplied_mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(secret)
64 .expect("HMAC accepts any key size");
65 supplied_mac.update(proof);
66 supplied_mac.verify_slice(&tag).is_ok() && proof.len() == 32
67 }
68 }
69}
70
71pub fn valid_client_hello(hello: &client::Hello) -> bool {
72 !hello.identity.is_empty()
73 && hello.identity.len() <= 255
74 && hello.challenge.len() == 32
75 && hello.challenge.iter().any(|byte| *byte != 0)
76 && hello.min_version != 0
77 && hello.min_version <= CLIENT_PROTOCOL_VERSION
78 && hello.max_version >= CLIENT_PROTOCOL_VERSION
79}
80
81pub fn preflight_client_frame(bytes: &[u8]) -> Result<(), &'static str> {
85 #[derive(Clone, Copy, PartialEq, Eq)]
86 enum Context {
87 Frame,
88 Request,
89 BatchGet,
90 BatchPut,
91 Query,
92 Response,
93 QueryResult,
94 Row,
95 Inspection,
96 Health,
97 Placement,
98 Cell,
99 }
100 fn varint(bytes: &[u8], pos: &mut usize) -> Result<u64, &'static str> {
101 let mut result = 0;
102 for shift in (0..70).step_by(7) {
103 let byte = *bytes.get(*pos).ok_or("truncated varint")?;
104 *pos += 1;
105 if shift == 63 && byte > 1 {
106 return Err("overflowing varint");
107 }
108 result |= u64::from(byte & 127) << shift;
109 if byte & 128 == 0 {
110 return Ok(result);
111 }
112 }
113 Err("overflowing varint")
114 }
115 fn walk(bytes: &[u8], context: Context) -> Result<(), &'static str> {
116 use Context::*;
117 let mut pos = 0;
118 let mut counts = [0usize; 10];
119 let mut variants = 0;
120 while pos < bytes.len() {
121 let tag = varint(bytes, &mut pos)?;
122 let field = tag >> 3;
123 if field == 0 {
124 return Err("invalid field");
125 }
126 if (context == Frame && (1..=4).contains(&field))
127 || (context == Request && (2..=9).contains(&field))
128 || (context == Cell && (1..=4).contains(&field))
129 {
130 variants += 1;
131 if variants > 1 || (context != Cell && tag & 7 != 2) {
132 return Err("duplicate or invalid message");
133 }
134 }
135 let limit = match (context, field) {
136 (BatchGet | BatchPut, 1) | (Query, 2) | (Response, 3 | 4) => Some(MAX_BATCH_ITEMS),
137 (Response, 7) | (QueryResult, 2) | (Inspection, 4 | 5) => Some(100_000),
138 (QueryResult, 1) | (Row, 2) => Some(32),
139 (Health, 3 | 4) | (Placement, 2) => Some(MAX_BATCH_ITEMS),
140 _ => None,
141 };
142 if let Some(limit) = limit {
143 let counter = counts
144 .get_mut(field as usize)
145 .ok_or("invalid repeated field")?;
146 *counter += 1;
147 if *counter > limit || tag & 7 != 2 {
148 return Err("invalid or excessive repeated field");
149 }
150 }
151 if context == Response && [5, 6].contains(&field) {
152 counts[field as usize] += 1;
153 if counts[field as usize] > 1 {
154 return Err("duplicate structured response");
155 }
156 }
157 match tag & 7 {
158 0 => {
159 varint(bytes, &mut pos)?;
160 }
161 1 => {
162 pos = pos.checked_add(8).ok_or("overflow")?;
163 }
164 5 => {
165 pos = pos.checked_add(4).ok_or("overflow")?;
166 }
167 2 => {
168 let length =
169 usize::try_from(varint(bytes, &mut pos)?).map_err(|_| "overflow")?;
170 let end = pos.checked_add(length).ok_or("overflow")?;
171 let inner = bytes.get(pos..end).ok_or("truncated field")?;
172 let nested = match (context, field) {
173 (Frame, 3) => Some(Request),
174 (Frame, 4) => Some(Response),
175 (Request, 5) => Some(BatchGet),
176 (Request, 6) => Some(BatchPut),
177 (Request, 7) => Some(Query),
178 (Query, 2) | (Row, 2) => Some(Cell),
179 (Response, 5) => Some(QueryResult),
180 (Response, 6) => Some(Inspection),
181 (QueryResult, 2) => Some(Row),
182 (Inspection, 4) => Some(Health),
183 (Inspection, 5) => Some(Placement),
184 _ => None,
185 };
186 if let Some(nested) = nested {
187 walk(inner, nested)?;
188 }
189 pos = end;
190 }
191 _ => return Err("invalid wire type"),
192 }
193 if pos > bytes.len() {
194 return Err("truncated field");
195 }
196 }
197 if context == Frame && variants != 1 {
198 return Err("missing message");
199 }
200 Ok(())
201 }
202 walk(bytes, Context::Frame)
203}