Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connection-Oriented SCCP: CR & CC, to start with #29

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
175 changes: 175 additions & 0 deletions cc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package sccp

import (
"fmt"
"io"

"github.com/wmnsk/go-sccp/params"
)

/*
Message type 2.1 F 1
Destination local reference 3.2 F 3
Source local reference 3.3 F 3
Protocol class 3.6 F 1
Credit 3.10 O 3
Called party address 3.4 O 4 minimum
Data 3.16 O 3-130
Importance 3.19 O 3
End of optional parameter 3.1 O 1
*/
type CC struct {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need such a detailed reference to the spec, and please follow the Go's convention of writing a comment as a doc (godoc). You can see udt.go for reference.

Type MsgType
DestinationLocalReference params.LocalReference
SourceLocalReference params.LocalReference
params.ProtocolClass

Opts []*params.Optional

Data *params.Optional
CalledPartyAddress *params.PartyAddress
Comment on lines +17 to +20
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handling of the optional parameters could be nicer, but it's due to the bad-designed parameter handling in the current code base. I will work on refactoring parameters.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, let me comment on this

In fact, for the practical case, I just need convenient access to Data and Called/Calling pty-s only.
To my knowledge, the only interface where connection-oriented SCCP is used, is GSM A-interface, and I can hardly imagine the real use of other params there.
I would appreciate to avoid making things complicated

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q.713 is such a small spec and I'm more comfortable to just have everything implemented rather than to ignore some of them. I think I can quickly make it done soon-ish.

}

func ParseCC(b []byte) (*CC, error) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow the function order (see udt.go and scmg.go). Also, constructor (NewCC) is missing.

msg := &CC{}
if err := msg.UnmarshalBinary(b); err != nil {
return nil, err
}

return msg, nil
}

func (msg *CC) UnmarshalBinary(b []byte) error {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (msg *CC) UnmarshalBinary(b []byte) error {
func (c *CC) UnmarshalBinary(b []byte) error {

Please use a single character c as a receiver, which is a convention of Go.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

l := uint8(len(b))

if l < (1 + 3 + 3 + 1 + 1) {
return io.ErrUnexpectedEOF
}

msg.Type = MsgType(b[0])
if err := msg.DestinationLocalReference.Read(b[1:4]); err != nil {
return err
}
if err := msg.SourceLocalReference.Read(b[4:7]); err != nil {
return err
}

msg.ProtocolClass = params.ProtocolClass(b[7])

optr := b[8]

if optr == 0 {
return nil
}
if optr != 1 {
return io.ErrUnexpectedEOF
}

if err := msg.parseOptional(b[9:]); err != nil {
return io.ErrUnexpectedEOF
}
return nil
}

func (msg *CC) parseOptional(b []byte) error {
p := uint8(0)
for p < uint8(len(b)) {
t := b[p]

if t == 0 {
return nil
}
if (p + 1) >= uint8(len(b)) {
return io.ErrUnexpectedEOF
}

l := b[p+1]
if (p + 1 + l) >= uint8(len(b)) {
return io.ErrUnexpectedEOF
}

o := &params.Optional{
Tag: t,
Len: l,
Value: b[p+2 : p+2+l],
}

switch t {
case params.DataTag:
msg.Data = o
case params.CdPtyAddrTag:
var err error
msg.CalledPartyAddress, err = params.ParsePartyAddress(b[p : p+2+l])
if err != nil {
return err
}
}

msg.Opts = append(msg.Opts, o)
p += 2 + l

}

return nil
}

// MarshalBinary returns the byte sequence generated from a UDT instance.
func (msg *CC) MarshalBinary() ([]byte, error) {
b := make([]byte, msg.MarshalLen())
if err := msg.MarshalTo(b); err != nil {
return nil, err
}

return b, nil
}

func (msg *CC) MarshalLen() int {
if len(msg.Opts) == 0 {
return 9 // 8 fixed + 0 ptr
}
l := 10 // 8 fixed + 1 ptr + last optional
for _, v := range msg.Opts {
l += int(v.Len) + 2
}

return l
}

func (msg *CC) MarshalTo(b []byte) error {
b[0] = uint8(msg.Type)
msg.DestinationLocalReference.Read(b[1:4])

Check failure on line 140 in cc.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `msg.DestinationLocalReference.Read` is not checked (errcheck)
msg.SourceLocalReference.Read(b[4:7])

Check failure on line 141 in cc.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `msg.SourceLocalReference.Read` is not checked (errcheck)
b[7] = byte(msg.ProtocolClass)

if len(msg.Opts) == 0 {
return nil
}

b[8] = 1
p := uint8(9)

for i := 0; i < len(msg.Opts); i++ {
b[p] = msg.Opts[i].Tag
b[p+1] = msg.Opts[i].Len
copy(b[p+2:], msg.Opts[i].Value)

p += msg.Opts[i].Len + 2
}
return nil
}

func (msg *CC) String() string {
if msg.CalledPartyAddress != nil {
return fmt.Sprintf("{Type: CC, CalledPartyAddress: %v}", msg.CalledPartyAddress)
}
return "{Type: CC}"
}
Comment on lines +149 to +154
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do similar to what UDT and SCMG do.


// MessageType returns the Message Type in int.
func (msg *CC) MessageType() MsgType {
return msg.Type
}

func (msg *CC) MessageTypeName() string {
return "CR"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return "CR"
return "CC"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
33 changes: 33 additions & 0 deletions cc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package sccp

import (
"bytes"
"encoding/hex"
"fmt"
"testing"
)

var mockCCs = [][]byte{
{0x2, 0x0, 0x3, 0x75, 0x3, 0x20, 0x48, 0x2, 0x0},
{0x2, 0x0, 0x70, 0x3e, 0x0, 0x0, 0x5, 0x2, 0x1, 0x3, 0x4, 0x43, 0x1c, 0x2d, 0xfe, 0x0},
}

func TestCC(t *testing.T) {
for i, v := range mockCCs {
cc, err := ParseCC(v)
if err != nil {
t.Fatal(i, err)
}
b, err := cc.MarshalBinary()
if err != nil {
t.Fatal(i, err)
}
if !bytes.Equal(v, b) {
fmt.Println(hex.EncodeToString(v))
fmt.Println(hex.EncodeToString(b))

t.Fatal(i, err)
}
fmt.Println(cc)
}
}
182 changes: 182 additions & 0 deletions cr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package sccp

import (
"encoding/hex"
"fmt"
"io"

"github.com/wmnsk/go-sccp/params"
)

/*
Message type code 2.1 F 1
Source local reference 3.3 F 3
Protocol class 3.6 F 1
Called party address 3.4 V 3 minimum
Credit 3.10 O 3
Calling party address 3.5 O 4 minimum
Data 3.16 O 3-130
Hop counter 3.18 O 3
Importance 3.19 O 3
End of optional parameters 3.1 O 1
*/

type CR struct {
Type MsgType
SourceLocalReference params.LocalReference
params.ProtocolClass
CalledPartyAddress *params.PartyAddress

Opts []*params.Optional // all others

// just pointers, not used for Marshal-ing, I kust really need these two
// similar objects are expected to be found in Opts
Data *params.Optional
CallingPartyAddress *params.PartyAddress

mptr uint8
optr uint8
}

func ParseCR(b []byte) (*CR, error) {
msg := &CR{}
if err := msg.UnmarshalBinary(b); err != nil {
return nil, err
}

return msg, nil
}

func (msg *CR) UnmarshalBinary(b []byte) error {
l := uint8(len(b))
if l <= (1 + 3 + 1 + 2 /*ptrs*/ + 3) { // where CdPA starts
return io.ErrUnexpectedEOF
}

msg.Type = MsgType(b[0])
if err := msg.SourceLocalReference.Read(b[1:4]); err != nil {
return err
}
msg.ProtocolClass = params.ProtocolClass(b[4])

msg.mptr = b[5]
if l < (5 + msg.mptr + 2) {
return io.ErrUnexpectedEOF
}
msg.optr = b[6]
if l < (6 + msg.optr + 1) {
return io.ErrUnexpectedEOF
}

var err error
if msg.CalledPartyAddress, err = params.ParsePartyAddress(b[5+msg.mptr : 6+msg.optr]); err != nil {
return err
}
if msg.optr == 0 {
return nil
}
return msg.parseOptional(b[6+msg.optr:])
}

func (msg *CR) parseOptional(b []byte) error {
p := uint8(0)
for p < uint8(len(b)) {
t := b[p]

if t == 0 {
return nil
}
if (p + 1) >= uint8(len(b)) {
return io.ErrUnexpectedEOF
}

l := b[p+1]
if (p + 1 + l) >= uint8(len(b)) {
return io.ErrUnexpectedEOF
}

o := &params.Optional{
Tag: t,
Len: l,
Value: b[p+2 : p+2+l],
}

switch t {
case params.DataTag:
msg.Data = o
case params.CgPtyAddrTag:
var err error
msg.CallingPartyAddress, err = params.ParsePartyAddress(b[p : p+2+l])
if err != nil {
return err
}
}

msg.Opts = append(msg.Opts, o)
p += 2 + l

}

return nil
}

// MarshalBinary returns the byte sequence generated from a UDT instance.
func (msg *CR) MarshalBinary() ([]byte, error) {
b := make([]byte, msg.MarshalLen())
if err := msg.MarshalTo(b); err != nil {
return nil, err
}

return b, nil
}

func (msg *CR) MarshalLen() int {
l := 5 + 2 + 1 // fixed + ptrs + last optional
for _, v := range msg.Opts {
l += int(v.Len) + 2
}
l += int(msg.CalledPartyAddress.Length) + 1

return l
}

func (msg *CR) MarshalTo(b []byte) error {
b[0] = uint8(msg.Type)
msg.SourceLocalReference.Read(b[1:4])

Check failure on line 145 in cr.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `msg.SourceLocalReference.Read` is not checked (errcheck)
b[4] = byte(msg.ProtocolClass)
b[5] = 2
b[6] = msg.CalledPartyAddress.Length + 2
if err := msg.CalledPartyAddress.MarshalTo(b[7 : 7+int(msg.CalledPartyAddress.Length)+1]); err != nil {
return err
}
p := 6 + msg.CalledPartyAddress.Length + 1 + 1
for i := 0; i < len(msg.Opts); i++ {
b[p] = msg.Opts[i].Tag
b[p+1] = msg.Opts[i].Len
copy(b[p+2:], msg.Opts[i].Value)

p += msg.Opts[i].Len + 2
}
return nil
}

func (msg *CR) String() string {
s := fmt.Sprintf("{Type: CR, CalledPartyAddress: %v", msg.CalledPartyAddress)
if msg.CallingPartyAddress != nil {
s += fmt.Sprintf(", CallingPartyAddress: %v", msg.CallingPartyAddress)
}
if msg.Data != nil {
s += fmt.Sprintf(", DataLength: %d, Data: %s", msg.Data.Len, hex.EncodeToString(msg.Data.Value))
}

return s + "}"
}

// MessageType returns the Message Type in int.
func (msg *CR) MessageType() MsgType {
return msg.Type
}

func (msg *CR) MessageTypeName() string {
return "CR"
}
Loading
Loading