epicinium_keycode/
keycode.rs

1/*
2 * Part of epicinium_keycode
3 * developed by Sander in 't Veld.
4 *
5 * Originally developed by A Bunch of Hacks
6 * as part of Epicinium.
7 *
8 * Copyright (c) 2018-2022 Sander in 't Veld
9 *
10 * License: MIT.
11 *
12 * [authors:]
13 * Sander in 't Veld (sander@abunchofhacks.coop)
14 */
15
16use crate::base32;
17
18use serde::Deserialize;
19use serde::Deserializer;
20use serde::Serialize;
21use serde::Serializer;
22
23/// A 60-bit unique id that can be encoded
24/// as a big-endian base32-word of length 12 (since 32^12 = 2^60).
25#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
26pub struct Keycode(pub u64);
27
28/// Create a lossy keycode by interleaving a 16 bit key and the 44
29/// least-significant bits of a data element into a 60 bit number.
30/// If the data element is enough to ensure uniqueness, varying the key helps
31/// visually identifying the Keycode.
32pub fn keycode(key: u16, data: u64) -> Keycode
33{
34	// The left-most 5 bits of the keycode are based on the 5 most-significant
35	// bits of the key; the next 5 bots are based on the next 4 bits of the
36	// key and the 1 most-significant bit of the data; ...; the six right-most
37	// groups of 5 bits are based on the 30 least-significant bits of the data.
38	const BITES: [i8; 12] = [5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0];
39
40	let mut k_bitstring = key as u64;
41	let mut d_bitstring = data;
42	let mut result: u64 = 0;
43	let mut placement_offset = 0;
44
45	// We fill the word from right (least-significant) to left (most-sign.).
46	for i in (0..12).rev()
47	{
48		// Consume the `bite` least-significant bits from the key and
49		// the `5 - bite` least-significant bits from the data, then combine
50		// them to be the next 5 bits.
51		let bite = BITES[i];
52		let k_mask = (1u64 << bite) - 1;
53		let d_mask = (1u64 << (5 - bite)) - 1;
54		let k_bits = k_bitstring & k_mask;
55		let d_bits = d_bitstring & d_mask;
56		let r_bits = (k_bits << (5 - bite)) | d_bits;
57		k_bitstring >>= bite;
58		d_bitstring >>= 5 - bite;
59		// Then add these 5-bits to the result.
60		result |= r_bits << placement_offset;
61		placement_offset += 5;
62	}
63
64	debug_assert!(result < (1 << 60));
65	Keycode(result)
66}
67
68impl std::fmt::Display for Keycode
69{
70	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
71	{
72		let mut bits: u64 = self.0;
73		let mut word = vec![0u8; 12];
74
75		// We fill the word from right (least-significant) to left (most-sign.).
76		for i in (0..12).rev()
77		{
78			// Consume the 5 least-significant bits from the keycode
79			// as a 5-bit index, i.e. a number from 0 to 31 (inclusive).
80			let nickel = (bits & 0x1F) as u8;
81			bits >>= 5;
82			// Then convert the 5-bit index to Base32.
83			word[i] = base32::letter_from_nickel(nickel);
84		}
85
86		let x = String::from_utf8(word).unwrap();
87		write!(f, "{}", x)
88	}
89}
90
91impl std::str::FromStr for Keycode
92{
93	type Err = base32::DecodeError;
94
95	fn from_str(s: &str) -> Result<Keycode, base32::DecodeError>
96	{
97		let mut bits: u64 = 0;
98
99		if !s.is_ascii()
100		{
101			return Err(base32::DecodeError::NonAscii {
102				source: s.to_string(),
103			});
104		}
105		else if s.len() < 12
106		{
107			return Err(base32::DecodeError::WordTooShort {
108				source: s.to_string(),
109				min_length_in_bits: 60,
110			});
111		}
112		else if s.len() > 12
113		{
114			return Err(base32::DecodeError::WordTooLong {
115				source: s.to_string(),
116				max_length_in_bits: 60,
117			});
118		}
119
120		// We parse the word from left (most-significant) to right (least).
121		for x in s.bytes()
122		{
123			let nickel: u8 = base32::nickel_from_letter(x)?;
124			debug_assert!(nickel <= 31);
125
126			// Push in 5 bits.
127			bits <<= 5;
128			bits |= nickel as u64;
129		}
130
131		Ok(Keycode(bits))
132	}
133}
134
135impl Serialize for Keycode
136{
137	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
138	where
139		S: Serializer,
140	{
141		self.to_string().serialize(serializer)
142	}
143}
144
145impl<'de> Deserialize<'de> for Keycode
146{
147	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
148	where
149		D: Deserializer<'de>,
150	{
151		let s = String::deserialize(deserializer)?;
152		std::str::FromStr::from_str(&s).map_err(::serde::de::Error::custom)
153	}
154}
155
156#[cfg(test)]
157mod tests
158{
159	use super::*;
160	use pretty_assertions::assert_eq;
161
162	#[test]
163	fn test_inverse() -> Result<(), base32::DecodeError>
164	{
165		for _ in 0..1000
166		{
167			let key: u16 = rand::random();
168			let data: u64 = rand::random();
169			let keycode = keycode(key, data);
170			let repr = keycode.to_string();
171			let base32_word = base32::encode(&keycode.0.to_be_bytes());
172			assert_eq!(base32_word, format!("0{}", repr));
173			assert_eq!(base32::decode(&base32_word)?, keycode.0.to_be_bytes());
174			let decoded: Keycode = repr.parse()?;
175			assert_eq!(decoded, keycode, "(repr = {})", repr);
176			assert_eq!(decoded.to_string(), repr);
177		}
178		Ok(())
179	}
180
181	#[test]
182	fn test_endianness() -> Result<(), base32::DecodeError>
183	{
184		for _ in 0..1000
185		{
186			let key: u16 = rand::random();
187			let key2: u16 = rand::random();
188			let data: u64 = rand::random();
189			let data2: u64 = rand::random();
190			let keycode1 = keycode(key, data);
191			let keycode_with_key2 = keycode(key2, data);
192			let keycode_with_data2 = keycode(key, data2);
193			let repr1 = keycode1.to_string();
194			let repr_with_key2 = keycode_with_key2.to_string();
195			let repr_with_data2 = keycode_with_data2.to_string();
196			assert_eq!(&repr1[6..12], &repr_with_key2[6..12]);
197			assert_eq!(&repr1[0..1], &repr_with_data2[0..1]);
198		}
199		Ok(())
200	}
201}