git.gammaspectra.live/P2Pool/consensus/v3@v3.8.0/monero/client/levin/client.go (about)

     1  package levin
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"time"
    10  )
    11  
    12  const DialTimeout = 15 * time.Second
    13  
    14  type Client struct {
    15  	conn net.Conn
    16  }
    17  
    18  type ClientConfig struct {
    19  	ContextDialer ContextDialer
    20  }
    21  
    22  type ClientOption func(*ClientConfig)
    23  
    24  type ContextDialer interface {
    25  	DialContext(ctx context.Context, network, addr string) (net.Conn, error)
    26  }
    27  
    28  func WithContextDialer(v ContextDialer) func(*ClientConfig) {
    29  	return func(c *ClientConfig) {
    30  		c.ContextDialer = v
    31  	}
    32  }
    33  
    34  func NewClient(ctx context.Context, addr string, opts ...ClientOption) (*Client, error) {
    35  	cfg := &ClientConfig{
    36  		ContextDialer: &net.Dialer{},
    37  	}
    38  	for _, opt := range opts {
    39  		opt(cfg)
    40  	}
    41  
    42  	conn, err := cfg.ContextDialer.DialContext(ctx, "tcp", addr)
    43  	if err != nil {
    44  		return nil, fmt.Errorf("dial ctx: %w", err)
    45  	}
    46  
    47  	return &Client{
    48  		conn: conn,
    49  	}, nil
    50  }
    51  
    52  func (c *Client) Close() error {
    53  	if c.conn == nil {
    54  		return nil
    55  	}
    56  
    57  	if err := c.conn.Close(); err != nil {
    58  		return fmt.Errorf("close: %w", err)
    59  	}
    60  
    61  	return nil
    62  }
    63  
    64  func (c *Client) Handshake(ctx context.Context) (*Node, error) {
    65  	payload := (&PortableStorage{
    66  		Entries: []Entry{
    67  			{
    68  				Name: "node_data",
    69  				Serializable: &Section{
    70  					Entries: []Entry{
    71  						{
    72  							Name:         "network_id",
    73  							Serializable: BoostString(string(MainnetNetworkId)),
    74  						},
    75  					},
    76  				},
    77  			},
    78  		},
    79  	}).Bytes()
    80  
    81  	reqHeaderB := NewRequestHeader(CommandHandshake, uint64(len(payload))).Bytes()
    82  
    83  	if _, err := c.conn.Write(reqHeaderB); err != nil {
    84  		return nil, fmt.Errorf("write header: %w", err)
    85  	}
    86  
    87  	if _, err := c.conn.Write(payload); err != nil {
    88  		return nil, fmt.Errorf("write payload: %w", err)
    89  	}
    90  
    91  again:
    92  	responseHeaderB := make([]byte, LevinHeaderSizeBytes)
    93  	if _, err := io.ReadFull(c.conn, responseHeaderB); err != nil {
    94  		return nil, fmt.Errorf("read full header: %w", err)
    95  	}
    96  
    97  	respHeader, err := NewHeaderFromBytesBytes(responseHeaderB)
    98  	if err != nil {
    99  		return nil, fmt.Errorf("new header from resp bytes: %w", err)
   100  	}
   101  
   102  	dest := new(bytes.Buffer)
   103  
   104  	if respHeader.Length != 0 {
   105  		if _, err := io.CopyN(dest, c.conn, int64(respHeader.Length)); err != nil {
   106  			return nil, fmt.Errorf("copy payload to stdout: %w", err)
   107  		}
   108  	}
   109  
   110  	if respHeader.Command != CommandHandshake {
   111  		dest.Reset()
   112  		goto again
   113  	}
   114  
   115  	ps, err := NewPortableStorageFromBytes(dest.Bytes())
   116  	if err != nil {
   117  		return nil, fmt.Errorf("new portable storage from bytes: %w", err)
   118  	}
   119  
   120  	peerList := NewNodeFromEntries(ps.Entries)
   121  	return &peerList, nil
   122  }
   123  
   124  func (c *Client) Ping(ctx context.Context) error {
   125  	reqHeaderB := NewRequestHeader(CommandPing, 0).Bytes()
   126  
   127  	if _, err := c.conn.Write(reqHeaderB); err != nil {
   128  		return fmt.Errorf("write: %w", err)
   129  	}
   130  
   131  	responseHeaderB := make([]byte, LevinHeaderSizeBytes)
   132  	if _, err := io.ReadFull(c.conn, responseHeaderB); err != nil {
   133  		return fmt.Errorf("read full header: %w", err)
   134  	}
   135  
   136  	respHeader, err := NewHeaderFromBytesBytes(responseHeaderB)
   137  	if err != nil {
   138  		return fmt.Errorf("new header from resp bytes: %w", err)
   139  	}
   140  
   141  	fmt.Printf("%+v\n", respHeader)
   142  
   143  	return nil
   144  }