/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Original Work: MIT License, Copyright (c) [2015-2018] all contributors
 * https://github.com/marianobarrios/tls-channel
 */

package com.mongodb.internal.connection.tlschannel.impl;

import com.mongodb.diagnostics.logging.Logger;
import com.mongodb.diagnostics.logging.Loggers;
import com.mongodb.internal.connection.tlschannel.BufferAllocator;
import org.bson.ByteBuf;

import java.nio.Buffer;
import java.nio.ByteBuffer;

import static com.mongodb.internal.connection.tlschannel.impl.TlsChannelImpl.MAX_TLS_PACKET_SIZE;
import static java.lang.String.format;

public class BufferHolder {

    private static final Logger LOGGER = Loggers.getLogger("connection.tls");
    // Round to next highest power of two to account for PowerOfTwoBufferPool allocation style
    private static final byte[] ZEROS = new byte[roundUpToNextHighestPowerOfTwo(MAX_TLS_PACKET_SIZE)];

    public final String name;
    public final BufferAllocator allocator;
    public final boolean plainData;
    public final int maxSize;
    public final boolean opportunisticDispose;

    private ByteBuf byteBuf;
    public ByteBuffer buffer;
    public int lastSize;

    public BufferHolder(final String name, final BufferAllocator allocator, final int initialSize,
                        final int maxSize, final boolean plainData, final boolean opportunisticDispose) {
        this.name = name;
        this.allocator = allocator;
        this.buffer = null;
        this.maxSize = maxSize;
        this.plainData = plainData;
        this.opportunisticDispose = opportunisticDispose;
        this.lastSize = initialSize;
    }

    public void prepare() {
        if (buffer == null) {
            byteBuf = allocator.allocate(lastSize);
            buffer = byteBuf.asNIO();
        }
    }

    public boolean release() {
        if (opportunisticDispose && buffer.position() == 0) {
            return dispose();
        } else {
            return false;
        }
    }

    public boolean dispose() {
        if (buffer != null) {
            allocator.free(byteBuf);
            buffer = null;
            return true;
        } else {
            return false;
        }
    }

    public void resize(final int newCapacity) {
        if (newCapacity > maxSize) {
            throw new IllegalArgumentException(format("new capacity (%s) bigger than absolute max size (%s)", newCapacity, maxSize));
        }
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace(format("resizing buffer %s, increasing from %s to %s (manual sizing)", name, buffer.capacity(), newCapacity));
        }
        resizeImpl(newCapacity);
    }

    public void enlarge() {
        if (buffer.capacity() >= maxSize) {
            throw new IllegalStateException(
                    format("%s buffer insufficient despite having capacity of %d", name, buffer.capacity()));
        }
        int newCapacity = Math.min(lastSize * 2, maxSize);
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace(format("enlarging buffer %s, increasing from %s to %s (automatic enlarge)", name, buffer.capacity(), newCapacity));
        }
        resizeImpl(newCapacity);
    }

    private void resizeImpl(final int newCapacity) {
        ByteBuf newByteBuf = allocator.allocate(newCapacity);
        ByteBuffer newBuffer = newByteBuf.asNIO();
        ((Buffer) buffer).flip();
        newBuffer.put(buffer);
        if (plainData) {
            zero();
        }
        allocator.free(byteBuf);
        byteBuf = newByteBuf;
        buffer = newBuffer;
        lastSize = newCapacity;
    }

    
Fill with zeros the remaining of the supplied buffer. This method does not change the buffer position.

Typically used for security reasons, with buffers that contains now-unused plaintext.

/** * Fill with zeros the remaining of the supplied buffer. This method does * not change the buffer position. * <p> * Typically used for security reasons, with buffers that contains * now-unused plaintext. */
public void zeroRemaining() { ((Buffer) buffer).mark(); buffer.put(ZEROS, 0, buffer.remaining()); ((Buffer) buffer).reset(); }
Fill the buffer with zeros. This method does not change the buffer position.

Typically used for security reasons, with buffers that contains now-unused plaintext.

/** * Fill the buffer with zeros. This method does not change the buffer position. * <p> * Typically used for security reasons, with buffers that contains * now-unused plaintext. */
public void zero() { ((Buffer) buffer).mark(); ((Buffer) buffer).position(0); buffer.put(ZEROS, 0, buffer.remaining()); ((Buffer) buffer).reset(); } public boolean nullOrEmpty() { return buffer == null || buffer.position() == 0; } private static int roundUpToNextHighestPowerOfTwo(final int size) { int v = size; v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } }