use crate::error::{Result, WkbError};
pub const COPY_BINARY_SIGNATURE: [u8; 11] = *b"PGCOPY\n\xff\r\n\0";
const EWKB_SRID_FLAG: u32 = 0x2000_0000;
pub struct CopyBinaryEncoder {
buf: Vec<u8>,
}
impl CopyBinaryEncoder {
pub fn new() -> Self {
let mut buf = Vec::new();
buf.extend_from_slice(©_BINARY_SIGNATURE);
buf.extend_from_slice(&0i32.to_be_bytes()); buf.extend_from_slice(&0i32.to_be_bytes()); Self { buf }
}
pub fn begin_row(&mut self, field_count: i16) {
self.buf.extend_from_slice(&field_count.to_be_bytes());
}
pub fn write_field_bytes(&mut self, bytes: &[u8]) {
self.buf
.extend_from_slice(&(bytes.len() as i32).to_be_bytes());
self.buf.extend_from_slice(bytes);
}
pub fn write_null(&mut self) {
self.buf.extend_from_slice(&(-1i32).to_be_bytes());
}
pub fn finish(mut self) -> Vec<u8> {
self.buf.extend_from_slice(&(-1i16).to_be_bytes());
self.buf
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
}
impl Default for CopyBinaryEncoder {
fn default() -> Self {
Self::new()
}
}
pub fn ewkb_from_wkb(wkb: &[u8], srid: i32) -> Result<Vec<u8>> {
if wkb.len() < 5 {
return Err(WkbError::BufferTooShort {
expected: 5,
actual: wkb.len(),
}
.into());
}
let order_byte = wkb[0];
let little_endian = match order_byte {
0x00 => false,
0x01 => true,
other => return Err(WkbError::InvalidByteOrder { byte: other }.into()),
};
let type_bytes: [u8; 4] = [wkb[1], wkb[2], wkb[3], wkb[4]];
let type_word = if little_endian {
u32::from_le_bytes(type_bytes)
} else {
u32::from_be_bytes(type_bytes)
};
if type_word & EWKB_SRID_FLAG != 0 {
return Ok(wkb.to_vec());
}
let ewkb_type = type_word | EWKB_SRID_FLAG;
let ewkb_type_bytes = if little_endian {
ewkb_type.to_le_bytes()
} else {
ewkb_type.to_be_bytes()
};
let srid_bytes = if little_endian {
srid.to_le_bytes()
} else {
srid.to_be_bytes()
};
let mut out = Vec::with_capacity(wkb.len() + 4);
out.push(order_byte);
out.extend_from_slice(&ewkb_type_bytes);
out.extend_from_slice(&srid_bytes);
out.extend_from_slice(&wkb[5..]);
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_emits_header() {
let encoder = CopyBinaryEncoder::new();
assert_eq!(encoder.len(), 19);
assert!(!encoder.is_empty());
}
#[test]
fn test_finish_minimal_stream() {
let encoder = CopyBinaryEncoder::new();
let payload = encoder.finish();
assert_eq!(payload.len(), 21);
assert_eq!(&payload[payload.len() - 2..], &[0xFF, 0xFF]);
}
#[test]
fn test_ewkb_rejects_short_buffer() {
let result = ewkb_from_wkb(&[0x01, 0x01, 0x00], 4326);
assert!(result.is_err());
}
#[test]
fn test_ewkb_rejects_bad_byte_order() {
let result = ewkb_from_wkb(&[0x02, 0x01, 0x00, 0x00, 0x00], 4326);
assert!(result.is_err());
}
#[test]
fn test_ewkb_idempotent_when_already_ewkb() {
let ewkb = [
0x01, 0x01, 0x00, 0x00, 0x20, 0xE6, 0x10, 0x00, 0x00, ];
let out = ewkb_from_wkb(&ewkb, 3857).expect("ewkb conversion failed");
assert_eq!(out, ewkb);
}
}