use crate::date::IsmDate;
use crate::generated::values;
use crate::span::Span;
pub use values::{DeclassExemption, DissemControl, SciControl, SciControlBare};
#[non_exhaustive]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct IsmAttributes {
pub classification: Option<MarkingClassification>,
pub sci_controls: Box<[SciControl]>,
pub sci_markings: Box<[SciMarking]>,
pub sar_markings: Option<SarMarking>,
pub aea_markings: Box<[AeaMarking]>,
pub fgi_marker: Option<FgiMarker>,
pub dissem_controls: Box<[DissemControl]>,
pub non_ic_dissem: Box<[NonIcDissem]>,
pub rel_to: Box<[CountryCode]>,
pub declassify_on: Option<IsmDate>,
pub classified_by: Option<Box<str>>,
pub derived_from: Option<Box<str>>,
pub declass_exemption: Option<DeclassExemption>,
pub token_spans: Box<[TokenSpan]>,
}
impl IsmAttributes {
pub fn us_classification(&self) -> Option<Classification> {
match self.classification {
Some(MarkingClassification::Us(c)) => Some(c),
Some(MarkingClassification::Conflict { us, .. }) => Some(us),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenSpan {
pub kind: TokenKind,
pub span: Span,
pub text: Box<str>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TokenKind {
Classification,
SciControl,
SciSystem,
SciCompartment,
SciSubCompartment,
#[deprecated(note = "use SarIndicator/SarProgram/SarCompartment/SarSubCompartment")]
SarIdentifier,
SarIndicator,
SarProgram,
SarCompartment,
SarSubCompartment,
AeaMarking,
FgiMarker,
DissemControl,
NonIcDissem,
RelToTrigraph,
RelToBlock,
DeclassExemption,
DeclassDate,
Separator,
Unknown,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SarMarking {
pub indicator: SarIndicator,
pub programs: Box<[SarProgram]>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SarIndicator {
Abbrev,
Full,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SarProgram {
pub identifier: Box<str>,
pub compartments: Box<[SarCompartment]>,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SarCompartment {
pub identifier: Box<str>,
pub sub_compartments: Box<[Box<str>]>,
}
impl SarMarking {
pub fn new(indicator: SarIndicator, programs: Box<[SarProgram]>) -> Self {
Self {
indicator,
programs,
}
}
}
impl SarProgram {
pub fn new(identifier: Box<str>, compartments: Box<[SarCompartment]>) -> Self {
Self {
identifier,
compartments,
}
}
}
impl SarCompartment {
pub fn new(identifier: Box<str>, sub_compartments: Box<[Box<str>]>) -> Self {
Self {
identifier,
sub_compartments,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MarkingClassification {
Us(Classification),
Fgi(FgiClassification),
Nato(NatoClassification),
Joint(JointClassification),
Conflict {
us: Classification,
foreign: Box<ForeignClassification>,
},
}
impl MarkingClassification {
pub fn effective_level(&self) -> Classification {
match self {
Self::Us(c) => *c,
Self::Fgi(f) => f.level,
Self::Nato(n) => n.us_equivalent(),
Self::Joint(j) => j.level,
Self::Conflict { us, .. } => *us,
}
}
}
impl Default for MarkingClassification {
fn default() -> Self {
Self::Us(Classification::Unclassified)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ForeignClassification {
Fgi(FgiClassification),
Nato(NatoClassification),
Joint(JointClassification),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Classification {
Unclassified,
Restricted,
Confidential,
Secret,
TopSecret,
}
impl Classification {
pub fn banner_str(self) -> &'static str {
match self {
Self::Unclassified => "UNCLASSIFIED",
Self::Restricted => "RESTRICTED",
Self::Confidential => "CONFIDENTIAL",
Self::Secret => "SECRET",
Self::TopSecret => "TOP SECRET",
}
}
pub fn portion_str(self) -> &'static str {
match self {
Self::Unclassified => "U",
Self::Restricted => "R",
Self::Confidential => "C",
Self::Secret => "S",
Self::TopSecret => "TS",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FgiClassification {
pub countries: Box<[CountryCode]>,
pub level: Classification,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NatoClassification {
NatoUnclassified, NatoRestricted, NatoConfidential, NatoConfidentialAtomal, NatoSecret, NatoSecretAtomal, CosmicTopSecret, CosmicTopSecretAtomal, CosmicTopSecretBohemia, CosmicTopSecretBalk, }
impl NatoClassification {
pub fn banner_str(self) -> &'static str {
match self {
Self::NatoUnclassified => "NATO UNCLASSIFIED",
Self::NatoRestricted => "NATO RESTRICTED",
Self::NatoConfidential => "NATO CONFIDENTIAL",
Self::NatoConfidentialAtomal => "NATO CONFIDENTIAL ATOMAL",
Self::NatoSecret => "NATO SECRET",
Self::NatoSecretAtomal => "NATO SECRET ATOMAL",
Self::CosmicTopSecret => "COSMIC TOP SECRET",
Self::CosmicTopSecretAtomal => "COSMIC TOP SECRET ATOMAL",
Self::CosmicTopSecretBohemia => "COSMIC TOP SECRET-BOHEMIA",
Self::CosmicTopSecretBalk => "COSMIC TOP SECRET-BALK",
}
}
pub fn portion_str(self) -> &'static str {
match self {
Self::NatoUnclassified => "NU",
Self::NatoRestricted => "NR",
Self::NatoConfidential => "NC",
Self::NatoConfidentialAtomal => "NCA",
Self::NatoSecret => "NS",
Self::NatoSecretAtomal => "NSAT",
Self::CosmicTopSecret => "CTS",
Self::CosmicTopSecretAtomal => "CTSA",
Self::CosmicTopSecretBohemia => "CTS-B",
Self::CosmicTopSecretBalk => "CTS-BALK",
}
}
pub fn base_level(self) -> NatoLevel {
match self {
Self::NatoUnclassified => NatoLevel::NatoUnclassified,
Self::NatoRestricted => NatoLevel::NatoRestricted,
Self::NatoConfidential | Self::NatoConfidentialAtomal => NatoLevel::NatoConfidential,
Self::NatoSecret | Self::NatoSecretAtomal => NatoLevel::NatoSecret,
Self::CosmicTopSecret
| Self::CosmicTopSecretAtomal
| Self::CosmicTopSecretBohemia
| Self::CosmicTopSecretBalk => NatoLevel::CosmicTopSecret,
}
}
pub fn us_equivalent(self) -> Classification {
match self.base_level() {
NatoLevel::NatoUnclassified => Classification::Unclassified,
NatoLevel::NatoRestricted => Classification::Restricted,
NatoLevel::NatoConfidential => Classification::Confidential,
NatoLevel::NatoSecret => Classification::Secret,
NatoLevel::CosmicTopSecret => Classification::TopSecret,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum NatoLevel {
NatoUnclassified,
NatoRestricted,
NatoConfidential,
NatoSecret,
CosmicTopSecret,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JointClassification {
pub level: Classification,
pub countries: Box<[CountryCode]>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum AeaMarking {
Rd(RdBlock),
Frd(FrdBlock),
DodUcni,
DoeUcni,
Tfni,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RdBlock {
pub cnwdi: bool,
pub sigma: Box<[u8]>,
}
impl Default for RdBlock {
fn default() -> Self {
Self {
cnwdi: false,
sigma: Box::new([]),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FrdBlock {
pub sigma: Box<[u8]>,
}
impl Default for FrdBlock {
fn default() -> Self {
Self {
sigma: Box::new([]),
}
}
}
impl AeaMarking {
pub fn banner_str(&self) -> String {
match self {
Self::Rd(rd) => {
let mut s = "RD".to_owned();
if rd.cnwdi {
s.push_str("-CNWDI");
}
if !rd.sigma.is_empty() {
s.push_str("-SIGMA ");
let nums: Vec<String> = rd.sigma.iter().map(|n| n.to_string()).collect();
s.push_str(&nums.join(" "));
}
s
}
Self::Frd(frd) => {
let mut s = "FRD".to_owned();
if !frd.sigma.is_empty() {
s.push_str("-SIGMA ");
let nums: Vec<String> = frd.sigma.iter().map(|n| n.to_string()).collect();
s.push_str(&nums.join(" "));
}
s
}
Self::DodUcni => "DOD UCNI".to_owned(),
Self::DoeUcni => "DOE UCNI".to_owned(),
Self::Tfni => "TFNI".to_owned(),
}
}
pub fn portion_str(&self) -> String {
match self {
Self::Rd(rd) => {
let mut s = "RD".to_owned();
if rd.cnwdi {
s.push_str("-CNWDI");
}
if !rd.sigma.is_empty() {
s.push_str("-SG ");
let nums: Vec<String> = rd.sigma.iter().map(|n| n.to_string()).collect();
s.push_str(&nums.join(" "));
}
s
}
Self::Frd(frd) => {
let mut s = "FRD".to_owned();
if !frd.sigma.is_empty() {
s.push_str("-SG ");
let nums: Vec<String> = frd.sigma.iter().map(|n| n.to_string()).collect();
s.push_str(&nums.join(" "));
}
s
}
Self::DodUcni => "DCNI".to_owned(),
Self::DoeUcni => "UCNI".to_owned(),
Self::Tfni => "TFNI".to_owned(),
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"DOD UCNI" | "DCNI" => return Some(Self::DodUcni),
"DOE UCNI" | "UCNI" => return Some(Self::DoeUcni),
"TFNI" | "TRANSCLASSIFIED FOREIGN NUCLEAR INFORMATION" => return Some(Self::Tfni),
_ => {}
}
if s == "RD" || s == "RESTRICTED DATA" {
return Some(Self::Rd(RdBlock::default()));
}
if let Some(rest) = s
.strip_prefix("RD-")
.or_else(|| s.strip_prefix("RESTRICTED DATA-"))
{
return Self::parse_rd_modifiers(rest);
}
if s == "FRD" || s == "FORMERLY RESTRICTED DATA" {
return Some(Self::Frd(FrdBlock::default()));
}
if let Some(rest) = s
.strip_prefix("FRD-")
.or_else(|| s.strip_prefix("FORMERLY RESTRICTED DATA-"))
{
return Self::parse_frd_modifiers(rest);
}
None
}
fn parse_rd_modifiers(s: &str) -> Option<Self> {
let mut cnwdi = false;
let mut rest = s;
if let Some(after) = rest.strip_prefix("CNWDI") {
cnwdi = true;
rest = after.strip_prefix('-').unwrap_or(after);
} else if rest == "N" {
return Some(Self::Rd(RdBlock {
cnwdi: true,
sigma: Box::new([]),
}));
}
let sigma = parse_sigma_numbers(rest);
if rest.is_empty() || !sigma.is_empty() {
Some(Self::Rd(RdBlock {
cnwdi,
sigma: sigma.into(),
}))
} else {
None
}
}
fn parse_frd_modifiers(s: &str) -> Option<Self> {
let sigma = parse_sigma_numbers(s);
if !sigma.is_empty() {
Some(Self::Frd(FrdBlock {
sigma: sigma.into(),
}))
} else {
None
}
}
}
fn parse_sigma_numbers(s: &str) -> Vec<u8> {
let rest = s
.strip_prefix("SIGMA ")
.or_else(|| s.strip_prefix("SG "))
.unwrap_or("");
if rest.is_empty() {
return vec![];
}
rest.split_whitespace()
.filter_map(|n| n.parse::<u8>().ok())
.collect()
}
impl std::fmt::Display for AeaMarking {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.portion_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FgiMarker {
pub countries: Box<[CountryCode]>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum NonIcDissem {
Limdis,
Exdis,
Nodis,
Sbu,
SbuNf,
Les,
LesNf,
Ssi,
}
impl NonIcDissem {
pub fn banner_str(self) -> &'static str {
match self {
Self::Limdis => "LIMDIS",
Self::Exdis => "EXDIS",
Self::Nodis => "NODIS",
Self::Sbu => "SBU",
Self::SbuNf => "SBU NOFORN",
Self::Les => "LES",
Self::LesNf => "LES NOFORN",
Self::Ssi => "SSI",
}
}
pub fn portion_str(self) -> &'static str {
match self {
Self::Limdis => "DS",
Self::Exdis => "XD",
Self::Nodis => "ND",
Self::Sbu => "SBU",
Self::SbuNf => "SBU-NF",
Self::Les => "LES",
Self::LesNf => "LES-NF",
Self::Ssi => "SSI",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"LIMDIS" | "DS" => Some(Self::Limdis),
"EXDIS" | "XD" => Some(Self::Exdis),
"NODIS" | "ND" => Some(Self::Nodis),
"SBU" => Some(Self::Sbu),
"SBU NOFORN" | "SBU-NF" => Some(Self::SbuNf),
"LES" => Some(Self::Les),
"LES NOFORN" | "LES-NF" => Some(Self::LesNf),
"SSI" => Some(Self::Ssi),
_ => None,
}
}
pub fn carries_noforn(self) -> bool {
matches!(self, Self::SbuNf | Self::LesNf)
}
pub fn propagates_to_classified_banner(self) -> bool {
match self {
Self::Limdis | Self::Sbu | Self::SbuNf => false,
Self::Exdis | Self::Nodis | Self::Les | Self::LesNf | Self::Ssi => true,
}
}
pub const ALL: &[NonIcDissem] = &[
Self::Limdis,
Self::Exdis,
Self::Nodis,
Self::Sbu,
Self::SbuNf,
Self::Les,
Self::LesNf,
Self::Ssi,
];
}
impl std::fmt::Display for NonIcDissem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.portion_str())
}
}
const COUNTRY_CODE_CAPACITY: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CountryCode {
bytes: [u8; COUNTRY_CODE_CAPACITY],
len: u8,
}
impl CountryCode {
pub const USA: Self = match Self::try_new(b"USA") {
Some(c) => c,
None => panic!("CountryCode::USA literal must satisfy try_new invariants"),
};
#[inline]
const fn is_valid_byte(b: u8) -> bool {
b.is_ascii_uppercase() || b.is_ascii_digit() || b == b'_'
}
#[inline]
pub const fn try_new(bytes: &[u8]) -> Option<Self> {
let len = bytes.len();
if len < 2 || len > COUNTRY_CODE_CAPACITY {
return None;
}
let mut padded = [0u8; COUNTRY_CODE_CAPACITY];
let mut i = 0;
while i < len {
if !Self::is_valid_byte(bytes[i]) {
return None;
}
padded[i] = bytes[i];
i += 1;
}
Some(Self {
bytes: padded,
len: len as u8,
})
}
#[inline]
pub fn as_str(&self) -> &str {
#[allow(unsafe_code)]
unsafe {
std::str::from_utf8_unchecked(self.as_bytes())
}
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len as usize]
}
#[inline]
pub const fn len(&self) -> usize {
self.len as usize
}
#[inline]
pub const fn is_empty(&self) -> bool {
false
}
}
impl std::fmt::Display for CountryCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod country_code_tests {
use super::CountryCode;
#[test]
fn try_new_accepts_two_byte_eu() {
let eu = CountryCode::try_new(b"EU").unwrap();
assert_eq!(eu.as_str(), "EU");
assert_eq!(eu.len(), 2);
}
#[test]
fn try_new_accepts_three_byte_trigraph() {
let usa = CountryCode::try_new(b"USA").unwrap();
assert_eq!(usa, CountryCode::USA);
assert_eq!(usa.as_str(), "USA");
}
#[test]
fn try_new_accepts_four_byte_tetragraph() {
let fvey = CountryCode::try_new(b"FVEY").unwrap();
assert_eq!(fvey.as_str(), "FVEY");
assert_eq!(fvey.len(), 4);
}
#[test]
fn try_new_accepts_australia_group_with_underscore() {
let ag = CountryCode::try_new(b"AUSTRALIA_GROUP").unwrap();
assert_eq!(ag.as_str(), "AUSTRALIA_GROUP");
assert_eq!(ag.len(), 15);
}
#[test]
fn try_new_accepts_digits_in_ax2_ax3() {
assert_eq!(CountryCode::try_new(b"AX2").unwrap().as_str(), "AX2");
assert_eq!(CountryCode::try_new(b"AX3").unwrap().as_str(), "AX3");
}
#[test]
fn try_new_rejects_too_short() {
assert!(CountryCode::try_new(b"").is_none());
assert!(CountryCode::try_new(b"X").is_none());
}
#[test]
fn try_new_rejects_too_long() {
assert!(CountryCode::try_new(b"ABCDEFGHIJKLMNOPQ").is_none());
}
#[test]
fn try_new_rejects_lowercase() {
assert!(CountryCode::try_new(b"usa").is_none());
assert!(CountryCode::try_new(b"Fvey").is_none());
}
#[test]
fn try_new_rejects_non_ascii() {
let bytes = "ÉU".as_bytes();
assert!(CountryCode::try_new(bytes).is_none());
}
#[test]
fn ord_matches_str_lex_for_mixed_lengths() {
let eu = CountryCode::try_new(b"EU").unwrap();
let aus = CountryCode::try_new(b"AUS").unwrap();
let usa = CountryCode::USA;
let usab = CountryCode::try_new(b"USAB").unwrap();
let mut all = [eu, aus, usa, usab];
all.sort();
assert_eq!(all[0].as_str(), "AUS");
assert_eq!(all[1].as_str(), "EU");
assert_eq!(all[2].as_str(), "USA");
assert_eq!(all[3].as_str(), "USAB");
}
#[test]
fn copy_semantics_preserved() {
let original = CountryCode::USA;
let copy = original;
assert_eq!(original, copy);
assert_eq!(original.as_str(), copy.as_str());
}
#[test]
fn display_renders_active_bytes_only() {
let usa = CountryCode::USA;
let fvey = CountryCode::try_new(b"FVEY").unwrap();
let ag = CountryCode::try_new(b"AUSTRALIA_GROUP").unwrap();
assert_eq!(format!("{usa}"), "USA");
assert_eq!(format!("{fvey}"), "FVEY");
assert_eq!(format!("{ag}"), "AUSTRALIA_GROUP");
}
#[test]
fn as_bytes_excludes_zero_padding() {
let usa = CountryCode::USA;
assert_eq!(usa.as_bytes(), b"USA");
let fvey = CountryCode::try_new(b"FVEY").unwrap();
assert_eq!(fvey.as_bytes(), b"FVEY");
}
#[test]
fn is_empty_invariant_always_false() {
assert!(!CountryCode::USA.is_empty());
assert!(!CountryCode::try_new(b"EU").unwrap().is_empty());
assert!(!CountryCode::try_new(b"AUSTRALIA_GROUP").unwrap().is_empty());
}
#[test]
fn usa_constant_matches_try_new() {
let runtime = CountryCode::try_new(b"USA").unwrap();
assert_eq!(CountryCode::USA, runtime);
assert_eq!(CountryCode::USA.as_bytes(), runtime.as_bytes());
assert_eq!(CountryCode::USA.len(), runtime.len());
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SciMarking {
pub system: SciControlSystem,
pub compartments: Box<[SciCompartment]>,
pub canonical_enum: Option<SciControl>,
}
impl SciMarking {
pub fn new(
system: SciControlSystem,
compartments: Box<[SciCompartment]>,
canonical_enum: Option<SciControl>,
) -> Self {
Self {
system,
compartments,
canonical_enum,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SciControlSystem {
Published(SciControlBare),
Custom(Box<str>),
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SciCompartment {
pub identifier: Box<str>,
pub sub_compartments: Box<[Box<str>]>,
}
impl SciCompartment {
pub fn new(identifier: Box<str>, sub_compartments: Box<[Box<str>]>) -> Self {
Self {
identifier,
sub_compartments,
}
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn classification_ord_is_restrictiveness() {
assert!(Classification::Unclassified < Classification::Restricted);
assert!(Classification::Restricted < Classification::Confidential);
assert!(Classification::Confidential < Classification::Secret);
assert!(Classification::Secret < Classification::TopSecret);
}
#[test]
fn classification_banner_portion_round_trip() {
for c in [
Classification::Unclassified,
Classification::Restricted,
Classification::Confidential,
Classification::Secret,
Classification::TopSecret,
] {
assert!(!c.banner_str().is_empty());
assert!(!c.portion_str().is_empty());
}
}
#[test]
fn nato_us_equivalent_mapping() {
assert_eq!(
NatoClassification::CosmicTopSecret.us_equivalent(),
Classification::TopSecret,
);
assert_eq!(
NatoClassification::NatoSecret.us_equivalent(),
Classification::Secret,
);
assert_eq!(
NatoClassification::NatoRestricted.us_equivalent(),
Classification::Restricted,
);
}
#[test]
fn nato_banner_portion_round_trip() {
for n in [
NatoClassification::NatoUnclassified,
NatoClassification::NatoRestricted,
NatoClassification::NatoConfidential,
NatoClassification::NatoConfidentialAtomal,
NatoClassification::NatoSecret,
NatoClassification::NatoSecretAtomal,
NatoClassification::CosmicTopSecret,
NatoClassification::CosmicTopSecretAtomal,
NatoClassification::CosmicTopSecretBohemia,
NatoClassification::CosmicTopSecretBalk,
] {
assert!(!n.banner_str().is_empty());
assert!(!n.portion_str().is_empty());
}
}
#[test]
fn us_classification_convenience_returns_us() {
let attrs = IsmAttributes {
classification: Some(MarkingClassification::Us(Classification::Secret)),
..Default::default()
};
assert_eq!(attrs.us_classification(), Some(Classification::Secret));
}
#[test]
fn us_classification_convenience_returns_none_for_nato() {
let attrs = IsmAttributes {
classification: Some(MarkingClassification::Nato(NatoClassification::NatoSecret)),
..Default::default()
};
assert_eq!(attrs.us_classification(), None);
}
#[test]
fn us_classification_convenience_returns_resolved_for_conflict() {
let attrs = IsmAttributes {
classification: Some(MarkingClassification::Conflict {
us: Classification::TopSecret,
foreign: Box::new(ForeignClassification::Nato(
NatoClassification::CosmicTopSecret,
)),
}),
..Default::default()
};
assert_eq!(attrs.us_classification(), Some(Classification::TopSecret));
}
}