1use {
11 super::{
12 EXIT_BAD_SIGNATURE, EXIT_MISMATCH, EXIT_NO_APPROVAL, EXIT_REFUSED,
13 cli::{action_name, pattern_name},
14 verify::{CheckedEntry, VerifyOutcome},
15 },
16 krabka_protocol::krabka::{break_glass as bg, freeze as api},
17};
18
19#[must_use]
26pub fn exit_for_code(code: i16) -> i32 {
27 use crate::broker_codes as codes;
28
29 match code {
30 0 => 0,
31 codes::BREAK_GLASS_APPROVAL_REQUIRED => EXIT_NO_APPROVAL,
32 codes::OPERATOR_SIGNATURE_INVALID | codes::OPERATOR_SIGNATURE_REQUIRED => {
33 EXIT_BAD_SIGNATURE
34 }
35 _ => EXIT_REFUSED,
36 }
37}
38
39#[must_use]
44pub fn code_name(code: i16) -> String {
45 use crate::broker_codes as codes;
46
47 let note = match code {
48 codes::BREAK_GLASS_APPROVAL_REQUIRED => {
49 Some("no approved break-glass proposal covers this")
50 }
51 codes::BREAK_GLASS_DUPLICATE_APPROVER => Some("this principal already approved"),
52 codes::BREAK_GLASS_NOT_AN_APPROVER => Some("this principal is not a configured approver"),
53 codes::OPERATOR_SIGNATURE_INVALID => Some("the operator signature did not verify"),
54 codes::OPERATOR_SIGNATURE_REQUIRED => Some("this action needs an operator signature"),
55 codes::FREEZE_SCOPE_INVALID => Some("the scope is empty or reaches an internal topic"),
56 codes::FREEZE_LIMIT_EXCEEDED => Some("the registry is at freeze.max_entries"),
57 _ => None,
58 };
59 note.map_or_else(
60 || format!("error {code}"),
61 |note| format!("error {code} ({note})"),
62 )
63}
64
65pub fn described_error(code: i16, message: Option<&str>) -> String {
67 format!(
68 "{}{}",
69 code_name(code),
70 message.map_or_else(String::new, |m| format!(": {m}"))
71 )
72}
73
74pub fn report_error(code: i16, message: Option<&str>) -> i32 {
76 eprintln!("{}", described_error(code, message));
77 exit_for_code(code)
78}
79
80pub fn report_set_freeze(response: &api::SetTopicFreezeResponse, frozen: bool) -> i32 {
82 if response.error_code != 0 {
83 return report_error(response.error_code, response.error_message.as_deref());
84 }
85 println!("{}\tok", if frozen { "frozen" } else { "thawed" });
86 0
87}
88
89pub fn print_freeze(freeze: &api::DescribedTopicFreeze, checked: Option<&CheckedEntry>) {
91 let proof = match checked.map(|entry| entry.unproved) {
92 None => String::new(),
93 Some(None) => format!("\tverified by {}", freeze.key_id),
94 Some(Some(unproved)) => format!("\t{}", unproved.reason()),
95 };
96 println!(
97 "{}:{}\tset by {} at {}\t{}{proof}",
98 pattern_name(freeze.pattern_type),
99 freeze.scope,
100 freeze.set_by,
101 freeze.set_at_ms,
102 freeze.reason,
103 );
104}
105
106pub fn print_proposal(proposal: &bg::DescribedBreakGlassProposal) {
108 let state = if proposal.withdrawn {
109 "withdrawn"
110 } else if proposal.consumed_at_ms > 0 {
111 "consumed"
112 } else {
113 "open"
114 };
115 println!(
116 "proposal {} {state}",
117 uuid::Uuid::from_bytes(proposal.proposal_id.0)
118 );
119 println!(" action {}", action_name(proposal.action));
120 println!(" target {}", proposal.target);
121 println!(" proposer {}", proposal.proposer);
122 println!(" reason {}", proposal.reason);
123 println!(" created {}", proposal.created_at_ms);
124 println!(" expires {}", proposal.expires_at_ms);
125 for approval in &proposal.approvals {
126 println!(
127 " approved {} at {} {}",
128 approval.principal,
129 approval.approved_at_ms,
130 approval_evidence(approval)
131 );
132 }
133}
134
135fn approval_evidence(approval: &bg::BreakGlassApproval) -> String {
142 const PREFIX: usize = 8;
144
145 if approval.key_id.is_empty() {
146 return "unsigned".to_owned();
147 }
148 format!(
149 "signed by {} ({}...)",
150 approval.key_id,
151 hex::encode(
152 approval
153 .signature
154 .get(..PREFIX)
155 .unwrap_or(&approval.signature)
156 )
157 )
158}
159
160pub fn report_verify(freezes: &[api::DescribedTopicFreeze], outcome: &VerifyOutcome) -> i32 {
166 for (freeze, checked) in freezes.iter().zip(&outcome.entries) {
167 print_freeze(freeze, Some(checked));
168 }
169 if outcome.any_signature_failed() {
170 eprintln!("the registry does not verify against the operator keys on this machine");
171 return EXIT_BAD_SIGNATURE;
172 }
173 if outcome.any_key_is_unknown() {
174 eprintln!("the registry names an operator key that this machine does not hold");
175 return EXIT_MISMATCH;
176 }
177 println!(
178 "{} of {} entries are proved by an operator signature",
179 outcome.proved(),
180 outcome.entries.len()
181 );
182 0
183}
184
185#[cfg(test)]
186mod tests;