| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
 | 
 | 
package sun.security.ssl;  | 
 | 
 | 
 | 
import java.io.IOException;  | 
 | 
import java.nio.ByteBuffer;  | 
 | 
import java.util.LinkedList;  | 
 | 
import javax.net.ssl.SSLHandshakeException;  | 
 | 
 | 
 | 
import sun.security.ssl.SSLCipher.SSLWriteCipher;  | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { | 
 | 
 | 
 | 
    private HandshakeFragment fragmenter = null;  | 
 | 
    private boolean isTalkingToV2 = false;        | 
 | 
    private ByteBuffer v2ClientHello = null;      | 
 | 
 | 
 | 
    private volatile boolean isCloseWaiting = false;  | 
 | 
 | 
 | 
    SSLEngineOutputRecord(HandshakeHash handshakeHash) { | 
 | 
        super(handshakeHash, SSLWriteCipher.nullTlsWriteCipher());  | 
 | 
 | 
 | 
        this.packetSize = SSLRecord.maxRecordSize;  | 
 | 
        this.protocolVersion = ProtocolVersion.NONE;  | 
 | 
    }  | 
 | 
 | 
 | 
    @Override  | 
 | 
    public synchronized void close() throws IOException { | 
 | 
        if (!isClosed) { | 
 | 
            if (fragmenter != null && fragmenter.hasAlert()) { | 
 | 
                isCloseWaiting = true;  | 
 | 
            } else { | 
 | 
                super.close();  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    boolean isClosed() { | 
 | 
        return isClosed || isCloseWaiting;  | 
 | 
    }  | 
 | 
 | 
 | 
    @Override  | 
 | 
    void encodeAlert(byte level, byte description) throws IOException { | 
 | 
        if (isClosed()) { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { | 
 | 
                SSLLogger.warning("outbound has closed, ignore outbound " + | 
 | 
                    "alert message: " + Alert.nameOf(description));  | 
 | 
            }  | 
 | 
            return;  | 
 | 
        }  | 
 | 
 | 
 | 
        if (fragmenter == null) { | 
 | 
           fragmenter = new HandshakeFragment();  | 
 | 
        }  | 
 | 
 | 
 | 
        fragmenter.queueUpAlert(level, description);  | 
 | 
    }  | 
 | 
 | 
 | 
    @Override  | 
 | 
    void encodeHandshake(byte[] source,  | 
 | 
            int offset, int length) throws IOException { | 
 | 
        if (isClosed()) { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { | 
 | 
                SSLLogger.warning("outbound has closed, ignore outbound " + | 
 | 
                        "handshake message",  | 
 | 
                        ByteBuffer.wrap(source, offset, length));  | 
 | 
            }  | 
 | 
            return;  | 
 | 
        }  | 
 | 
 | 
 | 
        if (fragmenter == null) { | 
 | 
           fragmenter = new HandshakeFragment();  | 
 | 
        }  | 
 | 
 | 
 | 
        if (firstMessage) { | 
 | 
            firstMessage = false;  | 
 | 
 | 
 | 
            if ((helloVersion == ProtocolVersion.SSL20Hello) &&  | 
 | 
                (source[offset] == SSLHandshake.CLIENT_HELLO.id) &&  | 
 | 
                                              | 
 | 
                (source[offset + 4 + 2 + 32] == 0)) { | 
 | 
                                            // V3 session ID is empty  | 
 | 
                                            //  4: handshake header size  | 
 | 
                                            //  2: client_version in ClientHello  | 
 | 
                                            // 32: random in ClientHello  | 
 | 
 | 
 | 
                  | 
 | 
                v2ClientHello = encodeV2ClientHello(  | 
 | 
                        source, (offset + 4), (length - 4));  | 
 | 
 | 
 | 
                v2ClientHello.position(2);       | 
 | 
                handshakeHash.deliver(v2ClientHello);  | 
 | 
                v2ClientHello.position(0);  | 
 | 
 | 
 | 
                return;  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        byte handshakeType = source[offset];  | 
 | 
        if (handshakeHash.isHashable(handshakeType)) { | 
 | 
            handshakeHash.deliver(source, offset, length);  | 
 | 
        }  | 
 | 
 | 
 | 
        fragmenter.queueUpFragment(source, offset, length);  | 
 | 
    }  | 
 | 
 | 
 | 
    @Override  | 
 | 
    void encodeChangeCipherSpec() throws IOException { | 
 | 
        if (isClosed()) { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { | 
 | 
                SSLLogger.warning("outbound has closed, ignore outbound " + | 
 | 
                    "change_cipher_spec message");  | 
 | 
            }  | 
 | 
            return;  | 
 | 
        }  | 
 | 
 | 
 | 
        if (fragmenter == null) { | 
 | 
           fragmenter = new HandshakeFragment();  | 
 | 
        }  | 
 | 
        fragmenter.queueUpChangeCipherSpec();  | 
 | 
    }  | 
 | 
 | 
 | 
    @Override  | 
 | 
    void encodeV2NoCipher() throws IOException { | 
 | 
        isTalkingToV2 = true;  | 
 | 
    }  | 
 | 
 | 
 | 
    @Override  | 
 | 
    Ciphertext encode(  | 
 | 
        ByteBuffer[] srcs, int srcsOffset, int srcsLength,  | 
 | 
        ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { | 
 | 
 | 
 | 
        if (isClosed) { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { | 
 | 
                SSLLogger.warning("outbound has closed, ignore outbound " + | 
 | 
                    "application data or cached messages");  | 
 | 
            }  | 
 | 
 | 
 | 
            return null;  | 
 | 
        } else if (isCloseWaiting) { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { | 
 | 
                SSLLogger.warning("outbound has closed, ignore outbound " + | 
 | 
                    "application data");  | 
 | 
            }  | 
 | 
 | 
 | 
            srcs = null;      | 
 | 
        }  | 
 | 
 | 
 | 
        return encode(srcs, srcsOffset, srcsLength, dsts[0]);  | 
 | 
    }  | 
 | 
 | 
 | 
    private Ciphertext encode(ByteBuffer[] sources, int offset, int length,  | 
 | 
            ByteBuffer destination) throws IOException { | 
 | 
 | 
 | 
        if (writeCipher.authenticator.seqNumOverflow()) { | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { | 
 | 
                SSLLogger.fine(  | 
 | 
                    "sequence number extremely close to overflow " +  | 
 | 
                    "(2^64-1 packets). Closing connection.");  | 
 | 
            }  | 
 | 
 | 
 | 
            throw new SSLHandshakeException("sequence number overflow"); | 
 | 
        }  | 
 | 
 | 
 | 
        // Don't process the incoming record until all of the  | 
 | 
          | 
 | 
        Ciphertext ct = acquireCiphertext(destination);  | 
 | 
        if (ct != null) { | 
 | 
            return ct;  | 
 | 
        }  | 
 | 
 | 
 | 
        if (sources == null || sources.length == 0) { | 
 | 
            return null;  | 
 | 
        }  | 
 | 
 | 
 | 
        int srcsRemains = 0;  | 
 | 
        for (int i = offset; i < offset + length; i++) { | 
 | 
            srcsRemains += sources[i].remaining();  | 
 | 
        }  | 
 | 
 | 
 | 
        if (srcsRemains == 0) { | 
 | 
            return null;  | 
 | 
        }  | 
 | 
 | 
 | 
        int dstLim = destination.limit();  | 
 | 
        boolean isFirstRecordOfThePayload = true;  | 
 | 
        int packetLeftSize = Math.min(maxRecordSize, packetSize);  | 
 | 
        boolean needMorePayload = true;  | 
 | 
        long recordSN = 0L;  | 
 | 
        while (needMorePayload) { | 
 | 
            int fragLen;  | 
 | 
            if (isFirstRecordOfThePayload && needToSplitPayload()) { | 
 | 
                needMorePayload = true;  | 
 | 
 | 
 | 
                fragLen = 1;  | 
 | 
                isFirstRecordOfThePayload = false;  | 
 | 
            } else { | 
 | 
                needMorePayload = false;  | 
 | 
 | 
 | 
                if (packetLeftSize > 0) { | 
 | 
                    fragLen = writeCipher.calculateFragmentSize(  | 
 | 
                            packetLeftSize, headerSize);  | 
 | 
 | 
 | 
                    fragLen = Math.min(fragLen, Record.maxDataSize);  | 
 | 
                } else { | 
 | 
                    fragLen = Record.maxDataSize;  | 
 | 
                }  | 
 | 
 | 
 | 
                if (fragmentSize > 0) { | 
 | 
                    fragLen = Math.min(fragLen, fragmentSize);  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            int dstPos = destination.position();  | 
 | 
            int dstContent = dstPos + headerSize +  | 
 | 
                                writeCipher.getExplicitNonceSize();  | 
 | 
            destination.position(dstContent);  | 
 | 
 | 
 | 
            int remains = Math.min(fragLen, destination.remaining());  | 
 | 
            fragLen = 0;  | 
 | 
            int srcsLen = offset + length;  | 
 | 
            for (int i = offset; (i < srcsLen) && (remains > 0); i++) { | 
 | 
                int amount = Math.min(sources[i].remaining(), remains);  | 
 | 
                int srcLimit = sources[i].limit();  | 
 | 
                sources[i].limit(sources[i].position() + amount);  | 
 | 
                destination.put(sources[i]);  | 
 | 
                sources[i].limit(srcLimit);           | 
 | 
                remains -= amount;  | 
 | 
                fragLen += amount;  | 
 | 
 | 
 | 
                if (remains > 0) { | 
 | 
                    offset++;  | 
 | 
                    length--;  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            destination.limit(destination.position());  | 
 | 
            destination.position(dstContent);  | 
 | 
 | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("record")) { | 
 | 
                SSLLogger.fine(  | 
 | 
                        "WRITE: " + protocolVersion + " " +  | 
 | 
                        ContentType.APPLICATION_DATA.name +  | 
 | 
                        ", length = " + destination.remaining());  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            recordSN = encrypt(writeCipher,  | 
 | 
                    ContentType.APPLICATION_DATA.id, destination,  | 
 | 
                    dstPos, dstLim, headerSize,  | 
 | 
                    protocolVersion);  | 
 | 
 | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("packet")) { | 
 | 
                ByteBuffer temporary = destination.duplicate();  | 
 | 
                temporary.limit(temporary.position());  | 
 | 
                temporary.position(dstPos);  | 
 | 
                SSLLogger.fine("Raw write", temporary); | 
 | 
            }  | 
 | 
 | 
 | 
            packetLeftSize -= destination.position() - dstPos;  | 
 | 
 | 
 | 
              | 
 | 
            destination.limit(dstLim);  | 
 | 
 | 
 | 
            if (isFirstAppOutputRecord) { | 
 | 
                isFirstAppOutputRecord = false;  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        return new Ciphertext(ContentType.APPLICATION_DATA.id,  | 
 | 
                SSLHandshake.NOT_APPLICABLE.id, recordSN);  | 
 | 
    }  | 
 | 
 | 
 | 
    private Ciphertext acquireCiphertext(  | 
 | 
            ByteBuffer destination) throws IOException { | 
 | 
        if (isTalkingToV2) {              // SSLv2Hello | 
 | 
            // We don't support SSLv2.  Send an SSLv2 error message  | 
 | 
            // so that the connection can be closed gracefully.  | 
 | 
            //  | 
 | 
              | 
 | 
            destination.put(SSLRecord.v2NoCipher);  | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("packet")) { | 
 | 
                SSLLogger.fine("Raw write", SSLRecord.v2NoCipher); | 
 | 
            }  | 
 | 
 | 
 | 
            isTalkingToV2 = false;  | 
 | 
 | 
 | 
            return new Ciphertext(ContentType.ALERT.id,  | 
 | 
                    SSLHandshake.NOT_APPLICABLE.id, -1L);  | 
 | 
        }  | 
 | 
 | 
 | 
        if (v2ClientHello != null) { | 
 | 
            // deliver the SSLv2 format ClientHello message  | 
 | 
            //  | 
 | 
              | 
 | 
            if (SSLLogger.isOn) { | 
 | 
                if (SSLLogger.isOn("record")) { | 
 | 
                     SSLLogger.fine(Thread.currentThread().getName() +  | 
 | 
                            ", WRITE: SSLv2 ClientHello message" +  | 
 | 
                            ", length = " + v2ClientHello.remaining());  | 
 | 
                }  | 
 | 
 | 
 | 
                if (SSLLogger.isOn("packet")) { | 
 | 
                    SSLLogger.fine("Raw write", v2ClientHello); | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            destination.put(v2ClientHello);  | 
 | 
            v2ClientHello = null;  | 
 | 
 | 
 | 
            return new Ciphertext(ContentType.HANDSHAKE.id,  | 
 | 
                   SSLHandshake.CLIENT_HELLO.id, -1L);  | 
 | 
        }  | 
 | 
 | 
 | 
        if (fragmenter != null) { | 
 | 
            return fragmenter.acquireCiphertext(destination);  | 
 | 
        }  | 
 | 
 | 
 | 
        return null;  | 
 | 
    }  | 
 | 
 | 
 | 
    @Override  | 
 | 
    boolean isEmpty() { | 
 | 
        return (!isTalkingToV2) && (v2ClientHello == null) &&  | 
 | 
                ((fragmenter == null) || fragmenter.isEmpty());  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
    private static class RecordMemo { | 
 | 
        byte            contentType;  | 
 | 
        byte            majorVersion;  | 
 | 
        byte            minorVersion;  | 
 | 
        SSLWriteCipher  encodeCipher;  | 
 | 
 | 
 | 
        byte[]          fragment;  | 
 | 
    }  | 
 | 
 | 
 | 
    private static class HandshakeMemo extends RecordMemo { | 
 | 
        byte            handshakeType;  | 
 | 
        int             acquireOffset;  | 
 | 
    }  | 
 | 
 | 
 | 
    final class HandshakeFragment { | 
 | 
        private LinkedList<RecordMemo> handshakeMemos = new LinkedList<>();  | 
 | 
 | 
 | 
        void queueUpFragment(byte[] source,  | 
 | 
                int offset, int length) throws IOException { | 
 | 
            HandshakeMemo memo = new HandshakeMemo();  | 
 | 
 | 
 | 
            memo.contentType = ContentType.HANDSHAKE.id;  | 
 | 
            memo.majorVersion = protocolVersion.major;    | 
 | 
            memo.minorVersion = protocolVersion.minor;  | 
 | 
            memo.encodeCipher = writeCipher;  | 
 | 
 | 
 | 
            memo.handshakeType = source[offset];  | 
 | 
            memo.acquireOffset = 0;  | 
 | 
            memo.fragment = new byte[length - 4];         | 
 | 
                                                        //    1: HandshakeType  | 
 | 
                                                          | 
 | 
            System.arraycopy(source, offset + 4, memo.fragment, 0, length - 4);  | 
 | 
 | 
 | 
            handshakeMemos.add(memo);  | 
 | 
        }  | 
 | 
 | 
 | 
        void queueUpChangeCipherSpec() { | 
 | 
            RecordMemo memo = new RecordMemo();  | 
 | 
 | 
 | 
            memo.contentType = ContentType.CHANGE_CIPHER_SPEC.id;  | 
 | 
            memo.majorVersion = protocolVersion.major;  | 
 | 
            memo.minorVersion = protocolVersion.minor;  | 
 | 
            memo.encodeCipher = writeCipher;  | 
 | 
 | 
 | 
            memo.fragment = new byte[1];  | 
 | 
            memo.fragment[0] = 1;  | 
 | 
 | 
 | 
            handshakeMemos.add(memo);  | 
 | 
        }  | 
 | 
 | 
 | 
        void queueUpAlert(byte level, byte description) { | 
 | 
            RecordMemo memo = new RecordMemo();  | 
 | 
 | 
 | 
            memo.contentType = ContentType.ALERT.id;  | 
 | 
            memo.majorVersion = protocolVersion.major;  | 
 | 
            memo.minorVersion = protocolVersion.minor;  | 
 | 
            memo.encodeCipher = writeCipher;  | 
 | 
 | 
 | 
            memo.fragment = new byte[2];  | 
 | 
            memo.fragment[0] = level;  | 
 | 
            memo.fragment[1] = description;  | 
 | 
 | 
 | 
            handshakeMemos.add(memo);  | 
 | 
        }  | 
 | 
 | 
 | 
        Ciphertext acquireCiphertext(ByteBuffer dstBuf) throws IOException { | 
 | 
            if (isEmpty()) { | 
 | 
                return null;  | 
 | 
            }  | 
 | 
 | 
 | 
            RecordMemo memo = handshakeMemos.getFirst();  | 
 | 
            HandshakeMemo hsMemo = null;  | 
 | 
            if (memo.contentType == ContentType.HANDSHAKE.id) { | 
 | 
                hsMemo = (HandshakeMemo)memo;  | 
 | 
            }  | 
 | 
 | 
 | 
            // ChangeCipherSpec message is pretty small.  Don't worry about  | 
 | 
              | 
 | 
            int fragLen;  | 
 | 
            if (packetSize > 0) { | 
 | 
                fragLen = Math.min(maxRecordSize, packetSize);  | 
 | 
                fragLen = memo.encodeCipher.calculateFragmentSize(  | 
 | 
                        fragLen, headerSize);  | 
 | 
            } else { | 
 | 
                fragLen = Record.maxDataSize;  | 
 | 
            }  | 
 | 
 | 
 | 
            if (fragmentSize > 0) { | 
 | 
                fragLen = Math.min(fragLen, fragmentSize);  | 
 | 
            }  | 
 | 
 | 
 | 
            int dstPos = dstBuf.position();  | 
 | 
            int dstLim = dstBuf.limit();  | 
 | 
            int dstContent = dstPos + headerSize +  | 
 | 
                                    memo.encodeCipher.getExplicitNonceSize();  | 
 | 
            dstBuf.position(dstContent);  | 
 | 
 | 
 | 
            if (hsMemo != null) { | 
 | 
                int remainingFragLen = fragLen;  | 
 | 
                while ((remainingFragLen > 0) && !handshakeMemos.isEmpty()) { | 
 | 
                    int memoFragLen = hsMemo.fragment.length;  | 
 | 
                    if (hsMemo.acquireOffset == 0) { | 
 | 
                          | 
 | 
                        if (remainingFragLen <= 4) { | 
 | 
                            break;  | 
 | 
                        }  | 
 | 
 | 
 | 
                        dstBuf.put(hsMemo.handshakeType);  | 
 | 
                        dstBuf.put((byte)((memoFragLen >> 16) & 0xFF));  | 
 | 
                        dstBuf.put((byte)((memoFragLen >> 8) & 0xFF));  | 
 | 
                        dstBuf.put((byte)(memoFragLen & 0xFF));  | 
 | 
 | 
 | 
                        remainingFragLen -= 4;  | 
 | 
                    } // Otherwise, handshake message is fragmented.  | 
 | 
 | 
 | 
                    int chipLen = Math.min(remainingFragLen,  | 
 | 
                            (memoFragLen - hsMemo.acquireOffset));  | 
 | 
                    dstBuf.put(hsMemo.fragment, hsMemo.acquireOffset, chipLen);  | 
 | 
 | 
 | 
                    hsMemo.acquireOffset += chipLen;  | 
 | 
                    if (hsMemo.acquireOffset == memoFragLen) { | 
 | 
                        handshakeMemos.removeFirst();  | 
 | 
 | 
 | 
                          | 
 | 
                        if ((remainingFragLen > chipLen) &&  | 
 | 
                                 !handshakeMemos.isEmpty()) { | 
 | 
 | 
 | 
                              | 
 | 
                            RecordMemo rm = handshakeMemos.getFirst();  | 
 | 
                            if (rm.contentType == ContentType.HANDSHAKE.id &&  | 
 | 
                                    rm.encodeCipher == hsMemo.encodeCipher) { | 
 | 
                                hsMemo = (HandshakeMemo)rm;  | 
 | 
                            } else { | 
 | 
                                  | 
 | 
                                break;  | 
 | 
                            }  | 
 | 
                        }  | 
 | 
                    }  | 
 | 
 | 
 | 
                    remainingFragLen -= chipLen;  | 
 | 
                }  | 
 | 
            } else { | 
 | 
                fragLen = Math.min(fragLen, memo.fragment.length);  | 
 | 
                dstBuf.put(memo.fragment, 0, fragLen);  | 
 | 
 | 
 | 
                handshakeMemos.removeFirst();  | 
 | 
            }  | 
 | 
 | 
 | 
            dstBuf.limit(dstBuf.position());  | 
 | 
            dstBuf.position(dstContent);  | 
 | 
 | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("record")) { | 
 | 
                SSLLogger.fine(  | 
 | 
                        "WRITE: " + protocolVersion + " " +  | 
 | 
                        ContentType.nameOf(memo.contentType) +  | 
 | 
                        ", length = " + dstBuf.remaining());  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            long recordSN = encrypt(  | 
 | 
                    memo.encodeCipher,  | 
 | 
                    memo.contentType, dstBuf,  | 
 | 
                    dstPos, dstLim, headerSize,  | 
 | 
                    ProtocolVersion.valueOf(memo.majorVersion,  | 
 | 
                            memo.minorVersion));  | 
 | 
 | 
 | 
            if (SSLLogger.isOn && SSLLogger.isOn("packet")) { | 
 | 
                ByteBuffer temporary = dstBuf.duplicate();  | 
 | 
                temporary.limit(temporary.position());  | 
 | 
                temporary.position(dstPos);  | 
 | 
                SSLLogger.fine("Raw write", temporary); | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            dstBuf.limit(dstLim);  | 
 | 
 | 
 | 
              | 
 | 
            if (hsMemo != null) { | 
 | 
                return new Ciphertext(hsMemo.contentType,  | 
 | 
                        hsMemo.handshakeType, recordSN);  | 
 | 
            } else { | 
 | 
                if (isCloseWaiting &&  | 
 | 
                        memo.contentType == ContentType.ALERT.id) { | 
 | 
                    close();  | 
 | 
                }  | 
 | 
 | 
 | 
                return new Ciphertext(memo.contentType,  | 
 | 
                        SSLHandshake.NOT_APPLICABLE.id, recordSN);  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        boolean isEmpty() { | 
 | 
            return handshakeMemos.isEmpty();  | 
 | 
        }  | 
 | 
 | 
 | 
        boolean hasAlert() { | 
 | 
            for (RecordMemo memo : handshakeMemos) { | 
 | 
                if (memo.contentType == ContentType.ALERT.id) { | 
 | 
                    return true;  | 
 | 
                }  | 
 | 
            }  | 
 | 
 | 
 | 
            return false;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    boolean needToSplitPayload() { | 
 | 
        return (!protocolVersion.useTLS11PlusSpec()) &&  | 
 | 
                writeCipher.isCBCMode() && !isFirstAppOutputRecord &&  | 
 | 
                Record.enableCBCProtection;  | 
 | 
    }  | 
 | 
}  |