package client import ( "context" "crypto/tls" "errors" "time" v2accounting "github.com/nspcc-dev/neofs-api-go/v2/accounting" "github.com/nspcc-dev/neofs-api-go/v2/rpc" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" ) // Client represents virtual connection to the NeoFS network to communicate // with NeoFS server using NeoFS API protocol. It is designed to provide // an abstraction interface from the protocol details of data transfer over // a network in NeoFS. // // Client can be created using simple Go variable declaration. Before starting // work with the Client, it SHOULD BE correctly initialized (see Init method). // Before executing the NeoFS operations using the Client, connection to the // server MUST BE correctly established (see Dial method and pay attention // to the mandatory parameters). Using the Client before connecting have // been established can lead to a panic. After the work, the Client SHOULD BE // closed (see Close method): it frees internal and system resources which were // allocated for the period of work of the Client. Calling Init/Dial/Close method // during the communication process step strongly discouraged as it leads to // undefined behavior. // // Each method which produces a NeoFS API call may return a server response. // Status responses are returned in the result structure, and can be cast // to built-in error instance (or in the returned error if the client is // configured accordingly). Certain statuses can be checked using [apistatus] // and standard [errors] packages. // All possible responses are documented in methods, however, some may be // returned from all of them (pay attention to the presence of the pointer sign): // - *[apistatus.ServerInternal] on internal server error; // - *[apistatus.NodeUnderMaintenance] if a server is under maintenance; // - *[apistatus.SuccessDefaultV2] on default success. // // Client MUST NOT be copied by value: use pointer to Client instead. // // See client package overview to get some examples. type Client struct { prm PrmInit c client.Client server neoFSAPIServer } // Init brings the Client instance to its initial state. // // One-time method call during application init stage (before Dial) is expected. // Calling multiple times leads to undefined behavior. // // See docs of PrmInit methods for details. See also Dial / Close. func (c *Client) Init(prm PrmInit) { c.prm = prm } // Dial establishes a connection to the server from the NeoFS network. // Returns an error describing failure reason. If failed, the Client // SHOULD NOT be used. // // Uses the context specified by SetContext if it was called with non-nil // argument, otherwise context.Background() is used. Dial returns context // errors, see context package docs for details. // // Panics if required parameters are set incorrectly, look carefully // at the method documentation. // // One-time method call during application start-up stage (after Init ) is expected. // Calling multiple times leads to undefined behavior. // // Return client errors: // - [ErrMissingServer] // - [ErrNonPositiveTimeout] // // See also Init / Close. func (c *Client) Dial(prm PrmDial) error { if prm.endpoint == "" { return ErrMissingServer } if prm.timeoutDialSet { if prm.timeoutDial <= 0 { return ErrNonPositiveTimeout } } else { prm.timeoutDial = 5 * time.Second } if prm.streamTimeoutSet { if prm.streamTimeout <= 0 { return ErrNonPositiveTimeout } } else { prm.streamTimeout = 10 * time.Second } c.c = *client.New(append( client.WithNetworkURIAddress(prm.endpoint, prm.tlsConfig), client.WithDialTimeout(prm.timeoutDial), client.WithRWTimeout(prm.streamTimeout), )...) c.setNeoFSAPIServer((*coreServer)(&c.c)) if prm.parentCtx == nil { prm.parentCtx = context.Background() } // TODO: (neofs-api-go#382) perform generic dial stage of the client.Client _, err := rpc.Balance(&c.c, new(v2accounting.BalanceRequest), client.WithContext(prm.parentCtx), ) // return context errors since they signal about dial problem if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return err } return nil } // sets underlying provider of neoFSAPIServer. The method is used for testing as an approach // to skip Dial stage and override NeoFS API server. MUST NOT be used outside test code. // In real applications wrapper over github.com/nspcc-dev/neofs-api-go/v2/rpc/client // is statically used. func (c *Client) setNeoFSAPIServer(server neoFSAPIServer) { c.server = server } // Close closes underlying connection to the NeoFS server. Implements io.Closer. // MUST NOT be called before successful Dial. Can be called concurrently // with server operations processing on running goroutines: in this case // they are likely to fail due to a connection error. // // One-time method call during application shutdown stage (after Init and Dial) // is expected. Calling multiple times leads to undefined behavior. // // See also Init / Dial. func (c *Client) Close() error { return c.c.Conn().Close() } // PrmInit groups initialization parameters of Client instances. // // See also Init. type PrmInit struct { signer neofscrypto.Signer cbRespInfo func(ResponseMetaInfo) error netMagic uint64 } // SetDefaultSigner sets Client private signer to be used for the protocol // communication by default. // // Required for operations without custom signer parametrization (see corresponding Prm* docs). func (x *PrmInit) SetDefaultSigner(signer neofscrypto.Signer) { x.signer = signer } // SetResponseInfoCallback makes the Client to pass ResponseMetaInfo from each // NeoFS server response to f. Nil (default) means ignore response meta info. func (x *PrmInit) SetResponseInfoCallback(f func(ResponseMetaInfo) error) { x.cbRespInfo = f } // PrmDial groups connection parameters for the Client. // // See also Dial. type PrmDial struct { endpoint string tlsConfig *tls.Config timeoutDialSet bool timeoutDial time.Duration streamTimeoutSet bool streamTimeout time.Duration parentCtx context.Context } // SetServerURI sets server URI in the NeoFS network. // Required parameter. // // Format of the URI: // // [scheme://]host:port // // Supported schemes: // // grpc // grpcs // // See also SetTLSConfig. func (x *PrmDial) SetServerURI(endpoint string) { x.endpoint = endpoint } // SetTLSConfig sets tls.Config to open TLS client connection // to the NeoFS server. Nil (default) means insecure connection. // // See also SetServerURI. func (x *PrmDial) SetTLSConfig(tlsConfig *tls.Config) { x.tlsConfig = tlsConfig } // SetTimeout sets the timeout for connection to be established. // MUST BE positive. If not called, 5s timeout will be used by default. func (x *PrmDial) SetTimeout(timeout time.Duration) { x.timeoutDialSet = true x.timeoutDial = timeout } // SetStreamTimeout sets the timeout for individual operations in streaming RPC. // MUST BE positive. If not called, 10s timeout will be used by default. func (x *PrmDial) SetStreamTimeout(timeout time.Duration) { x.streamTimeoutSet = true x.streamTimeout = timeout } // SetContext allows to specify optional base context within which connection // should be established. // // Context SHOULD NOT be nil. func (x *PrmDial) SetContext(ctx context.Context) { x.parentCtx = ctx }