-
Notifications
You must be signed in to change notification settings - Fork 17
/
conn.go
159 lines (142 loc) · 4.24 KB
/
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
package epp
import (
"encoding/binary"
"encoding/xml"
"io"
"net"
"sync"
"time"
)
// IgnoreEOF returns err unless err == io.EOF,
// in which case it returns nil.
func IgnoreEOF(err error) error {
if err == io.EOF {
return nil
}
return err
}
// Conn represents a single connection to an EPP server.
// Reads and writes are serialized, so it is safe for concurrent use.
type Conn struct {
// Conn is the underlying net.Conn (usually a TLS connection).
net.Conn
// Timeout defines the timeout for network operations.
// It must be set at initialization. Changing it after
// a connection is already opened will have no effect.
Timeout time.Duration
// m protects Greeting and LoginResult.
m sync.Mutex
// Greeting holds the last received greeting message from the server,
// indicating server name, status, data policy and capabilities.
//
// Deprecated: This field is written to upon opening a new EPP connection and should not be modified.
Greeting
// LoginResult holds the last received login response message's Result
// from the server, in which some servers might include diagnostics such
// as connection count limits.
//
// Deprecated: this field is written to by the Login method but otherwise is not used by this package.
LoginResult Result
// mRead synchronizes connection reads.
mRead sync.Mutex
// mWrite synchronizes connection writes.
mWrite sync.Mutex
done chan struct{}
}
// NewConn initializes an epp.Conn from a net.Conn and performs the EPP
// handshake. It reads and stores the initial EPP <greeting> message.
// https://tools.ietf.org/html/rfc5730#section-2.4
func NewConn(conn net.Conn) (*Conn, error) {
return NewTimeoutConn(conn, 0)
}
// NewTimeoutConn initializes an epp.Conn like NewConn, limiting the duration of network
// operations on conn using Set(Read|Write)Deadline.
func NewTimeoutConn(conn net.Conn, timeout time.Duration) (*Conn, error) {
c := &Conn{
Conn: conn,
Timeout: timeout,
done: make(chan struct{}),
}
g, err := c.readGreeting()
if err == nil {
c.m.Lock()
c.Greeting = g
c.m.Unlock()
}
return c, err
}
// Close sends an EPP <logout> command and closes the connection c.
func (c *Conn) Close() error {
select {
case <-c.done:
return net.ErrClosed
default:
}
c.Logout()
close(c.done)
return c.Conn.Close()
}
// writeRequest writes a single EPP request (x) for writing on c.
// writeRequest can be called from multiple goroutines.
func (c *Conn) writeRequest(x []byte) error {
c.mWrite.Lock()
defer c.mWrite.Unlock()
if c.Timeout > 0 {
c.Conn.SetWriteDeadline(time.Now().Add(c.Timeout))
}
return writeDataUnit(c.Conn, x)
}
// readResponse dequeues and returns a EPP response from c.
// It returns an error if the EPP response contains an error Result.
// readResponse can be called from multiple goroutines.
func (c *Conn) readResponse() (*Response, error) {
c.mRead.Lock()
defer c.mRead.Unlock()
if c.Timeout > 0 {
c.Conn.SetReadDeadline(time.Now().Add(c.Timeout))
}
n, err := readDataUnitHeader(c.Conn)
if err != nil {
return nil, err
}
r := io.LimitedReader{R: c.Conn, N: int64(n)}
res := &Response{}
err = IgnoreEOF(scanResponse.Scan(xml.NewDecoder(&r), res))
if err != nil {
return res, err
}
if res.Result.IsError() {
return res, &res.Result
}
return res, err
}
// writeDataUnit writes x to w.
// Bytes written are prefixed with 32-bit header specifying the total size
// of the data unit (message + 4 byte header), in network (big-endian) order.
// http://www.ietf.org/rfc/rfc4934.txt
func writeDataUnit(w io.Writer, x []byte) error {
logXML("<-- WRITE DATA UNIT -->", x)
s := uint32(4 + len(x))
err := binary.Write(w, binary.BigEndian, s)
if err != nil {
return err
}
_, err = w.Write(x)
return err
}
// readDataUnitHeader reads a single EPP data unit header from r, returning the payload size or an error.
// An EPP data unit is prefixed with 32-bit header specifying the total size
// of the data unit (message + 4 byte header), in network (big-endian) order.
// http://www.ietf.org/rfc/rfc4934.txt
func readDataUnitHeader(r io.Reader) (uint32, error) {
var n uint32
err := binary.Read(r, binary.BigEndian, &n)
if err != nil {
return 0, err
}
if n < 4 {
return 0, io.ErrUnexpectedEOF
}
// https://tools.ietf.org/html/rfc5734#section-4
return n - 4, err
}