gotelem/socketcan/socketcan.go
saji 3c1a96c8e0
All checks were successful
Go / build (1.21) (push) Successful in 1m18s
Go / build (1.22) (push) Successful in 1m16s
format
2024-03-07 06:18:49 -06:00

220 lines
4.5 KiB
Go

//go:build linux
/*
Package socketcan provides a wrapper around the Linux socketCAN interface.
*/
package socketcan
import (
"encoding/binary"
"errors"
"fmt"
"net"
"github.com/kschamplin/gotelem/internal/can"
"golang.org/x/sys/unix"
)
// A CanSocket is a CAN device that uses the socketCAN linux drivers to write to real
// CAN hardware.
type CanSocket struct {
iface *net.Interface
addr *unix.SockaddrCAN
fd int
}
// CanFilter is a filter for an interface.
type CanFilter interface {
Inverted() bool
Mask() uint32
Id() uint32
}
// standardFrameSize is the full size in bytes of the default CAN frame.
const standardFrameSize = unix.CAN_MTU
// we use the base CAN_MTU since the FD MTU is not in sys/unix. but we know it's +64-8 bytes
const fdFrameSize = unix.CAN_MTU + 56
// NewCanSocket constructs a new CanSocket and binds it to the interface given by ifname
func NewCanSocket(ifname string) (*CanSocket, error) {
var sck CanSocket
fd, err := unix.Socket(unix.AF_CAN, unix.SOCK_RAW, unix.CAN_RAW)
if err != nil {
return nil, err
}
sck.fd = fd
iface, err := net.InterfaceByName(ifname)
if err != nil {
return nil, err
}
sck.iface = iface
sck.addr = &unix.SockaddrCAN{Ifindex: sck.iface.Index}
err = unix.Bind(sck.fd, sck.addr)
if err != nil {
return nil, err
}
return &sck, nil
}
// Close closes the socket.
func (sck *CanSocket) Close() error {
return unix.Close(sck.fd)
}
// Name returns the name of the socket.
func (sck *CanSocket) Name() string {
return sck.iface.Name
}
// SetErrFilter sets if error packets should be sent upstream
func (sck *CanSocket) SetErrFilter(shouldFilter bool) error {
var err error
var errmask = 0
if shouldFilter {
errmask = unix.CAN_ERR_MASK
}
err = unix.SetsockoptInt(sck.fd, unix.SOL_CAN_RAW, unix.CAN_RAW_ERR_FILTER, errmask)
return err
}
// SetFDMode enables or disables the transmission of CAN FD packets.
func (sck *CanSocket) SetFDMode(enable bool) error {
var val int
if enable {
val = 1
} else {
val = 0
}
err := unix.SetsockoptInt(sck.fd, unix.SOL_CAN_RAW, unix.CAN_RAW_FD_FRAMES, val)
return err
}
// SetFilters will set the socketCAN filters based on a standard CAN filter list.
func (sck *CanSocket) SetFilters(filters []CanFilter) error {
// helper function to make a filter.
// id and mask are straightforward, if inverted is true, the filter
// will reject anything that matches.
makeFilter := func(filter CanFilter) unix.CanFilter {
f := unix.CanFilter{Id: filter.Id(), Mask: filter.Mask()}
if filter.Inverted() {
f.Id = f.Id | unix.CAN_INV_FILTER
}
return f
}
convertedFilters := make([]unix.CanFilter, len(filters))
for i, filt := range filters {
convertedFilters[i] = makeFilter(filt)
}
return unix.SetsockoptCanRawFilter(sck.fd, unix.SOL_CAN_RAW, unix.CAN_RAW_FILTER, convertedFilters)
}
// Send sends a CAN frame
func (sck *CanSocket) Send(msg *can.Frame) error {
buf := make([]byte, fdFrameSize)
idToWrite := msg.Id.Id
if msg.Id.Extended {
idToWrite &= unix.CAN_EFF_MASK
idToWrite |= unix.CAN_EFF_FLAG
}
switch msg.Kind {
case can.CanRTRFrame:
idToWrite |= unix.CAN_RTR_FLAG
case can.CanErrFrame:
return errors.New("you can't send error frames")
default:
return errors.New("unknown frame type")
}
binary.LittleEndian.PutUint32(buf[:4], idToWrite)
// write the length, it's one byte, so do it directly.
payloadLength := len(msg.Data)
buf[4] = byte(payloadLength)
if payloadLength > 64 {
return fmt.Errorf("payload too large: %d", payloadLength)
}
// copy in the data now.
copy(buf[8:], msg.Data)
// send the buffer using unix syscalls!
var err error
if payloadLength > 8 {
err = unix.Send(sck.fd, buf, 0)
} else {
err = unix.Send(sck.fd, buf[:standardFrameSize], 0)
}
if err != nil {
return fmt.Errorf("error sending frame: %w", err)
}
return nil
}
func (sck *CanSocket) Recv() (*can.Frame, error) {
// todo: support extended frames.
buf := make([]byte, fdFrameSize)
_, err := unix.Read(sck.fd, buf)
if err != nil {
return nil, err
}
raw_id := binary.LittleEndian.Uint32(buf[0:4])
var id can.CanID
id.Id = raw_id
if raw_id&unix.CAN_EFF_FLAG != 0 {
// extended id frame
id.Extended = true
} else {
// it's a normal can frame
id.Extended = false
}
var k can.Kind = can.CanDataFrame
if raw_id&unix.CAN_ERR_FLAG != 0 {
// we got an error...
k = can.CanErrFrame
}
if raw_id&unix.CAN_RTR_FLAG != 0 {
k = can.CanRTRFrame
}
dataLength := uint8(buf[4])
result := &can.Frame{
Id: id,
Kind: k,
Data: buf[8 : dataLength+8],
}
return result, nil
}