blob: 63be60b5ec717dbb7606f87dbd7796e20b90c0cc [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 version
import (
"fmt"
inaming "v.io/x/ref/profiles/internal/naming"
"v.io/v23/naming"
"v.io/v23/rpc/version"
"v.io/v23/verror"
)
// Range represents a range of RPC versions.
type Range struct {
Min, Max version.RPCVersion
}
var (
// SupportedRange represents the range of protocol verions supported by this
// implementation.
// Max should be incremented whenever we make a protocol
// change that's not both forward and backward compatible.
// Min should be incremented whenever we want to remove
// support for old protocol versions.
SupportedRange = &Range{Min: version.RPCVersion5, Max: version.RPCVersion9}
// Export the methods on supportedRange.
Endpoint = SupportedRange.Endpoint
ProxiedEndpoint = SupportedRange.ProxiedEndpoint
CommonVersion = SupportedRange.CommonVersion
CheckCompatibility = SupportedRange.CheckCompatibility
// Which version to guess servers support if it's unknown.
// TODO(mattr): This is a hack. Once RPCVersion9 is released and versions
// are negotiated, we wont have to guess anymore and this code should
// be removed. This is required until version 9 is live.
// In fact, once version9 is the minimum supported version, much of the
// code in this file can be eliminated.
maxVersionGuess = version.RPCVersion8
)
const pkgPath = "v.io/x/ref/profiles/internal/rpc/version"
func reg(id, msg string) verror.IDAction {
return verror.Register(verror.ID(pkgPath+id), verror.NoRetry, msg)
}
var (
// These errors are intended to be used as arguments to higher
// level errors and hence {1}{2} is omitted from their format
// strings to avoid repeating these n-times in the final error
// message visible to the user.
ErrNoCompatibleVersion = reg(".errNoCompatibleVersionErr", "no compatible RPC version available{:3} not in range {4}..{5}")
ErrUnknownVersion = reg(".errUnknownVersionErr", "there was not enough information to determine a version")
ErrDeprecatedVersion = reg(".errDeprecatedVersionError", "some of the provided version information is deprecated")
errInternalTypeConversionError = reg(".errInternalTypeConversionError", "failed to convert {3} to v.io/ref/profiles/internal/naming.Endpoint {3}")
)
// IsVersionError returns true if err is a versioning related error.
func IsVersionError(err error) bool {
id := verror.ErrorID(err)
return id == ErrNoCompatibleVersion.ID || id == ErrUnknownVersion.ID
}
// Endpoint returns an endpoint with the Min/MaxRPCVersion properly filled in
// to match this implementations supported protocol versions.
func (r *Range) Endpoint(protocol, address string, rid naming.RoutingID) *inaming.Endpoint {
return &inaming.Endpoint{
Protocol: protocol,
Address: address,
RID: rid,
MinRPCVersion: r.Min,
MaxRPCVersion: r.Max,
}
}
// intersectRanges finds the intersection between ranges
// supported by two endpoints. We make an assumption here that if one
// of the endpoints has an UnknownVersion we assume it has the same
// extent as the other endpoint. If both endpoints have Unknown for a
// version number, an error is produced.
// For example:
// a == (2, 4) and b == (Unknown, Unknown), intersect(a,b) == (2, 4)
// a == (2, Unknown) and b == (3, 4), intersect(a,b) == (3, 4)
func intersectRanges(amin, amax, bmin, bmax version.RPCVersion) (min, max version.RPCVersion, err error) {
// TODO(mattr): this may be incorrect. Ensure that when we talk to a server who
// advertises (5,8) and we support (5, 9) but v5 EPs (so we may get d, d here) that
// we use v8 and don't send setupVC.
d := version.DeprecatedRPCVersion
if amin == d || amax == d || bmin == d || bmax == d {
return d, d, verror.New(ErrDeprecatedVersion, nil)
}
u := version.UnknownRPCVersion
min = amin
if min == u || (bmin != u && bmin > min) {
min = bmin
}
max = amax
if max == u || (bmax != u && bmax < max) {
max = bmax
}
// TODO(mattr): This is a hack. Once RPCVersion9 is released and versions
// are negotiated, we wont have to guess anymore and this code should
// be removed. This is required until version 9 is live.
if max > maxVersionGuess && (amax == u || bmax == u) {
max = maxVersionGuess
}
if min == u || max == u {
err = verror.New(ErrUnknownVersion, nil)
} else if min > max {
err = verror.New(ErrNoCompatibleVersion, nil, u, min, max)
}
return
}
func intersectEndpoints(a, b *inaming.Endpoint) (min, max version.RPCVersion, err error) {
return intersectRanges(a.MinRPCVersion, a.MaxRPCVersion, b.MinRPCVersion, b.MaxRPCVersion)
}
func (r1 *Range) Intersect(r2 *Range) (*Range, error) {
min, max, err := intersectRanges(r1.Min, r1.Max, r2.Min, r2.Max)
if err != nil {
return nil, err
}
r := &Range{Min: min, Max: max}
return r, nil
}
// ProxiedEndpoint returns an endpoint with the Min/MaxRPCVersion properly filled in
// to match the intersection of capabilities of this process and the proxy.
func (r *Range) ProxiedEndpoint(rid naming.RoutingID, proxy naming.Endpoint) (*inaming.Endpoint, error) {
proxyEP, ok := proxy.(*inaming.Endpoint)
if !ok {
return nil, verror.New(errInternalTypeConversionError, nil, fmt.Sprintf("%T", proxy))
}
ep := &inaming.Endpoint{
Protocol: proxyEP.Protocol,
Address: proxyEP.Address,
RID: rid,
MinRPCVersion: r.Min,
MaxRPCVersion: r.Max,
}
// This is the endpoint we are going to advertise. It should only claim to support versions in
// the intersection of those we support and those the proxy supports.
var err error
ep.MinRPCVersion, ep.MaxRPCVersion, err = intersectEndpoints(ep, proxyEP)
if err != nil {
return nil, fmt.Errorf("attempting to register with incompatible proxy: %s", proxy)
}
return ep, nil
}
// CommonVersion determines which version of the RPC protocol should be used
// between two endpoints. Returns an error if the resulting version is incompatible
// with this RPC implementation.
func (r *Range) CommonVersion(a, b naming.Endpoint) (version.RPCVersion, error) {
aEP, ok := a.(*inaming.Endpoint)
if !ok {
return 0, verror.New(errInternalTypeConversionError, nil, fmt.Sprintf("%T", a))
}
bEP, ok := b.(*inaming.Endpoint)
if !ok {
return 0, verror.New(errInternalTypeConversionError, nil, fmt.Sprintf("%T", b))
}
_, max, err := intersectEndpoints(aEP, bEP)
if err != nil {
return 0, err
}
// We want to use the maximum common version of the protocol. We just
// need to make sure that it is supported by this RPC implementation.
if max < r.Min || max > r.Max {
return version.UnknownRPCVersion, verror.New(ErrNoCompatibleVersion, nil, max, r.Min, r.Max)
}
return max, nil
}
// CheckCompatibility returns an error if the given endpoint is incompatible
// with this RPC implementation. It returns nil otherwise.
func (r *Range) CheckCompatibility(remote naming.Endpoint) error {
remoteEP, ok := remote.(*inaming.Endpoint)
if !ok {
return verror.New(errInternalTypeConversionError, nil, fmt.Sprintf("%T", remote))
}
if remoteEP.MinRPCVersion == version.DeprecatedRPCVersion &&
remoteEP.MaxRPCVersion == version.DeprecatedRPCVersion {
// If the remote endpoint no longer contains version information
// then compatibility wont be decided here. We simply return
// true and allow the version negotiation to figure it out.
return nil
}
_, _, err := intersectRanges(r.Min, r.Max,
remoteEP.MinRPCVersion, remoteEP.MaxRPCVersion)
return err
}