├── .github ├── dependabot.yml └── workflows │ ├── go.yml │ └── golangci-lint.yml ├── LICENSE ├── README.md ├── codec_test.go ├── component.go ├── dialogue-pdu.go ├── dialogue.go ├── errors.go ├── examples └── client │ └── client.go ├── go.mod ├── go.sum ├── ie.go ├── logger.go ├── tcap.go └── transaction.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "20:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test 3 | jobs: 4 | test-linux: 5 | strategy: 6 | matrix: 7 | go-version: [1.21.x, 1.22.x] 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Install Go 11 | uses: actions/setup-go@v1 12 | with: 13 | go-version: ${{ matrix.go-version }} 14 | - name: Checkout code 15 | uses: actions/checkout@v1 16 | - name: Test 17 | run: go test ./... 18 | - name: Bench 19 | run: go test -benchmem -bench . ./... 20 | #test-macos: 21 | # strategy: 22 | # matrix: 23 | # go-version: [1.21.x, 1.22.x] 24 | # runs-on: macos-latest 25 | # steps: 26 | # - name: Install Go 27 | # uses: actions/setup-go@v1 28 | # with: 29 | # go-version: ${{ matrix.go-version }} 30 | # - name: Checkout code 31 | # uses: actions/checkout@v1 32 | # - name: Test 33 | # run: go test ./... 34 | # - name: Bench 35 | # run: go test -benchmem -bench . ./... 36 | #test-windows: 37 | # strategy: 38 | # matrix: 39 | # go-version: [1.21.x, 1.22.x] 40 | # runs-on: windows-latest 41 | # steps: 42 | # - name: Install Go 43 | # uses: actions/setup-go@v1 44 | # with: 45 | # go-version: ${{ matrix.go-version }} 46 | # - name: Checkout code 47 | # uses: actions/checkout@v1 48 | # - name: Test 49 | # run: go test ./... 50 | # - name: Bench 51 | # run: go test -benchmem -bench . ./... 52 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | - main 9 | pull_request: 10 | jobs: 11 | golangci: 12 | name: lint 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version: '1.21' 19 | cache: false 20 | - name: golangci-lint 21 | uses: golangci/golangci-lint-action@v4 22 | with: 23 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 24 | version: "latest" 25 | # Optional: golangci-lint command line arguments. 26 | args: --issues-exit-code=1 --timeout=3m0s --tests=false --no-config --max-issues-per-linter=4095 --max-same-issues=1023 --disable-all -E=errcheck -E=gosimple -E=govet -E=ineffassign -E=staticcheck -E=typecheck -E=unused -E=bodyclose -E=dogsled -E=goconst -E=gocritic -E=gofmt -E=goimports -E=goprintffuncname -E=gosec -E=misspell -E=nakedret -E=prealloc -E=rowserrcheck -E=stylecheck -E=unconvert -E=unparam -E=exportloopref -E=gomodguard -E=asciicheck -E=errorlint 27 | # Optional: show only new issues if it's a pull request. The default value is `false`. 28 | only-new-issues: true 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2024 Yoshiyuki Kurauchi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-tcap 2 | 3 | Simple TCAP implementation in the Go Programming Language. 4 | 5 | ![CI status](https://github.com/wmnsk/go-tcap/actions/workflows/go.yml/badge.svg) 6 | [![golangci-lint](https://github.com/wmnsk/go-tcap/actions/workflows/golangci-lint.yml/badge.svg)](https://github.com/wmnsk/go-tcap/actions/workflows/golangci-lint.yml) 7 | [![Go Reference](https://pkg.go.dev/badge/github.com/wmnsk/go-tcap.svg)](https://pkg.go.dev/github.com/wmnsk/go-tcap) 8 | [![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/wmnsk/go-tcap/blob/master/LICENSE) 9 | 10 | Package tcap provides simple and painless handling of TCAP (Transaction Capabilities Application Part) in SS7/SIGTRAN protocol stack, intended for Go developers to use. 11 | 12 | ## Disclaimer 13 | 14 | Though TCAP is an ASN.1-based protocol, this implementation does not use any ASN.1 parser. That makes this implementation flexible enough to create arbitrary payload with any combinations, which is useful for testing while it also means that many of the features in TCAP are not supported yet. 15 | 16 | This is still an experimental project, and currently in its very early stage of development. Any part of implementations(including exported APIs) may be changed before released as v1.0.0. 17 | 18 | ## Getting started 19 | 20 | ### Prerequisites 21 | 22 | Run `go mod tidy` in your project's directory to collect the required packages automatically. 23 | 24 | _This project follows [the Release Policy of Go](https://golang.org/doc/devel/release.html#policy)._ 25 | 26 | ### Running examples 27 | 28 | A sample client is available in [examples/client/](./examples/client/), which, by default, establishes SCTP/M3UA connection with a server sends a MAP cancelLocation. 29 | 30 | ``` 31 | Transaction Capabilities Application Part 32 | begin 33 | [Transaction Id: 11111111] 34 | Source Transaction ID 35 | oid: 0.0.17.773.1.1.1 (id-as-dialogue) 36 | dialogueRequest 37 | components: 1 item 38 | Component: invoke (1) 39 | invoke 40 | invokeID: 0 41 | opCode: localValue (0) 42 | CONSTRUCTOR 43 | CONSTRUCTOR Tag 44 | Tag: 0x00 45 | Length: 10 46 | Parameter (0x04) 47 | Tag: 0x04 48 | Length: 8 49 | Data: 00 50 | GSM Mobile Application 51 | Component: invoke (1) 52 | invoke 53 | invokeID: 0 54 | opCode: localValue (0) 55 | localValue: cancelLocation (3) 56 | identity: imsi-WithLMSI (1) 57 | imsi-WithLMSI 58 | IMSI: 001010123456789 59 | [Association IMSI: 001010123456789] 60 | ``` 61 | 62 | Some parameters can be speficied from command-line arguments. Other parameters including the ones in lower layers (such as Point Code in M3UA, Global Title in SCCP, etc.) should be updated by modifying the source code. 63 | 64 | ``` 65 | $ ./client -h 66 | Usage of client: 67 | -addr string 68 | Remote IP and Port to connect to. (default "127.0.0.2:2905") 69 | -opcode int 70 | Operation Code in int. (default 3) 71 | -otid int 72 | Originating Transaction ID in uint32. (default 286331153) 73 | -payload string 74 | Hex representation of the payload (default "040800010121436587f9") 75 | ``` 76 | 77 | _If you are looking for a server that just can accept a SCTP/M3UA connection to receive a TCAP packet, [server example in go-m3ua project](https://github.com/wmnsk/go-m3ua/blob/master/examples/server/m3ua-server.go) would be a nice choice for you._ 78 | 79 | ## Supported Features 80 | 81 | ### Transaction Portion 82 | 83 | #### Message Types 84 | 85 | | Message type | Supported? | 86 | |----------------|------------| 87 | | Unidirectional | | 88 | | Begin | Yes | 89 | | End | Yes | 90 | | Continue | Yes | 91 | | Abort | Yes | 92 | 93 | #### Fields 94 | 95 | | Tag | Supported? | 96 | |----------------------------|------------| 97 | | Originating Transaction ID | Yes | 98 | | Destination Transaction ID | Yes | 99 | | P-Abort Cause | Yes | 100 | 101 | ### Component Portion 102 | 103 | #### Component types 104 | 105 | | Component type | Supported? | 106 | |--------------------------|------------| 107 | | Invoke | Yes | 108 | | Return Result (Last) | Yes | 109 | | Return Result (Not Last) | Yes | 110 | | Return Error | Yes | 111 | | Reject | Yes | 112 | 113 | 114 | ### Dialogue Portion 115 | 116 | #### Dialogue types 117 | 118 | | Dialogue type | Supported? | 119 | |-------------------------------------|------------| 120 | | Dialogue Request (AARQ-apdu) | Yes | 121 | | Dialogue Response (AARE-apdu) | Yes | 122 | | Dialogue Abort (ABRT-apdu) | Yes | 123 | | Unidirectional Dialogue (AUDT-apdu) | | 124 | 125 | #### Elements 126 | 127 | | Tag | Type | Supported? | 128 | |-----------------------------|--------------|------------| 129 | | Object Identifier | Structured | Yes | 130 | | Single-ASN.1-type | Structured | Yes | 131 | | Dialogue PDU | Structured | Yes | 132 | | Object Identifier | Unstructured | | 133 | | Single-ASN.1-type | Unstructured | | 134 | | Unidirectional Dialogue PDU | Unstructured | | 135 | 136 | 137 | ## Author(s) 138 | 139 | Yoshiyuki Kurauchi ([Website](https://wmnsk.com/)) 140 | 141 | ## LICENSE 142 | 143 | [MIT](https://github.com/wmnsk/go-tcap/blob/master/LICENSE) 144 | -------------------------------------------------------------------------------- /codec_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2024 go-tcap authors. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | package tcap_test 6 | 7 | import ( 8 | "encoding" 9 | "testing" 10 | 11 | "github.com/pascaldekloe/goe/verify" 12 | "github.com/wmnsk/go-tcap" 13 | ) 14 | 15 | type serializable interface { 16 | encoding.BinaryMarshaler 17 | MarshalLen() int 18 | } 19 | 20 | var testcases = []struct { 21 | description string 22 | structured serializable 23 | serialized []byte 24 | parseFunc func(b []byte) (serializable, error) 25 | }{ 26 | // TCAP (All) 27 | // TODO: Add more patterns 28 | { 29 | description: "TCAP/Begin - AARQ - Invoke / MAP cancelLocation", 30 | structured: tcap.NewBeginInvokeWithDialogue( 31 | 0x11111111, // OTID 32 | tcap.DialogueAsID, // DialogueType 33 | tcap.LocationCancellationContext, // ACN 34 | 3, // ACN Version 35 | 0, // Invoke Id 36 | 3, // OpCode 37 | []byte{0x04, 0x08, 0x00, 0x01, 0x01, 0x21, 0x43, 0x65, 0x87, 0xf9}, // Payload 38 | ), 39 | serialized: []byte{ 40 | // Transaction Portion 41 | 0x62, 0x3c, 0x48, 0x04, 0x11, 0x11, 0x11, 0x11, 0x6b, 0x1e, 0x28, 0x1c, 0x06, 0x07, 0x00, 0x11, 42 | 0x86, 0x05, 0x01, 0x01, 0x01, 43 | // Dialogue Portion 44 | 0xa0, 0x11, 0x60, 0x0f, 0x80, 0x02, 0x07, 0x80, 0xa1, 0x09, 0x06, 0x07, 0x04, 0x00, 0x00, 0x01, 45 | 0x00, 0x02, 0x03, 46 | // Component Portion 47 | 0x6c, 0x14, 0xa1, 0x12, 0x02, 0x01, 0x00, 0x02, 0x01, 0x03, 0x30, 0x0a, 0x04, 0x08, 0x00, 0x01, 48 | 0x01, 0x21, 0x43, 0x65, 0x87, 0xf9, 49 | }, 50 | parseFunc: func(b []byte) (serializable, error) { 51 | v, err := tcap.Parse(b) 52 | if err != nil { 53 | return nil, err 54 | } 55 | // clear unnecessary payload 56 | v.Transaction.Payload = nil 57 | v.Dialogue.SingleAsn1Type.Value = nil 58 | v.Dialogue.Payload = nil 59 | 60 | return v, nil 61 | }, 62 | }, { 63 | description: "TCAP/End - AARE - ReturnResultLast / MAP cancelLocation", 64 | structured: tcap.NewEndReturnResultWithDialogue( 65 | 0x11111111, // OTID 66 | tcap.DialogueAsID, // DialogueType 67 | tcap.LocationCancellationContext, // ACN 68 | 3, // ACN Version 69 | 0, // Invoke Id 70 | 3, // OpCode 71 | true, // is last? 72 | nil, // Payload 73 | ), 74 | serialized: []byte{ 75 | // Transaction Portion 76 | 0x64, 0x3e, 0x49, 0x04, 0x11, 0x11, 0x11, 0x11, 0x6b, 0x2a, 0x28, 0x28, 0x06, 0x07, 0x00, 0x11, 77 | 0x86, 0x05, 0x01, 0x01, 0x01, 78 | // Dialogue Portion 79 | 0xa0, 0x1d, 0x61, 0x1b, 0x80, 0x02, 0x07, 0x80, 0xa1, 0x09, 0x06, 0x07, 0x04, 0x00, 0x00, 0x01, 80 | 0x00, 0x02, 0x03, 0xa2, 0x03, 0x02, 0x01, 0x00, 0xa3, 0x05, 0xa1, 0x03, 0x02, 0x01, 0x00, 81 | // Component Portion 82 | 0x6c, 0x0a, 0xa2, 0x08, 0x02, 0x01, 0x00, 0x30, 0x03, 0x02, 0x01, 0x03, 83 | }, 84 | parseFunc: func(b []byte) (serializable, error) { 85 | v, err := tcap.Parse(b) 86 | if err != nil { 87 | return nil, err 88 | } 89 | // clear unnecessary payload 90 | v.Transaction.Payload = nil 91 | v.Dialogue.SingleAsn1Type.Value = nil 92 | v.Dialogue.Payload = nil 93 | v.Components.Component[0].ResultRetres.Value = nil 94 | 95 | return v, nil 96 | }, 97 | }, { 98 | description: "TCAP/Begin - AARQ - Invoke", 99 | structured: tcap.NewBeginInvokeWithDialogue( 100 | 0x11111111, // OTID 101 | tcap.DialogueAsID, // DialogueType 102 | tcap.AnyTimeInfoEnquiryContext, // ACN 103 | 3, // ACN Version 104 | 0, // Invoke Id 105 | 71, // OpCode 106 | []byte{0xde, 0xad, 0xbe, 0xef}, // Payload 107 | ), 108 | serialized: []byte{ 109 | // Transaction Portion 110 | 0x62, 0x36, 0x48, 0x04, 0x11, 0x11, 0x11, 0x11, 111 | // Dialogue Portion 112 | 0x6b, 0x1e, 0x28, 0x1c, 0x06, 0x07, 0x00, 0x11, 0x86, 0x05, 0x01, 0x01, 0x01, 0xa0, 0x11, 0x60, 113 | 0x0f, 0x80, 0x02, 0x07, 0x80, 0xa1, 0x09, 0x06, 0x07, 0x04, 0x00, 0x00, 0x01, 0x00, 0x1d, 0x03, 114 | // Component Portion 115 | 0x6c, 0x0e, 0xa1, 0x0c, 0x02, 0x01, 0x00, 0x02, 0x01, 0x47, 0x30, 0x04, 0xde, 0xad, 0xbe, 0xef, 116 | }, 117 | parseFunc: func(b []byte) (serializable, error) { 118 | v, err := tcap.Parse(b) 119 | if err != nil { 120 | return nil, err 121 | } 122 | // clear unnecessary payload 123 | v.Transaction.Payload = nil 124 | v.Dialogue.SingleAsn1Type.Value = nil 125 | v.Dialogue.Payload = nil 126 | v.Components.Component[0].Parameter.IE = nil 127 | 128 | return v, nil 129 | }, 130 | }, { 131 | description: "TCAP/Continue - NoDialogue - Invoke / MAP unstructuredSS-Notify", 132 | structured: tcap.NewContinueInvoke( 133 | 0x11111111, // OTID 134 | 0x22222222, // DTID 135 | 1, // Invoke Id 136 | 61, // OpCode 137 | []byte{ 138 | 0x04, 0x01, 0x0f, 0x04, 0x09, 0xaa, 0x1b, 0x2e, 0x47, 0xab, 0xd9, 0x46, 0xaa, 0x11, 0x80, 0x07, 139 | 0x91, 0x18, 0x08, 0x11, 0x11, 0x22, 0x22, 140 | }, // Payload 141 | ), 142 | serialized: []byte{ 143 | // Transaction Portion 144 | 0x65, 0x2f, 0x48, 0x04, 0x11, 0x11, 0x11, 0x11, 0x49, 0x04, 0x22, 0x22, 0x22, 0x22, 145 | // Component Portion 146 | 0x6c, 0x21, 0xa1, 0x1f, 0x02, 0x01, 0x01, 0x02, 0x01, 0x3d, 0x30, 0x17, 0x04, 0x01, 0x0f, 0x04, 147 | 0x09, 0xaa, 0x1b, 0x2e, 0x47, 0xab, 0xd9, 0x46, 0xaa, 0x11, 0x80, 0x07, 0x91, 0x18, 0x08, 0x11, 148 | 0x11, 0x22, 0x22, 149 | }, 150 | parseFunc: func(b []byte) (serializable, error) { 151 | v, err := tcap.Parse(b) 152 | if err != nil { 153 | return nil, err 154 | } 155 | // clear unnecessary payload 156 | v.Transaction.Payload = nil 157 | 158 | return v, nil 159 | }, 160 | }, { 161 | description: "ParseBER / TCAP/Continue - NoDialogue - Invoke / MAP unstructuredSS-Notify", 162 | structured: tcap.NewContinueInvoke( 163 | 0x11111111, // OTID 164 | 0x22222222, // DTID 165 | 1, // Invoke Id 166 | 61, // OpCode 167 | []byte{ 168 | 0x04, 0x01, 0x0f, 0x04, 0x09, 0xaa, 0x1b, 0x2e, 0x47, 0xab, 0xd9, 0x46, 0xaa, 0x11, 0x80, 0x07, 169 | 0x91, 0x18, 0x08, 0x11, 0x11, 0x22, 0x22, 170 | }, // Payload 171 | ), 172 | serialized: []byte{ 173 | // Transaction Portion 174 | 0x65, 0x2f, 0x48, 0x04, 0x11, 0x11, 0x11, 0x11, 0x49, 0x04, 0x22, 0x22, 0x22, 0x22, 175 | // Component Portion 176 | 0x6c, 0x21, 0xa1, 0x1f, 0x02, 0x01, 0x01, 0x02, 0x01, 0x3d, 0x30, 0x17, 0x04, 0x01, 0x0f, 0x04, 177 | 0x09, 0xaa, 0x1b, 0x2e, 0x47, 0xab, 0xd9, 0x46, 0xaa, 0x11, 0x80, 0x07, 0x91, 0x18, 0x08, 0x11, 178 | 0x11, 0x22, 0x22, 179 | }, 180 | parseFunc: func(b []byte) (serializable, error) { 181 | v, err := tcap.ParseBER(b) 182 | if err != nil { 183 | return nil, err 184 | } 185 | v1 := v[0] 186 | 187 | // clear unnecessary payload 188 | v1.Transaction.Payload = nil 189 | 190 | return v1, nil 191 | }, 192 | }, { 193 | description: "TCAP/End - AARE - ReturnResultLast", 194 | structured: tcap.NewEndReturnResultWithDialogue( 195 | 0x11111111, // OTID 196 | tcap.DialogueAsID, // DialogueType 197 | tcap.AnyTimeInfoEnquiryContext, // ACN 198 | 3, // ACN Version 199 | 1, // Invoke Id 200 | 71, // OpCode 201 | true, // Last or not 202 | []byte{0xde, 0xad, 0xbe, 0xef}, // Payload 203 | ), 204 | serialized: []byte{ 205 | // Transaction Portion 206 | 0x64, 0x44, 0x49, 0x04, 0x11, 0x11, 0x11, 0x11, 207 | // Dialogue Portion 208 | 0x6b, 0x2a, 0x28, 0x28, 0x06, 0x07, 0x00, 0x11, 0x86, 0x05, 0x01, 0x01, 0x01, 0xa0, 0x1d, 0x61, 209 | 0x1b, 0x80, 0x02, 0x07, 0x80, 0xa1, 0x09, 0x06, 0x07, 0x04, 0x00, 0x00, 0x01, 0x00, 0x1d, 0x03, 210 | 0xa2, 0x03, 0x02, 0x01, 0x00, 0xa3, 0x05, 0xa1, 211 | 0x03, 0x02, 0x01, 0x00, 212 | // Component Portion 213 | 0x6c, 0x10, 0xa2, 0x0e, 0x02, 0x01, 0x01, 0x30, 0x09, 0x02, 0x01, 0x47, 0x30, 0x04, 0xde, 0xad, 214 | 0xbe, 0xef, 215 | }, 216 | parseFunc: func(b []byte) (serializable, error) { 217 | v, err := tcap.Parse(b) 218 | if err != nil { 219 | return nil, err 220 | } 221 | // clear unnecessary payload 222 | v.Transaction.Payload = nil 223 | v.Dialogue.SingleAsn1Type.Value = nil 224 | v.Dialogue.Payload = nil 225 | v.Components.Component[0].ResultRetres.Value = nil 226 | v.Components.Component[0].Parameter.IE = nil 227 | 228 | return v, nil 229 | }, 230 | }, 231 | // Transaction Portion 232 | { 233 | description: "Transaction/Unidirectional", 234 | structured: tcap.NewUnidirectional( 235 | []byte{0xca, 0xfe}, 236 | ), 237 | serialized: []byte{0x61, 0x02, 0xca, 0xfe}, 238 | parseFunc: func(b []byte) (serializable, error) { return tcap.ParseTransaction(b) }, 239 | }, { 240 | description: "Transaction/Begin", 241 | structured: tcap.NewBegin(0xdeadbeef, []byte{0xfa, 0xce}), 242 | serialized: []byte{0x62, 0x08, 0x48, 0x04, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce}, 243 | parseFunc: func(b []byte) (serializable, error) { return tcap.ParseTransaction(b) }, 244 | }, { 245 | description: "Transaction/End", 246 | structured: tcap.NewEnd(0xdeadbeef, []byte{0xfa, 0xce}), 247 | serialized: []byte{0x64, 0x08, 0x49, 0x04, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce}, 248 | parseFunc: func(b []byte) (serializable, error) { return tcap.ParseTransaction(b) }, 249 | }, { 250 | description: "Transaction/Continue", 251 | structured: tcap.NewContinue(0xdeadbeef, 0xdeadbeef, []byte{0xfa, 0xce}), 252 | serialized: []byte{ 253 | 0x65, 0x0e, 0x48, 0x04, 0xde, 0xad, 0xbe, 0xef, 0x49, 0x04, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 254 | }, 255 | parseFunc: func(b []byte) (serializable, error) { return tcap.ParseTransaction(b) }, 256 | }, { 257 | description: "Transaction/Abort", 258 | structured: tcap.NewAbort(0xdeadbeef, tcap.UnrecognizedMessageType, []byte{0xfa, 0xce}), 259 | serialized: []byte{ 260 | 0x67, 0x0b, 0x49, 0x04, 0xde, 0xad, 0xbe, 0xef, 0x4a, 0x01, 0x00, 0xfa, 0xce, 261 | }, 262 | parseFunc: func(b []byte) (serializable, error) { return tcap.ParseTransaction(b) }, 263 | }, 264 | // Dialogue Portion 265 | { 266 | description: "Dialogue/AARQ", 267 | structured: tcap.NewDialogue( 268 | 1, 1, // OID, Version 269 | tcap.NewAARQ( 270 | // Version, Context, ContextVersion 271 | 1, tcap.AnyTimeInfoEnquiryContext, 3, 272 | ), 273 | []byte{0xde, 0xad, 0xbe, 0xef}, 274 | ), 275 | serialized: []byte{ 276 | 0x6b, 0x22, 0x28, 0x20, 0x06, 0x07, 0x00, 0x11, 0x86, 0x05, 0x01, 0x01, 0x01, 0xa0, 0x11, 0x60, 277 | 0x0f, 0x80, 0x02, 0x07, 0x80, 0xa1, 0x09, 0x06, 0x07, 0x04, 0x00, 0x00, 0x01, 0x00, 0x1d, 0x03, 278 | 0xde, 0xad, 0xbe, 0xef, 279 | }, 280 | parseFunc: func(b []byte) (serializable, error) { 281 | v, err := tcap.ParseDialogue(b) 282 | if err != nil { 283 | return nil, err 284 | } 285 | // clear unnecessary payload 286 | v.SingleAsn1Type.Value = nil 287 | 288 | return v, nil 289 | }, 290 | }, { 291 | description: "Dialogue/AARQ/UserInformation", 292 | structured: tcap.NewDialogue( 293 | 1, 1, // OID, Version 294 | tcap.NewAARQ( 295 | // Version, Context, ContextVersion 296 | 1, tcap.AnyTimeInfoEnquiryContext, 3, 297 | tcap.NewIE(0xBE, []byte{0xde, 0xad, 0xbe, 0xef}), 298 | ), 299 | []byte{0xde, 0xad, 0xbe, 0xef}, 300 | ), 301 | serialized: []byte{ 302 | 0x6b, 0x28, 0x28, 0x26, 0x06, 0x07, 0x00, 0x11, 0x86, 0x05, 0x01, 0x01, 0x01, 0xa0, 0x17, 0x60, 303 | 0x15, 0x80, 0x02, 0x07, 0x80, 0xa1, 0x09, 0x06, 0x07, 0x04, 0x00, 0x00, 0x01, 0x00, 0x1d, 0x03, 304 | 0xbe, 0x04, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 305 | }, 306 | parseFunc: func(b []byte) (serializable, error) { 307 | v, err := tcap.ParseDialogue(b) 308 | if err != nil { 309 | return nil, err 310 | } 311 | 312 | b, err = v.MarshalBinary() 313 | if err != nil { 314 | return nil, err 315 | } 316 | 317 | // Purposely do not clean v.SingleAsn1Type.Value the first time 318 | 319 | v, err = tcap.ParseDialogue(b) 320 | if err != nil { 321 | return nil, err 322 | } 323 | 324 | v.SingleAsn1Type.Value = nil 325 | 326 | return v, nil 327 | }, 328 | }, { 329 | description: "Dialogue/AARE", 330 | structured: tcap.NewDialogue( 331 | 1, 1, // OID, Version 332 | tcap.NewAARE( 333 | // Version, Context, ContextVersion 334 | 1, tcap.AnyTimeInfoEnquiryContext, 3, 335 | // Result, ResultSourceDiag, Reason 336 | 0, 1, 0, 337 | ), 338 | []byte{0xde, 0xad, 0xbe, 0xef}, 339 | ), 340 | serialized: []byte{ 341 | 0x6b, 0x2e, 0x28, 0x2c, 0x06, 0x07, 0x00, 0x11, 0x86, 0x05, 0x01, 0x01, 0x01, 0xa0, 0x1d, 0x61, 342 | 0x1b, 0x80, 0x02, 0x07, 0x80, 0xa1, 0x09, 0x06, 0x07, 0x04, 0x00, 0x00, 0x01, 0x00, 0x1d, 0x03, 343 | 0xa2, 0x03, 0x02, 0x01, 0x00, 0xa3, 0x05, 0xa1, 0x03, 0x02, 0x01, 0x00, 0xde, 0xad, 0xbe, 0xef, 344 | }, 345 | parseFunc: func(b []byte) (serializable, error) { 346 | v, err := tcap.ParseDialogue(b) 347 | if err != nil { 348 | return nil, err 349 | } 350 | // clear unnecessary payload 351 | v.SingleAsn1Type.Value = nil 352 | 353 | return v, nil 354 | }, 355 | }, 356 | // Component Portion 357 | { 358 | description: "Components/invoke", 359 | structured: tcap.NewComponents(tcap.NewInvoke(0, 0, 71, true, []byte{0xde, 0xad, 0xbe, 0xef})), 360 | serialized: []byte{ 361 | 0x6c, 0x0e, 0xa1, 0x0c, 0x02, 0x01, 0x00, 0x02, 0x01, 0x47, 0x30, 0x04, 0xde, 0xad, 0xbe, 0xef, 362 | }, 363 | parseFunc: func(b []byte) (serializable, error) { 364 | v, err := tcap.ParseComponents(b) 365 | if err != nil { 366 | return nil, err 367 | } 368 | // clear unnecessary payload 369 | v.Component[0].Parameter.IE = nil 370 | 371 | return v, nil 372 | }, 373 | }, { 374 | description: "Components/returnResultLast", 375 | structured: tcap.NewComponents(tcap.NewReturnResult(0, 71, true, true, []byte{0xde, 0xad, 0xbe, 0xef})), 376 | serialized: []byte{ 377 | 0x6c, 0x10, 0xa2, 0x0e, 0x02, 0x01, 0x00, 0x30, 0x09, 0x02, 0x01, 0x47, 0x30, 0x04, 0xde, 0xad, 378 | 0xbe, 0xef, 379 | }, 380 | parseFunc: func(b []byte) (serializable, error) { 381 | v, err := tcap.ParseComponents(b) 382 | if err != nil { 383 | return nil, err 384 | } 385 | // clear unnecessary payload 386 | v.Component[0].ResultRetres.Value = nil 387 | v.Component[0].Parameter.IE = nil 388 | 389 | return v, nil 390 | }, 391 | }, { 392 | description: "Components/returnError", 393 | structured: tcap.NewComponents(tcap.NewReturnError(0, 71, true, []byte{0xde, 0xad, 0xbe, 0xef})), 394 | serialized: []byte{ 395 | 0x6c, 0x0e, 0xa3, 0x0c, 0x02, 0x01, 0x00, 0x02, 0x01, 0x47, 0x30, 0x04, 0xde, 0xad, 0xbe, 0xef, 396 | }, 397 | parseFunc: func(b []byte) (serializable, error) { 398 | v, err := tcap.ParseComponents(b) 399 | if err != nil { 400 | return nil, err 401 | } 402 | // clear unnecessary payload 403 | v.Component[0].Parameter.IE = nil 404 | 405 | return v, nil 406 | }, 407 | }, 408 | // Generic IE 409 | { 410 | description: "IE/Single", 411 | structured: tcap.NewIE(tcap.NewTag(01, 0, 0x08), []byte{0xde, 0xad, 0xbe, 0xef}), 412 | serialized: []byte{0x48, 0x04, 0xde, 0xad, 0xbe, 0xef}, 413 | parseFunc: func(b []byte) (serializable, error) { return tcap.ParseIE(b) }, 414 | }, 415 | } 416 | 417 | func TestCodec(t *testing.T) { 418 | t.Helper() 419 | 420 | for _, c := range testcases { 421 | t.Run("Parse / "+c.description, func(t *testing.T) { 422 | msg, err := c.parseFunc(c.serialized) 423 | if err != nil { 424 | t.Fatal(err) 425 | } 426 | 427 | if got, want := msg, c.structured; !verify.Values(t, "", got, want) { 428 | t.Fail() 429 | } 430 | }) 431 | 432 | t.Run("Marshal / "+c.description, func(t *testing.T) { 433 | b, err := c.structured.MarshalBinary() 434 | if err != nil { 435 | t.Fatal(err) 436 | } 437 | 438 | if got, want := b, c.serialized; !verify.Values(t, "", got, want) { 439 | t.Fail() 440 | } 441 | }) 442 | 443 | t.Run("Len / "+c.description, func(t *testing.T) { 444 | if got, want := c.structured.MarshalLen(), len(c.serialized); got != want { 445 | t.Fatalf("got %v want %v", got, want) 446 | } 447 | }) 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /component.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2024 go-tcap authors. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | package tcap 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | ) 11 | 12 | // Component Type definitions. 13 | const ( 14 | Invoke int = iota + 1 15 | ReturnResultLast 16 | ReturnError 17 | Reject 18 | _ 19 | _ 20 | ReturnResultNotLast 21 | ) 22 | 23 | // Problem Type definitions. 24 | const ( 25 | GeneralProblem int = iota 26 | InvokeProblem 27 | ReturnResultProblem 28 | ReturnErrorProblem 29 | ) 30 | 31 | // General Problem Code definitions. 32 | const ( 33 | UnrecognizedComponent uint8 = iota 34 | MistypedComponent 35 | BadlyStructuredComponent 36 | ) 37 | 38 | // Invoke Problem Code definitions. 39 | const ( 40 | InvokeProblemDuplicateInvokeID uint8 = iota 41 | InvokeProblemUnrecognizedOperation 42 | InvokeProblemMistypedParameter 43 | InvokeProblemResourceLimitation 44 | InvokeProblemInitiatingRelease 45 | InvokeProblemUnrecognizedLinkedID 46 | InvokeProblemLinkedResponseUnexpected 47 | InvokeProblemUnexpectedLinkedOperation 48 | ) 49 | 50 | // ReturnResult Problem Code definitions. 51 | const ( 52 | ResultProblemUnrecognizedInvokeID uint8 = iota 53 | ResultProblemReturnResultUnexpected 54 | ResultProblemMistypedParameter 55 | ) 56 | 57 | // ReturnError Problem Code definitions. 58 | const ( 59 | ErrorProblemUnrecognizedInvokeID uint8 = iota 60 | ErrorProblemReturnErrorUnexpected 61 | ErrorProblemUnrecognizedError 62 | ErrorProblemUnexpectedError 63 | ErrorProblemMistypedParameter 64 | ) 65 | 66 | // Components represents a TCAP Components(Header). 67 | // 68 | // This is a TCAP Components' Header part. Contents are in Component field. 69 | type Components struct { 70 | Tag Tag 71 | Length uint8 72 | Component []*Component 73 | } 74 | 75 | // Component represents a TCAP Component. 76 | type Component struct { 77 | Type Tag 78 | Length uint8 79 | InvokeID *IE 80 | LinkedID *IE 81 | ResultRetres *IE 82 | SequenceTag *IE 83 | OperationCode *IE 84 | ErrorCode *IE 85 | ProblemCode *IE 86 | Parameter *IE 87 | } 88 | 89 | // NewComponents creates a new Components. 90 | func NewComponents(comps ...*Component) *Components { 91 | c := &Components{ 92 | Tag: NewApplicationWideConstructorTag(12), 93 | Component: comps, 94 | } 95 | c.SetLength() 96 | 97 | return c 98 | } 99 | 100 | // NewInvoke returns a new single Invoke Component. 101 | func NewInvoke(invID, lkID, opCode int, isLocal bool, param []byte) *Component { 102 | c := &Component{ 103 | Type: NewContextSpecificConstructorTag(Invoke), 104 | InvokeID: &IE{ 105 | Tag: NewUniversalPrimitiveTag(2), 106 | Length: 1, 107 | Value: []byte{uint8(invID)}, 108 | }, 109 | OperationCode: NewOperationCode(opCode, isLocal), 110 | } 111 | 112 | if lkID > 0 { 113 | c.LinkedID = &IE{ 114 | Tag: NewContextSpecificPrimitiveTag(0), 115 | Length: 1, 116 | Value: []byte{uint8(lkID)}, 117 | } 118 | } 119 | 120 | if param != nil { 121 | if err := c.setParameterFromBytes(param); err != nil { 122 | logf("failed to build Parameter: %v", err) 123 | } 124 | } 125 | 126 | c.SetLength() 127 | return c 128 | } 129 | 130 | // NewReturnResult returns a new single ReturnResultLast or ReturnResultNotLast Component. 131 | func NewReturnResult(invID, opCode int, isLocal, isLast bool, param []byte) *Component { 132 | tag := ReturnResultNotLast 133 | if isLast { 134 | tag = ReturnResultLast 135 | } 136 | 137 | c := &Component{ 138 | Type: NewContextSpecificConstructorTag(tag), 139 | ResultRetres: &IE{ 140 | Tag: NewUniversalConstructorTag(0x10), 141 | }, 142 | InvokeID: &IE{ 143 | Tag: NewUniversalPrimitiveTag(2), 144 | Length: 1, 145 | Value: []byte{uint8(invID)}, 146 | }, 147 | OperationCode: NewOperationCode(opCode, isLocal), 148 | } 149 | 150 | if param != nil { 151 | if err := c.setParameterFromBytes(param); err != nil { 152 | logf("failed to build Parameter: %v", err) 153 | } 154 | } 155 | 156 | c.SetLength() 157 | return c 158 | } 159 | 160 | // NewReturnError returns a new single ReturnError Component. 161 | func NewReturnError(invID, errCode int, isLocal bool, param []byte) *Component { 162 | c := &Component{ 163 | Type: NewContextSpecificConstructorTag(ReturnError), 164 | InvokeID: &IE{ 165 | Tag: NewUniversalPrimitiveTag(2), 166 | Length: 1, 167 | Value: []byte{uint8(invID)}, 168 | }, 169 | ErrorCode: NewErrorCode(errCode, isLocal), 170 | } 171 | 172 | if param != nil { 173 | if err := c.setParameterFromBytes(param); err != nil { 174 | logf("failed to build Parameter: %v", err) 175 | } 176 | } 177 | 178 | c.SetLength() 179 | return c 180 | } 181 | 182 | // NewReject returns a new single Reject Component. 183 | func NewReject(invID, problemType int, problemCode uint8, param []byte) *Component { 184 | c := &Component{ 185 | Type: NewContextSpecificConstructorTag(Invoke), 186 | InvokeID: &IE{ 187 | Tag: NewUniversalPrimitiveTag(2), 188 | Length: 1, 189 | Value: []byte{uint8(invID)}, 190 | }, 191 | ProblemCode: &IE{ 192 | Tag: NewContextSpecificPrimitiveTag(problemType), 193 | Length: 1, 194 | Value: []byte{problemCode}, 195 | }, 196 | } 197 | 198 | if param != nil { 199 | if err := c.setParameterFromBytes(param); err != nil { 200 | logf("failed to build Parameter: %v", err) 201 | } 202 | } 203 | 204 | c.SetLength() 205 | return c 206 | } 207 | 208 | // NewOperationCode returns a Operation Code. 209 | func NewOperationCode(code int, isLocal bool) *IE { 210 | var tag = 6 211 | if isLocal { 212 | tag = 2 213 | } 214 | return &IE{ 215 | Tag: NewUniversalPrimitiveTag(tag), 216 | Length: 1, 217 | Value: []byte{uint8(code)}, 218 | } 219 | } 220 | 221 | // NewErrorCode returns a Error Code. 222 | func NewErrorCode(code int, isLocal bool) *IE { 223 | return NewOperationCode(code, isLocal) 224 | } 225 | 226 | // MarshalBinary returns the byte sequence generated from a Components instance. 227 | func (c *Components) MarshalBinary() ([]byte, error) { 228 | b := make([]byte, c.MarshalLen()) 229 | if err := c.MarshalTo(b); err != nil { 230 | return nil, err 231 | } 232 | return b, nil 233 | } 234 | 235 | // MarshalTo puts the byte sequence in the byte array given as b. 236 | func (c *Components) MarshalTo(b []byte) error { 237 | b[0] = uint8(c.Tag) 238 | b[1] = c.Length 239 | 240 | cursor := 2 241 | for _, comp := range c.Component { 242 | compLen := comp.MarshalLen() 243 | if err := comp.MarshalTo(b[cursor : cursor+compLen]); err != nil { 244 | return err 245 | } 246 | cursor += compLen 247 | } 248 | return nil 249 | } 250 | 251 | // MarshalBinary returns the byte sequence generated from a Components instance. 252 | func (c *Component) MarshalBinary() ([]byte, error) { 253 | b := make([]byte, c.MarshalLen()) 254 | if err := c.MarshalTo(b); err != nil { 255 | return nil, err 256 | } 257 | return b, nil 258 | } 259 | 260 | // MarshalTo puts the byte sequence in the byte array given as b. 261 | func (c *Component) MarshalTo(b []byte) error { 262 | b[0] = uint8(c.Type) 263 | b[1] = c.Length 264 | 265 | var offset = 2 266 | if field := c.InvokeID; field != nil { 267 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 268 | return err 269 | } 270 | offset += field.MarshalLen() 271 | } 272 | 273 | switch c.Type.Code() { 274 | case Invoke: 275 | if field := c.LinkedID; field != nil { 276 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 277 | return err 278 | } 279 | offset += field.MarshalLen() 280 | } 281 | 282 | if field := c.OperationCode; field != nil { 283 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 284 | return err 285 | } 286 | offset += field.MarshalLen() 287 | } 288 | 289 | if field := c.Parameter; field != nil { 290 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 291 | return err 292 | } 293 | } 294 | case ReturnResultLast, ReturnResultNotLast: 295 | if field := c.ResultRetres; field != nil { 296 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 297 | return err 298 | } 299 | offset += field.MarshalLen() 300 | } 301 | 302 | if field := c.OperationCode; field != nil { 303 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 304 | return err 305 | } 306 | offset += field.MarshalLen() 307 | } 308 | 309 | if field := c.Parameter; field != nil { 310 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 311 | return err 312 | } 313 | } 314 | case ReturnError: 315 | if field := c.ErrorCode; field != nil { 316 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 317 | return err 318 | } 319 | offset += field.MarshalLen() 320 | } 321 | 322 | if field := c.Parameter; field != nil { 323 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 324 | return err 325 | } 326 | } 327 | case Reject: 328 | if field := c.ProblemCode; field != nil { 329 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 330 | return err 331 | } 332 | } 333 | } 334 | return nil 335 | } 336 | 337 | // ParseComponents parses given byte sequence as an Components. 338 | func ParseComponents(b []byte) (*Components, error) { 339 | c := &Components{} 340 | if err := c.UnmarshalBinary(b); err != nil { 341 | return nil, err 342 | } 343 | return c, nil 344 | } 345 | 346 | // UnmarshalBinary sets the values retrieved from byte sequence in an Components. 347 | func (c *Components) UnmarshalBinary(b []byte) error { 348 | if len(b) < 2 { 349 | return io.ErrUnexpectedEOF 350 | } 351 | 352 | c.Tag = Tag(b[0]) 353 | c.Length = b[1] 354 | 355 | var offset = 2 356 | for { 357 | if len(b) < 2 { 358 | break 359 | } 360 | 361 | comp, err := ParseComponent(b[offset:]) 362 | if err != nil { 363 | return err 364 | } 365 | c.Component = append(c.Component, comp) 366 | 367 | if len(b[offset:]) == int(comp.Length)+2 { 368 | break 369 | } 370 | b = b[offset+comp.MarshalLen()-2:] 371 | } 372 | return nil 373 | } 374 | 375 | // ParseComponent parses given byte sequence as an Component. 376 | func ParseComponent(b []byte) (*Component, error) { 377 | c := &Component{} 378 | if err := c.UnmarshalBinary(b); err != nil { 379 | return nil, err 380 | } 381 | return c, nil 382 | } 383 | 384 | // UnmarshalBinary sets the values retrieved from byte sequence in an Component. 385 | func (c *Component) UnmarshalBinary(b []byte) error { 386 | if len(b) < 2 { 387 | return io.ErrUnexpectedEOF 388 | } 389 | c.Type = Tag(b[0]) 390 | c.Length = b[1] 391 | 392 | var err error 393 | var offset = 2 394 | c.InvokeID, err = ParseIE(b[offset:]) 395 | if err != nil { 396 | return err 397 | } 398 | offset += c.InvokeID.MarshalLen() 399 | 400 | switch c.Type.Code() { 401 | case Invoke: 402 | /* TODO: Implement LinkedID Parser. 403 | c.LinkedID, err = ParseIE(b[offset:]) 404 | if err != nil { 405 | return err 406 | } 407 | offset += c.LinkedID.MarshalLen() 408 | */ 409 | c.OperationCode, err = ParseIE(b[offset:]) 410 | if err != nil { 411 | return err 412 | } 413 | offset += c.OperationCode.MarshalLen() 414 | 415 | if offset >= len(b) { 416 | return nil 417 | } 418 | c.Parameter, err = ParseIERecursive(b[offset:]) 419 | if err != nil { 420 | return err 421 | } 422 | case ReturnResultLast, ReturnResultNotLast: 423 | c.ResultRetres, err = ParseIE(b[offset:]) 424 | if err != nil { 425 | return err 426 | } 427 | offset = 0 428 | b = c.ResultRetres.Value[offset:] 429 | 430 | c.OperationCode, err = ParseIE(b[offset:]) 431 | if err != nil { 432 | return err 433 | } 434 | offset += c.OperationCode.MarshalLen() 435 | 436 | if offset >= len(b) { 437 | return nil 438 | } 439 | c.Parameter, err = ParseIERecursive(b[offset:]) 440 | if err != nil { 441 | return err 442 | } 443 | case ReturnError: 444 | c.ErrorCode, err = ParseIE(b[offset:]) 445 | if err != nil { 446 | return err 447 | } 448 | offset += c.ErrorCode.MarshalLen() 449 | 450 | if offset >= len(b) { 451 | return nil 452 | } 453 | c.Parameter, err = ParseIERecursive(b[offset:]) 454 | if err != nil { 455 | return err 456 | } 457 | case Reject: 458 | c.ProblemCode, err = ParseIE(b[offset:]) 459 | if err != nil { 460 | return err 461 | } 462 | } 463 | return nil 464 | } 465 | 466 | // setParameterFromBytes sets the Parameter field from given bytes. 467 | // 468 | // It sets the value as it is if the given bytes cannot be parsed as (a set of) IE. 469 | func (c *Component) setParameterFromBytes(b []byte) error { 470 | if b == nil { 471 | return io.ErrUnexpectedEOF 472 | } 473 | ies, err := ParseMultiIEs(b) 474 | if err != nil { 475 | logf("failed to parse given bytes, building it anyway: %v", err) 476 | c.Parameter = &IE{ 477 | // TODO: tag should not be determined here. 478 | Tag: NewUniversalConstructorTag(0x10), 479 | Value: b, 480 | } 481 | 482 | return nil 483 | } 484 | 485 | c.Parameter = &IE{ 486 | // TODO: tag should not be determined here. 487 | Tag: NewUniversalConstructorTag(0x10), 488 | Value: b, 489 | IE: ies, 490 | } 491 | return nil 492 | } 493 | 494 | // SetValsFrom sets the values from IE parsed by ParseBER. 495 | func (c *Components) SetValsFrom(berParsed *IE) error { 496 | c.Tag = berParsed.Tag 497 | c.Length = berParsed.Length 498 | for _, ie := range berParsed.IE { 499 | comp := &Component{ 500 | Type: ie.Tag, 501 | Length: ie.Length, 502 | } 503 | 504 | switch ie.Tag { 505 | case 0xa1: // Invoke 506 | for i, iex := range ie.IE { 507 | switch iex.Tag { 508 | case 0x02: 509 | if i == 0 { 510 | comp.InvokeID = iex 511 | } else { 512 | comp.OperationCode = iex 513 | } 514 | case 0x30: 515 | comp.Parameter = iex 516 | } 517 | } 518 | case 0xa2, 0xa7: // ReturnResult(Not)Last 519 | for i, iex := range ie.IE { 520 | switch iex.Tag { 521 | case 0x02: 522 | if i == 0 { 523 | comp.InvokeID = iex 524 | } 525 | case 0x30: 526 | comp.ResultRetres = iex 527 | for _, riex := range iex.IE { 528 | switch riex.Tag { 529 | case 0x02: 530 | comp.OperationCode = riex 531 | case 0x30: 532 | comp.Parameter = riex 533 | } 534 | } 535 | } 536 | } 537 | case 0xa3: // ReturnError 538 | for i, iex := range ie.IE { 539 | switch iex.Tag { 540 | case 0x02: 541 | if i == 0 { 542 | comp.InvokeID = iex 543 | } else { 544 | comp.ErrorCode = iex 545 | } 546 | case 0x30: 547 | comp.Parameter = iex 548 | } 549 | } 550 | } 551 | 552 | c.Component = append(c.Component, comp) 553 | } 554 | 555 | return nil 556 | } 557 | 558 | // MarshalLen returns the serial length of Components. 559 | func (c *Components) MarshalLen() int { 560 | var l = 2 561 | for _, comp := range c.Component { 562 | l += comp.MarshalLen() 563 | } 564 | return l 565 | } 566 | 567 | // MarshalLen returns the serial length of Component. 568 | func (c *Component) MarshalLen() int { 569 | var l = 2 + c.InvokeID.MarshalLen() 570 | switch c.Type.Code() { 571 | case Invoke: 572 | if field := c.LinkedID; field != nil { 573 | l += field.MarshalLen() 574 | } 575 | if field := c.OperationCode; field != nil { 576 | l += field.MarshalLen() 577 | } 578 | if field := c.Parameter; field != nil { 579 | l += field.MarshalLen() 580 | } 581 | case ReturnResultLast, ReturnResultNotLast: 582 | if field := c.ResultRetres; field != nil { 583 | l += field.MarshalLen() 584 | } 585 | if field := c.OperationCode; field != nil { 586 | l += field.MarshalLen() 587 | } 588 | if field := c.Parameter; field != nil { 589 | l += field.MarshalLen() 590 | } 591 | case ReturnError: 592 | if field := c.ErrorCode; field != nil { 593 | l += field.MarshalLen() 594 | } 595 | if field := c.Parameter; field != nil { 596 | l += field.MarshalLen() 597 | } 598 | case Reject: 599 | if field := c.ProblemCode; field != nil { 600 | l += field.MarshalLen() 601 | } 602 | } 603 | return l 604 | } 605 | 606 | // SetLength sets the length in Length field. 607 | func (c *Components) SetLength() { 608 | c.Length = 0 609 | for _, comp := range c.Component { 610 | comp.SetLength() 611 | c.Length += uint8(comp.MarshalLen()) 612 | } 613 | } 614 | 615 | // SetLength sets the length in Length field. 616 | func (c *Component) SetLength() { 617 | l := 0 618 | if field := c.InvokeID; field != nil { 619 | field.SetLength() 620 | } 621 | if field := c.LinkedID; field != nil { 622 | field.SetLength() 623 | } 624 | if field := c.OperationCode; field != nil { 625 | field.SetLength() 626 | l += c.OperationCode.MarshalLen() 627 | } 628 | if field := c.ErrorCode; field != nil { 629 | field.SetLength() 630 | l += c.ErrorCode.MarshalLen() 631 | } 632 | if field := c.Parameter; field != nil { 633 | field.SetLength() 634 | l += c.Parameter.MarshalLen() 635 | } 636 | if field := c.ProblemCode; field != nil { 637 | field.SetLength() 638 | l += c.ProblemCode.MarshalLen() 639 | } 640 | if field := c.SequenceTag; field != nil { 641 | field.SetLength() 642 | l += c.SequenceTag.MarshalLen() 643 | } 644 | if field := c.ResultRetres; field != nil { 645 | field.Length = uint8(l) 646 | } 647 | c.Length = uint8(c.MarshalLen() - 2) 648 | } 649 | 650 | // ComponentTypeString returns the Component Type in string. 651 | func (c *Component) ComponentTypeString() string { 652 | switch c.Type.Code() { 653 | case Invoke: 654 | return "invoke" 655 | case ReturnResultLast: 656 | return "returnResultLast" 657 | case ReturnError: 658 | return "returnError" 659 | case Reject: 660 | return "reject" 661 | case ReturnResultNotLast: 662 | return "returnResultNotLast" 663 | } 664 | return "" 665 | } 666 | 667 | // InvID returns the InvID in string. 668 | func (c *Component) InvID() uint8 { 669 | if c.InvokeID != nil { 670 | return c.InvokeID.Value[0] 671 | } 672 | return 0 673 | } 674 | 675 | // OpCode returns the OpCode in string. 676 | func (c *Component) OpCode() uint8 { 677 | if c.Type.Code() == ReturnError { 678 | return c.ErrorCode.Value[0] 679 | } else if c.Type.Code() != Reject { 680 | return c.OperationCode.Value[0] 681 | } 682 | return 0 683 | } 684 | 685 | // String returns Components in human readable string. 686 | func (c *Components) String() string { 687 | return fmt.Sprintf("{Tag: %#x, Length: %d, Component: %v}", 688 | c.Tag, 689 | c.Length, 690 | c.Component, 691 | ) 692 | } 693 | 694 | // String returns Component in human readable string. 695 | func (c *Component) String() string { 696 | return fmt.Sprintf("{Type: %#x, Length: %d, ResultRetres: %v, InvokeID: %v, LinkedID: %v, OperationCode: %v, ErrorCode: %v, ProblemCode: %v, Parameter: %v}", 697 | c.Type, 698 | c.Length, 699 | c.ResultRetres, 700 | c.InvokeID, 701 | c.LinkedID, 702 | c.OperationCode, 703 | c.ErrorCode, 704 | c.ProblemCode, 705 | c.Parameter, 706 | ) 707 | } 708 | -------------------------------------------------------------------------------- /dialogue-pdu.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2024 go-tcap authors. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | package tcap 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | ) 11 | 12 | // Code definitions. 13 | const ( 14 | AARQ = iota 15 | AARE 16 | ABRT 17 | // AUDT = 0 18 | ) 19 | 20 | // Application Context definitions. 21 | const ( 22 | _ uint8 = iota 23 | NetworkLocUpContext 24 | LocationCancellationContext 25 | RoamingNumberEnquiryContext 26 | IstAlertingContext 27 | LocationInfoRetrievalContext 28 | CallControlTransferContext 29 | ReportingContext 30 | CallCompletionContext 31 | ServiceTerminationContext 32 | ResetContext 33 | HandoverControlContext 34 | SIWFSAllocationContext 35 | EquipmentMngtContext 36 | InfoRetrievalContext 37 | InterVlrInfoRetrievalContext 38 | SubscriberDataMngtContext 39 | TracingContext 40 | NetworkFunctionalSsContext 41 | NetworkUnstructuredSsContext 42 | ShortMsgGatewayContext 43 | ShortMsgRelayContext 44 | SubscriberDataModificationNotificationContext 45 | ShortMsgAlertContext 46 | MwdMngtContext 47 | ShortMsgMTRelayContext 48 | ImsiRetrievalContext 49 | MsPurgingContext 50 | SubscriberInfoEnquiryContext 51 | AnyTimeInfoEnquiryContext 52 | _ 53 | GroupCallControlContext 54 | GprsLocationUpdateContext 55 | GprsLocationInfoRetrievalContext 56 | FailureReportContext 57 | GprsNotifyContext 58 | SsInvocationNotificationContext 59 | LocationSvcGatewayContext 60 | LocationSvcEnquiryContext 61 | AuthenticationFailureReportContext 62 | _ 63 | _ 64 | MmEventReportingContext 65 | AnyTimeInfoHandlingContext 66 | ) 67 | 68 | // Result Value defnitions. 69 | const ( 70 | Accepted uint8 = iota 71 | RejectPerm 72 | ) 73 | 74 | // Dialogue Service Diagnostic Tag defnitions. 75 | const ( 76 | _ int = iota 77 | DialogueServiceUser 78 | DialogueServiceProvider 79 | ) 80 | 81 | // Reason defnitions for Dialogue Service User Diagnostic in ResultSourceDiagnostic. 82 | const ( 83 | Null uint8 = iota 84 | NoReasonGiven 85 | ApplicationContextNameNotSupplied 86 | NoCommonDialoguePortion = 2 // same as above... 87 | ) 88 | 89 | // Abort Source defnitions. 90 | const ( 91 | AbortDialogueServiceUser int = iota 92 | AbortDialogueServiceProvider 93 | ) 94 | 95 | // DialoguePDU represents a DialoguePDU field in Dialogue. 96 | type DialoguePDU struct { 97 | Type Tag 98 | Length uint8 99 | ProtocolVersion *IE 100 | ApplicationContextName *IE 101 | Result *IE 102 | ResultSourceDiagnostic *IE 103 | AbortSource *IE 104 | UserInformation *IE 105 | } 106 | 107 | // NewDialoguePDU creates a new DialoguePDU. 108 | func NewDialoguePDU(dtype, pver int, ctx, ctxver, result uint8, diagsrc int, diagreason, abortsrc uint8, userinfo ...*IE) *DialoguePDU { 109 | d := &DialoguePDU{ 110 | Type: NewApplicationWideConstructorTag(dtype), 111 | ProtocolVersion: &IE{ 112 | Tag: NewContextSpecificPrimitiveTag(0), 113 | Value: []byte{uint8(pver << 7)}, 114 | }, 115 | ApplicationContextName: NewApplicationContextName(ctx, ctxver), 116 | Result: NewResult(result), 117 | ResultSourceDiagnostic: NewResultSourceDiagnostic(diagsrc, diagreason), 118 | AbortSource: &IE{ 119 | Tag: NewContextSpecificPrimitiveTag(0), 120 | Length: 1, 121 | Value: []byte{abortsrc}, 122 | }, 123 | } 124 | if len(userinfo) > 0 { 125 | d.UserInformation = &IE{ 126 | Tag: NewContextSpecificConstructorTag(30), 127 | Value: userinfo[0].Value, 128 | } 129 | d.UserInformation.SetLength() 130 | } 131 | d.SetLength() 132 | return d 133 | } 134 | 135 | // NewApplicationContextName creates a new ApplicationContextName as an IE. 136 | // Note: In this function, each length in fields are hard-coded. 137 | func NewApplicationContextName(ctx, ver uint8) *IE { 138 | return &IE{ 139 | Tag: NewContextSpecificConstructorTag(1), 140 | Length: uint8(9), 141 | Value: []byte{0x06, 0x07, 4, 0, 0, 1, 0, ctx, ver}, 142 | } 143 | } 144 | 145 | // NewResult returns a new Result. 146 | func NewResult(res uint8) *IE { 147 | return &IE{ 148 | Tag: NewContextSpecificConstructorTag(2), 149 | Length: 3, 150 | Value: []byte{0x02, 0x01, res}, 151 | } 152 | } 153 | 154 | // NewResultSourceDiagnostic returns a new ResultSourceDiagnostic as an IE. 155 | func NewResultSourceDiagnostic(dtype int, reason uint8) *IE { 156 | return &IE{ 157 | Tag: NewContextSpecificConstructorTag(3), 158 | Length: 5, 159 | Value: []byte{ 160 | uint8(NewContextSpecificConstructorTag(dtype)), 161 | 0x03, // MarshalLength 162 | 0x02, 0x01, reason, // Integer Tag, Length and Reason value. 163 | }, 164 | } 165 | } 166 | 167 | // NewAbortSource returns a new AbortSource as an IE. 168 | func NewAbortSource(src uint8) *IE { 169 | return &IE{ 170 | Tag: NewContextSpecificPrimitiveTag(4), 171 | Length: 1, 172 | Value: []byte{src}, 173 | } 174 | } 175 | 176 | // NewAARQ returns a new AARQ(Dialogue Request). 177 | func NewAARQ(protover int, context, contextver uint8, userinfo ...*IE) *DialoguePDU { 178 | d := &DialoguePDU{ 179 | Type: NewApplicationWideConstructorTag(AARQ), 180 | ProtocolVersion: &IE{ 181 | Tag: NewContextSpecificPrimitiveTag(0), 182 | Value: []byte{0x07, uint8(protover << 7)}, // I don't actually know what the 0x07(padding) means... 183 | }, 184 | ApplicationContextName: NewApplicationContextName(context, contextver), 185 | } 186 | if len(userinfo) > 0 { 187 | d.UserInformation = &IE{ 188 | Tag: NewContextSpecificConstructorTag(30), 189 | Value: userinfo[0].Value, 190 | } 191 | d.UserInformation.SetLength() 192 | } 193 | d.SetLength() 194 | return d 195 | } 196 | 197 | // NewAARE returns a new AARE(Dialogue Response). 198 | func NewAARE(protover int, context, contextver, result uint8, diagsrc int, reason uint8, userinfo ...*IE) *DialoguePDU { 199 | d := &DialoguePDU{ 200 | Type: NewApplicationWideConstructorTag(AARE), 201 | ProtocolVersion: &IE{ 202 | Tag: NewContextSpecificPrimitiveTag(0), 203 | Value: []byte{0x07, uint8(protover << 7)}, // I don't actually know what the 0x07(padding) means... 204 | }, 205 | ApplicationContextName: NewApplicationContextName(context, contextver), 206 | Result: NewResult(result), 207 | ResultSourceDiagnostic: NewResultSourceDiagnostic(diagsrc, reason), 208 | } 209 | if len(userinfo) > 0 { 210 | d.UserInformation = &IE{ 211 | Tag: NewContextSpecificConstructorTag(30), 212 | Value: userinfo[0].Value, 213 | } 214 | d.UserInformation.SetLength() 215 | } 216 | d.SetLength() 217 | return d 218 | } 219 | 220 | // NewABRT returns a new ABRT(Dialogue Abort). 221 | func NewABRT(abortsrc uint8, userinfo ...*IE) *DialoguePDU { 222 | d := &DialoguePDU{ 223 | Type: NewApplicationWideConstructorTag(ABRT), 224 | AbortSource: &IE{ 225 | Tag: NewContextSpecificPrimitiveTag(0), 226 | Length: 1, 227 | Value: []byte{abortsrc}, 228 | }, 229 | } 230 | if len(userinfo) > 0 { 231 | d.UserInformation = &IE{ 232 | Tag: NewContextSpecificConstructorTag(30), 233 | Value: userinfo[0].Value, 234 | } 235 | d.UserInformation.SetLength() 236 | } 237 | d.SetLength() 238 | return d 239 | } 240 | 241 | /* 242 | // NewAUDT returns a new AUDT(Unidirectional Dialogue). 243 | func NewAUDT(protover int, context, contextver uint8, userinfo ...*IE) *DialoguePDU { 244 | d := NewDialoguePDU( 245 | AUDT, 246 | protover, 247 | context, 248 | contextver, 249 | 0, 250 | 0, 251 | 0, 252 | 0, 253 | ) 254 | if len(userinfo) > 0 { 255 | d.UserInformation = userinfo[0] 256 | } 257 | d.ProtocolVersion.Clear() 258 | d.ApplicationContextName.Clear() 259 | d.Result.Clear() 260 | d.ResultSourceDiagnostic.Clear() 261 | d.SetLength() 262 | 263 | return d 264 | } 265 | */ 266 | 267 | // MarshalBinary returns the byte sequence generated from a DialoguePDU. 268 | func (d *DialoguePDU) MarshalBinary() ([]byte, error) { 269 | b := make([]byte, d.MarshalLen()) 270 | if err := d.MarshalTo(b); err != nil { 271 | return nil, fmt.Errorf("failed to marshal DialoguePDU: %w", err) 272 | } 273 | return b, nil 274 | } 275 | 276 | // MarshalTo puts the byte sequence in the byte array given as b. 277 | func (d *DialoguePDU) MarshalTo(b []byte) error { 278 | if len(b) < 2 { 279 | return io.ErrUnexpectedEOF 280 | } 281 | 282 | b[0] = uint8(d.Type) 283 | b[1] = d.Length 284 | 285 | switch d.Type.Code() { 286 | case AARQ: 287 | return d.marshalAARQTo(b) 288 | case AARE: 289 | return d.marshalAARETo(b) 290 | case ABRT: 291 | return d.marshalABRTTo(b) 292 | default: 293 | return &InvalidCodeError{Code: d.Type.Code()} 294 | } 295 | } 296 | 297 | func (d *DialoguePDU) marshalAARQTo(b []byte) error { 298 | var offset = 2 299 | if field := d.ProtocolVersion; field != nil { 300 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 301 | return err 302 | } 303 | offset += field.MarshalLen() 304 | } 305 | 306 | if field := d.ApplicationContextName; field != nil { 307 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 308 | return err 309 | } 310 | offset += field.MarshalLen() 311 | } 312 | 313 | if field := d.UserInformation; field != nil { 314 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 315 | return err 316 | } 317 | } 318 | 319 | return nil 320 | } 321 | 322 | func (d *DialoguePDU) marshalAARETo(b []byte) error { 323 | var offset = 2 324 | if field := d.ProtocolVersion; field != nil { 325 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 326 | return err 327 | } 328 | offset += field.MarshalLen() 329 | } 330 | 331 | if field := d.ApplicationContextName; field != nil { 332 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 333 | return err 334 | } 335 | offset += field.MarshalLen() 336 | } 337 | 338 | if field := d.Result; field != nil { 339 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 340 | return err 341 | } 342 | offset += field.MarshalLen() 343 | } 344 | 345 | if field := d.ResultSourceDiagnostic; field != nil { 346 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 347 | return err 348 | } 349 | offset += field.MarshalLen() 350 | } 351 | 352 | if field := d.UserInformation; field != nil { 353 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 354 | return err 355 | } 356 | } 357 | 358 | return nil 359 | } 360 | 361 | func (d *DialoguePDU) marshalABRTTo(b []byte) error { 362 | var offset = 2 363 | if field := d.AbortSource; field != nil { 364 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 365 | return err 366 | } 367 | offset += field.MarshalLen() 368 | } 369 | 370 | if field := d.UserInformation; field != nil { 371 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 372 | return err 373 | } 374 | } 375 | 376 | return nil 377 | } 378 | 379 | // ParseDialoguePDU parses given byte sequence as an DialoguePDU. 380 | func ParseDialoguePDU(b []byte) (*DialoguePDU, error) { 381 | d := &DialoguePDU{} 382 | if err := d.UnmarshalBinary(b); err != nil { 383 | return nil, err 384 | } 385 | return d, nil 386 | } 387 | 388 | // UnmarshalBinary sets the values retrieved from byte sequence in an DialoguePDU. 389 | func (d *DialoguePDU) UnmarshalBinary(b []byte) error { 390 | if len(b) < 4 { 391 | return io.ErrUnexpectedEOF 392 | } 393 | 394 | d.Type = Tag(b[0]) 395 | d.Length = b[1] 396 | 397 | switch d.Type.Code() { 398 | case AARQ: 399 | return d.parseAARQFromBytes(b) 400 | case AARE: 401 | return d.parseAAREFromBytes(b) 402 | case ABRT: 403 | return d.parseABRTFromBytes(b) 404 | default: 405 | return &InvalidCodeError{Code: d.Type.Code()} 406 | } 407 | } 408 | 409 | func (d *DialoguePDU) parseAARQFromBytes(b []byte) error { 410 | var err error 411 | var offset = 2 412 | d.ProtocolVersion, err = ParseIE(b[offset:]) 413 | if err != nil { 414 | return err 415 | } 416 | offset += d.ProtocolVersion.MarshalLen() 417 | 418 | d.ApplicationContextName, err = ParseIE(b[offset:]) 419 | if err != nil { 420 | return err 421 | } 422 | offset += d.ApplicationContextName.MarshalLen() 423 | 424 | if offset < len(b)-1 { 425 | if b[offset] == uint8(NewContextSpecificConstructorTag(30)) { 426 | d.UserInformation, err = ParseIE(b[offset:]) 427 | if err != nil { 428 | return err 429 | } 430 | } 431 | } 432 | 433 | return nil 434 | } 435 | 436 | func (d *DialoguePDU) parseAAREFromBytes(b []byte) error { 437 | var err error 438 | var offset = 2 439 | d.ProtocolVersion, err = ParseIE(b[offset:]) 440 | if err != nil { 441 | return err 442 | } 443 | offset += d.ProtocolVersion.MarshalLen() 444 | 445 | d.ApplicationContextName, err = ParseIE(b[offset:]) 446 | if err != nil { 447 | return err 448 | } 449 | offset += d.ApplicationContextName.MarshalLen() 450 | 451 | d.Result, err = ParseIE(b[offset:]) 452 | if err != nil { 453 | return err 454 | } 455 | offset += d.Result.MarshalLen() 456 | 457 | d.ResultSourceDiagnostic, err = ParseIE(b[offset:]) 458 | if err != nil { 459 | return err 460 | } 461 | offset += d.ResultSourceDiagnostic.MarshalLen() 462 | 463 | if offset < len(b)-1 { 464 | if b[offset] == uint8(NewContextSpecificConstructorTag(30)) { 465 | d.UserInformation, err = ParseIE(b[offset:]) 466 | if err != nil { 467 | return err 468 | } 469 | } 470 | } 471 | 472 | return nil 473 | } 474 | 475 | func (d *DialoguePDU) parseABRTFromBytes(b []byte) error { 476 | var err error 477 | var offset = 2 478 | d.AbortSource, err = ParseIE(b[offset:]) 479 | if err != nil { 480 | return err 481 | } 482 | offset += d.AbortSource.MarshalLen() 483 | if offset < len(b)-1 { 484 | if b[offset] == uint8(NewContextSpecificConstructorTag(30)) { 485 | d.UserInformation, err = ParseIE(b[offset:]) 486 | if err != nil { 487 | return err 488 | } 489 | } 490 | } 491 | 492 | return nil 493 | } 494 | 495 | // MarshalLen returns the serial length of DialoguePDU. 496 | func (d *DialoguePDU) MarshalLen() int { 497 | l := 2 498 | switch d.Type.Code() { 499 | case AARQ: 500 | if field := d.ProtocolVersion; field != nil { 501 | l += field.MarshalLen() 502 | } 503 | if field := d.ApplicationContextName; field != nil { 504 | l += field.MarshalLen() 505 | } 506 | case AARE: 507 | if field := d.ProtocolVersion; field != nil { 508 | l += field.MarshalLen() 509 | } 510 | if field := d.ApplicationContextName; field != nil { 511 | l += field.MarshalLen() 512 | } 513 | if field := d.Result; field != nil { 514 | l += field.MarshalLen() 515 | } 516 | if field := d.ResultSourceDiagnostic; field != nil { 517 | l += field.MarshalLen() 518 | } 519 | case ABRT: 520 | if field := d.AbortSource; field != nil { 521 | l += field.MarshalLen() 522 | } 523 | } 524 | 525 | if field := d.UserInformation; field != nil { 526 | l += field.MarshalLen() 527 | } 528 | return l 529 | } 530 | 531 | // SetLength sets the length in Length field. 532 | func (d *DialoguePDU) SetLength() { 533 | switch d.Type.Code() { 534 | case AARQ: 535 | if field := d.ProtocolVersion; field != nil { 536 | field.SetLength() 537 | } 538 | if field := d.ApplicationContextName; field != nil { 539 | field.SetLength() 540 | } 541 | case AARE: 542 | if field := d.ProtocolVersion; field != nil { 543 | field.SetLength() 544 | } 545 | if field := d.ApplicationContextName; field != nil { 546 | field.SetLength() 547 | } 548 | if field := d.Result; field != nil { 549 | field.SetLength() 550 | } 551 | if field := d.ResultSourceDiagnostic; field != nil { 552 | field.SetLength() 553 | } 554 | case ABRT: 555 | if field := d.AbortSource; field != nil { 556 | field.SetLength() 557 | } 558 | } 559 | if field := d.UserInformation; field != nil { 560 | field.SetLength() 561 | } 562 | d.Length = uint8(d.MarshalLen() - 2) 563 | } 564 | 565 | // DialogueType returns the name of Dialogue Type in string. 566 | func (d *DialoguePDU) DialogueType() string { 567 | switch d.Type.Code() { 568 | case AARQ: 569 | return "AARQ" 570 | case AARE: 571 | return "AARE" 572 | case ABRT: 573 | return "ABRT" 574 | default: 575 | return "" 576 | } 577 | } 578 | 579 | // Version returns Protocol Version in string. 580 | func (d *DialoguePDU) Version() string { 581 | if d.Type.Code() == AARQ || d.Type.Code() == AARE { 582 | return fmt.Sprintf("%d", d.ProtocolVersion.Value[len(d.ProtocolVersion.Value)-1]>>7) 583 | } 584 | return "" 585 | } 586 | 587 | // Context returns the Context part of ApplicationContextName in string. 588 | func (d *DialoguePDU) Context() string { 589 | appCtx := d.ApplicationContextName 590 | if appCtx == nil { 591 | return "" 592 | } 593 | if len(appCtx.Value) < 8 { 594 | return "" 595 | } 596 | 597 | if d.Type.Code() == AARQ || d.Type.Code() == AARE { 598 | switch appCtx.Value[7] { 599 | case NetworkLocUpContext: 600 | return "networkLocUpContext" 601 | case LocationCancellationContext: 602 | return "locationCancellationContext" 603 | case RoamingNumberEnquiryContext: 604 | return "roamingNumberEnquiryContext" 605 | case IstAlertingContext: 606 | return "istAlertingContext" 607 | case LocationInfoRetrievalContext: 608 | return "locationInfoRetrievalContext" 609 | case CallControlTransferContext: 610 | return "callControlTransferContext" 611 | case ReportingContext: 612 | return "reportingContext" 613 | case CallCompletionContext: 614 | return "callCompletionContext" 615 | case ServiceTerminationContext: 616 | return "serviceTerminationContext" 617 | case ResetContext: 618 | return "resetContext" 619 | case HandoverControlContext: 620 | return "handoverControlContext" 621 | case SIWFSAllocationContext: 622 | return "sIWFSAllocationContext" 623 | case EquipmentMngtContext: 624 | return "equipmentMngtContext" 625 | case InfoRetrievalContext: 626 | return "infoRetrievalContext" 627 | case InterVlrInfoRetrievalContext: 628 | return "interVlrInfoRetrievalContext" 629 | case SubscriberDataMngtContext: 630 | return "SubscriberDataMngtContext" 631 | case TracingContext: 632 | return "tracingContext" 633 | case NetworkFunctionalSsContext: 634 | return "networkFunctionalSsContext" 635 | case NetworkUnstructuredSsContext: 636 | return "networkUnstructuredSsContext" 637 | case ShortMsgGatewayContext: 638 | return "shortMsgGatewayContext" 639 | case ShortMsgRelayContext: 640 | return "shortMsgRelayContext" 641 | case SubscriberDataModificationNotificationContext: 642 | return "subscriberDataModificationNotificationContext" 643 | case ShortMsgAlertContext: 644 | return "shortMsgAlertContext" 645 | case MwdMngtContext: 646 | return "mwdMngtContext" 647 | case ShortMsgMTRelayContext: 648 | return "shortMsgMTRelayContext" 649 | case ImsiRetrievalContext: 650 | return "imsiRetrievalContext" 651 | case MsPurgingContext: 652 | return "msPurgingContext" 653 | case SubscriberInfoEnquiryContext: 654 | return "subscriberInfoEnquiryContext" 655 | case AnyTimeInfoEnquiryContext: 656 | return "anyTimeInfoEnquiryContext" 657 | case GroupCallControlContext: 658 | return "groupCallControlContext" 659 | case GprsLocationUpdateContext: 660 | return "gprsLocationUpdateContext" 661 | case GprsLocationInfoRetrievalContext: 662 | return "gprsLocationInfoRetrievalContext" 663 | case FailureReportContext: 664 | return "failureReportContext" 665 | case GprsNotifyContext: 666 | return "gprsNotifyContext" 667 | case SsInvocationNotificationContext: 668 | return "ssInvocationNotificationContext" 669 | case LocationSvcGatewayContext: 670 | return "locationSvcGatewayContext" 671 | case LocationSvcEnquiryContext: 672 | return "locationSvcEnquiryContext" 673 | case AuthenticationFailureReportContext: 674 | return "authenticationFailureReportContext" 675 | case MmEventReportingContext: 676 | return "mmEventReportingContext" 677 | case AnyTimeInfoHandlingContext: 678 | return "anyTimeInfoHandlingContext" 679 | } 680 | } 681 | 682 | return "" 683 | } 684 | 685 | // ContextVersion returns the Version part of ApplicationContextName in string. 686 | func (d *DialoguePDU) ContextVersion() string { 687 | appCtx := d.ApplicationContextName 688 | if appCtx == nil { 689 | return "" 690 | } 691 | if len(appCtx.Value) < 8 { 692 | return "" 693 | } 694 | 695 | if d.Type.Code() == AARQ || d.Type.Code() == AARE { 696 | return fmt.Sprintf("%d", appCtx.Value[8]) 697 | } 698 | return "" 699 | } 700 | 701 | // String returns DialoguePDU in human readable string. 702 | func (d *DialoguePDU) String() string { 703 | return fmt.Sprintf("{Type: %#x, Length: %d, ProtocolVersion: %v, ApplicationContextName: %v, Result: %v, ResultSourceDiagnostic: %v, AbortSource: %v, UserInformation: %v}", 704 | d.Type, 705 | d.Length, 706 | d.ProtocolVersion, 707 | d.ApplicationContextName, 708 | d.Result, 709 | d.ResultSourceDiagnostic, 710 | d.AbortSource, 711 | d.UserInformation, 712 | ) 713 | } 714 | -------------------------------------------------------------------------------- /dialogue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2024 go-tcap authors. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | package tcap 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | ) 11 | 12 | // Dialogue OID: Dialogue-As-ID and Unidialogue-As-Id. 13 | const ( 14 | DialogueAsID uint8 = iota + 1 15 | UnidialogueAsID 16 | ) 17 | 18 | // Dialogue represents a Dialogue Portion of TCAP. 19 | type Dialogue struct { 20 | Tag Tag 21 | Length uint8 22 | ExternalTag Tag 23 | ExternalLength uint8 24 | ObjectIdentifier *IE 25 | SingleAsn1Type *IE 26 | DialoguePDU *DialoguePDU 27 | Payload []byte 28 | } 29 | 30 | // NewDialogue creates a new Dialogue with the DialoguePDU given. 31 | func NewDialogue(oid, ver uint8, pdu *DialoguePDU, payload []byte) *Dialogue { 32 | d := &Dialogue{ 33 | Tag: NewApplicationWideConstructorTag(11), 34 | ExternalTag: NewUniversalConstructorTag(8), 35 | ObjectIdentifier: &IE{ 36 | Tag: NewUniversalPrimitiveTag(6), 37 | Length: 7, 38 | Value: []byte{0, 17, 134, 5, 1, oid, ver}, 39 | }, 40 | SingleAsn1Type: &IE{ 41 | Tag: NewContextSpecificConstructorTag(0), 42 | Length: uint8(pdu.MarshalLen()), 43 | }, 44 | DialoguePDU: pdu, 45 | Payload: payload, 46 | } 47 | d.SetLength() 48 | 49 | return d 50 | } 51 | 52 | // MarshalBinary returns the byte sequence generated from a Dialogue. 53 | func (d *Dialogue) MarshalBinary() ([]byte, error) { 54 | b := make([]byte, d.MarshalLen()) 55 | if err := d.MarshalTo(b); err != nil { 56 | return nil, fmt.Errorf("failed to marshal Dialogue: %w", err) 57 | } 58 | return b, nil 59 | } 60 | 61 | // MarshalTo puts the byte sequence in the byte array given as b. 62 | func (d *Dialogue) MarshalTo(b []byte) error { 63 | if len(b) < 4 { 64 | return io.ErrUnexpectedEOF 65 | } 66 | b[0] = uint8(d.Tag) 67 | b[1] = d.Length 68 | b[2] = uint8(d.ExternalTag) 69 | b[3] = d.ExternalLength 70 | 71 | var offset = 4 72 | if field := d.ObjectIdentifier; field != nil { 73 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 74 | return err 75 | } 76 | offset += field.MarshalLen() 77 | } 78 | 79 | if d.SingleAsn1Type == nil { 80 | copy(b[offset:], d.Payload) 81 | return nil 82 | } 83 | 84 | if field := d.DialoguePDU; field != nil { 85 | d.SingleAsn1Type.Value = make([]byte, field.MarshalLen()) 86 | if err := field.MarshalTo(d.SingleAsn1Type.Value); err != nil { 87 | return err 88 | } 89 | } 90 | 91 | d.SingleAsn1Type.SetLength() 92 | if err := d.SingleAsn1Type.MarshalTo(b[offset : offset+d.SingleAsn1Type.MarshalLen()]); err != nil { 93 | return err 94 | } 95 | offset += d.SingleAsn1Type.MarshalLen() 96 | 97 | copy(b[offset:], d.Payload) 98 | 99 | return nil 100 | } 101 | 102 | // ParseDialogue parses given byte sequence as an Dialogue. 103 | func ParseDialogue(b []byte) (*Dialogue, error) { 104 | d := &Dialogue{} 105 | if err := d.UnmarshalBinary(b); err != nil { 106 | return nil, err 107 | } 108 | return d, nil 109 | } 110 | 111 | // UnmarshalBinary sets the values retrieved from byte sequence in an Dialogue. 112 | func (d *Dialogue) UnmarshalBinary(b []byte) error { 113 | l := len(b) 114 | if l < 5 { 115 | return io.ErrUnexpectedEOF 116 | } 117 | 118 | d.Tag = Tag(b[0]) 119 | d.Length = b[1] 120 | d.ExternalTag = Tag(b[2]) 121 | d.ExternalLength = b[3] 122 | 123 | var err error 124 | var offset = 4 125 | d.ObjectIdentifier, err = ParseIE(b[offset:]) 126 | if err != nil { 127 | return err 128 | } 129 | offset += d.ObjectIdentifier.MarshalLen() 130 | 131 | d.SingleAsn1Type, err = ParseIE(b[offset:]) 132 | if err != nil { 133 | return err 134 | } 135 | offset += d.SingleAsn1Type.MarshalLen() 136 | 137 | d.DialoguePDU, err = ParseDialoguePDU(d.SingleAsn1Type.Value) 138 | if err != nil { 139 | return err 140 | } 141 | 142 | d.Payload = b[offset:] 143 | 144 | return nil 145 | } 146 | 147 | // SetValsFrom sets the values from IE parsed by ParseBER. 148 | func (d *Dialogue) SetValsFrom(berParsed *IE) error { 149 | d.Tag = berParsed.Tag 150 | d.Length = berParsed.Length 151 | for _, ie := range berParsed.IE { 152 | var dpdu *IE 153 | if ie.Tag == 0x28 { 154 | d.ExternalTag = ie.Tag 155 | d.ExternalLength = ie.Length 156 | for _, iex := range ie.IE { 157 | switch iex.Tag { 158 | case 0x06: 159 | d.ObjectIdentifier = iex 160 | case 0xa0: 161 | d.SingleAsn1Type = iex 162 | dpdu = iex.IE[0] 163 | } 164 | } 165 | } 166 | 167 | switch dpdu.Tag.Code() { 168 | case AARQ, AARE, ABRT: 169 | d.DialoguePDU = &DialoguePDU{ 170 | Type: dpdu.Tag, 171 | Length: dpdu.Length, 172 | } 173 | } 174 | for _, iex := range dpdu.IE { 175 | switch iex.Tag { 176 | case 0x80: 177 | d.DialoguePDU.ProtocolVersion = iex 178 | case 0xa1: 179 | d.DialoguePDU.ApplicationContextName = iex 180 | case 0xa2: 181 | d.DialoguePDU.Result = iex 182 | case 0xa3: 183 | d.DialoguePDU.ResultSourceDiagnostic = iex 184 | } 185 | } 186 | } 187 | return nil 188 | } 189 | 190 | // MarshalLen returns the serial length of Dialogue. 191 | func (d *Dialogue) MarshalLen() int { 192 | l := 4 193 | if field := d.ObjectIdentifier; field != nil { 194 | l += field.MarshalLen() 195 | } 196 | if field := d.DialoguePDU; field != nil { 197 | l += field.MarshalLen() + 2 // 2 = singleAsn1Type IE Header 198 | } 199 | 200 | return l + len(d.Payload) 201 | } 202 | 203 | // SetLength sets the length in Length field. 204 | func (d *Dialogue) SetLength() { 205 | if d.ObjectIdentifier != nil { 206 | d.ObjectIdentifier.SetLength() 207 | } 208 | if d.DialoguePDU != nil { 209 | d.DialoguePDU.SetLength() 210 | } 211 | 212 | d.Length = uint8(d.MarshalLen() - 2) 213 | d.ExternalLength = uint8(d.MarshalLen() - 4) 214 | } 215 | 216 | // String returns the SCCP common header values in human readable format. 217 | func (d *Dialogue) String() string { 218 | return fmt.Sprintf("{Tag: %#x, Length: %d, ExternalTag: %x, ExternalLength: %d, ObjectIdentifier: %v, SingleAsn1Type: %v, DialoguePDU: %v, Payload: %x}", 219 | d.Tag, 220 | d.Length, 221 | d.ExternalTag, 222 | d.ExternalLength, 223 | d.ObjectIdentifier, 224 | d.SingleAsn1Type, 225 | d.DialoguePDU, 226 | d.Payload, 227 | ) 228 | } 229 | 230 | // Version returns Protocol Version in string. 231 | func (d *Dialogue) Version() string { 232 | if d.DialoguePDU == nil { 233 | return "" 234 | } 235 | 236 | return d.DialoguePDU.Version() 237 | } 238 | 239 | // Context returns the Context part of ApplicationContextName in string. 240 | func (d *Dialogue) Context() string { 241 | if d.DialoguePDU == nil { 242 | return "" 243 | } 244 | 245 | return d.DialoguePDU.Context() 246 | } 247 | 248 | // ContextVersion returns the Version part of ApplicationContextName in string. 249 | func (d *Dialogue) ContextVersion() string { 250 | if d.DialoguePDU == nil { 251 | return "" 252 | } 253 | 254 | return d.DialoguePDU.ContextVersion() 255 | } 256 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2024 go-tcap authors. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | package tcap 6 | 7 | import "fmt" 8 | 9 | // InvalidCodeError indicates that Code in TCAP message is invalid. 10 | type InvalidCodeError struct { 11 | Code int 12 | } 13 | 14 | // Error returns error message with violating content. 15 | func (e *InvalidCodeError) Error() string { 16 | return fmt.Sprintf("tcap: got invalid code: %d", e.Code) 17 | } 18 | -------------------------------------------------------------------------------- /examples/client/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2024 go-tcap authors. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | // Command client creates Begin/Invoke packet with given parameters, and send it to the specified address. 6 | // By default, it sends MAP cancelLocation. The parameters in the lower layers(SCTP/M3UA/SCCP) cannot be 7 | // specified from command-line arguments. Update this source code itself to update them. 8 | package main 9 | 10 | import ( 11 | "context" 12 | "encoding/hex" 13 | "flag" 14 | "log" 15 | 16 | "github.com/ishidawataru/sctp" 17 | "github.com/wmnsk/go-m3ua" 18 | m3params "github.com/wmnsk/go-m3ua/messages/params" 19 | "github.com/wmnsk/go-sccp" 20 | "github.com/wmnsk/go-sccp/params" 21 | "github.com/wmnsk/go-sccp/utils" 22 | "github.com/wmnsk/go-tcap" 23 | ) 24 | 25 | func main() { 26 | var ( 27 | addr = flag.String("addr", "127.0.0.2:2905", "Remote IP and Port to connect to.") 28 | otid = flag.Int("otid", 0x11111111, "Originating Transaction ID in uint32.") 29 | opcode = flag.Int("opcode", 3, "Operation Code in int.") 30 | payload = flag.String("payload", "040800010121436587f9", "Hex representation of the payload") 31 | ) 32 | flag.Parse() 33 | 34 | p, err := hex.DecodeString(*payload) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | tcapBytes, err := tcap.NewBeginInvokeWithDialogue( 40 | uint32(*otid), // OTID 41 | tcap.DialogueAsID, // DialogueType 42 | tcap.LocationCancellationContext, // ACN 43 | 3, // ACN Version 44 | 0, // Invoke Id 45 | *opcode, // OpCode 46 | p, // Payload 47 | ).MarshalBinary() 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | 52 | // create *Config to be used in M3UA connection 53 | m3config := m3ua.NewConfig( 54 | 0x11111111, // OriginatingPointCode 55 | 0x22222222, // DestinationPointCode 56 | m3params.ServiceIndSCCP, // ServiceIndicator 57 | 0, // NetworkIndicator 58 | 0, // MessagePriority 59 | 1, // SignalingLinkSelection 60 | ).EnableHeartbeat(0, 0) 61 | 62 | // setup SCTP peer on the specified IPs and Port. 63 | raddr, err := sctp.ResolveSCTPAddr("sctp", *addr) 64 | if err != nil { 65 | log.Fatalf("Failed to resolve SCTP address: %s", err) 66 | } 67 | 68 | // setup underlying SCTP/M3UA connection first 69 | ctx, cancel := context.WithCancel(context.Background()) 70 | defer cancel() 71 | m3conn, err := m3ua.Dial(ctx, "m3ua", nil, raddr, m3config) 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | cdPA, err := utils.StrToSwappedBytes("1234567890123456", "0") 77 | if err != nil { 78 | log.Fatal(err) 79 | } 80 | cgPA, err := utils.StrToSwappedBytes("9876543210", "0") 81 | if err != nil { 82 | log.Fatal(err) 83 | } 84 | 85 | // create UDT message with CdPA, CgPA and payload 86 | udt, err := sccp.NewUDT( 87 | 1, // Protocol Class 88 | true, // Message handling 89 | params.NewPartyAddress( // CalledPartyAddress: 1234567890123456 90 | 0x12, 0, 6, 0x00, // Indicator, SPC, SSN, TT 91 | 0x01, 0x01, 0x04, // NP, ES, NAI 92 | cdPA, // GlobalTitleInformation 93 | ), 94 | params.NewPartyAddress( // CallingPartyAddress: 9876543210 95 | 0x12, 0, 7, 0x01, // Indicator, SPC, SSN, TT 96 | 0x01, 0x02, 0x04, // NP, ES, NAI 97 | cgPA, // GlobalTitleInformation 98 | ), 99 | tcapBytes, 100 | ).MarshalBinary() 101 | if err != nil { 102 | log.Fatal(err) 103 | } 104 | 105 | // send once 106 | if _, err := m3conn.Write(udt); err != nil { 107 | log.Fatal(err) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wmnsk/go-tcap 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/ishidawataru/sctp v0.0.0-20210707070123-9a39160e9062 7 | github.com/pascaldekloe/goe v0.1.1 8 | github.com/wmnsk/go-m3ua v0.1.9 9 | github.com/wmnsk/go-sccp v0.0.2 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 2 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | github.com/ishidawataru/sctp v0.0.0-20210707070123-9a39160e9062 h1:G1+wBT0dwjIrBdLy0MIG0i+E4CQxEnedHXdauJEIH6g= 4 | github.com/ishidawataru/sctp v0.0.0-20210707070123-9a39160e9062/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= 5 | github.com/pascaldekloe/goe v0.1.1 h1:Ah6WQ56rZONR3RW3qWa2NCZ6JAVvSpUcoLBaOmYFt9Q= 6 | github.com/pascaldekloe/goe v0.1.1/go.mod h1:KSyfaxQOh0HZPjDP1FL/kFtbqYqrALJTaMafFUIccqU= 7 | github.com/wmnsk/go-m3ua v0.1.9 h1:3dhHT63HQ9CVFRwqKrHi54nIonHbRDnuYPIhLnUQ4Pg= 8 | github.com/wmnsk/go-m3ua v0.1.9/go.mod h1:OtE8HG+xgrjwmdVbXI+x4Xkm4HtdhDha+Ztbb/H8HSQ= 9 | github.com/wmnsk/go-sccp v0.0.2 h1:OC4yX+95H6HstxXdxHzHy7ByNWAHErUhf1E2DZ7t5Gs= 10 | github.com/wmnsk/go-sccp v0.0.2/go.mod h1:u3PWZ/bd3SqWQbeopgSvd4W3ILI9D4O/1GkYIJ3xXoU= 11 | -------------------------------------------------------------------------------- /ie.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2024 go-tcap authors. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | package tcap 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | ) 11 | 12 | // Tag is a Tag in TCAP IE 13 | type Tag uint8 14 | 15 | // Class definitions. 16 | const ( 17 | Universal int = iota 18 | ApplicationWide 19 | ContextSpecific 20 | Private 21 | ) 22 | 23 | // Type definitions. 24 | const ( 25 | Primitive int = iota 26 | Constructor 27 | ) 28 | 29 | // NewTag creates a new Tag. 30 | func NewTag(cls, form, code int) Tag { 31 | return Tag((cls << 6) | (form << 5) | code) 32 | } 33 | 34 | // NewUniversalPrimitiveTag creates a new NewUniversalPrimitiveTag. 35 | func NewUniversalPrimitiveTag(code int) Tag { 36 | return NewTag(Universal, Primitive, code) 37 | } 38 | 39 | // NewUniversalConstructorTag creates a new NewUniversalConstructorTag. 40 | func NewUniversalConstructorTag(code int) Tag { 41 | return NewTag(Universal, Constructor, code) 42 | } 43 | 44 | // NewApplicationWidePrimitiveTag creates a new NewApplicationWidePrimitiveTag. 45 | func NewApplicationWidePrimitiveTag(code int) Tag { 46 | return NewTag(ApplicationWide, Primitive, code) 47 | } 48 | 49 | // NewApplicationWideConstructorTag creates a new NewApplicationWideConstructorTag. 50 | func NewApplicationWideConstructorTag(code int) Tag { 51 | return NewTag(ApplicationWide, Constructor, code) 52 | } 53 | 54 | // NewContextSpecificPrimitiveTag creates a new NewContextSpecificPrimitiveTag. 55 | func NewContextSpecificPrimitiveTag(code int) Tag { 56 | return NewTag(ContextSpecific, Primitive, code) 57 | } 58 | 59 | // NewContextSpecificConstructorTag creates a new NewContextSpecificConstructorTag. 60 | func NewContextSpecificConstructorTag(code int) Tag { 61 | return NewTag(ContextSpecific, Constructor, code) 62 | } 63 | 64 | // NewPrivatePrimitiveTag creates a new NewPrivatePrimitiveTag. 65 | func NewPrivatePrimitiveTag(code int) Tag { 66 | return NewTag(Private, Primitive, code) 67 | } 68 | 69 | // NewPrivateConstructorTag creates a new NewPrivateConstructorTag. 70 | func NewPrivateConstructorTag(code int) Tag { 71 | return NewTag(Private, Constructor, code) 72 | } 73 | 74 | // Class returns the Class retieved from a Tag. 75 | func (t Tag) Class() int { 76 | return int(t) >> 6 & 0x3 77 | } 78 | 79 | // Form returns the Form retieved from a Tag. 80 | func (t Tag) Form() int { 81 | return int(t) >> 5 & 0x1 82 | } 83 | 84 | // Code returns the Code retieved from a Tag. 85 | func (t Tag) Code() int { 86 | return int(t) & 0x1f 87 | } 88 | 89 | // IE is a General Structure of TCAP Information Elements. 90 | type IE struct { 91 | Tag 92 | Length uint8 93 | Value []byte 94 | IE []*IE 95 | } 96 | 97 | // NewIE creates a new IE. 98 | func NewIE(tag Tag, value []byte) *IE { 99 | i := &IE{ 100 | Tag: tag, 101 | Value: value, 102 | } 103 | i.SetLength() 104 | 105 | return i 106 | } 107 | 108 | // MarshalBinary returns the byte sequence generated from a IE instance. 109 | func (i *IE) MarshalBinary() ([]byte, error) { 110 | b := make([]byte, i.MarshalLen()) 111 | if err := i.MarshalTo(b); err != nil { 112 | return nil, err 113 | } 114 | return b, nil 115 | } 116 | 117 | // MarshalTo puts the byte sequence in the byte array given as b. 118 | func (i *IE) MarshalTo(b []byte) error { 119 | if len(b) < 2 { 120 | return io.ErrUnexpectedEOF 121 | } 122 | 123 | b[0] = uint8(i.Tag) 124 | b[1] = i.Length 125 | copy(b[2:i.MarshalLen()], i.Value) 126 | return nil 127 | } 128 | 129 | // ParseMultiIEs parses multiple (unspecified number of) IEs to []*IE at a time. 130 | func ParseMultiIEs(b []byte) ([]*IE, error) { 131 | var ies []*IE 132 | for { 133 | if len(b) == 0 { 134 | break 135 | } 136 | 137 | i, err := ParseIE(b) 138 | if err != nil { 139 | return nil, err 140 | } 141 | ies = append(ies, i) 142 | b = b[i.MarshalLen():] 143 | continue 144 | } 145 | return ies, nil 146 | } 147 | 148 | // ParseIE parses given byte sequence as an IE. 149 | func ParseIE(b []byte) (*IE, error) { 150 | i := &IE{} 151 | if err := i.UnmarshalBinary(b); err != nil { 152 | return nil, err 153 | } 154 | return i, nil 155 | } 156 | 157 | // UnmarshalBinary sets the values retrieved from byte sequence in an IE. 158 | func (i *IE) UnmarshalBinary(b []byte) error { 159 | l := len(b) 160 | if l < 3 { 161 | return io.ErrUnexpectedEOF 162 | } 163 | 164 | i.Tag = Tag(b[0]) 165 | i.Length = b[1] 166 | if l < 2+int(i.Length) { 167 | return io.ErrUnexpectedEOF 168 | } 169 | i.Value = b[2 : 2+int(i.Length)] 170 | return nil 171 | } 172 | 173 | // ParseAsBer parses given byte sequence as multiple IEs. 174 | // 175 | // Deprecated: use ParseAsBER instead. 176 | func ParseAsBer(b []byte) ([]*IE, error) { 177 | return ParseAsBER(b) 178 | } 179 | 180 | // ParseAsBER parses given byte sequence as multiple IEs. 181 | func ParseAsBER(b []byte) ([]*IE, error) { 182 | var ies []*IE 183 | for { 184 | if len(b) == 0 { 185 | break 186 | } 187 | 188 | i, err := ParseIERecursive(b) 189 | if err != nil { 190 | return nil, err 191 | } 192 | ies = append(ies, i) 193 | 194 | if len(i.IE) == 0 { 195 | b = b[i.MarshalLen():] 196 | continue 197 | } 198 | 199 | if i.IE[0].MarshalLen() < i.MarshalLen()-2 { 200 | var l = 2 201 | for _, ie := range i.IE { 202 | l += ie.MarshalLen() 203 | } 204 | b = b[l:] 205 | continue 206 | } 207 | b = b[i.MarshalLen():] 208 | } 209 | return ies, nil 210 | } 211 | 212 | // ParseIERecursive parses given byte sequence as an IE. 213 | func ParseIERecursive(b []byte) (*IE, error) { 214 | i := &IE{} 215 | if err := i.ParseRecursive(b); err != nil { 216 | return nil, err 217 | } 218 | return i, nil 219 | } 220 | 221 | // ParseRecursive sets the values retrieved from byte sequence in an IE. 222 | func (i *IE) ParseRecursive(b []byte) error { 223 | l := len(b) 224 | if l < 2 { 225 | return io.ErrUnexpectedEOF 226 | } 227 | 228 | i.Tag = Tag(b[0]) 229 | i.Length = b[1] 230 | if int(i.Length)+2 > len(b) { 231 | return nil 232 | } 233 | i.Value = b[2 : 2+int(i.Length)] 234 | 235 | if i.Tag.Form() == 1 { 236 | x, err := ParseAsBER(i.Value) 237 | if err != nil { 238 | return nil 239 | } 240 | i.IE = append(i.IE, x...) 241 | } 242 | 243 | return nil 244 | } 245 | 246 | // MarshalLen returns the serial length of IE. 247 | func (i *IE) MarshalLen() int { 248 | return 2 + len(i.Value) 249 | } 250 | 251 | // SetLength sets the length in Length field. 252 | func (i *IE) SetLength() { 253 | i.Length = uint8(len(i.Value)) 254 | } 255 | 256 | // String returns IE in human readable string. 257 | func (i *IE) String() string { 258 | return fmt.Sprintf("{Tag: %#x, Length: %d, Value: %x, IE: %v}", 259 | i.Tag, 260 | i.Length, 261 | i.Value, 262 | i.IE, 263 | ) 264 | } 265 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2024 go-tcap authors. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | package tcap 6 | 7 | import ( 8 | "io" 9 | "log" 10 | "os" 11 | "sync" 12 | ) 13 | 14 | var ( 15 | logger = log.New(os.Stderr, "", log.LstdFlags) 16 | logMu sync.Mutex 17 | ) 18 | 19 | // SetLogger replaces the standard logger with arbitrary *log.Logger. 20 | // 21 | // This package prints just informational logs from goroutines working background 22 | // that might help developers test the program but can be ignored safely. More 23 | // important ones that needs any action by caller would be returned as errors. 24 | func SetLogger(l *log.Logger) { 25 | if l == nil { 26 | log.Println("Don't pass nil to SetLogger: use DisableLogging instead.") 27 | } 28 | 29 | setLogger(l) 30 | } 31 | 32 | // EnableLogging enables the logging from the package. 33 | // If l is nil, it uses default logger provided by the package. 34 | // Logging is enabled by default. 35 | // 36 | // See also: SetLogger. 37 | func EnableLogging(l *log.Logger) { 38 | logMu.Lock() 39 | defer logMu.Unlock() 40 | 41 | setLogger(l) 42 | } 43 | 44 | // DisableLogging disables the logging from the package. 45 | // Logging is enabled by default. 46 | func DisableLogging() { 47 | logMu.Lock() 48 | defer logMu.Unlock() 49 | 50 | logger.SetOutput(io.Discard) 51 | } 52 | 53 | func setLogger(l *log.Logger) { 54 | if l == nil { 55 | l = log.New(os.Stderr, "", log.LstdFlags) 56 | } 57 | 58 | logMu.Lock() 59 | defer logMu.Unlock() 60 | 61 | logger = l 62 | } 63 | 64 | func logf(format string, v ...interface{}) { 65 | logMu.Lock() 66 | defer logMu.Unlock() 67 | 68 | logger.Printf(format, v...) 69 | } 70 | -------------------------------------------------------------------------------- /tcap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2024 go-tcap authors. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | /* 6 | Package tcap provides simple and painless handling of TCAP(Transaction Capabilities Application Part) in SS7/SIGTRAN protocol stack. 7 | 8 | Though TCAP is ASN.1-based protocol, this implementation does not use any ASN.1 parser. 9 | That makes this implementation flexible enough to create arbitrary payload with any combinations, which is useful for testing. 10 | */ 11 | package tcap 12 | 13 | import ( 14 | "encoding/binary" 15 | "fmt" 16 | ) 17 | 18 | // TCAP represents a General Structure of TCAP Information Elements. 19 | type TCAP struct { 20 | Transaction *Transaction 21 | Dialogue *Dialogue 22 | Components *Components 23 | } 24 | 25 | // NewBeginInvoke creates a new TCAP of type Transaction=Begin, Component=Invoke. 26 | func NewBeginInvoke(otid uint32, invID, opCode int, payload []byte) *TCAP { 27 | t := &TCAP{ 28 | Transaction: NewBegin(otid, []byte{}), 29 | Components: NewComponents(NewInvoke(invID, -1, opCode, true, payload)), 30 | } 31 | t.SetLength() 32 | 33 | return t 34 | } 35 | 36 | // NewBeginInvokeWithDialogue creates a new TCAP of type Transaction=Begin, Component=Invoke with Dialogue Portion. 37 | func NewBeginInvokeWithDialogue(otid uint32, dlgType, ctx, ctxver uint8, invID, opCode int, payload []byte) *TCAP { 38 | t := NewBeginInvoke(otid, invID, opCode, payload) 39 | t.Dialogue = NewDialogue(dlgType, 1, NewAARQ(1, ctx, ctxver), []byte{}) 40 | t.SetLength() 41 | 42 | return t 43 | } 44 | 45 | // NewContinueInvoke creates a new TCAP of type Transaction=Continue, Component=Invoke. 46 | func NewContinueInvoke(otid, dtid uint32, invID, opCode int, payload []byte) *TCAP { 47 | t := &TCAP{ 48 | Transaction: NewContinue(otid, dtid, []byte{}), 49 | Components: NewComponents(NewInvoke(invID, -1, opCode, true, payload)), 50 | } 51 | t.SetLength() 52 | 53 | return t 54 | } 55 | 56 | // NewEndReturnResult creates a new TCAP of type Transaction=End, Component=ReturnResult. 57 | func NewEndReturnResult(dtid uint32, invID, opCode int, isLast bool, payload []byte) *TCAP { 58 | t := &TCAP{ 59 | Transaction: NewEnd(dtid, []byte{}), 60 | Components: NewComponents(NewReturnResult(invID, opCode, true, isLast, payload)), 61 | } 62 | t.SetLength() 63 | 64 | return t 65 | } 66 | 67 | // NewEndReturnResultWithDialogue creates a new TCAP of type Transaction=End, Component=ReturnResult with Dialogue Portion. 68 | func NewEndReturnResultWithDialogue(dtid uint32, dlgType, ctx, ctxver uint8, invID, opCode int, isLast bool, payload []byte) *TCAP { 69 | t := NewEndReturnResult(dtid, invID, opCode, isLast, payload) 70 | t.Dialogue = NewDialogue(dlgType, 1, NewAARE(1, ctx, ctxver, Accepted, DialogueServiceUser, Null), []byte{}) 71 | t.SetLength() 72 | 73 | return t 74 | } 75 | 76 | // MarshalBinary returns the byte sequence generated from a TCAP instance. 77 | func (t *TCAP) MarshalBinary() ([]byte, error) { 78 | b := make([]byte, t.MarshalLen()) 79 | if err := t.MarshalTo(b); err != nil { 80 | return nil, err 81 | } 82 | return b, nil 83 | } 84 | 85 | // MarshalTo puts the byte sequence in the byte array given as b. 86 | func (t *TCAP) MarshalTo(b []byte) error { 87 | var offset = 0 88 | if portion := t.Transaction; portion != nil { 89 | if err := portion.MarshalTo(b[offset : offset+portion.MarshalLen()]); err != nil { 90 | return err 91 | } 92 | offset += portion.MarshalLen() 93 | } 94 | 95 | if portion := t.Dialogue; portion != nil { 96 | if err := portion.MarshalTo(b[offset : offset+portion.MarshalLen()]); err != nil { 97 | return err 98 | } 99 | offset += portion.MarshalLen() 100 | } 101 | 102 | if portion := t.Components; portion != nil { 103 | if err := portion.MarshalTo(b[offset : offset+portion.MarshalLen()]); err != nil { 104 | return err 105 | } 106 | } 107 | 108 | return nil 109 | } 110 | 111 | // Parse parses given byte sequence as a TCAP. 112 | func Parse(b []byte) (*TCAP, error) { 113 | t := &TCAP{} 114 | if err := t.UnmarshalBinary(b); err != nil { 115 | return nil, err 116 | } 117 | 118 | return t, nil 119 | } 120 | 121 | // UnmarshalBinary sets the values retrieved from byte sequence in a TCAP. 122 | func (t *TCAP) UnmarshalBinary(b []byte) error { 123 | var err error 124 | var offset = 0 125 | 126 | t.Transaction, err = ParseTransaction(b[offset:]) 127 | if err != nil { 128 | return err 129 | } 130 | if len(t.Transaction.Payload) == 0 { 131 | return nil 132 | } 133 | 134 | switch t.Transaction.Payload[0] { 135 | case 0x6b: 136 | t.Dialogue, err = ParseDialogue(t.Transaction.Payload) 137 | if err != nil { 138 | return err 139 | } 140 | if len(t.Dialogue.Payload) == 0 { 141 | return nil 142 | } 143 | 144 | t.Components, err = ParseComponents(t.Dialogue.Payload) 145 | if err != nil { 146 | return err 147 | } 148 | case 0x6c: 149 | t.Components, err = ParseComponents(t.Transaction.Payload) 150 | if err != nil { 151 | return err 152 | } 153 | } 154 | 155 | return nil 156 | } 157 | 158 | // ParseBer parses given byte sequence as a TCAP. 159 | // 160 | // Deprecated: use ParseBER instead. 161 | func ParseBer(b []byte) ([]*TCAP, error) { 162 | return ParseBER(b) 163 | } 164 | 165 | // ParseBER parses given byte sequence as a TCAP. 166 | func ParseBER(b []byte) ([]*TCAP, error) { 167 | parsed, err := ParseAsBER(b) 168 | if err != nil { 169 | return nil, err 170 | } 171 | 172 | tcaps := make([]*TCAP, len(parsed)) 173 | for i, tx := range parsed { 174 | t := &TCAP{ 175 | Transaction: &Transaction{}, 176 | } 177 | 178 | if err := t.Transaction.SetValsFrom(tx); err != nil { 179 | return nil, err 180 | } 181 | 182 | for _, dx := range tx.IE { 183 | switch dx.Tag { 184 | case 0x6b: 185 | t.Dialogue = &Dialogue{} 186 | if err := t.Dialogue.SetValsFrom(dx); err != nil { 187 | return nil, err 188 | } 189 | case 0x6c: 190 | t.Components = &Components{} 191 | if err := t.Components.SetValsFrom(dx); err != nil { 192 | return nil, err 193 | } 194 | } 195 | } 196 | 197 | tcaps[i] = t 198 | } 199 | 200 | return tcaps, nil 201 | } 202 | 203 | // MarshalLen returns the serial length of TCAP. 204 | func (t *TCAP) MarshalLen() int { 205 | l := 0 206 | if portion := t.Components; portion != nil { 207 | l += portion.MarshalLen() 208 | } 209 | if portion := t.Dialogue; portion != nil { 210 | l += portion.MarshalLen() 211 | } 212 | if portion := t.Transaction; portion != nil { 213 | l += portion.MarshalLen() 214 | } 215 | return l 216 | } 217 | 218 | // SetLength sets the length in Length field. 219 | func (t *TCAP) SetLength() { 220 | if portion := t.Components; portion != nil { 221 | portion.SetLength() 222 | } 223 | if portion := t.Dialogue; portion != nil { 224 | portion.SetLength() 225 | } 226 | if portion := t.Transaction; portion != nil { 227 | portion.SetLength() 228 | if c := t.Components; c != nil { 229 | portion.Length += uint8(c.MarshalLen()) 230 | } 231 | if d := t.Dialogue; d != nil { 232 | portion.Length += uint8(d.MarshalLen()) 233 | } 234 | } 235 | } 236 | 237 | // OTID returns the TCAP Originating Transaction ID in Transaction Portion in uint32. 238 | func (t *TCAP) OTID() uint32 { 239 | if ts := t.Transaction; ts != nil { 240 | if otid := ts.OrigTransactionID; otid != nil { 241 | return binary.BigEndian.Uint32(otid.Value) 242 | } 243 | } 244 | 245 | return 0 246 | } 247 | 248 | // DTID returns the TCAP Originating Transaction ID in Transaction Portion in uint32. 249 | func (t *TCAP) DTID() uint32 { 250 | if ts := t.Transaction; ts != nil { 251 | if dtid := ts.DestTransactionID; dtid != nil { 252 | return binary.BigEndian.Uint32(dtid.Value) 253 | } 254 | } 255 | 256 | return 0 257 | } 258 | 259 | // AppContextName returns the ACN in string. 260 | func (t *TCAP) AppContextName() string { 261 | if d := t.Dialogue; d != nil { 262 | return d.Context() 263 | } 264 | 265 | return "" 266 | } 267 | 268 | // AppContextNameWithVersion returns the ACN with ACN Version in string. 269 | // 270 | // TODO: Looking for a better way to return the value in the same format... 271 | func (t *TCAP) AppContextNameWithVersion() string { 272 | if d := t.Dialogue; d != nil { 273 | return d.Context() + "-v" + d.ContextVersion() 274 | } 275 | 276 | return "" 277 | } 278 | 279 | // AppContextNameOid returns the ACN with ACN Version in OID formatted string. 280 | // 281 | // TODO: Looking for a better way to return the value in the same format... 282 | func (t *TCAP) AppContextNameOid() string { 283 | if r := t.Dialogue; r != nil { 284 | if rp := r.DialoguePDU; rp != nil { 285 | var oid = "0." 286 | for i, x := range rp.ApplicationContextName.Value[2:] { 287 | oid += fmt.Sprint(x) 288 | if i <= 6 { 289 | break 290 | } 291 | oid += "." 292 | } 293 | return oid 294 | } 295 | } 296 | 297 | return "" 298 | } 299 | 300 | // ComponentType returns the ComponentType in Component Portion in the list of string. 301 | // 302 | // The returned value is of type []string, as it may have multiple Components. 303 | func (t *TCAP) ComponentType() []string { 304 | if c := t.Components; c != nil { 305 | var iids []string 306 | for _, cm := range c.Component { 307 | iids = append(iids, cm.ComponentTypeString()) 308 | } 309 | return iids 310 | } 311 | 312 | return nil 313 | } 314 | 315 | // InvokeID returns the InvokeID in Component Portion in the list of string. 316 | // 317 | // The returned value is of type []string, as it may have multiple Components. 318 | func (t *TCAP) InvokeID() []uint8 { 319 | if c := t.Components; c != nil { 320 | var iids []uint8 321 | for _, cm := range c.Component { 322 | iids = append(iids, cm.InvID()) 323 | } 324 | 325 | return iids 326 | } 327 | 328 | return nil 329 | } 330 | 331 | // OpCode returns the OpCode in Component Portion in the list of string. 332 | // 333 | // The returned value is of type []string, as it may have multiple Components. 334 | func (t *TCAP) OpCode() []uint8 { 335 | if c := t.Components; c != nil { 336 | var ops []uint8 337 | for _, cm := range c.Component { 338 | ops = append(ops, cm.OpCode()) 339 | } 340 | 341 | return ops 342 | } 343 | 344 | return nil 345 | } 346 | 347 | // LayerPayload returns the upper layer as byte slice. 348 | // 349 | // The returned value is of type [][]byte, as it may have multiple Components. 350 | func (t *TCAP) LayerPayload() [][]byte { 351 | if c := t.Components; c != nil { 352 | var ret [][]byte 353 | for _, cm := range c.Component { 354 | ret = append(ret, cm.Parameter.Value) 355 | } 356 | 357 | return ret 358 | } 359 | 360 | return nil 361 | } 362 | 363 | // String returns TCAP in human readable string. 364 | func (t *TCAP) String() string { 365 | return fmt.Sprintf("{Transaction: %v, Dialogue: %v, Components: %v}", 366 | t.Transaction, 367 | t.Dialogue, 368 | t.Components, 369 | ) 370 | } 371 | -------------------------------------------------------------------------------- /transaction.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2024 go-tcap authors. All rights reserved. 2 | // Use of this source code is governed by a MIT-style license that can be 3 | // found in the LICENSE file. 4 | 5 | package tcap 6 | 7 | import ( 8 | "encoding/binary" 9 | "fmt" 10 | ) 11 | 12 | // Message Type definitions. 13 | const ( 14 | Unidirectional int = iota + 1 15 | Begin 16 | _ 17 | End 18 | Continue 19 | _ 20 | Abort 21 | ) 22 | 23 | // Abort Cause definitions. 24 | const ( 25 | UnrecognizedMessageType uint8 = iota 26 | UnrecognizedTransactionID 27 | BadlyFormattedTransactionPortion 28 | IncorrectTransactionPortion 29 | ResourceLimitation 30 | ) 31 | 32 | // Transaction represents a Transaction Portion of TCAP. 33 | type Transaction struct { 34 | Type Tag 35 | Length uint8 36 | OrigTransactionID *IE 37 | DestTransactionID *IE 38 | PAbortCause *IE 39 | Payload []byte 40 | } 41 | 42 | // NewTransaction returns a new Transaction Portion. 43 | func NewTransaction(mtype int, otid, dtid uint32, cause uint8, payload []byte) *Transaction { 44 | t := &Transaction{ 45 | Type: NewApplicationWideConstructorTag(mtype), 46 | OrigTransactionID: &IE{ 47 | Tag: NewApplicationWidePrimitiveTag(8), 48 | Value: make([]byte, 4), 49 | }, 50 | DestTransactionID: &IE{ 51 | Tag: NewApplicationWidePrimitiveTag(9), 52 | Value: make([]byte, 4), 53 | }, 54 | PAbortCause: &IE{ 55 | Tag: NewApplicationWidePrimitiveTag(10), 56 | Value: []byte{cause}, 57 | }, 58 | Payload: payload, 59 | } 60 | binary.BigEndian.PutUint32(t.OrigTransactionID.Value, otid) 61 | binary.BigEndian.PutUint32(t.DestTransactionID.Value, dtid) 62 | t.SetLength() 63 | 64 | return t 65 | } 66 | 67 | // NewUnidirectional returns Unidirectional type of Transacion Portion. 68 | func NewUnidirectional(payload []byte) *Transaction { 69 | t := NewTransaction( 70 | Unidirectional, // Type: Unidirectional 71 | 0, // otid 72 | 0, // dtid 73 | 0, // cause 74 | payload, // payload 75 | ) 76 | t.OrigTransactionID = nil 77 | t.DestTransactionID = nil 78 | t.PAbortCause = nil 79 | return t 80 | } 81 | 82 | // NewBegin returns Begin type of Transacion Portion. 83 | func NewBegin(otid uint32, payload []byte) *Transaction { 84 | t := &Transaction{ 85 | Type: NewApplicationWideConstructorTag(Begin), 86 | OrigTransactionID: &IE{ 87 | Tag: NewApplicationWidePrimitiveTag(8), 88 | Value: make([]byte, 4), 89 | }, 90 | Payload: payload, 91 | } 92 | binary.BigEndian.PutUint32(t.OrigTransactionID.Value, otid) 93 | t.SetLength() 94 | 95 | return t 96 | } 97 | 98 | // NewEnd returns End type of Transacion Portion. 99 | func NewEnd(otid uint32, payload []byte) *Transaction { 100 | t := &Transaction{ 101 | Type: NewApplicationWideConstructorTag(End), 102 | DestTransactionID: &IE{ 103 | Tag: NewApplicationWidePrimitiveTag(9), 104 | Value: make([]byte, 4), 105 | }, 106 | Payload: payload, 107 | } 108 | binary.BigEndian.PutUint32(t.DestTransactionID.Value, otid) 109 | t.SetLength() 110 | 111 | return t 112 | } 113 | 114 | // NewContinue returns Continue type of Transacion Portion. 115 | func NewContinue(otid, dtid uint32, payload []byte) *Transaction { 116 | t := NewTransaction( 117 | Continue, // Type: Continue 118 | otid, // otid 119 | dtid, // dtid 120 | 0, // cause 121 | payload, // payload 122 | ) 123 | t.PAbortCause = nil 124 | return t 125 | } 126 | 127 | // NewAbort returns Abort type of Transacion Portion. 128 | func NewAbort(dtid uint32, cause uint8, payload []byte) *Transaction { 129 | t := NewTransaction( 130 | Abort, // Type: Abort 131 | 0, // otid 132 | dtid, // dtid 133 | cause, // cause 134 | payload, // payload 135 | ) 136 | t.OrigTransactionID = nil 137 | return t 138 | } 139 | 140 | // MarshalBinary returns the byte sequence generated from a Transaction instance. 141 | func (t *Transaction) MarshalBinary() ([]byte, error) { 142 | b := make([]byte, t.MarshalLen()) 143 | if err := t.MarshalTo(b); err != nil { 144 | return nil, err 145 | } 146 | return b, nil 147 | } 148 | 149 | // MarshalTo puts the byte sequence in the byte array given as b. 150 | func (t *Transaction) MarshalTo(b []byte) error { 151 | b[0] = uint8(t.Type) 152 | b[1] = t.Length 153 | 154 | var offset = 2 155 | switch t.Type.Code() { 156 | case Unidirectional: 157 | break 158 | case Begin: 159 | if field := t.OrigTransactionID; field != nil { 160 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 161 | return err 162 | } 163 | offset += field.MarshalLen() 164 | } 165 | case End: 166 | if field := t.DestTransactionID; field != nil { 167 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 168 | return err 169 | } 170 | offset += field.MarshalLen() 171 | } 172 | case Continue: 173 | if field := t.OrigTransactionID; field != nil { 174 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 175 | return err 176 | } 177 | offset += field.MarshalLen() 178 | } 179 | 180 | if field := t.DestTransactionID; field != nil { 181 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 182 | return err 183 | } 184 | offset += field.MarshalLen() 185 | } 186 | case Abort: 187 | if field := t.DestTransactionID; field != nil { 188 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 189 | return err 190 | } 191 | offset += field.MarshalLen() 192 | } 193 | 194 | if field := t.PAbortCause; field != nil { 195 | if err := field.MarshalTo(b[offset : offset+field.MarshalLen()]); err != nil { 196 | return err 197 | } 198 | offset += field.MarshalLen() 199 | } 200 | } 201 | copy(b[offset:t.MarshalLen()], t.Payload) 202 | return nil 203 | } 204 | 205 | // ParseTransaction parses given byte sequence as an Transaction. 206 | func ParseTransaction(b []byte) (*Transaction, error) { 207 | t := &Transaction{} 208 | if err := t.UnmarshalBinary(b); err != nil { 209 | return nil, err 210 | } 211 | return t, nil 212 | } 213 | 214 | // UnmarshalBinary sets the values retrieved from byte sequence in an Transaction. 215 | func (t *Transaction) UnmarshalBinary(b []byte) error { 216 | t.Type = Tag(b[0]) 217 | t.Length = b[1] 218 | 219 | var err error 220 | var offset = 2 221 | switch t.Type.Code() { 222 | case Unidirectional: 223 | break 224 | case Begin: 225 | t.OrigTransactionID, err = ParseIE(b[offset : offset+6]) 226 | if err != nil { 227 | return err 228 | } 229 | offset += t.OrigTransactionID.MarshalLen() 230 | case End: 231 | t.DestTransactionID, err = ParseIE(b[offset : offset+6]) 232 | if err != nil { 233 | return err 234 | } 235 | offset += t.DestTransactionID.MarshalLen() 236 | case Continue: 237 | t.OrigTransactionID, err = ParseIE(b[offset : offset+6]) 238 | if err != nil { 239 | return err 240 | } 241 | offset += t.OrigTransactionID.MarshalLen() 242 | t.DestTransactionID, err = ParseIE(b[offset : offset+6]) 243 | if err != nil { 244 | return err 245 | } 246 | offset += t.DestTransactionID.MarshalLen() 247 | case Abort: 248 | t.DestTransactionID, err = ParseIE(b[offset : offset+6]) 249 | if err != nil { 250 | return err 251 | } 252 | offset += t.DestTransactionID.MarshalLen() 253 | t.PAbortCause, err = ParseIE(b[offset : offset+3]) 254 | if err != nil { 255 | return err 256 | } 257 | offset += t.PAbortCause.MarshalLen() 258 | } 259 | t.Payload = b[offset:] 260 | return nil 261 | } 262 | 263 | // SetValsFrom sets the values from IE parsed by ParseBER. 264 | func (t *Transaction) SetValsFrom(berParsed *IE) error { 265 | t.Type = berParsed.Tag 266 | t.Length = berParsed.Length 267 | for _, ie := range berParsed.IE { 268 | switch ie.Tag { 269 | case 0x48: 270 | t.OrigTransactionID = ie 271 | case 0x49: 272 | t.DestTransactionID = ie 273 | case 0x4a: 274 | t.PAbortCause = ie 275 | } 276 | } 277 | return nil 278 | } 279 | 280 | // MarshalLen returns the serial length of Transaction. 281 | func (t *Transaction) MarshalLen() int { 282 | l := 2 283 | switch t.Type.Code() { 284 | case Unidirectional: 285 | break 286 | case Begin: 287 | if field := t.OrigTransactionID; field != nil { 288 | l += field.MarshalLen() 289 | } 290 | case End: 291 | if field := t.DestTransactionID; field != nil { 292 | l += field.MarshalLen() 293 | } 294 | case Continue: 295 | if field := t.OrigTransactionID; field != nil { 296 | l += field.MarshalLen() 297 | } 298 | if field := t.DestTransactionID; field != nil { 299 | l += field.MarshalLen() 300 | } 301 | case Abort: 302 | if field := t.DestTransactionID; field != nil { 303 | l += field.MarshalLen() 304 | } 305 | if field := t.PAbortCause; field != nil { 306 | l += field.MarshalLen() 307 | } 308 | } 309 | return l + len(t.Payload) 310 | } 311 | 312 | // SetLength sets the length in Length field. 313 | func (t *Transaction) SetLength() { 314 | if field := t.OrigTransactionID; field != nil { 315 | field.SetLength() 316 | } 317 | if field := t.DestTransactionID; field != nil { 318 | field.SetLength() 319 | } 320 | if field := t.PAbortCause; field != nil { 321 | field.SetLength() 322 | } 323 | t.Length = uint8(t.MarshalLen() - 2) 324 | } 325 | 326 | // MessageTypeString returns the name of Message Type in string. 327 | func (t *Transaction) MessageTypeString() string { 328 | switch t.Type.Code() { 329 | case Unidirectional: 330 | return "Unidirectional" 331 | case Begin: 332 | return "Begin" 333 | case End: 334 | return "End" 335 | case Continue: 336 | return "Continue" 337 | case Abort: 338 | return "Abort" 339 | } 340 | return "" 341 | } 342 | 343 | // OTID returns the OrigTransactionID in string. 344 | func (t *Transaction) OTID() string { 345 | switch t.Type.Code() { 346 | case Begin, Continue: 347 | if field := t.OrigTransactionID; field != nil { 348 | return fmt.Sprintf("%04x", field.Value) 349 | } 350 | } 351 | return "" 352 | } 353 | 354 | // DTID returns the DestTransactionID in string. 355 | func (t *Transaction) DTID() string { 356 | switch t.Type.Code() { 357 | case End, Continue, Abort: 358 | if field := t.DestTransactionID; field != nil { 359 | return fmt.Sprintf("%04x", field.Value) 360 | } 361 | } 362 | return "" 363 | } 364 | 365 | // AbortCause returns the P-Abort Cause in string. 366 | func (t *Transaction) AbortCause() string { 367 | cause := t.PAbortCause 368 | if cause == nil { 369 | return "" 370 | } 371 | 372 | if t.Type.Code() == Abort { 373 | switch t.PAbortCause.Value[0] { 374 | case UnrecognizedMessageType: 375 | return "UnrecognizedMessageType" 376 | case UnrecognizedTransactionID: 377 | return "UnrecognizedTransactionID" 378 | case BadlyFormattedTransactionPortion: 379 | return "BadlyFormattedTransactionPortion" 380 | case IncorrectTransactionPortion: 381 | return "IncorrectTransactionPortion" 382 | case ResourceLimitation: 383 | return "ResourceLimitation" 384 | } 385 | } 386 | return "" 387 | } 388 | 389 | // String returns Transaction in human readable string. 390 | func (t *Transaction) String() string { 391 | return fmt.Sprintf("{Type: %#x, Length: %d, OrigTransactionID: %v, DestTransactionID: %v, PAbortCause: %v, Payload: %x}", 392 | t.Type, 393 | t.Length, 394 | t.OrigTransactionID, 395 | t.DestTransactionID, 396 | t.PAbortCause, 397 | t.Payload, 398 | ) 399 | } 400 | --------------------------------------------------------------------------------