blob: 026e876aad028f3b79d5b8f885017b5f0d24f4db [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package s7
import (
"encoding/hex"
"fmt"
"regexp"
"strconv"
"strings"
apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
readWriteModel "github.com/apache/plc4x/plc4go/protocols/s7/readwrite/model"
"github.com/apache/plc4x/plc4go/spi/utils"
"github.com/pkg/errors"
)
type TagType uint8
//go:generate stringer -type TagType
const (
S7Tag TagType = 0x00
S7StringTag TagType = 0x01
)
func (i TagType) GetName() string {
return i.String()
}
type TagHandler struct {
addressPattern *regexp.Regexp
dataBlockAddressPattern *regexp.Regexp
dataBlockShortPattern *regexp.Regexp
dataBlockStringAddressPattern *regexp.Regexp
dataBlockStringShortPattern *regexp.Regexp
plcProxyAddressPattern *regexp.Regexp
}
func NewTagHandler() TagHandler {
return TagHandler{
addressPattern: regexp.MustCompile(`^%(?P<memoryArea>.)(?P<transferSizeCode>[XBWD]?)(?P<byteOffset>\d{1,7})(.(?P<bitOffset>[0-7]))?:(?P<dataType>[a-zA-Z_]+)(\[(?P<numElements>\d+)])?`),
//blockNumber usually has its max hat around 64000 --> 5digits
dataBlockAddressPattern: regexp.MustCompile(`^%DB(?P<blockNumber>\d{1,5}).DB(?P<transferSizeCode>[XBWD]?)(?P<byteOffset>\d{1,7})(.(?P<bitOffset>[0-7]))?:(?P<dataType>[a-zA-Z_]+)(\[(?P<numElements>\d+)])?`),
dataBlockShortPattern: regexp.MustCompile(`^%DB(?P<blockNumber>\d{1,5}):(?P<byteOffset>\d{1,7})(.(?P<bitOffset>[0-7]))?:(?P<dataType>[a-zA-Z_]+)(\[(?P<numElements>\d+)])?`),
dataBlockStringAddressPattern: regexp.MustCompile(`^%DB(?P<blockNumber>\d{1,5}).DB(?P<transferSizeCode>[XBWD]?)(?P<byteOffset>\d{1,7})(.(?P<bitOffset>[0-7]))?:(?P<dataType>STRING|WSTRING)\((?P<stringLength>\d{1,3})\)(\[(?P<numElements>\d+)])?`),
dataBlockStringShortPattern: regexp.MustCompile(`^%DB(?P<blockNumber>\d{1,5}):(?P<byteOffset>\d{1,7})(.(?P<bitOffset>[0-7]))?:(?P<dataType>STRING|WSTRING)\((?P<stringLength>\d{1,3})\)(\[(?P<numElements>\d+)])?`),
plcProxyAddressPattern: regexp.MustCompile(`[0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2}`),
}
}
const (
DATA_TYPE = "dataType"
STRING_LENGTH = "stringLength"
TRANSFER_SIZE_CODE = "transferSizeCode"
BLOCK_NUMBER = "blockNumber"
BYTE_OFFSET = "byteOffset"
BIT_OFFSET = "bitOffset"
NUM_ELEMENTS = "numElements"
MEMORY_AREA = "memoryArea"
)
func (m TagHandler) ParseTag(tagAddress string) (apiModel.PlcTag, error) {
if match := utils.GetSubgroupMatches(m.dataBlockStringAddressPattern, tagAddress); match != nil {
dataType, ok := readWriteModel.TransportSizeByName(match[DATA_TYPE])
if !ok {
return nil, errors.Errorf("Unknown type %s", match[DATA_TYPE])
}
parsedStringLength, err := strconv.ParseUint(match[STRING_LENGTH], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting stringlength")
}
stringLength := uint16(parsedStringLength)
memoryArea := readWriteModel.MemoryArea_DATA_BLOCKS
transferSizeCode := getSizeCode(match[TRANSFER_SIZE_CODE])
parsedByteOffset, err := strconv.ParseUint(match[BYTE_OFFSET], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
byteOffset, err := checkByteOffset(parsedByteOffset)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
bitOffset := uint8(0)
if match[BIT_OFFSET] != "" {
parsedBitOffset, err := strconv.ParseUint(match[BIT_OFFSET], 10, 8)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
bitOffset = uint8(parsedBitOffset)
} else if dataType == readWriteModel.TransportSize_BOOL {
return nil, errors.New("Expected bit offset for BOOL parameters.")
}
numElements := uint16(1)
if match[NUM_ELEMENTS] != "" {
parsedNumElements, err := strconv.ParseUint(match[NUM_ELEMENTS], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting numelements")
}
numElements = uint16(parsedNumElements)
}
if (transferSizeCode != 0) && (dataType.ShortName() != transferSizeCode) {
return nil, errors.Errorf("Transfer size code '%d' doesn't match specified data type '%s'", transferSizeCode, dataType)
}
return NewStringTag(memoryArea, 0, byteOffset, bitOffset, numElements, stringLength, dataType), nil
} else if match := utils.GetSubgroupMatches(m.dataBlockStringShortPattern, tagAddress); match != nil {
dataType, ok := readWriteModel.TransportSizeByName(match[DATA_TYPE])
if !ok {
return nil, errors.Errorf("Unknown type %s", match[DATA_TYPE])
}
parsedStringLength, err := strconv.ParseUint(match[STRING_LENGTH], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting stringlength")
}
stringLength := uint16(parsedStringLength)
memoryArea := readWriteModel.MemoryArea_DATA_BLOCKS
parsedBlockNumber, err := strconv.ParseUint(match[BLOCK_NUMBER], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting blocknumber")
}
blockNumber, err := checkDatablockNumber(parsedBlockNumber)
if err != nil {
return nil, errors.Wrap(err, "Error checking blocknumber")
}
parsedByteOffset, err := strconv.ParseUint(match[BYTE_OFFSET], 10, 8)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
byteOffset, err := checkByteOffset(parsedByteOffset)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
bitOffset := uint8(0)
numElements := uint16(1)
if match[NUM_ELEMENTS] != "" {
parsedNumElements, err := strconv.ParseUint(match[NUM_ELEMENTS], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting numelements")
}
numElements = uint16(parsedNumElements)
}
return NewStringTag(memoryArea, blockNumber, byteOffset, bitOffset, numElements, stringLength, dataType), nil
} else if match := utils.GetSubgroupMatches(m.dataBlockAddressPattern, tagAddress); match != nil {
dataType, ok := readWriteModel.TransportSizeByName(match[DATA_TYPE])
if !ok {
return nil, errors.Errorf("Unknown type %s", match[DATA_TYPE])
}
memoryArea := readWriteModel.MemoryArea_DATA_BLOCKS
transferSizeCode := getSizeCode(match[TRANSFER_SIZE_CODE])
parsedBlockNumber, err := strconv.ParseUint(match[BLOCK_NUMBER], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting blocknumber")
}
blockNumber, err := checkDatablockNumber(parsedBlockNumber)
if err != nil {
return nil, errors.Wrap(err, "Error checking blocknumber")
}
parsedByteOffset, err := strconv.ParseUint(match[BYTE_OFFSET], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
byteOffset, err := checkByteOffset(parsedByteOffset)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
bitOffset := uint8(0)
if match[BIT_OFFSET] != "" {
parsedBitOffset, err := strconv.ParseUint(match[BIT_OFFSET], 10, 8)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
bitOffset = uint8(parsedBitOffset)
} else if dataType == readWriteModel.TransportSize_BOOL {
return nil, errors.New("Expected bit offset for BOOL parameters.")
}
numElements := uint16(1)
if match[NUM_ELEMENTS] != "" {
parsedNumElements, err := strconv.ParseUint(match[NUM_ELEMENTS], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting numelements")
}
numElements = uint16(parsedNumElements)
}
if (transferSizeCode != 0) && (dataType.ShortName() != transferSizeCode) {
return nil, errors.Errorf("Transfer size code '%d' doesn't match specified data type '%s'", transferSizeCode, dataType)
}
return NewTag(memoryArea, blockNumber, byteOffset, bitOffset, numElements, dataType), nil
} else if match := utils.GetSubgroupMatches(m.dataBlockShortPattern, tagAddress); match != nil {
dataType, ok := readWriteModel.TransportSizeByName(match[DATA_TYPE])
if !ok {
return nil, errors.Errorf("Unknown type %s", match[DATA_TYPE])
}
memoryArea := readWriteModel.MemoryArea_DATA_BLOCKS
parsedBlockNumber, err := strconv.ParseUint(match[BLOCK_NUMBER], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting blocknumber")
}
blockNumber, err := checkDatablockNumber(parsedBlockNumber)
if err != nil {
return nil, errors.Wrap(err, "Error checking blocknumber")
}
parsedByteOffset, err := strconv.ParseUint(match[BYTE_OFFSET], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
byteOffset, err := checkByteOffset(parsedByteOffset)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
bitOffset := uint8(0)
if match[BIT_OFFSET] != "" {
parsedBitOffset, err := strconv.ParseUint(match[BIT_OFFSET], 10, 8)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
bitOffset = uint8(parsedBitOffset)
} else if dataType == readWriteModel.TransportSize_BOOL {
return nil, errors.New("Expected bit offset for BOOL parameters.")
}
numElements := uint16(1)
if match[NUM_ELEMENTS] != "" {
parsedNumElements, err := strconv.ParseUint(match[NUM_ELEMENTS], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting numelements")
}
numElements = uint16(parsedNumElements)
}
return NewTag(memoryArea, blockNumber, byteOffset, bitOffset, numElements, dataType), nil
} else if match := utils.GetSubgroupMatches(m.plcProxyAddressPattern, tagAddress); match != nil {
addressData, err := hex.DecodeString(strings.ReplaceAll(tagAddress, "[-]", ""))
if err != nil {
return nil, errors.Wrapf(err, "Unable to parse address: %s", tagAddress)
}
s7Address, err := readWriteModel.S7AddressAnyParse(addressData)
if err != nil {
return nil, errors.Wrapf(err, "Unable to parse address: %s", tagAddress)
}
s7AddressAny := s7Address.(readWriteModel.S7AddressAny)
if (s7AddressAny.GetTransportSize() != readWriteModel.TransportSize_BOOL) && s7AddressAny.GetBitAddress() != 0 {
return nil, errors.New("A bit offset other than 0 is only supported for type BOOL")
}
return NewTag(
s7AddressAny.GetArea(),
s7AddressAny.GetDbNumber(),
s7AddressAny.GetByteAddress(),
s7AddressAny.GetBitAddress(),
s7AddressAny.GetNumberOfElements(),
s7AddressAny.GetTransportSize(),
), nil
} else if match := utils.GetSubgroupMatches(m.addressPattern, tagAddress); match != nil {
dataType, ok := readWriteModel.TransportSizeByName(match[DATA_TYPE])
if !ok {
return nil, errors.Errorf("Unknown type %s", match[DATA_TYPE])
}
memoryArea, err := getMemoryAreaForShortName(match[MEMORY_AREA])
if err != nil {
return nil, errors.Wrap(err, "Error getting memory area")
}
transferSizeCode := getSizeCode(match[TRANSFER_SIZE_CODE])
parsedTransferSizeCode, err := strconv.ParseUint(match[BYTE_OFFSET], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
byteOffset, err := checkByteOffset(parsedTransferSizeCode)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
bitOffset := uint8(0)
if match[BIT_OFFSET] != "" {
parsedBitOffset, err := strconv.ParseUint(match[BIT_OFFSET], 10, 8)
if err != nil {
return nil, errors.Wrap(err, "Error converting byteoffset")
}
bitOffset = uint8(parsedBitOffset)
} else if dataType == readWriteModel.TransportSize_BOOL {
return nil, errors.New("Expected bit offset for BOOL parameters.")
}
numElements := uint16(1)
if match[NUM_ELEMENTS] != "" {
parsedNumElements, err := strconv.ParseUint(match[NUM_ELEMENTS], 10, 16)
if err != nil {
return nil, errors.Wrap(err, "Error converting numelements")
}
numElements = uint16(parsedNumElements)
}
if (transferSizeCode != 0) && (dataType.ShortName() != transferSizeCode) {
return nil, errors.Errorf("Transfer size code '%d' doesn't match specified data type '%s'", transferSizeCode, dataType)
}
if (dataType != readWriteModel.TransportSize_BOOL) && bitOffset != 0 {
return nil, errors.New("A bit offset other than 0 is only supported for type BOOL")
}
return NewTag(memoryArea, 0, byteOffset, bitOffset, numElements, dataType), nil
}
return nil, errors.Errorf("Unable to parse %s", tagAddress)
}
func (m TagHandler) ParseQuery(query string) (apiModel.PlcQuery, error) {
return nil, fmt.Errorf("queries not supported")
}
func checkDatablockNumber(blockNumber uint64) (uint16, error) {
//ToDo check the value or add reference - limit eventually depending on active S7 --> make a case selection
if blockNumber > 64000 || blockNumber < 1 {
return 0, errors.New("Datablock numbers larger than 64000 or smaller than 1 are not supported.")
}
return uint16(blockNumber), nil
}
func checkByteOffset(byteOffset uint64) (uint16, error) {
//ToDo check the value or add reference
if byteOffset > 2097151 || byteOffset < 0 {
return 0, errors.New("ByteOffset must be smaller than 2097151 and positive.")
}
return uint16(byteOffset), nil
}
func getSizeCode(value string) uint8 {
if value == "" {
return 0
}
if len(value) > 1 {
return 0
}
chars := []rune(value)
parsedSizeCode, err := strconv.ParseUint(string(chars[0]), 10, 8)
if err != nil {
return 0
}
return uint8(parsedSizeCode)
}
func getMemoryAreaForShortName(shortName string) (readWriteModel.MemoryArea, error) {
for _, memoryArea := range readWriteModel.MemoryAreaValues {
if memoryArea.ShortName() == shortName {
return memoryArea, nil
}
}
return 0, errors.Errorf("Unknown memory area for short name: '%s'", shortName)
}