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 }