Skip to main content

haste_hl7v2/
mllp.rs

1use haste_fhir_model::r4::generated::terminology::IssueType;
2use haste_fhir_operation_error::OperationOutcomeError;
3use std::io::Read;
4
5const START_BLOCK: u8 = 0x0B;
6const END_BLOCK: u8 = 0x1C;
7const CARRIAGE_RETURN: u8 = 0x0D;
8const COMMIT_ACK: u8 = 0x06;
9const COMMIT_NAK: u8 = 0x15;
10
11pub struct MllpFormatter;
12
13impl MllpFormatter {
14    #[must_use]
15    pub fn encode(payload: &[u8]) -> Vec<u8> {
16        let mut buf = Vec::with_capacity(payload.len() + 3);
17        buf.push(START_BLOCK);
18        buf.extend_from_slice(payload);
19        buf.push(END_BLOCK);
20        buf.push(CARRIAGE_RETURN);
21        buf
22    }
23
24    /// Decodes an MLLP-framed message and returns the enclosed payload.
25    ///
26    /// # Errors
27    ///
28    /// Returns an [`OperationOutcomeError`] if `framed` is not a valid MLLP frame,
29    /// that is, if it is shorter than the minimum frame length or does not start
30    /// with `<SB>` and end with `<EB><CR>`.
31    pub fn decode(framed: &[u8]) -> Result<&[u8], OperationOutcomeError> {
32        if framed.len() < 4
33            || framed[0] != START_BLOCK
34            || framed[framed.len() - 2] != END_BLOCK
35            || framed[framed.len() - 1] != CARRIAGE_RETURN
36        {
37            let k = String::from_utf8_lossy(framed);
38            return Err(OperationOutcomeError::error(
39                IssueType::exception(),
40                format!("Expected MLLP frame <SB>...<EB><CR>, got: {k:?}"),
41            ));
42        }
43        Ok(&framed[1..framed.len() - 2])
44    }
45
46    #[must_use]
47    pub fn ack() -> [u8; 4] {
48        [START_BLOCK, COMMIT_ACK, END_BLOCK, CARRIAGE_RETURN]
49    }
50
51    #[must_use]
52    pub fn nak() -> [u8; 4] {
53        [START_BLOCK, COMMIT_NAK, END_BLOCK, CARRIAGE_RETURN]
54    }
55
56    #[must_use]
57    pub fn is_ack(bytes: &[u8]) -> bool {
58        bytes == Self::ack()
59    }
60
61    #[must_use]
62    pub fn is_nak(bytes: &[u8]) -> bool {
63        bytes == Self::nak()
64    }
65
66    /// Reads a single MLLP frame from `reader`, returning the raw framed bytes,
67    /// including the `START_BLOCK`, `END_BLOCK`, and `CARRIAGE_RETURN`
68    /// delimiters.
69    ///
70    /// # Errors
71    ///
72    /// Returns an [`OperationOutcomeError`] if:
73    /// - the stream ends before a complete MLLP frame is received,
74    /// - the frame does not begin with `START_BLOCK`, or
75    /// - an I/O error occurs while reading from the stream.
76    pub fn read_frame<R: Read>(reader: &mut R) -> Result<Vec<u8>, OperationOutcomeError> {
77        let mut buf = Vec::new();
78        let mut byte = [0u8; 1];
79        loop {
80            match reader.read(&mut byte) {
81                Ok(0) => {
82                    return Err(OperationOutcomeError::error(
83                        IssueType::exception(),
84                        "Connection closed before complete MLLP frame".to_string(),
85                    ));
86                }
87                Ok(_) => {
88                    buf.push(byte[0]);
89                    let len = buf.len();
90
91                    if buf[0] != START_BLOCK {
92                        return Err(OperationOutcomeError::error(
93                            IssueType::exception(),
94                            "MLLP frame does not start with START_BLOCK".to_string(),
95                        ));
96                    }
97
98                    if len >= 2 && buf[len - 2] == END_BLOCK && buf[len - 1] == CARRIAGE_RETURN {
99                        return Ok(buf);
100                    }
101                }
102                Err(e) => {
103                    return Err(OperationOutcomeError::error(
104                        IssueType::exception(),
105                        format!("Failed to read from stream: {e}"),
106                    ));
107                }
108            }
109        }
110    }
111}