use serde::de::{DeserializeOwned, Error as DeError};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use serde_json::value::RawValue;
use crate::IpcMessage;
use crate::validation::{Validate, ValidationError};
pub struct WireCompatibility;
impl WireCompatibility {
pub const CURRENT: u32 = 1;
pub const MIN_SUPPORTED: u32 = 1;
pub const LEGACY_UNVERSIONED: u32 = 1;
#[must_use]
pub fn classify(version: u32) -> Compatibility {
if version < Self::MIN_SUPPORTED {
Compatibility::TooOld
} else if version > Self::CURRENT {
Compatibility::TooNew
} else {
Compatibility::Supported
}
}
pub fn accept(version: u32) -> Result<SupportedWireVersion, CompatibilityError> {
match Self::classify(version) {
Compatibility::Supported => Ok(SupportedWireVersion(version)),
Compatibility::TooOld => Err(CompatibilityError::TooOld {
found: version,
min: Self::MIN_SUPPORTED,
}),
Compatibility::TooNew => Err(CompatibilityError::TooNew {
found: version,
current: Self::CURRENT,
}),
}
}
}
const _: () = {
assert!(WireCompatibility::MIN_SUPPORTED > 0);
assert!(WireCompatibility::CURRENT >= WireCompatibility::MIN_SUPPORTED);
assert!(
WireCompatibility::LEGACY_UNVERSIONED >= WireCompatibility::MIN_SUPPORTED
&& WireCompatibility::LEGACY_UNVERSIONED <= WireCompatibility::CURRENT
);
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Compatibility {
Supported,
TooOld,
TooNew,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SupportedWireVersion(u32);
impl SupportedWireVersion {
#[must_use]
pub const fn get(self) -> u32 {
self.0
}
}
#[must_use]
pub fn classify_wire_version(version: u32) -> Compatibility {
WireCompatibility::classify(version)
}
pub fn accept_wire_version(version: u32) -> Result<SupportedWireVersion, CompatibilityError> {
WireCompatibility::accept(version)
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum CompatibilityError {
#[error(
"wire version {found} is too old (minimum supported is {min}); upgrade the producer to emit version {min} or newer"
)]
TooOld {
found: u32,
min: u32,
},
#[error(
"wire version {found} is too new (current is {current}); upgrade corpus-ipc or have the producer emit version {current} or older"
)]
TooNew {
found: u32,
current: u32,
},
}
#[derive(Debug, thiserror::Error)]
pub enum EnvelopeError {
#[error(transparent)]
Compatibility(#[from] CompatibilityError),
#[error("compatibility envelope must be a JSON object")]
NotAnObject,
#[error("missing wire_version on compatibility envelope")]
MissingVersion,
#[error("wire_version must be a non-negative integer fitting u32, got {0}")]
InvalidVersion(String),
#[error("missing payload on compatibility envelope")]
MissingPayload,
#[error("envelope JSON error: {0}")]
Json(serde_json::Error),
#[error("failed to decode envelope payload: {0}")]
Payload(serde_json::Error),
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct WireEnvelope<T> {
pub wire_version: u32,
pub payload: T,
}
impl<T> WireEnvelope<T> {
#[must_use]
pub fn new(payload: T) -> Self {
Self {
wire_version: WireCompatibility::CURRENT,
payload,
}
}
#[must_use]
pub fn into_payload(self) -> T {
self.payload
}
}
impl<T: DeserializeOwned> WireEnvelope<T> {
pub fn decode_json(bytes: &[u8]) -> Result<Self, EnvelopeError> {
envelope_from_raw_parts(parse_raw_envelope(bytes)?)
}
pub fn from_json_value(value: Value) -> Result<Self, EnvelopeError> {
let Value::Object(mut obj) = value else {
return Err(EnvelopeError::NotAnObject);
};
let version_val = obj
.remove("wire_version")
.ok_or(EnvelopeError::MissingVersion)?;
let version = parse_wire_version(&version_val)?;
WireCompatibility::accept(version)?;
let payload_val = obj.remove("payload").ok_or(EnvelopeError::MissingPayload)?;
let payload = serde_json::from_value(payload_val).map_err(EnvelopeError::Payload)?;
Ok(Self {
wire_version: version,
payload,
})
}
}
impl<T: Serialize> WireEnvelope<T> {
pub fn encode_json(&self) -> Result<Vec<u8>, EnvelopeError> {
serde_json::to_vec(self).map_err(EnvelopeError::Json)
}
}
impl<'de, T> Deserialize<'de> for WireEnvelope<T>
where
T: DeserializeOwned,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;
Self::from_json_value(value).map_err(D::Error::custom)
}
}
pub fn encode_ipc_message_json(message: &IpcMessage) -> Result<Vec<u8>, EnvelopeError> {
WireEnvelope::new(message).encode_json()
}
#[derive(Debug, thiserror::Error)]
pub enum CanonicalEncodeError {
#[error("payload failed validation before canonical encoding: {0}")]
Validation(#[from] ValidationError),
#[error("canonical JSON serialization error: {0}")]
Json(serde_json::Error),
}
pub fn encode_canonical_ipc_message(message: &IpcMessage) -> Result<Vec<u8>, CanonicalEncodeError> {
message.validate()?;
let envelope = WireEnvelope::new(message);
let mut value =
crate::canonical_ser::serialize_f32_as_f64(&envelope, serde_json::value::Serializer)
.map_err(CanonicalEncodeError::Json)?;
value.sort_all_objects();
serde_json::to_vec(&value).map_err(CanonicalEncodeError::Json)
}
pub fn decode_ipc_message_json(bytes: &[u8]) -> Result<IpcMessage, EnvelopeError> {
match parse_raw_envelope(bytes) {
Ok(raw) if matches!(raw.wire_version, OptionalValue::Present(_)) => {
Ok(envelope_from_raw_parts::<IpcMessage>(raw)?.into_payload())
}
Ok(_) => {
WireCompatibility::accept(WireCompatibility::LEGACY_UNVERSIONED)?;
serde_json::from_slice(bytes).map_err(EnvelopeError::Payload)
}
Err(EnvelopeError::NotAnObject) => {
let value = serde_json::from_slice(bytes).map_err(EnvelopeError::Json)?;
decode_ipc_message_value(value)
}
Err(EnvelopeError::Json(raw_error)) => {
let value = serde_json::from_slice::<Value>(bytes).map_err(EnvelopeError::Json)?;
if matches!(&value, Value::Object(obj) if obj.contains_key("wire_version")) {
return Err(EnvelopeError::Json(raw_error));
}
decode_ipc_message_value(value)
}
Err(other) => Err(other),
}
}
pub fn decode_ipc_message_value(value: Value) -> Result<IpcMessage, EnvelopeError> {
match &value {
Value::String(_) => {
WireCompatibility::accept(WireCompatibility::LEGACY_UNVERSIONED)?;
serde_json::from_value(value).map_err(EnvelopeError::Payload)
}
Value::Object(obj) if obj.contains_key("wire_version") => {
Ok(WireEnvelope::<IpcMessage>::from_json_value(value)?.into_payload())
}
Value::Object(_) => {
WireCompatibility::accept(WireCompatibility::LEGACY_UNVERSIONED)?;
serde_json::from_value(value).map_err(EnvelopeError::Payload)
}
_ => Err(EnvelopeError::NotAnObject),
}
}
#[derive(Default)]
enum OptionalValue {
#[default]
Absent,
Present(Value),
}
impl<'de> Deserialize<'de> for OptionalValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Self::Present(Value::deserialize(deserializer)?))
}
}
#[derive(Deserialize)]
struct RawEnvelope {
#[serde(default)]
wire_version: OptionalValue,
#[serde(default)]
payload: OptionalRaw,
}
#[derive(Default)]
enum OptionalRaw {
#[default]
Absent,
Present(Box<RawValue>),
}
impl<'de> Deserialize<'de> for OptionalRaw {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Self::Present(Box::<RawValue>::deserialize(deserializer)?))
}
}
fn envelope_from_raw_parts<T: DeserializeOwned>(
raw: RawEnvelope,
) -> Result<WireEnvelope<T>, EnvelopeError> {
let OptionalValue::Present(version_val) = raw.wire_version else {
return Err(EnvelopeError::MissingVersion);
};
let version = parse_wire_version(&version_val)?;
WireCompatibility::accept(version)?;
let OptionalRaw::Present(payload_raw) = raw.payload else {
return Err(EnvelopeError::MissingPayload);
};
let payload = serde_json::from_str(payload_raw.get()).map_err(EnvelopeError::Payload)?;
Ok(WireEnvelope {
wire_version: version,
payload,
})
}
fn parse_raw_envelope(bytes: &[u8]) -> Result<RawEnvelope, EnvelopeError> {
match bytes.iter().copied().find(|b| !b.is_ascii_whitespace()) {
Some(b'{') => serde_json::from_slice(bytes).map_err(EnvelopeError::Json),
Some(_) => {
if json_is_valid_non_object(bytes) {
Err(EnvelopeError::NotAnObject)
} else {
serde_json::from_slice(bytes).map_err(EnvelopeError::Json)
}
}
None => serde_json::from_slice(bytes).map_err(EnvelopeError::Json),
}
}
fn json_is_valid_non_object(bytes: &[u8]) -> bool {
matches!(serde_json::from_slice::<Value>(bytes), Ok(value) if !value.is_object())
}
fn parse_wire_version(value: &Value) -> Result<u32, EnvelopeError> {
match value {
Value::Number(n) => n
.as_u64()
.and_then(|v| u32::try_from(v).ok())
.ok_or_else(|| EnvelopeError::InvalidVersion(n.to_string())),
other => Err(EnvelopeError::InvalidVersion(other.to_string())),
}
}