package noise

import (
	
	

	pool 
	
)

// MaxTransportMsgLength is the Noise-imposed maximum transport message length,
// inclusive of the MAC size (16 bytes, Poly1305 for noise-libp2p).
const MaxTransportMsgLength = 0xffff

// MaxPlaintextLength is the maximum payload size. It is MaxTransportMsgLength
// minus the MAC size. Payloads over this size will be automatically chunked.
const MaxPlaintextLength = MaxTransportMsgLength - chacha20poly1305.Overhead

// LengthPrefixLength is the length of the length prefix itself, which precedes
// all transport messages in order to delimit them. In bytes.
const LengthPrefixLength = 2

// Read reads from the secure connection, returning plaintext data in `buf`.
//
// Honours io.Reader in terms of behaviour.
func ( *secureSession) ( []byte) (int, error) {
	.readLock.Lock()
	defer .readLock.Unlock()

	// 1. If we have queued received bytes:
	//   1a. If len(buf) < len(queued), saturate buf, update seek pointer, return.
	//   1b. If len(buf) >= len(queued), copy remaining to buf, release queued buffer back into pool, return.
	//
	// 2. Else, read the next message off the wire; next_len is length prefix.
	//   2a. If len(buf) >= next_len, copy the message to input buffer (zero-alloc path), and return.
	//   2b. If len(buf) >= (next_len - length of Authentication Tag), get buffer from pool, read encrypted message into it.
	//       decrypt message directly into the input buffer and return the buffer obtained from the pool.
	//   2c. If len(buf) < next_len, obtain buffer from pool, copy entire message into it, saturate buf, update seek pointer.
	if .qbuf != nil {
		// we have queued bytes; copy as much as we can.
		 := copy(, .qbuf[.qseek:])
		.qseek += 
		if .qseek == len(.qbuf) {
			// queued buffer is now empty, reset and release.
			pool.Put(.qbuf)
			.qseek, .qbuf = 0, nil
		}
		return , nil
	}

	// length of the next encrypted message.
	,  := .readNextInsecureMsgLen()
	if  != nil {
		return 0, 
	}

	// If the buffer is atleast as big as the encrypted message size,
	// we can read AND decrypt in place.
	if len() >=  {
		if  := .readNextMsgInsecure([:]);  != nil {
			return 0, 
		}

		,  := .decrypt([:0], [:])
		if  != nil {
			return 0, 
		}

		return len(), nil
	}

	// otherwise, we get a buffer from the pool so we can read the message into it
	// and then decrypt in place, since we're retaining the buffer (or a view thereof).
	 := pool.Get()
	if  := .readNextMsgInsecure();  != nil {
		return 0, 
	}

	if .qbuf,  = .decrypt([:0], );  != nil {
		return 0, 
	}

	// copy as many bytes as we can; update seek pointer.
	.qseek = copy(, .qbuf)

	return .qseek, nil
}

// Write encrypts the plaintext `in` data and sends it on the
// secure connection.
func ( *secureSession) ( []byte) (int, error) {
	.writeLock.Lock()
	defer .writeLock.Unlock()

	var (
		 int
		    []byte
		   = len()
	)

	if  < MaxPlaintextLength {
		 = pool.Get( + chacha20poly1305.Overhead + LengthPrefixLength)
	} else {
		 = pool.Get(MaxTransportMsgLength + LengthPrefixLength)
	}

	defer pool.Put()

	for  <  {
		 :=  + MaxPlaintextLength
		if  >  {
			 = 
		}

		,  := .encrypt([:LengthPrefixLength], [:])
		if  != nil {
			return 0, 
		}

		binary.BigEndian.PutUint16(, uint16(len()-LengthPrefixLength))

		_,  = .writeMsgInsecure()
		if  != nil {
			return , 
		}
		 = 
	}
	return , nil
}

// readNextInsecureMsgLen reads the length of the next message on the insecureConn channel.
func ( *secureSession) () (int, error) {
	,  := io.ReadFull(.insecureReader, .rlen[:])
	if  != nil {
		return 0, 
	}

	return int(binary.BigEndian.Uint16(.rlen[:])), 
}

// readNextMsgInsecure tries to read exactly len(buf) bytes into buf from
// the insecureConn channel and returns the error, if any.
// Ideally, for reading a message, you'd first want to call `readNextInsecureMsgLen`
// to determine the size of the next message to be read from the insecureConn channel and then call
// this function with a buffer of exactly that size.
func ( *secureSession) ( []byte) error {
	,  := io.ReadFull(.insecureReader, )
	return 
}

// writeMsgInsecure writes to the insecureConn conn.
// data will be prefixed with its length in bytes, written as a 16-bit uint in network order.
func ( *secureSession) ( []byte) (int, error) {
	return .insecureConn.Write()
}