/*
 * MinIO Cloud Storage, (C) 2017, 2018 MinIO, Inc.
 *
 * 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 cmd

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"testing"

	humanize "github.com/dustin/go-humanize"
	"github.com/klauspost/compress/zstd"
	"github.com/minio/minio-go/v7/pkg/encrypt"
	"github.com/minio/minio/cmd/crypto"
	xhttp "github.com/minio/minio/cmd/http"
	"github.com/minio/sio"
)

var encryptRequestTests = []struct {
	header   map[string]string
	metadata map[string]string
}{
	{
		header: map[string]string{
			xhttp.AmzServerSideEncryptionCustomerAlgorithm: "AES256",
			xhttp.AmzServerSideEncryptionCustomerKey:       "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
			xhttp.AmzServerSideEncryptionCustomerKeyMD5:    "bY4wkxQejw9mUJfo72k53A==",
		},
		metadata: map[string]string{},
	},
	{
		header: map[string]string{
			xhttp.AmzServerSideEncryptionCustomerAlgorithm: "AES256",
			xhttp.AmzServerSideEncryptionCustomerKey:       "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
			xhttp.AmzServerSideEncryptionCustomerKeyMD5:    "bY4wkxQejw9mUJfo72k53A==",
		},
		metadata: map[string]string{
			xhttp.AmzServerSideEncryptionCustomerKey: "XAm0dRrJsEsyPb1UuFNezv1bl9hxuYsgUVC/MUctE2k=",
		},
	},
}

func TestEncryptRequest(t *testing.T) {
	defer func(flag bool) { globalIsTLS = flag }(globalIsTLS)
	globalIsTLS = true
	for i, test := range encryptRequestTests {
		content := bytes.NewReader(make([]byte, 64))
		req := &http.Request{Header: http.Header{}}
		for k, v := range test.header {
			req.Header.Set(k, v)
		}
		_, _, err := EncryptRequest(content, req, "bucket", "object", test.metadata)

		if err != nil {
			t.Fatalf("Test %d: Failed to encrypt request: %v", i, err)
		}
		if kdf, ok := test.metadata[crypto.MetaAlgorithm]; !ok {
			t.Errorf("Test %d: ServerSideEncryptionKDF must be part of metadata: %v", i, kdf)
		}
		if iv, ok := test.metadata[crypto.MetaIV]; !ok {
			t.Errorf("Test %d: crypto.SSEIV must be part of metadata: %v", i, iv)
		}
		if mac, ok := test.metadata[crypto.MetaSealedKeySSEC]; !ok {
			t.Errorf("Test %d: ServerSideEncryptionKeyMAC must be part of metadata: %v", i, mac)
		}
	}
}

var decryptObjectInfoTests = []struct {
	info    ObjectInfo
	request *http.Request
	expErr  error
}{
	{
		info:    ObjectInfo{Size: 100},
		request: &http.Request{Header: http.Header{}},
		expErr:  nil,
	},
	{
		info:    ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}},
		request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryption: []string{xhttp.AmzEncryptionAES}}},
		expErr:  nil,
	},
	{
		info:    ObjectInfo{Size: 0, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}},
		request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryption: []string{xhttp.AmzEncryptionAES}}},
		expErr:  nil,
	},
	{
		info:    ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.MetaSealedKeySSEC: "EAAfAAAAAAD7v1hQq3PFRUHsItalxmrJqrOq6FwnbXNarxOOpb8jTWONPPKyM3Gfjkjyj6NCf+aB/VpHCLCTBA=="}},
		request: &http.Request{Header: http.Header{}},
		expErr:  errEncryptedObject,
	},
	{
		info:    ObjectInfo{Size: 100, UserDefined: map[string]string{}},
		request: &http.Request{Method: http.MethodGet, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}}},
		expErr:  errInvalidEncryptionParameters,
	},
	{
		info:    ObjectInfo{Size: 100, UserDefined: map[string]string{}},
		request: &http.Request{Method: http.MethodHead, Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}}},
		expErr:  errInvalidEncryptionParameters,
	},
	{
		info:    ObjectInfo{Size: 31, UserDefined: map[string]string{crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm}},
		request: &http.Request{Header: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{xhttp.AmzEncryptionAES}}},
		expErr:  errObjectTampered,
	},
}

func TestDecryptObjectInfo(t *testing.T) {
	for i, test := range decryptObjectInfoTests {
		if encrypted, err := DecryptObjectInfo(&test.info, test.request); err != test.expErr {
			t.Errorf("Test %d: Decryption returned wrong error code: got %d , want %d", i, err, test.expErr)
		} else if enc := crypto.IsEncrypted(test.info.UserDefined); encrypted && enc != encrypted {
			t.Errorf("Test %d: Decryption thinks object is encrypted but it is not", i)
		} else if !encrypted && enc != encrypted {
			t.Errorf("Test %d: Decryption thinks object is not encrypted but it is", i)
		}
	}
}

var decryptETagTests = []struct {
	ObjectKey  crypto.ObjectKey
	ObjectInfo ObjectInfo
	ShouldFail bool
	ETag       string
}{
	{
		ObjectKey:  [32]byte{},
		ObjectInfo: ObjectInfo{ETag: "20000f00f27834c9a2654927546df57f9e998187496394d4ee80f3d9978f85f3c7d81f72600cdbe03d80dc5a13d69354"},
		ETag:       "8ad3fe6b84bf38489e95c701c84355b6",
	},
	{
		ObjectKey:  [32]byte{},
		ObjectInfo: ObjectInfo{ETag: "20000f00f27834c9a2654927546df57f9e998187496394d4ee80f3d9978f85f3c7d81f72600cdbe03d80dc5a13d6935"},
		ETag:       "",
		ShouldFail: true, // ETag is not a valid hex value
	},
	{
		ObjectKey:  [32]byte{},
		ObjectInfo: ObjectInfo{ETag: "00000f00f27834c9a2654927546df57f9e998187496394d4ee80f3d9978f85f3c7d81f72600cdbe03d80dc5a13d69354"},
		ETag:       "",
		ShouldFail: true, // modified ETag
	},

	// Special tests for ETags that end with a '-x'
	{
		ObjectKey:  [32]byte{},
		ObjectInfo: ObjectInfo{ETag: "916516b396f0f4d4f2a0e7177557bec4-1"},
		ETag:       "916516b396f0f4d4f2a0e7177557bec4-1",
	},
	{
		ObjectKey:  [32]byte{},
		ObjectInfo: ObjectInfo{ETag: "916516b396f0f4d4f2a0e7177557bec4-738"},
		ETag:       "916516b396f0f4d4f2a0e7177557bec4-738",
	},
	{
		ObjectKey:  [32]byte{},
		ObjectInfo: ObjectInfo{ETag: "916516b396f0f4d4f2a0e7177557bec4-Q"},
		ETag:       "",
		ShouldFail: true, // Q is not a number
	},
	{
		ObjectKey:  [32]byte{},
		ObjectInfo: ObjectInfo{ETag: "16516b396f0f4d4f2a0e7177557bec4-1"},
		ETag:       "",
		ShouldFail: true, // ETag prefix is not a valid hex value
	},
	{
		ObjectKey:  [32]byte{},
		ObjectInfo: ObjectInfo{ETag: "16516b396f0f4d4f2a0e7177557bec4-1-2"},
		ETag:       "",
		ShouldFail: true, // ETag contains multiple: -
	},
}

func TestDecryptETag(t *testing.T) {
	for i, test := range decryptETagTests {
		etag, err := DecryptETag(test.ObjectKey, test.ObjectInfo)
		if err != nil && !test.ShouldFail {
			t.Fatalf("Test %d: should succeed but failed: %v", i, err)
		}
		if err == nil && test.ShouldFail {
			t.Fatalf("Test %d: should fail but succeeded", i)
		}
		if err == nil {
			if etag != test.ETag {
				t.Fatalf("Test %d: ETag mismatch: got %s - want %s", i, etag, test.ETag)
			}
		}
	}
}

// Tests for issue reproduced when getting the right encrypted
// offset of the object.
func TestGetDecryptedRange_Issue50(t *testing.T) {
	rs, err := parseRequestRangeSpec("bytes=594870256-594870263")
	if err != nil {
		t.Fatal(err)
	}

	objInfo := ObjectInfo{
		Bucket: "bucket",
		Name:   "object",
		Size:   595160760,
		UserDefined: map[string]string{
			crypto.MetaMultipart:                   "",
			crypto.MetaIV:                          "HTexa=",
			crypto.MetaAlgorithm:                   "DAREv2-HMAC-SHA256",
			crypto.MetaSealedKeySSEC:               "IAA8PGAA==",
			ReservedMetadataPrefix + "actual-size": "594870264",
			"content-type":                         "application/octet-stream",
			"etag":                                 "166b1545b4c1535294ee0686678bea8c-2",
		},
		Parts: []ObjectPartInfo{
			{
				Number:     1,
				Size:       297580380,
				ActualSize: 297435132,
			},
			{
				Number:     2,
				Size:       297580380,
				ActualSize: 297435132,
			},
		},
	}

	encOff, encLength, skipLen, seqNumber, partStart, err := objInfo.GetDecryptedRange(rs)
	if err != nil {
		t.Fatalf("Test: failed %s", err)
	}
	if encOff != 595127964 {
		t.Fatalf("Test: expected %d, got %d", 595127964, encOff)
	}
	if encLength != 32796 {
		t.Fatalf("Test: expected %d, got %d", 32796, encLength)
	}
	if skipLen != 32756 {
		t.Fatalf("Test: expected %d, got %d", 32756, skipLen)
	}
	if seqNumber != 4538 {
		t.Fatalf("Test: expected %d, got %d", 4538, seqNumber)
	}
	if partStart != 1 {
		t.Fatalf("Test: expected %d, got %d", 1, partStart)
	}
}

func TestGetDecryptedRange(t *testing.T) {
	var (
		pkgSz     = int64(64) * humanize.KiByte
		minPartSz = int64(5) * humanize.MiByte
		maxPartSz = int64(5) * humanize.GiByte

		getEncSize = func(s int64) int64 {
			v, _ := sio.EncryptedSize(uint64(s))
			return int64(v)
		}
		udMap = func(isMulti bool) map[string]string {
			m := map[string]string{
				crypto.MetaAlgorithm: crypto.InsecureSealAlgorithm,
				crypto.MetaMultipart: "1",
			}
			if !isMulti {
				delete(m, crypto.MetaMultipart)
			}
			return m
		}
	)

	// Single part object tests
	var (
		mkSPObj = func(s int64) ObjectInfo {
			return ObjectInfo{
				Size:        getEncSize(s),
				UserDefined: udMap(false),
			}
		}
	)

	testSP := []struct {
		decSz int64
		oi    ObjectInfo
	}{
		{0, mkSPObj(0)},
		{1, mkSPObj(1)},
		{pkgSz - 1, mkSPObj(pkgSz - 1)},
		{pkgSz, mkSPObj(pkgSz)},
		{2*pkgSz - 1, mkSPObj(2*pkgSz - 1)},
		{minPartSz, mkSPObj(minPartSz)},
		{maxPartSz, mkSPObj(maxPartSz)},
	}

	for i, test := range testSP {
		{
			// nil range
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(nil)
			if err != nil {
				t.Errorf("Case %d: unexpected err: %v", i, err)
			}
			if skip != 0 || sn != 0 || ps != 0 || o != 0 || l != getEncSize(test.decSz) {
				t.Errorf("Case %d: test failed: %d %d %d %d %d", i, o, l, skip, sn, ps)
			}
		}

		if test.decSz >= 10 {
			// first 10 bytes
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(&HTTPRangeSpec{false, 0, 9})
			if err != nil {
				t.Errorf("Case %d: unexpected err: %v", i, err)
			}
			var rLen = pkgSz + 32
			if test.decSz < pkgSz {
				rLen = test.decSz + 32
			}
			if skip != 0 || sn != 0 || ps != 0 || o != 0 || l != rLen {
				t.Errorf("Case %d: test failed: %d %d %d %d %d", i, o, l, skip, sn, ps)
			}
		}

		kb32 := int64(32) * humanize.KiByte
		if test.decSz >= (64+32)*humanize.KiByte {
			// Skip the first 32Kib, and read the next 64Kib
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(&HTTPRangeSpec{false, kb32, 3*kb32 - 1})
			if err != nil {
				t.Errorf("Case %d: unexpected err: %v", i, err)
			}
			var rLen = (pkgSz + 32) * 2
			if test.decSz < 2*pkgSz {
				rLen = (pkgSz + 32) + (test.decSz - pkgSz + 32)
			}
			if skip != kb32 || sn != 0 || ps != 0 || o != 0 || l != rLen {
				t.Errorf("Case %d: test failed: %d %d %d %d %d", i, o, l, skip, sn, ps)
			}
		}

		if test.decSz >= (64*2+32)*humanize.KiByte {
			// Skip the first 96Kib and read the next 64Kib
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(&HTTPRangeSpec{false, 3 * kb32, 5*kb32 - 1})
			if err != nil {
				t.Errorf("Case %d: unexpected err: %v", i, err)
			}
			var rLen = (pkgSz + 32) * 2
			if test.decSz-pkgSz < 2*pkgSz {
				rLen = (pkgSz + 32) + (test.decSz - pkgSz + 32*2)
			}
			if skip != kb32 || sn != 1 || ps != 0 || o != pkgSz+32 || l != rLen {
				t.Errorf("Case %d: test failed: %d %d %d %d %d", i, o, l, skip, sn, ps)
			}
		}

	}

	// Multipart object tests
	var (
		// make a multipart object-info given part sizes
		mkMPObj = func(sizes []int64) ObjectInfo {
			r := make([]ObjectPartInfo, len(sizes))
			sum := int64(0)
			for i, s := range sizes {
				r[i].Number = i
				r[i].Size = getEncSize(s)
				sum += r[i].Size
			}
			return ObjectInfo{
				Size:        sum,
				UserDefined: udMap(true),
				Parts:       r,
			}
		}
		// Simple useful utilities
		repeat = func(k int64, n int) []int64 {
			a := []int64{}
			for i := 0; i < n; i++ {
				a = append(a, k)
			}
			return a
		}
		lsum = func(s []int64) int64 {
			sum := int64(0)
			for _, i := range s {
				if i < 0 {
					return -1
				}
				sum += i
			}
			return sum
		}
		esum = func(oi ObjectInfo) int64 {
			sum := int64(0)
			for _, i := range oi.Parts {
				sum += i.Size
			}
			return sum
		}
	)

	s1 := []int64{5487701, 5487799, 3}
	s2 := repeat(5487701, 5)
	s3 := repeat(maxPartSz, 10000)
	testMPs := []struct {
		decSizes []int64
		oi       ObjectInfo
	}{
		{s1, mkMPObj(s1)},
		{s2, mkMPObj(s2)},
		{s3, mkMPObj(s3)},
	}

	// This function is a reference (re-)implementation of
	// decrypted range computation, written solely for the purpose
	// of the unit tests.
	//
	// `s` gives the decrypted part sizes, and the other
	// parameters describe the desired read segment. When
	// `isFromEnd` is true, `skipLen` argument is ignored.
	decryptedRangeRef := func(s []int64, skipLen, readLen int64, isFromEnd bool) (o, l, skip int64, sn uint32, ps int) {
		oSize := lsum(s)
		if isFromEnd {
			skipLen = oSize - readLen
		}
		if skipLen < 0 || readLen < 0 || oSize < 0 || skipLen+readLen > oSize {
			t.Fatalf("Impossible read specified: %d %d %d", skipLen, readLen, oSize)
		}

		var cumulativeSum, cumulativeEncSum int64
		toRead := readLen
		readStart := false
		for i, v := range s {
			partOffset := int64(0)
			partDarePkgOffset := int64(0)
			if !readStart && cumulativeSum+v > skipLen {
				// Read starts at the current part
				readStart = true

				partOffset = skipLen - cumulativeSum

				// All return values except `l` are
				// calculated here.
				sn = uint32(partOffset / pkgSz)
				skip = partOffset % pkgSz
				ps = i
				o = cumulativeEncSum + int64(sn)*(pkgSz+32)

				partDarePkgOffset = partOffset - skip
			}
			if readStart {
				currentPartBytes := v - partOffset
				currentPartDareBytes := v - partDarePkgOffset
				if currentPartBytes < toRead {
					toRead -= currentPartBytes
					l += getEncSize(currentPartDareBytes)
				} else {
					// current part has the last
					// byte required
					lbPartOffset := partOffset + toRead - 1

					// round up the lbPartOffset
					// to the end of the
					// corresponding DARE package
					lbPkgEndOffset := lbPartOffset - (lbPartOffset % pkgSz) + pkgSz
					if lbPkgEndOffset > v {
						lbPkgEndOffset = v
					}
					bytesToDrop := v - lbPkgEndOffset

					// Last segment to update `l`
					l += getEncSize(currentPartDareBytes - bytesToDrop)
					break
				}
			}

			cumulativeSum += v
			cumulativeEncSum += getEncSize(v)
		}
		return
	}

	for i, test := range testMPs {
		{
			// nil range
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(nil)
			if err != nil {
				t.Errorf("Case %d: unexpected err: %v", i, err)
			}
			if o != 0 || l != esum(test.oi) || skip != 0 || sn != 0 || ps != 0 {
				t.Errorf("Case %d: test failed: %d %d %d %d %d", i, o, l, skip, sn, ps)
			}
		}

		// Skip 1Mib and read 1Mib (in the decrypted object)
		//
		// The check below ensures the object is large enough
		// for the read.
		if lsum(test.decSizes) >= 2*humanize.MiByte {
			skipLen, readLen := int64(1)*humanize.MiByte, int64(1)*humanize.MiByte
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(&HTTPRangeSpec{false, skipLen, skipLen + readLen - 1})
			if err != nil {
				t.Errorf("Case %d: unexpected err: %v", i, err)
			}

			oRef, lRef, skipRef, snRef, psRef := decryptedRangeRef(test.decSizes, skipLen, readLen, false)
			if o != oRef || l != lRef || skip != skipRef || sn != snRef || ps != psRef {
				t.Errorf("Case %d: test failed: %d %d %d %d %d (Ref: %d %d %d %d %d)",
					i, o, l, skip, sn, ps, oRef, lRef, skipRef, snRef, psRef)
			}
		}

		// Read the last 6Mib+1 bytes of the (decrypted)
		// object
		//
		// The check below ensures the object is large enough
		// for the read.
		readLen := int64(6)*humanize.MiByte + 1
		if lsum(test.decSizes) >= readLen {
			o, l, skip, sn, ps, err := test.oi.GetDecryptedRange(&HTTPRangeSpec{true, -readLen, -1})
			if err != nil {
				t.Errorf("Case %d: unexpected err: %v", i, err)
			}

			oRef, lRef, skipRef, snRef, psRef := decryptedRangeRef(test.decSizes, 0, readLen, true)
			if o != oRef || l != lRef || skip != skipRef || sn != snRef || ps != psRef {
				t.Errorf("Case %d: test failed: %d %d %d %d %d (Ref: %d %d %d %d %d)",
					i, o, l, skip, sn, ps, oRef, lRef, skipRef, snRef, psRef)
			}
		}

	}
}

var getDefaultOptsTests = []struct {
	headers        http.Header
	copySource     bool
	metadata       map[string]string
	encryptionType encrypt.Type
	err            error
}{
	{headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"},
		xhttp.AmzServerSideEncryptionCustomerKey:    []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
		xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
		copySource:     false,
		metadata:       nil,
		encryptionType: encrypt.SSEC,
		err:            nil}, // 0
	{headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"},
		xhttp.AmzServerSideEncryptionCustomerKey:    []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
		xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
		copySource:     true,
		metadata:       nil,
		encryptionType: "",
		err:            nil}, // 1
	{headers: http.Header{xhttp.AmzServerSideEncryptionCustomerAlgorithm: []string{"AES256"},
		xhttp.AmzServerSideEncryptionCustomerKey:    []string{"Mz"},
		xhttp.AmzServerSideEncryptionCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
		copySource:     false,
		metadata:       nil,
		encryptionType: "",
		err:            crypto.ErrInvalidCustomerKey}, // 2
	{headers: http.Header{xhttp.AmzServerSideEncryption: []string{"AES256"}},
		copySource:     false,
		metadata:       nil,
		encryptionType: encrypt.S3,
		err:            nil}, // 3
	{headers: http.Header{},
		copySource: false,
		metadata: map[string]string{crypto.MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)),
			crypto.MetaKeyID:             "kms-key",
			crypto.MetaDataEncryptionKey: "m-key"},
		encryptionType: encrypt.S3,
		err:            nil}, // 4
	{headers: http.Header{},
		copySource: true,
		metadata: map[string]string{crypto.MetaSealedKeyS3: base64.StdEncoding.EncodeToString(make([]byte, 64)),
			crypto.MetaKeyID:             "kms-key",
			crypto.MetaDataEncryptionKey: "m-key"},
		encryptionType: "",
		err:            nil}, // 5
	{headers: http.Header{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{"AES256"},
		xhttp.AmzServerSideEncryptionCopyCustomerKey:    []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
		xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
		copySource:     true,
		metadata:       nil,
		encryptionType: encrypt.SSEC,
		err:            nil}, // 6
	{headers: http.Header{xhttp.AmzServerSideEncryptionCopyCustomerAlgorithm: []string{"AES256"},
		xhttp.AmzServerSideEncryptionCopyCustomerKey:    []string{"MzJieXRlc2xvbmdzZWNyZXRrZXltdXN0cHJvdmlkZWQ="},
		xhttp.AmzServerSideEncryptionCopyCustomerKeyMD5: []string{"7PpPLAK26ONlVUGOWlusfg=="}},
		copySource:     false,
		metadata:       nil,
		encryptionType: "",
		err:            nil}, // 7
}

func TestGetDefaultOpts(t *testing.T) {
	for i, test := range getDefaultOptsTests {
		opts, err := getDefaultOpts(test.headers, test.copySource, test.metadata)
		if test.err != err {
			t.Errorf("Case %d: expected err: %v , actual err: %v", i, test.err, err)
		}
		if err == nil {
			if opts.ServerSideEncryption == nil && test.encryptionType != "" {
				t.Errorf("Case %d: expected opts to be of %v encryption type", i, test.encryptionType)

			}
			if opts.ServerSideEncryption != nil && test.encryptionType != opts.ServerSideEncryption.Type() {
				t.Errorf("Case %d: expected opts to have encryption type %v but was %v ", i, test.encryptionType, opts.ServerSideEncryption.Type())
			}
		}
	}
}
func Test_decryptObjectInfo(t *testing.T) {
	var testSet []struct {
		Bucket  string
		Name    string
		UserDef map[string]string
	}
	file, err := os.Open("testdata/decryptObjectInfo.json.zst")
	if err != nil {
		t.Fatal(err)
	}
	defer file.Close()
	dec, err := zstd.NewReader(file)
	if err != nil {
		t.Fatal(err)
	}
	defer dec.Close()
	js := json.NewDecoder(dec)
	err = js.Decode(&testSet)
	if err != nil {
		t.Fatal(err)
	}

	os.Setenv("MINIO_KMS_MASTER_KEY", "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574")
	defer os.Setenv("MINIO_KMS_MASTER_KEY", "")
	GlobalKMS, err = crypto.NewKMS(crypto.KMSConfig{})
	if err != nil {
		t.Fatal(err)
	}

	var dst [32]byte
	for i := range testSet {
		t.Run(fmt.Sprint("case-", i), func(t *testing.T) {
			test := &testSet[i]
			_, err := decryptObjectInfo(dst[:], test.Bucket, test.Name, test.UserDef)
			if err != nil {
				t.Fatal(err)
			}
		})
	}
}

func Benchmark_decryptObjectInfo(b *testing.B) {
	var testSet []struct {
		Bucket  string
		Name    string
		UserDef map[string]string
	}
	file, err := os.Open("testdata/decryptObjectInfo.json.zst")
	if err != nil {
		b.Fatal(err)
	}
	defer file.Close()
	dec, err := zstd.NewReader(file)
	if err != nil {
		b.Fatal(err)
	}
	defer dec.Close()
	js := json.NewDecoder(dec)
	err = js.Decode(&testSet)
	if err != nil {
		b.Fatal(err)
	}

	os.Setenv("MINIO_KMS_MASTER_KEY", "my-minio-key:6368616e676520746869732070617373776f726420746f206120736563726574")
	defer os.Setenv("MINIO_KMS_MASTER_KEY", "")
	GlobalKMS, err = crypto.NewKMS(crypto.KMSConfig{})
	if err != nil {
		b.Fatal(err)
	}

	b.ReportAllocs()
	b.ResetTimer()
	b.SetBytes(int64(len(testSet)))
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			var dst [32]byte
			for i := range testSet {
				test := &testSet[i]
				_, err := decryptObjectInfo(dst[:], test.Bucket, test.Name, test.UserDef)
				if err != nil {
					b.Fatal(err)
				}
			}
		}
	})
}
