blob: e38f3cf0dfe7e7a37fe217cca3ad9fcbe9eba860 [file] [log] [blame]
// 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"
"v.io/v23/naming"
"v.io/v23/rpc/version"
)
const (
separator = "@"
suffix = "@@"
blessingsSeparator = ","
)
var (
errInvalidEndpointString = errors.New("invalid endpoint string")
hostportEP = regexp.MustCompile("^(?:\\((.*)\\)@)?([^@]+)$")
)
// Network is the string returned by naming.Endpoint.Network implementations
// defined in this package.
const Network = "v23"
// Endpoint is a naming.Endpoint implementation used to convey RPC information.
type Endpoint struct {
Protocol string
Address string
RID naming.RoutingID
MinRPCVersion version.RPCVersion
MaxRPCVersion version.RPCVersion
Blessings []string
IsMountTable bool
IsLeaf bool
}
// NewEndpoint creates a new endpoint from a string as per naming.NewEndpoint
func NewEndpoint(input string) (*Endpoint, error) {
ep := new(Endpoint)
// We have to guess this is a mount table if we don't know.
ep.IsMountTable = true
// 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]
}
err := ep.parseHostPort(blessing, hostport)
return ep, err
}
// 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 nil, fmt.Errorf("invalid version: %v", err)
}
switch version {
case 1:
err = ep.parseV1(parts)
case 2:
err = ep.parseV2(parts)
case 3:
err = ep.parseV3(parts)
case 4:
err = ep.parseV4(parts)
case 5:
err = ep.parseV5(parts)
default:
err = errInvalidEndpointString
}
return ep, err
}
func (ep *Endpoint) parseHostPort(blessing, hostport string) error {
// Could be in host:port format.
if _, _, err := net.SplitHostPort(hostport); err != nil {
return errInvalidEndpointString
}
ep.Protocol = naming.UnknownProtocol
ep.Address = hostport
ep.RID = naming.NullRoutingID
if len(blessing) > 0 {
ep.Blessings = []string{blessing}
}
return nil
}
func (ep *Endpoint) parseV1(parts []string) error {
if len(parts) != 4 {
return errInvalidEndpointString
}
ep.Protocol = parts[1]
if len(ep.Protocol) == 0 {
ep.Protocol = naming.UnknownProtocol
}
ep.Address = parts[2]
if len(ep.Address) == 0 {
ep.Address = net.JoinHostPort("", "0")
}
if err := ep.RID.FromString(parts[3]); err != nil {
return fmt.Errorf("invalid routing id: %v", err)
}
return nil
}
func parseRPCVersion(input string) (version.RPCVersion, error) {
if input == "" {
return version.UnknownRPCVersion, nil
}
v, err := strconv.ParseUint(input, 10, 32)
if err != nil {
err = fmt.Errorf("invalid RPC version: %s, %v", input, err)
}
return version.RPCVersion(v), err
}
func printRPCVersion(v version.RPCVersion) string {
if v == version.UnknownRPCVersion {
return ""
}
return strconv.FormatUint(uint64(v), 10)
}
func parseMountTableFlag(input string) (bool, bool, error) {
switch len(input) {
case 0:
return true, false, nil
case 1:
switch f := input[0]; f {
case 'l':
return false, true, nil
case 'm':
return true, false, nil
case 's':
return false, false, nil
default:
return false, false, fmt.Errorf("%c is not one of 'l', 'm', or 's'", f)
}
}
return false, false, fmt.Errorf("flag is either missing or too long")
}
func (ep *Endpoint) parseV2(parts []string) error {
var err error
if len(parts) != 6 {
return errInvalidEndpointString
}
if err = ep.parseV1(parts[:4]); err != nil {
return err
}
if ep.MinRPCVersion, err = parseRPCVersion(parts[4]); err != nil {
return fmt.Errorf("invalid RPC version: %v", err)
}
if ep.MaxRPCVersion, err = parseRPCVersion(parts[5]); err != nil {
return fmt.Errorf("invalid RPC version: %v", err)
}
return nil
}
func (ep *Endpoint) parseV3(parts []string) error {
var err error
if len(parts) != 7 {
return errInvalidEndpointString
}
if err = ep.parseV2(parts[:6]); err != nil {
return err
}
if ep.IsMountTable, ep.IsLeaf, err = parseMountTableFlag(parts[6]); err != nil {
return fmt.Errorf("invalid mount table flag: %v", err)
}
return nil
}
func (ep *Endpoint) parseV4(parts []string) error {
if len(parts) < 7 {
return errInvalidEndpointString
}
if err := ep.parseV3(parts[:7]); err != nil {
return err
}
// Join the remaining and re-split.
if str := strings.Join(parts[7:], separator); len(str) > 0 {
ep.Blessings = strings.Split(str, blessingsSeparator)
}
return nil
}
func (ep *Endpoint) parseV5(parts []string) error {
if len(parts) < 5 {
return errInvalidEndpointString
}
var err error
if err = ep.parseV1(parts[:4]); err != nil {
return err
}
if ep.IsMountTable, ep.IsLeaf, err = parseMountTableFlag(parts[4]); err != nil {
return fmt.Errorf("invalid mount table flag: %v", err)
}
// Join the remaining and re-split.
if str := strings.Join(parts[5:], separator); len(str) > 0 {
ep.Blessings = strings.Split(str, blessingsSeparator)
}
ep.MinRPCVersion = version.DeprecatedRPCVersion
ep.MaxRPCVersion = version.DeprecatedRPCVersion
return nil
}
func (ep *Endpoint) RoutingID() naming.RoutingID {
//nologcall
return ep.RID
}
func (ep *Endpoint) Network() string {
//nologcall
return Network
}
var defaultVersion = 5
func (ep *Endpoint) VersionedString(version int) string {
switch version {
default:
return ep.VersionedString(defaultVersion)
case 1:
return fmt.Sprintf("@1@%s@%s@@", ep.Protocol, ep.Address)
case 2:
return fmt.Sprintf("@2@%s@%s@%s@%s@%s@@",
ep.Protocol, ep.Address, ep.RID,
printRPCVersion(ep.MinRPCVersion), printRPCVersion(ep.MaxRPCVersion))
case 3:
mt := "s"
if ep.IsMountTable {
mt = "m"
}
return fmt.Sprintf("@3@%s@%s@%s@%s@%s@%s@@",
ep.Protocol, ep.Address, ep.RID,
printRPCVersion(ep.MinRPCVersion), printRPCVersion(ep.MaxRPCVersion),
mt)
case 4:
mt := "s"
switch {
case ep.IsLeaf:
mt = "l"
case ep.IsMountTable:
mt = "m"
}
blessings := strings.Join(ep.Blessings, blessingsSeparator)
return fmt.Sprintf("@4@%s@%s@%s@%s@%s@%s@%s@@",
ep.Protocol, ep.Address, ep.RID,
printRPCVersion(ep.MinRPCVersion), printRPCVersion(ep.MaxRPCVersion),
mt, blessings)
case 5:
mt := "s"
switch {
case ep.IsLeaf:
mt = "l"
case ep.IsMountTable:
mt = "m"
}
blessings := strings.Join(ep.Blessings, blessingsSeparator)
return fmt.Sprintf("@5@%s@%s@%s@%s@%s@@",
ep.Protocol, ep.Address, ep.RID, mt, blessings)
}
}
func (ep *Endpoint) String() string {
//nologcall
// Use version 4 if blessings are present, otherwise there is a loss of information.
v := defaultVersion
if len(ep.Blessings) > 0 && v < 4 {
v = 4
}
return ep.VersionedString(v)
}
func (ep *Endpoint) Name() string {
//nologcall
return naming.JoinAddressName(ep.String(), "")
}
func (ep *Endpoint) Addr() net.Addr {
//nologcall
return &addr{network: ep.Protocol, address: ep.Address}
}
func (ep *Endpoint) ServesMountTable() bool {
//nologcall
return ep.IsMountTable
}
func (ep *Endpoint) ServesLeaf() bool {
//nologcall
return ep.IsLeaf
}
func (ep *Endpoint) BlessingNames() []string {
//nologcall
return ep.Blessings
}
func (ep *Endpoint) RPCVersionRange() version.RPCVersionRange {
//nologcall
return version.RPCVersionRange{Min: ep.MinRPCVersion, Max: ep.MaxRPCVersion}
}
type addr struct {
network, address string
}
func (a *addr) Network() string {
return a.network
}
func (a *addr) String() string {
return a.address
}