1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package tls
6
7import (
8	"bufio"
9	"encoding/hex"
10	"errors"
11	"flag"
12	"fmt"
13	"io"
14	"io/ioutil"
15	"net"
16	"strconv"
17	"strings"
18	"sync"
19)
20
21// TLS reference tests run a connection against a reference implementation
22// (OpenSSL) of TLS and record the bytes of the resulting connection. The Go
23// code, during a test, is configured with deterministic randomness and so the
24// reference test can be reproduced exactly in the future.
25//
26// In order to save everyone who wishes to run the tests from needing the
27// reference implementation installed, the reference connections are saved in
28// files in the testdata directory. Thus running the tests involves nothing
29// external, but creating and updating them requires the reference
30// implementation.
31//
32// Tests can be updated by running them with the -update flag. This will cause
33// the test files. Generally one should combine the -update flag with -test.run
34// to updated a specific test. Since the reference implementation will always
35// generate fresh random numbers, large parts of the reference connection will
36// always change.
37
38var update = flag.Bool("update", false, "update golden files on disk")
39
40// recordingConn is a net.Conn that records the traffic that passes through it.
41// WriteTo can be used to produce output that can be later be loaded with
42// ParseTestData.
43type recordingConn struct {
44	net.Conn
45	sync.Mutex
46	flows   [][]byte
47	reading bool
48}
49
50func (r *recordingConn) Read(b []byte) (n int, err error) {
51	if n, err = r.Conn.Read(b); n == 0 {
52		return
53	}
54	b = b[:n]
55
56	r.Lock()
57	defer r.Unlock()
58
59	if l := len(r.flows); l == 0 || !r.reading {
60		buf := make([]byte, len(b))
61		copy(buf, b)
62		r.flows = append(r.flows, buf)
63	} else {
64		r.flows[l-1] = append(r.flows[l-1], b[:n]...)
65	}
66	r.reading = true
67	return
68}
69
70func (r *recordingConn) Write(b []byte) (n int, err error) {
71	if n, err = r.Conn.Write(b); n == 0 {
72		return
73	}
74	b = b[:n]
75
76	r.Lock()
77	defer r.Unlock()
78
79	if l := len(r.flows); l == 0 || r.reading {
80		buf := make([]byte, len(b))
81		copy(buf, b)
82		r.flows = append(r.flows, buf)
83	} else {
84		r.flows[l-1] = append(r.flows[l-1], b[:n]...)
85	}
86	r.reading = false
87	return
88}
89
90// WriteTo writes Go source code to w that contains the recorded traffic.
91func (r *recordingConn) WriteTo(w io.Writer) {
92	// TLS always starts with a client to server flow.
93	clientToServer := true
94
95	for i, flow := range r.flows {
96		source, dest := "client", "server"
97		if !clientToServer {
98			source, dest = dest, source
99		}
100		fmt.Fprintf(w, ">>> Flow %d (%s to %s)\n", i+1, source, dest)
101		dumper := hex.Dumper(w)
102		dumper.Write(flow)
103		dumper.Close()
104		clientToServer = !clientToServer
105	}
106}
107
108func parseTestData(r io.Reader) (flows [][]byte, err error) {
109	var currentFlow []byte
110
111	scanner := bufio.NewScanner(r)
112	for scanner.Scan() {
113		line := scanner.Text()
114		// If the line starts with ">>> " then it marks the beginning
115		// of a new flow.
116		if strings.HasPrefix(line, ">>> ") {
117			if len(currentFlow) > 0 || len(flows) > 0 {
118				flows = append(flows, currentFlow)
119				currentFlow = nil
120			}
121			continue
122		}
123
124		// Otherwise the line is a line of hex dump that looks like:
125		// 00000170  fc f5 06 bf (...)  |.....X{&?......!|
126		// (Some bytes have been omitted from the middle section.)
127
128		if i := strings.IndexByte(line, ' '); i >= 0 {
129			line = line[i:]
130		} else {
131			return nil, errors.New("invalid test data")
132		}
133
134		if i := strings.IndexByte(line, '|'); i >= 0 {
135			line = line[:i]
136		} else {
137			return nil, errors.New("invalid test data")
138		}
139
140		hexBytes := strings.Fields(line)
141		for _, hexByte := range hexBytes {
142			val, err := strconv.ParseUint(hexByte, 16, 8)
143			if err != nil {
144				return nil, errors.New("invalid hex byte in test data: " + err.Error())
145			}
146			currentFlow = append(currentFlow, byte(val))
147		}
148	}
149
150	if len(currentFlow) > 0 {
151		flows = append(flows, currentFlow)
152	}
153
154	return flows, nil
155}
156
157// tempFile creates a temp file containing contents and returns its path.
158func tempFile(contents string) string {
159	file, err := ioutil.TempFile("", "go-tls-test")
160	if err != nil {
161		panic("failed to create temp file: " + err.Error())
162	}
163	path := file.Name()
164	file.WriteString(contents)
165	file.Close()
166	return path
167}
168