linuxutils_misc/
uuidgen.rs1use linuxutils_common::man::ManContent;
2
3pub const MAN: ManContent = ManContent::empty();
4
5use clap::Parser;
6use std::process::ExitCode;
7use uuid::Uuid;
8
9#[derive(Parser)]
10#[command(name = "uuidgen", version, about = "Create a new UUID value")]
11pub struct Args {
12 #[arg(short = 'r', long = "random")]
14 random: bool,
15
16 #[arg(short = 't', long = "time")]
18 time: bool,
19
20 #[arg(short = '6', long = "time-v6")]
22 time_v6: bool,
23
24 #[arg(short = '7', long = "time-v7")]
26 time_v7: bool,
27
28 #[arg(
30 short = 'm',
31 long = "md5",
32 requires = "namespace",
33 requires = "name"
34 )]
35 md5: bool,
36
37 #[arg(
39 short = 's',
40 long = "sha1",
41 requires = "namespace",
42 requires = "name"
43 )]
44 sha1: bool,
45
46 #[arg(short = 'n', long = "namespace")]
48 namespace: Option<String>,
49
50 #[arg(short = 'N', long = "name")]
52 name: Option<String>,
53
54 #[arg(short = 'C', long = "count", default_value = "1")]
56 count: usize,
57
58 #[arg(short = 'x', long = "hex")]
60 hex: bool,
61}
62
63pub fn run(args: Args) -> ExitCode {
64 for _ in 0..args.count {
65 let u = match generate(&args) {
66 Ok(u) => u,
67 Err(e) => {
68 eprintln!("uuidgen: {e}");
69 return ExitCode::FAILURE;
70 }
71 };
72 println!("{}", u.as_hyphenated());
73 }
74 ExitCode::SUCCESS
75}
76
77fn generate(args: &Args) -> Result<Uuid, String> {
78 if args.md5 || args.sha1 {
79 let ns = parse_namespace(args.namespace.as_deref().unwrap())?;
80 let name_str = args.name.as_deref().unwrap();
81 let name_bytes = if args.hex {
82 hex_decode(name_str)?
83 } else {
84 name_str.as_bytes().to_vec()
85 };
86 if args.md5 {
87 Ok(Uuid::new_v3(&ns, &name_bytes))
88 } else {
89 Ok(Uuid::new_v5(&ns, &name_bytes))
90 }
91 } else if args.time {
92 Ok(generate_v1())
93 } else if args.time_v6 {
94 let ts = uuid::timestamp::Timestamp::now(uuid::NoContext);
95 let node = random_node_id();
96 Ok(Uuid::new_v6(ts, &node))
97 } else if args.time_v7 {
98 let ts = uuid::timestamp::Timestamp::now(uuid::NoContext);
99 Ok(Uuid::new_v7(ts))
100 } else {
101 Ok(Uuid::new_v4())
103 }
104}
105
106fn parse_namespace(ns: &str) -> Result<Uuid, String> {
107 match ns {
108 "@dns" => Ok(Uuid::NAMESPACE_DNS),
109 "@url" => Ok(Uuid::NAMESPACE_URL),
110 "@oid" => Ok(Uuid::NAMESPACE_OID),
111 "@x500" => Ok(Uuid::NAMESPACE_X500),
112 other => Uuid::parse_str(other)
113 .map_err(|e| format!("invalid namespace: {e}")),
114 }
115}
116
117fn hex_decode(s: &str) -> Result<Vec<u8>, String> {
118 let s = s.replace([' ', '-', ':'], "");
119 if !s.len().is_multiple_of(2) {
120 return Err("hex string must have even length".to_string());
121 }
122 (0..s.len())
123 .step_by(2)
124 .map(|i| {
125 u8::from_str_radix(&s[i..i + 2], 16)
126 .map_err(|e| format!("invalid hex: {e}"))
127 })
128 .collect()
129}
130
131fn generate_v1() -> Uuid {
132 let ts = uuid::timestamp::Timestamp::now(uuid::NoContext);
133 let node = random_node_id();
134 Uuid::new_v1(ts, &node)
135}
136
137fn random_node_id() -> [u8; 6] {
138 let u = Uuid::new_v4();
139 let mut node = [0u8; 6];
140 node.copy_from_slice(&u.as_bytes()[..6]);
141 node[0] |= 0x01; node
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn default_generates_v4() {
151 let args = Args {
152 random: false,
153 time: false,
154 time_v6: false,
155 time_v7: false,
156 md5: false,
157 sha1: false,
158 namespace: None,
159 name: None,
160 count: 1,
161 hex: false,
162 };
163 let u = generate(&args).unwrap();
164 assert_eq!(u.get_version(), Some(uuid::Version::Random));
165 }
166
167 #[test]
168 fn sha1_is_deterministic() {
169 let args = Args {
170 random: false,
171 time: false,
172 time_v6: false,
173 time_v7: false,
174 md5: false,
175 sha1: true,
176 namespace: Some("@dns".to_string()),
177 name: Some("example.com".to_string()),
178 count: 1,
179 hex: false,
180 };
181 let u1 = generate(&args).unwrap();
182 let u2 = generate(&args).unwrap();
183 assert_eq!(u1, u2);
184 assert_eq!(u1.get_version(), Some(uuid::Version::Sha1));
185 }
186
187 #[test]
188 fn md5_is_deterministic() {
189 let args = Args {
190 random: false,
191 time: false,
192 time_v6: false,
193 time_v7: false,
194 md5: true,
195 sha1: false,
196 namespace: Some("@dns".to_string()),
197 name: Some("example.com".to_string()),
198 count: 1,
199 hex: false,
200 };
201 let u1 = generate(&args).unwrap();
202 let u2 = generate(&args).unwrap();
203 assert_eq!(u1, u2);
204 assert_eq!(u1.get_version(), Some(uuid::Version::Md5));
205 }
206
207 #[test]
208 fn namespace_aliases() {
209 assert_eq!(parse_namespace("@dns").unwrap(), Uuid::NAMESPACE_DNS);
210 assert_eq!(parse_namespace("@url").unwrap(), Uuid::NAMESPACE_URL);
211 assert_eq!(parse_namespace("@oid").unwrap(), Uuid::NAMESPACE_OID);
212 assert_eq!(parse_namespace("@x500").unwrap(), Uuid::NAMESPACE_X500);
213 }
214
215 #[test]
216 fn hex_decode_works() {
217 assert_eq!(hex_decode("48656c6c6f").unwrap(), b"Hello");
218 assert!(hex_decode("zz").is_err());
219 assert!(hex_decode("abc").is_err());
220 }
221}