1use super::dataset::{self, IMPLICIT_LE};
5use anyhow::{bail, ensure, Context, Result};
6use serde_json::{json, Map, Value};
7use tokio::io::{AsyncRead, AsyncReadExt};
8
9pub const APPLICATION_CONTEXT: &str = "1.2.840.10008.3.1.1.1";
10pub const VERIFICATION: &str = "1.2.840.10008.1.1";
11pub const PATIENT_ROOT_FIND: &str = "1.2.840.10008.5.1.4.1.2.1.1";
12pub const STUDY_ROOT_FIND: &str = "1.2.840.10008.5.1.4.1.2.2.1";
13pub const STORAGE_PREFIX: &str = "1.2.840.10008.5.1.4.1.1.";
14pub const IMPLEMENTATION_CLASS: &str = "1.2.826.0.1.3680043.10.1408.1";
15pub const IMPLEMENTATION_VERSION: &str = "NETGET_1";
16pub const MAX_PDU: u32 = 1 << 20;
18
19pub const C_STORE_RQ: u16 = 0x0001;
20pub const C_FIND_RQ: u16 = 0x0020;
21pub const C_ECHO_RQ: u16 = 0x0030;
22pub const C_CANCEL_RQ: u16 = 0x0FFF;
23pub const NO_DATASET: u16 = 0x0101;
24
25#[derive(Debug, Clone, PartialEq)]
26pub struct PresentationContext {
27 pub id: u8,
28 pub abstract_syntax: String,
29 pub transfer_syntaxes: Vec<String>,
30}
31
32#[derive(Debug, Clone, PartialEq)]
33pub struct AssociateRq {
34 pub called: String,
35 pub calling: String,
36 pub contexts: Vec<PresentationContext>,
37 pub max_pdu: u32,
38 pub implementation: Option<String>,
39}
40
41#[derive(Debug, Clone, PartialEq)]
42pub enum Pdu {
43 AssociateRq(AssociateRq),
44 AssociateAc(Vec<(u8, u8, String)>, u32),
46 AssociateRj {
47 result: u8,
48 source: u8,
49 reason: u8,
50 },
51 Data(Vec<(u8, u8, Vec<u8>)>),
53 ReleaseRq,
54 ReleaseRp,
55 Abort {
56 source: u8,
57 reason: u8,
58 },
59}
60
61fn ae(raw: &[u8]) -> String {
62 String::from_utf8_lossy(raw).trim().to_owned()
63}
64
65fn uid(raw: &[u8]) -> Result<String> {
66 let s = std::str::from_utf8(raw)?
67 .trim_end_matches(['\0', ' '])
68 .to_owned();
69 ensure!(
70 !s.is_empty() && s.len() <= 64 && s.bytes().all(|b| b.is_ascii_digit() || b == b'.'),
71 "invalid UID"
72 );
73 Ok(s)
74}
75
76pub async fn read<R: AsyncRead + Unpin>(r: &mut R) -> Result<Option<Pdu>> {
77 let mut head = [0u8; 6];
78 match r.read_exact(&mut head).await {
79 Ok(_) => {}
80 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
81 Err(e) => return Err(e.into()),
82 }
83 let len = u32::from_be_bytes([head[2], head[3], head[4], head[5]]);
84 ensure!(len <= MAX_PDU + 6, "PDU of {len} bytes exceeds the maximum");
85 let mut body = vec![0u8; len as usize];
86 r.read_exact(&mut body).await?;
87 Ok(Some(decode(head[0], &body)?))
88}
89
90fn items(b: &[u8]) -> Result<Vec<(u8, &[u8])>> {
91 let mut out = Vec::new();
92 let mut i = 0;
93 while i < b.len() {
94 ensure!(i + 4 <= b.len(), "truncated item");
95 let t = b[i];
96 let l = u16::from_be_bytes([b[i + 2], b[i + 3]]) as usize;
97 let body = b
98 .get(i + 4..i + 4 + l)
99 .context("item longer than its PDU")?;
100 out.push((t, body));
101 i += 4 + l;
102 }
103 Ok(out)
104}
105
106pub fn decode(kind: u8, b: &[u8]) -> Result<Pdu> {
107 Ok(match kind {
108 0x01 | 0x02 => {
109 ensure!(b.len() >= 68, "short association PDU");
110 let mut contexts = Vec::new();
111 let mut accepted = Vec::new();
112 let mut max_pdu = 0;
113 let mut implementation = None;
114 let mut app_ok = false;
115 for (t, body) in items(&b[68..])? {
116 match t {
117 0x10 => {
118 app_ok = std::str::from_utf8(body)
119 .map(|s| s.trim_end_matches('\0') == APPLICATION_CONTEXT)
120 .unwrap_or(false)
121 }
122 0x20 if kind == 0x01 => {
123 ensure!(body.len() >= 4, "short presentation context");
124 let mut abs = None;
125 let mut ts = Vec::new();
126 for (st, sb) in items(&body[4..])? {
127 match st {
128 0x30 => abs = Some(uid(sb)?),
129 0x40 => ts.push(uid(sb)?),
130 _ => {}
131 }
132 }
133 contexts.push(PresentationContext {
134 id: body[0],
135 abstract_syntax: abs
136 .context("presentation context without abstract syntax")?,
137 transfer_syntaxes: ts,
138 });
139 }
140 0x21 if kind == 0x02 => {
141 ensure!(body.len() >= 4, "short presentation context");
142 let ts = items(&body[4..])?
143 .into_iter()
144 .find(|(st, _)| *st == 0x40)
145 .map(|(_, sb)| uid(sb))
146 .transpose()?
147 .unwrap_or_default();
148 accepted.push((body[0], body[2], ts));
149 }
150 0x50 => {
151 for (st, sb) in items(body)? {
152 match st {
153 0x51 if sb.len() == 4 => {
154 max_pdu = u32::from_be_bytes([sb[0], sb[1], sb[2], sb[3]])
155 }
156 0x52 => implementation = uid(sb).ok(),
157 _ => {}
158 }
159 }
160 }
161 _ => {}
162 }
163 }
164 ensure!(app_ok, "unsupported application context");
165 if kind == 0x01 {
166 ensure!(contexts.len() <= 128, "too many presentation contexts");
167 Pdu::AssociateRq(AssociateRq {
168 called: ae(&b[4..20]),
169 calling: ae(&b[20..36]),
170 contexts,
171 max_pdu,
172 implementation,
173 })
174 } else {
175 Pdu::AssociateAc(accepted, max_pdu)
176 }
177 }
178 0x03 => {
179 ensure!(b.len() >= 4, "short A-ASSOCIATE-RJ");
180 Pdu::AssociateRj {
181 result: b[1],
182 source: b[2],
183 reason: b[3],
184 }
185 }
186 0x04 => {
187 let mut pdvs = Vec::new();
188 let mut i = 0;
189 while i < b.len() {
190 ensure!(i + 6 <= b.len(), "truncated PDV");
191 let l = u32::from_be_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) as usize;
192 ensure!(l >= 2 && i + 4 + l <= b.len(), "PDV longer than its PDU");
193 pdvs.push((b[i + 4], b[i + 5], b[i + 6..i + 4 + l].to_vec()));
194 i += 4 + l;
195 }
196 Pdu::Data(pdvs)
197 }
198 0x05 => Pdu::ReleaseRq,
199 0x06 => Pdu::ReleaseRp,
200 0x07 => {
201 ensure!(b.len() >= 4, "short A-ABORT");
202 Pdu::Abort {
203 source: b[2],
204 reason: b[3],
205 }
206 }
207 other => bail!("unknown PDU type {other:#04x}"),
208 })
209}
210
211fn item(t: u8, body: &[u8]) -> Vec<u8> {
212 let mut v = vec![t, 0];
213 v.extend_from_slice(&(body.len() as u16).to_be_bytes());
214 v.extend_from_slice(body);
215 v
216}
217
218fn ae_field(s: &str) -> [u8; 16] {
219 let mut f = [b' '; 16];
220 for (i, b) in s.bytes().take(16).enumerate() {
221 f[i] = b;
222 }
223 f
224}
225
226fn user_info(max_pdu: u32) -> Vec<u8> {
227 let mut ui = item(0x51, &max_pdu.to_be_bytes());
228 ui.extend(item(0x52, IMPLEMENTATION_CLASS.as_bytes()));
229 ui.extend(item(0x55, IMPLEMENTATION_VERSION.as_bytes()));
230 item(0x50, &ui)
231}
232
233fn frame(kind: u8, body: Vec<u8>) -> Vec<u8> {
234 let mut v = vec![kind, 0];
235 v.extend_from_slice(&(body.len() as u32).to_be_bytes());
236 v.extend(body);
237 v
238}
239
240fn assoc_head(called: &str, calling: &str) -> Vec<u8> {
241 let mut b = vec![0, 1, 0, 0];
242 b.extend_from_slice(&ae_field(called));
243 b.extend_from_slice(&ae_field(calling));
244 b.extend_from_slice(&[0u8; 32]);
245 b.extend(item(0x10, APPLICATION_CONTEXT.as_bytes()));
246 b
247}
248
249pub fn encode(p: &Pdu, called: &str, calling: &str) -> Vec<u8> {
250 match p {
251 Pdu::AssociateRq(rq) => {
252 let mut b = assoc_head(&rq.called, &rq.calling);
253 for c in &rq.contexts {
254 let mut pc = vec![c.id, 0, 0, 0];
255 pc.extend(item(0x30, c.abstract_syntax.as_bytes()));
256 for ts in &c.transfer_syntaxes {
257 pc.extend(item(0x40, ts.as_bytes()));
258 }
259 b.extend(item(0x20, &pc));
260 }
261 b.extend(user_info(MAX_PDU));
262 frame(0x01, b)
263 }
264 Pdu::AssociateAc(results, _) => {
265 let mut b = assoc_head(called, calling);
266 for (id, result, ts) in results {
267 let mut pc = vec![*id, 0, *result, 0];
268 pc.extend(item(0x40, ts.as_bytes()));
269 b.extend(item(0x21, &pc));
270 }
271 b.extend(user_info(MAX_PDU));
272 frame(0x02, b)
273 }
274 Pdu::AssociateRj {
275 result,
276 source,
277 reason,
278 } => frame(0x03, vec![0, *result, *source, *reason]),
279 Pdu::Data(pdvs) => {
280 let mut b = Vec::new();
281 for (id, flags, bytes) in pdvs {
282 b.extend_from_slice(&((bytes.len() + 2) as u32).to_be_bytes());
283 b.push(*id);
284 b.push(*flags);
285 b.extend_from_slice(bytes);
286 }
287 frame(0x04, b)
288 }
289 Pdu::ReleaseRq => frame(0x05, vec![0; 4]),
290 Pdu::ReleaseRp => frame(0x06, vec![0; 4]),
291 Pdu::Abort { source, reason } => frame(0x07, vec![0, 0, *source, *reason]),
292 }
293}
294
295pub fn data_pdus(context: u8, command: bool, bytes: &[u8], peer_max: u32) -> Vec<Vec<u8>> {
298 let max = if peer_max == 0 {
299 MAX_PDU
300 } else {
301 peer_max.min(MAX_PDU)
302 } as usize;
303 let chunk = max.saturating_sub(6).max(256);
304 let mut out = Vec::new();
305 let pieces: Vec<&[u8]> = if bytes.is_empty() {
306 vec![&[][..]]
307 } else {
308 bytes.chunks(chunk).collect()
309 };
310 let n = pieces.len();
311 for (i, piece) in pieces.into_iter().enumerate() {
312 let flags = (command as u8) | if i + 1 == n { 0x02 } else { 0 };
313 out.push(encode(
314 &Pdu::Data(vec![(context, flags, piece.to_vec())]),
315 "",
316 "",
317 ));
318 }
319 out
320}
321
322pub fn command(fields: &[(u32, Value)]) -> Vec<u8> {
324 let mut m = Map::new();
325 for (tag, v) in fields {
326 m.insert(dataset::tag_key(*tag), v.clone());
327 }
328 let body = dataset::encode(&m, IMPLICIT_LE).expect("command sets encode");
329 let mut out = Vec::new();
330 out.extend_from_slice(&0u16.to_le_bytes());
331 out.extend_from_slice(&0u16.to_le_bytes());
332 out.extend_from_slice(&4u32.to_le_bytes());
333 out.extend_from_slice(&(body.len() as u32).to_le_bytes());
334 out.extend(body);
335 out
336}
337
338pub fn us(v: u16) -> Value {
339 json!({"vr": "US", "Value": [v]})
340}
341
342pub fn ui(v: &str) -> Value {
343 json!({"vr": "UI", "Value": [v]})
344}
345
346#[derive(Default, Debug)]
349pub struct Assembly {
350 pub context: u8,
351 pub command: Vec<u8>,
352 pub command_done: bool,
353 pub dataset: Vec<u8>,
354 pub dataset_done: bool,
355}
356
357pub struct Message {
358 pub context: u8,
359 pub command: Map<String, Value>,
360 pub dataset: Option<Vec<u8>>,
361}
362
363impl Assembly {
364 pub fn feed(&mut self, pdvs: Vec<(u8, u8, Vec<u8>)>) -> Result<Option<Message>> {
366 for (ctx, flags, bytes) in pdvs {
367 self.context = ctx;
368 let is_command = flags & 1 == 1;
369 let last = flags & 2 == 2;
370 if is_command {
371 ensure!(
372 !self.command_done,
373 "command fragment after the command ended"
374 );
375 ensure!(
376 self.command.len() + bytes.len() <= 64 * 1024,
377 "command set too large"
378 );
379 self.command.extend(bytes);
380 self.command_done = last;
381 } else {
382 ensure!(self.command_done, "dataset before its command");
383 ensure!(
384 self.dataset.len() + bytes.len() <= dataset::MAX_DATASET,
385 "dataset over 16 MiB"
386 );
387 self.dataset.extend(bytes);
388 self.dataset_done = last;
389 }
390 }
391 if !self.command_done {
392 return Ok(None);
393 }
394 let cmd = dataset::decode(&self.command, IMPLICIT_LE)?;
395 let has_dataset = cmd
396 .get("00000800")
397 .and_then(|v| v["Value"][0].as_u64())
398 .is_some_and(|t| t != NO_DATASET as u64);
399 if has_dataset && !self.dataset_done {
400 return Ok(None);
401 }
402 let done = std::mem::take(self);
403 Ok(Some(Message {
404 context: done.context,
405 command: cmd,
406 dataset: has_dataset.then_some(done.dataset),
407 }))
408 }
409}
410
411pub fn field_u16(cmd: &Map<String, Value>, tag: u32) -> Option<u16> {
412 cmd.get(&dataset::tag_key(tag))
413 .and_then(|v| v["Value"][0].as_u64())
414 .map(|v| v as u16)
415}
416
417pub fn field_str(cmd: &Map<String, Value>, tag: u32) -> Option<String> {
418 dataset::text(cmd, tag)
419}