Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pkg/socketcan/dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ const udp = "udp"

type DialOption func(*dialOpts)

// IDFilter matches a frame when `frame.ID & filter.Mask == filter.ID & filter.Mask`
type IDFilter struct {
ID uint32
Mask uint32
// Set flag to filter out matching frames rather than include them
Exclude bool
}

// Dial connects to the address on the named net.
//
// Linux only: If net is "can" it creates a SocketCAN connection to the device
Expand Down
25 changes: 25 additions & 0 deletions pkg/socketcan/dialraw_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

type dialOpts struct {
errorFrameMask *int
rawFilters []unix.CanFilter
}

func dialRaw(device string, opt ...DialOption) (conn net.Conn, err error) {
Expand All @@ -37,6 +38,11 @@ func dialRaw(device string, opt ...DialOption) (conn net.Conn, err error) {
return nil, fmt.Errorf("set error filter: %w", err)
}
}
if len(opts.rawFilters) != 0 {
if err = unix.SetsockoptCanRawFilter(fd, unix.SOL_CAN_RAW, unix.CAN_RAW_FILTER, opts.rawFilters); err != nil {
return nil, fmt.Errorf("set raw filter: %w", err)
}
}
// put fd in non-blocking mode so the created file will be registered by the runtime poller (Go >= 1.12)
if err := unix.SetNonblock(fd, true); err != nil {
return nil, fmt.Errorf("set nonblock: %w", err)
Expand All @@ -55,3 +61,22 @@ func WithReceiveErrorFrames() DialOption {
o.errorFrameMask = &canErrMask
}
}

// WithFilterReceivedFramesByID returns a DialOption which filters the
// received CAN messages to include only frames with ID that match an
// ID/Mask in one of the given IDFilters (or do not match, in the case
// that the Exclude flag is set).
func WithFilterReceivedFramesByID(filters []IDFilter) DialOption {
return func(o *dialOpts) {
for _, filter := range filters {
id := filter.ID
if filter.Exclude {
id |= unix.CAN_INV_FILTER
}
o.rawFilters = append(o.rawFilters, unix.CanFilter{
Id: id,
Mask: filter.Mask,
})
}
}
}
114 changes: 114 additions & 0 deletions pkg/socketcan/dialraw_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,120 @@ func TestConn_ReadErrorFrame(t *testing.T) {
assert.Equal(t, errorFrame.ErrorClass, ErrorClassBusError)
}

func TestConn_IDFilterInclude(t *testing.T) {
requireVCAN0(t)
// filter to receive only frames with this CAN ID
id := uint32(32)
filters := []IDFilter{
{ID: id, Mask: unix.CAN_SFF_MASK},
}
// given a reader and writer
reader, err := Dial("can", "vcan0", WithFilterReceivedFramesByID(filters))
assert.NilError(t, err)
writer, err := Dial("can", "vcan0")
assert.NilError(t, err)
// when the reader reads
var g errgroup.Group
var hasReadFrame bool
var readFrame can.Frame
g.Go(func() error {
rec := NewReceiver(reader)
if !rec.Receive() {
return fmt.Errorf("receive")
}
readFrame = rec.Frame()
hasReadFrame = true
return reader.Close()
})
// and the writer writes a frame
writeFrame := can.Frame{ID: id}
tr := NewTransmitter(writer)
ctx, done := context.WithTimeout(context.Background(), time.Second)
defer done()
assert.NilError(t, tr.TransmitFrame(ctx, writeFrame))
assert.NilError(t, writer.Close())
// expecting to receive the frame with id in the filter list
assert.NilError(t, g.Wait())
assert.Assert(t, hasReadFrame)
assert.Equal(t, readFrame.ID, id)
}

func TestConn_IDFilterIgnore(t *testing.T) {
requireVCAN0(t)
// filter to receive only frames with this CAN ID
includeId := uint32(32)
filters := []IDFilter{
{ID: includeId, Mask: unix.CAN_SFF_MASK},
}
// send frame with this CAN ID
excludeId := uint32(64)
// given a reader and writer
reader, err := Dial("can", "vcan0", WithFilterReceivedFramesByID(filters))
assert.NilError(t, err)
writer, err := Dial("can", "vcan0")
assert.NilError(t, err)
assert.NilError(t, reader.SetDeadline(time.Now().Add(time.Second)))
// when the reader reads
var g errgroup.Group
var hasReadFrame bool
g.Go(func() error {
rec := NewReceiver(reader)
if !rec.Receive() {
return fmt.Errorf("receive")
}
_ = rec.Frame()
hasReadFrame = true
return reader.Close()
})
// and the writer writes a frame
writeFrame := can.Frame{ID: excludeId}
tr := NewTransmitter(writer)
ctx, done := context.WithTimeout(context.Background(), time.Second)
defer done()
assert.NilError(t, tr.TransmitFrame(ctx, writeFrame))
assert.NilError(t, writer.Close())
// expecting not to receive the frame with id not in the filter list
assert.Error(t, g.Wait(), "receive")
assert.Assert(t, !hasReadFrame)
}

func TestConn_IDFilterExclude(t *testing.T) {
requireVCAN0(t)
// filter to receive only frames WITHOUT this CAN ID
id := uint32(32)
filters := []IDFilter{
{ID: id, Mask: unix.CAN_SFF_MASK, Exclude: true},
}
// given a reader and writer
reader, err := Dial("can", "vcan0", WithFilterReceivedFramesByID(filters))
assert.NilError(t, err)
writer, err := Dial("can", "vcan0")
assert.NilError(t, err)
assert.NilError(t, reader.SetDeadline(time.Now().Add(time.Second)))
// when the reader reads
var g errgroup.Group
var hasReadFrame bool
g.Go(func() error {
rec := NewReceiver(reader)
if !rec.Receive() {
return fmt.Errorf("receive")
}
_ = rec.Frame()
hasReadFrame = true
return reader.Close()
})
// and the writer writes a frame
writeFrame := can.Frame{ID: id}
tr := NewTransmitter(writer)
ctx, done := context.WithTimeout(context.Background(), time.Second)
defer done()
assert.NilError(t, tr.TransmitFrame(ctx, writeFrame))
assert.NilError(t, writer.Close())
// expecting not to receive the frame with id excluded by the filter list
assert.Error(t, g.Wait(), "receive")
assert.Assert(t, !hasReadFrame)
}

func requireVCAN0(t *testing.T) {
t.Helper()
if _, err := net.InterfaceByName("vcan0"); err != nil {
Expand Down
5 changes: 5 additions & 0 deletions pkg/socketcan/dialraw_others.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ func WithReceiveErrorFrames() DialOption {
return func(o *dialOpts) {
}
}

func WithFilterReceivedFramesByID(filters []IDFilter) DialOption {
return func(o *dialOpts) {
}
}