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.net; 040 041import java.io.BufferedOutputStream; 042import java.io.EOFException; 043import java.io.IOException; 044import java.io.InputStream; 045import java.io.OutputStream; 046 047import org.dcm4che3.data.Tag; 048import org.dcm4che3.data.UID; 049import org.dcm4che3.data.Attributes; 050import org.dcm4che3.io.DicomOutputStream; 051import org.dcm4che3.net.pdu.AAbort; 052import org.dcm4che3.net.pdu.AAssociateAC; 053import org.dcm4che3.net.pdu.AAssociateRJ; 054import org.dcm4che3.net.pdu.AAssociateRQ; 055import org.dcm4che3.net.pdu.AAssociateRQAC; 056import org.dcm4che3.net.pdu.CommonExtendedNegotiation; 057import org.dcm4che3.net.pdu.ExtendedNegotiation; 058import org.dcm4che3.net.pdu.PresentationContext; 059import org.dcm4che3.net.pdu.RoleSelection; 060import org.dcm4che3.net.pdu.UserIdentityAC; 061import org.dcm4che3.net.pdu.UserIdentityRQ; 062 063/** 064 * @author Gunter Zeilinger <gunterze@gmail.com> 065 * 066 */ 067class PDUEncoder extends PDVOutputStream { 068 069 private Association as; 070 private OutputStream out; 071 private byte[] buf = new byte[Connection.DEF_MAX_PDU_LENGTH + 6]; 072 private int pos; 073 private int pdvpcid; 074 private int pdvcmd; 075 private int pdvpos; 076 private int maxpdulen; 077 private Thread th; 078 private Object dimseLock = new Object(); 079 080 public PDUEncoder(Association as, OutputStream out) { 081 this.as = as; 082 this.out = (out instanceof BufferedOutputStream) ? out : new BufferedOutputStream(out); 083 } 084 085 public void write(AAssociateRQ rq) throws IOException { 086 encode(rq, PDUType.A_ASSOCIATE_RQ, ItemType.RQ_PRES_CONTEXT); 087 writePDU(pos - 6); 088 } 089 090 public void write(AAssociateAC ac) throws IOException { 091 encode(ac, PDUType.A_ASSOCIATE_AC, ItemType.AC_PRES_CONTEXT); 092 writePDU(pos - 6); 093 } 094 095 public void write(AAssociateRJ rj) throws IOException { 096 write(PDUType.A_ASSOCIATE_RJ, rj.getResult(), rj.getSource(), 097 rj.getReason()); 098 } 099 100 public void writeAReleaseRQ() throws IOException { 101 write(PDUType.A_RELEASE_RQ, 0, 0, 0); 102 } 103 104 public void writeAReleaseRP() throws IOException { 105 write(PDUType.A_RELEASE_RP, 0, 0, 0); 106 } 107 108 public void write(AAbort aa) throws IOException { 109 write(PDUType.A_ABORT, 0, aa.getSource(), aa.getReason()); 110 } 111 112 private synchronized void write(int pdutype, int result, int source, 113 int reason) throws IOException { 114 byte[] b = { 115 (byte) pdutype, 116 0, 117 0, 0, 0, 4, // pdulen 118 0, 119 (byte) result, 120 (byte) source, 121 (byte) reason 122 }; 123 out.write(b); 124 out.flush(); 125 } 126 127 private synchronized void writePDU(int pdulen) throws IOException { 128 try { 129 out.write(buf, 0, 6 + pdulen); 130 out.flush(); 131 } catch (IOException e) { 132 as.onIOException(e); 133 throw e; 134 } 135 pdvpos = 6; 136 pos = 12; 137 } 138 139 private void encode(AAssociateRQAC rqac, int pduType, int pcItemType) { 140 rqac.checkCallingAET(); 141 rqac.checkCalledAET(); 142 143 int pdulen = rqac.length(); 144 if (buf.length < 6 + pdulen) 145 buf = new byte[6 + pdulen]; 146 pos = 0; 147 put(pduType); 148 put(0); 149 putInt(pdulen); 150 putShort(rqac.getProtocolVersion()); 151 put(0); 152 put(0); 153 encodeAET(rqac.getCalledAET()); 154 encodeAET(rqac.getCallingAET()); 155 put(rqac.getReservedBytes(), 0, 32); 156 encodeStringItem(ItemType.APP_CONTEXT, rqac.getApplicationContext()); 157 for (PresentationContext pc : rqac.getPresentationContexts()) 158 encode(pc, pcItemType); 159 encodeUserInfo(rqac); 160 } 161 162 private void put(int ch) { 163 buf[pos++] = (byte) ch; 164 } 165 166 private void put(byte[] b) { 167 put(b, 0, b.length); 168 } 169 170 private void put(byte[] b, int off, int len) { 171 System.arraycopy(b, off, buf, pos, len); 172 pos += len; 173 } 174 175 private void putShort(int v) { 176 buf[pos++] = (byte) (v >> 8); 177 buf[pos++] = (byte) v; 178 } 179 180 private void putInt(int v) { 181 buf[pos++] = (byte) (v >> 24); 182 buf[pos++] = (byte) (v >> 16); 183 buf[pos++] = (byte) (v >> 8); 184 buf[pos++] = (byte) v; 185 } 186 187 @SuppressWarnings("deprecation") 188 private void putString(String s) { 189 int len = s.length(); 190 s.getBytes(0, len, buf, pos); 191 pos += len; 192 } 193 194 private void encode(byte[] b) { 195 putShort(b.length); 196 put(b, 0, b.length); 197 } 198 199 private void encode(String s) { 200 putShort(s.length()); 201 putString(s); 202 } 203 204 private void encodeAET(String aet) { 205 int endpos = pos + 16; 206 putString(aet); 207 while (pos < endpos) 208 put(0x20); 209 } 210 211 private void encodeItemHeader(int type, int len) { 212 put(type); 213 put(0); 214 putShort(len); 215 } 216 217 private void encodeStringItem(int type, String s) { 218 if (s == null) 219 return; 220 221 encodeItemHeader(type, s.length()); 222 putString(s); 223 } 224 225 private void encode(PresentationContext pc, int pcItemType) { 226 encodeItemHeader(pcItemType, pc.length()); 227 put(pc.getPCID()); 228 put(0); 229 put(pc.getResult()); 230 put(0); 231 encodeStringItem(ItemType.ABSTRACT_SYNTAX, pc.getAbstractSyntax()); 232 for (String ts : pc.getTransferSyntaxes()) 233 encodeStringItem(ItemType.TRANSFER_SYNTAX, ts); 234 } 235 236 private void encodeUserInfo(AAssociateRQAC rqac) { 237 encodeItemHeader(ItemType.USER_INFO, rqac.userInfoLength()); 238 encodeMaxPDULength(rqac.getMaxPDULength()); 239 encodeStringItem(ItemType.IMPL_CLASS_UID, rqac.getImplClassUID()); 240 if (rqac.isAsyncOps()) 241 encodeAsyncOpsWindow(rqac); 242 for (RoleSelection rs : rqac.getRoleSelections()) 243 encode(rs); 244 encodeStringItem(ItemType.IMPL_VERSION_NAME, rqac.getImplVersionName()); 245 for (ExtendedNegotiation extNeg : rqac.getExtendedNegotiations()) 246 encode(extNeg); 247 for (CommonExtendedNegotiation extNeg : 248 rqac.getCommonExtendedNegotiations()) 249 encode(extNeg); 250 encode(rqac.getUserIdentityRQ()); 251 encode(rqac.getUserIdentityAC()); 252 } 253 254 private void encodeMaxPDULength(int maxPDULength) { 255 encodeItemHeader(ItemType.MAX_PDU_LENGTH, 4); 256 putInt(maxPDULength); 257 } 258 259 private void encodeAsyncOpsWindow(AAssociateRQAC rqac) { 260 encodeItemHeader(ItemType.ASYNC_OPS_WINDOW, 4); 261 putShort(rqac.getMaxOpsInvoked()); 262 putShort(rqac.getMaxOpsPerformed()); 263 } 264 265 private void encode(RoleSelection rs) { 266 encodeItemHeader(ItemType.ROLE_SELECTION, rs.length()); 267 encode(rs.getSOPClassUID()); 268 put(rs.isSCU() ? 1 : 0); 269 put(rs.isSCP() ? 1 : 0); 270 } 271 272 private void encode(ExtendedNegotiation extNeg) { 273 encodeItemHeader(ItemType.EXT_NEG, extNeg.length()); 274 encode(extNeg.getSOPClassUID()); 275 put(extNeg.getInformation()); 276 } 277 278 private void encode(CommonExtendedNegotiation extNeg) { 279 encodeItemHeader(ItemType.COMMON_EXT_NEG, extNeg.length()); 280 encode(extNeg.getSOPClassUID()); 281 encode(extNeg.getServiceClassUID()); 282 putShort(extNeg.getRelatedGeneralSOPClassUIDsLength()); 283 for (String cuid : extNeg.getRelatedGeneralSOPClassUIDs()) 284 encode(cuid); 285 } 286 287 private void encode(UserIdentityRQ userIdentity) { 288 if (userIdentity == null) 289 return; 290 291 encodeItemHeader(ItemType.RQ_USER_IDENTITY, userIdentity.length()); 292 put(userIdentity.getType()); 293 put(userIdentity.isPositiveResponseRequested() ? 1 : 0); 294 encode(userIdentity.getPrimaryField()); 295 encode(userIdentity.getSecondaryField()); 296 } 297 298 private void encode(UserIdentityAC userIdentity) { 299 if (userIdentity == null) 300 return; 301 302 encodeItemHeader(ItemType.AC_USER_IDENTITY, userIdentity.length()); 303 encode(userIdentity.getServerResponse()); 304 } 305 306 @Override 307 public void write(int b) throws IOException { 308 checkThread(); 309 flushPDataTF(); 310 put(b); 311 } 312 313 @Override 314 public void write(byte[] b, int off, int len) throws IOException { 315 checkThread(); 316 int pos = off; 317 int remaining = len; 318 while (remaining > 0) { 319 flushPDataTF(); 320 int write = Math.min(remaining, free()); 321 put(b, pos, write); 322 pos += write; 323 remaining -= write; 324 } 325 } 326 327 @Override 328 public void close() { 329 checkThread(); 330 encodePDVHeader(PDVType.LAST); 331 } 332 333 @Override 334 public void copyFrom(InputStream in, int len) throws IOException { 335 checkThread(); 336 int remaining = len; 337 while (remaining > 0) { 338 flushPDataTF(); 339 int copy = in.read(buf, pos, Math.min(remaining, free())); 340 if (copy == -1) 341 throw new EOFException(); 342 pos += copy; 343 remaining -= copy; 344 } 345 } 346 347 @Override 348 public void copyFrom(InputStream in) throws IOException { 349 checkThread(); 350 for (;;) { 351 flushPDataTF(); 352 int copy = in.read(buf, pos, free()); 353 if (copy == -1) 354 return; 355 pos += copy; 356 } 357 } 358 359 private void checkThread() { 360 if (th != Thread.currentThread()) 361 throw new IllegalStateException("Entered by wrong thread"); 362 } 363 364 private int free() { 365 return maxpdulen + 6 - pos; 366 } 367 368 private void flushPDataTF() throws IOException { 369 if (free() > 0) 370 return; 371 encodePDVHeader(PDVType.PENDING); 372 as.writePDataTF(); 373 } 374 375 private void encodePDVHeader(int last) { 376 final int endpos = pos; 377 final int pdvlen = endpos - pdvpos - 4; 378 pos = pdvpos; 379 putInt(pdvlen); 380 put(pdvpcid); 381 put(pdvcmd | last); 382 pos = endpos; 383 Association.LOG.trace("{} << PDV[len={}, pcid={}, mch={}]", 384 new Object[] { as, pdvlen, pdvpcid, (pdvcmd | last) }); 385 } 386 387 public void writePDataTF() throws IOException { 388 int pdulen = pos - 6; 389 pos = 0; 390 put(PDUType.P_DATA_TF); 391 put(0); 392 putInt(pdulen); 393 Association.LOG.trace("{} << P-DATA-TF[len={}]", 394 new Object[] { as, pdulen }); 395 writePDU(pdulen); 396 } 397 398 public void writeDIMSE(PresentationContext pc, Attributes cmd, 399 DataWriter dataWriter) throws IOException { 400 synchronized (dimseLock) { 401 int pcid = pc.getPCID(); 402 String tsuid = pc.getTransferSyntax(); 403 if (Dimse.LOG.isInfoEnabled()) { 404 Dimse dimse = Dimse.valueOf(cmd.getInt(Tag.CommandField, -1)); 405 Dimse.LOG.info("{} << {}", as, dimse.toString(cmd, pcid, tsuid)); 406 Dimse.LOG.debug("Command:\n{}", cmd); 407 if (dataWriter instanceof DataWriterAdapter) 408 Dimse.LOG.debug("Dataset:\n{}", 409 ((DataWriterAdapter) dataWriter).getDataset()); 410 } 411 this.th = Thread.currentThread(); 412 maxpdulen = as.getMaxPDULengthSend(); 413 if (buf.length < maxpdulen + 6) 414 buf = new byte[maxpdulen + 6]; 415 416 pdvpcid = pcid; 417 pdvcmd = PDVType.COMMAND; 418 DicomOutputStream cmdout = 419 new DicomOutputStream(this, UID.ImplicitVRLittleEndian); 420 cmdout.writeCommand(cmd); 421 cmdout.close(); 422 if (dataWriter != null) { 423 if (!as.isPackPDV()) { 424 as.writePDataTF(); 425 } else { 426 pdvpos = pos; 427 pos += 6; 428 } 429 pdvcmd = PDVType.DATA; 430 dataWriter.writeTo(this, tsuid); 431 close(); 432 } 433 as.writePDataTF(); 434 this.th = null; 435 } 436 } 437}