#![allow(dead_code)]
use crate::ffi::apple::{
kDNSServiceErr_NoError, TXTRecordContainsKey, TXTRecordCreate, TXTRecordDeallocate,
TXTRecordGetBytesPtr, TXTRecordGetCount, TXTRecordGetLength, TXTRecordGetValuePtr,
TXTRecordRef, TXTRecordRemoveValue, TXTRecordSetValue,
};
use crate::os::apple::register::RegistrationError;
use std::collections::HashMap;
use std::ffi::{c_void, CString};
use std::mem;
use std::ptr;
use std::slice;
pub struct TXTRecord {
raw: TXTRecordRef,
}
impl Default for TXTRecord {
fn default() -> Self {
TXTRecord::new()
}
}
impl From<HashMap<String, String>> for TXTRecord {
fn from(hash: HashMap<String, String>) -> Self {
let mut txt = TXTRecord::new();
for (key, value) in hash {
if let Err(e) = txt.insert(&key, Some(&value)) {
error!("Error inserting {}={} into TXTRecord: {:?}", key, value, e);
}
}
txt
}
}
impl TXTRecord {
pub fn new() -> TXTRecord {
unsafe {
let mut record = TXTRecord { raw: mem::zeroed() };
TXTRecordCreate(&mut record.raw, 0, ptr::null_mut());
record
}
}
pub fn insert<V>(&mut self, key: &str, value: Option<V>) -> Result<(), RegistrationError>
where
V: AsRef<[u8]>,
{
let value = value.as_ref().map(|x| x.as_ref());
let key = CString::new(key).or(Err(RegistrationError::InvalidString))?;
let value_size = value.map_or(0, |x| x.len().min(u8::max_value() as usize) as u8);
let result = unsafe {
TXTRecordSetValue(
&mut self.raw,
key.as_ptr(),
value_size,
value.map_or(ptr::null(), |x| x.as_ptr() as *const c_void),
)
};
if result == kDNSServiceErr_NoError {
return Ok(());
}
Err(RegistrationError::ServiceError(result))
}
pub fn remove(&mut self, key: &str) {
let key = match CString::new(key) {
Ok(key) => key,
Err(_) => {
return;
}
};
unsafe {
TXTRecordRemoveValue(&mut self.raw, key.as_ptr());
}
}
pub fn contains_key(&self, key: &str) -> bool {
let key = match CString::new(key) {
Ok(key) => key,
Err(_) => {
return false;
}
};
unsafe {
TXTRecordContainsKey(self.raw_bytes_len(), self.raw_bytes_ptr(), key.as_ptr()) != 0
}
}
pub fn get(&self, key: &str) -> Option<&[u8]> {
let key = match CString::new(key) {
Ok(key) => key,
Err(_) => {
return None;
}
};
let mut value_len = 0u8;
unsafe {
let ptr = TXTRecordGetValuePtr(
self.raw_bytes_len(),
self.raw_bytes_ptr(),
key.as_ptr(),
&mut value_len,
);
if ptr.is_null() {
None
} else {
Some(slice::from_raw_parts(ptr as *const u8, value_len as usize))
}
}
}
pub fn len(&self) -> u16 {
unsafe { TXTRecordGetCount(self.raw_bytes_len(), self.raw_bytes_ptr()) }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn raw_bytes(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self.raw_bytes_ptr() as *const u8,
self.raw_bytes_len() as usize,
)
}
}
pub fn raw_bytes_len(&self) -> u16 {
unsafe { TXTRecordGetLength(&self.raw) }
}
pub fn raw_bytes_ptr(&self) -> *const c_void {
unsafe { TXTRecordGetBytesPtr(&self.raw) }
}
}
impl Drop for TXTRecord {
fn drop(&mut self) {
unsafe {
TXTRecordDeallocate(&mut self.raw);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn txt_creation() {
let mut record = TXTRecord::new();
assert_eq!(record.insert("test", Some("value1")), Ok(()));
assert_eq!(record.len(), 1);
assert_eq!(record.raw_bytes_len(), 12);
assert_eq!(record.raw_bytes(), b"\x0Btest=value1");
assert!(record.contains_key("test"));
assert_eq!(record.get("test"), Some(&b"value1"[..]));
assert_eq!(record.insert("test2", Some([1u8, 2, 3])), Ok(()));
assert_eq!(record.len(), 2);
assert_eq!(record.raw_bytes_len(), 22);
assert_eq!(record.raw_bytes(), b"\x0Btest=value1\x09test2=\x01\x02\x03");
assert!(record.contains_key("test2"));
assert_eq!(record.get("test2"), Some(&[1u8, 2, 3][..]));
assert_eq!(record.insert("test", None::<&str>), Ok(()));
assert_eq!(record.len(), 2);
assert_eq!(record.raw_bytes_len(), 15);
assert_eq!(record.raw_bytes(), b"\x09test2=\x01\x02\x03\x04test");
assert_eq!(record.get("test"), None);
}
}