// Copyright 2023 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.

package x509

import (
	"encoding"
	"encoding/asn1"
	"math"
	"testing"
)

var oidTests = []struct {
	raw   []byte
	valid bool
	str   string
	ints  []uint64
}{
	{[]byte{}, false, "", nil},
	{[]byte{0x80, 0x01}, false, "", nil},
	{[]byte{0x01, 0x80, 0x01}, false, "", nil},

	{[]byte{1, 2, 3}, true, "0.1.2.3", []uint64{0, 1, 2, 3}},
	{[]byte{41, 2, 3}, true, "1.1.2.3", []uint64{1, 1, 2, 3}},
	{[]byte{86, 2, 3}, true, "2.6.2.3", []uint64{2, 6, 2, 3}},

	{[]byte{41, 255, 255, 255, 127}, true, "1.1.268435455", []uint64{1, 1, 268435455}},
	{[]byte{41, 0x87, 255, 255, 255, 127}, true, "1.1.2147483647", []uint64{1, 1, 2147483647}},
	{[]byte{41, 255, 255, 255, 255, 127}, true, "1.1.34359738367", []uint64{1, 1, 34359738367}},
	{[]byte{42, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "1.2.9223372036854775807", []uint64{1, 2, 9223372036854775807}},
	{[]byte{43, 0x81, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "1.3.18446744073709551615", []uint64{1, 3, 18446744073709551615}},
	{[]byte{44, 0x83, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "1.4.36893488147419103231", nil},
	{[]byte{85, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.5.1180591620717411303423", nil},
	{[]byte{85, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.5.19342813113834066795298815", nil},

	{[]byte{255, 255, 255, 127}, true, "2.268435375", []uint64{2, 268435375}},
	{[]byte{0x87, 255, 255, 255, 127}, true, "2.2147483567", []uint64{2, 2147483567}},
	{[]byte{255, 127}, true, "2.16303", []uint64{2, 16303}},
	{[]byte{255, 255, 255, 255, 127}, true, "2.34359738287", []uint64{2, 34359738287}},
	{[]byte{255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.9223372036854775727", []uint64{2, 9223372036854775727}},
	{[]byte{0x81, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.18446744073709551535", []uint64{2, 18446744073709551535}},
	{[]byte{0x83, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.36893488147419103151", nil},
	{[]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.1180591620717411303343", nil},
	{[]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127}, true, "2.19342813113834066795298735", nil},

	{[]byte{41, 0x80 | 66, 0x80 | 44, 0x80 | 11, 33}, true, "1.1.139134369", []uint64{1, 1, 139134369}},
	{[]byte{0x80 | 66, 0x80 | 44, 0x80 | 11, 33}, true, "2.139134289", []uint64{2, 139134289}},
}

func TestOID(t *testing.T) {
	for _, v := range oidTests {
		oid, ok := newOIDFromDER(v.raw)
		if ok != v.valid {
			t.Errorf("newOIDFromDER(%v) = (%v, %v); want = (OID, %v)", v.raw, oid, ok, v.valid)
			continue
		}

		if !ok {
			continue
		}

		if str := oid.String(); str != v.str {
			t.Errorf("(%#v).String() = %v, want; %v", oid, str, v.str)
		}

		var asn1OID asn1.ObjectIdentifier
		for _, v := range v.ints {
			if v > math.MaxInt32 {
				asn1OID = nil
				break
			}
			asn1OID = append(asn1OID, int(v))
		}

		o, ok := oid.toASN1OID()
		if shouldOk := asn1OID != nil; shouldOk != ok {
			t.Errorf("(%#v).toASN1OID() = (%v, %v); want = (%v, %v)", oid, o, ok, asn1OID, shouldOk)
			continue
		}

		if asn1OID != nil && !o.Equal(asn1OID) {
			t.Errorf("(%#v).toASN1OID() = (%v, true); want = (%v, true)", oid, o, asn1OID)
		}

		if v.ints != nil {
			oid2, err := OIDFromInts(v.ints)
			if err != nil {
				t.Errorf("OIDFromInts(%v) = (%v, %v); want = (%v, nil)", v.ints, oid2, err, oid)
			}
			if !oid2.Equal(oid) {
				t.Errorf("OIDFromInts(%v) = (%v, nil); want = (%v, nil)", v.ints, oid2, oid)
			}
		}
	}
}

func TestInvalidOID(t *testing.T) {
	cases := []struct {
		str  string
		ints []uint64
	}{
		{str: "", ints: []uint64{}},
		{str: "1", ints: []uint64{1}},
		{str: "3", ints: []uint64{3}},
		{str: "3.100.200", ints: []uint64{3, 100, 200}},
		{str: "1.81", ints: []uint64{1, 81}},
		{str: "1.81.200", ints: []uint64{1, 81, 200}},
	}

	for _, tt := range cases {
		oid, err := OIDFromInts(tt.ints)
		if err == nil {
			t.Errorf("OIDFromInts(%v) = (%v, %v); want = (OID{}, %v)", tt.ints, oid, err, errInvalidOID)
		}

		oid2, err := ParseOID(tt.str)
		if err == nil {
			t.Errorf("ParseOID(%v) = (%v, %v); want = (OID{}, %v)", tt.str, oid2, err, errInvalidOID)
		}

		var oid3 OID
		err = oid3.UnmarshalText([]byte(tt.str))
		if err == nil {
			t.Errorf("(*OID).UnmarshalText(%v) = (%v, %v); want = (OID{}, %v)", tt.str, oid3, err, errInvalidOID)
		}
	}
}

func TestOIDEqual(t *testing.T) {
	var cases = []struct {
		oid  OID
		oid2 OID
		eq   bool
	}{
		{oid: mustNewOIDFromInts([]uint64{1, 2, 3}), oid2: mustNewOIDFromInts([]uint64{1, 2, 3}), eq: true},
		{oid: mustNewOIDFromInts([]uint64{1, 2, 3}), oid2: mustNewOIDFromInts([]uint64{1, 2, 4}), eq: false},
		{oid: mustNewOIDFromInts([]uint64{1, 2, 3}), oid2: mustNewOIDFromInts([]uint64{1, 2, 3, 4}), eq: false},
		{oid: mustNewOIDFromInts([]uint64{2, 33, 22}), oid2: mustNewOIDFromInts([]uint64{2, 33, 23}), eq: false},
		{oid: OID{}, oid2: OID{}, eq: true},
		{oid: OID{}, oid2: mustNewOIDFromInts([]uint64{2, 33, 23}), eq: false},
	}

	for _, tt := range cases {
		if eq := tt.oid.Equal(tt.oid2); eq != tt.eq {
			t.Errorf("(%v).Equal(%v) = %v, want %v", tt.oid, tt.oid2, eq, tt.eq)
		}
	}
}

var (
	_ encoding.BinaryMarshaler   = OID{}
	_ encoding.BinaryUnmarshaler = new(OID)
	_ encoding.TextMarshaler     = OID{}
	_ encoding.TextUnmarshaler   = new(OID)
)

func TestOIDMarshal(t *testing.T) {
	cases := []struct {
		in  string
		out OID
		err error
	}{
		{in: "", err: errInvalidOID},
		{in: "0", err: errInvalidOID},
		{in: "1", err: errInvalidOID},
		{in: ".1", err: errInvalidOID},
		{in: ".1.", err: errInvalidOID},
		{in: "1.", err: errInvalidOID},
		{in: "1..", err: errInvalidOID},
		{in: "1.2.", err: errInvalidOID},
		{in: "1.2.333.", err: errInvalidOID},
		{in: "1.2.333..", err: errInvalidOID},
		{in: "1.2..", err: errInvalidOID},
		{in: "+1.2", err: errInvalidOID},
		{in: "-1.2", err: errInvalidOID},
		{in: "1.-2", err: errInvalidOID},
		{in: "1.2.+333", err: errInvalidOID},
	}

	for _, v := range oidTests {
		oid, ok := newOIDFromDER(v.raw)
		if !ok {
			continue
		}
		cases = append(cases, struct {
			in  string
			out OID
			err error
		}{
			in:  v.str,
			out: oid,
			err: nil,
		})
	}

	for _, tt := range cases {
		o, err := ParseOID(tt.in)
		if err != tt.err {
			t.Errorf("ParseOID(%q) = %v; want = %v", tt.in, err, tt.err)
			continue
		}

		var o2 OID
		err = o2.UnmarshalText([]byte(tt.in))
		if err != tt.err {
			t.Errorf("(*OID).UnmarshalText(%q) = %v; want = %v", tt.in, err, tt.err)
			continue
		}

		if err != nil {
			continue
		}

		if !o.Equal(tt.out) {
			t.Errorf("(*OID).UnmarshalText(%q) = %v; want = %v", tt.in, o, tt.out)
			continue
		}

		if !o2.Equal(tt.out) {
			t.Errorf("ParseOID(%q) = %v; want = %v", tt.in, o2, tt.out)
			continue
		}

		marshalled, err := o.MarshalText()
		if string(marshalled) != tt.in || err != nil {
			t.Errorf("(%#v).MarshalText() = (%v, %v); want = (%v, nil)", o, string(marshalled), err, tt.in)
			continue
		}

		textAppend := make([]byte, 4)
		textAppend, err = o.AppendText(textAppend)
		textAppend = textAppend[4:]
		if string(textAppend) != tt.in || err != nil {
			t.Errorf("(%#v).AppendText() = (%v, %v); want = (%v, nil)", o, string(textAppend), err, tt.in)
			continue
		}

		binary, err := o.MarshalBinary()
		if err != nil {
			t.Errorf("(%#v).MarshalBinary() = %v; want = nil", o, err)
		}

		var o3 OID
		if err := o3.UnmarshalBinary(binary); err != nil {
			t.Errorf("(*OID).UnmarshalBinary(%v) = %v; want = nil", binary, err)
		}

		if !o3.Equal(tt.out) {
			t.Errorf("(*OID).UnmarshalBinary(%v) = %v; want = %v", binary, o3, tt.out)
			continue
		}

		binaryAppend := make([]byte, 4)
		binaryAppend, err = o.AppendBinary(binaryAppend)
		binaryAppend = binaryAppend[4:]
		if err != nil {
			t.Errorf("(%#v).AppendBinary() = %v; want = nil", o, err)
		}

		var o4 OID
		if err := o4.UnmarshalBinary(binaryAppend); err != nil {
			t.Errorf("(*OID).UnmarshalBinary(%v) = %v; want = nil", binaryAppend, err)
		}

		if !o4.Equal(tt.out) {
			t.Errorf("(*OID).UnmarshalBinary(%v) = %v; want = %v", binaryAppend, o4, tt.out)
			continue
		}
	}
}

func TestOIDEqualASN1OID(t *testing.T) {
	maxInt32PlusOne := int64(math.MaxInt32) + 1
	var cases = []struct {
		oid  OID
		oid2 asn1.ObjectIdentifier
		eq   bool
	}{
		{oid: mustNewOIDFromInts([]uint64{1, 2, 3}), oid2: asn1.ObjectIdentifier{1, 2, 3}, eq: true},
		{oid: mustNewOIDFromInts([]uint64{1, 2, 3}), oid2: asn1.ObjectIdentifier{1, 2, 4}, eq: false},
		{oid: mustNewOIDFromInts([]uint64{1, 2, 3}), oid2: asn1.ObjectIdentifier{1, 2, 3, 4}, eq: false},
		{oid: mustNewOIDFromInts([]uint64{1, 33, 22}), oid2: asn1.ObjectIdentifier{1, 33, 23}, eq: false},
		{oid: mustNewOIDFromInts([]uint64{1, 33, 23}), oid2: asn1.ObjectIdentifier{1, 33, 22}, eq: false},
		{oid: mustNewOIDFromInts([]uint64{1, 33, 127}), oid2: asn1.ObjectIdentifier{1, 33, 127}, eq: true},
		{oid: mustNewOIDFromInts([]uint64{1, 33, 128}), oid2: asn1.ObjectIdentifier{1, 33, 127}, eq: false},
		{oid: mustNewOIDFromInts([]uint64{1, 33, 128}), oid2: asn1.ObjectIdentifier{1, 33, 128}, eq: true},
		{oid: mustNewOIDFromInts([]uint64{1, 33, 129}), oid2: asn1.ObjectIdentifier{1, 33, 129}, eq: true},
		{oid: mustNewOIDFromInts([]uint64{1, 33, 128}), oid2: asn1.ObjectIdentifier{1, 33, 129}, eq: false},
		{oid: mustNewOIDFromInts([]uint64{1, 33, 129}), oid2: asn1.ObjectIdentifier{1, 33, 128}, eq: false},
		{oid: mustNewOIDFromInts([]uint64{1, 33, 255}), oid2: asn1.ObjectIdentifier{1, 33, 255}, eq: true},
		{oid: mustNewOIDFromInts([]uint64{1, 33, 256}), oid2: asn1.ObjectIdentifier{1, 33, 256}, eq: true},
		{oid: mustNewOIDFromInts([]uint64{2, 33, 257}), oid2: asn1.ObjectIdentifier{2, 33, 256}, eq: false},
		{oid: mustNewOIDFromInts([]uint64{2, 33, 256}), oid2: asn1.ObjectIdentifier{2, 33, 257}, eq: false},

		{oid: mustNewOIDFromInts([]uint64{1, 33}), oid2: asn1.ObjectIdentifier{1, 33, math.MaxInt32}, eq: false},
		{oid: mustNewOIDFromInts([]uint64{1, 33, math.MaxInt32}), oid2: asn1.ObjectIdentifier{1, 33}, eq: false},
		{oid: mustNewOIDFromInts([]uint64{1, 33, math.MaxInt32}), oid2: asn1.ObjectIdentifier{1, 33, math.MaxInt32}, eq: true},
		{
			oid:  mustNewOIDFromInts([]uint64{1, 33, math.MaxInt32 + 1}),
			oid2: asn1.ObjectIdentifier{1, 33 /*convert to int, so that it compiles on 32bit*/, int(maxInt32PlusOne)},
			eq:   false,
		},

		{oid: mustNewOIDFromInts([]uint64{1, 33, 256}), oid2: asn1.ObjectIdentifier{}, eq: false},
		{oid: OID{}, oid2: asn1.ObjectIdentifier{1, 33, 256}, eq: false},
		{oid: OID{}, oid2: asn1.ObjectIdentifier{}, eq: false},
	}

	for _, tt := range cases {
		if eq := tt.oid.EqualASN1OID(tt.oid2); eq != tt.eq {
			t.Errorf("(%v).EqualASN1OID(%v) = %v, want %v", tt.oid, tt.oid2, eq, tt.eq)
		}
	}
}

func TestOIDUnmarshalBinary(t *testing.T) {
	for _, tt := range oidTests {
		var o OID
		err := o.UnmarshalBinary(tt.raw)

		expectErr := errInvalidOID
		if tt.valid {
			expectErr = nil
		}

		if err != expectErr {
			t.Errorf("(o *OID).UnmarshalBinary(%v) = %v; want = %v; (o = %v)", tt.raw, err, expectErr, o)
		}
	}
}

func BenchmarkOIDMarshalUnmarshalText(b *testing.B) {
	oid := mustNewOIDFromInts([]uint64{1, 2, 3, 9999, 1024})
	for range b.N {
		text, err := oid.MarshalText()
		if err != nil {
			b.Fatal(err)
		}
		var o OID
		if err := o.UnmarshalText(text); err != nil {
			b.Fatal(err)
		}
	}
}
