mllp_rs/
lib.rs

1//! Simple MLLP implementation
2//!
3//! "The goal of the MLLP Message Transport protocol is to provide an interface between HL7
4//! Applications and the transport protocol that uses minimal overhead." (from *HL7 Version 3 Standard:
5//! Transport Specification - MLLP, Release 2*).
6//!
7//! MLLP is a simple protocol used for transmitting HL7 messages between HL7 applications. It goes
8//! like this `<SB>...<EB><CR>`, where:
9//! - SB is the Start Block Character, 0x0B.
10//! - EB is the End Block Character, 0x1C.
11//! - CR is the Carriage Return Character, 0x0D.
12//! This is called the Block Format.
13//!
14//! MLLP contains 2 other formats, the Commit Acknowledgement
15//! Block `<SB><ACK><EB><CR>`, and the Negative Commit Acknowledgement Block `<SB><NACK><EB><CR>`,
16//! where:
17//! - ACK is the acknowledgement character, 0x06.
18//! - NAK is the negative-acknowledgement character, 0x15.
19//!
20//! # Quick start
21//!
22//! Client side code might look like this:
23//! ```
24//! use std::io::prelude::*;
25//! use std::net::TcpStream;
26//! use mllp_rs::MllpCodec;
27//!
28//! // Client side
29//! let mut stream = TcpStream::connect("127.0.0.1:5000")?;
30//! let _ = stream.write(MllpCodec::encode("MSH|^~\&|WIR|||36|20200514123930||VXU^V04^VXU_V04|43|P|2.5.1|||ER".as_bytes()).as_bytes());
31//! ```
32//!
33//! Server side code might look like this:
34//! ```
35//! use std::io::prelude::*;
36//! use std::net::TcpListener;
37//! use mllp_rs::MllpCodec;
38//!
39//! let mut listener = TcpListener::bind(addr).unwrap();
40//! for stream in listener.incoming() {
41//!     let mut buf: Vec<u8> = vec![];
42//!     let _ = stream?.read_to_end(&mut buf);
43//!     let decoded_data = String::from_utf8_lossy(MllpCodec::decode(buf.as_slice())?);
44//! }
45//! ```
46
47extern crate core;
48
49use std::fmt;
50
51/// Start Block
52const SB: u8 = 11u8;
53/// End Block
54const EB: u8 = 28u8;
55/// Carriage Return
56const CR: u8 = 13u8;
57const ACK: u8 = 6u8;
58/// Negative ACK
59const NAK: u8 = 15u8;
60
61pub struct MllpCodec { }
62
63impl MllpCodec {
64    pub fn encode(with: &[u8]) -> Vec<u8> {
65        let mut buf: Vec<u8> = vec![];
66
67        buf.push(SB);
68        buf.extend(with.iter());
69        buf.push(EB);
70        buf.push(CR);
71
72        buf
73    }
74
75    pub fn decode(with: &[u8]) -> Result<&[u8], MllpSyntaxError> {
76        assert!(with.len() >= 4);
77
78        let sb = with[0];
79        let hl7 = &with[1..with.len() - 2];
80        let eb = with[with.len() - 2];
81        let cr = with[with.len() - 1];
82
83        if sb == SB && eb == EB && cr == CR {
84            Ok(hl7)
85        } else {
86            Err(MllpSyntaxError)
87        }
88    }
89
90    /// Creates an MLLP ACK.
91    /// ```
92    /// use mllp_rs::MllpCodec;
93    ///
94    /// let ack = MllpCodec::ack();
95    /// ```
96    pub fn ack() -> [u8;4] {
97        [SB, ACK, EB, CR]
98    }
99
100    /// Creates an MLLP NAK (Negative ACK).
101    /// ```
102    /// use mllp_rs::MllpCodec;
103    ///
104    /// let nak = MllpCodec::nak();
105    /// ```
106    pub fn nak() -> [u8;4] {
107        [SB, NAK, EB, CR]
108    }
109
110    pub fn is_ack(with: &[u8]) -> bool {
111        with == Self::ack()
112    }
113
114    pub fn is_nak(with: &[u8]) -> bool {
115        with == Self::nak()
116    }
117}
118
119#[derive(Debug)]
120pub struct MllpSyntaxError;
121
122impl fmt::Display for MllpSyntaxError {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        write!(f, "Expected bytes <SB>...<EB><CR>")
125    }
126}
127
128impl std::error::Error for MllpSyntaxError { }
129
130#[cfg(test)]
131mod tests {
132    use std::io::{Read, Write};
133    use std::net::{SocketAddr, TcpListener, TcpStream};
134    use std::sync::mpsc;
135    use std::thread;
136    use std::time::Duration;
137    use crate::MllpCodec;
138
139    #[test]
140    fn encode_and_decode_same_message() {
141        let data = "MSH|^~\\&|ZIS|1^AHospital|||200405141144||¶ADT^A01|20041104082400|P|2.3|||AL|NE|||8859/15|¶EVN|A01|20041104082400.0000+0100|20041104082400¶PID||\"\"|10||Vries^Danny^D.^^de||19951202|M|||Rembrandlaan^7^Leiden^^7301TH^\"\"^^P||\"\"|\"\"||\"\"|||||||\"\"|\"\"¶PV1||I|3w^301^\"\"^01|S|||100^van den Berg^^A.S.^^\"\"^dr|\"\"||9||||H||||20041104082400.0000+0100";
142        let encoded_data = MllpCodec::encode(data.as_bytes());
143        let decoded_data = MllpCodec::decode(encoded_data.as_slice());
144
145        assert!(decoded_data.is_ok());
146        assert_eq!(decoded_data.unwrap(), data.as_bytes());
147    }
148
149    #[test]
150    fn listen_and_receive_mllp_packet() {
151        let data = "MSH|^~\\&|ZIS|1^AHospital|||200405141144||¶ADT^A01|20041104082400|P|2.3|||AL|NE|||8859/15|¶EVN|A01|20041104082400.0000+0100|20041104082400¶PID||\"\"|10||Vries^Danny^D.^^de||19951202|M|||Rembrandlaan^7^Leiden^^7301TH^\"\"^^P||\"\"|\"\"||\"\"|||||||\"\"|\"\"¶PV1||I|3w^301^\"\"^01|S|||100^van den Berg^^A.S.^^\"\"^dr|\"\"||9||||H||||20041104082400.0000+0100";
152        let original_data = data.clone();
153        let addr = "127.0.0.1:5000";
154        let (tx, rx) = mpsc::channel();
155
156        let handler = thread::spawn(move || {
157            let listener = TcpListener::bind(addr).unwrap();
158            tx.send(true).unwrap();
159
160            for stream in listener.incoming() {
161                assert!(stream.is_ok());
162                let mut buf: Vec<u8> = vec![];
163                let _ = stream.unwrap().read_to_end(&mut buf);
164                let decoded_data = String::from_utf8_lossy(MllpCodec::decode(buf.as_slice()).unwrap());
165                assert_eq!(decoded_data, data);
166                break;
167            }
168            // close the socket server
169            drop(listener);
170        });
171
172        let handler2 = thread::spawn(move || {
173            for received in rx {
174                if received {
175                    let socket_addr = SocketAddr::from(([127, 0, 0, 1], 5000));
176                    let mut stream = TcpStream::connect_timeout(&socket_addr, Duration::from_secs(3)).unwrap();
177                    let _ = stream.write(MllpCodec::encode(original_data.as_bytes()).as_slice());
178                }
179            }
180
181        });
182
183        handler2.join().expect("TODO: panic message server");
184        handler.join().expect("TODO: panic message listener");
185    }
186
187    #[test]
188    fn it_creates_ack() {
189        let ack = MllpCodec::ack();
190        assert!(MllpCodec::is_ack(&ack));
191    }
192
193    #[test]
194    fn it_creates_nak() {
195        let nak = MllpCodec::nak();
196        assert!(MllpCodec::is_nak(&nak));
197    }
198}