241 lines
7.3 KiB
Go
241 lines
7.3 KiB
Go
package siemens
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
)
|
|
|
|
// TPKTPacket is defined in RFC 1006
|
|
type TPKTPacket struct {
|
|
// Data is the packet's content
|
|
Data []byte
|
|
}
|
|
|
|
const tpktLength = 4 // 4 bytes (excluding Data slice)
|
|
|
|
// Marshal encodes a TPKTPacket to binary.
|
|
func (tpktPacket *TPKTPacket) Marshal() ([]byte, error) {
|
|
|
|
totalLength := len(tpktPacket.Data) + tpktLength
|
|
bytes := make([]byte, 0, totalLength)
|
|
|
|
bytes = append(bytes, byte(3)) // version
|
|
bytes = append(bytes, byte(0)) // reserved
|
|
uint16BytesHolder := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(uint16BytesHolder, uint16(totalLength))
|
|
bytes = append(bytes, uint16BytesHolder...)
|
|
bytes = append(bytes, tpktPacket.Data...)
|
|
|
|
return bytes, nil
|
|
}
|
|
|
|
// Unmarshal decodes a TPKTPacket from binary.
|
|
func (tpktPacket *TPKTPacket) Unmarshal(bytes []byte) error {
|
|
|
|
if len(bytes) < tpktLength {
|
|
return errS7PacketTooShort
|
|
}
|
|
|
|
tpktPacket.Data = bytes[tpktLength:]
|
|
|
|
return nil
|
|
}
|
|
|
|
// COTPConnectionPacket is defined in RFC 892.
|
|
type COTPConnectionPacket struct {
|
|
// DestinationRef is the DST-REF TPDU field
|
|
DestinationRef uint16
|
|
|
|
// SourceRef is the SCE-REF TPDU field
|
|
SourceRef uint16
|
|
|
|
// DestinationTSAP is the destination transport service access point.
|
|
DestinationTSAP uint16
|
|
|
|
// SourceTSAP is the source transport service access point.
|
|
SourceTSAP uint16
|
|
|
|
// TPDUSize is the size (in bytes) of the TPDU
|
|
TPDUSize byte
|
|
}
|
|
|
|
const cotpConnRequestLength = 18
|
|
|
|
// Marshal encodes a COTPConnectionPacket to binary.
|
|
func (cotpConnPacket *COTPConnectionPacket) Marshal() ([]byte, error) {
|
|
bytes := make([]byte, 0, cotpConnRequestLength)
|
|
uint16BytesHolder := make([]byte, 2)
|
|
|
|
bytes = append(bytes, byte(cotpConnRequestLength-1)) // length of packet (excluding 1-byte length header)
|
|
bytes = append(bytes, byte(0xe0)) // connection request code
|
|
binary.BigEndian.PutUint16(uint16BytesHolder, cotpConnPacket.DestinationRef)
|
|
bytes = append(bytes, uint16BytesHolder...)
|
|
binary.BigEndian.PutUint16(uint16BytesHolder, cotpConnPacket.SourceRef)
|
|
bytes = append(bytes, uint16BytesHolder...)
|
|
bytes = append(bytes, byte(0)) // class 0 transport protocol with no flags
|
|
bytes = append(bytes, byte(0xc1)) // code for identifier of the calling TSAP field
|
|
bytes = append(bytes, byte(2)) // byte-length of subsequent field SourceTSAP
|
|
binary.BigEndian.PutUint16(uint16BytesHolder, cotpConnPacket.SourceTSAP)
|
|
bytes = append(bytes, uint16BytesHolder...)
|
|
bytes = append(bytes, byte(0xc2)) // code fo identifier of the called TSAP field
|
|
bytes = append(bytes, byte(2)) // byte-length of subsequent field DestinationTSAP
|
|
binary.BigEndian.PutUint16(uint16BytesHolder, cotpConnPacket.DestinationTSAP)
|
|
bytes = append(bytes, uint16BytesHolder...)
|
|
bytes = append(bytes, byte(0xc0)) // code for proposed maximum TPDU size field
|
|
bytes = append(bytes, byte(1)) // byte-length of subsequent field
|
|
bytes = append(bytes, cotpConnPacket.TPDUSize)
|
|
|
|
return bytes, nil
|
|
}
|
|
|
|
// Unmarshal decodes a COTPConnectionPacket from binary that must be a connection confirmation.
|
|
func (cotpConnPacket *COTPConnectionPacket) Unmarshal(bytes []byte) error {
|
|
|
|
if bytes == nil || len(bytes) < 2 {
|
|
return errInvalidPacket
|
|
}
|
|
|
|
if sizeByte := bytes[0]; int(sizeByte)+1 != len(bytes) {
|
|
return errS7PacketTooShort
|
|
}
|
|
|
|
if pduType := bytes[1]; pduType != 0xd0 {
|
|
return errors.New("Not a connection confirmation packet")
|
|
}
|
|
|
|
// TODO: implement these fields with proper bounds checking
|
|
// cotpConnPacket.DestinationRef = binary.BigEndian.Uint16(bytes[2:4])
|
|
// cotpConnPacket.SourceRef = binary.BigEndian.Uint16(bytes[4:6])
|
|
// cotpConnPacket.DestinationTSAP
|
|
// cotpConnPacket.SourceTSAP
|
|
// cotpConnPacket.TPDUSize
|
|
|
|
return nil
|
|
}
|
|
|
|
// COTPDataPacket wraps the state / interface for a COTP data packet.
|
|
type COTPDataPacket struct {
|
|
Data []byte
|
|
}
|
|
|
|
const cotpDataPacketHeaderLength = 2
|
|
|
|
// Marshal encodes a COTPDataPacket to binary.
|
|
func (cotpDataPacket *COTPDataPacket) Marshal() ([]byte, error) {
|
|
bytes := make([]byte, 0, cotpDataPacketHeaderLength+len(cotpDataPacket.Data))
|
|
|
|
bytes = append(bytes, byte(2)) // data header length
|
|
bytes = append(bytes, byte(0xf0)) // code for data packet
|
|
bytes = append(bytes, byte(0x80)) // code for data packet
|
|
bytes = append(bytes, cotpDataPacket.Data...)
|
|
|
|
return bytes, nil
|
|
}
|
|
|
|
// Unmarshal decodes a COTPDataPacket from binary.
|
|
func (cotpDataPacket *COTPDataPacket) Unmarshal(bytes []byte) error {
|
|
|
|
if bytes == nil || len(bytes) < 1 {
|
|
return errInvalidPacket
|
|
}
|
|
|
|
headerSize := bytes[0]
|
|
|
|
if int(headerSize+1) > len(bytes) {
|
|
return errInvalidPacket
|
|
}
|
|
|
|
cotpDataPacket.Data = bytes[headerSize+1:]
|
|
|
|
return nil
|
|
}
|
|
|
|
// S7Packet represents an S7 packet.
|
|
type S7Packet struct {
|
|
PDUType byte
|
|
RequestId uint16
|
|
Parameters []byte
|
|
Data []byte
|
|
Error uint16
|
|
}
|
|
|
|
const (
|
|
S7_PROTOCOL_ID = byte(0x32)
|
|
S7_REQUEST_ID = uint16(0)
|
|
S7_REQUEST = byte(0x01)
|
|
S7_REQUEST_USER_DATA = byte(0x07)
|
|
S7_ACKNOWLEDGEMENT = byte(0x02)
|
|
S7_RESPONSE = byte(0x03)
|
|
S7_SZL_REQUEST = byte(0x04)
|
|
S7_SZL_FUNCTIONS = byte(0x04)
|
|
S7_SZL_READ = byte(0x01)
|
|
S7_SZL_MODULE_IDENTIFICATION = uint16(0x11)
|
|
S7_SZL_COMPONENT_IDENTIFICATION = uint16(0x1c)
|
|
S7_DATA_BYTE_OFFSET = 12 // offset for real data
|
|
)
|
|
|
|
const s7PacketHeaderLength = 3
|
|
|
|
// Marshal encodes a S7Packet to binary.
|
|
func (s7Packet *S7Packet) Marshal() ([]byte, error) {
|
|
|
|
if s7Packet.PDUType != S7_REQUEST && s7Packet.PDUType != S7_REQUEST_USER_DATA {
|
|
return nil, errors.New("Invalid PDU request type")
|
|
}
|
|
|
|
bytes := make([]byte, 0, s7PacketHeaderLength+len(s7Packet.Data))
|
|
uint16BytesHolder := make([]byte, 2)
|
|
|
|
bytes = append(bytes, S7_PROTOCOL_ID) // s7 protocol id
|
|
bytes = append(bytes, s7Packet.PDUType)
|
|
binary.BigEndian.PutUint16(uint16BytesHolder, 0)
|
|
bytes = append(bytes, uint16BytesHolder...) // reserved
|
|
binary.BigEndian.PutUint16(uint16BytesHolder, s7Packet.RequestId)
|
|
bytes = append(bytes, uint16BytesHolder...)
|
|
binary.BigEndian.PutUint16(uint16BytesHolder, uint16(len(s7Packet.Parameters)))
|
|
bytes = append(bytes, uint16BytesHolder...)
|
|
binary.BigEndian.PutUint16(uint16BytesHolder, uint16(len(s7Packet.Data)))
|
|
bytes = append(bytes, uint16BytesHolder...)
|
|
bytes = append(bytes, s7Packet.Parameters...)
|
|
bytes = append(bytes, s7Packet.Data...)
|
|
|
|
return bytes, nil
|
|
}
|
|
|
|
// Unmarshal decodes a S7Packet from binary.
|
|
func (s7Packet *S7Packet) Unmarshal(bytes []byte) (err error) {
|
|
if bytes == nil || len(bytes) < 1 {
|
|
return errInvalidPacket
|
|
}
|
|
|
|
if protocolId := bytes[0]; protocolId != S7_PROTOCOL_ID {
|
|
return errNotS7
|
|
}
|
|
|
|
var headerSize int
|
|
pduType := bytes[1]
|
|
|
|
if pduType == S7_ACKNOWLEDGEMENT || pduType == S7_RESPONSE {
|
|
headerSize = 12
|
|
s7Packet.Error = binary.BigEndian.Uint16(bytes[10:12])
|
|
} else if pduType == S7_REQUEST || pduType == S7_REQUEST_USER_DATA {
|
|
headerSize = 10
|
|
} else {
|
|
return errors.New("Unknown PDU type " + string(pduType))
|
|
}
|
|
|
|
s7Packet.PDUType = pduType
|
|
s7Packet.RequestId = binary.BigEndian.Uint16(bytes[4:6])
|
|
paramLength := int(binary.BigEndian.Uint16(bytes[6:8]))
|
|
dataLength := int(binary.BigEndian.Uint16(bytes[8:10]))
|
|
|
|
if paramLength < 0 || dataLength < 0 || headerSize+paramLength+dataLength > len(bytes) {
|
|
return errInvalidPacket
|
|
}
|
|
|
|
s7Packet.Parameters = bytes[headerSize : headerSize+paramLength]
|
|
s7Packet.Data = bytes[headerSize+paramLength : headerSize+paramLength+dataLength]
|
|
|
|
return nil
|
|
}
|