diff --git a/cc.go b/cc.go new file mode 100644 index 0000000..bcb201f --- /dev/null +++ b/cc.go @@ -0,0 +1,163 @@ +package sccp + +import ( + "fmt" + "io" + + "github.com/wmnsk/go-sccp/params" + "github.com/wmnsk/go-sccp/utils" +) + +type CC struct { + Type MsgType + DestinationLocalReference uint32 + SourceLocalReference uint32 + params.ProtocolClass + + Opts []*params.Optional + + Data *params.Optional + CalledPartyAddress *params.PartyAddress +} + +// ParseCC decodes given byte sequence as a SCCP CC. +func ParseCC(b []byte) (*CC, error) { + msg := &CC{} + if err := msg.UnmarshalBinary(b); err != nil { + return nil, err + } + + return msg, nil +} + +func (msg *CC) UnmarshalBinary(b []byte) error { + l := uint8(len(b)) + + if l < (1 + 3 + 3 + 1 + 1) { + return io.ErrUnexpectedEOF + } + + msg.Type = MsgType(b[0]) + msg.DestinationLocalReference = utils.Uint24To32(b[1:4]) + msg.SourceLocalReference = utils.Uint24To32(b[4:7]) + + 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 := ¶ms.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 CC 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 +} + +// MarshalTo puts the byte sequence in the byte array given as b. +// SCCP is dependent on the Pointers when serializing, which means that it might fail when invalid Pointers are set. +func (msg *CC) MarshalTo(b []byte) error { + b[0] = uint8(msg.Type) + copy(b[1:4], utils.Uint32To24(msg.DestinationLocalReference)) + copy(b[4:7], utils.Uint32To24(msg.SourceLocalReference)) + 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}" +} + +// MessageType returns the Message Type in int. +func (msg *CC) MessageType() MsgType { + return msg.Type +} + +func (msg *CC) MessageTypeName() string { + return "CC" +} diff --git a/cc_test.go b/cc_test.go new file mode 100644 index 0000000..ca5f1e7 --- /dev/null +++ b/cc_test.go @@ -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) + } +} diff --git a/cr.go b/cr.go new file mode 100644 index 0000000..f975ea5 --- /dev/null +++ b/cr.go @@ -0,0 +1,168 @@ +package sccp + +import ( + "encoding/hex" + "fmt" + "io" + + "github.com/wmnsk/go-sccp/params" + "github.com/wmnsk/go-sccp/utils" +) + +type CR struct { + Type MsgType + SourceLocalReference uint32 + 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]) + msg.SourceLocalReference = utils.Uint24To32(b[1:4]) + 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 := ¶ms.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 CR 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) + copy(b[1:4], utils.Uint32To24(msg.SourceLocalReference)) + 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" +} diff --git a/cr_test.go b/cr_test.go new file mode 100644 index 0000000..80b7d78 --- /dev/null +++ b/cr_test.go @@ -0,0 +1,28 @@ +package sccp + +import ( + "bytes" + "encoding/hex" + "fmt" + "testing" +) + +var mockPagingCR = []byte{0x1, 0x0, 0x3, 0x7a, 0x2, 0x2, 0x6, 0x4, 0x43, 0xe5, 0x34, 0xfe, 0x4, 0x4, 0x43, 0x1c, 0x2d, 0xfe, 0xf, 0x27, 0x0, 0x25, 0x57, 0x5, 0x8, 0x0, 0x52, 0xf0, 0x93, 0x51, 0x7d, 0x5a, 0xe7, 0x17, 0xd, 0x6, 0x27, 0x0, 0x3, 0x50, 0x58, 0x82, 0x5, 0xf4, 0x57, 0xf9, 0x39, 0xf2, 0x7d, 0x8, 0x83, 0x97, 0x57, 0x84, 0x17, 0x7, 0x80, 0x81, 0x85, 0x0} + +func TestCR(t *testing.T) { + cr, err := ParseCR(mockPagingCR) + if err != nil { + t.Fatal(err) + } + b, err := cr.MarshalBinary() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(mockPagingCR, b) { + fmt.Println(hex.EncodeToString(mockPagingCR)) + fmt.Println(hex.EncodeToString(b)) + + t.Fatal(err) + } + fmt.Println(cr) +} diff --git a/dt1.go b/dt1.go new file mode 100644 index 0000000..2741466 --- /dev/null +++ b/dt1.go @@ -0,0 +1,85 @@ +package sccp + +import ( + "encoding/hex" + "fmt" + "io" + + "github.com/wmnsk/go-sccp/utils" +) + +type DT1 struct { + Type MsgType + DestinationLocalReference uint32 + Segmenting uint8 + Data []byte +} + +func ParseDT1(b []byte) (*DT1, error) { + msg := &DT1{} + if err := msg.UnmarshalBinary(b); err != nil { + return nil, err + } + + return msg, nil +} + +func (msg *DT1) UnmarshalBinary(b []byte) error { + l := uint8(len(b)) + if l <= (1 + 3 + 1 + 1) { + return io.ErrUnexpectedEOF + } + + msg.Type = MsgType(b[0]) + msg.DestinationLocalReference = utils.Uint24To32(b[1:4]) + + msg.Segmenting = b[4] + + if b[5] != 1 { // pointer to var, ae next position + return io.ErrUnexpectedEOF + } + + dlen := b[6] + if l != (dlen + 6 + 1) { + return io.ErrUnexpectedEOF + } + + msg.Data = b[7:] + return nil +} + +func (msg *DT1) MarshalBinary() ([]byte, error) { + b := make([]byte, msg.MarshalLen()) + if err := msg.MarshalTo(b); err != nil { + return nil, err + } + + return b, nil +} + +func (msg *DT1) MarshalLen() int { + return len(msg.Data) + 7 +} + +func (msg *DT1) MarshalTo(b []byte) error { + b[0] = uint8(msg.Type) + copy(b[1:4], utils.Uint32To24(msg.DestinationLocalReference)) + b[4] = msg.Segmenting + b[5] = 1 + b[6] = byte(len(msg.Data)) + copy(b[7:], msg.Data) + return nil +} + +func (msg *DT1) String() string { + return fmt.Sprintf("{Type: DT1, DataLength: %d, Data: %s}", len(msg.Data), hex.EncodeToString(msg.Data)) +} + +// MessageType returns the Message Type in int. +func (msg *DT1) MessageType() MsgType { + return msg.Type +} + +func (msg *DT1) MessageTypeName() string { + return "DT1" +} diff --git a/dt1_test.go b/dt1_test.go new file mode 100644 index 0000000..eecc333 --- /dev/null +++ b/dt1_test.go @@ -0,0 +1,35 @@ +package sccp + +import ( + "bytes" + "encoding/hex" + "fmt" + "testing" +) + +var mockDT1s = [][]byte{ + {0x6, 0x3, 0x20, 0x48, 0x0, 0x1, 0x14, 0x0, 0x12, 0x55, 0x20, 0xd, 0x6, 0x32, 0x17, 0x9, 0x83, 0x96, 0x80, 0x3, 0x56, 0x83, 0x84, 0x18, 0xf5, 0x2c, 0x2}, + {0x6, 0x0, 0x80, 0x2a, 0x0, 0x1, 0x3, 0x0, 0x1, 0x21}, + {0x6, 0x3, 0x20, 0x48, 0x0, 0x1, 0x5, 0x1, 0x80, 0x2, 0x5, 0x9b}, + {0x6, 0x0, 0x70, 0x3e, 0x0, 0x1, 0x37, 0x0, 0x35, 0x12, 0x17, 0x15, 0x6, 0x2b, 0xb7, 0x40, 0xb, 0xe2, 0x40, 0x1a, 0x5, 0xd0, 0x63, 0x41, 0x91, 0x3, 0x6, 0x28, 0x95, 0xa, 0x43, 0x91, 0x44, 0x21, 0x98, 0x2c, 0x2, 0x40, 0x21, 0x7c, 0x6, 0x7, 0x6, 0xc5, 0xca, 0x1b, 0x2a, 0x7d, 0x8, 0x83, 0x97, 0x57, 0x84, 0x17, 0x7, 0x80, 0x81, 0x7e, 0x3, 0x83, 0xff, 0x57}, +} + +func TestDT1(t *testing.T) { + for i, v := range mockDT1s { + dt1, err := ParseDT1(v) + if err != nil { + t.Fatal(i, err) + } + b, err := dt1.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(dt1) + } +} diff --git a/params/connection-oriented.go b/params/connection-oriented.go new file mode 100644 index 0000000..a4298c5 --- /dev/null +++ b/params/connection-oriented.go @@ -0,0 +1,49 @@ +package params + +import ( + "io" +) + +const ( + DataTag uint8 = 0x0F + CdPtyAddrTag uint8 = 0x03 + CgPtyAddrTag uint8 = 0x04 +) + +type Optional struct { + Tag uint8 + Len uint8 + Value []byte +} + +func ParseOptional(b []byte) ([]*Optional, error) { + p := uint8(0) + opts := make([]*Optional, 0) + for p < uint8(len(b)) { + t := b[p] + + if t == 0 { + return opts, nil + } + if (p + 1) >= uint8(len(b)) { + return nil, io.ErrUnexpectedEOF + } + + l := b[p+1] + if (p + 1 + l) >= uint8(len(b)) { + return nil, io.ErrUnexpectedEOF + } + + o := &Optional{ + Tag: t, + Len: l, + Value: b[p+2 : p+2+l], + } + + opts = append(opts, o) + p += 2 + l + + } + + return opts, nil +} diff --git a/rlc.go b/rlc.go new file mode 100644 index 0000000..36103b6 --- /dev/null +++ b/rlc.go @@ -0,0 +1,67 @@ +package sccp + +import ( + "io" + + "github.com/wmnsk/go-sccp/utils" +) + +type RLC struct { + Type MsgType + DestinationLocalReference uint32 + SourceLocalReference uint32 +} + +func ParseRLC(b []byte) (*RLC, error) { + msg := &RLC{} + if err := msg.UnmarshalBinary(b); err != nil { + return nil, err + } + + return msg, nil +} + +func (msg *RLC) UnmarshalBinary(b []byte) error { + l := uint8(len(b)) + if l != 7 { + return io.ErrUnexpectedEOF + } + + msg.Type = MsgType(b[0]) + msg.DestinationLocalReference = utils.Uint24To32(b[1:4]) + msg.SourceLocalReference = utils.Uint24To32(b[4:]) + return nil +} + +func (msg *RLC) MarshalBinary() ([]byte, error) { + b := make([]byte, msg.MarshalLen()) + if err := msg.MarshalTo(b); err != nil { + return nil, err + } + + return b, nil +} + +func (msg *RLC) MarshalLen() int { + return 7 +} + +func (msg *RLC) MarshalTo(b []byte) error { + b[0] = uint8(msg.Type) + copy((b[1:4]), utils.Uint32To24(msg.DestinationLocalReference)) + copy(b[4:], utils.Uint32To24(msg.SourceLocalReference)) + return nil +} + +func (msg *RLC) String() string { + return "{Type: RLC}" +} + +// MessageType returns the Message Type in int. +func (msg *RLC) MessageType() MsgType { + return msg.Type +} + +func (msg *RLC) MessageTypeName() string { + return "RLC" +} diff --git a/rlc_test.go b/rlc_test.go new file mode 100644 index 0000000..ccc8458 --- /dev/null +++ b/rlc_test.go @@ -0,0 +1,28 @@ +package sccp + +import ( + "bytes" + "encoding/hex" + "fmt" + "testing" +) + +var mockRLC = []byte{0x5, 0x0, 0x70, 0x3e, 0x0, 0x0, 0x5} + +func TestRLC(t *testing.T) { + r, err := ParseRLC(mockRLC) + if err != nil { + t.Fatal(err) + } + b, err := r.MarshalBinary() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(mockRLC, b) { + fmt.Println(hex.EncodeToString(mockRLC)) + fmt.Println(hex.EncodeToString(b)) + + t.Fatal(err) + } + fmt.Println(r) +} diff --git a/sccp.go b/sccp.go index 728e1c2..af972fc 100644 --- a/sccp.go +++ b/sccp.go @@ -59,10 +59,11 @@ type Message interface { func ParseMessage(b []byte) (Message, error) { var m Message switch MsgType(b[0]) { - /* TODO: implement! - case CR: - case CC: - case CREF: + case MsgTypeCR: + m = &CR{} + case MsgTypeCC: + m = &CC{} + /* TODO: implement! case CREF: case RLSD: case RLC: case DT1: