// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package subprocess

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
)

// The following structures reflect the JSON of ACVP KTS-IFC tests. See
// https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-ifc.html#name-test-vectors

type ktsVectorSet struct {
	Groups   []ktsTestGroup `json:"testGroups"`
	Revision string         `json:"revision"`
}

type ktsTestGroup struct {
	ID         uint64    `json:"tgId"`
	Type       string    `json:"testType"`
	Scheme     string    `json:"scheme"`
	Role       string    `json:"kasRole"`
	Modulo     uint64    `json:"modulo"`
	KeyGen     string    `json:"keyGenerationMethod"`
	KTSConf    ktsConfig `json:"ktsConfiguration"`
	OutputBits uint64    `json:"l"`
	Tests      []ktsTest `json:"tests"`
}

type ktsConfig struct {
	HashAlg               string `json:"hashAlg"`
	AssociatedDataPattern string `json:"associatedDataPattern"`
	Encoding              string `json:"encoding"`
}

type ktsTest struct {
	ID uint64 `json:"tcId"`

	ServerN string `json:"serverN,omitempty"`
	ServerE string `json:"serverE,omitempty"`
	ServerC string `json:"serverC,omitempty"`

	IutN    string `json:"iutN,omitempty"`
	IutE    string `json:"iutE,omitempty"`
	IutP    string `json:"iutP,omitempty"`
	IutQ    string `json:"iutQ,omitempty"`
	IutD    string `json:"iutD,omitempty"`
	IutDmp1 string `json:"iutDmp1,omitempty"`
	IutDmq1 string `json:"iutDmq1,omitempty"`
	IutIqmp string `json:"iutIqmp,omitempty"`
}

type ktsTestGroupResponse struct {
	ID    uint64            `json:"tgId"`
	Tests []ktsTestResponse `json:"tests"`
}

type ktsTestResponse struct {
	ID   uint64 `json:"tcId"`
	IutC string `json:"iutC,omitempty"` // initiator role only
	Dkm  string `json:"dkm,omitempty"`
}

type kts struct {
	hashAlgs map[string]bool // the supported hash algorithm primitives
}

func (k *kts) Process(vectorSet []byte, m Transactable) (any, error) {
	var parsed ktsVectorSet
	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
		return nil, err
	}

	if parsed.Revision != "Sp800-56Br2" {
		return nil, fmt.Errorf("unsupported revision %q", parsed.Revision)
	}

	var ret []ktsTestGroupResponse
	for _, group := range parsed.Groups {
		group := group
		response := ktsTestGroupResponse{
			ID: group.ID,
		}

		if group.Type != "AFT" {
			return nil, fmt.Errorf("unsupported test type %q in test group %d", group.Type, group.ID)
		}

		if group.Scheme != "KTS-OAEP-basic" {
			return nil, fmt.Errorf("unsupported scheme %q in test group %d", group.Scheme, group.ID)
		}

		switch group.KeyGen {
		case "rsakpg1-basic", "rsakpg1-crt", "rsakpg2-basic", "rsakpg2-crt":
			// Supported key generation method.
		default:
			return nil, fmt.Errorf(
				"unsupported key generation method %q in test group %d", group.KeyGen, group.ID)
		}

		if group.OutputBits%8 != 0 {
			return nil, fmt.Errorf("%d bit L in test group %d: fractional bytes not supported", group.OutputBits, group.ID)
		}

		if _, ok := k.hashAlgs[group.KTSConf.HashAlg]; !ok {
			return nil, fmt.Errorf("test group %d specifies unsupported hash alg %q", group.ID, group.KTSConf.HashAlg)
		}

		var testResponses []ktsTestResponse
		for _, test := range group.Tests {
			test := test

			var err error
			switch group.Role {
			case "initiator":
				err = k.processInitiator(m, &testResponses, group.KTSConf.HashAlg, group.OutputBits, test)
			case "responder":
				err = k.processResponder(m, &testResponses, group.KTSConf.HashAlg, group.KeyGen, test)
			default:
				err = fmt.Errorf("unknown role %q", group.Role)
			}

			if err != nil {
				return nil, err
			}
		}

		m.Barrier(func() {
			response.Tests = testResponses
			ret = append(ret, response)
		})
	}

	if err := m.Flush(); err != nil {
		return nil, err
	}

	return ret, nil
}

func (k *kts) processInitiator(m Transactable, responses *[]ktsTestResponse, hashAlg string, outputBits uint64, test ktsTest) error {
	outputBytes := uint32le(uint32(outputBits / 8))

	nBytes, err := hex.DecodeString(test.ServerN)
	if err != nil {
		return fmt.Errorf("invalid ServerN: %v", err)
	}
	eBytes, err := hex.DecodeString(test.ServerE)
	if err != nil {
		return fmt.Errorf("invalid ServerE: %v", err)
	}

	cmd := fmt.Sprintf("KTS-IFC/%s/initiator", hashAlg)
	args := [][]byte{outputBytes, nBytes, eBytes}

	m.TransactAsync(cmd, 2, args, func(result [][]byte) error {
		*responses = append(*responses,
			ktsTestResponse{
				ID:   test.ID,
				IutC: hex.EncodeToString(result[0]),
				Dkm:  hex.EncodeToString(result[1]),
			})
		return nil
	})

	return nil
}

func (k *kts) processResponder(m Transactable, responses *[]ktsTestResponse, hashAlg string, keyGen string, test ktsTest) error {
	nBytes, err := hex.DecodeString(test.IutN)
	if err != nil {
		return fmt.Errorf("invalid IutN: %v", err)
	}

	eBytes, err := hex.DecodeString(test.IutE)
	if err != nil {
		return fmt.Errorf("invalid IutE: %v", err)
	}

	pBytes, err := hex.DecodeString(test.IutP)
	if err != nil {
		return fmt.Errorf("invalid IutP: %v", err)
	}

	qBytes, err := hex.DecodeString(test.IutQ)
	if err != nil {
		return fmt.Errorf("invalid IutQ: %v", err)
	}

	cBytes, err := hex.DecodeString(test.ServerC)
	if err != nil {
		return fmt.Errorf("invalid ServerC: %v", err)
	}

	cmd := fmt.Sprintf("KTS-IFC/%s/responder", hashAlg)
	var args [][]byte

	if keyGen == "rsakpg1-basic" || keyGen == "rsakpg2-basic" {
		dBytes, err := hex.DecodeString(test.IutD)
		if err != nil {
			return fmt.Errorf("invalid IutD: %v", err)
		}
		args = [][]byte{nBytes, eBytes, pBytes, qBytes, dBytes, cBytes}
	} else {
		dmp1Bytes, err := hex.DecodeString(test.IutDmp1)
		if err != nil {
			return fmt.Errorf("invalid IutDmp1: %v", err)
		}

		dmq1Bytes, err := hex.DecodeString(test.IutDmq1)
		if err != nil {
			return fmt.Errorf("invalid IutDmq1: %v", err)
		}

		iqmpBytes, err := hex.DecodeString(test.IutIqmp)
		if err != nil {
			return fmt.Errorf("invalid IutIqmp: %v", err)
		}

		cmd = cmd + "/crt"
		args = [][]byte{nBytes, eBytes, pBytes, qBytes, dmp1Bytes, dmq1Bytes, iqmpBytes, cBytes}
	}

	m.TransactAsync(cmd, 1, args, func(result [][]byte) error {
		*responses = append(*responses,
			ktsTestResponse{
				ID:  test.ID,
				Dkm: hex.EncodeToString(result[0]),
			})
		return nil
	})

	return nil
}
