use dicom_toolkit_core::error::DcmResult;
use dicom_toolkit_data::{io::reader::DicomReader, DataSet};
use dicom_toolkit_dict::tags;
use crate::association::Association;
use crate::services::provider::{GetEvent, GetServiceProvider};
use crate::services::recv_command_data_bytes;
#[derive(Debug, Clone)]
pub struct GetRequest {
pub sop_class_uid: String,
pub query: Vec<u8>,
pub context_id: u8,
pub priority: u16,
}
#[derive(Debug, Clone)]
pub struct GetResponse {
pub status: u16,
pub remaining: Option<u16>,
pub completed: Option<u16>,
pub failed: Option<u16>,
pub warning: Option<u16>,
pub dataset: Option<Vec<u8>>,
}
#[derive(Debug, Clone)]
pub struct ReceivedInstance {
pub sop_class_uid: String,
pub sop_instance_uid: String,
pub transfer_syntax_uid: String,
pub dataset: Vec<u8>,
}
#[derive(Debug)]
pub struct GetResult {
pub responses: Vec<GetResponse>,
pub instances: Vec<ReceivedInstance>,
}
pub async fn c_get(assoc: &mut Association, req: GetRequest) -> DcmResult<GetResult> {
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, 0x0010); 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 responses = Vec::new();
let mut instances = Vec::new();
loop {
let (ctx_id, rsp_cmd) = assoc.recv_dimse_command().await?;
let command_field = rsp_cmd.get_u16(tags::COMMAND_FIELD).unwrap_or(0);
match command_field {
0x0001 => {
let sop_class = rsp_cmd
.get_string(tags::AFFECTED_SOP_CLASS_UID)
.unwrap_or_default()
.trim_end_matches('\0')
.to_string();
let sop_instance = rsp_cmd
.get_string(tags::AFFECTED_SOP_INSTANCE_UID)
.unwrap_or_default()
.trim_end_matches('\0')
.to_string();
let store_msg_id = rsp_cmd.get_u16(tags::MESSAGE_ID).unwrap_or(1);
let transfer_syntax_uid = 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 data = assoc.recv_dimse_data().await?;
instances.push(ReceivedInstance {
sop_class_uid: sop_class.clone(),
sop_instance_uid: sop_instance.clone(),
transfer_syntax_uid,
dataset: data,
});
let mut store_rsp = DataSet::new();
store_rsp.set_uid(tags::AFFECTED_SOP_CLASS_UID, &sop_class);
store_rsp.set_u16(tags::COMMAND_FIELD, 0x8001); store_rsp.set_u16(tags::MESSAGE_ID_BEING_RESPONDED_TO, store_msg_id);
store_rsp.set_u16(tags::COMMAND_DATA_SET_TYPE, 0x0101); store_rsp.set_uid(tags::AFFECTED_SOP_INSTANCE_UID, &sop_instance);
store_rsp.set_u16(tags::STATUS, 0x0000); assoc.send_dimse_command(ctx_id, &store_rsp).await?;
}
0x8010 => {
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);
let dataset = if has_dataset {
Some(assoc.recv_dimse_data().await?)
} else {
None
};
responses.push(GetResponse {
status,
remaining: rsp_cmd.get_u16(tags::NUMBER_OF_REMAINING_SUB_OPERATIONS),
completed: rsp_cmd.get_u16(tags::NUMBER_OF_COMPLETED_SUB_OPERATIONS),
failed: rsp_cmd.get_u16(tags::NUMBER_OF_FAILED_SUB_OPERATIONS),
warning: rsp_cmd.get_u16(tags::NUMBER_OF_WARNING_SUB_OPERATIONS),
dataset,
});
let is_pending = status == 0xFF00 || status == 0xFF01;
if !is_pending {
break;
}
}
_ => {
break;
}
}
}
Ok(GetResult {
responses,
instances,
})
}
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";
pub async fn handle_get_rq<P>(
assoc: &mut Association,
ctx_id: u8,
cmd: &DataSet,
provider: &P,
) -> DcmResult<()>
where
P: GetServiceProvider,
{
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 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(&ts)
.unwrap_or_else(|_| DataSet::new());
let event = GetEvent {
calling_ae: assoc.calling_ae.clone(),
sop_class_uid: sop_class.clone(),
identifier,
};
let items = provider.on_get(event).await;
let total = items.len() as u16;
let mut completed: u16 = 0;
let mut failed: u16 = 0;
for item in &items {
let remaining = total.saturating_sub(completed + failed + 1);
let store_ctx = assoc.find_context(&item.sop_class_uid).map(|pc| pc.id);
if let Some(store_ctx_id) = store_ctx {
let sub_msg_id = next_message_id();
let mut store_rq = DataSet::new();
store_rq.set_uid(tags::AFFECTED_SOP_CLASS_UID, &item.sop_class_uid);
store_rq.set_u16(tags::COMMAND_FIELD, 0x0001); store_rq.set_u16(tags::MESSAGE_ID, sub_msg_id);
store_rq.set_u16(tags::PRIORITY, 0);
store_rq.set_u16(tags::COMMAND_DATA_SET_TYPE, 0x0000); store_rq.set_uid(tags::AFFECTED_SOP_INSTANCE_UID, &item.sop_instance_uid);
assoc.send_dimse_command(store_ctx_id, &store_rq).await?;
assoc.send_dimse_data(store_ctx_id, &item.dataset).await?;
let (_rsp_ctx, store_rsp) = assoc.recv_dimse_command().await?;
let store_status = store_rsp.get_u16(tags::STATUS).unwrap_or(0xFFFF);
if store_status == 0x0000 {
completed += 1;
} else {
failed += 1;
}
let mut pending_rsp = DataSet::new();
pending_rsp.set_uid(tags::AFFECTED_SOP_CLASS_UID, &sop_class);
pending_rsp.set_u16(tags::COMMAND_FIELD, 0x8010); pending_rsp.set_u16(tags::MESSAGE_ID_BEING_RESPONDED_TO, msg_id);
pending_rsp.set_u16(tags::COMMAND_DATA_SET_TYPE, 0x0101); pending_rsp.set_u16(tags::STATUS, 0xFF00); pending_rsp.set_u16(tags::NUMBER_OF_REMAINING_SUB_OPERATIONS, remaining);
pending_rsp.set_u16(tags::NUMBER_OF_COMPLETED_SUB_OPERATIONS, completed);
pending_rsp.set_u16(tags::NUMBER_OF_FAILED_SUB_OPERATIONS, failed);
pending_rsp.set_u16(tags::NUMBER_OF_WARNING_SUB_OPERATIONS, 0);
assoc.send_dimse_command(ctx_id, &pending_rsp).await?;
} else {
failed += 1;
}
}
let final_status: u16 = if failed > 0 { 0xB000 } else { 0x0000 };
let mut final_rsp = DataSet::new();
final_rsp.set_uid(tags::AFFECTED_SOP_CLASS_UID, &sop_class);
final_rsp.set_u16(tags::COMMAND_FIELD, 0x8010); 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, final_status);
final_rsp.set_u16(tags::NUMBER_OF_REMAINING_SUB_OPERATIONS, 0);
final_rsp.set_u16(tags::NUMBER_OF_COMPLETED_SUB_OPERATIONS, completed);
final_rsp.set_u16(tags::NUMBER_OF_FAILED_SUB_OPERATIONS, failed);
final_rsp.set_u16(tags::NUMBER_OF_WARNING_SUB_OPERATIONS, 0);
assoc.send_dimse_command(ctx_id, &final_rsp).await
}
#[cfg(test)]
mod tests {
use crate::dimse;
use dicom_toolkit_data::DataSet;
use dicom_toolkit_dict::tags;
#[test]
fn c_get_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.3");
cmd.set_u16(tags::COMMAND_FIELD, 0x0010); 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(0x0010));
assert_eq!(decoded.get_u16(tags::PRIORITY), Some(0));
assert_eq!(decoded.get_u16(tags::COMMAND_DATA_SET_TYPE), Some(0x0000));
}
#[test]
fn c_get_rsp_pending_has_sub_operation_counts() {
let mut rsp = DataSet::new();
rsp.set_u16(tags::COMMAND_FIELD, 0x8010); rsp.set_u16(tags::MESSAGE_ID_BEING_RESPONDED_TO, 1);
rsp.set_u16(tags::COMMAND_DATA_SET_TYPE, 0x0101); rsp.set_u16(tags::STATUS, 0xFF00); rsp.set_u16(tags::NUMBER_OF_REMAINING_SUB_OPERATIONS, 5);
rsp.set_u16(tags::NUMBER_OF_COMPLETED_SUB_OPERATIONS, 2);
rsp.set_u16(tags::NUMBER_OF_FAILED_SUB_OPERATIONS, 0);
rsp.set_u16(tags::NUMBER_OF_WARNING_SUB_OPERATIONS, 0);
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_eq!(
decoded.get_u16(tags::NUMBER_OF_REMAINING_SUB_OPERATIONS),
Some(5)
);
assert_eq!(
decoded.get_u16(tags::NUMBER_OF_COMPLETED_SUB_OPERATIONS),
Some(2)
);
}
#[test]
fn c_get_rsp_final_success() {
let mut rsp = DataSet::new();
rsp.set_u16(tags::COMMAND_FIELD, 0x8010);
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);
rsp.set_u16(tags::NUMBER_OF_COMPLETED_SUB_OPERATIONS, 7);
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::NUMBER_OF_COMPLETED_SUB_OPERATIONS),
Some(7)
);
}
}