jdk/src/share/classes/sun/security/ssl/EngineWriter.java
changeset 2 90ce3da70b43
child 5506 202f599c92aa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/security/ssl/EngineWriter.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2003-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.security.ssl;
+
+import javax.net.ssl.*;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import sun.misc.HexDumpEncoder;
+
+/**
+ * A class to help abstract away SSLEngine writing synchronization.
+ */
+final class EngineWriter {
+
+    /*
+     * Outgoing handshake Data waiting for a ride is stored here.
+     * Normal application data is written directly into the outbound
+     * buffer, but handshake data can be written out at any time,
+     * so we have buffer it somewhere.
+     *
+     * When wrap is called, we first check to see if there is
+     * any data waiting, then if we're in a data transfer state,
+     * we try to write app data.
+     *
+     * This will contain either ByteBuffers, or the marker
+     * HandshakeStatus.FINISHED to signify that a handshake just completed.
+     */
+    private LinkedList<Object> outboundList;
+
+    private boolean outboundClosed = false;
+
+    /* Class and subclass dynamic debugging support */
+    private static final Debug debug = Debug.getInstance("ssl");
+
+    EngineWriter() {
+        outboundList = new LinkedList<Object>();
+    }
+
+    /*
+     * Upper levels assured us we had room for at least one packet of data.
+     * As per the SSLEngine spec, we only return one SSL packets worth of
+     * data.
+     */
+    private HandshakeStatus getOutboundData(ByteBuffer dstBB) {
+
+        Object msg = outboundList.removeFirst();
+        assert(msg instanceof ByteBuffer);
+
+        ByteBuffer bbIn = (ByteBuffer) msg;
+        assert(dstBB.remaining() >= bbIn.remaining());
+
+        dstBB.put(bbIn);
+
+        /*
+         * If we have more data in the queue, it's either
+         * a finished message, or an indication that we need
+         * to call wrap again.
+         */
+        if (hasOutboundDataInternal()) {
+            msg = outboundList.getFirst();
+            if (msg == HandshakeStatus.FINISHED) {
+                outboundList.removeFirst();     // consume the message
+                return HandshakeStatus.FINISHED;
+            } else {
+                return HandshakeStatus.NEED_WRAP;
+            }
+        } else {
+            return null;
+        }
+    }
+
+    /*
+     * Properly orders the output of the data written to the wrap call.
+     * This is only handshake data, application data goes through the
+     * other writeRecord.
+     */
+    synchronized void writeRecord(EngineOutputRecord outputRecord,
+            MAC writeMAC, CipherBox writeCipher) throws IOException {
+
+        /*
+         * Only output if we're still open.
+         */
+        if (outboundClosed) {
+            throw new IOException("writer side was already closed.");
+        }
+
+        outputRecord.write(writeMAC, writeCipher);
+
+        /*
+         * Did our handshakers notify that we just sent the
+         * Finished message?
+         *
+         * Add an "I'm finished" message to the queue.
+         */
+        if (outputRecord.isFinishedMsg()) {
+            outboundList.addLast(HandshakeStatus.FINISHED);
+        }
+    }
+
+    /*
+     * Output the packet info.
+     */
+    private void dumpPacket(EngineArgs ea, boolean hsData) {
+        try {
+            HexDumpEncoder hd = new HexDumpEncoder();
+
+            ByteBuffer bb = ea.netData.duplicate();
+
+            int pos = bb.position();
+            bb.position(pos - ea.deltaNet());
+            bb.limit(pos);
+
+            System.out.println("[Raw write" +
+                (hsData ? "" : " (bb)") + "]: length = " +
+                bb.remaining());
+            hd.encodeBuffer(bb, System.out);
+        } catch (IOException e) { }
+    }
+
+    /*
+     * Properly orders the output of the data written to the wrap call.
+     * Only app data goes through here, handshake data goes through
+     * the other writeRecord.
+     *
+     * Shouldn't expect to have an IOException here.
+     *
+     * Return any determined status.
+     */
+    synchronized HandshakeStatus writeRecord(
+            EngineOutputRecord outputRecord, EngineArgs ea, MAC writeMAC,
+            CipherBox writeCipher) throws IOException {
+
+        /*
+         * If we have data ready to go, output this first before
+         * trying to consume app data.
+         */
+        if (hasOutboundDataInternal()) {
+            HandshakeStatus hss = getOutboundData(ea.netData);
+
+            if (debug != null && Debug.isOn("packet")) {
+                /*
+                 * We could have put the dump in
+                 * OutputRecord.write(OutputStream), but let's actually
+                 * output when it's actually output by the SSLEngine.
+                 */
+                dumpPacket(ea, true);
+            }
+
+            return hss;
+        }
+
+        /*
+         * If we are closed, no more app data can be output.
+         * Only existing handshake data (above) can be obtained.
+         */
+        if (outboundClosed) {
+            throw new IOException("The write side was already closed");
+        }
+
+        outputRecord.write(ea, writeMAC, writeCipher);
+
+        if (debug != null && Debug.isOn("packet")) {
+            dumpPacket(ea, false);
+        }
+
+        /*
+         * No way new outbound handshake data got here if we're
+         * locked properly.
+         *
+         * We don't have any status we can return.
+         */
+        return null;
+    }
+
+    /*
+     * We already hold "this" lock, this is the callback from the
+     * outputRecord.write() above.  We already know this
+     * writer can accept more data (outboundClosed == false),
+     * and the closure is sync'd.
+     */
+    void putOutboundData(ByteBuffer bytes) {
+        outboundList.addLast(bytes);
+    }
+
+    /*
+     * This is for the really rare case that someone is writing from
+     * the *InputRecord* before we know what to do with it.
+     */
+    synchronized void putOutboundDataSync(ByteBuffer bytes)
+            throws IOException {
+
+        if (outboundClosed) {
+            throw new IOException("Write side already closed");
+        }
+
+        outboundList.addLast(bytes);
+    }
+
+    /*
+     * Non-synch'd version of this method, called by internals
+     */
+    private boolean hasOutboundDataInternal() {
+        return (outboundList.size() != 0);
+    }
+
+    synchronized boolean hasOutboundData() {
+        return hasOutboundDataInternal();
+    }
+
+    synchronized boolean isOutboundDone() {
+        return outboundClosed && !hasOutboundDataInternal();
+    }
+
+    synchronized void closeOutbound() {
+        outboundClosed = true;
+    }
+
+}