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