// Copyright 2012 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 (amd64 || arm64 || ppc64 || ppc64le) && !purego

package aes

import (
	"crypto/internal/fips140deps/cpu"
	"crypto/internal/fips140deps/godebug"
	"crypto/internal/impl"
)

//go:noescape
func encryptBlockAsm(nr int, xk *uint32, dst, src *byte)

//go:noescape
func decryptBlockAsm(nr int, xk *uint32, dst, src *byte)

//go:noescape
func expandKeyAsm(nr int, key *byte, enc *uint32, dec *uint32)

var supportsAES = cpu.X86HasAES && cpu.X86HasSSE41 && cpu.X86HasSSSE3 ||
	cpu.ARM64HasAES || cpu.PPC64 || cpu.PPC64le

func init() {
	if cpu.AMD64 {
		impl.Register("aes", "AES-NI", &supportsAES)
	}
	if cpu.ARM64 {
		impl.Register("aes", "Armv8.0", &supportsAES)
	}
	if cpu.PPC64 || cpu.PPC64le {
		// The POWER architecture doesn't have a way to turn off AES support
		// at runtime with GODEBUG=cpu.something=off, so introduce a new GODEBUG
		// knob for that. It's intentionally only checked at init() time, to
		// avoid the performance overhead of checking it every time.
		if godebug.Value("#ppc64aes") == "off" {
			supportsAES = false
		}
		impl.Register("aes", "POWER8", &supportsAES)
	}
}

// checkGenericIsExpected is called by the variable-time implementation to make
// sure it is not used when hardware support is available. It shouldn't happen,
// but this way it's more evidently correct.
func checkGenericIsExpected() {
	if supportsAES {
		panic("crypto/aes: internal error: using generic implementation despite hardware support")
	}
}

type block struct {
	blockExpanded
}

func newBlock(c *Block, key []byte) *Block {
	switch len(key) {
	case aes128KeySize:
		c.rounds = aes128Rounds
	case aes192KeySize:
		c.rounds = aes192Rounds
	case aes256KeySize:
		c.rounds = aes256Rounds
	}
	if supportsAES {
		expandKeyAsm(c.rounds, &key[0], &c.enc[0], &c.dec[0])
	} else {
		expandKeyGeneric(&c.blockExpanded, key)
	}
	return c
}

// EncryptionKeySchedule is used from the GCM implementation to access the
// precomputed AES key schedule, to pass to the assembly implementation.
func EncryptionKeySchedule(c *Block) []uint32 {
	return c.enc[:c.roundKeysSize()]
}

func encryptBlock(c *Block, dst, src []byte) {
	if supportsAES {
		encryptBlockAsm(c.rounds, &c.enc[0], &dst[0], &src[0])
	} else {
		encryptBlockGeneric(&c.blockExpanded, dst, src)
	}
}

func decryptBlock(c *Block, dst, src []byte) {
	if supportsAES {
		decryptBlockAsm(c.rounds, &c.dec[0], &dst[0], &src[0])
	} else {
		decryptBlockGeneric(&c.blockExpanded, dst, src)
	}
}
