// Copyright 2017 Google LLC. 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 (
	"bytes"
	"encoding/hex"
	"net"
	"reflect"
	"strings"
	"testing"

	"github.com/google/certificate-transparency-go/asn1"
	"github.com/google/certificate-transparency-go/x509/pkix"
)

func TestParseGeneralNames(t *testing.T) {
	var tests = []struct {
		data    string // as hex
		want    GeneralNames
		wantErr string
	}{
		{
			data: ("3012" +
				("8210" + "7777772e676f6f676c652e636f2e756b")),
			want: GeneralNames{
				DNSNames: []string{"www.google.co.uk"},
			},
		},
		{
			data: ("3024" +
				("8210" + "7777772e676f6f676c652e636f2e756b") +
				("8610" + "7777772e676f6f676c652e636f2e756b")),
			want: GeneralNames{
				DNSNames: []string{"www.google.co.uk"},
				URIs:     []string{"www.google.co.uk"},
			},
		},
		{
			data:    "0a0101",
			wantErr: "failed to parse GeneralNames sequence",
		},
		{
			data:    "0a",
			wantErr: "failed to parse GeneralNames:",
		},
		{
			data:    "03000a0101",
			wantErr: "trailing data",
		},
		{
			data:    ("3005" + ("8703" + "010203")),
			wantErr: "invalid IP length",
		},
	}
	for _, test := range tests {
		inData := fromHex(test.data)
		var got GeneralNames
		err := parseGeneralNames(inData, &got)
		if err != nil {
			if test.wantErr == "" {
				t.Errorf("parseGeneralNames(%s)=%v; want nil", test.data, err)
			} else if !strings.Contains(err.Error(), test.wantErr) {
				t.Errorf("parseGeneralNames(%s)=%v; want %q", test.data, err, test.wantErr)
			}
			continue
		}
		if test.wantErr != "" {
			t.Errorf("parseGeneralNames(%s)=%+v,nil; want %q", test.data, got, test.wantErr)
			continue
		}
		if !reflect.DeepEqual(got, test.want) {
			t.Errorf("parseGeneralNames(%s)=%+v; want %+v", test.data, got, test.want)
		}
	}
}

func TestParseGeneralName(t *testing.T) {
	var tests = []struct {
		data     string // as hex
		withMask bool
		want     GeneralNames
		wantErr  string
	}{
		{
			data: ("a008" +
				("0603" + "551d0e") + // OID: subject-key-id
				("0a01" + "01")), // enum=1
			want: GeneralNames{
				OtherNames: []OtherName{
					{
						TypeID: OIDExtensionSubjectKeyId,
						Value: asn1.RawValue{
							Class:      asn1.ClassUniversal,
							Tag:        asn1.TagEnum,
							IsCompound: false,
							Bytes:      fromHex("01"),
							FullBytes:  fromHex("0a0101"),
						},
					},
				},
			},
		},
		{
			data: ("8008" +
				("0603" + "551d0e") + // OID: subject-key-id
				("0a01" + "01")), // enum=1
			wantErr: "not compound",
		},
		{
			data: ("a005" +
				("0603" + "551d0e")), // OID: subject-key-id
			wantErr: "sequence truncated",
		},
		{
			data: ("8110" + "77777740676f6f676c652e636f2e756b"),
			want: GeneralNames{
				EmailAddresses: []string{"www@google.co.uk"},
			},
		},
		{
			data: ("8210" + "7777772e676f6f676c652e636f2e756b"),
			want: GeneralNames{
				DNSNames: []string{"www.google.co.uk"},
			},
		},
		{
			data: ("844b" +
				("3049" +
					("310b" +
						("3009" +
							("0603" + "550406") +
							("1302" + "5553"))) + // "US"
					("3113" +
						("3011" +
							("0603" + "55040a") +
							("130a" + "476f6f676c6520496e63"))) + // "Google Inc"
					("3125" +
						("3023" +
							("0603" + "550403") +
							("131c" + "476f6f676c6520496e7465726e657420417574686f72697479204732"))))), // "GoogleInternet Authority G2"
			want: GeneralNames{
				DirectoryNames: []pkix.Name{
					{
						Country:      []string{"US"},
						Organization: []string{"Google Inc"},
						CommonName:   "Google Internet Authority G2",
						Names: []pkix.AttributeTypeAndValue{
							{Type: pkix.OIDCountry, Value: "US"},
							{Type: pkix.OIDOrganization, Value: "Google Inc"},
							{Type: pkix.OIDCommonName, Value: "Google Internet Authority G2"},
						},
					},
				},
			},
		},
		{
			data:    ("8410" + "7777772e676f6f676c652e636f2e756b"),
			wantErr: "failed to unmarshal GeneralNames.directoryName",
		},
		{
			data: ("8610" + "7777772e676f6f676c652e636f2e756b"),
			want: GeneralNames{
				URIs: []string{"www.google.co.uk"},
			},
		},
		{
			data: ("8704" + "01020304"),
			want: GeneralNames{
				IPNets: []net.IPNet{{IP: net.IP{1, 2, 3, 4}}},
			},
		},
		{
			data:     ("8708" + "01020304ffffff00"),
			withMask: true,
			want: GeneralNames{
				IPNets: []net.IPNet{{IP: net.IP{1, 2, 3, 4}, Mask: net.IPMask{0xff, 0xff, 0xff, 0x00}}},
			},
		},
		{
			data: ("8710" + "01020304111213142122232431323334"),
			want: GeneralNames{
				IPNets: []net.IPNet{{IP: net.IP{1, 2, 3, 4, 0x11, 0x12, 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, 0x31, 0x32, 0x33, 0x34}}},
			},
		},
		{
			data:    ("8703" + "010203"),
			wantErr: "invalid IP length",
		},
		{
			data:     ("8707" + "01020304ffffff"),
			withMask: true,
			wantErr:  "invalid IP/mask length",
		},
		{
			data: ("8803" + "551d0e"), // OID: subject-key-id
			want: GeneralNames{
				RegisteredIDs: []asn1.ObjectIdentifier{OIDExtensionSubjectKeyId},
			},
		},
		{
			data:    ("8803" + "551d8e"),
			wantErr: "syntax error",
		},
		{
			data:    ("9003" + "551d8e"),
			wantErr: "unknown tag",
		},
		{
			data:    ("8803"),
			wantErr: "data truncated",
		},
	}

	for _, test := range tests {
		inData := fromHex(test.data)
		var got GeneralNames
		_, err := parseGeneralName(inData, &got, test.withMask)
		if err != nil {
			if test.wantErr == "" {
				t.Errorf("parseGeneralName(%s)=%v; want nil", test.data, err)
			} else if !strings.Contains(err.Error(), test.wantErr) {
				t.Errorf("parseGeneralName(%s)=%v; want %q", test.data, err, test.wantErr)
			}
			continue
		}
		if test.wantErr != "" {
			t.Errorf("parseGeneralName(%s)=%+v,nil; want %q", test.data, got, test.wantErr)
			continue
		}
		if !reflect.DeepEqual(got, test.want) {
			t.Errorf("parseGeneralName(%s)=%+v; want %+v", test.data, got, test.want)
		}
		if got.Empty() {
			t.Errorf("parseGeneralName(%s).Empty(%+v)=true; want false", test.data, got)
		}
		if gotLen, wantLen := got.Len(), 1; gotLen != wantLen {
			t.Errorf("parseGeneralName(%s).Len(%+v)=%d; want %d", test.data, got, gotLen, wantLen)
		}
		if !bytes.Equal(inData, fromHex(test.data)) {
			t.Errorf("parseGeneralName(%s) modified data to %x", test.data, inData)
		}

		// Wrap the GeneralName up in a SEQUENCE and check that we get the same result using parseGeneralNames.
		if test.withMask {
			continue
		}
		seqData := append([]byte{0x30, byte(len(inData))}, inData...)
		var gotSeq GeneralNames
		err = parseGeneralNames(seqData, &gotSeq)
		if err != nil {
			t.Errorf("parseGeneralNames(%x)=%v; want nil", seqData, err)
			continue
		}
		if !reflect.DeepEqual(gotSeq, test.want) {
			t.Errorf("parseGeneralNames(%x)=%+v; want %+v", seqData, gotSeq, test.want)
		}
	}
}

func fromHex(s string) []byte {
	d, _ := hex.DecodeString(s)
	return d
}
