use std::{
io::{BufRead, Write},
num::ParseIntError,
slice::ChunksExact,
str::{self, FromStr},
};
use quick_error::ResultExt;
use crate::deser::fits::error::FitsError;
const VALUE_INDICATOR: &[u8; 2] = b"= ";
pub(super) fn write_primary_hdu<R: Write>(writer: &mut R) -> Result<(), FitsError> {
let mut header_block = [b' '; 2880];
header_block[0..30].copy_from_slice(b"SIMPLE = T");
header_block[80..110].copy_from_slice(b"BITPIX = 8");
header_block[160..190].copy_from_slice(b"NAXIS = 0");
header_block[240..270].copy_from_slice(b"EXTEND = T");
header_block[320..323].copy_from_slice(b"END");
writer.write_all(&header_block[..])?;
Ok(())
}
#[allow(dead_code)]
pub(super) fn write_uint_keyword_record(dest: &mut [u8], keyword: &[u8; 8], val: u64) {
write_keyword_record(dest, keyword, &val.to_string())
}
#[allow(dead_code)]
pub(super) fn write_str_keyword_record(dest: &mut [u8], keyword: &[u8; 8], val: &str) {
write_keyword_record(dest, keyword, &format!("'{}'", val))
}
pub(super) fn write_keyword_record(dest: &mut [u8], keyword: &[u8; 8], value_part: &str) {
debug_assert_eq!(dest.len(), 80);
dest[0..8].copy_from_slice(&keyword[..]);
dest[8..10].copy_from_slice(VALUE_INDICATOR);
let val_bytes = value_part.as_bytes();
dest[10..10 + val_bytes.len()].copy_from_slice(val_bytes);
}
pub(super) fn write_uint_mandatory_keyword_record(dest: &mut [u8], keyword: &[u8; 8], val: u64) {
debug_assert_eq!(dest.len(), 80);
dest[0..8].copy_from_slice(&keyword[..]);
dest[8..10].copy_from_slice(VALUE_INDICATOR);
let val = val.to_string();
let val_bytes = val.as_bytes();
dest[30 - val_bytes.len()..30].copy_from_slice(val_bytes);
}
pub(super) fn consume_primary_hdu<R: BufRead>(
reader: &mut R,
header_block: &mut [u8; 2880],
) -> Result<(), FitsError> {
let mut chunks_of_80 = next_36_chunks_of_80_bytes(reader, header_block)?;
check_keyword_and_val(chunks_of_80.next().unwrap(), b"SIMPLE ", b"T")?;
chunks_of_80.next().unwrap(); check_keyword_and_val(chunks_of_80.next().unwrap(), b"NAXIS ", b"0")?;
while !contains_end(&mut chunks_of_80) {
chunks_of_80 = next_36_chunks_of_80_bytes(reader, header_block)?;
}
Ok(())
}
pub(super) fn next_36_chunks_of_80_bytes<'a, R: BufRead>(
reader: &'a mut R,
header_block: &'a mut [u8; 2880],
) -> Result<ChunksExact<'a, u8>, FitsError> {
reader
.read_exact(header_block)
.map_err(FitsError::Io)
.map(|()| header_block.chunks_exact(80))
}
fn contains_end<'a, I: Iterator<Item = &'a [u8]>>(chunks_of_80: &'a mut I) -> bool {
for kw_rc in chunks_of_80 {
debug_assert_eq!(kw_rc.len(), 80);
if &kw_rc[0..4] == b"END " {
return true;
}
}
false
}
pub(super) fn check_keyword_and_val(
keyword_record: &[u8],
expected_kw: &[u8],
expected_val: &[u8],
) -> Result<(), FitsError> {
check_expected_keyword(keyword_record, expected_kw)
.and_then(|()| check_for_value_indicator(keyword_record))
.and_then(|()| check_expected_value(keyword_record, expected_val))
}
pub(super) fn check_keyword_and_parse_uint_val<T>(
keyword_record: &[u8],
expected_kw: &[u8],
) -> Result<T, FitsError>
where
T: Into<u64> + FromStr<Err = ParseIntError>,
{
check_expected_keyword(keyword_record, expected_kw)
.and_then(|()| check_for_value_indicator(keyword_record))
.and_then(|()| parse_uint_val::<T>(keyword_record))
}
pub(super) fn check_keyword_and_get_str_val<'a>(
keyword_record: &'a [u8],
expected_kw: &[u8],
) -> Result<&'a str, FitsError> {
check_expected_keyword(keyword_record, expected_kw)
.and_then(|()| check_for_value_indicator(keyword_record))
.and_then(|()| {
get_str_val_no_quote(keyword_record).map(|bytes| unsafe { str::from_utf8_unchecked(bytes) })
})
}
pub(super) fn check_expected_keyword(
keyword_record: &[u8],
expected: &[u8],
) -> Result<(), FitsError> {
debug_assert!(keyword_record.len() == 80); debug_assert!(expected.len() <= 8); if &keyword_record[..expected.len()] == expected {
Ok(())
} else {
let expected = String::from(unsafe { str::from_utf8_unchecked(expected) }.trim_end());
let actual = String::from_utf8_lossy(&keyword_record[..expected.len()])
.trim_end()
.to_string();
Err(FitsError::UnexpectedKeyword(expected, actual))
}
}
pub(super) fn check_for_value_indicator(keyword_record: &[u8]) -> Result<(), FitsError> {
debug_assert!(keyword_record.len() == 80); if get_value_indicator(keyword_record) == VALUE_INDICATOR {
Ok(())
} else {
let keyword_record = String::from_utf8_lossy(keyword_record)
.trim_end()
.to_string();
Err(FitsError::ValueIndicatorNotFound(keyword_record))
}
}
pub(super) fn get_keyword(keyword_record: &[u8]) -> &[u8] {
&keyword_record[..8]
}
pub(super) fn get_value_indicator(keyword_record: &[u8]) -> &[u8] {
&keyword_record[8..10]
}
pub(super) fn get_value(keyword_record: &[u8]) -> &[u8] {
&keyword_record[10..]
}
pub(super) fn get_left_trimmed_value(keyword_record: &[u8]) -> &[u8] {
get_value(keyword_record).trim_ascii_start()
}
pub(super) fn check_expected_value(
keyword_record: &[u8],
expected: &[u8],
) -> Result<(), FitsError> {
debug_assert!(keyword_record.len() == 80); let src = get_value(keyword_record);
let lt_src = src.trim_ascii_start();
if lt_src.len() >= expected.len() && <_src[..expected.len()] == expected {
Ok(())
} else {
let keyword = String::from_utf8_lossy(&src[0..8]).trim_end().to_string();
let expected = String::from(unsafe { str::from_utf8_unchecked(expected) });
let actual = String::from_utf8_lossy(<_src[..expected.len()]).to_string();
Err(FitsError::UnexpectedValue(keyword, expected, actual))
}
}
pub(super) fn get_str_val_no_quote(keyword_record: &[u8]) -> Result<&[u8], FitsError> {
let mut it = get_left_trimmed_value(keyword_record).split_inclusive(|c| *c == b'\'');
if let Some([b'\'']) = it.next() {
if let Some([subslice @ .., b'\'']) = it.next() {
return Ok(subslice.trim_ascii_end());
}
}
let keyword_record = String::from_utf8_lossy(keyword_record)
.trim_end()
.to_string();
Err(FitsError::StringValueNotFound(keyword_record))
}
pub(super) fn parse_uint_val<T>(keyword_record: &[u8]) -> Result<T, FitsError>
where
T: Into<u64> + FromStr<Err = ParseIntError>,
{
let src = get_left_trimmed_value(keyword_record);
let to = index_of_last_digit(src);
if to == 0 {
let keyword_record = String::from_utf8_lossy(keyword_record)
.trim_end()
.to_string();
Err(FitsError::UintValueNotFound(keyword_record))
} else {
let str_val = unsafe { str::from_utf8_unchecked(&src[..to]) };
let res = str_val.parse::<T>().context(str_val.to_string())?;
Ok(res)
}
}
pub(super) fn index_of_last_digit(src: &[u8]) -> usize {
for (i, c) in src.iter().enumerate() {
if !c.is_ascii_digit() {
return i;
}
}
src.len()
}
#[cfg(test)]
mod tests {
use crate::deser::fits::common::write_primary_hdu;
#[test]
fn test_write_primary_hdu() {
let mut buf: Vec<u8> = Default::default();
write_primary_hdu(&mut buf).unwrap();
assert_eq!(buf.len(), 2880);
}
}