8e34bdcdf7
Give *recordingConn the correct WriteTo signature to be an io.WriterTo. This makes vet happy. It also means that it'll report errors, which were previously being ignored. Updates #11041 Change-Id: I13f171407d63f4b62427679bff362eb74faddca5 Reviewed-on: https://go-review.googlesource.com/27121 Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
180 lines
4.5 KiB
Go
180 lines
4.5 KiB
Go
// Copyright 2013 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 tls
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/hex"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// TLS reference tests run a connection against a reference implementation
|
|
// (OpenSSL) of TLS and record the bytes of the resulting connection. The Go
|
|
// code, during a test, is configured with deterministic randomness and so the
|
|
// reference test can be reproduced exactly in the future.
|
|
//
|
|
// In order to save everyone who wishes to run the tests from needing the
|
|
// reference implementation installed, the reference connections are saved in
|
|
// files in the testdata directory. Thus running the tests involves nothing
|
|
// external, but creating and updating them requires the reference
|
|
// implementation.
|
|
//
|
|
// Tests can be updated by running them with the -update flag. This will cause
|
|
// the test files. Generally one should combine the -update flag with -test.run
|
|
// to updated a specific test. Since the reference implementation will always
|
|
// generate fresh random numbers, large parts of the reference connection will
|
|
// always change.
|
|
|
|
var update = flag.Bool("update", false, "update golden files on disk")
|
|
|
|
// recordingConn is a net.Conn that records the traffic that passes through it.
|
|
// WriteTo can be used to produce output that can be later be loaded with
|
|
// ParseTestData.
|
|
type recordingConn struct {
|
|
net.Conn
|
|
sync.Mutex
|
|
flows [][]byte
|
|
reading bool
|
|
}
|
|
|
|
func (r *recordingConn) Read(b []byte) (n int, err error) {
|
|
if n, err = r.Conn.Read(b); n == 0 {
|
|
return
|
|
}
|
|
b = b[:n]
|
|
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
if l := len(r.flows); l == 0 || !r.reading {
|
|
buf := make([]byte, len(b))
|
|
copy(buf, b)
|
|
r.flows = append(r.flows, buf)
|
|
} else {
|
|
r.flows[l-1] = append(r.flows[l-1], b[:n]...)
|
|
}
|
|
r.reading = true
|
|
return
|
|
}
|
|
|
|
func (r *recordingConn) Write(b []byte) (n int, err error) {
|
|
if n, err = r.Conn.Write(b); n == 0 {
|
|
return
|
|
}
|
|
b = b[:n]
|
|
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
if l := len(r.flows); l == 0 || r.reading {
|
|
buf := make([]byte, len(b))
|
|
copy(buf, b)
|
|
r.flows = append(r.flows, buf)
|
|
} else {
|
|
r.flows[l-1] = append(r.flows[l-1], b[:n]...)
|
|
}
|
|
r.reading = false
|
|
return
|
|
}
|
|
|
|
// WriteTo writes Go source code to w that contains the recorded traffic.
|
|
func (r *recordingConn) WriteTo(w io.Writer) (int64, error) {
|
|
// TLS always starts with a client to server flow.
|
|
clientToServer := true
|
|
var written int64
|
|
for i, flow := range r.flows {
|
|
source, dest := "client", "server"
|
|
if !clientToServer {
|
|
source, dest = dest, source
|
|
}
|
|
n, err := fmt.Fprintf(w, ">>> Flow %d (%s to %s)\n", i+1, source, dest)
|
|
written += int64(n)
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
dumper := hex.Dumper(w)
|
|
n, err = dumper.Write(flow)
|
|
written += int64(n)
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
err = dumper.Close()
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
clientToServer = !clientToServer
|
|
}
|
|
return written, nil
|
|
}
|
|
|
|
func parseTestData(r io.Reader) (flows [][]byte, err error) {
|
|
var currentFlow []byte
|
|
|
|
scanner := bufio.NewScanner(r)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
// If the line starts with ">>> " then it marks the beginning
|
|
// of a new flow.
|
|
if strings.HasPrefix(line, ">>> ") {
|
|
if len(currentFlow) > 0 || len(flows) > 0 {
|
|
flows = append(flows, currentFlow)
|
|
currentFlow = nil
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Otherwise the line is a line of hex dump that looks like:
|
|
// 00000170 fc f5 06 bf (...) |.....X{&?......!|
|
|
// (Some bytes have been omitted from the middle section.)
|
|
|
|
if i := strings.IndexByte(line, ' '); i >= 0 {
|
|
line = line[i:]
|
|
} else {
|
|
return nil, errors.New("invalid test data")
|
|
}
|
|
|
|
if i := strings.IndexByte(line, '|'); i >= 0 {
|
|
line = line[:i]
|
|
} else {
|
|
return nil, errors.New("invalid test data")
|
|
}
|
|
|
|
hexBytes := strings.Fields(line)
|
|
for _, hexByte := range hexBytes {
|
|
val, err := strconv.ParseUint(hexByte, 16, 8)
|
|
if err != nil {
|
|
return nil, errors.New("invalid hex byte in test data: " + err.Error())
|
|
}
|
|
currentFlow = append(currentFlow, byte(val))
|
|
}
|
|
}
|
|
|
|
if len(currentFlow) > 0 {
|
|
flows = append(flows, currentFlow)
|
|
}
|
|
|
|
return flows, nil
|
|
}
|
|
|
|
// tempFile creates a temp file containing contents and returns its path.
|
|
func tempFile(contents string) string {
|
|
file, err := ioutil.TempFile("", "go-tls-test")
|
|
if err != nil {
|
|
panic("failed to create temp file: " + err.Error())
|
|
}
|
|
path := file.Name()
|
|
file.WriteString(contents)
|
|
file.Close()
|
|
return path
|
|
}
|