// 
// Decompiled by Procyon v0.6.0
// 

package com.google.crypto.tink.subtle;

import java.nio.channels.ClosedChannelException;
import java.security.GeneralSecurityException;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;

class StreamingAeadEncryptingChannel implements WritableByteChannel
{
    private WritableByteChannel ciphertextChannel;
    private StreamSegmentEncrypter encrypter;
    ByteBuffer ptBuffer;
    ByteBuffer ctBuffer;
    private int plaintextSegmentSize;
    boolean open;
    
    @CanIgnoreReturnValue
    private int writeWithCheck(final WritableByteChannel dst, final ByteBuffer src) throws IOException {
        final int r = src.remaining();
        final int n = dst.write(src);
        if (n < 0 || n > r) {
            throw new IOException("Invalid return value from dst.write: n = " + n + ", r = " + r);
        }
        if (src.remaining() != r - n) {
            throw new IOException("Unexpected state after of src after writing to dst:  src.remaining() = " + src.remaining() + " != r - n = " + r + " - " + n);
        }
        return n;
    }
    
    public StreamingAeadEncryptingChannel(final NonceBasedStreamingAead streamAead, final WritableByteChannel ciphertextChannel, final byte[] associatedData) throws GeneralSecurityException, IOException {
        this.open = true;
        this.ciphertextChannel = ciphertextChannel;
        this.encrypter = streamAead.newStreamSegmentEncrypter(associatedData);
        this.plaintextSegmentSize = streamAead.getPlaintextSegmentSize();
        (this.ptBuffer = ByteBuffer.allocate(this.plaintextSegmentSize)).limit(this.plaintextSegmentSize - streamAead.getCiphertextOffset());
        (this.ctBuffer = ByteBuffer.allocate(streamAead.getCiphertextSegmentSize())).put(this.encrypter.getHeader());
        this.ctBuffer.flip();
        this.writeWithCheck(ciphertextChannel, this.ctBuffer);
    }
    
    @Override
    public synchronized int write(final ByteBuffer pt) throws IOException {
        if (!this.open) {
            throw new ClosedChannelException();
        }
        if (this.ctBuffer.remaining() > 0) {
            this.writeWithCheck(this.ciphertextChannel, this.ctBuffer);
        }
        final int startPosition = pt.position();
        while (pt.remaining() > this.ptBuffer.remaining()) {
            if (this.ctBuffer.remaining() > 0) {
                return pt.position() - startPosition;
            }
            final int sliceSize = this.ptBuffer.remaining();
            final ByteBuffer slice = pt.slice();
            slice.limit(sliceSize);
            pt.position(pt.position() + sliceSize);
            try {
                this.ptBuffer.flip();
                this.ctBuffer.clear();
                if (slice.remaining() != 0) {
                    this.encrypter.encryptSegment(this.ptBuffer, slice, false, this.ctBuffer);
                }
                else {
                    this.encrypter.encryptSegment(this.ptBuffer, false, this.ctBuffer);
                }
            }
            catch (final GeneralSecurityException ex) {
                throw new IOException(ex);
            }
            this.ctBuffer.flip();
            this.writeWithCheck(this.ciphertextChannel, this.ctBuffer);
            this.ptBuffer.clear();
            this.ptBuffer.limit(this.plaintextSegmentSize);
        }
        this.ptBuffer.put(pt);
        return pt.position() - startPosition;
    }
    
    @Override
    public synchronized void close() throws IOException {
        if (!this.open) {
            return;
        }
        while (this.ctBuffer.remaining() > 0) {
            final int n = this.writeWithCheck(this.ciphertextChannel, this.ctBuffer);
            if (n <= 0) {
                throw new IOException("Failed to write ciphertext before closing");
            }
        }
        try {
            this.ctBuffer.clear();
            this.ptBuffer.flip();
            this.encrypter.encryptSegment(this.ptBuffer, true, this.ctBuffer);
        }
        catch (final GeneralSecurityException ex) {
            throw new IOException(ex);
        }
        this.ctBuffer.flip();
        while (this.ctBuffer.remaining() > 0) {
            final int n = this.writeWithCheck(this.ciphertextChannel, this.ctBuffer);
            if (n <= 0) {
                throw new IOException("Failed to write ciphertext before closing");
            }
        }
        this.ciphertextChannel.close();
        this.open = false;
    }
    
    @Override
    public boolean isOpen() {
        return this.open;
    }
}
