1use std::{error, fmt, result, str};
2
3use crate::{builder::HarshBuilder, shuffle};
4
5type Result<T, E = Error> = result::Result<T, E>;
6
7#[derive(Clone, Debug)]
8pub enum Error {
9 Hex,
10 Decode(DecodeError),
11}
12
13#[derive(Clone, Debug)]
14pub enum DecodeError {
15 Value,
16 Hash,
17}
18
19impl fmt::Display for DecodeError {
20 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
21 match self {
22 DecodeError::Value => f.write_str("Found bad value"),
23 DecodeError::Hash => f.write_str("Malformed hashid"),
24 }
25 }
26}
27
28impl error::Error for DecodeError {}
29
30impl fmt::Display for Error {
31 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32 match self {
33 Error::Hex => f.write_str("Failed to decode hex value"),
34 Error::Decode(e) => match e {
35 DecodeError::Value => f.write_str("Found bad value"),
36 DecodeError::Hash => f.write_str("Malformed hashid"),
37 },
38 }
39 }
40}
41
42impl error::Error for Error {
43 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
44 match self {
45 Error::Hex => None,
46 Error::Decode(ref e) => Some(e),
47 }
48 }
49}
50
51#[derive(Clone, Debug)]
57pub struct Harsh {
58 alphabet: Box<[u8]>,
59 guards: Box<[u8]>,
60 hash_length: usize,
61 salt: Box<[u8]>,
62 separators: Box<[u8]>,
63}
64
65impl Harsh {
66 pub fn new() -> Self {
68 HarshBuilder::new()
69 .build()
70 .expect("Default options should not fail")
71 }
72
73 pub fn builder() -> HarshBuilder {
75 HarshBuilder::new()
76 }
77
78 pub(crate) fn initialize(
79 alphabet: Box<[u8]>,
80 guards: Box<[u8]>,
81 hash_length: usize,
82 salt: Box<[u8]>,
83 separators: Box<[u8]>,
84 ) -> Self {
85 Harsh {
86 alphabet,
87 guards,
88 hash_length,
89 salt,
90 separators,
91 }
92 }
93
94 pub fn encode(&self, values: &[u64]) -> String {
96 if values.is_empty() {
97 return String::new();
98 }
99
100 let nhash = create_nhash(values);
101
102 let mut alphabet = self.alphabet.clone();
103 let mut buffer = String::new();
104
105 let idx = (nhash % alphabet.len() as u64) as usize;
106 let lottery = alphabet[idx];
107 buffer.push(lottery as char);
108
109 for (idx, &value) in values.iter().enumerate() {
110 let mut value = value;
111 let mut temp = Vec::with_capacity(self.salt.len() + alphabet.len() + 1);
112 temp.push(lottery);
113 temp.extend_from_slice(&self.salt);
114 temp.extend_from_slice(&alphabet);
115
116 let alphabet_len = alphabet.len();
117 shuffle(&mut alphabet, &temp[..alphabet_len]);
118
119 let last = hash(value, &alphabet);
120 buffer.push_str(&last);
121
122 if idx + 1 < values.len() {
123 value %= (last.bytes().next().unwrap_or(0) as usize + idx) as u64;
124 buffer
125 .push(self.separators[(value % self.separators.len() as u64) as usize] as char);
126 }
127 }
128
129 if buffer.len() < self.hash_length {
130 let guard_index = (nhash as usize
131 + buffer.bytes().next().expect("hellfire and damnation") as usize)
132 % self.guards.len();
133 let guard = self.guards[guard_index];
134 buffer.insert(0, guard as char);
135
136 if buffer.len() < self.hash_length {
137 let guard_index = (nhash as usize
138 + buffer.as_bytes()[2] as usize)
139 % self.guards.len();
140 let guard = self.guards[guard_index];
141 buffer.push(guard as char);
142 }
143 }
144
145 let half_length = alphabet.len() / 2;
146 while buffer.len() < self.hash_length {
147 {
148 let alphabet_copy = alphabet.clone();
149 shuffle(&mut alphabet, &alphabet_copy);
150 }
151
152 let (left, right) = alphabet.split_at(half_length);
153 buffer = format!(
154 "{}{}{}",
155 String::from_utf8_lossy(right),
156 buffer,
157 String::from_utf8_lossy(left)
158 );
159
160 let excess = buffer.len() as i32 - self.hash_length as i32;
161 if excess > 0 {
162 let marker = excess as usize / 2;
163 buffer = buffer[marker..marker + self.hash_length].to_owned();
164 }
165 }
166
167 buffer
168 }
169
170 pub fn decode<T: AsRef<str>>(&self, input: T) -> Result<Vec<u64>> {
172 let mut value = input.as_ref().as_bytes();
173
174 if let Some(guard_idx) = value.iter().position(|u| self.guards.contains(u)) {
175 value = &value[(guard_idx + 1)..];
176 }
177
178 if let Some(guard_idx) = value.iter().rposition(|u| self.guards.contains(u)) {
179 value = &value[..guard_idx];
180 }
181
182 if value.len() < 2 {
183 return Err(Error::Decode(DecodeError::Hash));
184 }
185
186 let mut alphabet = self.alphabet.clone();
187
188 let lottery = value[0];
189 let value = &value[1..];
190 let segments = value.split(|u| self.separators.contains(u));
191
192 let result: Option<Vec<_>> = segments
193 .into_iter()
194 .map(|segment| {
195 let mut buffer = Vec::with_capacity(self.salt.len() + alphabet.len() + 1);
196 buffer.push(lottery);
197 buffer.extend_from_slice(&self.salt);
198 buffer.extend_from_slice(&alphabet);
199
200 let alphabet_len = alphabet.len();
201 shuffle(&mut alphabet, &buffer[..alphabet_len]);
202 unhash(segment, &alphabet)
203 })
204 .collect();
205
206 match result {
207 None => Err(Error::Decode(DecodeError::Value)),
208 Some(result) => {
209 if self.encode(&result) == input.as_ref() {
210 Ok(result)
211 } else {
212 Err(Error::Decode(DecodeError::Hash))
213 }
214 }
215 }
216 }
217
218 pub fn encode_hex(&self, hex: &str) -> Result<String> {
220 let values: Option<Vec<_>> = hex
221 .as_bytes()
222 .chunks(12)
223 .map(|chunk| {
224 str::from_utf8(chunk)
225 .ok()
226 .and_then(|s| u64::from_str_radix(&("1".to_owned() + s), 16).ok())
227 })
228 .collect();
229
230 match values {
231 Some(values) => Ok(self.encode(&values)),
232 None => Err(Error::Hex),
233 }
234 }
235
236 pub fn decode_hex(&self, value: &str) -> Result<String> {
238 use std::fmt::Write;
239
240 let values = self.decode(value)?;
241
242 let mut result = String::new();
243 let mut buffer = String::new();
244
245 for n in values {
246 write!(buffer, "{:x}", n).unwrap();
247 result.push_str(&buffer[1..]);
248 buffer.clear();
249 }
250
251 Ok(result)
252 }
253}
254
255impl Default for Harsh {
256 fn default() -> Self {
257 Harsh::new()
258 }
259}
260
261#[inline]
262fn create_nhash(values: &[u64]) -> u64 {
263 values
264 .iter()
265 .enumerate()
266 .fold(0, |a, (idx, value)| a + (value % (idx + 100) as u64))
267}
268
269fn hash(mut value: u64, alphabet: &[u8]) -> String {
270 let length = alphabet.len() as u64;
271 let mut hash = Vec::new();
272
273 loop {
274 hash.push(alphabet[(value % length) as usize]);
275 value /= length;
276
277 if value == 0 {
278 hash.reverse();
279 return String::from_utf8(hash).expect("omg fml");
280 }
281 }
282}
283
284fn unhash(input: &[u8], alphabet: &[u8]) -> Option<u64> {
285 input.iter().enumerate().fold(Some(0), |a, (idx, &value)| {
286 let pos = alphabet.iter().position(|&item| item == value)? as u64;
287 let b = (alphabet.len() as u64).checked_pow((input.len() - idx - 1) as u32)?;
288 let c = pos.checked_mul(b)?;
289 a.map(|a| a + c)
290 })
291}
292
293#[cfg(test)]
294mod tests {
295 use super::{Harsh, HarshBuilder};
296
297 #[test]
298 fn harsh_default_does_not_panic() {
299 Harsh::default();
300 }
301
302 #[test]
303 fn can_encode() {
304 let harsh = HarshBuilder::new()
305 .salt("this is my salt")
306 .build()
307 .expect("failed to initialize harsh");
308
309 assert_eq!(
310 "4o6Z7KqxE",
311 harsh.encode(&[1226198605112]),
312 "error encoding [1226198605112]"
313 );
314 assert_eq!("laHquq", harsh.encode(&[1, 2, 3]));
315 }
316
317 #[test]
318 fn can_encode_with_guards() {
319 let harsh = HarshBuilder::new()
320 .salt("this is my salt")
321 .length(8)
322 .build()
323 .expect("failed to initialize harsh");
324
325 assert_eq!("GlaHquq0", harsh.encode(&[1, 2, 3]));
326 }
327
328 #[test]
329 fn can_encode_with_padding() {
330 let harsh = HarshBuilder::new()
331 .salt("this is my salt")
332 .length(12)
333 .build()
334 .expect("failed to initialize harsh");
335
336 assert_eq!("9LGlaHquq06D", harsh.encode(&[1, 2, 3]));
337 }
338
339 #[test]
340 fn can_decode() {
341 let harsh = HarshBuilder::new()
342 .salt("this is my salt")
343 .build()
344 .expect("failed to initialize harsh");
345
346 assert_eq!(
347 &[1226198605112],
348 &harsh.decode("4o6Z7KqxE").expect("failed to decode")[..],
349 "error decoding \"4o6Z7KqxE\""
350 );
351 assert_eq!(
352 &[1u64, 2, 3],
353 &harsh.decode("laHquq").expect("failed to decode")[..]
354 );
355 }
356
357 #[test]
358 fn can_decode_with_guards() {
359 let harsh = HarshBuilder::new()
360 .salt("this is my salt")
361 .length(8)
362 .build()
363 .expect("failed to initialize harsh");
364
365 assert_eq!(
366 &[1u64, 2, 3],
367 &harsh.decode("GlaHquq0").expect("failed to decode")[..]
368 );
369 }
370
371 #[test]
372 fn can_decode_with_padding() {
373 let harsh = HarshBuilder::new()
374 .salt("this is my salt")
375 .length(12)
376 .build()
377 .expect("failed to initialize harsh");
378
379 assert_eq!(
380 &[1u64, 2, 3],
381 &harsh.decode("9LGlaHquq06D").expect("failed to decode")[..]
382 );
383 }
384
385 #[test]
386 fn can_encode_hex() {
387 let harsh = HarshBuilder::new()
388 .salt("this is my salt")
389 .build()
390 .expect("failed to initialize harsh");
391
392 assert_eq!(
393 "lzY",
394 &harsh.encode_hex("FA").expect("Failed to encode"),
395 "error encoding `FA`"
396 );
397 assert_eq!(
398 "MemE",
399 &harsh.encode_hex("26dd").expect("Failed to encode"),
400 "error encoding `26dd`"
401 );
402 assert_eq!(
403 "eBMrb",
404 &harsh.encode_hex("FF1A").expect("Failed to encode"),
405 "error encoding `FF1A`"
406 );
407 assert_eq!(
408 "D9NPE",
409 &harsh.encode_hex("12abC").expect("Failed to encode"),
410 "error encoding `12abC`"
411 );
412 assert_eq!(
413 "9OyNW",
414 &harsh.encode_hex("185b0").expect("Failed to encode"),
415 "error encoding `185b0`"
416 );
417 assert_eq!(
418 "MRWNE",
419 &harsh.encode_hex("17b8d").expect("Failed to encode"),
420 "error encoding `17b8d`"
421 );
422 assert_eq!(
423 "4o6Z7KqxE",
424 &harsh.encode_hex("1d7f21dd38").expect("Failed to encode"),
425 "error encoding `1d7f21dd38`"
426 );
427 assert_eq!(
428 "ooweQVNB",
429 &harsh.encode_hex("20015111d").expect("Failed to encode"),
430 "error encoding `20015111d`"
431 );
432 assert_eq!(
433 "kRNrpKlJ",
434 &harsh.encode_hex("deadbeef").expect("Failed to encode"),
435 "error encoding `deadbeef`"
436 );
437
438 let harsh = HarshBuilder::new().build().unwrap();
439 assert_eq!(
440 "y42LW46J9luq3Xq9XMly",
441 &harsh
442 .encode_hex("507f1f77bcf86cd799439011",)
443 .expect("failed to encode",),
444 "error encoding `507f1f77bcf86cd799439011`"
445 );
446 }
447
448 #[test]
449 fn can_encode_hex_with_guards() {
450 let harsh = HarshBuilder::new()
451 .salt("this is my salt")
452 .length(10)
453 .build()
454 .expect("failed to initialize harsh");
455
456 assert_eq!(
457 "GkRNrpKlJd",
458 &harsh.encode_hex("deadbeef").expect("Failed to encode"),
459 );
460 }
461
462 #[test]
463 fn can_encode_hex_with_padding() {
464 let harsh = HarshBuilder::new()
465 .salt("this is my salt")
466 .length(12)
467 .build()
468 .expect("failed to initialize harsh");
469
470 assert_eq!(
471 "RGkRNrpKlJde",
472 &harsh.encode_hex("deadbeef").expect("Failed to encode"),
473 );
474 }
475
476 #[test]
477 fn can_decode_hex() {
478 let harsh = HarshBuilder::new()
479 .salt("this is my salt")
480 .build()
481 .expect("failed to initialize harsh");
482
483 assert_eq!(
484 "fa",
485 harsh.decode_hex("lzY").expect("failed to decode"),
486 "error decoding `FA`"
487 );
488 assert_eq!(
489 "26dd",
490 harsh.decode_hex("MemE").expect("failed to decode"),
491 "error decoding `26dd`"
492 );
493 assert_eq!(
494 "ff1a",
495 harsh.decode_hex("eBMrb").expect("failed to decode"),
496 "error decoding `FF1A`"
497 );
498 assert_eq!(
499 "12abc",
500 harsh.decode_hex("D9NPE").expect("failed to decode"),
501 "error decoding `12abC`"
502 );
503 assert_eq!(
504 "185b0",
505 harsh.decode_hex("9OyNW").expect("failed to decode"),
506 "error decoding `185b0`"
507 );
508 assert_eq!(
509 "17b8d",
510 harsh.decode_hex("MRWNE").expect("failed to decode"),
511 "error decoding `17b8d`"
512 );
513 assert_eq!(
514 "1d7f21dd38",
515 harsh.decode_hex("4o6Z7KqxE").expect("failed to decode"),
516 "error decoding `1d7f21dd38`"
517 );
518 assert_eq!(
519 "20015111d",
520 harsh.decode_hex("ooweQVNB").expect("failed to decode"),
521 "error decoding `20015111d`"
522 );
523 assert_eq!(
524 "deadbeef",
525 harsh.decode_hex("kRNrpKlJ").expect("failed to decode"),
526 "error decoding `deadbeef`"
527 );
528
529 let harsh = HarshBuilder::new().build().unwrap();
530 assert_eq!(
531 "507f1f77bcf86cd799439011",
532 harsh
533 .decode_hex("y42LW46J9luq3Xq9XMly",)
534 .expect("failed to decode",),
535 "error decoding `y42LW46J9luq3Xq9XMly`"
536 );
537 }
538
539 #[test]
540 fn can_decode_hex_with_guards() {
541 let harsh = HarshBuilder::new()
542 .salt("this is my salt")
543 .length(10)
544 .build()
545 .expect("failed to initialize harsh");
546
547 assert_eq!(
548 "deadbeef",
549 harsh.decode_hex("GkRNrpKlJd").expect("failed to decode"),
550 "failed to decode GkRNrpKlJd"
551 );
552 }
553
554 #[test]
555 fn can_decode_hex_with_padding() {
556 let harsh = HarshBuilder::new()
557 .salt("this is my salt")
558 .length(12)
559 .build()
560 .expect("failed to initialize harsh");
561
562 assert_eq!(
563 "deadbeef",
564 harsh.decode_hex("RGkRNrpKlJde").expect("failed to decode"),
565 "failed to decode RGkRNrpKlJde"
566 );
567 }
568
569 #[test]
570 fn can_encode_with_custom_alphabet() {
571 let harsh = HarshBuilder::new()
572 .alphabet("abcdefghijklmnopqrstuvwxyz")
573 .build()
574 .expect("failed to initialize harsh");
575
576 assert_eq!(
577 "mdfphx",
578 harsh.encode(&[1, 2, 3]),
579 "failed to encode [1, 2, 3]"
580 );
581 }
582
583 #[test]
584 #[should_panic]
585 fn can_decode_with_invalid_alphabet() {
586 let harsh = Harsh::default();
587 harsh.decode("this$ain't|a\number").unwrap();
588 }
589
590 #[test]
591 fn can_decode_with_custom_alphabet() {
592 let harsh = HarshBuilder::new()
593 .alphabet("abcdefghijklmnopqrstuvwxyz")
594 .build()
595 .expect("failed to initialize harsh");
596
597 assert_eq!(
598 &[1, 2, 3],
599 &harsh.decode("mdfphx").expect("failed to decode")[..],
600 "failed to decode mdfphx"
601 );
602 }
603
604 #[test]
605 fn create_nhash() {
606 let values = &[1, 2, 3];
607 let nhash = super::create_nhash(values);
608 assert_eq!(6, nhash);
609 }
610
611 #[test]
612 fn hash() {
613 let result = super::hash(22, b"abcdefghijklmnopqrstuvwxyz");
614 assert_eq!("w", result);
615 }
616
617 #[test]
618 fn shuffle() {
619 let salt = b"1234";
620 let mut values = "asdfzxcvqwer".bytes().collect::<Vec<_>>();
621 super::shuffle(&mut values, salt);
622
623 assert_eq!("vdwqfrzcsxae", String::from_utf8_lossy(&values));
624 }
625
626 #[test]
627 fn guard_characters_should_be_added_to_left_first() {
628 let harsh = HarshBuilder::new().length(3).build().unwrap();
629 let hashed_value = harsh.encode(&[1]);
630
631 assert_eq!(&hashed_value, "ejR");
632 assert_eq!(vec![1], harsh.decode("ejR").unwrap());
633 }
634
635 #[test]
636 #[should_panic]
637 fn appended_garbage_data_invalidates_hashid() {
638 let harsh = HarshBuilder::new().length(4).build().unwrap();
639 let id = harsh.encode(&[1, 2]) + "12";
640 harsh.decode(id).unwrap();
641 }
642}