Skip to content

Commit

Permalink
variable structure qualifier type, omits counting in codec
Browse files Browse the repository at this point in the history
  • Loading branch information
pascaldekloe committed Jun 30, 2024
1 parent 15eb5ea commit 23d44f0
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 118 deletions.
176 changes: 77 additions & 99 deletions info/asdu.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
}

Expand All @@ -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]
Expand All @@ -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
}
Expand All @@ -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 <EOF>", u.ID, u.Info)
}
return fmt.Sprintf("%s seq: %#x <EOF>", u.ID, u.Info)
Expand All @@ -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 <EOF>", addr, u.Info[start:])
for i := u.ObjAddrSize; ; {
if i+dataSize > len(u.Info) {
fmt.Fprintf(buf, " %d:%#x <EOF>", 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 <EOF>", u.Info[start:i])
if i+u.ObjAddrSize > len(u.Info) {
fmt.Fprintf(buf, " %#x <EOF>", u.Info[i:])
break
}
addr = u.ObjAddr(u.Info[start:])
addr = u.ObjAddr(u.Info[i : i+u.ObjAddrSize])
i += u.ObjAddrSize
}
}

Expand All @@ -228,53 +247,25 @@ 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
}
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
Expand All @@ -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)
Expand All @@ -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
}

Expand Down
40 changes: 30 additions & 10 deletions info/asdu_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <percyc> 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 <back> 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 <spont> 1:0x0203 4:0x05 <EOF>",
}, {
&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 <init> 99:0x0001020304 100:0x05 <EOF>",
},
Expand Down
2 changes: 1 addition & 1 deletion notify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion track/track.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
15 changes: 8 additions & 7 deletions track/track_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 23d44f0

Please sign in to comment.