-
Notifications
You must be signed in to change notification settings - Fork 28
/
endpoint.go
257 lines (230 loc) · 7.53 KB
/
endpoint.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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
// Copyright 2015 The Vanadium 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 naming
import (
"errors"
"fmt"
"net"
"regexp"
"strconv"
"strings"
)
const (
separator = "@"
suffix = "@@"
blessingsSeparator = ","
routeSeparator = ","
)
var (
errInvalidEndpointString = errors.New("invalid endpoint string")
hostportEP = regexp.MustCompile(`^(?:\((.*)\)@)?([^@]+)$`)
// DefaultEndpointVersion is the default of endpoints that we will create
// when the version is otherwise unspecified.
DefaultEndpointVersion = 6
)
// Endpoint represents unique identifiers for entities communicating over a
// network. End users don't use endpoints - they deal solely with object names,
// with the MountTable providing translation of object names to endpoints.
type Endpoint struct {
Protocol string
Address string
// RoutingID returns the RoutingID associated with this Endpoint.
RoutingID RoutingID
routes []string
blessingNames []string
// ServesMountTable is true if this endpoint serves a mount table.
// TODO(mattr): Remove it?
ServesMountTable bool
}
// ParseEndpoint returns an Endpoint by parsing the supplied endpoint
// string as per the format described above. It can be used to test
// a string to see if it's in valid endpoint format.
//
// NewEndpoint will accept strings both in the @ format described
// above and in internet host:port format.
//
// All implementations of NewEndpoint should provide appropriate
// defaults for any endpoint subfields not explicitly provided as
// follows:
// - a missing protocol will default to a protocol appropriate for the
// implementation hosting NewEndpoint
// - a missing host:port will default to :0 - i.e. any port on all
// interfaces
// - a missing routing id should default to the null routing id
// - a missing codec version should default to AnyCodec
// - a missing RPC version should default to the highest version
// supported by the runtime implementation hosting NewEndpoint
func ParseEndpoint(input string) (Endpoint, error) {
// If the endpoint does not end in a @, it must be in [blessing@]host:port format.
if parts := hostportEP.FindStringSubmatch(input); len(parts) > 0 {
hostport := parts[len(parts)-1]
var blessing string
if len(parts) > 2 {
blessing = parts[1]
}
return parseHostPort(blessing, hostport)
}
// Trim the prefix and suffix and parse the rest.
input = strings.TrimPrefix(strings.TrimSuffix(input, suffix), separator)
parts := strings.Split(input, separator)
version, err := strconv.ParseUint(parts[0], 10, 16)
if err != nil {
return Endpoint{}, fmt.Errorf("invalid version: %v", err)
}
switch version {
case 6:
return parseV6(parts)
default:
return Endpoint{}, errInvalidEndpointString
}
}
func parseHostPort(blessing, hostport string) (Endpoint, error) {
// Could be in host:port format.
var ep Endpoint
if _, _, err := net.SplitHostPort(hostport); err != nil {
return ep, errInvalidEndpointString
}
if strings.HasSuffix(hostport, "#") {
hostport = strings.TrimSuffix(hostport, "#")
} else {
ep.ServesMountTable = true
}
ep.Protocol = UnknownProtocol
ep.Address = hostport
ep.RoutingID = NullRoutingID
if len(blessing) > 0 {
ep.blessingNames = []string{blessing}
}
return ep, nil
}
func parseV6(parts []string) (Endpoint, error) {
var ep Endpoint
if len(parts) < 6 {
return ep, errInvalidEndpointString
}
ep.Protocol = parts[1]
if len(ep.Protocol) == 0 {
ep.Protocol = UnknownProtocol
}
var ok bool
if ep.Address, ok = Unescape(parts[2]); !ok {
return ep, fmt.Errorf("invalid address: bad escape %s", parts[2])
}
if len(ep.Address) == 0 {
ep.Address = net.JoinHostPort("", "0")
}
if len(parts[3]) > 0 {
ep.routes = strings.Split(parts[3], routeSeparator)
for i := range ep.routes {
if ep.routes[i], ok = Unescape(ep.routes[i]); !ok {
return ep, fmt.Errorf("invalid route: bad escape %s", ep.routes[i])
}
}
}
if err := ep.RoutingID.FromString(parts[4]); err != nil {
return ep, fmt.Errorf("invalid routing id: %v", err)
}
switch p := parts[5]; p {
case "", "m":
ep.ServesMountTable = true
case "s", "l":
default:
return ep, fmt.Errorf("invalid mount table flag (%v)", p)
}
// Join the remaining and re-split.
if str := strings.Join(parts[6:], separator); len(str) > 0 {
ep.blessingNames = strings.Split(str, blessingsSeparator)
}
return ep, nil
}
// WithBlessingNames derives a new endpoint with the given
// blessing names, but otherwise identical to e.
func (e Endpoint) WithBlessingNames(names []string) Endpoint {
e.blessingNames = append([]string{}, names...)
return e
}
// WithRoutes derives a new endpoint with the given
// blessing names, but otherwise identical to e.
func (e Endpoint) WithRoutes(routes []string) Endpoint {
e.routes = append([]string{}, routes...)
return e
}
// BlessingNames returns the blessings that the process associated with
// this Endpoint will present.
func (e Endpoint) BlessingNames() []string {
return append([]string{}, e.blessingNames...)
}
// Routes returns the local routing identifiers used for proxying connections
// with multiple proxies.
func (e Endpoint) Routes() []string {
return append([]string{}, e.routes...)
}
// IsZero returns true if the endpoint is equivalent to the zero value.
func (e Endpoint) IsZero() bool {
return e.Protocol == "" &&
e.Address == "" &&
e.RoutingID == RoutingID{} &&
len(e.routes) == 0 &&
len(e.blessingNames) == 0 &&
!e.ServesMountTable
}
// VersionedString returns a string in the specified format. If the version
// number is unsupported, the current 'default' version will be used.
func (e Endpoint) VersionedString(version int) string {
// nologcall
switch version {
case 6:
mt := "s"
if e.ServesMountTable {
mt = "m"
}
blessings := strings.Join(e.blessingNames, blessingsSeparator)
escaped := make([]string, len(e.routes))
for i := range e.routes {
escaped[i] = Escape(e.routes[i], routeSeparator)
}
routes := strings.Join(escaped, routeSeparator)
return fmt.Sprintf("@6@%s@%s@%s@%s@%s@%s@@",
e.Protocol, Escape(e.Address, "@"), routes, e.RoutingID, mt, blessings)
default:
return e.VersionedString(DefaultEndpointVersion)
}
}
func (e Endpoint) String() string {
return e.VersionedString(DefaultEndpointVersion)
}
// Name returns a string reprsentation of this Endpoint that can
// be used as a name with rpc.StartCall.
func (e Endpoint) Name() string {
return JoinAddressName(e.String(), "")
}
// Addr returns a net.Addr whose String method will return the
// the underlying network address encoded in the endpoint rather than
// the endpoint string itself.
// For example, for TCP based endpoints it will return a net.Addr
// whose network is "tcp" and string representation is <host>:<port>,
// than the full Vanadium endpoint as per the String method above.
func (e Endpoint) Addr() net.Addr {
return addr{network: e.Protocol, address: e.Address}
}
type addr struct {
network, address string
}
// Network returns "v23" so that Endpoint can implement net.Addr.
func (a addr) Network() string {
return a.network
}
// String returns a string representation of the endpoint.
//
// The String method formats the endpoint as:
// @<version>@<version specific fields>@@
// Where version is an unsigned integer.
//
// Version 6 is the current version for RPC:
// @6@<protocol>@<address>@<route>[,<route>]...@<routingid>@m|s@[<blessing>[,<blessing>]...]@@
//
// Along with Network, this method ensures that Endpoint implements net.Addr.
func (a addr) String() string {
return a.address
}