/*
 *
 * Copyright 2014 gRPC 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 transport

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"math"
	"net"
	"reflect"
	"strings"
	"testing"
	"time"

	"golang.org/x/net/http2"
	"google.golang.org/grpc/internal/envconfig"
	imem "google.golang.org/grpc/internal/mem"
	"google.golang.org/grpc/internal/testutils"
	"google.golang.org/grpc/internal/transport/readyreader"
	"google.golang.org/grpc/mem"
)

func (s) TestDecodeTimeout(t *testing.T) {
	for _, test := range []struct {
		// input
		s string
		// output
		d       time.Duration
		wantErr bool
	}{

		{"00000001n", time.Nanosecond, false},
		{"10u", time.Microsecond * 10, false},
		{"00000010m", time.Millisecond * 10, false},
		{"1234S", time.Second * 1234, false},
		{"00000001M", time.Minute, false},
		{"09999999S", time.Second * 9999999, false},
		{"99999999S", time.Second * 99999999, false},
		{"99999999M", time.Minute * 99999999, false},
		{"2562047H", time.Hour * 2562047, false},
		{"2562048H", time.Duration(math.MaxInt64), false},
		{"99999999H", time.Duration(math.MaxInt64), false},
		{"-1S", 0, true},
		{"1234x", 0, true},
		{"1234s", 0, true},
		{"1234", 0, true},
		{"1", 0, true},
		{"", 0, true},
		{"9a1S", 0, true},
		{"0S", 0, false}, // PROTOCOL-HTTP2.md requires positive integers, but we allow it to timeout instead
		{"00000000S", 0, false},
		{"000000000S", 0, true}, // PROTOCOL-HTTP2.md allows at most 8 digits
	} {
		d, err := decodeTimeout(test.s)
		gotErr := err != nil
		if d != test.d || gotErr != test.wantErr {
			t.Errorf("timeoutDecode(%q) = %d, %v, want %d, wantErr=%v",
				test.s, int64(d), err, int64(test.d), test.wantErr)
		}
	}
}

func (s) TestEncodeGrpcMessage(t *testing.T) {
	for _, tt := range []struct {
		input    string
		expected string
	}{
		{"", ""},
		{"Hello", "Hello"},
		{"\u0000", "%00"},
		{"%", "%25"},
		{"系统", "%E7%B3%BB%E7%BB%9F"},
		{string([]byte{0xff, 0xfe, 0xfd}), "%EF%BF%BD%EF%BF%BD%EF%BF%BD"},
	} {
		actual := encodeGrpcMessage(tt.input)
		if tt.expected != actual {
			t.Errorf("encodeGrpcMessage(%q) = %q, want %q", tt.input, actual, tt.expected)
		}
	}

	// make sure that all the visible ASCII chars except '%' are not percent encoded.
	for i := ' '; i <= '~' && i != '%'; i++ {
		output := encodeGrpcMessage(string(i))
		if output != string(i) {
			t.Errorf("encodeGrpcMessage(%v) = %v, want %v", string(i), output, string(i))
		}
	}

	// make sure that all the invisible ASCII chars and '%' are percent encoded.
	for i := rune(0); i == '%' || (i >= rune(0) && i < ' ') || (i > '~' && i <= rune(127)); i++ {
		output := encodeGrpcMessage(string(i))
		expected := fmt.Sprintf("%%%02X", i)
		if output != expected {
			t.Errorf("encodeGrpcMessage(%v) = %v, want %v", string(i), output, expected)
		}
	}
}

func (s) TestDecodeGrpcMessage(t *testing.T) {
	for _, tt := range []struct {
		input    string
		expected string
	}{
		{"", ""},
		{"Hello", "Hello"},
		{"H%61o", "Hao"},
		{"H%6", "H%6"},
		{"%G0", "%G0"},
		{"%E7%B3%BB%E7%BB%9F", "系统"},
		{"%EF%BF%BD", "�"},
	} {
		actual := decodeGrpcMessage(tt.input)
		if tt.expected != actual {
			t.Errorf("decodeGrpcMessage(%q) = %q, want %q", tt.input, actual, tt.expected)
		}
	}

	// make sure that all the visible ASCII chars except '%' are not percent decoded.
	for i := ' '; i <= '~' && i != '%'; i++ {
		output := decodeGrpcMessage(string(i))
		if output != string(i) {
			t.Errorf("decodeGrpcMessage(%v) = %v, want %v", string(i), output, string(i))
		}
	}

	// make sure that all the invisible ASCII chars and '%' are percent decoded.
	for i := rune(0); i == '%' || (i >= rune(0) && i < ' ') || (i > '~' && i <= rune(127)); i++ {
		output := decodeGrpcMessage(fmt.Sprintf("%%%02X", i))
		if output != string(i) {
			t.Errorf("decodeGrpcMessage(%v) = %v, want %v", fmt.Sprintf("%%%02X", i), output, string(i))
		}
	}
}

// Decode an encoded string should get the same thing back, except for invalid
// utf8 chars.
func (s) TestDecodeEncodeGrpcMessage(t *testing.T) {
	testCases := []struct {
		orig string
		want string
	}{
		{"", ""},
		{"hello", "hello"},
		{"h%6", "h%6"},
		{"%G0", "%G0"},
		{"系统", "系统"},
		{"Hello, 世界", "Hello, 世界"},

		{string([]byte{0xff, 0xfe, 0xfd}), "���"},
		{string([]byte{0xff}) + "Hello" + string([]byte{0xfe}) + "世界" + string([]byte{0xfd}), "�Hello�世界�"},
	}
	for _, tC := range testCases {
		got := decodeGrpcMessage(encodeGrpcMessage(tC.orig))
		if got != tC.want {
			t.Errorf("decodeGrpcMessage(encodeGrpcMessage(%q)) = %q, want %q", tC.orig, got, tC.want)
		}
	}
}

const binaryValue = "\u0080"

func (s) TestEncodeMetadataHeader(t *testing.T) {
	for _, test := range []struct {
		// input
		kin string
		vin string
		// output
		vout string
	}{
		{"key", "abc", "abc"},
		{"KEY", "abc", "abc"},
		{"key-bin", "abc", "YWJj"},
		{"key-bin", binaryValue, "woA"},
	} {
		v := encodeMetadataHeader(test.kin, test.vin)
		if !reflect.DeepEqual(v, test.vout) {
			t.Fatalf("encodeMetadataHeader(%q, %q) = %q, want %q", test.kin, test.vin, v, test.vout)
		}
	}
}

func (s) TestDecodeMetadataHeader(t *testing.T) {
	for _, test := range []struct {
		// input
		kin string
		vin string
		// output
		vout string
		err  error
	}{
		{"a", "abc", "abc", nil},
		{"key-bin", "Zm9vAGJhcg==", "foo\x00bar", nil},
		{"key-bin", "Zm9vAGJhcg", "foo\x00bar", nil},
		{"key-bin", "woA=", binaryValue, nil},
		{"a", "abc,efg", "abc,efg", nil},
	} {
		v, err := decodeMetadataHeader(test.kin, test.vin)
		if !reflect.DeepEqual(v, test.vout) || !reflect.DeepEqual(err, test.err) {
			t.Fatalf("decodeMetadataHeader(%q, %q) = %q, %v, want %q, %v", test.kin, test.vin, v, err, test.vout, test.err)
		}
	}
}

func (s) TestParseDialTarget(t *testing.T) {
	for _, test := range []struct {
		target, wantNet, wantAddr string
	}{
		{"unix:a", "unix", "a"},
		{"unix:a/b/c", "unix", "a/b/c"},
		{"unix:/a", "unix", "/a"},
		{"unix:/a/b/c", "unix", "/a/b/c"},
		{"unix://a", "unix", "a"},
		{"unix://a/b/c", "unix", "/b/c"},
		{"unix:///a", "unix", "/a"},
		{"unix:///a/b/c", "unix", "/a/b/c"},
		{"unix:etcd:0", "unix", "etcd:0"},
		{"unix:///tmp/unix-3", "unix", "/tmp/unix-3"},
		{"unix://domain", "unix", "domain"},
		{"unix://etcd:0", "unix", "etcd:0"},
		{"unix:///etcd:0", "unix", "/etcd:0"},
		{"passthrough://unix://domain", "tcp", "passthrough://unix://domain"},
		{"https://google.com:443", "tcp", "https://google.com:443"},
		{"dns:///google.com", "tcp", "dns:///google.com"},
		{"/unix/socket/address", "tcp", "/unix/socket/address"},
	} {
		gotNet, gotAddr := ParseDialTarget(test.target)
		if gotNet != test.wantNet || gotAddr != test.wantAddr {
			t.Errorf("ParseDialTarget(%q) = %s, %s want %s, %s", test.target, gotNet, gotAddr, test.wantNet, test.wantAddr)
		}
	}
}

type badNetworkConn struct {
	net.Conn
}

func (c *badNetworkConn) Write([]byte) (int, error) {
	return 0, io.EOF
}

// This test ensures Write() on a broken network connection does not lead to
// an infinite loop. See https://github.com/grpc/grpc-go/issues/7389 for more details.
func (s) TestWriteBadConnection(t *testing.T) {
	data := []byte("test_data")
	// Configure the bufWriter with a batchsize that results in data being flushed
	// to the underlying conn, midway through Write().
	writeBufferSize := (len(data) - 1) / 2
	writer := newBufWriter(&badNetworkConn{}, writeBufferSize, ioBufferPool(writeBufferSize))

	errCh := make(chan error, 1)
	go func() {
		_, err := writer.Write(data)
		errCh <- err
	}()

	select {
	case <-time.After(time.Second):
		t.Fatalf("Write() did not return in time")
	case err := <-errCh:
		if !errors.Is(err, io.EOF) {
			t.Fatalf("Write() = %v, want error presence = %v", err, io.EOF)
		}
	}
	if writer.bufHandle != nil {
		t.Fatalf("failed Write() retained pooled buffer: handle=%p", writer.bufHandle)
	}
}

func (s) TestBufWriterSharedFlushAllocations(t *testing.T) {
	pool := imem.NewDirtySimplePool()
	writer := newBufWriter(io.Discard, defaultWriteBufSize, pool)
	payload := make([]byte, 128)

	allocs := testing.AllocsPerRun(1000, func() {
		if _, err := writer.Write(payload); err != nil {
			t.Fatalf("Write() failed: %v", err)
		}
		if err := writer.Flush(); err != nil {
			t.Fatalf("Flush() failed: %v", err)
		}
	})
	if allocs != 0 {
		t.Fatalf("shared Write()+Flush() allocated %v times, want 0", allocs)
	}
}

func (s) TestBufWriterSharedBufferOwnership(t *testing.T) {
	const batchSize = 8
	pool := imem.NewDirtySimplePool()
	var conn bytes.Buffer
	writer := newBufWriter(&conn, batchSize, pool)

	if _, err := writer.Write([]byte("abc")); err != nil {
		t.Fatalf("Write() failed: %v", err)
	}
	if writer.bufHandle == nil {
		t.Fatal("Write() did not retain the pooled buffer handle")
	}
	if err := writer.Flush(); err != nil {
		t.Fatalf("Flush() failed: %v", err)
	}
	if writer.bufHandle != nil {
		t.Fatalf("Flush() retained pooled buffer: handle=%p", writer.bufHandle)
	}
	if got := conn.String(); got != "abc" {
		t.Fatalf("conn data = %q, want %q", got, "abc")
	}
	if err := writer.Flush(); err != nil {
		t.Fatalf("second Flush() failed: %v", err)
	}

	if _, err := writer.Write([]byte("12345678")); err != nil {
		t.Fatalf("full-buffer Write() failed: %v", err)
	}
	if writer.bufHandle == nil {
		t.Fatal("full-buffer Write() released the handle during its internal flush")
	}
	if writer.offset != 0 {
		t.Fatalf("full-buffer Write() offset = %d, want 0", writer.offset)
	}
	if err := writer.Flush(); err != nil {
		t.Fatalf("Flush() after full-buffer Write() failed: %v", err)
	}
	if writer.bufHandle != nil {
		t.Fatalf("Flush() retained pooled buffer: handle=%p", writer.bufHandle)
	}
}

func (s) TestBufWriterFlushErrorReleasesSharedBuffer(t *testing.T) {
	pool := imem.NewDirtySimplePool()
	writer := newBufWriter(&badNetworkConn{}, 8, pool)

	if _, err := writer.Write([]byte("abc")); err != nil {
		t.Fatalf("Write() failed before flush: %v", err)
	}
	if writer.bufHandle == nil {
		t.Fatal("Write() did not retain the pooled buffer handle")
	}
	if err := writer.Flush(); !errors.Is(err, io.EOF) {
		t.Fatalf("Flush() error = %v, want %v", err, io.EOF)
	}
	if writer.bufHandle != nil {
		t.Fatalf("failed Flush() retained pooled buffer: handle=%p", writer.bufHandle)
	}
	if _, err := writer.Write([]byte("def")); !errors.Is(err, io.EOF) {
		t.Fatalf("Write() after failed Flush() error = %v, want %v", err, io.EOF)
	}
	if writer.bufHandle != nil {
		t.Fatal("Write() after failed Flush() acquired another pooled buffer")
	}
}

func BenchmarkBufWriter(b *testing.B) {
	const payloadSize = 128
	payload := make([]byte, payloadSize)

	for _, test := range []struct {
		name string
		pool *imem.SimpleBufferPool
	}{
		{name: "Shared", pool: imem.NewDirtySimplePool()},
		{name: "Unshared"},
	} {
		b.Run(test.name, func(b *testing.B) {
			b.Run("New", func(b *testing.B) {
				b.ReportAllocs()
				for b.Loop() {
					writer := newBufWriter(io.Discard, defaultWriteBufSize, test.pool)
					_ = writer
				}
			})

			b.Run("WriteAndFlush", func(b *testing.B) {
				writer := newBufWriter(io.Discard, defaultWriteBufSize, test.pool)
				b.ReportAllocs()
				b.SetBytes(payloadSize)
				for b.Loop() {
					if _, err := writer.Write(payload); err != nil {
						b.Fatal(err)
					}
					if err := writer.Flush(); err != nil {
						b.Fatal(err)
					}
				}
			})

			b.Run("EmptyFlush", func(b *testing.B) {
				writer := newBufWriter(io.Discard, defaultWriteBufSize, test.pool)
				b.ReportAllocs()
				for b.Loop() {
					if err := writer.Flush(); err != nil {
						b.Fatal(err)
					}
				}
			})

			b.Run("FullBufferWrite", func(b *testing.B) {
				writer := newBufWriter(io.Discard, defaultWriteBufSize, test.pool)
				fullBuffer := make([]byte, defaultWriteBufSize)
				b.ReportAllocs()
				b.SetBytes(defaultWriteBufSize)
				for b.Loop() {
					if _, err := writer.Write(fullBuffer); err != nil {
						b.Fatal(err)
					}
				}
			})
		})
	}
}

func BenchmarkDecodeGrpcMessage(b *testing.B) {
	input := "Hello, %E4%B8%96%E7%95%8C"
	want := "Hello, 世界"
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		got := decodeGrpcMessage(input)
		if got != want {
			b.Fatalf("decodeGrpcMessage(%q) = %s, want %s", input, got, want)
		}
	}
}

func BenchmarkEncodeGrpcMessage(b *testing.B) {
	input := "Hello, 世界"
	want := "Hello, %E4%B8%96%E7%95%8C"
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		got := encodeGrpcMessage(input)
		if got != want {
			b.Fatalf("encodeGrpcMessage(%q) = %s, want %s", input, got, want)
		}
	}
}

func buildDataFrame(h http2.FrameHeader, payload []byte) []byte {
	buf := new(bytes.Buffer)
	buf.Write([]byte{
		byte(h.Length >> 16),
		byte(h.Length >> 8),
		byte(h.Length),
		byte(h.Type),
		byte(h.Flags),
		byte(h.StreamID >> 24),
		byte(h.StreamID >> 16),
		byte(h.StreamID >> 8),
		byte(h.StreamID),
	})
	buf.Write(payload)
	return buf.Bytes()
}

func (s) TestFramer_ParseDataFrame(t *testing.T) {
	tests := []struct {
		name                string
		wire                []byte // from frame header onward
		wantData            []byte
		wantErr             error
		wantErrDetailSubstr string
	}{
		{
			name: "good_padded",
			wire: buildDataFrame(http2.FrameHeader{
				Type: http2.FrameData, Length: 6, StreamID: 1, Flags: http2.FlagDataPadded,
			}, []byte{
				2,             // pad length
				'f', 'o', 'o', // data
				0, 0, // padding
			}),
			wantData: []byte("foo"),
		},
		{
			name: "good_unpadded",
			wire: buildDataFrame(http2.FrameHeader{
				Type: http2.FrameData, Length: 3, StreamID: 1, Flags: 0,
			}, []byte("foo")),
			wantData: []byte("foo"),
		},
		{
			name: "stream_id_0",
			wire: buildDataFrame(http2.FrameHeader{
				Type: http2.FrameData, Length: 1, StreamID: 0, Flags: 0,
			}, []byte{0}),
			wantErr:             http2.ConnectionError(http2.ErrCodeProtocol),
			wantErrDetailSubstr: "DATA frame with stream ID 0",
		},
		{
			name: "pad_size_bigger_than_payload",
			wire: buildDataFrame(http2.FrameHeader{
				Type: http2.FrameData, Length: 4, StreamID: 1, Flags: http2.FlagDataPadded,
			}, []byte{
				4,        // pad length of 4
				'f', 'o', // data 'fo' is 2 bytes
				0, // padding 0 is 1 byte.
			}), // pad length 4 but only 3 bytes for data+padding available in payload after pad length byte
			wantErr:             http2.ConnectionError(http2.ErrCodeProtocol),
			wantErrDetailSubstr: "pad size larger than data payload",
		},
		{
			name: "padded_zero_data_some_padding",
			wire: buildDataFrame(http2.FrameHeader{
				Type: http2.FrameData, Length: 3, StreamID: 1, Flags: http2.FlagDataPadded,
			}, []byte{
				2,    // pad length 2
				0, 0, // padding
			}),
			wantData: []byte{},
		},
		{
			name: "padded_short_payload_reading_pad_flag",
			wire: buildDataFrame(http2.FrameHeader{
				Type: http2.FrameData, Length: 0, StreamID: 1, Flags: http2.FlagDataPadded,
			}, []byte{}),
			wantErr: io.ErrUnexpectedEOF,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			fr := newFramer(bytes.NewBuffer(tc.wire), defaultWriteBufSize, defaultReadBufSize, false, defaultClientMaxHeaderListSize, mem.DefaultBufferPool())
			f, err := fr.readFrame()

			if err != tc.wantErr {
				t.Fatalf("readFrame() returned unexpected error: %v, want %v", err, tc.wantErr)
			}
			gotErrDetailStr := ""
			if fr.errDetail != nil {
				gotErrDetailStr = fr.errDetail.Error()
			}
			if !strings.Contains(gotErrDetailStr, tc.wantErrDetailSubstr) {
				t.Fatalf("errorDetail() returned unexpected error string: %q, want substring %q", gotErrDetailStr, tc.wantErrDetailSubstr)
			}

			if tc.wantErr != nil {
				return
			}
			df, ok := f.(*parsedDataFrame)
			if !ok {
				t.Fatalf("readFrame() returned %T, want *parsedDataFrame", f)
			}
			if gotData := df.data.ReadOnlyData(); !bytes.Equal(gotData, tc.wantData) {
				t.Fatalf("parsedDataFrame.Data() = %q, want %q", gotData, tc.wantData)
			}
			df.data.Free()
		})
	}
}

type testReadyReader struct {
	readyreader.Reader
}

func (t *testReadyReader) Read([]byte) (int, error) {
	return 0, io.EOF
}

func (s) TestBufferedReader(t *testing.T) {
	normalReader := bytes.NewReader(nil)

	tests := []struct {
		name          string
		reader        io.Reader
		bufSize       int
		enablePooling bool
		wantTypeOf    any
	}{
		{
			name:          "bufSize_0",
			reader:        normalReader,
			bufSize:       0,
			enablePooling: true,
			wantTypeOf:    (*bytes.Reader)(nil),
		},
		{
			name:          "env_var_disabled_normal_reader",
			reader:        normalReader,
			bufSize:       10,
			enablePooling: false,
			wantTypeOf:    (*bufio.Reader)(nil),
		},
		{
			name:          "env_var_disabled_ready_reader",
			reader:        &testReadyReader{},
			bufSize:       10,
			enablePooling: false,
			wantTypeOf:    (*bufio.Reader)(nil),
		},
		{
			name:          "env_var_enabled_normal_reader",
			reader:        normalReader,
			bufSize:       10,
			enablePooling: true,
			wantTypeOf:    (*bufio.Reader)(nil),
		},
		{
			name:          "env_var_enabled_ready_reader",
			reader:        &testReadyReader{},
			bufSize:       10,
			enablePooling: true,
			wantTypeOf:    readyreader.NewBuffered(nil, 10, mem.DefaultBufferPool()),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			testutils.SetEnvConfig(t, &envconfig.EnableHTTPFramerReadBufferPooling, tt.enablePooling)

			got := bufferedReader(tt.reader, tt.bufSize)

			gotType := reflect.TypeOf(got)
			wantType := reflect.TypeOf(tt.wantTypeOf)

			if gotType != wantType {
				t.Errorf("bufferedReader() type = %v, want %v", gotType, wantType)
			}
		})
	}
}
