1use serde::{Deserialize, Serialize};
13
14pub const TDF_SPEC_VERSION: &str = "4.3.0";
16
17#[derive(Debug, Serialize, Deserialize)]
19pub struct TdfManifest {
20 pub payload: Payload,
21 #[serde(rename = "encryptionInformation")]
22 pub encryption_information: EncryptionInformation,
23 #[serde(rename = "schemaVersion", skip_serializing_if = "Option::is_none")]
24 pub schema_version: Option<String>,
25 #[serde(rename = "tdf_spec_version", skip_serializing_if = "Option::is_none")]
28 pub tdf_spec_version: Option<String>,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub gguf: Option<crate::GgufIndex>,
32 #[serde(
36 default,
37 deserialize_with = "null_as_empty_vec",
38 skip_serializing_if = "Vec::is_empty"
39 )]
40 pub assertions: Vec<Assertion>,
41}
42
43fn null_as_empty_vec<'de, D>(deserializer: D) -> Result<Vec<Assertion>, D::Error>
46where
47 D: serde::Deserializer<'de>,
48{
49 Ok(Option::<Vec<Assertion>>::deserialize(deserializer)?.unwrap_or_default())
50}
51
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub struct Assertion {
58 pub id: String,
60 #[serde(rename = "type")]
62 pub assertion_type: String,
63 pub scope: String,
65 #[serde(rename = "appliesToState", skip_serializing_if = "Option::is_none")]
68 pub applies_to_state: Option<String>,
69 pub statement: AssertionStatement,
71 #[serde(skip_serializing_if = "Option::is_none")]
74 pub binding: Option<AssertionBinding>,
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79pub struct AssertionStatement {
80 #[serde(default, skip_serializing_if = "String::is_empty")]
82 pub format: String,
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub schema: Option<String>,
86 #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
90 pub value: serde_json::Value,
91}
92
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95pub struct AssertionBinding {
100 #[serde(default, skip_serializing_if = "String::is_empty")]
102 pub method: String,
103 #[serde(default, skip_serializing_if = "String::is_empty")]
105 pub signature: String,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct Payload {
111 #[serde(rename = "type")]
112 pub payload_type: String,
113 pub url: String,
114 pub protocol: String,
115 #[serde(rename = "isEncrypted")]
116 pub is_encrypted: bool,
117 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
118 pub mime_type: Option<String>,
119 #[serde(rename = "tdf_spec_version", skip_serializing_if = "Option::is_none")]
120 pub tdf_spec_version: Option<String>,
121}
122
123impl Default for Payload {
124 fn default() -> Self {
125 Self {
126 payload_type: "reference".to_string(),
127 url: "0.payload".to_string(),
128 protocol: "zip".to_string(),
129 is_encrypted: true,
130 mime_type: Some("application/octet-stream".to_string()),
131 tdf_spec_version: None,
135 }
136 }
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct EncryptionInformation {
142 #[serde(rename = "type")]
143 pub encryption_type: String,
144 #[serde(rename = "keyAccess")]
145 pub key_access: Vec<KeyAccess>,
146 pub method: EncryptionMethod,
147 #[serde(rename = "integrityInformation")]
148 pub integrity_information: IntegrityInformation,
149 pub policy: String,
150}
151
152#[derive(Debug, Serialize, Deserialize, Clone)]
154pub struct PolicyBinding {
155 pub alg: String,
156 pub hash: String,
157}
158
159pub mod key_access_type {
161 pub const WRAPPED: &str = "wrapped";
163 pub const EC_WRAPPED: &str = "ec-wrapped";
165 pub const REMOTE: &str = "remote";
167 pub const REMOTE_WRAPPED: &str = "remoteWrapped";
169}
170
171#[derive(Debug, Serialize, Deserialize, Clone)]
173pub struct KeyAccess {
174 #[serde(rename = "type")]
175 pub access_type: String,
176 pub url: String,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub kid: Option<String>,
179 #[serde(skip_serializing_if = "Option::is_none")]
181 pub sid: Option<String>,
182 pub protocol: String,
183 #[serde(rename = "wrappedKey")]
184 pub wrapped_key: String,
185 #[serde(rename = "policyBinding")]
186 pub policy_binding: PolicyBinding,
187 #[serde(rename = "encryptedMetadata", skip_serializing_if = "Option::is_none")]
188 pub encrypted_metadata: Option<String>,
189 #[serde(rename = "schemaVersion", skip_serializing_if = "Option::is_none")]
190 pub schema_version: Option<String>,
191 #[serde(rename = "ephemeralPublicKey", skip_serializing_if = "Option::is_none")]
193 pub ephemeral_public_key: Option<String>,
194}
195
196impl KeyAccess {
197 pub fn new(url: String) -> Self {
199 KeyAccess {
200 access_type: key_access_type::WRAPPED.to_string(),
201 url,
202 kid: None,
203 sid: None,
204 protocol: "kas".to_string(),
205 wrapped_key: String::new(),
206 policy_binding: PolicyBinding {
207 alg: "HS256".to_string(),
208 hash: String::new(),
209 },
210 encrypted_metadata: None,
211 schema_version: Some("1.0".to_string()),
212 ephemeral_public_key: None,
213 }
214 }
215
216 pub fn new_ec_wrapped(url: String) -> Self {
218 KeyAccess {
219 access_type: key_access_type::EC_WRAPPED.to_string(),
220 url,
221 kid: None,
222 sid: None,
223 protocol: "kas".to_string(),
224 wrapped_key: String::new(),
225 policy_binding: PolicyBinding {
226 alg: "HS256".to_string(),
227 hash: String::new(),
228 },
229 encrypted_metadata: None,
230 schema_version: Some("1.0".to_string()),
231 ephemeral_public_key: None,
232 }
233 }
234
235 pub fn is_ec_wrapped(&self) -> bool {
237 self.access_type == key_access_type::EC_WRAPPED
238 }
239
240 pub fn is_rsa_wrapped(&self) -> bool {
242 self.access_type == key_access_type::WRAPPED
243 }
244
245 pub fn set_ephemeral_public_key(&mut self, pem: String) {
247 self.ephemeral_public_key = Some(pem);
248 }
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct EncryptionMethod {
254 pub algorithm: String,
255 #[serde(rename = "isStreamable", default)]
257 pub is_streamable: bool,
258 #[serde(default)]
262 pub iv: String,
263}
264
265impl Default for EncryptionMethod {
266 fn default() -> Self {
267 Self {
268 algorithm: "AES-256-GCM".to_string(),
269 is_streamable: true,
270 iv: String::new(),
271 }
272 }
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct IntegrityInformation {
278 #[serde(rename = "rootSignature")]
279 pub root_signature: RootSignature,
280 #[serde(rename = "segmentHashAlg")]
281 pub segment_hash_alg: String,
282 pub segments: Vec<Segment>,
283 #[serde(rename = "segmentSizeDefault")]
284 pub segment_size_default: u64,
285 #[serde(rename = "encryptedSegmentSizeDefault")]
286 pub encrypted_segment_size_default: u64,
287}
288
289impl Default for IntegrityInformation {
290 fn default() -> Self {
291 Self {
292 root_signature: RootSignature::default(),
293 segment_hash_alg: "GMAC".to_string(),
294 segments: Vec::new(),
295 segment_size_default: 1024 * 1024, encrypted_segment_size_default: 1024 * 1024 + 28, }
298 }
299}
300
301impl IntegrityInformation {
302 pub fn segment_sizes(&self) -> Vec<(u64, u64)> {
308 self.segments
309 .iter()
310 .map(|s| {
311 (
312 s.segment_size.unwrap_or(self.segment_size_default),
313 s.encrypted_segment_size
314 .unwrap_or(self.encrypted_segment_size_default),
315 )
316 })
317 .collect()
318 }
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct RootSignature {
324 pub alg: String,
325 pub sig: String,
326}
327
328impl Default for RootSignature {
329 fn default() -> Self {
330 Self {
331 alg: "HS256".to_string(),
332 sig: String::new(),
333 }
334 }
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct Segment {
340 pub hash: String,
341 #[serde(rename = "segmentSize", skip_serializing_if = "Option::is_none")]
342 pub segment_size: Option<u64>,
343 #[serde(
344 rename = "encryptedSegmentSize",
345 skip_serializing_if = "Option::is_none"
346 )]
347 pub encrypted_segment_size: Option<u64>,
348}
349
350impl TdfManifest {
351 pub fn new(payload_url: String, kas_url: String) -> Self {
353 TdfManifest {
354 payload: Payload {
355 payload_type: "reference".to_string(),
356 url: payload_url,
357 protocol: "zip".to_string(),
358 is_encrypted: true,
359 mime_type: Some("application/octet-stream".to_string()),
360 tdf_spec_version: None,
362 },
363 encryption_information: EncryptionInformation {
364 encryption_type: "split".to_string(),
365 key_access: vec![KeyAccess::new(kas_url)],
366 method: EncryptionMethod {
367 algorithm: "AES-256-GCM".to_string(),
368 is_streamable: true,
369 iv: String::new(),
370 },
371 integrity_information: IntegrityInformation {
372 root_signature: RootSignature {
373 alg: "HS256".to_string(),
374 sig: String::new(),
375 },
376 segment_hash_alg: "GMAC".to_string(),
377 segments: Vec::new(),
378 segment_size_default: 1024 * 1024, encrypted_segment_size_default: 1024 * 1024 + 28, },
381 policy: String::new(),
382 },
383 schema_version: Some(TDF_SPEC_VERSION.to_string()),
384 tdf_spec_version: None,
385 gguf: None,
386 assertions: Vec::new(),
387 }
388 }
389
390 pub fn spec_version(&self) -> Option<&str> {
394 [
395 self.schema_version.as_deref(),
396 self.tdf_spec_version.as_deref(),
397 self.payload.tdf_spec_version.as_deref(),
398 ]
399 .into_iter()
400 .flatten()
401 .find(|v| !v.is_empty())
402 }
403
404 pub fn set_policy_raw(&mut self, policy: &str) {
406 use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
407 self.encryption_information.policy = BASE64.encode(policy);
408 }
409
410 pub fn get_policy_raw(&self) -> Result<String, base64::DecodeError> {
412 use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
413 let bytes = BASE64.decode(&self.encryption_information.policy)?;
414 String::from_utf8(bytes)
415 .map_err(|err| base64::DecodeError::InvalidByte(err.utf8_error().valid_up_to(), 0))
416 }
417
418 pub fn add_segment(
420 &mut self,
421 hash: String,
422 segment_size: Option<u64>,
423 encrypted_segment_size: Option<u64>,
424 ) {
425 self.encryption_information
426 .integrity_information
427 .segments
428 .push(Segment {
429 hash,
430 segment_size,
431 encrypted_segment_size,
432 });
433 }
434
435 pub fn to_json(&self) -> Result<String, serde_json::Error> {
437 serde_json::to_string(self)
438 }
439
440 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
442 serde_json::from_str(json)
443 }
444}
445
446impl KeyAccess {
447 pub fn set_encrypted_metadata(&mut self, metadata: &str) {
449 use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
450 self.encrypted_metadata = Some(BASE64.encode(metadata));
451 }
452
453 pub fn clear_encrypted_metadata(&mut self) {
455 self.encrypted_metadata = None;
456 }
457}
458
459#[cfg(test)]
460mod gguf_index_tests {
461 use super::*;
462
463 #[test]
464 fn gguf_index_round_trips_and_stays_absent_by_default() {
465 let plain = TdfManifest::new(
467 "0.payload".to_string(),
468 "https://kas.example.com".to_string(),
469 );
470 let value: serde_json::Value = serde_json::from_str(&plain.to_json().unwrap()).unwrap();
471 assert!(
472 value.get("gguf").is_none(),
473 "gguf must be absent when unset: {value}"
474 );
475 assert!(
476 value.get("tdf_spec_version").is_none(),
477 "tdf_spec_version must be absent when unset: {value}"
478 );
479
480 let mut m = TdfManifest::new("header".to_string(), "https://kas.example.com".to_string());
482 m.tdf_spec_version = Some("4.3.0".to_string());
483 m.gguf = Some(crate::GgufIndex {
484 profile: crate::GGUF_TDF_PROFILE_V1.to_string(),
485 alignment: 32,
486 header_bytes: 64,
487 virtual_size: 352,
488 max_segment: 128,
489 tensors: vec![crate::GgufTensor {
490 name: "token_embd.weight".to_string(),
491 offset: 64,
492 size: 256,
493 segments: [1, 3],
494 }],
495 segments: vec![crate::GgufSegment {
496 id: 0,
497 kind: crate::GgufSegmentKind::Header,
498 plain: 64,
499 entry: "header".to_string(),
500 }],
501 });
502
503 let json = m.to_json().unwrap();
504 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
505 assert_eq!(value["tdf_spec_version"], "4.3.0");
506 assert_eq!(value["gguf"]["profile"], "gguf-tdf/1");
507 assert_eq!(value["gguf"]["headerBytes"], 64);
508 assert_eq!(value["gguf"]["maxSegment"], 128);
509 assert_eq!(value["gguf"]["segments"][0]["kind"], "header");
510 assert_eq!(value["gguf"]["tensors"][0]["segments"][1], 3);
511
512 let back = TdfManifest::from_json(&json).unwrap();
513 assert_eq!(back.gguf.as_ref().unwrap().virtual_size, 352);
514 assert_eq!(back.tdf_spec_version.as_deref(), Some("4.3.0"));
515 }
516}
517
518#[cfg(test)]
519mod spec_version_tests {
520 use super::*;
521
522 fn minimal_json(top: &str, payload_extra: &str) -> String {
523 format!(
524 r#"{{
525 "payload": {{"type":"reference","url":"0.payload","protocol":"zip","isEncrypted":true{payload_extra}}},
526 "encryptionInformation": {{
527 "type":"split","keyAccess":[],
528 "method":{{"algorithm":"AES-256-GCM","isStreamable":true,"iv":""}},
529 "integrityInformation":{{"rootSignature":{{"alg":"HS256","sig":""}},"segmentHashAlg":"GMAC","segments":[],"segmentSizeDefault":0,"encryptedSegmentSizeDefault":0}},
530 "policy":""
531 }}{top}
532 }}"#
533 )
534 }
535
536 #[test]
537 fn spec_version_prefers_schema_version() {
538 let m = TdfManifest::from_json(&minimal_json(
539 r#","schemaVersion":"4.3.0","tdf_spec_version":"9.9.9""#,
540 r#","tdf_spec_version":"8.8.8""#,
541 ))
542 .unwrap();
543 assert_eq!(m.spec_version(), Some("4.3.0"));
544 }
545
546 #[test]
547 fn spec_version_then_top_level_tdf_spec_version() {
548 let m = TdfManifest::from_json(&minimal_json(
549 r#","tdf_spec_version":"9.9.9""#,
550 r#","tdf_spec_version":"8.8.8""#,
551 ))
552 .unwrap();
553 assert_eq!(m.spec_version(), Some("9.9.9"));
554 }
555
556 #[test]
557 fn spec_version_then_payload_tdf_spec_version() {
558 let m =
559 TdfManifest::from_json(&minimal_json("", r#","tdf_spec_version":"8.8.8""#)).unwrap();
560 assert_eq!(m.spec_version(), Some("8.8.8"));
561 }
562
563 #[test]
564 fn spec_version_absent_is_none() {
565 let m = TdfManifest::from_json(&minimal_json("", "")).unwrap();
566 assert_eq!(m.spec_version(), None);
567 }
568
569 #[test]
570 fn new_manifest_writes_schema_version_4_3_0_and_no_tdf_spec_version() {
571 let m = TdfManifest::new(
572 "0.payload".to_string(),
573 "https://kas.example.com".to_string(),
574 );
575 let v: serde_json::Value = serde_json::from_str(&m.to_json().unwrap()).unwrap();
576 assert_eq!(v["schemaVersion"], TDF_SPEC_VERSION);
577 assert_eq!(v["schemaVersion"], "4.3.0");
578 assert!(v.get("tdf_spec_version").is_none());
579 assert!(v["payload"].get("tdf_spec_version").is_none());
580 }
581}
582
583#[cfg(test)]
584mod peer_method_tolerance_tests {
585 use super::*;
586
587 #[test]
589 fn method_without_iv_or_is_streamable_parses() {
590 let m: EncryptionMethod = serde_json::from_str(r#"{"algorithm":"AES-256-GCM"}"#).unwrap();
591 assert_eq!(m.algorithm, "AES-256-GCM");
592 assert!(!m.is_streamable);
593 assert_eq!(m.iv, "");
594
595 let m: EncryptionMethod =
596 serde_json::from_str(r#"{"algorithm":"AES-256-GCM","isStreamable":true}"#).unwrap();
597 assert!(m.is_streamable);
598 assert_eq!(m.iv, "");
599 }
600}
601
602#[cfg(test)]
603mod sid_and_assertion_tests {
604 use super::*;
605
606 const MANIFEST_WITH_SID_AND_ASSERTION: &str = r#"{
609 "payload": {"type":"reference","url":"0.payload","protocol":"zip","isEncrypted":true,"mimeType":"text/plain"},
610 "encryptionInformation": {
611 "type":"split",
612 "keyAccess":[{
613 "type":"wrapped",
614 "url":"https://kas.example.com",
615 "kid":"r1",
616 "sid":"split-id-1",
617 "protocol":"kas",
618 "wrappedKey":"AAAA",
619 "policyBinding":{"alg":"HS256","hash":"BBBB"}
620 }],
621 "method":{"algorithm":"AES-256-GCM","isStreamable":true,"iv":""},
622 "integrityInformation":{"rootSignature":{"alg":"HS256","sig":""},"segmentHashAlg":"GMAC","segments":[],"segmentSizeDefault":0,"encryptedSegmentSizeDefault":0},
623 "policy":""
624 },
625 "assertions":[{
626 "id":"nato-label-1",
627 "type":"handling",
628 "scope":"payload",
629 "appliesToState":"encrypted",
630 "statement":{
631 "schema":"urn:nato:stanag:4774:confidentialitymetadatalabel:1:0",
632 "format":"json-structured",
633 "value":{
634 "Xmlns":"urn:nato:stanag:4774:confidentialitymetadatalabel:1:0",
635 "CreationTime":"2015-08-29T16:15:00Z",
636 "ConfidentialityInformation":{"PolicyIdentifier":"NATO","Classification":"SECRET","nested":[1,true,null,{"k":"v"}]}
637 }
638 },
639 "binding":{"method":"jws","signature":"eyJhbGciOiJSUzI1NiJ9.e30.sig"}
640 }],
641 "schemaVersion":"4.3.0"
642 }"#;
643
644 #[test]
645 fn manifest_round_trips_key_access_sid_and_assertions() {
646 let input: serde_json::Value =
647 serde_json::from_str(MANIFEST_WITH_SID_AND_ASSERTION).unwrap();
648
649 let m = TdfManifest::from_json(MANIFEST_WITH_SID_AND_ASSERTION).unwrap();
650
651 assert_eq!(
653 m.encryption_information.key_access[0].sid.as_deref(),
654 Some("split-id-1")
655 );
656 assert_eq!(m.assertions.len(), 1);
657 let a = &m.assertions[0];
658 assert_eq!(a.id, "nato-label-1");
659 assert_eq!(a.assertion_type, "handling");
660 assert_eq!(a.scope, "payload");
661 assert_eq!(a.applies_to_state.as_deref(), Some("encrypted"));
662 assert_eq!(a.statement.format, "json-structured");
663 assert_eq!(
664 a.statement.schema.as_deref(),
665 Some("urn:nato:stanag:4774:confidentialitymetadatalabel:1:0")
666 );
667 assert!(
668 a.statement.value.is_object(),
669 "statement.value must stay structured JSON: {:?}",
670 a.statement.value
671 );
672 let b = a.binding.as_ref().expect("binding present");
673 assert_eq!(b.method, "jws");
674 assert_eq!(b.signature, "eyJhbGciOiJSUzI1NiJ9.e30.sig");
675
676 let output: serde_json::Value = serde_json::from_str(&m.to_json().unwrap()).unwrap();
678 assert_eq!(output, input);
679 }
680
681 #[test]
682 fn manifest_with_string_statement_value_round_trips() {
683 let json = MANIFEST_WITH_SID_AND_ASSERTION
684 .replace(r#""format":"json-structured","#, r#""format":"string","#);
685 let mut input: serde_json::Value = serde_json::from_str(&json).unwrap();
687 input["assertions"][0]["statement"]["value"] =
688 serde_json::Value::String("plain text".into());
689
690 let m: TdfManifest = serde_json::from_value(input.clone()).unwrap();
691 assert_eq!(
692 m.assertions[0].statement.value,
693 serde_json::json!("plain text")
694 );
695
696 let output = serde_json::to_value(&m).unwrap();
697 assert_eq!(output, input);
698 }
699
700 #[test]
701 fn manifest_without_sid_or_assertions_serializes_unchanged() {
702 let m = TdfManifest::new(
704 "0.payload".to_string(),
705 "https://kas.example.com".to_string(),
706 );
707 assert!(m.encryption_information.key_access[0].sid.is_none());
708 assert!(m.assertions.is_empty());
709
710 let v: serde_json::Value = serde_json::from_str(&m.to_json().unwrap()).unwrap();
711 assert!(v.get("assertions").is_none(), "{v}");
712 assert!(
713 v["encryptionInformation"]["keyAccess"][0]
714 .get("sid")
715 .is_none(),
716 "{v}"
717 );
718
719 let json = MANIFEST_WITH_SID_AND_ASSERTION.replace(r#""sid":"split-id-1","#, "");
721 let mut without: serde_json::Value = serde_json::from_str(&json).unwrap();
722 without.as_object_mut().unwrap().remove("assertions");
723 let m: TdfManifest = serde_json::from_value(without).unwrap();
724 assert!(m.assertions.is_empty());
725 assert!(m.encryption_information.key_access[0].sid.is_none());
726 }
727
728 #[test]
731 fn assertions_null_parses_as_empty() {
732 let json = r#"{
733 "payload": {"type":"reference","url":"0.payload","protocol":"zip","isEncrypted":true},
734 "encryptionInformation": {
735 "type":"split",
736 "keyAccess":[],
737 "method":{"algorithm":"AES-256-GCM","isStreamable":true,"iv":""},
738 "integrityInformation":{"rootSignature":{"alg":"HS256","sig":""},"segmentHashAlg":"GMAC","segments":[],"segmentSizeDefault":0,"encryptedSegmentSizeDefault":0},
739 "policy":""
740 },
741 "assertions": null
742 }"#;
743 let m = TdfManifest::from_json(json).expect("null assertions must parse");
744 assert!(m.assertions.is_empty());
745 }
746
747 #[test]
750 fn unsigned_assertion_with_empty_binding_object_parses_and_round_trips() {
751 let json = r#"{"id":"a1","type":"handling","scope":"payload","statement":{"format":"string","value":"x"},"binding":{}}"#;
752 let a: Assertion = serde_json::from_str(json).expect("empty binding object must parse");
753 let binding = a.binding.as_ref().expect("binding object is present");
754 assert_eq!(binding.method, "");
755 assert_eq!(binding.signature, "");
756
757 let out = serde_json::to_value(&a).unwrap();
758 assert_eq!(
759 out,
760 serde_json::from_str::<serde_json::Value>(json).unwrap()
761 );
762 }
763}
764
765#[cfg(test)]
766mod segment_size_fallback_tests {
767 use super::*;
768
769 #[test]
770 fn segment_sizes_fall_back_to_integrity_defaults_when_omitted() {
771 let mut info = IntegrityInformation {
772 segment_size_default: 1000,
773 encrypted_segment_size_default: 1028,
774 ..IntegrityInformation::default()
775 };
776 info.segments.push(Segment {
779 hash: "a".into(),
780 segment_size: None,
781 encrypted_segment_size: None,
782 });
783 info.segments.push(Segment {
785 hash: "b".into(),
786 segment_size: Some(7),
787 encrypted_segment_size: Some(35),
788 });
789 info.segments.push(Segment {
791 hash: "c".into(),
792 segment_size: Some(1000),
793 encrypted_segment_size: None,
794 });
795
796 assert_eq!(
797 info.segment_sizes(),
798 vec![(1000, 1028), (7, 35), (1000, 1028)]
799 );
800 }
801}