mirror of
https://github.com/mikedilger/chorus.git
synced 2026-08-01 07:21:39 +00:00
types: Filter
This commit is contained in:
parent
575eec6787
commit
5ac2496e18
515
src/types/filter/mod.rs
Normal file
515
src/types/filter/mod.rs
Normal file
@ -0,0 +1,515 @@
|
||||
use super::{Event, Id, Kind, Pubkey, Tags, Time};
|
||||
use crate::Error;
|
||||
use std::fmt;
|
||||
|
||||
/*
|
||||
* 0 [2 bytes] length of entire structure
|
||||
* 2 [2 bytes] num_ids
|
||||
* 4 [2 bytes] num_authors
|
||||
* 6 [2 bytes] num_kinds
|
||||
* 8 [4 bytes] limit u32. Set to u32::max if limit was not set.
|
||||
* 12 [4 bytes] PADDING
|
||||
* 16 [8 bytes] since u64. Set to 0 if since was not set.
|
||||
* 24 [8 bytes] until u64. Set to u64::max if until was not set.
|
||||
* 32 [ID] array
|
||||
* [Pubkey] array starts at 32 + num_ids*32
|
||||
* [Kind] array starts at 32 + num_ids*32 + num_authors*32
|
||||
* [Tags] object starts at 32 + num_ids*32 + num_authors*32 * num_kinds*2
|
||||
*/
|
||||
|
||||
const NUM_IDS_OFFSET: usize = 2;
|
||||
const NUM_AUTHORS_OFFSET: usize = 4;
|
||||
const NUM_KINDS_OFFSET: usize = 6;
|
||||
const LIMIT_OFFSET: usize = 8;
|
||||
const SINCE_OFFSET: usize = 16;
|
||||
const UNTIL_OFFSET: usize = 24;
|
||||
const ARRAYS_OFFSET: usize = 32;
|
||||
const ID_SIZE: usize = 32;
|
||||
const PUBKEY_SIZE: usize = 32;
|
||||
const KIND_SIZE: usize = 2;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Filter<'a>(&'a [u8]);
|
||||
|
||||
impl<'a> Filter<'a> {
|
||||
pub fn delineate(input: &'a [u8]) -> Result<Filter<'a>, Error> {
|
||||
if input.len() < ARRAYS_OFFSET {
|
||||
return Err(Error::EndOfInput);
|
||||
}
|
||||
let len = parse_u16!(input, 0) as usize;
|
||||
if input.len() < len {
|
||||
return Err(Error::EndOfInput);
|
||||
}
|
||||
Ok(Filter(&input[0..len]))
|
||||
}
|
||||
|
||||
pub fn copy(&self, output: &mut [u8]) -> Result<(), Error> {
|
||||
if output.len() < self.0.len() {
|
||||
return Err(Error::EndOfInput);
|
||||
}
|
||||
output[..self.0.len()].copy_from_slice(self.0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn num_ids(&self) -> usize {
|
||||
parse_u16!(self.0, NUM_IDS_OFFSET) as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ids(&'a self) -> FilterIdIter<'a> {
|
||||
FilterIdIter {
|
||||
filter: self,
|
||||
next: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn num_authors(&self) -> usize {
|
||||
parse_u16!(self.0, NUM_AUTHORS_OFFSET) as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn start_of_authors(&self) -> usize {
|
||||
ARRAYS_OFFSET + self.num_ids() * ID_SIZE
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn authors(&'a self) -> FilterAuthorIter<'a> {
|
||||
FilterAuthorIter {
|
||||
filter: self,
|
||||
start_of_authors: self.start_of_authors(),
|
||||
next: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn num_kinds(&self) -> usize {
|
||||
parse_u16!(self.0, NUM_KINDS_OFFSET) as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn start_of_kinds(&self) -> usize {
|
||||
ARRAYS_OFFSET + self.num_ids() * ID_SIZE + self.num_authors() * PUBKEY_SIZE
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn kinds(&'a self) -> FilterKindIter<'a> {
|
||||
FilterKindIter {
|
||||
filter: self,
|
||||
start_of_kinds: self.start_of_kinds(),
|
||||
next: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn start_of_tags(&self) -> usize {
|
||||
ARRAYS_OFFSET
|
||||
+ self.num_ids() * ID_SIZE
|
||||
+ self.num_authors() * PUBKEY_SIZE
|
||||
+ self.num_kinds() * KIND_SIZE
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tags(&'a self) -> Result<Tags<'a>, Error> {
|
||||
Tags::delineate(&self.0[self.start_of_tags()..])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn limit(&self) -> u32 {
|
||||
parse_u32!(self.0, LIMIT_OFFSET)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn since(&self) -> Time {
|
||||
Time(parse_u64!(self.0, SINCE_OFFSET))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn until(&self) -> Time {
|
||||
Time(parse_u64!(self.0, UNTIL_OFFSET))
|
||||
}
|
||||
|
||||
pub fn event_matches(&self, event: &Event) -> Result<bool, Error> {
|
||||
// ids
|
||||
if self.num_ids() != 0 && !self.ids().any(|id| id == event.id()) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// authors
|
||||
if self.num_authors() != 0 && !self.authors().any(|pk| pk == event.pubkey()) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// kinds
|
||||
if self.num_kinds() != 0 && !self.kinds().any(|kind| kind == event.kind()) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// since
|
||||
if event.created_at() < self.since() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// until
|
||||
if event.created_at() > self.until() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// tags
|
||||
let filter_tags = self.tags()?;
|
||||
if !filter_tags.is_empty() {
|
||||
let event_tags = event.tags()?;
|
||||
if event_tags.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
while let Some(letter) = filter_tags.get_string(i, 0) {
|
||||
let mut j = 1;
|
||||
let mut found = false;
|
||||
while let Some(value) = filter_tags.get_string(i, j) {
|
||||
if event_tags.matches(letter, value) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
if !found {
|
||||
return Ok(false);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn as_json(&self) -> Result<Vec<u8>, Error> {
|
||||
let mut output: Vec<u8> = Vec::with_capacity(256);
|
||||
output.push(b'{');
|
||||
let mut first = true;
|
||||
|
||||
if self.num_ids() > 0 {
|
||||
output.extend(br#""ids":["#);
|
||||
for (i, id) in self.ids().enumerate() {
|
||||
if i > 0 {
|
||||
output.push(b',');
|
||||
}
|
||||
output.push(b'"');
|
||||
let pos = output.len();
|
||||
output.resize(pos + 64, 0);
|
||||
id.write_hex(&mut output[pos..])?;
|
||||
output.push(b'"');
|
||||
}
|
||||
output.push(b']');
|
||||
first = false;
|
||||
}
|
||||
|
||||
if self.num_authors() > 0 {
|
||||
if !first {
|
||||
output.push(b',');
|
||||
}
|
||||
output.extend(br#""authors":["#);
|
||||
for (i, pk) in self.authors().enumerate() {
|
||||
if i > 0 {
|
||||
output.push(b',');
|
||||
}
|
||||
output.push(b'"');
|
||||
let pos = output.len();
|
||||
output.resize(pos + 64, 0);
|
||||
pk.write_hex(&mut output[pos..])?;
|
||||
output.push(b'"');
|
||||
}
|
||||
output.push(b']');
|
||||
first = false;
|
||||
}
|
||||
|
||||
if self.num_kinds() > 0 {
|
||||
if !first {
|
||||
output.push(b',');
|
||||
}
|
||||
output.extend(br#""kinds":["#);
|
||||
for (i, k) in self.kinds().enumerate() {
|
||||
if i > 0 {
|
||||
output.push(b',');
|
||||
}
|
||||
output.extend(format!("{}", k.0).as_bytes());
|
||||
}
|
||||
output.push(b']');
|
||||
first = false;
|
||||
}
|
||||
|
||||
let tags = self.tags()?;
|
||||
if !tags.is_empty() {
|
||||
// Filter 'tags' are not an array of arrays, they are just a convenient
|
||||
// way to store similar data. They also elide the '#'. So we have to
|
||||
// iterate here, we cannot use tags.as_json()
|
||||
for tag in tags.iter() {
|
||||
if !first {
|
||||
output.push(b',');
|
||||
}
|
||||
for (i, bytes) in tag.enumerate() {
|
||||
if i == 0 {
|
||||
output.extend(b"\"#");
|
||||
output.extend(bytes);
|
||||
output.extend(b"\":[");
|
||||
} else {
|
||||
if i > 1 {
|
||||
output.push(b',');
|
||||
}
|
||||
output.push(b'"');
|
||||
output.extend(bytes);
|
||||
output.push(b'"');
|
||||
}
|
||||
}
|
||||
output.push(b']');
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
if self.limit() != u32::MAX {
|
||||
if !first {
|
||||
output.push(b',');
|
||||
}
|
||||
output.extend(format!(r#""limit":{}"#, self.limit()).as_bytes());
|
||||
first = false;
|
||||
}
|
||||
|
||||
if self.since() != Time::min() {
|
||||
if !first {
|
||||
output.push(b',');
|
||||
}
|
||||
output.extend(format!(r#""since":{}"#, self.since().0).as_bytes());
|
||||
first = false;
|
||||
}
|
||||
|
||||
if self.until() != Time::max() {
|
||||
if !first {
|
||||
output.push(b',');
|
||||
}
|
||||
output.extend(format!(r#""until":{}"#, self.until().0).as_bytes());
|
||||
}
|
||||
|
||||
output.push(b'}');
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Filter<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Ok(bytes) = self.as_json() {
|
||||
let s = unsafe { std::str::from_utf8_unchecked(&bytes) };
|
||||
write!(f, "{s}")
|
||||
} else {
|
||||
write!(f, "{{Corrupted Event}}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FilterIdIter<'a> {
|
||||
filter: &'a Filter<'a>,
|
||||
next: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for FilterIdIter<'a> {
|
||||
type Item = Id;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let num_ids = parse_u16!(self.filter.0, NUM_IDS_OFFSET) as usize;
|
||||
if self.next >= num_ids {
|
||||
None
|
||||
} else {
|
||||
let offset = ARRAYS_OFFSET + self.next * ID_SIZE;
|
||||
self.next += 1;
|
||||
if self.filter.0.len() < offset + ID_SIZE {
|
||||
None
|
||||
} else {
|
||||
Some(Id(self.filter.0[offset..offset + ID_SIZE]
|
||||
.try_into()
|
||||
.unwrap()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FilterAuthorIter<'a> {
|
||||
filter: &'a Filter<'a>,
|
||||
start_of_authors: usize,
|
||||
next: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for FilterAuthorIter<'a> {
|
||||
type Item = Pubkey;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let num_authors = parse_u16!(self.filter.0, NUM_AUTHORS_OFFSET) as usize;
|
||||
if self.next >= num_authors {
|
||||
None
|
||||
} else {
|
||||
let offset = self.start_of_authors + self.next * PUBKEY_SIZE;
|
||||
self.next += 1;
|
||||
if self.filter.0.len() < offset + PUBKEY_SIZE {
|
||||
None
|
||||
} else {
|
||||
Some(Pubkey(
|
||||
self.filter.0[offset..offset + PUBKEY_SIZE]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FilterKindIter<'a> {
|
||||
filter: &'a Filter<'a>,
|
||||
start_of_kinds: usize,
|
||||
next: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for FilterKindIter<'a> {
|
||||
type Item = Kind;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let num_kinds = parse_u16!(self.filter.0, NUM_KINDS_OFFSET) as usize;
|
||||
if self.next >= num_kinds {
|
||||
None
|
||||
} else {
|
||||
let offset = self.start_of_kinds + self.next * KIND_SIZE;
|
||||
self.next += 1;
|
||||
if self.filter.0.len() < offset + KIND_SIZE {
|
||||
None
|
||||
} else {
|
||||
Some(Kind(parse_u16!(self.filter.0, offset)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_filter() {
|
||||
/*
|
||||
* {
|
||||
* "ids": [ "6b43bc2e373b6d9330ff571f3f4e6d897b32d01d65227df3fa41cdf731c63c3a",
|
||||
* "1f47034c9d6d0539382a86ba31766f00f2b8312ab167c036729422ec9e7085e8"],
|
||||
* "authors": [ "52b4a076bcbbbdc3a1aefa3735816cf74993b1b8db202b01c883c58be7fad8bd",
|
||||
* "ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49" ],
|
||||
* "kinds": [ 1, 5, 30023 ],
|
||||
* "since": 1702161345,
|
||||
* "#p": [ "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52" ],
|
||||
* }
|
||||
*/
|
||||
|
||||
// For comparison
|
||||
let id1 = Id::read_hex(b"6b43bc2e373b6d9330ff571f3f4e6d897b32d01d65227df3fa41cdf731c63c3a")
|
||||
.unwrap();
|
||||
|
||||
let id2 = Id::read_hex(b"1f47034c9d6d0539382a86ba31766f00f2b8312ab167c036729422ec9e7085e8")
|
||||
.unwrap();
|
||||
let pk1 =
|
||||
Pubkey::read_hex(b"52b4a076bcbbbdc3a1aefa3735816cf74993b1b8db202b01c883c58be7fad8bd")
|
||||
.unwrap();
|
||||
let pk2 =
|
||||
Pubkey::read_hex(b"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49")
|
||||
.unwrap();
|
||||
let tagged =
|
||||
Pubkey::read_hex(b"fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52")
|
||||
.unwrap();
|
||||
|
||||
let data: Vec<u8> = vec![
|
||||
211, 0, // length of structure
|
||||
2, 0, // number of IDs
|
||||
2, 0, // number of authors
|
||||
3, 0, // number of kinds
|
||||
255, 255, 255, 255, // limit
|
||||
0, 0, 0, 0, // padding
|
||||
0xC1, 0xEB, 0x74, 0x65, 0, 0, 0, 0, // since
|
||||
255, 255, 255, 255, 255, 255, 255, 255, // until
|
||||
0x6b, 0x43, 0xbc, 0x2e, 0x37, 0x3b, 0x6d, 0x93, 0x30, 0xff, 0x57, 0x1f, 0x3f, 0x4e,
|
||||
0x6d, 0x89, 0x7b, 0x32, 0xd0, 0x1d, 0x65, 0x22, 0x7d, 0xf3, 0xfa, 0x41, 0xcd, 0xf7,
|
||||
0x31, 0xc6, 0x3c, 0x3a, // ID 1
|
||||
0x1f, 0x47, 0x03, 0x4c, 0x9d, 0x6d, 0x05, 0x39, 0x38, 0x2a, 0x86, 0xba, 0x31, 0x76,
|
||||
0x6f, 0x00, 0xf2, 0xb8, 0x31, 0x2a, 0xb1, 0x67, 0xc0, 0x36, 0x72, 0x94, 0x22, 0xec,
|
||||
0x9e, 0x70, 0x85, 0xe8, // ID 2
|
||||
0x52, 0xb4, 0xa0, 0x76, 0xbc, 0xbb, 0xbd, 0xc3, 0xa1, 0xae, 0xfa, 0x37, 0x35, 0x81,
|
||||
0x6c, 0xf7, 0x49, 0x93, 0xb1, 0xb8, 0xdb, 0x20, 0x2b, 0x01, 0xc8, 0x83, 0xc5, 0x8b,
|
||||
0xe7, 0xfa, 0xd8, 0xbd, // Pubkey 1
|
||||
0xee, 0x11, 0xa5, 0xdf, 0xf4, 0x0c, 0x19, 0xa5, 0x55, 0xf4, 0x1f, 0xe4, 0x2b, 0x48,
|
||||
0xf0, 0x0e, 0x61, 0x8c, 0x91, 0x22, 0x56, 0x22, 0xae, 0x37, 0xb6, 0xc2, 0xbb, 0x67,
|
||||
0xb7, 0x6c, 0x4e, 0x49, // Pubkey 2
|
||||
1, 0, 5, 0, 71, 117, // 3 kinds
|
||||
// Tags
|
||||
45, 0, // tags_len
|
||||
1, 0, // num_tags
|
||||
6, 0, // first tag offset at 6
|
||||
2, 0, // 2 fields long
|
||||
1, 0, // 1st field is 1 byte
|
||||
112, // "p"
|
||||
32, 0, // 2nd field is 32 bytes
|
||||
// 2nd field
|
||||
0xfa, 0x98, 0x4b, 0xd7, 0xdb, 0xb2, 0x82, 0xf0, 0x7e, 0x16, 0xe7, 0xae, 0x87, 0xb2,
|
||||
0x6a, 0x2a, 0x7b, 0x9b, 0x90, 0xb7, 0x24, 0x6a, 0x44, 0x77, 0x1f, 0x0c, 0xf5, 0xae,
|
||||
0x58, 0x01, 0x8f, 0x52,
|
||||
];
|
||||
|
||||
let filter = Filter::delineate(&data).unwrap();
|
||||
|
||||
assert_eq!(filter.num_ids(), 2);
|
||||
let mut ids = filter.ids();
|
||||
assert_eq!(ids.next().unwrap(), id1);
|
||||
assert_eq!(ids.next().unwrap(), id2);
|
||||
assert!(ids.next().is_none());
|
||||
|
||||
assert_eq!(filter.num_authors(), 2);
|
||||
let mut authors = filter.authors();
|
||||
assert_eq!(authors.next().unwrap(), pk1);
|
||||
assert_eq!(authors.next().unwrap(), pk2);
|
||||
assert!(authors.next().is_none());
|
||||
|
||||
assert_eq!(filter.num_kinds(), 3);
|
||||
let mut kinds = filter.kinds();
|
||||
assert_eq!(kinds.next().unwrap(), Kind(1));
|
||||
assert_eq!(kinds.next().unwrap(), Kind(5));
|
||||
assert_eq!(kinds.next().unwrap(), Kind(30023));
|
||||
assert!(kinds.next().is_none());
|
||||
|
||||
assert_eq!(filter.limit(), u32::MAX);
|
||||
assert_eq!(filter.since(), Time(1702161345));
|
||||
assert_eq!(filter.until(), Time::max());
|
||||
|
||||
let tags = filter.tags().unwrap();
|
||||
assert_eq!(tags.len(), 1);
|
||||
let mut iter = tags.iter();
|
||||
let mut tag = iter.next().unwrap();
|
||||
assert!(iter.next().is_none());
|
||||
assert_eq!(tag.next().unwrap(), b"p");
|
||||
let p_bytes = tag.next().unwrap();
|
||||
assert!(tag.next().is_none());
|
||||
let pk = Pubkey(p_bytes.try_into().unwrap());
|
||||
assert_eq!(pk, tagged);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,9 @@
|
||||
mod event;
|
||||
pub use event::Event;
|
||||
|
||||
mod filter;
|
||||
pub use filter::Filter;
|
||||
|
||||
mod id;
|
||||
pub use id::Id;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user