coffio 0.1.0

Abstraction layer for symmetric data encryption, primarily designed for database column encryption.
Documentation
use base64ct::{Base64UrlUnpadded, Encoding};

const CANONICALIZATION_BUFFER_SIZE: usize = 1024;
const CANONICALIZATION_SEPARATOR: &str = ":";

#[inline]
pub(crate) fn join_canonicalized_str(elems: &[String]) -> String {
	elems.join(CANONICALIZATION_SEPARATOR)
}

pub(crate) fn canonicalize(context: &[impl AsRef<[u8]>]) -> String {
	match context.len() {
		0 => String::new(),
		1 => Base64UrlUnpadded::encode_string(context[0].as_ref()),
		_ => {
			let mut ret = String::with_capacity(CANONICALIZATION_BUFFER_SIZE);
			for (i, ctx_elem) in context.iter().enumerate() {
				if i != 0 {
					ret += CANONICALIZATION_SEPARATOR;
				}
				ret += &Base64UrlUnpadded::encode_string(ctx_elem.as_ref());
			}
			ret
		}
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	const EMPTY_CTX: &[[u8; 0]] = &[];

	#[test]
	fn canonicalize_empty() {
		let canon = canonicalize(EMPTY_CTX);
		assert_eq!(canon, String::new());
	}

	#[test]
	fn canonicalize_one() {
		let canon = canonicalize(&["test"]);
		assert_eq!(&canon, "dGVzdA");
	}

	#[test]
	fn canonicalize_many() {
		let canon = canonicalize(&["test", "bis", "ter", ""]);
		assert_eq!(&canon, "dGVzdA:Ymlz:dGVy:");
	}

	#[test]
	fn test_join_canonicalized_empty() {
		assert_eq!(join_canonicalized_str(&[]), "");
	}

	#[test]
	fn test_join_canonicalized_one() {
		assert_eq!(
			join_canonicalized_str(&["QWO7RGDt".to_string()]),
			"QWO7RGDt"
		);
	}

	#[test]
	fn test_join_canonicalized_one_empty() {
		assert_eq!(join_canonicalized_str(&[String::new()]), "");
	}

	#[test]
	fn test_join_canonicalized_empty_str() {
		assert_eq!(join_canonicalized_str(&[String::new(), String::new()]), ":");
	}

	#[test]
	fn test_join_canonicalized_with_data() {
		assert_eq!(
			join_canonicalized_str(&["QWO7RGDt:f-JmDPvU".into(), "_Sfx61Fp".into()]),
			"QWO7RGDt:f-JmDPvU:_Sfx61Fp"
		);
	}
}