1#[cfg(feature = "cli")]
4use std::{
5 fs::{File, read},
6 io::Read,
7 path::{Path, PathBuf},
8};
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12#[cfg(feature = "cli")]
13use signstar_crypto::passphrase::Passphrase;
14use yubihsm::{
15 Capability as YubiHsmCapability,
16 command::Code,
17 object::{Filter, Id, Type},
18 opaque::Algorithm,
19 wrap::Message,
20};
21
22use crate::{
23 Credentials,
24 automation::CommandReturnValue,
25 backup::Label,
26 object::{AuthenticationKey, Capabilities, Domains, KeyInfo, ObjectId, WrapKey},
27};
28#[cfg(feature = "cli")]
29use crate::{
30 object::{WrapKeyFromPassphrase, WrapKeyKind},
31 user::FileBackedCredentials,
32};
33
34#[derive(Clone, Copy, Debug)]
36#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
37#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
38pub enum AuditOption {
39 On,
41
42 Off,
44
45 Fix,
47}
48
49impl From<AuditOption> for yubihsm::AuditOption {
50 fn from(value: AuditOption) -> Self {
51 match value {
52 AuditOption::On => Self::On,
53 AuditOption::Off => Self::Off,
54 AuditOption::Fix => Self::Fix,
55 }
56 }
57}
58
59#[derive(Debug, strum::Display)]
61#[strum(serialize_all = "snake_case")]
62#[cfg_attr(feature = "serde", derive(Serialize))]
63#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
64pub enum CommandName {
65 DeviceInfo,
67
68 ResetDeviceAndReconnect,
70
71 GetLogEntries,
73
74 SetForceAuditOption,
76
77 SetCommandAuditOption,
79
80 PutAuthenticationKey,
82
83 GenerateAsymmetricKey,
85
86 SignEd25519,
88
89 PutOpaque,
91
92 GetOpaque,
94
95 PutWrapKey,
97
98 ExportWrapped,
100
101 ImportWrapped,
103
104 DeleteObject,
106
107 GetObjectInfo,
109
110 ListObjects,
112}
113
114impl From<&Command> for CommandName {
115 fn from(value: &Command) -> Self {
116 match value {
117 Command::DeviceInfo => Self::DeviceInfo,
118 Command::ResetDeviceAndReconnect => Self::ResetDeviceAndReconnect,
119 Command::GetLogEntries => Self::GetLogEntries,
120 Command::SetForceAuditOption(_) => Self::SetForceAuditOption,
121 Command::SetCommandAuditOption { .. } => Self::SetCommandAuditOption,
122 Command::PutAuthenticationKey { .. } => Self::PutAuthenticationKey,
123 Command::GenerateAsymmetricKey { .. } => Self::GenerateAsymmetricKey,
124 Command::SignEd25519 { .. } => Self::SignEd25519,
125 Command::PutOpaque { .. } => Self::PutOpaque,
126 Command::GetOpaque { .. } => Self::GetOpaque,
127 Command::PutWrapKey { .. } => Self::PutWrapKey,
128 Command::ExportWrapped { .. } => Self::ExportWrapped,
129 Command::ImportWrapped { .. } => Self::ImportWrapped,
130 Command::DeleteObject(_) => Self::DeleteObject,
131 Command::GetObjectInfo(_) => Self::GetObjectInfo,
132 Command::ListObjects(_) => Self::ListObjects,
133 }
134 }
135}
136
137impl From<&CommandReturnValue> for CommandName {
138 fn from(value: &CommandReturnValue) -> Self {
139 match value {
140 CommandReturnValue::DeviceInfo(_) => Self::DeviceInfo,
141 CommandReturnValue::ResetDeviceAndReconnect => Self::ResetDeviceAndReconnect,
142 CommandReturnValue::GetLogEntries(_) => Self::GetLogEntries,
143 CommandReturnValue::SetForceAuditOption => Self::SetForceAuditOption,
144 CommandReturnValue::SetCommandAuditOption => Self::SetCommandAuditOption,
145 CommandReturnValue::PutAuthenticationKey { .. } => Self::PutAuthenticationKey,
146 CommandReturnValue::GenerateAsymmetricKey { .. } => Self::GenerateAsymmetricKey,
147 CommandReturnValue::SignEd25519 { .. } => Self::SignEd25519,
148 CommandReturnValue::PutOpaque { .. } => Self::PutOpaque,
149 CommandReturnValue::GetOpaque { .. } => Self::GetOpaque,
150 CommandReturnValue::PutWrapKey { .. } => Self::PutWrapKey,
151 CommandReturnValue::ExportWrapped { .. } => Self::ExportWrapped,
152 CommandReturnValue::ImportWrapped { .. } => Self::ImportWrapped,
153 CommandReturnValue::DeleteObject => Self::DeleteObject,
154 CommandReturnValue::GetObjectInfo(_) => Self::GetObjectInfo,
155 CommandReturnValue::ListObjects(_) => Self::ListObjects,
156 }
157 }
158}
159
160#[cfg(feature = "cli")]
161impl From<&FileBackedCommand> for CommandName {
162 fn from(value: &FileBackedCommand) -> Self {
163 match value {
164 FileBackedCommand::DeviceInfo => Self::DeviceInfo,
165 FileBackedCommand::ResetDeviceAndReconnect => Self::ResetDeviceAndReconnect,
166 FileBackedCommand::GetLogEntries => Self::GetLogEntries,
167 FileBackedCommand::SetForceAuditOption(_) => Self::SetForceAuditOption,
168 FileBackedCommand::SetCommandAuditOption { .. } => Self::SetCommandAuditOption,
169 FileBackedCommand::PutAuthenticationKey { .. } => Self::PutAuthenticationKey,
170 FileBackedCommand::GenerateAsymmetricKey { .. } => Self::GenerateAsymmetricKey,
171 FileBackedCommand::SignEd25519 { .. } => Self::SignEd25519,
172 FileBackedCommand::PutOpaque { .. } => Self::PutOpaque,
173 FileBackedCommand::GetOpaque { .. } => Self::GetOpaque,
174 FileBackedCommand::PutWrapKey { .. } => Self::PutWrapKey,
175 FileBackedCommand::ExportWrapped { .. } => Self::ExportWrapped,
176 FileBackedCommand::ImportWrapped { .. } => Self::ImportWrapped,
177 FileBackedCommand::DeleteObject(_) => Self::DeleteObject,
178 FileBackedCommand::GetObjectInfo(_) => Self::GetObjectInfo,
179 FileBackedCommand::ListObjects(_) => Self::ListObjects,
180 }
181 }
182}
183
184#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
190#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
191#[derive(Clone, Copy, Debug, strum::Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
192#[strum(serialize_all = "kebab-case")]
193pub enum ObjectType {
194 Opaque,
196
197 AuthenticationKey,
199
200 AsymmetricKey,
202
203 WrapKey,
205
206 HmacKey,
208
209 Template,
211
212 OtpAeakey,
214}
215
216impl From<Type> for ObjectType {
217 fn from(value: Type) -> Self {
218 match value {
219 Type::Opaque => Self::Opaque,
220 Type::AuthenticationKey => Self::AuthenticationKey,
221 Type::AsymmetricKey => Self::AsymmetricKey,
222 Type::WrapKey => Self::WrapKey,
223 Type::HmacKey => Self::HmacKey,
224 Type::Template => Self::Template,
225 Type::OtpAeadKey => Self::OtpAeakey,
226 }
227 }
228}
229
230impl From<&ObjectType> for Type {
231 fn from(value: &ObjectType) -> Self {
232 match value {
233 ObjectType::Opaque => Self::Opaque,
234 ObjectType::AuthenticationKey => Self::AuthenticationKey,
235 ObjectType::AsymmetricKey => Self::AsymmetricKey,
236 ObjectType::WrapKey => Self::WrapKey,
237 ObjectType::HmacKey => Self::HmacKey,
238 ObjectType::Template => Self::Template,
239 ObjectType::OtpAeakey => Self::OtpAeadKey,
240 }
241 }
242}
243
244#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
252#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
253#[derive(Clone, Debug)]
254pub enum ListObjectFilter {
255 Capabilities(Capabilities),
257
258 Domains(Domains),
260
261 Id(Id),
263
264 Type(ObjectType),
266}
267
268impl From<&ListObjectFilter> for Filter {
269 fn from(value: &ListObjectFilter) -> Self {
270 match value {
271 ListObjectFilter::Capabilities(capabilities) => {
272 Filter::Capabilities(capabilities.into())
273 }
274 ListObjectFilter::Domains(domains) => Filter::Domains(domains.into()),
275 ListObjectFilter::Id(id) => Filter::Id(*id),
276 ListObjectFilter::Type(typ) => Filter::Type(typ.into()),
277 }
278 }
279}
280
281#[derive(Clone, Debug)]
286#[cfg(feature = "cli")]
287#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
288#[cfg_attr(feature = "serde", serde(try_from = "PathBuf", into = "PathBuf"))]
289pub struct OpaqueDataFile(PathBuf);
290
291#[cfg(feature = "cli")]
292impl OpaqueDataFile {
293 pub fn new(path: impl AsRef<Path>) -> Result<Self, crate::Error> {
303 let path = path.as_ref();
304 if !path.is_file() {
305 return Err(crate::automation::Error::OpaqueDataNotAFile {
306 path: path.to_path_buf(),
307 }
308 .into());
309 }
310 let file = File::open(path).map_err(|source| crate::Error::IoPath {
311 path: path.to_path_buf(),
312 context: "opening an opaque data file for reading",
313 source,
314 })?;
315 let data_length = file
316 .metadata()
317 .map_err(|source| crate::Error::IoPath {
318 path: path.to_path_buf(),
319 context: "retrieving metadata of an opaque data file",
320 source,
321 })?
322 .len() as usize;
323 if data_length > OpaqueData::MAX_DATA_SIZE {
324 return Err(crate::automation::Error::OpaqueDataFileLength {
325 path: path.to_path_buf(),
326 data_length,
327 }
328 .into());
329 }
330
331 Ok(Self(path.to_path_buf()))
332 }
333}
334
335#[cfg(feature = "cli")]
336impl TryFrom<PathBuf> for OpaqueDataFile {
337 type Error = crate::Error;
338
339 fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
340 Self::new(&value)
341 }
342}
343
344#[cfg(feature = "cli")]
345impl From<OpaqueDataFile> for PathBuf {
346 fn from(value: OpaqueDataFile) -> Self {
347 value.0
348 }
349}
350
351#[cfg(feature = "cli")]
352impl TryFrom<&OpaqueDataFile> for Vec<u8> {
353 type Error = crate::Error;
354
355 fn try_from(value: &OpaqueDataFile) -> Result<Self, Self::Error> {
369 let mut file = File::open(value.0.as_path()).map_err(|source| crate::Error::IoPath {
370 path: value.0.clone(),
371 context: "opening an opaque data file for reading",
372 source,
373 })?;
374 let mut buffer = Vec::new();
375 file.read_to_end(&mut buffer)
376 .map_err(|source| crate::Error::IoPath {
377 path: value.0.clone(),
378 context: "reading the contents of an opaque data file",
379 source,
380 })?;
381
382 Ok(buffer)
383 }
384}
385
386#[derive(Clone, Debug)]
396pub struct OpaqueData(Vec<u8>);
397
398impl OpaqueData {
399 pub const MAX_DATA_SIZE: usize = 1980;
407
408 pub fn new(data: Vec<u8>) -> Result<Self, crate::Error> {
414 if data.len() > OpaqueData::MAX_DATA_SIZE {
415 return Err(crate::automation::Error::OpaqueDataLength {
416 data_length: data.len(),
417 }
418 .into());
419 }
420
421 Ok(Self(data))
422 }
423}
424
425#[cfg(feature = "cli")]
426impl TryFrom<&OpaqueDataFile> for OpaqueData {
427 type Error = crate::Error;
428
429 fn try_from(value: &OpaqueDataFile) -> Result<Self, Self::Error> {
430 let data: Vec<u8> = value.try_into()?;
431 Self::new(data)
432 }
433}
434
435impl From<&OpaqueData> for Vec<u8> {
436 fn from(value: &OpaqueData) -> Self {
437 value.0.clone()
438 }
439}
440
441#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
449#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
450#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
451pub enum OpaqueDataAlgorithm {
452 OpaqueData,
454
455 OpaqueX590Certificate,
457}
458
459impl From<Algorithm> for OpaqueDataAlgorithm {
460 fn from(value: Algorithm) -> Self {
461 match value {
462 Algorithm::Data => Self::OpaqueData,
463 Algorithm::X509Certificate => Self::OpaqueX590Certificate,
464 }
465 }
466}
467
468impl From<&OpaqueDataAlgorithm> for Algorithm {
469 fn from(value: &OpaqueDataAlgorithm) -> Self {
470 match value {
471 OpaqueDataAlgorithm::OpaqueData => Self::Data,
472 OpaqueDataAlgorithm::OpaqueX590Certificate => Self::X509Certificate,
473 }
474 }
475}
476
477#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
479#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
480#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
481pub enum OpaqueDataCapabilities {
482 None,
484
485 ExportableUnderWrap,
487}
488
489impl From<&OpaqueDataCapabilities> for YubiHsmCapability {
490 fn from(value: &OpaqueDataCapabilities) -> Self {
491 match value {
492 OpaqueDataCapabilities::None => YubiHsmCapability::empty(),
493 OpaqueDataCapabilities::ExportableUnderWrap => YubiHsmCapability::EXPORTABLE_UNDER_WRAP,
494 }
495 }
496}
497
498#[derive(Debug)]
500pub enum Command {
501 DeviceInfo,
503
504 ResetDeviceAndReconnect,
509
510 GetLogEntries,
512
513 SetForceAuditOption(AuditOption),
520
521 SetCommandAuditOption {
528 command: Code,
530
531 setting: AuditOption,
533 },
534
535 PutAuthenticationKey {
539 info: KeyInfo,
541
542 delegated_caps: Capabilities,
545
546 authentication_key: AuthenticationKey,
548 },
549
550 GenerateAsymmetricKey {
552 info: KeyInfo,
554 },
555
556 SignEd25519 {
558 key_id: Id,
560
561 data: Vec<u8>,
563 },
564
565 PutWrapKey {
570 info: KeyInfo,
572
573 delegated_caps: Capabilities,
576
577 wrapping_key: WrapKey,
579 },
580
581 PutOpaque {
587 id: Id,
589
590 label: Label,
592
593 domains: Domains,
595
596 capabilities: OpaqueDataCapabilities,
598
599 algorithm: OpaqueDataAlgorithm,
601
602 data: OpaqueData,
604 },
605
606 GetOpaque {
608 id: Id,
610 },
611
612 ExportWrapped {
614 wrap_key_id: Id,
616
617 object: ObjectId,
619 },
620
621 ImportWrapped {
623 wrap_key_id: Id,
625
626 message: Message,
628 },
629
630 DeleteObject(ObjectId),
632
633 GetObjectInfo(ObjectId),
635
636 ListObjects(Vec<ListObjectFilter>),
638}
639
640#[cfg(feature = "cli")]
641impl TryFrom<&FileBackedCommand> for Command {
642 type Error = crate::Error;
643
644 fn try_from(value: &FileBackedCommand) -> Result<Self, Self::Error> {
650 Ok(match value {
651 FileBackedCommand::DeviceInfo => Command::DeviceInfo,
652 FileBackedCommand::ResetDeviceAndReconnect => Command::ResetDeviceAndReconnect,
653 FileBackedCommand::GetLogEntries => Command::GetLogEntries,
654 FileBackedCommand::SetForceAuditOption(audit_option) => {
655 Command::SetForceAuditOption(*audit_option)
656 }
657 FileBackedCommand::SetCommandAuditOption { command, setting } => {
658 Command::SetCommandAuditOption {
659 command: (*command),
660 setting: (*setting),
661 }
662 }
663 FileBackedCommand::PutAuthenticationKey {
664 info,
665 delegated_caps,
666 passphrase_file,
667 } => Command::PutAuthenticationKey {
668 info: info.clone(),
669 delegated_caps: delegated_caps.clone(),
670 authentication_key: AuthenticationKey::try_from(passphrase_file.as_path())?,
671 },
672 FileBackedCommand::GenerateAsymmetricKey { info } => {
673 Command::GenerateAsymmetricKey { info: info.clone() }
674 }
675 FileBackedCommand::SignEd25519 { key_id, data } => Command::SignEd25519 {
676 key_id: (*key_id),
677 data: data.to_vec(),
678 },
679 FileBackedCommand::PutOpaque {
680 id,
681 label,
682 domains,
683 capabilities,
684 algorithm,
685 data_file,
686 } => Command::PutOpaque {
687 id: *id,
688 label: label.clone(),
689 domains: domains.clone(),
690 capabilities: *capabilities,
691 algorithm: *algorithm,
692 data: OpaqueData::try_from(data_file)?,
693 },
694 FileBackedCommand::GetOpaque { id, .. } => Command::GetOpaque { id: *id },
695 FileBackedCommand::PutWrapKey {
696 info,
697 delegated_caps,
698 passphrase_file,
699 } => Command::PutWrapKey {
700 info: info.clone(),
701 delegated_caps: delegated_caps.clone(),
702 wrapping_key: WrapKey::try_from(WrapKeyFromPassphrase::new(
703 &Passphrase::try_from(passphrase_file.as_path())?,
704 WrapKeyKind::Aes256,
705 )?)?,
706 },
707 FileBackedCommand::ExportWrapped {
708 wrap_key_id,
709 object,
710 wrapped_file: _,
711 } => Command::ExportWrapped {
712 wrap_key_id: (*wrap_key_id),
713 object: (*object),
714 },
715 FileBackedCommand::ImportWrapped {
716 wrap_key_id,
717 wrapped_file,
718 } => {
719 let message =
720 Message::from_vec(read(wrapped_file.as_path()).map_err(|source| {
721 Self::Error::IoPath {
722 path: wrapped_file.clone(),
723 context: "reading a file under wrap",
724 source,
725 }
726 })?)
727 .map_err(|source| Self::Error::InvalidWrap {
728 context: "reading the wrapped file",
729 source,
730 })?;
731
732 Command::ImportWrapped {
733 wrap_key_id: (*wrap_key_id),
734 message,
735 }
736 }
737 FileBackedCommand::DeleteObject(id) => Command::DeleteObject(*id),
738 FileBackedCommand::GetObjectInfo(id) => Command::GetObjectInfo(*id),
739 FileBackedCommand::ListObjects(filters) => Command::ListObjects(filters.clone()),
740 })
741 }
742}
743
744#[derive(Debug)]
749#[cfg(feature = "cli")]
750#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
751#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
752pub enum FileBackedCommand {
753 DeviceInfo,
755
756 ResetDeviceAndReconnect,
761
762 GetLogEntries,
764
765 SetForceAuditOption(AuditOption),
772
773 SetCommandAuditOption {
780 command: Code,
782
783 setting: AuditOption,
785 },
786
787 PutAuthenticationKey {
791 #[cfg_attr(feature = "serde", serde(flatten))]
793 info: KeyInfo,
794
795 delegated_caps: Capabilities,
798
799 passphrase_file: PathBuf,
801 },
802
803 GenerateAsymmetricKey {
805 #[cfg_attr(feature = "serde", serde(flatten))]
807 info: KeyInfo,
808 },
809
810 SignEd25519 {
812 key_id: Id,
814
815 data: Vec<u8>,
817 },
818
819 PutOpaque {
825 id: Id,
827
828 label: Label,
830
831 domains: Domains,
833
834 capabilities: OpaqueDataCapabilities,
836
837 algorithm: OpaqueDataAlgorithm,
839
840 data_file: OpaqueDataFile,
842 },
843
844 PutWrapKey {
849 #[cfg_attr(feature = "serde", serde(flatten))]
851 info: KeyInfo,
852
853 delegated_caps: Capabilities,
856
857 passphrase_file: PathBuf,
859 },
860
861 GetOpaque {
863 data_file: PathBuf,
865
866 id: Id,
868 },
869
870 ExportWrapped {
872 wrap_key_id: Id,
874
875 #[cfg_attr(feature = "serde", serde(flatten))]
877 object: ObjectId,
878
879 wrapped_file: PathBuf,
881 },
882
883 ImportWrapped {
885 wrap_key_id: Id,
887
888 wrapped_file: PathBuf,
890 },
891
892 DeleteObject(ObjectId),
894
895 GetObjectInfo(ObjectId),
897
898 ListObjects(Vec<ListObjectFilter>),
900}
901
902#[derive(Debug)]
907pub struct AuthenticatedCommandChain {
908 auth: Credentials,
909 commands: Vec<Command>,
910}
911
912impl AuthenticatedCommandChain {
913 pub fn new(auth: Credentials, commands: Vec<Command>) -> Self {
915 Self { auth, commands }
916 }
917
918 pub fn auth(&self) -> &Credentials {
920 &self.auth
921 }
922
923 pub fn commands(&self) -> &[Command] {
925 &self.commands
926 }
927}
928
929#[cfg(feature = "cli")]
934#[derive(Debug)]
935#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
936pub struct FileBackedAuthenticatedCommandChain {
937 pub(crate) auth: FileBackedCredentials,
938 pub(crate) commands: Vec<FileBackedCommand>,
939}
940
941#[cfg(test)]
942mod tests {
943 #[cfg(feature = "cli")]
944 use std::io::Write;
945
946 #[cfg(feature = "cli")]
947 use tempfile::{NamedTempFile, TempDir};
948 use testresult::TestResult;
949
950 use super::*;
951
952 const LARGE_DATA_LENGTH: usize = OpaqueData::MAX_DATA_SIZE + 1;
953
954 #[test]
956 fn opaque_data_new_fails_on_large_data() -> TestResult {
957 let data = Vec::from_iter([1; LARGE_DATA_LENGTH]);
958 match OpaqueData::new(data) {
959 Err(crate::Error::Automation(crate::automation::Error::OpaqueDataLength {
960 ..
961 })) => {}
962 Err(error) => panic!(
963 "Expected to fail with Error::OpaqueDataLength, but got a different error instead: {error}"
964 ),
965 Ok(opaque_data) => panic!(
966 "Expected to fail with Error::OpaqueDataLength, succeeded instead: {opaque_data:?}"
967 ),
968 };
969
970 Ok(())
971 }
972
973 #[cfg(feature = "cli")]
975 #[test]
976 fn path_from_opaque_data_file() -> TestResult {
977 let data_file = {
978 let mut data_file = NamedTempFile::new()?;
979 let data: Vec<u8> = Vec::from_iter([1; 1]);
980 data_file.write_all(data.as_slice())?;
981 data_file
982 };
983 let opaque_data_file = OpaqueDataFile::new(data_file.path())?;
984 let _path: PathBuf = opaque_data_file.into();
985
986 Ok(())
987 }
988
989 #[cfg(feature = "cli")]
991 #[test]
992 fn opaque_data_file_new_fails_on_dir() -> TestResult {
993 let temp_dir = TempDir::new()?;
994
995 match OpaqueDataFile::new(temp_dir.path()) {
996 Err(crate::Error::Automation(crate::automation::Error::OpaqueDataNotAFile {
997 ..
998 })) => {}
999 Err(error) => panic!(
1000 "Expected to fail with Error::OpaqueDataNotAFile, but got a different error instead: {error}"
1001 ),
1002 Ok(opaque_data) => panic!(
1003 "Expected to fail with Error::OpaqueDataNotAFile, succeeded instead: {opaque_data:?}"
1004 ),
1005 };
1006
1007 Ok(())
1008 }
1009
1010 #[cfg(feature = "cli")]
1012 #[test]
1013 fn opaque_data_file_new_fails_on_large_data() -> TestResult {
1014 let data_file = {
1015 let mut data_file = NamedTempFile::new()?;
1016 let data: Vec<u8> = Vec::from_iter([1; LARGE_DATA_LENGTH]);
1017 data_file.write_all(data.as_slice())?;
1018 data_file
1019 };
1020
1021 match OpaqueDataFile::new(data_file.path()) {
1022 Err(crate::Error::Automation(crate::automation::Error::OpaqueDataFileLength {
1023 ..
1024 })) => {}
1025 Err(error) => panic!(
1026 "Expected to fail with Error::OpaqueDataFileLength, but got a different error instead: {error}"
1027 ),
1028 Ok(opaque_data) => panic!(
1029 "Expected to fail with Error::OpaqueDataFileLength, succeeded instead: {opaque_data:?}"
1030 ),
1031 };
1032
1033 Ok(())
1034 }
1035}