// SPDX-License-Identifier: BSD-3-Clause
//
// Generated from CryptoGAMS chacha-armv4.pl by plan9-xlate.pl. The
// generated chacha20_arm.s carries the upstream BSD-3-Clause notice
// in its file header.

//go:build arm

//go:generate sh ../regen/regen.sh

// Package chacha20 implements the ChaCha20 stream cipher with the
// CryptoGAMS ARMv4 assembly inner loop, regenerated by
// tsasm/arm/regen/plan9-xlate.pl. It only builds on GOARCH=arm;
// other architectures should use golang.org/x/crypto/chacha20.
package chacha20

import (
	"encoding/binary"
	"unsafe"

	"golang.org/x/sys/cpu"
)

const (
	KeySize   = 32
	NonceSize = 12
	BlockSize = 64
)

// ChaCha20_ctr32 is the scalar upstream entry point: it XORs a
// ChaCha20 keystream against `length` bytes from inp into out, using
// key (32 bytes) and ctr_nonce (16 bytes: counter as little-endian
// u32 followed by the 12-byte nonce).
//
//go:noescape
func ChaCha20_ctr32(out, inp unsafe.Pointer, length uintptr, key, ctr_nonce unsafe.Pointer)

// ChaCha20_neon is the NEON entry point. The CryptoGAMS NEON loop's
// .Loop_neon_outer head does `cmp r11,#128; bls .Lbreak_neon` and
// then branches cross-function into ChaCha20_ctr32's main loop --
// which we rewrite to UDF, since Plan 9 has no way to express that
// cross-function branch. So `length` must be either 0..256, an exact
// multiple of 256, or 256*K + R with R in 129..255. XORKeyStream
// trims to a safe length and sends any leftover 1..128-byte tail
// through ChaCha20_ctr32.
//
//go:noescape
func ChaCha20_neon(out, inp unsafe.Pointer, length uintptr, key, ctr_nonce unsafe.Pointer)

// XORKeyStream XORs a ChaCha20 keystream into in, writing to out, using
// the given 32-byte key, 12-byte IETF nonce, and starting 32-bit block
// counter. out and in must have the same length and may overlap exactly
// or not at all. It returns the next block counter
// (counter + ceil(len/64)).
func XORKeyStream(out, in []byte, key *[KeySize]byte, nonce *[NonceSize]byte, counter uint32) uint32 {
	if len(out) < len(in) {
		panic("chacha20: output buffer shorter than input")
	}
	if len(in) == 0 {
		return counter
	}

	// CryptoGAMS expects the counter+nonce as a single 16-byte block:
	// 4 little-endian words.
	var ctrNonce [16]byte
	binary.LittleEndian.PutUint32(ctrNonce[0:4], counter)
	copy(ctrNonce[4:], nonce[:])

	// On NEON-capable hardware, hand a "good" prefix length to
	// ChaCha20_neon. The CryptoGAMS NEON loop processes 256 bytes per
	// outer iteration and only re-checks for break-out into the scalar
	// path when entering .Loop_neon_outer; on the first pass through
	// .Loop_neon_enter that check doesn't fire, so any length up to
	// 256 bytes is safe. After that, the inner test `cmp r11,#128;
	// bls .Lbreak_neon` jumps cross-function into ChaCha20_ctr32 --
	// which we replaced with UDF -- whenever the post-iteration
	// remainder falls in 1..128 bytes. We avoid that by trimming to a
	// length whose mod-256 tail is either 0 or > 128, sending the
	// trimmed remainder (the small 1..128 byte tail when it shows up)
	// to the scalar path.
	pos := 0
	if cpu.ARM.HasNEON {
		n := len(in)
		neonLen := n
		if n > 256 {
			if rem := n % 256; rem != 0 && rem <= 128 {
				neonLen = n - rem
			}
		}
		if neonLen > 0 {
			ChaCha20_neon(
				unsafe.Pointer(&out[0]),
				unsafe.Pointer(&in[0]),
				uintptr(neonLen),
				unsafe.Pointer(&key[0]),
				unsafe.Pointer(&ctrNonce[0]),
			)
			pos = neonLen
			binary.LittleEndian.PutUint32(ctrNonce[0:4], counter+uint32(neonLen>>6))
		}
	}

	if pos < len(in) {
		ChaCha20_ctr32(
			unsafe.Pointer(&out[pos]),
			unsafe.Pointer(&in[pos]),
			uintptr(len(in)-pos),
			unsafe.Pointer(&key[0]),
			unsafe.Pointer(&ctrNonce[0]),
		)
	}

	return counter + uint32((len(in)+63)>>6)
}
