/
logging_conn.go
205 lines (174 loc) · 5.81 KB
/
logging_conn.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// logging_conn.go defines LoggingListener, an implementation of net.Listener
// that allows tests to read the text transported over a client-server
// connection, and confirm, in the case of a TLS connection, that nothing
// sensitive was transported in plain text.
package cert
import (
"bytes"
"context"
"errors"
"io"
"net"
"sync"
"time"
)
type loggingPipeAddr struct{}
func (o loggingPipeAddr) Network() string {
return "logging-pipe"
}
func (o loggingPipeAddr) String() string {
return "logging-pipe"
}
// loggingPipe is a struct containing two buffers, which log all of the traffic
// passing through LoggingPipe in each direction
type loggingPipe struct {
ClientToServerBuf, ServerToClientBuf bytes.Buffer
clientReader, serverReader io.Reader
clientWriter, serverWriter io.WriteCloser
}
// newLoggingPipe initializes a loggingPipe
func newLoggingPipe() *loggingPipe {
p := &loggingPipe{}
p.clientReader, p.clientWriter = io.Pipe()
p.clientReader = io.TeeReader(p.clientReader, &p.ServerToClientBuf)
p.serverReader, p.serverWriter = io.Pipe()
p.serverReader = io.TeeReader(p.serverReader, &p.ClientToServerBuf)
return p
}
// Close closes 'l' (no more reading/writing will be possible)
func (p *loggingPipe) Close() error {
p.clientWriter.Close()
p.serverWriter.Close()
return nil
}
// clientConn returns a loggingConn at the oposite end of the loggingPipe as
// serverConn. There is no fundamental difference between the clientConn and
// the serverConn, as communication is full duplex, but distinguishing the two
// ends of the pipe as client and server, rather than e.g. left and right,
// hopefully makes the calling code easier to read
func (p *loggingPipe) clientConn() *loggingConn {
return &loggingConn{
pipe: p,
r: p.clientReader,
w: p.serverWriter,
}
}
// serverConn returns a loggingConn at the opposite end of the loggingPipe of
// clientConn (see the clientConn description for more information)
func (p *loggingPipe) serverConn() *loggingConn {
return &loggingConn{
pipe: p,
r: p.serverReader,
w: p.clientWriter,
}
}
// loggingConn is an implementation of net.Conn that communicates with another
// party over a loggingPipe.
type loggingConn struct {
// pipe is the loggingPipe over which this connection is communicating
pipe *loggingPipe
r io.Reader
w io.WriteCloser
}
// Read implements the corresponding method of net.Conn
func (l *loggingConn) Read(b []byte) (n int, err error) {
return l.r.Read(b)
}
// Write implements the corresponding method of net.Conn
func (l *loggingConn) Write(b []byte) (n int, err error) {
return l.w.Write(b)
}
// Close implements the corresponding method of net.Conn
func (l *loggingConn) Close() error {
return l.pipe.Close()
}
// LocalAddr implements the corresponding method of net.Conn
func (l *loggingConn) LocalAddr() net.Addr {
return loggingPipeAddr{}
}
// RemoteAddr implements the corresponding method of net.Conn
func (l *loggingConn) RemoteAddr() net.Addr {
return loggingPipeAddr{}
}
// SetDeadline implements the corresponding method of net.Conn
func (l *loggingConn) SetDeadline(t time.Time) error {
return errors.New("not implemented")
}
// SetReadDeadline implements the corresponding method of net.Conn
func (l *loggingConn) SetReadDeadline(t time.Time) error {
return errors.New("not implemented")
}
// SetWriteDeadline implements the corresponding method of net.Conn
func (l *loggingConn) SetWriteDeadline(t time.Time) error {
return errors.New("not implemented")
}
// TestListener implements the net.Listener interface, returning loggingConns
type TestListener struct {
// conn is the first (and last) non-nil connection returned from a call to
// Accept()
conn *loggingConn
connMu sync.Mutex
// connCh provides connections (or nil) to Accept()
connCh chan net.Conn
}
// NewTestListener initializes and returns a new TestListener. To create
// a new connection that that this Listener will serve on, call Dial(). To see
// the logged communication over that connection's pipe, see ClientToServerLog
// and ServerToClientLog
func NewTestListener() *TestListener {
return &TestListener{
connCh: make(chan net.Conn),
}
}
// Dial initializes a new connection and releases a blocked call to Accept()
func (l *TestListener) Dial(context.Context, string, string) (net.Conn, error) {
l.connMu.Lock()
defer l.connMu.Unlock()
if l.conn != nil {
return nil, errors.New("Dial() has already been called on this TestListener")
}
// Initialize logging pipe
p := newLoggingPipe()
l.conn = p.serverConn()
// send serverConn to Accept() and close l.connCh (so future callers to
// Accept() get nothing)
l.connCh <- p.serverConn()
close(l.connCh)
return p.clientConn(), nil
}
// ClientToServerLog returns the log of client -> server communication over the
// first (and only) connection spawned by this listener
func (l *TestListener) ClientToServerLog() []byte {
return l.conn.pipe.ClientToServerBuf.Bytes()
}
// ServerToClientLog the log of server -> client communication over the first
// (and only) connection spawned by this listener
func (l *TestListener) ServerToClientLog() []byte {
return l.conn.pipe.ServerToClientBuf.Bytes()
}
// Accept implements the corresponding method of net.Listener for
// TestListener
func (l *TestListener) Accept() (net.Conn, error) {
conn := <-l.connCh
if conn == nil {
return nil, errors.New("Accept() has already been called on this TestListener")
}
return conn, nil
}
// Close implements the corresponding method of net.Listener for
// TestListener. Any blocked Accept operations will be unblocked and return
// errors.
func (l *TestListener) Close() error {
l.connMu.Lock()
defer l.connMu.Unlock()
c := <-l.connCh
if c != nil {
close(l.connCh)
}
return nil
}
// Addr implements the corresponding method of net.Listener for
// TestListener
func (l *TestListener) Addr() net.Addr {
return loggingPipeAddr{}
}