1use block_modes::{BlockMode, Cbc};
2use block_padding::ZeroPadding;
3use des::Des;
4use regex::Regex;
5use std::collections::BTreeMap;
6use std::error::Error;
7use std::mem::replace;
8use time::macros::format_description;
9use time::Date;
10use time::Duration;
11
12struct DateRange(Date, Date);
14impl Iterator for DateRange {
15 type Item = Date;
16 fn next(&mut self) -> Option<Self::Item> {
17 if self.0 <= self.1 {
18 let next = self.0 + Duration::days(1);
19 Some(replace(&mut self.0, next))
20 } else {
21 None
22 }
23 }
24}
25
26fn derive_from_input(date: &str, padded_seed: &str) -> String {
27 use vals::{ALPHANUM, TABLE1, TABLE2};
28 let fmt_date = validate_date(date).unwrap();
29 let date_components: Vec<i32> = date
31 .split('-')
32 .map(|i| i.parse::<i32>().expect("Error parsing date string"))
33 .collect();
34 let year = date_components[0];
35 let year_trimmed = year.to_string()[2..].parse::<i32>().unwrap();
37 let month = date_components[1];
38 let day = date_components[2];
39 let day_of_week = fmt_date.weekday().number_days_from_monday() as usize;
40 let a: Vec<i32> = (0..8)
41 .map(|i| match i {
42 0..=4 => TABLE1[day_of_week][i],
43 5 => day,
44 6 => {
45 if ((year_trimmed + month) - day) < 0 {
46 (((year_trimmed + month) - day) + 36) % 36
47 } else {
48 ((year_trimmed + month) - day) % 36
49 }
50 }
51 _ => (((3 + ((year_trimmed + month) % 12)) * day) % 37) % 36,
52 })
53 .collect();
54 let b: Vec<i32> = padded_seed.chars().map(|c| c as i32).collect();
55 let first_eight: Vec<i32> = (0..8).map(|i| (a[i] + b[i]) % 36).collect();
56 let sum_of_parts: i32 = first_eight.iter().sum();
57 let mut c: Vec<i32> = Vec::from(first_eight);
58 c.push(sum_of_parts % 36);
59 let last_value = (c[8] % 6).pow(2) as f64;
60 if (last_value - last_value.floor()) < 0.5 {
61 c.push(last_value.floor() as i32)
62 } else {
63 c.push(last_value.ceil() as i32);
64 }
65 let d: Vec<i32> = (0..10)
66 .map(|i| c[TABLE2[(c[8] % 6) as usize][i] as usize])
67 .collect();
68 let vec_a: Vec<i32> = padded_seed
69 .chars()
70 .enumerate()
71 .map(|(i, c)| (c as i32 + d[i]) % 36)
72 .collect();
73 let vec_b: String = (0..10)
74 .map(|i: i32| ALPHANUM[vec_a[i as usize] as usize])
75 .collect();
76 return vec_b;
77}
78
79fn pad_seed(seed: &str) -> String {
80 let mut padded = seed.to_string();
81 if seed.len() == 4 {
82 let diff = format!("{}{}", &seed, &seed[0..2]);
83 padded.push_str(&diff);
84 return padded;
85 }
86 let diff: String = (0..10 - seed.len())
87 .into_iter()
88 .map(|i| seed.as_bytes()[i as usize] as char)
89 .into_iter()
90 .collect();
91 padded.push_str(&diff);
92 padded
93}
94
95fn validate_seed(seed: &str) -> Result<String, Box<dyn Error>> {
96 use vals::DEFAULT_SEED;
97 if seed == DEFAULT_SEED {
98 return Ok(seed.to_string());
99 }
100 if seed.len() < 4 || seed.len() > 8 {
102 Err("Seed should be >= 4 and <= 8 characters long.")?;
103 }
104 let padded_seed: String = pad_seed(seed);
105 return Ok(padded_seed);
106}
107
108fn validate_date(date: &str) -> Result<Date, Box<dyn Error>> {
109 let fmt = format_description!("[year]-[month]-[day]");
110 let date_regex: Regex = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
111 if !date_regex.is_match(date) {
112 Err("Invalid date format, must be YYYY-MM-DD")?;
113 }
114 let parsed_date = Date::parse(date, fmt);
115 if parsed_date.is_ok() {
116 return Ok(parsed_date.unwrap());
117 } else {
118 let err = format!(
119 "Unable to parse date '{}'. Year, month or day value out of range.",
120 date
121 );
122 return Err(err.into());
123 }
124}
125
126fn validate_range(date_begin: &str, date_end: &str) -> Result<bool, Box<dyn Error>> {
127 let maybe_begin = validate_date(date_begin);
128 let maybe_end = validate_date(date_end);
129 if maybe_begin.is_err() {
130 return Err(maybe_begin.unwrap_err());
131 } else if maybe_end.is_err() {
132 return Err(maybe_end.unwrap_err());
133 }
134 let begin = maybe_begin.unwrap();
135 let end = maybe_end.unwrap();
136 if end - begin <= time::Duration::days(0) {
137 Err("Invalid date range. Beginning date must occur before end date, and the values cannot be the same.")?;
138 }
139 if end - begin > Duration::days(365) {
140 Err("Invalid date range. Official tooling does not allow a date range exceeding 1 year.")?;
141 }
142 return Ok(true);
143}
144
145pub fn generate(date: &str, seed: &str) -> Result<String, Box<dyn Error>> {
165 let valid_date = validate_date(date);
166 if valid_date.is_err() {
167 Err(valid_date.unwrap_err())?;
168 }
169 let valid_seed = validate_seed(seed);
170 if valid_seed.is_err() {
171 let err_str = &valid_seed.as_ref();
172 Err(err_str.unwrap_err().to_string())?;
173 }
174
175 return Ok(derive_from_input(date, &valid_seed.unwrap()));
176}
177
178pub fn generate_multiple(
198 date_begin: &str,
199 date_end: &str,
200 seed: &str,
201) -> Result<BTreeMap<String, String>, Box<dyn Error>> {
202 let begin = validate_date(date_begin);
203 let end = validate_date(date_end);
204 if begin.is_err() {
205 return Err(begin.unwrap_err());
206 }
207 if end.is_err() {
208 return Err(end.unwrap_err());
209 }
210 let valid_range = validate_range(date_begin, date_end);
211 if valid_range.is_err() {
212 return Err(valid_range.unwrap_err());
213 }
214 let date_range = DateRange(begin.unwrap(), end.unwrap());
215 let valid_seed = validate_seed(seed);
216 if valid_seed.is_err() {
217 return Err(valid_seed.unwrap_err());
218 }
219 let mut potd_map = BTreeMap::new();
220 for date in date_range {
221 let date_string = date.to_string();
222 let potd = derive_from_input(&date_string, valid_seed.as_ref().unwrap());
223 potd_map.insert(date_string, potd);
224 }
225 return Ok(potd_map);
226}
227
228pub fn seed_to_des(seed: &str) -> Result<String, Box<dyn Error>> {
253 use vals::{DEFAULT_DES, DEFAULT_SEED};
254 if seed == DEFAULT_SEED {
255 return Ok(DEFAULT_DES.to_string());
256 }
257 if seed.len() < 4 || seed.len() > 8 {
258 Err("Seed should be >= 4 and <= 8 characters long.")?;
259 }
260 let key = [20, 157, 64, 213, 193, 46, 85, 2];
261 let iv = [0, 0, 0, 0, 0, 0, 0, 0];
262 type DesCbc = Cbc<Des, ZeroPadding>;
263 let cipher = DesCbc::new_from_slices(&key, &iv).unwrap();
264 let mut seed_buffer = [0u8; 8];
265 seed_buffer[..seed.len()].copy_from_slice(seed.as_bytes());
266 let encrypted_seed = cipher.encrypt(&mut seed_buffer, seed.len()).unwrap();
267 let seed_string: String = encrypted_seed
268 .iter()
269 .map(|i| {
270 if i == &encrypted_seed[7] {
271 format!("{:X}", i)
272 } else {
273 format!("{:X}.", i)
274 }
275 })
276 .collect();
277 Ok(seed_string)
278}
279
280#[cfg(test)]
281mod tests;
282pub mod vals;