├── 1756-pm020_-en-p.pdf ├── LICENSE ├── bufferx └── bufferx.go ├── command └── command.go ├── config.go ├── context.go ├── example.go ├── go.mod ├── messages ├── listIdentity │ ├── decode.go │ └── new.go ├── listInterface │ ├── decode.go │ └── new.go ├── listServices │ ├── decode.go │ └── new.go ├── nop │ └── new.go ├── packet │ ├── cmm.go │ ├── commonPacketFormat.go │ ├── data.go │ ├── messageRouter.go │ ├── packet.go │ ├── services.go │ ├── ucmm.go │ └── utils.go ├── registerSession │ └── new.go ├── sendRRData │ ├── decode.go │ └── new.go ├── sendUnitData │ ├── decode.go │ └── new.go └── unRegisterSession │ └── new.go ├── path └── path.go ├── request.go ├── tag.go ├── tcp.go ├── types └── types.go └── utils └── len.go /1756-pm020_-en-p.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loki-os/go-ethernet-ip/535f79752f63a4122c37c7f0f9b95ece01a8f7b4/1756-pm020_-en-p.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /bufferx/bufferx.go: -------------------------------------------------------------------------------- 1 | package bufferx 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | ) 7 | 8 | type BufferX struct { 9 | *bytes.Buffer 10 | err error 11 | } 12 | 13 | func (b *BufferX) WL(target interface{}) { 14 | b.err = binary.Write(b, binary.LittleEndian, target) 15 | } 16 | 17 | func (b *BufferX) WB(target interface{}) { 18 | b.err = binary.Write(b, binary.BigEndian, target) 19 | } 20 | 21 | func (b *BufferX) RL(target interface{}) { 22 | b.err = binary.Read(b.Buffer, binary.LittleEndian, target) 23 | } 24 | 25 | func (b *BufferX) RB(target interface{}) { 26 | b.err = binary.Read(b.Buffer, binary.BigEndian, target) 27 | } 28 | 29 | func (b *BufferX) Error() error { 30 | return b.err 31 | } 32 | 33 | func New(data []byte) *BufferX { 34 | var buffer *bytes.Buffer 35 | if data == nil { 36 | buffer = new(bytes.Buffer) 37 | } else { 38 | buffer = bytes.NewBuffer(data) 39 | } 40 | return &BufferX{ 41 | Buffer: buffer, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /command/command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import "github.com/loki-os/go-ethernet-ip/types" 4 | 5 | type Command types.UInt 6 | 7 | const ( 8 | NOP Command = 0x00 9 | ListServices Command = 0x04 10 | ListIdentity Command = 0x63 11 | ListInterfaces Command = 0x64 12 | RegisterSession Command = 0x65 13 | UnRegisterSession Command = 0x66 14 | SendRRData Command = 0x6F 15 | SendUnitData Command = 0x70 16 | IndicateStatus Command = 0x72 17 | Cancel Command = 0x73 18 | ) 19 | 20 | var commandMap = map[Command]string{ 21 | NOP: "NOP", 22 | ListServices: "ListServices", 23 | ListIdentity: "ListIdentity", 24 | ListInterfaces: "ListInterfaces", 25 | RegisterSession: "RegisterSession", 26 | UnRegisterSession: "UnRegisterSession", 27 | SendRRData: "SendRRData", 28 | SendUnitData: "SendUnitData", 29 | IndicateStatus: "IndicateStatus", 30 | Cancel: "Cancel", 31 | } 32 | 33 | func CheckValid(command Command) bool { 34 | _, ok := commandMap[command] 35 | return ok 36 | } 37 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package go_ethernet_ip 2 | 3 | import "github.com/loki-os/go-ethernet-ip/types" 4 | 5 | type Config struct { 6 | TCPPort uint16 7 | UDPPort uint16 8 | Slot uint8 9 | TimeTick types.USInt 10 | TimeTickOut types.USInt 11 | } 12 | 13 | func DefaultConfig() *Config { 14 | cfg := &Config{} 15 | cfg.TCPPort = 0xAF12 16 | cfg.UDPPort = 0xAF12 17 | cfg.Slot = 0 18 | cfg.TimeTick = types.USInt(3) 19 | cfg.TimeTickOut = types.USInt(250) 20 | return cfg 21 | } 22 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package go_ethernet_ip 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/types" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func contextGenerator() types.ULInt { 10 | time.Sleep(time.Nanosecond) 11 | rand.Seed(time.Now().UnixNano()) 12 | return types.ULInt(rand.Int63()) 13 | } 14 | -------------------------------------------------------------------------------- /example.go: -------------------------------------------------------------------------------- 1 | package go_ethernet_ip 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | ) 7 | 8 | func Init(ip string) { 9 | conn, err := NewTCP(ip, nil) 10 | if err != nil { 11 | // cannot resolve host 12 | log.Fatalln(err) 13 | } 14 | 15 | err = conn.Connect() 16 | if err != nil { 17 | // cannot connect to host 18 | log.Fatalln(err) 19 | } 20 | 21 | Tags, err := conn.AllTags() 22 | if err != nil { 23 | // cannot get tags 24 | log.Fatalln(err) 25 | } 26 | 27 | targetTag := Tags["tagName"] 28 | 29 | err = targetTag.Read() 30 | if err != nil { 31 | // cannot read tag 32 | log.Fatalln(err) 33 | } 34 | 35 | // IF U Need any other type of tag, you should implement it yourself.only support int32 and string. 36 | i32Result := targetTag.Int32() 37 | log.Println(i32Result) 38 | 39 | stringResult := targetTag.String() 40 | log.Println(stringResult) 41 | 42 | targetTag.SetInt32(123) 43 | err = targetTag.Write() 44 | if err != nil { 45 | // cannot write tag 46 | log.Fatalln(err) 47 | } 48 | 49 | // tag group, multiple tags sync read/write. 50 | lock := new(sync.Mutex) 51 | tagGroup := NewTagGroup(lock) 52 | targetTag1 := Tags["tagName1"] 53 | targetTag2 := Tags["tagName2"] 54 | tagGroup.Add(targetTag1) 55 | tagGroup.Add(targetTag2) 56 | targetTag1.SetInt32(123) 57 | targetTag2.SetString("hello") 58 | err = tagGroup.Write() 59 | if err != nil { 60 | // cannot write tag 61 | log.Fatalln(err) 62 | } 63 | 64 | // lower level api, you can use it directly. 65 | identities, err := conn.ListIdentity() 66 | log.Println(identities) 67 | 68 | interfaces, err := conn.ListInterface() 69 | log.Println(interfaces) 70 | 71 | services, err := conn.ListServices() 72 | log.Println(services) 73 | 74 | // open connection forward, but it disconnected after few times. 75 | // if you need this future you need to fix it. 76 | // actually, I don't have abplc device to test after business done. 77 | conn.ForwardOpen() 78 | var tag = new(Tag) 79 | conn.InitializeTag("OP.UDT_Alarm.DINT_065_096", tag) 80 | log.Println("Name: ", tag.Name()) 81 | log.Println(tag.GetValue()) 82 | 83 | tag = new(Tag) 84 | conn.InitializeTag("wowtag", tag) 85 | log.Println("Name: ", tag.Name()) 86 | log.Println(tag.GetValue()) 87 | 88 | tag = new(Tag) 89 | conn.InitializeTag("wotag[0]", tag) 90 | log.Println("Name: ", tag.Name()) 91 | log.Println(tag.GetValue()) 92 | 93 | tag = new(Tag) 94 | conn.InitializeTag("wotag[1]", tag) 95 | log.Println("Name: ", tag.Name()) 96 | log.Println(tag.GetValue()) 97 | 98 | tag = new(Tag) 99 | conn.InitializeTag("OP_Format[0].REAL_Performance[0]", tag) 100 | log.Println("Name: ", tag.Name()) 101 | //log.Println("Type: ", tag.Type) 102 | log.Println(tag.GetValue()) 103 | 104 | tag = new(Tag) 105 | conn.InitializeTag("wwwtag[1,0,1]", tag) 106 | log.Println("Name: ", tag.Name()) 107 | log.Println(tag.GetValue()) 108 | 109 | tag = new(Tag) 110 | conn.InitializeTag("stringtag", tag) 111 | log.Println("Name: ", tag.Name()) 112 | log.Println(tag.GetValue()) 113 | } 114 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/loki-os/go-ethernet-ip 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /messages/listIdentity/decode.go: -------------------------------------------------------------------------------- 1 | package listIdentity 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/bufferx" 5 | "github.com/loki-os/go-ethernet-ip/messages/packet" 6 | "github.com/loki-os/go-ethernet-ip/types" 7 | ) 8 | 9 | type ListIdentityItem struct { 10 | ItemTypeCode types.UInt 11 | ItemLength types.UInt 12 | EncapsulationProtocolVersion types.UInt 13 | SinFamily types.Int 14 | SinPort types.UInt 15 | SinAddr types.UDInt 16 | SinZero types.ULInt 17 | VendorID types.UInt 18 | DeviceType types.UInt 19 | ProductCode types.UInt 20 | Major types.USInt 21 | Minor types.USInt 22 | Status types.Word 23 | SerialNumber types.UDInt 24 | NameLength types.USInt 25 | ProductName []byte 26 | State types.USInt 27 | } 28 | 29 | type ListIdentity struct { 30 | ItemCount types.UInt 31 | Items []ListIdentityItem 32 | } 33 | 34 | func Decode(packet *packet.Packet) (*ListIdentity, error) { 35 | result := new(ListIdentity) 36 | io := bufferx.New(packet.SpecificData) 37 | io.RL(&result.ItemCount) 38 | 39 | for i := types.UInt(0); i < result.ItemCount; i++ { 40 | item := &ListIdentityItem{} 41 | io.RL(&item.ItemTypeCode) 42 | io.RL(&item.ItemLength) 43 | io.RL(&item.EncapsulationProtocolVersion) 44 | io.RB(&item.SinFamily) 45 | io.RB(&item.SinPort) 46 | io.RB(&item.SinAddr) 47 | io.RB(&item.SinZero) 48 | io.RL(&item.VendorID) 49 | io.RL(&item.DeviceType) 50 | io.RL(&item.ProductCode) 51 | io.RL(&item.Major) 52 | io.RL(&item.Minor) 53 | io.RL(&item.Status) 54 | io.RL(&item.SerialNumber) 55 | io.RL(&item.NameLength) 56 | item.ProductName = make([]byte, item.NameLength) 57 | io.RL(&item.ProductName) 58 | io.RL(&item.State) 59 | result.Items = append(result.Items, *item) 60 | } 61 | 62 | return result, nil 63 | } 64 | -------------------------------------------------------------------------------- /messages/listIdentity/new.go: -------------------------------------------------------------------------------- 1 | package listIdentity 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/command" 5 | "github.com/loki-os/go-ethernet-ip/messages/packet" 6 | "github.com/loki-os/go-ethernet-ip/types" 7 | ) 8 | 9 | func New(context types.ULInt) (*packet.Packet, error) { 10 | return &packet.Packet{ 11 | Header: packet.Header{ 12 | Command: command.ListIdentity, 13 | Length: 0, 14 | SessionHandle: 0, 15 | Status: 0, 16 | SenderContext: context, 17 | Options: 0, 18 | }, 19 | SpecificData: nil, 20 | }, nil 21 | } 22 | -------------------------------------------------------------------------------- /messages/listInterface/decode.go: -------------------------------------------------------------------------------- 1 | package listInterface 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/bufferx" 5 | "github.com/loki-os/go-ethernet-ip/messages/packet" 6 | "github.com/loki-os/go-ethernet-ip/types" 7 | ) 8 | 9 | type ListInterfaceItem struct { 10 | ItemTypeCode types.UInt 11 | ItemLength types.UInt 12 | ItemData []byte 13 | } 14 | 15 | type ListInterface struct { 16 | ItemCount types.UInt 17 | Items []ListInterfaceItem 18 | } 19 | 20 | func Decode(packet *packet.Packet) (*ListInterface, error) { 21 | result := new(ListInterface) 22 | io := bufferx.New(packet.SpecificData) 23 | io.RL(&result.ItemCount) 24 | 25 | for i := types.UInt(0); i < result.ItemCount; i++ { 26 | item := &ListInterfaceItem{} 27 | io.RL(&item.ItemTypeCode) 28 | io.RL(&item.ItemLength) 29 | item.ItemData = make([]byte, item.ItemLength) 30 | io.RL(&item.ItemData) 31 | result.Items = append(result.Items, *item) 32 | } 33 | 34 | return result, nil 35 | } 36 | -------------------------------------------------------------------------------- /messages/listInterface/new.go: -------------------------------------------------------------------------------- 1 | package listInterface 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/command" 5 | "github.com/loki-os/go-ethernet-ip/messages/packet" 6 | "github.com/loki-os/go-ethernet-ip/types" 7 | ) 8 | 9 | func New(context types.ULInt) (*packet.Packet, error) { 10 | return &packet.Packet{ 11 | Header: packet.Header{ 12 | Command: command.ListInterfaces, 13 | Length: 0, 14 | SessionHandle: 0, 15 | Status: 0, 16 | SenderContext: context, 17 | Options: 0, 18 | }, 19 | SpecificData: nil, 20 | }, nil 21 | } 22 | -------------------------------------------------------------------------------- /messages/listServices/decode.go: -------------------------------------------------------------------------------- 1 | package listServices 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/bufferx" 5 | "github.com/loki-os/go-ethernet-ip/messages/packet" 6 | "github.com/loki-os/go-ethernet-ip/types" 7 | ) 8 | 9 | type ListServicesItem struct { 10 | ItemTypeCode types.UInt 11 | ItemLength types.UInt 12 | Version types.UInt 13 | Flags types.UInt 14 | Name []byte 15 | } 16 | 17 | type ListServices struct { 18 | ItemCount types.UInt 19 | Items []ListServicesItem 20 | } 21 | 22 | func Decode(packet *packet.Packet) (*ListServices, error) { 23 | result := new(ListServices) 24 | io := bufferx.New(packet.SpecificData) 25 | io.RL(&result.ItemCount) 26 | 27 | for i := types.UInt(0); i < result.ItemCount; i++ { 28 | item := &ListServicesItem{} 29 | io.RL(&item.ItemTypeCode) 30 | io.RL(&item.ItemLength) 31 | io.RL(&item.Version) 32 | io.RL(&item.Flags) 33 | item.Name = make([]byte, 16) 34 | io.RL(&item.Name) 35 | result.Items = append(result.Items, *item) 36 | } 37 | 38 | return result, nil 39 | } 40 | -------------------------------------------------------------------------------- /messages/listServices/new.go: -------------------------------------------------------------------------------- 1 | package listServices 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/command" 5 | "github.com/loki-os/go-ethernet-ip/messages/packet" 6 | "github.com/loki-os/go-ethernet-ip/types" 7 | ) 8 | 9 | func New(context types.ULInt) (*packet.Packet, error) { 10 | return &packet.Packet{ 11 | Header: packet.Header{ 12 | Command: command.ListServices, 13 | Length: 0, 14 | SessionHandle: 0, 15 | Status: 0, 16 | SenderContext: context, 17 | Options: 0, 18 | }, 19 | SpecificData: nil, 20 | }, nil 21 | } 22 | -------------------------------------------------------------------------------- /messages/nop/new.go: -------------------------------------------------------------------------------- 1 | package nop 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/command" 5 | "github.com/loki-os/go-ethernet-ip/messages/packet" 6 | "github.com/loki-os/go-ethernet-ip/types" 7 | ) 8 | 9 | func New(data []byte) (*packet.Packet, error) { 10 | return &packet.Packet{ 11 | Header: packet.Header{ 12 | Command: command.NOP, 13 | Length: types.UInt(len(data)), 14 | SessionHandle: 0, 15 | Status: 0, 16 | SenderContext: 0, 17 | Options: 0, 18 | }, 19 | SpecificData: data, 20 | }, nil 21 | } 22 | -------------------------------------------------------------------------------- /messages/packet/cmm.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/bufferx" 5 | "github.com/loki-os/go-ethernet-ip/types" 6 | ) 7 | 8 | func NewCMM(connectionID types.UDInt, sequenceNumber types.UInt, mr *MessageRouterRequest) *CommonPacketFormat { 9 | io := bufferx.New(nil) 10 | io.WL(connectionID) 11 | 12 | io1 := bufferx.New(nil) 13 | io1.WL(sequenceNumber) 14 | io1.WL(mr.Encode()) 15 | 16 | cpf := NewCommonPacketFormat([]CommonPacketFormatItem{ 17 | { 18 | TypeID: ItemIDConnectionBased, 19 | Data: io.Bytes(), 20 | }, 21 | { 22 | TypeID: ItemIDConnectedTransportPacket, 23 | Data: io1.Bytes(), 24 | }, 25 | }) 26 | 27 | return cpf 28 | } 29 | -------------------------------------------------------------------------------- /messages/packet/commonPacketFormat.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/bufferx" 5 | "github.com/loki-os/go-ethernet-ip/types" 6 | ) 7 | 8 | type ItemID types.UInt 9 | 10 | const ( 11 | ItemIDUCMM ItemID = 0x0000 12 | ItemIDListIdentityResponse ItemID = 0x000C 13 | ItemIDConnectionBased ItemID = 0x00A1 14 | ItemIDConnectedTransportPacket ItemID = 0x00B1 15 | ItemIDUnconnectedMessage ItemID = 0x00B2 16 | ItemIDListServicesResponse ItemID = 0x0100 17 | ItemIDSockaddrInfoO2T ItemID = 0x8000 18 | ItemIDSockaddrInfoT2O ItemID = 0x8001 19 | ItemIDSequencedAddressItem ItemID = 0x8002 20 | ) 21 | 22 | type CommonPacketFormatItem struct { 23 | TypeID ItemID 24 | Length types.UInt 25 | Data []byte 26 | } 27 | 28 | func (i *CommonPacketFormatItem) Encode() []byte { 29 | if i.Length == 0 { 30 | i.Length = types.UInt(len(i.Data)) 31 | } 32 | io := bufferx.New(nil) 33 | io.WL(i.TypeID) 34 | io.WL(i.Length) 35 | io.WL(i.Data) 36 | return io.Bytes() 37 | } 38 | 39 | func (i *CommonPacketFormatItem) Decode(io *bufferx.BufferX) { 40 | io.RL(&i.TypeID) 41 | io.RL(&i.Length) 42 | i.Data = make([]byte, i.Length) 43 | io.RL(&i.Data) 44 | } 45 | 46 | type CommonPacketFormat struct { 47 | ItemCount types.UInt 48 | Items []CommonPacketFormatItem 49 | } 50 | 51 | func (c *CommonPacketFormat) Encode() []byte { 52 | if c.ItemCount == 0 { 53 | c.ItemCount = types.UInt(len(c.Items)) 54 | } 55 | 56 | io := bufferx.New(nil) 57 | io.WL(c.ItemCount) 58 | 59 | for _, item := range c.Items { 60 | io.WL(item.Encode()) 61 | } 62 | 63 | return io.Bytes() 64 | } 65 | 66 | func (c *CommonPacketFormat) Decode(io *bufferx.BufferX) { 67 | io.RL(&c.ItemCount) 68 | 69 | for i := types.UInt(0); i < c.ItemCount; i++ { 70 | item := &CommonPacketFormatItem{} 71 | item.Decode(io) 72 | c.Items = append(c.Items, *item) 73 | } 74 | } 75 | 76 | func NewCommonPacketFormat(items []CommonPacketFormatItem) *CommonPacketFormat { 77 | return &CommonPacketFormat{ 78 | ItemCount: types.UInt(len(items)), 79 | Items: items, 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /messages/packet/data.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/bufferx" 5 | "github.com/loki-os/go-ethernet-ip/types" 6 | ) 7 | 8 | type SpecificData struct { 9 | InterfaceHandle types.UDInt 10 | TimeOut types.UInt 11 | Packet *CommonPacketFormat 12 | } 13 | 14 | func (r *SpecificData) Encode() []byte { 15 | io := bufferx.New(nil) 16 | io.WL(r.InterfaceHandle) 17 | io.WL(r.TimeOut) 18 | io.WL(r.Packet.Encode()) 19 | return io.Bytes() 20 | } 21 | 22 | func (r *SpecificData) Decode(data []byte) { 23 | io := bufferx.New(data) 24 | io.RL(&r.InterfaceHandle) 25 | io.RL(&r.TimeOut) 26 | r.Packet = new(CommonPacketFormat) 27 | r.Packet.Decode(io) 28 | } 29 | -------------------------------------------------------------------------------- /messages/packet/messageRouter.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/bufferx" 5 | "github.com/loki-os/go-ethernet-ip/types" 6 | "github.com/loki-os/go-ethernet-ip/utils" 7 | ) 8 | 9 | type MessageRouterRequest struct { 10 | Service types.USInt 11 | RequestPathSize types.USInt 12 | RequestPath []byte 13 | RequestData []byte 14 | } 15 | 16 | func (m *MessageRouterRequest) Encode() []byte { 17 | if m.RequestPathSize == 0 { 18 | m.RequestPathSize = utils.Len(m.RequestPath) 19 | } 20 | 21 | io := bufferx.New(nil) 22 | io.WL(m.Service) 23 | io.WL(m.RequestPathSize) 24 | io.WL(m.RequestPath) 25 | io.WL(m.RequestData) 26 | 27 | return io.Bytes() 28 | } 29 | 30 | func NewMessageRouter(service types.USInt, path []byte, data []byte) *MessageRouterRequest { 31 | return &MessageRouterRequest{ 32 | Service: service, 33 | RequestPathSize: utils.Len(path), 34 | RequestPath: path, 35 | RequestData: data, 36 | } 37 | } 38 | 39 | type MessageRouterResponse struct { 40 | ReplyService types.USInt 41 | Reserved types.USInt 42 | GeneralStatus types.USInt 43 | SizeOfAdditionalStatus types.USInt 44 | AdditionalStatus []byte 45 | ResponseData []byte 46 | } 47 | 48 | func (m *MessageRouterResponse) Decode(data []byte) { 49 | io := bufferx.New(data) 50 | io.RL(&m.ReplyService) 51 | io.RL(&m.Reserved) 52 | io.RL(&m.GeneralStatus) 53 | io.RL(&m.SizeOfAdditionalStatus) 54 | m.AdditionalStatus = make([]byte, m.SizeOfAdditionalStatus*2) 55 | io.RL(&m.AdditionalStatus) 56 | m.ResponseData = make([]byte, io.Len()) 57 | io.RL(&m.ResponseData) 58 | } 59 | -------------------------------------------------------------------------------- /messages/packet/packet.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "errors" 5 | "github.com/loki-os/go-ethernet-ip/bufferx" 6 | "github.com/loki-os/go-ethernet-ip/command" 7 | "github.com/loki-os/go-ethernet-ip/types" 8 | ) 9 | 10 | type Header struct { 11 | Command command.Command 12 | Length types.UInt 13 | SessionHandle types.UDInt 14 | Status types.UDInt 15 | SenderContext types.ULInt 16 | Options types.UDInt 17 | } 18 | 19 | type Packet struct { 20 | Header 21 | SpecificData []byte 22 | } 23 | 24 | func (p *Packet) Encode() ([]byte, error) { 25 | if p.Length > 65511 { 26 | return nil, errors.New("specific data over length 65511") 27 | } 28 | 29 | if !command.CheckValid(p.Command) { 30 | return nil, errors.New("command not supported") 31 | } 32 | 33 | buffer := bufferx.New(nil) 34 | buffer.WL(p.Header) 35 | buffer.WL(p.SpecificData) 36 | if buffer.Error() != nil { 37 | return nil, buffer.Error() 38 | } 39 | 40 | return buffer.Bytes(), nil 41 | } 42 | -------------------------------------------------------------------------------- /messages/packet/services.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import "github.com/loki-os/go-ethernet-ip/types" 4 | 5 | const ( 6 | ServiceGetInstanceAttributeList types.USInt = 0x55 7 | ServiceGetAttributes types.USInt = 0x03 8 | ServiceGetAttributeAll types.USInt = 0x01 9 | ServiceGetAttributeSingle types.USInt = 0x0e 10 | ServiceReset types.USInt = 0x05 11 | ServiceStart types.USInt = 0x06 12 | ServiceStop types.USInt = 0x07 13 | ServiceCreate types.USInt = 0x08 14 | ServiceDelete types.USInt = 0x09 15 | ServiceMultipleServicePacket types.USInt = 0x0a 16 | ServiceApplyAttributes types.USInt = 0x0d 17 | ServiceSetAttributeSingle types.USInt = 0x10 18 | ServiceFindNext types.USInt = 0x11 19 | ServiceReadTag types.USInt = 0x4c 20 | ServiceWriteTag types.USInt = 0x4d 21 | ServiceReadTagFragmented types.USInt = 0x52 22 | ServiceWriteTagFragmented types.USInt = 0x53 23 | ServiceReadModifyWriteTag types.USInt = 0x4e 24 | ServiceForwardOpen types.USInt = 0x54 25 | ServiceForwardOpenLarge types.USInt = 0x5b 26 | ServiceForwardClose types.USInt = 0x4e 27 | ) 28 | -------------------------------------------------------------------------------- /messages/packet/ucmm.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/bufferx" 5 | "github.com/loki-os/go-ethernet-ip/path" 6 | "github.com/loki-os/go-ethernet-ip/types" 7 | "github.com/loki-os/go-ethernet-ip/utils" 8 | ) 9 | 10 | type UnConnectedSend struct { 11 | TimeTick types.USInt 12 | TimeOutTicks types.USInt 13 | MessageRequestSize types.UInt 14 | MessageRequest *MessageRouterRequest 15 | Pad types.USInt 16 | RouterPathSize types.USInt 17 | Reserved types.USInt 18 | RouterPath []byte 19 | } 20 | 21 | func (u *UnConnectedSend) Encode() []byte { 22 | mr := u.MessageRequest.Encode() 23 | 24 | io := bufferx.New(nil) 25 | 26 | io.WL(u.TimeTick) 27 | io.WL(u.TimeOutTicks) 28 | io.WL(types.UInt(len(mr))) 29 | io.WL(mr) 30 | 31 | if len(mr)%2 == 1 { 32 | io.WL(uint8(0)) 33 | } 34 | 35 | io.WL(utils.Len(u.RouterPath)) 36 | io.WL(uint8(0)) 37 | io.WL(u.RouterPath) 38 | 39 | return io.Bytes() 40 | } 41 | 42 | func UnConnected(slot uint8, timeTick types.USInt, timeOutTicks types.USInt, mr *MessageRouterRequest) *MessageRouterRequest { 43 | ucs := UnConnectedSend{ 44 | TimeTick: timeTick, 45 | TimeOutTicks: timeOutTicks, 46 | MessageRequest: mr, 47 | RouterPath: path.PortBuild([]byte{slot}, 1, true), 48 | } 49 | 50 | return NewMessageRouter(0x52, Paths( 51 | path.LogicalBuild(path.LogicalTypeClassID, 06, true), 52 | path.LogicalBuild(path.LogicalTypeInstanceID, 01, true), 53 | ), ucs.Encode()) 54 | } 55 | 56 | func NewUCMM(mr *MessageRouterRequest) *CommonPacketFormat { 57 | cpf := NewCommonPacketFormat([]CommonPacketFormatItem{ 58 | { 59 | TypeID: ItemIDUCMM, 60 | Data: nil, 61 | }, 62 | { 63 | TypeID: ItemIDUnconnectedMessage, 64 | Data: mr.Encode(), 65 | }, 66 | }) 67 | 68 | return cpf 69 | } 70 | -------------------------------------------------------------------------------- /messages/packet/utils.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/bufferx" 5 | ) 6 | 7 | func Paths(arg ...[]byte) []byte { 8 | io := bufferx.New(nil) 9 | for i := 0; i < len(arg); i++ { 10 | io.WL(arg[i]) 11 | } 12 | return io.Bytes() 13 | } 14 | -------------------------------------------------------------------------------- /messages/registerSession/new.go: -------------------------------------------------------------------------------- 1 | package registerSession 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/bufferx" 5 | "github.com/loki-os/go-ethernet-ip/command" 6 | "github.com/loki-os/go-ethernet-ip/messages/packet" 7 | "github.com/loki-os/go-ethernet-ip/types" 8 | ) 9 | 10 | type specificData struct { 11 | ProtocolVersion types.UInt 12 | OptionsFlags types.UInt 13 | } 14 | 15 | func (s *specificData) Encode() ([]byte, error) { 16 | buffer := bufferx.New(nil) 17 | buffer.WL(s.ProtocolVersion) 18 | buffer.WL(s.OptionsFlags) 19 | if buffer.Error() != nil { 20 | return nil, buffer.Error() 21 | } 22 | return buffer.Bytes(), nil 23 | } 24 | 25 | func New(context types.ULInt) (*packet.Packet, error) { 26 | specificData := specificData{ 27 | ProtocolVersion: 1, 28 | OptionsFlags: 0, 29 | } 30 | specificDataBytes, err := specificData.Encode() 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | return &packet.Packet{ 36 | Header: packet.Header{ 37 | Command: command.RegisterSession, 38 | Length: 4, 39 | SessionHandle: 0, 40 | Status: 0, 41 | SenderContext: context, 42 | Options: 0, 43 | }, 44 | SpecificData: specificDataBytes, 45 | }, nil 46 | } 47 | -------------------------------------------------------------------------------- /messages/sendRRData/decode.go: -------------------------------------------------------------------------------- 1 | package sendRRData 2 | 3 | import "github.com/loki-os/go-ethernet-ip/messages/packet" 4 | 5 | func Decode(_packet *packet.Packet) (*packet.SpecificData, error) { 6 | result := new(packet.SpecificData) 7 | result.Decode(_packet.SpecificData) 8 | return result, nil 9 | } 10 | -------------------------------------------------------------------------------- /messages/sendRRData/new.go: -------------------------------------------------------------------------------- 1 | package sendRRData 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/command" 5 | "github.com/loki-os/go-ethernet-ip/messages/packet" 6 | "github.com/loki-os/go-ethernet-ip/types" 7 | ) 8 | 9 | func New(session types.UDInt, context types.ULInt, cpf *packet.CommonPacketFormat, timeout types.UInt) (*packet.Packet, error) { 10 | specificData := &packet.SpecificData{ 11 | InterfaceHandle: 0, 12 | TimeOut: timeout, 13 | Packet: cpf, 14 | } 15 | specificDataBytes := specificData.Encode() 16 | return &packet.Packet{ 17 | Header: packet.Header{ 18 | Command: command.SendRRData, 19 | Length: types.UInt(len(specificDataBytes)), 20 | SessionHandle: session, 21 | Status: 0, 22 | SenderContext: context, 23 | Options: 0, 24 | }, 25 | SpecificData: specificDataBytes, 26 | }, nil 27 | } 28 | -------------------------------------------------------------------------------- /messages/sendUnitData/decode.go: -------------------------------------------------------------------------------- 1 | package sendUnitData 2 | 3 | import "github.com/loki-os/go-ethernet-ip/messages/packet" 4 | 5 | func Decode(_packet *packet.Packet) (*packet.SpecificData, error) { 6 | result := new(packet.SpecificData) 7 | result.Decode(_packet.SpecificData) 8 | return result, nil 9 | } 10 | -------------------------------------------------------------------------------- /messages/sendUnitData/new.go: -------------------------------------------------------------------------------- 1 | package sendUnitData 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/command" 5 | "github.com/loki-os/go-ethernet-ip/messages/packet" 6 | "github.com/loki-os/go-ethernet-ip/types" 7 | ) 8 | 9 | func New(session types.UDInt, context types.ULInt, cpf *packet.CommonPacketFormat) (*packet.Packet, error) { 10 | specificData := &packet.SpecificData{ 11 | InterfaceHandle: 0, 12 | TimeOut: 0, 13 | Packet: cpf, 14 | } 15 | specificDataBytes := specificData.Encode() 16 | return &packet.Packet{ 17 | Header: packet.Header{ 18 | Command: command.SendUnitData, 19 | Length: types.UInt(len(specificDataBytes)), 20 | SessionHandle: session, 21 | Status: 0, 22 | SenderContext: context, 23 | Options: 0, 24 | }, 25 | SpecificData: specificDataBytes, 26 | }, nil 27 | } 28 | -------------------------------------------------------------------------------- /messages/unRegisterSession/new.go: -------------------------------------------------------------------------------- 1 | package unRegisterSession 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/command" 5 | "github.com/loki-os/go-ethernet-ip/messages/packet" 6 | "github.com/loki-os/go-ethernet-ip/types" 7 | ) 8 | 9 | func New(session types.UDInt, context types.ULInt) (*packet.Packet, error) { 10 | return &packet.Packet{ 11 | Header: packet.Header{ 12 | Command: command.UnRegisterSession, 13 | Length: 0, 14 | SessionHandle: session, 15 | Status: 0, 16 | SenderContext: context, 17 | Options: 0, 18 | }, 19 | SpecificData: nil, 20 | }, nil 21 | } 22 | -------------------------------------------------------------------------------- /path/path.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "github.com/loki-os/go-ethernet-ip/bufferx" 5 | "github.com/loki-os/go-ethernet-ip/types" 6 | ) 7 | 8 | type SegmentType types.USInt 9 | 10 | const ( 11 | SegmentTypePort SegmentType = 0 << 5 12 | SegmentTypeLogical SegmentType = 1 << 5 13 | SegmentTypeNetwork SegmentType = 2 << 5 14 | SegmentTypeSymbolic SegmentType = 3 << 5 15 | SegmentTypeData SegmentType = 4 << 5 16 | SegmentTypeDataType1 SegmentType = 5 << 5 17 | SegmentTypeDataType2 SegmentType = 6 << 5 18 | ) 19 | 20 | func Paths(arg ...[]byte) []byte { 21 | io := bufferx.New(nil) 22 | for i := 0; i < len(arg); i++ { 23 | io.WL(arg[i]) 24 | } 25 | return io.Bytes() 26 | } 27 | 28 | type DataTypes types.USInt 29 | 30 | const ( 31 | DataTypeSimple DataTypes = 0x0 32 | DataTypeANSI DataTypes = 0x11 33 | ) 34 | 35 | type LogicalType types.USInt 36 | 37 | const ( 38 | LogicalTypeClassID LogicalType = 0 << 2 39 | LogicalTypeInstanceID LogicalType = 1 << 2 40 | LogicalTypeMemberID LogicalType = 2 << 2 41 | LogicalTypeConnPoint LogicalType = 3 << 2 42 | LogicalTypeAttributeID LogicalType = 4 << 2 43 | LogicalTypeSpecial LogicalType = 5 << 2 44 | LogicalTypeServiceID LogicalType = 6 << 2 45 | ) 46 | 47 | func DataBuild(tp DataTypes, data []byte, padded bool) []byte { 48 | io := bufferx.New(nil) 49 | 50 | firstByte := uint8(SegmentTypeData) | uint8(tp) 51 | io.WL(firstByte) 52 | 53 | length := uint8(len(data)) 54 | io.WL(length) 55 | 56 | io.WL(data) 57 | 58 | if padded && io.Len()%2 == 1 { 59 | io.WL(uint8(0)) 60 | } 61 | 62 | return io.Bytes() 63 | } 64 | 65 | func LogicalBuild(tp LogicalType, address types.UDInt, padded bool) []byte { 66 | format := uint8(0) 67 | 68 | if address <= 255 { 69 | format = 0 70 | } else if address > 255 && address <= 65535 { 71 | format = 1 72 | } else { 73 | format = 2 74 | } 75 | 76 | io := bufferx.New(nil) 77 | firstByte := uint8(SegmentTypeLogical) | uint8(tp) | format 78 | io.WL(firstByte) 79 | 80 | if address > 255 && address <= 65535 && padded { 81 | io.WL(uint8(0)) 82 | } 83 | 84 | if address <= 255 { 85 | io.WL(uint8(address)) 86 | } else if address > 255 && address <= 65535 { 87 | io.WL(uint16(address)) 88 | } else { 89 | io.WL(address) 90 | } 91 | 92 | return io.Bytes() 93 | } 94 | 95 | func PortBuild(link []byte, portID uint16, padded bool) []byte { 96 | extendedLinkTag := len(link) > 1 97 | extendedPortTag := !(portID < 15) 98 | 99 | io := bufferx.New(nil) 100 | 101 | firstByte := uint8(SegmentTypePort) 102 | if extendedLinkTag { 103 | firstByte = firstByte | 0x10 104 | } 105 | 106 | if !extendedPortTag { 107 | firstByte = firstByte | uint8(portID) 108 | } else { 109 | firstByte = firstByte | 0xf 110 | } 111 | 112 | io.WL(firstByte) 113 | 114 | if extendedLinkTag { 115 | io.WL(uint8(len(link))) 116 | } 117 | 118 | if extendedPortTag { 119 | io.WL(portID) 120 | } 121 | 122 | io.WL(link) 123 | 124 | if padded && io.Len()%2 == 1 { 125 | io.WL(uint8(0)) 126 | } 127 | 128 | return io.Bytes() 129 | } 130 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | package go_ethernet_ip 2 | 3 | import ( 4 | "errors" 5 | "github.com/loki-os/go-ethernet-ip/bufferx" 6 | "github.com/loki-os/go-ethernet-ip/messages/listIdentity" 7 | "github.com/loki-os/go-ethernet-ip/messages/listInterface" 8 | "github.com/loki-os/go-ethernet-ip/messages/listServices" 9 | "github.com/loki-os/go-ethernet-ip/messages/packet" 10 | "github.com/loki-os/go-ethernet-ip/messages/registerSession" 11 | "github.com/loki-os/go-ethernet-ip/messages/sendRRData" 12 | "github.com/loki-os/go-ethernet-ip/messages/sendUnitData" 13 | "github.com/loki-os/go-ethernet-ip/messages/unRegisterSession" 14 | "github.com/loki-os/go-ethernet-ip/path" 15 | "github.com/loki-os/go-ethernet-ip/types" 16 | "github.com/loki-os/go-ethernet-ip/utils" 17 | "math/rand" 18 | ) 19 | 20 | func (t *EIPTCP) request(packet *packet.Packet) (*packet.Packet, error) { 21 | t.requestLock.Lock() 22 | defer t.requestLock.Unlock() 23 | 24 | if t.tcpConn == nil { 25 | return nil, errors.New("connect first") 26 | } 27 | 28 | b, err := packet.Encode() 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | if err := t.write(b); err != nil { 34 | return nil, err 35 | } 36 | 37 | return t.read() 38 | } 39 | 40 | func (t *EIPTCP) RegisterSession() error { 41 | ctx := contextGenerator() 42 | requestPacket, err := registerSession.New(ctx) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | responsePacket, err := t.request(requestPacket) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | t.session = responsePacket.SessionHandle 53 | return nil 54 | } 55 | 56 | func (t *EIPTCP) UnRegisterSession() error { 57 | ctx := contextGenerator() 58 | requestPacket, err := unRegisterSession.New(t.session, ctx) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | _, _ = t.request(requestPacket) 64 | 65 | _ = t.tcpConn.Close() 66 | t.tcpConn = nil 67 | return nil 68 | } 69 | 70 | func (t *EIPTCP) ListInterface() (*listInterface.ListInterface, error) { 71 | ctx := contextGenerator() 72 | requestPacket, err := listInterface.New(ctx) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | responsePacket, err := t.request(requestPacket) 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | return listInterface.Decode(responsePacket) 83 | } 84 | 85 | func (t *EIPTCP) ListServices() (*listServices.ListServices, error) { 86 | ctx := contextGenerator() 87 | requestPacket, err := listServices.New(ctx) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | responsePacket, err := t.request(requestPacket) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | return listServices.Decode(responsePacket) 98 | } 99 | 100 | func (t *EIPTCP) ListIdentity() (*listIdentity.ListIdentity, error) { 101 | ctx := contextGenerator() 102 | requestPacket, err := listIdentity.New(ctx) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | responsePacket, err := t.request(requestPacket) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | return listIdentity.Decode(responsePacket) 113 | } 114 | 115 | func (t *EIPTCP) SendRRData(cpf *packet.CommonPacketFormat, timeout types.UInt) (*packet.SpecificData, error) { 116 | ctx := contextGenerator() 117 | requestPacket, err := sendRRData.New(t.session, ctx, cpf, timeout) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | responsePacket, err := t.request(requestPacket) 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | return sendRRData.Decode(responsePacket) 128 | } 129 | 130 | func (t *EIPTCP) SendUnitData(cpf *packet.CommonPacketFormat) (*packet.SpecificData, error) { 131 | ctx := contextGenerator() 132 | requestPacket, err := sendUnitData.New(t.session, ctx, cpf) 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | responsePacket, err := t.request(requestPacket) 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | spd, err := sendUnitData.Decode(responsePacket) 143 | if spd != nil { 144 | spd.Packet.Items[1].Data = spd.Packet.Items[1].Data[2:] 145 | } 146 | return spd, err 147 | } 148 | 149 | func (t *EIPTCP) Send(mr *packet.MessageRouterRequest) (*packet.SpecificData, error) { 150 | if !t.established { 151 | mr = packet.UnConnected(t.config.Slot, t.config.TimeTick, t.config.TimeTickOut, mr) 152 | } 153 | if t.established { 154 | t.seqNum += 1 155 | return t.SendUnitData(packet.NewCMM(t.connID, t.seqNum, mr)) 156 | } else { 157 | return t.SendRRData(packet.NewUCMM(mr), 10) 158 | } 159 | } 160 | 161 | func (t *EIPTCP) ForwardOpen() error { 162 | io := bufferx.New(nil) 163 | // TimePerTick 164 | io.WL(types.USInt(3)) 165 | // Timeout Ticks 166 | io.WL(types.USInt(125)) 167 | // O->T Connection ID 168 | io.WL(types.UDInt(0)) 169 | // T->O Connection ID 170 | io.WL(types.UDInt(rand.Intn(2147483647))) 171 | // Connection Serial Number 172 | io.WL(types.UInt(rand.Intn(32767))) 173 | // Originator VendorID 174 | io.WL(types.UInt(0x3333)) 175 | // Originator Serial Number 176 | io.WL(types.UDInt(0x1337)) 177 | // TimeOut Multiplier 178 | io.WL(types.UDInt(5)) 179 | // O->T RPI 180 | io.WL(types.UDInt(1000000)) 181 | // O->T Network Connection Params 182 | io.WL(types.UInt(0x43f4)) 183 | // T->O RPI 184 | io.WL(types.UDInt(1000000)) 185 | // T->O Network Connection Params 186 | io.WL(types.UInt(0x43f4)) 187 | // TransportClass_Trigger (Vol.1 - 3-4.4.3) -> Target is a Server, Application object of Transport Class 3. 188 | io.WL(types.USInt(0xA3)) 189 | 190 | portPath := packet.Paths( 191 | path.PortBuild([]byte{t.config.Slot}, 1, true), 192 | path.LogicalBuild(path.LogicalTypeClassID, 0x02, true), 193 | path.LogicalBuild(path.LogicalTypeInstanceID, 0x01, true), 194 | ) 195 | io.WL(utils.Len(portPath)) 196 | io.WL(portPath) 197 | 198 | mr := packet.NewMessageRouter(packet.ServiceForwardOpen, packet.Paths( 199 | path.LogicalBuild(path.LogicalTypeClassID, 0x06, true), 200 | path.LogicalBuild(path.LogicalTypeInstanceID, 0x01, true), 201 | ), io.Bytes()) 202 | 203 | sd, err := t.SendRRData(packet.NewUCMM(mr), 10) 204 | if err != nil { 205 | return err 206 | } 207 | 208 | rmr := &packet.MessageRouterResponse{} 209 | rmr.Decode(sd.Packet.Items[1].Data) 210 | io1 := bufferx.New(rmr.ResponseData) 211 | io1.RL(&t.connID) 212 | t.established = true 213 | 214 | return nil 215 | } 216 | 217 | func (t *EIPTCP) ForwardOpenLarge() error { 218 | io := bufferx.New(nil) 219 | // TimePerTick 220 | io.WL(types.USInt(3)) 221 | // Timeout Ticks 222 | io.WL(types.USInt(125)) 223 | // O->T Connection ID 224 | io.WL(types.UDInt(0)) 225 | // T->O Connection ID 226 | io.WL(types.UDInt(rand.Intn(2147483647))) 227 | // Connection Serial Number 228 | io.WL(types.UInt(rand.Intn(32767))) 229 | // Originator VendorID 230 | io.WL(types.UInt(0x3333)) 231 | // Originator Serial Number 232 | io.WL(types.UDInt(0x1337)) 233 | // TimeOut Multiplier 234 | io.WL(types.UDInt(5)) 235 | // O->T RPI 236 | io.WL(types.UDInt(1000000)) 237 | // O->T Network Connection Params 238 | io.WL(types.UDInt(0x42000FA2)) 239 | // T->O RPI 240 | io.WL(types.UDInt(1000000)) 241 | // T->O Network Connection Params 242 | io.WL(types.UDInt(0x42000FA2)) 243 | // TransportClass_Trigger (Vol.1 - 3-4.4.3) -> Target is a Server, Application object of Transport Class 3. 244 | io.WL(types.USInt(0xA3)) 245 | 246 | portPath := packet.Paths( 247 | path.PortBuild([]byte{t.config.Slot}, 1, true), 248 | path.LogicalBuild(path.LogicalTypeClassID, 0x02, true), 249 | path.LogicalBuild(path.LogicalTypeInstanceID, 0x01, true), 250 | ) 251 | io.WL(utils.Len(portPath)) 252 | io.WL(portPath) 253 | 254 | mr := packet.NewMessageRouter(packet.ServiceForwardOpenLarge, packet.Paths( 255 | path.LogicalBuild(path.LogicalTypeClassID, 0x06, true), 256 | path.LogicalBuild(path.LogicalTypeInstanceID, 0x01, true), 257 | ), io.Bytes()) 258 | 259 | sd, err := t.SendRRData(packet.NewUCMM(mr), 10) 260 | if err != nil { 261 | return err 262 | } 263 | 264 | rmr := &packet.MessageRouterResponse{} 265 | rmr.Decode(sd.Packet.Items[1].Data) 266 | io1 := bufferx.New(rmr.ResponseData) 267 | io1.RL(&t.connID) 268 | t.established = true 269 | 270 | return nil 271 | } 272 | -------------------------------------------------------------------------------- /tag.go: -------------------------------------------------------------------------------- 1 | package go_ethernet_ip 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "errors" 8 | "fmt" 9 | "github.com/loki-os/go-ethernet-ip/bufferx" 10 | "github.com/loki-os/go-ethernet-ip/messages/packet" 11 | "github.com/loki-os/go-ethernet-ip/path" 12 | "github.com/loki-os/go-ethernet-ip/types" 13 | "math" 14 | "strconv" 15 | "strings" 16 | "sync" 17 | "unicode" 18 | ) 19 | 20 | const ( 21 | NULL types.UInt = 0x00 22 | BOOL types.UInt = 0xc1 23 | SINT types.UInt = 0xc2 24 | INT types.UInt = 0xc3 25 | DINT types.UInt = 0xc4 26 | LINT types.UInt = 0xc5 27 | USINT types.UInt = 0xc6 28 | UINT types.UInt = 0xc7 29 | UDINT types.UInt = 0xc8 30 | ULINT types.UInt = 0xc9 31 | REAL types.UInt = 0xca 32 | LREAL types.UInt = 0xcb 33 | STRING types.UInt = 0xfce 34 | STRING2 types.UInt = 0x8fce 35 | ) 36 | 37 | var TypeMap = map[types.UInt]string{ 38 | NULL: "NULL", 39 | BOOL: "BOOL", 40 | SINT: "SINT", 41 | INT: "INT", 42 | DINT: "DINT", 43 | LINT: "LINT", 44 | USINT: "USINT", 45 | UINT: "UINT", 46 | UDINT: "UDINT", 47 | ULINT: "ULINT", 48 | REAL: "REAL", 49 | LREAL: "LREAL", 50 | STRING: "STRING", 51 | STRING2: "STRING", 52 | } 53 | 54 | type Tag struct { 55 | Lock *sync.Mutex 56 | TCP *EIPTCP 57 | 58 | instanceID types.UDInt 59 | nameLen types.UInt 60 | name []byte 61 | Type types.UInt 62 | dim1Len types.UDInt 63 | dim2Len types.UDInt 64 | dim3Len types.UDInt 65 | changed bool 66 | 67 | value []byte 68 | wValue []byte 69 | Onchange func() 70 | } 71 | 72 | func (t *Tag) Read() error { 73 | t.Lock.Lock() 74 | defer t.Lock.Unlock() 75 | res, err := t.TCP.Send(t.readRequest()) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | mrres := new(packet.MessageRouterResponse) 81 | mrres.Decode(res.Packet.Items[1].Data) 82 | 83 | t.readParser(mrres, nil) 84 | return nil 85 | } 86 | 87 | func (t *Tag) readRequest() *packet.MessageRouterRequest { 88 | //create new io buffer for request data 89 | io := bufferx.New(nil) 90 | 91 | //Define number of elements to read 92 | if length := t.count(); length > 0{ 93 | io.WL(length) //Read all indices of tag 94 | } else { 95 | io.WL(types.UInt(1)) //read a single element if t.dimXlen are not defined 96 | } 97 | //split tag name into array of segment names and check if 'ANSI Extended Symbolic Segments' need to be used 98 | if inPaths := strings.Split(string(t.name), "."); t.instanceID > 0 && len(inPaths) < 2 { 99 | //Only logical segments need to be used. 100 | mr := packet.NewMessageRouter(packet.ServiceReadTag, packet.Paths( 101 | path.LogicalBuild(path.LogicalTypeClassID, 0x6B, true), 102 | path.LogicalBuild(path.LogicalTypeInstanceID, t.instanceID, true), 103 | ), io.Bytes()) 104 | return mr 105 | } else { 106 | //'ANSI Extended Symbolic Segments' need to be used 107 | var paths []byte //initialize the 'Request Path' 108 | var iinit int //initialize the initial i for the 'Request Path' loop 109 | if t.instanceID == 0 { //check if first segment can be replaced by logical segments 110 | iinit = 0 //first segment is not able to be logical 111 | } else { 112 | iinit = 1//first segment is able to be logical. Create first segments 113 | paths = packet.Paths(paths, path.LogicalBuild(path.LogicalTypeClassID, 0x6B, true)) 114 | paths = packet.Paths(paths, path.LogicalBuild(path.LogicalTypeInstanceID,t.instanceID,true)) 115 | } 116 | //Request Path loop: 117 | for i:=iinit;i"), true), 138 | path.DataBuild(path.DataTypeANSI, []byte(""), true), 139 | path.DataBuild(path.DataTypeANSI, []byte(""), true), 140 | )*/ 141 | 142 | mr := packet.NewMessageRouter(packet.ServiceReadTag, paths, io.Bytes()) 143 | return mr 144 | } 145 | } 146 | 147 | func (t *Tag) readParser(mr *packet.MessageRouterResponse, cb func(func())) error { 148 | if mr.GeneralStatus > 0 { 149 | errorByte := make([]byte , 1) 150 | errorByte = append(errorByte, byte(mr.GeneralStatus)) 151 | return errors.New("error code: " + hex.EncodeToString(errorByte)) 152 | } 153 | 154 | io := bufferx.New(mr.ResponseData) 155 | 156 | //Read the tag type 157 | ttype := types.UInt(0) 158 | io.RL(&ttype) 159 | 160 | //If the tag type is actually a structure handle then read it. 161 | if ttype == 0x2a0 { //per the documentation 0x2a0 means tag is not atomic! 162 | io.RL(&ttype) 163 | } 164 | 165 | //If the tag type is not defined, define it. 166 | if t.Type == 0 { 167 | t.Type = ttype 168 | } 169 | 170 | //Read the tag value 171 | payload := make([]byte, io.Len()) 172 | io.RL(payload) 173 | 174 | //if the tag value changed, call the OnChange callback 175 | if bytes.Compare(t.value, payload) != 0 { 176 | t.value = payload 177 | if t.Onchange != nil { 178 | if cb == nil { 179 | go t.Onchange() 180 | } else { 181 | cb(t.Onchange) 182 | } 183 | } 184 | } 185 | return nil 186 | } 187 | 188 | func (t *Tag) Write() error { 189 | t.Lock.Lock() 190 | defer t.Lock.Unlock() 191 | if t.wValue == nil { 192 | return nil 193 | } 194 | _, err := t.TCP.Send(multiple(t.writeRequest())) 195 | if err == nil { 196 | if t.wValue != nil { 197 | copy(t.value, t.wValue) 198 | t.wValue = nil 199 | } 200 | } 201 | return err 202 | } 203 | 204 | func (t *Tag) writeRequest() []*packet.MessageRouterRequest { 205 | var result []*packet.MessageRouterRequest 206 | if 0x8000&t.Type == 0 { 207 | io := bufferx.New(nil) 208 | io.WL(t.Type) 209 | io.WL(t.count()) 210 | io.WL(t.wValue) 211 | 212 | mr := packet.NewMessageRouter(packet.ServiceWriteTag, packet.Paths( 213 | path.LogicalBuild(path.LogicalTypeClassID, 0x6B, true), 214 | path.LogicalBuild(path.LogicalTypeInstanceID, t.instanceID, true), 215 | ), io.Bytes()) 216 | result = append(result, mr) 217 | } else { 218 | // only string 219 | io := bufferx.New(nil) 220 | io.WL(DINT) 221 | io.WL(types.UInt(1)) 222 | io.WL(types.UDInt(len(t.wValue))) 223 | mr1 := packet.NewMessageRouter(packet.ServiceWriteTag, packet.Paths( 224 | path.LogicalBuild(path.LogicalTypeClassID, 0x6B, true), 225 | path.LogicalBuild(path.LogicalTypeInstanceID, t.instanceID, true), 226 | path.DataBuild(path.DataTypeANSI, []byte("LEN"), true), 227 | ), io.Bytes()) 228 | result = append(result, mr1) 229 | 230 | io1 := bufferx.New(nil) 231 | io1.WL(SINT) 232 | io1.WL(types.UInt(len(t.wValue))) 233 | io1.WL(t.wValue) 234 | mr2 := packet.NewMessageRouter(packet.ServiceWriteTag, packet.Paths( 235 | path.LogicalBuild(path.LogicalTypeClassID, 0x6B, true), 236 | path.LogicalBuild(path.LogicalTypeInstanceID, t.instanceID, true), 237 | path.DataBuild(path.DataTypeANSI, []byte("DATA"), true), 238 | ), io1.Bytes()) 239 | result = append(result, mr2) 240 | } 241 | 242 | return result 243 | } 244 | 245 | func (t *Tag) SetInt32(i int32) { 246 | t.changed = true 247 | io := bufferx.New(nil) 248 | io.WL(i) 249 | t.wValue = io.Bytes() 250 | } 251 | 252 | func (t *Tag) SetString(i string) { 253 | t.changed = true 254 | io := bufferx.New(nil) 255 | io.WL([]byte(i)) 256 | t.wValue = io.Bytes() 257 | } 258 | 259 | func (t *Tag) dims() types.USInt { 260 | return types.USInt((0x6000 & t.Type) >> 13) 261 | } 262 | 263 | func (t *Tag) TypeString() string { 264 | var _type string 265 | if 0x8000&t.Type == 0 { 266 | _type = "atomic" 267 | } else { 268 | _type = "struct" 269 | } 270 | 271 | return fmt.Sprintf("%#04x(%6s) | %s | %d dims", uint16(t.Type), TypeMap[0xFFF&t.Type], _type, (0x6000&t.Type)>>13) 272 | } 273 | 274 | func (t *Tag) Name() string { 275 | return string(t.name) 276 | } 277 | 278 | func (t *Tag) count() types.UInt { 279 | a := types.UInt(1) 280 | if t.dim1Len > 0 { 281 | a = types.UInt(t.dim1Len) 282 | } 283 | b := types.UInt(1) 284 | if t.dim2Len > 0 { 285 | b = types.UInt(t.dim2Len) 286 | } 287 | c := types.UInt(1) 288 | if t.dim3Len > 0 { 289 | c = types.UInt(t.dim3Len) 290 | } 291 | return a * b * c 292 | } 293 | 294 | func (t *Tag) GetValue() interface{} { 295 | switch t.Type { 296 | case NULL: 297 | return nil 298 | case BOOL: 299 | return t.Bool() 300 | case SINT: 301 | return t.Int8() 302 | case USINT: 303 | return t.UInt8() 304 | case INT: 305 | return t.UInt16() 306 | case UINT: 307 | return t.UInt16() 308 | case UDINT: 309 | return t.UInt32() 310 | case DINT: 311 | return t.Int32() 312 | case LINT: 313 | return t.Int64() 314 | case ULINT: 315 | return t.UInt64() 316 | case REAL: 317 | return t.Float32() 318 | case LREAL: 319 | return t.Float64() 320 | case STRING: 321 | return t.String() 322 | case STRING2: 323 | return t.String() 324 | } 325 | return t.value 326 | } 327 | 328 | func (t *Tag) Bool() bool { 329 | io := bufferx.New(t.value) 330 | var val bool 331 | io.RL(&val) 332 | return val 333 | } 334 | 335 | func (t *Tag) Int8() int8 { 336 | io := bufferx.New(t.value) 337 | var val int8 338 | io.RL(&val) 339 | return val 340 | } 341 | 342 | func (t *Tag) UInt8() uint8 { 343 | io := bufferx.New(t.value) 344 | var val uint8 345 | io.RL(&val) 346 | return val 347 | } 348 | 349 | func (t *Tag) Int16() int16 { 350 | io := bufferx.New(t.value) 351 | var val int16 352 | io.RL(&val) 353 | return val 354 | } 355 | 356 | func (t *Tag) UInt16() uint16 { 357 | io := bufferx.New(t.value) 358 | var val uint16 359 | io.RL(&val) 360 | return val 361 | } 362 | 363 | func (t *Tag) Int32() int32 { 364 | io := bufferx.New(t.value) 365 | var val int32 366 | io.RL(&val) 367 | return val 368 | } 369 | 370 | func (t *Tag) UInt32() uint32 { 371 | io := bufferx.New(t.value) 372 | var val uint32 373 | io.RL(&val) 374 | return val 375 | } 376 | 377 | func (t *Tag) Int64() int64 { 378 | io := bufferx.New(t.value) 379 | var val int64 380 | io.RL(&val) 381 | return val 382 | } 383 | 384 | func (t *Tag) UInt64() uint64 { 385 | io := bufferx.New(t.value) 386 | var val uint64 387 | io.RL(&val) 388 | return val 389 | } 390 | 391 | func (t *Tag) Float64() float64 { 392 | bits := binary.LittleEndian.Uint64(t.value) 393 | return math.Float64frombits(bits) 394 | } 395 | 396 | func (t *Tag) Float32() float32 { 397 | bits := binary.LittleEndian.Uint32(t.value) 398 | return math.Float32frombits(bits) 399 | } 400 | 401 | func (t *Tag) String() string { 402 | io := bufferx.New(t.value) 403 | _len := types.UDInt(0) 404 | io.RL(&_len) 405 | if _len > 88 { 406 | return "" 407 | } 408 | val := make([]byte, _len) 409 | io.RL(&val) 410 | for i := range val { 411 | if !unicode.IsPrint(rune(val[i])) { 412 | return "some rune cant print" 413 | } 414 | } 415 | return string(val) 416 | } 417 | 418 | func (t *Tag) XInt32() int32 { 419 | var _value []byte 420 | if len(t.wValue) > 0 { 421 | _value = t.wValue 422 | } else { 423 | _value = t.value 424 | } 425 | io := bufferx.New(_value) 426 | var val int32 427 | io.RL(&val) 428 | return val 429 | } 430 | 431 | func (t *Tag) XString() string { 432 | var _value []byte 433 | if len(t.wValue) > 0 { 434 | _value = t.wValue 435 | return string(_value) 436 | } else { 437 | _value = t.value 438 | io := bufferx.New(_value) 439 | _len := types.UDInt(0) 440 | io.RL(&_len) 441 | if _len > 88 { 442 | return "" 443 | } 444 | val := make([]byte, _len) 445 | io.RL(&val) 446 | for i := range val { 447 | if !unicode.IsPrint(rune(val[i])) { 448 | return "some rune cant print" 449 | } 450 | } 451 | return string(val) 452 | } 453 | } 454 | 455 | func multiple(mrs []*packet.MessageRouterRequest) *packet.MessageRouterRequest { 456 | if len(mrs) == 1 { 457 | return mrs[0] 458 | } else { 459 | io := bufferx.New(nil) 460 | io.WL(types.UInt(len(mrs))) 461 | offset := 2 * (len(mrs) + 1) // offset0 = 上一个(2) + 所有offset的长度的长度综合 2xN 462 | io.WL(types.UInt(offset)) 463 | for i := range mrs { 464 | if i != len(mrs)-1 { 465 | offset += len(mrs[i].Encode()) 466 | io.WL(types.UInt(offset)) 467 | } 468 | } 469 | for i := range mrs { 470 | io.WL(mrs[i].Encode()) 471 | } 472 | return packet.NewMessageRouter(packet.ServiceMultipleServicePacket, packet.Paths( 473 | path.LogicalBuild(path.LogicalTypeClassID, 0x02, true), 474 | path.LogicalBuild(path.LogicalTypeInstanceID, 0x01, true), 475 | ), io.Bytes()) 476 | } 477 | } 478 | 479 | func (t *EIPTCP) AllTags() (map[string]*Tag, error) { 480 | result := make(map[string]*Tag) 481 | return t.allTags(result, 0) 482 | } 483 | 484 | func (t *EIPTCP) allTags(tagMap map[string]*Tag, instanceID types.UDInt) (map[string]*Tag, error) { 485 | paths := packet.Paths( 486 | path.LogicalBuild(path.LogicalTypeClassID, 0x6B, true), 487 | path.LogicalBuild(path.LogicalTypeInstanceID, instanceID, true), 488 | ) 489 | 490 | io := bufferx.New(nil) 491 | io.WL(types.UInt(3)) 492 | io.WL(types.UInt(1)) 493 | io.WL(types.UInt(2)) //type 494 | io.WL(types.UInt(8)) //dims 495 | 496 | mr := packet.NewMessageRouter(packet.ServiceGetInstanceAttributeList, paths, io.Bytes()) 497 | 498 | res, err := t.Send(mr) 499 | if err != nil { 500 | return nil, err 501 | } 502 | 503 | mrres := new(packet.MessageRouterResponse) 504 | mrres.Decode(res.Packet.Items[1].Data) 505 | 506 | io1 := bufferx.New(mrres.ResponseData) 507 | for io1.Len() > 0 { 508 | tag := new(Tag) 509 | tag.TCP = t 510 | tag.Lock = new(sync.Mutex) 511 | 512 | io1.RL(&tag.instanceID) 513 | io1.RL(&tag.nameLen) 514 | tag.name = make([]byte, tag.nameLen) 515 | io1.RL(tag.name) 516 | io1.RL(&tag.Type) 517 | io1.RL(&tag.dim1Len) 518 | io1.RL(&tag.dim2Len) 519 | io1.RL(&tag.dim3Len) 520 | 521 | tagMap[tag.Name()] = tag 522 | instanceID = tag.instanceID 523 | } 524 | 525 | if mrres.GeneralStatus == 0x06 { 526 | return t.allTags(tagMap, instanceID+1) 527 | } 528 | 529 | return tagMap, nil 530 | } 531 | 532 | type TagGroup struct { 533 | tags map[types.UDInt]*Tag 534 | Tcp *EIPTCP 535 | Lock *sync.Mutex 536 | } 537 | 538 | func NewTagGroup(lock *sync.Mutex) *TagGroup { 539 | return &TagGroup{tags: make(map[types.UDInt]*Tag), Lock: lock} 540 | } 541 | 542 | func (tg *TagGroup) Add(tag *Tag) { 543 | if tg.Tcp == nil { 544 | tg.Tcp = tag.TCP 545 | } else { 546 | if tg.Tcp != tag.TCP { 547 | return 548 | } 549 | } 550 | tg.tags[tag.instanceID] = tag 551 | } 552 | 553 | func (tg *TagGroup) Remove(tag *Tag) { 554 | delete(tg.tags, tag.instanceID) 555 | } 556 | 557 | func (tg *TagGroup) Read() error { 558 | tg.Lock.Lock() 559 | defer tg.Lock.Unlock() 560 | if len(tg.tags) == 0 { 561 | return nil 562 | } 563 | 564 | if len(tg.tags) == 1 { 565 | for _, v := range tg.tags { 566 | return v.Read() 567 | } 568 | } 569 | 570 | var list []types.UDInt 571 | var mrs []*packet.MessageRouterRequest 572 | 573 | for i := range tg.tags { 574 | one := tg.tags[i] 575 | one.Lock.Lock() 576 | defer one.Lock.Unlock() 577 | list = append(list, one.instanceID) 578 | mrs = append(mrs, one.readRequest()) 579 | } 580 | 581 | _sb := multiple(mrs) 582 | res, err := tg.Tcp.Send(_sb) 583 | if err != nil { 584 | return err 585 | } 586 | 587 | rmr := &packet.MessageRouterResponse{} 588 | rmr.Decode(res.Packet.Items[1].Data) 589 | 590 | io1 := bufferx.New(rmr.ResponseData) 591 | count := types.UInt(0) 592 | io1.RL(&count) 593 | 594 | if int(count) != len(list) { 595 | return nil 596 | } 597 | 598 | var offsets []types.UInt 599 | for i := types.UInt(0); i < count; i++ { 600 | one := types.UInt(0) 601 | io1.RL(&one) 602 | offsets = append(offsets, one) 603 | } 604 | var cbs []func() 605 | for i2 := range list { 606 | mr := &packet.MessageRouterResponse{} 607 | if (i2 + 1) != len(offsets) { 608 | mr.Decode(rmr.ResponseData[offsets[i2]:offsets[i2+1]]) 609 | } else { 610 | mr.Decode(rmr.ResponseData[offsets[i2]:]) 611 | } 612 | tg.tags[list[i2]].readParser(mr, func(f func()) { 613 | cbs = append(cbs, f) 614 | }) 615 | } 616 | for i := range cbs { 617 | go cbs[i]() 618 | } 619 | 620 | return nil 621 | } 622 | 623 | func (tg *TagGroup) Write() error { 624 | tg.Lock.Lock() 625 | defer tg.Lock.Unlock() 626 | var list []types.UDInt 627 | var mrs []*packet.MessageRouterRequest 628 | 629 | for i := range tg.tags { 630 | one := tg.tags[i] 631 | one.Lock.Lock() 632 | defer one.Lock.Unlock() 633 | if one.changed { 634 | list = append(list, one.instanceID) 635 | mrs = append(mrs, one.writeRequest()...) 636 | one.changed = false 637 | } 638 | } 639 | 640 | if len(list) == 0 { 641 | return nil 642 | } 643 | 644 | _, err := tg.Tcp.Send(multiple(mrs)) 645 | if err != nil { 646 | return err 647 | } 648 | for i := range tg.tags { 649 | if tg.tags[i].wValue != nil { 650 | copy(tg.tags[i].value, tg.tags[i].wValue) 651 | tg.tags[i].wValue = nil 652 | } 653 | } 654 | 655 | return nil 656 | } 657 | 658 | func (t *EIPTCP) InitializeTag(name string, tag *Tag) { 659 | tag.Lock = new(sync.Mutex) 660 | tag.TCP = t 661 | nameBytes := []byte(name) 662 | if nameBytes != nil { 663 | tag.nameLen = types.UInt(len(nameBytes)) 664 | tag.name = nameBytes 665 | } 666 | tag.instanceID = 0 667 | tag.Read() 668 | return 669 | } 670 | -------------------------------------------------------------------------------- /tcp.go: -------------------------------------------------------------------------------- 1 | package go_ethernet_ip 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/loki-os/go-ethernet-ip/bufferx" 7 | "github.com/loki-os/go-ethernet-ip/messages/packet" 8 | "github.com/loki-os/go-ethernet-ip/types" 9 | "net" 10 | "sync" 11 | ) 12 | 13 | type EIPTCP struct { 14 | config *Config 15 | tcpAddr *net.TCPAddr 16 | tcpConn *net.TCPConn 17 | session types.UDInt 18 | 19 | established bool 20 | connID types.UDInt 21 | seqNum types.UInt 22 | 23 | requestLock *sync.Mutex 24 | } 25 | 26 | func (t *EIPTCP) reset() { 27 | t.established = false 28 | } 29 | 30 | func (t *EIPTCP) Connect() error { 31 | t.reset() 32 | 33 | tcpConnection, err := net.DialTCP("tcp", nil, t.tcpAddr) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | err = tcpConnection.SetKeepAlive(true) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | t.tcpConn = tcpConnection 44 | 45 | if err := t.RegisterSession(); err != nil { 46 | return err 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func (t *EIPTCP) write(data []byte) error { 53 | _, err := t.tcpConn.Write(data) 54 | return err 55 | } 56 | 57 | func (t *EIPTCP) read() (*packet.Packet, error) { 58 | buf := make([]byte, 1024*64) 59 | length, err := t.tcpConn.Read(buf) 60 | if err != nil { 61 | return nil, err 62 | } 63 | return t.parse(buf[0:length]) 64 | } 65 | 66 | func (t *EIPTCP) parse(buf []byte) (*packet.Packet, error) { 67 | if len(buf) < 24 { 68 | return nil, errors.New("invalid packet, length < 24") 69 | } 70 | _packet := new(packet.Packet) 71 | buffer := bufferx.New(buf) 72 | buffer.RL(&_packet.Header) 73 | if buffer.Error() != nil { 74 | return nil, buffer.Error() 75 | } 76 | if _packet.Options != 0 { 77 | return nil, errors.New("wrong packet with non-zero option") 78 | } 79 | if int(_packet.Length) != buffer.Len() { 80 | return nil, errors.New("wrong packet length") 81 | } 82 | _packet.SpecificData = make([]byte, _packet.Length) 83 | buffer.RL(_packet.SpecificData) 84 | if buffer.Error() != nil { 85 | return nil, buffer.Error() 86 | } 87 | return _packet, nil 88 | } 89 | 90 | func NewTCP(address string, config *Config) (*EIPTCP, error) { 91 | if config == nil { 92 | config = DefaultConfig() 93 | } 94 | 95 | tcpAddress, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", address, config.TCPPort)) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | return &EIPTCP{ 101 | requestLock: new(sync.Mutex), 102 | config: config, 103 | tcpAddr: tcpAddress, 104 | }, nil 105 | } 106 | -------------------------------------------------------------------------------- /types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Octet uint8 4 | type Bool uint8 5 | type Byte uint8 6 | type Word uint16 7 | type DWord uint32 8 | type LWord uint64 9 | type USInt uint8 10 | type UInt uint16 11 | type UDInt uint32 12 | type ULInt uint64 13 | type SInt int8 14 | type Int int16 15 | type DInt int32 16 | type LInt int64 17 | type Real float32 18 | type LReal float64 19 | -------------------------------------------------------------------------------- /utils/len.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/loki-os/go-ethernet-ip/types" 4 | 5 | func Len(data []byte) types.USInt { 6 | _len := len(data) 7 | if _len%2 == 1 { 8 | _len += 1 9 | } 10 | return types.USInt(_len / 2) 11 | } 12 | --------------------------------------------------------------------------------