use dicom_toolkit_core::error::DcmResult;
use dicom_toolkit_data::{io::reader::DicomReader, io::writer::DicomWriter, DataSet};
use dicom_toolkit_dict::tags;
use crate::association::Association;
use crate::services::provider::{FindEvent, FindServiceProvider};
use crate::services::recv_command_data_bytes;
#[derive(Debug, Clone)]
pub struct FindRequest {
pub sop_class_uid: String,
pub query: Vec<u8>,
pub context_id: u8,
pub priority: u16,
}
pub async fn c_find(assoc: &mut Association, req: FindRequest) -> DcmResult<Vec<Vec<u8>>> {
let msg_id = next_message_id();
let mut cmd = DataSet::new();
cmd.set_uid(tags::AFFECTED_SOP_CLASS_UID, &req.sop_class_uid);
cmd.set_u16(tags::COMMAND_FIELD, 0x0020); cmd.set_u16(tags::MESSAGE_ID, msg_id);
cmd.set_u16(tags::PRIORITY, req.priority);
cmd.set_u16(tags::COMMAND_DATA_SET_TYPE, 0x0000);
assoc.send_dimse_command(req.context_id, &cmd).await?;
assoc.send_dimse_data(req.context_id, &req.query).await?;
let mut results: Vec<Vec<u8>> = Vec::new();
loop {
let (_ctx, rsp_cmd) = assoc.recv_dimse_command().await?;
let status = rsp_cmd.get_u16(tags::STATUS).unwrap_or(0xFFFF);
let has_dataset = rsp_cmd
.get_u16(tags::COMMAND_DATA_SET_TYPE)
.map(|v| v != 0x0101)
.unwrap_or(false);
if has_dataset {
let data = assoc.recv_dimse_data().await?;
results.push(data);
}
let is_pending = status == 0xFF00 || status == 0xFF01;
if !is_pending {
break;
}
}
Ok(results)
}
fn next_message_id() -> u16 {
use std::sync::atomic::{AtomicU16, Ordering};
static ID: AtomicU16 = AtomicU16::new(1);
ID.fetch_add(1, Ordering::Relaxed)
}
const TS_EXPLICIT_LE: &str = "1.2.840.10008.1.2.1";
fn encode_dataset(ds: &DataSet, transfer_syntax: &str) -> DcmResult<Vec<u8>> {
let mut buf = Vec::new();
DicomWriter::new(&mut buf).write_dataset(ds, transfer_syntax)?;
Ok(buf)
}
pub async fn handle_find_rq<P>(
assoc: &mut Association,
ctx_id: u8,
cmd: &DataSet,
provider: &P,
) -> DcmResult<()>
where
P: FindServiceProvider,
{
let sop_class = cmd
.get_string(tags::AFFECTED_SOP_CLASS_UID)
.unwrap_or_default()
.trim_end_matches('\0')
.to_string();
let msg_id = cmd.get_u16(tags::MESSAGE_ID).unwrap_or(1);
let query_bytes = recv_command_data_bytes(assoc, cmd).await?;
let negotiated_ts = assoc
.context_by_id(ctx_id)
.map(|pc| pc.transfer_syntax.trim_end_matches('\0').to_string())
.unwrap_or_else(|| TS_EXPLICIT_LE.to_string());
let identifier = DicomReader::new(query_bytes.as_slice())
.read_dataset(&negotiated_ts)
.unwrap_or_else(|_| DataSet::new());
let event = FindEvent {
calling_ae: assoc.calling_ae.clone(),
sop_class_uid: sop_class.clone(),
identifier,
};
let matches = provider.on_find(event).await;
for result_ds in &matches {
let result_bytes = encode_dataset(result_ds, &negotiated_ts)?;
let mut rsp = DataSet::new();
rsp.set_uid(tags::AFFECTED_SOP_CLASS_UID, &sop_class);
rsp.set_u16(tags::COMMAND_FIELD, 0x8020); rsp.set_u16(tags::MESSAGE_ID_BEING_RESPONDED_TO, msg_id);
rsp.set_u16(tags::COMMAND_DATA_SET_TYPE, 0x0000); rsp.set_u16(tags::STATUS, 0xFF00);
assoc.send_dimse_command(ctx_id, &rsp).await?;
assoc.send_dimse_data(ctx_id, &result_bytes).await?;
}
let mut final_rsp = DataSet::new();
final_rsp.set_uid(tags::AFFECTED_SOP_CLASS_UID, &sop_class);
final_rsp.set_u16(tags::COMMAND_FIELD, 0x8020); final_rsp.set_u16(tags::MESSAGE_ID_BEING_RESPONDED_TO, msg_id);
final_rsp.set_u16(tags::COMMAND_DATA_SET_TYPE, 0x0101); final_rsp.set_u16(tags::STATUS, 0x0000);
assoc.send_dimse_command(ctx_id, &final_rsp).await
}
#[cfg(test)]
mod tests {
use super::{encode_dataset, TS_EXPLICIT_LE};
use crate::dimse;
use dicom_toolkit_data::{io::reader::DicomReader, DataSet};
use dicom_toolkit_dict::{tags, Vr};
#[test]
fn c_find_rq_command_build() {
let mut cmd = DataSet::new();
cmd.set_uid(tags::AFFECTED_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.2.1.1");
cmd.set_u16(tags::COMMAND_FIELD, 0x0020);
cmd.set_u16(tags::MESSAGE_ID, 1);
cmd.set_u16(tags::PRIORITY, 0);
cmd.set_u16(tags::COMMAND_DATA_SET_TYPE, 0x0000);
let bytes = dimse::encode_command_dataset(&cmd);
let decoded = dimse::decode_command_dataset(&bytes).unwrap();
assert_eq!(decoded.get_u16(tags::COMMAND_FIELD), Some(0x0020));
assert_eq!(decoded.get_u16(tags::PRIORITY), Some(0));
assert_eq!(decoded.get_u16(tags::COMMAND_DATA_SET_TYPE), Some(0x0000));
}
#[test]
fn c_find_rsp_pending() {
let mut rsp = DataSet::new();
rsp.set_u16(tags::COMMAND_FIELD, 0x8020); rsp.set_u16(tags::MESSAGE_ID_BEING_RESPONDED_TO, 1);
rsp.set_u16(tags::COMMAND_DATA_SET_TYPE, 0x0000); rsp.set_u16(tags::STATUS, 0xFF00);
let bytes = dimse::encode_command_dataset(&rsp);
let decoded = dimse::decode_command_dataset(&bytes).unwrap();
assert_eq!(decoded.get_u16(tags::STATUS), Some(0xFF00));
assert!(decoded.get_u16(tags::COMMAND_DATA_SET_TYPE).unwrap() != 0x0101);
}
#[test]
fn c_find_rsp_final_success() {
let mut rsp = DataSet::new();
rsp.set_u16(tags::COMMAND_FIELD, 0x8020);
rsp.set_u16(tags::MESSAGE_ID_BEING_RESPONDED_TO, 1);
rsp.set_u16(tags::COMMAND_DATA_SET_TYPE, 0x0101); rsp.set_u16(tags::STATUS, 0x0000);
let bytes = dimse::encode_command_dataset(&rsp);
let decoded = dimse::decode_command_dataset(&bytes).unwrap();
assert_eq!(decoded.get_u16(tags::STATUS), Some(0x0000));
assert_eq!(decoded.get_u16(tags::COMMAND_DATA_SET_TYPE), Some(0x0101));
}
#[test]
fn encode_dataset_uses_requested_transfer_syntax() {
let mut ds = DataSet::new();
ds.set_string(tags::PATIENT_NAME, Vr::PN, "DOE^JOHN");
let explicit = encode_dataset(&ds, TS_EXPLICIT_LE).unwrap();
assert_eq!(&explicit[4..6], b"PN");
let implicit = encode_dataset(&ds, "1.2.840.10008.1.2").unwrap();
assert_ne!(&implicit[4..6], b"PN");
let decoded = DicomReader::new(implicit.as_slice())
.read_dataset("1.2.840.10008.1.2")
.unwrap();
assert_eq!(decoded.get_string(tags::PATIENT_NAME), Some("DOE^JOHN"));
}
}