use std::hash::{DefaultHasher, Hash, Hasher};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum CursorKind {
Find,
Outline,
Read,
Explain,
Search,
}
impl CursorKind {
const fn code(self) -> char {
match self {
Self::Find => 'f',
Self::Outline => 'o',
Self::Read => 'r',
Self::Explain => 'e',
Self::Search => 's',
}
}
}
pub(super) fn fingerprint(parts: &[&str]) -> u64 {
let mut hasher = DefaultHasher::new();
for part in parts {
part.hash(&mut hasher);
}
hasher.finish()
}
pub(super) fn encode(kind: CursorKind, fingerprint: u64, position: u64) -> String {
format!("c1-{}-{fingerprint:016x}-{position:016x}", kind.code())
}
pub(super) fn decode(
value: Option<&str>,
kind: CursorKind,
expected_fingerprint: u64,
) -> Result<u64, String> {
let Some(value) = value else {
return Ok(0);
};
let mut fields = value.split('-');
let valid_prefix = fields.next() == Some("c1");
let valid_kind = fields
.next()
.is_some_and(|value| value.len() == 1 && value.starts_with(kind.code()));
let fingerprint = fields
.next()
.and_then(|value| u64::from_str_radix(value, 16).ok());
let position = fields
.next()
.and_then(|value| u64::from_str_radix(value, 16).ok());
if !valid_prefix
|| !valid_kind
|| fields.next().is_some()
|| fingerprint != Some(expected_fingerprint)
{
return Err(
"cursor is invalid or belongs to a different request; restart without it".to_owned(),
);
}
position.ok_or_else(|| {
"cursor is invalid or belongs to a different request; restart without it".to_owned()
})
}
pub(super) const fn join_position(offset: u32, byte: u32) -> u64 {
(offset as u64) << 32 | byte as u64
}
pub(super) const fn split_position(position: u64) -> (u32, u32) {
let bytes = position.to_be_bytes();
(
u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
)
}
#[cfg(test)]
mod tests {
use super::{CursorKind, decode, encode, fingerprint, join_position, split_position};
#[test]
fn cursors_are_bound_to_tool_and_normalized_request() {
let fingerprint = fingerprint(&["manual/1/git", "needle"]);
let token = encode(CursorKind::Search, fingerprint, join_position(20, 17));
assert_eq!(
split_position(
decode(Some(&token), CursorKind::Search, fingerprint).expect("valid cursor")
),
(20, 17)
);
assert!(decode(Some(&token), CursorKind::Read, fingerprint).is_err());
assert!(decode(Some(&token), CursorKind::Search, fingerprint + 1).is_err());
}
#[test]
fn malformed_cursors_fail_without_partial_interpretation() {
for value in ["", "c1-s", "c1-s-not-hex-0000000000000000", "c2-s-0-0"] {
assert!(decode(Some(value), CursorKind::Search, 0).is_err());
}
}
}