You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2022/07/14 16:10:39 UTC

[plc4x] branch develop updated: feat(plc4go/plc4xpcapanalyzer): analyzer should now be able to handle segmented messages (for c-bus)

This is an automated email from the ASF dual-hosted git repository.

sruehl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git


The following commit(s) were added to refs/heads/develop by this push:
     new 929558b9c feat(plc4go/plc4xpcapanalyzer): analyzer should now be able to handle segmented messages (for c-bus)
929558b9c is described below

commit 929558b9c84f68dec444a9cd89c7c5b5d9d8e706
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Thu Jul 14 18:10:32 2022 +0200

    feat(plc4go/plc4xpcapanalyzer): analyzer should now be able to handle segmented messages (for c-bus)
    
    + Improvements on extractor
---
 .../internal/analyzer/analyzer.go                  | 14 +++-
 .../internal/cbusanalyzer/analyzer.go              | 90 ++++++++++++++++++++--
 .../plc4xpcapanalyzer/internal/common/common.go    |  6 ++
 .../internal/extractor/extractor.go                | 30 +++++---
 4 files changed, 119 insertions(+), 21 deletions(-)

diff --git a/plc4go/tools/plc4xpcapanalyzer/internal/analyzer/analyzer.go b/plc4go/tools/plc4xpcapanalyzer/internal/analyzer/analyzer.go
index 2d648e2c3..00d7863dc 100644
--- a/plc4go/tools/plc4xpcapanalyzer/internal/analyzer/analyzer.go
+++ b/plc4go/tools/plc4xpcapanalyzer/internal/analyzer/analyzer.go
@@ -54,6 +54,7 @@ func Analyze(pcapFile, protocolType string) {
 		serializePackage = bacnetanalyzer.SerializePackage
 	case "c-bus":
 		analyzer := cbusanalyzer.Analyzer{Client: net.ParseIP(config.AnalyzeConfigInstance.Client)}
+		analyzer.Init()
 		packageParse = analyzer.PackageParse
 		serializePackage = analyzer.SerializePackage
 		prettyPrint = analyzer.PrettyPrint
@@ -112,9 +113,16 @@ func Analyze(pcapFile, protocolType string) {
 		}
 		payload := applicationLayer.Payload()
 		if parsed, err := packageParse(packetInformation, payload); err != nil {
-			parseFails++
-			// TODO: write report to xml or something
-			log.Warn().Stringer("packetInformation", packetInformation).Err(err).Msgf("No.[%d] Error parsing package", realPacketNumber)
+			switch err {
+			case common.ErrUnterminatedPackage:
+				log.Info().Stringer("packetInformation", packetInformation).Msgf("No.[%d] is unterminated", realPacketNumber)
+			case common.ErrEmptyPackage:
+				log.Info().Stringer("packetInformation", packetInformation).Msgf("No.[%d] is empty", realPacketNumber)
+			default:
+				parseFails++
+				// TODO: write report to xml or something
+				log.Error().Stringer("packetInformation", packetInformation).Err(err).Msgf("No.[%d] Error parsing package", realPacketNumber)
+			}
 			continue
 		} else {
 			log.Info().Stringer("packetInformation", packetInformation).Msgf("No.[%d] Parsed", realPacketNumber)
diff --git a/plc4go/tools/plc4xpcapanalyzer/internal/cbusanalyzer/analyzer.go b/plc4go/tools/plc4xpcapanalyzer/internal/cbusanalyzer/analyzer.go
index 2af94956a..f47adf517 100644
--- a/plc4go/tools/plc4xpcapanalyzer/internal/cbusanalyzer/analyzer.go
+++ b/plc4go/tools/plc4xpcapanalyzer/internal/cbusanalyzer/analyzer.go
@@ -31,23 +31,83 @@ import (
 )
 
 type Analyzer struct {
-	Client         net.IP
-	requestContext model.RequestContext
-	cBusOptions    model.CBusOptions
-	initialized    bool
+	Client                 net.IP
+	requestContext         model.RequestContext
+	cBusOptions            model.CBusOptions
+	initialized            bool
+	currentInboundPayloads map[string][]byte
+}
+
+func (a *Analyzer) Init() {
+	if a.initialized {
+		return
+	}
+	a.requestContext = model.NewRequestContext(false, false, false)
+	a.cBusOptions = model.NewCBusOptions(config.CBusConfigInstance.Connect, config.CBusConfigInstance.Smart, config.CBusConfigInstance.Idmon, config.CBusConfigInstance.Exstat, config.CBusConfigInstance.Monitor, config.CBusConfigInstance.Monall, config.CBusConfigInstance.Pun, config.CBusConfigInstance.Pcn, config.CBusConfigInstance.Srchk)
+	a.currentInboundPayloads = make(map[string][]byte)
+	a.initialized = true
 }
 
 func (a *Analyzer) PackageParse(packetInformation common.PacketInformation, payload []byte) (interface{}, error) {
 	if !a.initialized {
 		log.Warn().Msg("Not initialized... doing that now")
-		a.requestContext = model.NewRequestContext(false, false, false)
-		a.cBusOptions = model.NewCBusOptions(config.CBusConfigInstance.Connect, config.CBusConfigInstance.Smart, config.CBusConfigInstance.Idmon, config.CBusConfigInstance.Exstat, config.CBusConfigInstance.Monitor, config.CBusConfigInstance.Monall, config.CBusConfigInstance.Pun, config.CBusConfigInstance.Pcn, config.CBusConfigInstance.Srchk)
-		a.initialized = true
+		a.Init()
 	}
 	log.Debug().Msgf("Parsing %s with requestContext\n%v\nBusOptions\n%s", packetInformation, a.requestContext, a.cBusOptions)
 	isResponse := packetInformation.DstIp.Equal(a.Client)
 	log.Debug().Stringer("packetInformation", packetInformation).Msgf("isResponse: %t", isResponse)
-	parse, err := model.CBusMessageParse(utils.NewReadBufferByteBased(payload), isResponse, a.requestContext, a.cBusOptions, uint16(len(payload)))
+	payload = filterXOnXOff(payload)
+	if len(payload) == 0 {
+		return nil, common.ErrEmptyPackage
+	}
+	// Check if we have a termination in the middle
+	currentPayload := a.currentInboundPayloads[packetInformation.SrcIp.String()]
+	currentPayload = append(currentPayload, payload...)
+	shouldClearInboundPayload := true
+	// Check if we have a merged message
+	for i, b := range currentPayload {
+		if i == 0 {
+			// TODO: we ignore the first byte as this is typical for reset etc... so maybe this is good or bad we will see
+			continue
+		}
+		switch b {
+		case 0x0D:
+			if i+1 < len(currentPayload) && currentPayload[i+1] == 0x0A {
+				// If we know the next is a newline we jump to that index...
+				i++
+			}
+			// ... other than that the logic is the same
+			fallthrough
+		case 0x0A:
+			// We have a merged message if we are not at the end
+			if i < len(currentPayload)-1 {
+				log.Warn().Stringer("packetInformation", packetInformation).Msgf("we have a split at index %d", i)
+				// In this case we need to put the tail into our "buffer"
+				a.currentInboundPayloads[packetInformation.SrcIp.String()] = currentPayload[i+1:]
+				// and use the beginning as current payload
+				currentPayload = currentPayload[:i+1]
+				shouldClearInboundPayload = false
+			}
+		}
+	}
+	a.currentInboundPayloads[packetInformation.SrcIp.String()] = currentPayload
+	if lastElement := currentPayload[len(currentPayload)-1]; (!isResponse /*a request must end with cr*/ && lastElement != 0x0D /*cr*/) || (isResponse /*a response must end with lf*/ && lastElement != 0x0A /*lf*/) {
+		return nil, common.ErrUnterminatedPackage
+	} else {
+		log.Debug().Msgf("Last element %x", lastElement)
+		if shouldClearInboundPayload {
+			if currentSavedPayload := a.currentInboundPayloads[packetInformation.SrcIp.String()]; currentSavedPayload != nil {
+				// We remove our current payload from the beginning of the cache
+				for i, b := range currentPayload {
+					if currentSavedPayload[i] != b {
+						panic("programming error... at this point they should start with the identical bytes")
+					}
+				}
+			}
+			a.currentInboundPayloads[packetInformation.SrcIp.String()] = nil
+		}
+	}
+	parse, err := model.CBusMessageParse(utils.NewReadBufferByteBased(currentPayload), isResponse, a.requestContext, a.cBusOptions, uint16(len(currentPayload)))
 	if err != nil {
 		return nil, errors.Wrap(err, "Error parsing CBusCommand")
 	}
@@ -109,6 +169,20 @@ func (a *Analyzer) PackageParse(packetInformation common.PacketInformation, payl
 	return parse, nil
 }
 
+func filterXOnXOff(payload []byte) []byte {
+	n := 0
+	for _, b := range payload {
+		switch b {
+		case 0x11: // Filter XON
+		case 0x13: // Filter XOFF
+		default:
+			payload[n] = b
+			n++
+		}
+	}
+	return payload[:n]
+}
+
 func (a *Analyzer) SerializePackage(message interface{}) ([]byte, error) {
 	if message, ok := message.(model.CBusMessage); !ok {
 		log.Fatal().Msgf("Unsupported type %T supplied", message)
diff --git a/plc4go/tools/plc4xpcapanalyzer/internal/common/common.go b/plc4go/tools/plc4xpcapanalyzer/internal/common/common.go
index 941365c4b..d95ac04e1 100644
--- a/plc4go/tools/plc4xpcapanalyzer/internal/common/common.go
+++ b/plc4go/tools/plc4xpcapanalyzer/internal/common/common.go
@@ -21,6 +21,7 @@ package common
 
 import (
 	"fmt"
+	"github.com/pkg/errors"
 	"net"
 	"time"
 )
@@ -36,3 +37,8 @@ type PacketInformation struct {
 func (p PacketInformation) String() string {
 	return fmt.Sprintf("%s (SrcIp:%v, DstIp:%v)", p.Description, p.SrcIp, p.DstIp)
 }
+
+// ErrUnterminatedPackage is used when a transmission is incomplete (usually when package is split)
+var ErrUnterminatedPackage = errors.New("ErrUnterminatedPackage")
+
+var ErrEmptyPackage = errors.New("ErrEmptyPackage")
diff --git a/plc4go/tools/plc4xpcapanalyzer/internal/extractor/extractor.go b/plc4go/tools/plc4xpcapanalyzer/internal/extractor/extractor.go
index 058cff647..4495ea7cf 100644
--- a/plc4go/tools/plc4xpcapanalyzer/internal/extractor/extractor.go
+++ b/plc4go/tools/plc4xpcapanalyzer/internal/extractor/extractor.go
@@ -56,22 +56,30 @@ func Extract(pcapFile, protocolType string) {
 		clientRequestWriter := color.New(color.FgGreen)
 		clientRequestIndicatorWriter := color.New(color.FgHiGreen)
 		printPayload = func(packetInformation common.PacketInformation, payload []byte) {
+			payloadString := ""
 			suffix := ""
-			if properTerminated := payload[len(payload)-1] == 0x0D || payload[len(payload)-1] == 0x0A; properTerminated {
-				suffix = "\n"
+			extraInformation := ""
+			if config.ExtractConfigInstance.Verbosity > 2 {
+				extraInformation = fmt.Sprintf("(No.[%d])", packetInformation.PacketNumber)
+			}
+			if len(payload) > 0 {
+				if properTerminated := payload[len(payload)-1] == 0x0D || payload[len(payload)-1] == 0x0A; properTerminated {
+					suffix = "\n"
+				}
+				quotedPayload := fmt.Sprintf("%+q", payload)
+				unquotedPayload := quotedPayload[1 : len(quotedPayload)-1]
+				payloadString = unquotedPayload
 			}
-			quotedPayload := fmt.Sprintf("%+q", payload)
-			unquotedPayload := quotedPayload[1 : len(quotedPayload)-1]
 			if isResponse := packetInformation.DstIp.Equal(clientIp); isResponse {
 				if config.ExtractConfigInstance.ShowDirectionalIndicators {
-					_, _ = serverResponseIndicatorWriter.Fprint(stderr, "(<--pci)")
+					_, _ = serverResponseIndicatorWriter.Fprintf(stderr, "%s(<--pci)", extraInformation)
 				}
-				_, _ = serverResponseWriter.Fprintf(stdout, "%s%s", unquotedPayload, suffix)
+				_, _ = serverResponseWriter.Fprintf(stdout, "%s%s", payloadString, suffix)
 			} else {
 				if config.ExtractConfigInstance.ShowDirectionalIndicators {
-					_, _ = clientRequestIndicatorWriter.Fprint(stderr, "(-->pci)")
+					_, _ = clientRequestIndicatorWriter.Fprintf(stderr, "%s(-->pci)", extraInformation)
 				}
-				_, _ = clientRequestWriter.Fprintf(stdout, "%s%s", unquotedPayload, suffix)
+				_, _ = clientRequestWriter.Fprintf(stdout, "%s%s", payloadString, suffix)
 			}
 		}
 	}
@@ -122,12 +130,14 @@ func Extract(pcapFile, protocolType string) {
 			packetInformation.DstIp = networkLayer.DstIP
 		}
 
+		var payload []byte
 		applicationLayer := packet.ApplicationLayer()
 		if applicationLayer == nil {
 			log.Info().Stringer("packetInformation", packetInformation).Msgf("No.[%d] No application layer", realPacketNumber)
-			continue
+		} else {
+			payload = applicationLayer.Payload()
 		}
-		payload := applicationLayer.Payload()
+
 		log.Debug().Msgf("Got payload %x", payload)
 		if config.ExtractConfigInstance.Verbosity > 1 {
 			printPayload(packetInformation, payload)