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 }