Skip to content

Commit

Permalink
feat: store audit message
Browse files Browse the repository at this point in the history
  • Loading branch information
Chinlinlee committed Aug 25, 2023
1 parent 4b6ba4d commit e9c68cc
Show file tree
Hide file tree
Showing 126 changed files with 9,392 additions and 1,532 deletions.
22 changes: 22 additions & 0 deletions api/dicom-web/controller/STOW-RS/service/stow-rs.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const { logger } = require("../../../../../utils/logs/log");

const { raccoonConfig } = require("../../../../../config-class");
const { DicomWebService } = require("../../../service/dicom-web.service");
const { AuditManager } = require("@models/DICOM/audit/auditManager");

const {
apiPath: DICOM_WEB_API_PATH
Expand Down Expand Up @@ -118,6 +119,18 @@ class StowRsService {
dicomJsonModel.setMinifyDicomJsonAndTempBigValueTags();
dicomJsonModel.setUidObj();

let auditManager = new AuditManager();
let remoteAddress = _.get(this.request, "socket.remoteAddress", "127.0.0.1");
let onBeginTransferringDicomInstancesMsg = await auditManager.onBeginTransferringDicomInstances(
"0",
remoteAddress, this.request.headers.host.split(':')[0],
`${raccoonConfig.serverConfig.host}:${raccoonConfig.serverConfig.port}`, `${raccoonConfig.serverConfig.host}`,
[dicomJsonModel.uidObj.studyUID], [dicomJsonModel.uidObj.sopClass],
dicomJsonModel.uidObj.patientID, dicomJsonModel.getPatientDicomJson()['00100010']['Value'][0]['Alphabetic']
);
logger.info(JSON.stringify(onBeginTransferringDicomInstancesMsg));
// TODO: Store Audit Message to DB

let isSameStudyIDStatus = this.isSameStudyID_(this.responseMessage);
if (!isSameStudyIDStatus) {
this.responseCode = 409;
Expand All @@ -143,6 +156,15 @@ class StowRsService {
_.set(sopSeq, "00081190.Value", [retrieveUrlObj.instance]);
this.responseMessage["00081199"]["Value"].push(sopSeq);

let onDicomInstancesTransferredMsg = await auditManager.onDicomInstancesTransferred("C", "0",
remoteAddress, this.request.headers.host.split(':')[0],
`${raccoonConfig.serverConfig.host}:${raccoonConfig.serverConfig.port}`, `${raccoonConfig.serverConfig.host}`,
[dicomJsonModel.uidObj.studyUID], [dicomJsonModel.uidObj.sopClass],
dicomJsonModel.uidObj.patientID, dicomJsonModel.getPatientDicomJson()['00100010']['Value'][0]['Alphabetic']
);
logger.info(JSON.stringify(onDicomInstancesTransferredMsg));
// TODO: Store Audit Message to DB

return {
dicomJsonModel,
dicomFileSaveInfo
Expand Down
19 changes: 17 additions & 2 deletions dimse/c-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,34 @@ const { createCStoreSCPInjectProxy } = require("@java-wrapper/org/github/chinlin
const { default: SimpleCStoreSCP } = require("@java-wrapper/org/github/chinlinlee/dcm777/net/SimpleCStoreSCP");
const { default: File } = require("@java-wrapper/java/io/File");
const { StowRsService } = require("@root/api/dicom-web/controller/STOW-RS/service/stow-rs.service");
const { default: Association } = require("@dcm4che/net/Association");
const { PresentationContext } = require("@dcm4che/net/pdu/PresentationContext");
const { Attributes } = require("@dcm4che/data/Attributes");

const cStoreScpInjectProxy = createCStoreSCPInjectProxy({
postDimseRQ: async (association, presentationContext, dimse, requestAttr, data, responseAttr) => {
await association.tryWriteDimseRSP(presentationContext, responseAttr);
},
/**
*
* @param {Association} association
* @param {PresentationContext} presentationContext
* @param {Attributes} requestAttr
* @param {*} data
* @param {*} responseAttr
* @param {*} file
*/
postStore: async (association, presentationContext, requestAttr, data, responseAttr, file) => {
let absPath = await file.getAbsolutePath();

let stowRsService = new StowRsService({
headers: {
host: "fake-host"
host: await association.getRemoteHostName()
},
params: {}
params: {},
socket: {
remoteAddress: await association.getCallingAET()
}
}, []);

/** @type {formidable.File} */
Expand Down
209 changes: 209 additions & 0 deletions models/DICOM/audit/auditManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
const _ = require("lodash");

const { ActiveParticipant } = require("@dcm4che/audit/ActiveParticipant");
const { AuditMessages } = require("@dcm4che/audit/AuditMessages");
const { AuditMessages$EventActionCode } = require("@dcm4che/audit/AuditMessages$EventActionCode");
const { AuditMessages$EventID } = require("@dcm4che/audit/AuditMessages$EventID");
const { AuditMessages$NetworkAccessPointTypeCode } = require("@dcm4che/audit/AuditMessages$NetworkAccessPointTypeCode");
const { AuditMessages$ParticipantObjectIDTypeCode } = require("@dcm4che/audit/AuditMessages$ParticipantObjectIDTypeCode");
const { AuditMessages$ParticipantObjectTypeCode } = require("@dcm4che/audit/AuditMessages$ParticipantObjectTypeCode");
const { AuditMessages$ParticipantObjectTypeCodeRole } = require("@dcm4che/audit/AuditMessages$ParticipantObjectTypeCodeRole");
const { AuditMessages$RoleIDCode } = require("@dcm4che/audit/AuditMessages$RoleIDCode");
const { EventID } = require("@dcm4che/audit/EventID");
const { EventIdentification } = require("@dcm4che/audit/EventIdentification");
const { ParticipantObjectContainsStudy } = require("@dcm4che/audit/ParticipantObjectContainsStudy");
const { ParticipantObjectDescription } = require("@dcm4che/audit/ParticipantObjectDescription");
const { ParticipantObjectIdentification } = require("@dcm4che/audit/ParticipantObjectIdentification");
const { SOPClass } = require("@dcm4che/audit/SOPClass");
const { Calendar } = require("@java-wrapper/java/util/Calendar");
const { Common } = require("@java-wrapper/org/github/chinlinlee/dcm777/common/Common");

class AuditManager {
constructor() { }

/**
* A.X.3.3 Begin Transferring DICOM Instances
* 當有DICOM檔案開始傳輸。
* 該事件通常用於 C-STORE、STOW-RS 或是 C-MOVE、WADO。
* @param {string} eventResult 該事件最終的結果? 必須為"0"、"4"、"8"、"12"其中一個,分別對應到Success、MinorFailure、SeriousFailure、MajorFailure,或是使用EventOutcomeIndicator類別(最終也會取得String)。
* @param {string} clientAETitle 發送者的AETitle
* @param {string} clientHostname 發送者的位址
* @param {string} serverAETitle 伺服器端的AETitle
* @param {string} serverHostname 伺服器端的位址
* @param {string[]} StudyInstanceUIDs 所有此次傳輸有關聯的StudyInstanceUID
* @param {string[]} SOPClassUIDs 所有此次傳輸有關聯的SOPClassUID
* @param {string} PatientID 一個此次傳輸關聯的PatientID
* @param {string} PatientName 一個此次傳輸關聯的PatientName
*/
async onBeginTransferringDicomInstances(
eventResult,
clientAETitle, clientHostname,
serverAETitle, serverHostname,
StudyInstanceUIDs, SOPClassUIDs,
PatientID, PatientName
) {
// #region Event
let theEvent = await EventIdentification.newInstanceAsync();
await theEvent.setEventID(EventID.BeginTransferringDICOMInstances);
await theEvent.setEventActionCode(AuditMessages$EventActionCode.Execute);
await theEvent.setEventDateTime(await Calendar.getInstance()); // 日期時間為當前時間自動取得
await theEvent.setEventOutcomeIndicator(eventResult);
// #endregion

// #region Active Participant: Application started (1)
let client = await ActiveParticipant.newInstanceAsync();
await client.setUserID(clientAETitle);
await client.setAlternativeUserID(clientAETitle);
await client.setUserName("");
await client.setUserIsRequestor(false);
await (await client.getRoleIDCode()).add(AuditMessages$RoleIDCode.Source);
await client.setNetworkAccessPointTypeCode(AuditMessages$NetworkAccessPointTypeCode.IPAddress);
await client.setNetworkAccessPointID(clientHostname);
// #endregion

// #region Active Participant: Process receiving the data (1); 正在接收者(Server)
let server = await ActiveParticipant.newInstanceAsync();
await server.setUserID(serverAETitle);
await server.setAlternativeUserID(serverAETitle);
await server.setUserName("");
await server.setUserIsRequestor(false);
await (await server.getRoleIDCode()).add(AuditMessages$RoleIDCode.Destination);
await server.setNetworkAccessPointTypeCode(AuditMessages$NetworkAccessPointTypeCode.IPAddress);
await server.setNetworkAccessPointID(serverHostname);
// #endregion

// #region Participating Object: Studies being transferred (1..N) 接收到的DICOM檔案的所有Study。
let theStudies = [];
for (let i = 0; i < SOPClassUIDs.length; i++) {
let theStudy = await ParticipantObjectIdentification.newInstanceAsync();
await theStudy.setParticipantObjectTypeCode(AuditMessages$ParticipantObjectTypeCode.SystemObject);
await theStudy.setParticipantObjectTypeCodeRole(AuditMessages$ParticipantObjectTypeCodeRole.Report);
await theStudy.setParticipantObjectIDTypeCode(AuditMessages$ParticipantObjectIDTypeCode.StudyInstanceUID);
await theStudy.setParticipantObjectID(StudyInstanceUIDs[i]);

let theSOPClass = await SOPClass.newInstanceAsync();
await theSOPClass.setUID(SOPClassUIDs[i]);
let theParticipantObjectDescription = await ParticipantObjectDescription.newInstanceAsync();
await (await theParticipantObjectDescription.getSOPClass()).add(theSOPClass);
await theStudy.setParticipantObjectDescription(theParticipantObjectDescription);
theStudies.push(theStudy);
}
// #endregion

// #region Participating Object: Patient (1); 接收到的DICOM,其Patient。
let thePatient = await ParticipantObjectIdentification.newInstanceAsync();
await thePatient.setParticipantObjectTypeCode(AuditMessages$ParticipantObjectTypeCode.Person);
await thePatient.setParticipantObjectTypeCodeRole(AuditMessages$ParticipantObjectTypeCodeRole.Patient);
await thePatient.setParticipantObjectIDTypeCode(AuditMessages$ParticipantObjectIDTypeCode.PatientNumber);
await thePatient.setParticipantObjectID(PatientID);
await thePatient.setParticipantObjectName(PatientName);
// #endregion

// #region 將上述已經記錄完成的Real World Entities,組合成一個Audit Message。
let theActiveParticipants = [client, server];

let ParticipantObjects = [...theStudies, thePatient];
let msg = await AuditMessages.createMessage(theEvent, theActiveParticipants, ParticipantObjects);
// #endregion

return await AuditManager.toJson(msg);
}

/**
* A.X.3.7 DICOM Instances Transferred
* 當有DICOM檔案傳輸時。
* 該事件通常用於 C-STORE、STOW-RS 或是 C-MOVE、WADO。
* @param {"C" | "R" | "U" | "D"} CRUD 該事件是屬於新增、讀取、更新、刪除哪一個? 必須為"C"、"R"、"U"、"D"其中一個,或是使用EventActionCode類別的CRUD(最終也會取得String)。
* @param {"0" | "4" | "8" | "12"} eventResult 該事件最終的結果? 必須為"0"、"4"、"8"、"12"其中一個,分別對應到Success、MinorFailure、SeriousFailure、MajorFailure,或是使用EventOutcomeIndicator類別(最終也會取得String)。
* @param {string} clientAETitle 發送者的AETitle
* @param {string} clientHostname 發送者的位址
* @param {string} serverAETitle 伺服器端的AETitle
* @param {string} serverHostname 伺服器端的位址
* @param {string[]} StudyInstanceUIDs 所有此次傳輸有關聯的StudyInstanceUID
* @param {string[]} SOPClassUIDs 所有此次傳輸有關聯的SOPClassUID
* @param {string} PatientID 一個此次傳輸關聯的PatientID
* @param {string} PatientName 一個此次傳輸關聯的PatientName
*/
async onDicomInstancesTransferred(CRUD, eventResult,
clientAETitle, clientHostname,
serverAETitle, serverHostname,
StudyInstanceUIDs, SOPClassUIDs,
PatientID, PatientName
) {

// #region Event
let theEvent = await EventIdentification.newInstanceAsync();
await theEvent.setEventID(EventID.DICOMInstancesTransferred);
await theEvent.setEventActionCode(CRUD);
await theEvent.setEventDateTime(await Calendar.getInstance()); // 日期時間為當前時間自動取得
await theEvent.setEventOutcomeIndicator(eventResult);
// #endregion

// #region Active Participant: Process that sent the data (1) 發送者(web client 或 STORESCU等...)
let client = await ActiveParticipant.newInstanceAsync();
await client.setUserID(clientAETitle);
await client.setAlternativeUserID(clientAETitle);
await client.setUserName("");
await client.setUserIsRequestor(false);
await (await client.getRoleIDCode()).add(AuditMessages$RoleIDCode.Source);
await client.setNetworkAccessPointTypeCode(AuditMessages$NetworkAccessPointTypeCode.IPAddress);
await client.setNetworkAccessPointID(clientHostname);
// #endregion


// #region Active Participant: The process that received the data. (1) 接收者(Server)
let server = await ActiveParticipant.newInstanceAsync();
await server.setUserID(serverAETitle);
await server.setAlternativeUserID(serverAETitle);
await server.setUserName("");
await server.setUserIsRequestor(false);
await (await server.getRoleIDCode()).add(AuditMessages$RoleIDCode.Destination);
await server.setNetworkAccessPointTypeCode(AuditMessages$NetworkAccessPointTypeCode.IPAddress);
await server.setNetworkAccessPointID(serverHostname);
// #endregion

// #region Participating Object: Studies being transferred (1..N) 接收到的DICOM檔案的所有Study
let theStudies = [];
for (let i = 0; i < SOPClassUIDs.length; i++) {
let theStudy = await ParticipantObjectIdentification.newInstanceAsync();
await theStudy.setParticipantObjectTypeCode(AuditMessages$ParticipantObjectTypeCode.SystemObject);
await theStudy.setParticipantObjectTypeCodeRole(AuditMessages$ParticipantObjectTypeCodeRole.Report);

await theStudy.setParticipantObjectIDTypeCode(AuditMessages$ParticipantObjectIDTypeCode.StudyInstanceUID);
await theStudy.setParticipantObjectID(StudyInstanceUIDs[i]);

let theSOPClass = await SOPClass.newInstanceAsync();
await theSOPClass.setUID(SOPClassUIDs[i]);
let theParticipantObjectDescription = await ParticipantObjectDescription.newInstanceAsync();
await (await theParticipantObjectDescription.getSOPClass()).add(theSOPClass);
await theStudy.setParticipantObjectDescription(theParticipantObjectDescription);
theStudies.push(theStudy);
}
// #endregion

// #region Participating Object: Patient (1); 接收到的DICOM,其Patient。
let thePatient = await ParticipantObjectIdentification.newInstanceAsync();
await thePatient.setParticipantObjectTypeCode(AuditMessages$ParticipantObjectTypeCode.Person);
await thePatient.setParticipantObjectTypeCodeRole(AuditMessages$ParticipantObjectTypeCodeRole.Patient);
await thePatient.setParticipantObjectIDTypeCode(AuditMessages$ParticipantObjectIDTypeCode.PatientNumber);
await thePatient.setParticipantObjectID(PatientID);
await thePatient.setParticipantObjectName(PatientName);
// #endregion

// #region 將上述已經記錄完成的Real World Entities,組合成一個Audit Message。
let theActiveParticipants = [ client, server ];
let ParticipantObjects = [...theStudies, thePatient];
let msg = await AuditMessages.createMessage(theEvent, theActiveParticipants, ParticipantObjects);
// #endregion

return await AuditManager.toJson(msg);
}

static async toJson(msg) {
let msgJsonString = await Common.convertAuditMessageToJsonString(msg);
let msgJson = JSON.parse(msgJsonString);
return msgJson;
}
}

module.exports.AuditManager = AuditManager;
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified models/DICOM/dcm4che/javaNode/dcm4chee/lib/qrscp/dcm777-5.29.2.jar
Binary file not shown.
16 changes: 8 additions & 8 deletions models/DICOM/dcm4che/wrapper/java/io/Reader.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,6 @@ export declare class ReaderClass extends JavaClass {
* @return original return type: 'void'
*/
resetSync(): void;
/**
* @return original return type: 'java.io.Reader'
*/
static nullReader(): Promise<Reader | null>;
/**
* @return original return type: 'java.io.Reader'
*/
static nullReaderSync(): Reader | null;
/**
* @return original return type: 'boolean'
*/
Expand All @@ -122,6 +114,14 @@ export declare class ReaderClass extends JavaClass {
* @return original return type: 'boolean'
*/
readySync(): boolean;
/**
* @return original return type: 'java.io.Reader'
*/
static nullReader(): Promise<Reader | null>;
/**
* @return original return type: 'java.io.Reader'
*/
static nullReaderSync(): Reader | null;
/**
* @param var0 original type: 'long'
* @param var1 original type: 'int'
Expand Down
Loading

0 comments on commit e9c68cc

Please sign in to comment.