diff --git a/README.md b/README.md index 3cca3c8..8d6066f 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Run `go mod tidy` to download the dependency, and you're ready to start developi | Reset confirm | RSC | 4.15 | - | | Protocol data unit error | ERR | 4.16 | - | | Inactivity test | IT | 4.17 | - | -| Extended unitdata | XUDT | 4.18 | - | +| Extended unitdata | XUDT | 4.18 | Yes | | Extended unitdata service | XUDTS | 4.19 | - | | Long unitdata | LUDT | 4.20 | - | | Long unitdata service | LUDTS | 4.21 | - | @@ -46,30 +46,30 @@ Run `go mod tidy` to download the dependency, and you're ready to start developi | Parameter name | Reference | Supported? | | --------------------------- | --------- | ---------- | -| End of optional parameters | 3.1 | | -| Destination local reference | 3.2 | | -| Source local reference | 3.3 | | +| End of optional parameters | 3.1 | Yes | +| Destination local reference | 3.2 | Yes | +| Source local reference | 3.3 | Yes | | Called party address | 3.4 | Yes | | Calling party address | 3.5 | Yes | | Protocol class | 3.6 | Yes | -| Segmenting/reassembling | 3.7 | | -| Receive sequence number | 3.8 | | -| Sequencing/segmenting | 3.9 | | -| Credit | 3.10 | | -| Release cause | 3.11 | | -| Return cause | 3.12 | | -| Reset cause | 3.13 | | -| Error cause | 3.14 | | -| Refusal cause | 3.15 | | +| Segmenting/reassembling | 3.7 | Yes | +| Receive sequence number | 3.8 | Yes | +| Sequencing/segmenting | 3.9 | Yes | +| Credit | 3.10 | Yes | +| Release cause | 3.11 | Yes | +| Return cause | 3.12 | Yes | +| Reset cause | 3.13 | Yes | +| Error cause | 3.14 | Yes | +| Refusal cause | 3.15 | Yes | | Data | 3.16 | Yes | -| Segmentation | 3.17 | | -| Hop counter | 3.18 | | -| Importance | 3.19 | | -| Long data | 3.20 | | +| Segmentation | 3.17 | Yes | +| Hop counter | 3.18 | Yes | +| Importance | 3.19 | Yes | +| Long data | 3.20 | Yes | ## Author(s) -Yoshiyuki Kurauchi ([Website](https://wmnsk.com/)) +Yoshiyuki Kurauchi ([Website](https://wmnsk.com/)) and [contributors](https://github.com/wmnsk/go-sccp/graphs/contributors). ## LICENSE diff --git a/constant_string.go b/constant_string.go new file mode 100644 index 0000000..c69a3e3 --- /dev/null +++ b/constant_string.go @@ -0,0 +1,66 @@ +// Code generated by "stringer -type MsgType,SCMGType -linecomment -output constant_string.go"; DO NOT EDIT. + +package sccp + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[MsgTypeCR-1] + _ = x[MsgTypeCC-2] + _ = x[MsgTypeCREF-3] + _ = x[MsgTypeRLSD-4] + _ = x[MsgTypeRLC-5] + _ = x[MsgTypeDT1-6] + _ = x[MsgTypeDT2-7] + _ = x[MsgTypeAK-8] + _ = x[MsgTypeUDT-9] + _ = x[MsgTypeUDTS-10] + _ = x[MsgTypeED-11] + _ = x[MsgTypeEA-12] + _ = x[MsgTypeRSR-13] + _ = x[MsgTypeRSC-14] + _ = x[MsgTypeERR-15] + _ = x[MsgTypeIT-16] + _ = x[MsgTypeXUDT-17] + _ = x[MsgTypeXUDTS-18] + _ = x[MsgTypeLUDT-19] + _ = x[MsgTypeLUDTS-20] +} + +const _MsgType_name = "CRCCCREFRLSDRLCDT1DT2AKUDTUDTSEDEARSRRSCERRITXUDTXUDTSLUDTLUDTS" + +var _MsgType_index = [...]uint8{0, 2, 4, 8, 12, 15, 18, 21, 23, 26, 30, 32, 34, 37, 40, 43, 45, 49, 54, 58, 63} + +func (i MsgType) String() string { + i -= 1 + if i >= MsgType(len(_MsgType_index)-1) { + return "MsgType(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _MsgType_name[_MsgType_index[i]:_MsgType_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[SCMGTypeSSA-1] + _ = x[SCMGTypeSSP-2] + _ = x[SCMGTypeSST-3] + _ = x[SCMGTypeSOR-4] + _ = x[SCMGTypeSOG-5] + _ = x[SCMGTypeSSC-6] +} + +const _SCMGType_name = "SSASSPSSTSORSOGSSC" + +var _SCMGType_index = [...]uint8{0, 3, 6, 9, 12, 15, 18} + +func (i SCMGType) String() string { + i -= 1 + if i >= SCMGType(len(_SCMGType_index)-1) { + return "SCMGType(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _SCMGType_name[_SCMGType_index[i]:_SCMGType_index[i+1]] +} diff --git a/errors.go b/errors.go index 13f3fd5..6aefde4 100644 --- a/errors.go +++ b/errors.go @@ -9,7 +9,7 @@ import ( ) // UnsupportedTypeError indicates the value in Version field is invalid. -type UnsupportedTypeError byte +type UnsupportedTypeError uint8 // Error returns the type of receiver and some additional message. func (e UnsupportedTypeError) Error() string { diff --git a/examples/client/simple-client.go b/examples/client/simple-client.go index 3ec3b8a..0cae3d4 100644 --- a/examples/client/simple-client.go +++ b/examples/client/simple-client.go @@ -72,51 +72,60 @@ func main() { log.Fatal(err) } - cd, err := utils.EncodeBCD("1234567890123456") - if err != nil { - log.Fatal(err) - } - cg, err := utils.EncodeBCD("9876543210") - if err != nil { - log.Fatal(err) - } - gti := params.GTITTNPESNAI ai := params.NewAddressIndicator(false, true, false, gti) - cdPA := params.NewPartyAddressTyped( + cdPA := params.NewCalledPartyAddress( ai, 0, 6, params.NewGlobalTitle( gti, params.TranslationType(0), params.NPISDNTelephony, params.ESBCDOdd, params.NAIInternationalNumber, - cd, + utils.MustBCDEncode("1234567890123456"), ), ) - cgPA := params.NewPartyAddressTyped( + cgPA := params.NewCallingPartyAddress( ai, 0, 7, params.NewGlobalTitle( gti, params.TranslationType(1), params.NPISDNMobile, params.ESBCDOdd, params.NAIInternationalNumber, - cg, + utils.MustBCDEncode("9876543210"), ), ) // create UDT message with CdPA, CgPA and payload - udt, err := sccp.NewUDT( + udt := sccp.NewUDT( 1, // Protocol Class true, // Message handling cdPA, cgPA, payload, // payload - ).MarshalBinary() + ) + u, err := udt.MarshalBinary() + if err != nil { + log.Fatal(err) + } + + xudt := sccp.NewXUDT( + 1, // Protocol Class + true, // Message handling + 2, // Hop Counter + cdPA, + cgPA, + payload, // payload + params.NewSegmentation(true, 1, 2, 0x123456), + params.NewImportance(10), + ) + x, err := xudt.MarshalBinary() if err != nil { log.Fatal(err) } // send once - if _, err := m3conn.Write(udt); err != nil { + i := 1 + log.Printf("Sending %04d: %v", i, udt) + if _, err := m3conn.Write(u); err != nil { log.Fatal(err) } @@ -129,7 +138,17 @@ func main() { ticker.Stop() os.Exit(1) case <-ticker.C: - if _, err := m3conn.Write(udt); err != nil { + i++ + + var msg sccp.Message = udt + b := u + if i%2 == 0 { + msg = xudt + b = x + } + + log.Printf("Sending %04d: %v", i, msg) + if _, err := m3conn.Write(b); err != nil { log.Fatal(err) } } diff --git a/examples/server/receiver.go b/examples/server/receiver.go new file mode 100644 index 0000000..e6a92b0 --- /dev/null +++ b/examples/server/receiver.go @@ -0,0 +1,108 @@ +// Copyright 2018-2024 go-sccp authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +/* +Command receiver receives SCCP messages from the client and prints them out. +*/ +package main + +import ( + "context" + "errors" + "flag" + "io" + "log" + "net" + "time" + + "github.com/wmnsk/go-m3ua/messages/params" + "github.com/wmnsk/go-sccp" + + "github.com/ishidawataru/sctp" + "github.com/wmnsk/go-m3ua" +) + +func serve(conn *m3ua.Conn) { + defer conn.Close() + + buf := make([]byte, 1500) + for { + n, err := conn.Read(buf) + if err != nil { + if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) { + log.Printf("Closed M3UA conn with: %s, waiting to come back on", conn.RemoteAddr()) + return + } + log.Printf("Error reading from M3UA conn: %s", err) + return + } + + b := make([]byte, n) + copy(b, buf[:n]) + go func() { + msg, err := sccp.ParseMessage(b) + if err != nil { + log.Printf("Failed to parse SCCP message: %s, %x", err, b) + return + } + + log.Printf("Received SCCP message: %v", msg) + }() + } +} + +func main() { + var ( + addr = flag.String("addr", "127.0.0.1:2905", "Source IP and Port listen.") + ) + flag.Parse() + + // see go-m3ua for the details of the configuration. + // https://github.com/wmnsk/go-m3ua + config := m3ua.NewServerConfig( + &m3ua.HeartbeatInfo{ + Enabled: true, + Interval: 0, + Timer: time.Duration(5 * time.Second), + }, + 0x22222222, // OriginatingPointCode + 0x11111111, // DestinationPointCode + 1, // AspIdentifier + params.TrafficModeLoadshare, // TrafficModeType + 0, // NetworkAppearance + 0, // CorrelationID + []uint32{1, 2}, // RoutingContexts + params.ServiceIndSCCP, // ServiceIndicator + 0, // NetworkIndicator + 0, // MessagePriority + 1, // SignalingLinkSelection + ) + config.AspIdentifier = nil + config.CorrelationID = nil + + laddr, err := sctp.ResolveSCTPAddr("sctp", *addr) + if err != nil { + log.Fatalf("Failed to resolve SCTP address: %s", err) + } + + listener, err := m3ua.Listen("m3ua", laddr, config) + if err != nil { + log.Fatalf("Failed to listen: %s", err) + } + log.Printf("Waiting for connection on: %s", listener.Addr()) + + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + for { + conn, err := listener.Accept(ctx) + if err != nil { + log.Fatalf("Failed to accept M3UA: %s", err) + } + log.Printf("Connected with: %s", conn.RemoteAddr()) + + go serve(conn) + } +} diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..135164e --- /dev/null +++ b/logger.go @@ -0,0 +1,69 @@ +// Copyright 2019-2024 go-sccp authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +package sccp + +import ( + "io" + "log" + "os" + "sync" +) + +var ( + logger = log.New(os.Stderr, "", log.LstdFlags) + logMu sync.Mutex +) + +// SetLogger replaces the standard logger with arbitrary *log.Logger. +// +// This package prints just informational logs from goroutines working background +// that might help developers test the program but can be ignored safely. More +// important ones that needs any action by caller would be returned as errors. +func SetLogger(l *log.Logger) { + if l == nil { + log.Println("Don't pass nil to SetLogger: use DisableLogging instead.") + } + + setLogger(l) +} + +// EnableLogging enables the logging from the package. +// If l is nil, it uses default logger provided by the package. +// Logging is enabled by default. +// +// See also: SetLogger. +func EnableLogging(l *log.Logger) { + logMu.Lock() + defer logMu.Unlock() + + setLogger(l) +} + +// DisableLogging disables the logging from the package. +// Logging is enabled by default. +func DisableLogging() { + logMu.Lock() + defer logMu.Unlock() + + logger.SetOutput(io.Discard) +} + +func setLogger(l *log.Logger) { + if l == nil { + l = log.New(os.Stderr, "", log.LstdFlags) + } + + logMu.Lock() + defer logMu.Unlock() + + logger = l +} + +func logf(format string, v ...interface{}) { + logMu.Lock() + defer logMu.Unlock() + + logger.Printf(format, v...) +} diff --git a/params/constant_string.go b/params/constant_string.go new file mode 100644 index 0000000..c7a2bc6 --- /dev/null +++ b/params/constant_string.go @@ -0,0 +1,345 @@ +// Code generated by "stringer -type ParameterNameCode,ParameterType,ReleaseCauseValue,ReturnCauseValue,ResetCauseValue,ErrorCauseValue,RefusalCauseValue,GlobalTitleIndicator,NatureOfAddressIndicator,NumberingPlan,EncodingScheme -linecomment -output constant_string.go"; DO NOT EDIT. + +package params + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[PCodeEndOfOptionalParameters-0] + _ = x[PCodeDestinationLocalReference-1] + _ = x[PCodeSourceLocalReference-2] + _ = x[PCodeCalledPartyAddress-3] + _ = x[PCodeCallingPartyAddress-4] + _ = x[PCodeProtocolClass-5] + _ = x[PCodeSegmentingReassembling-6] + _ = x[PCodeReceiveSequenceNumber-7] + _ = x[PCodeSequencingSegmenting-8] + _ = x[PCodeCredit-9] + _ = x[PCodeReleaseCause-10] + _ = x[PCodeReturnCause-11] + _ = x[PCodeResetCause-12] + _ = x[PCodeErrorCause-13] + _ = x[PCodeRefusalCause-14] + _ = x[PCodeData-15] + _ = x[PCodeSegmentation-16] + _ = x[PCodeHopCounter-17] + _ = x[PCodeImportance-18] + _ = x[PCodeLongData-19] +} + +const _ParameterNameCode_name = "End of optional parametersDestination local referenceSource local referenceCalled party addressCalling party addressProtocol classSegmenting/reassemblingReceive sequence numberSequencing/segmentingCreditRelease causeReturn causeReset causeError causeRefusal causeDataSegmentationHop CounterImportanceLong data" + +var _ParameterNameCode_index = [...]uint16{0, 26, 53, 75, 95, 116, 130, 153, 176, 197, 203, 216, 228, 239, 250, 263, 267, 279, 290, 300, 309} + +func (i ParameterNameCode) String() string { + if i >= ParameterNameCode(len(_ParameterNameCode_index)-1) { + return "ParameterNameCode(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ParameterNameCode_name[_ParameterNameCode_index[i]:_ParameterNameCode_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[PTypeF-0] + _ = x[PTypeV-1] + _ = x[PTypeO-2] +} + +const _ParameterType_name = "FVO" + +var _ParameterType_index = [...]uint8{0, 1, 2, 3} + +func (i ParameterType) String() string { + if i >= ParameterType(len(_ParameterType_index)-1) { + return "ParameterType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ParameterType_name[_ParameterType_index[i]:_ParameterType_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ReleaseCauseEndUserOriginated-0] + _ = x[ReleaseCauseEndUserCongestion-1] + _ = x[ReleaseCauseEndUserFailure-2] + _ = x[ReleaseCauseSCCPUserOriginated-3] + _ = x[ReleaseCauseRemoteProcedureError-4] + _ = x[ReleaseCauseInconsistentConnectionData-5] + _ = x[ReleaseCauseAccessFailure-6] + _ = x[ReleaseCauseAccessCongestion-7] + _ = x[ReleaseCauseSubsystemFailure-8] + _ = x[ReleaseCauseSubsystemCongestion-9] + _ = x[ReleaseCauseMTPFailure-10] + _ = x[ReleaseCauseNetworkCongestion-11] + _ = x[ReleaseCauseExpirationOfResetTimer-12] + _ = x[ReleaseCauseExpirationOfReceiveInactivityTimer-13] + _ = x[ReleaseCauseUnqualified-15] + _ = x[ReleaseCauseSCCPFailure-16] +} + +const ( + _ReleaseCauseValue_name_0 = "end user originatedend user congestionend user failureSCCP user originatedremote procedure errorinconsistent connection dataaccess failureaccess congestionsubsystem failuresubsystem congestionMTP failurenetwork congestionexpiration of reset timerexpiration of receive inactivity timer" + _ReleaseCauseValue_name_1 = "unqualifiedSCCP failure" +) + +var ( + _ReleaseCauseValue_index_0 = [...]uint16{0, 19, 38, 54, 74, 96, 124, 138, 155, 172, 192, 203, 221, 246, 284} + _ReleaseCauseValue_index_1 = [...]uint8{0, 11, 23} +) + +func (i ReleaseCauseValue) String() string { + switch { + case i <= 13: + return _ReleaseCauseValue_name_0[_ReleaseCauseValue_index_0[i]:_ReleaseCauseValue_index_0[i+1]] + case 15 <= i && i <= 16: + i -= 15 + return _ReleaseCauseValue_name_1[_ReleaseCauseValue_index_1[i]:_ReleaseCauseValue_index_1[i+1]] + default: + return "ReleaseCauseValue(" + strconv.FormatInt(int64(i), 10) + ")" + } +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ReturnCauseNoTranslationForAnAddressOfSuchNature-0] + _ = x[ReturnCauseNoTranslationForThisSpecificAddress-1] + _ = x[ReturnCauseSubsystemCongestion-2] + _ = x[ReturnCauseSubsystemFailure-3] + _ = x[ReturnCauseUnequippedUser-4] + _ = x[ReturnCauseMTPFailure-5] + _ = x[ReturnCauseNetworkCongestion-6] + _ = x[ReturnCauseUnqualified-7] + _ = x[ReturnCauseErrorInMessageTransport-8] + _ = x[ReturnCauseErrorInLocalProcessing-9] + _ = x[ReturnCauseDestinationCannotPerformReassembly-10] + _ = x[ReturnCauseSCCPFailure-11] + _ = x[ReturnCauseHopCounterViolation-12] + _ = x[ReturnCauseSegmentationNotSupported-13] + _ = x[ReturnCauseSegmentationFailure-14] +} + +const _ReturnCauseValue_name = "no translation for an address of such natureno translation for this specific addresssubsystem congestionsubsystem failureunequipped userMTP failurenetwork congestionunqualifiederror in message transporterror in local processingdestination cannot perform reassemblySCCP failurehop counter violationsegmentation not supportedsegmentation failure" + +var _ReturnCauseValue_index = [...]uint16{0, 44, 84, 104, 121, 136, 147, 165, 176, 202, 227, 264, 276, 297, 323, 343} + +func (i ReturnCauseValue) String() string { + if i >= ReturnCauseValue(len(_ReturnCauseValue_index)-1) { + return "ReturnCauseValue(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ReturnCauseValue_name[_ReturnCauseValue_index[i]:_ReturnCauseValue_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ResetCauseEndUserOriginated-0] + _ = x[ResetCauseSCCPUserOriginated-1] + _ = x[ResetCauseMessageOutOfOrderIncorrectSendSequenceNumber-2] + _ = x[ResetCauseMessageOutOfOrderIncorrectReceiveSequenceNumber-3] + _ = x[ResetCauseRemoteProcedureErrorMessageOutOfWindow-4] + _ = x[ResetCauseRemoteProcedureErrorIncorrectSendSequenceNumberAfterReinitialization-5] + _ = x[ResetCauseRemoteProcedureErrorGeneral-6] + _ = x[ResetCauseRemoteEndUserOperational-7] + _ = x[ResetCauseNetworkOperational-8] + _ = x[ResetCauseAccessOperational-9] + _ = x[ResetCauseNetworkCongestion-10] + _ = x[ResetCauseUnqualified-12] +} + +const ( + _ResetCauseValue_name_0 = "end user originatedSCCP user originatedmessage out of order - incorrect P(S)message out of order - incorrect P(R)remote procedure error - message out of windowremote procedure error - incorrect P(S) after (re)initializationremote procedure error - generalremote end user operationalnetwork operationalaccess operationalnetwork congestion" + _ResetCauseValue_name_1 = "unqualified" +) + +var ( + _ResetCauseValue_index_0 = [...]uint16{0, 19, 39, 76, 113, 159, 223, 255, 282, 301, 319, 337} +) + +func (i ResetCauseValue) String() string { + switch { + case i <= 10: + return _ResetCauseValue_name_0[_ResetCauseValue_index_0[i]:_ResetCauseValue_index_0[i+1]] + case i == 12: + return _ResetCauseValue_name_1 + default: + return "ResetCauseValue(" + strconv.FormatInt(int64(i), 10) + ")" + } +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ErrorCauseLocalReferenceNumberMismatchUnassignedDestinationLRN-0] + _ = x[ErrorCauseLocalReferenceNumberMismatchInconsistentSourceLRN-1] + _ = x[ErrorCausePointCodeMismatch-2] + _ = x[ErrorCauseServiceClassMismatch-3] + _ = x[ErrorCauseUnqualified-4] +} + +const _ErrorCauseValue_name = "local reference number (LRN) mismatch - unassigned destination LRNlocal reference number (LRN) mismatch - inconsistent source LRNpoint code mismatchservice class mismatchunqualified" + +var _ErrorCauseValue_index = [...]uint8{0, 66, 129, 148, 170, 181} + +func (i ErrorCauseValue) String() string { + if i >= ErrorCauseValue(len(_ErrorCauseValue_index)-1) { + return "ErrorCauseValue(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ErrorCauseValue_name[_ErrorCauseValue_index[i]:_ErrorCauseValue_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[RefusalCauseEndUserOriginated-0] + _ = x[RefusalCauseEndUserCongestion-1] + _ = x[RefusalCauseEndUserFailure-2] + _ = x[RefusalCauseSCCPUserOriginated-3] + _ = x[RefusalCauseDestinationAddressUnknown-4] + _ = x[RefusalCauseDestinationInaccessible-5] + _ = x[RefusalCauseNetworkResourceQoSNotAvailableNonTransient-6] + _ = x[RefusalCauseNetworkResourceQoSNotAvailableTransient-7] + _ = x[RefusalCauseAccessFailure-8] + _ = x[RefusalCauseAccessCongestion-9] + _ = x[RefusalCauseSubsystemFailure-10] + _ = x[RefusalCauseSubsystemCongestion-11] + _ = x[RefusalCauseExpirationOfTheConnectionEstablishmentTimer-12] + _ = x[RefusalCauseIncompatibleUserData-13] + _ = x[RefusalCauseUnqualified-15] + _ = x[RefusalCauseHopCounterViolation-16] + _ = x[RefusalCauseSCCPFailure-17] + _ = x[RefusalCauseNoTranslationForAnAddressOfSuchNature-18] + _ = x[RefusalCauseUnequippedUser-19] +} + +const ( + _RefusalCauseValue_name_0 = "end user originatedend user congestionend user failureSCCP user originateddestination address unknowndestination inaccessiblenetwork resource - QoS not available/non-transientnetwork resource - QoS not available/transientaccess failureaccess congestionsubsystem failuresubsystem congestionexpiration of the connection establishment timerincompatible user data" + _RefusalCauseValue_name_1 = "unqualifiedhop counter violationSCCP failureno translation for an address of such natureunequipped user" +) + +var ( + _RefusalCauseValue_index_0 = [...]uint16{0, 19, 38, 54, 74, 101, 125, 175, 221, 235, 252, 269, 289, 337, 359} + _RefusalCauseValue_index_1 = [...]uint8{0, 11, 32, 44, 88, 103} +) + +func (i RefusalCauseValue) String() string { + switch { + case i <= 13: + return _RefusalCauseValue_name_0[_RefusalCauseValue_index_0[i]:_RefusalCauseValue_index_0[i+1]] + case 15 <= i && i <= 19: + i -= 15 + return _RefusalCauseValue_name_1[_RefusalCauseValue_index_1[i]:_RefusalCauseValue_index_1[i+1]] + default: + return "RefusalCauseValue(" + strconv.FormatInt(int64(i), 10) + ")" + } +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[GTINoGT-0] + _ = x[GTINAIOnly-1] + _ = x[GTITTOnly-2] + _ = x[GTITTNPES-3] + _ = x[GTITTNPESNAI-4] +} + +const _GlobalTitleIndicator_name = "no global title includedglobal title includes nature of address indicator onlyglobal title includes translation type onlyglobal title includes translation type, numbering plan, and encoding schemeglobal title includes translation type, numbering plan, encoding scheme, and nature of address indicator" + +var _GlobalTitleIndicator_index = [...]uint16{0, 24, 78, 121, 196, 300} + +func (i GlobalTitleIndicator) String() string { + if i >= GlobalTitleIndicator(len(_GlobalTitleIndicator_index)-1) { + return "GlobalTitleIndicator(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _GlobalTitleIndicator_name[_GlobalTitleIndicator_index[i]:_GlobalTitleIndicator_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[NAIUnknown-0] + _ = x[NAISubscriberNumber-1] + _ = x[NAINationalSignificantNumber-3] + _ = x[NAIInternationalNumber-4] +} + +const ( + _NatureOfAddressIndicator_name_0 = "unknownsubscriber number" + _NatureOfAddressIndicator_name_1 = "national significant numberinternational number" +) + +var ( + _NatureOfAddressIndicator_index_0 = [...]uint8{0, 7, 24} + _NatureOfAddressIndicator_index_1 = [...]uint8{0, 27, 47} +) + +func (i NatureOfAddressIndicator) String() string { + switch { + case i <= 1: + return _NatureOfAddressIndicator_name_0[_NatureOfAddressIndicator_index_0[i]:_NatureOfAddressIndicator_index_0[i+1]] + case 3 <= i && i <= 4: + i -= 3 + return _NatureOfAddressIndicator_name_1[_NatureOfAddressIndicator_index_1[i]:_NatureOfAddressIndicator_index_1[i+1]] + default: + return "NatureOfAddressIndicator(" + strconv.FormatInt(int64(i), 10) + ")" + } +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[NPUnknown-0] + _ = x[NPISDNTelephony-1] + _ = x[NPGeneric-2] + _ = x[NPData-3] + _ = x[NPTelex-4] + _ = x[NPMaritimeMobile-5] + _ = x[NPLandMobile-6] + _ = x[NPISDNMobile-7] + _ = x[NPPrivate-14] +} + +const ( + _NumberingPlan_name_0 = "unknownISDN/telephony numbering plangeneric numbering plandata numbering plantelex numbering planmaritime mobile numbering planland mobile numbering planISDN/mobile numbering plan" + _NumberingPlan_name_1 = "private network or network-specific numbering plan" +) + +var ( + _NumberingPlan_index_0 = [...]uint8{0, 7, 36, 58, 77, 97, 127, 153, 179} +) + +func (i NumberingPlan) String() string { + switch { + case i <= 7: + return _NumberingPlan_name_0[_NumberingPlan_index_0[i]:_NumberingPlan_index_0[i+1]] + case i == 14: + return _NumberingPlan_name_1 + default: + return "NumberingPlan(" + strconv.FormatInt(int64(i), 10) + ")" + } +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ESUnknown-0] + _ = x[ESBCDOdd-1] + _ = x[ESBCDEven-2] + _ = x[ESNationalSpecific-3] +} + +const _EncodingScheme_name = "unknownBCD, odd number of digitsBCD, even number of digitsnational specific" + +var _EncodingScheme_index = [...]uint8{0, 7, 32, 58, 75} + +func (i EncodingScheme) String() string { + if i >= EncodingScheme(len(_EncodingScheme_index)-1) { + return "EncodingScheme(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _EncodingScheme_name[_EncodingScheme_index[i]:_EncodingScheme_index[i+1]] +} diff --git a/params/global-title.go b/params/global-title.go index 86b8dcf..42a7a62 100644 --- a/params/global-title.go +++ b/params/global-title.go @@ -3,6 +3,8 @@ package params import ( "fmt" "io" + + "github.com/wmnsk/go-sccp/utils" ) // GlobalTitle is a GlobalTitle inside the Called/Calling Party Address. @@ -21,11 +23,13 @@ type GlobalTitle struct { // See Q.713 3.4.1 for more details. type GlobalTitleIndicator uint8 +// GlobalTitleIndicator values. const ( - GTINAIOnly GlobalTitleIndicator = 0b0001 - GTITTOnly GlobalTitleIndicator = 0b0010 - GTITTNPES GlobalTitleIndicator = 0b0011 - GTITTNPESNAI GlobalTitleIndicator = 0b0100 + GTINoGT GlobalTitleIndicator = 0b0000 // no global title included + GTINAIOnly GlobalTitleIndicator = 0b0001 // global title includes nature of address indicator only + GTITTOnly GlobalTitleIndicator = 0b0010 // global title includes translation type only + GTITTNPES GlobalTitleIndicator = 0b0011 // global title includes translation type, numbering plan, and encoding scheme + GTITTNPESNAI GlobalTitleIndicator = 0b0100 // global title includes translation type, numbering plan, encoding scheme, and nature of address indicator ) // NatureOfAddressIndicator is a type of Nature of Address Indicator. @@ -33,11 +37,11 @@ type NatureOfAddressIndicator uint8 // NatureOfAddressIndicator values. const ( - NAIUnknown NatureOfAddressIndicator = 0b00000000 - NAISubscriberNumber NatureOfAddressIndicator = 0b00000001 - _ NatureOfAddressIndicator = 0b00000010 // Reserved for national use - NAINationalSignificantNumber NatureOfAddressIndicator = 0b00000011 - NAIInternationalNumber NatureOfAddressIndicator = 0b00000100 + NAIUnknown NatureOfAddressIndicator = 0b00000000 // unknown + NAISubscriberNumber NatureOfAddressIndicator = 0b00000001 // subscriber number + _ NatureOfAddressIndicator = 0b00000010 // reserved for national use + NAINationalSignificantNumber NatureOfAddressIndicator = 0b00000011 // national significant number + NAIInternationalNumber NatureOfAddressIndicator = 0b00000100 // international number ) // Even returns the NatureOfAddressIndicator with the last bit set to 0. @@ -58,15 +62,15 @@ type NumberingPlan uint8 // NumberingPlan values. const ( - NPUnknown NumberingPlan = 0b0000 - NPISDNTelephony NumberingPlan = 0b0001 - NPGeneric NumberingPlan = 0b0010 - NPData NumberingPlan = 0b0011 - NPTelex NumberingPlan = 0b0100 - NPMaritimeMobile NumberingPlan = 0b0101 - NPLandMobile NumberingPlan = 0b0110 - NPISDNMobile NumberingPlan = 0b0111 - NPPrivate NumberingPlan = 0b1110 + NPUnknown NumberingPlan = 0b0000 // unknown + NPISDNTelephony NumberingPlan = 0b0001 // ISDN/telephony numbering plan + NPGeneric NumberingPlan = 0b0010 // generic numbering plan + NPData NumberingPlan = 0b0011 // data numbering plan + NPTelex NumberingPlan = 0b0100 // telex numbering plan + NPMaritimeMobile NumberingPlan = 0b0101 // maritime mobile numbering plan + NPLandMobile NumberingPlan = 0b0110 // land mobile numbering plan + NPISDNMobile NumberingPlan = 0b0111 // ISDN/mobile numbering plan + NPPrivate NumberingPlan = 0b1110 // private network or network-specific numbering plan ) // EncodingScheme is a type of Encoding Scheme. @@ -74,10 +78,10 @@ type EncodingScheme uint8 // EncodingScheme values. const ( - ESUnknown EncodingScheme = 0b0000 - ESBCDOdd EncodingScheme = 0b0001 - ESBCDEven EncodingScheme = 0b0010 - ESNationalSpecific EncodingScheme = 0b0011 + ESUnknown EncodingScheme = 0b0000 // unknown + ESBCDOdd EncodingScheme = 0b0001 // BCD, odd number of digits + ESBCDEven EncodingScheme = 0b0010 // BCD, even number of digits + ESNationalSpecific EncodingScheme = 0b0011 // national specific ) // NewGlobalTitle creates a new GlobalTitle. @@ -114,6 +118,36 @@ func NewGlobalTitle( return gt } +// Write serializes GlobalTitle to the given byte sequence. +func (g *GlobalTitle) Write(b []byte) (int, error) { + l := g.MarshalLen() + if len(b) < l { + return 0, io.ErrUnexpectedEOF + } + + n := 0 + switch g.GTI { + case GTINAIOnly: + b[n] = uint8(g.NatureOfAddressIndicator) + n++ + case GTITTOnly: + b[n] = uint8(g.TranslationType) + n++ + case GTITTNPES: + b[n] = uint8(g.TranslationType) + b[n+1] = uint8(g.NumberingPlan)<<4 | uint8(g.EncodingScheme) + n += 2 + case GTITTNPESNAI: + b[n] = uint8(g.TranslationType) + b[n+1] = uint8(g.NumberingPlan)<<4 | uint8(g.EncodingScheme) + b[n+2] = uint8(g.NatureOfAddressIndicator) + n += 3 + } + + copy(b[n:l], g.AddressInformation) + return n, nil +} + // MarshalBinary returns the byte sequence generated from a GlobalTitle. func (g *GlobalTitle) MarshalBinary() []byte { b := make([]byte, g.MarshalLen()) @@ -126,29 +160,9 @@ func (g *GlobalTitle) MarshalBinary() []byte { // MarshalTo puts the byte sequence in the byte array given as b. func (g *GlobalTitle) MarshalTo(b []byte) error { - if len(b) < g.MarshalLen() { - return io.ErrUnexpectedEOF + if _, err := g.Write(b); err != nil { + return err } - offset := 0 - switch g.GTI { - case GTINAIOnly: - b[offset] = uint8(g.NatureOfAddressIndicator) - offset++ - case GTITTOnly: - b[offset] = uint8(g.TranslationType) - offset++ - case GTITTNPES: - b[offset] = uint8(g.TranslationType) - b[offset+1] = uint8(g.NumberingPlan)<<4 | uint8(g.EncodingScheme) - offset += 2 - case GTITTNPESNAI: - b[offset] = uint8(g.TranslationType) - b[offset+1] = uint8(g.NumberingPlan)<<4 | uint8(g.EncodingScheme) - b[offset+2] = uint8(g.NatureOfAddressIndicator) - offset += 3 - } - - copy(b[offset:g.MarshalLen()], g.AddressInformation) return nil } @@ -164,6 +178,41 @@ func ParseGlobalTitle(gti GlobalTitleIndicator, b []byte) (*GlobalTitle, error) return g, nil } +// Read sets the values retrieved from byte sequence in a GlobalTitle. +// +// Since GlobalTitle is a part of PartyAddress, and it does not know the length of the +// AddressInformation, it reads until the end of the given byte sequence. Thus, the +// caller should take care of the length of the byte sequence. +func (g *GlobalTitle) Read(b []byte) (int, error) { + if len(b) < g.lenByGTI() { + return 0, io.ErrUnexpectedEOF + } + + n := 0 + switch g.GTI { + case GTINAIOnly: + g.NatureOfAddressIndicator = NatureOfAddressIndicator(b[n]) + n++ + case GTITTOnly: + g.TranslationType = TranslationType(b[n]) + n++ + case GTITTNPES: + g.TranslationType = TranslationType(b[n]) + g.NumberingPlan = NumberingPlan(b[n+1] >> 4) + g.EncodingScheme = EncodingScheme(b[n+1] & 0x0F) + n += 2 + case GTITTNPESNAI: + g.TranslationType = TranslationType(b[n]) + g.NumberingPlan = NumberingPlan(b[n+1] >> 4) + g.EncodingScheme = EncodingScheme(b[n+1] & 0x0F) + g.NatureOfAddressIndicator = NatureOfAddressIndicator(b[n+2]) + n += 3 + } + + g.AddressInformation = b[n:] + return len(b), nil +} + // UnmarshalBinary sets the values retrieved from byte sequence in a GlobalTitle. // The given byte sequence should not include the excess bytes for the parent PartyAddress. // otherwise, AddressInformation will include them. @@ -222,9 +271,22 @@ func (g *GlobalTitle) lenByGTI() int { return l } +// IsOddDigits reports whether AddressInformation is odd number or not. +func (g *GlobalTitle) IsOddDigits() bool { + return g.EncodingScheme == ESBCDOdd +} + // String returns the GlobalTitle in a human-readable format. func (g *GlobalTitle) String() string { - return fmt.Sprintf("{GTI: %d, TransationType: %d, NumberingPlan: %d, EncodingScheme: %d, NatureOfAddressIndicator: %d, AddressInformation: %#x}", - g.GTI, g.TranslationType, g.NumberingPlan, g.EncodingScheme, g.NatureOfAddressIndicator, g.AddressInformation, + return fmt.Sprintf("{GTI: %#04b, TransationType: %d, NumberingPlan: %s, EncodingScheme: %s, NatureOfAddressIndicator: %s, AddressInformation: %s}", + g.GTI, g.TranslationType, g.NumberingPlan, g.EncodingScheme, g.NatureOfAddressIndicator, g.Address(), ) } + +// Address returns the AddressInformation in a human-friendly string. +func (g *GlobalTitle) Address() string { + if g.AddressInformation == nil { + return "" + } + return utils.BCDDecode(g.IsOddDigits(), g.AddressInformation) +} diff --git a/params/logger.go b/params/logger.go new file mode 100644 index 0000000..01cb570 --- /dev/null +++ b/params/logger.go @@ -0,0 +1,69 @@ +// Copyright 2019-2024 go-sccp authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +package params + +import ( + "io" + "log" + "os" + "sync" +) + +var ( + logger = log.New(os.Stderr, "", log.LstdFlags) + logMu sync.Mutex +) + +// SetLogger replaces the standard logger with arbitrary *log.Logger. +// +// This package prints just informational logs from goroutines working background +// that might help developers test the program but can be ignored safely. More +// important ones that needs any action by caller would be returned as errors. +func SetLogger(l *log.Logger) { + if l == nil { + log.Println("Don't pass nil to SetLogger: use DisableLogging instead.") + } + + setLogger(l) +} + +// EnableLogging enables the logging from the package. +// If l is nil, it uses default logger provided by the package. +// Logging is enabled by default. +// +// See also: SetLogger. +func EnableLogging(l *log.Logger) { + logMu.Lock() + defer logMu.Unlock() + + setLogger(l) +} + +// DisableLogging disables the logging from the package. +// Logging is enabled by default. +func DisableLogging() { + logMu.Lock() + defer logMu.Unlock() + + logger.SetOutput(io.Discard) +} + +func setLogger(l *log.Logger) { + if l == nil { + l = log.New(os.Stderr, "", log.LstdFlags) + } + + logMu.Lock() + defer logMu.Unlock() + + logger = l +} + +func logf(format string, v ...interface{}) { + logMu.Lock() + defer logMu.Unlock() + + logger.Printf(format, v...) +} diff --git a/params/params.go b/params/params.go new file mode 100644 index 0000000..fa42414 --- /dev/null +++ b/params/params.go @@ -0,0 +1,2022 @@ +// Copyright 2019-2024 go-sccp authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. +package params + +import ( + "encoding/binary" + "fmt" + "io" + + "github.com/wmnsk/go-sccp/utils" +) + +// UnsupportedParameterError indicates the value in Version field is invalid. +type UnsupportedParameterError uint8 + +// Error returns the type of receiver and some additional message. +func (e UnsupportedParameterError) Error() string { + return fmt.Sprintf("sccp: got unsupported type %d", e) +} + +// Parameter is an interface that all SCCP parameters have to implement. +type Parameter interface { + io.ReadWriter + MarshalLen() int + Code() ParameterNameCode + fmt.Stringer +} + +// ParameterType is a type for Parameter described in the tables in section 4 of Q.713. +type ParameterType uint8 + +// ParameterType values. +const ( + // F: mandatory fixed length parameter + PTypeF ParameterType = 0 // F + // V: mandatory variable length parameter + PTypeV ParameterType = 1 // V + // O: optional parameter of fixed or variable length + PTypeO ParameterType = 2 // O +) + +// ParameterNameCode is a type of Parameter Name Code defined in Q.713 Table 2. +type ParameterNameCode uint8 + +// ParameterNameCode values. +const ( + // O + PCodeEndOfOptionalParameters ParameterNameCode = 0b00000000 // End of optional parameters + // F + PCodeDestinationLocalReference ParameterNameCode = 0b00000001 // Destination local reference + // F + PCodeSourceLocalReference ParameterNameCode = 0b00000010 // Source local reference + // V, O + PCodeCalledPartyAddress ParameterNameCode = 0b00000011 // Called party address + // V, O + PCodeCallingPartyAddress ParameterNameCode = 0b00000100 // Calling party address + // F + PCodeProtocolClass ParameterNameCode = 0b00000101 // Protocol class + // F + PCodeSegmentingReassembling ParameterNameCode = 0b00000110 // Segmenting/reassembling + // F + PCodeReceiveSequenceNumber ParameterNameCode = 0b00000111 // Receive sequence number + // F + PCodeSequencingSegmenting ParameterNameCode = 0b00001000 // Sequencing/segmenting + // F, O + PCodeCredit ParameterNameCode = 0b00001001 // Credit + // F + PCodeReleaseCause ParameterNameCode = 0b00001010 // Release cause + // F + PCodeReturnCause ParameterNameCode = 0b00001011 // Return cause + // F + PCodeResetCause ParameterNameCode = 0b00001100 // Reset cause + // F + PCodeErrorCause ParameterNameCode = 0b00001101 // Error cause + // F + PCodeRefusalCause ParameterNameCode = 0b00001110 // Refusal cause + // V, O + PCodeData ParameterNameCode = 0b00001111 // Data + // O + PCodeSegmentation ParameterNameCode = 0b00010000 // Segmentation + // F, O + PCodeHopCounter ParameterNameCode = 0b00010001 // Hop Counter + // O + PCodeImportance ParameterNameCode = 0b00010010 // Importance + // V + PCodeLongData ParameterNameCode = 0b00010011 // Long data +) + +// ParseOptionalParameters parses optional parameters from the given byte sequence. +func ParseOptionalParameters(b []byte) ([]Parameter, int, error) { + var params []Parameter + var offset int + for len(b) > 0 { + p, n, err := ParseOptionalParameter(b[offset:]) + if err != nil { + return nil, offset, err + } + params = append(params, p) + if p.Code() == PCodeEndOfOptionalParameters { + break + } + offset += n + } + return params, offset, nil +} + +// ParseOptionalParameter parses a single optional parameter from the given byte sequence. +func ParseOptionalParameter(b []byte) (Parameter, int, error) { + if len(b) < 1 { + return nil, 0, io.ErrUnexpectedEOF + } + + var p Parameter + switch ParameterNameCode(b[0]) { + case PCodeEndOfOptionalParameters: + p = &EndOfOptionalParameters{paramType: PTypeO} + case PCodeCalledPartyAddress: + p = &PartyAddress{ + paramType: PTypeO, + code: PCodeCalledPartyAddress, + } + case PCodeCallingPartyAddress: + p = &PartyAddress{ + paramType: PTypeO, + code: PCodeCallingPartyAddress, + } + case PCodeCredit: + p = &Credit{paramType: PTypeO} + case PCodeData: + p = &Data{paramType: PTypeO} + case PCodeSegmentation: + p = &Segmentation{paramType: PTypeO} + case PCodeHopCounter: + p = &HopCounter{paramType: PTypeO} + case PCodeImportance: + p = &Importance{paramType: PTypeO} + default: + return nil, 0, UnsupportedParameterError(b[0]) + } + + n, err := p.Read(b) + if err != nil { + return nil, n, err + } + return p, n, nil +} + +/* +Specific Parameter implementations + +Each parameter should implement the following functions/methods: +- Constructor: NewParameterName creates a new ParameterName. +- Parser: ParseParameterName parses the given byte sequence as a ParameterName. +- Read: Read sets the values retrieved from byte sequence in a ParameterName. +- Write: Write serializes the ParameterName parameter and returns it as a byte slice. +- MarshalLen: MarshalLen returns the serial length of ParameterName. +- Code: Code returns the ParameterName in ParameterNameCode. +- Value: Value returns the ParameterName in specific type. +- String: String returns the ParameterName in string. +- Others (helpers): Optionally, each parameter can have helper functions to get specific values. + +*/ + +// EndOfOptionalParameters represents the End Of Optional Parameters. +type EndOfOptionalParameters struct { + paramType ParameterType + code ParameterNameCode + length int + value uint8 +} + +// NewEndOfOptionalParameters creates a new EndOfOptionalParameters. +func NewEndOfOptionalParameters() *EndOfOptionalParameters { + return &EndOfOptionalParameters{ + paramType: PTypeO, + code: PCodeEndOfOptionalParameters, + length: 1, + value: 0, + } +} + +// ParseEndOfOptionalParameters parses the given byte sequence as an EndOfOptionalParameters. +func ParseEndOfOptionalParameters(b []byte) (*EndOfOptionalParameters, int, error) { + e := &EndOfOptionalParameters{} + n, err := e.Read(b) + if err != nil { + return nil, n, err + } + + return e, n, nil +} + +// Read sets the values retrieved from byte sequence in a EndOfOptionalParameters. +func (e *EndOfOptionalParameters) Read(b []byte) (int, error) { + n := 1 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + e.paramType = PTypeO + e.code = PCodeEndOfOptionalParameters + e.length = n + e.value = b[0] + + if e.value != 0 { + logf("invalid parameter %s=%d: must be 0", e.code, e.value) + } + + return n, nil +} + +// Write serializes the EndOfOptionalParameters parameter and returns it as a byte slice. +func (e *EndOfOptionalParameters) Write(b []byte) (int, error) { + if len(b) < e.length { + return 0, io.ErrUnexpectedEOF + } + + b[0] = e.value + return e.length, nil +} + +// MarshalLen returns the serial length of EndOfOptionalParameters. +func (e *EndOfOptionalParameters) MarshalLen() int { + return e.length +} + +// Code returns the EndOfOptionalParameters in ParameterNameCode. +func (e *EndOfOptionalParameters) Code() ParameterNameCode { + return e.code +} + +// Value returns the EndOfOptionalParameters in uint8. +func (e *EndOfOptionalParameters) Value() uint8 { + return e.value +} + +// String returns the EndOfOptionalParameters in string. +func (e *EndOfOptionalParameters) String() string { + return fmt.Sprintf("{%s (%s): %d}", e.code, e.paramType, e.value) +} + +// LocalReference represents the Destination/Source Local Reference. +type LocalReference struct { + paramType ParameterType + code ParameterNameCode + length int + value []byte +} + +// NewLocalReference creates a new LocalReference. +// +// LocalReference parameter is three octets long. The fourth +// octet is masked out. +func NewLocalReference(c ParameterNameCode, v uint32) *LocalReference { + return &LocalReference{ + paramType: PTypeF, + code: c, + length: 3, + value: utils.Uint32To24(v), + } +} + +// NewDestinationLocalReference creates a new LocalReference for Destination. +func NewDestinationLocalReference(v uint32) *LocalReference { + return NewLocalReference(PCodeDestinationLocalReference, v) +} + +// NewSourceLocalReference creates a new LocalReference for Source. +func NewSourceLocalReference(v uint32) *LocalReference { + return NewLocalReference(PCodeSourceLocalReference, v) +} + +// ParseDestinationLocalReference parses the given byte sequence as a Destination Local Reference. +func ParseDestinationLocalReference(b []byte) (*LocalReference, int, error) { + return parseLocalReference(PCodeDestinationLocalReference, b) +} + +// ParseSourceLocalReference parses the given byte sequence as a Source Local Reference. +func ParseSourceLocalReference(b []byte) (*LocalReference, int, error) { + return parseLocalReference(PCodeSourceLocalReference, b) +} + +func parseLocalReference(c ParameterNameCode, b []byte) (*LocalReference, int, error) { + l := &LocalReference{ + code: c, + } + + n, err := l.Read(b) + if err != nil { + return nil, n, err + } + + return l, n, nil +} + +// Read sets the values retrieved from byte sequence in a LocalReference. +func (l *LocalReference) Read(b []byte) (int, error) { + n := 3 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + // code must be set by the calle, or use ParseDestination/SourceLocalReference. + l.length = n + l.value = b[:n] + return n, nil +} + +// Write serializes the LocalReference parameter and returns it as a byte slice. +func (l *LocalReference) Write(b []byte) (int, error) { + if len(b) < l.length { + return 0, io.ErrUnexpectedEOF + } + + copy(b, l.value) + return l.length, nil +} + +// MarshalLen returns the serial length of LocalReference. +func (l *LocalReference) MarshalLen() int { + return l.length +} + +// Code returns the LocalReference in ParameterNameCode. +func (l *LocalReference) Code() ParameterNameCode { + return l.code +} + +// Value returns the LocalReference in []byte. +func (l *LocalReference) Value() []byte { + return l.value +} + +// String returns the LocalReference in string. +func (l *LocalReference) String() string { + if l.code == PCodeDestinationLocalReference || l.code == PCodeSourceLocalReference { + return fmt.Sprintf("{%s (%s): %d}", l.code, l.paramType, l.Uint32()) + } + return fmt.Sprintf("{%s (%s): %d}", "(Destination or Source) local reference", l.paramType, l.Uint32()) +} + +// Uint32 returns the LocalReference in uint32. +func (l *LocalReference) Uint32() uint32 { + return utils.Uint24To32(l.value) +} + +// PartyAddress is a SCCP parameter that represents a Called/Calling Party Address. +type PartyAddress struct { + paramType ParameterType + code ParameterNameCode + length int + + Indicator uint8 + SignalingPointCode uint16 + SubsystemNumber uint8 + *GlobalTitle +} + +// NewAddressIndicator creates a new AddressIndicator, which is meant to be used in +// NewCalled/CallingPartyAddress as the first argument. +// +// The last bit, which is "reserved for national use", is always set to 0. +// You can set the bit to 1 by doing `| 0b10000000` to the result of this function. +func NewAddressIndicator(hasPC, hasSSN, routeOnSSN bool, gti GlobalTitleIndicator) uint8 { + var ai uint8 + if hasPC { + ai |= 0b00000001 + } + if hasSSN { + ai |= 0b00000010 + } + if routeOnSSN { + ai |= 0b01000000 + } + ai |= uint8(gti) << 2 + + return ai +} + +// NewPartyAddress creates a new PartyAddress from properly-typed values. +// +// The given SPC and SSN are set to 0 if the corresponding bit is not properly set in the +// AddressIndicator. Use NewAddressIndicator to create a proper AddressIndicator. +// +// When you are aware of the type of PartyAddress you are creating, you can use +// NewCalled/CallingPartyAddress to create a PartyAddress with the correct code. +// Otherwise, you can use AsCalled/Calling to set the code after creating a PartyAddress. +func NewPartyAddress(cdcg ParameterNameCode, ai uint8, spc uint16, ssn uint8, gt *GlobalTitle) *PartyAddress { + if cdcg != PCodeCalledPartyAddress && cdcg != PCodeCallingPartyAddress { + logf("invalid parameter code: expected %v or %v, got %v", PCodeCalledPartyAddress, PCodeCallingPartyAddress, cdcg) + } + + p := &PartyAddress{ + paramType: PTypeV, + code: cdcg, + Indicator: ai, + GlobalTitle: gt, + } + + if p.HasPC() { + p.SignalingPointCode = spc + } + + if p.HasSSN() { + p.SubsystemNumber = ssn + } + + p.SetLength() + return p +} + +// NewPartyAddressOptional creates a new PartyAddress from properly-typed values. +func NewPartyAddressOptional(cdcg ParameterNameCode, ai uint8, spc uint16, ssn uint8, gt *GlobalTitle) *PartyAddress { + p := NewPartyAddress(cdcg, ai, spc, ssn, gt) + p.paramType = PTypeO + return p +} + +// NewCalledPartyAddress creates a new PartyAddress for Called Party Address. +func NewCalledPartyAddress(ai uint8, spc uint16, ssn uint8, gt *GlobalTitle) *PartyAddress { + return NewPartyAddress(PCodeCalledPartyAddress, ai, spc, ssn, gt) +} + +// NewCallingPartyAddress creates a new PartyAddress for Calling Party Address. +func NewCallingPartyAddress(ai uint8, spc uint16, ssn uint8, gt *GlobalTitle) *PartyAddress { + return NewPartyAddress(PCodeCallingPartyAddress, ai, spc, ssn, gt) +} + +// NewCalledPartyAddressOptional creates a new PartyAddress for Called Party Address as an optional parameter. +func NewCalledPartyAddressOptional(ai uint8, spc uint16, ssn uint8, gt *GlobalTitle) *PartyAddress { + return NewPartyAddressOptional(PCodeCalledPartyAddress, ai, spc, ssn, gt) +} + +// NewCallingPartyAddressOptional creates a new PartyAddress for Calling Party Address as an optional parameter. +func NewCallingPartyAddressOptional(ai uint8, spc uint16, ssn uint8, gt *GlobalTitle) *PartyAddress { + return NewPartyAddressOptional(PCodeCallingPartyAddress, ai, spc, ssn, gt) +} + +// ParseCalledPartyAddress parses the given byte sequence as a mandatory fixed length +// Called Party Address and returns it as a PartyAddress. +func ParseCalledPartyAddress(b []byte) (*PartyAddress, int, error) { + return parsePartyAddress(PTypeV, PCodeCalledPartyAddress, b) +} + +// ParseCallingPartyAddress parses the given byte sequence as a mandatory fixed length +// Calling Party Address and returns it as a PartyAddress. +func ParseCallingPartyAddress(b []byte) (*PartyAddress, int, error) { + return parsePartyAddress(PTypeV, PCodeCallingPartyAddress, b) +} + +// ParseCalledPartyAddressOptional parses the given byte sequence as an optional +// Called Party Address and returns it as a PartyAddress. +func ParseCalledPartyAddressOptional(b []byte) (*PartyAddress, int, error) { + return parsePartyAddress(PTypeO, PCodeCalledPartyAddress, b) +} + +// ParseCallingPartyAddressOptional parses the given byte sequence as an optional +// Calling Party Address and returns it as a PartyAddress. +func ParseCallingPartyAddressOptional(b []byte) (*PartyAddress, int, error) { + return parsePartyAddress(PTypeO, PCodeCallingPartyAddress, b) +} + +func parsePartyAddress(ptype ParameterType, code ParameterNameCode, b []byte) (*PartyAddress, int, error) { + p := &PartyAddress{ + paramType: ptype, + code: code, + } + + n, err := p.Read(b) + if err != nil { + return nil, n, err + } + + return p, n, nil +} + +// Read sets the values retrieved from byte sequence in a PartyAddress. +func (p *PartyAddress) Read(b []byte) (int, error) { + if p.paramType == PTypeO { + return p.readOptional(b) + } + + // force to read as V if it's not O + p.paramType = PTypeV + return p.read(b) +} + +func (p *PartyAddress) read(b []byte) (int, error) { + var n = 2 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + p.length = int(b[0]) + p.Indicator = b[1] + + if int(p.length) != len(b)-1 { + return n, io.ErrUnexpectedEOF + } + + if p.HasPC() { + end := n + 2 + if end >= len(b) { + return n, io.ErrUnexpectedEOF + } + p.SignalingPointCode = binary.BigEndian.Uint16(b[n:end]) + n = end + } + + if p.HasSSN() { + p.SubsystemNumber = b[n] + n++ + } + + gti := p.GTI() + if gti == 0 { + return n, nil + } + + p.GlobalTitle = &GlobalTitle{GTI: gti} + m, err := p.GlobalTitle.Read(b[n : int(p.length)+1]) + if err != nil { + return n + m, err + } + n += m + + return n, nil +} + +func (p *PartyAddress) readOptional(b []byte) (int, error) { + n := 3 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + p.code = ParameterNameCode(b[0]) + if p.code != PCodeCalledPartyAddress && p.code != PCodeCallingPartyAddress { + logf( + "invalid parameter code: expected %d or %d, got %d", + PCodeCalledPartyAddress, PCodeCallingPartyAddress, p.code, + ) + } + + return p.read(b[1:]) +} + +// Write serializes the PartyAddress parameter and returns it as a byte slice. +func (p *PartyAddress) Write(b []byte) (int, error) { + if p.paramType == PTypeV { + return p.write(b) + } + return p.writeOptional(b) +} + +func (p *PartyAddress) write(b []byte) (int, error) { + if len(b) < p.MarshalLen() { + return 0, io.ErrUnexpectedEOF + } + + b[0] = uint8(p.length) + b[1] = p.Indicator + + var n = 2 + if p.HasPC() { + binary.BigEndian.PutUint16(b[n:n+2], p.SignalingPointCode) + n += 2 + } + + if p.HasSSN() { + b[n] = p.SubsystemNumber + n++ + } + + if p.GlobalTitle != nil { + m, err := p.GlobalTitle.Write(b[n : n+p.GlobalTitle.MarshalLen()]) + if err != nil { + return n + m, err + } + n += m + } + + return n, nil +} + +func (p *PartyAddress) writeOptional(b []byte) (int, error) { + if len(b) < p.MarshalLen() { + return 0, io.ErrUnexpectedEOF + } + + b[0] = uint8(p.code) + n, err := p.write(b[1:]) + if err != nil { + return n + 1, err + } + + return n + 1, nil +} + +// MarshalLen returns the serial length. +func (p *PartyAddress) MarshalLen() int { + l := 2 + if p.HasPC() { + l += 2 + } + + if p.HasSSN() { + l++ + } + + if p.GlobalTitle != nil { + l = l + p.GlobalTitle.MarshalLen() + } + + return l +} + +// Code returns the PartyAddress in ParameterNameCode. +func (p *PartyAddress) Code() ParameterNameCode { + return p.code +} + +// Value returns the PartyAddress as it is. +func (p *PartyAddress) Value() *PartyAddress { + return p +} + +// String returns the PartyAddress values in human readable format. +func (p *PartyAddress) String() string { + return fmt.Sprintf("{%s (%s): {length: %d, Indicator: %#08b, SignalingPointCode: %d, SubsystemNumber: %d, GlobalTitle: %v}}", + p.code, p.paramType, p.length, p.Indicator, p.SignalingPointCode, p.SubsystemNumber, p.GlobalTitle, + ) +} + +// RouteOnGT reports whether the packet is routed on Global Title or not. +func (p *PartyAddress) RouteOnGT() bool { + return (int(p.Indicator) >> 6 & 0b1) == 0 +} + +// RouteOnSSN reports whether the packet is routed on SSN or not. +func (p *PartyAddress) RouteOnSSN() bool { + return !p.RouteOnGT() +} + +// GTI returns GlobalTitleIndicator value retrieved from Indicator. +func (p *PartyAddress) GTI() GlobalTitleIndicator { + return gti(int(p.Indicator)) +} + +func gti(ai int) GlobalTitleIndicator { + return GlobalTitleIndicator(ai >> 2 & 0b1111) +} + +// HasSSN reports whether PartyAddress has a Subsystem Number. +func (p *PartyAddress) HasSSN() bool { + return (int(p.Indicator) >> 1 & 0b1) == 1 +} + +// HasPC reports whether PartyAddress has a Signaling Point Code. +func (p *PartyAddress) HasPC() bool { + return (int(p.Indicator) & 0b1) == 1 +} + +// SetLength sets the length in length field. +// This should be called after changing the values in PartyAddress. +func (p *PartyAddress) SetLength() { + p.length = p.MarshalLen() - 1 +} + +// ProtocolClass is a Protocol Class SCCP parameter. +type ProtocolClass struct { + paramType ParameterType + code ParameterNameCode + length int + value uint8 +} + +// NewProtocolClass creates a new ProtocolClass. +func NewProtocolClass(cls int, returnOnError bool) *ProtocolClass { + p := &ProtocolClass{ + paramType: PTypeF, + code: PCodeProtocolClass, + length: 1, + value: uint8(cls), + } + + if returnOnError { + p.value = uint8(cls | 0x80) + } + + return p +} + +// ParseProtocolClass parses the given byte sequence as a ProtocolClass. +func ParseProtocolClass(b []byte) (*ProtocolClass, int, error) { + p := &ProtocolClass{} + n, err := p.Read(b) + if err != nil { + return nil, n, err + } + + return p, n, nil +} + +// Read sets the values retrieved from byte sequence in a ProtocolClass. +func (p *ProtocolClass) Read(b []byte) (int, error) { + n := 1 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + p.code = PCodeProtocolClass + p.length = n + p.value = b[0] + + return n, nil +} + +// Write serializes the ProtocolClass parameter and returns it as a byte slice. +func (p *ProtocolClass) Write(b []byte) (int, error) { + if len(b) < p.length { + return 0, io.ErrUnexpectedEOF + } + + b[0] = p.value + return p.length, nil +} + +// MarshalLen returns the serial length of ProtocolClass. +func (p *ProtocolClass) MarshalLen() int { + return p.length +} + +// Code returns the ProtocolClass in ParameterNameCode. +func (p *ProtocolClass) Code() ParameterNameCode { + return p.code +} + +// Value returns the ProtocolClass in uint8. +func (p *ProtocolClass) Value() uint8 { + return uint8(p.value) +} + +// String returns the ProtocolClass in string. +func (p *ProtocolClass) String() string { + return fmt.Sprintf( + "{%s (%s): {Class: %d, ReturnOnError: %v}}", + p.code, p.paramType, p.Class(), p.ReturnOnError(), + ) +} + +// Class returns the class part from ProtocolClass parameter. +func (p *ProtocolClass) Class() int { + return int(p.value) & 0xf +} + +// ReturnOnError judges if ProtocolClass has "Return Message On Error" option. +func (p *ProtocolClass) ReturnOnError() bool { + return (int(p.value) >> 7) == 1 +} + +// SegmentingReassembling represents the Segmenting/Reassembling. +type SegmentingReassembling struct { + paramType ParameterType + code ParameterNameCode + length int + value uint8 +} + +// NewSegmentingReassembling creates a new SegmentingReassembling. +func NewSegmentingReassembling(moreData bool) *SegmentingReassembling { + v := uint8(0) + if moreData { + v = 1 + } + + return &SegmentingReassembling{ + paramType: PTypeF, + code: PCodeSegmentingReassembling, + length: 1, + value: v, + } +} + +// ParseSegmentingReassembling parses the given byte sequence as a SegmentingReassembling. +func ParseSegmentingReassembling(b []byte) (*SegmentingReassembling, int, error) { + s := &SegmentingReassembling{} + n, err := s.Read(b) + if err != nil { + return nil, n, err + } + + return s, n, nil +} + +// Read sets the values retrieved from byte sequence in a SegmentingReassembling. +func (s *SegmentingReassembling) Read(b []byte) (int, error) { + n := 1 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + s.paramType = PTypeF + s.code = PCodeSegmentingReassembling + s.length = n + s.value = b[0] + + return n, nil +} + +// Write serializes the SegmentingReassembling parameter and returns it as a byte slice. +func (s *SegmentingReassembling) Write(b []byte) (int, error) { + if len(b) < s.length { + return 0, io.ErrUnexpectedEOF + } + + b[0] = s.value + return s.length, nil +} + +// MarshalLen returns the serial length of SegmentingReassembling. +func (s *SegmentingReassembling) MarshalLen() int { + return s.length +} + +// Code returns the SegmentingReassembling in ParameterNameCode. +func (s *SegmentingReassembling) Code() ParameterNameCode { + return s.code +} + +// Value returns the SegmentingReassembling in uint8. +func (s *SegmentingReassembling) Value() uint8 { + return s.value +} + +// String returns the SegmentingReassembling in string. +func (s *SegmentingReassembling) String() string { + return fmt.Sprintf("{%s (%s): %d}", s.code, s.paramType, s.value) +} + +// MoreData judges if the message has more data. +func (s *SegmentingReassembling) MoreData() bool { + return s.value&0b1 == 1 +} + +// ReceiveSequenceNumber represents the Receive Sequence Number. +type ReceiveSequenceNumber struct { + paramType ParameterType + code ParameterNameCode + length int + value uint8 +} + +// NewReceiveSequenceNumber creates a new ReceiveSequenceNumber. +// The value is masked out to 0b11111110 since the LSB is spare. +func NewReceiveSequenceNumber(v uint8) *ReceiveSequenceNumber { + return &ReceiveSequenceNumber{ + paramType: PTypeF, + code: PCodeReceiveSequenceNumber, + length: 1, + value: v & 0b11111110, + } +} + +// ParseReceiveSequenceNumber parses the given byte sequence as a ReceiveSequenceNumber. +func ParseReceiveSequenceNumber(b []byte) (*ReceiveSequenceNumber, int, error) { + r := &ReceiveSequenceNumber{} + n, err := r.Read(b) + if err != nil { + return nil, n, err + } + + return r, n, nil +} + +// Read sets the values retrieved from byte sequence in a ReceiveSequenceNumber. +func (r *ReceiveSequenceNumber) Read(b []byte) (int, error) { + n := 1 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + r.paramType = PTypeF + r.code = PCodeReceiveSequenceNumber + r.length = n + r.value = b[0] & 0b11111110 + + return n, nil +} + +// Write serializes the ReceiveSequenceNumber parameter and returns it as a byte slice. +func (r *ReceiveSequenceNumber) Write(b []byte) (int, error) { + if len(b) < r.length { + return 0, io.ErrUnexpectedEOF + } + + b[0] = r.value & 0b11111110 + return r.length, nil +} + +// MarshalLen returns the serial length of ReceiveSequenceNumber. +func (r *ReceiveSequenceNumber) MarshalLen() int { + return r.length +} + +// Code returns the ReceiveSequenceNumber in ParameterNameCode. +func (r *ReceiveSequenceNumber) Code() ParameterNameCode { + return r.code +} + +// Value returns the ReceiveSequenceNumber in uint8. +func (r *ReceiveSequenceNumber) Value() uint8 { + return r.value +} + +// String returns the ReceiveSequenceNumber in string. +func (r *ReceiveSequenceNumber) String() string { + return fmt.Sprintf("{%s (%s): %d}", r.code, r.paramType, r.value) +} + +// SequencingSegmenting represents the Sequencing/Segmenting. +type SequencingSegmenting struct { + paramType ParameterType + code ParameterNameCode + length int + SendSequenceNumber uint8 + ReceiveSequenceNumber uint8 + MoreData bool +} + +// NewSequencingSegmenting creates a new SequencingSegmenting. +func NewSequencingSegmenting(snd, rcv uint8, moreData bool) *SequencingSegmenting { + return &SequencingSegmenting{ + paramType: PTypeF, + code: PCodeSequencingSegmenting, + length: 2, + SendSequenceNumber: snd & 0b01111111, + ReceiveSequenceNumber: rcv & 0b01111111, + MoreData: moreData, + } +} + +// ParseSequencingSegmenting parses the given byte sequence as a SequencingSegmenting. +func ParseSequencingSegmenting(b []byte) (*SequencingSegmenting, int, error) { + s := &SequencingSegmenting{} + n, err := s.Read(b) + if err != nil { + return nil, n, err + } + + return s, n, nil +} + +// Read sets the values retrieved from byte sequence in a SequencingSegmenting. +func (s *SequencingSegmenting) Read(b []byte) (int, error) { + n := 2 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + s.paramType = PTypeF + s.code = PCodeSequencingSegmenting + s.length = n + + s.SendSequenceNumber = b[0] & 0b11111110 + s.ReceiveSequenceNumber = b[1] & 0b11111110 + s.MoreData = b[1]&0b00000001 == 1 + + return n, nil +} + +// Write serializes the SequencingSegmenting parameter and returns it as a byte slice. +func (s *SequencingSegmenting) Write(b []byte) (int, error) { + if len(b) < s.length { + return 0, io.ErrUnexpectedEOF + } + + b[0] = s.SendSequenceNumber + b[1] = s.ReceiveSequenceNumber + + if s.MoreData { + b[1] |= 0b00000001 + } + + return s.length, nil +} + +// MarshalLen returns the serial length of SequencingSegmenting. +func (s *SequencingSegmenting) MarshalLen() int { + return s.length +} + +// Code returns the SequencingSegmenting in ParameterNameCode. +func (s *SequencingSegmenting) Code() ParameterNameCode { + return s.code +} + +// Value returns the SequencingSegmenting as it is. +func (s *SequencingSegmenting) Value() *SequencingSegmenting { + return s +} + +// String returns the SequencingSegmenting in string. +func (s *SequencingSegmenting) String() string { + return fmt.Sprintf( + "{%s: {SendSequenceNumber=%d, ReceiveSequenceNumber=%d, MoreData=%t}}", + s.code, s.SendSequenceNumber, s.ReceiveSequenceNumber, s.MoreData, + ) +} + +// Credit represents the Credit. +type Credit struct { + paramType ParameterType + code ParameterNameCode + length int + value uint8 +} + +// NewCredit creates a new Credit. +func NewCredit(v uint8) *Credit { + return &Credit{ + paramType: PTypeF, + code: PCodeCredit, + length: 1, + value: v, + } +} + +// NewCreditOptional creates a new optional Credit. +func NewCreditOptional(v uint8) *Credit { + c := NewCredit(v) + c.paramType = PTypeO + return c +} + +// ParseCredit parses the given byte sequence as a Credit. +func ParseCredit(b []byte) (*Credit, int, error) { + c := &Credit{} + n, err := c.Read(b) + if err != nil { + return nil, n, err + } + + return c, n, nil +} + +// ParseCreditOptional parses the given byte sequence as an optional Credit. +func ParseCreditOptional(b []byte) (*Credit, int, error) { + c := &Credit{paramType: PTypeO} + n, err := c.Read(b) + if err != nil { + return nil, n, err + } + + return c, n, nil +} + +// Read sets the values retrieved from byte sequence in a Credit. +func (c *Credit) Read(b []byte) (int, error) { + if c.paramType == PTypeO { + return c.readOptional(b) + } + return c.read(b) +} + +// read sets the values retrieved from byte sequence in a Credit. +func (c *Credit) read(b []byte) (int, error) { + n := 1 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + c.paramType = PTypeF + c.code = PCodeCredit + c.length = n + c.value = b[0] + + return n, nil +} + +func (c *Credit) readOptional(b []byte) (int, error) { + n := 3 + if len(b) < n { + return 0, nil + } + + c.code = ParameterNameCode(b[0]) + if c.code != PCodeCredit { + logf("invalid parameter code: expected %d, got %d", PCodeCredit, c.code) + } + + c.length = int(b[1]) + if c.length != n-2 { + logf("%s: invalid length: expected %d, got %d", PCodeCredit, n-2, c.length) + } + + c.value = b[2] + return n, nil +} + +// Write serializes the Credit parameter and returns it as a byte slice. +func (c *Credit) Write(b []byte) (int, error) { + if c.paramType == PTypeO { + return c.writeOptional(b) + } + return c.write(b) +} + +// write serializes the Credit parameter and returns it as a byte slice. +func (c *Credit) write(b []byte) (int, error) { + if len(b) < c.length { + return 0, io.ErrUnexpectedEOF + } + + b[0] = c.value + return c.length, nil +} + +func (c *Credit) writeOptional(b []byte) (int, error) { + if len(b) < c.length { + return 0, io.ErrUnexpectedEOF + } + + b[0] = uint8(c.code) + b[1] = uint8(c.length) + b[2] = c.value + + return c.length, nil +} + +// MarshalLen returns the serial length of Credit. +func (c *Credit) MarshalLen() int { + if c.paramType == PTypeO { + return 2 + c.length + } + return c.length +} + +// Code returns the Credit in ParameterNameCode. +func (c *Credit) Code() ParameterNameCode { + return c.code +} + +// Value returns the Credit in uint8. +func (c *Credit) Value() uint8 { + return c.value +} + +// String returns the Credit in string. +func (c *Credit) String() string { + return fmt.Sprintf("{%s (%s): %d}", c.code, c.paramType, c.value) +} + +// Cause represents a common structure for all Cause types. +type Cause[T ~uint8] struct { + paramType ParameterType + code ParameterNameCode + length int + value T +} + +// NewCause creates a new Cause with the given value and its type. +func NewCause[T ~uint8](value T) *Cause[T] { + c := &Cause[T]{ + paramType: PTypeF, + length: 1, + value: value, + } + + switch any(c).(type) { + case *ReleaseCause: + c.code = PCodeReleaseCause + case *ReturnCause: + c.code = PCodeReturnCause + case *ResetCause: + c.code = PCodeResetCause + case *ErrorCause: + c.code = PCodeErrorCause + case *RefusalCause: + c.code = PCodeRefusalCause + default: + logf("invalid Cause type: %T", c) + } + + return c +} + +// Read sets the values retrieved from byte sequence in a Cause. +func (c *Cause[T]) Read(b []byte) (int, error) { + n := 1 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + c.paramType = PTypeF + + switch any(c).(type) { + case *ReleaseCause: + c.code = PCodeReleaseCause + case *ReturnCause: + c.code = PCodeReturnCause + case *ResetCause: + c.code = PCodeResetCause + case *ErrorCause: + c.code = PCodeErrorCause + case *RefusalCause: + c.code = PCodeRefusalCause + default: + return 0, UnsupportedParameterError(b[0]) + } + + c.length = n + c.value = T(b[0]) + + return n, nil +} + +// Write serializes the Cause parameter and returns it as a byte slice. +func (c *Cause[T]) Write(b []byte) (int, error) { + if len(b) < c.length { + return 0, io.ErrUnexpectedEOF + } + + b[0] = uint8(c.value) + return c.length, nil +} + +// MarshalLen returns the serial length of Cause. +func (c *Cause[T]) MarshalLen() int { + return c.length +} + +// Code returns the code in the Cause. +func (c *Cause[T]) Code() ParameterNameCode { + return c.code +} + +// Value returns the value in the Cause. +func (c *Cause[T]) Value() T { + return T(c.value) +} + +// String returns the Cause as a string. +func (c *Cause[T]) String() string { + return fmt.Sprintf("{%s (%s): %v}", c.code, c.paramType, c.value) +} + +// ReleaseCauseValue is a type for ReleaseCause. +type ReleaseCauseValue uint8 + +// ReleaseCauseValue values. +const ( + ReleaseCauseEndUserOriginated ReleaseCauseValue = 0b00000000 // end user originated + ReleaseCauseEndUserCongestion ReleaseCauseValue = 0b00000001 // end user congestion + ReleaseCauseEndUserFailure ReleaseCauseValue = 0b00000010 // end user failure + ReleaseCauseSCCPUserOriginated ReleaseCauseValue = 0b00000011 // SCCP user originated + ReleaseCauseRemoteProcedureError ReleaseCauseValue = 0b00000100 // remote procedure error + ReleaseCauseInconsistentConnectionData ReleaseCauseValue = 0b00000101 // inconsistent connection data + ReleaseCauseAccessFailure ReleaseCauseValue = 0b00000110 // access failure + ReleaseCauseAccessCongestion ReleaseCauseValue = 0b00000111 // access congestion + ReleaseCauseSubsystemFailure ReleaseCauseValue = 0b00001000 // subsystem failure + ReleaseCauseSubsystemCongestion ReleaseCauseValue = 0b00001001 // subsystem congestion + ReleaseCauseMTPFailure ReleaseCauseValue = 0b00001010 // MTP failure + ReleaseCauseNetworkCongestion ReleaseCauseValue = 0b00001011 // network congestion + ReleaseCauseExpirationOfResetTimer ReleaseCauseValue = 0b00001100 // expiration of reset timer + ReleaseCauseExpirationOfReceiveInactivityTimer ReleaseCauseValue = 0b00001101 // expiration of receive inactivity timer + _ ReleaseCauseValue = 0b00001110 // reserved + ReleaseCauseUnqualified ReleaseCauseValue = 0b00001111 // unqualified + ReleaseCauseSCCPFailure ReleaseCauseValue = 0b00010000 // SCCP failure +) + +// ReleaseCause is a specific Cause for ReleaseCause. +type ReleaseCause = Cause[ReleaseCauseValue] + +// ParseReleaseCause parses the given byte sequence as a ReleaseCause. +func ParseReleaseCause(b []byte) (*ReleaseCause, int, error) { + c := &ReleaseCause{} + n, err := c.Read(b) + if err != nil { + return nil, n, err + } + + return c, n, nil +} + +// ReturnCause is a specific Cause for ReturnCause. +type ReturnCauseValue uint8 + +// ReturnCauseValue values. +const ( + ReturnCauseNoTranslationForAnAddressOfSuchNature ReturnCauseValue = 0b00000000 // no translation for an address of such nature + ReturnCauseNoTranslationForThisSpecificAddress ReturnCauseValue = 0b00000001 // no translation for this specific address + ReturnCauseSubsystemCongestion ReturnCauseValue = 0b00000010 // subsystem congestion + ReturnCauseSubsystemFailure ReturnCauseValue = 0b00000011 // subsystem failure + ReturnCauseUnequippedUser ReturnCauseValue = 0b00000100 // unequipped user + ReturnCauseMTPFailure ReturnCauseValue = 0b00000101 // MTP failure + ReturnCauseNetworkCongestion ReturnCauseValue = 0b00000110 // network congestion + ReturnCauseUnqualified ReturnCauseValue = 0b00000111 // unqualified + ReturnCauseErrorInMessageTransport ReturnCauseValue = 0b00001000 // error in message transport + ReturnCauseErrorInLocalProcessing ReturnCauseValue = 0b00001001 // error in local processing + ReturnCauseDestinationCannotPerformReassembly ReturnCauseValue = 0b00001010 // destination cannot perform reassembly + ReturnCauseSCCPFailure ReturnCauseValue = 0b00001011 // SCCP failure + ReturnCauseHopCounterViolation ReturnCauseValue = 0b00001100 // hop counter violation + ReturnCauseSegmentationNotSupported ReturnCauseValue = 0b00001101 // segmentation not supported + ReturnCauseSegmentationFailure ReturnCauseValue = 0b00001110 // segmentation failure +) + +// ReturnCause is a specific instance of Cause. +type ReturnCause = Cause[ReturnCauseValue] + +// ParseReturnCause parses the given byte sequence as a ReturnCause. +func ParseReturnCause(b []byte) (*ReturnCause, int, error) { + c := &ReturnCause{} + n, err := c.Read(b) + if err != nil { + return nil, n, err + } + + return c, n, nil +} + +// ResetCauseValue is a type for ResetCause. +type ResetCauseValue uint8 + +// ResetCauseValue values. +const ( + ResetCauseEndUserOriginated ResetCauseValue = 0b00000000 // end user originated + ResetCauseSCCPUserOriginated ResetCauseValue = 0b00000001 // SCCP user originated + ResetCauseMessageOutOfOrderIncorrectSendSequenceNumber ResetCauseValue = 0b00000010 // message out of order - incorrect P(S) + ResetCauseMessageOutOfOrderIncorrectReceiveSequenceNumber ResetCauseValue = 0b00000011 // message out of order - incorrect P(R) + ResetCauseRemoteProcedureErrorMessageOutOfWindow ResetCauseValue = 0b00000100 // remote procedure error - message out of window + ResetCauseRemoteProcedureErrorIncorrectSendSequenceNumberAfterReinitialization ResetCauseValue = 0b00000101 // remote procedure error - incorrect P(S) after (re)initialization + ResetCauseRemoteProcedureErrorGeneral ResetCauseValue = 0b00000110 // remote procedure error - general + ResetCauseRemoteEndUserOperational ResetCauseValue = 0b00000111 // remote end user operational + ResetCauseNetworkOperational ResetCauseValue = 0b00001000 // network operational + ResetCauseAccessOperational ResetCauseValue = 0b00001001 // access operational + ResetCauseNetworkCongestion ResetCauseValue = 0b00001010 // network congestion + _ ResetCauseValue = 0b00001011 + ResetCauseUnqualified ResetCauseValue = 0b00001100 // unqualified +) + +// ResetCause is a specific Cause for ResetCause. +type ResetCause = Cause[ResetCauseValue] + +// ParseResetCause parses the given byte sequence as a ResetCause. +func ParseResetCause(b []byte) (*ResetCause, int, error) { + c := &ResetCause{} + n, err := c.Read(b) + if err != nil { + return nil, n, err + } + + return c, n, nil +} + +// ErrorCauseValue is a type for ErrorCause. +type ErrorCauseValue uint8 + +// ErrorCauseValue values. +const ( + ErrorCauseLocalReferenceNumberMismatchUnassignedDestinationLRN ErrorCauseValue = 0b00000000 // local reference number (LRN) mismatch - unassigned destination LRN + ErrorCauseLocalReferenceNumberMismatchInconsistentSourceLRN ErrorCauseValue = 0b00000001 // local reference number (LRN) mismatch - inconsistent source LRN + ErrorCausePointCodeMismatch ErrorCauseValue = 0b00000010 // point code mismatch + ErrorCauseServiceClassMismatch ErrorCauseValue = 0b00000011 // service class mismatch + ErrorCauseUnqualified ErrorCauseValue = 0b00000100 // unqualified +) + +// ErrorCause is a specific Cause for ErrorCause. +type ErrorCause = Cause[ErrorCauseValue] + +// ParseErrorCause parses the given byte sequence as a ErrorCause. +func ParseErrorCause(b []byte) (*ErrorCause, int, error) { + c := &ErrorCause{} + n, err := c.Read(b) + if err != nil { + return nil, n, err + } + + return c, n, nil +} + +// RefusalCauseValue is a type for RefusalCause. +type RefusalCauseValue uint8 + +// RefusalCauseValue values. +const ( + RefusalCauseEndUserOriginated RefusalCauseValue = 0b00000000 // end user originated + RefusalCauseEndUserCongestion RefusalCauseValue = 0b00000001 // end user congestion + RefusalCauseEndUserFailure RefusalCauseValue = 0b00000010 // end user failure + RefusalCauseSCCPUserOriginated RefusalCauseValue = 0b00000011 // SCCP user originated + RefusalCauseDestinationAddressUnknown RefusalCauseValue = 0b00000100 // destination address unknown + RefusalCauseDestinationInaccessible RefusalCauseValue = 0b00000101 // destination inaccessible + RefusalCauseNetworkResourceQoSNotAvailableNonTransient RefusalCauseValue = 0b00000110 // network resource - QoS not available/non-transient + RefusalCauseNetworkResourceQoSNotAvailableTransient RefusalCauseValue = 0b00000111 // network resource - QoS not available/transient + RefusalCauseAccessFailure RefusalCauseValue = 0b00001000 // access failure + RefusalCauseAccessCongestion RefusalCauseValue = 0b00001001 // access congestion + RefusalCauseSubsystemFailure RefusalCauseValue = 0b00001010 // subsystem failure + RefusalCauseSubsystemCongestion RefusalCauseValue = 0b00001011 // subsystem congestion + RefusalCauseExpirationOfTheConnectionEstablishmentTimer RefusalCauseValue = 0b00001100 // expiration of the connection establishment timer + RefusalCauseIncompatibleUserData RefusalCauseValue = 0b00001101 // incompatible user data + _ RefusalCauseValue = 0b00001110 // reserved + RefusalCauseUnqualified RefusalCauseValue = 0b00001111 // unqualified + RefusalCauseHopCounterViolation RefusalCauseValue = 0b00010000 // hop counter violation + RefusalCauseSCCPFailure RefusalCauseValue = 0b00010001 // SCCP failure + RefusalCauseNoTranslationForAnAddressOfSuchNature RefusalCauseValue = 0b00010010 // no translation for an address of such nature + RefusalCauseUnequippedUser RefusalCauseValue = 0b00010011 // unequipped user +) + +// RefusalCause is a specific Cause for RefusalCause. +type RefusalCause = Cause[RefusalCauseValue] + +// ParseRefusalCause parses the given byte sequence as a RefusalCause. +func ParseRefusalCause(b []byte) (*RefusalCause, int, error) { + c := &RefusalCause{} + n, err := c.Read(b) + if err != nil { + return nil, n, err + } + + return c, n, nil +} + +// Data represents the Data. +type Data struct { + paramType ParameterType + code ParameterNameCode + length int + value []byte +} + +// NewData creates a new Data. +func NewData(v []byte) *Data { + return &Data{ + paramType: PTypeV, + code: PCodeData, + length: len(v), + value: v, + } +} + +// NewDataOptional creates a new Data as an optional parameter. +func NewDataOptional(v []byte) *Data { + d := NewData(v) + d.paramType = PTypeO + return d +} + +// ParseData parses the given byte sequence as a Data. +func ParseData(b []byte) (*Data, int, error) { + d := &Data{} + n, err := d.Read(b) + if err != nil { + return nil, n, err + } + + return d, n, nil +} + +// ParseDataOptional parses the given byte sequence as an optional Data. +func ParseDataOptional(b []byte) (*Data, int, error) { + d := &Data{paramType: PTypeO} + n, err := d.Read(b) + if err != nil { + return nil, n, err + } + + return d, n, nil +} + +// Read sets the values retrieved from byte sequence in a Data. +func (d *Data) Read(b []byte) (int, error) { + if d.paramType == PTypeO { + return d.readOptional(b) + } + + // force to read as V if it's not O + if d.paramType == PTypeF { + d.paramType = PTypeV + } + return d.read(b) +} + +// read sets the values retrieved from byte sequence in a Data. +func (d *Data) read(b []byte) (int, error) { + n := len(b) + if n < 1 { + return 0, io.ErrUnexpectedEOF + } + + d.code = PCodeData + d.length = int(b[0]) + if d.length == 0 { + d.value = nil + return 1, nil + } + + if n < d.length+1 { + return 1, io.ErrUnexpectedEOF + } + + d.value = b[1 : d.length+1] + + return n, nil +} + +func (d *Data) readOptional(b []byte) (int, error) { + n := len(b) + + d.code = ParameterNameCode(b[0]) + if d.code != PCodeData { + logf("invalid parameter code: expected %d, got %d", PCodeData, d.code) + } + + m, err := d.read(b[1:]) + n += m + if err != nil { + return n, err + } + + return n, nil +} + +// Write serializes the Data parameter and returns it as a byte slice. +func (d *Data) Write(b []byte) (int, error) { + if d.paramType == PTypeO { + return d.writeOptional(b) + } + return d.write(b) +} + +// write serializes the Data parameter and returns it as a byte slice. +func (d *Data) write(b []byte) (int, error) { + if len(b) < d.length+1 { + return 0, io.ErrUnexpectedEOF + } + + b[0] = uint8(d.length) + if d.length == 0 { + return 1, nil + } + + copy(b[1:d.length+1], d.value) + return d.length, nil +} + +func (d *Data) writeOptional(b []byte) (int, error) { + if len(b) < d.length { + return 0, io.ErrUnexpectedEOF + } + + b[0] = uint8(d.code) + b[1] = uint8(d.length) + copy(b[2:], d.value) + return d.length, nil +} + +// MarshalLen returns the serial length of Data. +func (d *Data) MarshalLen() int { + if d.paramType == PTypeO { + return 2 + len(d.value) + } + return 1 + len(d.value) +} + +// Code returns the Data in ParameterNameCode. +func (d *Data) Code() ParameterNameCode { + return d.code +} + +// Value returns the Data in []byte. +func (d *Data) Value() []byte { + return d.value +} + +// String returns the Data in string. +func (d *Data) String() string { + return fmt.Sprintf("{%s (%s): %x}", d.code, d.paramType, d.value) +} + +// Segmentation represents the Segmentation. +type Segmentation struct { + paramType ParameterType + code ParameterNameCode + length int + FirstSegment bool + Class uint8 + RemainingSegments uint8 + LocalReference uint32 // 3-octet +} + +// NewSegmentation creates a new Segmentation. +func NewSegmentation(first bool, cls, rem uint8, lrn uint32) *Segmentation { + return &Segmentation{ + paramType: PTypeO, + code: PCodeSegmentation, + length: 4, + FirstSegment: first, + Class: cls & 0b1, + RemainingSegments: rem & 0b1111, + LocalReference: lrn & 0x00ffffff, + } +} + +// NewSegmentationOptional creates a new optional Segmentation. +func NewSegmentationOptional(first bool, cls, rem uint8, lrn uint32) *Segmentation { + return NewSegmentation(first, cls, rem, lrn) +} + +// ParseSegmentation parses the given byte sequence as a Segmentation. +func ParseSegmentation(b []byte) (*Segmentation, int, error) { + s := &Segmentation{} + n, err := s.Read(b) + if err != nil { + return nil, n, err + } + + return s, n, nil +} + +// ParseSegmentationOptional parses the given byte sequence as an optional Segmentation. +func ParseSegmentationOptional(b []byte) (*Segmentation, int, error) { + return ParseSegmentation(b) +} + +// Read sets the values retrieved from byte sequence in a Segmentation. +func (s *Segmentation) Read(b []byte) (int, error) { + if s.paramType != PTypeO { + s.paramType = PTypeO + } + + n := 6 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + s.code = ParameterNameCode(b[0]) + if s.code != PCodeSegmentation { + logf("invalid parameter code: expected %d, got %d", PCodeSegmentation, s.code) + } + + s.length = int(b[1]) + if s.length != n-2 { + logf("%s: invalid length: expected %d, got %d", PCodeSegmentation, n-2, s.length) + } + + s.FirstSegment = b[2]>>7&0b1 == 1 + s.Class = b[2] >> 6 & 0b1 + s.RemainingSegments = b[2] & 0b1111 + s.LocalReference = utils.Uint24To32(b[3:6]) + + return n, nil +} + +// Write serializes the Segmentation parameter and returns it as a byte slice. +func (s *Segmentation) Write(b []byte) (int, error) { + if s.paramType != PTypeO { + logf("Segmentation parameter must be optional: %v", s) + } + + n := s.length + 2 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + b[0] = uint8(s.code) + b[1] = uint8(s.length) + + if s.FirstSegment { + b[2] |= 0b10000000 + } + + b[2] |= s.Class & 0b1 << 6 + b[2] |= s.RemainingSegments & 0b111 + + copy(b[3:], utils.Uint32To24(s.LocalReference)) + + return n, nil +} + +// MarshalLen returns the serial length of Segmentation. +func (s *Segmentation) MarshalLen() int { + return s.length + 2 +} + +// Code returns the Segmentation in ParameterNameCode. +func (s *Segmentation) Code() ParameterNameCode { + return s.code +} + +// Value returns the Segmentation as it is. +func (s *Segmentation) Value() *Segmentation { + return s +} + +// String returns the Segmentation in string. +func (s *Segmentation) String() string { + return fmt.Sprintf( + "{%s (%s): {FirstSegment=%t, Class=%d, RemainingSegments=%d, LocalReference=%d}}", + s.code, s.paramType, s.FirstSegment, s.Class, s.RemainingSegments, s.LocalReference, + ) +} + +// HopCounter represents the Hop Counter. +type HopCounter struct { + paramType ParameterType + code ParameterNameCode + length int + value uint8 +} + +// NewHopCounter creates a new HopCounter. +func NewHopCounter(v uint8) *HopCounter { + return &HopCounter{ + paramType: PTypeF, + code: PCodeHopCounter, + length: 1, + value: v, + } +} + +// NewHopCounterOptional creates a new optional HopCounter. +func NewHopCounterOptional(v uint8) *HopCounter { + h := NewHopCounter(v) + h.paramType = PTypeO + return h +} + +// ParseHopCounter parses the given byte sequence as a HopCounter. +func ParseHopCounter(b []byte) (*HopCounter, int, error) { + h := &HopCounter{} + n, err := h.Read(b) + if err != nil { + return nil, n, err + } + + return h, n, nil +} + +// ParseHopCounterOptional parses the given byte sequence as an optional HopCounter. +func ParseHopCounterOptional(b []byte) (*HopCounter, int, error) { + h := &HopCounter{paramType: PTypeO} + n, err := h.Read(b) + if err != nil { + return nil, n, err + } + + return h, n, nil +} + +// Read sets the values retrieved from byte sequence in a HopCounter. +func (h *HopCounter) Read(b []byte) (int, error) { + if h.paramType == PTypeO { + return h.readOptional(b) + } + return h.read(b) +} + +// read sets the values retrieved from byte sequence in a HopCounter. +func (h *HopCounter) read(b []byte) (int, error) { + n := 1 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + h.paramType = PTypeF + h.code = PCodeHopCounter + h.length = n + h.value = b[0] + + return n, nil +} + +func (h *HopCounter) readOptional(b []byte) (int, error) { + n := 3 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + h.code = ParameterNameCode(b[0]) + if h.code != PCodeHopCounter { + logf("invalid parameter code: expected %d, got %d", PCodeHopCounter, h.code) + } + h.length = int(b[1]) + if h.length != n-2 { + logf("%s: invalid length: expected %d, got %d", PCodeHopCounter, n-2, h.length) + } + h.value = b[2] + + return n, nil +} + +// Write serializes the HopCounter parameter and returns it as a byte slice. +func (h *HopCounter) Write(b []byte) (int, error) { + if h.paramType == PTypeO { + return h.writeOptional(b) + } + return h.write(b) +} + +// write serializes the HopCounter parameter and returns it as a byte slice. +func (h *HopCounter) write(b []byte) (int, error) { + if len(b) < h.length { + return 0, io.ErrUnexpectedEOF + } + + b[0] = h.value + return h.length, nil +} + +func (h *HopCounter) writeOptional(b []byte) (int, error) { + if len(b) < h.length { + return 0, io.ErrUnexpectedEOF + } + + b[0] = uint8(h.code) + b[1] = uint8(h.length) + b[2] = h.value + + return h.length, nil +} + +// MarshalLen returns the serial length of HopCounter. +func (h *HopCounter) MarshalLen() int { + if h.paramType == PTypeO { + return 2 + h.length + } + return h.length +} + +// Code returns the HopCounter in ParameterNameCode. +func (h *HopCounter) Code() ParameterNameCode { + return h.code +} + +// Value returns the HopCounter in uint8. +func (h *HopCounter) Value() uint8 { + return h.value +} + +// String returns the HopCounter in string. +func (h *HopCounter) String() string { + return fmt.Sprintf("{%s (%s): %d}", h.code, h.paramType, h.value) +} + +// Importance represents the Importance. +type Importance struct { + paramType ParameterType + code ParameterNameCode + length int + value uint8 +} + +// NewImportance creates a new Importance. +func NewImportance(v uint8) *Importance { + return &Importance{ + paramType: PTypeO, + code: PCodeImportance, + length: 1, + value: v & 0b111, + } +} + +// NewImportanceOptional creates a new optional Importance. +func NewImportanceOptional(v uint8) *Importance { + return NewImportance(v) +} + +// ParseImportance parses the given byte sequence as an Importance. +func ParseImportance(b []byte) (*Importance, int, error) { + i := &Importance{} + n, err := i.Read(b) + if err != nil { + return nil, n, err + } + + return i, n, nil +} + +// ParseImportanceOptional parses the given byte sequence as an optional Importance. +func ParseImportanceOptional(b []byte) (*Importance, int, error) { + return ParseImportance(b) +} + +// Read sets the values retrieved from byte sequence in a Importance. +func (i *Importance) Read(b []byte) (int, error) { + if i.paramType != PTypeO { + i.paramType = PTypeO + } + + n := 3 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + i.code = ParameterNameCode(b[0]) + if i.code != PCodeImportance { + logf("invalid parameter code: expected %d, got %d", PCodeImportance, i.code) + } + + i.length = int(b[1]) + if i.length != n-2 { + logf("%s: invalid length: expected %d, got %d", PCodeImportance, n-2, i.length) + } + + i.value = b[2] & 0b111 + + return n, nil +} + +// Write serializes the Importance parameter and returns it as a byte slice. +func (i *Importance) Write(b []byte) (int, error) { + if i.paramType != PTypeO { + logf("Importance parameter must be optional: %v", i) + } + + n := i.length + 2 + if len(b) < n { + return 0, io.ErrUnexpectedEOF + } + + b[0] = uint8(i.code) + b[1] = uint8(i.length) + b[2] = i.value + + return n, nil +} + +// MarshalLen returns the serial length of Importance. +func (i *Importance) MarshalLen() int { + return i.length + 2 +} + +// Code returns the Importance in ParameterNameCode. +func (i *Importance) Code() ParameterNameCode { + return i.code +} + +// Value returns the Importance in uint8. +func (i *Importance) Value() uint8 { + return i.value +} + +// String returns the Importance in string. +func (i *Importance) String() string { + return fmt.Sprintf("{%s (%s): %d}", i.code, i.paramType, i.value) +} + +// LongData represents the Long Data. +type LongData struct { + paramType ParameterType + code ParameterNameCode + length int + value []byte +} + +// NewLongData creates a new LongData. +func NewLongData(v []byte) *LongData { + return &LongData{ + paramType: PTypeV, + code: PCodeLongData, + length: len(v), + value: v, + } +} + +// ParseLongData parses the given byte sequence as a LongData. +func ParseLongData(b []byte) (*LongData, int, error) { + l := &LongData{} + n, err := l.Read(b) + if err != nil { + return nil, n, err + } + + return l, n, nil +} + +// Read sets the values retrieved from byte sequence in a LongData. +func (l *LongData) Read(b []byte) (int, error) { + n := len(b) + + l.paramType = PTypeV + l.code = PCodeLongData + + l.length = int(binary.BigEndian.Uint16(b[:2])) + if n < l.length+2 { + return n, io.ErrUnexpectedEOF + } + + l.value = b[2 : l.length+2] + return n, nil +} + +// Write serializes the LongData parameter and returns it as a byte slice. +func (l *LongData) Write(b []byte) (int, error) { + if len(b) < l.length+2 { + return 0, io.ErrUnexpectedEOF + } + + binary.BigEndian.PutUint16(b, uint16(l.length)) + copy(b[2:], l.value) + return l.length, nil +} + +// MarshalLen returns the serial length of LongData. +func (l *LongData) MarshalLen() int { + return l.length + 2 +} + +// Code returns the LongData in ParameterNameCode. +func (l *LongData) Code() ParameterNameCode { + return l.code +} + +// Value returns the LongData in []byte. +func (l *LongData) Value() []byte { + return l.value +} + +// String returns the LongData in string. +func (l *LongData) String() string { + return fmt.Sprintf("{%s (%s): %x}", l.code, l.paramType, l.value) +} diff --git a/params/params_test.go b/params/params_test.go index 23901c9..2ee1c8b 100644 --- a/params/params_test.go +++ b/params/params_test.go @@ -1,11 +1,9 @@ // Copyright 2019-2024 go-sccp authors. All rights reserved. // Use of this source code is governed by a MIT-style license that can be // found in the LICENSE file. - package params_test import ( - "encoding" "io" "testing" @@ -14,21 +12,39 @@ import ( ) type serializable interface { - encoding.BinaryMarshaler - MarshalLen() int + io.ReadWriter } -type decodeFunc func([]byte) (serializable, error) - -var testcases = []struct { +var cases = []struct { description string structured serializable serialized []byte - decodeFunc + parseFunc func([]byte) (serializable, int, error) }{ { - description: "PartyAddress", - structured: params.NewPartyAddressTyped( + description: "EndOfOptionalParameters", + structured: params.NewEndOfOptionalParameters(), + serialized: []byte{0x00}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseEndOfOptionalParameters(b) + }, + }, { + description: "DestinationLocalReference", + structured: params.NewDestinationLocalReference(0x123456), + serialized: []byte{0x12, 0x34, 0x56}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseDestinationLocalReference(b) + }, + }, { + description: "SourceLocalReference", + structured: params.NewSourceLocalReference(0x123456), + serialized: []byte{0x12, 0x34, 0x56}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseSourceLocalReference(b) + }, + }, { + description: "CalledPartyAddress w/ GlobalTitle", + structured: params.NewCalledPartyAddress( params.NewAddressIndicator(false, true, false, params.GTITTNPESNAI), 0, 6, // SPC, SSN params.NewGlobalTitle( @@ -45,78 +61,219 @@ var testcases = []struct { serialized: []byte{ 0x0a, 0x12, 0x06, 0x00, 0x11, 0x04, 0x21, 0x43, 0x65, 0x87, 0x09, }, - decodeFunc: func(b []byte) (serializable, error) { - v, err := params.ParsePartyAddress(b) - if err != nil { - return nil, err - } - - return v, nil + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseCalledPartyAddress(b) }, }, { - description: "PartyAddress/2-bytes", - structured: params.NewPartyAddressTyped( - params.NewAddressIndicator(false, true, true, params.GlobalTitleIndicator(0)), + description: "CallingPartyAddress w/ GlobalTitle", + structured: params.NewCallingPartyAddress( + params.NewAddressIndicator(false, true, false, params.GTITTNPESNAI), + 0, 6, // SPC, SSN + params.NewGlobalTitle( + params.GTITTNPESNAI, + params.TranslationType(0), + params.NPISDNTelephony, + params.ESBCDOdd, + params.NAIInternationalNumber, + []byte{ + 0x21, 0x43, 0x65, 0x87, 0x09, + }, + ), + ), + serialized: []byte{ + 0x0a, 0x12, 0x06, 0x00, 0x11, 0x04, 0x21, 0x43, 0x65, 0x87, 0x09, + }, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseCallingPartyAddress(b) + }, + }, { + description: "CalledPartyAddress/2-bytes", + structured: params.NewCalledPartyAddress( + params.NewAddressIndicator(false, true, true, params.GTINoGT), 0, 6, nil, // SPC, SSN, GT ), serialized: []byte{ 0x02, 0x42, 0x06, }, - decodeFunc: func(b []byte) (serializable, error) { - v, err := params.ParsePartyAddress(b) - if err != nil { - return nil, err - } - - return v, nil + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseCalledPartyAddress(b) + }, + }, { + description: "ProtocolClass/Class 1, no ReturnOnError", + structured: params.NewProtocolClass(1, false), + serialized: []byte{0x01}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseProtocolClass(b) + }, + }, { + description: "ProtocolClass/Class 4, ReturnOnError", + structured: params.NewProtocolClass(4, true), + serialized: []byte{0x84}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseProtocolClass(b) + }, + }, { + description: "SegmentingReassembling/More data", + structured: params.NewSegmentingReassembling(true), + serialized: []byte{0x01}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseSegmentingReassembling(b) + }, + }, { + description: "SegmentingReassembling/No more data", + structured: params.NewSegmentingReassembling(false), + serialized: []byte{0x00}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseSegmentingReassembling(b) + }, + }, { + description: "ReceiveSequenceNumber", + structured: params.NewReceiveSequenceNumber(0x76), + serialized: []byte{0x76}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseReceiveSequenceNumber(b) + }, + }, { + description: "SequencingSegmenting/More data", + structured: params.NewSequencingSegmenting(0x76, 0x78, true), + serialized: []byte{0x76, 0x79}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseSequencingSegmenting(b) + }, + }, { + description: "SequencingSegmenting/No more data", + structured: params.NewSequencingSegmenting(0x76, 0x78, false), + serialized: []byte{0x76, 0x78}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseSequencingSegmenting(b) + }, + }, { + description: "Credit/Fixed", + structured: params.NewCredit(0x77), + serialized: []byte{0x77}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseCredit(b) + }, + }, { + description: "Credit/Optional", + structured: params.NewCreditOptional(0x77), + serialized: []byte{0x09, 0x01, 0x77}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseCreditOptional(b) + }, + }, { + description: "ReleaseCause/Generics", + structured: params.NewCause(params.ReleaseCauseSCCPUserOriginated), + serialized: []byte{0x03}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseReleaseCause(b) + }, + }, { + description: "ReturnCause", + structured: params.NewCause(params.ReturnCauseSubsystemFailure), + serialized: []byte{0x03}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseReturnCause(b) + }, + }, { + description: "ResetCause", + structured: params.NewCause(params.ResetCauseMessageOutOfOrderIncorrectReceiveSequenceNumber), + serialized: []byte{0x03}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseResetCause(b) + }, + }, { + description: "ErrorCause", + structured: params.NewCause(params.ErrorCauseServiceClassMismatch), + serialized: []byte{0x03}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseErrorCause(b) + }, + }, { + description: "RefusalCause", + structured: params.NewCause(params.RefusalCauseSCCPUserOriginated), + serialized: []byte{0x03}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseRefusalCause(b) + }, + }, { + description: "Data/Variable", + structured: params.NewData([]byte{0xde, 0xad, 0xbe, 0xef}), + serialized: []byte{0x04, 0xde, 0xad, 0xbe, 0xef}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseData(b) + }, + }, { + description: "Data/Optional", + structured: params.NewDataOptional([]byte{0xde, 0xad, 0xbe, 0xef}), + serialized: []byte{0x0f, 0x04, 0xde, 0xad, 0xbe, 0xef}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseDataOptional(b) + }, + }, { + description: "Segmentation", + structured: params.NewSegmentation(true, 1, 2, 0x123456), + serialized: []byte{0x10, 0x04, 0xc2, 0x12, 0x34, 0x56}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseSegmentation(b) + }, + }, { + description: "HopCounter/Fixed", + structured: params.NewHopCounter(0x03), + serialized: []byte{0x03}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseHopCounter(b) + }, + }, { + description: "HopCounter/Optional", + structured: params.NewHopCounterOptional(0x03), + serialized: []byte{0x11, 0x01, 0x03}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseHopCounterOptional(b) + }, + }, { + description: "Importance", + structured: params.NewImportance(0x07), + serialized: []byte{0x12, 0x01, 0x07}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseImportance(b) + }, + }, { + description: "LongData/512 bytes", + structured: params.NewLongData([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff}), + serialized: []byte{0x02, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff}, + parseFunc: func(b []byte) (serializable, int, error) { + return params.ParseLongData(b) }, }, } -func TestStructuredParams(t *testing.T) { +func TestParams(t *testing.T) { t.Helper() - for _, c := range testcases { + for _, c := range cases { t.Run(c.description, func(t *testing.T) { t.Run("Decode", func(t *testing.T) { - prm, err := c.decodeFunc(c.serialized) + prm, _, err := c.parseFunc(c.serialized) if err != nil { t.Fatal(err) } if got, want := prm, c.structured; !verify.Values(t, "", got, want) { - t.Fail() + t.Errorf("got: %v, want: %v", got, want) } }) t.Run("Serialize", func(t *testing.T) { - b, err := c.structured.MarshalBinary() - if err != nil { + b := make([]byte, len(c.serialized)) + if _, err := c.structured.Write(b); err != nil { t.Fatal(err) } if got, want := b, c.serialized; !verify.Values(t, "", got, want) { - t.Fail() - } - }) - - t.Run("Len", func(t *testing.T) { - if got, want := c.structured.MarshalLen(), len(c.serialized); got != want { - t.Fatalf("got %v want %v", got, want) + t.Errorf("got: %v, want: %v", got, want) } }) }) } } - -func TestPartialStructuredParams(t *testing.T) { - for _, c := range testcases { - for i := range c.serialized { - partial := c.serialized[:i] - _, err := c.decodeFunc(partial) - if err != io.ErrUnexpectedEOF { - t.Errorf("%#x: got error %v, want unexpected EOF", partial, err) - } - } - } -} diff --git a/params/party-address.go b/params/party-address.go deleted file mode 100644 index b6ca3d1..0000000 --- a/params/party-address.go +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright 2019-2024 go-sccp authors. All rights reserved. -// Use of this source code is governed by a MIT-style license that can be -// found in the LICENSE file. - -package params - -import ( - "encoding/binary" - "fmt" - "io" - - "github.com/wmnsk/go-sccp/utils" -) - -// PartyAddress is a SCCP parameter that represents a Called/Calling Party Address. -type PartyAddress struct { - Length uint8 - Indicator uint8 - SignalingPointCode uint16 - SubsystemNumber uint8 - *GlobalTitle -} - -// NewAddressIndicator creates a new AddressIndicator, which is meant to be used in -// NewPartyAddress as the first argument. -// -// The last bit, which is "reserved for national use", is always set to 0. -// You can set the bit to 1 by doing `| 0b10000000` to the result of this function. -func NewAddressIndicator(hasPC, hasSSN, routeOnSSN bool, gti GlobalTitleIndicator) uint8 { - var ai uint8 - if hasPC { - ai |= 0b00000001 - } - if hasSSN { - ai |= 0b00000010 - } - if routeOnSSN { - ai |= 0b01000000 - } - ai |= uint8(gti) << 2 - - return ai -} - -// NewPartyAddress creates a new PartyAddress including GlobalTitle. -// Deprecated: Use NewPartyAddressTyped instead. -func NewPartyAddress(ai, spc, ssn, tt, np, es, nai int, addr []byte) *PartyAddress { - var globalTitle *GlobalTitle - gti := gti(ai) - if gti == 0 { - globalTitle = nil - } else { - globalTitle = NewGlobalTitle( - gti, - TranslationType(tt), - NumberingPlan(np), - EncodingScheme(es), - NatureOfAddressIndicator(nai), - addr, - ) - } - return NewPartyAddressTyped(uint8(ai), uint16(spc), uint8(ssn), globalTitle) -} - -// NewPartyAddress creates a new PartyAddress from properly-typed values. -// -// The given SPC and SSN are set to 0 if the corresponding bit is not properly set in the -// AddressIndicator. Use NewAddressIndicator to create a proper AddressIndicator. -func NewPartyAddressTyped(ai uint8, spc uint16, ssn uint8, gt *GlobalTitle) *PartyAddress { - p := &PartyAddress{ - Indicator: ai, - GlobalTitle: gt, - } - - if p.HasPC() { - p.SignalingPointCode = spc - } - - if p.HasSSN() { - p.SubsystemNumber = ssn - } - - p.SetLength() - return p -} - -// MarshalBinary returns the byte sequence generated from a PartyAddress instance. -func (p *PartyAddress) MarshalBinary() ([]byte, error) { - b := make([]byte, p.MarshalLen()) - if err := p.MarshalTo(b); err != nil { - return nil, err - } - return b, nil -} - -// MarshalTo puts the byte sequence in the byte array given as b. -func (p *PartyAddress) MarshalTo(b []byte) error { - b[0] = p.Length - b[1] = p.Indicator - var offset = 2 - if p.HasPC() { - binary.BigEndian.PutUint16(b[offset:offset+2], p.SignalingPointCode) - offset += 2 - } - if p.HasSSN() { - b[offset] = p.SubsystemNumber - offset++ - } - - if p.GlobalTitle != nil { - return p.GlobalTitle.MarshalTo(b[offset:p.MarshalLen()]) - } - - return nil -} - -// ParsePartyAddress decodes given byte sequence as a SCCP common header. -func ParsePartyAddress(b []byte) (*PartyAddress, error) { - p := new(PartyAddress) - if err := p.UnmarshalBinary(b); err != nil { - return nil, err - } - - return p, nil -} - -// UnmarshalBinary sets the values retrieved from byte sequence in a SCCP common header. -func (p *PartyAddress) UnmarshalBinary(b []byte) error { - if len(b) < 2 { - return io.ErrUnexpectedEOF - } - p.Length = b[0] - l := int(p.Length) - if l >= len(b) { - return io.ErrUnexpectedEOF - } - p.Indicator = b[1] - - var offset = 2 - if p.HasPC() { - end := offset + 2 - if end >= len(b) { - return io.ErrUnexpectedEOF - } - p.SignalingPointCode = binary.BigEndian.Uint16(b[offset:end]) - offset = end - } - if p.HasSSN() { - p.SubsystemNumber = b[offset] - offset++ - } - - gti := p.GTI() - if gti == 0 { - return nil - } - - gt := &GlobalTitle{GTI: gti} - if err := gt.UnmarshalBinary(b[offset : l+1]); err != nil { - return err - } - p.GlobalTitle = gt - - return nil -} - -// MarshalLen returns the serial length. -func (p *PartyAddress) MarshalLen() int { - l := 2 - if p.HasPC() { - l += 2 - } - if p.HasSSN() { - l++ - } - - if p.GlobalTitle != nil { - l = l + p.GlobalTitle.MarshalLen() - } - - return l -} - -// SetLength sets the length in Length field. -func (p *PartyAddress) SetLength() { - p.Length = uint8(p.MarshalLen()) - 1 -} - -// RouteOnGT reports whether the packet is routed on Global Title or not. -func (p *PartyAddress) RouteOnGT() bool { - return (int(p.Indicator) >> 6 & 0b1) == 0 -} - -// RouteOnSSN reports whether the packet is routed on SSN or not. -func (p *PartyAddress) RouteOnSSN() bool { - return !p.RouteOnGT() -} - -// GTI returns GlobalTitleIndicator value retrieved from Indicator. -func (p *PartyAddress) GTI() GlobalTitleIndicator { - return gti(int(p.Indicator)) -} - -func gti(ai int) GlobalTitleIndicator { - return GlobalTitleIndicator(ai >> 2 & 0b1111) -} - -// HasSSN reports whether PartyAddress has a Subsystem Number. -func (p *PartyAddress) HasSSN() bool { - return (int(p.Indicator) >> 1 & 0b1) == 1 -} - -// HasPC reports whether PartyAddress has a Signaling Point Code. -func (p *PartyAddress) HasPC() bool { - return (int(p.Indicator) & 0b1) == 1 -} - -// IsOddDigits reports whether AddressInformation is odd number or not. -func (p *PartyAddress) IsOddDigits() bool { - return p.EncodingScheme == 1 -} - -// GTString returns the AddressInformation in human readable string. -func (p *PartyAddress) GTString() string { - return utils.SwappedBytesToStr(p.AddressInformation, p.IsOddDigits()) -} - -// String returns the PartyAddress values in human readable format. -func (p *PartyAddress) String() string { - return fmt.Sprintf("{Length: %d, Indicator: %#08b, SignalingPointCode: %d, SubsystemNumber: %d, GlobalTitle: %v}", - p.Length, p.Indicator, p.SignalingPointCode, p.SubsystemNumber, p.GlobalTitle, - ) -} diff --git a/params/protocol-class.go b/params/protocol-class.go deleted file mode 100644 index 20f8518..0000000 --- a/params/protocol-class.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2019-2024 go-sccp authors. All rights reserved. -// Use of this source code is governed by a MIT-style license that can be -// found in the LICENSE file. - -package params - -// ProtocolClass is a Protocol Class SCCP parameter. -type ProtocolClass uint8 - -// NewProtocolClass creates a new ProtocolClass. -func NewProtocolClass(cls int, opts bool) ProtocolClass { - if opts { - return ProtocolClass(cls | 0x80) - } - return ProtocolClass(cls) -} - -// Class returns the class part from ProtocolClass parameter. -func (p ProtocolClass) Class() int { - return int(p) & 0xf -} - -// ReturnOnError judges if ProtocolClass has "Return Message On Error" option. -func (p ProtocolClass) ReturnOnError() bool { - return (int(p) >> 7) == 0 -} diff --git a/params/protocol-class_test.go b/params/protocol-class_test.go deleted file mode 100644 index f870e3a..0000000 --- a/params/protocol-class_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2019-2024 go-sccp authors. All rights reserved. -// Use of this source code is governed by a MIT-style license that can be -// found in the LICENSE file. - -package params - -import ( - "testing" -) - -var testProtocolClassBytes = []byte{ - 0x01, 0x02, 0x03, 0x04, - 0x81, 0x82, 0x83, 0x84, -} - -func TestNewProtocolClass(t *testing.T) { - protocolClasses := []ProtocolClass{ - NewProtocolClass(1, false), - NewProtocolClass(2, false), - NewProtocolClass(3, false), - NewProtocolClass(4, false), - NewProtocolClass(1, true), - NewProtocolClass(2, true), - NewProtocolClass(3, true), - NewProtocolClass(4, true), - } - for i, p := range protocolClasses { - x := testProtocolClassBytes[i] - if uint8(p) != x { - t.Errorf("Bytes doesn't match. Want: %#x, Got: %#x at %dth", x, p, i) - } - } -} - -func TestClass(t *testing.T) { - protocolClasses := []ProtocolClass{ - NewProtocolClass(1, false), - NewProtocolClass(2, false), - NewProtocolClass(3, false), - NewProtocolClass(4, false), - NewProtocolClass(1, true), - NewProtocolClass(2, true), - NewProtocolClass(3, true), - NewProtocolClass(4, true), - } - for i, p := range protocolClasses { - if i < 4 { - if got, want := p.Class(), i+1; got != want { - t.Errorf("Class doesn't match. Want: %d, Got: %d when ProtocolClass is %x", want, got, p) - } - } else if i >= 4 { - if got, want := p.Class(), i-3; got != want { - t.Errorf("Class doesn't match. Want: %d, Got: %d when ProtocolClass is %x", want, got, p) - } - } - } -} - -func TestReturnOnError(t *testing.T) { - protocolClasses := []ProtocolClass{ - NewProtocolClass(1, false), - NewProtocolClass(2, false), - NewProtocolClass(3, false), - NewProtocolClass(4, false), - NewProtocolClass(1, true), - NewProtocolClass(2, true), - NewProtocolClass(3, true), - NewProtocolClass(4, true), - } - for i, p := range protocolClasses { - if i < 4 { - if got := p.ReturnOnError(); !got { - t.Errorf("ReturnOnError doesn't match. Want: %v, Got: %v when ProtocolClass is %x", false, got, p) - } - } else if i >= 4 { - if got := p.ReturnOnError(); got { - t.Errorf("ReturnOnError doesn't match. Want: %v, Got: %v when ProtocolClass is %x", true, got, p) - } - } - } -} diff --git a/sccp.go b/sccp.go index 728e1c2..8ced956 100644 --- a/sccp.go +++ b/sccp.go @@ -13,6 +13,7 @@ package sccp import ( "encoding" "fmt" + "io" ) // MsgType is type of SCCP message. @@ -20,27 +21,27 @@ type MsgType uint8 // Message Type definitions. const ( - _ MsgType = iota - MsgTypeCR - MsgTypeCC - MsgTypeCREF - MsgTypeRLSD - MsgTypeRLC - MsgTypeDT1 - MsgTypeDT2 - MsgTypeAK - MsgTypeUDT - MsgTypeUDTS - MsgTypeED - MsgTypeEA - MsgTypeRSR - MsgTypeRSC - MsgTypeERR - MsgTypeIT - MsgTypeXUDT - MsgTypeXUDTS - MsgTypeLUDT - MsgTypeLUDTS + _ MsgType = iota + MsgTypeCR // CR + MsgTypeCC // CC + MsgTypeCREF // CREF + MsgTypeRLSD // RLSD + MsgTypeRLC // RLC + MsgTypeDT1 // DT1 + MsgTypeDT2 // DT2 + MsgTypeAK // AK + MsgTypeUDT // UDT + MsgTypeUDTS // UDTS + MsgTypeED // ED + MsgTypeEA // EA + MsgTypeRSR // RSR + MsgTypeRSC // RSC + MsgTypeERR // ERR + MsgTypeIT // IT + MsgTypeXUDT // XUDT + MsgTypeXUDTS // XUDTS + MsgTypeLUDT // LUDT + MsgTypeLUDTS // LUDTS ) // Message is an interface that defines SCCP messages. @@ -55,34 +56,40 @@ type Message interface { } // ParseMessage decodes the byte sequence into Message by Message Type. -// Currently this only supports UDT type of message only. func ParseMessage(b []byte) (Message, error) { + if len(b) < 1 { + return nil, fmt.Errorf("invalid SCCP message %v: %w", b, io.ErrUnexpectedEOF) + } + var m Message switch MsgType(b[0]) { /* TODO: implement! - case CR: - case CC: - case CREF: - case RLSD: - case RLC: - case DT1: - case DT2: - case AK: + case MsgTypeCR: + case MsgTypeCC: + case MsgTypeCREF: + case MsgTypeRLSD: + case MsgTypeRLC: + case MsgTypeDT1: + case MsgTypeDT2: + case MsgTypeAK: */ case MsgTypeUDT: m = &UDT{} /* TODO: implement! - case UDTS: - case ED: - case EA: - case RSR: - case RSC: - case ERR: - case IT: - case XUDT: - case XUDTS: - case LUDT: - case LUDTS: + case MsgTypeUDTS: + case MsgTypeED: + case MsgTypeEA: + case MsgTypeRSR: + case MsgTypeRSC: + case MsgTypeERR: + case MsgTypeIT: + */ + case MsgTypeXUDT: + m = &XUDT{} + /* TODO: implement! + case MsgTypeXUDTS: + case MsgTypeLUDT: + case MsgTypeLUDTS: */ default: return nil, UnsupportedTypeError(b[0]) diff --git a/sccp_test.go b/sccp_test.go index ca76ee1..af18a49 100644 --- a/sccp_test.go +++ b/sccp_test.go @@ -7,7 +7,6 @@ package sccp_test import ( "encoding" "io" - "log" "strings" "testing" @@ -18,6 +17,7 @@ import ( type serializable interface { encoding.BinaryMarshaler + MarshalTo([]byte) error MarshalLen() int } @@ -25,14 +25,14 @@ var testcases = []struct { description string structured serializable serialized []byte - decodeFunc func([]byte) (serializable, error) + parseFunc func([]byte) (serializable, error) }{ { description: "UDT", structured: sccp.NewUDT( 1, // Protocol Class true, // Message handling - params.NewPartyAddressTyped( + params.NewCalledPartyAddress( params.NewAddressIndicator(false, true, false, params.GTITTNPESNAI), 0, 6, // SPC, SSN params.NewGlobalTitle( @@ -44,7 +44,7 @@ var testcases = []struct { []byte{0x21, 0x43, 0x65, 0x87, 0x09, 0x21, 0x43, 0x65}, ), ), - params.NewPartyAddressTyped( + params.NewCallingPartyAddress( params.NewAddressIndicator(false, true, false, params.GTITTNPESNAI), 0, 7, // SPC, SSN params.NewGlobalTitle( @@ -59,17 +59,15 @@ var testcases = []struct { []byte{0xde, 0xad, 0xbe, 0xef}, ), serialized: []byte{ - 0x09, 0x81, 0x03, 0x10, 0x1a, 0x0d, 0x12, 0x06, 0x00, 0x11, 0x04, 0x21, 0x43, 0x65, 0x87, 0x09, - 0x21, 0x43, 0x65, 0x0a, 0x12, 0x07, 0x00, 0x12, 0x04, 0x89, 0x67, 0x45, 0x23, 0x01, 0x04, 0xde, - 0xad, 0xbe, 0xef, + 0x09, + 0x81, + 0x03, 0x10, 0x1a, + 0x0d, 0x12, 0x06, 0x00, 0x11, 0x04, 0x21, 0x43, 0x65, 0x87, 0x09, 0x21, 0x43, 0x65, + 0x0a, 0x12, 0x07, 0x00, 0x12, 0x04, 0x89, 0x67, 0x45, 0x23, 0x01, + 0x04, 0xde, 0xad, 0xbe, 0xef, }, - decodeFunc: func(b []byte) (serializable, error) { - v, err := sccp.ParseUDT(b) - if err != nil { - return nil, err - } - - return v, nil + parseFunc: func(b []byte) (serializable, error) { + return sccp.ParseUDT(b) }, }, { @@ -77,54 +75,126 @@ var testcases = []struct { structured: sccp.NewUDT( 1, // Protocol Class true, // Message handling - params.NewPartyAddress( // CalledPartyAddress: 1234567890123456 - 0x42, 0, 6, 0x00, // Indicator, SPC, SSN, TT - 0x00, 0x00, 0x00, // NP, ES, NAI - nil, // GlobalTitleInformation - ), - params.NewPartyAddress( // CalledPartyAddress: 1234567890123456 - 0x42, 0, 7, 0x00, // Indicator, SPC, SSN, TT - 0x00, 0x00, 0x00, // NP, ES, NAI - nil, // GlobalTitleInformation - ), + params.NewCalledPartyAddress(0x42, 0, 6, nil), + params.NewCallingPartyAddress(0x42, 0, 7, nil), nil, ), serialized: []byte{ 0x09, 0x81, 0x03, 0x05, 0x07, 0x02, 0x42, 0x06, 0x02, 0x42, 0x07, 0x00, }, - decodeFunc: func(b []byte) (serializable, error) { - v, err := sccp.ParseUDT(b) - if err != nil { - return nil, err - } - - return v, nil + parseFunc: func(b []byte) (serializable, error) { + return sccp.ParseUDT(b) + }, + }, + { + description: "XUDT/No optionals", + structured: sccp.NewXUDT( + 1, // Protocol Class + true, // Message handling + 2, // Hop Counter + params.NewCalledPartyAddress( + params.NewAddressIndicator(false, true, false, params.GTITTNPESNAI), + 0, 6, // SPC, SSN + params.NewGlobalTitle( + params.GTITTNPESNAI, + params.TranslationType(0), + params.NPISDNTelephony, + params.ESBCDOdd, + params.NAIInternationalNumber, + []byte{0x21, 0x43, 0x65, 0x87, 0x09, 0x21, 0x43, 0x65}, + ), + ), + params.NewCallingPartyAddress( + params.NewAddressIndicator(false, true, false, params.GTITTNPESNAI), + 0, 7, // SPC, SSN + params.NewGlobalTitle( + params.GTITTNPESNAI, + params.TranslationType(0), + params.NPISDNTelephony, + params.ESBCDEven, + params.NAIInternationalNumber, + []byte{0x89, 0x67, 0x45, 0x23, 0x01}, + ), + ), + []byte{0xde, 0xad, 0xbe, 0xef}, + ), + serialized: []byte{ + 0x11, // MsgType + 0x81, // Protocol Class + 0x02, // Hop Counter + 0x04, 0x11, 0x1b, 0x00, // Pointers + 0x0d, 0x12, 0x06, 0x00, 0x11, 0x04, 0x21, 0x43, 0x65, 0x87, 0x09, 0x21, 0x43, 0x65, // CdPA + 0x0a, 0x12, 0x07, 0x00, 0x12, 0x04, 0x89, 0x67, 0x45, 0x23, 0x01, // CgPA + 0x04, 0xde, 0xad, 0xbe, 0xef, // Data + }, + parseFunc: func(b []byte) (serializable, error) { + return sccp.ParseXUDT(b) + }, + }, + { + description: "XUDT/with optionals", + structured: sccp.NewXUDT( + 1, // Protocol Class + true, // Message handling + 2, // Hop Counter + params.NewCalledPartyAddress( + params.NewAddressIndicator(false, true, false, params.GTITTNPESNAI), + 0, 6, // SPC, SSN + params.NewGlobalTitle( + params.GTITTNPESNAI, + params.TranslationType(0), + params.NPISDNTelephony, + params.ESBCDOdd, + params.NAIInternationalNumber, + []byte{0x21, 0x43, 0x65, 0x87, 0x09, 0x21, 0x43, 0x65}, + ), + ), + params.NewCallingPartyAddress( + params.NewAddressIndicator(false, true, false, params.GTITTNPESNAI), + 0, 7, // SPC, SSN + params.NewGlobalTitle( + params.GTITTNPESNAI, + params.TranslationType(0), + params.NPISDNTelephony, + params.ESBCDEven, + params.NAIInternationalNumber, + []byte{0x89, 0x67, 0x45, 0x23, 0x01}, + ), + ), + []byte{0xde, 0xad, 0xbe, 0xef}, + params.NewSegmentation(true, 1, 2, 0xffffff), + params.NewImportance(2), + ), + serialized: []byte{ + 0x11, // MsgType + 0x81, // Protocol Class + 0x02, // Hop Counter + 0x04, 0x11, 0x1b, 0x1f, // Pointers + 0x0d, 0x12, 0x06, 0x00, 0x11, 0x04, 0x21, 0x43, 0x65, 0x87, 0x09, 0x21, 0x43, 0x65, // CdPA + 0x0a, 0x12, 0x07, 0x00, 0x12, 0x04, 0x89, 0x67, 0x45, 0x23, 0x01, // CgPA + 0x04, 0xde, 0xad, 0xbe, 0xef, // Data + 0x10, 0x04, 0xc2, 0xff, 0xff, 0xff, // Segmentation + 0x12, 0x01, 0x02, // Importance + 0x00, // End of optional parameters + }, + parseFunc: func(b []byte) (serializable, error) { + return sccp.ParseXUDT(b) }, }, { description: "SCMG SSA", structured: sccp.NewSCMG(sccp.SCMGTypeSSA, 9, 405, 0, 0), serialized: []byte{0x1, 0x09, 0x95, 0x01, 0x00}, - decodeFunc: func(b []byte) (serializable, error) { - v, err := sccp.ParseSCMG(b) - if err != nil { - return nil, err - } - - return v, nil + parseFunc: func(b []byte) (serializable, error) { + return sccp.ParseSCMG(b) }, }, { description: "SCMG SSC", structured: sccp.NewSCMG(sccp.SCMGTypeSSC, 9, 405, 0, 4), serialized: []byte{0x6, 0x09, 0x95, 0x01, 0x00, 0x04}, - decodeFunc: func(b []byte) (serializable, error) { - v, err := sccp.ParseSCMG(b) - if err != nil { - return nil, err - } - - return v, nil + parseFunc: func(b []byte) (serializable, error) { + return sccp.ParseSCMG(b) }, }, } @@ -135,7 +205,7 @@ func TestMessages(t *testing.T) { for _, c := range testcases { t.Run(c.description, func(t *testing.T) { t.Run("Decode", func(t *testing.T) { - msg, err := c.decodeFunc(c.serialized) + msg, err := c.parseFunc(c.serialized) if err != nil { t.Fatal(err) } @@ -148,7 +218,6 @@ func TestMessages(t *testing.T) { t.Run("Serialize", func(t *testing.T) { b, err := c.structured.MarshalBinary() if err != nil { - log.Println(err) t.Fatal(err) } @@ -191,9 +260,9 @@ func TestPartialStructuredMessages(t *testing.T) { } for i := range c.serialized { partial := c.serialized[:i] - _, err := c.decodeFunc(partial) + _, err := c.parseFunc(partial) if err != io.ErrUnexpectedEOF { - t.Errorf("%#x: got error %v, want unexpected EOF", partial, err) + t.Errorf("parse %v / %#x: got error %v, want unexpected EOF", c.description, partial, err) } } @@ -202,8 +271,8 @@ func TestPartialStructuredMessages(t *testing.T) { continue } b := make([]byte, i) - if err := c.structured.(*sccp.UDT).MarshalTo(b); err != io.ErrUnexpectedEOF { - t.Errorf("%#x: got error %v, want unexpected EOF", b, err) + if err := c.structured.MarshalTo(b); err != io.ErrUnexpectedEOF { + t.Errorf("marshal %v / %#x: got error %v, want unexpected EOF", c.description, b, err) } } } diff --git a/scmg.go b/scmg.go index 6a6b7ee..c1fc577 100644 --- a/scmg.go +++ b/scmg.go @@ -15,13 +15,13 @@ type SCMGType uint8 // Table 23/Q.713 const ( - _ SCMGType = iota - SCMGTypeSSA - SCMGTypeSSP - SCMGTypeSST - SCMGTypeSOR - SCMGTypeSOG - SCMGTypeSSC + _ SCMGType = iota + SCMGTypeSSA // SSA + SCMGTypeSSP // SSP + SCMGTypeSST // SST + SCMGTypeSOR // SOR + SCMGTypeSOG // SOG + SCMGTypeSSC // SSC ) // SCMG represents a SCCP Management message (SCMG). @@ -35,16 +35,14 @@ type SCMG struct { } // NewSCMG creates a new SCMG. -func NewSCMG(typ SCMGType, affectedSSN uint8, affectedPC uint16, subsystemMultiplicityIndicator uint8, sccpCongestionLevel uint8) *SCMG { - s := &SCMG{ +func NewSCMG(typ SCMGType, assn uint8, apc uint16, smi uint8, scl uint8) *SCMG { + return &SCMG{ Type: typ, - AffectedSSN: affectedSSN, - AffectedPC: affectedPC, - SubsystemMultiplicityIndicator: subsystemMultiplicityIndicator, - SCCPCongestionLevel: sccpCongestionLevel, + AffectedSSN: assn, + AffectedPC: apc, + SubsystemMultiplicityIndicator: smi, + SCCPCongestionLevel: scl, } - - return s } // MarshalBinary returns the byte sequence generated from a SCMG instance. @@ -123,7 +121,7 @@ func (s *SCMG) MarshalLen() int { // String returns the SCMG values in human readable format. func (s *SCMG) String() string { - return fmt.Sprintf("{Type: %d, Affected SSN: %v, Affected PC: %v, Subsystem Multiplicity Indicator: %d, SCCP Congestion Level: %d}", + return fmt.Sprintf("%s: {AffectedSSN: %v, AffectedPC: %v, SubsystemMultiplicityIndicator: %d, SCCPCongestionLevel: %d}", s.Type, s.AffectedSSN, s.AffectedPC, @@ -139,5 +137,5 @@ func (s *SCMG) MessageType() SCMGType { // MessageTypeName returns the Message Type in string. func (s *SCMG) MessageTypeName() string { - return "SCMG" + return s.Type.String() } diff --git a/udt.go b/udt.go index 2ee1b18..617c479 100644 --- a/udt.go +++ b/udt.go @@ -11,32 +11,30 @@ import ( "github.com/wmnsk/go-sccp/params" ) -// UDT represents a SCCP Message Unit Data(UDT). +// UDT represents a SCCP Message Unit Data (UDT). type UDT struct { - Type MsgType - params.ProtocolClass - Ptr1, Ptr2, Ptr3 uint8 + Type MsgType + ProtocolClass *params.ProtocolClass CalledPartyAddress *params.PartyAddress CallingPartyAddress *params.PartyAddress - DataLength uint8 - Data []byte + Data *params.Data + + ptr1, ptr2, ptr3 uint8 } // NewUDT creates a new UDT. -func NewUDT(pcls int, mhandle bool, cdpa, cgpa *params.PartyAddress, data []byte) *UDT { +func NewUDT(pcls int, retOnErr bool, cdpa, cgpa *params.PartyAddress, data []byte) *UDT { u := &UDT{ - Type: MsgTypeUDT, - ProtocolClass: params.NewProtocolClass( - pcls, mhandle, - ), - Ptr1: 3, + Type: MsgTypeUDT, + ProtocolClass: params.NewProtocolClass(pcls, retOnErr), CalledPartyAddress: cdpa, CallingPartyAddress: cgpa, - Data: data, + Data: params.NewData(data), } - u.Ptr2 = u.Ptr1 + cdpa.Length - u.Ptr3 = u.Ptr2 + cgpa.Length - u.SetLength() + + u.ptr1 = 3 + u.ptr2 = u.ptr1 + uint8(cdpa.MarshalLen()) - 1 + u.ptr3 = u.ptr2 + uint8(cgpa.MarshalLen()) - 1 return u } @@ -60,35 +58,43 @@ func (u *UDT) MarshalTo(b []byte) error { } b[0] = uint8(u.Type) - b[1] = uint8(u.ProtocolClass) - b[2] = u.Ptr1 - if n := int(u.Ptr1); l < n { + + n := 1 + m, err := u.ProtocolClass.Write(b[1:]) + if err != nil { + return err + } + n += m + + b[n] = u.ptr1 + if p := int(u.ptr1); l < p { return io.ErrUnexpectedEOF } - b[3] = u.Ptr2 - if n := int(u.Ptr2 + 3); l < n { + b[n+1] = u.ptr2 + if p := int(u.ptr2 + 3); l < p { return io.ErrUnexpectedEOF } - b[4] = u.Ptr3 - if n := int(u.Ptr3 + 5); l < n { + b[n+2] = u.ptr3 + if p := int(u.ptr3 + 5); l < p { return io.ErrUnexpectedEOF } + n += 3 - if err := u.CalledPartyAddress.MarshalTo(b[5:int(u.Ptr2+3)]); err != nil { + cdpaEnd := int(u.ptr2 + 3) + cgpaEnd := int(u.ptr3 + 4) + if _, err := u.CalledPartyAddress.Write(b[n:cdpaEnd]); err != nil { return err } - if err := u.CallingPartyAddress.MarshalTo(b[int(u.Ptr2+3):int(u.Ptr3+4)]); err != nil { + + if _, err := u.CallingPartyAddress.Write(b[cdpaEnd:cgpaEnd]); err != nil { return err } - // succeed if the rest of buffer is longer than u.DataLength - b[u.Ptr3+4] = u.DataLength - if offset := int(u.Ptr3 + 5); len(b[offset:]) >= int(u.DataLength) { - copy(b[offset:], u.Data) - return nil + if _, err := u.Data.Write(b[cgpaEnd:]); err != nil { + return err } - return io.ErrUnexpectedEOF + return nil } // ParseUDT decodes given byte sequence as a SCCP UDT. @@ -109,66 +115,68 @@ func (u *UDT) UnmarshalBinary(b []byte) error { } u.Type = MsgType(b[0]) - u.ProtocolClass = params.ProtocolClass(b[1]) - u.Ptr1 = b[2] - if l < int(u.Ptr1) { + + offset := 1 + u.ProtocolClass = ¶ms.ProtocolClass{} + n, err := u.ProtocolClass.Read(b[offset:]) + if err != nil { + return err + } + offset += n + + u.ptr1 = b[offset] + if l < int(u.ptr1) { return io.ErrUnexpectedEOF } - u.Ptr2 = b[3] - if l < int(u.Ptr2+3) { // where CgPA starts + u.ptr2 = b[offset+1] + if l < int(u.ptr2+3) { // where CgPA starts return io.ErrUnexpectedEOF } - u.Ptr3 = b[4] - if l < int(u.Ptr3+5) { // where u.Data starts + u.ptr3 = b[offset+2] + if l < int(u.ptr3+5) { // where u.Data starts return io.ErrUnexpectedEOF } - var err error - u.CalledPartyAddress, err = params.ParsePartyAddress(b[5:int(u.Ptr2+3)]) + offset += 3 + cdpaEnd := int(u.ptr2 + 3) + cgpaEnd := int(u.ptr3 + 4) + u.CalledPartyAddress, _, err = params.ParseCalledPartyAddress(b[offset:cdpaEnd]) if err != nil { return err } - u.CallingPartyAddress, err = params.ParsePartyAddress(b[int(u.Ptr2+3):int(u.Ptr3+4)]) + + u.CallingPartyAddress, _, err = params.ParseCallingPartyAddress(b[cdpaEnd:cgpaEnd]) if err != nil { return err } - // succeed if the rest of buffer is longer than u.DataLength - u.DataLength = b[int(u.Ptr3+4)] - if offset, dataLen := int(u.Ptr3+5), int(u.DataLength); l >= offset+dataLen { - u.Data = b[offset : offset+dataLen] - return nil + u.Data = ¶ms.Data{} + if _, err := u.Data.Read(b[cgpaEnd:]); err != nil { + return err } - return io.ErrUnexpectedEOF + return nil } // MarshalLen returns the serial length. func (u *UDT) MarshalLen() int { - l := 6 - if param := u.CalledPartyAddress; param != nil { - l += param.MarshalLen() - } - if param := u.CallingPartyAddress; param != nil { + l := 5 // MsgType, ProtocolClass, pointers + + l += int(u.ptr3) - 1 // length without Data + if param := u.Data; param != nil { l += param.MarshalLen() } - l += len(u.Data) return l } -// SetLength sets the length in Length field. -func (u *UDT) SetLength() { - u.DataLength = uint8(len(u.Data)) -} - // String returns the UDT values in human readable format. func (u *UDT) String() string { - return fmt.Sprintf("{Type: %d, CalledPartyAddress: %v, CallingPartyAddress: %v, DataLength: %d, Data: %x}", + return fmt.Sprintf("%s: {ProtocolClass: %s, CalledPartyAddress: %v, CallingPartyAddress: %v, Data: %s}", u.Type, + u.ProtocolClass, u.CalledPartyAddress, u.CallingPartyAddress, - u.DataLength, u.Data, ) } @@ -180,15 +188,21 @@ func (u *UDT) MessageType() MsgType { // MessageTypeName returns the Message Type in string. func (u *UDT) MessageTypeName() string { - return "UDT" + return u.MessageType().String() } // CdGT returns the GT in CalledPartyAddress in human readable string. func (u *UDT) CdGT() string { - return u.CalledPartyAddress.GTString() + if u.CalledPartyAddress.GlobalTitle == nil { + return "" + } + return u.CalledPartyAddress.Address() } // CgGT returns the GT in CalledPartyAddress in human readable string. func (u *UDT) CgGT() string { - return u.CallingPartyAddress.GTString() + if u.CallingPartyAddress.GlobalTitle == nil { + return "" + } + return u.CallingPartyAddress.Address() } diff --git a/utils/utils.go b/utils/utils.go index 9f5787e..d0fd017 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -9,14 +9,24 @@ import ( "encoding/hex" ) -// EncodeBCD encodes a string into BCD-encoded bytes. -func EncodeBCD(s string) ([]byte, error) { +// BCDEncode encodes a string into BCD-encoded bytes. +func BCDEncode(s string) ([]byte, error) { return StrToSwappedBytes(s, "0") } -// DecodeBCD decodes BCD-encoded bytes into a string. -func DecodeBCD(isOdd bool, b []byte) (string, error) { - return SwappedBytesToStr(b, isOdd), nil +// MustBCDEncode is the same as BCDEncode but panics if any error occurs. +// Use this function only when you are sure that the input string is valid. +func MustBCDEncode(s string) []byte { + b, err := BCDEncode(s) + if err != nil { + panic(err) + } + return b +} + +// BCDDecode decodes BCD-encoded bytes into a string. +func BCDDecode(isOdd bool, b []byte) string { + return SwappedBytesToStr(b, isOdd) } // StrToSwappedBytes returns swapped bits from a byte. diff --git a/xudt.go b/xudt.go new file mode 100644 index 0000000..9c11d02 --- /dev/null +++ b/xudt.go @@ -0,0 +1,321 @@ +// Copyright 2019-2024 go-sccp authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file. + +package sccp + +import ( + "fmt" + "io" + + "github.com/wmnsk/go-sccp/params" +) + +// XUDT represents a SCCP Message Extended unitdata (XUDT). +type XUDT struct { + Type MsgType + ProtocolClass *params.ProtocolClass + HopCounter *params.HopCounter + CalledPartyAddress *params.PartyAddress + CallingPartyAddress *params.PartyAddress + Data *params.Data + Segmentation *params.Segmentation + Importance *params.Importance + EndOfOptionalParameters *params.EndOfOptionalParameters + + ptr1, ptr2, ptr3, ptr4 uint8 +} + +// NewXUDT creates a new XUDT. +func NewXUDT(pcls int, retOnErr bool, hc uint8, cdpa, cgpa *params.PartyAddress, data []byte, opts ...params.Parameter) *XUDT { + x := &XUDT{ + Type: MsgTypeXUDT, + ProtocolClass: params.NewProtocolClass(pcls, retOnErr), + HopCounter: params.NewHopCounter(hc), + CalledPartyAddress: cdpa, + CallingPartyAddress: cgpa, + Data: params.NewData(data), + } + + x.ptr1 = 4 + x.ptr2 = x.ptr1 + uint8(cdpa.MarshalLen()) - 1 + x.ptr3 = x.ptr2 + uint8(cgpa.MarshalLen()) - 1 + x.ptr4 = 0 + + for _, opt := range opts { + switch opt.Code() { + case params.PCodeSegmentation: + x.Segmentation = opt.(*params.Segmentation) + case params.PCodeImportance: + x.Importance = opt.(*params.Importance) + case params.PCodeEndOfOptionalParameters: + x.EndOfOptionalParameters = opt.(*params.EndOfOptionalParameters) + default: + logf("unexpected parameter: %s in NewXUDT", opt.Code()) + } + } + + if len(opts) > 0 { + x.ptr4 = x.ptr3 + uint8(x.Data.MarshalLen()) - 1 + // so that users don't have to give EndOfOptionalParameters explicitly + x.EndOfOptionalParameters = params.NewEndOfOptionalParameters() + } + + return x +} + +// MarshalBinary returns the byte sequence generated from a XUDT instance. +func (x *XUDT) MarshalBinary() ([]byte, error) { + b := make([]byte, x.MarshalLen()) + if err := x.MarshalTo(b); err != nil { + return nil, err + } + + return b, nil +} + +// 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 (x *XUDT) MarshalTo(b []byte) error { + l := len(b) + if l < 5 { + return io.ErrUnexpectedEOF + } + + b[0] = uint8(x.Type) + + n := 1 + m, err := x.ProtocolClass.Write(b[1:]) + if err != nil { + return err + } + n += m + + m, err = x.HopCounter.Write(b[n:]) + if err != nil { + return err + } + n += m + + b[n] = x.ptr1 + if p := int(x.ptr1); l < p { + return io.ErrUnexpectedEOF + } + b[n+1] = x.ptr2 + if p := int(x.ptr2 + 4); l < p { + return io.ErrUnexpectedEOF + } + b[n+2] = x.ptr3 + if p := int(x.ptr3 + 5); l < p { + return io.ErrUnexpectedEOF + } + b[n+3] = x.ptr4 + if p := int(x.ptr4 + 6); l < p { + return io.ErrUnexpectedEOF + } + n += 4 + + cdpaEnd := int(x.ptr2 + 4) + cgpaEnd := int(x.ptr3 + 5) + dataEnd := int(x.ptr4 + 6) + if _, err := x.CalledPartyAddress.Write(b[n:cdpaEnd]); err != nil { + return err + } + + if _, err := x.CallingPartyAddress.Write(b[cdpaEnd:cgpaEnd]); err != nil { + return err + } + + if _, err := x.Data.Write(b[cgpaEnd:]); err != nil { + return err + } + + if x.ptr4 == 0 { + return nil + } + + offset := dataEnd + if param := x.Segmentation; param != nil { + m, err := param.Write(b[offset:]) + if err != nil { + return err + } + offset += m + } + if param := x.Importance; param != nil { + m, err := param.Write(b[offset:]) + if err != nil { + return err + } + offset += m + } + if param := x.EndOfOptionalParameters; param != nil { + _, err := param.Write(b[offset:]) + if err != nil { + return err + } + } + + return nil +} + +// ParseXUDT decodes given byte sequence as a SCCP XUDT. +func ParseXUDT(b []byte) (*XUDT, error) { + x := &XUDT{} + if err := x.UnmarshalBinary(b); err != nil { + return nil, err + } + + return x, nil +} + +// UnmarshalBinary sets the values retrieved from byte sequence in a SCCP XUDT. +func (x *XUDT) UnmarshalBinary(b []byte) error { + l := len(b) + if l <= 5 { // where CdPA starts + return io.ErrUnexpectedEOF + } + + x.Type = MsgType(b[0]) + + offset := 1 + x.ProtocolClass = ¶ms.ProtocolClass{} + n, err := x.ProtocolClass.Read(b[offset:]) + if err != nil { + return err + } + offset += n + + x.HopCounter = ¶ms.HopCounter{} + n, err = x.HopCounter.Read(b[offset:]) + if err != nil { + return err + } + offset += n + + x.ptr1 = b[offset] + if l < int(x.ptr1) { + return io.ErrUnexpectedEOF + } + x.ptr2 = b[offset+1] + if l < int(x.ptr2+4) { // where CgPA starts + return io.ErrUnexpectedEOF + } + x.ptr3 = b[offset+2] + if l < int(x.ptr3+5) { // where Data starts + return io.ErrUnexpectedEOF + } + x.ptr4 = b[offset+3] + if m := int(x.ptr4); (m != 0) && l < m+7 { // where optional parameters start + return io.ErrUnexpectedEOF + } + + offset += 4 + cdpaEnd := int(x.ptr2 + 4) + cgpaEnd := int(x.ptr3 + 5) + dataEnd := int(x.ptr4 + 6) + x.CalledPartyAddress, _, err = params.ParseCalledPartyAddress(b[offset:cdpaEnd]) + if err != nil { + return err + } + + x.CallingPartyAddress, _, err = params.ParseCallingPartyAddress(b[cdpaEnd:cgpaEnd]) + if err != nil { + return err + } + + x.Data, _, err = params.ParseData(b[cgpaEnd:]) + if err != nil { + return err + } + + if x.ptr4 == 0 { + return nil + } + + opts, _, err := params.ParseOptionalParameters(b[dataEnd:]) + if err != nil { + return err + } + + for _, opt := range opts { + switch opt.Code() { + case params.PCodeSegmentation: + x.Segmentation = opt.(*params.Segmentation) + case params.PCodeImportance: + x.Importance = opt.(*params.Importance) + case params.PCodeEndOfOptionalParameters: + x.EndOfOptionalParameters = opt.(*params.EndOfOptionalParameters) + } + } + + return nil +} + +// MarshalLen returns the serial length. +func (x *XUDT) MarshalLen() int { + l := 7 // MsgType + ProtocolClass + HopCounter + Pointers + + // if optional parameters exist + if x.ptr4 != 0 { + l += int(x.ptr4) - 1 // length without optional parameters + if param := x.Segmentation; param != nil { + l += param.MarshalLen() + } + if param := x.Importance; param != nil { + l += param.MarshalLen() + } + if param := x.EndOfOptionalParameters; param != nil { + l += param.MarshalLen() + } + + return l + } + + l += int(x.ptr3) - 2 // length without Data + if param := x.Data; param != nil { + l += param.MarshalLen() + } + + return l +} + +// String returns the XUDT values in human readable format. +func (x *XUDT) String() string { + return fmt.Sprintf("%s: {ProtocolClass: %s, HopCounter: %s, CalledPartyAddress: %v, CallingPartyAddress: %v, Data: %s, Segmentation: %s, Importance: %s}", + x.Type, + x.ProtocolClass, + x.HopCounter, + x.CalledPartyAddress, + x.CallingPartyAddress, + x.Data, + x.Segmentation, + x.Importance, + ) +} + +// MessageType returns the Message Type in int. +func (x *XUDT) MessageType() MsgType { + return MsgTypeXUDT +} + +// MessageTypeName returns the Message Type in string. +func (x *XUDT) MessageTypeName() string { + return x.MessageType().String() +} + +// CdGT returns the GT in CalledPartyAddress in human readable string. +func (x *XUDT) CdGT() string { + if x.CalledPartyAddress.GlobalTitle == nil { + return "" + } + return x.CalledPartyAddress.Address() +} + +// CgGT returns the GT in CalledPartyAddress in human readable string. +func (x *XUDT) CgGT() string { + if x.CallingPartyAddress.GlobalTitle == nil { + return "" + } + return x.CallingPartyAddress.Address() +}