const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
const FNV_PRIME: u64 = 0x100000001b3;
pub const fn fnv1a(bytes: &[u8]) -> u64 {
let mut hash = FNV_OFFSET_BASIS;
let mut i = 0;
while i < bytes.len() {
hash ^= bytes[i] as u64;
hash = hash.wrapping_mul(FNV_PRIME);
i += 1;
}
hash
}
pub fn interface_hash(signatures: &[&str]) -> u64 {
let mut sorted: Vec<&str> = signatures.to_vec();
sorted.sort();
let combined = sorted.join("\n");
fnv1a(combined.as_bytes())
}
pub fn signature_string(
name: &str,
arg_types: &[String],
ret: &str,
wire_raw: bool,
streaming: bool,
client_streaming: bool,
) -> String {
let raw_marker = if wire_raw { "!raw" } else { "" };
let stream_marker = if streaming { "!stream" } else { "" };
let client_stream_marker = if client_streaming { "<stream" } else { "" };
format!(
"{}:{}->{}{}{}{}",
name,
arg_types.join(","),
ret,
raw_marker,
stream_marker,
client_stream_marker
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_input() {
assert_eq!(fnv1a(b""), FNV_OFFSET_BASIS);
}
#[test]
fn known_vector() {
let hash = fnv1a(b"fidius");
assert_ne!(hash, 0);
assert_eq!(hash, fnv1a(b"fidius"));
}
#[test]
fn order_independence() {
let a = interface_hash(&[
"process:&[u8],Value->Result<Vec<u8>,PluginError>",
"name:->String",
]);
let b = interface_hash(&[
"name:->String",
"process:&[u8],Value->Result<Vec<u8>,PluginError>",
]);
assert_eq!(a, b);
}
#[test]
fn sensitivity() {
let a = interface_hash(&["name:->String"]);
let b = interface_hash(&["name:->string"]); assert_ne!(a, b);
}
#[test]
fn streaming_markers_are_distinct() {
let args = ["u32".to_string()];
let unary = signature_string("read", &args, "Row", false, false, false);
let server = signature_string("read", &args, "Row", false, true, false);
let client = signature_string("read", &args, "Row", false, false, true);
let bidi = signature_string("read", &args, "Row", false, true, true);
assert!(server.ends_with("!stream"));
assert!(client.ends_with("<stream"));
assert!(bidi.ends_with("!stream<stream"));
let sigs = [&unary, &server, &client, &bidi];
for (i, a) in sigs.iter().enumerate() {
for b in &sigs[i + 1..] {
assert_ne!(
interface_hash(&[a.as_str()]),
interface_hash(&[b.as_str()]),
"signatures must hash distinctly: {a} vs {b}"
);
}
}
}
#[test]
fn different_signatures_differ() {
let a = interface_hash(&["foo:->i32"]);
let b = interface_hash(&["bar:->i32"]);
let c = interface_hash(&["foo:->u32"]);
assert_ne!(a, b);
assert_ne!(a, c);
assert_ne!(b, c);
}
}