├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── examples ├── client │ └── main.go ├── server │ └── main.go └── socket │ ├── connect │ └── main.go │ └── listen │ └── main.go ├── go.mod ├── go.sum ├── internal └── cmd │ └── generate │ ├── options │ └── main.go │ ├── static_consts │ └── main.go │ ├── stats │ └── main.go │ └── wrap │ └── main.go ├── pkg ├── addr.go ├── addr_test.go ├── callbacks.c ├── callbacks.go ├── callbacks.h ├── client.go ├── client_test.go ├── connection.go ├── connection_option.go ├── error.go ├── error_test.go ├── log.c ├── log.go ├── log.h ├── log_test.go ├── reject_reason.go ├── server.go ├── server_test.go ├── socket.go ├── socket_option.go ├── srt.go ├── srt_test.go ├── static_consts.c ├── static_consts.h ├── stats.go ├── stats_test.go ├── stream_id.go ├── stream_id_test.go ├── wrap.c ├── wrap.go └── wrap.h └── testdata └── linux ├── .gitignore └── Dockerfile /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | 11 | test: 12 | strategy: 13 | matrix: 14 | # Use macos-latest once latest version is compatible with libsrt 15 | os: [ubuntu-latest, macos-13] 16 | 17 | env: 18 | LIBSRT_VERSION: v1.5.3 19 | 20 | runs-on: ${{ matrix.os }} 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - if: ${{ runner.os == 'Windows' }} 25 | name: Set windows libsrt path 26 | run: | 27 | echo "LIBSRT_PATH=$(cygpath -u $(cd ~ && pwd))/libsrt" >> $env:GITHUB_ENV 28 | 29 | - if: ${{ runner.os != 'Windows' }} 30 | name: Set non-windows libsrt path 31 | run: | 32 | echo "LIBSRT_PATH=$(echo ~)/libsrt" >> $GITHUB_ENV 33 | 34 | - name: Cache libsrt 35 | id: cache-libsrt 36 | uses: actions/cache@v4 37 | with: 38 | path: ${{ env.LIBSRT_PATH }} 39 | key: libsrt-${{ env.LIBSRT_VERSION }}-${{ runner.os }} 40 | 41 | - if: ${{ steps.cache-libsrt.outputs.cache-hit != 'true' }} 42 | name: Install libsrt 43 | run: | 44 | make install-srt srcPath=${{ env.LIBSRT_PATH }}/src version=${{ env.LIBSRT_VERSION }} 45 | 46 | - name: Set environment variables 47 | run: | 48 | echo "LD_LIBRARY_PATH=${{ env.LIBSRT_PATH }}/lib" >> $GITHUB_ENV 49 | echo "CGO_LDFLAGS=-L${{ env.LIBSRT_PATH }}/lib/" >> $GITHUB_ENV 50 | echo "CGO_CFLAGS=-I${{ env.LIBSRT_PATH }}/include/" >> $GITHUB_ENV 51 | echo "PKG_CONFIG_PATH=${{ env.LIBSRT_PATH }}/lib/pkgconfig" >> $GITHUB_ENV 52 | 53 | - if: ${{ runner.os == 'macOS' }} 54 | name: Set darwin environment variables 55 | run: | 56 | echo "DYLD_FALLBACK_LIBRARY_PATH=${{ env.LIBSRT_PATH }}/lib" >> $GITHUB_ENV 57 | 58 | - name: Set up Go 59 | uses: actions/setup-go@v5 60 | with: 61 | go-version: '1.20' 62 | 63 | - name: Install dependencies 64 | run: go mod download 65 | 66 | - name: Run tests 67 | run: go test -race -covermode atomic -coverprofile=covprofile ./... 68 | 69 | - if: github.event_name != 'pull_request' 70 | name: Send coverage 71 | env: 72 | COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }} 73 | run: | 74 | go install github.com/mattn/goveralls@latest 75 | goveralls -coverprofile=covprofile -service=github 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | cover.out 3 | tmp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Quentin Renard 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | version = "v1.5.3" 2 | srcPath = "tmp/$(version)/src" 3 | currentDir := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) 4 | 5 | install-srt: 6 | rm -rf $(srcPath) 7 | mkdir -p $(srcPath) 8 | # cd $(srcPath) is necessary for windows build since otherwise git doesn't clone in the proper dir 9 | cd $(srcPath) && git clone https://github.com/Haivision/srt . 10 | cd $(srcPath) && git checkout $(version) 11 | cd $(srcPath) && ./configure --prefix=.. $(configure) 12 | cd $(srcPath) && make 13 | cd $(srcPath) && make install 14 | 15 | generate: 16 | go run internal/cmd/generate/options/main.go 17 | go run internal/cmd/generate/static_consts/main.go 18 | go run internal/cmd/generate/stats/main.go 19 | go run internal/cmd/generate/wrap/main.go 20 | 21 | test-coverage: 22 | go test -coverprofile cover.out github.com/asticode/go-astisrt/pkg 23 | go tool cover -html=cover.out 24 | 25 | test-linux-build: 26 | cd testdata/linux && docker build -t astisrt-test-linux . 27 | 28 | test-linux: 29 | docker run -v ${currentDir}/testdata/linux/gocache:/opt/astisrt/tmp/linux/gocache -v ${currentDir}/testdata/linux/gopath:/opt/astisrt/tmp/linux/gopath -v ${currentDir}:/opt/astisrt/tmp/linux/gopath/src/github.com/asticode/go-astisrt astisrt-test-linux -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoReportCard](http://goreportcard.com/badge/github.com/asticode/go-astisrt)](http://goreportcard.com/report/github.com/asticode/go-astisrt) 2 | [![GoDoc](https://godoc.org/github.com/asticode/go-astisrt?status.svg)](https://godoc.org/github.com/asticode/go-astisrt) 3 | [![Test](https://github.com/asticode/go-astisrt/actions/workflows/test.yml/badge.svg)](https://github.com/asticode/go-astisrt/actions/workflows/test.yml) 4 | [![Coveralls](https://coveralls.io/repos/github/asticode/go-astisrt/badge.svg?branch=master)](https://coveralls.io/github/asticode/go-astisrt) 5 | 6 | # SRT server, client and socket in GO 7 | 8 | First off, a big thanks to the [official GO bindings](https://github.com/Haivision/srtgo) that was an amazing source of inspiration for this project. 9 | 10 | However a few things bothered me in it therefore I decided to write my own bindings with a few goals in mind: 11 | 12 | - [x] split the API between both high level entities, similar to GO's `net/http` entities, with very guided methods (`ListenAndServe`, `Shutdown`, `Dial`, `Read`, `Write` etc.) and low level entities, closer to C, with I-should-know-what-I-am-doing-before-using-them methods (`Socket`, etc.) 13 | - [x] provide named and typed option setters and getters 14 | - [x] make sure all errors are handled properly since they are thread-stored and ban the use of runtime.LockOSThread() 15 | - [x] make sure there's a context specific to each connection in high level methods 16 | - [x] make sure pointers are the same between the `ListenCallback` and `Accept()`, and between the `ConnectCallback` and `Connect()` 17 | - [x] only use blocking mode in high level entities 18 | 19 | `astisrt` has been tested on `v1.5.3`. 20 | 21 | ## Examples 22 | 23 | Examples are located in the [examples](examples) directory 24 | 25 | WARNING: the code below doesn't handle errors for readibility purposes. However you SHOULD! 26 | 27 | ### Server 28 | 29 | [Go to full example](examples/server/main.go) 30 | 31 | ```go 32 | // Capture SIGTERM 33 | doneSignal := make(chan os.Signal, 1) 34 | signal.Notify(doneSignal, os.Interrupt) 35 | 36 | // Create server 37 | s, _ := astisrt.NewServer(astisrt.ServerOptions{ 38 | // Provide options that will be passed to accepted connections 39 | ConnectionOptions: []astisrt.ConnectionOption{ 40 | astisrt.WithLatency(300), 41 | astisrt.WithTranstype(astisrt.TranstypeLive), 42 | }, 43 | 44 | // Specify how an incoming connection should be handled before being accepted 45 | // When false is returned, the connection is rejected. 46 | OnBeforeAccept: func(c *astisrt.Connection, version int, streamID string) bool { 47 | // Check stream id 48 | if streamID != "test" { 49 | // Set reject reason 50 | c.SetPredefinedRejectReason(http.StatusNotFound) 51 | return false 52 | } 53 | 54 | // Update passphrase 55 | c.Options().SetPassphrase("passphrase") 56 | 57 | // Add stream id to context 58 | *c = *c.WithContext(context.WithValue(c.Context(), ctxKeyStreamID, streamID)) 59 | return true 60 | }, 61 | 62 | // Similar to http.Handler behavior, specify how a connection 63 | // will be handled once accepted 64 | Handler: astisrt.ServerHandlerFunc(func(c *astisrt.Connection) { 65 | // Get stream id from context 66 | if v := c.Context().Value(ctxKeyStreamID); v != nil { 67 | log.Printf("main: handling connection with stream id %s\n", v.(string)) 68 | } 69 | 70 | // Loop 71 | for { 72 | // Read 73 | b := make([]byte, 1500) 74 | n, _ := c.Read(b) 75 | 76 | // Log 77 | log.Printf("main: read `%s`\n", b[:n]) 78 | 79 | // Get stats 80 | s, _ := c.Stats(false, false) 81 | 82 | // Log 83 | log.Printf("main: %d total bytes received\n", s.ByteRecvTotal()) 84 | } 85 | }), 86 | 87 | // Addr the server should be listening to 88 | Host: "127.0.0.1", 89 | Port: 4000, 90 | }) 91 | defer s.Close() 92 | 93 | // Listen and serve in a goroutine 94 | doneListenAndServe := make(chan error) 95 | go func() { doneListenAndServe <- s.ListenAndServe(1) }() 96 | 97 | // Wait for SIGTERM 98 | <-doneSignal 99 | 100 | // Create shutdown context with a timeout to make sure it's cancelled if it takes too much time 101 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 102 | defer cancel() 103 | 104 | // Shutdown 105 | s.Shutdown(ctx) 106 | 107 | // Wait for listen and serve to be done 108 | <-doneListenAndServe 109 | ``` 110 | 111 | ### Client 112 | 113 | [Go to full example](examples/client/main.go) 114 | 115 | ```go 116 | // Capture SIGTERM 117 | doneSignal := make(chan os.Signal, 1) 118 | signal.Notify(doneSignal, os.Interrupt) 119 | 120 | // Dial 121 | doneWrite := make(chan err) 122 | c, _ := astisrt.Dial(astisrt.DialOptions{ 123 | // Provide options to the connection 124 | ConnectionOptions: []astisrt.ConnectionOption{ 125 | astisrt.WithLatency(300), 126 | astisrt.WithPassphrase("passphrase"), 127 | astisrt.WithStreamid("test"), 128 | }, 129 | 130 | // Callback when the connection is disconnected 131 | OnDisconnect: func(c *astisrt.Connection, err error) { doneWrite <- err }, 132 | 133 | // Addr that should be dialed 134 | Host: "127.0.0.1", 135 | Port: 4000, 136 | }) 137 | defer c.Close() 138 | 139 | // Write in a goroutine 140 | go func() { 141 | defer func() { close(doneWrite) }() 142 | 143 | // Loop 144 | r := bufio.NewReader(os.Stdin) 145 | for { 146 | // Read from stdin 147 | t, _ := r.ReadString('\n') 148 | 149 | // Write to the server 150 | c.Write([]byte(t)) 151 | } 152 | }() 153 | 154 | // Wait for either SIGTERM or write end 155 | select { 156 | case <-doneSignal: 157 | c.Close() 158 | case err := <-doneWrite: 159 | log.Println(err) 160 | } 161 | 162 | // Make sure write is done 163 | select { 164 | case <-doneWrite: 165 | default: 166 | } 167 | ``` 168 | 169 | ### Socket 170 | 171 | #### Listen 172 | 173 | [Go to full example](examples/socket/listen/main.go) 174 | 175 | ```go 176 | // Create socket 177 | s, _ := astisrt.NewSocket() 178 | defer s.Close() 179 | 180 | // Set listen callback 181 | s.SetListenCallback(func(s *astisrt.Socket, version int, addr *net.UDPAddr, streamID string) bool { 182 | // Check stream id 183 | if streamID != "test" { 184 | // Set reject reason 185 | s.SetRejectReason(1404) 186 | return false 187 | } 188 | 189 | // Update passphrase 190 | s.Options().SetPassphrase("passphrase") 191 | return true 192 | }) 193 | 194 | // Bind 195 | s.Bind("127.0.0.1", 4000) 196 | 197 | // Listen 198 | s.Listen(1) 199 | 200 | // Accept 201 | as, _, _ := s.Accept() 202 | 203 | // Receive message 204 | b := make([]byte, 1500) 205 | n, _ := as.ReceiveMessage(b) 206 | 207 | // Log 208 | log.Printf("main: received `%s`\n", b[:n]) 209 | ``` 210 | 211 | #### Connect 212 | 213 | [Go to full example](examples/socket/listen/main.go) 214 | 215 | ```go 216 | // Create socket 217 | s, _ := astisrt.NewSocket() 218 | defer s.Close() 219 | 220 | // Set connect callback 221 | doneConnect := make(chan error) 222 | s.SetConnectCallback(func(s *astisrt.Socket, addr *net.UDPAddr, token int, err error) { 223 | doneConnect <- err 224 | }) 225 | 226 | // Set passphrase 227 | s.Options().SetPassphrase("passphrase") 228 | 229 | // Set stream id 230 | s.Options().SetStreamid("test") 231 | 232 | // Connect 233 | s.Connect("127.0.0.1", 4000) 234 | 235 | // Send message 236 | s.SendMessage([]byte("this is a test message")) 237 | 238 | // Give time to the message to be received 239 | time.Sleep(500 * time.Millisecond) 240 | 241 | // Close socket 242 | s.Close() 243 | 244 | // Wait for disconnect 245 | <-doneConnect 246 | ``` 247 | 248 | ## Install `srtlib` from source 249 | 250 | You can find the instructions to install `srtlib` [here](https://github.com/Haivision/srt/tree/master/docs/build). 251 | 252 | However if you don't feel like doing it manually you can use the following command: 253 | 254 | ```sh 255 | $ make install-srt 256 | ``` 257 | 258 | `srtlib` will be built from source in a directory named `tmp` and located in you working directory. 259 | 260 | For your GO code to pick up `srtlib` dependency automatically, you'll need to add the following environment variables: 261 | 262 | (don't forget to replace `{{ path to your working directory }}` with the absolute path to your working directory) 263 | 264 | ```sh 265 | export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:{{ path to your working directory }}/tmp/v1.5.3/lib/", 266 | export CGO_LDFLAGS="-L{{ path to your working directory }}/tmp/v1.5.3/lib/", 267 | export CGO_CFLAGS="-I{{ path to your working directory }}/tmp/v1.5.3/include/", 268 | export PKG_CONFIG_PATH="{{ path to your working directory }}/tmp/v1.5.3/lib/pkgconfig", 269 | ``` 270 | -------------------------------------------------------------------------------- /examples/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "strings" 10 | 11 | astisrt "github.com/asticode/go-astisrt/pkg" 12 | ) 13 | 14 | func main() { 15 | // Handle logs 16 | astisrt.SetLogLevel(astisrt.LogLevelError) 17 | astisrt.SetLogHandler(func(ll astisrt.LogLevel, file, area, msg string, line int) { log.Println(msg) }) 18 | 19 | // Startup srt 20 | if err := astisrt.Startup(); err != nil { 21 | log.Fatal(fmt.Errorf("main: starting up srt failed: %w", err)) 22 | } 23 | 24 | // Make sure to clean up srt 25 | defer func() { 26 | if err := astisrt.CleanUp(); err != nil { 27 | log.Fatal(fmt.Errorf("main: cleaning up srt failed: %w", err)) 28 | } 29 | }() 30 | 31 | // Capture SIGTERM 32 | doneSignal := make(chan os.Signal, 1) 33 | signal.Notify(doneSignal, os.Interrupt) 34 | 35 | // Dial 36 | log.Println("main: dialing") 37 | doneWrite := make(chan error) 38 | c, err := astisrt.Dial(astisrt.DialOptions{ 39 | // Provide options to the connection 40 | ConnectionOptions: []astisrt.ConnectionOption{ 41 | astisrt.WithLatency(300), 42 | astisrt.WithPassphrase("passphrase"), 43 | astisrt.WithStreamid("test"), 44 | }, 45 | 46 | // Callback when the connection is disconnected 47 | OnDisconnect: func(c *astisrt.Connection, err error) { doneWrite <- err }, 48 | 49 | // Addr that should be dialed 50 | Host: "127.0.0.1", 51 | Port: 4000, 52 | }) 53 | if err != nil { 54 | log.Fatal(fmt.Errorf("main: dialing failed: %w", err)) 55 | } 56 | defer c.Close() 57 | 58 | // Write in a goroutine 59 | go func() { 60 | defer func() { close(doneWrite) }() 61 | 62 | // Loop 63 | log.Println("Write some text and press Enter to send it to the server. Write \"exit\" to quit.") 64 | r := bufio.NewReader(os.Stdin) 65 | for { 66 | // Read from stdin 67 | t, err := r.ReadString('\n') 68 | if err != nil { 69 | log.Println(fmt.Errorf("main: reading from stdin failed: %w", err)) 70 | return 71 | } 72 | t = strings.TrimSpace(t) 73 | 74 | // Exit 75 | if t == "exit" { 76 | return 77 | } 78 | 79 | // Write to the server 80 | if _, err = c.Write([]byte(t)); err != nil { 81 | log.Println(fmt.Errorf("main: writing to server failed: %w", err)) 82 | return 83 | } 84 | } 85 | }() 86 | 87 | // Wait for either SIGTERM or write end 88 | select { 89 | case <-doneSignal: 90 | c.Close() 91 | case err := <-doneWrite: 92 | log.Println(err) 93 | } 94 | 95 | // Make sure write is done 96 | select { 97 | case <-doneWrite: 98 | default: 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /examples/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "os" 9 | "os/signal" 10 | "time" 11 | 12 | astisrt "github.com/asticode/go-astisrt/pkg" 13 | ) 14 | 15 | type ctxKey string 16 | 17 | const ( 18 | ctxKeyStreamID ctxKey = "stream.id" 19 | ) 20 | 21 | func main() { 22 | // Handle logs 23 | astisrt.SetLogLevel(astisrt.LogLevelError) 24 | astisrt.SetLogHandler(func(ll astisrt.LogLevel, file, area, msg string, line int) { log.Println(msg) }) 25 | 26 | // Startup srt 27 | if err := astisrt.Startup(); err != nil { 28 | log.Fatal(fmt.Errorf("main: starting up srt failed: %w", err)) 29 | } 30 | 31 | // Make sure to clean up srt 32 | defer func() { 33 | if err := astisrt.CleanUp(); err != nil { 34 | log.Fatal(fmt.Errorf("main: cleaning up srt failed: %w", err)) 35 | } 36 | }() 37 | 38 | // Capture SIGTERM 39 | doneSignal := make(chan os.Signal, 1) 40 | signal.Notify(doneSignal, os.Interrupt) 41 | 42 | // Create server 43 | s, err := astisrt.NewServer(astisrt.ServerOptions{ 44 | // Provide options that will be passed to accepted connections 45 | ConnectionOptions: []astisrt.ConnectionOption{ 46 | astisrt.WithLatency(300), 47 | astisrt.WithTranstype(astisrt.TranstypeLive), 48 | }, 49 | 50 | // Specify how an incoming connection should be handled before being accepted 51 | // When false is returned, the connection is rejected. 52 | OnBeforeAccept: func(c *astisrt.Connection, version int, streamID string) bool { 53 | // Check stream id 54 | log.Printf("main: checking stream id %s\n", streamID) 55 | if streamID != "test" { 56 | // Set reject reason 57 | log.Println("main: invalid stream id") 58 | if err := c.SetPredefinedRejectReason(http.StatusNotFound); err != nil { 59 | log.Println(fmt.Errorf("main: setting predefined reject reason failed: %w", err)) 60 | } 61 | return false 62 | } 63 | 64 | // Update passphrase 65 | if err := c.Options().SetPassphrase("passphrase"); err != nil { 66 | log.Println(fmt.Errorf("main: setting passphrase failed: %w", err)) 67 | return false 68 | } 69 | 70 | // Add stream id to context 71 | log.Println("main: connection accepted") 72 | *c = *c.WithContext(context.WithValue(c.Context(), ctxKeyStreamID, streamID)) 73 | return true 74 | }, 75 | 76 | // Similar to http.Handler behavior, specify how a connection 77 | // will be handled once accepted 78 | Handler: astisrt.ServerHandlerFunc(func(c *astisrt.Connection) { 79 | // Get stream id from context 80 | if v := c.Context().Value(ctxKeyStreamID); v != nil { 81 | log.Printf("main: handling connection with stream id %s\n", v.(string)) 82 | } 83 | 84 | // Loop 85 | for { 86 | // Read 87 | b := make([]byte, 1500) 88 | n, err := c.Read(b) 89 | if err != nil { 90 | log.Println(fmt.Errorf("main: reading failed: %w", err)) 91 | return 92 | } 93 | 94 | // Log 95 | log.Printf("main: read `%s`\n", b[:n]) 96 | 97 | // Get stats 98 | s, err := c.Stats(false, false) 99 | if err != nil { 100 | log.Println(fmt.Errorf("main: getting stats failed: %w", err)) 101 | continue 102 | } 103 | 104 | // Log 105 | log.Printf("main: %d total bytes received\n", s.ByteRecvTotal()) 106 | } 107 | }), 108 | 109 | // Addr the server should be listening to 110 | Host: "127.0.0.1", 111 | Port: 4000, 112 | }) 113 | if err != nil { 114 | log.Fatal(fmt.Errorf("main: creating server failed: %w", err)) 115 | } 116 | defer s.Close() 117 | 118 | // Listen and serve in a goroutine 119 | doneListenAndServe := make(chan error) 120 | go func() { 121 | log.Println("main: listening") 122 | doneListenAndServe <- s.ListenAndServe(1) 123 | }() 124 | 125 | // Wait for SIGTERM 126 | <-doneSignal 127 | 128 | // Create shutdown context with a timeout to make sure it's cancelled if it takes too much time 129 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 130 | defer cancel() 131 | 132 | // Shutdown 133 | log.Println("main: shutting down") 134 | if err = s.Shutdown(ctx); err != nil { 135 | log.Println(fmt.Errorf("main: shutting down failed: %w", err)) 136 | } 137 | 138 | // Wait for listen and serve to be done 139 | <-doneListenAndServe 140 | } 141 | -------------------------------------------------------------------------------- /examples/socket/connect/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | astisrt "github.com/asticode/go-astisrt/pkg" 10 | ) 11 | 12 | func main() { 13 | // Handle logs 14 | astisrt.SetLogLevel(astisrt.LogLevelError) 15 | astisrt.SetLogHandler(func(ll astisrt.LogLevel, file, area, msg string, line int) { log.Println(msg) }) 16 | 17 | // Startup srt 18 | if err := astisrt.Startup(); err != nil { 19 | log.Fatal(fmt.Errorf("main: starting up srt failed: %w", err)) 20 | } 21 | 22 | // Make sure to clean up srt 23 | defer func() { 24 | if err := astisrt.CleanUp(); err != nil { 25 | log.Fatal(fmt.Errorf("main: cleaning up srt failed: %w", err)) 26 | } 27 | }() 28 | 29 | // Create socket 30 | s, err := astisrt.NewSocket() 31 | if err != nil { 32 | log.Fatal(fmt.Errorf("main: creating socket failed: %w", err)) 33 | } 34 | defer s.Close() 35 | 36 | // Set connect callback 37 | doneConnect := make(chan error) 38 | if err = s.SetConnectCallback(func(s *astisrt.Socket, addr *net.UDPAddr, token int, err error) { 39 | doneConnect <- err 40 | }); err != nil { 41 | log.Fatal(fmt.Errorf("main: setting connect callback failed: %w", err)) 42 | } 43 | 44 | // Set passphrase 45 | if err = s.Options().SetPassphrase("passphrase"); err != nil { 46 | log.Fatal(fmt.Errorf("main: setting passphrase failed: %w", err)) 47 | } 48 | 49 | // Set stream id 50 | if err = s.Options().SetStreamid("test"); err != nil { 51 | log.Fatal(fmt.Errorf("main: setting passphrase failed: %w", err)) 52 | } 53 | 54 | // Connect 55 | log.Println("main: connecting") 56 | if _, err = s.Connect("127.0.0.1", 4000); err != nil { 57 | log.Fatal(fmt.Errorf("main: connecting failed: %w", err)) 58 | } 59 | 60 | // Send message 61 | msg := "this is a test message" 62 | log.Printf("main: sending `%s`\n", msg) 63 | if _, err = s.SendMessage([]byte(msg)); err != nil { 64 | log.Fatal(fmt.Errorf("main: sending message failed: %w", err)) 65 | } 66 | 67 | // Give time to the message to be received 68 | time.Sleep(500 * time.Millisecond) 69 | 70 | // Close socket 71 | s.Close() 72 | 73 | // Wait for disconnect 74 | <-doneConnect 75 | } 76 | -------------------------------------------------------------------------------- /examples/socket/listen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | 8 | astisrt "github.com/asticode/go-astisrt/pkg" 9 | ) 10 | 11 | func main() { 12 | // Handle logs 13 | astisrt.SetLogLevel(astisrt.LogLevelError) 14 | astisrt.SetLogHandler(func(ll astisrt.LogLevel, file, area, msg string, line int) { log.Println(msg) }) 15 | 16 | // Startup srt 17 | if err := astisrt.Startup(); err != nil { 18 | log.Fatal(fmt.Errorf("main: starting up srt failed: %w", err)) 19 | } 20 | 21 | // Make sure to clean up srt 22 | defer func() { 23 | if err := astisrt.CleanUp(); err != nil { 24 | log.Fatal(fmt.Errorf("main: cleaning up srt failed: %w", err)) 25 | } 26 | }() 27 | 28 | // Create socket 29 | s, err := astisrt.NewSocket() 30 | if err != nil { 31 | log.Fatal(fmt.Errorf("main: creating socket failed: %w", err)) 32 | } 33 | defer s.Close() 34 | 35 | // Set listen callback 36 | if err = s.SetListenCallback(func(s *astisrt.Socket, version int, addr *net.UDPAddr, streamID string) bool { 37 | // Check stream id 38 | log.Printf("main: checking stream id %s\n", streamID) 39 | if streamID != "test" { 40 | // Set reject reason 41 | log.Println("main: invalid stream id") 42 | if err := s.SetRejectReason(1404); err != nil { 43 | log.Println(fmt.Errorf("main: setting reject reason failed: %w", err)) 44 | } 45 | return false 46 | } 47 | 48 | // Update passphrase 49 | if err := s.Options().SetPassphrase("passphrase"); err != nil { 50 | log.Println(fmt.Errorf("main: setting passphrase failed: %w", err)) 51 | return false 52 | } 53 | 54 | // Log 55 | log.Println("main: connection accepted") 56 | return true 57 | }); err != nil { 58 | log.Fatal(fmt.Errorf("main: setting listen callback failed: %w", err)) 59 | } 60 | 61 | // Bind 62 | if err = s.Bind("127.0.0.1", 4000); err != nil { 63 | log.Fatal(fmt.Errorf("main: binding failed: %w", err)) 64 | } 65 | 66 | // Listen 67 | log.Println("main: listening") 68 | if err = s.Listen(1); err != nil { 69 | log.Fatal(fmt.Errorf("main: listening failed: %w", err)) 70 | } 71 | 72 | // Accept 73 | as, _, err := s.Accept() 74 | if err != nil { 75 | log.Fatal(fmt.Errorf("main: accepting failed: %w", err)) 76 | } 77 | 78 | // Receive message 79 | b := make([]byte, 1500) 80 | n, err := as.ReceiveMessage(b) 81 | if err != nil { 82 | log.Fatal(fmt.Errorf("main: receiving message failed: %w", err)) 83 | return 84 | } 85 | 86 | // Log 87 | log.Printf("main: received `%s`\n", b[:n]) 88 | } 89 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/asticode/go-astisrt 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/stretchr/testify v1.8.0 7 | golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 8 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 9 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 10 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 11 | golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77 h1:C1tElbkWrsSkn3IRl1GCW/gETw1TywWIPgwZtXTZbYg= 12 | golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 16 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 17 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 18 | -------------------------------------------------------------------------------- /internal/cmd/generate/options/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | "text/template" 13 | "unicode" 14 | ) 15 | 16 | const version = "v1.5.3" 17 | 18 | var ( 19 | r1 = regexp.MustCompile("\\[`([\\w]+)`\\]") 20 | r2 = regexp.MustCompile("`([\\w]+)`") 21 | r3 = regexp.MustCompile("#### `([\\w]+)`") 22 | ) 23 | 24 | type enumValue string 25 | 26 | func (v enumValue) GoName() string { 27 | return cNameToCamel(string(v)) 28 | } 29 | 30 | type enum struct { 31 | Name string 32 | search string 33 | Type string 34 | Values []enumValue 35 | } 36 | 37 | func (e enum) GoName() string { 38 | return cNameToCamel(e.Name) 39 | } 40 | 41 | type option struct { 42 | Dir string 43 | goType string 44 | Name string 45 | Type string 46 | } 47 | 48 | func (opt option) HasRead() bool { 49 | return strings.ContainsRune(opt.Dir, 'R') 50 | } 51 | 52 | func (opt option) HasWrite() bool { 53 | return strings.ContainsRune(opt.Dir, 'W') 54 | } 55 | 56 | func capitalize(s string) string { 57 | if len(s) == 0 { 58 | return "" 59 | } 60 | r := []rune(s) 61 | return string(append([]rune{unicode.ToUpper(r[0])}, r[1:]...)) 62 | } 63 | 64 | func cNameToCamel(name string) (o string) { 65 | for idx, v := range strings.Split(name, "_") { 66 | if idx == 0 { 67 | continue 68 | } 69 | o += capitalize(strings.ToLower(v)) 70 | } 71 | return 72 | } 73 | 74 | func (opt option) GoFunctionName() string { 75 | return cNameToCamel(opt.Name) 76 | } 77 | 78 | func (opt option) CFunctionName() string { 79 | t := opt.Go2CType() 80 | if t == "syscall.Linger" { 81 | t = "linger" 82 | } 83 | return capitalize(t) 84 | } 85 | 86 | func (opt option) GoType() string { 87 | if opt.goType != "" { 88 | return opt.goType 89 | } 90 | return opt.Go2CType() 91 | } 92 | 93 | func (opt option) Go2CType() string { 94 | switch opt.Type { 95 | case "string": 96 | return "string" 97 | case "int32_t": 98 | return "int32" 99 | case "int64_t": 100 | return "int64" 101 | case "bool": 102 | return "bool" 103 | case "linger": 104 | return "syscall.Linger" 105 | } 106 | return "unknown" 107 | } 108 | 109 | type data struct { 110 | Enums []enum 111 | Options []option 112 | } 113 | 114 | func (d data) ConnectionOptions() (opts []option) { 115 | for _, opt := range d.Options { 116 | // Invalid options 117 | if opt.Name == "SRTO_RCVSYN" || opt.Name == "SRTO_SNDSYN" { 118 | continue 119 | } 120 | opts = append(opts, opt) 121 | } 122 | return 123 | } 124 | 125 | type tpl struct { 126 | content string 127 | filename string 128 | } 129 | 130 | var tpls = []tpl{ 131 | { 132 | content: `// Code generated by astisrt using internal/cmd/generate/options. DO NOT EDIT. 133 | package astisrt 134 | 135 | // #cgo LDFLAGS: -lsrt 136 | // #include 137 | import "C" 138 | import ( 139 | "syscall" 140 | "unsafe" 141 | ) 142 | 143 | // https://github.com/Haivision/srt/blob/master/docs/API/API-socket-options.md#list-of-options 144 | 145 | type SocketOptions struct { 146 | s *Socket 147 | } 148 | 149 | func (s *Socket) Options() *SocketOptions { 150 | return &SocketOptions{s: s} 151 | } 152 | 153 | func (so *SocketOptions) getInt64(o C.SRT_SOCKOPT) (int64, error) { 154 | var v int64 155 | size := int(unsafe.Sizeof(v)) 156 | err := cGetSockFlag(so.s.u, o, unsafe.Pointer(&v), &size) 157 | return v, err 158 | } 159 | 160 | func (so *SocketOptions) getInt32(o C.SRT_SOCKOPT) (int32, error) { 161 | var v int32 162 | size := int(unsafe.Sizeof(v)) 163 | err := cGetSockFlag(so.s.u, o, unsafe.Pointer(&v), &size) 164 | return v, err 165 | } 166 | 167 | func (so *SocketOptions) getString(o C.SRT_SOCKOPT) (string, error) { 168 | buf := make([]byte, 1024) 169 | size := len(buf) 170 | err := cGetSockFlag(so.s.u, o, unsafe.Pointer(&buf[0]), &size) 171 | return string(buf[:size]), err 172 | } 173 | 174 | func (so *SocketOptions) getBool(o C.SRT_SOCKOPT) (bool, error) { 175 | var v bool 176 | size := int(unsafe.Sizeof(v)) 177 | err := cGetSockFlag(so.s.u, o, unsafe.Pointer(&v), &size) 178 | return v, err 179 | } 180 | 181 | func (so *SocketOptions) getLinger(o C.SRT_SOCKOPT) (syscall.Linger, error) { 182 | var v syscall.Linger 183 | size := int(unsafe.Sizeof(v)) 184 | err := cGetSockFlag(so.s.u, o, unsafe.Pointer(&v), &size) 185 | return v, err 186 | } 187 | 188 | func (so *SocketOptions) setInt64(o C.SRT_SOCKOPT, v int64) error { 189 | return cSetSockFlag(so.s.u, o, unsafe.Pointer(&v), int(unsafe.Sizeof(v))) 190 | } 191 | 192 | func (so *SocketOptions) setInt32(o C.SRT_SOCKOPT, v int32) error { 193 | return cSetSockFlag(so.s.u, o, unsafe.Pointer(&v), int(unsafe.Sizeof(v))) 194 | } 195 | 196 | func (so *SocketOptions) setBool(o C.SRT_SOCKOPT, v bool) error { 197 | return cSetSockFlag(so.s.u, o, unsafe.Pointer(&v), int(unsafe.Sizeof(v))) 198 | } 199 | 200 | func (so *SocketOptions) setString(o C.SRT_SOCKOPT, v string) error { 201 | return cSetSockFlag(so.s.u, o, unsafe.Pointer(&[]byte(v)[0]), len(v)) 202 | } 203 | 204 | func (so *SocketOptions) setLinger(o C.SRT_SOCKOPT, v syscall.Linger) error { 205 | return cSetSockFlag(so.s.u, o, unsafe.Pointer(&v), int(unsafe.Sizeof(v))) 206 | } 207 | {{ range $enum := .Enums }} 208 | type {{ $enum.GoName }} {{ $enum.Type }} 209 | 210 | const ({{ range $value := $enum.Values }} 211 | {{ $enum.GoName }}{{ $value.GoName }} = {{ $enum.GoName }}(C.{{ $value }}){{ end }} 212 | ) 213 | {{ end }} 214 | {{ range $opt := .Options }}{{ if $opt.HasRead }} 215 | func (so *SocketOptions) {{ $opt.GoFunctionName }}() ({{ $opt.GoType }}, error) { 216 | v, err := so.get{{ $opt.CFunctionName }}(C.{{ $opt.Name }}) 217 | return {{ $opt.GoType }}(v), err 218 | } 219 | {{ end }}{{ if $opt.HasWrite }} 220 | func (so *SocketOptions) Set{{ $opt.GoFunctionName }}(v {{ $opt.GoType }}) error { 221 | return so.set{{ $opt.CFunctionName }}(C.{{ $opt.Name }}, {{ $opt.Go2CType }}(v)) 222 | } 223 | {{ end }}{{ end }}`, 224 | filename: "socket_option.go", 225 | }, 226 | { 227 | content: `// Code generated by astisrt using internal/cmd/generate/options. DO NOT EDIT. 228 | package astisrt 229 | 230 | import ( 231 | "fmt" 232 | "syscall" 233 | ) 234 | 235 | type ConnectionOptions struct { 236 | c *Connection 237 | } 238 | 239 | func (c *Connection) Options() *ConnectionOptions { 240 | return &ConnectionOptions{c: c} 241 | } 242 | 243 | type ConnectionOption struct { 244 | do func(s *Socket) error 245 | name string 246 | } 247 | 248 | func applyConnectionOptions(s *Socket, opts []ConnectionOption) (err error) { 249 | for _, opt := range opts { 250 | if err = opt.do(s); err != nil { 251 | err = fmt.Errorf("astisrt: applying %s option failed: %w", opt.name, err) 252 | return 253 | } 254 | } 255 | return 256 | } 257 | {{ range $opt := .ConnectionOptions }}{{ if $opt.HasRead }} 258 | func (co *ConnectionOptions) {{ $opt.GoFunctionName }}() ({{ $opt.GoType }}, error) { 259 | return co.c.s.Options().{{ $opt.GoFunctionName }}() 260 | } 261 | {{ end }}{{ if $opt.HasWrite }} 262 | func (co *ConnectionOptions) Set{{ $opt.GoFunctionName }}(v {{ $opt.GoType }}) error { 263 | return co.c.s.Options().Set{{ $opt.GoFunctionName }}(v) 264 | } 265 | 266 | func With{{ $opt.GoFunctionName }}(v {{ $opt.GoType }}) ConnectionOption { 267 | return ConnectionOption{ 268 | do: func(s *Socket) error { return s.Options().Set{{ $opt.GoFunctionName }}(v) }, 269 | name: "{{ $opt.GoFunctionName }}", 270 | } 271 | } 272 | {{ end }}{{ end }}`, 273 | filename: "connection_option.go", 274 | }, 275 | } 276 | 277 | func main() { 278 | dir, err := os.Getwd() 279 | if err != nil { 280 | log.Fatal(fmt.Errorf("main: getting working directory failed: %w", err)) 281 | } 282 | 283 | opts, enums, err := options(dir) 284 | if err != nil { 285 | log.Fatal(fmt.Errorf("main: getting options failed: %w", err)) 286 | } 287 | 288 | for _, tpl := range tpls { 289 | log.Printf("generating %s\n", tpl.filename) 290 | 291 | f, err := os.Create(filepath.Join(dir, "pkg", tpl.filename)) 292 | if err != nil { 293 | log.Fatal(fmt.Errorf("main: creating file failed: %w", err)) 294 | } 295 | defer f.Close() 296 | 297 | if err = template.Must(template.New("tmpl").Parse(tpl.content)).Execute(f, data{ 298 | Enums: enums, 299 | Options: opts, 300 | }); err != nil { 301 | log.Fatal(fmt.Errorf("main: executing template failed: %w", err)) 302 | } 303 | } 304 | } 305 | 306 | type optionFilePosition int 307 | 308 | const ( 309 | optionFilePositionNone optionFilePosition = iota 310 | optionFilePositionList 311 | optionFilePositionListTable 312 | optionFilePositionEnum 313 | ) 314 | 315 | func options(dir string) (opts []option, enums []enum, err error) { 316 | // Open doc 317 | var f *os.File 318 | if f, err = os.Open(filepath.Join(dir, "tmp", version, "src", "docs", "API", "API-socket-options.md")); err != nil { 319 | err = fmt.Errorf("main: opening doc failed: %w", err) 320 | return 321 | } 322 | defer f.Close() 323 | 324 | // Loop 325 | var p optionFilePosition 326 | var enumNames []string 327 | s := bufio.NewScanner(f) 328 | scan: 329 | for s.Scan() { 330 | // Trim 331 | t := strings.TrimSpace(s.Text()) 332 | 333 | // Switch on file position 334 | switch p { 335 | case optionFilePositionNone: 336 | // Update file position 337 | if strings.HasPrefix(t, "## List of Options") { 338 | p = optionFilePositionList 339 | } else if strings.HasPrefix(t, "### Enumeration Types Used in Options") { 340 | p = optionFilePositionEnum 341 | } 342 | case optionFilePositionEnum: 343 | if strings.HasPrefix(t, "# ") || strings.HasPrefix(t, "## ") || strings.HasPrefix(t, "### ") { 344 | p = optionFilePositionNone 345 | } else if matches := r3.FindAllStringSubmatch(t, -1); len(matches) > 0 && len(matches[0]) > 1 { 346 | enumNames = append(enumNames, matches[0][1]) 347 | } 348 | case optionFilePositionList: 349 | // Update file position 350 | if strings.HasPrefix(t, "| :-") { 351 | p = optionFilePositionListTable 352 | } 353 | case optionFilePositionListTable: 354 | // We're done 355 | if t == "" { 356 | break scan 357 | } 358 | 359 | // Split 360 | split := strings.Split(t, "|") 361 | if len(split) < 9 { 362 | continue 363 | } 364 | 365 | // Create option 366 | opt := option{Dir: strings.TrimSpace(split[8])} 367 | 368 | // Match name 369 | matches := r1.FindAllStringSubmatch(split[1], -1) 370 | if len(matches) < 1 || len(matches[0]) < 2 { 371 | continue 372 | } 373 | opt.Name = matches[0][1] 374 | 375 | // Match type 376 | matches = r2.FindAllStringSubmatch(split[4], -1) 377 | if len(matches) < 1 || len(matches[0]) < 2 { 378 | continue 379 | } 380 | opt.Type = matches[0][1] 381 | 382 | // Append option 383 | opts = append(opts, opt) 384 | } 385 | } 386 | 387 | // Read header 388 | var b []byte 389 | if b, err = os.ReadFile(filepath.Join(dir, "tmp", version, "include", "srt", "srt.h")); err != nil { 390 | err = fmt.Errorf("main: reading header failed: %w", err) 391 | return 392 | } 393 | 394 | // Loop through enums 395 | for _, v := range enumNames { 396 | // Match 397 | matches := regexp.MustCompile(`(typedef )*enum `+v+`\n\{([^\}]*)`).FindAllStringSubmatch(string(b), -1) 398 | if len(matches) < 1 || len(matches[0]) < 3 { 399 | continue 400 | } 401 | 402 | // Create enum 403 | enum := enum{Name: v} 404 | 405 | // Get search 406 | if split := strings.Split(v, "_"); len(split) > 1 { 407 | enum.search = strings.Join(split[1:], "") 408 | } 409 | 410 | // Loop 411 | s := bufio.NewScanner(bytes.NewReader([]byte(matches[0][2]))) 412 | for s.Scan() { 413 | // Trim text 414 | t := strings.TrimSpace(s.Text()) 415 | if len(t) == 0 { 416 | continue 417 | } 418 | 419 | // Split 420 | split := strings.Split(t, "=") 421 | 422 | // Append value 423 | enum.Values = append(enum.Values, enumValue(strings.TrimSuffix(strings.TrimSpace(split[0]), ","))) 424 | } 425 | 426 | // Append enum 427 | enums = append(enums, enum) 428 | } 429 | 430 | // Loop through options 431 | for idxOpt, opt := range opts { 432 | // Loop through enums 433 | for idxEnum, enum := range enums { 434 | if strings.HasSuffix(opt.Name, "_"+enum.search) { 435 | enums[idxEnum].Type = opt.GoType() 436 | opts[idxOpt].goType = enum.GoName() 437 | } 438 | } 439 | } 440 | return 441 | } 442 | -------------------------------------------------------------------------------- /internal/cmd/generate/static_consts/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "regexp" 10 | "text/template" 11 | ) 12 | 13 | const version = "v1.5.3" 14 | 15 | var r = regexp.MustCompile(`\nstatic const ([^ ]+) ([^ ]+) =`) 16 | 17 | type staticConst struct { 18 | Name string 19 | Type string 20 | } 21 | 22 | func (c staticConst) CName() string { 23 | return c.Name + "_" 24 | } 25 | 26 | type tpl struct { 27 | content string 28 | filename string 29 | } 30 | 31 | var tpls = []tpl{ 32 | { 33 | content: `// Code generated by astisrt using internal/cmd/generate/static_consts. DO NOT EDIT. 34 | #include 35 | 36 | {{ range $const := . }}{{ $const.Type }} {{ $const.CName }} = {{ $const.Name }}; 37 | {{ end }}`, 38 | filename: "static_consts.c", 39 | }, 40 | { 41 | content: `// Code generated by astisrt using internal/cmd/generate/static_consts. DO NOT EDIT. 42 | #include 43 | 44 | {{ range $const := . }}static const {{ $const.Type }} {{ $const.CName }}; 45 | {{ end }}`, 46 | filename: "static_consts.h", 47 | }, 48 | } 49 | 50 | func main() { 51 | dir, err := os.Getwd() 52 | if err != nil { 53 | log.Fatal(fmt.Errorf("main: getting working directory failed: %w", err)) 54 | } 55 | 56 | cs, err := staticConsts(dir) 57 | if err != nil { 58 | log.Fatal(fmt.Errorf("main: getting stats failed: %w", err)) 59 | } 60 | 61 | for _, tpl := range tpls { 62 | log.Printf("generating %s\n", tpl.filename) 63 | 64 | f, err := os.Create(filepath.Join(dir, "pkg", tpl.filename)) 65 | if err != nil { 66 | log.Fatal(fmt.Errorf("main: creating file failed: %w", err)) 67 | } 68 | defer f.Close() 69 | 70 | if err = template.Must(template.New("tmpl").Parse(tpl.content)).Execute(f, cs); err != nil { 71 | log.Fatal(fmt.Errorf("main: executing template failed: %w", err)) 72 | } 73 | } 74 | } 75 | 76 | func staticConsts(dir string) (cs []staticConst, err error) { 77 | // Read header 78 | var b []byte 79 | if b, err = os.ReadFile(filepath.Join(dir, "tmp", version, "include", "srt", "srt.h")); err != nil { 80 | err = fmt.Errorf("main: reading header failed: %w", err) 81 | return 82 | } 83 | 84 | // Match 85 | matches := r.FindAllStringSubmatch(string(b), -1) 86 | if len(matches) < 1 { 87 | err = errors.New("main: invalid matches size") 88 | return 89 | } 90 | 91 | // Loop through matches 92 | for _, match := range matches { 93 | // Invalid length 94 | if len(match) < 3 { 95 | err = errors.New("main: invalid match size") 96 | return 97 | } 98 | 99 | // Append 100 | cs = append(cs, staticConst{ 101 | Name: match[2], 102 | Type: match[1], 103 | }) 104 | } 105 | return 106 | } 107 | -------------------------------------------------------------------------------- /internal/cmd/generate/stats/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "log" 9 | "os" 10 | "path/filepath" 11 | "regexp" 12 | "text/template" 13 | "unicode" 14 | ) 15 | 16 | const version = "v1.5.3" 17 | 18 | var r1 = regexp.MustCompile(`struct CBytePerfMon\n\{([^\}]*)`) 19 | var r2 = regexp.MustCompile(`[\s]*([\w]+)[\s]*([\w]+);`) 20 | 21 | type stat struct { 22 | Name string 23 | Type string 24 | } 25 | 26 | func capitalize(s string) string { 27 | if len(s) == 0 { 28 | return "" 29 | } 30 | r := []rune(s) 31 | return string(append([]rune{unicode.ToUpper(r[0])}, r[1:]...)) 32 | } 33 | 34 | func (s stat) GoName() string { 35 | return capitalize(s.Name) 36 | } 37 | 38 | func (s stat) GoType() string { 39 | switch s.Type { 40 | case "int64_t": 41 | return "int64" 42 | case "int": 43 | return "int" 44 | case "uint64_t": 45 | return "uint64" 46 | case "double": 47 | return "float64" 48 | } 49 | return "unknown" 50 | } 51 | 52 | func (s stat) ExpectedValue() string { 53 | switch s.Type { 54 | case "int64_t": 55 | return "int64(42)" 56 | case "int": 57 | return "int(42)" 58 | case "uint64_t": 59 | return "uint64(42)" 60 | case "double": 61 | return "float64(42)" 62 | } 63 | return "unknown" 64 | } 65 | 66 | type tpl struct { 67 | content string 68 | filename string 69 | } 70 | 71 | var tpls = []tpl{ 72 | { 73 | content: `// Code generated by astisrt using internal/cmd/generate/stats. DO NOT EDIT. 74 | package astisrt 75 | 76 | // #cgo LDFLAGS: -lsrt 77 | // #include 78 | import "C" 79 | 80 | type Stats struct { 81 | c C.SRT_TRACEBSTATS 82 | } 83 | 84 | func (s *Socket) Stats(clear, instantaneous bool) (ss Stats, err error) { 85 | err = cBiStats(s.u, &ss.c, boolToCInt(clear), boolToCInt(instantaneous)) 86 | return 87 | } 88 | 89 | {{ range $stat := . }} 90 | func (ss Stats) {{ $stat.GoName }}() {{ $stat.GoType }} { 91 | return {{ $stat.GoType }}(ss.c.{{ $stat.Name }}) 92 | } 93 | {{ end }}`, 94 | filename: "stats.go", 95 | }, 96 | { 97 | content: `// Code generated by astisrt using internal/cmd/generate/stats. DO NOT EDIT. 98 | package astisrt 99 | 100 | import ( 101 | "testing" 102 | 103 | "github.com/stretchr/testify/require" 104 | ) 105 | 106 | func TestStats(t *testing.T) { 107 | s := Stats{} 108 | {{ range $stat := . }}s.c.{{ $stat.Name }} = _Ctype_{{ $stat.Type }}({{ $stat.ExpectedValue }}) 109 | require.Equal(t, {{ $stat.ExpectedValue }}, s.{{ $stat.GoName }}()) 110 | {{ end }} 111 | }`, 112 | filename: "stats_test.go", 113 | }, 114 | } 115 | 116 | func main() { 117 | dir, err := os.Getwd() 118 | if err != nil { 119 | log.Fatal(fmt.Errorf("main: getting working directory failed: %w", err)) 120 | } 121 | 122 | ss, err := stats(dir) 123 | if err != nil { 124 | log.Fatal(fmt.Errorf("main: getting stats failed: %w", err)) 125 | } 126 | 127 | for _, tpl := range tpls { 128 | log.Printf("generating %s\n", tpl.filename) 129 | 130 | f, err := os.Create(filepath.Join(dir, "pkg", tpl.filename)) 131 | if err != nil { 132 | log.Fatal(fmt.Errorf("main: creating file failed: %w", err)) 133 | } 134 | defer f.Close() 135 | 136 | if err = template.Must(template.New("tmpl").Parse(tpl.content)).Execute(f, ss); err != nil { 137 | log.Fatal(fmt.Errorf("main: executing template failed: %w", err)) 138 | } 139 | } 140 | } 141 | 142 | func stats(dir string) (ss []stat, err error) { 143 | // Read header 144 | var b []byte 145 | if b, err = os.ReadFile(filepath.Join(dir, "tmp", version, "include", "srt", "srt.h")); err != nil { 146 | err = fmt.Errorf("main: reading header failed: %w", err) 147 | return 148 | } 149 | 150 | // Match 151 | matches := r1.FindAllSubmatch(b, -1) 152 | if len(matches) < 1 || len(matches[0]) < 2 { 153 | err = errors.New("main: invalid matches size") 154 | return 155 | } 156 | 157 | // Scan 158 | s := bufio.NewScanner(bytes.NewBuffer(matches[0][1])) 159 | for s.Scan() { 160 | // Match 161 | matches := r2.FindAllSubmatch(s.Bytes(), -1) 162 | if len(matches) < 1 || len(matches[0]) < 3 { 163 | continue 164 | } 165 | 166 | // Add stat 167 | ss = append(ss, stat{ 168 | Name: string(matches[0][2]), 169 | Type: string(matches[0][1]), 170 | }) 171 | } 172 | return 173 | } 174 | -------------------------------------------------------------------------------- /internal/cmd/generate/wrap/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | "text/template" 10 | ) 11 | 12 | type fnArg struct { 13 | Format string 14 | Name string 15 | Type string 16 | } 17 | 18 | type fn struct { 19 | CArgs []fnArg 20 | CName string 21 | RejectReasonArgName string 22 | GoArgs []fnArg 23 | GoName string 24 | Return string 25 | } 26 | 27 | func (f fn) joinArgs(args []fnArg) string { 28 | var ss []string 29 | for _, a := range args { 30 | s := a.Name 31 | if a.Format != "" { 32 | s = fmt.Sprintf(a.Format, a.Name) 33 | } 34 | ss = append(ss, s) 35 | } 36 | return strings.Join(ss, ", ") 37 | } 38 | 39 | func (f fn) JoinCArgs() string { 40 | return f.joinArgs(f.CArgs) 41 | } 42 | 43 | func (f fn) JoinGoArgs() string { 44 | s := f.joinArgs(f.GoArgs) 45 | if len(f.GoArgs) > 0 { 46 | s += ", " 47 | } 48 | return s 49 | } 50 | 51 | func (f fn) JoinCArgsWithType() (s string) { 52 | for _, a := range f.CArgs { 53 | if a.Type == "" { 54 | continue 55 | } 56 | s += a.Type + " " + a.Name + ", " 57 | } 58 | return 59 | } 60 | 61 | func (f fn) JoinGoArgsWithType() (s string) { 62 | var ss []string 63 | for _, a := range f.GoArgs { 64 | if a.Type == "" { 65 | continue 66 | } 67 | ss = append(ss, a.Name+" "+a.Type) 68 | } 69 | return strings.Join(ss, ", ") 70 | } 71 | 72 | func (f fn) CRetCompare() string { 73 | if f.Return == "SRTSOCKET" { 74 | return "SRT_INVALID_SOCK" 75 | } 76 | return "SRT_ERROR" 77 | } 78 | 79 | func (f fn) GoRetCompare() string { 80 | if f.Return == "SRTSOCKET" { 81 | return "SRT_INVALID_SOCK_" 82 | } 83 | return "SRT_ERROR_" 84 | } 85 | 86 | func (f fn) CReturn() string { 87 | if f.Return != "" { 88 | return f.Return 89 | } 90 | return "int" 91 | } 92 | 93 | func (f fn) GoOutputArgs() string { 94 | var args []string 95 | if f.Return == "SRTSOCKET" { 96 | args = append(args, "C.SRTSOCKET") 97 | } else if f.Return == "int" { 98 | args = append(args, "C.int") 99 | } 100 | if f.RejectReasonArgName != "" { 101 | args = append(args, "C.int") 102 | } 103 | args = append(args, "error") 104 | o := strings.Join(args, ", ") 105 | if len(args) > 1 { 106 | return "(" + o + ")" 107 | } 108 | return o 109 | } 110 | 111 | func (f fn) GoReturnArgsError() string { 112 | var args []string 113 | if f.Return == "SRTSOCKET" || f.Return == "int" { 114 | args = append(args, "ret") 115 | } 116 | if f.RejectReasonArgName != "" { 117 | args = append(args, "rejectReason") 118 | } 119 | args = append(args, "newError(srtErrno, sysErrno)") 120 | return strings.Join(args, ", ") 121 | } 122 | 123 | func (f fn) GoReturnArgsSuccess() string { 124 | var args []string 125 | if f.Return == "SRTSOCKET" || f.Return == "int" { 126 | args = append(args, "ret") 127 | } 128 | if f.RejectReasonArgName != "" { 129 | args = append(args, "rejectReason") 130 | } 131 | args = append(args, "nil") 132 | return strings.Join(args, ", ") 133 | } 134 | 135 | var fns = []fn{ 136 | { 137 | CArgs: []fnArg{ 138 | { 139 | Name: "u", 140 | Type: "SRTSOCKET", 141 | }, 142 | { 143 | Name: "opt", 144 | Type: "SRT_SOCKOPT", 145 | }, 146 | { 147 | Name: "optval", 148 | Type: "void*", 149 | }, 150 | { 151 | Name: "optlen", 152 | Type: "int*", 153 | }, 154 | }, 155 | CName: "getsockflag", 156 | GoArgs: []fnArg{ 157 | { 158 | Name: "s", 159 | Type: "C.SRTSOCKET", 160 | }, 161 | { 162 | Name: "o", 163 | Type: "C.SRT_SOCKOPT", 164 | }, 165 | { 166 | Name: "p", 167 | Type: "unsafe.Pointer", 168 | }, 169 | { 170 | Format: "(*C.int)(unsafe.Pointer(%s))", 171 | Name: "size", 172 | Type: "*int", 173 | }, 174 | }, 175 | GoName: "GetSockFlag", 176 | }, 177 | { 178 | CArgs: []fnArg{ 179 | { 180 | Name: "u", 181 | Type: "SRTSOCKET", 182 | }, 183 | { 184 | Name: "opt", 185 | Type: "SRT_SOCKOPT", 186 | }, 187 | { 188 | Name: "optval", 189 | Type: "const void*", 190 | }, 191 | { 192 | Name: "optlen", 193 | Type: "int", 194 | }, 195 | }, 196 | CName: "setsockflag", 197 | GoArgs: []fnArg{ 198 | { 199 | Name: "s", 200 | Type: "C.SRTSOCKET", 201 | }, 202 | { 203 | Name: "o", 204 | Type: "C.SRT_SOCKOPT", 205 | }, 206 | { 207 | Name: "p", 208 | Type: "unsafe.Pointer", 209 | }, 210 | { 211 | Format: "(C.int)(%s)", 212 | Name: "size", 213 | Type: "int", 214 | }, 215 | }, 216 | GoName: "SetSockFlag", 217 | }, 218 | { 219 | CName: "startup", 220 | GoName: "Startup", 221 | }, 222 | { 223 | CName: "cleanup", 224 | GoName: "Cleanup", 225 | }, 226 | { 227 | CName: "create_socket", 228 | GoName: "CreateSocket", 229 | Return: "SRTSOCKET", 230 | }, 231 | { 232 | CArgs: []fnArg{ 233 | { 234 | Name: "u", 235 | Type: "SRTSOCKET", 236 | }, 237 | }, 238 | CName: "close", 239 | GoArgs: []fnArg{ 240 | { 241 | Name: "s", 242 | Type: "C.SRTSOCKET", 243 | }, 244 | }, 245 | GoName: "Close", 246 | }, 247 | { 248 | CArgs: []fnArg{ 249 | { 250 | Name: "u", 251 | Type: "SRTSOCKET", 252 | }, 253 | { 254 | Name: "name", 255 | Type: "const struct sockaddr*", 256 | }, 257 | { 258 | Name: "namelen", 259 | Type: "int", 260 | }, 261 | }, 262 | CName: "bind", 263 | GoArgs: []fnArg{ 264 | { 265 | Name: "s", 266 | Type: "C.SRTSOCKET", 267 | }, 268 | { 269 | Name: "addr", 270 | Type: "*C.struct_sockaddr", 271 | }, 272 | { 273 | Name: "size", 274 | Type: "C.int", 275 | }, 276 | }, 277 | GoName: "Bind", 278 | }, 279 | { 280 | CArgs: []fnArg{ 281 | { 282 | Name: "u", 283 | Type: "SRTSOCKET", 284 | }, 285 | { 286 | Name: "backlog", 287 | Type: "int", 288 | }, 289 | }, 290 | CName: "listen", 291 | GoArgs: []fnArg{ 292 | { 293 | Name: "s", 294 | Type: "C.SRTSOCKET", 295 | }, 296 | { 297 | Name: "backlog", 298 | Type: "C.int", 299 | }, 300 | }, 301 | GoName: "Listen", 302 | }, 303 | { 304 | CArgs: []fnArg{ 305 | { 306 | Name: "u", 307 | Type: "SRTSOCKET", 308 | }, 309 | { 310 | Name: "name", 311 | Type: "const struct sockaddr*", 312 | }, 313 | { 314 | Name: "namelen", 315 | Type: "int", 316 | }, 317 | }, 318 | CName: "connect", 319 | GoArgs: []fnArg{ 320 | { 321 | Name: "s", 322 | Type: "C.SRTSOCKET", 323 | }, 324 | { 325 | Name: "addr", 326 | Type: "*C.struct_sockaddr", 327 | }, 328 | { 329 | Name: "size", 330 | Type: "C.int", 331 | }, 332 | }, 333 | GoName: "Connect", 334 | RejectReasonArgName: "u", 335 | }, 336 | { 337 | CArgs: []fnArg{ 338 | { 339 | Name: "u", 340 | Type: "SRTSOCKET", 341 | }, 342 | { 343 | Name: "addr", 344 | Type: "struct sockaddr*", 345 | }, 346 | { 347 | Name: "addrlen", 348 | Type: "int*", 349 | }, 350 | }, 351 | CName: "accept", 352 | GoArgs: []fnArg{ 353 | { 354 | Name: "s", 355 | Type: "C.SRTSOCKET", 356 | }, 357 | { 358 | Name: "addr", 359 | Type: "*C.struct_sockaddr", 360 | }, 361 | { 362 | Name: "size", 363 | Type: "*C.int", 364 | }, 365 | }, 366 | GoName: "Accept", 367 | Return: "SRTSOCKET", 368 | }, 369 | { 370 | CArgs: []fnArg{ 371 | { 372 | Name: "lsn", 373 | Type: "SRTSOCKET", 374 | }, 375 | { 376 | Name: "astisrt_listen_callback_fn", 377 | }, 378 | { 379 | Name: "opaque", 380 | Type: "void*", 381 | }, 382 | }, 383 | CName: "listen_callback", 384 | GoArgs: []fnArg{ 385 | { 386 | Format: "*%s", 387 | Name: "lsn", 388 | Type: "*C.SRTSOCKET", 389 | }, 390 | { 391 | Name: "unsafe.Pointer(lsn)", 392 | }, 393 | }, 394 | GoName: "ListenCallback", 395 | }, 396 | { 397 | CArgs: []fnArg{ 398 | { 399 | Name: "clr", 400 | Type: "SRTSOCKET", 401 | }, 402 | { 403 | Name: "astisrt_connect_callback_fn", 404 | }, 405 | { 406 | Name: "opaque", 407 | Type: "void*", 408 | }, 409 | }, 410 | CName: "connect_callback", 411 | GoArgs: []fnArg{ 412 | { 413 | Format: "*%s", 414 | Name: "clr", 415 | Type: "*C.SRTSOCKET", 416 | }, 417 | { 418 | Name: "unsafe.Pointer(clr)", 419 | }, 420 | }, 421 | GoName: "ConnectCallback", 422 | }, 423 | { 424 | CArgs: []fnArg{ 425 | { 426 | Name: "sock", 427 | Type: "SRTSOCKET", 428 | }, 429 | { 430 | Name: "value", 431 | Type: "int", 432 | }, 433 | }, 434 | CName: "setrejectreason", 435 | GoArgs: []fnArg{ 436 | { 437 | Name: "sock", 438 | Type: "C.SRTSOCKET", 439 | }, 440 | { 441 | Name: "value", 442 | Type: "C.int", 443 | }, 444 | }, 445 | GoName: "SetRejectReason", 446 | }, 447 | { 448 | CArgs: []fnArg{ 449 | { 450 | Name: "u", 451 | Type: "SRTSOCKET", 452 | }, 453 | { 454 | Name: "perf", 455 | Type: "SRT_TRACEBSTATS*", 456 | }, 457 | { 458 | Name: "clear", 459 | Type: "int", 460 | }, 461 | { 462 | Name: "instantaneous", 463 | Type: "int", 464 | }, 465 | }, 466 | CName: "bistats", 467 | GoArgs: []fnArg{ 468 | { 469 | Name: "u", 470 | Type: "C.SRTSOCKET", 471 | }, 472 | { 473 | Name: "perf", 474 | Type: "*C.SRT_TRACEBSTATS", 475 | }, 476 | { 477 | Name: "clear", 478 | Type: "C.int", 479 | }, 480 | { 481 | Name: "instantaneous", 482 | Type: "C.int", 483 | }, 484 | }, 485 | GoName: "BiStats", 486 | }, 487 | { 488 | CArgs: []fnArg{ 489 | { 490 | Name: "u", 491 | Type: "SRTSOCKET", 492 | }, 493 | { 494 | Name: "buf", 495 | Type: "char*", 496 | }, 497 | { 498 | Name: "len", 499 | Type: "int", 500 | }, 501 | { 502 | Name: "mctrl", 503 | Type: "SRT_MSGCTRL*", 504 | }, 505 | }, 506 | CName: "recvmsg2", 507 | GoArgs: []fnArg{ 508 | { 509 | Name: "u", 510 | Type: "C.SRTSOCKET", 511 | }, 512 | { 513 | Name: "buf", 514 | Type: "*C.char", 515 | }, 516 | { 517 | Name: "len", 518 | Type: "C.int", 519 | }, 520 | { 521 | Name: "mctrl", 522 | Type: "*C.SRT_MSGCTRL", 523 | }, 524 | }, 525 | GoName: "RecMsg2", 526 | Return: "int", 527 | }, 528 | { 529 | CArgs: []fnArg{ 530 | { 531 | Name: "u", 532 | Type: "SRTSOCKET", 533 | }, 534 | { 535 | Name: "buf", 536 | Type: "char*", 537 | }, 538 | { 539 | Name: "len", 540 | Type: "int", 541 | }, 542 | { 543 | Name: "mctrl", 544 | Type: "SRT_MSGCTRL*", 545 | }, 546 | }, 547 | CName: "sendmsg2", 548 | GoArgs: []fnArg{ 549 | { 550 | Name: "u", 551 | Type: "C.SRTSOCKET", 552 | }, 553 | { 554 | Name: "buf", 555 | Type: "*C.char", 556 | }, 557 | { 558 | Name: "len", 559 | Type: "C.int", 560 | }, 561 | { 562 | Name: "mctrl", 563 | Type: "*C.SRT_MSGCTRL", 564 | }, 565 | }, 566 | GoName: "SendMsg2", 567 | Return: "int", 568 | }, 569 | } 570 | 571 | type tpl struct { 572 | content string 573 | filename string 574 | } 575 | 576 | var tpls = []tpl{ 577 | { 578 | content: `// Code generated by astisrt using internal/cmd/generate/wrap. DO NOT EDIT. 579 | #include 580 | #include "callbacks.h" 581 | {{ range $fn := . }} 582 | {{ $fn.CReturn }} astisrt_{{ $fn.CName }}({{ $fn.JoinCArgsWithType }}int *srtErrno, int *sysErrno{{ if $fn.RejectReasonArgName }}, int *rejectReason{{ end }}) { 583 | {{ $fn.CReturn }} ret = srt_{{ $fn.CName }}({{ $fn.JoinCArgs }}); 584 | if (ret == {{ $fn.CRetCompare }}) { 585 | *srtErrno = srt_getlasterror(sysErrno); 586 | srt_clearlasterror();{{ if $fn.RejectReasonArgName }} 587 | if (*srtErrno == SRT_ECONNREJ) { 588 | *rejectReason = srt_getrejectreason({{ $fn.RejectReasonArgName }}); 589 | }{{ end }} 590 | } 591 | return ret; 592 | } 593 | {{ end }}`, 594 | filename: "wrap.c", 595 | }, 596 | { 597 | content: `// Code generated by astisrt using internal/cmd/generate/wrap. DO NOT EDIT. 598 | #include 599 | #include "callbacks.h" 600 | {{ range $fn := . }} 601 | {{ $fn.CReturn }} astisrt_{{ $fn.CName }}({{ $fn.JoinCArgsWithType }}int *srtErrno, int *sysErrno{{ if $fn.RejectReasonArgName }}, int *rejectReason{{ end }});{{ end }}`, 602 | filename: "wrap.h", 603 | }, 604 | { 605 | content: `// Code generated by astisrt using internal/cmd/generate/wrap. DO NOT EDIT. 606 | package astisrt 607 | 608 | // #cgo LDFLAGS: -lsrt 609 | // #include "static_consts.h" 610 | // #include 611 | // #include "wrap.h" 612 | import "C" 613 | import ( 614 | "unsafe" 615 | ) 616 | {{ range $fn := . }} 617 | func c{{ $fn.GoName }}({{ $fn.JoinGoArgsWithType }}) {{ $fn.GoOutputArgs }} { 618 | var srtErrno C.int 619 | var sysErrno C.int 620 | {{ if $fn.RejectReasonArgName }}var rejectReason C.int 621 | {{ end }}ret := C.astisrt_{{ $fn.CName }}({{ $fn.JoinGoArgs }}&srtErrno, &sysErrno{{ if $fn.RejectReasonArgName }}, &rejectReason{{ end }}) 622 | if ret == C.{{ $fn.GoRetCompare }} { 623 | return {{ $fn.GoReturnArgsError }} 624 | } 625 | return {{ $fn.GoReturnArgsSuccess }} 626 | } 627 | {{ end }}`, 628 | filename: "wrap.go", 629 | }, 630 | } 631 | 632 | func main() { 633 | dir, err := os.Getwd() 634 | if err != nil { 635 | log.Fatal(fmt.Errorf("main: getting working directory failed: %w", err)) 636 | } 637 | 638 | for _, tpl := range tpls { 639 | log.Printf("generating %s\n", tpl.filename) 640 | 641 | f, err := os.Create(filepath.Join(dir, "pkg", tpl.filename)) 642 | if err != nil { 643 | log.Fatal(fmt.Errorf("main: creating file failed: %w", err)) 644 | } 645 | defer f.Close() 646 | 647 | if err = template.Must(template.New("tmpl").Parse(tpl.content)).Execute(f, fns); err != nil { 648 | log.Fatal(fmt.Errorf("main: executing template failed: %w", err)) 649 | } 650 | } 651 | } 652 | -------------------------------------------------------------------------------- /pkg/addr.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | import "C" 4 | import ( 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "net" 9 | "syscall" 10 | "unsafe" 11 | 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | func orderPortBytes(port uint16, o binary.ByteOrder) uint16 { 16 | b := (*[2]byte)(unsafe.Pointer(&port)) 17 | return o.Uint16((*b)[:]) 18 | } 19 | 20 | func sockAddrPortFromRegularPort(port uint16) uint16 { 21 | return orderPortBytes(port, binary.BigEndian) 22 | } 23 | 24 | func regularPortFromSockAddrPort(port uint16) uint16 { 25 | return orderPortBytes(port, binary.BigEndian) 26 | } 27 | 28 | type sockaddr struct { 29 | inet4 *syscall.RawSockaddrInet4 30 | inet6 *syscall.RawSockaddrInet6 31 | } 32 | 33 | func (sa *sockaddr) addr() *C.struct_sockaddr { 34 | if sa.inet4 != nil { 35 | return (*C.struct_sockaddr)(unsafe.Pointer(sa.inet4)) 36 | } else if sa.inet6 != nil { 37 | return (*C.struct_sockaddr)(unsafe.Pointer(sa.inet6)) 38 | } 39 | return nil 40 | } 41 | 42 | func (sa *sockaddr) size() C.int { 43 | if sa.inet4 != nil { 44 | return syscall.SizeofSockaddrInet4 45 | } else if sa.inet6 != nil { 46 | return syscall.SizeofSockaddrInet6 47 | } 48 | return 0 49 | } 50 | 51 | func (sa *sockaddr) toUDP() *net.UDPAddr { 52 | if sa.inet4 != nil { 53 | return &net.UDPAddr{ 54 | IP: sa.inet4.Addr[:], 55 | Port: int(regularPortFromSockAddrPort(sa.inet4.Port)), 56 | } 57 | } else if sa.inet6 != nil { 58 | return &net.UDPAddr{ 59 | IP: sa.inet6.Addr[:], 60 | Port: int(regularPortFromSockAddrPort(sa.inet6.Port)), 61 | } 62 | } 63 | return nil 64 | } 65 | 66 | func newSockaddrFromHostAndPort(host string, port uint16) (addr *sockaddr, err error) { 67 | // Parse ip 68 | ip := net.ParseIP(host) 69 | if ip == nil { 70 | // Lookup ip 71 | var ips []net.IP 72 | if ips, err = net.LookupIP(host); err != nil { 73 | err = fmt.Errorf("astisrt: looking up ip failed: %w", err) 74 | return 75 | } 76 | 77 | // No ip 78 | if len(ips) < 1 { 79 | err = errors.New("astisrt: ip lookup didn't return enough ips") 80 | return 81 | } 82 | 83 | // Set ip 84 | ip = ips[0] 85 | } 86 | 87 | // Check ip 88 | if v := ip.To4(); v != nil { 89 | // Create sockaddr 90 | addr = &sockaddr{inet4: &syscall.RawSockaddrInet4{ 91 | Family: unix.AF_INET, 92 | Port: sockAddrPortFromRegularPort(port), 93 | }} 94 | 95 | // Copy addr 96 | copy(addr.inet4.Addr[:], v) 97 | } else if v := ip.To16(); v != nil { 98 | // Create sockaddr 99 | addr = &sockaddr{inet6: &syscall.RawSockaddrInet6{ 100 | Family: unix.AF_INET6, 101 | Port: sockAddrPortFromRegularPort(port), 102 | }} 103 | 104 | // Copy addr 105 | copy(addr.inet6.Addr[:], v) 106 | } else { 107 | err = errors.New("astisrt: ip is neither IPv4 nor IPv6") 108 | return 109 | } 110 | return 111 | } 112 | 113 | func newSockaddrFromSockaddrAny(in *syscall.RawSockaddrAny) (out *sockaddr, err error) { 114 | switch in.Addr.Family { 115 | case unix.AF_INET6: 116 | out = &sockaddr{inet6: (*syscall.RawSockaddrInet6)(unsafe.Pointer(in))} 117 | case unix.AF_INET: 118 | out = &sockaddr{inet4: (*syscall.RawSockaddrInet4)(unsafe.Pointer(in))} 119 | default: 120 | err = errors.New("astisrt: addr is neither IPv4 nor IPv6") 121 | return 122 | } 123 | return 124 | } 125 | -------------------------------------------------------------------------------- /pkg/addr_test.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestPortConversion(t *testing.T) { 10 | e1, e2 := uint16(4000), uint16(40975) 11 | require.Equal(t, e2, sockAddrPortFromRegularPort(e1)) 12 | require.Equal(t, e1, regularPortFromSockAddrPort(e2)) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/callbacks.c: -------------------------------------------------------------------------------- 1 | #include "_cgo_export.h" 2 | #include 3 | 4 | void astisrt_connect_callback_fn(void* opaque, SRTSOCKET ns, int errorcode, const struct sockaddr* peeraddr, int token) { 5 | return go2cConnectCallback(opaque, ns, errorcode, (struct sockaddr*) peeraddr, token); 6 | } 7 | 8 | int astisrt_listen_callback_fn(void* opaque, SRTSOCKET ns, int hs_version, const struct sockaddr* peeraddr, const char* streamid) { 9 | return go2cListenCallback(opaque, ns, hs_version, (struct sockaddr*) peeraddr, (char*) streamid); 10 | } -------------------------------------------------------------------------------- /pkg/callbacks.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | // #cgo LDFLAGS: -lsrt 4 | // #include 5 | // #include "static_consts.h" 6 | import "C" 7 | import ( 8 | "net" 9 | "sync" 10 | "syscall" 11 | "unsafe" 12 | ) 13 | 14 | type ConnectCallback func(s *Socket, addr *net.UDPAddr, token int, err error) 15 | type ListenCallback func(s *Socket, version int, addr *net.UDPAddr, streamID string) bool 16 | 17 | var ( 18 | acceptedSockets = make(map[C.SRTSOCKET]*Socket) 19 | acceptedSocketsMutex = &sync.Mutex{} 20 | connectCallbacks = make(map[C.SRTSOCKET]ConnectCallback) 21 | connectCallbacksMutex = &sync.Mutex{} 22 | listenCallbacks = make(map[C.SRTSOCKET]ListenCallback) 23 | listenCallbacksMutex = &sync.Mutex{} 24 | ) 25 | 26 | // For groups only 27 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_connect_callback 28 | func (s *Socket) SetConnectCallback(c ConnectCallback) (err error) { 29 | // We need to provide a GO pointer here otherwise it won't retrieve the proper callback later 30 | if err = cConnectCallback(&s.u); err != nil { 31 | return 32 | } 33 | connectCallbacksMutex.Lock() 34 | connectCallbacks[s.u] = c 35 | connectCallbacksMutex.Unlock() 36 | return 37 | } 38 | 39 | //export go2cConnectCallback 40 | func go2cConnectCallback(opaque unsafe.Pointer, u C.SRTSOCKET, errorcode C.int, peeraddr *C.struct_sockaddr, token C.int) { 41 | // Get callback 42 | connectCallbacksMutex.Lock() 43 | cb, ok := connectCallbacks[*(*C.SRTSOCKET)(opaque)] 44 | connectCallbacksMutex.Unlock() 45 | 46 | // No callback 47 | if !ok || cb == nil { 48 | return 49 | } 50 | 51 | // Create socket 52 | s := newSocketFromC(u) 53 | 54 | // Create sockaddr 55 | sa, err := newSockaddrFromSockaddrAny((*syscall.RawSockaddrAny)(unsafe.Pointer(peeraddr))) 56 | if err != nil { 57 | return 58 | } 59 | 60 | // Callback 61 | cb(s, sa.toUDP(), int(token), newError(errorcode, 0)) 62 | } 63 | 64 | //export go2cListenCallback 65 | func go2cListenCallback(opaque unsafe.Pointer, u C.SRTSOCKET, version C.int, peeraddr *C.struct_sockaddr, streamid *C.char) int { 66 | // Get callback 67 | listenCallbacksMutex.Lock() 68 | cb, ok := listenCallbacks[*(*C.SRTSOCKET)(opaque)] 69 | listenCallbacksMutex.Unlock() 70 | 71 | // No callback 72 | if !ok || cb == nil { 73 | return int(C.SRT_SUCCESS) 74 | } 75 | 76 | // Create socket 77 | s := newSocketFromC(u) 78 | 79 | // Create sockaddr 80 | sa, err := newSockaddrFromSockaddrAny((*syscall.RawSockaddrAny)(unsafe.Pointer(peeraddr))) 81 | if err != nil { 82 | return int(C.SRT_ERROR_) 83 | } 84 | 85 | // Callback 86 | if ok = cb(s, int(version), sa.toUDP(), C.GoString(streamid)); !ok { 87 | return int(C.SRT_ERROR_) 88 | } 89 | 90 | // Store socket so that Accept() gets the proper Go object 91 | acceptedSocketsMutex.Lock() 92 | acceptedSockets[u] = s 93 | acceptedSocketsMutex.Unlock() 94 | return int(C.SRT_SUCCESS) 95 | } 96 | 97 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_listen_callback 98 | func (s *Socket) SetListenCallback(c ListenCallback) (err error) { 99 | // We need to provide a GO pointer here otherwise it won't retrieve the proper callback later 100 | if err = cListenCallback(&s.u); err != nil { 101 | return 102 | } 103 | listenCallbacksMutex.Lock() 104 | listenCallbacks[s.u] = c 105 | listenCallbacksMutex.Unlock() 106 | return 107 | } 108 | -------------------------------------------------------------------------------- /pkg/callbacks.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void astisrt_connect_callback_fn(void* opaque, SRTSOCKET ns, int errorcode, const struct sockaddr* peeraddr, int token); 4 | int astisrt_listen_callback_fn(void* opaque, SRTSOCKET ns, int hs_version, const struct sockaddr* peeraddr, const char* streamid); -------------------------------------------------------------------------------- /pkg/client.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | type DialOnDisconnect func(c *Connection, err error) 9 | 10 | func (f DialOnDisconnect) connectCallback(c *Connection) ConnectCallback { 11 | return func(s *Socket, addr *net.UDPAddr, token int, err error) { f(c, err) } 12 | } 13 | 14 | type DialOptions struct { 15 | // Connection options 16 | ConnectionOptions []ConnectionOption 17 | 18 | // Host to connect to 19 | Host string 20 | 21 | // Callback executed when connection disconnects. 22 | OnDisconnect DialOnDisconnect 23 | 24 | // Port to connect to 25 | Port uint16 26 | } 27 | 28 | func Dial(o DialOptions) (c *Connection, err error) { 29 | // Create socket 30 | var s *Socket 31 | if s, err = NewSocket(); err != nil { 32 | err = fmt.Errorf("astisrt: creating socket failed: %w", err) 33 | return 34 | } 35 | 36 | // Make sure socket is closed in case of error 37 | defer func() { 38 | if err != nil { 39 | s.Close() 40 | } 41 | }() 42 | 43 | // Create connection 44 | c = newConnection(nil, s) 45 | 46 | // Apply connection options 47 | if err = applyConnectionOptions(s, o.ConnectionOptions); err != nil { 48 | err = fmt.Errorf("astisrt: applying connection options failed: %w", err) 49 | return 50 | } 51 | 52 | // Set callbacks 53 | if o.OnDisconnect != nil { 54 | if err = s.SetConnectCallback(o.OnDisconnect.connectCallback(c)); err != nil { 55 | err = fmt.Errorf("astisrt: setting connect callback failed: %w", err) 56 | return 57 | } 58 | } 59 | 60 | // Connect 61 | if c.addr, err = s.Connect(o.Host, o.Port); err != nil { 62 | err = fmt.Errorf("astisrt: connecting failed: %w", err) 63 | return 64 | } 65 | return 66 | } 67 | -------------------------------------------------------------------------------- /pkg/client_test.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | type disconnectResult struct { 10 | c *Connection 11 | err error 12 | } 13 | 14 | func TestDial(t *testing.T) { 15 | // Quiet logs 16 | SetLogHandler(func(l LogLevel, file, area, msg string, line int) {}) 17 | defer SetLogHandler(nil) 18 | 19 | // Create server 20 | srv, err := NewServer(ServerOptions{ 21 | Handler: ServerHandlerFunc(func(c *Connection) { c.Close() }), 22 | Host: "127.0.0.1", 23 | Port: 4000, 24 | }) 25 | require.NoError(t, err) 26 | chanListenAndServe := make(chan error) 27 | defer func() { 28 | srv.Close() 29 | <-chanListenAndServe 30 | }() 31 | 32 | // Listen and serve 33 | go func() { chanListenAndServe <- srv.ListenAndServe(1) }() 34 | 35 | // Dial 36 | chanOnDisconnect := make(chan disconnectResult) 37 | c, err := Dial(DialOptions{ 38 | ConnectionOptions: []ConnectionOption{WithStreamid("streamid")}, 39 | Host: "127.0.0.1", 40 | OnDisconnect: func(c *Connection, err error) { chanOnDisconnect <- disconnectResult{c: c, err: err} }, 41 | Port: 4000, 42 | }) 43 | require.NoError(t, err) 44 | defer c.Close() 45 | 46 | // Assert dial 47 | require.Equal(t, "127.0.0.1:4000", c.Addr().String()) 48 | s, err := c.Options().Streamid() 49 | require.NoError(t, err) 50 | require.Equal(t, "streamid", s) 51 | dr := <-chanOnDisconnect 52 | require.Equal(t, c, dr.c) 53 | require.ErrorIs(t, dr.err, ErrEconnlost) 54 | } 55 | -------------------------------------------------------------------------------- /pkg/connection.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | // #cgo LDFLAGS: -lsrt 4 | // #include 5 | import "C" 6 | import ( 7 | "context" 8 | "net" 9 | ) 10 | 11 | type Connection struct { 12 | addr *net.UDPAddr 13 | ctx context.Context 14 | s *Socket 15 | } 16 | 17 | func newConnection(addr *net.UDPAddr, s *Socket) *Connection { 18 | return &Connection{ 19 | addr: addr, 20 | ctx: context.Background(), 21 | s: s, 22 | } 23 | } 24 | 25 | func (c *Connection) Addr() *net.UDPAddr { 26 | return c.addr 27 | } 28 | 29 | func (c *Connection) Context() context.Context { 30 | return c.ctx 31 | } 32 | 33 | func (c *Connection) WithContext(ctx context.Context) *Connection { 34 | c.ctx = ctx 35 | return c 36 | } 37 | 38 | func (c *Connection) Close() error { 39 | return c.s.Close() 40 | } 41 | 42 | func (c *Connection) Read(b []byte) (int, error) { 43 | return c.s.ReceiveMessage(b) 44 | } 45 | 46 | func (c *Connection) Write(b []byte) (int, error) { 47 | return c.s.SendMessage(b) 48 | } 49 | 50 | func (c *Connection) RejectReason() RejectReason { 51 | return c.s.RejectReason() 52 | } 53 | 54 | // HTTP status must be [0:1000) 55 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_setrejectreason 56 | func (c *Connection) SetPredefinedRejectReason(httpStatus int) error { 57 | return c.s.SetRejectReason(RejectReason(C.SRT_REJC_PREDEFINED + C.int(httpStatus))) 58 | } 59 | 60 | // Status must be [0:1000) 61 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_setrejectreason 62 | func (c *Connection) SetUserDefinedRejectReason(status int) error { 63 | return c.s.SetRejectReason(RejectReason(C.SRT_REJC_USERDEFINED + C.int(status))) 64 | } 65 | 66 | func (c *Connection) Stats(clear, instantaneous bool) (Stats, error) { 67 | return c.s.Stats(clear, instantaneous) 68 | } 69 | -------------------------------------------------------------------------------- /pkg/connection_option.go: -------------------------------------------------------------------------------- 1 | // Code generated by astisrt using internal/cmd/generate/options. DO NOT EDIT. 2 | package astisrt 3 | 4 | import ( 5 | "fmt" 6 | "syscall" 7 | ) 8 | 9 | type ConnectionOptions struct { 10 | c *Connection 11 | } 12 | 13 | func (c *Connection) Options() *ConnectionOptions { 14 | return &ConnectionOptions{c: c} 15 | } 16 | 17 | type ConnectionOption struct { 18 | do func(s *Socket) error 19 | name string 20 | } 21 | 22 | func applyConnectionOptions(s *Socket, opts []ConnectionOption) (err error) { 23 | for _, opt := range opts { 24 | if err = opt.do(s); err != nil { 25 | err = fmt.Errorf("astisrt: applying %s option failed: %w", opt.name, err) 26 | return 27 | } 28 | } 29 | return 30 | } 31 | 32 | func (co *ConnectionOptions) Bindtodevice() (string, error) { 33 | return co.c.s.Options().Bindtodevice() 34 | } 35 | 36 | func (co *ConnectionOptions) SetBindtodevice(v string) error { 37 | return co.c.s.Options().SetBindtodevice(v) 38 | } 39 | 40 | func WithBindtodevice(v string) ConnectionOption { 41 | return ConnectionOption{ 42 | do: func(s *Socket) error { return s.Options().SetBindtodevice(v) }, 43 | name: "Bindtodevice", 44 | } 45 | } 46 | 47 | func (co *ConnectionOptions) SetCongestion(v string) error { 48 | return co.c.s.Options().SetCongestion(v) 49 | } 50 | 51 | func WithCongestion(v string) ConnectionOption { 52 | return ConnectionOption{ 53 | do: func(s *Socket) error { return s.Options().SetCongestion(v) }, 54 | name: "Congestion", 55 | } 56 | } 57 | 58 | func (co *ConnectionOptions) SetConntimeo(v int32) error { 59 | return co.c.s.Options().SetConntimeo(v) 60 | } 61 | 62 | func WithConntimeo(v int32) ConnectionOption { 63 | return ConnectionOption{ 64 | do: func(s *Socket) error { return s.Options().SetConntimeo(v) }, 65 | name: "Conntimeo", 66 | } 67 | } 68 | 69 | func (co *ConnectionOptions) Drifttracer() (bool, error) { 70 | return co.c.s.Options().Drifttracer() 71 | } 72 | 73 | func (co *ConnectionOptions) SetDrifttracer(v bool) error { 74 | return co.c.s.Options().SetDrifttracer(v) 75 | } 76 | 77 | func WithDrifttracer(v bool) ConnectionOption { 78 | return ConnectionOption{ 79 | do: func(s *Socket) error { return s.Options().SetDrifttracer(v) }, 80 | name: "Drifttracer", 81 | } 82 | } 83 | 84 | func (co *ConnectionOptions) SetEnforcedencryption(v bool) error { 85 | return co.c.s.Options().SetEnforcedencryption(v) 86 | } 87 | 88 | func WithEnforcedencryption(v bool) ConnectionOption { 89 | return ConnectionOption{ 90 | do: func(s *Socket) error { return s.Options().SetEnforcedencryption(v) }, 91 | name: "Enforcedencryption", 92 | } 93 | } 94 | 95 | func (co *ConnectionOptions) Event() (int32, error) { 96 | return co.c.s.Options().Event() 97 | } 98 | 99 | func (co *ConnectionOptions) Fc() (int32, error) { 100 | return co.c.s.Options().Fc() 101 | } 102 | 103 | func (co *ConnectionOptions) SetFc(v int32) error { 104 | return co.c.s.Options().SetFc(v) 105 | } 106 | 107 | func WithFc(v int32) ConnectionOption { 108 | return ConnectionOption{ 109 | do: func(s *Socket) error { return s.Options().SetFc(v) }, 110 | name: "Fc", 111 | } 112 | } 113 | 114 | func (co *ConnectionOptions) SetGroupconnect(v int32) error { 115 | return co.c.s.Options().SetGroupconnect(v) 116 | } 117 | 118 | func WithGroupconnect(v int32) ConnectionOption { 119 | return ConnectionOption{ 120 | do: func(s *Socket) error { return s.Options().SetGroupconnect(v) }, 121 | name: "Groupconnect", 122 | } 123 | } 124 | 125 | func (co *ConnectionOptions) SetGroupminstabletimeo(v int32) error { 126 | return co.c.s.Options().SetGroupminstabletimeo(v) 127 | } 128 | 129 | func WithGroupminstabletimeo(v int32) ConnectionOption { 130 | return ConnectionOption{ 131 | do: func(s *Socket) error { return s.Options().SetGroupminstabletimeo(v) }, 132 | name: "Groupminstabletimeo", 133 | } 134 | } 135 | 136 | func (co *ConnectionOptions) Grouptype() (int32, error) { 137 | return co.c.s.Options().Grouptype() 138 | } 139 | 140 | func (co *ConnectionOptions) Inputbw() (int64, error) { 141 | return co.c.s.Options().Inputbw() 142 | } 143 | 144 | func (co *ConnectionOptions) SetInputbw(v int64) error { 145 | return co.c.s.Options().SetInputbw(v) 146 | } 147 | 148 | func WithInputbw(v int64) ConnectionOption { 149 | return ConnectionOption{ 150 | do: func(s *Socket) error { return s.Options().SetInputbw(v) }, 151 | name: "Inputbw", 152 | } 153 | } 154 | 155 | func (co *ConnectionOptions) Iptos() (int32, error) { 156 | return co.c.s.Options().Iptos() 157 | } 158 | 159 | func (co *ConnectionOptions) SetIptos(v int32) error { 160 | return co.c.s.Options().SetIptos(v) 161 | } 162 | 163 | func WithIptos(v int32) ConnectionOption { 164 | return ConnectionOption{ 165 | do: func(s *Socket) error { return s.Options().SetIptos(v) }, 166 | name: "Iptos", 167 | } 168 | } 169 | 170 | func (co *ConnectionOptions) Ipttl() (int32, error) { 171 | return co.c.s.Options().Ipttl() 172 | } 173 | 174 | func (co *ConnectionOptions) SetIpttl(v int32) error { 175 | return co.c.s.Options().SetIpttl(v) 176 | } 177 | 178 | func WithIpttl(v int32) ConnectionOption { 179 | return ConnectionOption{ 180 | do: func(s *Socket) error { return s.Options().SetIpttl(v) }, 181 | name: "Ipttl", 182 | } 183 | } 184 | 185 | func (co *ConnectionOptions) Ipv6only() (int32, error) { 186 | return co.c.s.Options().Ipv6only() 187 | } 188 | 189 | func (co *ConnectionOptions) SetIpv6only(v int32) error { 190 | return co.c.s.Options().SetIpv6only(v) 191 | } 192 | 193 | func WithIpv6only(v int32) ConnectionOption { 194 | return ConnectionOption{ 195 | do: func(s *Socket) error { return s.Options().SetIpv6only(v) }, 196 | name: "Ipv6only", 197 | } 198 | } 199 | 200 | func (co *ConnectionOptions) Isn() (int32, error) { 201 | return co.c.s.Options().Isn() 202 | } 203 | 204 | func (co *ConnectionOptions) Kmpreannounce() (int32, error) { 205 | return co.c.s.Options().Kmpreannounce() 206 | } 207 | 208 | func (co *ConnectionOptions) SetKmpreannounce(v int32) error { 209 | return co.c.s.Options().SetKmpreannounce(v) 210 | } 211 | 212 | func WithKmpreannounce(v int32) ConnectionOption { 213 | return ConnectionOption{ 214 | do: func(s *Socket) error { return s.Options().SetKmpreannounce(v) }, 215 | name: "Kmpreannounce", 216 | } 217 | } 218 | 219 | func (co *ConnectionOptions) Kmrefreshrate() (int32, error) { 220 | return co.c.s.Options().Kmrefreshrate() 221 | } 222 | 223 | func (co *ConnectionOptions) SetKmrefreshrate(v int32) error { 224 | return co.c.s.Options().SetKmrefreshrate(v) 225 | } 226 | 227 | func WithKmrefreshrate(v int32) ConnectionOption { 228 | return ConnectionOption{ 229 | do: func(s *Socket) error { return s.Options().SetKmrefreshrate(v) }, 230 | name: "Kmrefreshrate", 231 | } 232 | } 233 | 234 | func (co *ConnectionOptions) Kmstate() (KmState, error) { 235 | return co.c.s.Options().Kmstate() 236 | } 237 | 238 | func (co *ConnectionOptions) Latency() (int32, error) { 239 | return co.c.s.Options().Latency() 240 | } 241 | 242 | func (co *ConnectionOptions) SetLatency(v int32) error { 243 | return co.c.s.Options().SetLatency(v) 244 | } 245 | 246 | func WithLatency(v int32) ConnectionOption { 247 | return ConnectionOption{ 248 | do: func(s *Socket) error { return s.Options().SetLatency(v) }, 249 | name: "Latency", 250 | } 251 | } 252 | 253 | func (co *ConnectionOptions) Linger() (syscall.Linger, error) { 254 | return co.c.s.Options().Linger() 255 | } 256 | 257 | func (co *ConnectionOptions) SetLinger(v syscall.Linger) error { 258 | return co.c.s.Options().SetLinger(v) 259 | } 260 | 261 | func WithLinger(v syscall.Linger) ConnectionOption { 262 | return ConnectionOption{ 263 | do: func(s *Socket) error { return s.Options().SetLinger(v) }, 264 | name: "Linger", 265 | } 266 | } 267 | 268 | func (co *ConnectionOptions) Lossmaxttl() (int32, error) { 269 | return co.c.s.Options().Lossmaxttl() 270 | } 271 | 272 | func (co *ConnectionOptions) SetLossmaxttl(v int32) error { 273 | return co.c.s.Options().SetLossmaxttl(v) 274 | } 275 | 276 | func WithLossmaxttl(v int32) ConnectionOption { 277 | return ConnectionOption{ 278 | do: func(s *Socket) error { return s.Options().SetLossmaxttl(v) }, 279 | name: "Lossmaxttl", 280 | } 281 | } 282 | 283 | func (co *ConnectionOptions) Maxbw() (int64, error) { 284 | return co.c.s.Options().Maxbw() 285 | } 286 | 287 | func (co *ConnectionOptions) SetMaxbw(v int64) error { 288 | return co.c.s.Options().SetMaxbw(v) 289 | } 290 | 291 | func WithMaxbw(v int64) ConnectionOption { 292 | return ConnectionOption{ 293 | do: func(s *Socket) error { return s.Options().SetMaxbw(v) }, 294 | name: "Maxbw", 295 | } 296 | } 297 | 298 | func (co *ConnectionOptions) SetMessageapi(v bool) error { 299 | return co.c.s.Options().SetMessageapi(v) 300 | } 301 | 302 | func WithMessageapi(v bool) ConnectionOption { 303 | return ConnectionOption{ 304 | do: func(s *Socket) error { return s.Options().SetMessageapi(v) }, 305 | name: "Messageapi", 306 | } 307 | } 308 | 309 | func (co *ConnectionOptions) Mininputbw() (int64, error) { 310 | return co.c.s.Options().Mininputbw() 311 | } 312 | 313 | func (co *ConnectionOptions) SetMininputbw(v int64) error { 314 | return co.c.s.Options().SetMininputbw(v) 315 | } 316 | 317 | func WithMininputbw(v int64) ConnectionOption { 318 | return ConnectionOption{ 319 | do: func(s *Socket) error { return s.Options().SetMininputbw(v) }, 320 | name: "Mininputbw", 321 | } 322 | } 323 | 324 | func (co *ConnectionOptions) Minversion() (int32, error) { 325 | return co.c.s.Options().Minversion() 326 | } 327 | 328 | func (co *ConnectionOptions) SetMinversion(v int32) error { 329 | return co.c.s.Options().SetMinversion(v) 330 | } 331 | 332 | func WithMinversion(v int32) ConnectionOption { 333 | return ConnectionOption{ 334 | do: func(s *Socket) error { return s.Options().SetMinversion(v) }, 335 | name: "Minversion", 336 | } 337 | } 338 | 339 | func (co *ConnectionOptions) Mss() (int32, error) { 340 | return co.c.s.Options().Mss() 341 | } 342 | 343 | func (co *ConnectionOptions) SetMss(v int32) error { 344 | return co.c.s.Options().SetMss(v) 345 | } 346 | 347 | func WithMss(v int32) ConnectionOption { 348 | return ConnectionOption{ 349 | do: func(s *Socket) error { return s.Options().SetMss(v) }, 350 | name: "Mss", 351 | } 352 | } 353 | 354 | func (co *ConnectionOptions) Nakreport() (bool, error) { 355 | return co.c.s.Options().Nakreport() 356 | } 357 | 358 | func (co *ConnectionOptions) SetNakreport(v bool) error { 359 | return co.c.s.Options().SetNakreport(v) 360 | } 361 | 362 | func WithNakreport(v bool) ConnectionOption { 363 | return ConnectionOption{ 364 | do: func(s *Socket) error { return s.Options().SetNakreport(v) }, 365 | name: "Nakreport", 366 | } 367 | } 368 | 369 | func (co *ConnectionOptions) Oheadbw() (int32, error) { 370 | return co.c.s.Options().Oheadbw() 371 | } 372 | 373 | func (co *ConnectionOptions) SetOheadbw(v int32) error { 374 | return co.c.s.Options().SetOheadbw(v) 375 | } 376 | 377 | func WithOheadbw(v int32) ConnectionOption { 378 | return ConnectionOption{ 379 | do: func(s *Socket) error { return s.Options().SetOheadbw(v) }, 380 | name: "Oheadbw", 381 | } 382 | } 383 | 384 | func (co *ConnectionOptions) Packetfilter() (string, error) { 385 | return co.c.s.Options().Packetfilter() 386 | } 387 | 388 | func (co *ConnectionOptions) SetPacketfilter(v string) error { 389 | return co.c.s.Options().SetPacketfilter(v) 390 | } 391 | 392 | func WithPacketfilter(v string) ConnectionOption { 393 | return ConnectionOption{ 394 | do: func(s *Socket) error { return s.Options().SetPacketfilter(v) }, 395 | name: "Packetfilter", 396 | } 397 | } 398 | 399 | func (co *ConnectionOptions) SetPassphrase(v string) error { 400 | return co.c.s.Options().SetPassphrase(v) 401 | } 402 | 403 | func WithPassphrase(v string) ConnectionOption { 404 | return ConnectionOption{ 405 | do: func(s *Socket) error { return s.Options().SetPassphrase(v) }, 406 | name: "Passphrase", 407 | } 408 | } 409 | 410 | func (co *ConnectionOptions) SetPayloadsize(v int32) error { 411 | return co.c.s.Options().SetPayloadsize(v) 412 | } 413 | 414 | func WithPayloadsize(v int32) ConnectionOption { 415 | return ConnectionOption{ 416 | do: func(s *Socket) error { return s.Options().SetPayloadsize(v) }, 417 | name: "Payloadsize", 418 | } 419 | } 420 | 421 | func (co *ConnectionOptions) Pbkeylen() (int32, error) { 422 | return co.c.s.Options().Pbkeylen() 423 | } 424 | 425 | func (co *ConnectionOptions) SetPbkeylen(v int32) error { 426 | return co.c.s.Options().SetPbkeylen(v) 427 | } 428 | 429 | func WithPbkeylen(v int32) ConnectionOption { 430 | return ConnectionOption{ 431 | do: func(s *Socket) error { return s.Options().SetPbkeylen(v) }, 432 | name: "Pbkeylen", 433 | } 434 | } 435 | 436 | func (co *ConnectionOptions) Peeridletimeo() (int32, error) { 437 | return co.c.s.Options().Peeridletimeo() 438 | } 439 | 440 | func (co *ConnectionOptions) SetPeeridletimeo(v int32) error { 441 | return co.c.s.Options().SetPeeridletimeo(v) 442 | } 443 | 444 | func WithPeeridletimeo(v int32) ConnectionOption { 445 | return ConnectionOption{ 446 | do: func(s *Socket) error { return s.Options().SetPeeridletimeo(v) }, 447 | name: "Peeridletimeo", 448 | } 449 | } 450 | 451 | func (co *ConnectionOptions) Peerlatency() (int32, error) { 452 | return co.c.s.Options().Peerlatency() 453 | } 454 | 455 | func (co *ConnectionOptions) SetPeerlatency(v int32) error { 456 | return co.c.s.Options().SetPeerlatency(v) 457 | } 458 | 459 | func WithPeerlatency(v int32) ConnectionOption { 460 | return ConnectionOption{ 461 | do: func(s *Socket) error { return s.Options().SetPeerlatency(v) }, 462 | name: "Peerlatency", 463 | } 464 | } 465 | 466 | func (co *ConnectionOptions) Peerversion() (int32, error) { 467 | return co.c.s.Options().Peerversion() 468 | } 469 | 470 | func (co *ConnectionOptions) Rcvbuf() (int32, error) { 471 | return co.c.s.Options().Rcvbuf() 472 | } 473 | 474 | func (co *ConnectionOptions) SetRcvbuf(v int32) error { 475 | return co.c.s.Options().SetRcvbuf(v) 476 | } 477 | 478 | func WithRcvbuf(v int32) ConnectionOption { 479 | return ConnectionOption{ 480 | do: func(s *Socket) error { return s.Options().SetRcvbuf(v) }, 481 | name: "Rcvbuf", 482 | } 483 | } 484 | 485 | func (co *ConnectionOptions) Rcvdata() (int32, error) { 486 | return co.c.s.Options().Rcvdata() 487 | } 488 | 489 | func (co *ConnectionOptions) Rcvkmstate() (int32, error) { 490 | return co.c.s.Options().Rcvkmstate() 491 | } 492 | 493 | func (co *ConnectionOptions) Rcvlatency() (int32, error) { 494 | return co.c.s.Options().Rcvlatency() 495 | } 496 | 497 | func (co *ConnectionOptions) SetRcvlatency(v int32) error { 498 | return co.c.s.Options().SetRcvlatency(v) 499 | } 500 | 501 | func WithRcvlatency(v int32) ConnectionOption { 502 | return ConnectionOption{ 503 | do: func(s *Socket) error { return s.Options().SetRcvlatency(v) }, 504 | name: "Rcvlatency", 505 | } 506 | } 507 | 508 | func (co *ConnectionOptions) Rcvtimeo() (int32, error) { 509 | return co.c.s.Options().Rcvtimeo() 510 | } 511 | 512 | func (co *ConnectionOptions) SetRcvtimeo(v int32) error { 513 | return co.c.s.Options().SetRcvtimeo(v) 514 | } 515 | 516 | func WithRcvtimeo(v int32) ConnectionOption { 517 | return ConnectionOption{ 518 | do: func(s *Socket) error { return s.Options().SetRcvtimeo(v) }, 519 | name: "Rcvtimeo", 520 | } 521 | } 522 | 523 | func (co *ConnectionOptions) Rendezvous() (bool, error) { 524 | return co.c.s.Options().Rendezvous() 525 | } 526 | 527 | func (co *ConnectionOptions) SetRendezvous(v bool) error { 528 | return co.c.s.Options().SetRendezvous(v) 529 | } 530 | 531 | func WithRendezvous(v bool) ConnectionOption { 532 | return ConnectionOption{ 533 | do: func(s *Socket) error { return s.Options().SetRendezvous(v) }, 534 | name: "Rendezvous", 535 | } 536 | } 537 | 538 | func (co *ConnectionOptions) Retransmitalgo() (int32, error) { 539 | return co.c.s.Options().Retransmitalgo() 540 | } 541 | 542 | func (co *ConnectionOptions) SetRetransmitalgo(v int32) error { 543 | return co.c.s.Options().SetRetransmitalgo(v) 544 | } 545 | 546 | func WithRetransmitalgo(v int32) ConnectionOption { 547 | return ConnectionOption{ 548 | do: func(s *Socket) error { return s.Options().SetRetransmitalgo(v) }, 549 | name: "Retransmitalgo", 550 | } 551 | } 552 | 553 | func (co *ConnectionOptions) Reuseaddr() (bool, error) { 554 | return co.c.s.Options().Reuseaddr() 555 | } 556 | 557 | func (co *ConnectionOptions) SetReuseaddr(v bool) error { 558 | return co.c.s.Options().SetReuseaddr(v) 559 | } 560 | 561 | func WithReuseaddr(v bool) ConnectionOption { 562 | return ConnectionOption{ 563 | do: func(s *Socket) error { return s.Options().SetReuseaddr(v) }, 564 | name: "Reuseaddr", 565 | } 566 | } 567 | 568 | func (co *ConnectionOptions) SetSender(v bool) error { 569 | return co.c.s.Options().SetSender(v) 570 | } 571 | 572 | func WithSender(v bool) ConnectionOption { 573 | return ConnectionOption{ 574 | do: func(s *Socket) error { return s.Options().SetSender(v) }, 575 | name: "Sender", 576 | } 577 | } 578 | 579 | func (co *ConnectionOptions) Sndbuf() (int32, error) { 580 | return co.c.s.Options().Sndbuf() 581 | } 582 | 583 | func (co *ConnectionOptions) SetSndbuf(v int32) error { 584 | return co.c.s.Options().SetSndbuf(v) 585 | } 586 | 587 | func WithSndbuf(v int32) ConnectionOption { 588 | return ConnectionOption{ 589 | do: func(s *Socket) error { return s.Options().SetSndbuf(v) }, 590 | name: "Sndbuf", 591 | } 592 | } 593 | 594 | func (co *ConnectionOptions) Snddata() (int32, error) { 595 | return co.c.s.Options().Snddata() 596 | } 597 | 598 | func (co *ConnectionOptions) SetSnddropdelay(v int32) error { 599 | return co.c.s.Options().SetSnddropdelay(v) 600 | } 601 | 602 | func WithSnddropdelay(v int32) ConnectionOption { 603 | return ConnectionOption{ 604 | do: func(s *Socket) error { return s.Options().SetSnddropdelay(v) }, 605 | name: "Snddropdelay", 606 | } 607 | } 608 | 609 | func (co *ConnectionOptions) Sndkmstate() (int32, error) { 610 | return co.c.s.Options().Sndkmstate() 611 | } 612 | 613 | func (co *ConnectionOptions) Sndtimeo() (int32, error) { 614 | return co.c.s.Options().Sndtimeo() 615 | } 616 | 617 | func (co *ConnectionOptions) SetSndtimeo(v int32) error { 618 | return co.c.s.Options().SetSndtimeo(v) 619 | } 620 | 621 | func WithSndtimeo(v int32) ConnectionOption { 622 | return ConnectionOption{ 623 | do: func(s *Socket) error { return s.Options().SetSndtimeo(v) }, 624 | name: "Sndtimeo", 625 | } 626 | } 627 | 628 | func (co *ConnectionOptions) State() (int32, error) { 629 | return co.c.s.Options().State() 630 | } 631 | 632 | func (co *ConnectionOptions) Streamid() (string, error) { 633 | return co.c.s.Options().Streamid() 634 | } 635 | 636 | func (co *ConnectionOptions) SetStreamid(v string) error { 637 | return co.c.s.Options().SetStreamid(v) 638 | } 639 | 640 | func WithStreamid(v string) ConnectionOption { 641 | return ConnectionOption{ 642 | do: func(s *Socket) error { return s.Options().SetStreamid(v) }, 643 | name: "Streamid", 644 | } 645 | } 646 | 647 | func (co *ConnectionOptions) Tlpktdrop() (bool, error) { 648 | return co.c.s.Options().Tlpktdrop() 649 | } 650 | 651 | func (co *ConnectionOptions) SetTlpktdrop(v bool) error { 652 | return co.c.s.Options().SetTlpktdrop(v) 653 | } 654 | 655 | func WithTlpktdrop(v bool) ConnectionOption { 656 | return ConnectionOption{ 657 | do: func(s *Socket) error { return s.Options().SetTlpktdrop(v) }, 658 | name: "Tlpktdrop", 659 | } 660 | } 661 | 662 | func (co *ConnectionOptions) SetTranstype(v Transtype) error { 663 | return co.c.s.Options().SetTranstype(v) 664 | } 665 | 666 | func WithTranstype(v Transtype) ConnectionOption { 667 | return ConnectionOption{ 668 | do: func(s *Socket) error { return s.Options().SetTranstype(v) }, 669 | name: "Transtype", 670 | } 671 | } 672 | 673 | func (co *ConnectionOptions) SetTsbpdmode(v bool) error { 674 | return co.c.s.Options().SetTsbpdmode(v) 675 | } 676 | 677 | func WithTsbpdmode(v bool) ConnectionOption { 678 | return ConnectionOption{ 679 | do: func(s *Socket) error { return s.Options().SetTsbpdmode(v) }, 680 | name: "Tsbpdmode", 681 | } 682 | } 683 | 684 | func (co *ConnectionOptions) UdpRcvbuf() (int32, error) { 685 | return co.c.s.Options().UdpRcvbuf() 686 | } 687 | 688 | func (co *ConnectionOptions) SetUdpRcvbuf(v int32) error { 689 | return co.c.s.Options().SetUdpRcvbuf(v) 690 | } 691 | 692 | func WithUdpRcvbuf(v int32) ConnectionOption { 693 | return ConnectionOption{ 694 | do: func(s *Socket) error { return s.Options().SetUdpRcvbuf(v) }, 695 | name: "UdpRcvbuf", 696 | } 697 | } 698 | 699 | func (co *ConnectionOptions) UdpSndbuf() (int32, error) { 700 | return co.c.s.Options().UdpSndbuf() 701 | } 702 | 703 | func (co *ConnectionOptions) SetUdpSndbuf(v int32) error { 704 | return co.c.s.Options().SetUdpSndbuf(v) 705 | } 706 | 707 | func WithUdpSndbuf(v int32) ConnectionOption { 708 | return ConnectionOption{ 709 | do: func(s *Socket) error { return s.Options().SetUdpSndbuf(v) }, 710 | name: "UdpSndbuf", 711 | } 712 | } 713 | 714 | func (co *ConnectionOptions) Version() (int32, error) { 715 | return co.c.s.Options().Version() 716 | } 717 | -------------------------------------------------------------------------------- /pkg/error.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | // #cgo LDFLAGS: -lsrt 4 | // #include 5 | import "C" 6 | import ( 7 | "sync" 8 | "syscall" 9 | ) 10 | 11 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#diagnostics-1 12 | 13 | type Error struct { 14 | srtErrno int 15 | sysErrno syscall.Errno 16 | } 17 | 18 | func newError(srtErrno, sysErrno C.int) error { 19 | return Error{ 20 | srtErrno: int(srtErrno), 21 | sysErrno: syscall.Errno(sysErrno), 22 | } 23 | } 24 | 25 | var ( 26 | ErrEunknown = newError(C.SRT_EUNKNOWN, 0) 27 | ErrSuccess = newError(C.SRT_SUCCESS, 0) 28 | ErrEconnsetup = newError(C.SRT_ECONNSETUP, 0) 29 | ErrEnoserver = newError(C.SRT_ENOSERVER, 0) 30 | ErrEconnrej = newError(C.SRT_ECONNREJ, 0) 31 | ErrEsockfail = newError(C.SRT_ESOCKFAIL, 0) 32 | ErrEsecfail = newError(C.SRT_ESECFAIL, 0) 33 | ErrEsclosed = newError(C.SRT_ESCLOSED, 0) 34 | ErrEconnfail = newError(C.SRT_ECONNFAIL, 0) 35 | ErrEconnlost = newError(C.SRT_ECONNLOST, 0) 36 | ErrEnoconn = newError(C.SRT_ENOCONN, 0) 37 | ErrEresource = newError(C.SRT_ERESOURCE, 0) 38 | ErrEthread = newError(C.SRT_ETHREAD, 0) 39 | ErrEnobuf = newError(C.SRT_ENOBUF, 0) 40 | ErrEsysobj = newError(C.SRT_ESYSOBJ, 0) 41 | ErrEfile = newError(C.SRT_EFILE, 0) 42 | ErrEinvrdoff = newError(C.SRT_EINVRDOFF, 0) 43 | ErrErdperm = newError(C.SRT_ERDPERM, 0) 44 | ErrEinvwroff = newError(C.SRT_EINVWROFF, 0) 45 | ErrEwrperm = newError(C.SRT_EWRPERM, 0) 46 | ErrEinvop = newError(C.SRT_EINVOP, 0) 47 | ErrEboundsock = newError(C.SRT_EBOUNDSOCK, 0) 48 | ErrEconnsock = newError(C.SRT_ECONNSOCK, 0) 49 | ErrEinvparam = newError(C.SRT_EINVPARAM, 0) 50 | ErrEinvsock = newError(C.SRT_EINVSOCK, 0) 51 | ErrEunboundsock = newError(C.SRT_EUNBOUNDSOCK, 0) 52 | ErrEnolisten = newError(C.SRT_ENOLISTEN, 0) 53 | ErrErdvnoserv = newError(C.SRT_ERDVNOSERV, 0) 54 | ErrErdvunbound = newError(C.SRT_ERDVUNBOUND, 0) 55 | ErrEinvalmsgapi = newError(C.SRT_EINVALMSGAPI, 0) 56 | ErrEinvalbufferapi = newError(C.SRT_EINVALBUFFERAPI, 0) 57 | ErrEduplisten = newError(C.SRT_EDUPLISTEN, 0) 58 | ErrElargemsg = newError(C.SRT_ELARGEMSG, 0) 59 | ErrEinvpollid = newError(C.SRT_EINVPOLLID, 0) 60 | ErrEpollempty = newError(C.SRT_EPOLLEMPTY, 0) 61 | ErrEbindconflict = newError(C.SRT_EBINDCONFLICT, 0) 62 | ErrEasyncfail = newError(C.SRT_EASYNCFAIL, 0) 63 | ErrEasyncsnd = newError(C.SRT_EASYNCSND, 0) 64 | ErrEasyncrcv = newError(C.SRT_EASYNCRCV, 0) 65 | ErrEtimeout = newError(C.SRT_ETIMEOUT, 0) 66 | ErrEcongest = newError(C.SRT_ECONGEST, 0) 67 | ErrEpeererr = newError(C.SRT_EPEERERR, 0) 68 | ) 69 | 70 | var errorStrMutex = &sync.Mutex{} 71 | 72 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_strerror 73 | func (err Error) Error() string { 74 | errorStrMutex.Lock() 75 | defer errorStrMutex.Unlock() 76 | return "astisrt: " + C.GoString(C.srt_strerror(C.int(err.srtErrno), C.int(err.sysErrno))) 77 | } 78 | 79 | func (err Error) Is(e error) bool { 80 | a, ok := e.(Error) 81 | if !ok { 82 | return false 83 | } 84 | return int(a.srtErrno) == int(err.srtErrno) 85 | } 86 | 87 | func (err Error) Unwrap() error { 88 | if err.sysErrno != 0 { 89 | return err.sysErrno 90 | } 91 | return nil 92 | } 93 | 94 | // TODO Add Temporary, Timeout, etc. functions 95 | -------------------------------------------------------------------------------- /pkg/error_test.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | import ( 4 | "errors" 5 | "syscall" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestError(t *testing.T) { 12 | err := newError(_Ctype_int(ErrEconnsetup.(Error).srtErrno), 3) 13 | require.Equal(t, "astisrt: Connection setup failure", err.Error()) 14 | require.True(t, errors.Is(err, ErrEconnsetup)) 15 | unwrap := errors.Unwrap(err) 16 | require.NotNil(t, unwrap) 17 | require.True(t, errors.Is(unwrap, syscall.Errno(3))) 18 | } 19 | -------------------------------------------------------------------------------- /pkg/log.c: -------------------------------------------------------------------------------- 1 | #include "_cgo_export.h" 2 | #include 3 | 4 | void astisrt_handlelog(void* opaque, int level, const char* file, int line, const char* area, const char* message) { 5 | go2cHandleLog(level, (char*) file, line, (char*) area, (char*) message); 6 | } 7 | 8 | void astisrt_setloghandler(int b) { 9 | if (b == 1) { 10 | srt_setloghandler(NULL, astisrt_handlelog); 11 | } else { 12 | srt_setloghandler(NULL, NULL); 13 | } 14 | } -------------------------------------------------------------------------------- /pkg/log.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | // #cgo LDFLAGS: -lsrt 4 | // #include 5 | // #include "log.h" 6 | import "C" 7 | import "sync" 8 | 9 | type LogHandler func(l LogLevel, file, area, msg string, line int) 10 | 11 | var ( 12 | logHandler LogHandler 13 | logHandlerMutex = &sync.Mutex{} 14 | ) 15 | 16 | type LogLevel int 17 | 18 | const ( 19 | LogLevelCritical = LogLevel(C.LOG_CRIT) 20 | LogLevelDebug = LogLevel(C.LOG_DEBUG) 21 | LogLevelError = LogLevel(C.LOG_ERR) 22 | LogLevelNotice = LogLevel(C.LOG_NOTICE) 23 | LogLevelWarning = LogLevel(C.LOG_WARNING) 24 | ) 25 | 26 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_setloglevel 27 | func SetLogLevel(l LogLevel) { 28 | C.srt_setloglevel(C.int(l)) 29 | } 30 | 31 | //export go2cHandleLog 32 | func go2cHandleLog(ll C.int, file *C.char, line C.int, area, msg *C.char) { 33 | logHandlerMutex.Lock() 34 | h := logHandler 35 | logHandlerMutex.Unlock() 36 | if h == nil { 37 | return 38 | } 39 | h(LogLevel(ll), C.GoString(file), C.GoString(area), C.GoString(msg), int(line)) 40 | } 41 | 42 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_setloghandler 43 | func SetLogHandler(h LogHandler) { 44 | logHandlerMutex.Lock() 45 | logHandler = h 46 | logHandlerMutex.Unlock() 47 | C.astisrt_setloghandler(boolToCInt(h != nil)) 48 | } 49 | -------------------------------------------------------------------------------- /pkg/log.h: -------------------------------------------------------------------------------- 1 | void astisrt_setloghandler(int b); -------------------------------------------------------------------------------- /pkg/log_test.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestLog(t *testing.T) { 10 | var msgs []string 11 | SetLogHandler(func(l LogLevel, file, area, msg string, line int) { msgs = append(msgs, msg) }) 12 | defer SetLogHandler(nil) 13 | 14 | SetLogLevel(LogLevelDebug) 15 | s1, err := NewSocket() 16 | require.NoError(t, err) 17 | defer s1.Close() 18 | require.True(t, len(msgs) > 0) 19 | 20 | msgs = []string{} 21 | SetLogLevel(LogLevelWarning) 22 | s2, err := NewSocket() 23 | require.NoError(t, err) 24 | defer s2.Close() 25 | require.True(t, len(msgs) == 0) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/reject_reason.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | // #cgo LDFLAGS: -lsrt 4 | // #include 5 | import "C" 6 | 7 | type RejectReason int 8 | 9 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_rejectreason_str 10 | func (r RejectReason) String() string { 11 | return C.GoString(C.srt_rejectreason_str(C.int(r))) 12 | } 13 | 14 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_getrejectreason 15 | func (s *Socket) RejectReason() RejectReason { 16 | return s.rr 17 | } 18 | 19 | // Reject reason must be [1000:3000) 20 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_setrejectreason 21 | func (s *Socket) SetRejectReason(r RejectReason) error { 22 | return cSetRejectReason(s.u, C.int(r)) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/server.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "sync" 9 | ) 10 | 11 | var ( 12 | ErrServerClosed = errors.New("astisrt: server closed") 13 | ) 14 | 15 | type ServerHandlerFunc func(c *Connection) 16 | 17 | func (f ServerHandlerFunc) ServeSRT(c *Connection) { f(c) } 18 | 19 | type ServerHandler interface { 20 | ServeSRT(c *Connection) 21 | } 22 | 23 | var NopServerHandler = nopServerHandler{} 24 | 25 | type nopServerHandler struct{} 26 | 27 | func (h nopServerHandler) ServeSRT(c *Connection) {} 28 | 29 | type Server struct { 30 | accepted map[*Socket]*Connection 31 | closed bool 32 | h ServerHandler 33 | handling map[*Connection]bool 34 | host string 35 | m *sync.Mutex 36 | port uint16 37 | s *Socket 38 | shutdownChan chan bool 39 | } 40 | 41 | type ServerOnBeforeAccept func(c *Connection, version int, streamID string) bool 42 | 43 | type ServerOptions struct { 44 | // Connection options 45 | ConnectionOptions []ConnectionOption 46 | 47 | // Handler to invoke on accepted connections 48 | // Defaults to NopServerHandler 49 | Handler ServerHandler 50 | 51 | // Host to bind with 52 | Host string 53 | 54 | // Callback executed before accepting a new connection. 55 | // Return true if connection should be accepted, false otherwise. 56 | OnBeforeAccept ServerOnBeforeAccept 57 | 58 | // Port to bind with 59 | Port uint16 60 | } 61 | 62 | func (srv *Server) listenCallback(cb ServerOnBeforeAccept) ListenCallback { 63 | return func(s *Socket, version int, addr *net.UDPAddr, streamID string) bool { 64 | // Create connection 65 | c := newConnection(addr, s) 66 | 67 | // Callback 68 | if ok := cb(c, version, streamID); !ok { 69 | return false 70 | } 71 | 72 | // Store connection 73 | srv.m.Lock() 74 | srv.accepted[s] = c 75 | srv.m.Unlock() 76 | return true 77 | } 78 | } 79 | 80 | func NewServer(o ServerOptions) (srv *Server, err error) { 81 | // Create server 82 | srv = &Server{ 83 | accepted: make(map[*Socket]*Connection), 84 | h: NopServerHandler, 85 | handling: make(map[*Connection]bool), 86 | host: o.Host, 87 | m: &sync.Mutex{}, 88 | port: o.Port, 89 | } 90 | 91 | // Update handler 92 | if o.Handler != nil { 93 | srv.h = o.Handler 94 | } 95 | 96 | // Create socket 97 | if srv.s, err = NewSocket(); err != nil { 98 | err = fmt.Errorf("astisrt: creating socket failed: %w", err) 99 | return 100 | } 101 | 102 | // Apply connection options 103 | if err = applyConnectionOptions(srv.s, o.ConnectionOptions); err != nil { 104 | err = fmt.Errorf("astisrt: applying connection options failed: %w", err) 105 | return 106 | } 107 | 108 | // Set callbacks 109 | if o.OnBeforeAccept != nil { 110 | if err = srv.s.SetListenCallback(srv.listenCallback(o.OnBeforeAccept)); err != nil { 111 | err = fmt.Errorf("astisrt: setting listen callback failed: %w", err) 112 | return 113 | } 114 | } 115 | return 116 | } 117 | 118 | func (srv *Server) close(shutdown bool) (err error) { 119 | // Lock 120 | srv.m.Lock() 121 | defer srv.m.Unlock() 122 | 123 | // Server is already closed 124 | if srv.closed { 125 | return nil 126 | } 127 | 128 | // Server is closed 129 | srv.closed = true 130 | 131 | // Create shutdown chan 132 | if shutdown && len(srv.handling) > 0 { 133 | srv.shutdownChan = make(chan bool) 134 | } 135 | 136 | // Close connections being handled 137 | for c := range srv.handling { 138 | if err = c.Close(); err != nil { 139 | err = fmt.Errorf("astisrt: closing connection being handled failed: %w", err) 140 | return 141 | } 142 | } 143 | 144 | // Close server socket 145 | if err = srv.s.Close(); err != nil { 146 | err = fmt.Errorf("astisrt: closing server socket failed: %w", err) 147 | return 148 | } 149 | return 150 | } 151 | 152 | func (srv *Server) Close() error { 153 | return srv.close(false) 154 | } 155 | 156 | func (srv *Server) ListenAndServe(backlog int) (err error) { 157 | // Server is closed 158 | srv.m.Lock() 159 | if srv.closed { 160 | srv.m.Unlock() 161 | return ErrServerClosed 162 | } 163 | srv.m.Unlock() 164 | 165 | // Bind 166 | if err = srv.s.Bind(srv.host, srv.port); err != nil { 167 | err = fmt.Errorf("astisrt: binding failed: %w", err) 168 | return 169 | } 170 | 171 | // Listen 172 | if err = srv.s.Listen(backlog); err != nil { 173 | err = fmt.Errorf("astisrt: listening failed: %w", err) 174 | return 175 | } 176 | 177 | // Loop 178 | for { 179 | // Accept 180 | var s *Socket 181 | var addr *net.UDPAddr 182 | if s, addr, err = srv.s.Accept(); err != nil { 183 | // Server is closed 184 | srv.m.Lock() 185 | if srv.closed { 186 | srv.m.Unlock() 187 | return ErrServerClosed 188 | } 189 | srv.m.Unlock() 190 | 191 | // TODO Retry on temporary error (e.g. http.Server) 192 | 193 | // Fatal error 194 | err = fmt.Errorf("astisrt: accepting failed: %w", err) 195 | return 196 | } 197 | 198 | // Get connection 199 | srv.m.Lock() 200 | c, ok := srv.accepted[s] 201 | if !ok { 202 | c = newConnection(addr, s) 203 | } else { 204 | delete(srv.accepted, s) 205 | } 206 | srv.m.Unlock() 207 | 208 | // Handle 209 | go srv.handleAcceptedConnection(c) 210 | } 211 | } 212 | 213 | func (srv *Server) handleAcceptedConnection(c *Connection) { 214 | // Store connection 215 | srv.m.Lock() 216 | if srv.closed { 217 | srv.m.Unlock() 218 | return 219 | } 220 | srv.handling[c] = true 221 | srv.m.Unlock() 222 | 223 | // Make sure connection is properly closed when handling is done 224 | defer func() { 225 | // Lock 226 | srv.m.Lock() 227 | defer srv.m.Unlock() 228 | 229 | // Close connection 230 | c.Close() 231 | 232 | // Remove connection 233 | delete(srv.handling, c) 234 | 235 | // Close shutdown chan 236 | if srv.shutdownChan != nil && len(srv.handling) == 0 { 237 | close(srv.shutdownChan) 238 | } 239 | }() 240 | 241 | // Serve 242 | srv.h.ServeSRT(c) 243 | } 244 | 245 | func (srv *Server) Shutdown(ctx context.Context) (err error) { 246 | // Close 247 | if err = srv.close(true); err != nil { 248 | err = fmt.Errorf("astisrt: closing failed: %w", err) 249 | return 250 | } 251 | 252 | // Get shutdown chan 253 | srv.m.Lock() 254 | c := srv.shutdownChan 255 | if c == nil { 256 | srv.m.Unlock() 257 | return 258 | } 259 | srv.m.Unlock() 260 | 261 | // Wait for either context to be done or shutdown chan to be closed 262 | select { 263 | case <-ctx.Done(): 264 | return ctx.Err() 265 | case <-c: 266 | return 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /pkg/server_test.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestServerConnectionOptions(t *testing.T) { 13 | // Create server 14 | latency := int32(42) 15 | srv, err := NewServer(ServerOptions{ 16 | ConnectionOptions: []ConnectionOption{WithLatency(latency)}, 17 | }) 18 | require.NoError(t, err) 19 | defer srv.Close() 20 | 21 | // Assert options 22 | v, err := srv.s.Options().Latency() 23 | require.NoError(t, err) 24 | require.Equal(t, latency, v) 25 | } 26 | 27 | func TestServerOnBeforeAccept(t *testing.T) { 28 | // Quiet logs 29 | SetLogHandler(func(l LogLevel, file, area, msg string, line int) {}) 30 | defer SetLogHandler(nil) 31 | 32 | // Create server 33 | srv, err := NewServer(ServerOptions{ 34 | Host: "127.0.0.1", 35 | OnBeforeAccept: func(c *Connection, version int, streamID string) bool { 36 | if streamID == "invalid" { 37 | c.SetPredefinedRejectReason(http.StatusNotFound) //nolint: errcheck 38 | return false 39 | } 40 | return true 41 | }, 42 | Port: 4000, 43 | }) 44 | require.NoError(t, err) 45 | chanListenAndServe := make(chan error) 46 | defer func() { 47 | srv.Close() 48 | <-chanListenAndServe 49 | }() 50 | 51 | // Listen and serve 52 | go func() { chanListenAndServe <- srv.ListenAndServe(1) }() 53 | 54 | // Dial with invalid stream id 55 | c1, err := Dial(DialOptions{ 56 | ConnectionOptions: []ConnectionOption{WithStreamid("invalid")}, 57 | Host: "127.0.0.1", 58 | Port: 4000, 59 | }) 60 | require.ErrorIs(t, err, ErrEconnrej) 61 | require.Equal(t, RejectReason(1404), c1.RejectReason()) 62 | require.Equal(t, "Application-defined rejection reason", c1.RejectReason().String()) 63 | 64 | // Dial with valid stream id 65 | c2, err := Dial(DialOptions{ 66 | ConnectionOptions: []ConnectionOption{WithStreamid("valid")}, 67 | Host: "127.0.0.1", 68 | Port: 4000, 69 | }) 70 | require.NoError(t, err) 71 | c2.Close() 72 | } 73 | 74 | type ctxKey string 75 | 76 | const ( 77 | ctxKeyTest ctxKey = "test" 78 | ) 79 | 80 | func TestServerHandler(t *testing.T) { 81 | // Quiet logs 82 | SetLogHandler(func(l LogLevel, file, area, msg string, line int) {}) 83 | defer SetLogHandler(nil) 84 | 85 | // Create server 86 | ctxValue := "test" 87 | chanCtxValue := make(chan string) 88 | srv, err := NewServer(ServerOptions{ 89 | Handler: ServerHandlerFunc(func(c *Connection) { 90 | var s string 91 | if v := c.Context().Value(ctxKeyTest); v != nil { 92 | s = v.(string) 93 | } 94 | chanCtxValue <- s 95 | }), 96 | Host: "127.0.0.1", 97 | OnBeforeAccept: func(c *Connection, version int, streamID string) bool { 98 | *c = *(c.WithContext(context.WithValue(context.Background(), ctxKeyTest, ctxValue))) 99 | return true 100 | }, 101 | Port: 4000, 102 | }) 103 | require.NoError(t, err) 104 | chanListenAndServe := make(chan error) 105 | defer func() { 106 | srv.Close() 107 | <-chanListenAndServe 108 | }() 109 | 110 | // Listen and serve 111 | go func() { chanListenAndServe <- srv.ListenAndServe(1) }() 112 | 113 | // Dial 114 | c, err := Dial(DialOptions{ 115 | Host: "127.0.0.1", 116 | Port: 4000, 117 | }) 118 | require.NoError(t, err) 119 | defer c.Close() 120 | 121 | // Assert 122 | require.Equal(t, ctxValue, <-chanCtxValue) 123 | } 124 | 125 | type readResult struct { 126 | b []byte 127 | err error 128 | } 129 | 130 | func TestServerReadWriteStats(t *testing.T) { 131 | // Quiet logs 132 | SetLogHandler(func(l LogLevel, file, area, msg string, line int) {}) 133 | defer SetLogHandler(nil) 134 | 135 | // Create server 136 | chanRead := make(chan readResult) 137 | srv, err := NewServer(ServerOptions{ 138 | Handler: ServerHandlerFunc(func(c *Connection) { 139 | b := make([]byte, 1500) 140 | n, err := c.Read(b) 141 | if err != nil { 142 | chanRead <- readResult{err: err} 143 | } else { 144 | chanRead <- readResult{b: b[:n]} 145 | } 146 | <-chanRead 147 | }), 148 | Host: "127.0.0.1", 149 | Port: 4000, 150 | }) 151 | require.NoError(t, err) 152 | chanListenAndServe := make(chan error) 153 | defer func() { 154 | srv.Close() 155 | <-chanListenAndServe 156 | }() 157 | 158 | // Listen and serve 159 | go func() { chanListenAndServe <- srv.ListenAndServe(1) }() 160 | 161 | // Dial 162 | c, err := Dial(DialOptions{ 163 | Host: "127.0.0.1", 164 | Port: 4000, 165 | }) 166 | require.NoError(t, err) 167 | defer c.Close() 168 | 169 | // Write 170 | msg := "test message" 171 | n, err := c.Write([]byte(msg)) 172 | require.NoError(t, err) 173 | require.Equal(t, 12, n) 174 | 175 | // Wait for read 176 | rr := <-chanRead 177 | 178 | // Assert 179 | require.NoError(t, rr.err) 180 | require.Equal(t, msg, string(rr.b)) 181 | 182 | // Get stats 183 | s, err := c.Stats(true, false) 184 | require.NoError(t, err) 185 | require.True(t, s.ByteSent() > 0) 186 | require.True(t, s.ByteSentTotal() > 0) 187 | s, err = c.Stats(true, false) 188 | require.NoError(t, err) 189 | require.False(t, s.ByteSent() > 0) 190 | 191 | // Release read 192 | chanRead <- readResult{} 193 | } 194 | 195 | func TestServerShutdownSuccess(t *testing.T) { 196 | // Quiet logs 197 | SetLogHandler(func(l LogLevel, file, area, msg string, line int) {}) 198 | defer SetLogHandler(nil) 199 | 200 | // Create server 201 | var handled bool 202 | chanHandle := make(chan bool) 203 | srv, err := NewServer(ServerOptions{ 204 | Handler: ServerHandlerFunc(func(c *Connection) { handled = <-chanHandle }), 205 | Host: "127.0.0.1", 206 | Port: 4000, 207 | }) 208 | require.NoError(t, err) 209 | chanListenAndServe := make(chan error) 210 | defer func() { 211 | srv.Close() 212 | <-chanListenAndServe 213 | }() 214 | 215 | // Listen and serve 216 | go func() { chanListenAndServe <- srv.ListenAndServe(1) }() 217 | 218 | // Dial 219 | c, err := Dial(DialOptions{ 220 | Host: "127.0.0.1", 221 | Port: 4000, 222 | }) 223 | require.NoError(t, err) 224 | defer c.Close() 225 | 226 | // Shutdown 227 | go func() { 228 | time.Sleep(100 * time.Millisecond) 229 | chanHandle <- true 230 | }() 231 | err = srv.Shutdown(context.Background()) 232 | require.NoError(t, err) 233 | require.True(t, handled) 234 | } 235 | 236 | func TestServerShutdownDeadlineExceeded(t *testing.T) { 237 | // Quiet logs 238 | SetLogHandler(func(l LogLevel, file, area, msg string, line int) {}) 239 | defer SetLogHandler(nil) 240 | 241 | // Create server 242 | chanHandle := make(chan bool) 243 | srv, err := NewServer(ServerOptions{ 244 | Handler: ServerHandlerFunc(func(c *Connection) { <-chanHandle }), 245 | Host: "127.0.0.1", 246 | Port: 4000, 247 | }) 248 | require.NoError(t, err) 249 | chanListenAndServe := make(chan error) 250 | defer func() { 251 | srv.Close() 252 | select { 253 | case <-chanListenAndServe: 254 | default: 255 | } 256 | }() 257 | 258 | // Listen and serve 259 | go func() { 260 | chanListenAndServe <- srv.ListenAndServe(1) 261 | close(chanListenAndServe) 262 | }() 263 | 264 | // Dial 265 | c, err := Dial(DialOptions{ 266 | Host: "127.0.0.1", 267 | Port: 4000, 268 | }) 269 | require.NoError(t, err) 270 | defer c.Close() 271 | 272 | // Shutdown 273 | ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 274 | defer cancel() 275 | err = srv.Shutdown(ctx) 276 | require.ErrorIs(t, err, context.DeadlineExceeded) 277 | err = <-chanListenAndServe 278 | require.ErrorIs(t, err, ErrServerClosed) 279 | 280 | // Second ListenAndServe should fail 281 | require.ErrorIs(t, srv.ListenAndServe(1), ErrServerClosed) 282 | 283 | // Cancel handle 284 | chanHandle <- true 285 | } 286 | -------------------------------------------------------------------------------- /pkg/socket.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | // #cgo LDFLAGS: -lsrt 4 | // #include 5 | import "C" 6 | import ( 7 | "errors" 8 | "fmt" 9 | "net" 10 | "syscall" 11 | "unsafe" 12 | ) 13 | 14 | type Socket struct { 15 | rr RejectReason 16 | u C.SRTSOCKET 17 | } 18 | 19 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_create_socket 20 | func NewSocket() (s *Socket, err error) { 21 | // Create c socket 22 | var u C.SRTSOCKET 23 | if u, err = cCreateSocket(); err != nil { 24 | return 25 | } 26 | 27 | // Create socket 28 | s = newSocketFromC(u) 29 | return 30 | } 31 | 32 | func newSocketFromC(u C.SRTSOCKET) *Socket { 33 | return &Socket{u: u} 34 | } 35 | 36 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_close 37 | func (s *Socket) Close() error { 38 | return cClose(s.u) 39 | } 40 | 41 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_bind 42 | func (s *Socket) Bind(host string, port uint16) (err error) { 43 | // Create sockaddr 44 | var sa *sockaddr 45 | if sa, err = newSockaddrFromHostAndPort(host, port); err != nil { 46 | err = fmt.Errorf("astisrt: creating sockaddr failed: %w", err) 47 | return 48 | } 49 | 50 | // Bind 51 | if err = cBind(s.u, sa.addr(), sa.size()); err != nil { 52 | err = fmt.Errorf("astisrt: binding failed: %w", err) 53 | return 54 | } 55 | return 56 | } 57 | 58 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_listen 59 | func (s *Socket) Listen(backlog int) error { 60 | return cListen(s.u, C.int(backlog)) 61 | } 62 | 63 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_connect 64 | func (s *Socket) Connect(host string, port uint16) (addr *net.UDPAddr, err error) { 65 | // Create sockaddr 66 | var sa *sockaddr 67 | if sa, err = newSockaddrFromHostAndPort(host, port); err != nil { 68 | err = fmt.Errorf("astisrt: creating sockaddr failed: %w", err) 69 | return 70 | } 71 | 72 | // Connect 73 | var rr C.int 74 | if rr, err = cConnect(s.u, sa.addr(), sa.size()); err != nil { 75 | if errors.Is(err, ErrEconnrej) { 76 | s.rr = RejectReason(rr) 77 | } 78 | err = fmt.Errorf("astisrt: connecting failed: %w", err) 79 | return 80 | } 81 | 82 | // Create udp addr 83 | addr = sa.toUDP() 84 | return 85 | } 86 | 87 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_accept 88 | func (s *Socket) Accept() (a *Socket, addr *net.UDPAddr, err error) { 89 | // Accept 90 | var u C.SRTSOCKET 91 | var raw syscall.RawSockaddrAny 92 | size := C.int(syscall.SizeofSockaddrAny) 93 | if u, err = cAccept(s.u, (*C.struct_sockaddr)(unsafe.Pointer(&raw)), &size); err != nil { 94 | err = fmt.Errorf("astisrt: accepting failed: %w", err) 95 | return 96 | } 97 | 98 | // Get socket 99 | acceptedSocketsMutex.Lock() 100 | var ok bool 101 | if a, ok = acceptedSockets[u]; ok { 102 | delete(acceptedSockets, u) 103 | } else { 104 | a = newSocketFromC(u) 105 | } 106 | acceptedSocketsMutex.Unlock() 107 | 108 | // Create sockaddr 109 | var sa *sockaddr 110 | if sa, err = newSockaddrFromSockaddrAny(&raw); err != nil { 111 | err = fmt.Errorf("astisrt: creating sockaddr failed: %w", err) 112 | return 113 | } 114 | 115 | // Create udp addr 116 | addr = sa.toUDP() 117 | return 118 | } 119 | 120 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_sendmsg2 121 | func (s *Socket) SendMessage(b []byte) (int, error) { 122 | n, err := cSendMsg2(s.u, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)), nil) 123 | return int(n), err 124 | } 125 | 126 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_recvmsg2 127 | func (s *Socket) ReceiveMessage(b []byte) (int, error) { 128 | n, err := cRecMsg2(s.u, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)), nil) 129 | return int(n), err 130 | } 131 | -------------------------------------------------------------------------------- /pkg/socket_option.go: -------------------------------------------------------------------------------- 1 | // Code generated by astisrt using internal/cmd/generate/options. DO NOT EDIT. 2 | package astisrt 3 | 4 | // #cgo LDFLAGS: -lsrt 5 | // #include 6 | import "C" 7 | import ( 8 | "syscall" 9 | "unsafe" 10 | ) 11 | 12 | // https://github.com/Haivision/srt/blob/master/docs/API/API-socket-options.md#list-of-options 13 | 14 | type SocketOptions struct { 15 | s *Socket 16 | } 17 | 18 | func (s *Socket) Options() *SocketOptions { 19 | return &SocketOptions{s: s} 20 | } 21 | 22 | func (so *SocketOptions) getInt64(o C.SRT_SOCKOPT) (int64, error) { 23 | var v int64 24 | size := int(unsafe.Sizeof(v)) 25 | err := cGetSockFlag(so.s.u, o, unsafe.Pointer(&v), &size) 26 | return v, err 27 | } 28 | 29 | func (so *SocketOptions) getInt32(o C.SRT_SOCKOPT) (int32, error) { 30 | var v int32 31 | size := int(unsafe.Sizeof(v)) 32 | err := cGetSockFlag(so.s.u, o, unsafe.Pointer(&v), &size) 33 | return v, err 34 | } 35 | 36 | func (so *SocketOptions) getString(o C.SRT_SOCKOPT) (string, error) { 37 | buf := make([]byte, 1024) 38 | size := len(buf) 39 | err := cGetSockFlag(so.s.u, o, unsafe.Pointer(&buf[0]), &size) 40 | return string(buf[:size]), err 41 | } 42 | 43 | func (so *SocketOptions) getBool(o C.SRT_SOCKOPT) (bool, error) { 44 | var v bool 45 | size := int(unsafe.Sizeof(v)) 46 | err := cGetSockFlag(so.s.u, o, unsafe.Pointer(&v), &size) 47 | return v, err 48 | } 49 | 50 | func (so *SocketOptions) getLinger(o C.SRT_SOCKOPT) (syscall.Linger, error) { 51 | var v syscall.Linger 52 | size := int(unsafe.Sizeof(v)) 53 | err := cGetSockFlag(so.s.u, o, unsafe.Pointer(&v), &size) 54 | return v, err 55 | } 56 | 57 | func (so *SocketOptions) setInt64(o C.SRT_SOCKOPT, v int64) error { 58 | return cSetSockFlag(so.s.u, o, unsafe.Pointer(&v), int(unsafe.Sizeof(v))) 59 | } 60 | 61 | func (so *SocketOptions) setInt32(o C.SRT_SOCKOPT, v int32) error { 62 | return cSetSockFlag(so.s.u, o, unsafe.Pointer(&v), int(unsafe.Sizeof(v))) 63 | } 64 | 65 | func (so *SocketOptions) setBool(o C.SRT_SOCKOPT, v bool) error { 66 | return cSetSockFlag(so.s.u, o, unsafe.Pointer(&v), int(unsafe.Sizeof(v))) 67 | } 68 | 69 | func (so *SocketOptions) setString(o C.SRT_SOCKOPT, v string) error { 70 | return cSetSockFlag(so.s.u, o, unsafe.Pointer(&[]byte(v)[0]), len(v)) 71 | } 72 | 73 | func (so *SocketOptions) setLinger(o C.SRT_SOCKOPT, v syscall.Linger) error { 74 | return cSetSockFlag(so.s.u, o, unsafe.Pointer(&v), int(unsafe.Sizeof(v))) 75 | } 76 | 77 | type Transtype int32 78 | 79 | const ( 80 | TranstypeLive = Transtype(C.SRTT_LIVE) 81 | TranstypeFile = Transtype(C.SRTT_FILE) 82 | TranstypeInvalid = Transtype(C.SRTT_INVALID) 83 | ) 84 | 85 | type KmState int32 86 | 87 | const ( 88 | KmStateKmSUnsecured = KmState(C.SRT_KM_S_UNSECURED) 89 | KmStateKmSSecuring = KmState(C.SRT_KM_S_SECURING) 90 | KmStateKmSSecured = KmState(C.SRT_KM_S_SECURED) 91 | KmStateKmSNosecret = KmState(C.SRT_KM_S_NOSECRET) 92 | KmStateKmSBadsecret = KmState(C.SRT_KM_S_BADSECRET) 93 | ) 94 | 95 | 96 | func (so *SocketOptions) Bindtodevice() (string, error) { 97 | v, err := so.getString(C.SRTO_BINDTODEVICE) 98 | return string(v), err 99 | } 100 | 101 | func (so *SocketOptions) SetBindtodevice(v string) error { 102 | return so.setString(C.SRTO_BINDTODEVICE, string(v)) 103 | } 104 | 105 | func (so *SocketOptions) SetCongestion(v string) error { 106 | return so.setString(C.SRTO_CONGESTION, string(v)) 107 | } 108 | 109 | func (so *SocketOptions) SetConntimeo(v int32) error { 110 | return so.setInt32(C.SRTO_CONNTIMEO, int32(v)) 111 | } 112 | 113 | func (so *SocketOptions) Drifttracer() (bool, error) { 114 | v, err := so.getBool(C.SRTO_DRIFTTRACER) 115 | return bool(v), err 116 | } 117 | 118 | func (so *SocketOptions) SetDrifttracer(v bool) error { 119 | return so.setBool(C.SRTO_DRIFTTRACER, bool(v)) 120 | } 121 | 122 | func (so *SocketOptions) SetEnforcedencryption(v bool) error { 123 | return so.setBool(C.SRTO_ENFORCEDENCRYPTION, bool(v)) 124 | } 125 | 126 | func (so *SocketOptions) Event() (int32, error) { 127 | v, err := so.getInt32(C.SRTO_EVENT) 128 | return int32(v), err 129 | } 130 | 131 | func (so *SocketOptions) Fc() (int32, error) { 132 | v, err := so.getInt32(C.SRTO_FC) 133 | return int32(v), err 134 | } 135 | 136 | func (so *SocketOptions) SetFc(v int32) error { 137 | return so.setInt32(C.SRTO_FC, int32(v)) 138 | } 139 | 140 | func (so *SocketOptions) SetGroupconnect(v int32) error { 141 | return so.setInt32(C.SRTO_GROUPCONNECT, int32(v)) 142 | } 143 | 144 | func (so *SocketOptions) SetGroupminstabletimeo(v int32) error { 145 | return so.setInt32(C.SRTO_GROUPMINSTABLETIMEO, int32(v)) 146 | } 147 | 148 | func (so *SocketOptions) Grouptype() (int32, error) { 149 | v, err := so.getInt32(C.SRTO_GROUPTYPE) 150 | return int32(v), err 151 | } 152 | 153 | func (so *SocketOptions) Inputbw() (int64, error) { 154 | v, err := so.getInt64(C.SRTO_INPUTBW) 155 | return int64(v), err 156 | } 157 | 158 | func (so *SocketOptions) SetInputbw(v int64) error { 159 | return so.setInt64(C.SRTO_INPUTBW, int64(v)) 160 | } 161 | 162 | func (so *SocketOptions) Iptos() (int32, error) { 163 | v, err := so.getInt32(C.SRTO_IPTOS) 164 | return int32(v), err 165 | } 166 | 167 | func (so *SocketOptions) SetIptos(v int32) error { 168 | return so.setInt32(C.SRTO_IPTOS, int32(v)) 169 | } 170 | 171 | func (so *SocketOptions) Ipttl() (int32, error) { 172 | v, err := so.getInt32(C.SRTO_IPTTL) 173 | return int32(v), err 174 | } 175 | 176 | func (so *SocketOptions) SetIpttl(v int32) error { 177 | return so.setInt32(C.SRTO_IPTTL, int32(v)) 178 | } 179 | 180 | func (so *SocketOptions) Ipv6only() (int32, error) { 181 | v, err := so.getInt32(C.SRTO_IPV6ONLY) 182 | return int32(v), err 183 | } 184 | 185 | func (so *SocketOptions) SetIpv6only(v int32) error { 186 | return so.setInt32(C.SRTO_IPV6ONLY, int32(v)) 187 | } 188 | 189 | func (so *SocketOptions) Isn() (int32, error) { 190 | v, err := so.getInt32(C.SRTO_ISN) 191 | return int32(v), err 192 | } 193 | 194 | func (so *SocketOptions) Kmpreannounce() (int32, error) { 195 | v, err := so.getInt32(C.SRTO_KMPREANNOUNCE) 196 | return int32(v), err 197 | } 198 | 199 | func (so *SocketOptions) SetKmpreannounce(v int32) error { 200 | return so.setInt32(C.SRTO_KMPREANNOUNCE, int32(v)) 201 | } 202 | 203 | func (so *SocketOptions) Kmrefreshrate() (int32, error) { 204 | v, err := so.getInt32(C.SRTO_KMREFRESHRATE) 205 | return int32(v), err 206 | } 207 | 208 | func (so *SocketOptions) SetKmrefreshrate(v int32) error { 209 | return so.setInt32(C.SRTO_KMREFRESHRATE, int32(v)) 210 | } 211 | 212 | func (so *SocketOptions) Kmstate() (KmState, error) { 213 | v, err := so.getInt32(C.SRTO_KMSTATE) 214 | return KmState(v), err 215 | } 216 | 217 | func (so *SocketOptions) Latency() (int32, error) { 218 | v, err := so.getInt32(C.SRTO_LATENCY) 219 | return int32(v), err 220 | } 221 | 222 | func (so *SocketOptions) SetLatency(v int32) error { 223 | return so.setInt32(C.SRTO_LATENCY, int32(v)) 224 | } 225 | 226 | func (so *SocketOptions) Linger() (syscall.Linger, error) { 227 | v, err := so.getLinger(C.SRTO_LINGER) 228 | return syscall.Linger(v), err 229 | } 230 | 231 | func (so *SocketOptions) SetLinger(v syscall.Linger) error { 232 | return so.setLinger(C.SRTO_LINGER, syscall.Linger(v)) 233 | } 234 | 235 | func (so *SocketOptions) Lossmaxttl() (int32, error) { 236 | v, err := so.getInt32(C.SRTO_LOSSMAXTTL) 237 | return int32(v), err 238 | } 239 | 240 | func (so *SocketOptions) SetLossmaxttl(v int32) error { 241 | return so.setInt32(C.SRTO_LOSSMAXTTL, int32(v)) 242 | } 243 | 244 | func (so *SocketOptions) Maxbw() (int64, error) { 245 | v, err := so.getInt64(C.SRTO_MAXBW) 246 | return int64(v), err 247 | } 248 | 249 | func (so *SocketOptions) SetMaxbw(v int64) error { 250 | return so.setInt64(C.SRTO_MAXBW, int64(v)) 251 | } 252 | 253 | func (so *SocketOptions) SetMessageapi(v bool) error { 254 | return so.setBool(C.SRTO_MESSAGEAPI, bool(v)) 255 | } 256 | 257 | func (so *SocketOptions) Mininputbw() (int64, error) { 258 | v, err := so.getInt64(C.SRTO_MININPUTBW) 259 | return int64(v), err 260 | } 261 | 262 | func (so *SocketOptions) SetMininputbw(v int64) error { 263 | return so.setInt64(C.SRTO_MININPUTBW, int64(v)) 264 | } 265 | 266 | func (so *SocketOptions) Minversion() (int32, error) { 267 | v, err := so.getInt32(C.SRTO_MINVERSION) 268 | return int32(v), err 269 | } 270 | 271 | func (so *SocketOptions) SetMinversion(v int32) error { 272 | return so.setInt32(C.SRTO_MINVERSION, int32(v)) 273 | } 274 | 275 | func (so *SocketOptions) Mss() (int32, error) { 276 | v, err := so.getInt32(C.SRTO_MSS) 277 | return int32(v), err 278 | } 279 | 280 | func (so *SocketOptions) SetMss(v int32) error { 281 | return so.setInt32(C.SRTO_MSS, int32(v)) 282 | } 283 | 284 | func (so *SocketOptions) Nakreport() (bool, error) { 285 | v, err := so.getBool(C.SRTO_NAKREPORT) 286 | return bool(v), err 287 | } 288 | 289 | func (so *SocketOptions) SetNakreport(v bool) error { 290 | return so.setBool(C.SRTO_NAKREPORT, bool(v)) 291 | } 292 | 293 | func (so *SocketOptions) Oheadbw() (int32, error) { 294 | v, err := so.getInt32(C.SRTO_OHEADBW) 295 | return int32(v), err 296 | } 297 | 298 | func (so *SocketOptions) SetOheadbw(v int32) error { 299 | return so.setInt32(C.SRTO_OHEADBW, int32(v)) 300 | } 301 | 302 | func (so *SocketOptions) Packetfilter() (string, error) { 303 | v, err := so.getString(C.SRTO_PACKETFILTER) 304 | return string(v), err 305 | } 306 | 307 | func (so *SocketOptions) SetPacketfilter(v string) error { 308 | return so.setString(C.SRTO_PACKETFILTER, string(v)) 309 | } 310 | 311 | func (so *SocketOptions) SetPassphrase(v string) error { 312 | return so.setString(C.SRTO_PASSPHRASE, string(v)) 313 | } 314 | 315 | func (so *SocketOptions) SetPayloadsize(v int32) error { 316 | return so.setInt32(C.SRTO_PAYLOADSIZE, int32(v)) 317 | } 318 | 319 | func (so *SocketOptions) Pbkeylen() (int32, error) { 320 | v, err := so.getInt32(C.SRTO_PBKEYLEN) 321 | return int32(v), err 322 | } 323 | 324 | func (so *SocketOptions) SetPbkeylen(v int32) error { 325 | return so.setInt32(C.SRTO_PBKEYLEN, int32(v)) 326 | } 327 | 328 | func (so *SocketOptions) Peeridletimeo() (int32, error) { 329 | v, err := so.getInt32(C.SRTO_PEERIDLETIMEO) 330 | return int32(v), err 331 | } 332 | 333 | func (so *SocketOptions) SetPeeridletimeo(v int32) error { 334 | return so.setInt32(C.SRTO_PEERIDLETIMEO, int32(v)) 335 | } 336 | 337 | func (so *SocketOptions) Peerlatency() (int32, error) { 338 | v, err := so.getInt32(C.SRTO_PEERLATENCY) 339 | return int32(v), err 340 | } 341 | 342 | func (so *SocketOptions) SetPeerlatency(v int32) error { 343 | return so.setInt32(C.SRTO_PEERLATENCY, int32(v)) 344 | } 345 | 346 | func (so *SocketOptions) Peerversion() (int32, error) { 347 | v, err := so.getInt32(C.SRTO_PEERVERSION) 348 | return int32(v), err 349 | } 350 | 351 | func (so *SocketOptions) Rcvbuf() (int32, error) { 352 | v, err := so.getInt32(C.SRTO_RCVBUF) 353 | return int32(v), err 354 | } 355 | 356 | func (so *SocketOptions) SetRcvbuf(v int32) error { 357 | return so.setInt32(C.SRTO_RCVBUF, int32(v)) 358 | } 359 | 360 | func (so *SocketOptions) Rcvdata() (int32, error) { 361 | v, err := so.getInt32(C.SRTO_RCVDATA) 362 | return int32(v), err 363 | } 364 | 365 | func (so *SocketOptions) Rcvkmstate() (int32, error) { 366 | v, err := so.getInt32(C.SRTO_RCVKMSTATE) 367 | return int32(v), err 368 | } 369 | 370 | func (so *SocketOptions) Rcvlatency() (int32, error) { 371 | v, err := so.getInt32(C.SRTO_RCVLATENCY) 372 | return int32(v), err 373 | } 374 | 375 | func (so *SocketOptions) SetRcvlatency(v int32) error { 376 | return so.setInt32(C.SRTO_RCVLATENCY, int32(v)) 377 | } 378 | 379 | func (so *SocketOptions) Rcvsyn() (bool, error) { 380 | v, err := so.getBool(C.SRTO_RCVSYN) 381 | return bool(v), err 382 | } 383 | 384 | func (so *SocketOptions) SetRcvsyn(v bool) error { 385 | return so.setBool(C.SRTO_RCVSYN, bool(v)) 386 | } 387 | 388 | func (so *SocketOptions) Rcvtimeo() (int32, error) { 389 | v, err := so.getInt32(C.SRTO_RCVTIMEO) 390 | return int32(v), err 391 | } 392 | 393 | func (so *SocketOptions) SetRcvtimeo(v int32) error { 394 | return so.setInt32(C.SRTO_RCVTIMEO, int32(v)) 395 | } 396 | 397 | func (so *SocketOptions) Rendezvous() (bool, error) { 398 | v, err := so.getBool(C.SRTO_RENDEZVOUS) 399 | return bool(v), err 400 | } 401 | 402 | func (so *SocketOptions) SetRendezvous(v bool) error { 403 | return so.setBool(C.SRTO_RENDEZVOUS, bool(v)) 404 | } 405 | 406 | func (so *SocketOptions) Retransmitalgo() (int32, error) { 407 | v, err := so.getInt32(C.SRTO_RETRANSMITALGO) 408 | return int32(v), err 409 | } 410 | 411 | func (so *SocketOptions) SetRetransmitalgo(v int32) error { 412 | return so.setInt32(C.SRTO_RETRANSMITALGO, int32(v)) 413 | } 414 | 415 | func (so *SocketOptions) Reuseaddr() (bool, error) { 416 | v, err := so.getBool(C.SRTO_REUSEADDR) 417 | return bool(v), err 418 | } 419 | 420 | func (so *SocketOptions) SetReuseaddr(v bool) error { 421 | return so.setBool(C.SRTO_REUSEADDR, bool(v)) 422 | } 423 | 424 | func (so *SocketOptions) SetSender(v bool) error { 425 | return so.setBool(C.SRTO_SENDER, bool(v)) 426 | } 427 | 428 | func (so *SocketOptions) Sndbuf() (int32, error) { 429 | v, err := so.getInt32(C.SRTO_SNDBUF) 430 | return int32(v), err 431 | } 432 | 433 | func (so *SocketOptions) SetSndbuf(v int32) error { 434 | return so.setInt32(C.SRTO_SNDBUF, int32(v)) 435 | } 436 | 437 | func (so *SocketOptions) Snddata() (int32, error) { 438 | v, err := so.getInt32(C.SRTO_SNDDATA) 439 | return int32(v), err 440 | } 441 | 442 | func (so *SocketOptions) SetSnddropdelay(v int32) error { 443 | return so.setInt32(C.SRTO_SNDDROPDELAY, int32(v)) 444 | } 445 | 446 | func (so *SocketOptions) Sndkmstate() (int32, error) { 447 | v, err := so.getInt32(C.SRTO_SNDKMSTATE) 448 | return int32(v), err 449 | } 450 | 451 | func (so *SocketOptions) Sndsyn() (bool, error) { 452 | v, err := so.getBool(C.SRTO_SNDSYN) 453 | return bool(v), err 454 | } 455 | 456 | func (so *SocketOptions) SetSndsyn(v bool) error { 457 | return so.setBool(C.SRTO_SNDSYN, bool(v)) 458 | } 459 | 460 | func (so *SocketOptions) Sndtimeo() (int32, error) { 461 | v, err := so.getInt32(C.SRTO_SNDTIMEO) 462 | return int32(v), err 463 | } 464 | 465 | func (so *SocketOptions) SetSndtimeo(v int32) error { 466 | return so.setInt32(C.SRTO_SNDTIMEO, int32(v)) 467 | } 468 | 469 | func (so *SocketOptions) State() (int32, error) { 470 | v, err := so.getInt32(C.SRTO_STATE) 471 | return int32(v), err 472 | } 473 | 474 | func (so *SocketOptions) Streamid() (string, error) { 475 | v, err := so.getString(C.SRTO_STREAMID) 476 | return string(v), err 477 | } 478 | 479 | func (so *SocketOptions) SetStreamid(v string) error { 480 | return so.setString(C.SRTO_STREAMID, string(v)) 481 | } 482 | 483 | func (so *SocketOptions) Tlpktdrop() (bool, error) { 484 | v, err := so.getBool(C.SRTO_TLPKTDROP) 485 | return bool(v), err 486 | } 487 | 488 | func (so *SocketOptions) SetTlpktdrop(v bool) error { 489 | return so.setBool(C.SRTO_TLPKTDROP, bool(v)) 490 | } 491 | 492 | func (so *SocketOptions) SetTranstype(v Transtype) error { 493 | return so.setInt32(C.SRTO_TRANSTYPE, int32(v)) 494 | } 495 | 496 | func (so *SocketOptions) SetTsbpdmode(v bool) error { 497 | return so.setBool(C.SRTO_TSBPDMODE, bool(v)) 498 | } 499 | 500 | func (so *SocketOptions) UdpRcvbuf() (int32, error) { 501 | v, err := so.getInt32(C.SRTO_UDP_RCVBUF) 502 | return int32(v), err 503 | } 504 | 505 | func (so *SocketOptions) SetUdpRcvbuf(v int32) error { 506 | return so.setInt32(C.SRTO_UDP_RCVBUF, int32(v)) 507 | } 508 | 509 | func (so *SocketOptions) UdpSndbuf() (int32, error) { 510 | v, err := so.getInt32(C.SRTO_UDP_SNDBUF) 511 | return int32(v), err 512 | } 513 | 514 | func (so *SocketOptions) SetUdpSndbuf(v int32) error { 515 | return so.setInt32(C.SRTO_UDP_SNDBUF, int32(v)) 516 | } 517 | 518 | func (so *SocketOptions) Version() (int32, error) { 519 | v, err := so.getInt32(C.SRTO_VERSION) 520 | return int32(v), err 521 | } 522 | -------------------------------------------------------------------------------- /pkg/srt.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | // #cgo LDFLAGS: -lsrt 4 | // #include 5 | import "C" 6 | 7 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_startup 8 | func Startup() error { return cStartup() } 9 | 10 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_cleanup 11 | func CleanUp() error { return cCleanup() } 12 | 13 | // https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md#srt_getversion 14 | func Version() uint32 { return uint32(C.srt_getversion()) } 15 | 16 | func boolToCInt(b bool) C.int { 17 | if b { 18 | return 1 19 | } 20 | return 0 21 | } 22 | -------------------------------------------------------------------------------- /pkg/srt_test.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestMain(m *testing.M) { 11 | var code int 12 | defer func(code *int) { 13 | os.Exit(*code) 14 | }(&code) 15 | 16 | if err := Startup(); err != nil { 17 | log.Fatal(fmt.Errorf("main: starting up failed: %w", err)) 18 | } 19 | 20 | defer func() { 21 | if err := CleanUp(); err != nil { 22 | log.Fatal(fmt.Errorf("main: cleaning up failed: %w", err)) 23 | } 24 | }() 25 | 26 | code = m.Run() 27 | } 28 | -------------------------------------------------------------------------------- /pkg/static_consts.c: -------------------------------------------------------------------------------- 1 | // Code generated by astisrt using internal/cmd/generate/static_consts. DO NOT EDIT. 2 | #include 3 | 4 | int32_t SRTGROUP_MASK_ = SRTGROUP_MASK; 5 | int SRT_LIVE_DEF_PLSIZE_ = SRT_LIVE_DEF_PLSIZE; 6 | int SRT_LIVE_MAX_PLSIZE_ = SRT_LIVE_MAX_PLSIZE; 7 | int SRT_LIVE_DEF_LATENCY_MS_ = SRT_LIVE_DEF_LATENCY_MS; 8 | SRTSOCKET SRT_INVALID_SOCK_ = SRT_INVALID_SOCK; 9 | int SRT_ERROR_ = SRT_ERROR; 10 | int32_t SRT_SEQNO_NONE_ = SRT_SEQNO_NONE; 11 | int32_t SRT_MSGNO_NONE_ = SRT_MSGNO_NONE; 12 | int32_t SRT_MSGNO_CONTROL_ = SRT_MSGNO_CONTROL; 13 | int SRT_MSGTTL_INF_ = SRT_MSGTTL_INF; 14 | uint32_t SRT_GFLAG_SYNCONMSG_ = SRT_GFLAG_SYNCONMSG; 15 | -------------------------------------------------------------------------------- /pkg/static_consts.h: -------------------------------------------------------------------------------- 1 | // Code generated by astisrt using internal/cmd/generate/static_consts. DO NOT EDIT. 2 | #include 3 | 4 | static const int32_t SRTGROUP_MASK_; 5 | static const int SRT_LIVE_DEF_PLSIZE_; 6 | static const int SRT_LIVE_MAX_PLSIZE_; 7 | static const int SRT_LIVE_DEF_LATENCY_MS_; 8 | static const SRTSOCKET SRT_INVALID_SOCK_; 9 | static const int SRT_ERROR_; 10 | static const int32_t SRT_SEQNO_NONE_; 11 | static const int32_t SRT_MSGNO_NONE_; 12 | static const int32_t SRT_MSGNO_CONTROL_; 13 | static const int SRT_MSGTTL_INF_; 14 | static const uint32_t SRT_GFLAG_SYNCONMSG_; 15 | -------------------------------------------------------------------------------- /pkg/stats.go: -------------------------------------------------------------------------------- 1 | // Code generated by astisrt using internal/cmd/generate/stats. DO NOT EDIT. 2 | package astisrt 3 | 4 | // #cgo LDFLAGS: -lsrt 5 | // #include 6 | import "C" 7 | 8 | type Stats struct { 9 | c C.SRT_TRACEBSTATS 10 | } 11 | 12 | func (s *Socket) Stats(clear, instantaneous bool) (ss Stats, err error) { 13 | err = cBiStats(s.u, &ss.c, boolToCInt(clear), boolToCInt(instantaneous)) 14 | return 15 | } 16 | 17 | 18 | func (ss Stats) MsTimeStamp() int64 { 19 | return int64(ss.c.msTimeStamp) 20 | } 21 | 22 | func (ss Stats) PktSentTotal() int64 { 23 | return int64(ss.c.pktSentTotal) 24 | } 25 | 26 | func (ss Stats) PktRecvTotal() int64 { 27 | return int64(ss.c.pktRecvTotal) 28 | } 29 | 30 | func (ss Stats) PktSndLossTotal() int { 31 | return int(ss.c.pktSndLossTotal) 32 | } 33 | 34 | func (ss Stats) PktRcvLossTotal() int { 35 | return int(ss.c.pktRcvLossTotal) 36 | } 37 | 38 | func (ss Stats) PktRetransTotal() int { 39 | return int(ss.c.pktRetransTotal) 40 | } 41 | 42 | func (ss Stats) PktSentACKTotal() int { 43 | return int(ss.c.pktSentACKTotal) 44 | } 45 | 46 | func (ss Stats) PktRecvACKTotal() int { 47 | return int(ss.c.pktRecvACKTotal) 48 | } 49 | 50 | func (ss Stats) PktSentNAKTotal() int { 51 | return int(ss.c.pktSentNAKTotal) 52 | } 53 | 54 | func (ss Stats) PktRecvNAKTotal() int { 55 | return int(ss.c.pktRecvNAKTotal) 56 | } 57 | 58 | func (ss Stats) UsSndDurationTotal() int64 { 59 | return int64(ss.c.usSndDurationTotal) 60 | } 61 | 62 | func (ss Stats) PktSndDropTotal() int { 63 | return int(ss.c.pktSndDropTotal) 64 | } 65 | 66 | func (ss Stats) PktRcvDropTotal() int { 67 | return int(ss.c.pktRcvDropTotal) 68 | } 69 | 70 | func (ss Stats) PktRcvUndecryptTotal() int { 71 | return int(ss.c.pktRcvUndecryptTotal) 72 | } 73 | 74 | func (ss Stats) ByteSentTotal() uint64 { 75 | return uint64(ss.c.byteSentTotal) 76 | } 77 | 78 | func (ss Stats) ByteRecvTotal() uint64 { 79 | return uint64(ss.c.byteRecvTotal) 80 | } 81 | 82 | func (ss Stats) ByteRcvLossTotal() uint64 { 83 | return uint64(ss.c.byteRcvLossTotal) 84 | } 85 | 86 | func (ss Stats) ByteRetransTotal() uint64 { 87 | return uint64(ss.c.byteRetransTotal) 88 | } 89 | 90 | func (ss Stats) ByteSndDropTotal() uint64 { 91 | return uint64(ss.c.byteSndDropTotal) 92 | } 93 | 94 | func (ss Stats) ByteRcvDropTotal() uint64 { 95 | return uint64(ss.c.byteRcvDropTotal) 96 | } 97 | 98 | func (ss Stats) ByteRcvUndecryptTotal() uint64 { 99 | return uint64(ss.c.byteRcvUndecryptTotal) 100 | } 101 | 102 | func (ss Stats) PktSent() int64 { 103 | return int64(ss.c.pktSent) 104 | } 105 | 106 | func (ss Stats) PktRecv() int64 { 107 | return int64(ss.c.pktRecv) 108 | } 109 | 110 | func (ss Stats) PktSndLoss() int { 111 | return int(ss.c.pktSndLoss) 112 | } 113 | 114 | func (ss Stats) PktRcvLoss() int { 115 | return int(ss.c.pktRcvLoss) 116 | } 117 | 118 | func (ss Stats) PktRetrans() int { 119 | return int(ss.c.pktRetrans) 120 | } 121 | 122 | func (ss Stats) PktRcvRetrans() int { 123 | return int(ss.c.pktRcvRetrans) 124 | } 125 | 126 | func (ss Stats) PktSentACK() int { 127 | return int(ss.c.pktSentACK) 128 | } 129 | 130 | func (ss Stats) PktRecvACK() int { 131 | return int(ss.c.pktRecvACK) 132 | } 133 | 134 | func (ss Stats) PktSentNAK() int { 135 | return int(ss.c.pktSentNAK) 136 | } 137 | 138 | func (ss Stats) PktRecvNAK() int { 139 | return int(ss.c.pktRecvNAK) 140 | } 141 | 142 | func (ss Stats) MbpsSendRate() float64 { 143 | return float64(ss.c.mbpsSendRate) 144 | } 145 | 146 | func (ss Stats) MbpsRecvRate() float64 { 147 | return float64(ss.c.mbpsRecvRate) 148 | } 149 | 150 | func (ss Stats) UsSndDuration() int64 { 151 | return int64(ss.c.usSndDuration) 152 | } 153 | 154 | func (ss Stats) PktReorderDistance() int { 155 | return int(ss.c.pktReorderDistance) 156 | } 157 | 158 | func (ss Stats) PktRcvAvgBelatedTime() float64 { 159 | return float64(ss.c.pktRcvAvgBelatedTime) 160 | } 161 | 162 | func (ss Stats) PktRcvBelated() int64 { 163 | return int64(ss.c.pktRcvBelated) 164 | } 165 | 166 | func (ss Stats) PktSndDrop() int { 167 | return int(ss.c.pktSndDrop) 168 | } 169 | 170 | func (ss Stats) PktRcvDrop() int { 171 | return int(ss.c.pktRcvDrop) 172 | } 173 | 174 | func (ss Stats) PktRcvUndecrypt() int { 175 | return int(ss.c.pktRcvUndecrypt) 176 | } 177 | 178 | func (ss Stats) ByteSent() uint64 { 179 | return uint64(ss.c.byteSent) 180 | } 181 | 182 | func (ss Stats) ByteRecv() uint64 { 183 | return uint64(ss.c.byteRecv) 184 | } 185 | 186 | func (ss Stats) ByteRcvLoss() uint64 { 187 | return uint64(ss.c.byteRcvLoss) 188 | } 189 | 190 | func (ss Stats) ByteRetrans() uint64 { 191 | return uint64(ss.c.byteRetrans) 192 | } 193 | 194 | func (ss Stats) ByteSndDrop() uint64 { 195 | return uint64(ss.c.byteSndDrop) 196 | } 197 | 198 | func (ss Stats) ByteRcvDrop() uint64 { 199 | return uint64(ss.c.byteRcvDrop) 200 | } 201 | 202 | func (ss Stats) ByteRcvUndecrypt() uint64 { 203 | return uint64(ss.c.byteRcvUndecrypt) 204 | } 205 | 206 | func (ss Stats) UsPktSndPeriod() float64 { 207 | return float64(ss.c.usPktSndPeriod) 208 | } 209 | 210 | func (ss Stats) PktFlowWindow() int { 211 | return int(ss.c.pktFlowWindow) 212 | } 213 | 214 | func (ss Stats) PktCongestionWindow() int { 215 | return int(ss.c.pktCongestionWindow) 216 | } 217 | 218 | func (ss Stats) PktFlightSize() int { 219 | return int(ss.c.pktFlightSize) 220 | } 221 | 222 | func (ss Stats) MsRTT() float64 { 223 | return float64(ss.c.msRTT) 224 | } 225 | 226 | func (ss Stats) MbpsBandwidth() float64 { 227 | return float64(ss.c.mbpsBandwidth) 228 | } 229 | 230 | func (ss Stats) ByteAvailSndBuf() int { 231 | return int(ss.c.byteAvailSndBuf) 232 | } 233 | 234 | func (ss Stats) ByteAvailRcvBuf() int { 235 | return int(ss.c.byteAvailRcvBuf) 236 | } 237 | 238 | func (ss Stats) MbpsMaxBW() float64 { 239 | return float64(ss.c.mbpsMaxBW) 240 | } 241 | 242 | func (ss Stats) ByteMSS() int { 243 | return int(ss.c.byteMSS) 244 | } 245 | 246 | func (ss Stats) PktSndBuf() int { 247 | return int(ss.c.pktSndBuf) 248 | } 249 | 250 | func (ss Stats) ByteSndBuf() int { 251 | return int(ss.c.byteSndBuf) 252 | } 253 | 254 | func (ss Stats) MsSndBuf() int { 255 | return int(ss.c.msSndBuf) 256 | } 257 | 258 | func (ss Stats) MsSndTsbPdDelay() int { 259 | return int(ss.c.msSndTsbPdDelay) 260 | } 261 | 262 | func (ss Stats) PktRcvBuf() int { 263 | return int(ss.c.pktRcvBuf) 264 | } 265 | 266 | func (ss Stats) ByteRcvBuf() int { 267 | return int(ss.c.byteRcvBuf) 268 | } 269 | 270 | func (ss Stats) MsRcvBuf() int { 271 | return int(ss.c.msRcvBuf) 272 | } 273 | 274 | func (ss Stats) MsRcvTsbPdDelay() int { 275 | return int(ss.c.msRcvTsbPdDelay) 276 | } 277 | 278 | func (ss Stats) PktSndFilterExtraTotal() int { 279 | return int(ss.c.pktSndFilterExtraTotal) 280 | } 281 | 282 | func (ss Stats) PktRcvFilterExtraTotal() int { 283 | return int(ss.c.pktRcvFilterExtraTotal) 284 | } 285 | 286 | func (ss Stats) PktRcvFilterSupplyTotal() int { 287 | return int(ss.c.pktRcvFilterSupplyTotal) 288 | } 289 | 290 | func (ss Stats) PktRcvFilterLossTotal() int { 291 | return int(ss.c.pktRcvFilterLossTotal) 292 | } 293 | 294 | func (ss Stats) PktSndFilterExtra() int { 295 | return int(ss.c.pktSndFilterExtra) 296 | } 297 | 298 | func (ss Stats) PktRcvFilterExtra() int { 299 | return int(ss.c.pktRcvFilterExtra) 300 | } 301 | 302 | func (ss Stats) PktRcvFilterSupply() int { 303 | return int(ss.c.pktRcvFilterSupply) 304 | } 305 | 306 | func (ss Stats) PktRcvFilterLoss() int { 307 | return int(ss.c.pktRcvFilterLoss) 308 | } 309 | 310 | func (ss Stats) PktReorderTolerance() int { 311 | return int(ss.c.pktReorderTolerance) 312 | } 313 | 314 | func (ss Stats) PktSentUniqueTotal() int64 { 315 | return int64(ss.c.pktSentUniqueTotal) 316 | } 317 | 318 | func (ss Stats) PktRecvUniqueTotal() int64 { 319 | return int64(ss.c.pktRecvUniqueTotal) 320 | } 321 | 322 | func (ss Stats) ByteSentUniqueTotal() uint64 { 323 | return uint64(ss.c.byteSentUniqueTotal) 324 | } 325 | 326 | func (ss Stats) ByteRecvUniqueTotal() uint64 { 327 | return uint64(ss.c.byteRecvUniqueTotal) 328 | } 329 | 330 | func (ss Stats) PktSentUnique() int64 { 331 | return int64(ss.c.pktSentUnique) 332 | } 333 | 334 | func (ss Stats) PktRecvUnique() int64 { 335 | return int64(ss.c.pktRecvUnique) 336 | } 337 | 338 | func (ss Stats) ByteSentUnique() uint64 { 339 | return uint64(ss.c.byteSentUnique) 340 | } 341 | 342 | func (ss Stats) ByteRecvUnique() uint64 { 343 | return uint64(ss.c.byteRecvUnique) 344 | } 345 | -------------------------------------------------------------------------------- /pkg/stats_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by astisrt using internal/cmd/generate/stats. DO NOT EDIT. 2 | package astisrt 3 | 4 | import ( 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestStats(t *testing.T) { 11 | s := Stats{} 12 | s.c.msTimeStamp = _Ctype_int64_t(int64(42)) 13 | require.Equal(t, int64(42), s.MsTimeStamp()) 14 | s.c.pktSentTotal = _Ctype_int64_t(int64(42)) 15 | require.Equal(t, int64(42), s.PktSentTotal()) 16 | s.c.pktRecvTotal = _Ctype_int64_t(int64(42)) 17 | require.Equal(t, int64(42), s.PktRecvTotal()) 18 | s.c.pktSndLossTotal = _Ctype_int(int(42)) 19 | require.Equal(t, int(42), s.PktSndLossTotal()) 20 | s.c.pktRcvLossTotal = _Ctype_int(int(42)) 21 | require.Equal(t, int(42), s.PktRcvLossTotal()) 22 | s.c.pktRetransTotal = _Ctype_int(int(42)) 23 | require.Equal(t, int(42), s.PktRetransTotal()) 24 | s.c.pktSentACKTotal = _Ctype_int(int(42)) 25 | require.Equal(t, int(42), s.PktSentACKTotal()) 26 | s.c.pktRecvACKTotal = _Ctype_int(int(42)) 27 | require.Equal(t, int(42), s.PktRecvACKTotal()) 28 | s.c.pktSentNAKTotal = _Ctype_int(int(42)) 29 | require.Equal(t, int(42), s.PktSentNAKTotal()) 30 | s.c.pktRecvNAKTotal = _Ctype_int(int(42)) 31 | require.Equal(t, int(42), s.PktRecvNAKTotal()) 32 | s.c.usSndDurationTotal = _Ctype_int64_t(int64(42)) 33 | require.Equal(t, int64(42), s.UsSndDurationTotal()) 34 | s.c.pktSndDropTotal = _Ctype_int(int(42)) 35 | require.Equal(t, int(42), s.PktSndDropTotal()) 36 | s.c.pktRcvDropTotal = _Ctype_int(int(42)) 37 | require.Equal(t, int(42), s.PktRcvDropTotal()) 38 | s.c.pktRcvUndecryptTotal = _Ctype_int(int(42)) 39 | require.Equal(t, int(42), s.PktRcvUndecryptTotal()) 40 | s.c.byteSentTotal = _Ctype_uint64_t(uint64(42)) 41 | require.Equal(t, uint64(42), s.ByteSentTotal()) 42 | s.c.byteRecvTotal = _Ctype_uint64_t(uint64(42)) 43 | require.Equal(t, uint64(42), s.ByteRecvTotal()) 44 | s.c.byteRcvLossTotal = _Ctype_uint64_t(uint64(42)) 45 | require.Equal(t, uint64(42), s.ByteRcvLossTotal()) 46 | s.c.byteRetransTotal = _Ctype_uint64_t(uint64(42)) 47 | require.Equal(t, uint64(42), s.ByteRetransTotal()) 48 | s.c.byteSndDropTotal = _Ctype_uint64_t(uint64(42)) 49 | require.Equal(t, uint64(42), s.ByteSndDropTotal()) 50 | s.c.byteRcvDropTotal = _Ctype_uint64_t(uint64(42)) 51 | require.Equal(t, uint64(42), s.ByteRcvDropTotal()) 52 | s.c.byteRcvUndecryptTotal = _Ctype_uint64_t(uint64(42)) 53 | require.Equal(t, uint64(42), s.ByteRcvUndecryptTotal()) 54 | s.c.pktSent = _Ctype_int64_t(int64(42)) 55 | require.Equal(t, int64(42), s.PktSent()) 56 | s.c.pktRecv = _Ctype_int64_t(int64(42)) 57 | require.Equal(t, int64(42), s.PktRecv()) 58 | s.c.pktSndLoss = _Ctype_int(int(42)) 59 | require.Equal(t, int(42), s.PktSndLoss()) 60 | s.c.pktRcvLoss = _Ctype_int(int(42)) 61 | require.Equal(t, int(42), s.PktRcvLoss()) 62 | s.c.pktRetrans = _Ctype_int(int(42)) 63 | require.Equal(t, int(42), s.PktRetrans()) 64 | s.c.pktRcvRetrans = _Ctype_int(int(42)) 65 | require.Equal(t, int(42), s.PktRcvRetrans()) 66 | s.c.pktSentACK = _Ctype_int(int(42)) 67 | require.Equal(t, int(42), s.PktSentACK()) 68 | s.c.pktRecvACK = _Ctype_int(int(42)) 69 | require.Equal(t, int(42), s.PktRecvACK()) 70 | s.c.pktSentNAK = _Ctype_int(int(42)) 71 | require.Equal(t, int(42), s.PktSentNAK()) 72 | s.c.pktRecvNAK = _Ctype_int(int(42)) 73 | require.Equal(t, int(42), s.PktRecvNAK()) 74 | s.c.mbpsSendRate = _Ctype_double(float64(42)) 75 | require.Equal(t, float64(42), s.MbpsSendRate()) 76 | s.c.mbpsRecvRate = _Ctype_double(float64(42)) 77 | require.Equal(t, float64(42), s.MbpsRecvRate()) 78 | s.c.usSndDuration = _Ctype_int64_t(int64(42)) 79 | require.Equal(t, int64(42), s.UsSndDuration()) 80 | s.c.pktReorderDistance = _Ctype_int(int(42)) 81 | require.Equal(t, int(42), s.PktReorderDistance()) 82 | s.c.pktRcvAvgBelatedTime = _Ctype_double(float64(42)) 83 | require.Equal(t, float64(42), s.PktRcvAvgBelatedTime()) 84 | s.c.pktRcvBelated = _Ctype_int64_t(int64(42)) 85 | require.Equal(t, int64(42), s.PktRcvBelated()) 86 | s.c.pktSndDrop = _Ctype_int(int(42)) 87 | require.Equal(t, int(42), s.PktSndDrop()) 88 | s.c.pktRcvDrop = _Ctype_int(int(42)) 89 | require.Equal(t, int(42), s.PktRcvDrop()) 90 | s.c.pktRcvUndecrypt = _Ctype_int(int(42)) 91 | require.Equal(t, int(42), s.PktRcvUndecrypt()) 92 | s.c.byteSent = _Ctype_uint64_t(uint64(42)) 93 | require.Equal(t, uint64(42), s.ByteSent()) 94 | s.c.byteRecv = _Ctype_uint64_t(uint64(42)) 95 | require.Equal(t, uint64(42), s.ByteRecv()) 96 | s.c.byteRcvLoss = _Ctype_uint64_t(uint64(42)) 97 | require.Equal(t, uint64(42), s.ByteRcvLoss()) 98 | s.c.byteRetrans = _Ctype_uint64_t(uint64(42)) 99 | require.Equal(t, uint64(42), s.ByteRetrans()) 100 | s.c.byteSndDrop = _Ctype_uint64_t(uint64(42)) 101 | require.Equal(t, uint64(42), s.ByteSndDrop()) 102 | s.c.byteRcvDrop = _Ctype_uint64_t(uint64(42)) 103 | require.Equal(t, uint64(42), s.ByteRcvDrop()) 104 | s.c.byteRcvUndecrypt = _Ctype_uint64_t(uint64(42)) 105 | require.Equal(t, uint64(42), s.ByteRcvUndecrypt()) 106 | s.c.usPktSndPeriod = _Ctype_double(float64(42)) 107 | require.Equal(t, float64(42), s.UsPktSndPeriod()) 108 | s.c.pktFlowWindow = _Ctype_int(int(42)) 109 | require.Equal(t, int(42), s.PktFlowWindow()) 110 | s.c.pktCongestionWindow = _Ctype_int(int(42)) 111 | require.Equal(t, int(42), s.PktCongestionWindow()) 112 | s.c.pktFlightSize = _Ctype_int(int(42)) 113 | require.Equal(t, int(42), s.PktFlightSize()) 114 | s.c.msRTT = _Ctype_double(float64(42)) 115 | require.Equal(t, float64(42), s.MsRTT()) 116 | s.c.mbpsBandwidth = _Ctype_double(float64(42)) 117 | require.Equal(t, float64(42), s.MbpsBandwidth()) 118 | s.c.byteAvailSndBuf = _Ctype_int(int(42)) 119 | require.Equal(t, int(42), s.ByteAvailSndBuf()) 120 | s.c.byteAvailRcvBuf = _Ctype_int(int(42)) 121 | require.Equal(t, int(42), s.ByteAvailRcvBuf()) 122 | s.c.mbpsMaxBW = _Ctype_double(float64(42)) 123 | require.Equal(t, float64(42), s.MbpsMaxBW()) 124 | s.c.byteMSS = _Ctype_int(int(42)) 125 | require.Equal(t, int(42), s.ByteMSS()) 126 | s.c.pktSndBuf = _Ctype_int(int(42)) 127 | require.Equal(t, int(42), s.PktSndBuf()) 128 | s.c.byteSndBuf = _Ctype_int(int(42)) 129 | require.Equal(t, int(42), s.ByteSndBuf()) 130 | s.c.msSndBuf = _Ctype_int(int(42)) 131 | require.Equal(t, int(42), s.MsSndBuf()) 132 | s.c.msSndTsbPdDelay = _Ctype_int(int(42)) 133 | require.Equal(t, int(42), s.MsSndTsbPdDelay()) 134 | s.c.pktRcvBuf = _Ctype_int(int(42)) 135 | require.Equal(t, int(42), s.PktRcvBuf()) 136 | s.c.byteRcvBuf = _Ctype_int(int(42)) 137 | require.Equal(t, int(42), s.ByteRcvBuf()) 138 | s.c.msRcvBuf = _Ctype_int(int(42)) 139 | require.Equal(t, int(42), s.MsRcvBuf()) 140 | s.c.msRcvTsbPdDelay = _Ctype_int(int(42)) 141 | require.Equal(t, int(42), s.MsRcvTsbPdDelay()) 142 | s.c.pktSndFilterExtraTotal = _Ctype_int(int(42)) 143 | require.Equal(t, int(42), s.PktSndFilterExtraTotal()) 144 | s.c.pktRcvFilterExtraTotal = _Ctype_int(int(42)) 145 | require.Equal(t, int(42), s.PktRcvFilterExtraTotal()) 146 | s.c.pktRcvFilterSupplyTotal = _Ctype_int(int(42)) 147 | require.Equal(t, int(42), s.PktRcvFilterSupplyTotal()) 148 | s.c.pktRcvFilterLossTotal = _Ctype_int(int(42)) 149 | require.Equal(t, int(42), s.PktRcvFilterLossTotal()) 150 | s.c.pktSndFilterExtra = _Ctype_int(int(42)) 151 | require.Equal(t, int(42), s.PktSndFilterExtra()) 152 | s.c.pktRcvFilterExtra = _Ctype_int(int(42)) 153 | require.Equal(t, int(42), s.PktRcvFilterExtra()) 154 | s.c.pktRcvFilterSupply = _Ctype_int(int(42)) 155 | require.Equal(t, int(42), s.PktRcvFilterSupply()) 156 | s.c.pktRcvFilterLoss = _Ctype_int(int(42)) 157 | require.Equal(t, int(42), s.PktRcvFilterLoss()) 158 | s.c.pktReorderTolerance = _Ctype_int(int(42)) 159 | require.Equal(t, int(42), s.PktReorderTolerance()) 160 | s.c.pktSentUniqueTotal = _Ctype_int64_t(int64(42)) 161 | require.Equal(t, int64(42), s.PktSentUniqueTotal()) 162 | s.c.pktRecvUniqueTotal = _Ctype_int64_t(int64(42)) 163 | require.Equal(t, int64(42), s.PktRecvUniqueTotal()) 164 | s.c.byteSentUniqueTotal = _Ctype_uint64_t(uint64(42)) 165 | require.Equal(t, uint64(42), s.ByteSentUniqueTotal()) 166 | s.c.byteRecvUniqueTotal = _Ctype_uint64_t(uint64(42)) 167 | require.Equal(t, uint64(42), s.ByteRecvUniqueTotal()) 168 | s.c.pktSentUnique = _Ctype_int64_t(int64(42)) 169 | require.Equal(t, int64(42), s.PktSentUnique()) 170 | s.c.pktRecvUnique = _Ctype_int64_t(int64(42)) 171 | require.Equal(t, int64(42), s.PktRecvUnique()) 172 | s.c.byteSentUnique = _Ctype_uint64_t(uint64(42)) 173 | require.Equal(t, uint64(42), s.ByteSentUnique()) 174 | s.c.byteRecvUnique = _Ctype_uint64_t(uint64(42)) 175 | require.Equal(t, uint64(42), s.ByteRecvUnique()) 176 | 177 | } -------------------------------------------------------------------------------- /pkg/stream_id.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | import ( 4 | "errors" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | // https://github.com/Haivision/srt/blob/master/docs/features/access-control.md 10 | 11 | type StreamIDKey string 12 | 13 | const ( 14 | StreamIDKeyHostName StreamIDKey = "h" 15 | StreamIDKeyMode StreamIDKey = "m" 16 | StreamIDKeyRessourceName StreamIDKey = "r" 17 | StreamIDKeySessionID StreamIDKey = "s" 18 | StreamIDKeyType StreamIDKey = "t" 19 | StreamIDKeyUserName StreamIDKey = "u" 20 | ) 21 | 22 | type StreamID map[StreamIDKey]StreamIDItem 23 | 24 | type StreamIDItem struct { 25 | Children map[StreamIDKey]StreamIDItem 26 | Value string 27 | } 28 | 29 | const streamIDPrefix = "#!:" 30 | 31 | func ParseStreamID(str string) (s StreamID, err error) { 32 | // Create stream id 33 | s = make(StreamID) 34 | 35 | // Handle prefix 36 | if !strings.HasPrefix(str, streamIDPrefix) { 37 | err = errors.New("astisrt: missing prefix") 38 | return 39 | } 40 | str = strings.TrimPrefix(str, streamIDPrefix) 41 | 42 | // Check format 43 | switch str[0] { 44 | // Comma-separated key-value pairs with no nesting 45 | case ':': 46 | // Loop through key/value pairs 47 | for _, pair := range strings.Split(strings.TrimPrefix(str, ":"), ",") { 48 | // Split on = 49 | split := strings.Split(pair, "=") 50 | if len(split) < 2 { 51 | continue 52 | } 53 | 54 | // Add child 55 | s[StreamIDKey(split[0])] = StreamIDItem{Value: split[1]} 56 | } 57 | // Nested block with one or several key-value pairs 58 | case '{': 59 | idx := 1 60 | parseStreamIDNestedFunc(str, &idx, s) 61 | // Invalid 62 | default: 63 | err = errors.New("astisrt: invalid format") 64 | return 65 | } 66 | return 67 | } 68 | 69 | func parseStreamIDNestedFunc(str string, idx *int, m map[StreamIDKey]StreamIDItem) { 70 | var key string 71 | var i StreamIDItem 72 | isKey := true 73 | for ; *idx < len(str); *idx++ { 74 | c := str[*idx] 75 | switch c { 76 | case '{': 77 | *idx++ 78 | i.Children = make(map[StreamIDKey]StreamIDItem) 79 | parseStreamIDNestedFunc(str, idx, i.Children) 80 | case '=': 81 | isKey = false 82 | case ',', '}': 83 | isKey = true 84 | m[StreamIDKey(key)] = i 85 | if c == ',' { 86 | key = "" 87 | i = StreamIDItem{} 88 | } else { 89 | return 90 | } 91 | default: 92 | if isKey { 93 | key += string(c) 94 | } else { 95 | i.Value += string(c) 96 | } 97 | } 98 | } 99 | } 100 | 101 | func (s StreamID) String() (str string) { 102 | // Add prefix 103 | str += streamIDPrefix 104 | 105 | // Nothing to do 106 | if len(s) == 0 { 107 | return 108 | } 109 | 110 | // Get format 111 | format := ':' 112 | for _, c := range s { 113 | if len(c.Children) > 0 { 114 | format = '{' 115 | break 116 | } 117 | } 118 | 119 | // Switch on format 120 | switch format { 121 | case '{': 122 | str += stringStreamIDNested(s) 123 | default: 124 | var ss []string 125 | for k, v := range s { 126 | ss = append(ss, string(k)+"="+v.Value) 127 | } 128 | sort.Strings(ss) 129 | str += ":" + strings.Join(ss, ",") 130 | } 131 | return 132 | } 133 | 134 | func stringStreamIDNested(m map[StreamIDKey]StreamIDItem) string { 135 | // Sort keys 136 | var ks []string 137 | for k := range m { 138 | ks = append(ks, string(k)) 139 | } 140 | sort.Strings(ks) 141 | 142 | // Loop through keys 143 | var ss []string 144 | for _, k := range ks { 145 | s := k + "=" 146 | v := m[StreamIDKey(k)] 147 | if len(v.Children) > 0 { 148 | s += stringStreamIDNested(v.Children) 149 | } else { 150 | s += v.Value 151 | } 152 | ss = append(ss, s) 153 | } 154 | return "{" + strings.Join(ss, ",") + "}" 155 | } 156 | 157 | func (s StreamID) MarshalText() ([]byte, error) { 158 | return []byte(s.String()), nil 159 | } 160 | 161 | func (s *StreamID) UnmarshalText(b []byte) (err error) { 162 | *s, err = ParseStreamID(string(b)) 163 | return err 164 | } 165 | -------------------------------------------------------------------------------- /pkg/stream_id_test.go: -------------------------------------------------------------------------------- 1 | package astisrt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestStreamID(t *testing.T) { 10 | _, err := ParseStreamID("invalid prefix") 11 | require.Error(t, err) 12 | _, err = ParseStreamID("#!:invalid format") 13 | require.Error(t, err) 14 | s, err := ParseStreamID("#!::r=r,invalid,u=u") 15 | require.NoError(t, err) 16 | require.Equal(t, StreamID{ 17 | StreamIDKeyRessourceName: {Value: "r"}, 18 | StreamIDKeyUserName: {Value: "u"}, 19 | }, s) 20 | require.Equal(t, "#!::r=r,u=u", s.String()) 21 | s2 := "#!:{key1={key11={key111=value111},key12=value12},key2=value2,key3={key31=value31}}" 22 | s, err = ParseStreamID(s2) 23 | require.NoError(t, err) 24 | gs2 := StreamID{ 25 | "key1": {Children: map[StreamIDKey]StreamIDItem{ 26 | "key11": {Children: map[StreamIDKey]StreamIDItem{"key111": {Value: "value111"}}}, 27 | "key12": {Value: "value12"}, 28 | }}, 29 | "key2": {Value: "value2"}, 30 | "key3": {Children: map[StreamIDKey]StreamIDItem{"key31": {Value: "value31"}}}, 31 | } 32 | require.Equal(t, gs2, s) 33 | require.Equal(t, s2, s.String()) 34 | s = make(StreamID) 35 | err = s.UnmarshalText([]byte(s2)) 36 | require.NoError(t, err) 37 | require.Equal(t, gs2, s) 38 | b, err := s.MarshalText() 39 | require.NoError(t, err) 40 | require.Equal(t, s2, string(b)) 41 | } 42 | -------------------------------------------------------------------------------- /pkg/wrap.c: -------------------------------------------------------------------------------- 1 | // Code generated by astisrt using internal/cmd/generate/wrap. DO NOT EDIT. 2 | #include 3 | #include "callbacks.h" 4 | 5 | int astisrt_getsockflag(SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen, int *srtErrno, int *sysErrno) { 6 | int ret = srt_getsockflag(u, opt, optval, optlen); 7 | if (ret == SRT_ERROR) { 8 | *srtErrno = srt_getlasterror(sysErrno); 9 | srt_clearlasterror(); 10 | } 11 | return ret; 12 | } 13 | 14 | int astisrt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen, int *srtErrno, int *sysErrno) { 15 | int ret = srt_setsockflag(u, opt, optval, optlen); 16 | if (ret == SRT_ERROR) { 17 | *srtErrno = srt_getlasterror(sysErrno); 18 | srt_clearlasterror(); 19 | } 20 | return ret; 21 | } 22 | 23 | int astisrt_startup(int *srtErrno, int *sysErrno) { 24 | int ret = srt_startup(); 25 | if (ret == SRT_ERROR) { 26 | *srtErrno = srt_getlasterror(sysErrno); 27 | srt_clearlasterror(); 28 | } 29 | return ret; 30 | } 31 | 32 | int astisrt_cleanup(int *srtErrno, int *sysErrno) { 33 | int ret = srt_cleanup(); 34 | if (ret == SRT_ERROR) { 35 | *srtErrno = srt_getlasterror(sysErrno); 36 | srt_clearlasterror(); 37 | } 38 | return ret; 39 | } 40 | 41 | SRTSOCKET astisrt_create_socket(int *srtErrno, int *sysErrno) { 42 | SRTSOCKET ret = srt_create_socket(); 43 | if (ret == SRT_INVALID_SOCK) { 44 | *srtErrno = srt_getlasterror(sysErrno); 45 | srt_clearlasterror(); 46 | } 47 | return ret; 48 | } 49 | 50 | int astisrt_close(SRTSOCKET u, int *srtErrno, int *sysErrno) { 51 | int ret = srt_close(u); 52 | if (ret == SRT_ERROR) { 53 | *srtErrno = srt_getlasterror(sysErrno); 54 | srt_clearlasterror(); 55 | } 56 | return ret; 57 | } 58 | 59 | int astisrt_bind(SRTSOCKET u, const struct sockaddr* name, int namelen, int *srtErrno, int *sysErrno) { 60 | int ret = srt_bind(u, name, namelen); 61 | if (ret == SRT_ERROR) { 62 | *srtErrno = srt_getlasterror(sysErrno); 63 | srt_clearlasterror(); 64 | } 65 | return ret; 66 | } 67 | 68 | int astisrt_listen(SRTSOCKET u, int backlog, int *srtErrno, int *sysErrno) { 69 | int ret = srt_listen(u, backlog); 70 | if (ret == SRT_ERROR) { 71 | *srtErrno = srt_getlasterror(sysErrno); 72 | srt_clearlasterror(); 73 | } 74 | return ret; 75 | } 76 | 77 | int astisrt_connect(SRTSOCKET u, const struct sockaddr* name, int namelen, int *srtErrno, int *sysErrno, int *rejectReason) { 78 | int ret = srt_connect(u, name, namelen); 79 | if (ret == SRT_ERROR) { 80 | *srtErrno = srt_getlasterror(sysErrno); 81 | srt_clearlasterror(); 82 | if (*srtErrno == SRT_ECONNREJ) { 83 | *rejectReason = srt_getrejectreason(u); 84 | } 85 | } 86 | return ret; 87 | } 88 | 89 | SRTSOCKET astisrt_accept(SRTSOCKET u, struct sockaddr* addr, int* addrlen, int *srtErrno, int *sysErrno) { 90 | SRTSOCKET ret = srt_accept(u, addr, addrlen); 91 | if (ret == SRT_INVALID_SOCK) { 92 | *srtErrno = srt_getlasterror(sysErrno); 93 | srt_clearlasterror(); 94 | } 95 | return ret; 96 | } 97 | 98 | int astisrt_listen_callback(SRTSOCKET lsn, void* opaque, int *srtErrno, int *sysErrno) { 99 | int ret = srt_listen_callback(lsn, astisrt_listen_callback_fn, opaque); 100 | if (ret == SRT_ERROR) { 101 | *srtErrno = srt_getlasterror(sysErrno); 102 | srt_clearlasterror(); 103 | } 104 | return ret; 105 | } 106 | 107 | int astisrt_connect_callback(SRTSOCKET clr, void* opaque, int *srtErrno, int *sysErrno) { 108 | int ret = srt_connect_callback(clr, astisrt_connect_callback_fn, opaque); 109 | if (ret == SRT_ERROR) { 110 | *srtErrno = srt_getlasterror(sysErrno); 111 | srt_clearlasterror(); 112 | } 113 | return ret; 114 | } 115 | 116 | int astisrt_setrejectreason(SRTSOCKET sock, int value, int *srtErrno, int *sysErrno) { 117 | int ret = srt_setrejectreason(sock, value); 118 | if (ret == SRT_ERROR) { 119 | *srtErrno = srt_getlasterror(sysErrno); 120 | srt_clearlasterror(); 121 | } 122 | return ret; 123 | } 124 | 125 | int astisrt_bistats(SRTSOCKET u, SRT_TRACEBSTATS* perf, int clear, int instantaneous, int *srtErrno, int *sysErrno) { 126 | int ret = srt_bistats(u, perf, clear, instantaneous); 127 | if (ret == SRT_ERROR) { 128 | *srtErrno = srt_getlasterror(sysErrno); 129 | srt_clearlasterror(); 130 | } 131 | return ret; 132 | } 133 | 134 | int astisrt_recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL* mctrl, int *srtErrno, int *sysErrno) { 135 | int ret = srt_recvmsg2(u, buf, len, mctrl); 136 | if (ret == SRT_ERROR) { 137 | *srtErrno = srt_getlasterror(sysErrno); 138 | srt_clearlasterror(); 139 | } 140 | return ret; 141 | } 142 | 143 | int astisrt_sendmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL* mctrl, int *srtErrno, int *sysErrno) { 144 | int ret = srt_sendmsg2(u, buf, len, mctrl); 145 | if (ret == SRT_ERROR) { 146 | *srtErrno = srt_getlasterror(sysErrno); 147 | srt_clearlasterror(); 148 | } 149 | return ret; 150 | } 151 | -------------------------------------------------------------------------------- /pkg/wrap.go: -------------------------------------------------------------------------------- 1 | // Code generated by astisrt using internal/cmd/generate/wrap. DO NOT EDIT. 2 | package astisrt 3 | 4 | // #cgo LDFLAGS: -lsrt 5 | // #include "static_consts.h" 6 | // #include 7 | // #include "wrap.h" 8 | import "C" 9 | import ( 10 | "unsafe" 11 | ) 12 | 13 | func cGetSockFlag(s C.SRTSOCKET, o C.SRT_SOCKOPT, p unsafe.Pointer, size *int) error { 14 | var srtErrno C.int 15 | var sysErrno C.int 16 | ret := C.astisrt_getsockflag(s, o, p, (*C.int)(unsafe.Pointer(size)), &srtErrno, &sysErrno) 17 | if ret == C.SRT_ERROR_ { 18 | return newError(srtErrno, sysErrno) 19 | } 20 | return nil 21 | } 22 | 23 | func cSetSockFlag(s C.SRTSOCKET, o C.SRT_SOCKOPT, p unsafe.Pointer, size int) error { 24 | var srtErrno C.int 25 | var sysErrno C.int 26 | ret := C.astisrt_setsockflag(s, o, p, (C.int)(size), &srtErrno, &sysErrno) 27 | if ret == C.SRT_ERROR_ { 28 | return newError(srtErrno, sysErrno) 29 | } 30 | return nil 31 | } 32 | 33 | func cStartup() error { 34 | var srtErrno C.int 35 | var sysErrno C.int 36 | ret := C.astisrt_startup(&srtErrno, &sysErrno) 37 | if ret == C.SRT_ERROR_ { 38 | return newError(srtErrno, sysErrno) 39 | } 40 | return nil 41 | } 42 | 43 | func cCleanup() error { 44 | var srtErrno C.int 45 | var sysErrno C.int 46 | ret := C.astisrt_cleanup(&srtErrno, &sysErrno) 47 | if ret == C.SRT_ERROR_ { 48 | return newError(srtErrno, sysErrno) 49 | } 50 | return nil 51 | } 52 | 53 | func cCreateSocket() (C.SRTSOCKET, error) { 54 | var srtErrno C.int 55 | var sysErrno C.int 56 | ret := C.astisrt_create_socket(&srtErrno, &sysErrno) 57 | if ret == C.SRT_INVALID_SOCK_ { 58 | return ret, newError(srtErrno, sysErrno) 59 | } 60 | return ret, nil 61 | } 62 | 63 | func cClose(s C.SRTSOCKET) error { 64 | var srtErrno C.int 65 | var sysErrno C.int 66 | ret := C.astisrt_close(s, &srtErrno, &sysErrno) 67 | if ret == C.SRT_ERROR_ { 68 | return newError(srtErrno, sysErrno) 69 | } 70 | return nil 71 | } 72 | 73 | func cBind(s C.SRTSOCKET, addr *C.struct_sockaddr, size C.int) error { 74 | var srtErrno C.int 75 | var sysErrno C.int 76 | ret := C.astisrt_bind(s, addr, size, &srtErrno, &sysErrno) 77 | if ret == C.SRT_ERROR_ { 78 | return newError(srtErrno, sysErrno) 79 | } 80 | return nil 81 | } 82 | 83 | func cListen(s C.SRTSOCKET, backlog C.int) error { 84 | var srtErrno C.int 85 | var sysErrno C.int 86 | ret := C.astisrt_listen(s, backlog, &srtErrno, &sysErrno) 87 | if ret == C.SRT_ERROR_ { 88 | return newError(srtErrno, sysErrno) 89 | } 90 | return nil 91 | } 92 | 93 | func cConnect(s C.SRTSOCKET, addr *C.struct_sockaddr, size C.int) (C.int, error) { 94 | var srtErrno C.int 95 | var sysErrno C.int 96 | var rejectReason C.int 97 | ret := C.astisrt_connect(s, addr, size, &srtErrno, &sysErrno, &rejectReason) 98 | if ret == C.SRT_ERROR_ { 99 | return rejectReason, newError(srtErrno, sysErrno) 100 | } 101 | return rejectReason, nil 102 | } 103 | 104 | func cAccept(s C.SRTSOCKET, addr *C.struct_sockaddr, size *C.int) (C.SRTSOCKET, error) { 105 | var srtErrno C.int 106 | var sysErrno C.int 107 | ret := C.astisrt_accept(s, addr, size, &srtErrno, &sysErrno) 108 | if ret == C.SRT_INVALID_SOCK_ { 109 | return ret, newError(srtErrno, sysErrno) 110 | } 111 | return ret, nil 112 | } 113 | 114 | func cListenCallback(lsn *C.SRTSOCKET) error { 115 | var srtErrno C.int 116 | var sysErrno C.int 117 | ret := C.astisrt_listen_callback(*lsn, unsafe.Pointer(lsn), &srtErrno, &sysErrno) 118 | if ret == C.SRT_ERROR_ { 119 | return newError(srtErrno, sysErrno) 120 | } 121 | return nil 122 | } 123 | 124 | func cConnectCallback(clr *C.SRTSOCKET) error { 125 | var srtErrno C.int 126 | var sysErrno C.int 127 | ret := C.astisrt_connect_callback(*clr, unsafe.Pointer(clr), &srtErrno, &sysErrno) 128 | if ret == C.SRT_ERROR_ { 129 | return newError(srtErrno, sysErrno) 130 | } 131 | return nil 132 | } 133 | 134 | func cSetRejectReason(sock C.SRTSOCKET, value C.int) error { 135 | var srtErrno C.int 136 | var sysErrno C.int 137 | ret := C.astisrt_setrejectreason(sock, value, &srtErrno, &sysErrno) 138 | if ret == C.SRT_ERROR_ { 139 | return newError(srtErrno, sysErrno) 140 | } 141 | return nil 142 | } 143 | 144 | func cBiStats(u C.SRTSOCKET, perf *C.SRT_TRACEBSTATS, clear C.int, instantaneous C.int) error { 145 | var srtErrno C.int 146 | var sysErrno C.int 147 | ret := C.astisrt_bistats(u, perf, clear, instantaneous, &srtErrno, &sysErrno) 148 | if ret == C.SRT_ERROR_ { 149 | return newError(srtErrno, sysErrno) 150 | } 151 | return nil 152 | } 153 | 154 | func cRecMsg2(u C.SRTSOCKET, buf *C.char, len C.int, mctrl *C.SRT_MSGCTRL) (C.int, error) { 155 | var srtErrno C.int 156 | var sysErrno C.int 157 | ret := C.astisrt_recvmsg2(u, buf, len, mctrl, &srtErrno, &sysErrno) 158 | if ret == C.SRT_ERROR_ { 159 | return ret, newError(srtErrno, sysErrno) 160 | } 161 | return ret, nil 162 | } 163 | 164 | func cSendMsg2(u C.SRTSOCKET, buf *C.char, len C.int, mctrl *C.SRT_MSGCTRL) (C.int, error) { 165 | var srtErrno C.int 166 | var sysErrno C.int 167 | ret := C.astisrt_sendmsg2(u, buf, len, mctrl, &srtErrno, &sysErrno) 168 | if ret == C.SRT_ERROR_ { 169 | return ret, newError(srtErrno, sysErrno) 170 | } 171 | return ret, nil 172 | } 173 | -------------------------------------------------------------------------------- /pkg/wrap.h: -------------------------------------------------------------------------------- 1 | // Code generated by astisrt using internal/cmd/generate/wrap. DO NOT EDIT. 2 | #include 3 | #include "callbacks.h" 4 | 5 | int astisrt_getsockflag(SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen, int *srtErrno, int *sysErrno); 6 | int astisrt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen, int *srtErrno, int *sysErrno); 7 | int astisrt_startup(int *srtErrno, int *sysErrno); 8 | int astisrt_cleanup(int *srtErrno, int *sysErrno); 9 | SRTSOCKET astisrt_create_socket(int *srtErrno, int *sysErrno); 10 | int astisrt_close(SRTSOCKET u, int *srtErrno, int *sysErrno); 11 | int astisrt_bind(SRTSOCKET u, const struct sockaddr* name, int namelen, int *srtErrno, int *sysErrno); 12 | int astisrt_listen(SRTSOCKET u, int backlog, int *srtErrno, int *sysErrno); 13 | int astisrt_connect(SRTSOCKET u, const struct sockaddr* name, int namelen, int *srtErrno, int *sysErrno, int *rejectReason); 14 | SRTSOCKET astisrt_accept(SRTSOCKET u, struct sockaddr* addr, int* addrlen, int *srtErrno, int *sysErrno); 15 | int astisrt_listen_callback(SRTSOCKET lsn, void* opaque, int *srtErrno, int *sysErrno); 16 | int astisrt_connect_callback(SRTSOCKET clr, void* opaque, int *srtErrno, int *sysErrno); 17 | int astisrt_setrejectreason(SRTSOCKET sock, int value, int *srtErrno, int *sysErrno); 18 | int astisrt_bistats(SRTSOCKET u, SRT_TRACEBSTATS* perf, int clear, int instantaneous, int *srtErrno, int *sysErrno); 19 | int astisrt_recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL* mctrl, int *srtErrno, int *sysErrno); 20 | int astisrt_sendmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL* mctrl, int *srtErrno, int *sysErrno); -------------------------------------------------------------------------------- /testdata/linux/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !Dockerfile -------------------------------------------------------------------------------- /testdata/linux/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19 2 | 3 | RUN \ 4 | apt-get update && \ 5 | DEBIAN_FRONTEND=noninteractive apt-get install -y \ 6 | tclsh pkg-config cmake libssl-dev build-essential git \ 7 | && apt-get clean 8 | 9 | ENV SRT_VERSION="v1.5.3" 10 | 11 | RUN \ 12 | cd /opt && \ 13 | git clone https://github.com/asticode/go-astisrt.git && \ 14 | cd go-astisrt && \ 15 | make install-srt version="${SRT_VERSION}" && \ 16 | mv tmp/${SRT_VERSION} /opt/srt && \ 17 | cd .. && \ 18 | rm -rf go-astisrt 19 | 20 | ENV LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/opt/srt/lib/" 21 | ENV CGO_CFLAGS="-I/opt/srt/include/" 22 | ENV CGO_LDFLAGS="-L/opt/srt/lib/" 23 | ENV PKG_CONFIG_PATH="/opt/srt/lib/pkgconfig" 24 | ENV GOCACHE="/opt/astisrt/tmp/linux/gocache" 25 | ENV GOPATH="/opt/astisrt/tmp/linux/gopath" 26 | 27 | CMD \ 28 | cd /opt/astisrt/tmp/linux/gopath/src/github.com/asticode/go-astisrt/pkg && \ 29 | go test --------------------------------------------------------------------------------