This repository has been archived on 2024-04-07. You can view files and clone it, but cannot push or open issues or pull requests.
go-monero/pkg/levin/client.go
Ciro S. Costa 5e09fd4047 pkg/levin: local_peerlist_new -> node
Signed-off-by: Ciro S. Costa <utxobr@protonmail.com>
2021-04-25 16:41:24 -04:00

145 lines
3 KiB
Go

package levin
import (
"bytes"
"context"
"fmt"
"io"
"net"
"time"
)
const DialTimeout = 15 * time.Second
type Client struct {
conn net.Conn
}
type ClientConfig struct {
ContextDialer ContextDialer
}
type ClientOption func(*ClientConfig)
type ContextDialer interface {
DialContext(ctx context.Context, network, addr string) (net.Conn, error)
}
func WithContextDialer(v ContextDialer) func(*ClientConfig) {
return func(c *ClientConfig) {
c.ContextDialer = v
}
}
func NewClient(ctx context.Context, addr string, opts ...ClientOption) (*Client, error) {
cfg := &ClientConfig{
ContextDialer: &net.Dialer{},
}
for _, opt := range opts {
opt(cfg)
}
conn, err := cfg.ContextDialer.DialContext(ctx, "tcp", addr)
if err != nil {
return nil, fmt.Errorf("dial ctx: %w", err)
}
return &Client{
conn: conn,
}, nil
}
func (c *Client) Close() error {
if c.conn == nil {
return nil
}
if err := c.conn.Close(); err != nil {
return fmt.Errorf("close: %w", err)
}
return nil
}
func (c *Client) Handshake(ctx context.Context) (*Node, error) {
payload := (&PortableStorage{
Entries: []Entry{
{
Name: "node_data",
Serializable: &Section{
Entries: []Entry{
{
Name: "network_id",
Serializable: BoostString(string(MainnetNetworkId)),
},
},
},
},
},
}).Bytes()
reqHeaderB := NewRequestHeader(CommandHandshake, uint64(len(payload))).Bytes()
if _, err := c.conn.Write(reqHeaderB); err != nil {
return nil, fmt.Errorf("write header: %w", err)
}
if _, err := c.conn.Write(payload); err != nil {
return nil, fmt.Errorf("write payload: %w", err)
}
again:
responseHeaderB := make([]byte, LevinHeaderSizeBytes)
if _, err := io.ReadFull(c.conn, responseHeaderB); err != nil {
return nil, fmt.Errorf("read full header: %w", err)
}
respHeader, err := NewHeaderFromBytesBytes(responseHeaderB)
if err != nil {
return nil, fmt.Errorf("new header from resp bytes: %w", err)
}
dest := new(bytes.Buffer)
if respHeader.Length != 0 {
if _, err := io.CopyN(dest, c.conn, int64(respHeader.Length)); err != nil {
return nil, fmt.Errorf("copy payload to stdout: %w", err)
}
}
if respHeader.Command != CommandHandshake {
dest.Reset()
goto again
}
ps, err := NewPortableStorageFromBytes(dest.Bytes())
if err != nil {
return nil, fmt.Errorf("new portable storage from bytes: %w", err)
}
peerList := NewNodeFromEntries(ps.Entries)
return &peerList, nil
}
func (c *Client) Ping(ctx context.Context) error {
reqHeaderB := NewRequestHeader(CommandPing, 0).Bytes()
if _, err := c.conn.Write(reqHeaderB); err != nil {
return fmt.Errorf("write: %w", err)
}
responseHeaderB := make([]byte, LevinHeaderSizeBytes)
if _, err := io.ReadFull(c.conn, responseHeaderB); err != nil {
return fmt.Errorf("read full header: %w", err)
}
respHeader, err := NewHeaderFromBytesBytes(responseHeaderB)
if err != nil {
return fmt.Errorf("new header from resp bytes: %w", err)
}
fmt.Printf("%+v\n", respHeader)
return nil
}