/*
 * Copyright (c) 2003, 2007, Oracle and/or its affiliates. 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.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.
/** * 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; } }