use std::ffi::{c_char, c_double, c_uchar, CStr};
use std::fmt;
use std::ptr;
use std::slice::from_raw_parts;
use std::str;
use bitflags::bitflags;
use serde_json::Value;
use raw::MS3Record;
use crate::error::{check, check_nst};
use crate::{raw, util, MSControlFlags, MSError, MSResult, MSSubSeconds, MSTimeFormat};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct RecordDetection {
pub format_version: u8,
pub rec_len: Option<u64>,
}
pub fn detect<T: AsRef<[u8]>>(buf: T) -> MSResult<RecordDetection> {
let mut format_version: u8 = 0;
let format_version_ptr = (&mut format_version) as *mut _;
let buf = buf.as_ref();
let rec_len = unsafe {
check(raw::ms3_detect(
buf.as_ptr() as *const _,
buf.len() as _,
format_version_ptr,
))
}?;
let rec_len = if rec_len == 0 {
None
} else {
Some(rec_len as u64)
};
Ok(RecordDetection {
format_version,
rec_len,
})
}
#[repr(u8)]
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum MSSampleType {
Unknown = 0, Text = 116, Integer32 = 105, Float32 = 102, Float64 = 100, }
impl MSSampleType {
pub fn from_char(ch: u8) -> Self {
match ch {
116 => Self::Text, 105 => Self::Integer32, 102 => Self::Float32, 100 => Self::Float64, _ => Self::Unknown,
}
}
}
#[repr(i16)]
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum MSDataEncoding {
Text = raw::DE_TEXT as i16,
Integer16 = raw::DE_INT16 as i16,
Integer32 = raw::DE_INT32 as i16,
Float32 = raw::DE_FLOAT32 as i16,
Float64 = raw::DE_FLOAT64 as i16,
Steim1 = raw::DE_STEIM1 as i16,
Steim2 = raw::DE_STEIM2 as i16,
GeoScope24 = raw::DE_GEOSCOPE24 as i16,
GeoScope163 = raw::DE_GEOSCOPE163 as i16,
GeoScope164 = raw::DE_GEOSCOPE164 as i16,
CDSN = raw::DE_CDSN as i16,
SRO = raw::DE_SRO as i16,
DWWSSN = raw::DE_DWWSSN as i16,
}
impl MSDataEncoding {
pub fn from_char(ch: u8) -> MSResult<Self> {
match ch as u32 {
raw::DE_TEXT => Ok(Self::Text),
raw::DE_INT16 => Ok(Self::Integer16),
raw::DE_INT32 => Ok(Self::Integer32),
raw::DE_FLOAT32 => Ok(Self::Float32),
raw::DE_FLOAT64 => Ok(Self::Float64),
raw::DE_STEIM1 => Ok(Self::Steim1),
raw::DE_STEIM2 => Ok(Self::Steim2),
raw::DE_GEOSCOPE24 => Ok(Self::GeoScope24),
raw::DE_GEOSCOPE163 => Ok(Self::GeoScope163),
raw::DE_GEOSCOPE164 => Ok(Self::GeoScope164),
raw::DE_CDSN => Ok(Self::CDSN),
raw::DE_SRO => Ok(Self::SRO),
raw::DE_DWWSSN => Ok(Self::DWWSSN),
other => Err(MSError::from_str(&format!(
"invalid data encoding type: {}",
other
))),
}
}
}
impl fmt::Display for MSDataEncoding {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
unsafe {
let encoding = CStr::from_ptr(raw::ms_encodingstr(
(*self as u8).try_into().map_err(|_| fmt::Error).unwrap(),
))
.to_string_lossy();
write!(f, "{}", encoding)
}
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MSBitFieldFlags: u8 {
const CALIBRATION_SIGNAL_PRESENT = 0b00000001;
const TIME_TAG_QUESTIONABLE = 0b00000010;
const CLOCK_LOCKED = 0b00000100;
const _ = !0;
}
}
#[derive(Debug)]
pub struct MSRecord(*mut MS3Record);
impl MSRecord {
fn ptr(&self) -> MS3Record {
unsafe { *self.0 }
}
pub(crate) fn get_raw(&self) -> *const MS3Record {
self.0
}
#[allow(dead_code)]
pub(crate) unsafe fn get_raw_mut(&mut self) -> *mut MS3Record {
self.0
}
pub fn parse(buf: &[u8], flags: MSControlFlags) -> MSResult<Self> {
let msr: *mut MS3Record = ptr::null_mut();
let mut msr = unsafe { raw::msr3_init(msr) };
if msr.is_null() {
return Err(MSError::from_str("failed to initialize record"));
}
unsafe {
let buf = &*(buf as *const [_] as *const [c_char]);
check(raw::msr3_parse(
buf.as_ptr(),
buf.len() as _,
(&mut msr) as *mut *mut MS3Record,
flags.bits(),
0,
))?
};
Ok(Self(msr))
}
pub unsafe fn from_raw(ptr: *mut MS3Record) -> Self {
Self(ptr)
}
pub fn into_raw(mut self) -> *mut MS3Record {
let rv = self.0;
self.0 = ptr::null_mut();
rv
}
pub fn unpack_data(&mut self) -> MSResult<i64> {
if !self.ptr().datasamples.is_null() {
return Ok(self.num_samples());
}
unsafe { check(raw::msr3_unpack_data(self.0, 0) as _) }
}
pub fn sid(&self) -> MSResult<String> {
let nslc = util::NetStaLocCha::from_sid(&self.ptr().sid)?;
Ok(nslc.to_string())
}
pub fn sid_lossy(&self) -> String {
util::to_string(&(self.ptr().sid))
}
pub fn network(&self) -> MSResult<String> {
let nslc = util::NetStaLocCha::from_sid(&self.ptr().sid)?;
Ok(nslc.net)
}
pub fn station(&self) -> MSResult<String> {
let nslc = util::NetStaLocCha::from_sid(&self.ptr().sid)?;
Ok(nslc.sta)
}
pub fn location(&self) -> MSResult<String> {
let nslc = util::NetStaLocCha::from_sid(&self.ptr().sid)?;
Ok(nslc.loc)
}
pub fn channel(&self) -> MSResult<String> {
let nslc = util::NetStaLocCha::from_sid(&self.ptr().sid)?;
Ok(nslc.cha)
}
pub fn raw(&self) -> Option<&[c_uchar]> {
if self.ptr().record.is_null() || self.ptr().reclen == 0 {
return None;
}
let ret = unsafe {
from_raw_parts(
self.ptr().record as *mut c_uchar,
self.ptr().reclen as usize,
)
};
Some(ret)
}
pub fn format_version(&self) -> u8 {
self.ptr().formatversion
}
pub fn flags(&self) -> MSBitFieldFlags {
MSBitFieldFlags::from_bits_retain(self.ptr().flags)
}
pub fn start_time(&self) -> MSResult<time::OffsetDateTime> {
util::nstime_to_time(self.ptr().starttime)
}
pub fn end_time(&self) -> MSResult<time::OffsetDateTime> {
unsafe { util::nstime_to_time(check_nst(raw::msr3_endtime(self.0))?) }
}
pub fn sample_rate_hz(&self) -> c_double {
unsafe { raw::msr3_sampratehz(&mut self.ptr() as *mut MS3Record) }
}
pub fn encoding(&self) -> MSResult<MSDataEncoding> {
MSDataEncoding::from_char(self.ptr().encoding as _)
}
pub fn pub_version(&self) -> u8 {
self.ptr().pubversion
}
pub fn sample_cnt(&self) -> i64 {
self.ptr().samplecnt as _
}
pub fn crc(&self) -> u32 {
self.ptr().crc
}
pub fn data_length(&self) -> u32 {
self.ptr().datalength
}
pub fn extra_headers(&self) -> Option<&[c_uchar]> {
if self.ptr().extra.is_null() || self.ptr().extralength == 0 {
return None;
}
let ret = unsafe {
from_raw_parts(
self.ptr().extra as *const c_uchar,
self.ptr().extralength as usize,
)
};
Some(ret)
}
pub fn data_samples<T>(&self) -> Option<&[T]> {
if self.ptr().datasamples.is_null() {
return None;
}
Some(unsafe {
from_raw_parts(
self.ptr().datasamples as *mut T,
self.ptr().samplecnt as usize,
)
})
}
pub fn data_size(&self) -> u64 {
self.ptr().datasize
}
pub fn num_samples(&self) -> i64 {
self.ptr().numsamples as _
}
pub fn sample_type(&self) -> MSSampleType {
MSSampleType::from_char(self.ptr().sampletype as _)
}
pub fn try_clone(&self) -> MSResult<Self> {
let rv = unsafe { raw::msr3_duplicate(self.0, true as _) };
if rv.is_null() {
return Err(MSError::from_str("failed to duplicate"));
}
Ok(Self(rv))
}
pub fn display(&self, detail: i8) -> RecordDisplay<'_> {
RecordDisplay { rec: self, detail }
}
}
impl fmt::Display for MSRecord {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let v = self.ptr();
write!(
f,
"{}, {}, {}, {} samples, {} Hz, {}",
self.sid_lossy(),
v.pubversion,
v.reclen,
v.samplecnt,
self.sample_rate_hz(),
util::nstime_to_string(
v.starttime,
MSTimeFormat::IsoMonthDayDoyZ,
MSSubSeconds::NanoMicro
)
.unwrap_or("invalid".to_string())
)
}
}
impl AsRef<[u8]> for MSRecord {
fn as_ref(&self) -> &[u8] {
unsafe {
from_raw_parts(
self.ptr().record as *mut c_uchar,
self.ptr().reclen as usize,
)
}
}
}
impl Drop for MSRecord {
fn drop(&mut self) {
unsafe {
raw::ms3_readmsr(
(&mut self.0) as *mut *mut MS3Record,
ptr::null(),
MSControlFlags::empty().bits(),
0,
);
}
}
}
pub struct RecordDisplay<'a> {
rec: &'a MSRecord,
detail: i8,
}
impl fmt::Debug for RecordDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.rec, f)
}
}
impl fmt::Display for RecordDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.detail > 0 {
writeln!(
f,
"{}, version {}, {} bytes (format: {})",
self.rec.sid_lossy(),
self.rec.pub_version(),
unsafe { (*self.rec.get_raw()).reclen },
self.rec.format_version()
)?;
let start_time = unsafe { (*self.rec.get_raw()).starttime };
let start_time = util::nstime_to_string(
start_time as _,
MSTimeFormat::IsoMonthDayDoyZ,
MSSubSeconds::NanoMicro,
)
.unwrap_or("invalid".to_string());
writeln!(f, " start time: {}", start_time)?;
writeln!(f, " number of samples: {}", self.rec.sample_cnt())?;
writeln!(f, " sample rate (Hz): {}", self.rec.sample_rate_hz())?;
let flags = self.rec.flags();
if self.detail > 1 {
writeln!(f, " flags: [{:08b}] 8 bits", flags.bits())?;
if !(flags & MSBitFieldFlags::CALIBRATION_SIGNAL_PRESENT).is_empty() {
writeln!(
f,
" [Bit 0] Calibration signals present"
)?;
}
if !(flags & MSBitFieldFlags::TIME_TAG_QUESTIONABLE).is_empty() {
writeln!(
f,
" [Bit 1] Time tag is questionable"
)?;
}
if !(flags & MSBitFieldFlags::CLOCK_LOCKED).is_empty() {
writeln!(f, " [Bit 2] Clock locked")?;
}
let bits = flags.bits();
if bits & (1 << 3) != 0 {
writeln!(f, " [Bit 3] Undefined bit set")?;
}
if bits & (1 << 4) != 0 {
writeln!(f, " [Bit 4] Undefined bit set")?;
}
if bits & (1 << 5) != 0 {
writeln!(f, " [Bit 5] Undefined bit set")?;
}
if bits & (1 << 6) != 0 {
writeln!(f, " [Bit 6] Undefined bit set")?;
}
if bits & (1 << 7) != 0 {
writeln!(f, " [Bit 7] Undefined bit set")?;
}
}
writeln!(f, " CRC: {:X}", self.rec.crc())?;
let extra_headers = self.rec.extra_headers();
let extra_headers_len = if let Some(extra_headers) = extra_headers {
extra_headers.len()
} else {
0
};
writeln!(f, " extra header length: {} bytes", extra_headers_len)?;
writeln!(
f,
" data payload length: {} bytes",
self.rec.data_length()
)?;
let encoding = self.rec.encoding().map_err(|_| fmt::Error)?;
writeln!(
f,
" payload encoding: {} (val: {})",
encoding, encoding as u8
)?;
if self.detail > 1 {
if let Some(extra_headers) = extra_headers {
writeln!(f, " extra headers:")?;
let json_str = as_json_pretty(extra_headers).map_err(|_| fmt::Error)?;
for line in json_str.lines() {
writeln!(f, " {}", line)?;
}
}
}
Ok(())
} else {
writeln!(f, "{}", self.rec)
}
}
}
fn as_json_pretty(slice: &[u8]) -> MSResult<String> {
str::from_utf8(slice)
.ok()
.and_then(|json_str| serde_json::from_str(json_str).ok())
.and_then(|v: Value| serde_json::to_string_pretty(&v).ok())
.ok_or_else(|| MSError::from_str("failed to pretty format JSON"))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test;
use std::fs::File;
use std::io::{BufReader, Read};
use pretty_assertions::assert_eq;
use time::format_description::well_known::Iso8601;
#[test]
fn test_detect() {
let test_data = vec![
(
"reference-testdata-text.mseed2",
RecordDetection {
format_version: 2,
rec_len: Some(512),
},
),
(
"reference-testdata-text.mseed3",
RecordDetection {
format_version: 3,
rec_len: Some(294),
},
),
(
"reference-testdata-steim2.mseed2",
RecordDetection {
format_version: 2,
rec_len: Some(512),
},
),
(
"reference-testdata-steim2.mseed3",
RecordDetection {
format_version: 3,
rec_len: Some(507),
},
),
(
"testdata-detection.record.mseed2",
RecordDetection {
format_version: 2,
rec_len: Some(512),
},
),
(
"testdata-no-blockette1000-steim1.mseed2",
RecordDetection {
format_version: 2,
rec_len: Some(4096),
},
),
];
for (f, expected) in &test_data {
let mut p = test::test_data_base_dir();
assert!(p.is_dir());
p.push(f);
let file = File::open(p).unwrap();
let mut reader = BufReader::new(file);
let mut buf = Vec::new();
reader.read_to_end(&mut buf).unwrap();
assert_eq!(&detect(buf).unwrap(), expected);
}
}
#[test]
fn test_parse_signal_mseed3() {
let mut p = test::test_data_base_dir();
assert!(p.is_dir());
p.push("testdata-3channel-signal.mseed3");
let ifs = File::open(p).unwrap();
let mut reader = BufReader::new(ifs);
let mut buf: Vec<u8> = vec![];
reader.read_to_end(&mut buf).unwrap();
let msr = MSRecord::parse(&buf, MSControlFlags::MSF_UNPACKDATA).unwrap();
assert_eq!(msr.network().unwrap(), "IU");
assert_eq!(msr.station().unwrap(), "COLA");
assert_eq!(msr.location().unwrap(), "00");
assert_eq!(msr.channel().unwrap(), "LH1");
assert_eq!(&msr.sid().unwrap(), "FDSN:IU_COLA_00_L_H_1");
assert_eq!(msr.format_version(), 3);
assert_eq!(
msr.start_time().unwrap().format(&Iso8601::DEFAULT).unwrap(),
"2010-02-27T06:50:00.069539000Z"
);
assert_eq!(
msr.end_time().unwrap().format(&Iso8601::DEFAULT).unwrap(),
"2010-02-27T06:52:14.069539000Z"
);
assert_eq!(msr.sample_rate_hz(), 1.0);
assert_eq!(msr.encoding().unwrap(), MSDataEncoding::Steim2);
assert_eq!(msr.pub_version(), 4);
assert_eq!(msr.sample_cnt(), 135);
assert_eq!(msr.crc(), 0x4F3EAB65);
{
let mut buf: Vec<u8> = vec![];
buf.extend_from_slice(msr.extra_headers().unwrap());
assert_eq!(
String::from_utf8(buf).unwrap(),
"{\"FDSN\":{\"Time\":{\"Quality\":100}}}"
);
}
assert_eq!(msr.data_length(), 384);
assert_eq!(msr.num_samples(), 135);
assert_eq!(msr.sample_type(), MSSampleType::Integer32);
{
let mut buf: Vec<i32> = vec![];
buf.extend_from_slice(msr.data_samples().unwrap());
assert_eq!(buf.len(), 135);
assert_eq!(buf[0], -502676);
assert_eq!(buf[1], -504105);
assert_eq!(buf[2], -507491);
assert_eq!(buf[3], -506991);
assert_eq!(buf[131], -505212);
assert_eq!(buf[132], -499533);
assert_eq!(buf[133], -495590);
assert_eq!(buf[134], -496168);
}
}
#[test]
fn test_parse_signal_mseed2() {
let mut p = test::test_data_base_dir();
assert!(p.is_dir());
p.push("testdata-3channel-signal.mseed2");
let ifs = File::open(p).unwrap();
let mut reader = BufReader::new(ifs);
let mut buf: Vec<u8> = vec![];
reader.read_to_end(&mut buf).unwrap();
let msr = MSRecord::parse(&buf, MSControlFlags::MSF_UNPACKDATA).unwrap();
assert_eq!(msr.network().unwrap(), "IU");
assert_eq!(msr.station().unwrap(), "COLA");
assert_eq!(msr.location().unwrap(), "00");
assert_eq!(msr.channel().unwrap(), "LH1");
assert_eq!(&msr.sid().unwrap(), "FDSN:IU_COLA_00_L_H_1");
assert_eq!(msr.format_version(), 2);
assert_eq!(
msr.start_time().unwrap().format(&Iso8601::DEFAULT).unwrap(),
"2010-02-27T06:50:00.069539000Z"
);
assert_eq!(
msr.end_time().unwrap().format(&Iso8601::DEFAULT).unwrap(),
"2010-02-27T06:52:14.069539000Z"
);
assert_eq!(msr.sample_rate_hz(), 1.0);
assert_eq!(msr.encoding().unwrap(), MSDataEncoding::Steim2);
assert_eq!(msr.pub_version(), 4);
assert_eq!(msr.sample_cnt(), 135);
assert_eq!(msr.crc(), 0);
{
let mut buf: Vec<u8> = vec![];
buf.extend_from_slice(msr.extra_headers().unwrap());
assert_eq!(
String::from_utf8(buf).unwrap(),
"{\"FDSN\":{\"Time\":{\"Quality\":100}}}"
);
}
assert_eq!(msr.data_length(), 448);
assert_eq!(msr.num_samples(), 135);
assert_eq!(msr.sample_type(), MSSampleType::Integer32);
{
let mut buf: Vec<i32> = vec![];
buf.extend_from_slice(msr.data_samples().unwrap());
assert_eq!(buf.len(), 135);
assert_eq!(buf[0], -502676);
assert_eq!(buf[1], -504105);
assert_eq!(buf[2], -507491);
assert_eq!(buf[3], -506991);
assert_eq!(buf[131], -505212);
assert_eq!(buf[132], -499533);
assert_eq!(buf[133], -495590);
assert_eq!(buf[134], -496168);
}
}
}