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