use entidb_codec::{from_cbor, to_canonical_cbor, Value};
use entidb_core::{CollectionId, Database, EntityId};
pub fn fuzz_cbor_decode(data: &[u8]) {
let _ = from_cbor(data);
}
pub fn fuzz_cbor_roundtrip(data: &[u8]) {
if let Ok(value) = from_cbor(data) {
if let Ok(encoded) = to_canonical_cbor(&value) {
if let Ok(decoded) = from_cbor(&encoded) {
assert_eq!(
format!("{:?}", value),
format!("{:?}", decoded),
"Roundtrip mismatch"
);
}
}
}
}
pub fn fuzz_database_operations(data: &[u8]) {
if data.len() < 4 {
return;
}
let db = match Database::open_in_memory() {
Ok(db) => db,
Err(_) => return,
};
let collection = CollectionId::new(1);
let mut offset = 0;
while offset + 17 <= data.len() {
let op = data[offset];
let id_bytes: [u8; 16] = data[offset + 1..offset + 17]
.try_into()
.unwrap_or([0u8; 16]);
let id = EntityId::from_bytes(id_bytes);
offset += 17;
match op % 4 {
0 => {
let payload_len = (data.get(offset).copied().unwrap_or(0) as usize) % 256;
offset += 1;
let payload: Vec<u8> = if offset + payload_len <= data.len() {
data[offset..offset + payload_len].to_vec()
} else {
vec![0u8; payload_len]
};
offset += payload_len;
let _ = db.transaction(|tx| {
tx.put(collection, id, payload)?;
Ok(())
});
}
1 => {
let _ = db.get(collection, id);
}
2 => {
let _ = db.transaction(|tx| {
tx.delete(collection, id)?;
Ok(())
});
}
3 => {
let _ = db.list(collection);
}
_ => {}
}
}
}
pub fn fuzz_entity_id(data: &[u8]) {
if data.len() >= 16 {
let bytes: [u8; 16] = data[..16].try_into().unwrap();
let id = EntityId::from_bytes(bytes);
let bytes2 = id.as_bytes();
assert_eq!(&bytes, bytes2);
let _ = format!("{}", id);
let _ = format!("{:?}", id);
}
}
pub fn fuzz_wal_record(data: &[u8]) {
use entidb_core::{WalRecord, WalRecordType};
for record_type in [
WalRecordType::Begin,
WalRecordType::Put,
WalRecordType::Delete,
WalRecordType::Commit,
WalRecordType::Abort,
WalRecordType::Checkpoint,
] {
let _ = WalRecord::decode_payload(record_type, data);
}
}
pub fn fuzz_segment_record(data: &[u8]) {
use entidb_core::SegmentRecord;
let _ = SegmentRecord::decode(data);
}
#[derive(Debug, Clone)]
pub enum FuzzOp {
Put {
collection: u8,
entity: [u8; 16],
data: Vec<u8>,
},
Get { collection: u8, entity: [u8; 16] },
Delete { collection: u8, entity: [u8; 16] },
List { collection: u8 },
Checkpoint,
}
impl FuzzOp {
pub fn parse_sequence(data: &[u8]) -> Vec<FuzzOp> {
let mut ops = Vec::new();
let mut offset = 0;
while offset < data.len() {
let op_type = data[offset];
offset += 1;
let op = match op_type % 5 {
0 => {
if offset + 17 > data.len() {
break;
}
let collection = data[offset];
let entity: [u8; 16] = data[offset + 1..offset + 17]
.try_into()
.unwrap_or([0u8; 16]);
offset += 17;
let data_len = data.get(offset).copied().unwrap_or(0) as usize;
offset += 1;
let payload = if offset + data_len <= data.len() {
data[offset..offset + data_len].to_vec()
} else {
break;
};
offset += data_len;
FuzzOp::Put {
collection,
entity,
data: payload,
}
}
1 => {
if offset + 17 > data.len() {
break;
}
let collection = data[offset];
let entity: [u8; 16] = data[offset + 1..offset + 17]
.try_into()
.unwrap_or([0u8; 16]);
offset += 17;
FuzzOp::Get { collection, entity }
}
2 => {
if offset + 17 > data.len() {
break;
}
let collection = data[offset];
let entity: [u8; 16] = data[offset + 1..offset + 17]
.try_into()
.unwrap_or([0u8; 16]);
offset += 17;
FuzzOp::Delete { collection, entity }
}
3 => {
if offset >= data.len() {
break;
}
let collection = data[offset];
offset += 1;
FuzzOp::List { collection }
}
4 => FuzzOp::Checkpoint,
_ => break,
};
ops.push(op);
}
ops
}
pub fn execute_sequence(ops: &[FuzzOp], db: &Database) {
for op in ops {
match op {
FuzzOp::Put {
collection,
entity,
data,
} => {
let coll = CollectionId::new(*collection as u32);
let id = EntityId::from_bytes(*entity);
let _ = db.transaction(|tx| {
tx.put(coll, id, data.clone())?;
Ok(())
});
}
FuzzOp::Get { collection, entity } => {
let coll = CollectionId::new(*collection as u32);
let id = EntityId::from_bytes(*entity);
let _ = db.get(coll, id);
}
FuzzOp::Delete { collection, entity } => {
let coll = CollectionId::new(*collection as u32);
let id = EntityId::from_bytes(*entity);
let _ = db.transaction(|tx| {
tx.delete(coll, id)?;
Ok(())
});
}
FuzzOp::List { collection } => {
let coll = CollectionId::new(*collection as u32);
let _ = db.list(coll);
}
FuzzOp::Checkpoint => {
let _ = db.checkpoint();
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fuzz_cbor_decode_empty() {
fuzz_cbor_decode(&[]);
}
#[test]
fn test_fuzz_cbor_decode_garbage() {
fuzz_cbor_decode(&[0xFF, 0xFF, 0xFF, 0xFF]);
}
#[test]
fn test_fuzz_cbor_roundtrip_valid() {
fuzz_cbor_roundtrip(&[0x18, 0x2a]);
}
#[test]
fn test_fuzz_database_operations_empty() {
fuzz_database_operations(&[]);
}
#[test]
fn test_fuzz_database_operations_random() {
fuzz_database_operations(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]);
}
#[test]
fn test_fuzz_entity_id() {
fuzz_entity_id(&[0u8; 16]);
fuzz_entity_id(&[0xFF; 16]);
}
#[test]
fn test_parse_fuzz_ops() {
let data = vec![
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1, 2, 3, 4, 5,
];
let ops = FuzzOp::parse_sequence(&data);
assert!(!ops.is_empty());
}
}