diff --git a/core/Cargo.toml b/core/Cargo.toml index 9fae854d..d4acf7d2 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -15,7 +15,7 @@ repository = "Enet4/dicom-rs" [dependencies] chrono = "0.4.6" itertools = "0.9.0" -quick-error = "1.2.3" -smallvec = "1.0.0" -safe-transmute = "0.11.0" num-traits = "0.2.12" +safe-transmute = "0.11.0" +smallvec = "1.0.0" +snafu = "0.6.8" diff --git a/core/src/dictionary/mod.rs b/core/src/dictionary/mod.rs index a5e32950..203b3388 100644 --- a/core/src/dictionary/mod.rs +++ b/core/src/dictionary/mod.rs @@ -7,6 +7,7 @@ pub mod stub; use crate::header::{Tag, VR}; use std::fmt::Debug; use std::str::FromStr; +use snafu::{Backtrace, OptionExt, ResultExt, Snafu, ensure}; /// Specification of a range of tags pertaining to an attribute. /// Very often, the dictionary of attributes indicates a unique `(group,elem)` @@ -35,9 +36,45 @@ impl TagRange { } } + /// An error returned when parsing an invalid tag range. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct TagRangeParseError(&'static str); +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum TagRangeParseError { + #[snafu(display("Not enough tag components, expected tag (group, element)"))] + MissingTag { + backtrace: Backtrace, + }, + #[snafu(display("Not enough tag components, expected tag element"))] + MissingTagElement { + backtrace: Backtrace, + }, + #[snafu(display("tag component `group` has an invalid length: got {} but must be 4", got))] + InvalidGroupLength { + got: usize, + backtrace: Backtrace, + }, + #[snafu(display("tag component `element` has an invalid length: got {} but must be 4", got))] + InvalidElementLength { + got: usize, + backtrace: Backtrace, + }, + #[snafu(display("unsupported tag range"))] + UnsupportedTagRange { + backtrace: Backtrace, + }, + #[snafu(display("invalid tag component `group`"))] + InvalidTagGroup { + backtrace: Backtrace, + source: std::num::ParseIntError, + }, + #[snafu(display("invalid tag component `element`"))] + InvalidTagElement { + backtrace: Backtrace, + source: std::num::ParseIntError, + }, +} + impl FromStr for TagRange { type Err = TagRangeParseError; @@ -47,49 +84,37 @@ impl FromStr for TagRange { s = &s[1..s.len() - 1]; } let mut parts = s.split(','); - let group = parts.next().ok_or(TagRangeParseError( - "not enough tag components, expected `group,element`", - ))?; - let elem = parts.next().ok_or(TagRangeParseError( - "not enough tag components, expected `element`", - ))?; - if group.len() != 4 { - return Err(TagRangeParseError( - "tag component `group` has an invalid length, must be 4", - )); - } - if elem.len() != 4 { - return Err(TagRangeParseError( - "tag component `element` has an invalid length, must be 4", - )); - } + let group = parts.next().context(MissingTag)?; + let elem = parts.next().context(MissingTagElement)?; + ensure!(group.len() == 4, InvalidGroupLength { got: group.len() }); + ensure!(elem.len() == 4, InvalidElementLength { got: elem.len() }); match (&group.as_bytes()[2..], &elem.as_bytes()[2..]) { - (b"xx", b"xx") => Err(TagRangeParseError("unsupported tag range")), + (b"xx", b"xx") => UnsupportedTagRange.fail(), (b"xx", _) => { // Group100 let group = u16::from_str_radix(&group[..2], 16) - .map_err(|_e| TagRangeParseError("Invalid component `group`"))? + .context(InvalidTagGroup)? << 8; let elem = u16::from_str_radix(elem, 16) - .map_err(|_e| TagRangeParseError("Invalid component `element`"))?; + .context(InvalidTagElement)?; Ok(TagRange::Group100(Tag(group, elem))) } (_, b"xx") => { // Element100 let group = u16::from_str_radix(group, 16) - .map_err(|_e| TagRangeParseError("Invalid component `group`"))?; + .context(InvalidTagGroup)?; let elem = u16::from_str_radix(&elem[..2], 16) - .map_err(|_e| TagRangeParseError("Invalid component `element`"))? + .context(InvalidTagElement)? << 8; Ok(TagRange::Element100(Tag(group, elem))) } (_, _) => { // single element let group = u16::from_str_radix(group, 16) - .map_err(|_e| TagRangeParseError("Invalid component `group`"))?; + .context(InvalidTagGroup)?; let elem = u16::from_str_radix(elem, 16) - .map_err(|_e| TagRangeParseError("Invalid component `element`"))?; + .context(InvalidTagElement)?; Ok(TagRange::Single(Tag(group, elem))) } } diff --git a/core/src/error.rs b/core/src/error.rs deleted file mode 100644 index 2e6f6dc0..00000000 --- a/core/src/error.rs +++ /dev/null @@ -1,162 +0,0 @@ -//! This module aggregates errors that may emerge from the library. -use crate::value::ValueType; -use crate::Tag; -use quick_error::quick_error; -use std::fmt; -use std::num::{ParseFloatError, ParseIntError}; -use std::result; - -quick_error! { - /// The main data type for errors in the library. - #[derive(Debug)] - pub enum Error { - /// Raised when the obtained data element was not the one expected. - UnexpectedTag(tag: Tag) { - display("Unexpected DICOM tag {}", tag) - } - /// Raised when the obtained length is inconsistent. - UnexpectedDataValueLength { - display("Inconsistent data value length in data element") - } - /// Error related to an invalid value read. - ReadValue(err: InvalidValueReadError) { - display("Invalid value read: {}", err) - from() - } - /// A failed attempt to cast a value to an inappropriate format. - CastValue(err: CastValueError) { - display("Failed value cast: {}", err) - from() - } - /// A failed attempt to convert a value to an inappropriate format. - ConvertValue(err: ConvertValueError) { - display("Failed value conversion: {}", err) - from() - } - } -} - -/// Type alias for a result from this library. -pub type Result = result::Result; - -quick_error! { - /** Triggered when a value parsing attempt fails. - */ - #[derive(Debug, PartialEq, Eq, Clone)] - pub enum InvalidValueReadError { - /// The value cannot be read as a primitive value. - NonPrimitiveType { - display("attempted to retrieve complex value as primitive") - } - /// The value's effective length cannot be resolved. - UnresolvedValueLength { - display("value length could not be resolved") - } - /// The value does not have the expected format. - InvalidToken(got: u8, expected: &'static str) { - display("invalid token: expected {} but got {:?}", expected, got) - } - /// The value does not have the expected length. - InvalidLength(got: usize, expected: &'static str) { - display("invalid length: expected {} but got {}", expected, got) - } - /// Invalid date or time component. - ParseDateTime(got: u32, expected: &'static str) { - display("invalid date/time component: expected {} but got {}", expected, got) - } - /// Invalid or ambiguous combination of date with time. - DateTimeZone { - display("Invalid or ambiguous combination of date with time") - } - /// chrono error when parsing a date or time. - Chrono(err: chrono::ParseError) { - display("failed to parse date/time: {}", err) - from() - } - /// The value cannot be parsed to a floating point number. - ParseFloat(err: ParseFloatError) { - display("Failed to parse text value as a floating point number") - from() - } - /// The value cannot be parsed to an integer. - ParseInteger(err: ParseIntError) { - display("Failed to parse text value as an integer") - from() - } - /// An attempt of reading more than the number of bytes in the length attribute was made. - UnexpectedEndOfElement { - display("Unexpected end of element") - } - /// The value cannot be converted to the target type requested. - NarrowConvert(original: String) { - display("Cannot convert `{}` to the target type requested", original) - } - } -} - -/// An error type for an attempt of accessing a value -/// in one internal representation as another. -/// -/// This error is raised whenever it is not possible to retrieve the requested -/// value, either because the inner representation is not compatible with the -/// requested value type, or a conversion would be required. In other words, -/// if a reference to the inner value cannot be obtained with -/// the requested target type (for example, retrieving a date from a string), -/// an error of this type is returned. -/// -/// If such a conversion is acceptable, please use conversion methods instead: -/// `to_date` instead of `date`, `to_str` instead of `string`, and so on. -/// The error type would then be [`ConvertValueError`]. -/// -/// [`ConvertValueError`]: ./struct.ConvertValueError.html -#[derive(Debug, Clone, PartialEq)] -pub struct CastValueError { - /// The value format requested - pub requested: &'static str, - /// The value's actual representation - pub got: ValueType, -} - -impl fmt::Display for CastValueError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "bad value cast: requested {} but value is {:?}", - self.requested, self.got - ) - } -} - -impl ::std::error::Error for CastValueError {} - -/// An error type for a failed attempt at converting a value -/// into another representation. -#[derive(Debug, Clone, PartialEq)] -pub struct ConvertValueError { - /// The value format requested - pub requested: &'static str, - /// The value's original representation - pub original: ValueType, - /// The reason why the conversion was unsuccessful, - /// or none if a conversion from the given original representation - /// is not possible - pub cause: Option, -} - -impl fmt::Display for ConvertValueError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "could not convert {:?} to a {}: ", - self.original, self.requested - )?; - if let Some(cause) = &self.cause { - write!(f, "{}", cause)?; - } else { - write!(f, "conversion not possible")?; - } - Ok(()) - } -} - -impl std::error::Error for ConvertValueError {} diff --git a/core/src/header.rs b/core/src/header.rs index 668ace64..674eb7ff 100644 --- a/core/src/header.rs +++ b/core/src/header.rs @@ -2,12 +2,37 @@ //! It comprises a variety of basic data types, such as the DICOM attribute tag, the //! element header, and element composite types. -use crate::error::{Error, Result}; use crate::value::{PrimitiveValue, Value}; use std::borrow::Cow; use std::cmp::Ordering; use std::fmt; use std::str::{from_utf8, FromStr}; +use snafu::{Backtrace, Snafu}; + +/// Error type for issues constructing a sequence item header. +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum SequenceItemHeaderError { + /// Unexpected header tag. + /// Only Item (0xFFFE, 0xE000), + /// Item Delimiter (0xFFFE, 0xE00D), + /// or Sequence Delimiter (0xFFFE, 0xE0DD) + /// are admitted. + #[snafu(display("Unexpected tag {}", tag))] + UnexpectedTag { + tag: Tag, + backtrace: Backtrace, + }, + /// Unexpected delimiter value length. + /// Must be zero for item delimiters. + #[snafu(display("Unexpected delimiter length {}", len))] + UnexpectedDelimiterLength { + len: Length, + backtrace: Backtrace, + } +} + +type Result = std::result::Result; /// Trait for any DICOM entity (element or item) which may have a length. pub trait HasLength { @@ -225,8 +250,8 @@ where } /// Retrieve the element's value as a single string. - pub fn to_str(&self) -> Result> { - self.value.to_str().map_err(From::from) + pub fn to_str(&self) -> Result, crate::value::CastValueError> { + self.value.to_str() } } @@ -346,7 +371,7 @@ impl SequenceItemHeader { // item delimiter // delimiters should not have a positive length if len != Length(0) { - Err(Error::UnexpectedDataValueLength) + UnexpectedDelimiterLength { len }.fail() } else { Ok(SequenceItemHeader::ItemDelimiter) } @@ -355,7 +380,7 @@ impl SequenceItemHeader { // sequence delimiter Ok(SequenceItemHeader::SequenceDelimiter) } - tag => Err(Error::UnexpectedTag(tag)), + tag => UnexpectedTag { tag }.fail() } } } @@ -680,6 +705,7 @@ impl Length { /// Create a new length value from its internal representation. /// This is equivalent to `Length(len)`. + #[inline] pub fn new(len: u32) -> Self { Length(len) } @@ -689,6 +715,7 @@ impl Length { /// # Panic /// /// This function will panic if `len` represents an undefined length. + #[inline] pub fn defined(len: u32) -> Self { assert_ne!(len, UNDEFINED_LEN); Length(len) @@ -696,6 +723,7 @@ impl Length { } impl From for Length { + #[inline] fn from(o: u32) -> Self { Length(o) } @@ -832,6 +860,7 @@ impl Length { /// Check whether the length is equally specified as another length. /// Unlike the implemented `PartialEq`, two undefined lengths are /// considered equivalent by this method. + #[inline] pub fn inner_eq(self, other: Length) -> bool { self.0 == other.0 } diff --git a/core/src/lib.rs b/core/src/lib.rs index c2309215..01ebd35f 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -2,11 +2,9 @@ #![deny(trivial_numeric_casts, unsafe_code, unstable_features)] #![warn( missing_debug_implementations, - missing_docs, unused_qualifications, unused_import_braces )] -#![recursion_limit = "60"] //! This is the core library of DICOM-rs containing various concepts, //! data structures and traits specific to DICOM content. @@ -29,12 +27,10 @@ //! [`value`]: ./value/index.html pub mod dictionary; -pub mod error; pub mod header; pub mod value; pub use dictionary::DataDictionary; -pub use error::{Error, Result}; pub use header::{DataElement, DataElementHeader, Length, Tag, VR}; pub use value::{PrimitiveValue, Value as DicomValue}; diff --git a/core/src/value/deserialize.rs b/core/src/value/deserialize.rs index bda37629..46d39be3 100644 --- a/core/src/value/deserialize.rs +++ b/core/src/value/deserialize.rs @@ -1,29 +1,82 @@ //! Parsing of primitive values -use crate::error::InvalidValueReadError; use chrono::{DateTime, FixedOffset, NaiveDate, NaiveTime, TimeZone}; use std::ops::{Add, Mul, Sub}; +use snafu::{Backtrace, OptionExt, Snafu}; const Z: i32 = b'0' as i32; -type Result = std::result::Result; +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum Error { + #[snafu(display("Unexpected end of element"))] + UnexpectedEndOfElement { + backtrace: Backtrace, + }, + #[snafu(display("Invalid date-time zone component"))] + InvalidDateTimeZone { + backtrace: Backtrace, + }, + #[snafu(display("Invalid hour component: got {}, but must be in 0..24", value))] + InvalidDateTimeHour { + value: u32, + backtrace: Backtrace, + }, + #[snafu(display("Invalid minute component: got {}, but must be in 0..60", value))] + InvalidDateTimeMinute { + value: u32, + backtrace: Backtrace, + }, + #[snafu(display("Invalid second component: got {}, but must be in 0..60", value))] + InvalidDateTimeSecond { + value: u32, + backtrace: Backtrace, + }, + #[snafu(display("Invalid microsecond component: got {}, but must be in 0..2_000_000", value))] + InvalidDateTimeMicrosecond { + value: u32, + backtrace: Backtrace, + }, + #[snafu(display("Unexpected token after date: got '{}', but must be '.', '+', or '-'", *value as char))] + UnexpectedAfterDateToken { + value: u8, + backtrace: Backtrace, + }, + #[snafu(display("Invalid number length: it is {}, but must be between 1 and 9", len))] + InvalidNumberLength { + len: usize, + backtrace: Backtrace, + }, + #[snafu(display("Invalid number token: got '{}', but must be a digit in '0'..='9'", *value as char))] + InvalidNumberToken { + value: u8, + backtrace: Backtrace, + }, + #[snafu(display("Invalid time zone sign token: got '{}', but must be '+' or '-'", *value as char))] + InvalidTimeZoneSignToken { + value: u8, + backtrace: Backtrace, + } +} + +type Result = std::result::Result; /** Decode a single DICOM Date (DA) into a `NaiveDate` value. */ pub fn parse_date(buf: &[u8]) -> Result<(NaiveDate, &[u8])> { // YYYY(MM(DD)?)? match buf.len() { - 0 | 5 | 7 => Err(InvalidValueReadError::UnexpectedEndOfElement), + 0 | 5 | 7 => UnexpectedEndOfElement.fail(), 1..=4 => { let year = read_number(buf)?; let date: Result<_> = NaiveDate::from_ymd_opt(year, 1, 1) - .ok_or_else(|| InvalidValueReadError::DateTimeZone); + .context(InvalidDateTimeZone); Ok((date?, &[])) } 6 => { let year = read_number(&buf[0..4])?; let month = (i32::from(buf[4]) - Z) * 10 + i32::from(buf[5]) - Z; let date: Result<_> = NaiveDate::from_ymd_opt(year, month as u32, 0) - .ok_or_else(|| InvalidValueReadError::DateTimeZone); + .context(InvalidDateTimeZone); Ok((date?, &buf[6..])) } len => { @@ -32,7 +85,7 @@ pub fn parse_date(buf: &[u8]) -> Result<(NaiveDate, &[u8])> { let month = (i32::from(buf[4]) - Z) * 10 + i32::from(buf[5]) - Z; let day = (i32::from(buf[6]) - Z) * 10 + i32::from(buf[7]) - Z; let date: Result<_> = NaiveDate::from_ymd_opt(year, month as u32, day as u32) - .ok_or_else(|| InvalidValueReadError::DateTimeZone); + .context(InvalidDateTimeZone); Ok((date?, &buf[8..])) } } @@ -50,32 +103,26 @@ fn naive_time_from_components( minute: u32, second: u32, micro: u32, -) -> std::result::Result { +) -> Result { if hour >= 24 { - return Err(InvalidValueReadError::ParseDateTime( - hour, - "hour (in 0..24)", - )); - } - if minute >= 60 { - return Err(InvalidValueReadError::ParseDateTime( - minute, - "minute (in 0..60)", - )); - } - if second >= 60 { - return Err(InvalidValueReadError::ParseDateTime( - second, - "second (in 0..60)", - )); - } - if micro >= 2_000_000 { - return Err(InvalidValueReadError::ParseDateTime( - second, - "microsecond (in 0..2_000_000)", - )); + InvalidDateTimeHour { + value: hour, + }.fail() + } else if minute >= 60 { + InvalidDateTimeMinute { + value: minute, + }.fail() + } else if second >= 60 { + InvalidDateTimeSecond { + value: second, + }.fail() + } else if micro >= 2_000_000 { + InvalidDateTimeMicrosecond { + value: micro, + }.fail() + } else { + Ok(NaiveTime::from_hms_micro(hour, minute, second, micro)) } - Ok(NaiveTime::from_hms_micro(hour, minute, second, micro)) } fn parse_time_impl(buf: &[u8], for_datetime: bool) -> Result<(NaiveTime, &[u8])> { @@ -83,7 +130,7 @@ fn parse_time_impl(buf: &[u8], for_datetime: bool) -> Result<(NaiveTime, &[u8])> // HH(MM(SS(.F{1,6})?)?)? match buf.len() { - 0 | 1 | 3 | 5 | 7 => Err(InvalidValueReadError::UnexpectedEndOfElement), + 0 | 1 | 3 | 5 | 7 => UnexpectedEndOfElement.fail(), 2 => { let hour = (i32::from(buf[0]) - Z) * 10 + i32::from(buf[1]) - Z; let time = naive_time_from_components(hour as u32, 0, 0, 0)?; @@ -110,7 +157,7 @@ fn parse_time_impl(buf: &[u8], for_datetime: bool) -> Result<(NaiveTime, &[u8])> match buf[6] { b'.' => { /* do nothing */ } b'+' | b'-' if for_datetime => { /* do nothing */ } - c => return Err(InvalidValueReadError::InvalidToken(c, "'.', '+', or '-'")), + c => return UnexpectedAfterDateToken { value: c }.fail(), } let buf = &buf[7..]; // read at most 6 bytes @@ -185,13 +232,12 @@ where T: Sub, { if text.is_empty() || text.len() > 9 { - return Err(InvalidValueReadError::InvalidLength( - text.len(), - "between 1 and 9", - )); + return InvalidNumberLength { + len: text.len(), + }.fail(); } if let Some(c) = text.iter().cloned().find(|&b| b < b'0' || b > b'9') { - return Err(InvalidValueReadError::InvalidToken(c, "digit in 0..9")); + return InvalidNumberToken { value: c }.fail(); } Ok(read_number_unchecked(text)) @@ -231,16 +277,16 @@ pub fn parse_datetime(buf: &[u8], dt_utc_offset: FixedOffset) -> Result return Err(InvalidValueReadError::UnexpectedEndOfElement), + 1 | 2 => return UnexpectedEndOfElement.fail(), _ => { let tz_sign = buf[0]; let buf = &buf[1..]; let (tz_h, tz_m) = match buf.len() { 1 => (i32::from(buf[0]) - Z, 0), - 2 => return Err(InvalidValueReadError::UnexpectedEndOfElement), + 2 => return UnexpectedEndOfElement.fail(), _ => { let (h_buf, m_buf) = buf.split_at(2); let tz_h = read_number(h_buf)?; @@ -252,7 +298,7 @@ pub fn parse_datetime(buf: &[u8], dt_utc_offset: FixedOffset) -> Result FixedOffset::east(s), b'-' => FixedOffset::west(s), - c => return Err(InvalidValueReadError::InvalidToken(c, "'+' or '-'")), + c => return InvalidTimeZoneSignToken { value: c }.fail(), } } }; @@ -260,7 +306,7 @@ pub fn parse_datetime(buf: &[u8], dt_utc_offset: FixedOffset) -> Result fmt::Result { + write!( + f, + "bad value cast: requested {} but value is {:?}", + self.requested, self.got + ) + } +} + +impl std::error::Error for CastValueError {} + +/// An error type for a failed attempt at converting a value +/// into another representation. +#[derive(Debug)] +pub struct ConvertValueError { + /// The value format requested + pub requested: &'static str, + /// The value's original representation + pub original: ValueType, + /// The reason why the conversion was unsuccessful, + /// or none if a conversion from the given original representation + /// is not possible + pub cause: Option, +} + +impl fmt::Display for ConvertValueError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "could not convert {:?} to a {}: ", + self.original, self.requested + )?; + if let Some(cause) = &self.cause { + write!(f, "{}", cause)?; + } else { + write!(f, "conversion not possible")?; + } + Ok(()) + } +} + +impl std::error::Error for ConvertValueError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.cause.as_ref().map(|x| x as _) + } +} + +pub type Result = std::result::Result; + // Re-exported from chrono pub use chrono::{DateTime, NaiveDate, NaiveTime}; @@ -524,13 +644,13 @@ impl PrimitiveValue { /// PrimitiveValue::I32(smallvec![ /// 1, 2, 5, /// ]) - /// .to_int::(), - /// Ok(1_u32), + /// .to_int::().ok(), + /// Some(1_u32), /// ); /// /// assert_eq!( - /// PrimitiveValue::from("505 ").to_int::(), - /// Ok(505), + /// PrimitiveValue::from("505 ").to_int::().ok(), + /// Some(505), /// ); /// ``` pub fn to_int(&self) -> Result @@ -539,65 +659,107 @@ impl PrimitiveValue { T: FromStr, { match self { - PrimitiveValue::Str(s) => s.trim_end().parse().map_err(|err| ConvertValueError { - requested: "integer", - original: self.value_type(), - cause: Some(InvalidValueReadError::ParseInteger(err)), - }), - PrimitiveValue::Strs(s) if !s.is_empty() => { - s[0].trim_end().parse().map_err(|err| ConvertValueError { + PrimitiveValue::Str(s) => { + s.trim_end() + .parse() + .context(ParseInteger) + .map_err(|err| ConvertValueError { + requested: "integer", + original: self.value_type(), + cause: Some(err), + }) + } + PrimitiveValue::Strs(s) if !s.is_empty() => s[0] + .trim_end() + .parse() + .context(ParseInteger) + .map_err(|err| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::ParseInteger(err)), - }) - } + cause: Some(err), + }), PrimitiveValue::U8(bytes) if !bytes.is_empty() => { T::from(bytes[0]).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(bytes[0].to_string())), + cause: Some( + NarrowConvert { + value: bytes[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::U16(s) if !s.is_empty() => { T::from(s[0]).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::I16(s) if !s.is_empty() => { T::from(s[0]).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::U32(s) if !s.is_empty() => { T::from(s[0]).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::I32(s) if !s.is_empty() => { T::from(s[0]).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::U64(s) if !s.is_empty() => { T::from(s[0]).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::I64(s) if !s.is_empty() => { T::from(s[0]).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } _ => Err(ConvertValueError { @@ -644,13 +806,13 @@ impl PrimitiveValue { /// PrimitiveValue::I32(smallvec![ /// 1, 2, 5, /// ]) - /// .to_multi_int::(), - /// Ok(vec![1_u32, 2, 5]), + /// .to_multi_int::().ok(), + /// Some(vec![1_u32, 2, 5]), /// ); /// /// assert_eq!( - /// dicom_value!(Strs, ["5050", "23 "]).to_multi_int::(), - /// Ok(vec![5050, 23]), + /// dicom_value!(Strs, ["5050", "23 "]).to_multi_int::().ok(), + /// Some(vec![5050, 23]), /// ); /// ``` pub fn to_multi_int(&self) -> Result, ConvertValueError> @@ -661,30 +823,40 @@ impl PrimitiveValue { match self { PrimitiveValue::Empty => Ok(Vec::new()), PrimitiveValue::Str(s) => { - let out = s.trim_end().parse().map_err(|err| ConvertValueError { - requested: "integer", - original: self.value_type(), - cause: Some(InvalidValueReadError::ParseInteger(err)), + let out = s.trim_end().parse().context(ParseInteger).map_err(|err| { + ConvertValueError { + requested: "integer", + original: self.value_type(), + cause: Some(err), + } })?; Ok(vec![out]) } - PrimitiveValue::Strs(s) => s - .iter() - .map(|v| { - v.trim_end().parse().map_err(|err| ConvertValueError { - requested: "integer", - original: self.value_type(), - cause: Some(InvalidValueReadError::ParseInteger(err)), + PrimitiveValue::Strs(s) => { + s.iter() + .map(|v| { + v.trim_end().parse().context(ParseInteger).map_err(|err| { + ConvertValueError { + requested: "integer", + original: self.value_type(), + cause: Some(err), + } + }) }) - }) - .collect::, _>>(), + .collect::, _>>() + } PrimitiveValue::U8(bytes) => bytes .iter() .map(|v| { T::from(*v).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -694,7 +866,12 @@ impl PrimitiveValue { T::from(*v).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -704,7 +881,12 @@ impl PrimitiveValue { T::from(*v).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -714,7 +896,12 @@ impl PrimitiveValue { T::from(*v).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -724,7 +911,12 @@ impl PrimitiveValue { T::from(*v).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -734,7 +926,12 @@ impl PrimitiveValue { T::from(*v).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -744,7 +941,12 @@ impl PrimitiveValue { T::from(*v).ok_or_else(|| ConvertValueError { requested: "integer", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -781,76 +983,118 @@ impl PrimitiveValue { /// PrimitiveValue::F32(smallvec![ /// 1.5, 2., 5., /// ]) - /// .to_float32(), - /// Ok(1.5_f32), + /// .to_float32().ok(), + /// Some(1.5_f32), /// ); /// /// assert_eq!( - /// PrimitiveValue::from("-6.75 ").to_float32(), - /// Ok(-6.75), + /// PrimitiveValue::from("-6.75 ").to_float32().ok(), + /// Some(-6.75), /// ); /// ``` pub fn to_float32(&self) -> Result { match self { - PrimitiveValue::Str(s) => s.trim_end().parse().map_err(|err| ConvertValueError { - requested: "float32", - original: self.value_type(), - cause: Some(InvalidValueReadError::ParseFloat(err)), - }), - PrimitiveValue::Strs(s) if !s.is_empty() => { - s[0].trim_end().parse().map_err(|err| ConvertValueError { + PrimitiveValue::Str(s) => { + s.trim_end() + .parse() + .context(ParseFloat) + .map_err(|err| ConvertValueError { + requested: "float32", + original: self.value_type(), + cause: Some(err), + }) + } + PrimitiveValue::Strs(s) if !s.is_empty() => s[0] + .trim_end() + .parse() + .context(ParseFloat) + .map_err(|err| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::ParseFloat(err)), - }) - } + cause: Some(err), + }), PrimitiveValue::U8(bytes) if !bytes.is_empty() => { NumCast::from(bytes[0]).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(bytes[0].to_string())), + cause: Some( + NarrowConvert { + value: bytes[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::U16(s) if !s.is_empty() => { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::I16(s) if !s.is_empty() => { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::U32(s) if !s.is_empty() => { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::I32(s) if !s.is_empty() => { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::U64(s) if !s.is_empty() => { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::I64(s) if !s.is_empty() => { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::F32(s) if !s.is_empty() => Ok(s[0]), @@ -858,7 +1102,12 @@ impl PrimitiveValue { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } _ => Err(ConvertValueError { @@ -895,34 +1144,41 @@ impl PrimitiveValue { /// PrimitiveValue::F32(smallvec![ /// 1.5, 2., 5., /// ]) - /// .to_multi_float32(), - /// Ok(vec![1.5_f32, 2., 5.]), + /// .to_multi_float32().ok(), + /// Some(vec![1.5_f32, 2., 5.]), /// ); /// /// assert_eq!( - /// PrimitiveValue::from("-6.75 ").to_multi_float32(), - /// Ok(vec![-6.75]), + /// PrimitiveValue::from("-6.75 ").to_multi_float32().ok(), + /// Some(vec![-6.75]), /// ); /// ``` pub fn to_multi_float32(&self) -> Result, ConvertValueError> { match self { PrimitiveValue::Empty => Ok(Vec::new()), PrimitiveValue::Str(s) => { - let out = s.trim_end().parse().map_err(|err| ConvertValueError { - requested: "float32", - original: self.value_type(), - cause: Some(InvalidValueReadError::ParseFloat(err)), - })?; + let out = + s.trim_end() + .parse() + .context(ParseFloat) + .map_err(|err| ConvertValueError { + requested: "float32", + original: self.value_type(), + cause: Some(err), + })?; Ok(vec![out]) } PrimitiveValue::Strs(s) => s .iter() .map(|v| { - v.trim_end().parse().map_err(|err| ConvertValueError { - requested: "float32", - original: self.value_type(), - cause: Some(InvalidValueReadError::ParseFloat(err)), - }) + v.trim_end() + .parse() + .context(ParseFloat) + .map_err(|err| ConvertValueError { + requested: "float32", + original: self.value_type(), + cause: Some(err), + }) }) .collect::, _>>(), PrimitiveValue::U8(bytes) => bytes @@ -931,7 +1187,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -941,7 +1202,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -951,7 +1217,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -961,7 +1232,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -971,7 +1247,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -981,7 +1262,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -991,7 +1277,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -1002,7 +1293,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float32", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -1037,83 +1333,130 @@ impl PrimitiveValue { /// PrimitiveValue::F64(smallvec![ /// 1.5, 2., 5., /// ]) - /// .to_float64(), - /// Ok(1.5_f64), + /// .to_float64().ok(), + /// Some(1.5_f64), /// ); /// /// assert_eq!( - /// PrimitiveValue::from("-6.75 ").to_float64(), - /// Ok(-6.75), + /// PrimitiveValue::from("-6.75 ").to_float64().ok(), + /// Some(-6.75), /// ); /// ``` pub fn to_float64(&self) -> Result { match self { - PrimitiveValue::Str(s) => s.trim_end().parse().map_err(|err| ConvertValueError { - requested: "float64", - original: self.value_type(), - cause: Some(InvalidValueReadError::ParseFloat(err)), - }), - PrimitiveValue::Strs(s) if !s.is_empty() => { - s[0].trim_end().parse().map_err(|err| ConvertValueError { + PrimitiveValue::Str(s) => { + s.trim_end() + .parse() + .context(ParseFloat) + .map_err(|err| ConvertValueError { + requested: "float64", + original: self.value_type(), + cause: Some(err), + }) + } + PrimitiveValue::Strs(s) if !s.is_empty() => s[0] + .trim_end() + .parse() + .context(ParseFloat) + .map_err(|err| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::ParseFloat(err)), - }) - } + cause: Some(err), + }), PrimitiveValue::U8(bytes) if !bytes.is_empty() => { NumCast::from(bytes[0]).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(bytes[0].to_string())), + cause: Some( + NarrowConvert { + value: bytes[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::U16(s) if !s.is_empty() => { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::I16(s) if !s.is_empty() => { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::U32(s) if !s.is_empty() => { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::I32(s) if !s.is_empty() => { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::U64(s) if !s.is_empty() => { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::I64(s) if !s.is_empty() => { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::F32(s) if !s.is_empty() => { NumCast::from(s[0]).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(s[0].to_string())), + cause: Some( + NarrowConvert { + value: s[0].to_string(), + } + .build(), + ), }) } PrimitiveValue::F64(s) if !s.is_empty() => Ok(s[0]), @@ -1151,33 +1494,40 @@ impl PrimitiveValue { /// PrimitiveValue::F64(smallvec![ /// 1.5, 2., 5., /// ]) - /// .to_multi_float64(), - /// Ok(vec![1.5_f64, 2., 5.]), + /// .to_multi_float64().ok(), + /// Some(vec![1.5_f64, 2., 5.]), /// ); /// /// assert_eq!( - /// PrimitiveValue::from("-6.75 ").to_multi_float64(), - /// Ok(vec![-6.75]), + /// PrimitiveValue::from("-6.75 ").to_multi_float64().ok(), + /// Some(vec![-6.75]), /// ); /// ``` pub fn to_multi_float64(&self) -> Result, ConvertValueError> { match self { PrimitiveValue::Str(s) => { - let out = s.trim_end().parse().map_err(|err| ConvertValueError { - requested: "float64", - original: self.value_type(), - cause: Some(InvalidValueReadError::ParseFloat(err)), - })?; + let out = + s.trim_end() + .parse() + .context(ParseFloat) + .map_err(|err| ConvertValueError { + requested: "float64", + original: self.value_type(), + cause: Some(err), + })?; Ok(vec![out]) } PrimitiveValue::Strs(s) => s .iter() .map(|v| { - v.trim_end().parse().map_err(|err| ConvertValueError { - requested: "float64", - original: self.value_type(), - cause: Some(InvalidValueReadError::ParseFloat(err)), - }) + v.trim_end() + .parse() + .context(ParseFloat) + .map_err(|err| ConvertValueError { + requested: "float64", + original: self.value_type(), + cause: Some(err), + }) }) .collect::, _>>(), PrimitiveValue::U8(bytes) => bytes @@ -1186,7 +1536,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -1196,7 +1551,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -1206,7 +1566,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -1216,7 +1581,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -1226,7 +1596,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -1236,7 +1611,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -1246,7 +1626,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -1256,7 +1641,12 @@ impl PrimitiveValue { NumCast::from(*v).ok_or_else(|| ConvertValueError { requested: "float64", original: self.value_type(), - cause: Some(InvalidValueReadError::NarrowConvert(v.to_string())), + cause: Some( + NarrowConvert { + value: v.to_string(), + } + .build(), + ), }) }) .collect::, _>>(), @@ -1290,16 +1680,16 @@ impl PrimitiveValue { /// PrimitiveValue::Date(smallvec![ /// NaiveDate::from_ymd(2014, 10, 12), /// ]) - /// .to_date(), - /// Ok(NaiveDate::from_ymd(2014, 10, 12)), + /// .to_date().ok(), + /// Some(NaiveDate::from_ymd(2014, 10, 12)), /// ); /// /// assert_eq!( /// PrimitiveValue::Strs(smallvec![ /// "20141012".to_string(), /// ]) - /// .to_date(), - /// Ok(NaiveDate::from_ymd(2014, 10, 12)), + /// .to_date().ok(), + /// Some(NaiveDate::from_ymd(2014, 10, 12)), /// ); /// ``` pub fn to_date(&self) -> Result { @@ -1307,6 +1697,7 @@ impl PrimitiveValue { PrimitiveValue::Date(v) if !v.is_empty() => Ok(v[0]), PrimitiveValue::Str(s) => super::deserialize::parse_date(s.as_bytes()) .map(|(date, _rest)| date) + .context(ParseDate) .map_err(|err| ConvertValueError { requested: "Date", original: self.value_type(), @@ -1315,6 +1706,7 @@ impl PrimitiveValue { PrimitiveValue::Strs(s) => { super::deserialize::parse_date(s.first().map(|s| s.as_bytes()).unwrap_or(&[])) .map(|(date, _rest)| date) + .context(ParseDate) .map_err(|err| ConvertValueError { requested: "Date", original: self.value_type(), @@ -1323,6 +1715,7 @@ impl PrimitiveValue { } PrimitiveValue::U8(bytes) => super::deserialize::parse_date(bytes) .map(|(date, _rest)| date) + .context(ParseDate) .map_err(|err| ConvertValueError { requested: "Date", original: self.value_type(), @@ -1354,13 +1747,13 @@ impl PrimitiveValue { /// # use chrono::NaiveTime; /// /// assert_eq!( - /// PrimitiveValue::from(NaiveTime::from_hms(11, 2, 45)).to_time(), - /// Ok(NaiveTime::from_hms(11, 2, 45)), + /// PrimitiveValue::from(NaiveTime::from_hms(11, 2, 45)).to_time().ok(), + /// Some(NaiveTime::from_hms(11, 2, 45)), /// ); /// /// assert_eq!( - /// PrimitiveValue::from("110245.78").to_time(), - /// Ok(NaiveTime::from_hms_milli(11, 2, 45, 780)), + /// PrimitiveValue::from("110245.78").to_time().ok(), + /// Some(NaiveTime::from_hms_milli(11, 2, 45, 780)), /// ); /// ``` pub fn to_time(&self) -> Result { @@ -1368,6 +1761,7 @@ impl PrimitiveValue { PrimitiveValue::Time(v) if !v.is_empty() => Ok(v[0]), PrimitiveValue::Str(s) => super::deserialize::parse_time(s.as_bytes()) .map(|(date, _rest)| date) + .context(ParseTime) .map_err(|err| ConvertValueError { requested: "Time", original: self.value_type(), @@ -1376,6 +1770,7 @@ impl PrimitiveValue { PrimitiveValue::Strs(s) => { super::deserialize::parse_time(s.first().map(|s| s.as_bytes()).unwrap_or(&[])) .map(|(date, _rest)| date) + .context(ParseTime) .map_err(|err| ConvertValueError { requested: "Time", original: self.value_type(), @@ -1384,6 +1779,7 @@ impl PrimitiveValue { } PrimitiveValue::U8(bytes) => super::deserialize::parse_time(bytes) .map(|(date, _rest)| date) + .context(ParseTime) .map_err(|err| ConvertValueError { requested: "Time", original: self.value_type(), @@ -1427,16 +1823,17 @@ impl PrimitiveValue { /// FixedOffset::east(0) /// .ymd(2012, 12, 21) /// .and_hms(9, 30, 1) - /// ).to_datetime(default_offset), - /// Ok(FixedOffset::east(0) + /// ).to_datetime(default_offset).ok(), + /// Some(FixedOffset::east(0) /// .ymd(2012, 12, 21) /// .and_hms(9, 30, 1) /// ), /// ); /// /// assert_eq!( - /// PrimitiveValue::from("20121221093001").to_datetime(default_offset), - /// Ok(FixedOffset::east(0) + /// PrimitiveValue::from("20121221093001") + /// .to_datetime(default_offset).ok(), + /// Some(FixedOffset::east(0) /// .ymd(2012, 12, 21) /// .and_hms(9, 30, 1) /// ), @@ -1449,24 +1846,26 @@ impl PrimitiveValue { match self { PrimitiveValue::DateTime(v) if !v.is_empty() => Ok(v[0]), PrimitiveValue::Str(s) => { - super::deserialize::parse_datetime(s.as_bytes(), default_offset).map_err(|err| { - ConvertValueError { + super::deserialize::parse_datetime(s.as_bytes(), default_offset) + .context(ParseDateTime) + .map_err(|err| ConvertValueError { requested: "DateTime", original: self.value_type(), cause: Some(err), - } - }) + }) } PrimitiveValue::Strs(s) => super::deserialize::parse_datetime( s.first().map(|s| s.as_bytes()).unwrap_or(&[]), default_offset, ) + .context(ParseDateTime) .map_err(|err| ConvertValueError { requested: "DateTime", original: self.value_type(), cause: Some(err), }), PrimitiveValue::U8(bytes) => super::deserialize::parse_datetime(bytes, default_offset) + .context(ParseDateTime) .map_err(|err| ConvertValueError { requested: "DateTime", original: self.value_type(), @@ -1766,8 +2165,8 @@ impl DicomValueType for PrimitiveValue { #[cfg(test)] mod tests { + use super::{ConvertValueError, InvalidValueReadError}; use crate::dicom_value; - use crate::error::{ConvertValueError, InvalidValueReadError}; use crate::value::{PrimitiveValue, ValueType}; use chrono::{NaiveDate, NaiveTime}; use smallvec::smallvec; @@ -1848,17 +2247,29 @@ mod tests { assert!(PrimitiveValue::Empty.to_int::().is_err()); // exact match - assert_eq!(PrimitiveValue::from(0x0601_u16).to_int(), Ok(0x0601_u16),); + assert_eq!( + PrimitiveValue::from(0x0601_u16).to_int().ok(), + Some(0x0601_u16), + ); // conversions are automatically applied - assert_eq!(PrimitiveValue::from(0x0601_u16).to_int(), Ok(0x0601_u32),); - assert_eq!(PrimitiveValue::from(0x0601_u16).to_int(), Ok(0x0601_i64),); - assert_eq!(PrimitiveValue::from(0x0601_u16).to_int(), Ok(0x0601_u64),); + assert_eq!( + PrimitiveValue::from(0x0601_u16).to_int().ok(), + Some(0x0601_u32), + ); + assert_eq!( + PrimitiveValue::from(0x0601_u16).to_int().ok(), + Some(0x0601_i64), + ); + assert_eq!( + PrimitiveValue::from(0x0601_u16).to_int().ok(), + Some(0x0601_u64), + ); // takes the first number - assert_eq!(dicom_value!(I32, [1, 2, 5]).to_int(), Ok(1),); + assert_eq!(dicom_value!(I32, [1, 2, 5]).to_int().ok(), Some(1),); // admits an integer as text - assert_eq!(dicom_value!(Strs, ["-73", "2"]).to_int(), Ok(-73),); + assert_eq!(dicom_value!(Strs, ["-73", "2"]).to_int().ok(), Some(-73),); // does not admit destructive conversions assert!(PrimitiveValue::from(-1).to_int::().is_err()); @@ -1870,45 +2281,39 @@ mod tests { requested: _, original: ValueType::Strs, // would try to parse as an integer and fail - cause: Some(InvalidValueReadError::ParseInteger(_)), + cause: Some(InvalidValueReadError::ParseInteger { .. }), }) )); } #[test] fn primitive_value_to_multi_int() { - assert_eq!(PrimitiveValue::Empty.to_multi_int::(), Ok(vec![])); + assert_eq!(PrimitiveValue::Empty.to_multi_int::().unwrap(), vec![]); let test_value = dicom_value!(U16, [0x0601, 0x5353, 3, 4]); // exact match + let numbers = test_value.to_multi_int::().unwrap(); + assert_eq!(numbers, vec![0x0601, 0x5353, 3, 4],); + // type is inferred on context + let numbers: Vec = test_value.to_multi_int().unwrap(); + assert_eq!(numbers, vec![0x0601_u32, 0x5353, 3, 4],); + let numbers: Vec = test_value.to_multi_int().unwrap(); + assert_eq!(numbers, vec![0x0601_i64, 0x5353, 3, 4],); assert_eq!( - test_value.to_multi_int(), - Ok(vec![0x0601_u16, 0x5353, 3, 4]), - ); - // conversions are automatically applied - assert_eq!( - test_value.to_multi_int(), - Ok(vec![0x0601_u32, 0x5353, 3, 4]), - ); - assert_eq!( - test_value.to_multi_int(), - Ok(vec![0x0601_i64, 0x5353, 3, 4]), - ); - assert_eq!( - test_value.to_multi_int(), - Ok(vec![0x0601_u64, 0x5353, 3, 4]), + test_value.to_multi_int::().unwrap(), + vec![0x0601_u64, 0x5353, 3, 4], ); // takes all numbers assert_eq!( - dicom_value!(I32, [1, 2, 5]).to_multi_int(), - Ok(vec![1, 2, 5]), + dicom_value!(I32, [1, 2, 5]).to_multi_int().ok(), + Some(vec![1, 2, 5]), ); // admits a integer as text, trailing space too assert_eq!( - dicom_value!(Strs, ["-73", "2 "]).to_multi_int(), - Ok(vec![-73, 2]), + dicom_value!(Strs, ["-73", "2 "]).to_multi_int().ok(), + Some(vec![-73, 2]), ); // does not admit destructive conversions @@ -1917,7 +2322,10 @@ mod tests { Err(ConvertValueError { original: ValueType::I32, // the cast from -1_i32 to u32 would fail - cause: Some(InvalidValueReadError::NarrowConvert(x)), + cause: Some(InvalidValueReadError::NarrowConvert { + value: x, + .. + }), .. }) if &x == "-1" )); @@ -1928,7 +2336,7 @@ mod tests { Err(ConvertValueError { original: ValueType::Strs, // the conversion from "-1" to u32 would fail - cause: Some(InvalidValueReadError::ParseInteger(_)), + cause: Some(InvalidValueReadError::ParseInteger { .. }), .. }) )); @@ -1940,24 +2348,32 @@ mod tests { requested: _, original: ValueType::Strs, // would try to parse as an integer and fail - cause: Some(InvalidValueReadError::ParseInteger(_)), + cause: Some(InvalidValueReadError::ParseInteger { .. }), }) )); } #[test] fn primitive_value_to_multi_floats() { - assert_eq!(PrimitiveValue::Empty.to_multi_float32(), Ok(vec![])); + assert_eq!(PrimitiveValue::Empty.to_multi_float32().ok(), Some(vec![])); let test_value = dicom_value!(U16, [1, 2, 3, 4]); - assert_eq!(test_value.to_multi_float32(), Ok(vec![1., 2., 3., 4.]),); - assert_eq!(test_value.to_multi_float64(), Ok(vec![1., 2., 3., 4.]),); + assert_eq!( + test_value.to_multi_float32().ok(), + Some(vec![1., 2., 3., 4.]), + ); + assert_eq!( + test_value.to_multi_float64().ok(), + Some(vec![1., 2., 3., 4.]), + ); // admits a number as text, trailing space too assert_eq!( - dicom_value!(Strs, ["7.25", "-12.5 "]).to_multi_float64(), - Ok(vec![7.25, -12.5]), + dicom_value!(Strs, ["7.25", "-12.5 "]) + .to_multi_float64() + .ok(), + Some(vec![7.25, -12.5]), ); // does not admit strings which are not numbers @@ -1967,7 +2383,7 @@ mod tests { requested: _, original: ValueType::Strs, // would try to parse as a float and fail - cause: Some(InvalidValueReadError::ParseFloat(_)), + cause: Some(InvalidValueReadError::ParseFloat { .. }), }) )); } diff --git a/dcmdump/Cargo.toml b/dcmdump/Cargo.toml index 9ec248af..31a57bff 100644 --- a/dcmdump/Cargo.toml +++ b/dcmdump/Cargo.toml @@ -14,9 +14,11 @@ readme = "README.md" repository = "Enet4/dicom-rs" [features] -default = ['dicom/inventory-registry'] +default = ['dicom/inventory-registry', 'dicom/backtraces'] [dependencies] dicom = { path = "../parent/", version = "0.2.0", default-features = false } term_size = "0.3.2" itertools = "0.9.0" +snafu = "0.6.8" + diff --git a/dcmdump/src/main.rs b/dcmdump/src/main.rs index 6bf89598..744be151 100644 --- a/dcmdump/src/main.rs +++ b/dcmdump/src/main.rs @@ -12,37 +12,70 @@ use dicom::core::dictionary::{DataDictionary, DictionaryEntry}; use dicom::core::header::Header; use dicom::core::value::{PrimitiveValue, Value as DicomValue}; use dicom::core::VR; +use dicom::encoding::transfer_syntax::TransferSyntaxIndex; use dicom::object::mem::{InMemDicomObject, InMemElement}; use dicom::object::{open_file, DefaultDicomObject, FileMetaTable, StandardDataDictionary}; -use dicom::encoding::transfer_syntax::TransferSyntaxIndex; use dicom::transfer_syntax::TransferSyntaxRegistry; - -use term_size; - +use snafu::ErrorCompat; use std::borrow::Cow; use std::io::{stdout, ErrorKind, Result as IoResult, Write}; -type DynResult = Result>; +/// Exit code for missing CLI arguments or --help +const ERROR_NO: i32 = -1; +/// Exit code for when an error emerged while reading the DICOM file. +const ERROR_READ: i32 = -2; +/// Exit code for when an error emerged while dumping the file. +const ERROR_PRINT: i32 = -3; -fn main() { - run().unwrap_or_else(|e| { - eprintln!("{:#}", e); - }); +fn report(err: E) +where + E: std::error::Error, + E: ErrorCompat, +{ + eprintln!("[ERROR] {}", err); + if let Some(source) = err.source() { + eprintln!(); + eprintln!("Caused by:"); + for (i, e) in std::iter::successors(Some(source), |e| e.source()).enumerate() { + eprintln!(" {}: {}", i, e); + } + } + + let env_backtrace = std::env::var("RUST_BACKTRACE").unwrap_or_default(); + let env_lib_backtrace = std::env::var("RUST_LIB_BACKTRACE").unwrap_or_default(); + if env_lib_backtrace == "1" || (env_backtrace == "1" && env_lib_backtrace != "0") { + if let Some(backtrace) = ErrorCompat::backtrace(&err) { + eprintln!(); + eprintln!("Backtrace:"); + eprintln!("{}", backtrace); + } + } } -fn run() -> DynResult<()> { +fn main() { let filename = ::std::env::args() .nth(1) - .expect("Missing path to DICOM file"); + .unwrap_or_else(|| "--help".to_string()); + + if filename == "--help" || filename == "-h" { + println!("Usage: dcmdump "); + std::process::exit(ERROR_NO); + } - let obj = open_file(filename)?; + let obj = open_file(filename).unwrap_or_else(|e| { + report(e); + std::process::exit(ERROR_READ); + }); match dump_file(obj) { Err(ref e) if e.kind() == ErrorKind::BrokenPipe => { - Ok(()) // handle broken pipe separately with a no-op + // handle broken pipe separately with a no-op + } + Err(e) => { + eprintln!("[ERROR] {}", e); + std::process::exit(ERROR_PRINT); } - Err(e) => Err(e.into()), // raise other errors - _ => Ok(()), // all good + _ => {} // all good } } @@ -86,7 +119,11 @@ where } else { writeln!(to, "Transfer Syntax: {} («UNKNOWN»)", meta.transfer_syntax)?; } - writeln!(to, "Implementation Class UID: {}", meta.implementation_class_uid)?; + writeln!( + to, + "Implementation Class UID: {}", + meta.implementation_class_uid + )?; if let Some(v) = meta.implementation_version_name.as_ref() { writeln!(to, "Implementation version name: {}", v)?; diff --git a/encoding/Cargo.toml b/encoding/Cargo.toml index a158a90c..8b7ae6a8 100644 --- a/encoding/Cargo.toml +++ b/encoding/Cargo.toml @@ -20,7 +20,7 @@ inventory-registry = ['inventory'] [dependencies] dicom-core = { path = "../core", version = "0.2.0" } dicom-dictionary-std = { path = "../dictionary-std", version = "0.2.0" } -quick-error = "1.2.3" encoding = "0.2.33" byteordered = "0.5.0" inventory = { version = "0.1.4", optional = true } +snafu = "0.6.8" diff --git a/encoding/src/decode/basic.rs b/encoding/src/decode/basic.rs index dbe246f7..e97795fd 100644 --- a/encoding/src/decode/basic.rs +++ b/encoding/src/decode/basic.rs @@ -2,10 +2,11 @@ //! may be in either Little Endian or Big Endian. use super::BasicDecode; -use crate::error::Result; use byteordered::{ByteOrdered, Endianness}; use std::io::Read; +type Result = std::io::Result; + /// A basic decoder of DICOM primitive elements in little endian. #[derive(Debug, Default, Clone, PartialEq)] pub struct LittleEndianBasicDecoder; diff --git a/encoding/src/decode/explicit_be.rs b/encoding/src/decode/explicit_be.rs new file mode 100644 index 00000000..860f7380 --- /dev/null +++ b/encoding/src/decode/explicit_be.rs @@ -0,0 +1,255 @@ +//! Explicit VR Big Endian syntax transfer implementation. + +use crate::decode::basic::BigEndianBasicDecoder; +use crate::decode::*; +use crate::decode::{BasicDecode, Decode, DecodeFrom}; +use byteordered::byteorder::{BigEndian, ByteOrder}; +use dicom_core::header::{DataElementHeader, Length, SequenceItemHeader}; +use dicom_core::{Tag, VR}; +use snafu::ResultExt; +use std::io::Read; + +/// A data element decoder for the Explicit VR Big Endian transfer syntax. +#[derive(Debug, Default, Clone)] +pub struct ExplicitVRBigEndianDecoder { + basic: BigEndianBasicDecoder, +} + +impl Decode for ExplicitVRBigEndianDecoder { + fn decode_header(&self, mut source: &mut S) -> Result<(DataElementHeader, usize)> + where + S: ?Sized + Read, + { + // retrieve tag + let Tag(group, element) = self.basic.decode_tag(&mut source).context(ReadHeaderTag)?; + + let mut buf = [0u8; 4]; + if group == 0xFFFE { + // item delimiters do not have VR or reserved field + source.read_exact(&mut buf).context(ReadItemLength)?; + let len = BigEndian::read_u32(&buf); + return Ok(( + DataElementHeader::new((group, element), VR::UN, Length(len)), + 8, // tag + len + )); + } + + // retrieve explicit VR + source.read_exact(&mut buf[0..2]).context(ReadVr)?; + let vr = VR::from_binary([buf[0], buf[1]]).unwrap_or(VR::UN); + + let bytes_read; + + // retrieve data length + let len = match vr { + VR::OB + | VR::OD + | VR::OF + | VR::OL + | VR::OW + | VR::SQ + | VR::UC + | VR::UR + | VR::UT + | VR::UN => { + // read 2 reserved bytes, then 4 bytes for data length + source.read_exact(&mut buf[0..2]).context(ReadReserved)?; + source.read_exact(&mut buf).context(ReadLength)?; + bytes_read = 12; + BigEndian::read_u32(&buf) + } + _ => { + // read 2 bytes for the data length + source.read_exact(&mut buf[0..2]).context(ReadLength)?; + bytes_read = 8; + u32::from(BigEndian::read_u16(&buf[0..2])) + } + }; + + Ok(( + DataElementHeader::new((group, element), vr, Length(len)), + bytes_read, + )) + } + + fn decode_item_header(&self, source: &mut S) -> Result + where + S: ?Sized + Read, + { + let mut buf = [0u8; 8]; + source.read_exact(&mut buf).context(ReadItemHeader)?; + // retrieve tag + let group = BigEndian::read_u16(&buf[0..2]); + let element = BigEndian::read_u16(&buf[2..4]); + let len = BigEndian::read_u32(&buf[4..8]); + + SequenceItemHeader::new((group, element), Length(len)).context(BadSequenceHeader) + } + + fn decode_tag(&self, source: &mut S) -> Result + where + S: ?Sized + Read, + { + let mut buf = [0u8; 4]; + source.read_exact(&mut buf).context(ReadTag)?; + Ok(Tag( + BigEndian::read_u16(&buf[0..2]), + BigEndian::read_u16(&buf[2..4]), + )) + } +} + +impl DecodeFrom for ExplicitVRBigEndianDecoder +where + S: Read, +{ + #[inline] + fn decode_header(&self, source: &mut S) -> Result<(DataElementHeader, usize)> { + Decode::decode_header(self, source) + } + + #[inline] + fn decode_item_header(&self, source: &mut S) -> Result { + Decode::decode_item_header(self, source) + } + + #[inline] + fn decode_tag(&self, source: &mut S) -> Result { + Decode::decode_tag(self, source) + } +} + +#[cfg(test)] +mod tests { + use super::ExplicitVRBigEndianDecoder; + use crate::decode::Decode; + use dicom_core::header::{HasLength, Header, Length}; + use dicom_core::{Tag, VR}; + use std::io::{Cursor, Read, Seek, SeekFrom}; + + // manually crafting some DICOM data elements + // Tag: (0002,0002) Media Storage SOP Class UID + // VR: UI + // Length: 26 + // Value: "1.2.840.10008.5.1.4.1.1.1" (with 1 padding '\0') + // -- + // Tag: (0002,0010) Transfer Syntax UID + // VR: UI + // Length: 20 + // Value: "1.2.840.10008.1.2.1" (w 1 padding '\0') == ExplicitVRLittleEndian + // -- + const RAW: &'static [u8; 62] = &[ + 0x00, 0x02, 0x00, 0x02, 0x55, 0x49, 0x00, 0x1a, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30, + 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, + 0x31, 0x2e, 0x31, 0x00, 0x00, 0x02, 0x00, 0x10, 0x55, 0x49, 0x00, 0x14, 0x31, 0x2e, 0x32, + 0x2e, 0x38, 0x34, 0x30, 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, + 0x31, 0x00, + ]; + + #[test] + fn decode_explicit_vr_be() { + let reader = ExplicitVRBigEndianDecoder::default(); + let mut cursor = Cursor::new(RAW.as_ref()); + { + // read first element + let (elem, bytes_read) = reader + .decode_header(&mut cursor) + .expect("should find an element"); + assert_eq!(elem.tag(), Tag(2, 2)); + assert_eq!(elem.vr(), VR::UI); + assert_eq!(elem.length(), Length(26)); + assert_eq!(bytes_read, 8); + // read only half of the data + let mut buffer = [0; 13]; + cursor + .read_exact(&mut buffer) + .expect("should read it fine"); + assert_eq!(&buffer, b"1.2.840.10008".as_ref()); + } + // cursor should now be @ #21 (there is no automatic skipping) + assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 21); + // cursor should now be @ #34 after skipping + assert_eq!(cursor.seek(SeekFrom::Current(13)).unwrap(), 34); + { + // read second element + let (elem, _bytes_read) = reader + .decode_header(&mut cursor) + .expect("should find an element"); + assert_eq!(elem.tag(), Tag(2, 16)); + assert_eq!(elem.vr(), VR::UI); + assert_eq!(elem.length(), Length(20)); + // read all data + let mut buffer = [0; 20]; + cursor + .read_exact(&mut buffer) + .expect("should read it fine"); + assert_eq!(&buffer, b"1.2.840.10008.1.2.1\0".as_ref()); + } + } + + // manually crafting some DICOM sequence/item delimiters + // Tag: (0008,103F) Series Description Code Sequence + // VR: SQ + // Reserved bytes: 0x0000 + // Length: 0xFFFF_FFFF + // -- + // Tag: (FFFE,E000) Item + // Length: 0xFFFF_FFFF (unspecified) + // -- + // Tag: (FFFE,E00D) Item Delimitation Item + // Length: 0 + // -- + // Tag: (FFFE,E0DD) Sequence Delimitation Item + // Length: 0 + // -- + const RAW_SEQUENCE_ITEMS: &'static [u8] = &[ + 0x00, 0x08, 0x10, 0x3F, b'S', b'Q', 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xE0, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xE0, 0x0D, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, + 0xE0, 0xDD, 0x00, 0x00, 0x00, 0x00, + ]; + + #[test] + fn decode_items() { + let dec = ExplicitVRBigEndianDecoder::default(); + let mut cursor = Cursor::new(RAW_SEQUENCE_ITEMS); + { + // read first element + let (elem, _bytes_read) = dec + .decode_header(&mut cursor) + .expect("should find an element header"); + assert_eq!(elem.tag(), Tag(8, 0x103F)); + assert_eq!(elem.vr(), VR::SQ); + assert!(elem.length().is_undefined()); + } + // cursor should now be @ #12 + assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 12); + { + let elem = dec + .decode_item_header(&mut cursor) + .expect("should find an item header"); + assert!(elem.is_item()); + assert_eq!(elem.tag(), Tag(0xFFFE, 0xE000)); + assert!(elem.length().is_undefined()); + } + // cursor should now be @ #20 + assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 20); + { + let elem = dec + .decode_item_header(&mut cursor) + .expect("should find an item header"); + assert!(elem.is_item_delimiter()); + assert_eq!(elem.tag(), Tag(0xFFFE, 0xE00D)); + assert_eq!(elem.length(), Length(0)); + } + // cursor should now be @ #28 + assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 28); + { + let elem = dec + .decode_item_header(&mut cursor) + .expect("should find an item header"); + assert!(elem.is_sequence_delimiter()); + assert_eq!(elem.tag(), Tag(0xFFFE, 0xE0DD)); + assert_eq!(elem.length(), Length(0)); + } + } +} diff --git a/encoding/src/decode/explicit_le.rs b/encoding/src/decode/explicit_le.rs new file mode 100644 index 00000000..1ee4f255 --- /dev/null +++ b/encoding/src/decode/explicit_le.rs @@ -0,0 +1,254 @@ +//! Explicit VR Little Endian syntax transfer implementation + +use crate::decode::basic::LittleEndianBasicDecoder; +use crate::decode::*; +use byteordered::byteorder::{ByteOrder, LittleEndian}; +use dicom_core::header::{DataElementHeader, Length, SequenceItemHeader}; +use dicom_core::{Tag, VR}; +use snafu::ResultExt; +use std::io::Read; + +/// A data element decoder for the Explicit VR Little Endian transfer syntax. +#[derive(Debug, Default, Clone)] +pub struct ExplicitVRLittleEndianDecoder { + basic: LittleEndianBasicDecoder, +} + +impl Decode for ExplicitVRLittleEndianDecoder { + fn decode_header(&self, mut source: &mut S) -> Result<(DataElementHeader, usize)> + where + S: ?Sized + Read, + { + // retrieve tag + let Tag(group, element) = self.basic.decode_tag(&mut source).context(ReadHeaderTag)?; + + let mut buf = [0u8; 4]; + if group == 0xFFFE { + // item delimiters do not have VR or reserved field + source.read_exact(&mut buf).context(ReadItemLength)?; + let len = LittleEndian::read_u32(&buf); + return Ok(( + DataElementHeader::new((group, element), VR::UN, Length(len)), + 8, // tag + len + )); + } + + // retrieve explicit VR + source.read_exact(&mut buf[0..2]).context(ReadVr)?; + let vr = VR::from_binary([buf[0], buf[1]]).unwrap_or(VR::UN); + let bytes_read; + + // retrieve data length + let len = match vr { + VR::OB + | VR::OD + | VR::OF + | VR::OL + | VR::OW + | VR::SQ + | VR::UC + | VR::UR + | VR::UT + | VR::UN => { + // read 2 reserved bytes, then 4 bytes for data length + source.read_exact(&mut buf[0..2]).context(ReadReserved)?; + source.read_exact(&mut buf).context(ReadLength)?; + bytes_read = 12; + LittleEndian::read_u32(&buf) + } + _ => { + // read 2 bytes for the data length + source.read_exact(&mut buf[0..2]).context(ReadLength)?; + bytes_read = 8; + u32::from(LittleEndian::read_u16(&buf[0..2])) + } + }; + + Ok(( + DataElementHeader::new((group, element), vr, Length(len)), + bytes_read, + )) + } + + fn decode_item_header(&self, source: &mut S) -> Result + where + S: ?Sized + Read, + { + let mut buf = [0u8; 8]; + source.read_exact(&mut buf).context(ReadItemHeader)?; + // retrieve tag + let group = LittleEndian::read_u16(&buf[0..2]); + let element = LittleEndian::read_u16(&buf[2..4]); + let len = LittleEndian::read_u32(&buf[4..8]); + + SequenceItemHeader::new((group, element), Length(len)).context(BadSequenceHeader) + } + + fn decode_tag(&self, source: &mut S) -> Result + where + S: ?Sized + Read, + { + let mut buf = [0u8; 4]; + source.read_exact(&mut buf).context(ReadTag)?; + Ok(Tag( + LittleEndian::read_u16(&buf[0..2]), + LittleEndian::read_u16(&buf[2..4]), + )) + } +} + +impl DecodeFrom for ExplicitVRLittleEndianDecoder +where + S: Read, +{ + #[inline] + fn decode_header(&self, source: &mut S) -> Result<(DataElementHeader, usize)> { + Decode::decode_header(self, source) + } + + #[inline] + fn decode_item_header(&self, source: &mut S) -> Result { + Decode::decode_item_header(self, source) + } + + #[inline] + fn decode_tag(&self, source: &mut S) -> Result { + Decode::decode_tag(self, source) + } +} + +#[cfg(test)] +mod tests { + use super::ExplicitVRLittleEndianDecoder; + use crate::decode::Decode; + use dicom_core::header::{HasLength, Header, Length}; + use dicom_core::{Tag, VR}; + use std::io::{Cursor, Read, Seek, SeekFrom}; + + // manually crafting some DICOM data elements + // Tag: (0002,0002) Media Storage SOP Class UID + // VR: UI + // Length: 26 + // Value: "1.2.840.10008.5.1.4.1.1.1\0" + // -- + // Tag: (0002,0010) Transfer Syntax UID + // VR: UI + // Length: 20 + // Value: "1.2.840.10008.1.2.1\0" == ExplicitVRLittleEndian + // -- + const RAW: &'static [u8; 62] = &[ + 0x02, 0x00, 0x02, 0x00, 0x55, 0x49, 0x1a, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30, + 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, + 0x31, 0x2e, 0x31, 0x00, 0x02, 0x00, 0x10, 0x00, 0x55, 0x49, 0x14, 0x00, 0x31, 0x2e, 0x32, + 0x2e, 0x38, 0x34, 0x30, 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, + 0x31, 0x00, + ]; + + #[test] + fn decode_data_elements() { + let dec = ExplicitVRLittleEndianDecoder::default(); + let mut cursor = Cursor::new(RAW.as_ref()); + { + // read first element + let (elem, bytes_read) = dec + .decode_header(&mut cursor) + .expect("should find an element"); + assert_eq!(elem.tag(), Tag(2, 2)); + assert_eq!(elem.vr(), VR::UI); + assert_eq!(elem.length(), Length(26)); + assert_eq!(bytes_read, 8); + // read only half of the value data + let mut buffer = [0; 13]; + cursor + .read_exact(&mut buffer) + .expect("should read it fine"); + assert_eq!(&buffer, b"1.2.840.10008".as_ref()); + } + // cursor should now be @ #21 (there is no automatic skipping) + assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 21); + // cursor should now be @ #34 after skipping + assert_eq!(cursor.seek(SeekFrom::Current(13)).unwrap(), 34); + { + // read second element + let (elem, _bytes_read) = dec + .decode_header(&mut cursor) + .expect("should find an element"); + assert_eq!(elem.tag(), Tag(2, 16)); + assert_eq!(elem.vr(), VR::UI); + assert_eq!(elem.length(), Length(20)); + // read all data + let mut buffer = [0; 20]; + cursor + .read_exact(&mut buffer) + .expect("should read it fine"); + assert_eq!(&buffer, b"1.2.840.10008.1.2.1\0".as_ref()); + } + } + + // manually crafting some DICOM sequence/item delimiters + // Tag: (0008,103F) Series Description Code Sequence + // VR: SQ + // Reserved bytes: 0x0000 + // Length: 0xFFFF_FFFF + // -- + // Tag: (FFFE,E000) Item + // Length: 0xFFFF_FFFF (unspecified) + // -- + // Tag: (FFFE,E00D) Item Delimitation Item + // Length: 0 + // -- + // Tag: (FFFE,E0DD) Sequence Delimitation Item + // Length: 0 + // -- + const RAW_SEQUENCE_ITEMS: &'static [u8] = &[ + 0x08, 0x00, 0x3F, 0x10, b'S', b'Q', 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x00, + 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x0D, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, + 0xDD, 0xE0, 0x00, 0x00, 0x00, 0x00, + ]; + + #[test] + fn decode_items() { + let dec = ExplicitVRLittleEndianDecoder::default(); + let mut cursor = Cursor::new(RAW_SEQUENCE_ITEMS); + { + // read first element + let (elem, bytes_read) = dec + .decode_header(&mut cursor) + .expect("should find an element header"); + assert_eq!(elem.tag(), Tag(8, 0x103F)); + assert_eq!(elem.vr(), VR::SQ); + assert!(elem.length().is_undefined()); + assert_eq!(bytes_read, 12); + } + // cursor should now be @ #12 + assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 12); + { + let elem = dec + .decode_item_header(&mut cursor) + .expect("should find an item header"); + assert!(elem.is_item()); + assert_eq!(elem.tag(), Tag(0xFFFE, 0xE000)); + assert!(elem.length().is_undefined()); + } + // cursor should now be @ #20 + assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 20); + { + let elem = dec + .decode_item_header(&mut cursor) + .expect("should find an item header"); + assert!(elem.is_item_delimiter()); + assert_eq!(elem.tag(), Tag(0xFFFE, 0xE00D)); + assert_eq!(elem.length(), Length(0)); + } + // cursor should now be @ #28 + assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 28); + { + let elem = dec + .decode_item_header(&mut cursor) + .expect("should find an item header"); + assert!(elem.is_sequence_delimiter()); + assert_eq!(elem.tag(), Tag(0xFFFE, 0xE0DD)); + assert_eq!(elem.length(), Length(0)); + } + } +} diff --git a/encoding/src/decode/implicit_le.rs b/encoding/src/decode/implicit_le.rs new file mode 100644 index 00000000..4c72517c --- /dev/null +++ b/encoding/src/decode/implicit_le.rs @@ -0,0 +1,319 @@ +//! Implicit VR Big Endian syntax transfer implementation + +use crate::decode::basic::LittleEndianBasicDecoder; +use crate::decode::*; +use byteordered::byteorder::{ByteOrder, LittleEndian}; +use dicom_core::dictionary::{DataDictionary, DictionaryEntry}; +use dicom_core::header::{DataElementHeader, Length, SequenceItemHeader}; +use dicom_core::{Tag, VR}; +use dicom_dictionary_std::StandardDataDictionary; +use snafu::ResultExt; +use std::fmt; +use std::io::Read; + +/// An ImplicitVRLittleEndianDecoder which uses the standard data dictionary. +pub type StandardImplicitVRLittleEndianDecoder = + ImplicitVRLittleEndianDecoder; + +/// A data element decoder for the Explicit VR Little Endian transfer syntax. +/// This type contains a reference to an attribute dictionary for resolving +/// value representations. +pub struct ImplicitVRLittleEndianDecoder { + dict: D, + basic: LittleEndianBasicDecoder, +} + +impl fmt::Debug for ImplicitVRLittleEndianDecoder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ImplicitVRLittleEndianDecoder") + .field("dict", &"«omitted»") + .field("basic", &self.basic) + .finish() + } +} + +impl ImplicitVRLittleEndianDecoder { + /// Retrieve this decoder using the standard data dictionary. + pub fn with_std_dict() -> Self { + ImplicitVRLittleEndianDecoder { + dict: StandardDataDictionary, + basic: LittleEndianBasicDecoder, + } + } + + /// Retrieve this decoder using the standard data dictionary. + pub fn new() -> Self { + Self::with_std_dict() + } +} + +impl Default for ImplicitVRLittleEndianDecoder { + fn default() -> Self { + ImplicitVRLittleEndianDecoder::with_std_dict() + } +} + +impl ImplicitVRLittleEndianDecoder +where + D: DataDictionary, +{ + /// Retrieve this decoder using a custom data dictionary. + pub fn with_dict(dictionary: D) -> Self { + ImplicitVRLittleEndianDecoder { + dict: dictionary, + basic: LittleEndianBasicDecoder, + } + } +} + +impl Decode for ImplicitVRLittleEndianDecoder +where + D: DataDictionary, +{ + fn decode_header(&self, mut source: &mut S) -> Result<(DataElementHeader, usize)> + where + S: ?Sized + Read, + { + // retrieve tag + let tag = self.basic.decode_tag(&mut source).context(ReadHeaderTag)?; + + let mut buf = [0u8; 4]; + source.read_exact(&mut buf).context(ReadLength)?; + let len = LittleEndian::read_u32(&buf); + + // VR resolution is done with the help of the data dictionary. + // In Implicit VR Little Endian, the VR of OB may not be used for Pixel + // Data (7FE0,0010). This edge case is addressed manually. + let vr = if tag == Tag(0x7FE0, 0x0010) { + VR::OW + } else { + self.dict + .by_tag(tag) + .map(|entry| entry.vr()) + .unwrap_or(VR::UN) + }; + Ok((DataElementHeader::new(tag, vr, Length(len)), 8)) + } + + fn decode_item_header(&self, mut source: &mut S) -> Result + where + S: ?Sized + Read, + { + let mut buf = [0u8; 4]; + + // retrieve tag + let tag = self.basic.decode_tag(&mut source).context(ReadHeaderTag)?; + + source.read_exact(&mut buf).context(ReadLength)?; + let len = LittleEndian::read_u32(&buf); + SequenceItemHeader::new(tag, Length(len)).context(BadSequenceHeader) + } + + #[inline] + fn decode_tag(&self, source: &mut S) -> Result + where + S: ?Sized + Read, + { + self.basic.decode_tag(source).context(ReadTag) + } +} + +impl DecodeFrom for ImplicitVRLittleEndianDecoder +where + S: Read, + D: DataDictionary, +{ + #[inline] + fn decode_header(&self, source: &mut S) -> Result<(DataElementHeader, usize)> { + Decode::decode_header(self, source) + } + + #[inline] + fn decode_item_header(&self, source: &mut S) -> Result { + Decode::decode_item_header(self, source) + } + + #[inline] + fn decode_tag(&self, source: &mut S) -> Result { + Decode::decode_tag(self, source) + } +} + +#[cfg(test)] +mod tests { + use super::ImplicitVRLittleEndianDecoder; + use crate::decode::Decode; + use dicom_core::dictionary::stub::StubDataDictionary; + use dicom_core::header::{HasLength, Header, Length, VR}; + use std::io::{Cursor, Read, Seek, SeekFrom}; + + // manually crafting some DICOM data elements + // Tag: (0002,0002) Media Storage SOP Class UID + // Length: 26 + // Value: "1.2.840.10008.5.1.4.1.1.1" (with 1 padding '\0') + // -- + // Tag: (0002,0010) Transfer Syntax UID + // Length: 20 + // Value: "1.2.840.10008.1.2.1" (w 1 padding '\0') == ExplicitVRLittleEndian + // -- + const RAW: &'static [u8; 62] = &[ + 0x02, 0x00, 0x02, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30, + 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, + 0x31, 0x2e, 0x31, 0x00, 0x02, 0x00, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x31, 0x2e, 0x32, + 0x2e, 0x38, 0x34, 0x30, 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, + 0x31, 0x00, + ]; + + const DICT: &'static StubDataDictionary = &StubDataDictionary; + + #[test] + fn implicit_vr_le() { + let reader = ImplicitVRLittleEndianDecoder::with_dict(DICT); + let mut cursor = Cursor::new(RAW.as_ref()); + { + // read first element + let (elem, bytes_read) = reader + .decode_header(&mut cursor) + .expect("should find an element"); + assert_eq!(elem.tag(), (0x0002, 0x0002)); + assert_eq!(elem.vr(), VR::UN); + assert_eq!(elem.length(), Length(26)); + assert_eq!(bytes_read, 8); + // read only half of the data + let mut buffer: Vec = Vec::with_capacity(13); + buffer.resize(13, 0); + cursor + .read_exact(buffer.as_mut_slice()) + .expect("should read it fine"); + assert_eq!(buffer.as_slice(), b"1.2.840.10008".as_ref()); + } + // cursor should now be @ #21 (there is no automatic skipping) + assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 21); + // cursor should now be @ #34 after skipping + assert_eq!(cursor.seek(SeekFrom::Current(13)).unwrap(), 34); + { + // read second element + let (elem, _bytes_read) = reader + .decode_header(&mut cursor) + .expect("should find an element"); + assert_eq!(elem.tag(), (0x0002, 0x0010)); + assert_eq!(elem.vr(), VR::UN); + assert_eq!(elem.length(), Length(20)); + // read all data + let mut buffer: Vec = Vec::with_capacity(20); + buffer.resize(20, 0); + cursor + .read_exact(buffer.as_mut_slice()) + .expect("should read it fine"); + assert_eq!(buffer.as_slice(), b"1.2.840.10008.1.2.1\0".as_ref()); + } + } + + #[test] + fn implicit_vr_le_with_standard_dictionary() { + let reader = ImplicitVRLittleEndianDecoder::with_std_dict(); + let mut cursor = Cursor::new(RAW.as_ref()); + { + // read first element + let (elem, _bytes_read) = reader + .decode_header(&mut cursor) + .expect("should find an element"); + assert_eq!(elem.tag(), (2, 2)); + assert_eq!(elem.vr(), VR::UI); + assert_eq!(elem.length(), Length(26)); + // cursor should be @ #8 + assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 8); + // don't read any data, just skip + // cursor should be @ #34 after skipping + assert_eq!( + cursor + .seek(SeekFrom::Current(elem.length().0 as i64)) + .unwrap(), + 34 + ); + } + { + // read second element + let (elem, _bytes_read) = reader + .decode_header(&mut cursor) + .expect("should find an element"); + assert_eq!(elem.tag(), (2, 16)); + assert_eq!(elem.vr(), VR::UI); + assert_eq!(elem.length(), Length(20)); + // read all data + let mut buffer: Vec = Vec::with_capacity(20); + buffer.resize(20, 0); + cursor + .read_exact(buffer.as_mut_slice()) + .expect("should read it fine"); + assert_eq!(buffer.as_slice(), b"1.2.840.10008.1.2.1\0".as_ref()); + } + } + + // manually crafting some DICOM sequence/item delimiters + // Tag: (0008,103F) Series Description Code Sequence + // Implicit VR: SQ + // Reserved bytes: 0x0000 + // Length: 0xFFFF_FFFF + // -- + // Tag: (FFFE,E000) Item + // Length: 0xFFFF_FFFF (unspecified) + // -- + // Tag: (FFFE,E00D) Item Delimitation Item + // Length: 0 + // -- + // Tag: (FFFE,E0DD) Sequence Delimitation Item + // Length: 0 + // -- + const RAW_SEQUENCE_ITEMS: &'static [u8] = &[ + 0x08, 0x00, 0x3F, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x00, 0xE0, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0x0D, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xDD, 0xE0, 0x00, 0x00, + 0x00, 0x00, + ]; + + #[test] + fn decode_items() { + let dec = ImplicitVRLittleEndianDecoder::default(); + let mut cursor = Cursor::new(RAW_SEQUENCE_ITEMS); + { + // read first element + let (elem, bytes_read) = dec + .decode_header(&mut cursor) + .expect("should find an element header"); + assert_eq!(elem.tag(), (8, 0x103F)); + assert_eq!(elem.vr(), VR::SQ); + assert!(elem.length().is_undefined()); + assert_eq!(bytes_read, 8); + } + // cursor should now be @ #8 + assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 8); + { + let elem = dec + .decode_item_header(&mut cursor) + .expect("should find an item header"); + assert!(elem.is_item()); + assert_eq!(elem.tag(), (0xFFFE, 0xE000)); + assert!(elem.length().is_undefined()); + } + // cursor should now be @ #16 + assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 16); + { + let elem = dec + .decode_item_header(&mut cursor) + .expect("should find an item header"); + assert!(elem.is_item_delimiter()); + assert_eq!(elem.tag(), (0xFFFE, 0xE00D)); + assert_eq!(elem.length(), Length(0)); + } + // cursor should now be @ #24 + assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 24); + { + let elem = dec + .decode_item_header(&mut cursor) + .expect("should find an item header"); + assert!(elem.is_sequence_delimiter()); + assert_eq!(elem.tag(), (0xFFFE, 0xE0DD)); + assert_eq!(elem.length(), Length(0)); + } + } +} diff --git a/encoding/src/decode/mod.rs b/encoding/src/decode/mod.rs index 8d81a3b5..24acdb24 100644 --- a/encoding/src/decode/mod.rs +++ b/encoding/src/decode/mod.rs @@ -1,19 +1,69 @@ //! This module contains all DICOM data element decoding logic. -use crate::error::Result; -use crate::transfer_syntax::explicit_le::ExplicitVRLittleEndianDecoder; -use crate::transfer_syntax::implicit_le::{ - ImplicitVRLittleEndianDecoder, StandardImplicitVRLittleEndianDecoder, -}; +use self::explicit_le::ExplicitVRLittleEndianDecoder; +use self::implicit_le::{ImplicitVRLittleEndianDecoder, StandardImplicitVRLittleEndianDecoder}; use byteordered::Endianness; use dicom_core::header::{DataElementHeader, SequenceItemHeader}; use dicom_core::Tag; -use std::io::Read; +use snafu::{Backtrace, Snafu}; +use std::io::{self, Read}; pub mod basic; -#[deprecated] +pub mod explicit_be; +pub mod explicit_le; +pub mod implicit_le; + +#[deprecated(since = "0.3.0")] pub use dicom_core::value::deserialize as primitive_value; +/// Module-level error type: +/// for errors which may occur while decoding DICOM data. +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum Error { + #[snafu(display("Failed to read the beginning (tag) of the header"))] + ReadHeaderTag { + backtrace: Option, + source: io::Error, + }, + #[snafu(display("Failed to read the item header"))] + ReadItemHeader { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to read the header's item length field"))] + ReadItemLength { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to read the header's tag field"))] + ReadTag { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to read the header's reserved bytes"))] + ReadReserved { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to read the header's element length field"))] + ReadLength { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to read the header's value representation"))] + ReadVr { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Bad sequence item header"))] + BadSequenceHeader { + source: dicom_core::header::SequenceItemHeaderError, + }, +} + +pub type Result = std::result::Result; + /** Obtain the default data element decoder. * According to the standard, data elements are encoded in Implicit * VR Little Endian by default. @@ -43,47 +93,47 @@ pub trait BasicDecode { fn endianness(&self) -> Endianness; /// Decode an unsigned short value from the given source. - fn decode_us(&self, source: S) -> Result + fn decode_us(&self, source: S) -> std::io::Result where S: Read; /// Decode an unsigned long value from the given source. - fn decode_ul(&self, source: S) -> Result + fn decode_ul(&self, source: S) -> std::io::Result where S: Read; /// Decode an unsigned very long value from the given source. - fn decode_uv(&self, source: S) -> Result + fn decode_uv(&self, source: S) -> std::io::Result where S: Read; /// Decode a signed short value from the given source. - fn decode_ss(&self, source: S) -> Result + fn decode_ss(&self, source: S) -> std::io::Result where S: Read; /// Decode a signed long value from the given source. - fn decode_sl(&self, source: S) -> Result + fn decode_sl(&self, source: S) -> std::io::Result where S: Read; /// Decode a signed very long value from the given source. - fn decode_sv(&self, source: S) -> Result + fn decode_sv(&self, source: S) -> std::io::Result where S: Read; /// Decode a single precision float value from the given source. - fn decode_fl(&self, source: S) -> Result + fn decode_fl(&self, source: S) -> std::io::Result where S: Read; /// Decode a double precision float value from the given source. - fn decode_fd(&self, source: S) -> Result + fn decode_fd(&self, source: S) -> std::io::Result where S: Read; /// Decode a DICOM attribute tag from the given source. - fn decode_tag(&self, mut source: S) -> Result + fn decode_tag(&self, mut source: S) -> std::io::Result where S: Read, { @@ -101,63 +151,63 @@ where self.as_ref().endianness() } - fn decode_us(&self, source: S) -> Result + fn decode_us(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_us(source) } - fn decode_ul(&self, source: S) -> Result + fn decode_ul(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_ul(source) } - fn decode_uv(&self, source: S) -> Result + fn decode_uv(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_uv(source) } - fn decode_ss(&self, source: S) -> Result + fn decode_ss(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_ss(source) } - fn decode_sl(&self, source: S) -> Result + fn decode_sl(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_sl(source) } - fn decode_sv(&self, source: S) -> Result + fn decode_sv(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_sv(source) } - fn decode_fl(&self, source: S) -> Result + fn decode_fl(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_fl(source) } - fn decode_fd(&self, source: S) -> Result + fn decode_fd(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_fd(source) } - fn decode_tag(&self, source: S) -> Result + fn decode_tag(&self, source: S) -> std::io::Result where S: Read, { @@ -173,63 +223,63 @@ where (*self).endianness() } - fn decode_us(&self, source: S) -> Result + fn decode_us(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_us(source) } - fn decode_ul(&self, source: S) -> Result + fn decode_ul(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_ul(source) } - fn decode_uv(&self, source: S) -> Result + fn decode_uv(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_uv(source) } - fn decode_ss(&self, source: S) -> Result + fn decode_ss(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_ss(source) } - fn decode_sl(&self, source: S) -> Result + fn decode_sl(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_sl(source) } - fn decode_sv(&self, source: S) -> Result + fn decode_sv(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_sv(source) } - fn decode_fl(&self, source: S) -> Result + fn decode_fl(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_fl(source) } - fn decode_fd(&self, source: S) -> Result + fn decode_fd(&self, source: S) -> std::io::Result where S: Read, { (**self).decode_fd(source) } - fn decode_tag(&self, source: S) -> Result + fn decode_tag(&self, source: S) -> std::io::Result where S: Read, { diff --git a/encoding/src/encode/basic.rs b/encoding/src/encode/basic.rs index a8dc95ef..a0b5e91e 100644 --- a/encoding/src/encode/basic.rs +++ b/encoding/src/encode/basic.rs @@ -2,10 +2,11 @@ //! use super::BasicEncode; -use crate::error::Result; use byteordered::{ByteOrdered, Endianness}; use std::io::Write; +type Result = std::io::Result; + /// A basic encoder of primitive elements in little endian. #[derive(Debug, Default, Clone, PartialEq)] pub struct LittleEndianBasicEncoder; diff --git a/encoding/src/encode/explicit_be.rs b/encoding/src/encode/explicit_be.rs new file mode 100644 index 00000000..a4803133 --- /dev/null +++ b/encoding/src/encode/explicit_be.rs @@ -0,0 +1,288 @@ +//! Explicit VR Big Endian syntax transfer implementation. + +use crate::encode::basic::BigEndianBasicEncoder; +use crate::encode::*; +use byteordered::byteorder::{BigEndian, ByteOrder}; +use byteordered::Endianness; +use dicom_core::header::{DataElementHeader, HasLength, Header}; +use dicom_core::{PrimitiveValue, Tag, VR}; +use std::io::{self, Write}; + +type Result = std::result::Result; + +/// A concrete encoder for the transfer syntax ExplicitVRBigEndian +#[derive(Debug, Default, Clone)] +pub struct ExplicitVRBigEndianEncoder { + basic: BigEndianBasicEncoder, +} + +impl BasicEncode for ExplicitVRBigEndianEncoder { + fn endianness(&self) -> Endianness { + Endianness::Big + } + + fn encode_us(&self, to: S, value: u16) -> io::Result<()> + where + S: Write, + { + self.basic.encode_us(to, value) + } + + fn encode_ul(&self, to: S, value: u32) -> io::Result<()> + where + S: Write, + { + self.basic.encode_ul(to, value) + } + + fn encode_uv(&self, to: S, value: u64) -> io::Result<()> + where + S: Write, + { + self.basic.encode_uv(to, value) + } + + fn encode_ss(&self, to: S, value: i16) -> io::Result<()> + where + S: Write, + { + self.basic.encode_ss(to, value) + } + + fn encode_sl(&self, to: S, value: i32) -> io::Result<()> + where + S: Write, + { + self.basic.encode_sl(to, value) + } + + fn encode_sv(&self, to: S, value: i64) -> io::Result<()> + where + S: Write, + { + self.basic.encode_sv(to, value) + } + + fn encode_fl(&self, to: S, value: f32) -> io::Result<()> + where + S: Write, + { + self.basic.encode_fl(to, value) + } + + fn encode_fd(&self, to: S, value: f64) -> io::Result<()> + where + S: Write, + { + self.basic.encode_fd(to, value) + } +} + +impl Encode for ExplicitVRBigEndianEncoder { + fn encode_tag(&self, mut to: W, tag: Tag) -> Result<()> + where + W: Write, + { + let mut buf = [0u8, 4]; + BigEndian::write_u16(&mut buf[..], tag.group()); + BigEndian::write_u16(&mut buf[2..], tag.element()); + to.write_all(&buf).context(WriteTag) + } + + fn encode_element_header(&self, mut to: W, de: DataElementHeader) -> Result + where + W: Write, + { + match de.vr() { + VR::OB + | VR::OD + | VR::OF + | VR::OL + | VR::OW + | VR::SQ + | VR::UC + | VR::UR + | VR::UT + | VR::UN => { + let mut buf = [0u8; 12]; + BigEndian::write_u16(&mut buf[0..], de.tag().group()); + BigEndian::write_u16(&mut buf[2..], de.tag().element()); + let vr_bytes = de.vr().to_bytes(); + buf[4] = vr_bytes[0]; + buf[5] = vr_bytes[1]; + // buf[6..8] is kept zero'd + BigEndian::write_u32(&mut buf[8..], de.length().0); + to.write_all(&buf).context(WriteHeader)?; + + Ok(12) + } + _ => { + let mut buf = [0u8; 8]; + BigEndian::write_u16(&mut buf[0..], de.tag().group()); + BigEndian::write_u16(&mut buf[2..], de.tag().element()); + let vr_bytes = de.vr().to_bytes(); + buf[4] = vr_bytes[0]; + buf[5] = vr_bytes[1]; + BigEndian::write_u16(&mut buf[6..], de.length().0 as u16); + to.write_all(&buf).context(WriteHeader)?; + + Ok(8) + } + } + } + + fn encode_item_header(&self, mut to: W, len: u32) -> Result<()> + where + W: Write, + { + let mut buf = [0u8; 8]; + BigEndian::write_u16(&mut buf, 0xFFFE); + BigEndian::write_u16(&mut buf[2..], 0xE000); + BigEndian::write_u32(&mut buf[4..], len); + to.write_all(&buf).context(WriteItemHeader) + } + + fn encode_item_delimiter(&self, mut to: W) -> Result<()> + where + W: Write, + { + let mut buf = [0u8; 8]; + BigEndian::write_u16(&mut buf, 0xFFFE); + BigEndian::write_u16(&mut buf[2..], 0xE00D); + // remaining bytes are already zero, so it's ready to write + to.write_all(&buf).context(WriteItemDelimiter) + } + + fn encode_sequence_delimiter(&self, mut to: W) -> Result<()> + where + W: Write, + { + let mut buf = [0u8; 8]; + BigEndian::write_u16(&mut buf, 0xFFFE); + BigEndian::write_u16(&mut buf[2..], 0xE0DD); + // remaining bytes are already zero, so it's ready to write + to.write_all(&buf).context(WriteSequenceDelimiter) + } + + fn encode_primitive(&self, to: W, value: &PrimitiveValue) -> Result + where + W: Write, + { + self.basic.encode_primitive(to, value) + } +} + +#[cfg(test)] +mod tests { + use super::ExplicitVRBigEndianEncoder; + use crate::encode::Encode; + use dicom_core::header::{DataElementHeader, Length}; + use dicom_core::{Tag, VR}; + use std::io::{Cursor, Write}; + + type Result = std::result::Result<(), Box>; + + // manually crafting some DICOM data elements + // Tag: (0002,0002) Media Storage SOP Class UID + // VR: UI + // Length: 26 + // Value: "1.2.840.10008.5.1.4.1.1.1" (with 1 padding '\0') + // -- + // Tag: (0002,0010) Transfer Syntax UID + // VR: UI + // Length: 20 + // Value: "1.2.840.10008.1.2.1" (w 1 padding '\0') == ExplicitVRLittleEndian + // -- + const RAW: &'static [u8; 62] = &[ + 0x00, 0x02, 0x00, 0x02, 0x55, 0x49, 0x00, 0x1a, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30, + 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, + 0x31, 0x2e, 0x31, 0x00, 0x00, 0x02, 0x00, 0x10, 0x55, 0x49, 0x00, 0x14, 0x31, 0x2e, 0x32, + 0x2e, 0x38, 0x34, 0x30, 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, + 0x31, 0x00, + ]; + + #[test] + fn encode_explicit_vr_be() { + let mut buf = [0u8; 62]; + { + let enc = ExplicitVRBigEndianEncoder::default(); + let mut writer = Cursor::new(&mut buf[..]); + + // encode first element + let de = DataElementHeader::new(Tag(0x0002, 0x0002), VR::UI, Length(26)); + let len = enc + .encode_element_header(&mut writer, de) + .expect("should write it fine"); + assert_eq!(len, 8); + writer + .write_all(b"1.2.840.10008.5.1.4.1.1.1\0".as_ref()) + .expect("should write the value fine"); + } + assert_eq!(&buf[0..8], &RAW[0..8]); + { + let enc = ExplicitVRBigEndianEncoder::default(); + let mut writer = Cursor::new(&mut buf[34..]); + + // encode second element + let de = DataElementHeader::new(Tag(0x0002, 0x0010), VR::UI, Length(20)); + let len = enc + .encode_element_header(&mut writer, de) + .expect("should write it fine"); + assert_eq!(len, 8); + writer + .write_all(b"1.2.840.10008.1.2.1\0".as_ref()) + .expect("should write the value fine"); + } + assert_eq!(&buf[34..42], &RAW[34..42]); + + assert_eq!(&buf[..], &RAW[..]); + } + + // manually crafting some DICOM sequence/item delimiters + // Tag: (0008,103F) Series Description Code Sequence + // VR: SQ + // Reserved bytes: 0x0000 + // Length: 0xFFFF_FFFF + // -- + // Tag: (FFFE,E000) Item + // Length: 0xFFFF_FFFF (unspecified) + // -- + // Tag: (FFFE,E00D) Item Delimitation Item + // Length: 0 + // -- + // Tag: (FFFE,E0DD) Sequence Delimitation Item + // Length: 0 + // -- + const RAW_SEQUENCE_ITEMS: &'static [u8] = &[ + 0x00, 0x08, 0x10, 0x3F, b'S', b'Q', 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xE0, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xE0, 0x0D, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, + 0xE0, 0xDD, 0x00, 0x00, 0x00, 0x00, + ]; + + #[test] + fn encode_items() -> Result { + let enc = ExplicitVRBigEndianEncoder::default(); + let mut out = Vec::new(); + + { + let bytes_written = enc.encode_element_header( + &mut out, + DataElementHeader::new(Tag(0x0008, 0x103F), VR::SQ, Length::UNDEFINED), + )?; + assert_eq!(bytes_written, 12); + } + assert_eq!(out.len(), 12); + + enc.encode_item_header(&mut out, Length::UNDEFINED.0)?; + assert_eq!(out.len(), 20); + + enc.encode_item_delimiter(&mut out)?; + assert_eq!(out.len(), 28); + + enc.encode_sequence_delimiter(&mut out)?; + + assert_eq!(&out[..], RAW_SEQUENCE_ITEMS); + + Ok(()) + } +} diff --git a/encoding/src/encode/explicit_le.rs b/encoding/src/encode/explicit_le.rs new file mode 100644 index 00000000..b15946cc --- /dev/null +++ b/encoding/src/encode/explicit_le.rs @@ -0,0 +1,283 @@ +//! Explicit VR Little Endian syntax transfer implementation + +use crate::encode::basic::LittleEndianBasicEncoder; +use crate::encode::*; +use byteordered::byteorder::{ByteOrder, LittleEndian}; +use byteordered::Endianness; +use dicom_core::header::{DataElementHeader, HasLength, Header}; +use dicom_core::{PrimitiveValue, Tag, VR}; +use snafu::ResultExt; +use std::io::{self, Write}; + +/// A concrete encoder for the transfer syntax ExplicitVRLittleEndian +#[derive(Debug, Default, Clone)] +pub struct ExplicitVRLittleEndianEncoder { + basic: LittleEndianBasicEncoder, +} + +impl BasicEncode for ExplicitVRLittleEndianEncoder { + fn endianness(&self) -> Endianness { + Endianness::Little + } + + fn encode_us(&self, to: S, value: u16) -> io::Result<()> + where + S: Write, + { + self.basic.encode_us(to, value) + } + + fn encode_ul(&self, to: S, value: u32) -> io::Result<()> + where + S: Write, + { + self.basic.encode_ul(to, value) + } + + fn encode_uv(&self, to: S, value: u64) -> io::Result<()> + where + S: Write, + { + self.basic.encode_uv(to, value) + } + + fn encode_ss(&self, to: S, value: i16) -> io::Result<()> + where + S: Write, + { + self.basic.encode_ss(to, value) + } + + fn encode_sl(&self, to: S, value: i32) -> io::Result<()> + where + S: Write, + { + self.basic.encode_sl(to, value) + } + + fn encode_sv(&self, to: S, value: i64) -> io::Result<()> + where + S: Write, + { + self.basic.encode_sv(to, value) + } + + fn encode_fl(&self, to: S, value: f32) -> io::Result<()> + where + S: Write, + { + self.basic.encode_fl(to, value) + } + + fn encode_fd(&self, to: S, value: f64) -> io::Result<()> + where + S: Write, + { + self.basic.encode_fd(to, value) + } +} + +impl Encode for ExplicitVRLittleEndianEncoder { + fn encode_tag(&self, mut to: W, tag: Tag) -> Result<()> + where + W: Write, + { + let mut buf = [0u8, 4]; + LittleEndian::write_u16(&mut buf[..], tag.group()); + LittleEndian::write_u16(&mut buf[2..], tag.element()); + to.write_all(&buf).context(WriteTag) + } + + fn encode_element_header(&self, mut to: W, de: DataElementHeader) -> Result + where + W: Write, + { + match de.vr() { + VR::OB + | VR::OD + | VR::OF + | VR::OL + | VR::OW + | VR::SQ + | VR::UC + | VR::UR + | VR::UT + | VR::UN => { + let mut buf = [0u8; 12]; + LittleEndian::write_u16(&mut buf[0..], de.tag().group()); + LittleEndian::write_u16(&mut buf[2..], de.tag().element()); + let vr_bytes = de.vr().to_bytes(); + buf[4] = vr_bytes[0]; + buf[5] = vr_bytes[1]; + // buf[6..8] is kept zero'd + LittleEndian::write_u32(&mut buf[8..], de.length().0); + to.write_all(&buf).context(WriteHeader)?; + Ok(12) + } + _ => { + let mut buf = [0u8; 8]; + LittleEndian::write_u16(&mut buf[0..], de.tag().group()); + LittleEndian::write_u16(&mut buf[2..], de.tag().element()); + let vr_bytes = de.vr().to_bytes(); + buf[4] = vr_bytes[0]; + buf[5] = vr_bytes[1]; + LittleEndian::write_u16(&mut buf[6..], de.length().0 as u16); + to.write_all(&buf).context(WriteHeader)?; + Ok(8) + } + } + } + + fn encode_item_header(&self, mut to: W, len: u32) -> Result<()> + where + W: Write, + { + let mut buf = [0u8; 8]; + LittleEndian::write_u16(&mut buf, 0xFFFE); + LittleEndian::write_u16(&mut buf[2..], 0xE000); + LittleEndian::write_u32(&mut buf[4..], len); + to.write_all(&buf).context(WriteItemHeader) + } + + fn encode_item_delimiter(&self, mut to: W) -> Result<()> + where + W: Write, + { + let mut buf = [0u8; 8]; + LittleEndian::write_u16(&mut buf, 0xFFFE); + LittleEndian::write_u16(&mut buf[2..], 0xE00D); + to.write_all(&buf).context(WriteItemDelimiter) + } + + fn encode_sequence_delimiter(&self, mut to: W) -> Result<()> + where + W: Write, + { + let mut buf = [0u8; 8]; + LittleEndian::write_u16(&mut buf, 0xFFFE); + LittleEndian::write_u16(&mut buf[2..], 0xE0DD); + to.write_all(&buf).context(WriteSequenceDelimiter) + } + + fn encode_primitive(&self, to: W, value: &PrimitiveValue) -> Result + where + W: Write, + { + self.basic.encode_primitive(to, value) + } +} + +#[cfg(test)] +mod tests { + use super::ExplicitVRLittleEndianEncoder; + use crate::encode::Encode; + use dicom_core::header::{DataElementHeader, Length}; + use dicom_core::{Tag, VR}; + use std::io::{Cursor, Write}; + + type Result = std::result::Result<(), Box>; + + // manually crafting some DICOM data elements + // Tag: (0002,0002) Media Storage SOP Class UID + // VR: UI + // Length: 26 + // Value: "1.2.840.10008.5.1.4.1.1.1\0" + // -- + // Tag: (0002,0010) Transfer Syntax UID + // VR: UI + // Length: 20 + // Value: "1.2.840.10008.1.2.1\0" == ExplicitVRLittleEndian + // -- + const RAW: &'static [u8; 62] = &[ + 0x02, 0x00, 0x02, 0x00, 0x55, 0x49, 0x1a, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30, + 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, + 0x31, 0x2e, 0x31, 0x00, 0x02, 0x00, 0x10, 0x00, 0x55, 0x49, 0x14, 0x00, 0x31, 0x2e, 0x32, + 0x2e, 0x38, 0x34, 0x30, 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, + 0x31, 0x00, + ]; + + #[test] + fn encode_data_elements() { + let mut buf = [0u8; 62]; + { + let enc = ExplicitVRLittleEndianEncoder::default(); + let mut writer = Cursor::new(&mut buf[..]); + + // encode first element + let de = DataElementHeader::new(Tag(0x0002, 0x0002), VR::UI, Length(26)); + let len = enc + .encode_element_header(&mut writer, de) + .expect("should write it fine"); + assert_eq!(len, 8); + writer + .write_all(b"1.2.840.10008.5.1.4.1.1.1\0".as_ref()) + .expect("should write the value fine"); + } + assert_eq!(&buf[0..8], &RAW[0..8]); + { + let enc = ExplicitVRLittleEndianEncoder::default(); + let mut writer = Cursor::new(&mut buf[34..]); + + // encode second element + let de = DataElementHeader::new(Tag(0x0002, 0x0010), VR::UI, Length(20)); + let len = enc + .encode_element_header(&mut writer, de) + .expect("should write it fine"); + assert_eq!(len, 8); + writer + .write_all(b"1.2.840.10008.1.2.1\0".as_ref()) + .expect("should write the value fine"); + } + assert_eq!(&buf[34..42], &RAW[34..42]); + + assert_eq!(&buf[..], &RAW[..]); + } + + // manually crafting some DICOM sequence/item delimiters + // Tag: (0008,103F) Series Description Code Sequence + // VR: SQ + // Reserved bytes: 0x0000 + // Length: 0xFFFF_FFFF + // -- + // Tag: (FFFE,E000) Item + // Length: 0xFFFF_FFFF (unspecified) + // -- + // Tag: (FFFE,E00D) Item Delimitation Item + // Length: 0 + // -- + // Tag: (FFFE,E0DD) Sequence Delimitation Item + // Length: 0 + // -- + const RAW_SEQUENCE_ITEMS: &'static [u8] = &[ + 0x08, 0x00, 0x3F, 0x10, b'S', b'Q', 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x00, + 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x0D, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, + 0xDD, 0xE0, 0x00, 0x00, 0x00, 0x00, + ]; + + #[test] + fn encode_items() -> Result { + let enc = ExplicitVRLittleEndianEncoder::default(); + let mut out = Vec::new(); + + { + let bytes_written = enc.encode_element_header( + &mut out, + DataElementHeader::new(Tag(0x0008, 0x103F), VR::SQ, Length::UNDEFINED), + )?; + assert_eq!(bytes_written, 12); + } + assert_eq!(out.len(), 12); + + enc.encode_item_header(&mut out, Length::UNDEFINED.0)?; + assert_eq!(out.len(), 20); + + enc.encode_item_delimiter(&mut out)?; + assert_eq!(out.len(), 28); + + enc.encode_sequence_delimiter(&mut out)?; + + assert_eq!(&out[..], RAW_SEQUENCE_ITEMS); + + Ok(()) + } +} diff --git a/encoding/src/encode/implicit_le.rs b/encoding/src/encode/implicit_le.rs new file mode 100644 index 00000000..c657d4e8 --- /dev/null +++ b/encoding/src/encode/implicit_le.rs @@ -0,0 +1,251 @@ +//! Implicit VR Big Endian syntax transfer implementation + +use crate::encode::basic::LittleEndianBasicEncoder; +use crate::encode::*; +use byteordered::byteorder::{ByteOrder, LittleEndian}; +use byteordered::Endianness; +use dicom_core::header::{DataElementHeader, HasLength, Header}; +use dicom_core::{PrimitiveValue, Tag}; +use std::io::{self, Write}; + +/// A concrete encoder for the transfer syntax ImplicitVRLittleEndian +#[derive(Debug, Default, Clone)] +pub struct ImplicitVRLittleEndianEncoder { + basic: LittleEndianBasicEncoder, +} + +impl BasicEncode for ImplicitVRLittleEndianEncoder { + fn endianness(&self) -> Endianness { + Endianness::Little + } + + fn encode_us(&self, to: S, value: u16) -> io::Result<()> + where + S: Write, + { + self.basic.encode_us(to, value) + } + + fn encode_ul(&self, to: S, value: u32) -> io::Result<()> + where + S: Write, + { + self.basic.encode_ul(to, value) + } + + fn encode_uv(&self, to: S, value: u64) -> io::Result<()> + where + S: Write, + { + self.basic.encode_uv(to, value) + } + + fn encode_ss(&self, to: S, value: i16) -> io::Result<()> + where + S: Write, + { + self.basic.encode_ss(to, value) + } + + fn encode_sl(&self, to: S, value: i32) -> io::Result<()> + where + S: Write, + { + self.basic.encode_sl(to, value) + } + + fn encode_sv(&self, to: S, value: i64) -> io::Result<()> + where + S: Write, + { + self.basic.encode_sv(to, value) + } + + fn encode_fl(&self, to: S, value: f32) -> io::Result<()> + where + S: Write, + { + self.basic.encode_fl(to, value) + } + + fn encode_fd(&self, to: S, value: f64) -> io::Result<()> + where + S: Write, + { + self.basic.encode_fd(to, value) + } +} + +impl Encode for ImplicitVRLittleEndianEncoder { + fn encode_tag(&self, mut to: W, tag: Tag) -> Result<()> + where + W: Write, + { + let mut buf = [0u8, 4]; + LittleEndian::write_u16(&mut buf[..], tag.group()); + LittleEndian::write_u16(&mut buf[2..], tag.element()); + to.write_all(&buf).context(WriteTag) + } + + fn encode_element_header(&self, mut to: W, de: DataElementHeader) -> Result + where + W: Write, + { + let mut buf = [0u8; 8]; + LittleEndian::write_u16(&mut buf[0..], de.tag().group()); + LittleEndian::write_u16(&mut buf[2..], de.tag().element()); + LittleEndian::write_u32(&mut buf[4..], de.length().0); + to.write_all(&buf).context(WriteHeader)?; + Ok(8) + } + + fn encode_item_header(&self, mut to: W, len: u32) -> Result<()> + where + W: Write, + { + let mut buf = [0u8; 8]; + LittleEndian::write_u16(&mut buf, 0xFFFE); + LittleEndian::write_u16(&mut buf[2..], 0xE000); + LittleEndian::write_u32(&mut buf[4..], len); + to.write_all(&buf).context(WriteItemHeader) + } + + fn encode_item_delimiter(&self, mut to: W) -> Result<()> + where + W: Write, + { + let mut buf = [0u8; 8]; + LittleEndian::write_u16(&mut buf, 0xFFFE); + LittleEndian::write_u16(&mut buf[2..], 0xE00D); + to.write_all(&buf).context(WriteItemDelimiter) + } + + fn encode_sequence_delimiter(&self, mut to: W) -> Result<()> + where + W: Write, + { + let mut buf = [0u8; 8]; + LittleEndian::write_u16(&mut buf, 0xFFFE); + LittleEndian::write_u16(&mut buf[2..], 0xE0DD); + to.write_all(&buf).context(WriteSequenceDelimiter) + } + + fn encode_primitive(&self, to: W, value: &PrimitiveValue) -> Result + where + W: Write, + { + self.basic.encode_primitive(to, value) + } +} + +#[cfg(test)] +mod tests { + use super::ImplicitVRLittleEndianEncoder; + use crate::encode::Encode; + use dicom_core::header::{DataElementHeader, Length, Tag, VR}; + use std::io::{Cursor, Write}; + + type Result = std::result::Result<(), Box>; + + // manually crafting some DICOM data elements + // Tag: (0002,0002) Media Storage SOP Class UID + // Length: 26 + // Value: "1.2.840.10008.5.1.4.1.1.1" (with 1 padding '\0') + // -- + // Tag: (0002,0010) Transfer Syntax UID + // Length: 20 + // Value: "1.2.840.10008.1.2.1" (w 1 padding '\0') == ExplicitVRLittleEndian + // -- + const RAW: &'static [u8; 62] = &[ + 0x02, 0x00, 0x02, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30, + 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, + 0x31, 0x2e, 0x31, 0x00, 0x02, 0x00, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x31, 0x2e, 0x32, + 0x2e, 0x38, 0x34, 0x30, 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, + 0x31, 0x00, + ]; + + #[test] + fn encode_implicit_vr_le() { + let mut buf = [0u8; 62]; + { + let enc = ImplicitVRLittleEndianEncoder::default(); + let mut writer = Cursor::new(&mut buf[..]); + + // encode first element + let de = DataElementHeader::new(Tag(0x0002, 0x0002), VR::UI, Length(26)); + let len = enc + .encode_element_header(&mut writer, de) + .expect("should write it fine"); + assert_eq!(len, 8); + writer + .write_all(b"1.2.840.10008.5.1.4.1.1.1\0".as_ref()) + .expect("should write the value fine"); + } + assert_eq!(&buf[0..8], &RAW[0..8]); + { + let enc = ImplicitVRLittleEndianEncoder::default(); + let mut writer = Cursor::new(&mut buf[34..]); + + // encode second element + let de = DataElementHeader::new(Tag(0x0002, 0x0010), VR::UI, Length(20)); + let len = enc + .encode_element_header(&mut writer, de) + .expect("should write it fine"); + assert_eq!(len, 8); + writer + .write_all(b"1.2.840.10008.1.2.1\0".as_ref()) + .expect("should write the value fine"); + } + assert_eq!(&buf[34..42], &RAW[34..42]); + + assert_eq!(&buf[..], &RAW[..]); + } + + // manually crafting some DICOM sequence/item delimiters + // Tag: (0008,103F) Series Description Code Sequence + // VR: SQ + // Reserved bytes: 0x0000 + // Length: 0xFFFF_FFFF + // -- + // Tag: (FFFE,E000) Item + // Length: 0xFFFF_FFFF (unspecified) + // -- + // Tag: (FFFE,E00D) Item Delimitation Item + // Length: 0 + // -- + // Tag: (FFFE,E0DD) Sequence Delimitation Item + // Length: 0 + // -- + const RAW_SEQUENCE_ITEMS: &'static [u8] = &[ + 0x08, 0x00, 0x3F, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x00, 0xE0, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0x0D, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xDD, 0xE0, 0x00, 0x00, + 0x00, 0x00, + ]; + + #[test] + fn encode_items() -> Result { + let enc = ImplicitVRLittleEndianEncoder::default(); + let mut out = Vec::new(); + + { + let bytes_written = enc.encode_element_header( + &mut out, + DataElementHeader::new(Tag(0x0008, 0x103F), VR::SQ, Length::UNDEFINED), + )?; + assert_eq!(bytes_written, 8); + } + assert_eq!(out.len(), 8); + + enc.encode_item_header(&mut out, Length::UNDEFINED.0)?; + assert_eq!(out.len(), 16); + + enc.encode_item_delimiter(&mut out)?; + assert_eq!(out.len(), 24); + + enc.encode_sequence_delimiter(&mut out)?; + + assert_eq!(&out[..], RAW_SEQUENCE_ITEMS,); + + Ok(()) + } +} diff --git a/encoding/src/encode/mod.rs b/encoding/src/encode/mod.rs index de15f952..c10c2b00 100644 --- a/encoding/src/encode/mod.rs +++ b/encoding/src/encode/mod.rs @@ -1,16 +1,94 @@ //! This module contains all DICOM data element encoding logic. -use crate::error::Result; use byteordered::Endianness; use dicom_core::{DataElementHeader, PrimitiveValue, Tag}; +use snafu::{Backtrace, ResultExt, Snafu}; use std::fmt; -use std::io::Write; +use std::io::{self, Write}; use std::marker::PhantomData; pub mod basic; +pub mod explicit_be; +pub mod explicit_le; +pub mod implicit_le; #[deprecated] pub use dicom_core::value::serialize as primitive_value; +/// Module-level error type: +/// for errors which may occur while encoding DICOM data. +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum Error { + #[snafu(display("Failed to write Date value"))] + WriteDate { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to write Time value"))] + WriteTime { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to write DateTime value"))] + WriteDateTime { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to write tag"))] + WriteTag { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to write tag group"))] + WriteTagGroup { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to write tag element"))] + WriteTagElement { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to write item header"))] + WriteItemHeader { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to write element header"))] + WriteHeader { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to write item delimiter"))] + WriteItemDelimiter { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to write sequence delimiter"))] + WriteSequenceDelimiter { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to write {} value", typ))] + WriteBinary { + typ: &'static str, + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to write string value"))] + WriteString { + backtrace: Backtrace, + source: io::Error, + }, + #[snafu(display("Failed to write bytes"))] + WriteBytes { + backtrace: Backtrace, + source: io::Error, + }, +} + +pub type Result = std::result::Result; + /// Type trait for an encoder of basic data properties. /// Unlike `Encode` (and similar to `BasicDecode`), this trait is not object /// safe because it's better to just provide a dynamic implementation. @@ -19,42 +97,42 @@ pub trait BasicEncode { fn endianness(&self) -> Endianness; /// Encode an unsigned short value to the given writer. - fn encode_us(&self, to: W, value: u16) -> Result<()> + fn encode_us(&self, to: W, value: u16) -> io::Result<()> where W: Write; /// Encode an unsigned long value to the given writer. - fn encode_ul(&self, to: W, value: u32) -> Result<()> + fn encode_ul(&self, to: W, value: u32) -> io::Result<()> where W: Write; /// Encode an unsigned very long value to the given writer. - fn encode_uv(&self, to: W, value: u64) -> Result<()> + fn encode_uv(&self, to: W, value: u64) -> io::Result<()> where W: Write; /// Encode a signed short value to the given writer. - fn encode_ss(&self, to: W, value: i16) -> Result<()> + fn encode_ss(&self, to: W, value: i16) -> io::Result<()> where W: Write; /// Encode a signed long value to the given writer. - fn encode_sl(&self, to: W, value: i32) -> Result<()> + fn encode_sl(&self, to: W, value: i32) -> io::Result<()> where W: Write; /// Encode a signed very long value to the given writer. - fn encode_sv(&self, to: W, value: i64) -> Result<()> + fn encode_sv(&self, to: W, value: i64) -> io::Result<()> where W: Write; /// Encode a single precision float value to the given writer. - fn encode_fl(&self, to: W, value: f32) -> Result<()> + fn encode_fl(&self, to: W, value: f32) -> io::Result<()> where W: Write; /// Encode a double precision float value to the given writer. - fn encode_fd(&self, to: W, value: f64) -> Result<()> + fn encode_fd(&self, to: W, value: f64) -> io::Result<()> where W: Write; @@ -82,21 +160,24 @@ pub trait BasicEncode { match value { Empty => Ok(0), // no-op Date(date) => encode_collection_delimited(&mut to, &*date, |to, date| { - primitive_value::encode_date(to, *date).map_err(From::from) - }), + primitive_value::encode_date(to, *date) + }) + .context(WriteDate), Time(time) => encode_collection_delimited(&mut to, &*time, |to, time| { - primitive_value::encode_time(to, *time).map_err(From::from) - }), + primitive_value::encode_time(to, *time) + }) + .context(WriteTime), DateTime(datetime) => { encode_collection_delimited(&mut to, &*datetime, |to, datetime| { - primitive_value::encode_datetime(to, *datetime).map_err(From::from) + primitive_value::encode_datetime(to, *datetime) }) + .context(WriteDateTime) } Str(s) => { // Note: this will always print in UTF-8. Consumers should // intercept string primitive values and encode them according // to the expected character set. - write!(to, "{}", s)?; + write!(to, "{}", s).context(WriteString)?; Ok(s.len()) } Strs(s) => encode_collection_delimited(&mut to, &*s, |to, s| { @@ -105,63 +186,72 @@ pub trait BasicEncode { // to the expected character set. write!(to, "{}", s)?; Ok(s.len()) - }), + }) + .context(WriteString), F32(values) => { for v in values { - self.encode_fl(&mut to, *v)?; + self.encode_fl(&mut to, *v) + .context(WriteBinary { typ: "F32" })?; } Ok(values.len() * 4) } F64(values) => { for v in values { - self.encode_fd(&mut to, *v)?; + self.encode_fd(&mut to, *v) + .context(WriteBinary { typ: "F64" })?; } Ok(values.len() * 8) } U64(values) => { for v in values { - self.encode_uv(&mut to, *v)?; + self.encode_uv(&mut to, *v) + .context(WriteBinary { typ: "U64" })?; } Ok(values.len() * 8) } I64(values) => { for v in values { - self.encode_sv(&mut to, *v)?; + self.encode_sv(&mut to, *v) + .context(WriteBinary { typ: "I64" })?; } Ok(values.len() * 8) } U32(values) => { for v in values { - self.encode_ul(&mut to, *v)?; + self.encode_ul(&mut to, *v) + .context(WriteBinary { typ: "U32" })?; } Ok(values.len() * 4) } I32(values) => { for v in values { - self.encode_sl(&mut to, *v)?; + self.encode_sl(&mut to, *v) + .context(WriteBinary { typ: "I32" })?; } Ok(values.len() * 4) } U16(values) => { for v in values { - self.encode_us(&mut to, *v)?; + self.encode_us(&mut to, *v) + .context(WriteBinary { typ: "U16" })?; } Ok(values.len() * 2) } I16(values) => { for v in values { - self.encode_ss(&mut to, *v)?; + self.encode_ss(&mut to, *v) + .context(WriteBinary { typ: "I16" })?; } Ok(values.len() * 2) } U8(values) => { - to.write_all(values)?; + to.write_all(values).context(WriteBytes)?; Ok(values.len()) } Tags(tags) => { for tag in tags { - self.encode_us(&mut to, tag.0)?; - self.encode_us(&mut to, tag.1)?; + self.encode_us(&mut to, tag.0).context(WriteTagGroup)?; + self.encode_us(&mut to, tag.1).context(WriteTagElement)?; } Ok(tags.len() * 4) } @@ -173,10 +263,10 @@ fn encode_collection_delimited( to: &mut W, col: &[T], mut encode_element_fn: F, -) -> Result +) -> io::Result where W: ?Sized + Write, - F: FnMut(&mut W, &T) -> Result, + F: FnMut(&mut W, &T) -> io::Result, { let mut acc = 0; for (i, v) in col.iter().enumerate() { @@ -215,7 +305,7 @@ pub trait Encode { W: Write, { self.encode_tag(&mut to, Tag(0xFFFE, 0xE00D))?; - to.write_all(&[0u8; 4])?; + to.write_all(&[0u8; 4]).context(WriteItemDelimiter)?; Ok(()) } @@ -225,7 +315,7 @@ pub trait Encode { W: Write, { self.encode_tag(&mut to, Tag(0xFFFE, 0xE0DD))?; - to.write_all(&[0u8; 4])?; + to.write_all(&[0u8; 4]).context(WriteSequenceDelimiter)?; Ok(()) } @@ -509,56 +599,56 @@ where self.inner.endianness() } - fn encode_us(&self, to: S, value: u16) -> Result<()> + fn encode_us(&self, to: S, value: u16) -> io::Result<()> where S: Write, { self.inner.encode_us(to, value) } - fn encode_ul(&self, to: S, value: u32) -> Result<()> + fn encode_ul(&self, to: S, value: u32) -> io::Result<()> where S: Write, { self.inner.encode_ul(to, value) } - fn encode_uv(&self, to: S, value: u64) -> Result<()> + fn encode_uv(&self, to: S, value: u64) -> io::Result<()> where S: Write, { self.inner.encode_uv(to, value) } - fn encode_ss(&self, to: S, value: i16) -> Result<()> + fn encode_ss(&self, to: S, value: i16) -> io::Result<()> where S: Write, { self.inner.encode_ss(to, value) } - fn encode_sl(&self, to: S, value: i32) -> Result<()> + fn encode_sl(&self, to: S, value: i32) -> io::Result<()> where S: Write, { self.inner.encode_sl(to, value) } - fn encode_sv(&self, to: S, value: i64) -> Result<()> + fn encode_sv(&self, to: S, value: i64) -> io::Result<()> where S: Write, { self.inner.encode_sv(to, value) } - fn encode_fl(&self, to: S, value: f32) -> Result<()> + fn encode_fl(&self, to: S, value: f32) -> io::Result<()> where S: Write, { self.inner.encode_fl(to, value) } - fn encode_fd(&self, to: S, value: f64) -> Result<()> + fn encode_fd(&self, to: S, value: f64) -> io::Result<()> where S: Write, { diff --git a/encoding/src/error.rs b/encoding/src/error.rs deleted file mode 100644 index 4726b7ca..00000000 --- a/encoding/src/error.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Crate-level error types. -use dicom_core::error::Error as CoreError; -pub use dicom_core::error::{CastValueError, ConvertValueError, InvalidValueReadError}; -use dicom_core::Tag; -use quick_error::quick_error; -use std::borrow::Cow; -use std::fmt; -use std::io; - -/// Type alias for a result from this crate. -pub type Result = ::std::result::Result; - -quick_error! { - /// The main data type for errors in the library. - #[derive(Debug)] - pub enum Error { - /// Raised when the obtained data element tag was not the one expected. - UnexpectedTag(tag: Tag) { - display("Unexpected DICOM tag {}", tag) - } - /// Raised when the obtained length is inconsistent. - UnexpectedDataValueLength { - display("Inconsistent data value length in data element") - } - /// Error related to an invalid value read. - ReadValue(err: InvalidValueReadError) { - from() - display("Invalid value read: {}", err) - } - /// Error related to a failed text encoding / decoding procedure. - TextEncoding(err: TextEncodingError) { - display("Failed text encoding/decoding: {}", err) - from() - } - /// A failed attempt to cast a value to an inappropriate format. - CastValue(err: CastValueError) { - display("Failed value cast: {}", err) - from() - } - /// A failed attempt to cast a value to an inappropriate format. - ConvertValue(err: ConvertValueError) { - display("Failed value conversion: {}", err) - from() - } - /// Other I/O errors. - Io(err: io::Error) { - display("I/O error: {}", err) - from() - } - } -} - -impl From for Error { - fn from(e: CoreError) -> Self { - match e { - CoreError::UnexpectedDataValueLength => Error::UnexpectedDataValueLength, - CoreError::UnexpectedTag(tag) => Error::UnexpectedTag(tag), - CoreError::ReadValue(e) => Error::ReadValue(e), - CoreError::CastValue(e) => Error::CastValue(e), - CoreError::ConvertValue(e) => Error::ConvertValue(e), - } - } -} - -/// An error type for text encoding issues. -#[derive(Debug, Clone, PartialEq)] -pub struct TextEncodingError(Cow<'static, str>); - -impl TextEncodingError { - /// Build an error from a cause text, as provided by the - /// `encoding` crate. - pub fn new>>(cause: E) -> Self { - TextEncodingError(cause.into()) - } -} - -impl fmt::Display for TextEncodingError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "encoding/decoding process failed: {}", self.0) - } -} - -impl ::std::error::Error for TextEncodingError {} diff --git a/encoding/src/lib.rs b/encoding/src/lib.rs index 791dd913..d1200943 100644 --- a/encoding/src/lib.rs +++ b/encoding/src/lib.rs @@ -1,7 +1,6 @@ #![deny(trivial_numeric_casts, unsafe_code, unstable_features)] #![warn( missing_debug_implementations, - missing_docs, unused_qualifications, unused_import_braces )] @@ -15,11 +14,9 @@ //! For the time being, all APIs are based on synchronous I/O. //! //! [transfer syntax specifier]: ./transfer_syntax/index.html -#![recursion_limit = "72"] pub mod decode; pub mod encode; -pub mod error; pub mod text; pub mod transfer_syntax; diff --git a/encoding/src/text.rs b/encoding/src/text.rs index c2f54b88..8b3ea285 100644 --- a/encoding/src/text.rs +++ b/encoding/src/text.rs @@ -18,10 +18,46 @@ //! //! [`SpecificCharacterSet`]: ./enum.SpecificCharacterSet.html -use crate::error::{Result, TextEncodingError}; use encoding::all::{GB18030, ISO_8859_1, ISO_8859_2, ISO_8859_3, ISO_8859_4, ISO_8859_5, UTF_8}; use encoding::{DecoderTrap, EncoderTrap, Encoding, RawDecoder, StringWriter}; +use std::borrow::Cow; use std::fmt::Debug; +use snafu::{Backtrace, Snafu}; + +/// An error type for text encoding issues. +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum EncodeTextError { + /// A custom error message, + /// for when the underlying error type does not encode error semantics + /// into type variants. + #[snafu(display("{}", message))] + EncodeCustom { + /// The error message in plain text. + message: Cow<'static, str>, + /// The generated backtrace, if available. + backtrace: Backtrace, + } +} + +/// An error type for text decoding issues. +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum DecodeTextError { + /// A custom error message, + /// for when the underlying error type does not encode error semantics + /// into type variants. + #[snafu(display("{}", message))] + DecodeCustom { + /// The error message in plain text. + message: Cow<'static, str>, + /// The generated backtrace, if available. + backtrace: Backtrace, + } +} + +type EncodeResult = Result; +type DecodeResult = Result; /// A holder of encoding and decoding mechanisms for text in DICOM content, /// which according to the standard, depends on the specific character set. @@ -38,12 +74,12 @@ pub trait TextCodec { /// Decode the given byte buffer as a single string. The resulting string /// _may_ contain backslash characters ('\') to delimit individual values, /// and should be split later on if required. - fn decode(&self, text: &[u8]) -> Result; + fn decode(&self, text: &[u8]) -> DecodeResult; /// Encode a text value into a byte vector. The input string can /// feature multiple text values by using the backslash character ('\') /// as the value delimiter. - fn encode(&self, text: &str) -> Result>; + fn encode(&self, text: &str) -> EncodeResult>; } impl TextCodec for Box @@ -54,11 +90,11 @@ where self.as_ref().name() } - fn decode(&self, text: &[u8]) -> Result { + fn decode(&self, text: &[u8]) -> DecodeResult { self.as_ref().decode(text) } - fn encode(&self, text: &str) -> Result> { + fn encode(&self, text: &str) -> EncodeResult> { self.as_ref().encode(text) } } @@ -71,11 +107,11 @@ where (**self).name() } - fn decode(&self, text: &[u8]) -> Result { + fn decode(&self, text: &[u8]) -> DecodeResult { (**self).decode(text) } - fn encode(&self, text: &str) -> Result> { + fn encode(&self, text: &str) -> EncodeResult> { (**self).encode(text) } } @@ -120,6 +156,18 @@ impl Default for SpecificCharacterSet { } impl SpecificCharacterSet { + /** Obtain the specific character set identified by the given code string. + * + * Supported code strings include the possible values + * in the respective DICOM element (0008, 0005). + * + * # Example + * + * ``` + * let character_set = SpecificCharacterSet::from_code("ISO_IR 100"); + * assert_eq!(character_set, Some(SpecificCharacterSet::IsoIr100)); + * ``` + */ pub fn from_code(uid: &str) -> Option { use self::SpecificCharacterSet::*; match uid.trim_end() { @@ -180,16 +228,16 @@ macro_rules! decl_character_set { $term } - fn decode(&self, text: &[u8]) -> Result { + fn decode(&self, text: &[u8]) -> DecodeResult { $val .decode(text, DecoderTrap::Call(decode_text_trap)) - .map_err(|e| TextEncodingError::new(e).into()) + .map_err(|message| DecodeCustom { message }.build()) } - fn encode(&self, text: &str) -> Result> { + fn encode(&self, text: &str) -> EncodeResult> { $val .encode(text, EncoderTrap::Strict) - .map_err(|e| TextEncodingError::new(e).into()) + .map_err(|message| EncodeCustom { message }.build()) } } }; @@ -204,18 +252,18 @@ impl TextCodec for DefaultCharacterSetCodec { "ISO_IR 6" } - fn decode(&self, text: &[u8]) -> Result { + fn decode(&self, text: &[u8]) -> DecodeResult { // Using 8859-1 because it is a superset. Reiterations of this impl // should check for invalid character codes (#40). ISO_8859_1 .decode(text, DecoderTrap::Call(decode_text_trap)) - .map_err(|e| TextEncodingError::new(e).into()) + .map_err(|message| DecodeCustom { message }.build()) } - fn encode(&self, text: &str) -> Result> { + fn encode(&self, text: &str) -> EncodeResult> { ISO_8859_1 .encode(text, EncoderTrap::Strict) - .map_err(|e| TextEncodingError::new(e).into()) + .map_err(|message| EncodeCustom { message }.build()) } } diff --git a/encoding/src/transfer_syntax/explicit_be.rs b/encoding/src/transfer_syntax/explicit_be.rs index a7500211..964f2263 100644 --- a/encoding/src/transfer_syntax/explicit_be.rs +++ b/encoding/src/transfer_syntax/explicit_be.rs @@ -1,462 +1,2 @@ -//! Explicit VR Big Endian syntax transfer implementation. - -use crate::decode::basic::BigEndianBasicDecoder; -use crate::decode::{BasicDecode, Decode, DecodeFrom}; -use crate::encode::basic::BigEndianBasicEncoder; -use crate::encode::{BasicEncode, Encode}; -use crate::error::Result; -use byteordered::byteorder::{BigEndian, ByteOrder}; -use byteordered::Endianness; -use dicom_core::header::{DataElementHeader, HasLength, Header, Length, SequenceItemHeader}; -use dicom_core::{PrimitiveValue, Tag, VR}; -use std::io::{Read, Write}; - -/// A data element decoder for the Explicit VR Big Endian transfer syntax. -#[derive(Debug, Default, Clone)] -pub struct ExplicitVRBigEndianDecoder { - basic: BigEndianBasicDecoder, -} - -impl Decode for ExplicitVRBigEndianDecoder { - fn decode_header(&self, mut source: &mut S) -> Result<(DataElementHeader, usize)> - where - S: ?Sized + Read, - { - // retrieve tag - let Tag(group, element) = self.basic.decode_tag(&mut source)?; - - let mut buf = [0u8; 4]; - if group == 0xFFFE { - // item delimiters do not have VR or reserved field - source.read_exact(&mut buf)?; - let len = BigEndian::read_u32(&buf); - return Ok(( - DataElementHeader::new((group, element), VR::UN, Length(len)), - 8, // tag + len - )); - } - - // retrieve explicit VR - source.read_exact(&mut buf[0..2])?; - let vr = VR::from_binary([buf[0], buf[1]]).unwrap_or(VR::UN); - - let bytes_read; - - // retrieve data length - let len = match vr { - VR::OB - | VR::OD - | VR::OF - | VR::OL - | VR::OW - | VR::SQ - | VR::UC - | VR::UR - | VR::UT - | VR::UN => { - // read 2 reserved bytes, then 4 bytes for data length - source.read_exact(&mut buf[0..2])?; - source.read_exact(&mut buf)?; - bytes_read = 12; - BigEndian::read_u32(&buf) - } - _ => { - // read 2 bytes for the data length - source.read_exact(&mut buf[0..2])?; - bytes_read = 8; - u32::from(BigEndian::read_u16(&buf[0..2])) - } - }; - - Ok(( - DataElementHeader::new((group, element), vr, Length(len)), - bytes_read, - )) - } - - fn decode_item_header(&self, source: &mut S) -> Result - where - S: ?Sized + Read, - { - let mut buf = [0u8; 8]; - source.read_exact(&mut buf)?; - // retrieve tag - let group = BigEndian::read_u16(&buf[0..2]); - let element = BigEndian::read_u16(&buf[2..4]); - let len = BigEndian::read_u32(&buf[4..8]); - - let header = SequenceItemHeader::new((group, element), Length(len))?; - Ok(header) - } - - fn decode_tag(&self, source: &mut S) -> Result - where - S: ?Sized + Read, - { - let mut buf = [0u8; 4]; - source.read_exact(&mut buf)?; - Ok(Tag( - BigEndian::read_u16(&buf[0..2]), - BigEndian::read_u16(&buf[2..4]), - )) - } -} - -impl DecodeFrom for ExplicitVRBigEndianDecoder -where - S: Read, -{ - fn decode_header(&self, source: &mut S) -> Result<(DataElementHeader, usize)> { - Decode::decode_header(self, source) - } - - fn decode_item_header(&self, source: &mut S) -> Result { - Decode::decode_item_header(self, source) - } - - fn decode_tag(&self, source: &mut S) -> Result { - Decode::decode_tag(self, source) - } -} - -/// A concrete encoder for the transfer syntax ExplicitVRBigEndian -#[derive(Debug, Default, Clone)] -pub struct ExplicitVRBigEndianEncoder { - basic: BigEndianBasicEncoder, -} - -impl BasicEncode for ExplicitVRBigEndianEncoder { - fn endianness(&self) -> Endianness { - Endianness::Big - } - - fn encode_us(&self, to: S, value: u16) -> Result<()> - where - S: Write, - { - self.basic.encode_us(to, value) - } - - fn encode_ul(&self, to: S, value: u32) -> Result<()> - where - S: Write, - { - self.basic.encode_ul(to, value) - } - - fn encode_uv(&self, to: S, value: u64) -> Result<()> - where - S: Write, - { - self.basic.encode_uv(to, value) - } - - fn encode_ss(&self, to: S, value: i16) -> Result<()> - where - S: Write, - { - self.basic.encode_ss(to, value) - } - - fn encode_sl(&self, to: S, value: i32) -> Result<()> - where - S: Write, - { - self.basic.encode_sl(to, value) - } - - fn encode_sv(&self, to: S, value: i64) -> Result<()> - where - S: Write, - { - self.basic.encode_sv(to, value) - } - - fn encode_fl(&self, to: S, value: f32) -> Result<()> - where - S: Write, - { - self.basic.encode_fl(to, value) - } - - fn encode_fd(&self, to: S, value: f64) -> Result<()> - where - S: Write, - { - self.basic.encode_fd(to, value) - } -} - -impl Encode for ExplicitVRBigEndianEncoder { - fn encode_tag(&self, mut to: W, tag: Tag) -> Result<()> - where - W: Write, - { - let mut buf = [0u8, 4]; - BigEndian::write_u16(&mut buf[..], tag.group()); - BigEndian::write_u16(&mut buf[2..], tag.element()); - to.write_all(&buf)?; - Ok(()) - } - - fn encode_element_header(&self, mut to: W, de: DataElementHeader) -> Result - where - W: Write, - { - match de.vr() { - VR::OB - | VR::OD - | VR::OF - | VR::OL - | VR::OW - | VR::SQ - | VR::UC - | VR::UR - | VR::UT - | VR::UN => { - let mut buf = [0u8; 12]; - BigEndian::write_u16(&mut buf[0..], de.tag().group()); - BigEndian::write_u16(&mut buf[2..], de.tag().element()); - let vr_bytes = de.vr().to_bytes(); - buf[4] = vr_bytes[0]; - buf[5] = vr_bytes[1]; - // buf[6..8] is kept zero'd - BigEndian::write_u32(&mut buf[8..], de.length().0); - to.write_all(&buf)?; - - Ok(12) - } - _ => { - let mut buf = [0u8; 8]; - BigEndian::write_u16(&mut buf[0..], de.tag().group()); - BigEndian::write_u16(&mut buf[2..], de.tag().element()); - let vr_bytes = de.vr().to_bytes(); - buf[4] = vr_bytes[0]; - buf[5] = vr_bytes[1]; - BigEndian::write_u16(&mut buf[6..], de.length().0 as u16); - to.write_all(&buf)?; - - Ok(8) - } - } - } - - fn encode_item_header(&self, mut to: W, len: u32) -> Result<()> - where - W: Write, - { - let mut buf = [0u8; 8]; - BigEndian::write_u16(&mut buf, 0xFFFE); - BigEndian::write_u16(&mut buf[2..], 0xE000); - BigEndian::write_u32(&mut buf[4..], len); - to.write_all(&buf)?; - Ok(()) - } - - fn encode_item_delimiter(&self, mut to: W) -> Result<()> - where - W: Write, - { - let mut buf = [0u8; 8]; - BigEndian::write_u16(&mut buf, 0xFFFE); - BigEndian::write_u16(&mut buf[2..], 0xE00D); - // remaining bytes are already zero, so it's ready to write - to.write_all(&buf)?; - Ok(()) - } - - fn encode_sequence_delimiter(&self, mut to: W) -> Result<()> - where - W: Write, - { - let mut buf = [0u8; 8]; - BigEndian::write_u16(&mut buf, 0xFFFE); - BigEndian::write_u16(&mut buf[2..], 0xE0DD); - // remaining bytes are already zero, so it's ready to write - to.write_all(&buf)?; - Ok(()) - } - - fn encode_primitive(&self, to: W, value: &PrimitiveValue) -> Result - where - W: Write, - { - self.basic.encode_primitive(to, value) - } -} - -#[cfg(test)] -mod tests { - use super::ExplicitVRBigEndianDecoder; - use super::ExplicitVRBigEndianEncoder; - use crate::decode::Decode; - use crate::encode::Encode; - use dicom_core::header::{DataElementHeader, HasLength, Header, Length}; - use dicom_core::{Tag, VR}; - use std::io::{Cursor, Read, Seek, SeekFrom, Write}; - - // manually crafting some DICOM data elements - // Tag: (0002,0002) Media Storage SOP Class UID - // VR: UI - // Length: 26 - // Value: "1.2.840.10008.5.1.4.1.1.1" (with 1 padding '\0') - // -- - // Tag: (0002,0010) Transfer Syntax UID - // VR: UI - // Length: 20 - // Value: "1.2.840.10008.1.2.1" (w 1 padding '\0') == ExplicitVRLittleEndian - // -- - const RAW: &'static [u8; 62] = &[ - 0x00, 0x02, 0x00, 0x02, 0x55, 0x49, 0x00, 0x1a, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30, - 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, - 0x31, 0x2e, 0x31, 0x00, 0x00, 0x02, 0x00, 0x10, 0x55, 0x49, 0x00, 0x14, 0x31, 0x2e, 0x32, - 0x2e, 0x38, 0x34, 0x30, 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, - 0x31, 0x00, - ]; - - #[test] - fn decode_explicit_vr_be() { - let reader = ExplicitVRBigEndianDecoder::default(); - let mut cursor = Cursor::new(RAW.as_ref()); - { - // read first element - let (elem, bytes_read) = reader - .decode_header(&mut cursor) - .expect("should find an element"); - assert_eq!(elem.tag(), Tag(2, 2)); - assert_eq!(elem.vr(), VR::UI); - assert_eq!(elem.length(), Length(26)); - assert_eq!(bytes_read, 8); - // read only half of the data - let mut buffer: Vec = Vec::with_capacity(13); - buffer.resize(13, 0); - cursor - .read_exact(buffer.as_mut_slice()) - .expect("should read it fine"); - assert_eq!(buffer.as_slice(), b"1.2.840.10008".as_ref()); - } - // cursor should now be @ #21 (there is no automatic skipping) - assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 21); - // cursor should now be @ #34 after skipping - assert_eq!(cursor.seek(SeekFrom::Current(13)).unwrap(), 34); - { - // read second element - let (elem, _bytes_read) = reader - .decode_header(&mut cursor) - .expect("should find an element"); - assert_eq!(elem.tag(), Tag(2, 16)); - assert_eq!(elem.vr(), VR::UI); - assert_eq!(elem.length(), Length(20)); - // read all data - let mut buffer: Vec = Vec::with_capacity(20); - buffer.resize(20, 0); - cursor - .read_exact(buffer.as_mut_slice()) - .expect("should read it fine"); - assert_eq!(buffer.as_slice(), b"1.2.840.10008.1.2.1\0".as_ref()); - } - } - - #[test] - fn encode_explicit_vr_be() { - let mut buf = [0u8; 62]; - { - let enc = ExplicitVRBigEndianEncoder::default(); - let mut writer = Cursor::new(&mut buf[..]); - - // encode first element - let de = DataElementHeader::new(Tag(0x0002, 0x0002), VR::UI, Length(26)); - let len = enc - .encode_element_header(&mut writer, de) - .expect("should write it fine"); - assert_eq!(len, 8); - writer - .write_all(b"1.2.840.10008.5.1.4.1.1.1\0".as_ref()) - .expect("should write the value fine"); - } - assert_eq!(&buf[0..8], &RAW[0..8]); - { - let enc = ExplicitVRBigEndianEncoder::default(); - let mut writer = Cursor::new(&mut buf[34..]); - - // encode second element - let de = DataElementHeader::new(Tag(0x0002, 0x0010), VR::UI, Length(20)); - let len = enc - .encode_element_header(&mut writer, de) - .expect("should write it fine"); - assert_eq!(len, 8); - writer - .write_all(b"1.2.840.10008.1.2.1\0".as_ref()) - .expect("should write the value fine"); - } - assert_eq!(&buf[34..42], &RAW[34..42]); - - assert_eq!(&buf[..], &RAW[..]); - } - - // manually crafting some DICOM sequence/item delimiters - // Tag: (0008,103F) Series Description Code Sequence - // VR: SQ - // Reserved bytes: 0x0000 - // Length: 0xFFFF_FFFF - // -- - // Tag: (FFFE,E000) Item - // Length: 0xFFFF_FFFF (unspecified) - // -- - // Tag: (FFFE,E00D) Item Delimitation Item - // Length: 0 - // -- - // Tag: (FFFE,E0DD) Sequence Delimitation Item - // Length: 0 - // -- - const RAW_SEQUENCE_ITEMS: &'static [u8] = &[ - 0x00, 0x08, 0x10, 0x3F, b'S', b'Q', 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xE0, - 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xE0, 0x0D, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, - 0xE0, 0xDD, 0x00, 0x00, 0x00, 0x00, - ]; - - #[test] - fn decode_items() { - let dec = ExplicitVRBigEndianDecoder::default(); - let mut cursor = Cursor::new(RAW_SEQUENCE_ITEMS); - { - // read first element - let (elem, _bytes_read) = dec - .decode_header(&mut cursor) - .expect("should find an element header"); - assert_eq!(elem.tag(), Tag(8, 0x103F)); - assert_eq!(elem.vr(), VR::SQ); - assert!(elem.length().is_undefined()); - } - // cursor should now be @ #12 - assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 12); - { - let elem = dec - .decode_item_header(&mut cursor) - .expect("should find an item header"); - assert!(elem.is_item()); - assert_eq!(elem.tag(), Tag(0xFFFE, 0xE000)); - assert!(elem.length().is_undefined()); - } - // cursor should now be @ #20 - assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 20); - { - let elem = dec - .decode_item_header(&mut cursor) - .expect("should find an item header"); - assert!(elem.is_item_delimiter()); - assert_eq!(elem.tag(), Tag(0xFFFE, 0xE00D)); - assert_eq!(elem.length(), Length(0)); - } - // cursor should now be @ #28 - assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 28); - { - let elem = dec - .decode_item_header(&mut cursor) - .expect("should find an item header"); - assert!(elem.is_sequence_delimiter()); - assert_eq!(elem.tag(), Tag(0xFFFE, 0xE0DD)); - assert_eq!(elem.length(), Length(0)); - } - } -} +pub use crate::decode::explicit_be::ExplicitVRBigEndianDecoder; +pub use crate::encode::explicit_be::ExplicitVRBigEndianEncoder; \ No newline at end of file diff --git a/encoding/src/transfer_syntax/explicit_le.rs b/encoding/src/transfer_syntax/explicit_le.rs index c236c017..d3b455f9 100644 --- a/encoding/src/transfer_syntax/explicit_le.rs +++ b/encoding/src/transfer_syntax/explicit_le.rs @@ -1,458 +1,2 @@ -//! Explicit VR Little Endian syntax transfer implementation - -use crate::decode::basic::LittleEndianBasicDecoder; -use crate::decode::{BasicDecode, Decode, DecodeFrom}; -use crate::encode::basic::LittleEndianBasicEncoder; -use crate::encode::{BasicEncode, Encode}; -use crate::error::Result; -use byteordered::byteorder::{ByteOrder, LittleEndian}; -use byteordered::Endianness; -use dicom_core::header::{DataElementHeader, HasLength, Header, Length, SequenceItemHeader}; -use dicom_core::{PrimitiveValue, Tag, VR}; -use std::io::{Read, Write}; - -/// A data element decoder for the Explicit VR Little Endian transfer syntax. -#[derive(Debug, Default, Clone)] -pub struct ExplicitVRLittleEndianDecoder { - basic: LittleEndianBasicDecoder, -} - -impl Decode for ExplicitVRLittleEndianDecoder { - fn decode_header(&self, mut source: &mut S) -> Result<(DataElementHeader, usize)> - where - S: ?Sized + Read, - { - // retrieve tag - let Tag(group, element) = self.basic.decode_tag(&mut source)?; - - let mut buf = [0u8; 4]; - if group == 0xFFFE { - // item delimiters do not have VR or reserved field - source.read_exact(&mut buf)?; - let len = LittleEndian::read_u32(&buf); - return Ok(( - DataElementHeader::new((group, element), VR::UN, Length(len)), - 8, // tag + len - )); - } - - // retrieve explicit VR - source.read_exact(&mut buf[0..2])?; - let vr = VR::from_binary([buf[0], buf[1]]).unwrap_or(VR::UN); - let bytes_read; - - // retrieve data length - let len = match vr { - VR::OB - | VR::OD - | VR::OF - | VR::OL - | VR::OW - | VR::SQ - | VR::UC - | VR::UR - | VR::UT - | VR::UN => { - // read 2 reserved bytes, then 4 bytes for data length - source.read_exact(&mut buf[0..2])?; - source.read_exact(&mut buf)?; - bytes_read = 12; - LittleEndian::read_u32(&buf) - } - _ => { - // read 2 bytes for the data length - source.read_exact(&mut buf[0..2])?; - bytes_read = 8; - u32::from(LittleEndian::read_u16(&buf[0..2])) - } - }; - - Ok(( - DataElementHeader::new((group, element), vr, Length(len)), - bytes_read, - )) - } - - fn decode_item_header(&self, source: &mut S) -> Result - where - S: ?Sized + Read, - { - let mut buf = [0u8; 8]; - source.read_exact(&mut buf)?; - // retrieve tag - let group = LittleEndian::read_u16(&buf[0..2]); - let element = LittleEndian::read_u16(&buf[2..4]); - let len = LittleEndian::read_u32(&buf[4..8]); - - let header = SequenceItemHeader::new((group, element), Length(len))?; - Ok(header) - } - - fn decode_tag(&self, source: &mut S) -> Result - where - S: ?Sized + Read, - { - let mut buf = [0u8; 4]; - source.read_exact(&mut buf)?; - Ok(Tag( - LittleEndian::read_u16(&buf[0..2]), - LittleEndian::read_u16(&buf[2..4]), - )) - } -} - -impl DecodeFrom for ExplicitVRLittleEndianDecoder -where - S: Read, -{ - fn decode_header(&self, source: &mut S) -> Result<(DataElementHeader, usize)> { - Decode::decode_header(self, source) - } - - fn decode_item_header(&self, source: &mut S) -> Result { - Decode::decode_item_header(self, source) - } - - fn decode_tag(&self, source: &mut S) -> Result { - Decode::decode_tag(self, source) - } -} - -/// A concrete encoder for the transfer syntax ExplicitVRLittleEndian -#[derive(Debug, Default, Clone)] -pub struct ExplicitVRLittleEndianEncoder { - basic: LittleEndianBasicEncoder, -} - -impl BasicEncode for ExplicitVRLittleEndianEncoder { - fn endianness(&self) -> Endianness { - Endianness::Little - } - - fn encode_us(&self, to: S, value: u16) -> Result<()> - where - S: Write, - { - self.basic.encode_us(to, value) - } - - fn encode_ul(&self, to: S, value: u32) -> Result<()> - where - S: Write, - { - self.basic.encode_ul(to, value) - } - - fn encode_uv(&self, to: S, value: u64) -> Result<()> - where - S: Write, - { - self.basic.encode_uv(to, value) - } - - fn encode_ss(&self, to: S, value: i16) -> Result<()> - where - S: Write, - { - self.basic.encode_ss(to, value) - } - - fn encode_sl(&self, to: S, value: i32) -> Result<()> - where - S: Write, - { - self.basic.encode_sl(to, value) - } - - fn encode_sv(&self, to: S, value: i64) -> Result<()> - where - S: Write, - { - self.basic.encode_sv(to, value) - } - - fn encode_fl(&self, to: S, value: f32) -> Result<()> - where - S: Write, - { - self.basic.encode_fl(to, value) - } - - fn encode_fd(&self, to: S, value: f64) -> Result<()> - where - S: Write, - { - self.basic.encode_fd(to, value) - } -} - -impl Encode for ExplicitVRLittleEndianEncoder { - fn encode_tag(&self, mut to: W, tag: Tag) -> Result<()> - where - W: Write, - { - let mut buf = [0u8, 4]; - LittleEndian::write_u16(&mut buf[..], tag.group()); - LittleEndian::write_u16(&mut buf[2..], tag.element()); - to.write_all(&buf)?; - Ok(()) - } - - fn encode_element_header(&self, mut to: W, de: DataElementHeader) -> Result - where - W: Write, - { - match de.vr() { - VR::OB - | VR::OD - | VR::OF - | VR::OL - | VR::OW - | VR::SQ - | VR::UC - | VR::UR - | VR::UT - | VR::UN => { - let mut buf = [0u8; 12]; - LittleEndian::write_u16(&mut buf[0..], de.tag().group()); - LittleEndian::write_u16(&mut buf[2..], de.tag().element()); - let vr_bytes = de.vr().to_bytes(); - buf[4] = vr_bytes[0]; - buf[5] = vr_bytes[1]; - // buf[6..8] is kept zero'd - LittleEndian::write_u32(&mut buf[8..], de.length().0); - to.write_all(&buf)?; - Ok(12) - } - _ => { - let mut buf = [0u8; 8]; - LittleEndian::write_u16(&mut buf[0..], de.tag().group()); - LittleEndian::write_u16(&mut buf[2..], de.tag().element()); - let vr_bytes = de.vr().to_bytes(); - buf[4] = vr_bytes[0]; - buf[5] = vr_bytes[1]; - LittleEndian::write_u16(&mut buf[6..], de.length().0 as u16); - to.write_all(&buf)?; - Ok(8) - } - } - } - - fn encode_item_header(&self, mut to: W, len: u32) -> Result<()> - where - W: Write, - { - let mut buf = [0u8; 8]; - LittleEndian::write_u16(&mut buf, 0xFFFE); - LittleEndian::write_u16(&mut buf[2..], 0xE000); - LittleEndian::write_u32(&mut buf[4..], len); - to.write_all(&buf)?; - Ok(()) - } - - fn encode_item_delimiter(&self, mut to: W) -> Result<()> - where - W: Write, - { - let mut buf = [0u8; 8]; - LittleEndian::write_u16(&mut buf, 0xFFFE); - LittleEndian::write_u16(&mut buf[2..], 0xE00D); - to.write_all(&buf)?; - Ok(()) - } - - fn encode_sequence_delimiter(&self, mut to: W) -> Result<()> - where - W: Write, - { - let mut buf = [0u8; 8]; - LittleEndian::write_u16(&mut buf, 0xFFFE); - LittleEndian::write_u16(&mut buf[2..], 0xE0DD); - to.write_all(&buf)?; - Ok(()) - } - - fn encode_primitive(&self, to: W, value: &PrimitiveValue) -> Result - where - W: Write, - { - self.basic.encode_primitive(to, value) - } -} - -#[cfg(test)] -mod tests { - use super::ExplicitVRLittleEndianDecoder; - use super::ExplicitVRLittleEndianEncoder; - use crate::decode::Decode; - use crate::encode::Encode; - use dicom_core::header::{DataElementHeader, HasLength, Header, Length}; - use dicom_core::{Tag, VR}; - use std::io::{Cursor, Read, Seek, SeekFrom, Write}; - - // manually crafting some DICOM data elements - // Tag: (0002,0002) Media Storage SOP Class UID - // VR: UI - // Length: 26 - // Value: "1.2.840.10008.5.1.4.1.1.1\0" - // -- - // Tag: (0002,0010) Transfer Syntax UID - // VR: UI - // Length: 20 - // Value: "1.2.840.10008.1.2.1\0" == ExplicitVRLittleEndian - // -- - const RAW: &'static [u8; 62] = &[ - 0x02, 0x00, 0x02, 0x00, 0x55, 0x49, 0x1a, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30, - 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, - 0x31, 0x2e, 0x31, 0x00, 0x02, 0x00, 0x10, 0x00, 0x55, 0x49, 0x14, 0x00, 0x31, 0x2e, 0x32, - 0x2e, 0x38, 0x34, 0x30, 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, - 0x31, 0x00, - ]; - - #[test] - fn decode_data_elements() { - let dec = ExplicitVRLittleEndianDecoder::default(); - let mut cursor = Cursor::new(RAW.as_ref()); - { - // read first element - let (elem, bytes_read) = dec - .decode_header(&mut cursor) - .expect("should find an element"); - assert_eq!(elem.tag(), Tag(2, 2)); - assert_eq!(elem.vr(), VR::UI); - assert_eq!(elem.length(), Length(26)); - assert_eq!(bytes_read, 8); - // read only half of the value data - let mut buffer: Vec = Vec::with_capacity(13); - buffer.resize(13, 0); - cursor - .read_exact(buffer.as_mut_slice()) - .expect("should read it fine"); - assert_eq!(buffer.as_slice(), b"1.2.840.10008".as_ref()); - } - // cursor should now be @ #21 (there is no automatic skipping) - assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 21); - // cursor should now be @ #34 after skipping - assert_eq!(cursor.seek(SeekFrom::Current(13)).unwrap(), 34); - { - // read second element - let (elem, _bytes_read) = dec - .decode_header(&mut cursor) - .expect("should find an element"); - assert_eq!(elem.tag(), Tag(2, 16)); - assert_eq!(elem.vr(), VR::UI); - assert_eq!(elem.length(), Length(20)); - // read all data - let mut buffer: Vec = Vec::with_capacity(20); - buffer.resize(20, 0); - cursor - .read_exact(buffer.as_mut_slice()) - .expect("should read it fine"); - assert_eq!(buffer.as_slice(), b"1.2.840.10008.1.2.1\0".as_ref()); - } - } - - #[test] - fn encode_data_elements() { - let mut buf = [0u8; 62]; - { - let enc = ExplicitVRLittleEndianEncoder::default(); - let mut writer = Cursor::new(&mut buf[..]); - - // encode first element - let de = DataElementHeader::new(Tag(0x0002, 0x0002), VR::UI, Length(26)); - let len = enc - .encode_element_header(&mut writer, de) - .expect("should write it fine"); - assert_eq!(len, 8); - writer - .write_all(b"1.2.840.10008.5.1.4.1.1.1\0".as_ref()) - .expect("should write the value fine"); - } - assert_eq!(&buf[0..8], &RAW[0..8]); - { - let enc = ExplicitVRLittleEndianEncoder::default(); - let mut writer = Cursor::new(&mut buf[34..]); - - // encode second element - let de = DataElementHeader::new(Tag(0x0002, 0x0010), VR::UI, Length(20)); - let len = enc - .encode_element_header(&mut writer, de) - .expect("should write it fine"); - assert_eq!(len, 8); - writer - .write_all(b"1.2.840.10008.1.2.1\0".as_ref()) - .expect("should write the value fine"); - } - assert_eq!(&buf[34..42], &RAW[34..42]); - - assert_eq!(&buf[..], &RAW[..]); - } - - // manually crafting some DICOM sequence/item delimiters - // Tag: (0008,103F) Series Description Code Sequence - // VR: SQ - // Reserved bytes: 0x0000 - // Length: 0xFFFF_FFFF - // -- - // Tag: (FFFE,E000) Item - // Length: 0xFFFF_FFFF (unspecified) - // -- - // Tag: (FFFE,E00D) Item Delimitation Item - // Length: 0 - // -- - // Tag: (FFFE,E0DD) Sequence Delimitation Item - // Length: 0 - // -- - const RAW_SEQUENCE_ITEMS: &'static [u8] = &[ - 0x08, 0x00, 0x3F, 0x10, b'S', b'Q', 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x00, - 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x0D, 0xE0, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, - 0xDD, 0xE0, 0x00, 0x00, 0x00, 0x00, - ]; - - #[test] - fn decode_items() { - let dec = ExplicitVRLittleEndianDecoder::default(); - let mut cursor = Cursor::new(RAW_SEQUENCE_ITEMS); - { - // read first element - let (elem, bytes_read) = dec - .decode_header(&mut cursor) - .expect("should find an element header"); - assert_eq!(elem.tag(), Tag(8, 0x103F)); - assert_eq!(elem.vr(), VR::SQ); - assert!(elem.length().is_undefined()); - assert_eq!(bytes_read, 12); - } - // cursor should now be @ #12 - assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 12); - { - let elem = dec - .decode_item_header(&mut cursor) - .expect("should find an item header"); - assert!(elem.is_item()); - assert_eq!(elem.tag(), Tag(0xFFFE, 0xE000)); - assert!(elem.length().is_undefined()); - } - // cursor should now be @ #20 - assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 20); - { - let elem = dec - .decode_item_header(&mut cursor) - .expect("should find an item header"); - assert!(elem.is_item_delimiter()); - assert_eq!(elem.tag(), Tag(0xFFFE, 0xE00D)); - assert_eq!(elem.length(), Length(0)); - } - // cursor should now be @ #28 - assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 28); - { - let elem = dec - .decode_item_header(&mut cursor) - .expect("should find an item header"); - assert!(elem.is_sequence_delimiter()); - assert_eq!(elem.tag(), Tag(0xFFFE, 0xE0DD)); - assert_eq!(elem.length(), Length(0)); - } - } -} +pub use crate::decode::explicit_le::ExplicitVRLittleEndianDecoder; +pub use crate::encode::explicit_le::ExplicitVRLittleEndianEncoder; \ No newline at end of file diff --git a/encoding/src/transfer_syntax/implicit_le.rs b/encoding/src/transfer_syntax/implicit_le.rs index ae7c2ea2..6b5361a6 100644 --- a/encoding/src/transfer_syntax/implicit_le.rs +++ b/encoding/src/transfer_syntax/implicit_le.rs @@ -1,426 +1,2 @@ -//! Implicit VR Big Endian syntax transfer implementation - -use crate::decode::basic::LittleEndianBasicDecoder; -use crate::decode::{BasicDecode, Decode, DecodeFrom}; -use crate::encode::basic::LittleEndianBasicEncoder; -use crate::encode::{BasicEncode, Encode}; -use crate::error::Result; -use byteordered::byteorder::{ByteOrder, LittleEndian}; -use byteordered::Endianness; -use dicom_core::dictionary::{DataDictionary, DictionaryEntry}; -use dicom_core::header::{DataElementHeader, HasLength, Header, Length, SequenceItemHeader}; -use dicom_core::{PrimitiveValue, Tag, VR}; -use dicom_dictionary_std::StandardDataDictionary; -use std::fmt; -use std::io::{Read, Write}; - -/// An ImplicitVRLittleEndianDecoder which uses the standard data dictionary. -pub type StandardImplicitVRLittleEndianDecoder = - ImplicitVRLittleEndianDecoder; - -/// A data element decoder for the Explicit VR Little Endian transfer syntax. -/// This type contains a reference to an attribute dictionary for resolving -/// value representations. -pub struct ImplicitVRLittleEndianDecoder { - dict: D, - basic: LittleEndianBasicDecoder, -} - -impl fmt::Debug for ImplicitVRLittleEndianDecoder { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("ImplicitVRLittleEndianDecoder") - .field("dict", &"«omitted»") - .field("basic", &self.basic) - .finish() - } -} - -impl ImplicitVRLittleEndianDecoder { - /// Retrieve this decoder using the standard data dictionary. - pub fn with_std_dict() -> Self { - ImplicitVRLittleEndianDecoder { - dict: StandardDataDictionary, - basic: LittleEndianBasicDecoder, - } - } - - /// Retrieve this decoder using the standard data dictionary. - pub fn new() -> Self { - Self::with_std_dict() - } -} - -impl Default for ImplicitVRLittleEndianDecoder { - fn default() -> Self { - ImplicitVRLittleEndianDecoder::with_std_dict() - } -} - -impl ImplicitVRLittleEndianDecoder -where - D: DataDictionary, -{ - /// Retrieve this decoder using a custom data dictionary. - pub fn with_dict(dictionary: D) -> Self { - ImplicitVRLittleEndianDecoder { - dict: dictionary, - basic: LittleEndianBasicDecoder, - } - } -} - -impl Decode for ImplicitVRLittleEndianDecoder -where - D: DataDictionary, -{ - fn decode_header(&self, mut source: &mut S) -> Result<(DataElementHeader, usize)> - where - S: ?Sized + Read, - { - // retrieve tag - let tag = self.basic.decode_tag(&mut source)?; - - let mut buf = [0u8; 4]; - source.read_exact(&mut buf)?; - let len = LittleEndian::read_u32(&buf); - - // VR resolution is done with the help of the data dictionary. - // In Implicit VR Little Endian, the VR of OB may not be used for Pixel - // Data (7FE0,0010). This edge case is addressed manually. - let vr = if tag == Tag(0x7FE0, 0x0010) { - VR::OW - } else { - self.dict - .by_tag(tag) - .map(|entry| entry.vr()) - .unwrap_or(VR::UN) - }; - Ok((DataElementHeader::new(tag, vr, Length(len)), 8)) - } - - fn decode_item_header(&self, mut source: &mut S) -> Result - where - S: ?Sized + Read, - { - let mut buf = [0u8; 4]; - - // retrieve tag - let tag = self.basic.decode_tag(&mut source)?; - - source.read_exact(&mut buf)?; - let len = LittleEndian::read_u32(&buf); - let header = SequenceItemHeader::new(tag, Length(len))?; - Ok(header) - } - - #[inline] - fn decode_tag(&self, source: &mut S) -> Result - where - S: ?Sized + Read, - { - self.basic.decode_tag(source) - } -} - -impl DecodeFrom for ImplicitVRLittleEndianDecoder -where - S: Read, - D: DataDictionary, -{ - fn decode_header(&self, source: &mut S) -> Result<(DataElementHeader, usize)> { - Decode::decode_header(self, source) - } - - fn decode_item_header(&self, source: &mut S) -> Result { - Decode::decode_item_header(self, source) - } - - fn decode_tag(&self, source: &mut S) -> Result { - Decode::decode_tag(self, source) - } -} - -/// A concrete encoder for the transfer syntax ImplicitVRLittleEndian -#[derive(Debug, Default, Clone)] -pub struct ImplicitVRLittleEndianEncoder { - basic: LittleEndianBasicEncoder, -} - -impl BasicEncode for ImplicitVRLittleEndianEncoder { - fn endianness(&self) -> Endianness { - Endianness::Little - } - - fn encode_us(&self, to: S, value: u16) -> Result<()> - where - S: Write, - { - self.basic.encode_us(to, value) - } - - fn encode_ul(&self, to: S, value: u32) -> Result<()> - where - S: Write, - { - self.basic.encode_ul(to, value) - } - - fn encode_uv(&self, to: S, value: u64) -> Result<()> - where - S: Write, - { - self.basic.encode_uv(to, value) - } - - fn encode_ss(&self, to: S, value: i16) -> Result<()> - where - S: Write, - { - self.basic.encode_ss(to, value) - } - - fn encode_sl(&self, to: S, value: i32) -> Result<()> - where - S: Write, - { - self.basic.encode_sl(to, value) - } - - fn encode_sv(&self, to: S, value: i64) -> Result<()> - where - S: Write, - { - self.basic.encode_sv(to, value) - } - - fn encode_fl(&self, to: S, value: f32) -> Result<()> - where - S: Write, - { - self.basic.encode_fl(to, value) - } - - fn encode_fd(&self, to: S, value: f64) -> Result<()> - where - S: Write, - { - self.basic.encode_fd(to, value) - } -} - -impl Encode for ImplicitVRLittleEndianEncoder { - fn encode_tag(&self, mut to: W, tag: Tag) -> Result<()> - where - W: Write, - { - let mut buf = [0u8, 4]; - LittleEndian::write_u16(&mut buf[..], tag.group()); - LittleEndian::write_u16(&mut buf[2..], tag.element()); - to.write_all(&buf)?; - Ok(()) - } - - fn encode_element_header(&self, mut to: W, de: DataElementHeader) -> Result - where - W: Write, - { - let mut buf = [0u8; 8]; - LittleEndian::write_u16(&mut buf[0..], de.tag().group()); - LittleEndian::write_u16(&mut buf[2..], de.tag().element()); - LittleEndian::write_u32(&mut buf[4..], de.length().0); - to.write_all(&buf)?; - Ok(8) - } - - fn encode_item_header(&self, mut to: W, len: u32) -> Result<()> - where - W: Write, - { - let mut buf = [0u8; 8]; - LittleEndian::write_u16(&mut buf, 0xFFFE); - LittleEndian::write_u16(&mut buf[2..], 0xE000); - LittleEndian::write_u32(&mut buf[4..], len); - to.write_all(&buf)?; - Ok(()) - } - - fn encode_item_delimiter(&self, mut to: W) -> Result<()> - where - W: Write, - { - let mut buf = [0u8; 8]; - LittleEndian::write_u16(&mut buf, 0xFFFE); - LittleEndian::write_u16(&mut buf[2..], 0xE00D); - to.write_all(&buf)?; - Ok(()) - } - - fn encode_sequence_delimiter(&self, mut to: W) -> Result<()> - where - W: Write, - { - let mut buf = [0u8; 8]; - LittleEndian::write_u16(&mut buf, 0xFFFE); - LittleEndian::write_u16(&mut buf[2..], 0xE0DD); - to.write_all(&buf)?; - Ok(()) - } - - fn encode_primitive(&self, to: W, value: &PrimitiveValue) -> Result - where - W: Write, - { - self.basic.encode_primitive(to, value) - } -} - -#[cfg(test)] -mod tests { - use super::ImplicitVRLittleEndianDecoder; - use super::ImplicitVRLittleEndianEncoder; - use crate::decode::Decode; - use crate::encode::Encode; - use dicom_core::dictionary::stub::StubDataDictionary; - use dicom_core::header::{DataElementHeader, HasLength, Header, Length, Tag, VR}; - use std::io::{Cursor, Read, Seek, SeekFrom, Write}; - - // manually crafting some DICOM data elements - // Tag: (0002,0002) Media Storage SOP Class UID - // Length: 26 - // Value: "1.2.840.10008.5.1.4.1.1.1" (with 1 padding '\0') - // -- - // Tag: (0002,0010) Transfer Syntax UID - // Length: 20 - // Value: "1.2.840.10008.1.2.1" (w 1 padding '\0') == ExplicitVRLittleEndian - // -- - const RAW: &'static [u8; 62] = &[ - 0x02, 0x00, 0x02, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30, - 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, - 0x31, 0x2e, 0x31, 0x00, 0x02, 0x00, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x31, 0x2e, 0x32, - 0x2e, 0x38, 0x34, 0x30, 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, - 0x31, 0x00, - ]; - - const DICT: &'static StubDataDictionary = &StubDataDictionary; - - #[test] - fn implicit_vr_le() { - let reader = ImplicitVRLittleEndianDecoder::with_dict(DICT); - let mut cursor = Cursor::new(RAW.as_ref()); - { - // read first element - let (elem, bytes_read) = reader - .decode_header(&mut cursor) - .expect("should find an element"); - assert_eq!(elem.tag(), (0x0002, 0x0002)); - assert_eq!(elem.vr(), VR::UN); - assert_eq!(elem.length(), Length(26)); - assert_eq!(bytes_read, 8); - // read only half of the data - let mut buffer: Vec = Vec::with_capacity(13); - buffer.resize(13, 0); - cursor - .read_exact(buffer.as_mut_slice()) - .expect("should read it fine"); - assert_eq!(buffer.as_slice(), b"1.2.840.10008".as_ref()); - } - // cursor should now be @ #21 (there is no automatic skipping) - assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 21); - // cursor should now be @ #34 after skipping - assert_eq!(cursor.seek(SeekFrom::Current(13)).unwrap(), 34); - { - // read second element - let (elem, _bytes_read) = reader - .decode_header(&mut cursor) - .expect("should find an element"); - assert_eq!(elem.tag(), (0x0002, 0x0010)); - assert_eq!(elem.vr(), VR::UN); - assert_eq!(elem.length(), Length(20)); - // read all data - let mut buffer: Vec = Vec::with_capacity(20); - buffer.resize(20, 0); - cursor - .read_exact(buffer.as_mut_slice()) - .expect("should read it fine"); - assert_eq!(buffer.as_slice(), b"1.2.840.10008.1.2.1\0".as_ref()); - } - } - - #[test] - fn implicit_vr_le_with_standard_dictionary() { - let reader = ImplicitVRLittleEndianDecoder::with_std_dict(); - let mut cursor = Cursor::new(RAW.as_ref()); - { - // read first element - let (elem, _bytes_read) = reader - .decode_header(&mut cursor) - .expect("should find an element"); - assert_eq!(elem.tag(), (2, 2)); - assert_eq!(elem.vr(), VR::UI); - assert_eq!(elem.length(), Length(26)); - // cursor should be @ #8 - assert_eq!(cursor.seek(SeekFrom::Current(0)).unwrap(), 8); - // don't read any data, just skip - // cursor should be @ #34 after skipping - assert_eq!( - cursor - .seek(SeekFrom::Current(elem.length().0 as i64)) - .unwrap(), - 34 - ); - } - { - // read second element - let (elem, _bytes_read) = reader - .decode_header(&mut cursor) - .expect("should find an element"); - assert_eq!(elem.tag(), (2, 16)); - assert_eq!(elem.vr(), VR::UI); - assert_eq!(elem.length(), Length(20)); - // read all data - let mut buffer: Vec = Vec::with_capacity(20); - buffer.resize(20, 0); - cursor - .read_exact(buffer.as_mut_slice()) - .expect("should read it fine"); - assert_eq!(buffer.as_slice(), b"1.2.840.10008.1.2.1\0".as_ref()); - } - } - - #[test] - fn encode_implicit_vr_le() { - let mut buf = [0u8; 62]; - { - let enc = ImplicitVRLittleEndianEncoder::default(); - let mut writer = Cursor::new(&mut buf[..]); - - // encode first element - let de = DataElementHeader::new(Tag(0x0002, 0x0002), VR::UI, Length(26)); - let len = enc - .encode_element_header(&mut writer, de) - .expect("should write it fine"); - assert_eq!(len, 8); - writer - .write_all(b"1.2.840.10008.5.1.4.1.1.1\0".as_ref()) - .expect("should write the value fine"); - } - assert_eq!(&buf[0..8], &RAW[0..8]); - { - let enc = ImplicitVRLittleEndianEncoder::default(); - let mut writer = Cursor::new(&mut buf[34..]); - - // encode second element - let de = DataElementHeader::new(Tag(0x0002, 0x0010), VR::UI, Length(20)); - let len = enc - .encode_element_header(&mut writer, de) - .expect("should write it fine"); - assert_eq!(len, 8); - writer - .write_all(b"1.2.840.10008.1.2.1\0".as_ref()) - .expect("should write the value fine"); - } - assert_eq!(&buf[34..42], &RAW[34..42]); - - assert_eq!(&buf[..], &RAW[..]); - } -} +pub use crate::decode::implicit_le::ImplicitVRLittleEndianDecoder; +pub use crate::encode::implicit_le::ImplicitVRLittleEndianEncoder; \ No newline at end of file diff --git a/encoding/src/transfer_syntax/mod.rs b/encoding/src/transfer_syntax/mod.rs index fb83c3b2..58074660 100644 --- a/encoding/src/transfer_syntax/mod.rs +++ b/encoding/src/transfer_syntax/mod.rs @@ -9,8 +9,14 @@ //! //! [`TransferSyntaxIndex`]: ./trait.TransferSyntaxIndex.html +/// Explicit VR Big Endian implementation +#[deprecated(since = "0.3.0", note = "See `decode::explicit_be` or `encode::explicit_be` instead")] pub mod explicit_be; +/// Explicit VR Little Endian implementation +#[deprecated(since = "0.3.0", note = "See `decode::explicit_le` or `encode::explicit_le` instead")] pub mod explicit_le; +/// Implicit VR Little Endian implementation +#[deprecated(since = "0.3.0", note = "See `decode::implicit_le` or `encode::implicit_le` instead")] pub mod implicit_le; use crate::decode::basic::BasicDecoder; diff --git a/object/Cargo.toml b/object/Cargo.toml index 810bb805..e46aa475 100644 --- a/object/Cargo.toml +++ b/object/Cargo.toml @@ -15,6 +15,7 @@ repository = "Enet4/dicom-rs" [features] default = [] inventory-registry = ['dicom-encoding/inventory-registry', 'dicom-transfer-syntax-registry/inventory-registry'] +backtraces = ['snafu/backtraces'] [dependencies] dicom-core = { path = "../core", version = "0.2.0" } @@ -25,6 +26,7 @@ dicom-transfer-syntax-registry = { path = "../transfer-syntax-registry", version itertools = "0.9.0" byteordered = "0.5.0" smallvec = "1.0.0" +snafu = "0.6.8" [dev-dependencies] dicom-test-files = "0.2.0" diff --git a/object/src/file.rs b/object/src/file.rs index 286e994e..f9807ebc 100644 --- a/object/src/file.rs +++ b/object/src/file.rs @@ -1,5 +1,4 @@ -use crate::DefaultDicomObject; -use dicom_parser::error::Result; +use crate::{DefaultDicomObject, Result}; use std::io::Read; use std::path::Path; diff --git a/object/src/lib.rs b/object/src/lib.rs index 5e1bd1b6..8c34be2d 100644 --- a/object/src/lib.rs +++ b/object/src/lib.rs @@ -11,8 +11,7 @@ //! //! ```no_run //! use dicom_object::open_file; -//! # use dicom_object::Result; -//! # fn foo() -> Result<()> { +//! # fn foo() -> Result<(), Box> { //! let obj = open_file("0001.dcm")?; //! let patient_name = obj.element_by_name("PatientName")?.to_str()?; //! let modality = obj.element_by_name("Modality")?.to_str()?; @@ -23,8 +22,8 @@ //! Elements can also be fetched by tag: //! //! ``` -//! # use dicom_object::{DicomObject, Result, Tag}; -//! # fn something(obj: T) -> Result<()> { +//! # use dicom_object::{DicomObject, Tag}; +//! # fn something(obj: T) -> Result<(), Box> { //! let e = obj.element(Tag(0x0002, 0x0002))?; //! # Ok(()) //! # } @@ -43,7 +42,6 @@ pub use crate::file::{from_reader, open_file}; pub use crate::meta::FileMetaTable; pub use dicom_core::Tag; pub use dicom_dictionary_std::StandardDataDictionary; -pub use dicom_parser::error::{Error, Result}; /// The default implementation of a root DICOM object. pub type DefaultDicomObject = RootDicomObject>; @@ -52,6 +50,7 @@ use dicom_core::header::Header; use dicom_encoding::{text::SpecificCharacterSet, transfer_syntax::TransferSyntaxIndex}; use dicom_parser::dataset::{DataSetWriter, IntoTokens}; use dicom_transfer_syntax_registry::TransferSyntaxRegistry; +use snafu::{Backtrace, OptionExt, ResultExt, Snafu}; use std::fs::File; use std::io::{BufWriter, Write}; use std::path::Path; @@ -81,6 +80,85 @@ pub trait DicomObject { } } +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum Error { + #[snafu(display("Could not open file '{}': {}", filename.display(), source))] + OpenFile { + filename: std::path::PathBuf, + backtrace: Backtrace, + source: std::io::Error, + }, + #[snafu(display("Could not read from file '{}': {}", filename.display(), source))] + ReadFile { + filename: std::path::PathBuf, + backtrace: Backtrace, + source: std::io::Error, + }, + #[snafu(display("Could not parse meta group data set"))] + ParseMetaDataSet { + #[snafu(backtrace)] + source: crate::meta::Error, + }, + #[snafu(display("Could not create data set parser"))] + CreateParser { + #[snafu(backtrace)] + source: dicom_parser::dataset::read::Error, + }, + #[snafu(display("Could not read data set token"))] + ReadToken { + #[snafu(backtrace)] + source: dicom_parser::dataset::read::Error, + }, + #[snafu(display("Could not write to file '{}': {}", filename.display(), source))] + WriteFile { + filename: std::path::PathBuf, + backtrace: Backtrace, + source: std::io::Error, + }, + #[snafu(display("Could not create data set printer"))] + CreatePrinter { + #[snafu(backtrace)] + source: dicom_parser::dataset::write::Error, + }, + #[snafu(display("Could not print meta group data set"))] + PrintMetaDataSet { + #[snafu(backtrace)] + source: crate::meta::Error, + }, + #[snafu(display("Could not print data set"))] + PrintDataSet { + #[snafu(backtrace)] + source: dicom_parser::dataset::write::Error, + }, + #[snafu(display("Unsupported transfer syntax `{}`", uid))] + UnsupportedTransferSyntax { uid: String, backtrace: Backtrace }, + #[snafu(display("No such data element with tag {}", tag))] + NoSuchDataElementTag { + tag: Tag, + backtrace: Backtrace, + }, + #[snafu(display("No such data element {} (with tag {})", alias, tag))] + NoSuchDataElementAlias { + tag: Tag, + alias: String, + backtrace: Backtrace, + }, + #[snafu(display("Unknown data attribute named `{}`", name))] + NoSuchAttributeName { name: String, backtrace: Backtrace }, + #[snafu(display("Missing element value"))] + MissingElementValue { backtrace: Backtrace }, + #[snafu(display("Unexpected token {:?}", token))] + UnexpectedToken { + token: dicom_parser::dataset::DataToken, + backtrace: Backtrace, + }, + #[snafu(display("Premature data set end"))] + PrematureEnd { backtrace: Backtrace }, +} + +pub type Result = std::result::Result; + /** A root DICOM object contains additional meta information about the object * (such as the DICOM file's meta header). */ @@ -107,29 +185,35 @@ where for<'a> &'a T: IntoTokens, { pub fn write_to_file>(&self, path: P) -> Result<()> { - let file = File::create(path)?; + let path = path.as_ref(); + let file = File::create(path).context(WriteFile { filename: path })?; let mut to = BufWriter::new(file); // write preamble - to.write_all(&[0_u8; 128][..])?; + to.write_all(&[0_u8; 128][..]) + .context(WriteFile { filename: path })?; // write magic sequence - to.write_all(b"DICM")?; + to.write_all(b"DICM") + .context(WriteFile { filename: path })?; // write meta group - self.meta.write(&mut to)?; + self.meta.write(&mut to).context(PrintMetaDataSet)?; // prepare encoder let registry = TransferSyntaxRegistry::default(); - let ts = registry - .get(&self.meta.transfer_syntax) - .ok_or_else(|| Error::UnsupportedTransferSyntax)?; + let ts = registry.get(&self.meta.transfer_syntax).with_context(|| { + UnsupportedTransferSyntax { + uid: self.meta.transfer_syntax.clone(), + } + })?; let cs = SpecificCharacterSet::Default; - let mut dset_writer = DataSetWriter::with_ts_cs(to, ts, cs)?; + let mut dset_writer = DataSetWriter::with_ts_cs(to, ts, cs).context(CreatePrinter)?; // write object - - dset_writer.write_sequence((&self.obj).into_tokens())?; + dset_writer + .write_sequence((&self.obj).into_tokens()) + .context(PrintDataSet)?; Ok(()) } diff --git a/object/src/mem.rs b/object/src/mem.rs index ff89c980..99b9cdcd 100644 --- a/object/src/mem.rs +++ b/object/src/mem.rs @@ -2,13 +2,18 @@ use itertools::Itertools; use smallvec::SmallVec; +use snafu::{OptionExt, ResultExt}; use std::collections::BTreeMap; use std::fs::File; use std::io::{BufReader, Read}; use std::path::Path; use crate::meta::FileMetaTable; -use crate::{DicomObject, RootDicomObject}; +use crate::{ + CreateParser, DicomObject, MissingElementValue, NoSuchAttributeName, NoSuchDataElementAlias, + NoSuchDataElementTag, OpenFile, ParseMetaDataSet, PrematureEnd, ReadFile, ReadToken, Result, + RootDicomObject, UnexpectedToken, UnsupportedTransferSyntax, +}; use dicom_core::dictionary::{DataDictionary, DictionaryEntry}; use dicom_core::header::{HasLength, Header}; use dicom_core::value::{Value, C}; @@ -16,8 +21,8 @@ use dicom_core::{DataElement, Length, Tag, VR}; use dicom_dictionary_std::StandardDataDictionary; use dicom_encoding::text::SpecificCharacterSet; use dicom_encoding::transfer_syntax::TransferSyntaxIndex; +use dicom_parser::dataset::read::Error as ParserError; use dicom_parser::dataset::{DataSetReader, DataToken}; -use dicom_parser::error::{DataSetSyntaxError, Error, Result}; use dicom_transfer_syntax_registry::TransferSyntaxRegistry; /// A full in-memory DICOM data element. @@ -26,6 +31,8 @@ pub type InMemElement = DataElement, InMemFragment>; /// The type of a pixel data fragment. pub type InMemFragment = Vec; +type ParserResult = std::result::Result; + /** A DICOM object that is fully contained in memory. */ #[derive(Debug, Clone)] @@ -61,7 +68,7 @@ where type Element = &'s InMemElement; fn element(&self, tag: Tag) -> Result { - self.entries.get(&tag).ok_or(Error::NoSuchDataElement) + self.entries.get(&tag).context(NoSuchDataElementTag { tag }) } fn element_by_name(&self, name: &str) -> Result { @@ -159,29 +166,37 @@ where P: AsRef, R: TransferSyntaxIndex, { - let mut file = BufReader::new(File::open(path)?); + let path = path.as_ref(); + let mut file = + BufReader::new(File::open(path).with_context(|| OpenFile { filename: path })?); // skip preamble { let mut buf = [0u8; 128]; // skip the preamble - file.read_exact(&mut buf)?; + file.read_exact(&mut buf) + .with_context(|| ReadFile { filename: path })?; } // read metadata header - let meta = FileMetaTable::from_reader(&mut file)?; + let meta = FileMetaTable::from_reader(&mut file).context(ParseMetaDataSet)?; // read rest of data according to metadata, feed it to object - let ts = ts_index - .get(&meta.transfer_syntax) - .ok_or(Error::UnsupportedTransferSyntax)?; - let cs = SpecificCharacterSet::Default; - let mut dataset = DataSetReader::new_with_dictionary(file, dict.clone(), ts, cs)?; - - Ok(RootDicomObject { - meta, - obj: InMemDicomObject::build_object(&mut dataset, dict, false, Length::UNDEFINED)?, - }) + if let Some(ts) = ts_index.get(&meta.transfer_syntax) { + let cs = SpecificCharacterSet::Default; + let mut dataset = DataSetReader::new_with_dictionary(file, dict.clone(), ts, cs) + .context(CreateParser)?; + + Ok(RootDicomObject { + meta, + obj: InMemDicomObject::build_object(&mut dataset, dict, false, Length::UNDEFINED)?, + }) + } else { + UnsupportedTransferSyntax { + uid: meta.transfer_syntax, + } + .fail() + } } /// Create a DICOM object by reading from a byte source. @@ -213,16 +228,21 @@ where let mut file = BufReader::new(src); // read metadata header - let meta = FileMetaTable::from_reader(&mut file)?; + let meta = FileMetaTable::from_reader(&mut file).context(ParseMetaDataSet)?; // read rest of data according to metadata, feed it to object - let ts = ts_index - .get(&meta.transfer_syntax) - .ok_or(Error::UnsupportedTransferSyntax)?; - let cs = SpecificCharacterSet::Default; - let mut dataset = DataSetReader::new_with_dictionary(file, dict.clone(), ts, cs)?; - let obj = InMemDicomObject::build_object(&mut dataset, dict, false, Length::UNDEFINED)?; - Ok(RootDicomObject { meta, obj }) + if let Some(ts) = ts_index.get(&meta.transfer_syntax) { + let cs = SpecificCharacterSet::Default; + let mut dataset = DataSetReader::new_with_dictionary(file, dict.clone(), ts, cs) + .context(CreateParser)?; + let obj = InMemDicomObject::build_object(&mut dataset, dict, false, Length::UNDEFINED)?; + Ok(RootDicomObject { meta, obj }) + } else { + UnsupportedTransferSyntax { + uid: meta.transfer_syntax, + } + .fail() + } } } @@ -294,13 +314,18 @@ where /// Retrieve a particular DICOM element by its tag. pub fn element(&self, tag: Tag) -> Result<&InMemElement> { - self.entries.get(&tag).ok_or(Error::NoSuchDataElement) + self.entries.get(&tag).context(NoSuchDataElementTag { tag }) } /// Retrieve a particular DICOM element by its name. pub fn element_by_name(&self, name: &str) -> Result<&InMemElement> { let tag = self.lookup_name(name)?; - self.element(tag) + self.entries + .get(&tag) + .with_context(|| NoSuchDataElementAlias { + tag, + alias: name.to_string(), + }) } /// Insert a data element to the object, replacing (and returning) any @@ -314,25 +339,25 @@ where /// Build an object by consuming a data set parser. fn build_object(dataset: &mut I, dict: D, in_item: bool, len: Length) -> Result where - I: Iterator>, + I: Iterator>, { let mut entries: BTreeMap> = BTreeMap::new(); // perform a structured parsing of incoming tokens while let Some(token) = dataset.next() { - let elem = match token? { + let elem = match token.context(ReadToken)? { DataToken::PixelSequenceStart => { let value = InMemDicomObject::build_encapsulated_data(&mut *dataset)?; DataElement::new(Tag(0x7fe0, 0x0010), VR::OB, value) } DataToken::ElementHeader(header) => { // fetch respective value, place it in the entries - let next_token = dataset.next().ok_or_else(|| Error::MissingElementValue)?; - match next_token? { + let next_token = dataset.next().context(MissingElementValue)?; + match next_token.context(ReadToken)? { DataToken::PrimitiveValue(v) => { InMemElement::new(header.tag, header.vr, Value::Primitive(v)) } token => { - return Err(DataSetSyntaxError::UnexpectedToken(token).into()); + return UnexpectedToken { token }.fail(); } } } @@ -345,7 +370,7 @@ where // end of item, leave now return Ok(InMemDicomObject { entries, dict, len }); } - token => return Err(DataSetSyntaxError::UnexpectedToken(token).into()), + token => return UnexpectedToken { token }.fail(), }; entries.insert(elem.tag(), elem); } @@ -359,7 +384,7 @@ where mut dataset: I, ) -> Result, InMemFragment>> where - I: Iterator>, + I: Iterator>, { // continue fetching tokens to retrieve: // - the offset table @@ -374,7 +399,7 @@ where let mut fragments = C::new(); while let Some(token) = dataset.next() { - match token? { + match token.context(ReadToken)? { DataToken::ItemValue(data) => { if offset_table.is_none() { offset_table = Some(data.into()); @@ -400,9 +425,7 @@ where | token @ DataToken::PixelSequenceStart | token @ DataToken::SequenceStart { .. } | token @ DataToken::PrimitiveValue(_) => { - return Err(Error::DataSetSyntax(DataSetSyntaxError::UnexpectedToken( - token, - ))); + return UnexpectedToken { token }.fail(); } } } @@ -421,29 +444,29 @@ where dict: &D, ) -> Result>> where - I: Iterator>, + I: Iterator>, { let mut items: C<_> = SmallVec::new(); while let Some(token) = dataset.next() { - match token? { + match token.context(ReadToken)? { DataToken::ItemStart { len } => { items.push(Self::build_object(&mut *dataset, dict.clone(), true, len)?); } DataToken::SequenceEnd => { return Ok(items); } - token => return Err(DataSetSyntaxError::UnexpectedToken(token).into()), + token => return UnexpectedToken { token }.fail(), }; } // iterator fully consumed without a sequence delimiter - Err(DataSetSyntaxError::PrematureEnd.into()) + PrematureEnd.fail() } fn lookup_name(&self, name: &str) -> Result { self.dict .by_name(name) - .ok_or(Error::NoSuchAttributeName) + .context(NoSuchAttributeName { name }) .map(|e| e.tag()) } } diff --git a/object/src/meta.rs b/object/src/meta.rs index e2a8c136..794469d1 100644 --- a/object/src/meta.rs +++ b/object/src/meta.rs @@ -9,11 +9,88 @@ use dicom_encoding::encode::EncoderFor; use dicom_encoding::text::{self, DefaultCharacterSetCodec, TextCodec}; use dicom_encoding::transfer_syntax::explicit_le::ExplicitVRLittleEndianEncoder; use dicom_parser::dataset::{DataSetWriter, IntoTokens}; -use dicom_parser::error::{Error, InvalidValueReadError, Result}; +use snafu::{ensure, Backtrace, OptionExt, ResultExt, Snafu}; use std::io::{Read, Write}; const DICM_MAGIC_CODE: [u8; 4] = [b'D', b'I', b'C', b'M']; +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum Error { + /// The file meta group parser could not read + /// the magic code `DICM` from its source. + #[snafu(display("Could not start reading DICOM data"))] + ReadMagicCode { + backtrace: Backtrace, + source: std::io::Error, + }, + + /// The file meta group parser could not fetch + /// the value of a data element from its source. + #[snafu(display("Could not read data value"))] + ReadValueData { + backtrace: Backtrace, + source: std::io::Error, + }, + + /// The file meta group parser could not decode + /// the text in one of its data elements. + #[snafu(display("Could not decode text in {}", name))] + DecodeText { + name: &'static str, + #[snafu(backtrace)] + source: dicom_encoding::text::DecodeTextError, + }, + + /// Invalid DICOM data, detected from checking the `DICM` code. + #[snafu(display("Invalid DICOM data"))] + NotDicom { backtrace: Backtrace }, + + /// An issue occurred while decoding the next data element + /// in the file meta data set. + #[snafu(display("Could not decode data element"))] + DecodeElement { + #[snafu(backtrace)] + source: dicom_encoding::decode::Error, + }, + + /// A data element with an unexpected tag was retrieved: + /// the parser was expecting another tag first, + /// or at least one that is part of the the file meta group. + #[snafu(display("Unexpected data element tagged {}", tag))] + UnexpectedTag { tag: Tag, backtrace: Backtrace }, + + /// A required file meta data element is missing. + #[snafu(display("Missing data element `{}`", alias))] + MissingElement { + alias: &'static str, + backtrace: Backtrace, + }, + + /// The value length of a data elements in the file meta group + /// was unexpected. + #[snafu(display("Unexpected length {} for data element tagged {}", length, tag))] + UnexpectedDataValueLength { + tag: Tag, + length: Length, + backtrace: Backtrace, + }, + + /// The value length of a data element is undefined, + /// but knowing the length is required in its context. + #[snafu(display("Undefined value length for data element tagged {}", tag))] + UndefinedValueLength { tag: Tag, backtrace: Backtrace }, + + /// The file meta group data set could not be written. + #[snafu(display("Could not write file meta group data set"))] + WriteSet { + #[snafu(backtrace)] + source: dicom_parser::dataset::write::Error, + }, +} + +type Result = std::result::Result; + /// DICOM File Meta Information Table. /// /// This data type contains the relevant parts of the file meta information table, as @@ -75,9 +152,9 @@ where T: TextCodec, { let mut v = vec![0; len as usize]; - source.read_exact(&mut v)?; + source.read_exact(&mut v).context(ReadValueData)?; *group_length_remaining -= 8 + len; - text.decode(&v).map_err(From::from) + text.decode(&v).context(DecodeText { name: text.name() }) } impl FileMetaTable { @@ -89,11 +166,9 @@ impl FileMetaTable { let mut buff: [u8; 4] = [0; 4]; { // check magic code - file.read_exact(&mut buff)?; + file.read_exact(&mut buff).context(ReadMagicCode)?; - if buff != DICM_MAGIC_CODE { - return Err(Error::InvalidFormat); - } + ensure!(buff == DICM_MAGIC_CODE, NotDicom); } let decoder = decode::file_header_decoder(); @@ -102,15 +177,19 @@ impl FileMetaTable { let builder = FileMetaTableBuilder::new(); let group_length: u32 = { - let (elem, _bytes_read) = decoder.decode_header(&mut file)?; + let (elem, _bytes_read) = decoder.decode_header(&mut file).context(DecodeElement)?; if elem.tag() != (0x0002, 0x0000) { - return Err(Error::UnexpectedTag(elem.tag())); + return UnexpectedTag { tag: elem.tag() }.fail(); } if elem.length() != Length(4) { - return Err(Error::UnexpectedDataValueLength); + return UnexpectedDataValueLength { + tag: elem.tag(), + length: elem.length(), + } + .fail(); } let mut buff: [u8; 4] = [0; 4]; - file.read_exact(&mut buff)?; + file.read_exact(&mut buff).context(ReadValueData)?; LittleEndian::read_u32(&buff) }; @@ -120,10 +199,10 @@ impl FileMetaTable { // Fetch optional data elements while group_length_remaining > 0 { - let (elem, _bytes_read) = decoder.decode_header(&mut file)?; + let (elem, _bytes_read) = decoder.decode_header(&mut file).context(DecodeElement)?; let elem_len = match elem.length().get() { None => { - return Err(Error::from(InvalidValueReadError::UnresolvedValueLength)); + return UndefinedValueLength { tag: elem.tag() }.fail(); } Some(len) => len, }; @@ -131,10 +210,14 @@ impl FileMetaTable { Tag(0x0002, 0x0001) => { // Implementation Version if elem.length() != Length(2) { - return Err(Error::UnexpectedDataValueLength); + return UnexpectedDataValueLength { + tag: elem.tag(), + length: elem.length(), + } + .fail(); } let mut hbuf = [0u8; 2]; - file.read_exact(&mut hbuf[..])?; + file.read_exact(&mut hbuf[..]).context(ReadValueData)?; group_length_remaining -= 14; builder.information_version(hbuf) @@ -170,52 +253,65 @@ impl FileMetaTable { Tag(0x0002, 0x0013) => { // Implementation Version Name let mut v = vec![0; elem_len as usize]; - file.read_exact(&mut v)?; + file.read_exact(&mut v).context(ReadValueData)?; group_length_remaining -= 8 + elem_len; - builder.implementation_version_name(text.decode(&v)?) + builder.implementation_version_name( + text.decode(&v).context(DecodeText { name: text.name() })?, + ) } Tag(0x0002, 0x0016) => { // Source Application Entity Title let mut v = vec![0; elem_len as usize]; - file.read_exact(&mut v)?; + file.read_exact(&mut v).context(ReadValueData)?; group_length_remaining -= 8 + elem_len; - builder.source_application_entity_title(text.decode(&v)?) + builder.source_application_entity_title( + text.decode(&v).context(DecodeText { name: text.name() })?, + ) } Tag(0x0002, 0x0017) => { // Sending Application Entity Title let mut v = vec![0; elem_len as usize]; - file.read_exact(&mut v)?; + file.read_exact(&mut v).context(ReadValueData)?; group_length_remaining -= 8 + elem_len; - builder.sending_application_entity_title(text.decode(&v)?) + builder.sending_application_entity_title( + text.decode(&v).context(DecodeText { name: text.name() })?, + ) } Tag(0x0002, 0x0018) => { // Receiving Application Entity Title let mut v = vec![0; elem_len as usize]; - file.read_exact(&mut v)?; + file.read_exact(&mut v).context(ReadValueData)?; group_length_remaining -= 8 + elem_len; - builder.receiving_application_entity_title(text.decode(&v)?) + builder.receiving_application_entity_title( + text.decode(&v).context(DecodeText { name: text.name() })?, + ) } Tag(0x0002, 0x0100) => { // Private Information Creator UID let mut v = vec![0; elem_len as usize]; - file.read_exact(&mut v)?; + file.read_exact(&mut v).context(ReadValueData)?; group_length_remaining -= 8 + elem_len; - builder.private_information_creator_uid(text.decode(&v)?) + builder.private_information_creator_uid( + text.decode(&v).context(DecodeText { name: text.name() })?, + ) } Tag(0x0002, 0x0102) => { // Private Information let mut v = vec![0; elem_len as usize]; - file.read_exact(&mut v)?; + file.read_exact(&mut v).context(ReadValueData)?; group_length_remaining -= 12 + elem_len; builder.private_information(v) } Tag(0x0002, _) => { // unknown tag, do nothing - // could be an unsupported or non-standard attribute + // could be an unsupported or non-standard attribute, + // consider logging (#49) builder } _ => { - // unexpected tag from another group! do nothing for now + // unexpected tag from another group! do nothing for now, + // but this could pose an issue up ahead (see #50) + // and should be logged (#49) builder } } @@ -318,6 +414,7 @@ impl FileMetaTable { .into_element_iter() .flat_map(IntoTokens::into_tokens), ) + .context(WriteSet) } } @@ -514,18 +611,21 @@ impl FileMetaTableBuilder { // Missing information version, will assume (00H, 01H). See #28 [0, 1] }); - let media_storage_sop_class_uid = self - .media_storage_sop_class_uid - .ok_or_else(|| Error::MissingMetaElement("MediaStorageSOPClassUID"))?; - let media_storage_sop_instance_uid = self - .media_storage_sop_instance_uid - .ok_or_else(|| Error::MissingMetaElement("MediaStorageSOPInstanceUID"))?; - let transfer_syntax = self - .transfer_syntax - .ok_or_else(|| Error::MissingMetaElement("TransferSyntax"))?; - let implementation_class_uid = self - .implementation_class_uid - .ok_or_else(|| Error::MissingMetaElement("ImplementationClassUID"))?; + let media_storage_sop_class_uid = + self.media_storage_sop_class_uid.context(MissingElement { + alias: "MediaStorageSOPClassUID", + })?; + let media_storage_sop_instance_uid = + self.media_storage_sop_instance_uid + .context(MissingElement { + alias: "MediaStorageSOPInstanceUID", + })?; + let transfer_syntax = self.transfer_syntax.context(MissingElement { + alias: "TransferSyntax", + })?; + let implementation_class_uid = self.implementation_class_uid.context(MissingElement { + alias: "ImplementationClassUID", + })?; fn dicom_len>(x: T) -> u32 { let o = x.as_ref().len() as u32; diff --git a/object/src/pixeldata.rs b/object/src/pixeldata.rs index 567cb36e..b1df80b5 100644 --- a/object/src/pixeldata.rs +++ b/object/src/pixeldata.rs @@ -3,9 +3,18 @@ //! In order to facilitate typical pixel data manipulation, this crate //! provides a common interface for retrieving that content as an image //! or a multi-dimensional array. - -use dicom_parser::error::{Error, Result}; use std::marker::PhantomData; +use snafu::{Backtrace, Snafu}; + +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum Error { + PixelIndexOutOfBounds { + backtrace: Backtrace, + } +} + +pub type Result = std::result::Result; /** Implemented by DICOM pixel data blocks retrieved from objects. * @@ -58,7 +67,7 @@ pub struct InMemoryPixelData { impl InMemoryPixelData { fn check_bounds(&self, w: u32, h: u32) -> Result<()> { if w >= self.cols || h >= self.rows { - Err(Error::PixelDataOutOfBounds) + PixelIndexOutOfBounds.fail() } else { Ok(()) } diff --git a/parent/Cargo.toml b/parent/Cargo.toml index 1ccbf264..b70278f1 100644 --- a/parent/Cargo.toml +++ b/parent/Cargo.toml @@ -13,6 +13,7 @@ repository = "Enet4/dicom-rs" [features] default = ['inventory-registry'] inventory-registry = ['dicom-encoding/inventory-registry', 'dicom-transfer-syntax-registry/inventory-registry'] +backtraces = ['dicom-object/backtraces'] [dependencies] dicom-core = { path = "../core", version = "0.2.0" } diff --git a/parser/Cargo.toml b/parser/Cargo.toml index fb6e829d..248c1b62 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -16,7 +16,7 @@ repository = "Enet4/dicom-rs" [dependencies] dicom-core = { path = "../core", version = "0.2.0" } dicom-encoding = { path = "../encoding", version = "0.2.0" } -quick-error = "1.2.3" chrono = "0.4.6" dicom-dictionary-std = { path = "../dictionary-std/", version = "0.2.0" } smallvec = "1.0.0" +snafu = "0.6.8" diff --git a/parser/src/dataset/read.rs b/parser/src/dataset/read.rs index 7909aa3c..e69512d4 100644 --- a/parser/src/dataset/read.rs +++ b/parser/src/dataset/read.rs @@ -4,19 +4,22 @@ //! The rest of the crate is used to obtain DICOM element headers and values. //! At this level, headers and values are treated as tokens which can be used //! to form a syntax tree of a full data set. -use crate::error::{Error, InvalidValueReadError, Result}; -use crate::stateful::decode::{DynStatefulDecoder, StatefulDecode, StatefulDecoder}; -use crate::util::{ReadSeek, SeekInterval}; +use crate::marker::DicomElementMarker; +use crate::stateful::decode::{ + DynStatefulDecoder, Error as DecoderError, StatefulDecode, StatefulDecoder, +}; +use crate::util::ReadSeek; use dicom_core::dictionary::DataDictionary; -use dicom_core::header::{DataElementHeader, HasLength, Header, Length, SequenceItemHeader}; +use dicom_core::header::{DataElementHeader, Header, Length, SequenceItemHeader}; use dicom_core::{Tag, VR}; use dicom_dictionary_std::StandardDataDictionary; use dicom_encoding::text::SpecificCharacterSet; use dicom_encoding::transfer_syntax::TransferSyntax; -use std::io::{Read, Seek, SeekFrom}; +use snafu::{Backtrace, ResultExt, Snafu}; +use std::cmp::Ordering; +use std::io::Read; use std::iter::Iterator; use std::marker::PhantomData; -use std::ops::DerefMut; use super::{DataToken, SeqTokenType}; @@ -26,6 +29,45 @@ where { } +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum Error { + #[snafu(display("Could not create decoder"))] + CreateDecoder { + #[snafu(backtrace)] + source: DecoderError, + }, + #[snafu(display("Could not read item header"))] + ReadItemHeader { + #[snafu(backtrace)] + source: DecoderError, + }, + #[snafu(display("Could not read element header"))] + ReadHeader { + #[snafu(backtrace)] + source: DecoderError, + }, + #[snafu(display("Could not read item value"))] + ReadValue { + #[snafu(backtrace)] + source: DecoderError, + }, + #[snafu(display( + "Inconsistent sequence end: expected end at {} bytes but read {}", + end_of_sequence, + bytes_read + ))] + InconsistentSequenceEnd { + end_of_sequence: u64, + bytes_read: u64, + backtrace: Backtrace, + }, + #[snafu()] + UnexpectedTag { tag: Tag, backtrace: Backtrace }, +} + +pub type Result = std::result::Result; + /// A reader-specific token representing a sequence or item start. #[derive(Debug, Copy, Clone, PartialEq)] struct SeqToken { @@ -68,7 +110,7 @@ impl<'s> DataSetReader, StandardDataDictionary> { where S: Read, { - let parser = DynStatefulDecoder::new_with(source, ts, cs)?; + let parser = DynStatefulDecoder::new_with(source, ts, cs).context(CreateDecoder)?; is_stateful_decode(&parser); @@ -97,7 +139,7 @@ impl<'s, D> DataSetReader, D> { where S: Read, { - let parser = DynStatefulDecoder::new_with(source, ts, cs)?; + let parser = DynStatefulDecoder::new_with(source, ts, cs).context(CreateDecoder)?; is_stateful_decode(&parser); @@ -191,7 +233,7 @@ where } Err(e) => { self.hard_break = true; - Some(Err(e)) + Some(Err(e).context(ReadItemHeader)) } } } else if let Some(SeqToken { @@ -212,7 +254,7 @@ where self.parser .read_bytes(&mut value[..]) .map(|_| Ok(DataToken::ItemValue(value))) - .unwrap_or_else(|e| Err(e)), + .unwrap_or_else(|e| Err(e).context(ReadValue)), ) } else if let Some(header) = self.last_header { if header.is_encapsulated_pixeldata() { @@ -240,12 +282,12 @@ where } item => { self.hard_break = true; - Some(Err(Error::UnexpectedTag(item.tag()))) + Some(UnexpectedTag { tag: item.tag() }.fail()) } }, Err(e) => { self.hard_break = true; - Some(Err(e)) + Some(Err(e).context(ReadItemHeader)) } } } else { @@ -255,7 +297,7 @@ where Err(e) => { self.hard_break = true; self.last_header = None; - return Some(Err(e)); + return Some(Err(e).context(ReadValue)); } }; @@ -301,18 +343,21 @@ where Some(Ok(DataToken::ElementHeader(header))) } } - Err(Error::Io(ref e)) if e.kind() == ::std::io::ErrorKind::UnexpectedEof => { - // TODO there might be a more informative way to check - // whether the end of a DICOM object was reached gracefully - // or with problems. This approach may consume trailing - // bytes, and will ignore the possibility of trailing bytes - // having already been interpreted as an element header. + Err(DecoderError::DecodeElementHeader { + source: dicom_encoding::decode::Error::ReadHeaderTag { source, .. }, + .. + }) if source.kind() == std::io::ErrorKind::UnexpectedEof => { + // Note: if `UnexpectedEof` was reached while trying to read + // an element tag, then we assume that + // the end of a DICOM object was reached gracefully. + // This approach is unlikely to consume trailing bytes, + // but may ignore the current depth of the data set tree. self.hard_break = true; None } Err(e) => { self.hard_break = true; - Some(Err(e)) + Some(Err(e).context(ReadHeader)) } } } @@ -326,25 +371,33 @@ where fn update_seq_delimiters(&mut self) -> Result> { if let Some(sd) = self.seq_delimiters.last() { if let Some(len) = sd.len.get() { - let eos = sd.base_offset + len as u64; + let end_of_sequence = sd.base_offset + len as u64; let bytes_read = self.parser.bytes_read(); - if eos == bytes_read { - // end of delimiter, as indicated by the element's length - let token; - match sd.typ { - SeqTokenType::Sequence => { - self.in_sequence = false; - token = DataToken::SequenceEnd; + match end_of_sequence.cmp(&bytes_read) { + Ordering::Equal => { + // end of delimiter, as indicated by the element's length + let token; + match sd.typ { + SeqTokenType::Sequence => { + self.in_sequence = false; + token = DataToken::SequenceEnd; + } + SeqTokenType::Item => { + self.in_sequence = true; + token = DataToken::ItemEnd; + } } - SeqTokenType::Item => { - self.in_sequence = true; - token = DataToken::ItemEnd; + self.seq_delimiters.pop(); + return Ok(Some(token)); + } + Ordering::Less => { + return InconsistentSequenceEnd { + end_of_sequence, + bytes_read, } + .fail(); } - self.seq_delimiters.pop(); - return Ok(Some(token)); - } else if eos < bytes_read { - return Err(Error::InconsistentSequenceEnd(eos, bytes_read)); + Ordering::Greater => {} // continue normally } } } @@ -382,7 +435,7 @@ impl<'s> LazyDataSetReader<&'s mut dyn Read, DynStatefulDecoder<'s>> { ts: &TransferSyntax, cs: SpecificCharacterSet, ) -> Result { - let parser = StatefulDecoder::new_with(source, ts, cs)?; + let parser = StatefulDecoder::new_with(source, ts, cs).context(CreateDecoder)?; Ok(LazyDataSetReader { parser, @@ -453,7 +506,7 @@ where }, Err(e) => { self.hard_break = true; - Some(Err(e)) + Some(Err(e).context(ReadItemHeader)) } } } else { @@ -480,73 +533,13 @@ where } Err(e) => { self.hard_break = true; - Some(Err(e)) + Some(Err(e).context(ReadHeader)) } } } } } -/// A data type for a DICOM element residing in a file, or any other source -/// with random access. A position in the file is kept for future access. -#[derive(Debug, PartialEq, Clone, Copy)] -pub struct DicomElementMarker { - /// The header, kept in memory. At this level, the value representation - /// "UN" may also refer to a non-applicable vr (i.e. for items and - /// delimiters). - pub header: DataElementHeader, - /// The ending position of the element's header (or the starting position - /// of the element's value if it exists), relative to the beginning of the - /// file. - pub pos: u64, -} - -impl DicomElementMarker { - /// Obtain an interval of the raw data associated to this element's data value. - pub fn get_data_stream>( - &self, - source: B, - ) -> Result> - where - S: ReadSeek, - { - let len = u64::from( - self.header - .length() - .get() - .ok_or(InvalidValueReadError::UnresolvedValueLength)?, - ); - let interval = SeekInterval::new_at(source, self.pos..len)?; - Ok(interval) - } - - /// Move the source to the position indicated by the marker - pub fn move_to_start>(&self, mut source: B) -> Result<()> - where - S: Seek, - { - source.seek(SeekFrom::Start(self.pos))?; - Ok(()) - } - - /// Getter for this element's value representation. May be `UN` - /// when this is not applicable. - pub fn vr(&self) -> VR { - self.header.vr() - } -} - -impl HasLength for DicomElementMarker { - fn length(&self) -> Length { - self.header.length() - } -} -impl Header for DicomElementMarker { - fn tag(&self) -> Tag { - self.header.tag() - } -} - #[cfg(test)] mod tests { use super::{DataSetReader, DataToken, StatefulDecode, StatefulDecoder}; diff --git a/parser/src/dataset/write.rs b/parser/src/dataset/write.rs index 8f98d1cf..852e29a2 100644 --- a/parser/src/dataset/write.rs +++ b/parser/src/dataset/write.rs @@ -1,14 +1,68 @@ //! Module for the data set reader use crate::dataset::*; -use crate::error::{DataSetSyntaxError, Error, Result}; use crate::stateful::encode::StatefulEncoder; use dicom_core::{DataElementHeader, Length, VR}; use dicom_encoding::encode::EncodeTo; use dicom_encoding::text::{SpecificCharacterSet, TextCodec}; use dicom_encoding::transfer_syntax::DynEncoder; use dicom_encoding::TransferSyntax; +use snafu::{Backtrace, OptionExt, ResultExt, Snafu}; use std::io::Write; +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum Error { + /// Unsupported transfer syntax for encoding + #[snafu(display("Unsupported transfer syntax {} ({})", ts_uid, ts_alias))] + UnsupportedTransferSyntax { + ts_uid: &'static str, + ts_alias: &'static str, + backtrace: Backtrace, + }, + /// Character set known, but not supported + #[snafu(display("Unsupported character set {:?}", charset))] + UnsupportedCharacterSet { + charset: SpecificCharacterSet, + backtrace: Backtrace, + }, + /// An element value token appeared without an introducing element header + #[snafu(display("Unexpected token {:?} without element header", token))] + UnexpectedToken { + token: DataToken, + backtrace: Backtrace, + }, + #[snafu(display("Could not write element header"))] + WriteHeader { + #[snafu(backtrace)] + source: crate::stateful::encode::Error, + }, + #[snafu(display("Could not write item header"))] + WriteItemHeader { + #[snafu(backtrace)] + source: crate::stateful::encode::Error, + }, + + #[snafu(display("Could not write sequence delimiter"))] + WriteSequenceDelimiter { + #[snafu(backtrace)] + source: crate::stateful::encode::Error, + }, + + #[snafu(display("Could not write item delimiter"))] + WriteItemDelimiter { + #[snafu(backtrace)] + source: crate::stateful::encode::Error, + }, + + #[snafu(display("Could not write element value"))] + WriteValue { + #[snafu(backtrace)] + source: crate::stateful::encode::Error, + }, +} + +pub type Result = std::result::Result; + /// A writer-specific token representing a sequence or item start. #[derive(Debug)] struct SeqToken { @@ -33,11 +87,14 @@ impl<'w, W: 'w> DataSetWriter, Box> where W: Write, { - pub fn with_ts_cs(to: W, ts: &TransferSyntax, cs: SpecificCharacterSet) -> Result { - let encoder = ts - .encoder_for() - .ok_or_else(|| Error::UnsupportedTransferSyntax)?; - let text = cs.codec().ok_or_else(|| Error::UnsupportedCharacterSet)?; + pub fn with_ts_cs(to: W, ts: &TransferSyntax, charset: SpecificCharacterSet) -> Result { + let encoder = ts.encoder_for().context(UnsupportedTransferSyntax { + ts_uid: ts.uid(), + ts_alias: ts.name(), + })?; + let text = charset + .codec() + .context(UnsupportedCharacterSet { charset })?; Ok(DataSetWriter::new(to, encoder, text)) } } @@ -113,7 +170,7 @@ where Ok(()) } DataToken::ElementHeader(de) => { - self.last_de = Some(de.clone()); + self.last_de = Some(de); self.write_impl(token) } token @ DataToken::PixelSequenceStart => { @@ -132,38 +189,50 @@ where fn write_impl(&mut self, token: DataToken) -> Result<()> { match token { DataToken::ElementHeader(header) => { - self.printer.encode_element_header(header)?; + self.printer + .encode_element_header(header) + .context(WriteHeader)?; } DataToken::SequenceStart { tag, len } => { self.printer - .encode_element_header(DataElementHeader::new(tag, VR::SQ, len))?; + .encode_element_header(DataElementHeader::new(tag, VR::SQ, len)) + .context(WriteHeader)?; } DataToken::PixelSequenceStart => { - self.printer.encode_element_header(DataElementHeader::new( - Tag(0x7fe0, 0x0010), - VR::OB, - Length::UNDEFINED, - ))?; + self.printer + .encode_element_header(DataElementHeader::new( + Tag(0x7fe0, 0x0010), + VR::OB, + Length::UNDEFINED, + )) + .context(WriteHeader)?; } DataToken::SequenceEnd => { - self.printer.encode_sequence_delimiter()?; + self.printer + .encode_sequence_delimiter() + .context(WriteSequenceDelimiter)?; } DataToken::ItemStart { len } => { - self.printer.encode_item_header(len.0)?; + self.printer + .encode_item_header(len.0) + .context(WriteItemHeader)?; } DataToken::ItemEnd => { - self.printer.encode_item_delimiter()?; + self.printer + .encode_item_delimiter() + .context(WriteItemDelimiter)?; } DataToken::PrimitiveValue(ref value) => { - let last_de = self - .last_de - .as_ref() - .ok_or_else(|| DataSetSyntaxError::UnexpectedToken(token.clone()))?; - self.printer.encode_primitive(last_de, value)?; + let last_de = self.last_de.as_ref().with_context(|| UnexpectedToken { + token: token.clone(), + })?; + self.printer + .encode_primitive(last_de, value) + .context(WriteValue)?; self.last_de = None; } DataToken::ItemValue(data) => { - self.printer.write_bytes(&data)?; + self.printer.write_bytes(&data).context(WriteValue)?; } } Ok(()) diff --git a/parser/src/error.rs b/parser/src/error.rs deleted file mode 100644 index c1b77248..00000000 --- a/parser/src/error.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! Crate-leve error types. -use crate::dataset::DataToken; -use dicom_core::error::Error as CoreError; -pub use dicom_core::error::{CastValueError, ConvertValueError, InvalidValueReadError}; -use dicom_core::Tag; -use dicom_encoding::error::{Error as EncodingError, TextEncodingError}; -use quick_error::quick_error; -use std::fmt; -use std::io; - -/// Type alias for a result from this crate. -pub type Result = ::std::result::Result; - -quick_error! { - /// The main data type for errors in the library. - #[derive(Debug)] - pub enum Error { - /// Not valid DICOM content, typically raised when checking the magic code. - InvalidFormat { - display("Content is not DICOM or is corrupted") - } - /// A required element in the meta group is missing - MissingMetaElement(name: &'static str) { - display("Missing required meta element `{}`", name) - } - /// Raised when the obtained data element was not the one expected. - UnexpectedTag(tag: Tag) { - display("Unexpected DICOM tag {}", tag) - } - InconsistentSequenceEnd(eos: u64, bytes_read: u64) { - display("already read {} bytes, but end of sequence is @ {} bytes", bytes_read, eos) - } - /// Raised when the obtained length is inconsistent. - UnexpectedDataValueLength { - display("Inconsistent data value length in data element") - } - /// Raised when a read was illegally attempted. - IllegalDataRead { - display("Illegal data value read") - } - /// Raised when the demanded transfer syntax is not supported. - UnsupportedTransferSyntax { - display("Unsupported transfer syntax") - } - /// Raised when the required character set is not supported. - UnsupportedCharacterSet { - display("Unsupported character set") - } - /// Raised when attempting to fetch an element by an unknown attribute name. - NoSuchAttributeName { - display("No such attribute name") - } - /// Raised when attempting to fetch an unexistent element. - NoSuchDataElement { - display("No such data element") - } - /// Raised when attempting to read pixel data out of bounds. - PixelDataOutOfBounds { - display("Pixel data access index out of bounds") - } - /// Raised when a data set parser couldn't fetch a value after a primitive - /// data element's header. - MissingElementValue { - display("Expected value after data element header, but was missing") - } - /// Raised while parsing a DICOM data set and found an unexpected - /// element header or value. - DataSetSyntax(err: DataSetSyntaxError) { - from() - display("Data set syntax error: {}", err) - } - /// Error related to an invalid value read. - ReadValue(err: InvalidValueReadError) { - from() - display("Invalid value read: {}", err) - } - /// Error related to a failed text encoding / decoding procedure. - TextEncoding(err: TextEncodingError) { - from() - display("Failed text encoding/decoding: {}", err) - } - /// A failed attempt to cast a value to an inappropriate format. - CastValue(err: CastValueError) { - from() - display("Failed value cast: {}", err) - } - /// A failed attempt to convert a value to an inappropriate format. - ConvertValue(err: ConvertValueError) { - display("Failed value conversion: {}", err) - from() - } - /// Other I/O errors. - Io(err: io::Error) { - from() - display("I/O error: {}", err) - } - } -} - -impl From for Error { - fn from(e: CoreError) -> Self { - match e { - CoreError::UnexpectedDataValueLength => Error::UnexpectedDataValueLength, - CoreError::UnexpectedTag(tag) => Error::UnexpectedTag(tag), - CoreError::ReadValue(e) => Error::ReadValue(e), - CoreError::CastValue(e) => Error::CastValue(e), - CoreError::ConvertValue(e) => Error::ConvertValue(e), - } - } -} - -impl From for Error { - fn from(e: EncodingError) -> Self { - match e { - EncodingError::UnexpectedTag(tag) => Error::UnexpectedTag(tag), - EncodingError::UnexpectedDataValueLength => Error::UnexpectedDataValueLength, - EncodingError::ReadValue(e) => Error::ReadValue(e), - EncodingError::TextEncoding(e) => Error::TextEncoding(e), - EncodingError::CastValue(e) => Error::CastValue(e), - EncodingError::ConvertValue(e) => Error::ConvertValue(e), - EncodingError::Io(e) => Error::Io(e), - } - } -} - -#[derive(Debug)] -pub enum DataSetSyntaxError { - PrematureEnd, - UnexpectedToken(DataToken), -} - -impl fmt::Display for DataSetSyntaxError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - DataSetSyntaxError::PrematureEnd => write!(f, "{}", self), - DataSetSyntaxError::UnexpectedToken(ref token) => write!(f, "{} {}", self, token), - } - } -} - -impl ::std::error::Error for DataSetSyntaxError { - fn description(&self) -> &str { - match self { - DataSetSyntaxError::PrematureEnd => "data set ended prematurely", - DataSetSyntaxError::UnexpectedToken(_) => "unexpected data set token", - } - } -} diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 75366e8f..cf76c414 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -7,10 +7,8 @@ //! //! For a more intuitive, object-oriented API, please see the `dicom-object` //! crate. -#![recursion_limit = "90"] - pub mod dataset; -pub mod error; +pub mod marker; pub mod stateful; mod util; diff --git a/parser/src/marker.rs b/parser/src/marker.rs new file mode 100644 index 00000000..eb5b6495 --- /dev/null +++ b/parser/src/marker.rs @@ -0,0 +1,81 @@ +use dicom_core::header::{DataElementHeader, Header, HasLength, VR, Length, Tag}; +use crate::util::{SeekInterval, ReadSeek}; +use std::ops::DerefMut; +use std::io::{Seek, SeekFrom}; +use snafu::{OptionExt, ResultExt, Snafu}; + +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum Error { + #[snafu(display("Unknown value length"))] + UnknownValueLength, + CreateInterval { + source: std::io::Error, + } +} + +pub type Result = std::result::Result; + +/// A data type for a DICOM element residing in a file, or any other source +/// with random access. A position in the file is kept for future access. +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct DicomElementMarker { + /// The header, kept in memory. At this level, the value representation + /// "UN" may also refer to a non-applicable vr (i.e. for items and + /// delimiters). + pub header: DataElementHeader, + /// The ending position of the element's header (or the starting position + /// of the element's value if it exists), relative to the beginning of the + /// file. + pub pos: u64, +} + +impl DicomElementMarker { + /// Obtain an interval of the raw data associated to this element's data value. + pub fn get_data_stream>( + &self, + source: B, + ) -> Result> + where + S: ReadSeek, + { + let len = u64::from( + self.header + .length() + .get() + .context(UnknownValueLength)?, + ); + let interval = SeekInterval::new_at(source, self.pos..len) + .context(CreateInterval)?; + Ok(interval) + } + + /// Move the source to the position indicated by the marker + pub fn move_to_start>( + &self, + mut source: B, + ) -> std::io::Result<()> + where + S: Seek, + { + source.seek(SeekFrom::Start(self.pos))?; + Ok(()) + } + + /// Getter for this element's value representation. May be `UN` + /// when this is not applicable. + pub fn vr(&self) -> VR { + self.header.vr() + } +} + +impl HasLength for DicomElementMarker { + fn length(&self) -> Length { + self.header.length() + } +} +impl Header for DicomElementMarker { + fn tag(&self) -> Tag { + self.header.tag() + } +} diff --git a/parser/src/stateful/decode.rs b/parser/src/stateful/decode.rs index 26e17a65..09a9dcf4 100644 --- a/parser/src/stateful/decode.rs +++ b/parser/src/stateful/decode.rs @@ -1,8 +1,6 @@ -//! This module provides a higher level abstraction for reading DICOM data. -//! The structures provided here can translate a byte data source into -//! an iterator of elements, with either sequential or random access. +//! Module holding a stateful DICOM data decoding abstraction, +//! which also supports text decoding. -use crate::error::{Error, Result}; use crate::util::n_times; use chrono::FixedOffset; use dicom_core::header::{DataElementHeader, HasLength, Length, SequenceItemHeader, Tag, VR}; @@ -10,7 +8,6 @@ use dicom_core::value::{PrimitiveValue, C}; use dicom_encoding::decode::basic::{BasicDecoder, LittleEndianBasicDecoder}; use dicom_encoding::decode::primitive_value::*; use dicom_encoding::decode::{BasicDecode, DecodeFrom}; -use dicom_encoding::error::{InvalidValueReadError, Result as EncodingResult, TextEncodingError}; use dicom_encoding::text::{ validate_da, validate_dt, validate_tm, DefaultCharacterSetCodec, DynamicTextCodec, SpecificCharacterSet, TextCodec, TextValidationOutcome, @@ -18,10 +15,117 @@ use dicom_encoding::text::{ use dicom_encoding::transfer_syntax::explicit_le::ExplicitVRLittleEndianDecoder; use dicom_encoding::transfer_syntax::{DynDecoder, TransferSyntax}; use smallvec::smallvec; +use snafu::{Backtrace, OptionExt, ResultExt, Snafu}; use std::fmt::Debug; use std::io::Read; use std::iter::Iterator; +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum Error { + #[snafu(display("Decoding in transfer syntax {} is unsupported", ts))] + UnsupportedTransferSyntax { + ts: &'static str, + backtrace: Backtrace, + }, + + #[snafu(display("Unsupported character set {:?}", charset))] + UnsupportedCharacterSet { + charset: SpecificCharacterSet, + backtrace: Backtrace, + }, + + #[snafu(display("Attempted to read non-primitive value at position {}", position))] + NonPrimitiveType { + position: u64, + backtrace: Backtrace, + }, + + #[snafu(display( + "Undefined value length of element tagged {} at position {}", + tag, + position + ))] + UndefinedValueLength { + tag: Tag, + position: u64, + backtrace: Backtrace, + }, + + #[snafu(display("Could not decode element header at position {}", position))] + DecodeElementHeader { + position: u64, + #[snafu(backtrace)] + source: dicom_encoding::decode::Error, + }, + + #[snafu(display("Could not decode element header at position {}", position))] + DecodeItemHeader { + position: u64, + #[snafu(backtrace)] + source: dicom_encoding::decode::Error, + }, + + #[snafu(display("Could not decode text at position {}", position))] + DecodeText { + position: u64, + #[snafu(backtrace)] + source: dicom_encoding::text::DecodeTextError, + }, + + #[snafu(display( + "Could not read value from source at position {}: {}", + position, + source + ))] + ReadValueData { + position: u64, + source: std::io::Error, + backtrace: Backtrace, + }, + + #[snafu(display("Failed value deserialization at position {}", position))] + DeserializeValue { + position: u64, + source: dicom_core::value::deserialize::Error, + }, + + #[snafu(display("Invalid integer value at position {}", position))] + ReadInt { + position: u64, + source: std::num::ParseIntError, + }, + + #[snafu(display("Invalid float value at position {}", position))] + ReadFloat { + position: u64, + source: std::num::ParseFloatError, + }, + + #[snafu(display("Invalid Date value element `{}` at position {}", string, position))] + InvalidDateValue { + position: u64, + string: String, + backtrace: Backtrace, + }, + + #[snafu(display("Invalid Time value element `{}` at position {}", string, position))] + InvalidTimeValue { + position: u64, + string: String, + backtrace: Backtrace, + }, + + #[snafu(display("Invalid DateTime value element `{}` at position {}", string, position))] + InvalidDateTimeValue { + position: u64, + string: String, + backtrace: Backtrace, + }, +} + +pub type Result = std::result::Result; + pub trait StatefulDecode { type Reader: Read; @@ -111,15 +215,21 @@ pub type DicomParser = StatefulDecoder; impl<'s> DynStatefulDecoder<'s> { /// Create a new DICOM parser for the given transfer syntax and character set. - pub fn new_with(from: S, ts: &TransferSyntax, cs: SpecificCharacterSet) -> Result + pub fn new_with( + from: S, + ts: &TransferSyntax, + charset: SpecificCharacterSet, + ) -> Result where S: Read, { let basic = ts.basic_decoder(); let decoder = ts .decoder() - .ok_or_else(|| Error::UnsupportedTransferSyntax)?; - let text = cs.codec().ok_or_else(|| Error::UnsupportedCharacterSet)?; + .context(UnsupportedTransferSyntax { ts: ts.name() })?; + let text = charset + .codec() + .context(UnsupportedCharacterSet { charset })?; Ok(DynStatefulDecoder::new( Box::from(from), @@ -182,16 +292,29 @@ where { // ---------------- private methods --------------------- + fn require_known_length(&self, header: &DataElementHeader) -> Result { + header + .length() + .get() + .map(|len| len as usize) + .context(UndefinedValueLength { + position: self.bytes_read, + tag: header.tag, + }) + } + fn read_value_tag(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // tags let ntags = len >> 2; let parts: Result> = n_times(ntags) .map(|_| { - let g = self.basic.decode_us(&mut self.from)?; - let e = self.basic.decode_us(&mut self.from)?; - Ok(Tag(g, e)) + self.basic + .decode_tag(&mut self.from) + .context(ReadValueData { + position: self.bytes_read, + }) }) .collect(); self.bytes_read += len as u64; @@ -199,33 +322,47 @@ where } fn read_value_ob(&mut self, header: &DataElementHeader) -> Result { - // TODO add support for OB value data length resolution - // (might need to delegate pixel data reading to a separate trait) - let len = require_known_length(header)?; + // Note: this function always expects a defined length OB value + // (pixel sequence detection needs to be done by the caller) + let len = self.require_known_length(header)?; // sequence of 8-bit integers (or arbitrary byte data) let mut buf = smallvec![0u8; len]; - self.from.read_exact(&mut buf)?; + self.from.read_exact(&mut buf).context(ReadValueData { + position: self.bytes_read, + })?; self.bytes_read += len as u64; Ok(PrimitiveValue::U8(buf)) } fn read_value_strs(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // sequence of strings self.buffer.resize_with(len, Default::default); - self.from.read_exact(&mut self.buffer)?; + self.from + .read_exact(&mut self.buffer) + .context(ReadValueData { + position: self.bytes_read, + })?; - let parts: EncodingResult> = match header.vr() { + let parts: Result> = match header.vr() { VR::AE | VR::CS | VR::AS => self .buffer .split(|v| *v == b'\\') - .map(|slice| DefaultCharacterSetCodec.decode(slice)) + .map(|slice| { + DefaultCharacterSetCodec.decode(slice).context(DecodeText { + position: self.bytes_read, + }) + }) .collect(), _ => self .buffer .split(|v| *v == b'\\') - .map(|slice| self.text.decode(slice)) + .map(|slice| { + self.text.decode(slice).context(DecodeText { + position: self.bytes_read, + }) + }) .collect(), }; @@ -234,44 +371,64 @@ where } fn read_value_str(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // a single string self.buffer.resize_with(len, Default::default); - self.from.read_exact(&mut self.buffer)?; + self.from + .read_exact(&mut self.buffer) + .context(ReadValueData { + position: self.bytes_read, + })?; self.bytes_read += len as u64; - Ok(PrimitiveValue::Str(self.text.decode(&self.buffer[..])?)) + Ok(PrimitiveValue::Str( + self.text.decode(&self.buffer[..]).context(DecodeText { + position: self.bytes_read, + })?, + )) } fn read_value_ss(&mut self, header: &DataElementHeader) -> Result { // sequence of 16-bit signed integers - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; let n = len >> 1; - let vec: EncodingResult> = n_times(n) - .map(|_| self.basic.decode_ss(&mut self.from)) + let vec: Result> = n_times(n) + .map(|_| { + self.basic.decode_ss(&mut self.from).context(ReadValueData { + position: self.bytes_read, + }) + }) .collect(); self.bytes_read += len as u64; Ok(PrimitiveValue::I16(vec?)) } fn read_value_fl(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // sequence of 32-bit floats let n = len >> 2; - let vec: EncodingResult> = n_times(n) - .map(|_| self.basic.decode_fl(&mut self.from)) + let vec: Result> = n_times(n) + .map(|_| { + self.basic.decode_fl(&mut self.from).context(ReadValueData { + position: self.bytes_read, + }) + }) .collect(); self.bytes_read += len as u64; Ok(PrimitiveValue::F32(vec?)) } fn read_value_da(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // sequence of dates self.buffer.resize_with(len, Default::default); - self.from.read_exact(&mut self.buffer)?; + self.from + .read_exact(&mut self.buffer) + .context(ReadValueData { + position: self.bytes_read, + })?; let buf = trim_trail_empty_bytes(&self.buffer); if buf.is_empty() { return Ok(PrimitiveValue::Empty); @@ -281,26 +438,36 @@ where let lossy_str = DefaultCharacterSetCodec .decode(buf) .unwrap_or_else(|_| "[byte stream]".to_string()); - return Err(TextEncodingError::new(format!( - "Invalid date value element \"{}\"", - lossy_str - )) - .into()); + return InvalidDateValue { + position: self.bytes_read, + string: lossy_str, + } + .fail(); } let vec: Result> = buf .split(|b| *b == b'\\') - .map(|part| Ok(parse_date(part)?.0)) + .map(|part| { + Ok(parse_date(part) + .context(DeserializeValue { + position: self.bytes_read, + })? + .0) + }) .collect(); self.bytes_read += len as u64; Ok(PrimitiveValue::Date(vec?)) } fn read_value_ds(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // sequence of doubles in text form self.buffer.resize_with(len, Default::default); - self.from.read_exact(&mut self.buffer)?; + self.from + .read_exact(&mut self.buffer) + .context(ReadValueData { + position: self.bytes_read, + })?; let buf = trim_trail_empty_bytes(&self.buffer); if buf.is_empty() { return Ok(PrimitiveValue::Empty); @@ -310,10 +477,13 @@ where .split(|b| *b == b'\\') .map(|slice| { let codec = SpecificCharacterSet::Default.codec().unwrap(); - let txt = codec.decode(slice)?; + let txt = codec.decode(slice).context(DecodeText { + position: self.bytes_read, + })?; let txt = txt.trim(); - txt.parse::() - .map_err(|e| Error::from(InvalidValueReadError::from(e))) + txt.parse::().context(ReadFloat { + position: self.bytes_read, + }) }) .collect(); self.bytes_read += len as u64; @@ -321,11 +491,15 @@ where } fn read_value_dt(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // sequence of datetimes self.buffer.resize_with(len, Default::default); - self.from.read_exact(&mut self.buffer)?; + self.from + .read_exact(&mut self.buffer) + .context(ReadValueData { + position: self.bytes_read, + })?; let buf = trim_trail_empty_bytes(&self.buffer); if buf.is_empty() { return Ok(PrimitiveValue::Empty); @@ -335,15 +509,21 @@ where let lossy_str = DefaultCharacterSetCodec .decode(buf) .unwrap_or_else(|_| "[byte stream]".to_string()); - return Err(TextEncodingError::new(format!( - "Invalid date-time value element \"{}\"", - lossy_str - )) - .into()); + return InvalidDateTimeValue { + position: self.bytes_read, + string: lossy_str, + } + .fail(); } let vec: Result> = buf .split(|b| *b == b'\\') - .map(|part| Ok(parse_datetime(part, self.dt_utc_offset)?)) + .map(|part| { + Ok( + parse_datetime(part, self.dt_utc_offset).context(DeserializeValue { + position: self.bytes_read, + })?, + ) + }) .collect(); self.bytes_read += len as u64; @@ -351,10 +531,14 @@ where } fn read_value_is(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // sequence of signed integers in text form self.buffer.resize_with(len, Default::default); - self.from.read_exact(&mut self.buffer)?; + self.from + .read_exact(&mut self.buffer) + .context(ReadValueData { + position: self.bytes_read, + })?; let buf = trim_trail_empty_bytes(&self.buffer); if buf.is_empty() { return Ok(PrimitiveValue::Empty); @@ -364,10 +548,13 @@ where .split(|v| *v == b'\\') .map(|slice| { let codec = SpecificCharacterSet::Default.codec().unwrap(); - let txt = codec.decode(slice)?; + let txt = codec.decode(slice).context(DecodeText { + position: self.bytes_read, + })?; let txt = txt.trim(); - txt.parse::() - .map_err(|e| Error::from(InvalidValueReadError::from(e))) + txt.parse::().context(ReadInt { + position: self.bytes_read, + }) }) .collect(); self.bytes_read += len as u64; @@ -375,11 +562,15 @@ where } fn read_value_tm(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // sequence of time instances self.buffer.resize_with(len, Default::default); - self.from.read_exact(&mut self.buffer)?; + self.from + .read_exact(&mut self.buffer) + .context(ReadValueData { + position: self.bytes_read, + })?; let buf = trim_trail_empty_bytes(&self.buffer); if buf.is_empty() { return Ok(PrimitiveValue::Empty); @@ -389,86 +580,114 @@ where let lossy_str = DefaultCharacterSetCodec .decode(buf) .unwrap_or_else(|_| "[byte stream]".to_string()); - return Err(TextEncodingError::new(format!( - "Invalid time value element \"{}\"", - lossy_str - )) - .into()); + return InvalidTimeValue { + position: self.bytes_read, + string: lossy_str, + } + .fail(); } let vec: std::result::Result, _> = buf .split(|b| *b == b'\\') - .map(|part| parse_time(part).map(|t| t.0)) + .map(|part| { + parse_time(part).map(|t| t.0).context(DeserializeValue { + position: self.bytes_read, + }) + }) .collect(); self.bytes_read += len as u64; Ok(PrimitiveValue::Time(vec?)) } fn read_value_od(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // sequence of 64-bit floats let n = len >> 3; - let vec: EncodingResult> = n_times(n) - .map(|_| self.basic.decode_fd(&mut self.from)) + let vec: Result> = n_times(n) + .map(|_| { + self.basic.decode_fd(&mut self.from).context(ReadValueData { + position: self.bytes_read, + }) + }) .collect(); self.bytes_read += len as u64; Ok(PrimitiveValue::F64(vec?)) } fn read_value_ul(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // sequence of 32-bit unsigned integers let n = len >> 2; - let vec: EncodingResult> = n_times(n) - .map(|_| self.basic.decode_ul(&mut self.from)) + let vec: Result> = n_times(n) + .map(|_| { + self.basic.decode_ul(&mut self.from).context(ReadValueData { + position: self.bytes_read, + }) + }) .collect(); self.bytes_read += len as u64; Ok(PrimitiveValue::U32(vec?)) } fn read_value_us(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // sequence of 16-bit unsigned integers let n = len >> 1; - let vec: EncodingResult> = n_times(n) - .map(|_| self.basic.decode_us(&mut self.from)) + let vec: Result> = n_times(n) + .map(|_| { + self.basic.decode_us(&mut self.from).context(ReadValueData { + position: self.bytes_read, + }) + }) .collect(); self.bytes_read += len as u64; Ok(PrimitiveValue::U16(vec?)) } fn read_value_uv(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // sequence of 64-bit unsigned integers let n = len >> 3; - let vec: EncodingResult> = n_times(n) - .map(|_| self.basic.decode_uv(&mut self.from)) + let vec: Result> = n_times(n) + .map(|_| { + self.basic.decode_uv(&mut self.from).context(ReadValueData { + position: self.bytes_read, + }) + }) .collect(); self.bytes_read += len as u64; Ok(PrimitiveValue::U64(vec?)) } fn read_value_sl(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // sequence of 32-bit signed integers let n = len >> 2; - let vec: EncodingResult> = n_times(n) - .map(|_| self.basic.decode_sl(&mut self.from)) + let vec: Result> = n_times(n) + .map(|_| { + self.basic.decode_sl(&mut self.from).context(ReadValueData { + position: self.bytes_read, + }) + }) .collect(); self.bytes_read += len as u64; Ok(PrimitiveValue::I32(vec?)) } fn read_value_sv(&mut self, header: &DataElementHeader) -> Result { - let len = require_known_length(header)?; + let len = self.require_known_length(header)?; // sequence of 64-bit signed integers let n = len >> 3; - let vec: EncodingResult> = n_times(n) - .map(|_| self.basic.decode_sv(&mut self.from)) + let vec: Result> = n_times(n) + .map(|_| { + self.basic.decode_sv(&mut self.from).context(ReadValueData { + position: self.bytes_read, + }) + }) .collect(); self.bytes_read += len as u64; Ok(PrimitiveValue::I64(vec?)) @@ -485,7 +704,7 @@ where fn set_character_set(&mut self, charset: SpecificCharacterSet) -> Result<()> { self.text = charset .codec() - .ok_or_else(|| Error::UnsupportedCharacterSet)?; + .context(UnsupportedCharacterSet { charset })?; Ok(()) } @@ -532,6 +751,9 @@ where fn decode_header(&mut self) -> Result { self.decoder .decode_header(&mut self.from) + .context(DecodeElementHeader { + position: self.bytes_read, + }) .map(|(header, bytes_read)| { self.bytes_read += bytes_read as u64; header @@ -542,6 +764,9 @@ where fn decode_item_header(&mut self) -> Result { self.decoder .decode_item_header(&mut self.from) + .context(DecodeItemHeader { + position: self.bytes_read, + }) .map(|header| { self.bytes_read += 8; header @@ -558,7 +783,9 @@ where VR::SQ => { // sequence objects should not head over here, they are // handled at a higher level - Err(Error::from(InvalidValueReadError::NonPrimitiveType)) + NonPrimitiveType { + position: self.bytes_read, + }.fail() } VR::AT => self.read_value_tag(header), VR::AE | VR::AS | VR::PN | VR::SH | VR::LO | VR::UC | VR::UI => { @@ -591,7 +818,9 @@ where match header.vr() { VR::SQ => { // sequence objects... should not work - Err(Error::from(InvalidValueReadError::NonPrimitiveType)) + NonPrimitiveType { + position: self.bytes_read, + }.fail() } VR::AT => self.read_value_tag(header), VR::AE @@ -628,7 +857,9 @@ where match header.vr() { VR::SQ => { // sequence objects... should not work - Err(Error::from(InvalidValueReadError::NonPrimitiveType)) + NonPrimitiveType { + position: self.bytes_read, + }.fail() } _ => self.read_value_ob(header), } @@ -643,7 +874,9 @@ where match header.vr() { VR::SQ => { // sequence objects... should not work - Err(Error::from(InvalidValueReadError::NonPrimitiveType)) + NonPrimitiveType { + position: self.bytes_read, + }.fail() } _ => Ok(self.from.by_ref().take( header @@ -656,7 +889,9 @@ where } fn read_bytes(&mut self, buf: &mut [u8]) -> Result<()> { - self.from.read_exact(buf)?; + self.from.read_exact(buf).context(ReadValueData { + position: self.bytes_read, + })?; self.bytes_read += buf.len() as u64; Ok(()) } @@ -674,16 +909,6 @@ fn trim_trail_empty_bytes(mut x: &[u8]) -> &[u8] { x } -fn require_known_length( - header: &DataElementHeader, -) -> std::result::Result { - header - .length() - .get() - .map(|len| len as usize) - .ok_or_else(|| InvalidValueReadError::UnresolvedValueLength) -} - #[cfg(test)] mod tests { use super::{StatefulDecode, StatefulDecoder}; diff --git a/parser/src/stateful/encode.rs b/parser/src/stateful/encode.rs index 98157368..83f592cd 100644 --- a/parser/src/stateful/encode.rs +++ b/parser/src/stateful/encode.rs @@ -1,8 +1,6 @@ //! Module holding a stateful DICOM data encoding abstraction, //! in a way which supports text encoding. -//! -use crate::error::{Error, Result}; use dicom_core::{value::PrimitiveValue, DataElementHeader, VR}; use dicom_encoding::transfer_syntax::DynEncoder; use dicom_encoding::{ @@ -10,8 +8,46 @@ use dicom_encoding::{ text::{DefaultCharacterSetCodec, SpecificCharacterSet, TextCodec}, TransferSyntax, }; +use snafu::{Backtrace, OptionExt, ResultExt, Snafu}; use std::io::Write; +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum Error { + #[snafu(display("Encoding in transfer syntax {} is unsupported", ts))] + UnsupportedTransferSyntax { + ts: &'static str, + backtrace: Backtrace, + }, + + #[snafu(display("Unsupported character set {:?}", charset))] + UnsupportedCharacterSet { + charset: SpecificCharacterSet, + backtrace: Backtrace, + }, + + #[snafu(display("Failed to encode a data piece at position {}", position))] + EncodeData { + position: u64, + source: dicom_encoding::encode::Error, + }, + + #[snafu(display("Could not encode text at position {}", position))] + EncodeText { + position: u64, + source: dicom_encoding::text::EncodeTextError, + }, + + #[snafu(display("Could not write value data to writer"))] + WriteValueData { + position: u64, + source: std::io::Error, + backtrace: Backtrace, + }, +} + +pub type Result = std::result::Result; + /// Also called a printer, this encoder type provides a stateful mid-level /// abstraction for writing DICOM content. Unlike `Encode`, /// the stateful encoder knows how to write text values and keeps track @@ -52,12 +88,14 @@ impl<'s> DynStatefulEncoder<'s> { pub fn from_transfer_syntax( to: Box, ts: TransferSyntax, - cs: SpecificCharacterSet, + charset: SpecificCharacterSet, ) -> Result { let encoder = ts .encoder() - .ok_or_else(|| Error::UnsupportedTransferSyntax)?; - let text = cs.codec().ok_or_else(|| Error::UnsupportedCharacterSet)?; + .context(UnsupportedTransferSyntax { ts: ts.uid() })?; + let text = charset + .codec() + .context(UnsupportedCharacterSet { charset })?; Ok(StatefulEncoder::new(to, encoder, text)) } @@ -71,35 +109,54 @@ where { /// Encode and write a data element header. pub fn encode_element_header(&mut self, de: DataElementHeader) -> Result<()> { - let bytes = self.encoder.encode_element_header(&mut self.to, de)?; + let bytes = self + .encoder + .encode_element_header(&mut self.to, de) + .context(EncodeData { + position: self.bytes_written, + })?; self.bytes_written += bytes as u64; Ok(()) } /// Encode and write an item header. pub fn encode_item_header(&mut self, len: u32) -> Result<()> { - self.encoder.encode_item_header(&mut self.to, len)?; + self.encoder + .encode_item_header(&mut self.to, len) + .context(EncodeData { + position: self.bytes_written, + })?; self.bytes_written += 8; Ok(()) } /// Encode and write an item delimiter. pub fn encode_item_delimiter(&mut self) -> Result<()> { - self.encoder.encode_item_delimiter(&mut self.to)?; + self.encoder + .encode_item_delimiter(&mut self.to) + .context(EncodeData { + position: self.bytes_written, + })?; self.bytes_written += 8; Ok(()) } /// Encode and write a sequence delimiter. pub fn encode_sequence_delimiter(&mut self) -> Result<()> { - self.encoder.encode_sequence_delimiter(&mut self.to)?; + self.encoder + .encode_sequence_delimiter(&mut self.to) + .context(EncodeData { + position: self.bytes_written, + })?; self.bytes_written += 8; Ok(()) } /// Write all bytes directly to the inner writer. pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<()> { - self.to.write_all(bytes)?; + self.to.write_all(bytes).context(WriteValueData { + position: self.bytes_written, + })?; self.bytes_written += bytes.len() as u64; Ok(()) } @@ -127,7 +184,12 @@ where Ok(()) } _ => { - let bytes = self.encoder.encode_primitive(&mut self.to, value)?; + let bytes = + self.encoder + .encode_primitive(&mut self.to, value) + .context(EncodeData { + position: self.bytes_written, + })?; self.bytes_written += bytes as u64; Ok(()) } @@ -137,7 +199,9 @@ where fn encode_text(&mut self, text: &str, vr: VR) -> Result<()> { let bytes = self.encode_text_untrailed(text, vr)?; if bytes % 2 == 1 { - self.to.write_all(b" ")?; + self.to.write_all(b" ").context(WriteValueData { + position: self.bytes_written, + })?; self.bytes_written += 1; } Ok(()) @@ -151,13 +215,17 @@ where for (i, text) in texts.iter().enumerate() { acc += self.encode_text_untrailed(text.as_ref(), vr)?; if i < texts.len() - 1 { - self.to.write_all(b"\\")?; + self.to.write_all(b"\\").context(WriteValueData { + position: self.bytes_written, + })?; acc += 1; self.bytes_written += 1; } } if acc % 2 == 1 { - self.to.write_all(b" ")?; + self.to.write_all(b" ").context(WriteValueData { + position: self.bytes_written, + })?; self.bytes_written += 1; } Ok(()) @@ -167,11 +235,17 @@ where let data = match vr { VR::AE | VR::AS | VR::CS | VR::DA | VR::DS | VR::DT | VR::IS | VR::TM | VR::UI => { // these VRs always use the default character repertoire - DefaultCharacterSetCodec.encode(text)? + DefaultCharacterSetCodec.encode(text).context(EncodeText { + position: self.bytes_written, + })? } - _ => self.text.encode(text)?, + _ => self.text.encode(text).context(EncodeText { + position: self.bytes_written, + })?, }; - self.to.write_all(&data)?; + self.to.write_all(&data).context(WriteValueData { + position: self.bytes_written, + })?; self.bytes_written += data.len() as u64; Ok(data.len()) } diff --git a/parser/src/stateful/mod.rs b/parser/src/stateful/mod.rs index 1eacb464..55d83262 100644 --- a/parser/src/stateful/mod.rs +++ b/parser/src/stateful/mod.rs @@ -1,4 +1,4 @@ //! Stateful counterparts for decoding and encoding DICOM content. pub mod decode; -pub mod encode; +pub mod encode; \ No newline at end of file diff --git a/scpproxy/Cargo.toml b/scpproxy/Cargo.toml index 9cd38285..29670551 100644 --- a/scpproxy/Cargo.toml +++ b/scpproxy/Cargo.toml @@ -15,6 +15,6 @@ repository = "Enet4/dicom-rs" [dependencies] clap = "2.33.0" -quick-error = "1.2.3" dicom-ul = { path = "../ul/", version = "0.1.0" } dicom-dictionary-std = { path = "../dictionary-std/", version = "0.2.0" } +snafu = "0.6.8" diff --git a/scpproxy/src/main.rs b/scpproxy/src/main.rs index 446d7a79..a09357cc 100644 --- a/scpproxy/src/main.rs +++ b/scpproxy/src/main.rs @@ -2,40 +2,84 @@ use clap::{App, Arg}; use dicom_ul::pdu::reader::{read_pdu, DEFAULT_MAX_PDU}; use dicom_ul::pdu::writer::write_pdu; use dicom_ul::pdu::Pdu; -use quick_error::quick_error; use std::net::{Shutdown, TcpListener, TcpStream}; use std::sync::mpsc; use std::sync::mpsc::{Receiver, Sender}; use std::thread; use std::thread::JoinHandle; +use snafu::{Backtrace, ErrorCompat, OptionExt, ResultExt, Snafu}; type Result = std::result::Result; -quick_error! { - #[derive(Debug)] - pub enum Error { - Io(err: std::io::Error) { - from() - } - DUL(err: dicom_ul::error::Error) { - from() - } - ThreadPanicked { - from() - } - RecvError(err: std::sync::mpsc::RecvError) { - from() +#[derive(Debug, Snafu)] +#[non_exhaustive] +enum Error { + #[snafu(display("Could not clone socket"))] + CloneSocket { + backtrace: Backtrace, + source: std::io::Error, + }, + #[snafu(display("Could not send message"))] + SendMessage { + backtrace: Backtrace, + source: std::sync::mpsc::SendError, + }, + #[snafu(display("Could not receive message"))] + ReceiveMessage { + backtrace: Backtrace, + source: std::sync::mpsc::RecvError, + }, + #[snafu(display("Could not close socket"))] + CloseSocket { + backtrace: Backtrace, + source: std::io::Error, + }, + #[snafu(display("Could not connect to destination SCP"))] + Connect { + backtrace: Backtrace, + source: std::io::Error, + }, + #[snafu(display("SCP reader thread panicked"))] + ScpReaderPanic { + backtrace: Backtrace, + }, + #[snafu(display("SCU reader thread panicked"))] + ScuReaderPanic { + backtrace: Backtrace, + } +} + +fn report(err: E) +where + E: std::error::Error, + E: ErrorCompat, +{ + eprintln!("[ERROR] {}", err); + if let Some(source) = err.source() { + eprintln!(); + eprintln!("Caused by:"); + for (i, e) in std::iter::successors(Some(source), |e| e.source()).enumerate() { + eprintln!(" {}: {}", i, e); } - SendError(err: std::sync::mpsc::SendError) { - from() + } + + let env_backtrace = std::env::var("RUST_BACKTRACE").unwrap_or_default(); + let env_lib_backtrace = std::env::var("RUST_LIB_BACKTRACE").unwrap_or_default(); + if env_lib_backtrace == "1" || (env_backtrace == "1" && env_lib_backtrace != "0") { + if let Some(backtrace) = ErrorCompat::backtrace(&err) { + eprintln!(); + eprintln!("Backtrace:"); + eprintln!("{}", backtrace); } } } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum ProviderType { - SCP, - SCU, + /// Service class provider + Scp, + /// Service class user + Scu, } #[derive(Debug)] @@ -44,9 +88,13 @@ pub enum ThreadMessage { to: ProviderType, pdu: Pdu, }, - Err { + ReadErr { + from: ProviderType, + err: dicom_ul::pdu::reader::Error, + }, + WriteErr { from: ProviderType, - err: dicom_ul::error::Error, + err: dicom_ul::pdu::writer::Error, }, Shutdown { initiator: ProviderType, @@ -65,28 +113,28 @@ fn run(scu_stream: &mut TcpStream, destination_addr: &str) -> Result<()> { let scp_reader_thread: JoinHandle>; { - let mut reader = scu_stream.try_clone()?; + let mut reader = scu_stream.try_clone().context(CloneSocket)?; let message_tx = message_tx.clone(); scu_reader_thread = thread::spawn(move || { loop { match read_pdu(&mut reader, DEFAULT_MAX_PDU) { Ok(pdu) => { message_tx.send(ThreadMessage::SendPdu { - to: ProviderType::SCP, + to: ProviderType::Scp, pdu, - })?; + }).context(SendMessage)?; } - Err(e) => { - if let dicom_ul::error::Error::NoPduAvailable = e { - message_tx.send(ThreadMessage::Shutdown { - initiator: ProviderType::SCU, - })?; - } else { - message_tx.send(ThreadMessage::Err { - from: ProviderType::SCU, - err: e, - })?; - } + Err(dicom_ul::pdu::reader::Error::NoPduAvailable {..}) => { + message_tx.send(ThreadMessage::Shutdown { + initiator: ProviderType::Scu, + }).context(SendMessage)?; + break; + } + Err(err) => { + message_tx.send(ThreadMessage::ReadErr { + from: ProviderType::Scu, + err, + }).context(SendMessage)?; break; } } @@ -97,27 +145,27 @@ fn run(scu_stream: &mut TcpStream, destination_addr: &str) -> Result<()> { } { - let mut reader = scp_stream.try_clone()?; + let mut reader = scp_stream.try_clone().context(CloneSocket)?; scp_reader_thread = thread::spawn(move || { loop { match read_pdu(&mut reader, DEFAULT_MAX_PDU) { Ok(pdu) => { message_tx.send(ThreadMessage::SendPdu { - to: ProviderType::SCU, + to: ProviderType::Scu, pdu, - })?; + }).context(SendMessage)?; } - Err(e) => { - if let dicom_ul::error::Error::NoPduAvailable = e { + Err(dicom_ul::pdu::reader::Error::NoPduAvailable { .. }) => { message_tx.send(ThreadMessage::Shutdown { - initiator: ProviderType::SCP, - })?; - } else { - message_tx.send(ThreadMessage::Err { - from: ProviderType::SCP, - err: e, - })?; - } + initiator: ProviderType::Scp, + }).context(SendMessage)?; + break; + } + Err(err) => { + message_tx.send(ThreadMessage::ReadErr { + from: ProviderType::Scp, + err, + }).context(SendMessage)?; break; } } @@ -128,20 +176,26 @@ fn run(scu_stream: &mut TcpStream, destination_addr: &str) -> Result<()> { } loop { - let message = message_rx.recv()?; + let message = message_rx.recv().context(ReceiveMessage)?; match message { ThreadMessage::SendPdu { to, pdu } => match to { - ProviderType::SCU => { + ProviderType::Scu => { println!("scu <---- scp: {:?}", &pdu); write_pdu(scu_stream, &pdu).unwrap(); } - ProviderType::SCP => { + ProviderType::Scp => { println!("scu ----> scp: {:?}", &pdu); write_pdu(scp_stream, &pdu).unwrap(); } }, - ThreadMessage::Err { from, err } => { - eprintln!("error from {:?}: {}", from, err); + ThreadMessage::ReadErr { from, err } => { + eprintln!("error reading from {:?}:", from); + report(err); + break; + } + ThreadMessage::WriteErr { from, err } => { + eprintln!("error writing to {:?}", from); + report(err); break; } ThreadMessage::Shutdown { initiator } => { @@ -151,22 +205,23 @@ fn run(scu_stream: &mut TcpStream, destination_addr: &str) -> Result<()> { } } - scu_stream.shutdown(Shutdown::Read)?; + scu_stream.shutdown(Shutdown::Read).context(CloseSocket)?; scu_reader_thread .join() - .map_err(|_| Error::ThreadPanicked)??; + .ok() + .context(ScuReaderPanic)??; - scp_stream.shutdown(Shutdown::Read)?; + scp_stream.shutdown(Shutdown::Read).context(CloseSocket)?; scp_reader_thread .join() - .map_err(|_| Error::ThreadPanicked)??; + .ok() + .context(ScpReaderPanic)??; Ok(()) } Err(e) => { - scu_stream.shutdown(Shutdown::Both)?; - eprintln!("error connection to destination SCP: {}", e); - Err(e.into()) + scu_stream.shutdown(Shutdown::Both).context(CloseSocket)?; + Err(e).context(Connect) } } } @@ -210,11 +265,11 @@ fn main() { match stream { Ok(ref mut scu_stream) => { if let Err(e) = run(scu_stream, &destination_addr) { - eprintln!("error: {}", e); + report(e); } } Err(e) => { - eprintln!("error: {}", e); + eprintln!("[ERROR] {}", e); } } } diff --git a/ul/Cargo.toml b/ul/Cargo.toml index f3651f46..36e528cf 100644 --- a/ul/Cargo.toml +++ b/ul/Cargo.toml @@ -14,9 +14,9 @@ readme = "README.md" repository = "Enet4/dicom-rs" [dependencies] -quick-error = "1.2.3" byteordered = "0.5.0" dicom-encoding = { path = "../encoding/", version = "0.2.0" } +snafu = "0.6.8" [dev-dependencies] matches = "0.1.8" diff --git a/ul/src/error.rs b/ul/src/error.rs deleted file mode 100644 index 8e7759b6..00000000 --- a/ul/src/error.rs +++ /dev/null @@ -1,57 +0,0 @@ -use quick_error::quick_error; - -/// Type alias for a result from this crate. -pub type Result = std::result::Result; - -quick_error! { - #[derive(Debug)] - pub enum Error { - Io(err: std::io::Error) { - from() - display("io error: {}", err) - } - NoPduAvailable { - display("no pdu was available") - } - InvalidMaxPdu { - display("invalid max pdu") - } - PduTooLarge { - display("the incoming pdu was too large") - } - InvalidPduVariable { - display("the pdu contained an invalid value") - } - MultipleTransferSyntaxesAccepted { - display("multiple transfer syntaxes were accepted") - } - InvalidRejectSourceOrReason { - display("the reject source or reason was invalid") - } - InvalidAbortSourceOrReason { - display("the abort service provider reason was invalid") - } - InvalidPresentationContextResultReason { - display("the presentation context result reason was invalid") - } - InvalidTransferSyntaxSubItem { - display("invalid transfer syntax sub-item") - } - UnknownPresentationContextSubItem { - display("unknown presentation context sub-item") - } - EncodingError(err: dicom_encoding::error::Error) { - from() - display("{} encoding error", err) - } - MissingApplicationContextName { - display("missing application context name") - } - MissingAbstractSyntax { - display("missing abstract syntax") - } - MissingTransferSyntax { - display("missing transfer syntax") - } - } -} diff --git a/ul/src/lib.rs b/ul/src/lib.rs index bb92b4bc..5326bf19 100644 --- a/ul/src/lib.rs +++ b/ul/src/lib.rs @@ -5,5 +5,4 @@ //! Eventually, a finite-state-machine and higher-level SCU/SCP helpers will be added that will make //! interacting with these types more idiomatic and friendly. -pub mod error; pub mod pdu; diff --git a/ul/src/pdu/mod.rs b/ul/src/pdu/mod.rs index 56e60433..de9f8e7e 100644 --- a/ul/src/pdu/mod.rs +++ b/ul/src/pdu/mod.rs @@ -1,9 +1,6 @@ pub mod reader; pub mod writer; -#[cfg(test)] -mod test; - #[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Debug)] pub struct PresentationContextProposed { pub id: u8, diff --git a/ul/src/pdu/reader.rs b/ul/src/pdu/reader.rs index b08c6476..771bd3e4 100644 --- a/ul/src/pdu/reader.rs +++ b/ul/src/pdu/reader.rs @@ -1,44 +1,132 @@ -use crate::error::{Error, Result}; use crate::pdu::*; use byteordered::byteorder::{BigEndian, ReadBytesExt}; use dicom_encoding::text::{SpecificCharacterSet, TextCodec}; +use snafu::{ensure, Backtrace, OptionExt, ResultExt, Snafu}; use std::io::{Cursor, ErrorKind, Read, Seek, SeekFrom}; pub const DEFAULT_MAX_PDU: u32 = 16_384; pub const MINIMUM_PDU_SIZE: u32 = 4_096; pub const MAXIMUM_PDU_SIZE: u32 = 131_072; +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum Error { + #[snafu(display("Invalid max PDU length {}", max_pdu_length))] + InvalidMaxPdu { + max_pdu_length: u32, + backtrace: Backtrace, + }, + + #[snafu(display("No PDU available"))] + NoPduAvailable { backtrace: Backtrace }, + + #[snafu(display("Could not read PDU"))] + ReadPdu { + source: std::io::Error, + backtrace: Backtrace, + }, + + #[snafu(display("Could not read PDU item"))] + ReadPduItem { + source: std::io::Error, + backtrace: Backtrace, + }, + + #[snafu(display("Could not read PDU field `{}`", field))] + ReadPduField { + field: &'static str, + source: std::io::Error, + backtrace: Backtrace, + }, + + #[snafu(display("Could not read {} reserved bytes", bytes))] + ReadReserved { + bytes: u32, + source: std::io::Error, + backtrace: Backtrace, + }, + + #[snafu(display( + "Incoming pdu was too large: length {}, maximum is {}", + pdu_length, + max_pdu_length + ))] + PduTooLarge { + pdu_length: u32, + max_pdu_length: u32, + backtrace: Backtrace, + }, + #[snafu(display("PDU contained an invalid value {:?}", var_item))] + InvalidPduVariable { + var_item: PduVariableItem, + backtrace: Backtrace, + }, + #[snafu(display("Multiple transfer syntaxes were accepted"))] + MultipleTransferSyntaxesAccepted { backtrace: Backtrace }, + #[snafu(display("Invalid reject source or reason"))] + InvalidRejectSourceOrReason { backtrace: Backtrace }, + #[snafu(display("Invalid abort service provider"))] + InvalidAbortSourceOrReason { backtrace: Backtrace }, + #[snafu(display("Invalid presentation context result reason"))] + InvalidPresentationContextResultReason { backtrace: Backtrace }, + #[snafu(display("invalid transfer syntax sub-item"))] + InvalidTransferSyntaxSubItem { backtrace: Backtrace }, + #[snafu(display("unknown presentation context sub-item"))] + UnknownPresentationContextSubItem { backtrace: Backtrace }, + #[snafu(display("Could not decode text field `{}`", field))] + DecodeText { + field: &'static str, + #[snafu(backtrace)] + source: dicom_encoding::text::DecodeTextError, + }, + #[snafu(display("Missing application context name"))] + MissingApplicationContextName { backtrace: Backtrace }, + #[snafu(display("Missing abstract syntax"))] + MissingAbstractSyntax { backtrace: Backtrace }, + #[snafu(display("Missing transfer syntax"))] + MissingTransferSyntax { backtrace: Backtrace }, +} + +pub type Result = std::result::Result; + pub fn read_pdu(reader: &mut R, max_pdu_length: u32) -> Result where R: Read, { - if max_pdu_length < MINIMUM_PDU_SIZE || max_pdu_length > MAXIMUM_PDU_SIZE { - return Err(Error::InvalidMaxPdu); - } + ensure!( + max_pdu_length >= MINIMUM_PDU_SIZE && max_pdu_length <= MAXIMUM_PDU_SIZE, + InvalidMaxPdu { max_pdu_length } + ); - // If we read can't read 2 bytes here, that means that there is no PDU + // If we can't read 2 bytes here, that means that there is no PDU // available. Normally, we want to just return the UnexpectedEof error. However, // this method can block and wake up when stream is closed, so in this case, we // want to know if we had trouble even beginning to read a PDU. We still return // UnexpectedEof if we get after we have already began reading a PDU message. let mut bytes = [0; 2]; if let Err(e) = reader.read_exact(&mut bytes) { - if e.kind() == ErrorKind::UnexpectedEof { - return Err(Error::NoPduAvailable); - } - return Err(e.into()); + ensure!(e.kind() != ErrorKind::UnexpectedEof, NoPduAvailable); + return Err(e).context(ReadPduField { field: "type" }); } let pdu_type = bytes[0]; - let pdu_length = reader.read_u32::()?; - - if pdu_length > max_pdu_length { - return Err(Error::PduTooLarge); - } + let pdu_length = reader + .read_u32::() + .context(ReadPduField { field: "length" })?; + + ensure!( + pdu_length <= max_pdu_length, + PduTooLarge { + pdu_length, + max_pdu_length + } + ); - let bytes = read_n(reader, pdu_length as usize)?; + let bytes = read_n(reader, pdu_length as usize).context(ReadPdu)?; let mut cursor = Cursor::new(bytes); - let codec = SpecificCharacterSet::Default.codec().unwrap(); + let codec = SpecificCharacterSet::Default + .codec() + .expect("Support for the default character set is mandatory"); match pdu_type { 0x01 => { @@ -53,11 +141,15 @@ where // Version 1 and shall be identified with bit 0 set. A receiver of this PDU // implementing only this version of the DICOM UL protocol shall only test that bit // 0 is set. - let protocol_version = cursor.read_u16::()?; + let protocol_version = cursor.read_u16::().context(ReadPduField { + field: "Protocol-version", + })?; // 9-10 - Reserved - This reserved field shall be sent with a value 0000H but not // tested to this value when received. - cursor.read_u16::()?; + cursor + .read_u16::() + .context(ReadReserved { bytes: 2_u32 })?; // 11-26 - Called-AE-title - Destination DICOM Application Name. It shall be encoded // as 16 characters as defined by the ISO 646:1990-Basic G0 Set with leading and @@ -65,8 +157,16 @@ where // meaning "no Application Name specified" shall not be used. For a complete // description of the use of this field, see Section 7.1.1.4. let mut ae_bytes = [0; 16]; - cursor.read_exact(&mut ae_bytes)?; - let called_ae_title = codec.decode(&ae_bytes)?.trim().to_string(); + cursor.read_exact(&mut ae_bytes).context(ReadPduField { + field: "Called-AE-title", + })?; + let called_ae_title = codec + .decode(&ae_bytes) + .context(DecodeText { + field: "Called-AE-title", + })? + .trim() + .to_string(); // 27-42 - Calling-AE-title - Source DICOM Application Name. It shall be encoded as // 16 characters as defined by the ISO 646:1990-Basic G0 Set with leading and @@ -74,12 +174,22 @@ where // meaning "no Application Name specified" shall not be used. For a complete // description of the use of this field, see Section 7.1.1.3. let mut ae_bytes = [0; 16]; - cursor.read_exact(&mut ae_bytes)?; - let calling_ae_title = codec.decode(&ae_bytes)?.trim().to_string(); + cursor.read_exact(&mut ae_bytes).context(ReadPduField { + field: "Calling-AE-title", + })?; + let calling_ae_title = codec + .decode(&ae_bytes) + .context(DecodeText { + field: "Calling-AE-title", + })? + .trim() + .to_string(); // 43-74 - Reserved - This reserved field shall be sent with a value 00H for all // bytes but not tested to this value when received - cursor.seek(SeekFrom::Current(32))?; + cursor + .seek(SeekFrom::Current(32)) + .context(ReadReserved { bytes: 32_u32 })?; // 75-xxx - Variable items - This variable field shall contain the following items: // one Application Context Item, one or more Presentation Context Items and one User @@ -96,8 +206,8 @@ where PduVariableItem::UserVariables(val) => { user_variables = val; } - _ => { - return Err(Error::InvalidPduVariable); + var_item => { + return InvalidPduVariable { var_item }.fail(); } } } @@ -105,7 +215,7 @@ where Ok(Pdu::AssociationRQ { protocol_version, application_context_name: application_context_name - .ok_or(Error::MissingApplicationContextName)?, + .context(MissingApplicationContextName)?, called_ae_title, calling_ae_title, presentation_contexts, @@ -124,11 +234,15 @@ where // Version 1 and shall be identified with bit 0 set. A receiver of this PDU // implementing only this version of the DICOM UL protocol shall only test that bit // 0 is set. - let protocol_version = cursor.read_u16::()?; + let protocol_version = cursor.read_u16::().context(ReadPduField { + field: "Protocol-version", + })?; // 9-10 - Reserved - This reserved field shall be sent with a value 0000H but not // tested to this value when received. - cursor.read_u16::()?; + cursor + .read_u16::() + .context(ReadReserved { bytes: 2_u32 })?; // 11-26 - Reserved - This reserved field shall be sent with a value identical to // the value received in the same field of the A-ASSOCIATE-RQ PDU, but its value @@ -139,7 +253,9 @@ where // 43-74 - Reserved - This reserved field shall be sent with a value identical to // the value received in the same field of the A-ASSOCIATE-RQ PDU, but its value // shall not be tested when received. - cursor.seek(SeekFrom::Current(16 + 16 + 32))?; + cursor + .seek(SeekFrom::Current(16 + 16 + 32)) + .context(ReadReserved { bytes: 64_u32 })?; // 75-xxx - Variable items - This variable field shall contain the following items: // one Application Context Item, one or more Presentation Context Item(s) and one @@ -156,8 +272,8 @@ where PduVariableItem::UserVariables(val) => { user_variables = val; } - _ => { - return Err(Error::InvalidPduVariable); + var_item => { + return InvalidPduVariable { var_item }.fail(); } } } @@ -165,7 +281,7 @@ where Ok(Pdu::AssociationAC { protocol_version, application_context_name: application_context_name - .ok_or(Error::MissingApplicationContextName)?, + .context(MissingApplicationContextName)?, presentation_contexts, user_variables, }) @@ -175,14 +291,16 @@ where // 7 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - cursor.read_u8()?; + cursor.read_u8().context(ReadReserved { bytes: 1_u32 })?; // 8 - Result - This Result field shall contain an integer value encoded as an unsigned // binary number. One of the following values shall be used: // 1 - rejected-permanent // 2 - rejected-transient - let result = AssociationRJResult::from(cursor.read_u8()?) - .ok_or(Error::InvalidRejectSourceOrReason)?; + let result = AssociationRJResult::from( + cursor.read_u8().context(ReadPduField { field: "Result" })?, + ) + .context(InvalidRejectSourceOrReason)?; // 9 - Source - This Source field shall contain an integer value encoded as an unsigned // binary number. One of the following values shall be used: 1 - DICOM UL @@ -205,8 +323,13 @@ where // 1 - temporary-congestio // 2 - local-limit-exceeded // 3-7 - reserved - let source = AssociationRJSource::from(cursor.read_u8()?, cursor.read_u8()?) - .ok_or(Error::InvalidRejectSourceOrReason)?; + let source = AssociationRJSource::from( + cursor.read_u8().context(ReadPduField { field: "Source" })?, + cursor.read_u8().context(ReadPduField { + field: "Reason/Diag.", + })?, + ) + .context(InvalidRejectSourceOrReason)?; Ok(Pdu::AssociationRJ { result, source }) } @@ -223,12 +346,16 @@ where // 1-4 - Item-length - This Item-length shall be the number of bytes from the first // byte of the following field to the last byte of the Presentation-data-value // field. It shall be encoded as an unsigned binary number. - let item_length = cursor.read_u32::()?; + let item_length = cursor.read_u32::().context(ReadPduField { + field: "Item-Length", + })?; // 5 - Presentation-context-ID - Presentation-context-ID values shall be odd // integers between 1 and 255, encoded as an unsigned binary number. For a complete // description of the use of this field see Section 7.1.1.13. - let presentation_context_id = cursor.read_u8()?; + let presentation_context_id = cursor.read_u8().context(ReadPduField { + field: "Presentation-context-ID", + })?; // 6-xxx - Presentation-data-value - This Presentation-data-value field shall // contain DICOM message information (command and/or data set) with a message @@ -245,7 +372,9 @@ where // does not contain the last fragment of a Message Data Set or of a Message Command. let value_type; let is_last; - let header = cursor.read_u8()?; + let header = cursor.read_u8().context(ReadPduField { + field: "Message Control Header", + })?; if header & 0x01 > 0 { value_type = PDataValueType::Command; @@ -258,7 +387,10 @@ where is_last = false; } - let data = read_n(&mut cursor, (item_length - 2) as usize)?; + let data = + read_n(&mut cursor, (item_length - 2) as usize).context(ReadPduField { + field: "Presentation-data-value", + })?; values.push(PDataValue { presentation_context_id, @@ -275,7 +407,9 @@ where // 7-10 - Reserved - This reserved field shall be sent with a value 00000000H but not // tested to this value when received. - cursor.seek(SeekFrom::Current(4))?; + cursor + .seek(SeekFrom::Current(4)) + .context(ReadPduField { field: "Reserved" })?; Ok(Pdu::ReleaseRQ) } @@ -284,7 +418,9 @@ where // 7-10 - Reserved - This reserved field shall be sent with a value 00000000H but not // tested to this value when received. - cursor.seek(SeekFrom::Current(4))?; + cursor + .seek(SeekFrom::Current(4)) + .context(ReadPduField { field: "Reserved" })?; Ok(Pdu::ReleaseRP) } @@ -293,11 +429,15 @@ where // 7 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - cursor.read_u8()?; + cursor + .read_u8() + .context(ReadPduField { field: "Reserved" })?; // 8 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - cursor.read_u8()?; + cursor + .read_u8() + .context(ReadPduField { field: "Reserved" })?; // 9 - Source - This Source field shall contain an integer value encoded as an unsigned // binary number. One of the following values shall be used: @@ -313,24 +453,30 @@ where // - 4 - unrecognized-PDU parameter // - 5 - unexpected-PDU parameter // - 6 - invalid-PDU-parameter value - let source = AbortRQSource::from(cursor.read_u8()?, cursor.read_u8()?) - .ok_or(Error::InvalidAbortSourceOrReason)?; + let source = AbortRQSource::from( + cursor.read_u8().context(ReadPduField { field: "Source" })?, + cursor.read_u8().context(ReadPduField { + field: "Reason/Diag", + })?, + ) + .context(InvalidAbortSourceOrReason)?; Ok(Pdu::AbortRQ { source }) } _ => { - let data = read_n(&mut cursor, pdu_length as usize)?; + let data = read_n(&mut cursor, pdu_length as usize) + .context(ReadPduField { field: "Unknown" })?; Ok(Pdu::Unknown { pdu_type, data }) } } } -fn read_n(reader: &mut R, bytes_to_read: usize) -> Result> +fn read_n(reader: &mut R, bytes_to_read: usize) -> std::io::Result> where R: Read, { - let mut result = vec![0; bytes_to_read]; - reader.read_exact(&mut result)?; + let mut result = Vec::new(); + reader.take(bytes_to_read as u64).read_to_end(&mut result)?; Ok(result) } @@ -339,15 +485,19 @@ where R: Read, { // 1 - Item-type - XXH - let item_type = reader.read_u8()?; + let item_type = reader + .read_u8() + .context(ReadPduField { field: "Item-type" })?; // 2 - Reserved - reader.read_u8()?; + reader.read_u8().context(ReadReserved { bytes: 1_u32 })?; // 3-4 - Item-length - let item_length = reader.read_u16::()?; + let item_length = reader.read_u16::().context(ReadPduField { + field: "Item-length", + })?; - let bytes = read_n(reader, item_length as usize)?; + let bytes = read_n(reader, item_length as usize).context(ReadPduItem)?; let mut cursor = Cursor::new(bytes); match item_type { @@ -359,7 +509,9 @@ where // 7.1.1.2. Application-context-names are structured as UIDs as defined in PS3.5 (see // Annex A for an overview of this concept). DICOM Application-context-names are // registered in PS3.7. - let val = codec.decode(&cursor.into_inner())?; + let val = codec.decode(&cursor.into_inner()).context(DecodeText { + field: "Application-context-name", + })?; Ok(PduVariableItem::ApplicationContext(val)) } 0x20 => { @@ -371,19 +523,21 @@ where // 5 - Presentation-context-ID - Presentation-context-ID values shall be odd integers // between 1 and 255, encoded as an unsigned binary number. For a complete description // of the use of this field see Section 7.1.1.13. - let presentation_context_id = cursor.read_u8()?; + let presentation_context_id = cursor.read_u8().context(ReadPduField { + field: "Presentation-context-ID", + })?; // 6 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - cursor.read_u8()?; + cursor.read_u8().context(ReadReserved { bytes: 1_u32 })?; // 7 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - cursor.read_u8()?; + cursor.read_u8().context(ReadReserved { bytes: 1_u32 })?; // 8 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - cursor.read_u8()?; + cursor.read_u8().context(ReadReserved { bytes: 1_u32 })?; // 9-xxx - Abstract/Transfer Syntax Sub-Items - This variable field shall contain the // following sub-items: one Abstract Syntax and one or more Transfer Syntax(es). For a @@ -391,14 +545,18 @@ where // and Section 9.3.2.2.2. while cursor.position() < cursor.get_ref().len() as u64 { // 1 - Item-type - XXH - let item_type = cursor.read_u8()?; + let item_type = cursor + .read_u8() + .context(ReadPduField { field: "Item-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested // to this value when received. - cursor.read_u8()?; + cursor.read_u8().context(ReadReserved { bytes: 1_u32 })?; // 3-4 - Item-length - let item_length = cursor.read_u16::()?; + let item_length = cursor.read_u16::().context(ReadPduField { + field: "Item-length", + })?; match item_type { 0x30 => { @@ -413,7 +571,14 @@ where // registered in PS3.4. abstract_syntax = Some( codec - .decode(&read_n(&mut cursor, item_length as usize)?)? + .decode(&read_n(&mut cursor, item_length as usize).context( + ReadPduField { + field: "Abstract-syntax-name", + }, + )?) + .context(DecodeText { + field: "Abstract-syntax-name", + })? .trim() .to_string(), ); @@ -430,13 +595,20 @@ where // registered in PS3.5. transfer_syntaxes.push( codec - .decode(&read_n(&mut cursor, item_length as usize)?)? + .decode(&read_n(&mut cursor, item_length as usize).context( + ReadPduField { + field: "Transfer-syntax-name", + }, + )?) + .context(DecodeText { + field: "Transfer-syntax-name", + })? .trim() .to_string(), ); } _ => { - return Err(Error::UnknownPresentationContextSubItem); + return UnknownPresentationContextSubItem.fail(); } } } @@ -444,7 +616,7 @@ where Ok(PduVariableItem::PresentationContextProposed( PresentationContextProposed { id: presentation_context_id, - abstract_syntax: abstract_syntax.ok_or(Error::MissingAbstractSyntax)?, + abstract_syntax: abstract_syntax.context(MissingAbstractSyntax)?, transfer_syntaxes, }, )) @@ -457,11 +629,13 @@ where // 5 - Presentation-context-ID - Presentation-context-ID values shall be odd integers // between 1 and 255, encoded as an unsigned binary number. For a complete description // of the use of this field see Section 7.1.1.13. - let presentation_context_id = cursor.read_u8()?; + let presentation_context_id = cursor.read_u8().context(ReadPduField { + field: "Presentation-context-ID", + })?; // 6 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - cursor.read_u8()?; + cursor.read_u8().context(ReadReserved { bytes: 1_u32 })?; // 7 - Result/Reason - This Result/Reason field shall contain an integer value encoded // as an unsigned binary number. One of the following values shall be used: @@ -470,12 +644,15 @@ where // 2 - no-reason (provider rejection) // 3 - abstract-syntax-not-supported (provider rejection) // 4 - transfer-syntaxes-not-supported (provider rejection) - let reason = PresentationContextResultReason::from(cursor.read_u8()?) - .ok_or(Error::InvalidPresentationContextResultReason)?; + let reason = + PresentationContextResultReason::from(cursor.read_u8().context(ReadPduField { + field: "Result/Reason", + })?) + .context(InvalidPresentationContextResultReason)?; // 8 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - cursor.read_u8()?; + cursor.read_u8().context(ReadReserved { bytes: 1_u32 })?; // 9-xxx - Transfer syntax sub-item - This variable field shall contain one Transfer // Syntax Sub-Item. When the Result/Reason field has a value other than acceptance (0), @@ -484,14 +661,18 @@ where // 9.3.3.2.1. while cursor.position() < cursor.get_ref().len() as u64 { // 1 - Item-type - XXH - let item_type = cursor.read_u8()?; + let item_type = cursor + .read_u8() + .context(ReadPduField { field: "Item-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested // to this value when received. - cursor.read_u8()?; + cursor.read_u8().context(ReadReserved { bytes: 1_u32 })?; // 3-4 - Item-length - let item_length = cursor.read_u16::()?; + let item_length = cursor.read_u16::().context(ReadPduField { + field: "Item-length", + })?; match item_type { 0x40 => { @@ -507,12 +688,21 @@ where match transfer_syntax { Some(_) => { // Multiple transfer syntax values cannot be proposed. - return Err(Error::MultipleTransferSyntaxesAccepted); + return MultipleTransferSyntaxesAccepted.fail(); } None => { transfer_syntax = Some( codec - .decode(&read_n(&mut cursor, item_length as usize)?)? + .decode( + &read_n(&mut cursor, item_length as usize).context( + ReadPduField { + field: "Transfer-syntax-name", + }, + )?, + ) + .context(DecodeText { + field: "Transfer-syntax-name", + })? .trim() .to_string(), ); @@ -520,7 +710,7 @@ where } } _ => { - return Err(Error::InvalidTransferSyntaxSubItem); + return InvalidTransferSyntaxSubItem.fail(); } } } @@ -529,7 +719,7 @@ where PresentationContextResult { id: presentation_context_id, reason, - transfer_syntax: transfer_syntax.ok_or(Error::MissingTransferSyntax)?, + transfer_syntax: transfer_syntax.context(MissingTransferSyntax)?, }, )) } @@ -543,13 +733,17 @@ where // defined in Annex D. while cursor.position() < cursor.get_ref().len() as u64 { // 1 - Item-type - XXH - let item_type = cursor.read_u8()?; + let item_type = cursor + .read_u8() + .context(ReadPduField { field: "Item-type" })?; // 2 - Reserved - cursor.read_u8()?; + cursor.read_u8().context(ReadReserved { bytes: 1_u32 })?; // 3-4 - Item-length - let item_length = cursor.read_u16::()?; + let item_length = cursor.read_u16::().context(ReadPduField { + field: "Item-length", + })?; match item_type { 0x51 => { @@ -564,8 +758,11 @@ where // the PDU length values used in the PDU-length field of the P-DATA-TF PDUs // received by the association-requestor. Otherwise, it shall be a protocol // error. - user_variables - .push(UserVariableItem::MaxLength(cursor.read_u32::()?)); + user_variables.push(UserVariableItem::MaxLength( + cursor.read_u32::().context(ReadPduField { + field: "Maximum-length-received", + })?, + )); } 0x52 => { // Implementation Class UID Sub-Item Structure @@ -575,7 +772,14 @@ where // Section D.3.3.2. The Implementation-class-uid field is structured as a // UID as defined in PS3.5. let implementation_class_uid = codec - .decode(&read_n(&mut cursor, item_length as usize)?)? + .decode(&read_n(&mut cursor, item_length as usize).context( + ReadPduField { + field: "Implementation-class-uid", + }, + )?) + .context(DecodeText { + field: "Implementation-class-uid", + })? .trim() .to_string(); user_variables.push(UserVariableItem::ImplementationClassUID( @@ -590,7 +794,14 @@ where // Section D.3.3.2. It shall be encoded as a string of 1 to 16 ISO 646:1990 // (basic G0 set) characters. let implementation_version_name = codec - .decode(&read_n(&mut cursor, item_length as usize)?)? + .decode(&read_n(&mut cursor, item_length as usize).context( + ReadPduField { + field: "Implementation-version-name", + }, + )?) + .context(DecodeText { + field: "Implementation-version-name", + })? .trim() .to_string(); user_variables.push(UserVariableItem::ImplementationVersionName( @@ -600,7 +811,8 @@ where _ => { user_variables.push(UserVariableItem::Unknown( item_type, - read_n(&mut cursor, item_length as usize)?, + read_n(&mut cursor, item_length as usize) + .context(ReadPduField { field: "Unknown" })?, )); } } diff --git a/ul/src/pdu/writer.rs b/ul/src/pdu/writer.rs index 878686e2..6abced07 100644 --- a/ul/src/pdu/writer.rs +++ b/ul/src/pdu/writer.rs @@ -1,35 +1,88 @@ -use crate::error::Result; use crate::pdu::*; use byteordered::byteorder::{BigEndian, WriteBytesExt}; -use dicom_encoding::text::{SpecificCharacterSet, TextCodec}; +use dicom_encoding::text::TextCodec; +use snafu::{Backtrace, ResultExt, Snafu}; use std::io::Write; -pub(crate) fn write_chunk_u32(writer: &mut dyn Write, func: F) -> Result<()> +#[derive(Debug, Snafu)] +#[non_exhaustive] +pub enum Error { + #[snafu(display("Could not write chunk of {} PDU structure", name))] + WriteChunk { + /// the name of the PDU structure + name: &'static str, + source: WriteChunkError, + }, + + #[snafu(display("Could not write field `{}`", field))] + WriteField { + field: &'static str, + backtrace: Backtrace, + source: std::io::Error, + }, + + #[snafu(display("Could not write {} reserved bytes", bytes))] + WriteReserved { + bytes: u32, + backtrace: Backtrace, + source: std::io::Error, + }, + + #[snafu(display("Could not write field `{}`", field))] + EncodeField { + field: &'static str, + #[snafu(backtrace)] + source: dicom_encoding::text::EncodeTextError, + }, +} + +pub type Result = std::result::Result; + +#[derive(Debug, Snafu)] +pub enum WriteChunkError { + #[snafu(display("Failed to build chunk"))] + BuildChunk { + #[snafu(backtrace)] + source: Box, + }, + #[snafu(display("Failed to write chunk length"))] + WriteLength { + backtrace: Backtrace, + source: std::io::Error, + }, + #[snafu(display("Failed to write chunk data"))] + WriteData { + backtrace: Backtrace, + source: std::io::Error, + }, +} + +fn write_chunk_u32(writer: &mut dyn Write, func: F) -> std::result::Result<(), WriteChunkError> where F: FnOnce(&mut Vec) -> Result<()>, { let mut data = vec![]; - func(&mut data)?; + func(&mut data).map_err(Box::from).context(BuildChunk)?; let length = data.len() as u32; - writer.write_u32::(length)?; + writer.write_u32::(length).context(WriteLength)?; - writer.write_all(&data)?; + writer.write_all(&data).context(WriteData)?; Ok(()) } -pub(crate) fn write_chunk_u16(writer: &mut dyn Write, func: F) -> Result<()> +fn write_chunk_u16(writer: &mut dyn Write, func: F) -> std::result::Result<(), WriteChunkError> where F: FnOnce(&mut Vec) -> Result<()>, { let mut data = vec![]; - func(&mut data)?; + func(&mut data).map_err(Box::from).context(BuildChunk)?; let length = data.len() as u16; - writer.write_u16::(length)?; + writer.write_u16::(length).context(WriteLength)?; - writer.write_all(&data)?; + writer.write_all(&data).context(WriteData)?; Ok(()) } @@ -38,7 +91,7 @@ pub fn write_pdu(writer: &mut W, pdu: &Pdu) -> Result<()> where W: Write, { - let codec = SpecificCharacterSet::Default.codec().unwrap(); + let codec = dicom_encoding::text::DefaultCharacterSetCodec; match pdu { Pdu::AssociationRQ { protocol_version, @@ -51,11 +104,15 @@ where // A-ASSOCIATE-RQ PDU Structure // 1 - PDU-type - 01H - writer.write_u8(0x01)?; + writer + .write_u8(0x01) + .context(WriteField { field: "PDU-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not // tested to this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u32(writer, |writer| { // 7-8 Protocol-version - This two byte field shall use one bit to identify @@ -63,33 +120,49 @@ where // This is Version 1 and shall be identified with bit 0 set. A receiver of this // PDU implementing only this version of the DICOM UL protocol shall only test // that bit 0 is set. - writer.write_u16::(*protocol_version)?; + writer + .write_u16::(*protocol_version) + .context(WriteField { + field: "Protocol-version", + })?; // 9-10 - Reserved - This reserved field shall be sent with a value 0000H but // not tested to this value when received. - writer.write_u16::(0x00)?; + writer + .write_u16::(0x00) + .context(WriteReserved { bytes: 2_u32 })?; // 11-26 - Called-AE-title - Destination DICOM Application Name. It shall be // encoded as 16 characters as defined by the ISO 646:1990-Basic G0 Set with // leading and trailing spaces (20H) being non-significant. The value made of 16 // spaces (20H) meaning "no Application Name specified" shall not be used. For a // complete description of the use of this field, see Section 7.1.1.4. - let mut ae_title_bytes = codec.encode(called_ae_title)?; - ae_title_bytes.resize(16, 32); // 32 is asci for space - writer.write_all(&ae_title_bytes)?; + let mut ae_title_bytes = codec.encode(called_ae_title).context(EncodeField { + field: "Called-AE-title", + })?; + ae_title_bytes.resize(16, b' '); + writer.write_all(&ae_title_bytes).context(WriteField { + field: "Called-AE-title", + })?; // 27-42 - Calling-AE-title - Source DICOM Application Name. It shall be encoded // as 16 characters as defined by the ISO 646:1990-Basic G0 Set with leading and // trailing spaces (20H) being non-significant. The value made of 16 spaces // (20H) meaning "no Application Name specified" shall not be used. For a // complete description of the use of this field, see Section 7.1.1.3. - let mut ae_title_bytes = codec.encode(calling_ae_title)?; - ae_title_bytes.resize(16, 32); // 32 is asci for space - writer.write_all(&ae_title_bytes)?; + let mut ae_title_bytes = codec.encode(calling_ae_title).context(EncodeField { + field: "Calling-AE-title", + })?; + ae_title_bytes.resize(16, b' '); + writer.write_all(&ae_title_bytes).context(WriteField { + field: "Called-AE-title", + })?; // 43-74 - Reserved - This reserved field shall be sent with a value 00H for all // bytes but not tested to this value when received - writer.write_all(&[b'0'; 32])?; + writer + .write_all(&[0; 32]) + .context(WriteReserved { bytes: 32_u32 })?; write_pdu_variable_application_context_name( writer, @@ -108,6 +181,9 @@ where write_pdu_variable_user_variables(writer, user_variables, &codec)?; Ok(()) + }) + .context(WriteChunk { + name: "A-ASSOCIATE-RQ", })?; Ok(()) @@ -121,11 +197,15 @@ where // A-ASSOCIATE-AC PDU Structure // 1 - PDU-type - 02H - writer.write_u8(0x02)?; + writer + .write_u8(0x02) + .context(WriteField { field: "PDU-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u32(writer, |writer| { // 7-8 - Protocol-version - This two byte field shall use one bit to identify each @@ -133,26 +213,38 @@ where // Version 1 and shall be identified with bit 0 set. A receiver of this PDU // implementing only this version of the DICOM UL protocol shall only test that bit // 0 is set. - writer.write_u16::(*protocol_version)?; + writer + .write_u16::(*protocol_version) + .context(WriteField { + field: "Protocol-version", + })?; // 9-10 - Reserved - This reserved field shall be sent with a value 0000H but not // tested to this value when received. - writer.write_u16::(0x00)?; + writer + .write_u16::(0x00) + .context(WriteReserved { bytes: 2_u32 })?; // 11-26 - Reserved - This reserved field shall be sent with a value identical to // the value received in the same field of the A-ASSOCIATE-RQ PDU, but its value // shall not be tested when received. TODO: write AE title - writer.write_all(&[0 as u8; 16])?; + writer + .write_all(&[0 as u8; 16]) + .context(WriteReserved { bytes: 16_u32 })?; // 27-42 - Reserved - This reserved field shall be sent with a value identical to // the value received in the same field of the A-ASSOCIATE-RQ PDU, but its value // shall not be tested when received. TODO: write AE title - writer.write_all(&[0 as u8; 16])?; + writer + .write_all(&[0 as u8; 16]) + .context(WriteReserved { bytes: 16_u32 })?; // 43-74 - Reserved - This reserved field shall be sent with a value identical to // the value received in the same field of the A-ASSOCIATE-RQ PDU, but its value // shall not be tested when received. - writer.write_all(&[0 as u8; 32])?; + writer + .write_all(&[0 as u8; 32]) + .context(WriteReserved { bytes: 32_u32 })?; // 75-xxx - Variable items - This variable field shall contain the following items: // one Application Context Item, one or more Presentation Context Item(s) and one @@ -176,29 +268,36 @@ where Ok(()) }) + .context(WriteChunk { + name: "A-ASSOCIATE-AC", + }) } Pdu::AssociationRJ { result, source } => { // 1 - PDU-type - 03H - writer.write_u8(0x03)?; + writer + .write_u8(0x03) + .context(WriteField { field: "PDU-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u32(writer, |writer| { // 7 - Reserved - This reserved field shall be sent with a value 00H but not tested to this value when received. - writer.write_u8(0x00)?; + writer.write_u8(0x00).context(WriteReserved { bytes: 1_u32 })?; // 8 - Result - This Result field shall contain an integer value encoded as an unsigned binary number. One of the following values shall be used: // - 1 - rejected-permanent // - 2 - rejected-transient - match result { + writer.write_u8(match result { AssociationRJResult::Permanent => { - writer.write_u8(0x01)?; + 0x01 } AssociationRJResult::Transient => { - writer.write_u8(0x02)?; + 0x02 } - } + }).context(WriteField { field: "AssociationRJResult" })?; // 9 - Source - This Source field shall contain an integer value encoded as an unsigned binary number. One of the following values shall be used: // - 1 - DICOM UL service-user @@ -222,64 +321,68 @@ where // 3-7 - reserved match source { AssociationRJSource::ServiceUser(reason) => { - writer.write_u8(0x01)?; - match reason { + writer.write_u8(0x01).context(WriteField { field: "AssociationRJServiceUserReason" })?; + writer.write_u8(match reason { AssociationRJServiceUserReason::NoReasonGiven => { - writer.write_u8(0x01)?; + 0x01 } AssociationRJServiceUserReason::ApplicationContextNameNotSupported => { - writer.write_u8(0x02)?; + 0x02 } AssociationRJServiceUserReason::CallingAETitleNotRecognized => { - writer.write_u8(0x03)?; + 0x03 } AssociationRJServiceUserReason::CalledAETitleNotRecognized => { - writer.write_u8(0x07)?; + 0x07 } AssociationRJServiceUserReason::Reserved(data) => { - writer.write_u8(*data)?; + *data } - } + }).context(WriteField { field: "AssociationRJServiceUserReason (2)" })?; } AssociationRJSource::ServiceProviderASCE(reason) => { - writer.write_u8(0x02)?; - match reason { + writer.write_u8(0x02).context(WriteField { field: "AssociationRJServiceProvider" })?; + writer.write_u8(match reason { AssociationRJServiceProviderASCEReason::NoReasonGiven => { - writer.write_u8(0x01)?; + 0x01 } AssociationRJServiceProviderASCEReason::ProtocolVersionNotSupported => { - writer.write_u8(0x02)?; + 0x02 } - } + }).context(WriteField { field: "AssociationRJServiceProvider (2)" })?; } AssociationRJSource::ServiceProviderPresentation(reason) => { - writer.write_u8(0x03)?; - match reason { + writer.write_u8(0x03).context(WriteField { field: "AssociationRJServiceProviderPresentationReason" })?; + writer.write_u8(match reason { AssociationRJServiceProviderPresentationReason::TemporaryCongestion => { - writer.write_u8(0x01)?; + 0x01 } AssociationRJServiceProviderPresentationReason::LocalLimitExceeded => { - writer.write_u8(0x02)?; + 0x02 } AssociationRJServiceProviderPresentationReason::Reserved(data) => { - writer.write_u8(*data)?; + *data } - } + }).context(WriteField { field: "AssociationRJServiceProviderPresentationReason (2)" })?; } } Ok(()) - })?; + }).context(WriteChunk { name: "AssociationRJ" })?; Ok(()) } Pdu::PData { data } => { // 1 - PDU-type - 04H - writer.write_u8(0x04)?; + writer + .write_u8(0x04) + .context(WriteField { field: "PDU-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u32(writer, |writer| { // 7-xxx - Presentation-data-value Item(s) - This variable data field shall contain @@ -291,7 +394,11 @@ where // 5 - Presentation-context-ID - Presentation-context-ID values shall be odd // integers between 1 and 255, encoded as an unsigned binary number. For a // complete description of the use of this field see Section 7.1.1.13. - writer.write_u8(presentation_data_value.presentation_context_id)?; + writer + .write_u8(presentation_data_value.presentation_context_id) + .context(WriteField { + field: "Presentation-context-ID", + })?; // 6-xxx - Presentation-data-value - This Presentation-data-value field // shall contain DICOM message information (command and/or data set) with a @@ -316,68 +423,94 @@ where if presentation_data_value.is_last { message_header |= 0x02; } - writer.write_u8(message_header)?; + writer.write_u8(message_header).context(WriteField { + field: "Presentation-data-value control header", + })?; // Message fragment - writer.write_all(&presentation_data_value.data)?; + writer + .write_all(&presentation_data_value.data) + .context(WriteField { + field: "Presentation-data-value", + })?; Ok(()) + }) + .context(WriteChunk { + name: "Presentation-data-value item", })?; } Ok(()) - })?; - - Ok(()) + }) + .context(WriteChunk { name: "PData" }) } Pdu::ReleaseRQ => { // 1 - PDU-type - 05H - writer.write_u8(0x05)?; + writer + .write_u8(0x05) + .context(WriteField { field: "PDU-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u32(writer, |writer| { - writer.write_all(&[0u8; 4])?; - - Ok(()) - })?; + writer.write_all(&[0u8; 4]).context(WriteField { + field: "ReleaseRQ data", + }) + }) + .context(WriteChunk { name: "ReleaseRQ" })?; Ok(()) } Pdu::ReleaseRP => { // 1 - PDU-type - 06H - writer.write_u8(0x06)?; + writer + .write_u8(0x06) + .context(WriteField { field: "PDU-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u32(writer, |writer| { - writer.write_all(&[0u8; 4])?; - - Ok(()) - })?; + writer.write_all(&[0u8; 4]).context(WriteField { + field: "ReleaseRP data", + }) + }) + .context(WriteChunk { name: "ReleaseRP" })?; Ok(()) } Pdu::AbortRQ { source } => { // 1 - PDU-type - 07H - writer.write_u8(0x07)?; + writer + .write_u8(0x07) + .context(WriteField { field: "PDU-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u32(writer, |writer| { // 7 - Reserved - This reserved field shall be sent with a value 00H but not tested // to this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; // 8 - Reserved - This reserved field shall be sent with a value 00H but not tested // to this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; // 9 - Source - This Source field shall contain an integer value encoded as an // unsigned binary number. One of the following values shall be used: @@ -397,54 +530,51 @@ where // shall not be significant. It shall be sent with a value 00H but not tested to // this value when received. match source { - AbortRQSource::ServiceUser => { - writer.write_u8(0x00)?; - writer.write_u8(0x00)?; - } - AbortRQSource::Reserved => { - writer.write_u8(0x00)?; - writer.write_u8(0x00)?; - } + AbortRQSource::ServiceUser => writer.write_all(&[0x00; 2]), + AbortRQSource::Reserved => writer.write_all(&[0x00; 2]), AbortRQSource::ServiceProvider(reason) => match reason { AbortRQServiceProviderReason::ReasonNotSpecifiedUnrecognizedPdu => { - writer.write_u8(0x00)?; - } - AbortRQServiceProviderReason::UnexpectedPdu => { - writer.write_u8(0x02)?; - } - AbortRQServiceProviderReason::Reserved => { - writer.write_u8(0x03)?; + writer.write_u8(0x00) } + AbortRQServiceProviderReason::UnexpectedPdu => writer.write_u8(0x02), + AbortRQServiceProviderReason::Reserved => writer.write_u8(0x03), AbortRQServiceProviderReason::UnrecognizedPduParameter => { - writer.write_u8(0x04)?; + writer.write_u8(0x04) } AbortRQServiceProviderReason::UnexpectedPduParameter => { - writer.write_u8(0x05)?; - } - AbortRQServiceProviderReason::InvalidPduParameter => { - writer.write_u8(0x06)?; + writer.write_u8(0x05) } + AbortRQServiceProviderReason::InvalidPduParameter => writer.write_u8(0x06), }, } + .context(WriteField { + field: "AbortRQSource", + })?; Ok(()) - })?; + }) + .context(WriteChunk { name: "AbortRQ" })?; Ok(()) } Pdu::Unknown { pdu_type, data } => { // 1 - PDU-type - XXH - writer.write_u8(*pdu_type)?; + writer + .write_u8(*pdu_type) + .context(WriteField { field: "PDU-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to // this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u32(writer, |writer| { - writer.write_all(data)?; - - Ok(()) - })?; + writer.write_all(data).context(WriteField { + field: "Unknown data", + }) + }) + .context(WriteChunk { name: "Unknown" })?; Ok(()) } @@ -458,11 +588,15 @@ fn write_pdu_variable_application_context_name( ) -> Result<()> { // Application Context Item Structure // 1 - Item-type - 10H - writer.write_u8(0x10)?; + writer + .write_u8(0x10) + .context(WriteField { field: "Item-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not // tested to this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u16(writer, |writer| { // 5-xxx - Application-context-name -A valid Application-context-name shall @@ -470,9 +604,20 @@ fn write_pdu_variable_application_context_name( // field see Section 7.1.1.2. Application-context-names are structured as // UIDs as defined in PS3.5 (see Annex A for an overview of this concept). // DICOM Application-context-names are registered in PS3.7. - writer.write_all(&codec.encode(application_context_name)?)?; - - Ok(()) + writer + .write_all( + &codec + .encode(application_context_name) + .context(EncodeField { + field: "Application-context-name", + })?, + ) + .context(WriteField { + field: "Application-context-name", + }) + }) + .context(WriteChunk { + name: "Application Context Item", })?; Ok(()) @@ -485,30 +630,44 @@ fn write_pdu_variable_presentation_context_proposed( ) -> Result<()> { // Presentation Context Item Structure // 1 - tem-type - 20H - writer.write_u8(0x20)?; + writer + .write_u8(0x20) + .context(WriteField { field: "Item-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not // tested to this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u16(writer, |writer| { // 5 - Presentation-context-ID - Presentation-context-ID values shall be // odd integers between 1 and 255, encoded as an unsigned binary number. // For a complete description of the use of this field see Section // 7.1.1.13. - writer.write_u8(presentation_context.id)?; + writer + .write_u8(presentation_context.id) + .context(WriteField { + field: "Presentation-context-ID", + })?; // 6 - Reserved - This reserved field shall be sent with a value 00H but // not tested to this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; // 7 - Reserved - This reserved field shall be sent with a value 00H but // not tested to this value when received - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; // 8 - Reserved - This reserved field shall be sent with a value 00H but // not tested to this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; // 9-xxx - Abstract/Transfer Syntax Sub-Items - This variable field // shall contain the following sub-items: one Abstract Syntax and one or @@ -518,12 +677,16 @@ fn write_pdu_variable_presentation_context_proposed( // Abstract Syntax Sub-Item Structure // 1 - Item-type 30H - writer.write_u8(0x30)?; + writer + .write_u8(0x30) + .context(WriteField { field: "Item-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H // but not tested to this value when // received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u16(writer, |writer| { // 5-xxx - Abstract-syntax-name - This variable field shall @@ -536,19 +699,34 @@ fn write_pdu_variable_presentation_context_proposed( // UIDs as defined in PS3.5 // (see Annex B for an overview of this concept). // DICOM Abstract-syntax-names are registered in PS3.4. - writer.write_all(&codec.encode(&presentation_context.abstract_syntax)?)?; - - Ok(()) + writer + .write_all( + &codec + .encode(&presentation_context.abstract_syntax) + .context(EncodeField { + field: "Abstract-syntax-name", + })?, + ) + .context(WriteField { + field: "Abstract-syntax-name", + }) + }) + .context(WriteChunk { + name: "Abstract Syntax Item", })?; for transfer_syntax in &presentation_context.transfer_syntaxes { // Transfer Syntax Sub-Item Structure // 1 - Item-type - 40H - writer.write_u8(0x40)?; + writer.write_u8(0x40).context(WriteField { + field: "Presentation-context Item-type", + })?; // 2 - Reserved - This reserved field shall be sent with a value 00H // but not tested to this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u16(writer, |writer| { // 5-xxx - Transfer-syntax-name(s) - This variable field shall @@ -559,13 +737,23 @@ fn write_pdu_variable_presentation_context_proposed( // structured as UIDs as defined in PS3.5 (see Annex B for an // overview of this concept). DICOM Transfer-syntax-names are // registered in PS3.5. - writer.write_all(&codec.encode(transfer_syntax)?)?; - - Ok(()) + writer + .write_all(&codec.encode(transfer_syntax).context(EncodeField { + field: "Transfer-syntax-name", + })?) + .context(WriteField { + field: "Transfer-syntax-name", + }) + }) + .context(WriteChunk { + name: "Transfer Syntax Sub-Item", })?; } Ok(()) + }) + .context(WriteChunk { + name: "Presentation Context Item", })?; Ok(()) @@ -577,21 +765,31 @@ fn write_pdu_variable_presentation_context_result( codec: &dyn TextCodec, ) -> Result<()> { // 1 - Item-type - 21H - writer.write_u8(0x21)?; + writer + .write_u8(0x21) + .context(WriteField { field: "Item-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to this // value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u16(writer, |writer| { // 5 - Presentation-context-ID - Presentation-context-ID values shall be odd integers // between 1 and 255, encoded as an unsigned binary number. For a complete description of // the use of this field see Section 7.1.1.13. - writer.write_u8(presentation_context.id)?; + writer + .write_u8(presentation_context.id) + .context(WriteField { + field: "Presentation-context-ID", + })?; // 6 - Reserved - This reserved field shall be sent with a value 00H but not tested to this // value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; // 7 - Result/Reason - This Result/Reason field shall contain an integer value encoded as an // unsigned binary number. One of the following values shall be used: @@ -600,27 +798,23 @@ fn write_pdu_variable_presentation_context_result( // 2 - no-reason (provider rejection) // 3 - abstract-syntax-not-supported (provider rejection) // 4 - transfer-syntaxes-not-supported (provider rejection) - match &presentation_context.reason { - PresentationContextResultReason::Acceptance => { - writer.write_u8(0)?; - } - PresentationContextResultReason::UserRejection => { - writer.write_u8(1)?; - } - PresentationContextResultReason::NoReason => { - writer.write_u8(2)?; - } - PresentationContextResultReason::AbstractSyntaxNotSupported => { - writer.write_u8(3)?; - } - PresentationContextResultReason::TransferSyntaxesNotSupported => { - writer.write_u8(4)?; - } - } + writer + .write_u8(match &presentation_context.reason { + PresentationContextResultReason::Acceptance => 0, + PresentationContextResultReason::UserRejection => 1, + PresentationContextResultReason::NoReason => 2, + PresentationContextResultReason::AbstractSyntaxNotSupported => 3, + PresentationContextResultReason::TransferSyntaxesNotSupported => 4, + }) + .context(WriteField { + field: "Presentation Context Result/Reason", + })?; // 8 - Reserved - This reserved field shall be sent with a value 00H but not tested to this // value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; // 9-xxx - Transfer syntax sub-item - This variable field shall contain one Transfer Syntax // Sub-Item. When the Result/Reason field has a value other than acceptance (0), this field @@ -628,11 +822,15 @@ fn write_pdu_variable_presentation_context_result( // description of the use and encoding of this item see Section 9.3.3.2.1. // 1 - Item-type - 40H - writer.write_u8(0x40)?; + writer + .write_u8(0x40) + .context(WriteField { field: "Item-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to this // value when received. - writer.write_u8(0x40)?; + writer + .write_u8(0x40) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u16(writer, |writer| { // 5-xxx - Transfer-syntax-name - This variable field shall contain the @@ -641,15 +839,29 @@ fn write_pdu_variable_presentation_context_result( // use of this field see Section 7.1.1.14. Transfer-syntax-names are structured as UIDs // as defined in PS3.5 (see Annex B for an overview of this concept). DICOM // Transfer-syntax-names are registered in PS3.5. - writer.write_all(&codec.encode(&presentation_context.transfer_syntax)?)?; + writer + .write_all( + &codec + .encode(&presentation_context.transfer_syntax) + .context(EncodeField { + field: "Transfer-syntax-name", + })?, + ) + .context(WriteField { + field: "Transfer-syntax-name", + })?; Ok(()) + }) + .context(WriteChunk { + name: "Transfer Syntax sub-item", })?; Ok(()) - })?; - - Ok(()) + }) + .context(WriteChunk { + name: "Presentation-context", + }) } fn write_pdu_variable_user_variables( @@ -662,11 +874,15 @@ fn write_pdu_variable_user_variables( } // 1 - Item-type - 50H - writer.write_u8(0x50)?; + writer + .write_u8(0x50) + .context(WriteField { field: "Item-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not tested to this // value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u16(writer, |writer| { // 5-xxx - User-data - This variable field shall contain User-data sub-items as defined by @@ -676,11 +892,15 @@ fn write_pdu_variable_user_variables( match user_variable { UserVariableItem::MaxLength(max_length) => { // 1 - Item-type - 51H - writer.write_u8(0x51)?; + writer + .write_u8(0x51) + .context(WriteField { field: "Item-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not // tested to this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u16(writer, |writer| { // 5-8 - Maximum-length-received - This parameter allows the @@ -692,62 +912,149 @@ fn write_pdu_variable_user_variables( // the PDU length values used in the PDU-length field of the P-DATA-TF PDUs // received by the association-requestor. Otherwise, it shall be a protocol // error. - writer.write_u32::(*max_length)?; - - Ok(()) + writer + .write_u32::(*max_length) + .context(WriteField { + field: "Maximum-length-received", + }) + }) + .context(WriteChunk { + name: "Maximum-length-received", })?; } UserVariableItem::ImplementationVersionName(implementation_version_name) => { // 1 - Item-type - 55H - writer.write_u8(0x55)?; + writer + .write_u8(0x55) + .context(WriteField { field: "Item-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not // tested to this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u16(writer, |writer| { // 5 - xxx - Implementation-version-name - This variable field shall contain // the Implementation-version-name of the Association-acceptor as defined in // Section D.3.3.2. It shall be encoded as a string of 1 to 16 ISO 646:1990 // (basic G0 set) characters. - writer.write_all(&codec.encode(implementation_version_name)?)?; - - Ok(()) + writer + .write_all(&codec.encode(implementation_version_name).context( + EncodeField { + field: "Implementation-version-name", + }, + )?) + .context(WriteField { + field: "Implementation-version-name", + }) + }) + .context(WriteChunk { + name: "Implementation-version-name", })?; } UserVariableItem::ImplementationClassUID(implementation_class_uid) => { // 1 - Item-type - 52H - writer.write_u8(0x52)?; + writer + .write_u8(0x52) + .context(WriteField { field: "Item-type" })?; // 2 - Reserved - This reserved field shall be sent with a value 00H but not // tested to this value when received. - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u16(writer, |writer| { //5 - xxx - Implementation-class-uid - This variable field shall contain // the Implementation-class-uid of the Association-acceptor as defined in // Section D.3.3.2. The Implementation-class-uid field is structured as a // UID as defined in PS3.5. - writer.write_all(&codec.encode(implementation_class_uid)?)?; - - Ok(()) + writer + .write_all(&codec.encode(implementation_class_uid).context( + EncodeField { + field: "Implementation-class-uid", + }, + )?) + .context(WriteField { + field: "Implementation-class-uid", + }) + }) + .context(WriteChunk { + name: "Implementation-class-uid", })?; } UserVariableItem::Unknown(item_type, data) => { - writer.write_u8(*item_type)?; + writer + .write_u8(*item_type) + .context(WriteField { field: "Item-type" })?; - writer.write_u8(0x00)?; + writer + .write_u8(0x00) + .context(WriteReserved { bytes: 1_u32 })?; write_chunk_u16(writer, |writer| { - writer.write_all(data)?; - Ok(()) - })?; + writer.write_all(data).context(WriteField { + field: "Unknown Data", + }) + }) + .context(WriteChunk { name: "Unknown" })?; } } } Ok(()) - })?; + }) + .context(WriteChunk { name: "User-data" }) +} - Ok(()) +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_write_chunks_with_preceding_u32_length() -> Result<()> { + let mut bytes = vec![0u8; 0]; + write_chunk_u32(&mut bytes, |writer| { + writer + .write_u8(0x02) + .context(WriteField { field: "Field1" })?; + write_chunk_u32(writer, |writer| { + writer + .write_u8(0x03) + .context(WriteField { field: "Field2" })?; + Ok(()) + }) + .context(WriteChunk { name: "Chunk2" }) + }) + .context(WriteChunk { name: "Chunk1" })?; + + assert_eq!(bytes.len(), 10); + assert_eq!(bytes, &[0, 0, 0, 6, 2, 0, 0, 0, 1, 3]); + + Ok(()) + } + + #[test] + fn can_write_chunks_with_preceding_u16_length() -> Result<()> { + let mut bytes = vec![0u8; 0]; + write_chunk_u16(&mut bytes, |writer| { + writer + .write_u8(0x02) + .context(WriteField { field: "Field1" })?; + write_chunk_u16(writer, |writer| { + writer + .write_u8(0x03) + .context(WriteField { field: "Field2" })?; + Ok(()) + }) + .context(WriteChunk { name: "Chunk2" }) + }) + .context(WriteChunk { name: "Chunk1" })?; + + assert_eq!(bytes.len(), 6); + assert_eq!(bytes, &[0, 4, 2, 0, 1, 3]); + + Ok(()) + } } diff --git a/ul/src/pdu/test.rs b/ul/tests/test.rs similarity index 77% rename from ul/src/pdu/test.rs rename to ul/tests/test.rs index 4886d910..4a37443d 100644 --- a/ul/src/pdu/test.rs +++ b/ul/tests/test.rs @@ -1,49 +1,11 @@ -use crate::error::*; -use crate::pdu::reader::*; -use crate::pdu::writer::*; -use crate::pdu::*; -use byteordered::byteorder::WriteBytesExt; +use dicom_ul::pdu::reader::{read_pdu, DEFAULT_MAX_PDU}; +use dicom_ul::pdu::writer::write_pdu; +use dicom_ul::pdu::{PDataValue, PDataValueType, Pdu, PresentationContextProposed, UserVariableItem}; use matches::matches; use std::io::Cursor; #[test] -fn can_write_chunks_with_preceding_u32_length() -> Result<()> { - let mut bytes = vec![0u8; 0]; - write_chunk_u32(&mut bytes, |writer| { - writer.write_u8(0x02)?; - write_chunk_u32(writer, |writer| { - writer.write_u8(0x03)?; - Ok(()) - })?; - Ok(()) - })?; - - assert_eq!(bytes.len(), 10); - assert_eq!(bytes, &[0, 0, 0, 6, 2, 0, 0, 0, 1, 3]); - - Ok(()) -} - -#[test] -fn can_write_chunks_with_preceding_u16_length() -> Result<()> { - let mut bytes = vec![0u8; 0]; - write_chunk_u16(&mut bytes, |writer| { - writer.write_u8(0x02)?; - write_chunk_u16(writer, |writer| { - writer.write_u8(0x03)?; - Ok(()) - })?; - Ok(()) - })?; - - assert_eq!(bytes.len(), 6); - assert_eq!(bytes, &[0, 4, 2, 0, 1, 3]); - - Ok(()) -} - -#[test] -fn can_read_write_associate_rq() -> Result<()> { +fn can_read_write_associate_rq() -> Result<(), Box> { let association_rq = Pdu::AssociationRQ { protocol_version: 2, calling_ae_title: "calling ae".to_string(), @@ -116,7 +78,7 @@ fn can_read_write_associate_rq() -> Result<()> { } #[test] -fn can_read_write_pdata() -> Result<()> { +fn can_read_write_pdata() -> Result<(), Box> { let pdata_rq = Pdu::PData { data: vec![PDataValue { presentation_context_id: 3,