blob: bafbd7436cdaeb1abae0d90c19e19ea0f1f830e3 [file] [log] [blame]
Holger Hildebrandtfa074992020-03-27 15:42:06 +00001// Copyright 2018, The GoPacket Authors, All rights reserved.
2//
3// Use of this source code is governed by a BSD-style license
4// that can be found in the LICENSE file in the root of the source
5// tree.
6//
7//******************************************************************************
8
9package layers
10
11import (
12 "encoding/binary"
13 "errors"
14 "github.com/google/gopacket"
15)
16
17//******************************************************************************
18//
19// ModbusTCP Decoding Layer
20// ------------------------------------------
21// This file provides a GoPacket decoding layer for ModbusTCP.
22//
23//******************************************************************************
24
25const mbapRecordSizeInBytes int = 7
26const modbusPDUMinimumRecordSizeInBytes int = 2
27const modbusPDUMaximumRecordSizeInBytes int = 253
28
29// ModbusProtocol type
30type ModbusProtocol uint16
31
32// ModbusProtocol known values.
33const (
34 ModbusProtocolModbus ModbusProtocol = 0
35)
36
37func (mp ModbusProtocol) String() string {
38 switch mp {
39 default:
40 return "Unknown"
41 case ModbusProtocolModbus:
42 return "Modbus"
43 }
44}
45
46//******************************************************************************
47
48// ModbusTCP Type
49// --------
50// Type ModbusTCP implements the DecodingLayer interface. Each ModbusTCP object
51// represents in a structured form the MODBUS Application Protocol header (MBAP) record present as the TCP
52// payload in an ModbusTCP TCP packet.
53//
54type ModbusTCP struct {
55 BaseLayer // Stores the packet bytes and payload (Modbus PDU) bytes .
56
57 TransactionIdentifier uint16 // Identification of a MODBUS Request/Response transaction
58 ProtocolIdentifier ModbusProtocol // It is used for intra-system multiplexing
59 Length uint16 // Number of following bytes (includes 1 byte for UnitIdentifier + Modbus data length
60 UnitIdentifier uint8 // Identification of a remote slave connected on a serial line or on other buses
61}
62
63//******************************************************************************
64
65// LayerType returns the layer type of the ModbusTCP object, which is LayerTypeModbusTCP.
66func (d *ModbusTCP) LayerType() gopacket.LayerType {
67 return LayerTypeModbusTCP
68}
69
70//******************************************************************************
71
72// decodeModbusTCP analyses a byte slice and attempts to decode it as an ModbusTCP
73// record of a TCP packet.
74//
75// If it succeeds, it loads p with information about the packet and returns nil.
76// If it fails, it returns an error (non nil).
77//
78// This function is employed in layertypes.go to register the ModbusTCP layer.
79func decodeModbusTCP(data []byte, p gopacket.PacketBuilder) error {
80
81 // Attempt to decode the byte slice.
82 d := &ModbusTCP{}
83 err := d.DecodeFromBytes(data, p)
84 if err != nil {
85 return err
86 }
87 // If the decoding worked, add the layer to the packet and set it
88 // as the application layer too, if there isn't already one.
89 p.AddLayer(d)
90 p.SetApplicationLayer(d)
91
92 return p.NextDecoder(d.NextLayerType())
93
94}
95
96//******************************************************************************
97
98// DecodeFromBytes analyses a byte slice and attempts to decode it as an ModbusTCP
99// record of a TCP packet.
100//
101// Upon succeeds, it loads the ModbusTCP object with information about the packet
102// and returns nil.
103// Upon failure, it returns an error (non nil).
104func (d *ModbusTCP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
105
106 // If the data block is too short to be a MBAP record, then return an error.
107 if len(data) < mbapRecordSizeInBytes+modbusPDUMinimumRecordSizeInBytes {
108 df.SetTruncated()
109 return errors.New("ModbusTCP packet too short")
110 }
111
112 if len(data) > mbapRecordSizeInBytes+modbusPDUMaximumRecordSizeInBytes {
113 df.SetTruncated()
114 return errors.New("ModbusTCP packet too long")
115 }
116
117 // ModbusTCP type embeds type BaseLayer which contains two fields:
118 // Contents is supposed to contain the bytes of the data at this level (MPBA).
119 // Payload is supposed to contain the payload of this level (PDU).
120 d.BaseLayer = BaseLayer{Contents: data[:mbapRecordSizeInBytes], Payload: data[mbapRecordSizeInBytes:len(data)]}
121
122 // Extract the fields from the block of bytes.
123 // The fields can just be copied in big endian order.
124 d.TransactionIdentifier = binary.BigEndian.Uint16(data[:2])
125 d.ProtocolIdentifier = ModbusProtocol(binary.BigEndian.Uint16(data[2:4]))
126 d.Length = binary.BigEndian.Uint16(data[4:6])
127
128 // Length should have the size of the payload plus one byte (size of UnitIdentifier)
129 if d.Length != uint16(len(d.BaseLayer.Payload)+1) {
130 df.SetTruncated()
131 return errors.New("ModbusTCP packet with wrong field value (Length)")
132 }
133 d.UnitIdentifier = uint8(data[6])
134
135 return nil
136}
137
138//******************************************************************************
139
140// NextLayerType returns the layer type of the ModbusTCP payload, which is LayerTypePayload.
141func (d *ModbusTCP) NextLayerType() gopacket.LayerType {
142 return gopacket.LayerTypePayload
143}
144
145//******************************************************************************
146
147// Payload returns Modbus Protocol Data Unit (PDU) composed by Function Code and Data, it is carried within ModbusTCP packets
148func (d *ModbusTCP) Payload() []byte {
149 return d.BaseLayer.Payload
150}