package network import ( "context" "errors" "net" "sync" "time" "github.com/number571/go-peer/pkg/crypto/scheme/layer1" "github.com/number571/go-peer/pkg/encoding" "github.com/number571/go-peer/pkg/network/conn" "github.com/number571/go-peer/pkg/storage/cache" ) var ( _ INode = &sNode{} ) type sNode struct { fMutex sync.RWMutex fSettings ISettings fHandlerF IHandlerF fListener net.Listener fCacheSetter cache.ICacheSetter fConnections map[string]conn.IConn } // Creating a node object managed by connections with multiple nodes. // Saves hashes of received messages to a buffer to prevent network cycling. // Redirects messages to handle routers by keys. func NewNode( pSettings ISettings, pHandlerF IHandlerF, pCacheSetter cache.ICacheSetter, ) INode { return &sNode{ fSettings: pSettings, fHandlerF: pHandlerF, fCacheSetter: pCacheSetter, fConnections: make(map[string]conn.IConn, pSettings.GetMaxConnects()), } } // Return settings interface. func (p *sNode) GetSettings() ISettings { return p.fSettings } func (p *sNode) GetCacheSetter() cache.ICacheSetter { return p.fCacheSetter } // Puts the hash of the message in the buffer and sends the message to all connections of the node. func (p *sNode) BroadcastMessage(pCtx context.Context, pMsg layer1.IMessage) error { connections := p.GetConnections() lenConnections := len(connections) // can't broadcast message to the network if len(connections) = 0 if lenConnections == 0 { return ErrNoConnections } // node can redirect received message hash := encoding.HexEncode(pMsg.GetHash()) _ = p.fCacheSetter.Set(hash, []byte{}) wg := sync.WaitGroup{} wg.Add(lenConnections) listErr := make([]error, lenConnections) i := 0 for a, c := range connections { chErr := make(chan error) go func(c conn.IConn) { chErr <- c.WriteMessage(pCtx, pMsg) }(c) go func(i int, a string) { defer wg.Done() timer := time.NewTimer(p.fSettings.GetWriteTimeout()) defer timer.Stop() select { case <-pCtx.Done(): listErr[i] = pCtx.Err() case <-timer.C: listErr[i] = ErrWriteTimeout case err := <-chErr: if err == nil { return } listErr[i] = errors.Join(ErrBroadcastMessage, err) } // if got error -> delete connection _ = p.DelConnection(a) }(i, a) i++ } wg.Wait() return errors.Join(listErr...) } // Opens a tcp connection to receive data from outside. // Checks the number of valid connections. // Redirects connections to the handle router. func (p *sNode) Run(pCtx context.Context) error { if p.fSettings.GetAddress() == "" { <-pCtx.Done() return pCtx.Err() } listener, err := net.Listen("tcp", p.fSettings.GetAddress()) if err != nil { return errors.Join(ErrCreateListener, err) } defer func() { _ = listener.Close() }() go func() { <-pCtx.Done() _ = listener.Close() }() p.setListener(listener) for { select { case <-pCtx.Done(): return pCtx.Err() default: tconn, err := p.getListener().Accept() if err != nil { return errors.Join(ErrListenerAccept, err) } if p.hasMaxConnSize() { _ = tconn.Close() continue } conn := conn.LoadConn(p.fSettings.GetConnSettings(), tconn) address := tconn.RemoteAddr().String() p.setConnection(address, conn) go p.handleConn(pCtx, address, conn) } } } // Retrieves the entire list of connections with addresses. func (p *sNode) GetConnections() map[string]conn.IConn { p.fMutex.RLock() defer p.fMutex.RUnlock() mapping := make(map[string]conn.IConn, len(p.fConnections)) for addr, conn := range p.fConnections { mapping[addr] = conn } return mapping } // Connects to the node at the specified address and automatically starts reading all incoming messages. // Checks the number of connections. func (p *sNode) AddConnection(pCtx context.Context, pAddress string) error { if p.hasMaxConnSize() { return ErrHasLimitConnections } if _, ok := p.getConnection(pAddress); ok { return ErrConnectionIsExist } sett := p.fSettings.GetConnSettings() conn, err := conn.Connect(pCtx, sett, pAddress) if err != nil { return errors.Join(ErrAddConnections, err) } p.setConnection(pAddress, conn) go p.handleConn(pCtx, pAddress, conn) return nil } // Disables the connection at the address and removes the connection from the connection list. func (p *sNode) DelConnection(pAddress string) error { p.fMutex.Lock() defer p.fMutex.Unlock() conn, ok := p.fConnections[pAddress] if !ok { return ErrConnectionIsNotExist } delete(p.fConnections, pAddress) if err := conn.Close(); err != nil { return errors.Join(ErrCloseConnection, err) } return nil } // Processes the received data from the connection. func (p *sNode) handleConn(pCtx context.Context, pAddress string, pConn conn.IConn) { defer func() { _ = p.DelConnection(pAddress) }() var ( readHeadCh = make(chan struct{}) readFullCh = make(chan layer1.IMessage) ) go p.messageReader( pCtx, pConn, readHeadCh, readFullCh, ) for { select { case <-pCtx.Done(): return case <-readHeadCh: select { case <-pCtx.Done(): return case <-time.After(p.fSettings.GetReadTimeout()): return case msg := <-readFullCh: if msg == nil { return } if ok := p.handleMessage(pCtx, pConn, msg); !ok { return } break } } } } func (p *sNode) messageReader( pCtx context.Context, pConn conn.IConn, readHeadCh chan<- struct{}, readFullCh chan<- layer1.IMessage, ) { for { select { case <-pCtx.Done(): return default: msg, err := pConn.ReadMessage(pCtx, readHeadCh) if err != nil { readFullCh <- nil return } readFullCh <- msg } } } // Processes the message for correctness and redirects it to the handler function. // Returns true if the message was successfully redirected to the handler function // > or if the message already existed in the hash value store. func (p *sNode) handleMessage(pCtx context.Context, pConn conn.IConn, pMsg layer1.IMessage) bool { hash := encoding.HexEncode(pMsg.GetHash()) if !p.fCacheSetter.Set(hash, []byte{}) { return true // hash of message already in queue } err := p.fHandlerF(pCtx, p, pConn, pMsg) return err == nil // function error = protocol error } // Checks the current number of connections with the limit. func (p *sNode) hasMaxConnSize() bool { p.fMutex.RLock() defer p.fMutex.RUnlock() maxConns := p.fSettings.GetMaxConnects() return uint64(len(p.fConnections)) >= maxConns } // Saves the connection to the map. func (p *sNode) getConnection(pAddress string) (conn.IConn, bool) { p.fMutex.RLock() defer p.fMutex.RUnlock() conn, ok := p.fConnections[pAddress] return conn, ok } // Saves the connection to the map. func (p *sNode) setConnection(pAddress string, pConn conn.IConn) { p.fMutex.Lock() defer p.fMutex.Unlock() p.fConnections[pAddress] = pConn } // Sets the listener. func (p *sNode) setListener(pListener net.Listener) { p.fMutex.Lock() defer p.fMutex.Unlock() p.fListener = pListener } // Gets the listener. func (p *sNode) getListener() net.Listener { p.fMutex.RLock() defer p.fMutex.RUnlock() return p.fListener }