1 /*
   2  * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.ssl;
  27 
  28 import java.io.IOException;
  29 import java.nio.ByteBuffer;
  30 import java.util.LinkedList;
  31 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
  32 import sun.misc.HexDumpEncoder;
  33 
  34 /**
  35  * A class to help abstract away SSLEngine writing synchronization.
  36  */
  37 final class EngineWriter {
  38 
  39     /*
  40      * Outgoing handshake Data waiting for a ride is stored here.
  41      * Normal application data is written directly into the outbound
  42      * buffer, but handshake data can be written out at any time,
  43      * so we have buffer it somewhere.
  44      *
  45      * When wrap is called, we first check to see if there is
  46      * any data waiting, then if we're in a data transfer state,
  47      * we try to write app data.
  48      *
  49      * This will contain either ByteBuffers, or the marker
  50      * HandshakeStatus.FINISHED to signify that a handshake just completed.
  51      */
  52     private LinkedList<Object> outboundList;
  53 
  54     private boolean outboundClosed = false;
  55 
  56     /* Class and subclass dynamic debugging support */
  57     private static final Debug debug = Debug.getInstance("ssl");
  58 
  59     EngineWriter() {
  60         outboundList = new LinkedList<Object>();
  61     }
  62 
  63     /*
  64      * Upper levels assured us we had room for at least one packet of data.
  65      * As per the SSLEngine spec, we only return one SSL packets worth of
  66      * data.
  67      */
  68     private HandshakeStatus getOutboundData(ByteBuffer dstBB) {
  69 
  70         Object msg = outboundList.removeFirst();
  71         assert(msg instanceof ByteBuffer);
  72 
  73         ByteBuffer bbIn = (ByteBuffer) msg;
  74         assert(dstBB.remaining() >= bbIn.remaining());
  75 
  76         dstBB.put(bbIn);
  77 
  78         /*
  79          * If we have more data in the queue, it's either
  80          * a finished message, or an indication that we need
  81          * to call wrap again.
  82          */
  83         if (hasOutboundDataInternal()) {
  84             msg = outboundList.getFirst();
  85             if (msg == HandshakeStatus.FINISHED) {
  86                 outboundList.removeFirst();     // consume the message
  87                 return HandshakeStatus.FINISHED;
  88             } else {
  89                 return HandshakeStatus.NEED_WRAP;
  90             }
  91         } else {
  92             return null;
  93         }
  94     }
  95 
  96     /*
  97      * Properly orders the output of the data written to the wrap call.
  98      * This is only handshake data, application data goes through the
  99      * other writeRecord.
 100      */
 101     synchronized void writeRecord(EngineOutputRecord outputRecord,
 102             Authenticator authenticator,
 103             CipherBox writeCipher) throws IOException {
 104 
 105         /*
 106          * Only output if we're still open.
 107          */
 108         if (outboundClosed) {
 109             throw new IOException("writer side was already closed.");
 110         }
 111 
 112         outputRecord.write(authenticator, writeCipher);
 113 
 114         /*
 115          * Did our handshakers notify that we just sent the
 116          * Finished message?
 117          *
 118          * Add an "I'm finished" message to the queue.
 119          */
 120         if (outputRecord.isFinishedMsg()) {
 121             outboundList.addLast(HandshakeStatus.FINISHED);
 122         }
 123     }
 124 
 125     /*
 126      * Output the packet info.
 127      */
 128     private void dumpPacket(EngineArgs ea, boolean hsData) {
 129         try {
 130             HexDumpEncoder hd = new HexDumpEncoder();
 131 
 132             ByteBuffer bb = ea.netData.duplicate();
 133 
 134             int pos = bb.position();
 135             bb.position(pos - ea.deltaNet());
 136             bb.limit(pos);
 137 
 138             System.out.println("[Raw write" +
 139                 (hsData ? "" : " (bb)") + "]: length = " +
 140                 bb.remaining());
 141             hd.encodeBuffer(bb, System.out);
 142         } catch (IOException e) { }
 143     }
 144 
 145     /*
 146      * Properly orders the output of the data written to the wrap call.
 147      * Only app data goes through here, handshake data goes through
 148      * the other writeRecord.
 149      *
 150      * Shouldn't expect to have an IOException here.
 151      *
 152      * Return any determined status.
 153      */
 154     synchronized HandshakeStatus writeRecord(
 155             EngineOutputRecord outputRecord, EngineArgs ea,
 156             Authenticator authenticator,
 157             CipherBox writeCipher) throws IOException {
 158 
 159         /*
 160          * If we have data ready to go, output this first before
 161          * trying to consume app data.
 162          */
 163         if (hasOutboundDataInternal()) {
 164             HandshakeStatus hss = getOutboundData(ea.netData);
 165 
 166             if (debug != null && Debug.isOn("packet")) {
 167                 /*
 168                  * We could have put the dump in
 169                  * OutputRecord.write(OutputStream), but let's actually
 170                  * output when it's actually output by the SSLEngine.
 171                  */
 172                 dumpPacket(ea, true);
 173             }
 174 
 175             return hss;
 176         }
 177 
 178         /*
 179          * If we are closed, no more app data can be output.
 180          * Only existing handshake data (above) can be obtained.
 181          */
 182         if (outboundClosed) {
 183             throw new IOException("The write side was already closed");
 184         }
 185 
 186         outputRecord.write(ea, authenticator, writeCipher);
 187 
 188         if (debug != null && Debug.isOn("packet")) {
 189             dumpPacket(ea, false);
 190         }
 191 
 192         /*
 193          * No way new outbound handshake data got here if we're
 194          * locked properly.
 195          *
 196          * We don't have any status we can return.
 197          */
 198         return null;
 199     }
 200 
 201     /*
 202      * We already hold "this" lock, this is the callback from the
 203      * outputRecord.write() above.  We already know this
 204      * writer can accept more data (outboundClosed == false),
 205      * and the closure is sync'd.
 206      */
 207     void putOutboundData(ByteBuffer bytes) {
 208         outboundList.addLast(bytes);
 209     }
 210 
 211     /*
 212      * This is for the really rare case that someone is writing from
 213      * the *InputRecord* before we know what to do with it.
 214      */
 215     synchronized void putOutboundDataSync(ByteBuffer bytes)
 216             throws IOException {
 217 
 218         if (outboundClosed) {
 219             throw new IOException("Write side already closed");
 220         }
 221 
 222         outboundList.addLast(bytes);
 223     }
 224 
 225     /*
 226      * Non-synch'd version of this method, called by internals
 227      */
 228     private boolean hasOutboundDataInternal() {
 229         return (outboundList.size() != 0);
 230     }
 231 
 232     synchronized boolean hasOutboundData() {
 233         return hasOutboundDataInternal();
 234     }
 235 
 236     synchronized boolean isOutboundDone() {
 237         return outboundClosed && !hasOutboundDataInternal();
 238     }
 239 
 240     synchronized void closeOutbound() {
 241         outboundClosed = true;
 242     }
 243 
 244 }