use ic_json::{parse, Json};
use isl_ontology::{export, ImplStatus, Kind, Policy};
pub fn json_of(s: &str) -> Result<Json, String> {
parse(s).map_err(|e| format!("internal: exporter produced invalid JSON: {e}"))
}
fn strs(items: &[&str]) -> Json {
Json::Array(items.iter().map(|s| Json::str(*s)).collect())
}
pub fn resolve_id(id: &str) -> Option<String> {
if export::describe(id).is_some() {
return Some(id.to_string());
}
for p in export::ID_PREFIXES {
let full = format!("{p}:{id}");
if export::describe(&full).is_some() {
return Some(full);
}
}
None
}
pub fn ontology_list(kind: Option<&str>) -> Result<Json, String> {
let kind = match kind {
None => None,
Some(k) => Some(Kind::from_id(k).ok_or_else(|| {
let known: Vec<&str> = Kind::ALL.iter().map(|k| k.id()).collect();
format!("unknown kind '{k}'; expected one of: {}", known.join(", "))
})?),
};
let items = isl_ontology::all()
.filter(|e| kind.is_none_or(|k| e.kind == k))
.map(|e| {
Json::object([
("id", Json::str(e.id)),
("name", Json::str(e.name)),
("kind", Json::str(e.kind.id())),
("code", Json::num(e.code)),
("status", Json::str(e.status.id())),
("fipsStatus", Json::str(e.fips.id())),
("postQuantum", Json::Bool(e.post_quantum)),
("summary", Json::str(e.summary)),
])
})
.collect();
Ok(Json::Array(items))
}
pub fn ontology_show(id: &str) -> Result<Json, String> {
let full =
resolve_id(id).ok_or_else(|| format!("no ontology entry '{id}'; try `ontology list`"))?;
json_of(&export::describe(&full).unwrap_or_default())
}
pub fn ontology_related(id: &str) -> Result<Json, String> {
let full = if id.starts_with(isl_ontology::IC_PREFIX) {
id.to_string()
} else {
resolve_id(id).ok_or_else(|| format!("no ontology entry '{id}'"))?
};
let items = isl_ontology::related(&full)
.into_iter()
.map(|r| {
Json::object([
("relation", Json::str(r.relation.id())),
("direction", Json::str(r.direction.id())),
("target", Json::str(r.other)),
])
})
.collect();
Ok(Json::object([
("id", Json::str(full)),
("relations", Json::Array(items)),
]))
}
pub fn recommend(
intent: &str,
fips: bool,
post_quantum: bool,
mutual: bool,
) -> Result<Json, String> {
let policy = Policy {
require_fips: fips,
require_post_quantum: post_quantum,
require_mutual_auth: mutual,
};
let r = isl_ontology::recommend(intent, &policy);
json_of(&export::recommendation_to_json(&r))
}
pub fn explain_error(id: &str) -> Result<Json, String> {
let full = if id.starts_with("error:") {
id.to_string()
} else {
format!("error:{id}")
};
let doc = isl_ontology::errors::get(&full).ok_or_else(|| {
format!(
"no error '{id}'; known errors: {}",
isl_ontology::errors::CATALOG
.iter()
.map(|e| e.id)
.collect::<Vec<_>>()
.join(", ")
)
})?;
json_of(&export::error_to_json(doc))
}
pub fn profiles() -> Result<Json, String> {
let items: Result<Vec<Json>, String> = isl_ontology::PROFILES
.iter()
.map(|p| json_of(&export::profile_to_json(p)))
.collect();
Ok(Json::Array(items?))
}
fn implemented(kind: Kind) -> Json {
Json::Array(
isl_ontology::by_kind(kind)
.filter(|e| e.status == ImplStatus::Implemented)
.map(|e| Json::str(e.id))
.collect(),
)
}
pub const DO178C_STATEMENT: &str = "IronSocketLayer is not DO-178C certified at any design assurance level. \
It provides a restricted profile (profile:dal-a), requirement identifiers traced to code and tests, \
no unsafe code, and no panics on peer input, to support a certification effort. DO-178C approval is \
granted to a specific airborne system by a certification authority on the evidence produced for that system.";
pub fn capabilities() -> Result<Json, String> {
let planned: Vec<Json> = isl_ontology::all()
.filter(|e| e.status == ImplStatus::Planned)
.map(|e| {
Json::object([
("id", Json::str(e.id)),
("reason", Json::str(e.status_reason)),
])
})
.collect();
Ok(Json::object([
("library", Json::str("ironsocketlayer")),
("version", Json::str(isl_ontology::VERSION)),
("ontologyVersion", Json::str(isl_ontology::ONTOLOGY_VERSION)),
("protocolVersions", implemented(Kind::ProtocolVersion)),
("quic", Json::object([
("tls", Json::Bool(true)),
("versions", strs(&["quic-v1 (RFC 9000/9001)", "quic-v2 (RFC 9369)"])),
("note", Json::str("IronSocketLayer supplies QUIC-TLS: handshake, per-level keys, packet and header protection. The QUIC transport (streams, loss recovery, congestion control) is out of scope.")),
])),
("cipherSuites", implemented(Kind::CipherSuite)),
("namedGroups", implemented(Kind::NamedGroup)),
("signatureSchemes", implemented(Kind::SignatureScheme)),
("extensions", implemented(Kind::Extension)),
("planned", Json::Array(planned)),
("cryptography", Json::str("IronCrypto (every primitive; nothing is implemented in IronSocketLayer)")),
("fipsValidated", Json::Bool(false)),
("fipsModuleState", Json::str(ic_fips::state().id())),
("fipsValidationStatement", Json::str(ic_fips::VALIDATION_STATEMENT)),
("do178cCertified", Json::Bool(false)),
("do178cStatement", Json::str(DO178C_STATEMENT)),
]))
}
pub fn selftest() -> Result<Json, String> {
match ic_fips::initialize() {
Ok(report) => Ok(Json::object([
("passed", Json::num(report.passed as f64)),
("failed", Json::num(report.failed as f64)),
("allPassed", Json::Bool(report.all_passed())),
("state", Json::str(ic_fips::state().id())),
(
"outcomes",
Json::Array(
report
.outcomes
.iter()
.map(|o| {
Json::object([
("algorithm", Json::str(o.algorithm)),
("passed", Json::Bool(o.passed)),
])
})
.collect(),
),
),
])),
Err(e) => Err(format!(
"self-test failed: {e}; the module is latched in its error state"
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ids_resolve_with_or_without_prefix() {
assert_eq!(
resolve_id("x25519mlkem768").as_deref(),
Some("group:x25519mlkem768")
);
assert_eq!(
resolve_id("profile:dal-a").as_deref(),
Some("profile:dal-a")
);
assert!(resolve_id("nonsense").is_none());
}
#[test]
fn capabilities_deny_validation_and_certification() {
let c = capabilities().unwrap();
assert_eq!(
c.get("fipsValidated").and_then(|v| v.as_bool()),
Some(false)
);
assert_eq!(
c.get("do178cCertified").and_then(|v| v.as_bool()),
Some(false)
);
assert!(c
.get("fipsValidationStatement")
.and_then(|v| v.as_str())
.unwrap()
.contains("NOT"));
}
#[test]
fn recommend_answers_fips_and_post_quantum_with_cnsa2() {
let r = recommend("intent:https-client", true, true, false).unwrap();
assert_eq!(
r.get("status").and_then(|v| v.as_str()),
Some("recommended")
);
assert!(r.to_string().contains("profile:cnsa-2"), "{r}");
let refusal: Result<isl_ontology::select::Recommendation, _> =
Err(isl_ontology::select::NoRecommendation::Unavailable {
profile: "profile:example",
reason: "needs an algorithm this build lacks",
});
let r = json_of(&export::recommendation_to_json(&refusal)).unwrap();
assert_eq!(
r.get("status").and_then(|v| v.as_str()),
Some("unavailable")
);
assert!(r.to_string().contains("Do not substitute"));
let r = recommend("intent:agent-to-agent-mtls", false, false, false).unwrap();
assert_eq!(
r.get("status").and_then(|v| v.as_str()),
Some("recommended")
);
}
}