| // |
| // 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 |
| // |
| // http://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 eip |
| |
| import ( |
| readWriteModel "github.com/apache/plc4x/plc4go/internal/plc4go/eip/readwrite/model" |
| "github.com/apache/plc4x/plc4go/internal/plc4go/spi" |
| plc4goModel "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model" |
| "github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils" |
| spiValues "github.com/apache/plc4x/plc4go/internal/plc4go/spi/values" |
| "github.com/apache/plc4x/plc4go/pkg/plc4go/model" |
| "github.com/apache/plc4x/plc4go/pkg/plc4go/values" |
| "github.com/pkg/errors" |
| "github.com/rs/zerolog/log" |
| "strconv" |
| "strings" |
| "time" |
| ) |
| |
| type Reader struct { |
| messageCodec spi.MessageCodec |
| tm *spi.RequestTransactionManager |
| configuration Configuration |
| sessionHandle *uint32 |
| } |
| |
| func NewReader(messageCodec spi.MessageCodec, tm *spi.RequestTransactionManager, configuration Configuration, sessionHandle *uint32) *Reader { |
| return &Reader{ |
| messageCodec: messageCodec, |
| tm: tm, |
| configuration: configuration, |
| sessionHandle: sessionHandle, |
| } |
| } |
| |
| func (m *Reader) Read(readRequest model.PlcReadRequest) <-chan model.PlcReadRequestResult { |
| log.Trace().Msg("Reading") |
| result := make(chan model.PlcReadRequestResult) |
| go func() { |
| |
| requestItems := make([]*readWriteModel.CipService, len(readRequest.GetFieldNames())) |
| for i, fieldName := range readRequest.GetFieldNames() { |
| plcField := readRequest.GetField(fieldName).(EIPPlcField) |
| tag := plcField.GetTag() |
| elements := uint16(1) |
| if plcField.GetElementNb() > 1 { |
| elements = plcField.GetElementNb() |
| } |
| ansi, err := toAnsi(tag) |
| if err != nil { |
| result <- model.PlcReadRequestResult{ |
| Request: readRequest, |
| Response: nil, |
| Err: errors.Wrapf(err, "Error encoding eip ansi for field %s", fieldName), |
| } |
| return |
| } |
| request := readWriteModel.NewCipReadRequest(getRequestSize(tag), ansi, elements) |
| requestItems[i] = request |
| } |
| if len(requestItems) > 1 { |
| nb := uint16(len(requestItems)) |
| offsets := make([]uint16, nb) |
| offset := 2 + nb*2 |
| for i := uint16(0); i < nb; i++ { |
| offsets[i] = offset |
| offset += requestItems[i].LengthInBytes() |
| } |
| |
| serviceArr := make([]*readWriteModel.CipService, nb) |
| for i := uint16(0); i < nb; i++ { |
| serviceArr[i] = requestItems[i] |
| } |
| |
| data := readWriteModel.NewServices(nb, offsets, serviceArr) |
| //Encapsulate the data |
| pkt := readWriteModel.NewCipRRData( |
| readWriteModel.NewCipExchange( |
| readWriteModel.NewCipUnconnectedRequest( |
| readWriteModel.NewMultipleServiceRequest(data), |
| m.configuration.backplane, |
| m.configuration.slot, |
| ), |
| ), |
| *m.sessionHandle, |
| 0, |
| make([]byte, 8), |
| 0, |
| ) |
| |
| // Start a new request-transaction (Is ended in the response-handler) |
| transaction := m.tm.StartTransaction() |
| transaction.Submit(func() { |
| // Send the over the wire |
| log.Trace().Msg("Send ") |
| if err := m.messageCodec.SendRequest( |
| pkt, |
| func(message interface{}) bool { |
| eipPacket := readWriteModel.CastEipPacket(message) |
| if eipPacket == nil { |
| return false |
| } |
| cipRRData := readWriteModel.CastCipRRData(eipPacket.Child) |
| if cipRRData == nil { |
| return false |
| } |
| if eipPacket.SessionHandle != *m.sessionHandle { |
| return false |
| } |
| multipleServiceResponse := readWriteModel.CastMultipleServiceResponse(cipRRData.Exchange.Service) |
| if multipleServiceResponse == nil { |
| return false |
| } |
| if multipleServiceResponse.ServiceNb != nb { |
| return false |
| } |
| return true |
| }, |
| func(message interface{}) error { |
| // Convert the response into an |
| log.Trace().Msg("convert response to ") |
| eipPacket := readWriteModel.CastEipPacket(message) |
| cipRRData := readWriteModel.CastCipRRData(eipPacket.Child) |
| multipleServiceResponse := readWriteModel.CastMultipleServiceResponse(cipRRData.Exchange.Service) |
| // Convert the eip response into a PLC4X response |
| log.Trace().Msg("convert response to PLC4X response") |
| readResponse, err := m.ToPlc4xReadResponse(multipleServiceResponse.Parent, readRequest) |
| |
| if err != nil { |
| result <- model.PlcReadRequestResult{ |
| Request: readRequest, |
| Err: errors.Wrap(err, "Error decoding response"), |
| } |
| return transaction.EndRequest() |
| } |
| result <- model.PlcReadRequestResult{ |
| Request: readRequest, |
| Response: readResponse, |
| } |
| return transaction.EndRequest() |
| }, |
| func(err error) error { |
| result <- model.PlcReadRequestResult{ |
| Request: readRequest, |
| Err: errors.Wrap(err, "got timeout while waiting for response"), |
| } |
| return transaction.EndRequest() |
| }, |
| time.Second*1); err != nil { |
| result <- model.PlcReadRequestResult{ |
| Request: readRequest, |
| Response: nil, |
| Err: errors.Wrap(err, "error sending message"), |
| } |
| _ = transaction.EndRequest() |
| } |
| }) |
| } else if len(requestItems) == 1 { |
| //Encapsulate the data |
| pkt := readWriteModel.NewCipRRData( |
| readWriteModel.NewCipExchange( |
| readWriteModel.NewCipUnconnectedRequest( |
| requestItems[0], |
| m.configuration.backplane, |
| m.configuration.slot, |
| ), |
| ), |
| *m.sessionHandle, |
| 0, |
| make([]byte, 8), |
| 0, |
| ) |
| |
| // Start a new request-transaction (Is ended in the response-handler) |
| transaction := m.tm.StartTransaction() |
| transaction.Submit(func() { |
| // Send the over the wire |
| log.Trace().Msg("Send ") |
| if err := m.messageCodec.SendRequest( |
| pkt, |
| func(message interface{}) bool { |
| eipPacket := readWriteModel.CastEipPacket(message) |
| if eipPacket == nil { |
| return false |
| } |
| cipRRData := readWriteModel.CastCipRRData(eipPacket.Child) |
| if cipRRData == nil { |
| return false |
| } |
| if eipPacket.SessionHandle != *m.sessionHandle { |
| return false |
| } |
| cipReadResponse := readWriteModel.CastCipReadResponse(cipRRData.Exchange.Service) |
| if cipReadResponse == nil { |
| return false |
| } |
| return true |
| }, |
| func(message interface{}) error { |
| // Convert the response into an |
| log.Trace().Msg("convert response to ") |
| eipPacket := readWriteModel.CastEipPacket(message) |
| cipRRData := readWriteModel.CastCipRRData(eipPacket.Child) |
| cipReadResponse := readWriteModel.CastCipReadResponse(cipRRData.Exchange.Service) |
| // Convert the eip response into a PLC4X response |
| log.Trace().Msg("convert response to PLC4X response") |
| readResponse, err := m.ToPlc4xReadResponse(cipReadResponse.Parent, readRequest) |
| |
| if err != nil { |
| result <- model.PlcReadRequestResult{ |
| Request: readRequest, |
| Err: errors.Wrap(err, "Error decoding response"), |
| } |
| return transaction.EndRequest() |
| } |
| result <- model.PlcReadRequestResult{ |
| Request: readRequest, |
| Response: readResponse, |
| } |
| return transaction.EndRequest() |
| }, |
| func(err error) error { |
| result <- model.PlcReadRequestResult{ |
| Request: readRequest, |
| Err: errors.Wrap(err, "got timeout while waiting for response"), |
| } |
| return transaction.EndRequest() |
| }, |
| time.Second*1); err != nil { |
| result <- model.PlcReadRequestResult{ |
| Request: readRequest, |
| Response: nil, |
| Err: errors.Wrap(err, "error sending message"), |
| } |
| _ = transaction.EndRequest() |
| } |
| }) |
| } |
| }() |
| return result |
| } |
| |
| func getRequestSize(tag string) int8 { |
| //We need the size of the request in words (0x91, tagLength, ... tag + possible pad) |
| // Taking half to get word size |
| isArray := false |
| isStruct := false |
| tagIsolated := tag |
| |
| if strings.Contains(tag, "[") { |
| isArray = true |
| tagIsolated = tag[0:strings.Index(tag, "[")] |
| } |
| |
| if strings.Contains(tag, ".") { |
| isStruct = true |
| tagIsolated = strings.Replace(tagIsolated, ".", "", -1) |
| } |
| dataLength := (len(tagIsolated) + 2) + (len(tagIsolated) % 2) |
| if isArray { |
| dataLength += 2 |
| } |
| if isStruct { |
| dataLength += 2 |
| } |
| requestPathSize := (int8)(dataLength / 2) |
| return requestPathSize |
| } |
| |
| func toAnsi(tag string) ([]int8, error) { |
| arrayIndex := 0 |
| isArray := false |
| isStruct := false |
| tagFinal := tag |
| if strings.Contains(tag, "[") { |
| isArray = true |
| index := tag[strings.Index(tag, "[")+1 : strings.Index(tag, "]")] |
| atoi, err := strconv.Atoi(index) |
| if err != nil { |
| return nil, err |
| } |
| arrayIndex = atoi |
| tagFinal = tag[0:strings.Index(tag, "[")] |
| } |
| if strings.Contains(tag, ".") { |
| tagFinal = tag[0:strings.Index(tag, ".")] |
| isStruct = true |
| } |
| isPadded := len(tagFinal)%2 != 0 |
| dataSegLength := 2 + len(tagFinal) |
| if isPadded { |
| dataSegLength += 1 |
| } |
| if isArray { |
| dataSegLength += 2 |
| } |
| |
| if isStruct { |
| for _, subStr := range strings.Split(tag[strings.Index(tag, ".")+1:], ".") { |
| dataSegLength += 2 + len(subStr) + len(subStr)%2 |
| } |
| } |
| |
| buffer := utils.NewLittleEndianWriteBufferByteBased() |
| |
| err := buffer.WriteByte("", 0x91) |
| if err != nil { |
| return nil, err |
| } |
| err = buffer.WriteByte("", byte(len(tagFinal))) |
| if err != nil { |
| return nil, err |
| } |
| |
| quoteToASCII := strconv.QuoteToASCII(tagFinal) |
| err = buffer.WriteByteArray("", []byte(quoteToASCII)[1:len(quoteToASCII)-1]) |
| if err != nil { |
| return nil, err |
| } |
| |
| if isPadded { |
| err = buffer.WriteByte("", 0x00) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| if isArray { |
| err = buffer.WriteByte("", 0x28) |
| if err != nil { |
| return nil, err |
| } |
| err = buffer.WriteByte("", byte(arrayIndex)) |
| if err != nil { |
| return nil, err |
| } |
| } |
| if isStruct { |
| ansi, err := toAnsi(tag[strings.Index(tag, ".")+1:]) |
| if err != nil { |
| return nil, err |
| } |
| err = buffer.WriteByteArray("", utils.Int8ArrayToByteArray(ansi)) |
| if err != nil { |
| return nil, err |
| } |
| } |
| return utils.Uint8ArrayToInt8Array(buffer.GetBytes()), nil |
| } |
| |
| func (m *Reader) ToPlc4xReadResponse(response *readWriteModel.CipService, readRequest model.PlcReadRequest) (model.PlcReadResponse, error) { |
| plcValues := map[string]values.PlcValue{} |
| responseCodes := map[string]model.PlcResponseCode{} |
| switch response.Child.(type) { |
| case *readWriteModel.CipReadResponse: // only 1 field |
| cipReadResponse := response.Child.(*readWriteModel.CipReadResponse) |
| fieldName := readRequest.GetFieldNames()[0] |
| field := readRequest.GetField(fieldName).(EIPPlcField) |
| code := decodeResponseCode(cipReadResponse.Status) |
| var plcValue values.PlcValue |
| _type := cipReadResponse.DataType |
| data := utils.NewLittleEndianReadBufferByteBased(utils.Int8ArrayToByteArray(cipReadResponse.Data)) |
| if code == model.PlcResponseCode_OK { |
| var err error |
| plcValue, err = parsePlcValue(field, data, _type) |
| if err != nil { |
| return nil, err |
| } |
| } |
| plcValues[fieldName] = plcValue |
| responseCodes[fieldName] = code |
| case *readWriteModel.MultipleServiceResponse: //Multiple response |
| multipleServiceResponse := response.Child.(*readWriteModel.MultipleServiceResponse) |
| nb := multipleServiceResponse.ServiceNb |
| arr := make([]*readWriteModel.CipService, nb) |
| read := utils.NewLittleEndianReadBufferByteBased(utils.Int8ArrayToByteArray(multipleServiceResponse.ServicesData)) |
| total := read.GetTotalBytes() |
| for i := uint16(0); i < nb; i++ { |
| length := uint16(0) |
| offset := multipleServiceResponse.Offsets[i] - multipleServiceResponse.Offsets[0] //Substract first offset as we only have the service in the buffer (not servicesNb and offsets) |
| if i == nb-1 { |
| length = uint16(total) - offset //Get the rest if last |
| } else { |
| length = multipleServiceResponse.Offsets[i+1] - offset - multipleServiceResponse.Offsets[0] //Calculate length with offsets (substracting first offset) |
| } |
| serviceBuf := utils.NewLittleEndianReadBufferByteBased(read.GetBytes()[offset : offset+length]) |
| var err error |
| arr[i], err = readWriteModel.CipServiceParse(serviceBuf, length) |
| if err != nil { |
| return nil, err |
| } |
| } |
| services := readWriteModel.NewServices(nb, multipleServiceResponse.Offsets, arr) |
| for i, fieldName := range readRequest.GetFieldNames() { |
| field := readRequest.GetField(fieldName).(EIPPlcField) |
| if cipReadResponse, ok := services.Services[i].Child.(*readWriteModel.CipReadResponse); ok { |
| code := decodeResponseCode(cipReadResponse.Status) |
| _type := cipReadResponse.DataType |
| data := utils.NewLittleEndianReadBufferByteBased(utils.Int8ArrayToByteArray(cipReadResponse.Data)) |
| var plcValue values.PlcValue |
| if code == model.PlcResponseCode_OK { |
| var err error |
| plcValue, err = parsePlcValue(field, data, _type) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| plcValues[fieldName] = plcValue |
| responseCodes[fieldName] = code |
| } else { |
| responseCodes[fieldName] = model.PlcResponseCode_INTERNAL_ERROR |
| } |
| } |
| default: |
| return nil, errors.Errorf("unsupported response type %T", response.Child) |
| } |
| |
| // Return the response |
| log.Trace().Msg("Returning the response") |
| return plc4goModel.NewDefaultPlcReadResponse(readRequest, responseCodes, plcValues), nil |
| } |
| |
| func parsePlcValue(field EIPPlcField, data utils.ReadBufferByteBased, _type readWriteModel.CIPDataTypeCode) (values.PlcValue, error) { |
| nb := field.GetElementNb() |
| if nb > 1 { |
| list := make([]values.PlcValue, 0) |
| for i := uint16(0); i < nb; i++ { |
| switch _type { |
| case readWriteModel.CIPDataTypeCode_DINT: |
| readInt32, err := data.ReadInt32("", _type.Size()*8) |
| if err != nil { |
| return nil, err |
| } |
| list = append(list, spiValues.NewPlcDINT(readInt32)) |
| case readWriteModel.CIPDataTypeCode_INT: |
| readInt16, err := data.ReadInt16("", _type.Size()*8) |
| if err != nil { |
| return nil, err |
| } |
| list = append(list, spiValues.NewPlcINT(readInt16)) |
| case readWriteModel.CIPDataTypeCode_SINT: |
| readInt8, err := data.ReadInt8("", _type.Size()*8) |
| if err != nil { |
| return nil, err |
| } |
| list = append(list, spiValues.NewPlcSINT(readInt8)) |
| case readWriteModel.CIPDataTypeCode_REAL: |
| if _type.Size()*8 != 64 { |
| panic("Unexpected size") |
| } |
| readFloat64, err := data.ReadFloat64("", true, 11, 52) |
| if err != nil { |
| return nil, err |
| } |
| list = append(list, spiValues.NewPlcLREAL(readFloat64)) |
| case readWriteModel.CIPDataTypeCode_BOOL: |
| bit, err := data.ReadBit("") |
| if err != nil { |
| return nil, err |
| } |
| list = append(list, spiValues.NewPlcBOOL(bit)) |
| default: |
| return nil, errors.Errorf("Unknown type %v", _type) |
| } |
| } |
| return spiValues.NewPlcList(list), nil |
| } else { |
| switch _type { |
| case readWriteModel.CIPDataTypeCode_SINT: |
| readByte, err := data.ReadInt8("", _type.Size()*8) |
| if err != nil { |
| return nil, err |
| } |
| return spiValues.NewPlcSINT(readByte), nil |
| case readWriteModel.CIPDataTypeCode_INT: |
| readInt16, err := data.ReadInt16("", _type.Size()*8) |
| if err != nil { |
| return nil, err |
| } |
| return spiValues.NewPlcINT(readInt16), nil |
| case readWriteModel.CIPDataTypeCode_DINT: |
| readInt32, err := data.ReadInt32("", _type.Size()*8) |
| if err != nil { |
| return nil, err |
| } |
| return spiValues.NewPlcDINT(readInt32), nil |
| case readWriteModel.CIPDataTypeCode_REAL: |
| if _type.Size()*8 != 64 { |
| panic("Unexpected size") |
| } |
| readFloat32, err := data.ReadFloat32("", true, 11, 52) |
| if err != nil { |
| return nil, err |
| } |
| return spiValues.NewPlcREAL(readFloat32), nil |
| case readWriteModel.CIPDataTypeCode_BOOL: |
| readBit, err := data.ReadBit("") |
| if err != nil { |
| return nil, err |
| } |
| return spiValues.NewPlcBOOL(readBit), nil |
| default: |
| return nil, errors.Errorf("Unknown type %v", _type) |
| } |
| } |
| } |
| |
| // Helper to convert the return codes returned from the eip into one of our standard |
| func decodeResponseCode(status uint8) model.PlcResponseCode { |
| //TODO other status |
| switch status { |
| case 0: |
| return model.PlcResponseCode_OK |
| default: |
| return model.PlcResponseCode_INTERNAL_ERROR |
| } |
| } |