//go:build gofuzz

/*
   Copyright The containerd 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
       http://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 fuzz

import (
	"context"
	"fmt"
	golangruntime "runtime"
	"strings"

	fuzz "github.com/AdaLogics/go-fuzz-headers"
	runtime "k8s.io/cri-api/pkg/apis/runtime/v1"

	"github.com/containerd/containerd/pkg/cri/sbserver"
	"github.com/containerd/containerd/pkg/cri/server"
	containerstore "github.com/containerd/containerd/pkg/cri/store/container"
	sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
)

var (
	// The APIs the fuzzer can call:
	ops = map[int]string{
		0:  "createContainer",
		1:  "removeContainer",
		2:  "addSandboxes",
		3:  "listContainers",
		4:  "startContainer",
		5:  "containerStats",
		6:  "listContainerStats",
		7:  "containerStatus",
		8:  "stopContainer",
		9:  "updateContainerResources",
		10: "listImages",
		11: "removeImages",
		12: "imageStatus",
		13: "imageFsInfo",
		14: "listPodSandbox",
		15: "portForward",
		16: "removePodSandbox",
		17: "runPodSandbox",
		18: "podSandboxStatus",
		19: "stopPodSandbox",
		20: "status",
		21: "updateRuntimeConfig",
	}
	executionOrder []string
)

func printExecutions() {
	if r := recover(); r != nil {
		var err string
		switch r.(type) {
		case string:
			err = r.(string)
		case golangruntime.Error:
			err = r.(golangruntime.Error).Error()
		case error:
			err = r.(error).Error()
		default:
			err = "uknown error type"
		}
		fmt.Println("Executions:")
		for _, eo := range executionOrder {
			fmt.Println(eo)
		}
		panic(err)
	}
}

func fuzzCRI(f *fuzz.ConsumeFuzzer, c server.CRIService) int {
	executionOrder = make([]string, 0)
	defer printExecutions()

	calls, err := f.GetInt()
	if err != nil {
		return 0
	}

	executionOrder = make([]string, 0)
	defer printExecutions()

	for i := 0; i < calls%40; i++ {
		op, err := f.GetInt()
		if err != nil {
			return 0
		}
		opType := op % len(ops)

		switch ops[opType] {
		case "createContainer":
			createContainerFuzz(c, f)
		case "removeContainer":
			removeContainerFuzz(c, f)
		case "addSandboxes":
			addSandboxesFuzz(c, f)
		case "listContainers":
			listContainersFuzz(c, f)
		case "startContainer":
			startContainerFuzz(c, f)
		case "containerStats":
			containerStatsFuzz(c, f)
		case "listContainerStats":
			listContainerStatsFuzz(c, f)
		case "containerStatus":
			containerStatusFuzz(c, f)
		case "stopContainer":
			stopContainerFuzz(c, f)
		case "updateContainerResources":
			updateContainerResourcesFuzz(c, f)
		case "listImages":
			listImagesFuzz(c, f)
		case "removeImages":
			removeImagesFuzz(c, f)
		case "imageStatus":
			imageStatusFuzz(c, f)
		case "imageFsInfo":
			imageFsInfoFuzz(c, f)
		case "listPodSandbox":
			listPodSandboxFuzz(c, f)
		case "portForward":
			portForwardFuzz(c, f)
		case "removePodSandbox":
			removePodSandboxFuzz(c, f)
		case "runPodSandbox":
			runPodSandboxFuzz(c, f)
		case "podSandboxStatus":
			podSandboxStatusFuzz(c, f)
		case "stopPodSandbox":
			stopPodSandboxFuzz(c, f)
		case "status":
			statusFuzz(c, f)
		case "updateRuntimeConfig":
			updateRuntimeConfigFuzz(c, f)
		}
	}
	return 1
}

func logExecution(apiName, request string) {
	var logString strings.Builder
	logString.WriteString(fmt.Sprintf("Calling %s with \n %s \n\n", apiName, request))
	executionOrder = append(executionOrder, logString.String())
}

// createContainerFuzz creates a CreateContainerRequest and passes
// it to c.CreateContainer
func createContainerFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.CreateContainerRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.CreateContainer(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.CreateContainer", reqString)
	return nil
}

// removeContainerFuzz creates a RemoveContainerRequest and passes
// it to c.RemoveContainer
func removeContainerFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.RemoveContainerRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.RemoveContainer(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.RemoveContainer", reqString)
	return nil
}

func sandboxStore(cs server.CRIService) (*sandboxstore.Store, error) {
	var (
		ss  *sandboxstore.Store
		err error
	)

	ss, err = server.SandboxStore(cs)
	if err != nil {
		ss, err = sbserver.SandboxStore(cs)
		if err != nil {
			return nil, err
		}
		return ss, nil
	}
	return ss, nil
}

// addSandboxesFuzz creates a sandbox and adds it to the sandboxstore
func addSandboxesFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	quantity, err := f.GetInt()
	if err != nil {
		return err
	}

	ss, err := sandboxStore(c)
	if err != nil {
		return err
	}

	for i := 0; i < quantity%20; i++ {
		newSandbox, err := getSandboxFuzz(f)
		if err != nil {
			return err
		}
		err = ss.Add(newSandbox)
		if err != nil {
			return err
		}
	}
	return nil
}

// getSandboxFuzz creates a sandbox
func getSandboxFuzz(f *fuzz.ConsumeFuzzer) (sandboxstore.Sandbox, error) {
	metadata := sandboxstore.Metadata{}
	status := sandboxstore.Status{}
	err := f.GenerateStruct(&metadata)
	if err != nil {
		return sandboxstore.Sandbox{}, err
	}
	err = f.GenerateStruct(&status)
	if err != nil {
		return sandboxstore.Sandbox{}, err
	}

	reqString := fmt.Sprintf("metadata: %+v\nstatus: %+v\n", metadata, status)
	logExecution("sandboxstore.NewSandbox", reqString)

	return sandboxstore.NewSandbox(metadata, status), nil
}

// listContainersFuzz creates a ListContainersRequest and passes
// it to c.ListContainers
func listContainersFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.ListContainersRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.ListContainers(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.ListContainers", reqString)
	return nil
}

// startContainerFuzz creates a StartContainerRequest and passes
// it to c.StartContainer
func startContainerFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.StartContainerRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.StartContainer(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.StartContainer", reqString)
	return nil
}

// containerStatsFuzz creates a ContainerStatsRequest and passes
// it to c.ContainerStats
func containerStatsFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.ContainerStatsRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.ContainerStats(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.ContainerStats", reqString)
	return nil
}

// listContainerStatsFuzz creates a ListContainerStatsRequest and
// passes it to c.ListContainerStats
func listContainerStatsFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.ListContainerStatsRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.ListContainerStats(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.ListContainerStats", reqString)
	return nil
}

// containerStatusFuzz creates a ContainerStatusRequest and passes
// it to c.ContainerStatus
func containerStatusFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.ContainerStatusRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.ContainerStatus(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.ContainerStatus", reqString)
	return nil
}

// stopContainerFuzz creates a StopContainerRequest and passes
// it to c.StopContainer
func stopContainerFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.StopContainerRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.StopContainer(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.StopContainer", reqString)
	return nil
}

// updateContainerResourcesFuzz creates a UpdateContainerResourcesRequest
// and passes it to c.UpdateContainerResources
func updateContainerResourcesFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.UpdateContainerResourcesRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.UpdateContainerResources(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.UpdateContainerResources", reqString)
	return nil
}

// listImagesFuzz creates a ListImagesRequest and passes it to
// c.ListImages
func listImagesFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.ListImagesRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.ListImages(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.ListImages", reqString)
	return nil
}

// removeImagesFuzz creates a RemoveImageRequest and passes it to
// c.RemoveImage
func removeImagesFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.RemoveImageRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.RemoveImage(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.RemoveImage", reqString)
	return nil
}

// imageStatusFuzz creates an ImageStatusRequest and passes it to
// c.ImageStatus
func imageStatusFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.ImageStatusRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.ImageStatus(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.ImageStatus", reqString)
	return nil
}

// imageFsInfoFuzz creates an ImageFsInfoRequest and passes it to
// c.ImageFsInfo
func imageFsInfoFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.ImageFsInfoRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.ImageFsInfo(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.ImageFsInfo", reqString)
	return nil
}

// listPodSandboxFuzz creates a ListPodSandboxRequest and passes
// it to c.ListPodSandbox
func listPodSandboxFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.ListPodSandboxRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.ListPodSandbox(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.ListPodSandbox", reqString)
	return nil
}

// portForwardFuzz creates a PortForwardRequest and passes it to
// c.PortForward
func portForwardFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.PortForwardRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.PortForward(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.PortForward", reqString)
	return nil
}

// removePodSandboxFuzz creates a RemovePodSandboxRequest and
// passes it to c.RemovePodSandbox
func removePodSandboxFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.RemovePodSandboxRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.RemovePodSandbox(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.RemovePodSandbox", reqString)
	return nil
}

// runPodSandboxFuzz creates a RunPodSandboxRequest and passes
// it to c.RunPodSandbox
func runPodSandboxFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.RunPodSandboxRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.RunPodSandbox(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.RunPodSandbox", reqString)
	return nil
}

// podSandboxStatusFuzz creates a PodSandboxStatusRequest and
// passes it to
func podSandboxStatusFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.PodSandboxStatusRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.PodSandboxStatus(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.PodSandboxStatus", reqString)
	return nil
}

// stopPodSandboxFuzz creates a StopPodSandboxRequest and passes
// it to c.StopPodSandbox
func stopPodSandboxFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.StopPodSandboxRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.StopPodSandbox(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.StopPodSandbox", reqString)
	return nil
}

// statusFuzz creates a StatusRequest and passes it to c.Status
func statusFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.StatusRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.Status(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.Status", reqString)
	return nil
}

func updateRuntimeConfigFuzz(c server.CRIService, f *fuzz.ConsumeFuzzer) error {
	r := &runtime.UpdateRuntimeConfigRequest{}
	err := f.GenerateStruct(r)
	if err != nil {
		return err
	}
	_, _ = c.UpdateRuntimeConfig(context.Background(), r)
	reqString := fmt.Sprintf("%+v", r)
	logExecution("c.UpdateRuntimeConfig", reqString)
	return nil
}

// This creates a container directly in the store.
func getContainer(f *fuzz.ConsumeFuzzer) (containerstore.Container, error) {
	metadata := containerstore.Metadata{}
	status := containerstore.Status{}

	err := f.GenerateStruct(&metadata)
	if err != nil {
		return containerstore.Container{}, err
	}
	err = f.GenerateStruct(&status)
	if err != nil {
		return containerstore.Container{}, err
	}
	container, err := containerstore.NewContainer(metadata, containerstore.WithFakeStatus(status))
	return container, err
}

func FuzzParseAuth(data []byte) int {
	f := fuzz.NewConsumer(data)
	auth := &runtime.AuthConfig{}
	err := f.GenerateStruct(auth)
	if err != nil {
		return 0
	}
	host, err := f.GetString()
	if err != nil {
		return 0
	}
	_, _, _ = server.ParseAuth(auth, host)
	return 1
}
