use byteorder::{LittleEndian, WriteBytesExt};
use std::io::{Seek, SeekFrom, Write};
const FILEMAGIC: &[u8; 4] = b"lCRF";
const MODELTYPE: &[u8; 4] = b"FOMC";
const VERSION_NUMBER: u32 = 100;
const HEADER_SIZE: u32 = 48;
const CHUNK_SIZE: u32 = 12;
const FEATURE_SIZE: u32 = 20;
const CQDB_MAGIC: &[u8; 4] = b"CQDB";
const CQDB_BYTEORDER: u32 = 0x62445371;
const NUM_TABLES: usize = 256;
const CQDB_HEADER_SIZE: usize = 24;
const CQDB_TABLEREF_SIZE: usize = NUM_TABLES * 8;
fn hashlittle(key: &[u8], initval: u32) -> u32 {
let mut a: u32 = 0xdeadbeef_u32
.wrapping_add(key.len() as u32)
.wrapping_add(initval);
let mut b: u32 = a;
let mut c: u32 = a;
let mut i = 0;
while i + 12 <= key.len() {
a = a.wrapping_add(u32::from_le_bytes([
key[i],
key[i + 1],
key[i + 2],
key[i + 3],
]));
b = b.wrapping_add(u32::from_le_bytes([
key[i + 4],
key[i + 5],
key[i + 6],
key[i + 7],
]));
c = c.wrapping_add(u32::from_le_bytes([
key[i + 8],
key[i + 9],
key[i + 10],
key[i + 11],
]));
a = a.wrapping_sub(c);
a ^= c.rotate_left(4);
c = c.wrapping_add(b);
b = b.wrapping_sub(a);
b ^= a.rotate_left(6);
a = a.wrapping_add(c);
c = c.wrapping_sub(b);
c ^= b.rotate_left(8);
b = b.wrapping_add(a);
a = a.wrapping_sub(c);
a ^= c.rotate_left(16);
c = c.wrapping_add(b);
b = b.wrapping_sub(a);
b ^= a.rotate_left(19);
a = a.wrapping_add(c);
c = c.wrapping_sub(b);
c ^= b.rotate_left(4);
b = b.wrapping_add(a);
i += 12;
}
let remaining = key.len() - i;
match remaining {
12 => {
c = c.wrapping_add(u32::from_le_bytes([
key[i + 8],
key[i + 9],
key[i + 10],
key[i + 11],
]));
b = b.wrapping_add(u32::from_le_bytes([
key[i + 4],
key[i + 5],
key[i + 6],
key[i + 7],
]));
a = a.wrapping_add(u32::from_le_bytes([
key[i],
key[i + 1],
key[i + 2],
key[i + 3],
]));
}
11 => {
c = c.wrapping_add((key[i + 10] as u32) << 16);
c = c.wrapping_add((key[i + 9] as u32) << 8);
c = c.wrapping_add(key[i + 8] as u32);
b = b.wrapping_add(u32::from_le_bytes([
key[i + 4],
key[i + 5],
key[i + 6],
key[i + 7],
]));
a = a.wrapping_add(u32::from_le_bytes([
key[i],
key[i + 1],
key[i + 2],
key[i + 3],
]));
}
10 => {
c = c.wrapping_add((key[i + 9] as u32) << 8);
c = c.wrapping_add(key[i + 8] as u32);
b = b.wrapping_add(u32::from_le_bytes([
key[i + 4],
key[i + 5],
key[i + 6],
key[i + 7],
]));
a = a.wrapping_add(u32::from_le_bytes([
key[i],
key[i + 1],
key[i + 2],
key[i + 3],
]));
}
9 => {
c = c.wrapping_add(key[i + 8] as u32);
b = b.wrapping_add(u32::from_le_bytes([
key[i + 4],
key[i + 5],
key[i + 6],
key[i + 7],
]));
a = a.wrapping_add(u32::from_le_bytes([
key[i],
key[i + 1],
key[i + 2],
key[i + 3],
]));
}
8 => {
b = b.wrapping_add(u32::from_le_bytes([
key[i + 4],
key[i + 5],
key[i + 6],
key[i + 7],
]));
a = a.wrapping_add(u32::from_le_bytes([
key[i],
key[i + 1],
key[i + 2],
key[i + 3],
]));
}
7 => {
b = b.wrapping_add((key[i + 6] as u32) << 16);
b = b.wrapping_add((key[i + 5] as u32) << 8);
b = b.wrapping_add(key[i + 4] as u32);
a = a.wrapping_add(u32::from_le_bytes([
key[i],
key[i + 1],
key[i + 2],
key[i + 3],
]));
}
6 => {
b = b.wrapping_add((key[i + 5] as u32) << 8);
b = b.wrapping_add(key[i + 4] as u32);
a = a.wrapping_add(u32::from_le_bytes([
key[i],
key[i + 1],
key[i + 2],
key[i + 3],
]));
}
5 => {
b = b.wrapping_add(key[i + 4] as u32);
a = a.wrapping_add(u32::from_le_bytes([
key[i],
key[i + 1],
key[i + 2],
key[i + 3],
]));
}
4 => {
a = a.wrapping_add(u32::from_le_bytes([
key[i],
key[i + 1],
key[i + 2],
key[i + 3],
]));
}
3 => {
a = a.wrapping_add((key[i + 2] as u32) << 16);
a = a.wrapping_add((key[i + 1] as u32) << 8);
a = a.wrapping_add(key[i] as u32);
}
2 => {
a = a.wrapping_add((key[i + 1] as u32) << 8);
a = a.wrapping_add(key[i] as u32);
}
1 => {
a = a.wrapping_add(key[i] as u32);
}
0 => return c,
_ => {}
}
c ^= b;
c = c.wrapping_sub(b.rotate_left(14));
a ^= c;
a = a.wrapping_sub(c.rotate_left(11));
b ^= a;
b = b.wrapping_sub(a.rotate_left(25));
c ^= b;
c = c.wrapping_sub(b.rotate_left(16));
a ^= c;
a = a.wrapping_sub(c.rotate_left(4));
b ^= a;
b = b.wrapping_sub(a.rotate_left(14));
c ^= b;
c = c.wrapping_sub(b.rotate_left(24));
c
}
#[derive(Clone, Copy, Default)]
struct Bucket {
hash: u32,
offset: u32,
}
pub struct CQDBWriter {
data: Vec<u8>,
tables: Vec<Vec<Bucket>>,
backward: Vec<u32>,
}
impl Default for CQDBWriter {
fn default() -> Self {
Self::new()
}
}
impl CQDBWriter {
pub fn new() -> Self {
Self {
data: Vec::new(),
tables: (0..NUM_TABLES).map(|_| Vec::new()).collect(),
backward: Vec::new(),
}
}
pub fn put(&mut self, s: &str, id: u32) {
let key = format!("{}\0", s);
let key_bytes = key.as_bytes();
let hv = hashlittle(key_bytes, 0);
let table_idx = (hv % 256) as usize;
let offset = (CQDB_HEADER_SIZE + CQDB_TABLEREF_SIZE + self.data.len()) as u32;
self.data.extend_from_slice(&id.to_le_bytes());
self.data
.extend_from_slice(&(key_bytes.len() as u32).to_le_bytes());
self.data.extend_from_slice(key_bytes);
self.tables[table_idx].push(Bucket { hash: hv, offset });
if id as usize >= self.backward.len() {
self.backward.resize(id as usize + 1, 0);
}
self.backward[id as usize] = offset;
}
pub fn write<W: Write + Seek>(&self, writer: &mut W) -> Result<u32, String> {
let start_pos = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))? as u32;
let data_end = CQDB_HEADER_SIZE + CQDB_TABLEREF_SIZE + self.data.len();
let mut hash_tables_size = 0usize;
for table in &self.tables {
if !table.is_empty() {
hash_tables_size += table.len() * 2 * 8; }
}
let bwd_offset = data_end + hash_tables_size;
let bwd_size = self.backward.len();
let total_size = bwd_offset + bwd_size * 4;
writer
.write_all(CQDB_MAGIC)
.map_err(|e| format!("Failed to write CQDB magic: {}", e))?;
writer
.write_u32::<LittleEndian>(total_size as u32)
.map_err(|e| format!("Failed to write size: {}", e))?;
writer
.write_u32::<LittleEndian>(0) .map_err(|e| format!("Failed to write flag: {}", e))?;
writer
.write_u32::<LittleEndian>(CQDB_BYTEORDER)
.map_err(|e| format!("Failed to write byteorder: {}", e))?;
writer
.write_u32::<LittleEndian>(bwd_size as u32)
.map_err(|e| format!("Failed to write bwd_size: {}", e))?;
writer
.write_u32::<LittleEndian>(bwd_offset as u32)
.map_err(|e| format!("Failed to write bwd_offset: {}", e))?;
let mut table_offset = data_end as u32;
for table in &self.tables {
if table.is_empty() {
writer
.write_u32::<LittleEndian>(0)
.map_err(|e| format!("Failed to write table offset: {}", e))?;
writer
.write_u32::<LittleEndian>(0)
.map_err(|e| format!("Failed to write table num: {}", e))?;
} else {
let bucket_size = table.len() * 2; writer
.write_u32::<LittleEndian>(table_offset)
.map_err(|e| format!("Failed to write table offset: {}", e))?;
writer
.write_u32::<LittleEndian>(bucket_size as u32)
.map_err(|e| format!("Failed to write table num: {}", e))?;
table_offset += (bucket_size * 8) as u32;
}
}
writer
.write_all(&self.data)
.map_err(|e| format!("Failed to write data: {}", e))?;
for table in &self.tables {
if !table.is_empty() {
let n = table.len() * 2;
let mut buckets = vec![Bucket::default(); n];
for src in table {
let mut k = ((src.hash >> 8) as usize) % n;
while buckets[k].offset != 0 {
k = (k + 1) % n;
}
buckets[k] = *src;
}
for b in &buckets {
writer
.write_u32::<LittleEndian>(b.hash)
.map_err(|e| format!("Failed to write bucket hash: {}", e))?;
writer
.write_u32::<LittleEndian>(b.offset)
.map_err(|e| format!("Failed to write bucket offset: {}", e))?;
}
}
}
for &offset in &self.backward {
writer
.write_u32::<LittleEndian>(offset)
.map_err(|e| format!("Failed to write backward link: {}", e))?;
}
let end_pos = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))? as u32;
Ok(end_pos - start_pos)
}
}
pub struct CRFsuiteFeature {
pub feat_type: u32, pub src: u32,
pub dst: u32,
pub weight: f64,
}
pub fn write_crfsuite_model<W: Write + Seek>(
writer: &mut W,
labels: &[String],
attributes: &[String],
features: &[CRFsuiteFeature],
) -> Result<(), String> {
writer
.seek(SeekFrom::Start(HEADER_SIZE as u64))
.map_err(|e| format!("Failed to seek: {}", e))?;
let off_features = HEADER_SIZE;
let feat_chunk_size = CHUNK_SIZE + (features.len() as u32) * FEATURE_SIZE;
writer
.write_all(b"FEAT")
.map_err(|e| format!("Failed to write FEAT magic: {}", e))?;
writer
.write_u32::<LittleEndian>(feat_chunk_size)
.map_err(|e| format!("Failed to write FEAT size: {}", e))?;
writer
.write_u32::<LittleEndian>(features.len() as u32)
.map_err(|e| format!("Failed to write FEAT num: {}", e))?;
for f in features {
writer
.write_u32::<LittleEndian>(f.feat_type)
.map_err(|e| format!("Failed to write feature type: {}", e))?;
writer
.write_u32::<LittleEndian>(f.src)
.map_err(|e| format!("Failed to write feature src: {}", e))?;
writer
.write_u32::<LittleEndian>(f.dst)
.map_err(|e| format!("Failed to write feature dst: {}", e))?;
writer
.write_f64::<LittleEndian>(f.weight)
.map_err(|e| format!("Failed to write feature weight: {}", e))?;
}
let off_labels = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))? as u32;
let mut labels_cqdb = CQDBWriter::new();
for (i, label) in labels.iter().enumerate() {
labels_cqdb.put(label, i as u32);
}
labels_cqdb.write(writer)?;
let off_attrs = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))? as u32;
let mut attrs_cqdb = CQDBWriter::new();
for (i, attr) in attributes.iter().enumerate() {
attrs_cqdb.put(attr, i as u32);
}
attrs_cqdb.write(writer)?;
let pos = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))? as u32;
let padding = (4 - (pos % 4)) % 4;
for _ in 0..padding {
writer
.write_u8(0)
.map_err(|e| format!("Failed to write padding: {}", e))?;
}
let off_labelrefs = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))? as u32;
let mut label_features: Vec<Vec<u32>> = vec![Vec::new(); labels.len()];
for (fid, f) in features.iter().enumerate() {
if f.feat_type == 1 {
if (f.src as usize) < labels.len() {
label_features[f.src as usize].push(fid as u32);
}
}
}
let _labelrefs_header_size = CHUNK_SIZE + (labels.len() as u32) * 4;
writer
.write_all(b"LFRF")
.map_err(|e| format!("Failed to write LFRF magic: {}", e))?;
let lfrf_size_pos = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))?;
writer
.write_u32::<LittleEndian>(0) .map_err(|e| format!("Failed to write LFRF size: {}", e))?;
writer
.write_u32::<LittleEndian>(labels.len() as u32)
.map_err(|e| format!("Failed to write LFRF num: {}", e))?;
let offsets_pos = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))?;
for _ in 0..labels.len() {
writer
.write_u32::<LittleEndian>(0)
.map_err(|e| format!("Failed to write offset placeholder: {}", e))?;
}
let mut label_offsets = Vec::with_capacity(labels.len());
for fids in &label_features {
let offset = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))? as u32;
label_offsets.push(offset);
writer
.write_u32::<LittleEndian>(fids.len() as u32)
.map_err(|e| format!("Failed to write num features: {}", e))?;
for &fid in fids {
writer
.write_u32::<LittleEndian>(fid)
.map_err(|e| format!("Failed to write feature id: {}", e))?;
}
}
let lfrf_end = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))? as u32;
writer
.seek(SeekFrom::Start(lfrf_size_pos))
.map_err(|e| format!("Failed to seek: {}", e))?;
writer
.write_u32::<LittleEndian>(lfrf_end - off_labelrefs)
.map_err(|e| format!("Failed to write LFRF size: {}", e))?;
writer
.seek(SeekFrom::Start(offsets_pos))
.map_err(|e| format!("Failed to seek: {}", e))?;
for offset in &label_offsets {
writer
.write_u32::<LittleEndian>(*offset)
.map_err(|e| format!("Failed to write offset: {}", e))?;
}
writer
.seek(SeekFrom::Start(lfrf_end as u64))
.map_err(|e| format!("Failed to seek: {}", e))?;
let pos = lfrf_end;
let padding = (4 - (pos % 4)) % 4;
for _ in 0..padding {
writer
.write_u8(0)
.map_err(|e| format!("Failed to write padding: {}", e))?;
}
let off_attrrefs = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))? as u32;
let mut attr_features: Vec<Vec<u32>> = vec![Vec::new(); attributes.len()];
for (fid, f) in features.iter().enumerate() {
if f.feat_type == 0 {
if (f.src as usize) < attributes.len() {
attr_features[f.src as usize].push(fid as u32);
}
}
}
writer
.write_all(b"AFRF")
.map_err(|e| format!("Failed to write AFRF magic: {}", e))?;
let afrf_size_pos = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))?;
writer
.write_u32::<LittleEndian>(0) .map_err(|e| format!("Failed to write AFRF size: {}", e))?;
writer
.write_u32::<LittleEndian>(attributes.len() as u32)
.map_err(|e| format!("Failed to write AFRF num: {}", e))?;
let attr_offsets_pos = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))?;
for _ in 0..attributes.len() {
writer
.write_u32::<LittleEndian>(0)
.map_err(|e| format!("Failed to write offset placeholder: {}", e))?;
}
let mut attr_offsets = Vec::with_capacity(attributes.len());
for fids in &attr_features {
let offset = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))? as u32;
attr_offsets.push(offset);
writer
.write_u32::<LittleEndian>(fids.len() as u32)
.map_err(|e| format!("Failed to write num features: {}", e))?;
for &fid in fids {
writer
.write_u32::<LittleEndian>(fid)
.map_err(|e| format!("Failed to write feature id: {}", e))?;
}
}
let afrf_end = writer
.stream_position()
.map_err(|e| format!("Failed to get position: {}", e))? as u32;
writer
.seek(SeekFrom::Start(afrf_size_pos))
.map_err(|e| format!("Failed to seek: {}", e))?;
writer
.write_u32::<LittleEndian>(afrf_end - off_attrrefs)
.map_err(|e| format!("Failed to write AFRF size: {}", e))?;
writer
.seek(SeekFrom::Start(attr_offsets_pos))
.map_err(|e| format!("Failed to seek: {}", e))?;
for offset in &attr_offsets {
writer
.write_u32::<LittleEndian>(*offset)
.map_err(|e| format!("Failed to write offset: {}", e))?;
}
writer
.seek(SeekFrom::Start(afrf_end as u64))
.map_err(|e| format!("Failed to seek: {}", e))?;
let file_size = afrf_end;
writer
.seek(SeekFrom::Start(0))
.map_err(|e| format!("Failed to seek: {}", e))?;
writer
.write_all(FILEMAGIC)
.map_err(|e| format!("Failed to write magic: {}", e))?;
writer
.write_u32::<LittleEndian>(file_size)
.map_err(|e| format!("Failed to write size: {}", e))?;
writer
.write_all(MODELTYPE)
.map_err(|e| format!("Failed to write type: {}", e))?;
writer
.write_u32::<LittleEndian>(VERSION_NUMBER)
.map_err(|e| format!("Failed to write version: {}", e))?;
writer
.write_u32::<LittleEndian>(features.len() as u32)
.map_err(|e| format!("Failed to write num_features: {}", e))?;
writer
.write_u32::<LittleEndian>(labels.len() as u32)
.map_err(|e| format!("Failed to write num_labels: {}", e))?;
writer
.write_u32::<LittleEndian>(attributes.len() as u32)
.map_err(|e| format!("Failed to write num_attrs: {}", e))?;
writer
.write_u32::<LittleEndian>(off_features)
.map_err(|e| format!("Failed to write off_features: {}", e))?;
writer
.write_u32::<LittleEndian>(off_labels)
.map_err(|e| format!("Failed to write off_labels: {}", e))?;
writer
.write_u32::<LittleEndian>(off_attrs)
.map_err(|e| format!("Failed to write off_attrs: {}", e))?;
writer
.write_u32::<LittleEndian>(off_labelrefs)
.map_err(|e| format!("Failed to write off_labelrefs: {}", e))?;
writer
.write_u32::<LittleEndian>(off_attrrefs)
.map_err(|e| format!("Failed to write off_attrrefs: {}", e))?;
writer
.seek(SeekFrom::Start(file_size as u64))
.map_err(|e| format!("Failed to seek: {}", e))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_hashlittle() {
let h1 = hashlittle(b"test\0", 0);
let h2 = hashlittle(b"test\0", 0);
assert_eq!(h1, h2);
let h3 = hashlittle(b"other\0", 0);
assert_ne!(h1, h3);
}
#[test]
fn test_cqdb_writer() {
let mut cqdb = CQDBWriter::new();
cqdb.put("B", 0);
cqdb.put("I", 1);
let mut buffer = Cursor::new(Vec::new());
let size = cqdb.write(&mut buffer).unwrap();
assert!(size > 0);
let data = buffer.into_inner();
assert_eq!(&data[0..4], b"CQDB");
}
}