1use std::fs;
7use std::net::{IpAddr, SocketAddr};
8use std::path::Path;
9
10use crate::error::{Error, Result};
11
12pub fn load_secret_key_bytes(path: &Path) -> Result<Option<[u8; 32]>> {
16 if !path.exists() {
17 return Ok(None);
18 }
19
20 let bytes = fs::read(path).map_err(|e| Error::SecretKey(e.to_string()))?;
21 let key_bytes: [u8; 32] = bytes
22 .as_slice()
23 .try_into()
24 .map_err(|_| Error::SecretKey(format!("invalid key file length in {}", path.display())))?;
25
26 Ok(Some(key_bytes))
27}
28
29pub fn generate_secret_key_file(path: &Path) -> Result<[u8; 32]> {
35 if path.exists() {
36 return Err(Error::SecretKey(format!(
37 "secret key already exists at {}",
38 path.display()
39 )));
40 }
41
42 let mut key_bytes = [0u8; 32];
43 use rand::RngCore;
44 rand::rngs::OsRng.fill_bytes(&mut key_bytes);
45
46 if let Some(parent) = path.parent() {
48 fs::create_dir_all(parent).map_err(|e| {
49 Error::SecretKey(format!("failed to create dir {}: {}", parent.display(), e))
50 })?;
51 }
52
53 fs::write(path, key_bytes)
54 .map_err(|e| Error::SecretKey(format!("failed to write {}: {}", path.display(), e)))?;
55
56 #[cfg(unix)]
58 {
59 use std::os::unix::fs::PermissionsExt;
60 let _ = fs::set_permissions(path, fs::Permissions::from_mode(0o400));
61 }
62
63 Ok(key_bytes)
64}
65
66pub fn socket_addr_to_multiaddr(addr: &SocketAddr) -> String {
68 match addr.ip() {
69 IpAddr::V4(ip) => format!("/ip4/{}/udp/{}/quic-v1", ip, addr.port()),
70 IpAddr::V6(ip) => format!("/ip6/{}/udp/{}/quic-v1", ip, addr.port()),
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use std::net::{Ipv4Addr, Ipv6Addr};
78 use std::path::PathBuf;
79
80 fn test_tmp_file(name: &str) -> PathBuf {
81 let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
82 .join("tmp")
83 .join("identity-tests");
84 fs::create_dir_all(&root).expect("failed creating test tmp directory");
85 root.join(name)
86 }
87
88 #[test]
89 fn multiaddr_ipv4() {
90 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 4433);
91 assert_eq!(
92 socket_addr_to_multiaddr(&addr),
93 "/ip4/127.0.0.1/udp/4433/quic-v1"
94 );
95 }
96
97 #[test]
98 fn multiaddr_ipv6() {
99 let addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 5555);
100 assert_eq!(socket_addr_to_multiaddr(&addr), "/ip6/::1/udp/5555/quic-v1");
101 }
102
103 #[test]
104 fn load_missing_returns_none() {
105 let path = test_tmp_file("nonexistent-key");
106 let _ = fs::remove_file(&path);
107 assert!(load_secret_key_bytes(&path).unwrap().is_none());
108 }
109
110 #[test]
111 fn generate_and_load_round_trip() {
112 let path = test_tmp_file("round-trip-key");
113 let _ = fs::remove_file(&path);
114
115 let generated = generate_secret_key_file(&path).unwrap();
116 let loaded = load_secret_key_bytes(&path).unwrap().unwrap();
117 assert_eq!(generated, loaded);
118
119 let _ = fs::remove_file(&path);
121 }
122
123 #[test]
124 fn generate_refuses_overwrite() {
125 let path = test_tmp_file("no-overwrite-key");
126 let _ = fs::remove_file(&path);
127
128 generate_secret_key_file(&path).unwrap();
129 let err = generate_secret_key_file(&path).unwrap_err();
130 assert!(matches!(err, crate::error::Error::SecretKey(_)));
131
132 let _ = fs::remove_file(&path);
134 }
135}