1use std::{fmt, num::ParseIntError, str::FromStr};
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum UriError {
9 #[error("unsupported URI scheme: '{0}'")]
10 UnsupportedScheme(String),
11 #[error("invalid handle format: {0}")]
12 ParseHandle(#[from] ParseIntError),
13 #[error("invalid context grip format: '{0}'")]
14 InvalidGripFormat(String),
15 #[error("I/O error: {0}")]
16 Io(#[from] std::io::Error),
17 #[error("operation not valid for this URI type")]
18 InvalidUriType,
19 #[error("invalid password format: {0}")]
20 InvalidPasswordFormat(String),
21}
22
23#[derive(Debug, Clone, PartialEq)]
25pub enum Uri {
26 Tpm(u32),
27 Context(String),
28 Path(std::path::PathBuf),
29 Password(Vec<u8>),
30 Session(u32),
31}
32
33impl Uri {
34 #[must_use]
36 pub fn is_parent(&self) -> bool {
37 matches!(self, Self::Tpm(_) | Self::Context(_) | Self::Path(_))
38 }
39
40 pub fn to_bytes(&self) -> Result<Vec<u8>, UriError> {
47 match self {
48 Self::Path(path) => Ok(std::fs::read(path)?),
49 _ => Err(UriError::InvalidUriType),
50 }
51 }
52
53 pub fn to_handle(&self) -> Result<u32, UriError> {
59 match self {
60 Self::Tpm(handle) | Self::Session(handle) => Ok(*handle),
61 _ => Err(UriError::InvalidUriType),
62 }
63 }
64}
65
66impl FromStr for Uri {
67 type Err = UriError;
68
69 fn from_str(s: &str) -> Result<Self, Self::Err> {
70 if let Some(handle_str) = s.strip_prefix("tpm://") {
71 let handle = u32::from_str_radix(handle_str.trim_start_matches("0x"), 16)?;
72 Ok(Self::Tpm(handle))
73 } else if let Some(handle_str) = s.strip_prefix("session://") {
74 let handle = u32::from_str_radix(handle_str.trim_start_matches("0x"), 16)?;
75 Ok(Self::Session(handle))
76 } else if let Some(grip) = s.strip_prefix("key://") {
77 if grip.len() == 16 && grip.chars().all(|c| c.is_ascii_hexdigit()) {
78 Ok(Self::Context(grip.to_string()))
79 } else {
80 Err(UriError::InvalidGripFormat(grip.to_string()))
81 }
82 } else if let Some(hex_pass) = s.strip_prefix("password://") {
83 let bytes = hex::decode(hex_pass)
84 .map_err(|e| UriError::InvalidPasswordFormat(e.to_string()))?;
85 Ok(Self::Password(bytes))
86 } else if s.contains("://") {
87 Err(UriError::UnsupportedScheme(
88 s.split_once("://").unwrap_or(("", "")).0.to_string(),
89 ))
90 } else {
91 Ok(Self::Path(s.to_string().into()))
92 }
93 }
94}
95
96impl fmt::Display for Uri {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 match self {
99 Self::Tpm(handle) => write!(f, "tpm://{handle:08x}"),
100 Self::Context(grip) => write!(f, "key://{grip}"),
101 Self::Path(path) => write!(f, "{}", path.to_string_lossy()),
102 Self::Password(bytes) => write!(f, "password://{}", hex::encode(bytes)),
103 Self::Session(handle) => write!(f, "session://{handle:08x}"),
104 }
105 }
106}