You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
8.5 KiB
259 lines
8.5 KiB
// Copyright (C) MongoDB, Inc. 2021-present.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License. You may obtain
|
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
package operation
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"runtime"
|
|
"strconv"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/internal"
|
|
"go.mongodb.org/mongo-driver/mongo/address"
|
|
"go.mongodb.org/mongo-driver/mongo/description"
|
|
"go.mongodb.org/mongo-driver/version"
|
|
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
|
|
"go.mongodb.org/mongo-driver/x/mongo/driver"
|
|
"go.mongodb.org/mongo-driver/x/mongo/driver/session"
|
|
)
|
|
|
|
// Hello is used to run the handshake operation.
|
|
type Hello struct {
|
|
appname string
|
|
compressors []string
|
|
saslSupportedMechs string
|
|
d driver.Deployment
|
|
clock *session.ClusterClock
|
|
speculativeAuth bsoncore.Document
|
|
topologyVersion *description.TopologyVersion
|
|
maxAwaitTimeMS *int64
|
|
serverAPI *driver.ServerAPIOptions
|
|
loadBalanced bool
|
|
|
|
res bsoncore.Document
|
|
}
|
|
|
|
var _ driver.Handshaker = (*Hello)(nil)
|
|
|
|
// NewHello constructs a Hello.
|
|
func NewHello() *Hello { return &Hello{} }
|
|
|
|
// AppName sets the application name in the client metadata sent in this operation.
|
|
func (h *Hello) AppName(appname string) *Hello {
|
|
h.appname = appname
|
|
return h
|
|
}
|
|
|
|
// ClusterClock sets the cluster clock for this operation.
|
|
func (h *Hello) ClusterClock(clock *session.ClusterClock) *Hello {
|
|
if h == nil {
|
|
h = new(Hello)
|
|
}
|
|
|
|
h.clock = clock
|
|
return h
|
|
}
|
|
|
|
// Compressors sets the compressors that can be used.
|
|
func (h *Hello) Compressors(compressors []string) *Hello {
|
|
h.compressors = compressors
|
|
return h
|
|
}
|
|
|
|
// SASLSupportedMechs retrieves the supported SASL mechanism for the given user when this operation
|
|
// is run.
|
|
func (h *Hello) SASLSupportedMechs(username string) *Hello {
|
|
h.saslSupportedMechs = username
|
|
return h
|
|
}
|
|
|
|
// Deployment sets the Deployment for this operation.
|
|
func (h *Hello) Deployment(d driver.Deployment) *Hello {
|
|
h.d = d
|
|
return h
|
|
}
|
|
|
|
// SpeculativeAuthenticate sets the document to be used for speculative authentication.
|
|
func (h *Hello) SpeculativeAuthenticate(doc bsoncore.Document) *Hello {
|
|
h.speculativeAuth = doc
|
|
return h
|
|
}
|
|
|
|
// TopologyVersion sets the TopologyVersion to be used for heartbeats.
|
|
func (h *Hello) TopologyVersion(tv *description.TopologyVersion) *Hello {
|
|
h.topologyVersion = tv
|
|
return h
|
|
}
|
|
|
|
// MaxAwaitTimeMS sets the maximum time for the server to wait for topology changes during a heartbeat.
|
|
func (h *Hello) MaxAwaitTimeMS(awaitTime int64) *Hello {
|
|
h.maxAwaitTimeMS = &awaitTime
|
|
return h
|
|
}
|
|
|
|
// ServerAPI sets the server API version for this operation.
|
|
func (h *Hello) ServerAPI(serverAPI *driver.ServerAPIOptions) *Hello {
|
|
h.serverAPI = serverAPI
|
|
return h
|
|
}
|
|
|
|
// LoadBalanced specifies whether or not this operation is being sent over a connection to a load balanced cluster.
|
|
func (h *Hello) LoadBalanced(lb bool) *Hello {
|
|
h.loadBalanced = lb
|
|
return h
|
|
}
|
|
|
|
// Result returns the result of executing this operation.
|
|
func (h *Hello) Result(addr address.Address) description.Server {
|
|
return description.NewServer(addr, bson.Raw(h.res))
|
|
}
|
|
|
|
// handshakeCommand appends all necessary command fields as well as client metadata, SASL supported mechs, and compression.
|
|
func (h *Hello) handshakeCommand(dst []byte, desc description.SelectedServer) ([]byte, error) {
|
|
dst, err := h.command(dst, desc)
|
|
if err != nil {
|
|
return dst, err
|
|
}
|
|
|
|
if h.saslSupportedMechs != "" {
|
|
dst = bsoncore.AppendStringElement(dst, "saslSupportedMechs", h.saslSupportedMechs)
|
|
}
|
|
if h.speculativeAuth != nil {
|
|
dst = bsoncore.AppendDocumentElement(dst, "speculativeAuthenticate", h.speculativeAuth)
|
|
}
|
|
var idx int32
|
|
idx, dst = bsoncore.AppendArrayElementStart(dst, "compression")
|
|
for i, compressor := range h.compressors {
|
|
dst = bsoncore.AppendStringElement(dst, strconv.Itoa(i), compressor)
|
|
}
|
|
dst, _ = bsoncore.AppendArrayEnd(dst, idx)
|
|
|
|
// append client metadata
|
|
idx, dst = bsoncore.AppendDocumentElementStart(dst, "client")
|
|
|
|
didx, dst := bsoncore.AppendDocumentElementStart(dst, "driver")
|
|
dst = bsoncore.AppendStringElement(dst, "name", "mongo-go-driver")
|
|
dst = bsoncore.AppendStringElement(dst, "version", version.Driver)
|
|
dst, _ = bsoncore.AppendDocumentEnd(dst, didx)
|
|
|
|
didx, dst = bsoncore.AppendDocumentElementStart(dst, "os")
|
|
dst = bsoncore.AppendStringElement(dst, "type", runtime.GOOS)
|
|
dst = bsoncore.AppendStringElement(dst, "architecture", runtime.GOARCH)
|
|
dst, _ = bsoncore.AppendDocumentEnd(dst, didx)
|
|
|
|
dst = bsoncore.AppendStringElement(dst, "platform", runtime.Version())
|
|
if h.appname != "" {
|
|
didx, dst = bsoncore.AppendDocumentElementStart(dst, "application")
|
|
dst = bsoncore.AppendStringElement(dst, "name", h.appname)
|
|
dst, _ = bsoncore.AppendDocumentEnd(dst, didx)
|
|
}
|
|
dst, _ = bsoncore.AppendDocumentEnd(dst, idx)
|
|
|
|
return dst, nil
|
|
}
|
|
|
|
// command appends all necessary command fields.
|
|
func (h *Hello) command(dst []byte, desc description.SelectedServer) ([]byte, error) {
|
|
// Use "hello" if topology is LoadBalanced, API version is declared or server
|
|
// has responded with "helloOk". Otherwise, use legacy hello.
|
|
if desc.Kind == description.LoadBalanced || h.serverAPI != nil || desc.Server.HelloOK {
|
|
dst = bsoncore.AppendInt32Element(dst, "hello", 1)
|
|
} else {
|
|
dst = bsoncore.AppendInt32Element(dst, internal.LegacyHello, 1)
|
|
}
|
|
dst = bsoncore.AppendBooleanElement(dst, "helloOk", true)
|
|
|
|
if tv := h.topologyVersion; tv != nil {
|
|
var tvIdx int32
|
|
|
|
tvIdx, dst = bsoncore.AppendDocumentElementStart(dst, "topologyVersion")
|
|
dst = bsoncore.AppendObjectIDElement(dst, "processId", tv.ProcessID)
|
|
dst = bsoncore.AppendInt64Element(dst, "counter", tv.Counter)
|
|
dst, _ = bsoncore.AppendDocumentEnd(dst, tvIdx)
|
|
}
|
|
if h.maxAwaitTimeMS != nil {
|
|
dst = bsoncore.AppendInt64Element(dst, "maxAwaitTimeMS", *h.maxAwaitTimeMS)
|
|
}
|
|
if h.loadBalanced {
|
|
// The loadBalanced parameter should only be added if it's true. We should never explicitly send
|
|
// loadBalanced=false per the load balancing spec.
|
|
dst = bsoncore.AppendBooleanElement(dst, "loadBalanced", true)
|
|
}
|
|
|
|
return dst, nil
|
|
}
|
|
|
|
// Execute runs this operation.
|
|
func (h *Hello) Execute(ctx context.Context) error {
|
|
if h.d == nil {
|
|
return errors.New("a Hello must have a Deployment set before Execute can be called")
|
|
}
|
|
|
|
return h.createOperation().Execute(ctx)
|
|
}
|
|
|
|
// StreamResponse gets the next streaming Hello response from the server.
|
|
func (h *Hello) StreamResponse(ctx context.Context, conn driver.StreamerConnection) error {
|
|
return h.createOperation().ExecuteExhaust(ctx, conn)
|
|
}
|
|
|
|
func (h *Hello) createOperation() driver.Operation {
|
|
return driver.Operation{
|
|
Clock: h.clock,
|
|
CommandFn: h.command,
|
|
Database: "admin",
|
|
Deployment: h.d,
|
|
ProcessResponseFn: func(info driver.ResponseInfo) error {
|
|
h.res = info.ServerResponse
|
|
return nil
|
|
},
|
|
ServerAPI: h.serverAPI,
|
|
}
|
|
}
|
|
|
|
// GetHandshakeInformation performs the MongoDB handshake for the provided connection and returns the relevant
|
|
// information about the server. This function implements the driver.Handshaker interface.
|
|
func (h *Hello) GetHandshakeInformation(ctx context.Context, _ address.Address, c driver.Connection) (driver.HandshakeInformation, error) {
|
|
err := driver.Operation{
|
|
Clock: h.clock,
|
|
CommandFn: h.handshakeCommand,
|
|
Deployment: driver.SingleConnectionDeployment{c},
|
|
Database: "admin",
|
|
ProcessResponseFn: func(info driver.ResponseInfo) error {
|
|
h.res = info.ServerResponse
|
|
return nil
|
|
},
|
|
ServerAPI: h.serverAPI,
|
|
}.Execute(ctx)
|
|
if err != nil {
|
|
return driver.HandshakeInformation{}, err
|
|
}
|
|
|
|
info := driver.HandshakeInformation{
|
|
Description: h.Result(c.Address()),
|
|
}
|
|
if speculativeAuthenticate, ok := h.res.Lookup("speculativeAuthenticate").DocumentOK(); ok {
|
|
info.SpeculativeAuthenticate = speculativeAuthenticate
|
|
}
|
|
if serverConnectionID, ok := h.res.Lookup("connectionId").AsInt64OK(); ok {
|
|
info.ServerConnectionID = &serverConnectionID
|
|
}
|
|
// Cast to bson.Raw to lookup saslSupportedMechs to avoid converting from bsoncore.Value to bson.RawValue for the
|
|
// StringSliceFromRawValue call.
|
|
if saslSupportedMechs, lookupErr := bson.Raw(h.res).LookupErr("saslSupportedMechs"); lookupErr == nil {
|
|
info.SaslSupportedMechs, err = internal.StringSliceFromRawValue("saslSupportedMechs", saslSupportedMechs)
|
|
}
|
|
return info, err
|
|
}
|
|
|
|
// FinishHandshake implements the Handshaker interface. This is a no-op function because a non-authenticated connection
|
|
// does not do anything besides the initial Hello for a handshake.
|
|
func (h *Hello) FinishHandshake(context.Context, driver.Connection) error {
|
|
return nil
|
|
}
|