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}