use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use oxirush_utils::tree::{Map, Value};
use super::paths::{EDITED, extended, member_shape, of_a_transfer, written_ie};
use super::{Protocol, Shape, Typed, same_name};
use crate::Error;
use crate::json::{self, hex};
const NESTING: usize = 64;
pub(crate) const LOSSY: &str = "_lossy";
fn unhex(value: &Value) -> Result<Vec<u8>, Error> {
let octets = value.as_str().and_then(json::unhex);
octets.ok_or_else(|| {
let written: String = value.to_string().chars().take(40).collect();
Error::value(format!(
"{written} is not octets: octets are written in hexadecimal, two digits for each"
))
})
}
fn contained(raw: Value, typed: Result<Typed, Error>) -> Value {
let decoded = typed.and_then(|typed| (typed.decode)(&unhex(&raw)?));
let decoded = decoded.and_then(|(decoded, exact, refused)| match refused {
Some(refused) => Err(refused),
None => Ok((decoded, exact)),
});
let mut transfer = Map::new();
transfer.insert("_raw_transfer".into(), raw);
match decoded {
Ok((decoded, exact)) => {
transfer.insert("decoded".into(), decoded);
if !exact {
transfer.insert(LOSSY.into(), true.into());
}
}
Err(error) => {
transfer.insert("_decode_error".into(), error.to_string().into());
}
};
Value::Object(transfer)
}
fn too_deep<T>() -> Result<T, Error> {
Err(Error::limit(format!(
"inspection nesting exceeds {NESTING}"
)))
}
fn no_direction() -> Error {
Error::form(
"the direction of a view is InitiatingMessage, SuccessfulOutcome or UnsuccessfulOutcome",
)
}
impl Protocol {
pub(crate) fn opened(&self, raw: Value, name: &str) -> Value {
let typed = (self.carried_typed)(name);
let carries_none = || Error::no_such_member(format!("octets carry no {name}"));
let mut transfer = contained(raw, typed.ok_or_else(carries_none));
self.expand(&mut transfer, 0, self.carried_shape(name));
transfer
}
fn carried_octets(&self, written: &Value, id: u16) -> Result<Value, Error> {
let carried = self.carried.iter().filter(|(carrier, ..)| *carrier == id);
let names: Vec<_> = carried.map(|(_, name, _)| *name).collect();
let mut members = written.as_object().into_iter().flatten();
match (members.next(), members.next()) {
(Some((name, octets)), None) if names.iter().any(|known| same_name(known, name)) => {
Ok(octets.clone())
}
_ => Err(Error::value(format!(
"IE {id} is written as its octets, or as {{NAME: VALUE}} for a type that they \
carry: {}",
names.join(", ")
))),
}
}
fn ie_type(&self, id: u16, shape: Shape) -> Result<Typed, Error> {
match (self.extension_typed)(id, extended(shape)) {
Some(typed) => Ok(typed),
None => (self.ie_typed)(id),
}
}
fn message_typed(&self, direction: &str, code: u8) -> Typed {
let mut messages = self.messages.iter();
let message = messages.find(|(of, procedure, ..)| *procedure == code && *of == direction);
message.map_or_else(self.any_message, |(_, _, _, typed, ..)| typed())
}
fn expand(&self, value: &mut Value, depth: usize, shape: Shape) {
let deep = depth > NESTING;
if let Some(object) = value.as_object_mut() {
let field = if object.contains_key("value") {
"value"
} else {
"extensionValue"
};
if let Some(id) = object
.get("id")
.and_then(Value::as_u64)
.and_then(|v| u16::try_from(v).ok())
&& let Some(raw) = object.get_mut(field).filter(|v| v.is_string())
{
let raw = raw.take();
let decoded = match deep {
true => too_deep(),
false => {
unhex(&raw).and_then(|bytes| (self.ie_type(id, shape)?.decode)(&bytes))
}
};
let decoded = decoded.and_then(|(decoded, exact, refused)| match refused {
Some(refused) => Err(refused),
None => Ok((decoded, exact)),
});
match decoded {
Ok((decoded, exact)) => {
if !exact {
object.insert(LOSSY.into(), true.into());
}
let decoded = match (self.ie_contents)(id) {
Some(contents) => contained(decoded, Ok(contents)),
None => decoded,
};
object.insert(field.into(), decoded);
}
Err(error) => {
object.insert(field.into(), raw.clone());
object.insert("_decode_error".into(), error.to_string().into());
}
}
object.insert("_raw_value".into(), raw);
object.insert("_original_id".into(), id.into());
if let Some(name) = self.ie_name_of(id) {
object.insert("_ie_name".into(), name.into());
}
}
let members = self.member_shapes(shape, object);
for (key, child) in object {
if !key.starts_with('_') {
if self.transfer_fields.contains(&key.as_str()) && child.is_string() {
let typed = match deep {
true => too_deep(),
false => (self.transfer_typed)(key),
};
*child = contained(child.take(), typed);
}
self.expand(child, depth + 1, member_shape(&members, key));
}
}
} else if let Some(array) = value.as_array_mut() {
for child in array {
self.expand(child, depth + 1, self.entry_shape(shape));
}
}
}
fn collapse(
&'static self,
value: &mut Value,
member: &str,
depth: usize,
shape: Shape,
) -> Result<(), Error> {
if depth > 4 * NESTING {
return Err(Error::limit(format!(
"inspection nesting exceeds {}",
4 * NESTING
)));
}
if let Some(object) = value.as_object_mut() {
written_ie(self, object)?;
let id = object
.get("id")
.and_then(Value::as_u64)
.and_then(|v| u16::try_from(v).ok());
let members = self.member_shapes(shape, object);
for (key, child) in object.iter_mut() {
if key.starts_with('_') {
continue;
}
self.collapse(child, key, depth + 1, member_shape(&members, key))?;
if let Some(raw) = child.get("_raw_transfer").cloned() {
self.received_transfer(child, key, id, raw)?;
} else if let Some(members) = child.as_object() {
let transfer = match key.as_str() {
"value" | "extensionValue" => id.and_then(self.ie_contents),
key if self.transfer_fields.contains(&key) => {
Some((self.transfer_typed)(key)?)
}
key => (self.carried_typed)(key),
};
if let Some(transfer) = transfer {
let alone = |member| members.get(member).filter(|_| members.len() == 1);
let wire = if !members.keys().any(|name| of_a_transfer(name)) {
Value::String(hex(&(transfer.encode)(child)?))
} else if let Some(written) = alone("decoded") {
Value::String(hex(&(transfer.encode)(written)?))
} else if let Some(octets) = alone("octets") {
octets.clone()
} else {
return Err(Error::form(format!(
"{key} is written with its decoded value alone, or as its octets"
)));
};
*child = wire;
}
}
}
let raw = object.shift_remove("_raw_value");
let original_id = object.shift_remove("_original_id");
let undecoded = object.shift_remove("_decode_error").is_some();
object.shift_remove("_ie_name");
object.shift_remove(EDITED);
object.shift_remove(LOSSY);
if let Some(id) = id {
let wire =
self.ie_octets(object, member, shape, id, raw, original_id, undecoded)?;
let field = match member.starts_with("iE-Extension") {
true => "extensionValue",
false => "value",
};
object.insert(field.into(), wire);
}
} else if let Some(array) = value.as_array_mut() {
for child in array {
self.collapse(child, member, depth + 1, self.entry_shape(shape))?;
}
}
Ok(())
}
fn received_transfer(
&self,
child: &mut Value,
key: &str,
id: Option<u16>,
raw: Value,
) -> Result<(), Error> {
let known = ["_raw_transfer", "decoded", "_decode_error", EDITED, LOSSY];
let mut members = child.as_object().into_iter().flatten();
if let Some((other, _)) = members.find(|(name, _)| !known.contains(&name.as_str())) {
return Err(Error::no_such_member(format!(
"{key} has no member {other:?}: a transfer has its decoded value"
)));
}
let Some(edited) = child.get("decoded") else {
*child = raw;
return Ok(());
};
let bytes = unhex(&raw)?;
let transfer = match key {
"value" | "extensionValue" => id.and_then(self.ie_contents).ok_or_else(|| {
Error::form("an IE that contains no type has no decoded value beside its octets")
})?,
key => match (self.carried_typed)(key) {
Some(carried) => carried,
None => (self.transfer_typed)(key)?,
},
};
let (original, ..) = (transfer.decode)(&bytes)?;
if &original == edited {
*child = raw;
} else {
if (transfer.encode)(&original)? != bytes {
return Err(Error::edit(format!(
"{key} does not encode back to the octets received"
)));
}
*child = Value::String(hex(&(transfer.encode)(edited)?));
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn ie_octets(
&self,
object: &mut Map,
member: &str,
shape: Shape,
id: u16,
raw: Option<Value>,
original_id: Option<Value>,
undecoded: bool,
) -> Result<Value, Error> {
let field = match member.starts_with("iE-Extension") {
true => "extensionValue",
false => "value",
};
if let Some(written) = object.get_mut(field).filter(|value| value.is_object())
&& self.carried.iter().any(|(carrier, ..)| *carrier == id)
{
*written = self.carried_octets(written, id)?;
}
let typed = |id: u16| {
let octets = format!("give its octets as _raw_value, without {field}");
self.ie_type(id, shape)
.map_err(|error| error.reworded(|what| format!("{what}: {octets}")))
};
Ok(match (raw, object.get(field)) {
(Some(raw), None) => raw,
(_, Some(edited)) if undecoded => {
if !edited.is_string() {
return Err(Error::edit(format!(
"IE {id} did not decode: its value is its octets in hexadecimal"
)));
}
edited.clone()
}
(Some(raw), Some(edited)) => {
let original_id = match &original_id {
Some(original) => (original.as_u64())
.and_then(|v| u16::try_from(v).ok())
.ok_or_else(|| {
Error::form(
"_original_id is the identifier that the octets were decoded \
as, a number",
)
})?,
None => id,
};
let bytes = unhex(&raw)?;
let received = self.ie_type(original_id, shape)?;
let (original, ..) = (received.decode)(&bytes)?;
if &original == edited && id == original_id {
raw
} else if (received.encode)(&original)? != bytes {
return Err(Error::edit(format!(
"IE {original_id} does not encode back to the octets received; \
replace its octets instead"
)));
} else {
Value::String(hex(&(typed(id)?.encode)(edited)?))
}
}
(None, Some(new)) => Value::String(hex(&(typed(id)?.encode)(new)?)),
(None, None) => {
return Err(Error::form(format!(
"IE {id} has no {field} and no _raw_value"
)));
}
})
}
fn message_name_of(&self, direction: &str, code: u8) -> Option<&'static str> {
let mut messages = self.messages.iter();
let message = messages.find(|(of, procedure, ..)| *procedure == code && *of == direction);
message.map(|(_, _, name, ..)| *name)
}
fn message_criticality(&self, name: &str) -> Option<&'static str> {
let mut messages = self.messages.iter();
let (_, code, ..) = messages.find(|(_, _, known, ..)| same_name(known, name))?;
let mut procedures = self.procedure_criticalities.iter();
let procedure = procedures.find(|(known, _)| known == code);
procedure.map(|(_, criticality)| *criticality)
}
}
pub fn view(
protocol: &'static Protocol,
direction: &'static str,
code: u8,
criticality: Value,
raw: &[u8],
) -> Result<Value, Error> {
let (mut message, exact, _) = (protocol.message_typed(direction, code).decode)(raw)?;
protocol.expand(&mut message, 0, protocol.message_shape(direction, code));
let mut view = Map::new();
view.insert("procedure_code".into(), code.into());
view.insert("direction".into(), direction.into());
view.insert("criticality".into(), criticality);
view.insert("message".into(), message);
view.insert("_raw_message".into(), hex(raw).into());
if !exact {
view.insert(LOSSY.into(), true.into());
}
Ok(Value::Object(view))
}
pub fn tree(
protocol: &'static Protocol,
direction: &'static str,
code: u8,
criticality: Value,
raw: &[u8],
) -> Value {
let name = protocol.message_name_of(direction, code);
let mut tree = Map::new();
match name {
Some(name) => {
tree.insert("message".into(), name.into());
}
None => {
tree.insert("procedure_code".into(), code.into());
tree.insert("direction".into(), direction.into());
}
}
let sent = name.and_then(|name| protocol.sent(|_, _, known| known == name));
let ies = view(protocol, direction, code, Value::Null, raw)
.ok()
.filter(|view| view.get(LOSSY).is_none())
.and_then(|mut view| view.pointer_mut("/message/protocolIEs").map(Value::take))
.filter(Value::is_array)
.and_then(|listed| protocol.by_name(listed, Shape::Ies(sent)).ok());
match ies {
Some(ies) => tree.insert("ies".into(), ies),
None => tree.insert("octets".into(), hex(raw).into()),
};
let assigned = name.and_then(|name| protocol.message_criticality(name));
if assigned.is_none() || criticality.as_str() != assigned {
tree.insert("criticality".into(), criticality);
}
Value::Object(tree)
}
pub struct Parts {
pub direction: &'static str,
pub code: u8,
pub criticality: Value,
pub message: Vec<u8>,
}
fn direction_of(direction: &str) -> Result<&'static str, Error> {
const DIRECTIONS: [&str; 3] = [
"InitiatingMessage",
"SuccessfulOutcome",
"UnsuccessfulOutcome",
];
let known = DIRECTIONS.iter().find(|known| **known == direction);
known.copied().ok_or_else(no_direction)
}
fn message_octets(
protocol: &'static Protocol,
direction: &str,
code: u8,
mut message: Value,
received: Option<&Value>,
) -> Result<Vec<u8>, Error> {
protocol.collapse(&mut message, "", 0, protocol.message_shape(direction, code))?;
let typed = protocol.message_typed(direction, code);
let Some(original) = received else {
return (typed.encode)(&message);
};
let original = unhex(original)?;
let (decoded, ..) = (typed.decode)(&original)?;
if decoded == message {
return Ok(original);
}
if (typed.encode)(&decoded)? != original {
return Err(Error::edit(
"the message does not encode back to the octets received",
));
}
(typed.encode)(&message)
}
pub fn tree_parts(protocol: &'static Protocol, tree: &Value) -> Result<Parts, Error> {
const FORM: &str = "a tree is {\"message\": NAME, \"ies\": [{NAME: VALUE}, …]}, with \
\"octets\" in the place of the IEs for a message as it is sent";
let form = || Error::form(FORM);
let written = tree.as_object().ok_or_else(form)?;
const MEMBERS: [&str; 6] = [
"message",
"procedure_code",
"direction",
"ies",
"octets",
"criticality",
];
if let Some(other) = written
.keys()
.find(|key| !MEMBERS.iter().any(|known| same_name(known, key)))
{
return Err(Error::form(format!(
"{other:?} is no member of a tree: {FORM}"
)));
}
for known in MEMBERS {
let mut spellings = written.keys().filter(|key| same_name(known, key));
if let (Some(first), Some(second)) = (spellings.next(), spellings.next()) {
return Err(Error::form(format!(
"the member {known:?} of a tree is written twice, as {first:?} and as {second:?}"
)));
}
}
let member = |name: &str| {
let mut members = written.iter();
members
.find(|(key, _)| same_name(key, name))
.map(|(_, value)| value)
};
let (direction, code, name) = match member("message") {
Some(name) => {
let name = name.as_str().ok_or_else(form)?;
let mut messages = protocol.messages.iter();
let message = messages.find(|(_, _, known, ..)| same_name(known, name));
let (direction, code, name, ..) = message.ok_or_else(|| {
let nearest = protocol.nearest_message(name);
Error::unknown_message(format!(
"{name:?} is not a message of {}{nearest}",
protocol.name
))
})?;
if member("procedure_code").or(member("direction")).is_some() {
return Err(Error::form(format!(
"{name} is named by its message alone: its procedure_code and its \
direction are those of its name"
)));
}
(*direction, *code, Some(*name))
}
None => {
const UNKNOWN: &str = "a procedure that the protocol does not have is written by \
its procedure_code, a number from 0 to 255, and its \
direction";
let code = member("procedure_code").and_then(Value::as_u64);
let code = code.and_then(|code| u8::try_from(code).ok());
let direction = member("direction").and_then(Value::as_str);
let (Some(code), Some(direction)) = (code, direction) else {
return Err(Error::form(format!("{FORM}; {UNKNOWN}")));
};
let direction = direction_of(direction)?;
if let Some(name) = protocol.message_name_of(direction, code) {
return Err(Error::form(format!(
"the {direction} of procedure {code} is {name}: it is written as \
{{\"message\": \"{name}\"}}"
)));
}
(direction, code, None)
}
};
let assigned = name.and_then(|name| protocol.message_criticality(name));
let criticality = match (member("criticality"), assigned) {
(Some(criticality), _) => criticality.clone(),
(None, Some(assigned)) => assigned.into(),
(None, None) => {
return Err(Error::form(format!(
"the {direction} of procedure {code} says its \"criticality\": the tables \
have none for it"
)));
}
};
let message = match (member("ies"), member("octets")) {
(Some(listed), None) if listed.is_array() => {
let sent = name.and_then(|name| protocol.sent(|_, _, known| known == name));
let mut message = Map::new();
message.insert(
"protocolIEs".into(),
protocol.written(listed, Shape::Ies(sent), 0)?,
);
message_octets(protocol, direction, code, Value::Object(message), None)?
}
(None, Some(octets)) => unhex(octets)?,
_ => return Err(form()),
};
Ok(Parts {
direction,
code,
criticality,
message,
})
}
pub fn view_parts(protocol: &'static Protocol, view: &Value) -> Result<Parts, Error> {
const MEMBERS: [&str; 6] = [
"procedure_code",
"direction",
"criticality",
"message",
"_raw_message",
LOSSY,
];
const FORM: &str =
"a view has its procedure_code, its direction, its criticality and its message";
let members = view.as_object().ok_or_else(|| Error::form(FORM))?;
if let Some(other) = members.keys().find(|key| !MEMBERS.contains(&key.as_str())) {
return Err(Error::form(format!(
"{other:?} is no member of a view: {FORM}"
)));
}
let code = view["procedure_code"]
.as_u64()
.and_then(|c| u8::try_from(c).ok())
.ok_or_else(|| Error::form("the procedure_code of a view is a number from 0 to 255"))?;
let direction = direction_of(view["direction"].as_str().ok_or_else(no_direction)?)?;
let message = view.get("message").ok_or_else(|| Error::form(FORM))?;
let received = view.get("_raw_message");
let message = message_octets(protocol, direction, code, message.clone(), received)?;
Ok(Parts {
direction,
code,
criticality: view["criticality"].clone(),
message,
})
}
pub fn view_of_jer(protocol: &'static Protocol, view: &Value) -> Result<Value, Error> {
let mut view = view.clone();
let code = view["procedure_code"]
.as_u64()
.and_then(|c| u8::try_from(c).ok())
.ok_or_else(|| Error::form("the procedure_code of a view is a number from 0 to 255"))?;
let direction = direction_of(view["direction"].as_str().ok_or_else(no_direction)?)?;
let shape = protocol.message_shape(direction, code);
if let Some(message) = view.get_mut("message") {
protocol.from_jer(message, shape);
}
Ok(view)
}
pub fn of_the_pdu(
protocol: &'static Protocol,
view: &Value,
direction: &str,
code: u8,
) -> Result<(), Error> {
let of = (view["direction"].as_str(), view["procedure_code"].as_u64());
let (Some(of), Some(procedure)) = of else {
return Ok(());
};
if (of, procedure) == (direction, u64::from(code)) {
return Ok(());
}
let message = |direction: &str, code: u64| {
let known = u8::try_from(code).ok();
match known.and_then(|code| protocol.message_name_of(direction, code)) {
Some(name) => name.to_string(),
None => format!("the {direction} of procedure {code}"),
}
};
Err(Error::form(format!(
"this view is of {}, and the PDU is {}: with_view takes a view of the PDU itself, and \
from_view any view",
message(of, procedure),
message(direction, code.into()),
)))
}