package main

import (
	"bytes"
	"crypto/tls"
	"encoding/binary"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"os"
	"testing/iotest"
	"time"
)

const maxFrame = 1024

func writeFrame(w io.Writer, s string) error {
	if len(s) > maxFrame {
		return fmt.Errorf("frame too large: %d", len(s))
	}
	var h [4]byte
	binary.BigEndian.PutUint32(h[:], uint32(len(s)))
	if _, err := w.Write(h[:]); err != nil {
		return err
	}
	_, err := io.WriteString(w, s)
	return err
}
func readFrame(r io.Reader) (string, error) {
	var h [4]byte
	if _, err := io.ReadFull(r, h[:]); err != nil {
		return "", err
	}
	n := binary.BigEndian.Uint32(h[:])
	if n > maxFrame {
		return "", fmt.Errorf("frame too large: %d", n)
	}
	b := make([]byte, int(n))
	_, err := io.ReadFull(r, b)
	return string(b), err
}
func framing(out io.Writer) error {
	var b bytes.Buffer
	for _, s := range []string{"go", "网络"} {
		if err := writeFrame(&b, s); err != nil {
			return err
		}
	}
	fmt.Fprintf(out, "wire=%x\n", b.Bytes())
	r := iotest.OneByteReader(&b)
	for i := 0; i < 2; i++ {
		s, err := readFrame(r)
		if err != nil {
			return err
		}
		fmt.Fprintf(out, "frame=%q bytes=%d\n", s, len(s))
	}
	var big [4]byte
	binary.BigEndian.PutUint32(big[:], maxFrame+1)
	_, err := readFrame(bytes.NewReader(big[:]))
	fmt.Fprintf(out, "oversize=%v\n", err)
	return nil
}
func tcpHalfClose(out io.Writer) error {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return err
	}
	defer l.Close()
	done := make(chan error, 1)
	go func() {
		c, e := l.Accept()
		if e != nil {
			done <- e
			return
		}
		defer c.Close()
		if e = c.SetDeadline(time.Now().Add(3 * time.Second)); e != nil {
			done <- e
			return
		}
		b, e := io.ReadAll(c)
		if e == nil {
			_, e = fmt.Fprintf(c, "received=%s", b)
		}
		done <- e
	}()
	c, err := net.DialTimeout("tcp", l.Addr().String(), 3*time.Second)
	if err != nil {
		return err
	}
	defer c.Close()
	if err = c.SetDeadline(time.Now().Add(3 * time.Second)); err != nil {
		return err
	}
	if _, err = io.WriteString(c, "hello"); err != nil {
		return err
	}
	if err = c.(*net.TCPConn).CloseWrite(); err != nil {
		return err
	}
	b, err := io.ReadAll(c)
	if err != nil {
		return err
	}
	if err = <-done; err != nil {
		return err
	}
	fmt.Fprintf(out, "after CloseWrite: %q\n", b)
	return nil
}
func udp(out io.Writer) error {
	r, err := net.ListenPacket("udp", "127.0.0.1:0")
	if err != nil {
		return err
	}
	defer r.Close()
	w, err := net.DialTimeout("udp", r.LocalAddr().String(), 3*time.Second)
	if err != nil {
		return err
	}
	defer w.Close()
	if err = w.SetWriteDeadline(time.Now().Add(3 * time.Second)); err != nil {
		return err
	}
	if err = r.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
		return err
	}
	for _, s := range []string{"hello", "bye"} {
		if _, err = io.WriteString(w, s); err != nil {
			return err
		}
	}
	b := make([]byte, 2)
	n, _, err := r.ReadFrom(b)
	if err != nil {
		return err
	}
	fmt.Fprintf(out, "udp small buffer=%q\n", b[:n])
	b = make([]byte, 10)
	n, _, err = r.ReadFrom(b)
	if err != nil {
		return err
	}
	fmt.Fprintf(out, "udp next datagram=%q\n", b[:n])
	return nil
}
func httpVersions(out io.Writer) error {
	for _, h2 := range []bool{false, true} {
		s := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, r.Proto) }))
		s.EnableHTTP2 = h2
		s.TLS = &tls.Config{MinVersion: tls.VersionTLS13}
		s.StartTLS()
		client := s.Client()
		client.Timeout = 3 * time.Second
		resp, err := client.Get(s.URL)
		if err != nil {
			s.Close()
			return err
		}
		b, err := io.ReadAll(resp.Body)
		closeErr := resp.Body.Close()
		s.Close()
		if err != nil {
			return err
		}
		if closeErr != nil {
			return closeErr
		}
		fmt.Fprintf(out, "http=%s tls13=%t alpn=%q body=%s\n", resp.Proto, resp.TLS.Version == tls.VersionTLS13, resp.TLS.NegotiatedProtocol, b)
	}
	return nil
}
func run(out io.Writer) error {
	for _, f := range []func(io.Writer) error{framing, tcpHalfClose, udp, httpVersions} {
		if err := f(out); err != nil {
			return err
		}
	}
	return nil
}
func main() {
	if err := run(os.Stdout); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
