github.com/blueinnovationsgroup/can-go@v0.0.0-20230518195432-d0567cda0028/pkg/socketcan/emulator.go (about)

     1  package socketcan
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"net"
     8  	"os"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/blueinnovationsgroup/can-go"
    14  	"golang.org/x/sync/errgroup"
    15  )
    16  
    17  type emulatorCfg struct {
    18  	address string
    19  	logger  *log.Logger
    20  }
    21  
    22  func defaultCfg() emulatorCfg {
    23  	stdLogger := log.New(os.Stderr, "emulator: ", log.Lshortfile|log.Ltime)
    24  	return emulatorCfg{
    25  		address: "239.64.142.206:0",
    26  		logger:  stdLogger,
    27  	}
    28  }
    29  
    30  // EmulatorOption represents a way to configure an Emulator prior to creating it.
    31  type EmulatorOption func(*emulatorCfg)
    32  
    33  // WithMulticastAddress sets the address for the multicast group that the Emulator should listen on.
    34  // A multicast address starts with 239.x.x.x, and using an address that does not conform to this
    35  // will lead to undefined behavior.
    36  func WithMulticastAddress(address string) EmulatorOption {
    37  	return func(cfg *emulatorCfg) {
    38  		cfg.address = address
    39  	}
    40  }
    41  
    42  // WithLogger makes the Emulator print out status messages with the provided logger.
    43  func WithLogger(l *log.Logger) EmulatorOption {
    44  	return func(cfg *emulatorCfg) {
    45  		cfg.logger = l
    46  	}
    47  }
    48  
    49  // NoLogger disables logging in the Emulator.
    50  func NoLogger(cfg *emulatorCfg) {
    51  	cfg.logger = log.New(&writeSink{}, "", log.LstdFlags)
    52  }
    53  
    54  // writeSink is an io.Writer which does not write to anything.
    55  //
    56  // Can be thought of as a /dev/null for writers.
    57  type writeSink struct{}
    58  
    59  // Write returns without actually writing to anything.
    60  func (w *writeSink) Write(buf []byte) (int, error) {
    61  	return len(buf), nil
    62  }
    63  
    64  // Emulator emulates a CAN bus.
    65  //
    66  // Emulator emulates a CAN bus by using UDP multicast. The emulator itself
    67  // does not own the multicast group but rather establishes a common
    68  // address/port pair for the CAN bus to be emulated on.
    69  // Emulator exposes a thread-safe API to callees and may therefore be
    70  // shared among different goroutines.
    71  type Emulator struct {
    72  	transceiver   *udpTxRx
    73  	logger        *log.Logger
    74  	reqSenderChan chan chan int
    75  	closeChan     chan struct{}
    76  	sync.Mutex
    77  	g  *errgroup.Group
    78  	rg errgroup.Group
    79  }
    80  
    81  // NewEmulator creates an Emulator to emulate a CAN bus.
    82  //
    83  // If no error is returned it is safe to `socketcan.Dial` the address
    84  // of the Emulator. The emulator will default to using multicast group `239.64.142.206`
    85  // with a random port that's decided when calling Emulator
    86  //
    87  // N.B. It is not possible to simply use `net.Dial` as for UDP multicast both
    88  // a transmitting connection and a writing connection. This is handled
    89  // by `socketcan.Dial` under the hood.
    90  func NewEmulator(options ...EmulatorOption) (*Emulator, error) {
    91  	cfg := defaultCfg()
    92  	for _, update := range options {
    93  		update(&cfg)
    94  	}
    95  	c, err := udpTransceiver("udp", cfg.address)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	return &Emulator{
   100  		transceiver:   c,
   101  		logger:        cfg.logger,
   102  		closeChan:     make(chan struct{}),
   103  		reqSenderChan: make(chan chan int),
   104  	}, nil
   105  }
   106  
   107  // Run an Emulator.
   108  //
   109  // This starts the listener and waits until the context is canceled
   110  // before tidying up.
   111  func (e *Emulator) Run(ctx context.Context) error {
   112  	e.Lock()
   113  	e.g, ctx = errgroup.WithContext(ctx)
   114  	ctxDone := ctx.Done()
   115  
   116  	// Listen for incoming frames.
   117  	// Keep track of unique senders, and notify newSenderChan.
   118  	newSenderChan := make(chan struct{})
   119  	e.g.Go(func() error {
   120  		e.logger.Printf("waiting for SocketCAN connection requests on udp://%s\n", e.Addr().String())
   121  		registeredSenders := make(map[string]bool)
   122  		for {
   123  			buffer := make([]byte, 8096)
   124  			_, _, src, err := e.transceiver.rx.ReadFrom(buffer)
   125  			if err != nil {
   126  				if isClosedError(err) {
   127  					return nil
   128  				}
   129  				return fmt.Errorf("read from udp: %w", err)
   130  			}
   131  			if !registeredSenders[src.String()] {
   132  				e.logger.Printf("received first frame from %s", src.String())
   133  				registeredSenders[src.String()] = true
   134  				select {
   135  				case <-ctxDone:
   136  					return nil
   137  				case newSenderChan <- struct{}{}:
   138  				}
   139  			}
   140  		}
   141  	})
   142  
   143  	// Close multicast listener when ctx is canceled
   144  	e.g.Go(func() error {
   145  		<-ctxDone
   146  		e.logger.Println("closing SocketCAN listener...")
   147  		e.Lock()
   148  		defer e.Unlock()
   149  		return e.transceiver.Close()
   150  	})
   151  
   152  	// Stop all started receivers when ctx is canceled
   153  	e.g.Go(func() error {
   154  		<-ctxDone
   155  		e.logger.Println("stopping receivers...")
   156  		close(e.closeChan)
   157  		e.Lock()
   158  		defer e.Unlock()
   159  		return e.rg.Wait()
   160  	})
   161  
   162  	// Keep track of the number of unique senders of the received frames, when the number of senders
   163  	// are requested on the reqSenderChan, send them on the provided channel.
   164  	e.g.Go(func() error {
   165  		nSenders := 0
   166  		for {
   167  			select {
   168  			case <-ctxDone:
   169  				return nil
   170  			case <-newSenderChan:
   171  				nSenders++
   172  			case req := <-e.reqSenderChan:
   173  				req <- nSenders
   174  			}
   175  		}
   176  	})
   177  	e.Unlock()
   178  	e.logger.Println("started emulator, waiting for cancel signal")
   179  	return e.g.Wait()
   180  }
   181  
   182  // Addr returns the address of the Emulator's multicast group.
   183  func (e *Emulator) Addr() net.Addr {
   184  	return e.transceiver.RemoteAddr()
   185  }
   186  
   187  // Receiver returns a Receiver connected to the Emulator.
   188  //
   189  // The emulator owns the underlying network connection an
   190  // will close it when the emulator is closed.
   191  func (e *Emulator) Receiver() (*Receiver, error) {
   192  	conn, err := udpTransceiver(e.Addr().Network(), e.Addr().String())
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  	e.Lock()
   197  	e.rg.Go(func() error {
   198  		<-e.closeChan
   199  		return conn.Close()
   200  	})
   201  	e.Unlock()
   202  	return NewReceiver(conn), nil
   203  }
   204  
   205  // TransmitFrame sends a CAN frame to the Emulator's multicast group.
   206  func (e *Emulator) TransmitFrame(ctx context.Context, f can.Frame) error {
   207  	conn, err := udpTransceiver(e.Addr().Network(), e.Addr().String())
   208  	if err != nil {
   209  		return fmt.Errorf("transmit CAN frame: %w", err)
   210  	}
   211  	errChan := make(chan error)
   212  	go func() {
   213  		if err := NewTransmitter(conn).TransmitFrame(ctx, f); err != nil {
   214  			errChan <- fmt.Errorf("transmit CAN frame: %w", err)
   215  		}
   216  		close(errChan)
   217  	}()
   218  	select {
   219  	case <-ctx.Done():
   220  		_ = conn.Close()
   221  		return ctx.Err()
   222  	case err := <-errChan:
   223  		_ = conn.Close()
   224  		if err != nil {
   225  			return fmt.Errorf("emulator: %w", err)
   226  		}
   227  		return nil
   228  	}
   229  }
   230  
   231  // TransmitMessage sends a CAN message to every emulator connection.
   232  func (e *Emulator) TransmitMessage(ctx context.Context, m can.Message) error {
   233  	f, err := m.MarshalFrame()
   234  	if err != nil {
   235  		return fmt.Errorf("transmit CAN message: %w", err)
   236  	}
   237  	if err := e.TransmitFrame(ctx, f); err != nil {
   238  		return fmt.Errorf("transmit CAN message: %w", err)
   239  	}
   240  	return nil
   241  }
   242  
   243  // WaitForSenders waits until either, n unique senders have been sending messages to the
   244  // multicast group, or the timeout is reached.
   245  func (e *Emulator) WaitForSenders(n int, timeout time.Duration) error {
   246  	reqChan := make(chan int)
   247  	timeoutChannel := time.After(timeout)
   248  	for {
   249  		select {
   250  		case <-timeoutChannel:
   251  			return fmt.Errorf("emulator timeout waiting for senders")
   252  		case e.reqSenderChan <- reqChan:
   253  			conns := <-reqChan
   254  			if conns < n {
   255  				// We don't want to keep the emulator
   256  				// busy with our requests all the time.
   257  				time.Sleep(time.Millisecond)
   258  				continue
   259  			}
   260  			return nil
   261  		}
   262  	}
   263  }
   264  
   265  func isClosedError(e error) bool {
   266  	if e == nil {
   267  		return false
   268  	}
   269  	return strings.Contains(e.Error(), "closed")
   270  }