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