From 840f8194843873ac642d5c170ff636f3ca9b0987 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Fri, 23 Aug 2024 16:03:35 +0200 Subject: [PATCH] refact: refactored can dbc logic --- modules/can/can.go | 30 ++-- modules/can/can_dbc.go | 169 +++++++++++++++++++++ modules/can/{dbc.go => can_dbc_compile.go} | 0 modules/can/can_dbc_load.go | 6 + modules/can/can_fuzz.go | 19 +-- modules/can/can_recon.go | 98 ++---------- 6 files changed, 214 insertions(+), 108 deletions(-) create mode 100644 modules/can/can_dbc.go rename modules/can/{dbc.go => can_dbc_compile.go} (100%) create mode 100644 modules/can/can_dbc_load.go diff --git a/modules/can/can.go b/modules/can/can.go index a57e4a24..185e3ef3 100644 --- a/modules/can/can.go +++ b/modules/can/can.go @@ -6,7 +6,6 @@ import ( "github.com/bettercap/bettercap/v2/session" "github.com/hashicorp/go-bexpr" - "go.einride.tech/can/pkg/descriptor" "go.einride.tech/can/pkg/socketcan" ) @@ -17,19 +16,17 @@ type CANModule struct { transport string filter string filterExpr *bexpr.Evaluator - dbcPath string - dbc *descriptor.Database - - conn net.Conn - recv *socketcan.Receiver - send *socketcan.Transmitter + dbc *DBC + conn net.Conn + recv *socketcan.Receiver + send *socketcan.Transmitter } func NewCanModule(s *session.Session) *CANModule { mod := &CANModule{ SessionModule: session.NewSessionModule("can", s), - dbcPath: "", filter: "", + dbc: &DBC{}, filterExpr: nil, transport: "can", deviceName: "can0", @@ -45,11 +42,6 @@ func NewCanModule(s *session.Session) *CANModule { "", "Network type, can be 'can' for SocketCAN or 'udp'.")) - mod.AddParam(session.NewStringParameter("can.dbc_path", - mod.dbcPath, - "", - "Optional path to DBC file for decoding.")) - mod.AddParam(session.NewStringParameter("can.filter", "", "", @@ -61,6 +53,18 @@ func NewCanModule(s *session.Session) *CANModule { return mod.Start() })) + mod.AddHandler(session.NewModuleHandler("can.load_dbc PATH", "", + "Start CAN-bus discovery.", + func(args []string) error { + return mod.Start() + })) + + mod.AddHandler(session.NewModuleHandler("can.dbc.load NAME", "can.dbc.load (.+)", + "Load a DBC file from the list of available ones or from disk.", + func(args []string) error { + return mod.dbcLoad(args[0]) + })) + mod.AddHandler(session.NewModuleHandler("can.recon off", "", "Stop CAN-bus discovery.", func(args []string) error { diff --git a/modules/can/can_dbc.go b/modules/can/can_dbc.go new file mode 100644 index 00000000..62cda4e7 --- /dev/null +++ b/modules/can/can_dbc.go @@ -0,0 +1,169 @@ +package can + +import ( + "fmt" + "os" + "sync" + + "github.com/evilsocket/islazy/str" + "go.einride.tech/can" + "go.einride.tech/can/pkg/descriptor" +) + +type DBC struct { + sync.RWMutex + + path string + db *descriptor.Database +} + +func (dbc *DBC) Loaded() bool { + dbc.RLock() + defer dbc.RUnlock() + + return dbc.db != nil +} + +func (dbc *DBC) LoadFile(mod *CANModule, path string) error { + input, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("can't read %s: %v", path, err) + } + return dbc.LoadData(mod, path, input) +} + +func (dbc *DBC) LoadData(mod *CANModule, name string, input []byte) error { + dbc.Lock() + defer dbc.Unlock() + + mod.Debug("compiling %s ...", name) + + result, err := dbcCompile(name, input) + if err != nil { + return fmt.Errorf("can't compile %s: %v", name, err) + } + + for _, warning := range result.Warnings { + mod.Warning("%v", warning) + } + + dbc.path = name + dbc.db = result.Database + + mod.Info("%s loaded", name) + return nil +} + +func (dbc *DBC) Parse(mod *CANModule, frame can.Frame, msg *Message) bool { + dbc.RLock() + defer dbc.RUnlock() + + // did we load any DBC database? + if dbc.db == nil { + return false + } + + // if the database contains this message id + if message, found := dbc.db.Message(frame.ID); found { + msg.Name = message.Name + + // find source full info in DBC nodes + sourceName := message.SenderNode + sourceDesc := "" + if sender, found := dbc.db.Node(message.SenderNode); found { + sourceName = sender.Name + sourceDesc = sender.Description + } + + // add CAN source if new + _, msg.Source = mod.Session.CAN.AddIfNew(sourceName, sourceDesc, frame.Data[:]) + + msg.Signals = make(map[string]string) + + // parse signals + for _, signal := range message.Signals { + var value string + + if signal.Length <= 32 && signal.IsFloat { + value = fmt.Sprintf("%f", signal.UnmarshalFloat(frame.Data)) + } else if signal.Length == 1 { + value = fmt.Sprintf("%v", signal.UnmarshalBool(frame.Data)) + } else if signal.IsSigned { + value = fmt.Sprintf("%d", signal.UnmarshalSigned(frame.Data)) + } else { + value = fmt.Sprintf("%d", signal.UnmarshalUnsigned(frame.Data)) + } + + msg.Signals[signal.Name] = str.Trim(fmt.Sprintf("%s %s", value, signal.Unit)) + } + + return true + } + + return false +} + +func (dbc *DBC) MessagesBySender(senderId string) []*descriptor.Message { + dbc.RLock() + defer dbc.RUnlock() + + fromSender := make([]*descriptor.Message, 0) + + if dbc.db == nil { + return fromSender + } + + for _, msg := range dbc.db.Messages { + if msg.SenderNode == senderId { + fromSender = append(fromSender, msg) + } + } + + return fromSender +} + +func (dbc *DBC) MessageById(frameID uint32) *descriptor.Message { + dbc.RLock() + defer dbc.RUnlock() + + if dbc.db == nil { + return nil + } + + if message, found := dbc.db.Message(frameID); found { + return message + } + return nil +} + +func (dbc *DBC) Messages() []*descriptor.Message { + dbc.RLock() + defer dbc.RUnlock() + + if dbc.db == nil { + return nil + } + + return dbc.db.Messages +} + +func (dbc *DBC) Senders() []string { + dbc.RLock() + defer dbc.RUnlock() + + senders := make([]string, 0) + if dbc.db == nil { + return senders + } + + uniq := make(map[string]bool) + for _, msg := range dbc.db.Messages { + uniq[msg.SenderNode] = true + } + + for sender, _ := range uniq { + senders = append(senders, sender) + } + + return senders +} diff --git a/modules/can/dbc.go b/modules/can/can_dbc_compile.go similarity index 100% rename from modules/can/dbc.go rename to modules/can/can_dbc_compile.go diff --git a/modules/can/can_dbc_load.go b/modules/can/can_dbc_load.go new file mode 100644 index 00000000..ce51b2b2 --- /dev/null +++ b/modules/can/can_dbc_load.go @@ -0,0 +1,6 @@ +package can + +func (mod *CANModule) dbcLoad(name string) error { + // load as file + return mod.dbc.LoadFile(mod, name) +} diff --git a/modules/can/can_fuzz.go b/modules/can/can_fuzz.go index 19888b8e..626c0cfd 100644 --- a/modules/can/can_fuzz.go +++ b/modules/can/can_fuzz.go @@ -10,7 +10,6 @@ import ( "github.com/dustin/go-humanize" "go.einride.tech/can" - "go.einride.tech/can/pkg/descriptor" ) func (mod *CANModule) Fuzz(id string) error { @@ -25,15 +24,9 @@ func (mod *CANModule) Fuzz(id string) error { if err != nil { if mod.dbc != nil { // not a number, use as node name - fromSender := make([]*descriptor.Message, 0) - for _, msg := range mod.dbc.Messages { - if msg.SenderNode == id { - fromSender = append(fromSender, msg) - } - } - + fromSender := mod.dbc.MessagesBySender(id) if len(fromSender) == 0 { - return fmt.Errorf("no messages defined in DBC file for node %s", id) + return fmt.Errorf("no messages defined in DBC file for node %s, available nodes: %s", id, mod.dbc.Senders()) } idx := rng.Intn(len(fromSender)) @@ -46,8 +39,8 @@ func (mod *CANModule) Fuzz(id string) error { } // if we have a DBC - if mod.dbc != nil { - if message, found := mod.dbc.Message(uint32(frameID)); found { + if mod.dbc.Loaded() { + if message := mod.dbc.MessageById(uint32(frameID)); message != nil { mod.Info("found as %s", message.Name) dataLen = int(message.Length) @@ -57,7 +50,7 @@ func (mod *CANModule) Fuzz(id string) error { } } else { avail := []string{} - for _, msg := range mod.dbc.Messages { + for _, msg := range mod.dbc.Messages() { avail = append(avail, fmt.Sprintf("%d (%s)", msg.ID, msg.Name)) } return fmt.Errorf("message with id %d not found in DBC file, available ids: %v", frameID, strings.Join(avail, ", ")) @@ -70,7 +63,7 @@ func (mod *CANModule) Fuzz(id string) error { return err } - mod.Warning("no can.dbc_path is set, creating frame with %d bytes of random data", dataLen) + mod.Warning("no dbc loaded, creating frame with %d bytes of random data", dataLen) } frame := can.Frame{ diff --git a/modules/can/can_recon.go b/modules/can/can_recon.go index c06a3d20..54db79c7 100644 --- a/modules/can/can_recon.go +++ b/modules/can/can_recon.go @@ -2,12 +2,9 @@ package can import ( "errors" - "fmt" - "os" "github.com/bettercap/bettercap/v2/network" "github.com/bettercap/bettercap/v2/session" - "github.com/evilsocket/islazy/str" "github.com/evilsocket/islazy/tui" "github.com/hashicorp/go-bexpr" "go.einride.tech/can" @@ -21,28 +18,6 @@ type Message struct { Signals map[string]string } -func (mod *CANModule) compileDBC() error { - input, err := os.ReadFile(mod.dbcPath) - if err != nil { - return fmt.Errorf("can't read %s: %v", mod.dbcPath, err) - } - - mod.Info("compiling %s ...", mod.dbcPath) - - result, err := dbcCompile(mod.dbcPath, input) - if err != nil { - return fmt.Errorf("can't compile %s: %v", mod.dbcPath, err) - } - - for _, warning := range result.Warnings { - mod.Warning("%v", warning) - } - - mod.dbc = result.Database - - return nil -} - func (mod *CANModule) Configure() error { var err error @@ -54,20 +29,10 @@ func (mod *CANModule) Configure() error { return err } else if mod.transport != "can" && mod.transport != "udp" { return errors.New("invalid transport") - } else if err, mod.dbcPath = mod.StringParam("can.dbc_path"); err != nil { - return err } else if err, mod.filter = mod.StringParam("can.filter"); err != nil { return err } - if mod.dbcPath != "" { - if err := mod.compileDBC(); err != nil { - return err - } - } else { - mod.Warning("no can.dbc_path specified, messages won't be parsed") - } - if mod.filter != "" { if mod.filterExpr, err = bexpr.CreateEvaluator(mod.filter); err != nil { return err @@ -85,49 +50,7 @@ func (mod *CANModule) Configure() error { return nil } -func (mod *CANModule) onFrame(frame can.Frame) { - msg := Message{ - Frame: frame, - } - - // if we have a DBC database - if mod.dbc != nil { - // if the database contains this message id - if message, found := mod.dbc.Message(frame.ID); found { - msg.Name = message.Name - - // find source full info in DBC nodes - sourceName := message.SenderNode - sourceDesc := "" - if sender, found := mod.dbc.Node(message.SenderNode); found { - sourceName = sender.Name - sourceDesc = sender.Description - } - - // add CAN source if new - _, msg.Source = mod.Session.CAN.AddIfNew(sourceName, sourceDesc, frame.Data[:]) - - msg.Signals = make(map[string]string) - - // parse signals - for _, signal := range message.Signals { - var value string - - if signal.Length <= 32 && signal.IsFloat { - value = fmt.Sprintf("%f", signal.UnmarshalFloat(frame.Data)) - } else if signal.Length == 1 { - value = fmt.Sprintf("%v", signal.UnmarshalBool(frame.Data)) - } else if signal.IsSigned { - value = fmt.Sprintf("%d", signal.UnmarshalSigned(frame.Data)) - } else { - value = fmt.Sprintf("%d", signal.UnmarshalUnsigned(frame.Data)) - } - - msg.Signals[signal.Name] = str.Trim(fmt.Sprintf("%s %s", value, signal.Unit)) - } - } - } - +func (mod *CANModule) isFilteredOut(frame can.Frame, msg Message) bool { // if we have an active filter if mod.filter != "" { if res, err := mod.filterExpr.Evaluate(map[string]interface{}{ @@ -137,11 +60,24 @@ func (mod *CANModule) onFrame(frame can.Frame) { mod.Error("error evaluating '%s': %v", mod.filter, err) } else if !res { mod.Debug("skipping can message %+v", msg) - return + return true } } - mod.Session.Events.Add("can.message", msg) + return false +} + +func (mod *CANModule) onFrame(frame can.Frame) { + msg := Message{ + Frame: frame, + } + + // try to parse with DBC if we have any + mod.dbc.Parse(mod, frame, &msg) + + if !mod.isFilteredOut(frame, msg) { + mod.Session.Events.Add("can.message", msg) + } } const canPrompt = "{br}{fw}{env.can.device} {fb}{reset} {bold}ยป {reset}" @@ -173,8 +109,6 @@ func (mod *CANModule) Stop() error { mod.conn = nil mod.recv = nil mod.send = nil - mod.dbc = nil - mod.dbcPath = "" mod.filter = "" } })