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