Hi All,
I skimmed the dcm4chee arc light source code and thought that there is a problematic issue in class CompressedFramesOutput.java . This class is used in WadoRs.java in the following code snippet
private void writeCompressedFrames(MultipartRelatedOutput output, RetrieveContext ctx, InstanceLocations inst,
int[] frameList, MediaType mediaType, StringBuffer bulkdataURL)
throws IOException {
int length = bulkdataURL.length();
compressedFramesOutput = new CompressedFramesOutput(ctx, inst, frameList, spoolDirectory(frameList));
for (int frame : frameList) {
OutputPart outputPart = output.addPart(compressedFramesOutput, mediaType);
bulkdataURL.setLength(length);
bulkdataURL.append(frame);
outputPart.getHeaders().putSingle("Content-Location", bulkdataURL.toString());
}
}
CompressedFramesOutput will be working if the frameList is passed as : [4, 2, 5].
Let see what happens in the code
@Override
public void write(OutputStream out) throws IOException {
try {
if (frameListIndex == 0)
initDicomInputStream();
if (dis == null) {
Files.copy(spoolFiles[frameListIndex++], out);
return;
}
int nextFrame = frameList[frameListIndex++];
while (frame < nextFrame) {
skipFrame();
frame++;
}
if (!dis.readItemHeader())
throw new IOException(
"Number of data fragments not sufficient for number of frames in requested object");
LOG.debug("Start writing compressed frame of {}", inst);
StreamUtils.copy(dis, out, dis.length());
LOG.debug("Finished writing compressed frame of {}", inst);
frame++;
if (allFramesRead())
close();
} catch (IOException e) {
close();
throw e;
}
}
Let’s simulate the execution:
- Initial State:
- frameList = [4, 2, 5], frame = 1, frameListIndex = 0.
- dis is initialized to read the DICOM file’s pixel data.
- First Call to write:
- nextFrame = frameList[0] = 4.
- frame = 1 < 4, so call skipFrame:
- frame = 1 doesn’t match any frame in frameList[0..2], so skip.
- Increment frame = 2.
- frame = 2 < 4, so call skipFrame:
- frame = 2 matches frameList[1] = 2, so spool frame 2 to spoolFiles[1].
- Increment frame = 3.
- frame = 3 < 4, so call skipFrame:
- frame = 3 doesn’t match any frame, so skip.
- Increment frame = 4.
- frame = 4 == nextFrame, read and write frame 4 to the output stream.
- Increment frame = 5, frameListIndex = 1.
- Second Call to write:
- nextFrame = frameList[1] = 2.
- frame = 5 > 2, so the logic breaks:
- The code doesn’t expect nextFrame to be less than the current frame, as it assumes frameList is sorted.
- It may attempt to skip or read incorrectly, potentially throwing an exception or writing the wrong frame.
- Spooling Issue:
- frame 2 was spooled correctly, but when frameListIndex = 1, the code expects to write frame 2 from the spooled file.
- However, the DICOM stream is already at frame 5, so it can’t go back to process frame 2 without re-reading the file, which the code doesn’t do.
- Result:
- The output may include frame 4, then attempt to write frame 2 from the spooled file, but frame 5 may be missed or written out of order.
- The code may throw an IOException if it tries to read past the available frames or encounters an invalid state.