use prost::Message;
use super::status::GrpcStatusCode;
use super::status::build_grpc_error_response;
use crate::responder::Responder;
use crate::types::Response;
pub const MAX_GRPC_MESSAGE_SIZE: usize = 4 * 1024 * 1024;
#[derive(Debug)]
pub enum GrpcError {
InvalidContentType,
BodyReadError(String),
InvalidFrame,
MessageTooLarge,
DecodeError(String),
CompressionUnsupported,
}
impl Responder for GrpcError {
fn into_response(self) -> Response {
let (status_code, message) = match self {
GrpcError::InvalidContentType => (
GrpcStatusCode::Unimplemented,
"invalid content-type; expected application/grpc",
),
GrpcError::BodyReadError(_) => (GrpcStatusCode::Internal, "failed to read request body"),
GrpcError::InvalidFrame => (GrpcStatusCode::InvalidArgument, "malformed gRPC frame"),
GrpcError::MessageTooLarge => (
GrpcStatusCode::ResourceExhausted,
"grpc message exceeds MAX_GRPC_MESSAGE_SIZE",
),
GrpcError::DecodeError(_) => (
GrpcStatusCode::InvalidArgument,
"failed to decode protobuf message",
),
GrpcError::CompressionUnsupported => (
GrpcStatusCode::Unimplemented,
"frame is compressed but no codec is configured",
),
};
build_grpc_error_response(status_code, message)
}
}
pub fn grpc_encode<T: Message>(msg: &T) -> Vec<u8> {
let msg_bytes = msg.encode_to_vec();
assert!(
u32::try_from(msg_bytes.len()).is_ok(),
"grpc_encode: message of {} bytes exceeds u32::MAX (4 GiB) — gRPC length-prefix would wrap",
msg_bytes.len()
);
let len = msg_bytes.len() as u32;
let mut frame = Vec::with_capacity(5 + msg_bytes.len());
frame.push(0); frame.extend_from_slice(&len.to_be_bytes());
frame.extend_from_slice(&msg_bytes);
frame
}
pub fn grpc_decode<T: Message + Default>(data: &[u8]) -> Result<(T, bool), GrpcError> {
if data.len() < 5 {
return Err(GrpcError::InvalidFrame);
}
let compressed = data[0] != 0;
if compressed {
return Err(GrpcError::CompressionUnsupported);
}
let msg_len = u32::from_be_bytes([data[1], data[2], data[3], data[4]]) as usize;
if msg_len > MAX_GRPC_MESSAGE_SIZE {
return Err(GrpcError::MessageTooLarge);
}
if data.len() < 5 + msg_len {
return Err(GrpcError::InvalidFrame);
}
let msg = T::decode(&data[5..5 + msg_len]).map_err(|e| GrpcError::DecodeError(e.to_string()))?;
Ok((msg, compressed))
}