tightbeam-rs 0.9.0

A secure, high-performance messaging protocol library
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
use std::sync::Arc;

use crate::asn1::Frame;
use crate::der::Sequence;

#[cfg(feature = "derive")]
use crate::Beamable;

/// Simple test message
#[cfg(feature = "derive")]
#[derive(Beamable, Clone, Debug, PartialEq, Sequence)]
pub struct TestMessage {
	pub content: String,
}

/// Simple test message
#[cfg(not(feature = "derive"))]
#[derive(Clone, Debug, PartialEq, Sequence)]
pub struct TestMessage {
	pub content: String,
}

#[cfg(not(feature = "derive"))]
impl crate::Message for TestMessage {
	const MUST_BE_NON_REPUDIABLE: bool = false;
	const MUST_BE_CONFIDENTIAL: bool = false;
	const MUST_BE_COMPRESSED: bool = false;
	const MUST_BE_PRIORITIZED: bool = false;
	const MIN_VERSION: crate::Version = crate::Version::V0;
}

#[cfg(feature = "derive")]
#[derive(Beamable, Clone, Debug, PartialEq, Sequence)]
#[beam(confidential)]
pub struct ConfidentialNote {
	pub content: String,
}

#[cfg(not(feature = "derive"))]
#[derive(Clone, Debug, PartialEq, Sequence)]
pub struct ConfidentialNote {
	pub content: String,
}

#[cfg(not(feature = "derive"))]
impl crate::Message for ConfidentialNote {
	const MUST_BE_CONFIDENTIAL: bool = true;
	const MUST_BE_NON_REPUDIABLE: bool = false;
	const MUST_BE_COMPRESSED: bool = false;
	const MUST_BE_PRIORITIZED: bool = false;
	const MIN_VERSION: crate::Version = crate::Version::V0;
}

#[cfg(feature = "derive")]
#[derive(Beamable, Clone, Debug, PartialEq, Sequence)]
#[beam(profile = 1)]
pub struct ConfidentialNonrepudiableNote {
	pub content: String,
}

#[cfg(feature = "derive")]
#[derive(Beamable, Clone, Debug, PartialEq, Sequence)]
#[beam(message_integrity, frame_integrity)]
pub struct IntegralNote {
	pub content: String,
}

#[cfg(not(feature = "derive"))]
#[derive(Clone, Debug, PartialEq, Sequence)]
pub struct ConfidentialNonrepudiableNote {
	pub content: String,
}

#[cfg(not(feature = "derive"))]
impl crate::Message for ConfidentialNonrepudiableNote {
	const MUST_BE_CONFIDENTIAL: bool = true;
	const MUST_BE_NON_REPUDIABLE: bool = true;
	const MUST_BE_COMPRESSED: bool = false;
	const MUST_BE_PRIORITIZED: bool = false;
	const MIN_VERSION: crate::Version = crate::Version::V0;
}

#[cfg(not(feature = "derive"))]
impl crate::Message for IntegralNote {
	const MUST_BE_NON_REPUDIABLE: bool = false;
	const MUST_BE_CONFIDENTIAL: bool = false;
	const MUST_BE_COMPRESSED: bool = false;
	const MUST_BE_PRIORITIZED: bool = false;
	const MUST_HAVE_MESSAGE_INTEGRITY: bool = true;
	const MUST_HAVE_FRAME_INTEGRITY: bool = true;
	const MIN_VERSION: crate::Version = crate::Version::V0;
}

pub fn create_test_message(content: Option<&str>) -> TestMessage {
	TestMessage {
		content: content.map(|c| c.into()).unwrap_or_else(|| "Hello TightBeam!".to_string()),
	}
}

pub fn create_v0_tightbeam(content: Option<&str>, id: Option<&str>) -> Frame {
	let message = create_test_message(content);

	// Get current time
	#[cfg(feature = "std")]
	let order = std::time::SystemTime::now()
		.duration_since(std::time::UNIX_EPOCH)
		.expect("Time went backwards")
		.as_secs();

	#[cfg(not(feature = "std"))]
	let order: u64 = 1_700_000_000;

	// Get a random id
	#[cfg(all(feature = "std", feature = "digest", feature = "random"))]
	let id = id.unwrap_or({
		use crate::crypto::hash::{Digest, Sha3_256};
		use crate::random::generate_random_bytes;

		let mut bytes: [u8; 32] = [0; 32];
		generate_random_bytes(&mut bytes, None).expect("Failed to generate random bytes");

		let hash = Sha3_256::digest(bytes);
		Box::leak(format!("{hash:x}").into_boxed_str())
	});

	#[cfg(not(all(feature = "std", feature = "digest", feature = "random")))]
	let id = id.unwrap_or("test-message-id");

	#[cfg(feature = "derive")]
	let result = compose! {
		V0: id: id,
			order: order,
			message: message
	};

	#[cfg(not(feature = "derive"))]
	let result = crate::Frame::new_v0(id, order, &message);

	result.expect("Failed to create TightBeam message")
}

/// Build a V1 frame carrying a Sha3-256 frame integrity (FI) digest.
#[cfg(all(feature = "builder", feature = "digest", feature = "sha3"))]
pub fn create_frame_with_frame_integrity() -> Frame {
	use crate::crypto::hash::Sha3_256;

	let message = create_test_message(None);
	compose! {
		V1: id: "fi-frame",
			order: 1u64,
			message: message,
			frame_integrity: type Sha3_256
	}
	.expect("Failed to create frame with frame integrity")
}

#[cfg(all(feature = "secp256k1", feature = "signature"))]
pub fn create_test_signing_key() -> k256::ecdsa::SigningKey {
	let secret_bytes = [1u8; 32];
	crate::crypto::sign::ecdsa::SigningKey::from_bytes(&secret_bytes.into()).expect("Failed to create signing key")
}

#[cfg(all(feature = "secp256k1", feature = "signature", feature = "x509"))]
pub fn create_test_certificate(signing_key: &k256::ecdsa::SigningKey) -> crate::x509::Certificate {
	use crate::der::Decode;
	use crate::spki::EncodePublicKey;

	let verifying_key = *signing_key.verifying_key();
	let public_key_der = verifying_key
		.to_public_key_der()
		.expect("test verifying key encodes to SPKI DER");

	let tbs_cert = crate::x509::TbsCertificate {
		version: crate::x509::Version::V3,
		serial_number: crate::x509::serial_number::SerialNumber::new(&[1]).expect("single-byte serial is valid"),
		signature: crate::spki::AlgorithmIdentifierOwned {
			oid: crate::oids::SIGNER_ECDSA_WITH_SHA3_256,
			parameters: None,
		},
		issuer: x509_cert::name::RdnSequence::default(),
		validity: crate::x509::time::Validity {
			not_before: crate::x509::time::Time::UtcTime(
				crate::der::asn1::UtcTime::from_unix_duration(core::time::Duration::from_secs(0))
					.expect("epoch is a valid UtcTime"),
			),
			not_after: crate::x509::time::Time::UtcTime(
				crate::der::asn1::UtcTime::from_unix_duration(core::time::Duration::from_secs(2_000_000_000))
					.expect("fixed not-after is a valid UtcTime"),
			),
		},
		subject: x509_cert::name::RdnSequence::default(),
		subject_public_key_info: crate::spki::SubjectPublicKeyInfoOwned::from_der(public_key_der.as_bytes())
			.expect("freshly encoded SPKI re-decodes"),
		issuer_unique_id: None,
		subject_unique_id: None,
		extensions: None,
	};

	crate::x509::Certificate {
		tbs_certificate: tbs_cert,
		signature_algorithm: crate::spki::AlgorithmIdentifierOwned {
			oid: crate::oids::SIGNER_ECDSA_WITH_SHA3_256,
			parameters: None,
		},
		signature: crate::der::asn1::BitString::new(0, vec![0; 64]).expect("64-byte placeholder is a valid BitString"),
	}
}

/// Wrap a DER-encodable extension value in an `Extension` (RFC 5280 §4.2).
#[cfg(all(feature = "secp256k1", feature = "signature", feature = "x509"))]
fn test_extension<T>(value: &T) -> crate::x509::ext::Extension
where
	T: crate::der::oid::AssociatedOid + crate::der::Encode,
{
	crate::x509::ext::Extension {
		extn_id: T::OID,
		critical: true,
		extn_value: crate::der::asn1::OctetString::new(value.to_der().expect("extension value encodes to DER"))
			.expect("DER bytes wrap in an OctetString"),
	}
}

/// Build CA issuer extensions: `basicConstraints` (RFC 5280 §4.2.1.9) and
/// `keyUsage` (§4.2.1.3), as required for certificate-path validation.
#[cfg(all(feature = "secp256k1", feature = "signature", feature = "x509"))]
pub fn ca_extensions(ca: bool, key_cert_sign: bool, path_len: Option<u8>) -> Vec<crate::x509::ext::Extension> {
	use crate::x509::ext::pkix::{BasicConstraints, KeyUsage, KeyUsages};

	let basic_constraints = BasicConstraints { ca, path_len_constraint: path_len };
	let usage = if key_cert_sign {
		KeyUsages::KeyCertSign
	} else {
		KeyUsages::DigitalSignature
	};
	let key_usage = KeyUsage(usage.into());

	vec![test_extension(&basic_constraints), test_extension(&key_usage)]
}

/// Digest whose output (16 bytes) is shorter than the 20-byte SKID
/// truncation window.
#[cfg(all(feature = "digest", feature = "sha3"))]
#[derive(Clone, Default)]
pub struct SixteenByteDigest(crate::crypto::hash::Sha3_256);

#[cfg(all(feature = "digest", feature = "sha3"))]
mod sixteen_byte_digest {
	use super::SixteenByteDigest;
	use crate::der::oid::AssociatedOid;

	impl digest::Update for SixteenByteDigest {
		fn update(&mut self, data: &[u8]) {
			digest::Update::update(&mut self.0, data);
		}
	}

	impl digest::OutputSizeUser for SixteenByteDigest {
		type OutputSize = digest::consts::U16;
	}

	impl digest::FixedOutput for SixteenByteDigest {
		fn finalize_into(self, out: &mut digest::Output<Self>) {
			let full = digest::FixedOutput::finalize_fixed(self.0);
			out.copy_from_slice(&full[..16]);
		}
	}

	impl digest::Reset for SixteenByteDigest {
		fn reset(&mut self) {
			digest::Reset::reset(&mut self.0);
		}
	}

	impl digest::FixedOutputReset for SixteenByteDigest {
		fn finalize_into_reset(&mut self, out: &mut digest::Output<Self>) {
			let full = digest::FixedOutputReset::finalize_fixed_reset(&mut self.0);
			out.copy_from_slice(&full[..16]);
		}
	}

	impl digest::HashMarker for SixteenByteDigest {}

	impl AssociatedOid for SixteenByteDigest {
		const OID: crate::der::asn1::ObjectIdentifier = crate::oids::HASH_SHA256;
	}
}

/// A certificate chain with root -> intermediate -> leaf certificates.
#[cfg(all(feature = "secp256k1", feature = "signature", feature = "x509"))]
pub struct TestCertificateChain {
	pub root: crate::x509::Certificate,
	pub intermediate: crate::x509::Certificate,
	pub leaf: crate::x509::Certificate,
	pub root_key: k256::ecdsa::SigningKey,
	pub intermediate_key: k256::ecdsa::SigningKey,
	pub leaf_key: k256::ecdsa::SigningKey,
}

/// Create a valid certificate chain: root -> intermediate -> leaf.
///
/// All certificates have proper issuer/subject chaining and valid signatures.
///
/// # Errors
///
/// Returns an error if key material, DER encoding, or signing fails.
#[cfg(all(feature = "secp256k1", feature = "signature", feature = "x509"))]
pub fn create_test_certificate_chain() -> crate::error::Result<TestCertificateChain> {
	use crate::crypto::hash::Sha3_256;
	use crate::crypto::sign::ecdsa::{Secp256k1, Signature, SigningKey};
	use crate::crypto::sign::sign_canonical;
	use crate::der::asn1::{BitString, UtcTime};
	use crate::der::{Decode, Encode};
	use crate::spki::{AlgorithmIdentifierOwned, EncodePublicKey, SubjectPublicKeyInfoOwned};
	use crate::x509::name::RdnSequence;
	use crate::x509::serial_number::SerialNumber;
	use crate::x509::time::{Time, Validity};
	use crate::x509::{Certificate, TbsCertificate, Version};

	use crate::der::asn1::{PrintableString, SetOfVec};
	use crate::x509::attr::AttributeTypeAndValue;
	use crate::x509::name::RelativeDistinguishedName;

	// Generate keys for each level
	let root_key = SigningKey::from_bytes(&[1u8; 32].into())?;
	let intermediate_key = SigningKey::from_bytes(&[2u8; 32].into())?;
	let leaf_key = SigningKey::from_bytes(&[3u8; 32].into())?;

	// Create unique Subject DNs for each certificate (CN=Root, CN=Intermediate, CN=Leaf)
	let cn_oid = crate::der::oid::ObjectIdentifier::new_unwrap("2.5.4.3"); // commonName

	let root_cn_str = PrintableString::new("Root")?;
	let root_cn = AttributeTypeAndValue { oid: cn_oid, value: crate::der::Any::from(&root_cn_str) };
	let root_name = RdnSequence::from(vec![RelativeDistinguishedName::from(SetOfVec::try_from(vec![root_cn])?)]);

	let inter_cn_str = PrintableString::new("Intermediate")?;
	let inter_cn = AttributeTypeAndValue { oid: cn_oid, value: crate::der::Any::from(&inter_cn_str) };
	let inter_name = RdnSequence::from(vec![RelativeDistinguishedName::from(SetOfVec::try_from(vec![inter_cn])?)]);

	let leaf_cn_str = PrintableString::new("Leaf")?;
	let leaf_cn = AttributeTypeAndValue { oid: cn_oid, value: crate::der::Any::from(&leaf_cn_str) };
	let leaf_name = RdnSequence::from(vec![RelativeDistinguishedName::from(SetOfVec::try_from(vec![leaf_cn])?)]);

	// Common validity period
	let validity = Validity {
		not_before: Time::UtcTime(UtcTime::from_unix_duration(core::time::Duration::from_secs(0))?),
		not_after: Time::UtcTime(UtcTime::from_unix_duration(core::time::Duration::from_secs(2_000_000_000))?),
	};

	let algorithm = AlgorithmIdentifierOwned { oid: crate::oids::SIGNER_ECDSA_WITH_SHA3_256, parameters: None };

	// Root certificate (self-signed)
	let root_pub_der = root_key.verifying_key().to_public_key_der()?;
	let root_tbs = TbsCertificate {
		version: Version::V3,
		serial_number: SerialNumber::new(&[1])?,
		signature: algorithm.clone(),
		issuer: root_name.clone(),
		validity,
		subject: root_name.clone(),
		subject_public_key_info: SubjectPublicKeyInfoOwned::from_der(root_pub_der.as_bytes())?,
		issuer_unique_id: None,
		subject_unique_id: None,
		// RFC 5280 §6.1.4(k),(n): root is a CA permitted to sign certificates.
		extensions: Some(ca_extensions(true, true, None)),
	};
	let root_tbs_der = root_tbs.to_der()?;
	let root_sig: Signature<Secp256k1> = sign_canonical::<Sha3_256, _>(&root_key, &root_tbs_der)?;
	let root = Certificate {
		tbs_certificate: root_tbs,
		signature_algorithm: algorithm.clone(),
		signature: BitString::new(0, root_sig.to_vec())?,
	};

	// Intermediate certificate (signed by root)
	let inter_pub_der = intermediate_key.verifying_key().to_public_key_der()?;
	let inter_tbs = TbsCertificate {
		version: Version::V3,
		serial_number: SerialNumber::new(&[2])?,
		signature: algorithm.clone(),
		issuer: root_name,
		validity,
		subject: inter_name.clone(),
		subject_public_key_info: SubjectPublicKeyInfoOwned::from_der(inter_pub_der.as_bytes())?,
		issuer_unique_id: None,
		subject_unique_id: None,
		// RFC 5280 §6.1.4(k),(n): intermediate is a CA permitted to sign certificates.
		extensions: Some(ca_extensions(true, true, None)),
	};
	let inter_tbs_der = inter_tbs.to_der()?;
	let inter_sig: Signature<Secp256k1> = sign_canonical::<Sha3_256, _>(&root_key, &inter_tbs_der)?;
	let intermediate = Certificate {
		tbs_certificate: inter_tbs,
		signature_algorithm: algorithm.clone(),
		signature: BitString::new(0, inter_sig.to_vec())?,
	};

	// Leaf certificate (signed by intermediate)
	let leaf_pub_der = leaf_key.verifying_key().to_public_key_der()?;
	let leaf_tbs = TbsCertificate {
		version: Version::V3,
		serial_number: SerialNumber::new(&[3])?,
		signature: algorithm.clone(),
		issuer: inter_name,
		validity,
		subject: leaf_name,
		subject_public_key_info: SubjectPublicKeyInfoOwned::from_der(leaf_pub_der.as_bytes())?,
		issuer_unique_id: None,
		subject_unique_id: None,
		extensions: None,
	};
	let leaf_tbs_der = leaf_tbs.to_der()?;
	let leaf_sig: Signature<Secp256k1> = sign_canonical::<Sha3_256, _>(&intermediate_key, &leaf_tbs_der)?;
	let leaf = Certificate {
		tbs_certificate: leaf_tbs,
		signature_algorithm: algorithm,
		signature: BitString::new(0, leaf_sig.to_vec())?,
	};

	Ok(TestCertificateChain { root, intermediate, leaf, root_key, intermediate_key, leaf_key })
}

#[cfg(feature = "aead")]
pub fn create_test_cipher_key() -> (
	crate::crypto::common::Key<crate::crypto::aead::Aes256Gcm>,
	crate::crypto::aead::Aes256Gcm,
) {
	use crate::crypto::aead::KeyInit;

	let key_bytes = [0x33; 32];
	let key = crate::crypto::common::Key::<crate::crypto::aead::Aes256Gcm>::from(key_bytes);
	let cipher = crate::crypto::aead::Aes256Gcm::new(&key);
	(key, cipher)
}

/// Create an expired test certificate for validation testing.
///
/// This certificate (from ssl.com) expired on August 17, 2019.
/// Useful for testing certificate expiry validation logic.
#[cfg(feature = "x509")]
pub fn create_expired_test_certificate() -> crate::x509::Certificate {
	crate::pem! {"
		-----BEGIN CERTIFICATE-----
		MIIF1TCCBVugAwIBAgIQdBJ26pggQyU+isEPM912FDAKBggqhkjOPQQDAzByMQsw
		CQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xETAP
		BgNVBAoMCFNTTCBDb3JwMS4wLAYDVQQDDCVTU0wuY29tIEVWIFNTTCBJbnRlcm1l
		ZGlhdGUgQ0EgRUNDIFIyMB4XDTE5MDgxNjIyMzU1N1oXDTE5MDgxNzIyMzU1N1ow
		gcgxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3Rv
		bjERMA8GA1UECgwIU1NMIENvcnAxFjAUBgNVBAUTDU5WMjAwODE2MTQyNDMxHzAd
		BgNVBAMMFmV4cGlyZWQtZWNjLWV2LnNzbC5jb20xHTAbBgNVBA8MFFByaXZhdGUg
		T3JnYW5pemF0aW9uMRcwFQYLKwYBBAGCNzwCAQIMBk5ldmFkYTETMBEGCysGAQQB
		gjc8AgEDEwJVUzB2MBAGByqGSM49AgEGBSuBBAAiA2IABKFkgNgOHrsYyyJHlGXK
		6C6SupADk+CLOKpMuoIdduURE4k5aXkmQ2yacYNmfqTTgC4oss/c2swxX9KDZdjW
		PYpFXfeCkorzKcX6RDJdUjD/78TiAT7fIyqcNyxuRpr4bqOCA10wggNZMB8GA1Ud
		IwQYMBaAFITu+Hk+CGR2SGQ59bG/iwJeC7wnMH4GCCsGAQUFBwEBBHIwcDBMBggr
		BgEFBQcwAoZAaHR0cDovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkvU1NMY29tLVN1
		YkNBLUVWLVNTTC1FQ0MtMzg0LVIyLmNydDAgBggrBgEFBQcwAYYUaHR0cDovL29j
		c3BzLnNzbC5jb20wPQYDVR0RBDYwNIIWZXhwaXJlZC1lY2MtZXYuc3NsLmNvbYIa
		d3d3LmV4cGlyZWQtZWNjLWV2LnNzbC5jb20wXwYDVR0gBFgwVjAHBgVngQwBATAN
		BgsqhGgBhvZ3AgUBATA8BgwrBgEEAYKpMAEDAQQwLDAqBggrBgEFBQcCARYeaHR0
		cHM6Ly93d3cuc3NsLmNvbS9yZXBvc2l0b3J5MB0GA1UdJQQWMBQGCCsGAQUFBwMC
		BggrBgEFBQcDATBHBgNVHR8EQDA+MDygOqA4hjZodHRwOi8vY3Jscy5zc2wuY29t
		L1NTTGNvbS1TdWJDQS1FVi1TU0wtRUNDLTM4NC1SMi5jcmwwHQYDVR0OBBYEFNK1
		Fhn8lNacFjqY/nVutrEtu8JTMA4GA1UdDwEB/wQEAwIHgDCCAX0GCisGAQQB1nkC
		BAIEggFtBIIBaQFnAHUAdH7agzGtMxCRIZzOJU9CcMK//V5CIAjGNzV55hB7zFYA
		AAFsnJvm3QAABAMARjBEAiAN3B7UPSzzszy+uYfXAZXKfHp6X8vkFL6FsvDknpv9
		cQIgTcE3kmHDPlQRwhkccghbl/ekwgY8CZHOSYmQcZzAKhoAdgDuS723dc5guuFC
		aR+r4Z5mow9+X7By2IMAxHuJeqj9ywAAAWycm+XxAAAEAwBHMEUCIF8Z0jTHQ0bU
		xRMWFVkPo/Fq8taoiTMF9rAX4/QZtmdNAiEA2kodKU2CXO2WOT257aF0v2gBLh7T
		2f8rrj8MYf4A1soAdgBVgdTCFpA2AUrqC5tXPFPwwOQ4eHAlCBcvo6odBxPTDAAA
		AWycm+ZjAAAEAwBHMEUCIQC/j+yfrh55hcKfaGRBvIOX/Wf+NWy/AUep9UiQaV/0
		oQIgO2WX6jOEyXN9ZtBDTxaspPhIcCIWOXNfn9PkzrEzaWQwCgYIKoZIzj0EAwMD
		aAAwZQIxAIM0MOzr0GX3Zeg3OZCOEKYe/yIXT2FlDMMVAFK0WdHI+lMVwQGacR0A
		+9Cvs7zTlQIwLkVrf3XF+P3afMrGhljvWAqPNHpf/jJsddq0DmSHgITOWCJXfytT
		dLAFZesIxt4p
		-----END CERTIFICATE-----
	"}
	.expect("Failed to parse expired test certificate")
}

pub fn create_test_hash_info() -> crate::DigestInfo {
	crate::DigestInfo {
		algorithm: crate::AlgorithmIdentifier {
			oid: crate::der::asn1::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1"), // SHA-256
			parameters: None,
		},
		digest: crate::asn1::OctetString::new(vec![0u8; 32]).expect("Failed to create OctetString"),
	}
}

/// Create a certificate and key pair that expires soon (within 24 hours).
/// Returns (certificate, signing_key).
///
/// Note: This certificate was generated on 2025-11-09 and expires 2025-11-10.
/// Tests using this will fail after the expiration date.
#[cfg(all(feature = "x509", feature = "secp256k1", feature = "signature"))]
pub fn create_expiring_test_certificate(
) -> crate::error::Result<(crate::x509::Certificate, crate::crypto::sign::ecdsa::Secp256k1SigningKey)> {
	// Certificate that expires on 2025-11-10 06:35:40 UTC
	let cert = crate::pem! {"
		-----BEGIN CERTIFICATE-----
		MIIBiTCCAS+gAwIBAgIBATALBglghkgBZQMEAwowLTErMCkGA1UEAwwiRXhwaXJp
		bmcgVG9tb3Jyb3cgVGVzdCBDZXJ0aWZpY2F0ZTAeFw0yNTExMDkwNjM1NDBaFw0y
		NTExMTAwNjM1NDBaMC0xKzApBgNVBAMMIkV4cGlyaW5nIFRvbW9ycm93IFRlc3Qg
		Q2VydGlmaWNhdGUwVjAQBgcqhkjOPQIBBgUrgQQACgNCAAQbhMVWexJkQJldPtWq
		ugVl1x4YNGBIGf+cF/Xp1d0Hj3C+r49Yi1QVB/7WpkLFq0Lf34Egp/Y53lEi1Hpp
		qOjRo0IwQDAdBgNVHQ4EFgQUQ65suXrNrzVLgobdsptJLJXpWHAwDwYDVR0TAQH/
		BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCwYJYIZIAWUDBAMKA0cAMEQCIA7h0vvv
		anyhANdtbmk2etj57E5fh1lI3Fg2bePzYq0kAiAL9r2v4pwP8s1uec4FT7HAUYf6
		T4zOUKsmsdnnAwTztg==
		-----END CERTIFICATE-----
	"}?;

	// Corresponding private key (fixed test key)
	let key_bytes: [u8; 32] = [0x01; 32];
	let signing_key = crate::crypto::sign::ecdsa::Secp256k1SigningKey::from_bytes(&key_bytes.into())?;

	Ok((cert, signing_key))
}

pub fn create_test_encryption_info() -> crate::EncryptedContentInfo {
	crate::EncryptedContentInfo {
		content_type: crate::oids::COMPRESSION_CONTENT,
		content_enc_alg: crate::AlgorithmIdentifier {
			oid: crate::der::asn1::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.1.42"), // AES-256-GCM
			parameters: None,
		},
		encrypted_content: Some(
			crate::der::asn1::OctetString::new(vec![0u8; 12]).expect("12-byte placeholder wraps in an OctetString"),
		),
	}
}

pub fn create_test_signer_info() -> crate::SignerInfo {
	use crate::cms::content_info::CmsVersion;
	use crate::cms::signed_data::SignerIdentifier;
	use crate::der::asn1::OctetString;
	use crate::x509::ext::pkix::SubjectKeyIdentifier;

	let skid = SubjectKeyIdentifier::from(OctetString::new([0u8; 20]).expect("20-byte SKID wraps in an OctetString"));

	crate::SignerInfo {
		version: CmsVersion::V1,
		sid: SignerIdentifier::SubjectKeyIdentifier(skid),
		digest_alg: crate::AlgorithmIdentifierOwned { oid: crate::oids::HASH_SHA3_256, parameters: None },
		signed_attrs: None,
		signature_algorithm: crate::AlgorithmIdentifierOwned {
			oid: crate::oids::SIGNER_ECDSA_WITH_SHA3_256,
			parameters: None,
		},
		signature: OctetString::new([0u8; 64]).expect("64-byte placeholder wraps in an OctetString"),
		unsigned_attrs: None,
	}
}

/// Generic builder test macro
///
/// Similar to test_case! but specifically designed for testing builders.
/// Automatically provides a base builder instance and allows customization.
/// Passes both the input message and the resulting frame to assertions.
///
/// The `message` parameter supports two forms:
/// - Direct value: `message: create_test_message(None)`
/// - Closure: `message: || { create_test_message(None) }`
#[macro_export]
macro_rules! test_builder {
	// Closure form: message: || { ... }
	(
		name: $test_name:ident,
		builder_type: $builder_type:ty,
		version: $version:expr,
		message: || $message_body:expr,
		setup: |$builder:ident, $msg:ident| $setup_body:expr,
		assertions: |$msg_result:ident, $frame_result:ident| $assertions_body:expr
	) => {
		$crate::test_builder!(@impl
			$test_name,
			$builder_type,
			$version,
			{ $message_body },
			|$builder, $msg| $setup_body,
			|$msg_result, $frame_result| $assertions_body
		);
	};

	// Direct value form: message: some_value
	(
		name: $test_name:ident,
		builder_type: $builder_type:ty,
		version: $version:expr,
		message: $message:expr,
		setup: |$builder:ident, $msg:ident| $setup_body:expr,
		assertions: |$msg_result:ident, $frame_result:ident| $assertions_body:expr
	) => {
		$crate::test_builder!(@impl
			$test_name,
			$builder_type,
			$version,
			$message,
			|$builder, $msg| $setup_body,
			|$msg_result, $frame_result| $assertions_body
		);
	};

	// Internal implementation (not exposed to users)
	(@impl
		$test_name:ident,
		$builder_type:ty,
		$version:expr,
		$message:expr,
		|$builder:ident, $msg:ident| $setup_body:expr,
		|$msg_result:ident, $frame_result:ident| $assertions_body:expr
	) => {
		#[test]
		fn $test_name() -> $crate::error::Result<()> {
			let $msg = $message;
			let msg_clone = $msg.clone();
			let $builder: $builder_type = <$builder_type>::from($version);
			let $frame_result = $setup_body;
			let $msg_result = msg_clone;
			$assertions_body
		}
	};
}

// Helper: match a Frame against various "expected" forms.
// - Frame => compare metadata.id
// - Result<Frame, E> => compare metadata.id from Ok(frame)
// - Any T: Beamable + PartialEq => decode T from frame.message and compare
pub trait ExpectedMatcher {
	fn matches(&self, frame: &Frame) -> bool;
}

impl ExpectedMatcher for crate::Frame {
	fn matches(&self, frame: &Frame) -> bool {
		frame.metadata.id == self.metadata.id
	}
}

impl ExpectedMatcher for Arc<crate::Frame> {
	fn matches(&self, frame: &Frame) -> bool {
		self.metadata.id == frame.metadata.id
	}
}

impl<E> ExpectedMatcher for Result<crate::Frame, E> {
	fn matches(&self, frame: &Frame) -> bool {
		match self {
			Ok(f) => frame.metadata.id == f.metadata.id,
			Err(_) => false,
		}
	}
}

impl<T> ExpectedMatcher for T
where
	T: crate::Message + PartialEq,
{
	fn matches(&self, frame: &Frame) -> bool {
		if let Ok(decoded) = crate::decode::<T>(&frame.message) {
			decoded == *self
		} else {
			false
		}
	}
}

/// Async test macro with worker setup
///
/// Automatically starts a worker and passes it to the assertions block for testing.
/// Properly manages worker lifecycle with shutdown.
#[macro_export]
macro_rules! test_worker {
	(
		name: $test_name:ident,
		setup: || $setup_body:expr,
		assertions: |$worker:ident| $assertions_body:expr
	) => {
		#[tokio::test]
		async fn $test_name() -> Result<(), Box<dyn std::error::Error>> {
			use $crate::colony::worker::Worker;

			// Build and start the worker
			let builder = $setup_body;
			let trace = std::sync::Arc::new($crate::trace::TraceCollector::new());
			let mut worker = <_ as $crate::colony::worker::Worker>::start(builder, trace).await?;

			// Run assertions with reference to worker
			let result = {
				let $worker = &mut worker;
				$assertions_body.await
			};

			// Graceful shutdown: close the queue and await run-loop exit
			worker.kill().await?;

			result
		}
	};
}

/// Async test macro with worker setup
///
/// Automatically starts a worker, creates a client, and passes the ready
/// client to the assertions block for testing. Properly manages worker
/// lifecycle.
#[macro_export]
macro_rules! test_servlet {
	// With worker_threads specified
	(
		name: $test_name:ident,
		worker_threads: $threads:literal,
		protocol: $protocol:ident,
		setup: || $setup_body:expr,
		assertions: |$client:ident| $assertions_body:expr
	) => {
		#[tokio::test(flavor = "multi_thread", worker_threads = $threads)]
		async fn $test_name() -> Result<(), Box<dyn std::error::Error>> {
			// Call the setup closure and await the resulting future
			let worker = $setup_body.await?;
			// Get the worker address
			let addr = worker.addr();

			// Create client
			let mut $client = $crate::client! {
				connect $protocol: addr
			};

			// Run assertions
			let result = $assertions_body.await;

			// Clean shutdown
			worker.stop();

			result
		}
	};

	// Without worker_threads (defaults to single threaded)
	(
		name: $test_name:ident,
		protocol: $protocol:ident,
		setup: || $setup_body:expr,
		assertions: |$client:ident| $assertions_body:expr
	) => {
		#[tokio::test]
		async fn $test_name() -> Result<(), Box<dyn std::error::Error>> {
			// Call the setup closure and await the resulting future
			let worker = ($setup_body).await?;

			// Get the worker address
			let addr = worker.addr();

			// Create client
			let mut $client = $crate::client! {
				async $protocol: connect addr
			}
			.await?;

			// Run assertions
			let result = $assertions_body.await;

			// Clean shutdown
			worker.stop();

			result
		}
	};
}

/// Async test macro for hives
///
/// Automatically starts a drone, creates a client, and passes the ready
/// client to the assertions block for testing. Properly manages drone lifecycle.
///
/// Note: Gate observation channels are not yet implemented for hives.
/// The `channels` parameter is reserved for future use.
#[macro_export]
macro_rules! test_drone {
	// With worker_threads and setup callback
	(
		name: $test_name:ident,
		worker_threads: $threads:literal,
		protocol: $protocol:ident,
		drone: $drone_type:ty,
		config: $config:expr,
		setup: |$setup_drone:ident| $setup_body:expr,
		assertions: |$client:ident, $channels:ident| $assertions_body:expr
	) => {
		#[tokio::test(flavor = "multi_thread", worker_threads = $threads)]
		async fn $test_name() -> Result<(), Box<dyn std::error::Error>> {
			// Start the drone
			let drone = <$drone_type as $crate::colony::servlet::Servlet<()>>::start(
				$crate::trace::TraceCollector::new(),
				$config,
			)
			.await?;

			// Call the setup closure and await the resulting future
			let $setup_drone = drone;
			let drone = $setup_body.await;

			// Get the drone address
			let addr = drone.addr();

			// Create client
			let mut $client = $crate::client! {
				connect $protocol: addr
			};

			// Placeholder channels tuple (not yet implemented for hives)
			let $channels = ((), ());

			// Run assertions
			let result = $assertions_body.await;

			// Clean shutdown
			drone.stop();

			result
		}
	};

	// Without worker_threads but with setup callback
	(
		name: $test_name:ident,
		protocol: $protocol:ident,
		drone: $drone_type:ty,
		config: $config:expr,
		setup: |$setup_drone:ident| $setup_body:expr,
		assertions: |$client:ident, $channels:ident| $assertions_body:expr
	) => {
		#[tokio::test]
		async fn $test_name() -> Result<(), Box<dyn std::error::Error>> {
			// Start the drone
			let drone = <$drone_type as $crate::colony::servlet::Servlet<()>>::start(
				::std::sync::Arc::new($crate::trace::TraceCollector::new()),
				$config,
			)
			.await?;

			// Call the setup closure and await the resulting future
			let $setup_drone = drone;
			let drone = $setup_body.await;

			// Get the drone address
			let addr = drone.addr();

			// Create client
			let mut $client = $crate::client! {
				connect $protocol: addr
			};

			// Placeholder channels tuple (not yet implemented for hives)
			let $channels = ((), ());

			// Run assertions
			let result = $assertions_body.await;

			// Clean shutdown
			drone.stop();

			result
		}
	};

	// Simple variant without setup callback, with worker_threads
	(
		name: $test_name:ident,
		worker_threads: $threads:literal,
		protocol: $protocol:ident,
		drone: $drone_type:ty,
		config: $config:expr,
		assertions: |$client:ident, $channels:ident| $assertions_body:expr
	) => {
		#[tokio::test(flavor = "multi_thread", worker_threads = $threads)]
		async fn $test_name() -> Result<(), Box<dyn std::error::Error>> {
			// Start the drone
			let drone = <$drone_type as $crate::colony::servlet::Servlet<()>>::start(
				$crate::trace::TraceCollector::new(),
				$config,
			)
			.await?;

			// Get the drone address
			let addr = drone.addr();

			// Create client
			let mut $client = $crate::client! {
				connect $protocol: addr
			};

			// Placeholder channels tuple (not yet implemented for hives)
			let $channels = ((), ());

			// Run assertions
			let result = $assertions_body.await;

			// Clean shutdown
			drone.stop();

			result
		}
	};

	// Simple variant without setup callback, without worker_threads
	(
		name: $test_name:ident,
		protocol: $protocol:ident,
		drone: $drone_type:ty,
		config: $config:expr,
		assertions: |$client:ident, $channels:ident| $assertions_body:expr
	) => {
		#[tokio::test]
		async fn $test_name() -> Result<(), Box<dyn std::error::Error>> {
			// Start the drone
			let drone = <$drone_type as $crate::colony::servlet::Servlet<()>>::start(
				$crate::trace::TraceCollector::new(),
				$config,
			)
			.await?;

			// Get the drone address
			let addr = drone.addr();

			// Create client
			let mut $client = $crate::client! {
				connect $protocol: addr
			};

			// Placeholder channels tuple (not yet implemented for hives)
			let $channels = ((), ());

			// Run assertions
			let result = $assertions_body.await;

			// Clean shutdown
			drone.stop();

			result
		}
	};
}

#[cfg(test)]
mod tests {
	use super::*;

	use crate::builder::MetadataBuilder;
	use crate::{Metadata, Version};

	#[test]
	#[cfg(feature = "std")]
	fn test_create_test_message() -> crate::error::Result<()> {
		let message = create_test_message(Some("Test content"));
		assert_eq!(message.content, "Test content");
		Ok(())
	}

	test_builder! {
		name: test_metadata_builder_basic,
		builder_type: MetadataBuilder,
		version: Version::V0,
		message: (),
		setup: |builder, _msg| {
			builder
				.with_id("test-id")
				.with_order(1696521600)
				.build()
		},
		assertions: |_msg, result| {
			let metadata: Metadata = result?;
			assert_eq!(metadata.id, b"test-id");
			assert_eq!(metadata.order, 1696521600);
			assert!(metadata.integrity.is_none());
			Ok(())
		}
	}
}