github.com/yggdrasil-network/yggdrasil-go@v0.5.6/src/core/nodeinfo.go (about)

     1  package core
     2  
     3  import (
     4  	"encoding/hex"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"runtime"
     9  	"time"
    10  
    11  	iwt "github.com/Arceliar/ironwood/types"
    12  	"github.com/Arceliar/phony"
    13  	"github.com/yggdrasil-network/yggdrasil-go/src/version"
    14  )
    15  
    16  type nodeinfo struct {
    17  	phony.Inbox
    18  	proto      *protoHandler
    19  	myNodeInfo json.RawMessage
    20  	callbacks  map[keyArray]nodeinfoCallback
    21  }
    22  
    23  type nodeinfoCallback struct {
    24  	call    func(nodeinfo json.RawMessage)
    25  	created time.Time
    26  }
    27  
    28  // Initialises the nodeinfo cache/callback maps, and starts a goroutine to keep
    29  // the cache/callback maps clean of stale entries
    30  func (m *nodeinfo) init(proto *protoHandler) {
    31  	m.Act(nil, func() {
    32  		m._init(proto)
    33  	})
    34  }
    35  
    36  func (m *nodeinfo) _init(proto *protoHandler) {
    37  	m.proto = proto
    38  	m.callbacks = make(map[keyArray]nodeinfoCallback)
    39  	m._cleanup()
    40  }
    41  
    42  func (m *nodeinfo) _cleanup() {
    43  	for boxPubKey, callback := range m.callbacks {
    44  		if time.Since(callback.created) > time.Minute {
    45  			delete(m.callbacks, boxPubKey)
    46  		}
    47  	}
    48  	time.AfterFunc(time.Second*30, func() {
    49  		m.Act(nil, m._cleanup)
    50  	})
    51  }
    52  
    53  func (m *nodeinfo) _addCallback(sender keyArray, call func(nodeinfo json.RawMessage)) {
    54  	m.callbacks[sender] = nodeinfoCallback{
    55  		created: time.Now(),
    56  		call:    call,
    57  	}
    58  }
    59  
    60  // Handles the callback, if there is one
    61  func (m *nodeinfo) _callback(sender keyArray, nodeinfo json.RawMessage) {
    62  	if callback, ok := m.callbacks[sender]; ok {
    63  		callback.call(nodeinfo)
    64  		delete(m.callbacks, sender)
    65  	}
    66  }
    67  
    68  func (m *nodeinfo) _getNodeInfo() json.RawMessage {
    69  	return m.myNodeInfo
    70  }
    71  
    72  // Set the current node's nodeinfo
    73  func (m *nodeinfo) setNodeInfo(given map[string]interface{}, privacy bool) (err error) {
    74  	phony.Block(m, func() {
    75  		err = m._setNodeInfo(given, privacy)
    76  	})
    77  	return
    78  }
    79  
    80  func (m *nodeinfo) _setNodeInfo(given map[string]interface{}, privacy bool) error {
    81  	newnodeinfo := make(map[string]interface{}, len(given))
    82  	for k, v := range given {
    83  		newnodeinfo[k] = v
    84  	}
    85  	if !privacy {
    86  		newnodeinfo["buildname"] = version.BuildName()
    87  		newnodeinfo["buildversion"] = version.BuildVersion()
    88  		newnodeinfo["buildplatform"] = runtime.GOOS
    89  		newnodeinfo["buildarch"] = runtime.GOARCH
    90  	}
    91  	newjson, err := json.Marshal(newnodeinfo)
    92  	switch {
    93  	case err != nil:
    94  		return fmt.Errorf("NodeInfo marshalling failed: %w", err)
    95  	case len(newjson) > 16384:
    96  		return fmt.Errorf("NodeInfo exceeds max length of 16384 bytes")
    97  	default:
    98  		m.myNodeInfo = newjson
    99  		return nil
   100  	}
   101  }
   102  
   103  func (m *nodeinfo) sendReq(from phony.Actor, key keyArray, callback func(nodeinfo json.RawMessage)) {
   104  	m.Act(from, func() {
   105  		m._sendReq(key, callback)
   106  	})
   107  }
   108  
   109  func (m *nodeinfo) _sendReq(key keyArray, callback func(nodeinfo json.RawMessage)) {
   110  	if callback != nil {
   111  		m._addCallback(key, callback)
   112  	}
   113  	_, _ = m.proto.core.PacketConn.WriteTo([]byte{typeSessionProto, typeProtoNodeInfoRequest}, iwt.Addr(key[:]))
   114  }
   115  
   116  func (m *nodeinfo) handleReq(from phony.Actor, key keyArray) {
   117  	m.Act(from, func() {
   118  		m._sendRes(key)
   119  	})
   120  }
   121  
   122  func (m *nodeinfo) handleRes(from phony.Actor, key keyArray, info json.RawMessage) {
   123  	m.Act(from, func() {
   124  		m._callback(key, info)
   125  	})
   126  }
   127  
   128  func (m *nodeinfo) _sendRes(key keyArray) {
   129  	bs := append([]byte{typeSessionProto, typeProtoNodeInfoResponse}, m._getNodeInfo()...)
   130  	_, _ = m.proto.core.PacketConn.WriteTo(bs, iwt.Addr(key[:]))
   131  }
   132  
   133  // Admin socket stuff
   134  
   135  type GetNodeInfoRequest struct {
   136  	Key string `json:"key"`
   137  }
   138  type GetNodeInfoResponse map[string]json.RawMessage
   139  
   140  func (m *nodeinfo) nodeInfoAdminHandler(in json.RawMessage) (interface{}, error) {
   141  	var req GetNodeInfoRequest
   142  	if err := json.Unmarshal(in, &req); err != nil {
   143  		return nil, err
   144  	}
   145  	if req.Key == "" {
   146  		return nil, fmt.Errorf("No remote public key supplied")
   147  	}
   148  	var key keyArray
   149  	var kbs []byte
   150  	var err error
   151  	if kbs, err = hex.DecodeString(req.Key); err != nil {
   152  		return nil, fmt.Errorf("Failed to decode public key: %w", err)
   153  	}
   154  	copy(key[:], kbs)
   155  	ch := make(chan []byte, 1)
   156  	m.sendReq(nil, key, func(info json.RawMessage) {
   157  		ch <- info
   158  	})
   159  	timer := time.NewTimer(6 * time.Second)
   160  	defer timer.Stop()
   161  	select {
   162  	case <-timer.C:
   163  		return nil, errors.New("Timed out waiting for response")
   164  	case info := <-ch:
   165  		var msg json.RawMessage
   166  		if err := msg.UnmarshalJSON(info); err != nil {
   167  			return nil, err
   168  		}
   169  		key := hex.EncodeToString(kbs[:])
   170  		res := GetNodeInfoResponse{key: msg}
   171  		return res, nil
   172  	}
   173  }