001/* ***** BEGIN LICENSE BLOCK ***** 002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 003 * 004 * The contents of this file are subject to the Mozilla Public License Version 005 * 1.1 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * http://www.mozilla.org/MPL/ 008 * 009 * Software distributed under the License is distributed on an "AS IS" basis, 010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 011 * for the specific language governing rights and limitations under the 012 * License. 013 * 014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 015 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 016 * 017 * The Initial Developer of the Original Code is 018 * Agfa Healthcare. 019 * Portions created by the Initial Developer are Copyright (C) 2011 020 * the Initial Developer. All Rights Reserved. 021 * 022 * Contributor(s): 023 * See @authors listed below 024 * 025 * Alternatively, the contents of this file may be used under the terms of 026 * either the GNU General Public License Version 2 or later (the "GPL"), or 027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 028 * in which case the provisions of the GPL or the LGPL are applicable instead 029 * of those above. If you wish to allow use of your version of this file only 030 * under the terms of either the GPL or the LGPL, and not to allow others to 031 * use your version of this file under the terms of the MPL, indicate your 032 * decision by deleting the provisions above and replace them with the notice 033 * and other provisions required by the GPL or the LGPL. If you do not delete 034 * the provisions above, a recipient may use your version of this file under 035 * the terms of any one of the MPL, the GPL or the LGPL. 036 * 037 * ***** END LICENSE BLOCK ***** */ 038 039package org.dcm4che3.io; 040 041import java.io.BufferedInputStream; 042import java.io.EOFException; 043import java.io.File; 044import java.io.FileInputStream; 045import java.io.FileOutputStream; 046import java.io.FilterInputStream; 047import java.io.IOException; 048import java.io.InputStream; 049import java.io.ObjectInputStream; 050import java.util.ArrayList; 051import java.util.Arrays; 052import java.util.Collections; 053import java.util.List; 054import java.util.zip.Inflater; 055import java.util.zip.InflaterInputStream; 056 057import org.dcm4che3.data.Attributes; 058import org.dcm4che3.data.BulkData; 059import org.dcm4che3.data.DatasetWithFMI; 060import org.dcm4che3.data.ElementDictionary; 061import org.dcm4che3.data.Fragments; 062import org.dcm4che3.data.ItemPointer; 063import org.dcm4che3.data.Sequence; 064import org.dcm4che3.data.Tag; 065import org.dcm4che3.data.UID; 066import org.dcm4che3.data.VR; 067import org.dcm4che3.util.ByteUtils; 068import org.dcm4che3.util.SafeClose; 069import org.dcm4che3.util.StreamUtils; 070import org.dcm4che3.util.TagUtils; 071import org.slf4j.Logger; 072import org.slf4j.LoggerFactory; 073 074/** 075 * @author Gunter Zeilinger <gunterze@gmail.com> 076 */ 077public class DicomInputStream extends FilterInputStream 078 implements DicomInputHandler { 079 080 public enum IncludeBulkData { NO, YES, URI } 081 082 private static final Logger LOG = 083 LoggerFactory.getLogger(DicomInputStream.class); 084 085 private static final String UNEXPECTED_NON_ZERO_ITEM_LENGTH = 086 "Unexpected item value of {} #{} @ {}"; 087 private static final String UNEXPECTED_ATTRIBUTE = 088 "Unexpected attribute {} #{} @ {}"; 089 private static final String MISSING_TRANSFER_SYNTAX = 090 "Missing Transfer Syntax (0002,0010) - assume Explicit VR Little Endian"; 091 private static final String MISSING_FMI_LENGTH = 092 "Missing or wrong File Meta Information Group Length (0002,0000)"; 093 private static final String NOT_A_DICOM_STREAM = 094 "Not a DICOM Stream"; 095 private static final String IMPLICIT_VR_BIG_ENDIAN = 096 "Implicit VR Big Endian encoded DICOM Stream"; 097 private static final String DEFLATED_WITH_ZLIB_HEADER = 098 "Deflated DICOM Stream with ZLIB Header"; 099 100 private static final int ZLIB_HEADER = 0x789c; 101 private static final int DEF_ALLOCATE_LIMIT = 0x4000000; // 64MiB 102 103 private int allocateLimit = DEF_ALLOCATE_LIMIT; 104 private String uri; 105 private String tsuid; 106 private byte[] preamble; 107 private Attributes fileMetaInformation; 108 private boolean hasfmi; 109 private boolean bigEndian; 110 private boolean explicitVR; 111 private IncludeBulkData includeBulkData = IncludeBulkData.YES; 112 private IncludeBulkData includeFragmentBulkData; 113 private long pos; 114 private long fmiEndPos = -1L; 115 private long tagPos; 116 private long markPos; 117 private int tag; 118 private VR vr; 119 private int length; 120 private DicomInputHandler handler = this; 121 private BulkDataDescriptor bulkDataDescriptor = BulkDataDescriptor.DEFAULT; 122 private final byte[] buffer = new byte[12]; 123 private ItemPointer[] itemPointers = {}; 124 private boolean decodeUNWithIVRLE = true; 125 private boolean addBulkDataReferences; 126 127 private boolean catBlkFiles = true; 128 private String blkFilePrefix = "blk"; 129 private String blkFileSuffix; 130 private File blkDirectory; 131 private ArrayList<File> blkFiles; 132 private String blkURI; 133 private FileOutputStream blkOut; 134 private long blkOutPos; 135 136 public DicomInputStream(InputStream in, String tsuid) throws IOException { 137 super(in); 138 switchTransferSyntax(tsuid); 139 } 140 141 public DicomInputStream(InputStream in) throws IOException { 142 super(in.markSupported() ? in : new BufferedInputStream(in)); 143 guessTransferSyntax(); 144 } 145 146 public DicomInputStream(File file) throws IOException { 147 this(new FileInputStream(file)); 148 uri = file.toURI().toString(); 149 } 150 151 public final String getTransferSyntax() { 152 return tsuid; 153 } 154 155 /** 156 * Returns the limit of initial allocated memory for element values. 157 * 158 * By default, the limit is set to 67108864 (64 MiB). 159 * 160 * @return Limit of initial allocated memory for value or -1 for no limit 161 * @see #setAllocateLimit(int) 162 */ 163 public final int getAllocateLimit() { 164 return allocateLimit; 165 } 166 167 /** 168 * Sets the limit of initial allocated memory for element values. If the 169 * value length exceeds the limit, a byte array with the specified size is 170 * allocated. If the array can filled with bytes read from this 171 * <code>DicomInputStream</code>, the byte array is reallocated with 172 * twice the previous length and filled again. That continues until 173 * the twice of the previous length exceeds the actual value length. Then 174 * the byte array is reallocated with actual value length and filled with 175 * the remaining bytes for the value from this <code>DicomInputStream</code>. 176 * 177 * The rational of the incrementing allocation of byte arrays is to avoid 178 * OutOfMemoryErrors on parsing corrupted DICOM streams. 179 * 180 * By default, the limit is set to 67108864 (64 MiB). 181 * 182 * @param allocateLimit limit of initial allocated memory or -1 for no limit 183 * 184 */ 185 public final void setAllocateLimit(int allocateLimit) { 186 this.allocateLimit = allocateLimit; 187 } 188 189 public final String getURI() { 190 return uri; 191 } 192 193 public final void setURI(String uri) { 194 this.uri = uri; 195 } 196 197 public final IncludeBulkData getIncludeBulkData() { 198 return includeBulkData; 199 } 200 201 public final void setIncludeBulkData(IncludeBulkData includeBulkData) { 202 if (includeBulkData == null) 203 throw new NullPointerException(); 204 this.includeBulkData = includeBulkData; 205 } 206 207 public final IncludeBulkData getIncludeFragmentBulkData() { 208 return includeFragmentBulkData; 209 } 210 211 public final BulkDataDescriptor getBulkDataDescriptor() { 212 return bulkDataDescriptor; 213 } 214 215 public final void setBulkDataDescriptor(BulkDataDescriptor bulkDataDescriptor) { 216 this.bulkDataDescriptor = bulkDataDescriptor; 217 } 218 219 public final String getBulkDataFilePrefix() { 220 return blkFilePrefix; 221 } 222 223 public final void setBulkDataFilePrefix(String blkFilePrefix) { 224 this.blkFilePrefix = blkFilePrefix; 225 } 226 227 public final String getBulkDataFileSuffix() { 228 return blkFileSuffix; 229 } 230 231 public final void setBulkDataFileSuffix(String blkFileSuffix) { 232 this.blkFileSuffix = blkFileSuffix; 233 } 234 235 public final File getBulkDataDirectory() { 236 return blkDirectory; 237 } 238 239 public final void setBulkDataDirectory(File blkDirectory) { 240 this.blkDirectory = blkDirectory; 241 } 242 243 public final boolean isConcatenateBulkDataFiles() { 244 return catBlkFiles; 245 } 246 247 public final void setConcatenateBulkDataFiles(boolean catBlkFiles) { 248 this.catBlkFiles = catBlkFiles; 249 } 250 251 public final List<File> getBulkDataFiles() { 252 if (blkFiles != null) 253 return blkFiles; 254 else 255 return Collections.emptyList(); 256 } 257 258 public final void setDicomInputHandler(DicomInputHandler handler) { 259 if (handler == null) 260 throw new NullPointerException("handler"); 261 this.handler = handler; 262 } 263 264 public boolean isDecodeUNWithIVRLE() { 265 return decodeUNWithIVRLE; 266 } 267 268 public void setDecodeUNWithIVRLE(boolean decodeUNWithIVRLE) { 269 this.decodeUNWithIVRLE = decodeUNWithIVRLE; 270 } 271 272 public boolean isAddBulkDataReferences() { 273 return addBulkDataReferences; 274 } 275 276 public void setAddBulkDataReferences(boolean addBulkDataReferences) { 277 this.addBulkDataReferences = addBulkDataReferences; 278 } 279 280 public final void setFileMetaInformationGroupLength(byte[] val) { 281 fmiEndPos = pos + ByteUtils.bytesToInt(val, 0, bigEndian); 282 } 283 284 public final byte[] getPreamble() { 285 return preamble; 286 } 287 288 public Attributes getFileMetaInformation() throws IOException { 289 readFileMetaInformation(); 290 return fileMetaInformation; 291 } 292 293 public final int level() { 294 return itemPointers.length; 295 } 296 297 public final int tag() { 298 return tag; 299 } 300 301 public final VR vr() { 302 return vr; 303 } 304 305 public final int length() { 306 return length; 307 } 308 309 public final long getPosition() { 310 return pos; 311 } 312 313 public void setPosition(long pos) { 314 this.pos = pos; 315 } 316 317 public long getTagPosition() { 318 return tagPos; 319 } 320 321 public final boolean bigEndian() { 322 return bigEndian; 323 } 324 325 public final boolean explicitVR() { 326 return explicitVR; 327 } 328 329 @Override 330 public void close() throws IOException { 331 SafeClose.close(blkOut); 332 super.close(); 333 } 334 335 @Override 336 public synchronized void mark(int readlimit) { 337 super.mark(readlimit); 338 markPos = pos; 339 } 340 341 @Override 342 public synchronized void reset() throws IOException { 343 super.reset(); 344 pos = markPos; 345 } 346 347 @Override 348 public final int read() throws IOException { 349 int read = super.read(); 350 if (read >= 0) 351 pos++; 352 return read; 353 } 354 355 @Override 356 public final int read(byte[] b, int off, int len) throws IOException { 357 int read = super.read(b, off, len); 358 if (read > 0) 359 pos += read; 360 return read; 361 } 362 363 @Override 364 public final int read(byte[] b) throws IOException { 365 return read(b, 0, b.length); 366 } 367 368 @Override 369 public final long skip(long n) throws IOException { 370 long skip = super.skip(n); 371 pos += skip; 372 return skip; 373 } 374 375 public void skipFully(long n) throws IOException { 376 StreamUtils.skipFully(this, n); 377 } 378 379 public void readFully(byte b[]) throws IOException { 380 readFully(b, 0, b.length); 381 } 382 383 public void readFully(byte b[], int off, int len) throws IOException { 384 StreamUtils.readFully(this, b, off, len); 385 } 386 387 public int readHeader() throws IOException { 388 byte[] buf = buffer; 389 tagPos = pos; 390 readFully(buf, 0, 8); 391 switch(tag = ByteUtils.bytesToTag(buf, 0, bigEndian)) { 392 case Tag.Item: 393 case Tag.ItemDelimitationItem: 394 case Tag.SequenceDelimitationItem: 395 vr = null; 396 break; 397 default: 398 if (explicitVR) { 399 vr = VR.valueOf(ByteUtils.bytesToVR(buf, 4)); 400 if (vr.headerLength() == 8) { 401 length = ByteUtils.bytesToUShort(buf, 6, bigEndian); 402 return tag; 403 } 404 readFully(buf, 4, 4); 405 } else { 406 vr = VR.UN; 407 } 408 } 409 length = ByteUtils.bytesToInt(buf, 4, bigEndian); 410 return tag; 411 } 412 413 public Attributes readCommand() throws IOException { 414 if (bigEndian || explicitVR) 415 throw new IllegalStateException( 416 "bigEndian=" + bigEndian + ", explicitVR=" + explicitVR ); 417 Attributes attrs = new Attributes(9); 418 readAttributes(attrs, -1, -1); 419 return attrs; 420 } 421 422 /** 423 * @return file meta information and complete dataset 424 */ 425 public DatasetWithFMI readDatasetWithFMI() throws IOException { 426 return readDatasetWithFMI(-1, -1); 427 } 428 429 /** 430 * @param len maximum length to read in bytes, use -1 for no limit 431 * @param stopTag stop reading at the given Tag, use -1 for no stop tag 432 * 433 * @return file meta information and dataset 434 */ 435 public DatasetWithFMI readDatasetWithFMI(int len, int stopTag) throws IOException { 436 Attributes dataset = readDataset(len, stopTag); 437 return new DatasetWithFMI(getFileMetaInformation(), dataset); 438 } 439 440 public Attributes readDataset(int len, int stopTag) throws IOException { 441 handler.startDataset(this); 442 readFileMetaInformation(); 443 Attributes attrs = new Attributes(bigEndian, 64); 444 readAttributes(attrs, len, stopTag); 445 attrs.trimToSize(); 446 handler.endDataset(this); 447 return attrs; 448 } 449 450 public Attributes readFileMetaInformation() throws IOException { 451 if (!hasfmi) 452 return null; // No File Meta Information 453 if (fileMetaInformation != null) 454 return fileMetaInformation; // already read 455 456 Attributes attrs = new Attributes(bigEndian, 9); 457 while (pos != fmiEndPos) { 458 mark(12); 459 readHeader(); 460 if (TagUtils.groupNumber(tag) != 2) { 461 LOG.warn(MISSING_FMI_LENGTH); 462 reset(); 463 break; 464 } 465 if (vr != null) { 466 if (vr == VR.UN) 467 vr = ElementDictionary.getStandardElementDictionary() 468 .vrOf(tag); 469 handler.readValue(this, attrs); 470 } else 471 skipAttribute(UNEXPECTED_ATTRIBUTE); 472 } 473 fileMetaInformation = attrs; 474 475 String tsuid = attrs.getString(Tag.TransferSyntaxUID, null); 476 if (tsuid == null) { 477 LOG.warn(MISSING_TRANSFER_SYNTAX); 478 tsuid = UID.ExplicitVRLittleEndian; 479 } 480 switchTransferSyntax(tsuid); 481 return attrs; 482 } 483 484 public void readAttributes(Attributes attrs, int len, int stopTag) 485 throws IOException { 486 ItemPointer[] prevItemPointers = itemPointers; 487 itemPointers = attrs.itemPointers(); 488 boolean undeflen = len == -1; 489 boolean hasStopTag = stopTag != -1; 490 long endPos = pos + (len & 0xffffffffL); 491 while (undeflen || this.pos < endPos) { 492 try { 493 readHeader(); 494 } catch (EOFException e) { 495 if (undeflen && pos == tagPos) 496 break; 497 throw e; 498 } 499 if (hasStopTag && tag == stopTag) 500 break; 501 if (vr != null) { 502 boolean prevBigEndian = bigEndian; 503 boolean prevExplicitVR = explicitVR; 504 try { 505 if (vr == VR.UN) { 506 if (decodeUNWithIVRLE) { 507 bigEndian = false; 508 explicitVR = false; 509 } 510 vr = ElementDictionary.vrOf(tag, 511 attrs.getPrivateCreator(tag)); 512 if (vr == VR.UN && length == -1) 513 vr = VR.SQ; // assumes UN with undefined length are SQ, 514 // will fail on UN fragments! 515 } 516 handler.readValue(this, attrs); 517 } finally { 518 bigEndian = prevBigEndian; 519 explicitVR = prevExplicitVR; 520 } 521 } else 522 skipAttribute(UNEXPECTED_ATTRIBUTE); 523 } 524 itemPointers = prevItemPointers; 525 } 526 527 @Override 528 public void readValue(DicomInputStream dis, Attributes attrs) 529 throws IOException { 530 checkIsThis(dis); 531 if (includeBulkData == IncludeBulkData.NO && length != -1 && isBulkData(attrs)) { 532 skipFully(length); 533 } else if (length == 0) { 534 attrs.setNull(tag, vr); 535 } else if (vr == VR.SQ) { 536 readSequence(length, attrs, tag); 537 } else if (length == -1) { 538 readFragments(attrs, tag, vr); 539 } else if (length == BulkData.MAGIC_LEN 540 && super.in instanceof ObjectInputStream) { 541 attrs.setValue(tag, vr, BulkData.deserializeFrom( 542 (ObjectInputStream) super.in)); 543 } else if (includeBulkData == IncludeBulkData.URI && isBulkData(attrs)) { 544 BulkData bulkData = createBulkData(); 545 attrs.setValue(tag, vr, bulkData); 546 if (addBulkDataReferences) { 547 attrs.getRoot().addBulkDataReference( 548 attrs.privateCreatorOf(tag), 549 tag, 550 vr, 551 bulkData, 552 attrs.itemPointers()); 553 } 554 } else { 555 byte[] b = readValue(); 556 if (!TagUtils.isGroupLength(tag)) { 557 if (bigEndian != attrs.bigEndian()) 558 vr.toggleEndian(b, false); 559 attrs.setBytes(tag, vr, b); 560 } else if (tag == Tag.FileMetaInformationGroupLength) 561 setFileMetaInformationGroupLength(b); 562 } 563 } 564 565 public BulkData createBulkData() throws IOException { 566 BulkData bulkData; 567 if (uri != null && !(super.in instanceof InflaterInputStream)) { 568 bulkData = new BulkData(uri, pos, length, bigEndian); 569 skipFully(length); 570 } else { 571 if (blkOut == null) { 572 File blkfile = File.createTempFile(blkFilePrefix, 573 blkFileSuffix, blkDirectory); 574 if (blkFiles == null) 575 blkFiles = new ArrayList<File>(); 576 blkFiles.add(blkfile); 577 blkURI = blkfile.toURI().toString(); 578 blkOut = new FileOutputStream(blkfile); 579 blkOutPos = 0L; 580 } 581 try { 582 StreamUtils.copy(this, blkOut, length); 583 } finally { 584 if (!catBlkFiles) { 585 SafeClose.close(blkOut); 586 blkOut = null; 587 } 588 } 589 bulkData = new BulkData(blkURI, blkOutPos, length, bigEndian); 590 blkOutPos += length; 591 } 592 return bulkData; 593 } 594 595 public boolean isBulkData(Attributes attrs) { 596 return bulkDataDescriptor.isBulkData( 597 attrs.getPrivateCreator(tag), tag, vr, length, itemPointers); 598 } 599 600 @Override 601 public void readValue(DicomInputStream dis, Sequence seq) 602 throws IOException { 603 checkIsThis(dis); 604 if (length == 0) { 605 seq.add(new Attributes(seq.getParent().bigEndian(), 0)); 606 return; 607 } 608 Attributes attrs = new Attributes(seq.getParent().bigEndian()); 609 seq.add(attrs); 610 readAttributes(attrs, length, Tag.ItemDelimitationItem); 611 attrs.trimToSize(); 612 } 613 614 @Override 615 public void readValue(DicomInputStream dis, Fragments frags) 616 throws IOException { 617 checkIsThis(dis); 618 if (includeFragmentBulkData == IncludeBulkData.NO) { 619 skipFully(length); 620 } else if (length == 0) { 621 frags.add(ByteUtils.EMPTY_BYTES); 622 } else if (length == BulkData.MAGIC_LEN 623 && super.in instanceof ObjectInputStream) { 624 frags.add(BulkData.deserializeFrom((ObjectInputStream) super.in)); 625 } else if (includeFragmentBulkData == IncludeBulkData.URI) { 626 frags.add(createBulkData()); 627 } else { 628 byte[] b = readValue(); 629 if (bigEndian != frags.bigEndian()) 630 frags.vr().toggleEndian(b, false); 631 frags.add(b); 632 } 633 } 634 635 @Override 636 public void startDataset(DicomInputStream dis) { 637 } 638 639 @Override 640 public void endDataset(DicomInputStream dis) { 641 } 642 643 private void checkIsThis(DicomInputStream dis) { 644 if (dis != this) 645 throw new IllegalArgumentException("dis != this"); 646 } 647 648 private void skipAttribute(String message) throws IOException { 649 LOG.warn(message, 650 new Object[] { TagUtils.toString(tag), length, tagPos }); 651 skip(length); 652 } 653 654 private void readSequence(int len, Attributes attrs, int sqtag) 655 throws IOException { 656 if (len == 0) { 657 attrs.setNull(sqtag, VR.SQ); 658 return; 659 } 660 Sequence seq = attrs.newSequence(sqtag, 10); 661 String privateCreator = attrs.getPrivateCreator(sqtag); 662 boolean undefLen = len == -1; 663 long endPos = pos + (len & 0xffffffffL); 664 for (int i = 0; undefLen || pos < endPos; ++i) { 665 readHeader(); 666 if (tag == Tag.Item) { 667 handler.readValue(this, seq); 668 } else if (tag == Tag.SequenceDelimitationItem) { 669 if (length != 0) 670 skipAttribute(UNEXPECTED_NON_ZERO_ITEM_LENGTH); 671 break; 672 } else 673 skipAttribute(UNEXPECTED_ATTRIBUTE); 674 } 675 if (seq.isEmpty()) 676 attrs.setNull(sqtag, VR.SQ); 677 else 678 seq.trimToSize(); 679 } 680 681 public Attributes readItem() throws IOException { 682 readHeader(); 683 if (tag != Tag.Item) 684 throw new IOException("Unexpected attribute " 685 + TagUtils.toString(tag) + " #" + length + " @ " + pos); 686 Attributes attrs = new Attributes(bigEndian); 687 attrs.setItemPosition(tagPos); 688 readAttributes(attrs, length, Tag.ItemDelimitationItem); 689 attrs.trimToSize(); 690 return attrs; 691 } 692 693 private void readFragments(Attributes attrs, int fragsTag, VR vr) 694 throws IOException { 695 includeFragmentBulkData = 696 includeBulkData == IncludeBulkData.YES || isBulkData(attrs) 697 ? includeBulkData 698 : IncludeBulkData.YES; 699 700 String privateCreator = attrs.getPrivateCreator(fragsTag); 701 Fragments frags = new Fragments(privateCreator, fragsTag, vr, attrs.bigEndian(), 10); 702 for (int i = 0; true; ++i) { 703 readHeader(); 704 if (tag == Tag.Item) { 705 handler.readValue(this, frags); 706 } else if (tag == Tag.SequenceDelimitationItem) { 707 if (length != 0) 708 skipAttribute(UNEXPECTED_NON_ZERO_ITEM_LENGTH); 709 break; 710 } else 711 skipAttribute(UNEXPECTED_ATTRIBUTE); 712 } 713 if (frags.isEmpty()) 714 attrs.setNull(fragsTag, vr); 715 else { 716 frags.trimToSize(); 717 attrs.setValue(fragsTag, vr, frags); 718 } 719 } 720 721 public byte[] readValue() throws IOException { 722 int valLen = length; 723 try { 724 if (valLen < 0) 725 throw new EOFException(); // assume InputStream length < 2 GiB 726 int allocLen = allocateLimit >= 0 727 ? Math.min(valLen, allocateLimit) 728 : valLen; 729 byte[] value = new byte[allocLen]; 730 readFully(value, 0, allocLen); 731 while (allocLen < valLen) { 732 int newLength = Math.min(valLen, allocLen << 1); 733 value = Arrays.copyOf(value, newLength); 734 readFully(value, allocLen, newLength - allocLen); 735 allocLen = newLength; 736 } 737 return value; 738 } catch (IOException e) { 739 LOG.warn("IOException during read of {} #{} @ {}", 740 TagUtils.toString(tag), length, tagPos, e); 741 throw e; 742 } 743 } 744 745 private void switchTransferSyntax(String tsuid) throws IOException { 746 this.tsuid = tsuid; 747 bigEndian = tsuid.equals(UID.ExplicitVRBigEndianRetired); 748 explicitVR = !tsuid.equals(UID.ImplicitVRLittleEndian); 749 if (tsuid.equals(UID.DeflatedExplicitVRLittleEndian) 750 || tsuid.equals(UID.JPIPReferencedDeflate)) { 751 if (hasZLIBHeader()) { 752 LOG.warn(DEFLATED_WITH_ZLIB_HEADER); 753 super.in = new InflaterInputStream(super.in); 754 } else 755 super.in = new InflaterInputStream(super.in, 756 new Inflater(true)); 757 } 758 } 759 760 private boolean hasZLIBHeader() throws IOException { 761 if (!markSupported()) 762 return false; 763 byte[] buf = buffer; 764 mark(2); 765 read(buf, 0, 2); 766 reset(); 767 return ByteUtils.bytesToUShortBE(buf, 0) == ZLIB_HEADER; 768 } 769 770 private void guessTransferSyntax() throws IOException { 771 byte[] b128 = new byte[128]; 772 byte[] buf = buffer; 773 mark(132); 774 int rlen = read(b128); 775 if (rlen == 128) { 776 read(buf, 0, 4); 777 if (buf[0] == 'D' && buf[1] == 'I' 778 && buf[2] == 'C' && buf[3] == 'M') { 779 preamble = b128.clone(); 780 if (!markSupported()) { 781 hasfmi = true; 782 tsuid = UID.ExplicitVRLittleEndian; 783 bigEndian = false; 784 explicitVR = true; 785 return; 786 } 787 mark(128); 788 read(b128); 789 } 790 } 791 if (rlen < 8 792 || !guessTransferSyntax(b128, rlen, false) 793 && !guessTransferSyntax(b128, rlen, true)) 794 throw new DicomStreamException(NOT_A_DICOM_STREAM); 795 reset(); 796 hasfmi = TagUtils.isFileMetaInformation( 797 ByteUtils.bytesToTag(b128, 0, bigEndian)); 798 } 799 800 private boolean guessTransferSyntax(byte[] b128, int rlen, boolean bigEndian) 801 throws DicomStreamException { 802 int tag1 = ByteUtils.bytesToTag(b128, 0, bigEndian); 803 VR vr = ElementDictionary.vrOf(tag1, null); 804 if (vr == VR.UN) 805 return false; 806 if (ByteUtils.bytesToVR(b128, 4) == vr.code()) { 807 this.tsuid = bigEndian ? UID.ExplicitVRBigEndianRetired 808 : UID.ExplicitVRLittleEndian; 809 this.bigEndian = bigEndian; 810 this.explicitVR = true; 811 return true; 812 } 813 int len = ByteUtils.bytesToInt(b128, 4, bigEndian); 814 if (len < 0 || 8 + len > rlen) 815 return false; 816 817 if (bigEndian) 818 throw new DicomStreamException(IMPLICIT_VR_BIG_ENDIAN); 819 820 this.tsuid = UID.ImplicitVRLittleEndian; 821 this.bigEndian = false; 822 this.explicitVR = false; 823 return true; 824 } 825}