// Copyright (c) 2019 Andreas Auernhammer. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.

// Package sioutil implements some I/O utility functions.
package sioutil

import (
	"crypto/rand"
	"io"

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

type nopCloser struct {
	io.Writer
}

func (nopCloser) Close() error { return nil }

// NopCloser returns a WriteCloser that wraps w
// and implements Close as a no-op.
func NopCloser(w io.Writer) io.WriteCloser {
	return nopCloser{w}
}

// NativeAES returns true when the executing CPU
// provides AES-GCM hardware instructions and
// an optimized assembler implementation is
// available.
//
// It is strongly recommended to only use AES-GCM
// when NativeAES() returns true. Otherwise, the
// AES-GCM implementation may be vulnerable to
// timing attacks.
// See: https://golang.org/pkg/crypto/aes
func NativeAES() bool {
	if cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ {
		return true
	}
	if cpu.ARM64.HasAES {
		return true
	}

	// Go 1.14 introduces an AES-GCM asm implementation
	// for PPC64le. Therefore, we have to use build tags
	// to determine whether we're compiling for PPC64 and
	// use Go 1.14 (or newer).
	// TODO(aead): Once we drop Go 1.13 support
	// (bump go version in go.mod) we can remove
	// pcc64-related build tags again.
	if ppcHasAES {
		return true
	}

	// On s390x, aes.NewCipher(...) returns a type
	// that provides AES asm implementations only
	// if all (CBC, CTR and GCM) AES hardware
	// instructions are available.
	// See: https://golang.org/src/crypto/aes/cipher_s390x.go#L39
	return cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR &&
		(cpu.S390X.HasAESGCM || cpu.S390X.HasGHASH)
}

// Random returns n randomly generated bytes if
// and only if err == nil.
//
// Random uses crypto/rand.Reader as cryptographically
// secure random number generator (CSPRNG).
func Random(n int) (b []byte, err error) {
	b = make([]byte, n)
	if _, err = io.ReadFull(rand.Reader, b); err != nil {
		return nil, err
	}
	return b, nil
}

// MustRandom returns n randomly generated bytes.
// It panics if it fails to read n random bytes
// from its entropy source.
//
// MustRandom uses crypto/rand.Reader as cryptographically
// secure random number generator (CSPRNG).
func MustRandom(n int) []byte {
	b, err := Random(n)
	if err != nil {
		panic("sioutil: out of entropy: " + err.Error())
	}
	return b
}
