reallyme_codec_proto/
lib.rs1#[cfg(feature = "generated")]
8use buffa::{DecodeOptions, EnumValue, Enumeration, Message};
9#[cfg(feature = "generated")]
10use serde::de::DeserializeOwned;
11#[cfg(feature = "generated")]
12use thiserror::Error;
13
14pub mod generated;
16
17#[cfg(feature = "generated")]
19pub const MAX_CODEC_PROTO_MESSAGE_BYTES: usize = 1024 * 1024;
20
21#[cfg(feature = "generated")]
23pub const MAX_CODEC_PROTO_ERROR_ENVELOPE_BYTES: usize = 4096;
24
25#[cfg(feature = "generated")]
27pub const MAX_CODEC_PROTO_JSON_BYTES: usize = 1_572_864;
28
29#[cfg(feature = "generated")]
30const CODEC_PROTO_RECURSION_LIMIT: u32 = 64;
31
32#[cfg(feature = "generated")]
33use generated::proto::reallyme::codec::v1::{
34 __buffa::oneof::codec_error::Error as CodecErrorBranchProto, CodecBackendError,
35 CodecBaseEncodingError, CodecCanonicalizationError, CodecError, CodecErrorReason,
36 CodecMultiformatError, CodecPemError,
37};
38
39#[cfg(feature = "generated")]
41#[derive(Debug, Clone, Copy, Eq, PartialEq)]
42#[non_exhaustive]
43pub enum CodecWireErrorBranch {
44 BaseEncoding,
46 Pem,
48 Multiformat,
50 Canonicalization,
52 Backend,
54}
55
56#[cfg(feature = "generated")]
58#[derive(Debug, Clone, Copy, Eq, Error, PartialEq)]
59#[error("codec wire boundary error")]
60pub struct CodecWireError {
61 branch: CodecWireErrorBranch,
62 reason: CodecErrorReason,
63}
64
65#[cfg(feature = "generated")]
67#[derive(Debug, Clone, Copy, Eq, Error, PartialEq)]
68#[non_exhaustive]
69pub enum CodecWireErrorConstructionError {
70 #[error("codec error reason does not belong to the selected branch")]
72 BranchReasonMismatch,
73}
74
75#[cfg(feature = "generated")]
77pub type CodecWireResult<T> = Result<T, CodecWireError>;
78
79#[cfg(feature = "generated")]
80impl CodecWireError {
81 #[must_use]
83 pub const fn branch(self) -> CodecWireErrorBranch {
84 self.branch
85 }
86
87 #[must_use]
89 pub const fn reason(self) -> CodecErrorReason {
90 self.reason
91 }
92
93 pub fn try_new(
100 branch: CodecWireErrorBranch,
101 reason: CodecErrorReason,
102 ) -> Result<Self, CodecWireErrorConstructionError> {
103 if !reason_is_valid_for_branch(branch, reason) {
104 return Err(CodecWireErrorConstructionError::BranchReasonMismatch);
105 }
106 Ok(Self { branch, reason })
107 }
108
109 #[must_use]
111 const fn backend_internal(reason: CodecErrorReason) -> Self {
112 Self {
113 branch: CodecWireErrorBranch::Backend,
114 reason,
115 }
116 }
117}
118
119#[cfg(feature = "generated")]
121#[must_use]
122pub fn encode_protobuf<M: Message>(message: &M) -> Vec<u8> {
123 message.encode_to_vec()
124}
125
126#[cfg(feature = "generated")]
133pub fn decode_protobuf<M: Message>(bytes: &[u8]) -> CodecWireResult<M> {
134 decode_protobuf_with_limit(bytes, MAX_CODEC_PROTO_MESSAGE_BYTES)
135}
136
137#[cfg(feature = "generated")]
148pub fn decode_json<M: DeserializeOwned + Message>(bytes: &[u8]) -> CodecWireResult<M> {
149 if bytes.len() > MAX_CODEC_PROTO_JSON_BYTES {
150 return Err(resource_limit_error());
151 }
152
153 let message: M = serde_json::from_slice(bytes).map_err(|_| {
154 CodecWireError::backend_internal(
155 CodecErrorReason::CODEC_ERROR_REASON_BACKEND_MALFORMED_JSON,
156 )
157 })?;
158 let encoded = encode_protobuf(&message);
159 if encoded.len() > MAX_CODEC_PROTO_MESSAGE_BYTES {
160 return Err(resource_limit_error());
161 }
162 Ok(message)
163}
164
165#[cfg(feature = "generated")]
167#[must_use]
168pub fn codec_error(error: CodecWireError) -> CodecError {
169 let reason = EnumValue::from(error.reason());
170 let branch = match error.branch() {
171 CodecWireErrorBranch::BaseEncoding => {
172 CodecErrorBranchProto::BaseEncoding(Box::new(CodecBaseEncodingError {
173 reason,
174 __buffa_unknown_fields: Default::default(),
175 }))
176 }
177 CodecWireErrorBranch::Pem => CodecErrorBranchProto::Pem(Box::new(CodecPemError {
178 reason,
179 __buffa_unknown_fields: Default::default(),
180 })),
181 CodecWireErrorBranch::Multiformat => {
182 CodecErrorBranchProto::Multiformat(Box::new(CodecMultiformatError {
183 reason,
184 __buffa_unknown_fields: Default::default(),
185 }))
186 }
187 CodecWireErrorBranch::Canonicalization => {
188 CodecErrorBranchProto::Canonicalization(Box::new(CodecCanonicalizationError {
189 reason,
190 __buffa_unknown_fields: Default::default(),
191 }))
192 }
193 CodecWireErrorBranch::Backend => {
194 CodecErrorBranchProto::Backend(Box::new(CodecBackendError {
195 reason,
196 __buffa_unknown_fields: Default::default(),
197 }))
198 }
199 };
200
201 CodecError {
202 error: Some(branch),
203 __buffa_unknown_fields: Default::default(),
204 }
205}
206
207#[cfg(feature = "generated")]
209#[must_use]
210pub fn codec_error_bytes(error: CodecWireError) -> Vec<u8> {
211 encode_protobuf(&codec_error(error))
212}
213
214#[cfg(feature = "generated")]
222pub fn decode_codec_error_payload(payload: &[u8]) -> CodecWireResult<CodecWireError> {
223 let error =
224 decode_protobuf_with_limit::<CodecError>(payload, MAX_CODEC_PROTO_ERROR_ENVELOPE_BYTES)?;
225 let (branch, reason) = match error.error {
226 Some(CodecErrorBranchProto::BaseEncoding(error)) => {
227 (CodecWireErrorBranch::BaseEncoding, error.reason)
228 }
229 Some(CodecErrorBranchProto::Pem(error)) => (CodecWireErrorBranch::Pem, error.reason),
230 Some(CodecErrorBranchProto::Multiformat(error)) => {
231 (CodecWireErrorBranch::Multiformat, error.reason)
232 }
233 Some(CodecErrorBranchProto::Canonicalization(error)) => {
234 (CodecWireErrorBranch::Canonicalization, error.reason)
235 }
236 Some(CodecErrorBranchProto::Backend(error)) => {
237 (CodecWireErrorBranch::Backend, error.reason)
238 }
239 None => return Err(malformed_protobuf_error()),
240 };
241
242 match reason.as_known() {
243 Some(reason) if reason_is_valid_for_branch(branch, reason) => {
244 Ok(CodecWireError { branch, reason })
245 }
246 Some(CodecErrorReason::CODEC_ERROR_REASON_UNSPECIFIED) | Some(_) | None => {
247 Err(malformed_protobuf_error())
248 }
249 }
250}
251
252#[cfg(feature = "generated")]
253fn decode_protobuf_with_limit<M: Message>(bytes: &[u8], max_bytes: usize) -> CodecWireResult<M> {
254 if bytes.len() > max_bytes {
255 return Err(resource_limit_error());
256 }
257
258 DecodeOptions::new()
259 .with_recursion_limit(CODEC_PROTO_RECURSION_LIMIT)
260 .with_max_message_size(max_bytes)
261 .decode_from_slice(bytes)
262 .map_err(|_| malformed_protobuf_error())
263}
264
265#[cfg(feature = "generated")]
266fn reason_is_valid_for_branch(branch: CodecWireErrorBranch, reason: CodecErrorReason) -> bool {
267 let value = reason.to_i32();
268 match branch {
269 CodecWireErrorBranch::BaseEncoding => (100..=199).contains(&value),
270 CodecWireErrorBranch::Pem => (200..=299).contains(&value),
271 CodecWireErrorBranch::Multiformat => (300..=399).contains(&value),
272 CodecWireErrorBranch::Canonicalization => (400..=499).contains(&value),
273 CodecWireErrorBranch::Backend => (500..=599).contains(&value),
274 }
275}
276
277#[cfg(feature = "generated")]
278fn malformed_protobuf_error() -> CodecWireError {
279 CodecWireError::backend_internal(
280 CodecErrorReason::CODEC_ERROR_REASON_BACKEND_MALFORMED_PROTOBUF,
281 )
282}
283
284#[cfg(feature = "generated")]
285fn resource_limit_error() -> CodecWireError {
286 CodecWireError::backend_internal(
287 CodecErrorReason::CODEC_ERROR_REASON_BACKEND_RESOURCE_LIMIT_EXCEEDED,
288 )
289}