// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build linux && go1.25 && !without_badtls

package ktls

import (
	"crypto/cipher"
	"crypto/tls"
	"errors"
	"net"
)

func (c *Conn) Write(b []byte) (int, error) {
	if !c.kernelTx {
		return c.Conn.Write(b)
	}
	// interlock with Close below
	for {
		x := c.rawConn.ActiveCall.Load()
		if x&1 != 0 {
			return 0, net.ErrClosed
		}
		if c.rawConn.ActiveCall.CompareAndSwap(x, x+2) {
			break
		}
	}
	defer c.rawConn.ActiveCall.Add(-2)

	//if err := c.Conn.HandshakeContext(context.Background()); err != nil {
	//	return 0, err
	//}

	c.rawConn.Out.Lock()
	defer c.rawConn.Out.Unlock()

	if err := *c.rawConn.Out.Err; err != nil {
		return 0, err
	}

	if !c.rawConn.IsHandshakeComplete.Load() {
		return 0, tls.AlertError(alertInternalError)
	}

	if *c.rawConn.CloseNotifySent {
		// return 0, errShutdown
		return 0, errors.New("tls: protocol is shutdown")
	}

	// TLS 1.0 is susceptible to a chosen-plaintext
	// attack when using block mode ciphers due to predictable IVs.
	// This can be prevented by splitting each Application Data
	// record into two records, effectively randomizing the RawIV.
	//
	// https://www.openssl.org/~bodo/tls-cbc.txt
	// https://bugzilla.mozilla.org/show_bug.cgi?id=665814
	// https://www.imperialviolet.org/2012/01/15/beastfollowup.html

	var m int
	if len(b) > 1 && *c.rawConn.Vers == tls.VersionTLS10 {
		if _, ok := (*c.rawConn.Out.Cipher).(cipher.BlockMode); ok {
			n, err := c.writeRecordLocked(recordTypeApplicationData, b[:1])
			if err != nil {
				return n, c.rawConn.Out.SetErrorLocked(err)
			}
			m, b = 1, b[1:]
		}
	}

	n, err := c.writeRecordLocked(recordTypeApplicationData, b)
	return n + m, c.rawConn.Out.SetErrorLocked(err)
}

func (c *Conn) writeRecordLocked(typ uint16, data []byte) (n int, err error) {
	if !c.kernelTx {
		return c.rawConn.WriteRecordLocked(typ, data)
	}
	/*for len(data) > 0 {
		m := len(data)
		if maxPayload := c.maxPayloadSizeForWrite(typ); m > maxPayload {
			m = maxPayload
		}
		_, err = c.writeKernelRecord(typ, data[:m])
		if err != nil {
			return
		}
		n += m
		data = data[m:]
	}*/
	return c.writeKernelRecord(typ, data)
}

const (
	// tcpMSSEstimate is a conservative estimate of the TCP maximum segment
	// size (MSS). A constant is used, rather than querying the kernel for
	// the actual MSS, to avoid complexity. The value here is the IPv6
	// minimum MTU (1280 bytes) minus the overhead of an IPv6 header (40
	// bytes) and a TCP header with timestamps (32 bytes).
	tcpMSSEstimate = 1208

	// recordSizeBoostThreshold is the number of bytes of application data
	// sent after which the TLS record size will be increased to the
	// maximum.
	recordSizeBoostThreshold = 128 * 1024
)

func (c *Conn) maxPayloadSizeForWrite(typ uint16) int {
	if /*c.config.DynamicRecordSizingDisabled ||*/ typ != recordTypeApplicationData {
		return maxPlaintext
	}

	if *c.rawConn.PacketsSent >= recordSizeBoostThreshold {
		return maxPlaintext
	}

	// Subtract TLS overheads to get the maximum payload size.
	payloadBytes := tcpMSSEstimate - recordHeaderLen - c.rawConn.Out.ExplicitNonceLen()
	if rawCipher := *c.rawConn.Out.Cipher; rawCipher != nil {
		switch ciph := rawCipher.(type) {
		case cipher.Stream:
			payloadBytes -= (*c.rawConn.Out.Mac).Size()
		case cipher.AEAD:
			payloadBytes -= ciph.Overhead()
		/*case cbcMode:
		blockSize := ciph.BlockSize()
		// The payload must fit in a multiple of blockSize, with
		// room for at least one padding byte.
		payloadBytes = (payloadBytes & ^(blockSize - 1)) - 1
		// The RawMac is appended before padding so affects the
		// payload size directly.
		payloadBytes -= c.out.mac.Size()*/
		default:
			panic("unknown cipher type")
		}
	}
	if *c.rawConn.Vers == tls.VersionTLS13 {
		payloadBytes-- // encrypted ContentType
	}

	// Allow packet growth in arithmetic progression up to max.
	pkt := *c.rawConn.PacketsSent
	*c.rawConn.PacketsSent++
	if pkt > 1000 {
		return maxPlaintext // avoid overflow in multiply below
	}

	n := payloadBytes * int(pkt+1)
	if n > maxPlaintext {
		n = maxPlaintext
	}
	return n
}
