1#[macro_use]
71extern crate anyhow;
72
73use anyhow::{Context, Result};
74use data::{
75 adjectives::ADJECTIVES, animals::ANIMALS, names::NAMES, personal_nouns::PERSONAL_NOUNS,
76 places::PLACES, verbs::VERBS,
77};
78use uuid::Uuid;
79
80mod data;
81
82const NORMAL: [u8; 12] = [12, 11, 14, 13, 13, 10, 12, 11, 14, 5, 6, 7];
99
100const SHORT: [u8; 5] = [6, 6, 7, 8, 5];
108
109fn to_bits(bytes: &[u8]) -> Vec<u8> {
111 let mut bits: Vec<u8> = Vec::with_capacity(128);
112
113 for b in bytes {
114 bits.extend(u16_to_bits(*b as u16, 8));
115 }
116
117 bits
118}
119
120fn to_bits_parted(bytes: &[u16]) -> Vec<u8> {
122 let mut bits: Vec<u8> = Vec::with_capacity(128);
123
124 for (i, b) in bytes.iter().enumerate() {
125 bits.extend(u16_to_bits(*b, NORMAL[i]));
126 }
127
128 bits
129}
130
131#[inline]
133fn u16_to_bits(mut b: u16, length: u8) -> Vec<u8> {
134 let mut bits = Vec::with_capacity(length as usize);
135
136 for _ in 0..length {
137 bits.push((b % 2) as u8);
138 b >>= 1;
139 }
140 bits.reverse();
141
142 bits
143}
144
145fn to_byte(bits: &[u8]) -> u16 {
147 let mut _byte = 0u16;
148
149 for b in bits {
150 _byte = 2 * _byte + *b as u16;
151 }
152 _byte
153}
154
155fn partition(parts: &[u8], bytes: &[u8]) -> [usize; 12] {
157 let mut bits: Vec<u8> = to_bits(bytes);
158
159 let mut _bytes: [usize; 12] = [0; 12];
160 for (idx, p) in parts.iter().enumerate() {
161 let tmp = bits.drain(0..(*p as usize));
162 _bytes[idx] = to_byte(tmp.as_slice()) as usize;
163 }
164
165 _bytes
166}
167
168fn de_partition(bits: &[u8]) -> [u8; 16] {
170 let mut bytes = [0; 16];
171
172 for i in 0..16 {
173 bytes[i] = to_byte(&bits[8 * i..8 * (i + 1)]) as u8;
174 }
175
176 bytes
177}
178
179#[inline]
180fn _generate(uuid: &Uuid) -> String {
181 let uuid = uuid.as_bytes();
183 let words = partition(&NORMAL, uuid);
185 format!(
187 "{} {} {} the {} of {} {} {} {} {} and {} {} {}",
188 NAMES[words[0]],
189 NAMES[words[1]],
190 NAMES[words[2]],
191 PERSONAL_NOUNS[words[3]],
192 PLACES[words[4]],
193 VERBS[words[5]],
194 NAMES[words[6]],
195 NAMES[words[7]],
196 NAMES[words[8]],
197 words[9],
198 ADJECTIVES[words[10]],
199 ANIMALS[words[11]]
200 )
201}
202
203pub fn generate() -> String {
207 let uuid = Uuid::new_v4();
209
210 _generate(&uuid)
212}
213
214pub fn generate_from(uuid: Uuid) -> String {
218 _generate(&uuid)
220}
221
222pub fn generate_inverse<S: AsRef<str>>(sentence: S) -> Result<Uuid> {
226 let splitted: Vec<&str> = sentence.as_ref().split(' ').collect();
228 if splitted.len() < 15 {
230 return Err(anyhow!(
231 "The sentence does not correspond to a one from uuid-readable-rs."
232 ));
233 }
234 let index_values = [
236 NAMES
237 .iter()
238 .position(|&r| r == splitted[0])
239 .context("NAMES (0) not found")? as u16,
240 NAMES
241 .iter()
242 .position(|&r| r == splitted[1])
243 .context("NAMES (1) not found")? as u16,
244 NAMES
245 .iter()
246 .position(|&r| r == splitted[2])
247 .context("NAMES (2) not found")? as u16,
248 PERSONAL_NOUNS
249 .iter()
250 .position(|&r| r == splitted[4])
251 .context("PERSONAL_NOUNS (4) not found")? as u16,
252 PLACES
253 .iter()
254 .position(|&r| r == splitted[6])
255 .context("PLACES (6) not found")? as u16,
256 VERBS
257 .iter()
258 .position(|&r| r == splitted[7])
259 .context("VERBS (7) not found")? as u16,
260 NAMES
261 .iter()
262 .position(|&r| r == splitted[8])
263 .context("NAMES (8) not found")? as u16,
264 NAMES
265 .iter()
266 .position(|&r| r == splitted[9])
267 .context("NAMES (9) not found")? as u16,
268 NAMES
269 .iter()
270 .position(|&r| r == splitted[10])
271 .context("NAMES (10) not found")? as u16,
272 splitted[12].parse::<u16>()?,
273 ADJECTIVES
274 .iter()
275 .position(|&r| r == splitted[13])
276 .context("ADJECTIVES (13) not found")? as u16,
277 ANIMALS
278 .iter()
279 .position(|&r| r == splitted[14])
280 .context("ANIMALS (14) not found")? as u16,
281 ];
282 let bits = to_bits_parted(&index_values);
284 let bytes = de_partition(&bits);
286
287 Ok(Uuid::from_slice(&bytes)?)
289}
290
291#[inline]
292fn _short(uuid: &Uuid) -> String {
293 let uuid = uuid.as_bytes();
295 let words = partition(&SHORT, uuid);
297
298 format!(
300 "{} {} by {} {} {}",
301 NAMES[words[0]], VERBS[words[1]], words[2], ADJECTIVES[words[3]], ANIMALS[words[4]],
302 )
303}
304
305pub fn short() -> String {
309 let uuid = Uuid::new_v4();
311
312 _short(&uuid)
314}
315
316pub fn short_from(uuid: Uuid) -> String {
320 _short(&uuid)
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 #[test]
329 fn test_adjectives_sanity() {
330 let mut tmp_vec = ADJECTIVES.to_vec();
331
332 for x in &tmp_vec {
333 assert!(!x.contains(" "));
334 }
335
336 let original_length = tmp_vec.len();
337 tmp_vec.sort();
338 tmp_vec.dedup();
339 let final_length = tmp_vec.len();
340
341 assert_eq!(original_length, final_length);
342 }
343
344 #[test]
345 fn test_animals_sanity() {
346 let mut tmp_vec = ANIMALS.to_vec();
347
348 for x in &tmp_vec {
349 assert!(!x.contains(" "));
350 }
351
352 let original_length = tmp_vec.len();
353 tmp_vec.sort();
354 tmp_vec.dedup();
355 let final_length = tmp_vec.len();
356
357 assert_eq!(original_length, final_length);
358 }
359
360 #[test]
361 fn test_names_sanity() {
362 let mut tmp_vec = NAMES.to_vec();
363
364 for x in &tmp_vec {
365 assert!(!x.contains(" "));
366 }
367
368 let original_length = tmp_vec.len();
369 tmp_vec.sort();
370 tmp_vec.dedup();
371 let final_length = tmp_vec.len();
372
373 assert_eq!(original_length, final_length);
374 }
375
376 #[test]
377 fn test_personal_nouns_sanity() {
378 let mut tmp_vec = PERSONAL_NOUNS.to_vec();
379
380 for x in &tmp_vec {
381 assert!(!x.contains(" "));
382 }
383
384 let original_length = tmp_vec.len();
385 tmp_vec.sort();
386 tmp_vec.dedup();
387 let final_length = tmp_vec.len();
388
389 assert_eq!(original_length, final_length);
390 }
391
392 #[test]
393 fn test_places_sanity() {
394 let mut tmp_vec = PLACES.to_vec();
395
396 for x in &tmp_vec {
397 assert!(!x.contains(" "));
398 }
399
400 let original_length = tmp_vec.len();
401 tmp_vec.sort();
402 tmp_vec.dedup();
403 let final_length = tmp_vec.len();
404
405 assert_eq!(original_length, final_length);
406 }
407
408 #[test]
409 fn test_verbs_sanity() {
410 let mut tmp_vec = VERBS.to_vec();
411
412 for x in &tmp_vec {
413 assert!(!x.contains(" "));
414 }
415
416 let original_length = tmp_vec.len();
417 tmp_vec.sort();
418 tmp_vec.dedup();
419 let final_length = tmp_vec.len();
420
421 assert_eq!(original_length, final_length);
422 }
423
424 #[test]
425 fn test_generate() {
426 let uuid = Uuid::parse_str("0ee001c7-12f3-4b29-a4cc-f48838b3587a").unwrap();
427
428 let g = generate_from(uuid);
429 assert_eq!(
430 g,
431 "Purdy Fusco Kask the loki of Manteo observed Barbe Lehet Pardew and 26 hard herons"
432 );
433 }
434
435 #[test]
436 fn test_short() {
437 let uuid = Uuid::parse_str("0ee001c7-12f3-4b29-a4cc-f48838b3587a").unwrap();
438
439 let s = short_from(uuid);
440 assert_eq!(s, "Egidius filled by 0 calm hawks");
441 }
442
443 #[test]
444 fn test_inverse() {
445 let uuid = Uuid::parse_str("0ee001c7-12f3-4b29-a4cc-f48838b3587a").unwrap();
446 let i = generate_inverse(&generate_from(uuid)).unwrap();
447 assert_eq!(i, uuid);
448 }
449
450 #[test]
451 fn test_bits_conversion() {
452 let arr = [41];
453 let bits = to_bits(&arr);
454 assert_eq!(bits, vec![0, 0, 1, 0, 1, 0, 0, 1]);
455
456 let byte = to_byte(&bits);
457 assert_eq!(byte, 41);
458 }
459
460 #[test]
461 fn test_compatibility() {
462 let uuid = Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap();
463 let zeroed = generate_from(uuid);
464 assert_eq!(zeroed, "Fusco Fusco Fusco the muleteer of Katy suspended Fusco Fusco Fusco and 0 mysterious rooks");
465
466 let uuid = Uuid::parse_str("ffffffff-ffff-ffff-ffff-ffffffffffff").unwrap();
467 let full = generate_from(uuid);
468 assert_eq!(full, "Antone Concordia Katharyn the minister of Mosinee trotted Antone Concordia Katharyn and 31 slow hogs");
469
470 let uuid = Uuid::parse_str("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF").unwrap();
471 let strange = generate_from(uuid);
472 assert_eq!(strange, "Antone Concordia Caravette the minister of Mosinee trotted Antone Concordia Katharyn and 31 slow hogs");
473 }
474
475 #[test]
476 fn test_bad_inverse() {
477 let sentence = "109812 ???./ ` the muleteer of Katy suspended Fusco Fusco Fusco and 0 mysterious rooks";
478 let rev = generate_inverse(sentence);
479 assert!(rev.is_err());
480
481 let sentence = "109812 ???./\0zdqdqz";
482 let rev = generate_inverse(sentence);
483 assert!(rev.is_err());
484 }
485}