From 23d44f0c98f2c9d0d4541c892e1a5bcec53b3295 Mon Sep 17 00:00:00 2001 From: "Pascal S. de Kloe" Date: Sun, 30 Jun 2024 21:09:40 +0200 Subject: [PATCH] variable structure qualifier type, omits counting in codec --- info/asdu.go | 176 +++++++++++++++++++------------------------- info/asdu_test.go | 40 +++++++--- notify.go | 2 +- track/track.go | 2 +- track/track_test.go | 15 ++-- 5 files changed, 117 insertions(+), 118 deletions(-) diff --git a/info/asdu.go b/info/asdu.go index 1c869ac..7590529 100644 --- a/info/asdu.go +++ b/info/asdu.go @@ -94,16 +94,33 @@ func (p *Params) ObjAddr(buf []byte) ObjAddr { return addr } +// A VarStructQual, or variable structure qualifier, defines the ASDU payload +// with a Count() and a Sequence flag. +type VarStructQual uint8 + +// Variable Structure Qualifier Flags +const ( + // The SQ flag signals that the information applies to a consecutive + // sequence of addresses, in which each information object address is + // one higher than its previous element. The address in the ASDU gives + // the offset (for the first information object). + Sequence = 0x80 +) + +// Count returns the number of information objects, which can be zero. +func (q VarStructQual) Count() int { return int(q & 0x7f) } + // ID identifies the application data. type ID struct { - Addr CommonAddr // station address + Type TypeID + Struct VarStructQual + Cause Cause // Originator Address [1, 255] or 0 for the default. // The applicability is controlled by Params.CauseSize. Orig uint8 - Type TypeID // information content - Cause Cause // submission category + Addr CommonAddr // station address } // String returns a compact label. @@ -119,7 +136,6 @@ type ASDU struct { *Params ID bootstrap [17]byte // prevents Info malloc - InfoSeq bool // marks Info as a sequence Info []byte // information object serial } @@ -141,7 +157,13 @@ func MustNewInro(p *Params, addr CommonAddr, orig uint8, group uint) *ASDU { u := ASDU{ Params: p, - ID: ID{addr, orig, C_IC_NA_1, Act}, + ID: ID{ + Type: C_IC_NA_1, + Struct: 0x01, + Cause: Act, + Addr: addr, + Orig: orig, + }, } u.Info = u.bootstrap[:p.ObjAddrSize+1] @@ -164,7 +186,7 @@ func (u *ASDU) Respond(t TypeID, c Cause) *ASDU { func (u *ASDU) Reply(c Cause) *ASDU { r := NewASDU(u.Params, u.ID) r.Cause = c | u.Cause&TestFlag - r.InfoSeq = u.InfoSeq + r.Struct = u.Struct & Sequence r.Info = append(r.Info, u.Info...) return r } @@ -173,16 +195,14 @@ func (u *ASDU) Reply(c Cause) *ASDU { func (u *ASDU) String() string { dataSize := ObjSize[u.Type] if dataSize == 0 { - if !u.InfoSeq { + if u.Struct&Sequence == 0 { return fmt.Sprintf("%s: %#x", u.ID, u.Info) } return fmt.Sprintf("%s seq: %#x", u.ID, u.Info) } - end := len(u.Info) - addrSize := u.ObjAddrSize - if end < addrSize { - if !u.InfoSeq { + if len(u.Info) < u.ObjAddrSize { + if u.Struct&Sequence == 0 { return fmt.Sprintf("%s: %#x ", u.ID, u.Info) } return fmt.Sprintf("%s seq: %#x ", u.ID, u.Info) @@ -191,28 +211,27 @@ func (u *ASDU) String() string { buf := bytes.NewBufferString(u.ID.String()) - for i := addrSize; ; { - start := i - i += dataSize - if i > end { - fmt.Fprintf(buf, " %d:%#x ", addr, u.Info[start:]) + for i := u.ObjAddrSize; ; { + if i+dataSize > len(u.Info) { + fmt.Fprintf(buf, " %d:%#x ", addr, u.Info[i:]) break } - fmt.Fprintf(buf, " %d:%#x", addr, u.Info[start:i]) - if i == end { + fmt.Fprintf(buf, " %d:%#x", addr, u.Info[i:i+dataSize]) + i += dataSize + + if i == len(u.Info) { break } - if u.InfoSeq { + if u.Struct&Sequence != 0 { addr++ } else { - start = i - i += addrSize - if i > end { - fmt.Fprintf(buf, " %#x ", u.Info[start:i]) + if i+u.ObjAddrSize > len(u.Info) { + fmt.Fprintf(buf, " %#x ", u.Info[i:]) break } - addr = u.ObjAddr(u.Info[start:]) + addr = u.ObjAddr(u.Info[i : i+u.ObjAddrSize]) + i += u.ObjAddrSize } } @@ -228,41 +247,13 @@ func (u *ASDU) MarshalBinary() (data []byte, err error) { return nil, errAddrZero } - // calculate the size declaration byte - // named "variable structure qualifier" - var vsq byte - { - // fixed element size - objSize := ObjSize[u.Type] - if objSize == 0 { - return nil, ErrType - } - - // See companion standard 101, subclause 7.2.2. - if u.InfoSeq { - objCount := (len(u.Info) - u.ObjAddrSize) / objSize - if objCount >= 128 { - return nil, errObjFit - } - vsq = byte(objCount) | 128 - } else { - objCount := len(u.Info) / (u.ObjAddrSize + objSize) - if objCount >= 128 { - return nil, errObjFit - } - vsq = byte(objCount) - } - } - data = make([]byte, 2+u.CauseSize+u.AddrSize+len(u.Info)) data[0] = byte(u.Type) - data[1] = vsq + data[1] = byte(u.Struct) data[2] = byte(u.Cause) + i := 3 // write index - i := 3 switch u.CauseSize { - default: - return nil, errParam case 1: if u.Orig != 0 { return nil, errOrigFit @@ -270,11 +261,11 @@ func (u *ASDU) MarshalBinary() (data []byte, err error) { case 2: data[i] = u.Orig i++ + default: + return nil, errParam } switch u.AddrSize { - default: - return nil, errParam case 1: if u.Addr == GlobalAddr { data[i] = 255 @@ -289,6 +280,8 @@ func (u *ASDU) MarshalBinary() (data []byte, err error) { i++ data[i] = byte(u.Addr >> 8) i++ + default: + return nil, errParam } copy(data[i:], u.Info) @@ -299,68 +292,53 @@ func (u *ASDU) MarshalBinary() (data []byte, err error) { // UnmarshalBinary honors the encoding.BinaryUnmarshaler interface. // Params must be set in advance. All other fields are initialized. func (u *ASDU) UnmarshalBinary(data []byte) error { - // data unit identifier size check - lenDUI := 2 + u.CauseSize + u.AddrSize - if lenDUI > len(data) { - return io.EOF + if len(data) < 3 { + if len(data) == 0 { + return io.EOF + } + return io.ErrUnexpectedEOF } - u.Info = append(u.bootstrap[:0], data[lenDUI:]...) u.Type = TypeID(data[0]) - - // fixed element size - objSize := ObjSize[u.Type] - if objSize == 0 { - return ErrType - } - - var size int - // read the variable structure qualifier - if vsq := data[1]; vsq > 127 { - u.InfoSeq = true - objCount := int(vsq & 127) - size = u.ObjAddrSize + (objCount * objSize) - } else { - u.InfoSeq = false - objCount := int(vsq) - size = objCount * (u.ObjAddrSize + objSize) - } - - switch { - case size == 0: - return errObjFit - case size > len(u.Info): - return io.EOF - case size < len(u.Info): - // not explicitly prohibited - u.Info = u.Info[:size] - } - + u.Struct = VarStructQual(data[1]) u.Cause = Cause(data[2]) + i := 3 // read index switch u.CauseSize { - default: - return errParam case 1: u.Orig = 0 case 2: + if len(data) < 4 { + return io.ErrUnexpectedEOF + } u.Orig = data[3] + i = 4 + default: + return errParam } switch u.AddrSize { - default: - return errParam case 1: - addr := CommonAddr(data[lenDUI-1]) - if addr == 255 { + if len(data) < i { + return io.ErrUnexpectedEOF + } + u.Addr = CommonAddr(data[i]) + i++ + if u.Addr == 255 { // map 8-bit variant to 16-bit equivalent - addr = GlobalAddr + u.Addr = GlobalAddr } - u.Addr = addr case 2: - u.Addr = CommonAddr(data[lenDUI-2]) | CommonAddr(data[lenDUI-1])<<8 + if len(data) < i+1 { + return io.ErrUnexpectedEOF + } + u.Addr = CommonAddr(data[i]) | CommonAddr(data[i+1])<<8 + i += 2 + default: + return errParam } + u.Info = append(u.bootstrap[:0], data[i:]...) return nil } diff --git a/info/asdu_test.go b/info/asdu_test.go index a91af5f..469481e 100644 --- a/info/asdu_test.go +++ b/info/asdu_test.go @@ -12,30 +12,50 @@ var goldenASDUs = []struct { { &ASDU{ Params: Wide, - ID: ID{1001, 7, M_SP_NA_1, Percyc}, - Info: []byte{1, 2, 3, 4}, + ID: ID{ + Type: M_SP_NA_1, + Struct: 1, + Cause: Percyc, + Orig: 7, + Addr: 1001, + }, + Info: []byte{1, 2, 3, 4}, }, "7@1001 M_SP_NA_1 197121:0x04", }, { &ASDU{ Params: Narrow, - ID: ID{42, 0, M_DP_NA_1, Back}, - Info: []byte{1, 2, 3, 4}, + ID: ID{ + Type: M_DP_NA_1, + Struct: 2, + Cause: Back, + Addr: 42, + }, + Info: []byte{1, 2, 3, 4}, }, "@42 M_DP_NA_1 1:0x02 3:0x04", }, { &ASDU{ Params: Narrow, - ID: ID{250, 0, M_ST_NA_1, Spont}, - Info: []byte{1, 2, 3, 4, 5}, + ID: ID{ + Type: M_ST_NA_1, + Struct: 2, + Cause: Spont, + Addr: 250, + }, + Info: []byte{1, 2, 3, 4, 5}, }, "@250 M_ST_NA_1 1:0x0203 4:0x05 ", }, { &ASDU{ - Params: Narrow, - ID: ID{12, 0, M_ME_NC_1, Init}, - InfoSeq: true, - Info: []byte{99, 0, 1, 2, 3, 4, 5}, + Params: Narrow, + ID: ID{ + Type: M_ME_NC_1, + Struct: 2 | Sequence, + Cause: Init, + Addr: 12, + }, + Info: []byte{99, 0, 1, 2, 3, 4, 5}, }, "@12 M_ME_NC_1 99:0x0001020304 100:0x05 ", }, diff --git a/notify.go b/notify.go index f5fcffb..9c64eb8 100644 --- a/notify.go +++ b/notify.go @@ -114,7 +114,7 @@ func (d *Delegate) notify(u *info.ASDU, c *Caller, errCh chan<- error) { return } - if u.InfoSeq { + if u.Struct&info.Sequence != 0 { addr++ i = end } else { diff --git a/track/track.go b/track/track.go index 2795707..dd97c1b 100644 --- a/track/track.go +++ b/track/track.go @@ -40,7 +40,7 @@ func (h *Head) Add(u *info.ASDU) { break } - if u.InfoSeq { + if u.Struct&info.Sequence != 0 { addr++ i = end } else { diff --git a/track/track_test.go b/track/track_test.go index 98d9796..591e7df 100644 --- a/track/track_test.go +++ b/track/track_test.go @@ -18,18 +18,19 @@ func TestInro(t *testing.T) { } u1 := info.NewASDU(info.Narrow, info.ID{ - Addr: 99, - Type: info.M_ME_NC_1, - Cause: info.Percyc | info.TestFlag, + Type: info.M_ME_NC_1, + Struct: 2, + Cause: info.Percyc | info.TestFlag, + Addr: 99, }) u1.Info = []byte{42, 1, 1, 1, 1, info.OK, 44, 2, 2, 2, 2, info.OK} u2 := info.NewASDU(info.Narrow, info.ID{ - Addr: 99, - Type: info.M_ME_NC_1, - Cause: info.Back | info.TestFlag, + Type: info.M_ME_NC_1, + Struct: 2 | info.Sequence, + Cause: info.Back | info.TestFlag, + Addr: 99, }) - u2.InfoSeq = true u2.Info = []byte{43, 3, 3, 3, 3, info.NotTopical, 4, 4, 4, 4, info.OK} var h Head