SmartPtr: Support load test for source by srs-bench. v6.0.130 (#4097)

1. Add live benchmark support in srs-bench, which only connects and
disconnects without any media transport, to test source creation and
disposal and verify source memory leaks.
2. SmartPtr: Support cleanup of HTTP-FLV stream. Unregister the HTTP-FLV
handler for the pattern and clean up the objects and resources.
3. Support benchmarking RTMP/SRT with srs-bench by integrating the gosrt
and oryx RTMP libraries.
4. Refine SRT and RTC sources by using a timer to clean up the sources,
following the same strategy as the Live source.

---------

Co-authored-by: Haibo Chen <495810242@qq.com>
Co-authored-by: Jacob Su <suzp1984@gmail.com>
pull/4100/head
Winlin 7 months ago committed by GitHub
parent e3d74fb045
commit 1f9309ae25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,20 +1,32 @@
.PHONY: help default clean bench test
.PHONY: help default clean bench pcap test all
default: bench test
clean:
rm -rf ./objs
all: bench test pcap test
#########################################################################################################
# SRS benchmark tool for SRS, janus, GB28181.
./objs/.format.bench.txt: *.go janus/*.go ./objs/.format.srs.txt ./objs/.format.gb28181.txt
gofmt -w *.go janus
mkdir -p objs && echo "done" > ./objs/.format.bench.txt
bench: ./objs/srs_bench ./objs/pcap_simulator
bench: ./objs/srs_bench
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
SRT_PREFIX := $(shell brew --prefix srt)
CGO_CFLAGS := -I$(SRT_PREFIX)/include
CGO_LDFLAGS := -L$(SRT_PREFIX)/lib -lsrt
else ifeq ($(UNAME_S),Linux)
CGO_CFLAGS := -I/usr/local/include
CGO_LDFLAGS := -L/usr/local/lib -lsrt -L/usr/local/ssl/lib -lcrypto -lstdc++ -lm -ldl
endif
./objs/srs_bench: ./objs/.format.bench.txt *.go janus/*.go srs/*.go vnet/*.go gb28181/*.go Makefile
go build -mod=vendor -o objs/srs_bench .
./objs/srs_bench: ./objs/.format.bench.txt *.go janus/*.go srs/*.go vnet/*.go gb28181/*.go live/*.go Makefile
CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -mod=vendor -o objs/srs_bench .
#########################################################################################################
# For all regression tests.
@ -35,6 +47,8 @@ test: ./objs/srs_test ./objs/srs_gb28181_test ./objs/srs_blackbox_test
gofmt -w pcap
mkdir -p objs && echo "done" > ./objs/.format.pcap.txt
pcap: ./objs/pcap_simulator
./objs/pcap_simulator: ./objs/.format.pcap.txt pcap/*.go Makefile
go build -mod=vendor -o ./objs/pcap_simulator ./pcap
@ -59,9 +73,10 @@ test: ./objs/srs_test ./objs/srs_gb28181_test ./objs/srs_blackbox_test
#########################################################################################################
# Help menu.
help:
@echo "Usage: make [default|bench|test|clean]"
@echo "Usage: make [default|bench|pcap|test|clean]"
@echo " default The default entry for make is bench+test"
@echo " bench Make the bench to ./objs/srs_bench"
@echo " pcap Make the pcap simulator to ./objs/pcap_simulator"
@echo " test Make the test tool to ./objs/srs_test and ./objs/srs_gb28181_test ./objs/srs_blackbox_test"
@echo " clean Remove all tools at ./objs"

@ -14,6 +14,8 @@ git clone -b feature/rtc https://github.com/ossrs/srs-bench.git &&
cd srs-bench && make
```
> Note: 依赖Go编译工具建议使用 Go 1.17 及以上的版本。
编译会生成下面的工具:
* `./objs/srs_bench` 压测模拟大量客户端的负载测试支持SRS、GB28181和Janus三种场景。
@ -33,7 +35,7 @@ cd srs/trunk && ./configure --h265=on --gb28181=on && make &&
具体场景,请按下面的操作启动测试。
## Player for Live
## Player for WHEP
直播播放压测,一个流,很多个播放。
@ -49,7 +51,7 @@ ffmpeg -re -i doc/source.200kbps.768x320.flv -c copy -f flv -y rtmp://localhost/
./objs/srs_bench -sr webrtc://localhost/live/livestream -nn 100
```
## Publisher for Live or RTC
## Publisher for WHIP
直播或会议场景推流压测,一般会推多个流。
@ -63,7 +65,7 @@ ffmpeg -re -i doc/source.200kbps.768x320.flv -c copy -f flv -y rtmp://localhost/
> 注意帧率是原始视频的帧率由于264中没有这个信息所以需要传递。
## Multipel Player or Publisher for RTC
## Multiple WHIP or WHEP for RTC
会议场景的播放压测会多个客户端播放多个流比如3人会议那么就有3个推流每个流有2个播放。
@ -84,7 +86,7 @@ ffmpeg -re -i doc/source.200kbps.768x320.flv -c copy -f flv -y rtmp://localhost/
> 备注URL的变量格式参考Go的`fmt.Sprintf`,比如可以用`webrtc://localhost/live/livestream_%03d`。
<a name="dvr"></a>
## DVR for Benchmark
## DVR for RTC Benchmark
录制场景,主要是把内容录制下来后,可分析,也可以用于推流。
@ -120,6 +122,37 @@ ffmpeg -re -i doc/source.200kbps.768x320.flv -c copy -f flv -y rtmp://localhost/
> Note: 可以传递更多参数详细参考SRS支持的参数。
## Reconnecting Load Test
建立连接和断开重连的压测可以测试SRS在多个Source时是否有内存泄露问题参考 [#3667](https://github.com/ossrs/srs/discussions/3667#discussioncomment-8969107)
RTMP重连测试
```bash
for ((i=0;;i++)); do
./objs/srs_bench -sfu=live -pr=rtmp://localhost/live${i}/stream -sn=1000 -cap=true;
sleep 10;
done
```
SRT重连测试
```bash
for ((i=0;;i++)); do
./objs/srs_bench -sfu=live -pr='srt://127.0.0.1:10080?streamid=#!::'m=publish,r=live${i}/stream -sn=1000 -cap=true;
sleep 10;
done
```
WebRTC重连测试
```bash
for ((i=0;;i++)); do
./objs/srs_bench -sfu=rtc -pr=webrtc://localhost/live${i}/livestream -sn=1000 -cap=true;
sleep 10;
done
```
## Regression Test
回归测试需要先启动[SRS](https://github.com/ossrs/srs/issues/307)支持WebRTC推拉流
@ -329,4 +362,50 @@ make -j10 && ./objs/srs_bench -sfu janus \
-nn 5
```
## Install LIBSRT
我们使用 [srtgo](https://github.com/Haivision/srtgo) 库测试SRT协议需要安装libsrt库
参考[macOS](https://github.com/Haivision/srt/blob/master/docs/build/build-macOS.md)
```bash
brew install srt
```
如果是Ubuntu可以参考[Ubuntu](https://github.com/Haivision/srt/blob/master/docs/build/package-managers.md):
```bash
apt-get install -y libsrt
```
安装完libsrt后直接编译srs-bench即可
```bash
make
```
## Ubuntu Docker
如果使用Ubuntu编译推荐使用 `ossrs/srs:ubuntu20` 作为镜像编译已经编译了openssl和libsrt启动容器
```bash
docker run --rm -it -v $(pwd):/g -w /g ossrs/srs:ubuntu20 make
```
## GoLand
使用GoLand编译和调试时需要设置libsrt的环境变量首先可以使用brew获取路径
```bash
brew --prefix srt
#/opt/homebrew/opt/srt
```
然后在GoLand中编辑配置 `Edit Configurations`,添加环境变量:
```bash
CGO_CFLAGS=-I/opt/homebrew/opt/srt/include;CGO_LDFLAGS=-L/opt/homebrew/opt/srt/lib -lsrt
```
> Note: 特别注意的是CGO_LDFLAGS是可以有空格的不能使用字符串否则找不到库。
2021.01, Winlin

@ -935,8 +935,8 @@ func TestSlow_SrtPublish_HttpTsPlay_HEVC_Basic(t *testing.T) {
}
// Note that HLS score is low, so we only check duration.
if dv := m.Duration(); dv < duration / 2 {
r5 = errors.Errorf("short duration=%v < %v, %v, %v", dv, duration / 2, m.String(), str)
if dv := m.Duration(); dv < duration/2 {
r5 = errors.Errorf("short duration=%v < %v, %v, %v", dv, duration/2, m.String(), str)
}
if v := m.Video(); v == nil {

@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
// # Copyright (c) 2022-2024 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
@ -57,7 +57,11 @@ func Parse(ctx context.Context) interface{} {
fl.Usage = func() {
fmt.Println(fmt.Sprintf("Usage: %v [Options]", os.Args[0]))
fmt.Println(fmt.Sprintf("Options:"))
fmt.Println(fmt.Sprintf(" -sfu The target SFU, srs or gb28181 or janus. Default: srs"))
fmt.Println(fmt.Sprintf(" -sfu The target server that can be rtc, live, janus, or gb28181. Default: rtc"))
fmt.Println(fmt.Sprintf(" rtc/srs: SRS WebRTC SFU server, for WebRTC/WHIP/WHEP."))
fmt.Println(fmt.Sprintf(" live: SRS live streaming server, for RTMP/HTTP-FLV/HLS."))
fmt.Println(fmt.Sprintf(" janus: Janus WebRTC SFU server, for janus private protocol."))
fmt.Println(fmt.Sprintf(" gb28181: GB media server, for GB protocol."))
fmt.Println(fmt.Sprintf("SIP:"))
fmt.Println(fmt.Sprintf(" -user The SIP username, ID of device."))
fmt.Println(fmt.Sprintf(" -random Append N number to user as random device ID, like 1320000001."))

@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
// # Copyright (c) 2022-2024 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in

@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
// # Copyright (c) 2022-2024 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in

@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
// # Copyright (c) 2022-2024 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in

@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
// # Copyright (c) 2022-2024 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in

@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
// # Copyright (c) 2022-2024 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in

@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2022 Winlin
// # Copyright (c) 2022-2024 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in

@ -5,6 +5,7 @@ go 1.17
require (
github.com/ghettovoice/gosip v0.0.0-20220929080231-de8ba881be83
github.com/google/gopacket v1.1.19
github.com/haivision/srtgo v0.0.0-20230627061225-a70d53fcd618
github.com/ossrs/go-oryx-lib v0.0.9
github.com/pion/ice/v2 v2.3.6
github.com/pion/interceptor v0.1.17
@ -28,6 +29,7 @@ require (
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.8 // indirect
github.com/mattn/go-pointer v0.0.1 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/pion/datachannel v1.5.5 // indirect
github.com/pion/dtls/v2 v2.2.7 // indirect
@ -40,12 +42,12 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
github.com/sirupsen/logrus v1.4.2 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 // indirect
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/term v0.8.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

@ -31,6 +31,8 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/haivision/srtgo v0.0.0-20230627061225-a70d53fcd618 h1:oGPTZa7I5wqmQs/UhWHj3ln6/CjQX2yQt784xx6H0wI=
github.com/haivision/srtgo v0.0.0-20230627061225-a70d53fcd618/go.mod h1:aTd4vOr9wtzkCbbocUFh6atlJy7H/iV5jhqEWlTdCdA=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
@ -44,6 +46,8 @@ github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaa
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@ -115,6 +119,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -122,8 +127,9 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 h1:hNna6Fi0eP1f2sMBe/rJicDmaHmoXGe1Ta84FPYHLuE=
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0=
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
@ -140,8 +146,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -161,8 +169,9 @@ golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -178,6 +187,7 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200926100807-9d91bd62050c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -193,8 +203,10 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -202,8 +214,10 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -213,8 +227,9 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=

@ -66,7 +66,11 @@ func Parse(ctx context.Context) {
fl.Usage = func() {
fmt.Println(fmt.Sprintf("Usage: %v [Options]", os.Args[0]))
fmt.Println(fmt.Sprintf("Options:"))
fmt.Println(fmt.Sprintf(" -sfu The target SFU, srs or gb28181 or janus. Default: srs"))
fmt.Println(fmt.Sprintf(" -sfu The target server that can be rtc, live, janus, or gb28181. Default: rtc"))
fmt.Println(fmt.Sprintf(" rtc/srs: SRS WebRTC SFU server, for WebRTC/WHIP/WHEP."))
fmt.Println(fmt.Sprintf(" live: SRS live streaming server, for RTMP/HTTP-FLV/HLS."))
fmt.Println(fmt.Sprintf(" janus: Janus WebRTC SFU server, for janus private protocol."))
fmt.Println(fmt.Sprintf(" gb28181: GB media server, for GB protocol."))
fmt.Println(fmt.Sprintf(" -nn The number of clients to simulate. Default: 1"))
fmt.Println(fmt.Sprintf(" -sn The number of streams to simulate. Variable: %%d. Default: 1"))
fmt.Println(fmt.Sprintf(" -delay The start delay in ms for each client or stream to simulate. Default: 50"))

@ -0,0 +1,195 @@
// The MIT License (MIT)
//
// # Copyright (c) 2021 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package live
import (
"context"
"flag"
"fmt"
"net"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
)
var closeAfterPublished bool
var pr string
var streams, delay int
var statListen string
func Parse(ctx context.Context) {
fl := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
var sfu string
fl.StringVar(&sfu, "sfu", "srs", "The SFU server, srs or gb28181 or janus")
fl.BoolVar(&closeAfterPublished, "cap", false, "")
fl.StringVar(&pr, "pr", "", "")
fl.IntVar(&streams, "sn", 1, "")
fl.IntVar(&delay, "delay", 10, "")
fl.StringVar(&statListen, "stat", "", "")
fl.Usage = func() {
fmt.Println(fmt.Sprintf("Usage: %v [Options]", os.Args[0]))
fmt.Println(fmt.Sprintf("Options:"))
fmt.Println(fmt.Sprintf(" -sfu The target server that can be rtc, live, janus, or gb28181. Default: rtc"))
fmt.Println(fmt.Sprintf(" rtc/srs: SRS WebRTC SFU server, for WebRTC/WHIP/WHEP."))
fmt.Println(fmt.Sprintf(" live: SRS live streaming server, for RTMP/HTTP-FLV/HLS."))
fmt.Println(fmt.Sprintf(" janus: Janus WebRTC SFU server, for janus private protocol."))
fmt.Println(fmt.Sprintf(" -sn The number of streams to simulate. Variable: %%d. Default: 1"))
fmt.Println(fmt.Sprintf(" -delay The start delay in ms for each client or stream to simulate. Default: 50"))
fmt.Println(fmt.Sprintf(" -stat [Optional] The stat server API listen port."))
fmt.Println(fmt.Sprintf("Publisher:"))
fmt.Println(fmt.Sprintf(" -pr The url to publish. If sn exceed 1, auto append variable %%d."))
fmt.Println(fmt.Sprintf(" -cap Whether to close connection after publish. Default: false"))
fmt.Println(fmt.Sprintf("\n例如1个推流无媒体传输:"))
fmt.Println(fmt.Sprintf(" %v -pr=rtmp://localhost/live/livestream -cap=true", os.Args[0]))
fmt.Println(fmt.Sprintf("\n例如2个推流无媒体传输"))
fmt.Println(fmt.Sprintf(" %v -pr=rtmp://localhost/live/livestream_%%d -sn=2 -cap=true", os.Args[0]))
fmt.Println()
}
_ = fl.Parse(os.Args[1:])
showHelp := streams <= 0
if pr == "" {
showHelp = true
}
if showHelp {
fl.Usage()
os.Exit(-1)
}
if statListen != "" && !strings.Contains(statListen, ":") {
statListen = ":" + statListen
}
summaryDesc := fmt.Sprintf("streams=%v", streams)
if pr != "" {
summaryDesc = fmt.Sprintf("%v, publish=(url=%v,cap=%v)",
summaryDesc, pr, closeAfterPublished)
}
logger.Tf(ctx, "Run benchmark with %v", summaryDesc)
}
func Run(ctx context.Context) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// Run tasks.
var wg sync.WaitGroup
defer wg.Wait()
// Run STAT API server.
wg.Add(1)
go func() {
defer wg.Done()
if statListen == "" {
return
}
var lc net.ListenConfig
ln, err := lc.Listen(ctx, "tcp", statListen)
if err != nil {
logger.Ef(ctx, "stat listen err+%v", err)
cancel()
return
}
mux := http.NewServeMux()
handleStat(ctx, mux, statListen)
srv := &http.Server{
Handler: mux,
BaseContext: func(listener net.Listener) context.Context {
return ctx
},
}
go func() {
<-ctx.Done()
srv.Shutdown(ctx)
}()
logger.Tf(ctx, "Stat listen at %v", statListen)
if err := srv.Serve(ln); err != nil {
if ctx.Err() == nil {
logger.Ef(ctx, "stat serve err+%v", err)
cancel()
}
return
}
}()
// Run all publishers.
publisherStartedCtx, publisherStartedCancel := context.WithCancel(ctx)
defer publisherStartedCancel()
for i := 0; pr != "" && i < streams && ctx.Err() == nil; i++ {
r_auto := pr
if streams > 1 && !strings.Contains(r_auto, "%") {
r_auto += "%d"
}
r2 := r_auto
if strings.Contains(r2, "%") {
r2 = fmt.Sprintf(r2, i)
}
gStatLive.Publishers.Expect++
gStatLive.Publishers.Alive++
wg.Add(1)
go func(pr string) {
defer wg.Done()
defer func() {
gStatLive.Publishers.Alive--
logger.Tf(ctx, "Publisher %v done, alive=%v", pr, gStatLive.Publishers.Alive)
<- publisherStartedCtx.Done()
if gStatLive.Publishers.Alive == 0 {
cancel()
}
}()
if err := startPublish(ctx, pr, closeAfterPublished); err != nil {
if errors.Cause(err) != context.Canceled {
logger.Wf(ctx, "Run err %+v", err)
}
}
}(r2)
if delay > 0 {
time.Sleep(time.Duration(delay) * time.Millisecond)
}
}
return nil
}

@ -0,0 +1,210 @@
// The MIT License (MIT)
//
// # Copyright (c) 2021 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package live
import (
"context"
"fmt"
"math/rand"
"net"
"net/url"
"strconv"
"strings"
"time"
"github.com/haivision/srtgo"
"github.com/ossrs/go-oryx-lib/amf0"
"github.com/ossrs/go-oryx-lib/errors"
"github.com/ossrs/go-oryx-lib/logger"
"github.com/ossrs/go-oryx-lib/rtmp"
)
func startPublish(ctx context.Context, r string, closeAfterPublished bool) error {
ctx = logger.WithContext(ctx)
logger.Tf(ctx, "Run publish url=%v, cap=%v", r, closeAfterPublished)
u, err := url.Parse(r)
if err != nil {
return errors.Wrapf(err, "parse %v", r)
}
if u.Scheme == "rtmp" {
return startPublishRTMP(ctx, u, closeAfterPublished)
} else if u.Scheme == "srt" {
return startPublishSRT(ctx, u, closeAfterPublished)
}
return fmt.Errorf("invalid schema %v of %v", u.Scheme, r)
}
func startPublishSRT(ctx context.Context, u *url.URL, closeAfterPublished bool) (err error) {
// Parse host and port.
port := 1935
if u.Port() != "" {
if port, err = strconv.Atoi(u.Port()); err != nil {
return errors.Wrapf(err, "parse port %v", u.Port())
}
}
ips, err := net.LookupIP(u.Hostname())
if err != nil {
return errors.Wrapf(err, "lookup %v", u.Hostname())
}
if len(ips) == 0 {
return errors.Errorf("no ips for %v", u.Hostname())
}
logger.Tf(ctx, "Parse url %v to host=%v, ip=%v, port=%v",
u.String(), u.Hostname(), ips[0], port)
// Setup libsrt.
client := srtgo.NewSrtSocket(ips[0].To4().String(), uint16(port),
map[string]string{
"transtype": "live",
"tsbpdmode": "false",
"tlpktdrop": "false",
"latency": "0",
"streamid": fmt.Sprintf("#%v", u.Fragment),
},
)
defer client.Close()
if err := client.Connect(); err != nil {
return errors.Wrapf(err, "SRT connect to %v:%v", u.Hostname(), port)
}
logger.Tf(ctx, "Connect to SRT server %v:%v success", u.Hostname(), port)
// We should wait for a while after connected to SRT server before quit. Because SRT server use timeout
// to detect UDP connection status, so we should never reconnect very fast.
select {
case <-ctx.Done():
case <-time.After(3 * time.Second):
logger.Tf(ctx, "SRT publish stream success, stream=%v", u.Fragment)
}
if closeAfterPublished {
logger.Tf(ctx, "Close connection after published")
return nil
}
return nil
}
func startPublishRTMP(ctx context.Context, u *url.URL, closeAfterPublished bool) (err error) {
parts := strings.Split(u.Path, "/")
if len(parts) == 0 {
return errors.Errorf("invalid path %v", u.Path)
}
app, stream := strings.Join(parts[:len(parts)-1], "/"), parts[len(parts)-1]
// Parse host and port.
port := 1935
if u.Port() != "" {
if port, err = strconv.Atoi(u.Port()); err != nil {
return errors.Wrapf(err, "parse port %v", u.Port())
}
}
ips, err := net.LookupIP(u.Hostname())
if err != nil {
return errors.Wrapf(err, "lookup %v", u.Hostname())
}
if len(ips) == 0 {
return errors.Errorf("no ips for %v", u.Hostname())
}
logger.Tf(ctx, "Parse url %v to host=%v, ip=%v, port=%v, app=%v, stream=%v",
u.String(), u.Hostname(), ips[0], port, app, stream)
// Connect via TCP client.
c, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: ips[0], Port: port})
if err != nil {
return errors.Wrapf(err, "dial %v %v", u.Hostname(), u.Port())
}
defer c.Close()
logger.Tf(ctx, "Connect to RTMP server %v:%v success", u.Hostname(), port)
// RTMP Handshake.
rd := rand.New(rand.NewSource(time.Now().UnixNano()))
hs := rtmp.NewHandshake(rd)
if err := hs.WriteC0S0(c); err != nil {
return errors.Wrap(err, "write c0")
}
if err := hs.WriteC1S1(c); err != nil {
return errors.Wrap(err, "write c1")
}
if _, err = hs.ReadC0S0(c); err != nil {
return errors.Wrap(err, "read s1")
}
s1, err := hs.ReadC1S1(c)
if err != nil {
return errors.Wrap(err, "read s1")
}
if _, err = hs.ReadC2S2(c); err != nil {
return errors.Wrap(err, "read s2")
}
if err := hs.WriteC2S2(c, s1); err != nil {
return errors.Wrap(err, "write c2")
}
logger.Tf(ctx, "RTMP handshake with %v:%v success", ips[0], port)
// Do connect and publish.
client := rtmp.NewProtocol(c)
connectApp := rtmp.NewConnectAppPacket()
tcURL := fmt.Sprintf("rtmp://%v%v", u.Hostname(), app)
connectApp.CommandObject.Set("tcUrl", amf0.NewString(tcURL))
if err = client.WritePacket(connectApp, 1); err != nil {
return errors.Wrap(err, "write connect app")
}
var connectAppRes *rtmp.ConnectAppResPacket
if _, err = client.ExpectPacket(&connectAppRes); err != nil {
return errors.Wrap(err, "expect connect app res")
}
logger.Tf(ctx, "RTMP connect app success, tcUrl=%v", tcURL)
createStream := rtmp.NewCreateStreamPacket()
if err = client.WritePacket(createStream, 1); err != nil {
return errors.Wrap(err, "write create stream")
}
var createStreamRes *rtmp.CreateStreamResPacket
if _, err = client.ExpectPacket(&createStreamRes); err != nil {
return errors.Wrap(err, "expect create stream res")
}
logger.Tf(ctx, "RTMP create stream success")
publish := rtmp.NewPublishPacket()
publish.StreamName = *amf0.NewString(stream)
if err = client.WritePacket(publish, 1); err != nil {
return errors.Wrap(err, "write publish")
}
logger.Tf(ctx, "RTMP publish stream success, stream=%v", stream)
if closeAfterPublished {
logger.Tf(ctx, "Close connection after published")
return nil
}
return nil
}

@ -0,0 +1,68 @@
// The MIT License (MIT)
//
// # Copyright (c) 2021 Winlin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package live
import (
"context"
"encoding/json"
"net/http"
"strings"
"github.com/ossrs/go-oryx-lib/logger"
)
type statLive struct {
Publishers struct {
Expect int `json:"expect"`
Alive int `json:"alive"`
} `json:"publishers"`
Subscribers struct {
Expect int `json:"expect"`
Alive int `json:"alive"`
} `json:"subscribers"`
PeerConnection interface{} `json:"random-pc"`
}
var gStatLive statLive
func handleStat(ctx context.Context, mux *http.ServeMux, l string) {
if strings.HasPrefix(l, ":") {
l = "127.0.0.1" + l
}
logger.Tf(ctx, "Handle http://%v/api/v1/sb/live", l)
mux.HandleFunc("/api/v1/sb/live", func(w http.ResponseWriter, r *http.Request) {
res := &struct {
Code int `json:"code"`
Data interface{} `json:"data"`
}{
0, &gStatLive,
}
b, err := json.Marshal(res)
if err != nil {
logger.Wf(ctx, "marshal %v err %+v", res, err)
return
}
w.Write(b)
})
}

@ -24,27 +24,31 @@ import (
"context"
"flag"
"fmt"
"github.com/ossrs/go-oryx-lib/logger"
"github.com/ossrs/srs-bench/gb28181"
"github.com/ossrs/srs-bench/janus"
"github.com/ossrs/srs-bench/srs"
"io/ioutil"
"os"
"os/signal"
"syscall"
"github.com/ossrs/go-oryx-lib/logger"
"github.com/ossrs/srs-bench/gb28181"
"github.com/ossrs/srs-bench/janus"
"github.com/ossrs/srs-bench/live"
"github.com/ossrs/srs-bench/srs"
)
func main() {
var sfu string
fl := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
fl.SetOutput(ioutil.Discard)
fl.StringVar(&sfu, "sfu", "srs", "The SFU server, srs or gb28181 or janus")
fl.StringVar(&sfu, "sfu", "rtc", "")
_ = fl.Parse(os.Args[1:])
ctx := context.Background()
var conf interface{}
if sfu == "srs" {
if sfu == "rtc" || sfu == "srs" {
srs.Parse(ctx)
} else if sfu == "live" {
live.Parse(ctx)
} else if sfu == "gb28181" {
conf = gb28181.Parse(ctx)
} else if sfu == "janus" {
@ -52,7 +56,11 @@ func main() {
} else {
fmt.Println(fmt.Sprintf("Usage: %v [Options]", os.Args[0]))
fmt.Println(fmt.Sprintf("Options:"))
fmt.Println(fmt.Sprintf(" -sfu The target SFU, srs or gb28181 or janus. Default: srs"))
fmt.Println(fmt.Sprintf(" -sfu The target server that can be rtc, live, janus, or gb28181. Default: rtc"))
fmt.Println(fmt.Sprintf(" rtc/srs: SRS WebRTC SFU server, for WebRTC/WHIP/WHEP."))
fmt.Println(fmt.Sprintf(" live: SRS live streaming server, for RTMP/HTTP-FLV/HLS."))
fmt.Println(fmt.Sprintf(" janus: Janus WebRTC SFU server, for janus private protocol."))
fmt.Println(fmt.Sprintf(" gb28181: GB media server, for GB protocol."))
os.Exit(-1)
}
@ -67,8 +75,10 @@ func main() {
}()
var err error
if sfu == "srs" {
if sfu == "rtc" || sfu == "srs" {
err = srs.Run(ctx)
} else if sfu == "live" {
err = live.Run(ctx)
} else if sfu == "gb28181" {
err = gb28181.Run(ctx, conf)
} else if sfu == "janus" {

@ -34,7 +34,7 @@ import (
)
// @see https://github.com/pion/webrtc/blob/master/examples/play-from-disk/main.go
func startPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps int, enableAudioLevel, enableTWCC bool) error {
func startPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps int, enableAudioLevel, enableTWCC, closeAfterPublished bool) error {
ctx = logger.WithContext(ctx)
logger.Tf(ctx, "Run publish url=%v, audio=%v, video=%v, fps=%v, audio-level=%v, twcc=%v",
@ -77,10 +77,13 @@ func startPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
return nil, err
}
if sourceAudio != "" {
// For CAP, we always add audio track, because both audio and video are disabled for CAP, which will
// cause failed when exchange SDP.
if sourceAudio != "" || closeAfterPublished {
aIngester = newAudioIngester(sourceAudio)
registry.Add(&rtpInteceptorFactory{aIngester.audioLevelInterceptor})
}
if sourceVideo != "" {
vIngester = newVideoIngester(sourceVideo)
registry.Add(&rtpInteceptorFactory{vIngester.markerInterceptor})
@ -178,6 +181,7 @@ func startPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
// Wait for event from context or tracks.
var wg sync.WaitGroup
defer wg.Wait()
wg.Add(1)
go func() {
@ -186,6 +190,18 @@ func startPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
doClose() // Interrupt the RTCP read.
}()
// If CAP, directly close the connection after published.
if closeAfterPublished {
select {
case <-ctx.Done():
case <-pcDoneCtx.Done():
}
logger.Tf(ctx, "Close connection after published")
cancel()
return nil
}
wg.Add(1)
go func() {
defer wg.Done()
@ -295,6 +311,5 @@ func startPublish(ctx context.Context, r, sourceAudio, sourceVideo string, fps i
}
}()
wg.Wait()
return nil
}

@ -2153,8 +2153,8 @@ func TestRtcDTLS_ClientPassive_ARQ_Certificate_After_ClientHello(t *testing.T) {
// case, we corrupt the ClientHello packet sent by srs-bench.
// Note that the passive mode is not being tested as the focus is solely on testing srs-server.
//
// [Corrupt] No.1 srs-bench: ClientHello(Epoch=0, Sequence=0), change length from 129 to 0xf.
// No.2 srs-server: Alert (Level: Fatal, Description: Illegal Parameter)
// [Corrupt] No.1 srs-bench: ClientHello(Epoch=0, Sequence=0), change length from 129 to 0xf.
// No.2 srs-server: Alert (Level: Fatal, Description: Illegal Parameter)
func TestRtcDTLS_ClientActive_Corrupt_ClientHello(t *testing.T) {
ctx := logger.WithContext(context.Background())
ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond)
@ -2209,10 +2209,11 @@ func TestRtcDTLS_ClientActive_Corrupt_ClientHello(t *testing.T) {
// case, we corrupt the ClientHello packet sent by srs-bench.
// Note that the passive mode is not being tested as the focus is solely on testing srs-server.
//
// No.1 srs-bench: ClientHello
// No.2 srs-server: ServerHello, Certificate, ServerKeyExchange, CertificateRequest, ServerHelloDone
// [Corrupt] No.3 srs-bench: Certificate, ClientKeyExchange, CertificateVerify, ChangeCipherSpec, Finished
// No.4 srs-server: Alert (Level: Fatal, Description: Illegal Parameter)
// No.1 srs-bench: ClientHello
// No.2 srs-server: ServerHello, Certificate, ServerKeyExchange, CertificateRequest, ServerHelloDone
// [Corrupt] No.3 srs-bench: Certificate, ClientKeyExchange, CertificateVerify, ChangeCipherSpec, Finished
// No.4 srs-server: Alert (Level: Fatal, Description: Illegal Parameter)
//
// [Corrupt] No.1 srs-bench: ClientHello(Epoch=0, Sequence=0), change length from 129 to 0xf.
// No.2 srs-server: Alert (Level: Fatal, Description: Illegal Parameter)
func TestRtcDTLS_ClientActive_Corrupt_Certificate(t *testing.T) {

@ -46,6 +46,8 @@ var clients, streams, delay int
var statListen string
var closeAfterPublished bool
func Parse(ctx context.Context) {
fl := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
@ -71,10 +73,16 @@ func Parse(ctx context.Context) {
fl.StringVar(&statListen, "stat", "", "")
fl.BoolVar(&closeAfterPublished, "cap", false, "")
fl.Usage = func() {
fmt.Println(fmt.Sprintf("Usage: %v [Options]", os.Args[0]))
fmt.Println(fmt.Sprintf("Options:"))
fmt.Println(fmt.Sprintf(" -sfu The target SFU, srs or gb28181 or janus. Default: srs"))
fmt.Println(fmt.Sprintf(" -sfu The target server that can be rtc, live, janus, or gb28181. Default: rtc"))
fmt.Println(fmt.Sprintf(" rtc/srs: SRS WebRTC SFU server, for WebRTC/WHIP/WHEP."))
fmt.Println(fmt.Sprintf(" live: SRS live streaming server, for RTMP/HTTP-FLV/HLS."))
fmt.Println(fmt.Sprintf(" janus: Janus WebRTC SFU server, for janus private protocol."))
fmt.Println(fmt.Sprintf(" gb28181: GB media server, for GB protocol."))
fmt.Println(fmt.Sprintf(" -nn The number of clients to simulate. Default: 1"))
fmt.Println(fmt.Sprintf(" -sn The number of streams to simulate. Variable: %%d. Default: 1"))
fmt.Println(fmt.Sprintf(" -delay The start delay in ms for each client or stream to simulate. Default: 50"))
@ -91,6 +99,7 @@ func Parse(ctx context.Context) {
fmt.Println(fmt.Sprintf(" -fps [Optional] The fps of .h264 source file."))
fmt.Println(fmt.Sprintf(" -sa [Optional] The file path to read audio, ignore if empty."))
fmt.Println(fmt.Sprintf(" -sv [Optional] The file path to read video, ignore if empty."))
fmt.Println(fmt.Sprintf(" -cap Whether to close connection after publish. Default: false"))
fmt.Println(fmt.Sprintf("\n例如1个播放1个推流:"))
fmt.Println(fmt.Sprintf(" %v -sr webrtc://localhost/live/livestream", os.Args[0]))
fmt.Println(fmt.Sprintf(" %v -pr webrtc://localhost/live/livestream -sa avatar.ogg -sv avatar.h264 -fps 25", os.Args[0]))
@ -114,7 +123,7 @@ func Parse(ctx context.Context) {
if sr == "" && pr == "" {
showHelp = true
}
if pr != "" && (sourceAudio == "" && sourceVideo == "") {
if pr != "" && !closeAfterPublished && (sourceAudio == "" && sourceVideo == "") {
showHelp = true
}
if showHelp {
@ -131,8 +140,8 @@ func Parse(ctx context.Context) {
summaryDesc = fmt.Sprintf("%v, play(url=%v, da=%v, dv=%v, pli=%v)", summaryDesc, sr, dumpAudio, dumpVideo, pli)
}
if pr != "" {
summaryDesc = fmt.Sprintf("%v, publish(url=%v, sa=%v, sv=%v, fps=%v)",
summaryDesc, pr, sourceAudio, sourceVideo, fps)
summaryDesc = fmt.Sprintf("%v, publish(url=%v, sa=%v, sv=%v, fps=%v, cap=%v)",
summaryDesc, pr, sourceAudio, sourceVideo, fps, closeAfterPublished)
}
logger.Tf(ctx, "Run benchmark with %v", summaryDesc)
@ -161,6 +170,7 @@ func Run(ctx context.Context) error {
// Run tasks.
var wg sync.WaitGroup
defer wg.Wait()
// Run STAT API server.
wg.Add(1)
@ -266,7 +276,7 @@ func Run(ctx context.Context) error {
gStatRTC.Publishers.Alive--
}()
if err := startPublish(ctx, pr, sourceAudio, sourceVideo, fps, audioLevel, videoTWCC); err != nil {
if err := startPublish(ctx, pr, sourceAudio, sourceVideo, fps, audioLevel, videoTWCC, closeAfterPublished); err != nil {
if errors.Cause(err) != context.Canceled {
logger.Wf(ctx, "Run err %+v", err)
}
@ -276,7 +286,5 @@ func Run(ctx context.Context) error {
time.Sleep(time.Duration(delay) * time.Millisecond)
}
wg.Wait()
return nil
}

@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

@ -0,0 +1,63 @@
[![PkgGoDev](https://pkg.go.dev/badge/github.com/haivision/srtgo)](https://pkg.go.dev/github.com/haivision/srtgo)
# srtgo
Go bindings for [SRT](https://github.com/Haivision/srt) (Secure Reliable Transport), the open source transport technology that optimizes streaming performance across unpredictable networks.
## Why srtgo?
The purpose of srtgo is easing the adoption of SRT transport technology. Using Go, with just a few lines of code you can implement an application that sends/receives data with all the benefits of SRT technology: security and reliability, while keeping latency low.
## Is this a new implementation of SRT?
No! We are just exposing the great work done by the community in the [SRT project](https://github.com/Haivision/srt) as a golang library. All the functionality and implementation still resides in the official SRT project.
# Features supported
* Basic API exposed to easy develop SRT sender/receiver apps
* Caller and Listener mode
* Live transport type
* File transport type
* Message/Buffer API
* SRT transport options up to SRT 1.4.1
* SRT Stats retrieval
# Usage
Example of a SRT receiver application:
``` go
package main
import (
"github.com/haivision/srtgo"
"fmt"
)
func main() {
options := make(map[string]string)
options["transtype"] = "file"
sck := srtgo.NewSrtSocket("0.0.0.0", 8090, options)
defer sck.Close()
sck.Listen(1)
s, _ := sck.Accept()
defer s.Close()
buf := make([]byte, 2048)
for {
n, _ := s.Read(buf)
if n == 0 {
break
}
fmt.Println("Received %d bytes", n)
}
//....
}
```
# Dependencies
* srtlib
You can find detailed instructions about how to install srtlib in its [README file](https://github.com/Haivision/srt#requirements)
gosrt has been developed with srt 1.4.1 as its main target and has been successfully tested in srt 1.3.4 and above.

@ -0,0 +1,69 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
SRTSOCKET srt_accept_wrapped(SRTSOCKET lsn, struct sockaddr* addr, int* addrlen, int *srterror, int *syserror)
{
int ret = srt_accept(lsn, addr, addrlen);
if (ret < 0) {
*srterror = srt_getlasterror(syserror);
}
return ret;
}
*/
import "C"
import (
"fmt"
"net"
"syscall"
"unsafe"
)
func srtAcceptImpl(lsn C.SRTSOCKET, addr *C.struct_sockaddr, addrlen *C.int) (C.SRTSOCKET, error) {
srterr := C.int(0)
syserr := C.int(0)
socket := C.srt_accept_wrapped(lsn, addr, addrlen, &srterr, &syserr)
if srterr != 0 {
srterror := SRTErrno(srterr)
if syserr < 0 {
srterror.wrapSysErr(syscall.Errno(syserr))
}
return socket, srterror
}
return socket, nil
}
// Accept an incoming connection
func (s SrtSocket) Accept() (*SrtSocket, *net.UDPAddr, error) {
var err error
if !s.blocking {
err = s.pd.wait(ModeRead)
if err != nil {
return nil, nil, err
}
}
var addr syscall.RawSockaddrAny
sclen := C.int(syscall.SizeofSockaddrAny)
socket, err := srtAcceptImpl(s.socket, (*C.struct_sockaddr)(unsafe.Pointer(&addr)), &sclen)
if err != nil {
return nil, nil, err
}
if socket == SRT_INVALID_SOCK {
return nil, nil, fmt.Errorf("srt accept, error accepting the connection: %w", srtGetAndClearError())
}
newSocket, err := newFromSocket(&s, socket)
if err != nil {
return nil, nil, fmt.Errorf("new socket could not be created: %w", err)
}
udpAddr, err := udpAddrFromSockaddr(&addr)
if err != nil {
return nil, nil, err
}
return newSocket, udpAddr, nil
}

@ -0,0 +1,7 @@
#include <srt/srt.h>
int srtListenCBWrapper(void* opaque, SRTSOCKET ns, int hs_version, struct sockaddr* peeraddr, char* streamid);
void srtConnectCBWrapper(void* opaque, SRTSOCKET ns, int errorcode, struct sockaddr* peeraddr, int token);
int srtListenCB(void* opaque, SRTSOCKET ns, int hs_version, const struct sockaddr* peeraddr, const char* streamid);
void srtConnectCB(void* opaque, SRTSOCKET ns, int errorcode, const struct sockaddr* peeraddr, int token);

@ -0,0 +1,17 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include "callback.h"
int srtListenCB(void* opaque, SRTSOCKET ns, int hs_version, const struct sockaddr* peeraddr, const char* streamid)
{
return srtListenCBWrapper(opaque, ns, hs_version, (struct sockaddr*)peeraddr, (char*)streamid);
}
void srtConnectCB(void* opaque, SRTSOCKET ns, int errorcode, const struct sockaddr* peeraddr, int token)
{
srtConnectCBWrapper(opaque, ns, errorcode, (struct sockaddr*)peeraddr, token);
}
*/
import "C"

@ -0,0 +1,242 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
*/
import "C"
import (
"strconv"
"syscall"
)
type SrtInvalidSock struct{}
type SrtRendezvousUnbound struct{}
type SrtSockConnected struct{}
type SrtConnectionRejected struct{}
type SrtConnectTimeout struct{}
type SrtSocketClosed struct{}
type SrtEpollTimeout struct{}
func (m *SrtInvalidSock) Error() string {
return "Socket u indicates no valid socket ID"
}
func (m *SrtRendezvousUnbound) Error() string {
return "Socket u is in rendezvous mode, but it wasn't bound"
}
func (m *SrtSockConnected) Error() string {
return "Socket u is already connected"
}
func (m *SrtConnectionRejected) Error() string {
return "Connection has been rejected"
}
func (m *SrtConnectTimeout) Error() string {
return "Connection has been timed out"
}
func (m *SrtSocketClosed) Error() string {
return "The socket has been closed"
}
func (m *SrtEpollTimeout) Error() string {
return "Operation has timed out"
}
func (m *SrtEpollTimeout) Timeout() bool {
return true
}
func (m *SrtEpollTimeout) Temporary() bool {
return true
}
//MUST be called from same OS thread that generated the error (i.e.: use runtime.LockOSThread())
func srtGetAndClearError() error {
defer C.srt_clearlasterror()
eSysErrno := C.int(0)
errno := C.srt_getlasterror(&eSysErrno)
srterr := SRTErrno(errno)
if eSysErrno != 0 {
return srterr.wrapSysErr(syscall.Errno(eSysErrno))
}
return srterr
}
//Based of off golang errno handling: https://cs.opensource.google/go/go/+/refs/tags/go1.16.6:src/syscall/syscall_unix.go;l=114
type SRTErrno int
func (e SRTErrno) Error() string {
//Workaround for unknown being -1
if e == Unknown {
return "Internal error when setting the right error code"
}
if 0 <= int(e) && int(e) < len(srterrors) {
s := srterrors[e]
if s != "" {
return s
}
}
return "srterrno: " + strconv.Itoa(int(e))
}
func (e SRTErrno) Is(target error) bool {
//for backwards compat
switch target.(type) {
case *SrtInvalidSock:
return e == EInvSock
case *SrtRendezvousUnbound:
return e == ERdvUnbound
case *SrtSockConnected:
return e == EConnSock
case *SrtConnectionRejected:
return e == EConnRej
case *SrtConnectTimeout:
return e == ETimeout
case *SrtSocketClosed:
return e == ESClosed
}
return false
}
func (e SRTErrno) Temporary() bool {
return e == EAsyncFAIL || e == EAsyncRCV || e == EAsyncSND || e == ECongest || e == ETimeout
}
func (e SRTErrno) Timeout() bool {
return e == ETimeout
}
func (e SRTErrno) wrapSysErr(errno syscall.Errno) error {
return &srtErrnoSysErrnoWrapped{
e: e,
eSys: errno,
}
}
type srtErrnoSysErrnoWrapped struct {
e SRTErrno
eSys syscall.Errno
}
func (e *srtErrnoSysErrnoWrapped) Error() string {
return e.e.Error()
}
func (e *srtErrnoSysErrnoWrapped) Is(target error) bool {
return e.e.Is(target)
}
func (e *srtErrnoSysErrnoWrapped) Temporary() bool {
return e.e.Temporary()
}
func (e *srtErrnoSysErrnoWrapped) Timeout() bool {
return e.e.Timeout()
}
func (e *srtErrnoSysErrnoWrapped) Unwrap() error {
return error(e.eSys)
}
//Shadows SRT_ERRNO srtcore/srt.h line 490+
const (
Unknown = SRTErrno(C.SRT_EUNKNOWN)
Success = SRTErrno(C.SRT_SUCCESS)
//Major: SETUP
EConnSetup = SRTErrno(C.SRT_ECONNSETUP)
ENoServer = SRTErrno(C.SRT_ENOSERVER)
EConnRej = SRTErrno(C.SRT_ECONNREJ)
ESockFail = SRTErrno(C.SRT_ESOCKFAIL)
ESecFail = SRTErrno(C.SRT_ESECFAIL)
ESClosed = SRTErrno(C.SRT_ESCLOSED)
//Major: CONNECTION
EConnFail = SRTErrno(C.SRT_ECONNFAIL)
EConnLost = SRTErrno(C.SRT_ECONNLOST)
ENoConn = SRTErrno(C.SRT_ENOCONN)
//Major: SYSTEMRES
EResource = SRTErrno(C.SRT_ERESOURCE)
EThread = SRTErrno(C.SRT_ETHREAD)
EnoBuf = SRTErrno(C.SRT_ENOBUF)
ESysObj = SRTErrno(C.SRT_ESYSOBJ)
//Major: FILESYSTEM
EFile = SRTErrno(C.SRT_EFILE)
EInvRdOff = SRTErrno(C.SRT_EINVRDOFF)
ERdPerm = SRTErrno(C.SRT_ERDPERM)
EInvWrOff = SRTErrno(C.SRT_EINVWROFF)
EWrPerm = SRTErrno(C.SRT_EWRPERM)
//Major: NOTSUP
EInvOp = SRTErrno(C.SRT_EINVOP)
EBoundSock = SRTErrno(C.SRT_EBOUNDSOCK)
EConnSock = SRTErrno(C.SRT_ECONNSOCK)
EInvParam = SRTErrno(C.SRT_EINVPARAM)
EInvSock = SRTErrno(C.SRT_EINVSOCK)
EUnboundSock = SRTErrno(C.SRT_EUNBOUNDSOCK)
ENoListen = SRTErrno(C.SRT_ENOLISTEN)
ERdvNoServ = SRTErrno(C.SRT_ERDVNOSERV)
ERdvUnbound = SRTErrno(C.SRT_ERDVUNBOUND)
EInvalMsgAPI = SRTErrno(C.SRT_EINVALMSGAPI)
EInvalBufferAPI = SRTErrno(C.SRT_EINVALBUFFERAPI)
EDupListen = SRTErrno(C.SRT_EDUPLISTEN)
ELargeMsg = SRTErrno(C.SRT_ELARGEMSG)
EInvPollID = SRTErrno(C.SRT_EINVPOLLID)
EPollEmpty = SRTErrno(C.SRT_EPOLLEMPTY)
//EBindConflict = SRTErrno(C.SRT_EBINDCONFLICT)
//Major: AGAIN
EAsyncFAIL = SRTErrno(C.SRT_EASYNCFAIL)
EAsyncSND = SRTErrno(C.SRT_EASYNCSND)
EAsyncRCV = SRTErrno(C.SRT_EASYNCRCV)
ETimeout = SRTErrno(C.SRT_ETIMEOUT)
ECongest = SRTErrno(C.SRT_ECONGEST)
//Major: PEERERROR
EPeer = SRTErrno(C.SRT_EPEERERR)
)
//Unknown cannot be here since it would have a negative index!
//Error strings taken from: https://github.com/Haivision/srt/blob/master/docs/API/API-functions.md
var srterrors = [...]string{
Success: "The value set when the last error was cleared and no error has occurred since then",
EConnSetup: "General setup error resulting from internal system state",
ENoServer: "Connection timed out while attempting to connect to the remote address",
EConnRej: "Connection has been rejected",
ESockFail: "An error occurred when trying to call a system function on an internally used UDP socket",
ESecFail: "A possible tampering with the handshake packets was detected, or encryption request wasn't properly fulfilled.",
ESClosed: "A socket that was vital for an operation called in blocking mode has been closed during the operation",
EConnFail: "General connection failure of unknown details",
EConnLost: "The socket was properly connected, but the connection has been broken",
ENoConn: "The socket is not connected",
EResource: "System or standard library error reported unexpectedly for unknown purpose",
EThread: "System was unable to spawn a new thread when requried",
EnoBuf: "System was unable to allocate memory for buffers",
ESysObj: "System was unable to allocate system specific objects",
EFile: "General filesystem error (for functions operating with file transmission)",
EInvRdOff: "Failure when trying to read from a given position in the file",
ERdPerm: "Read permission was denied when trying to read from file",
EInvWrOff: "Failed to set position in the written file",
EWrPerm: "Write permission was denied when trying to write to a file",
EInvOp: "Invalid operation performed for the current state of a socket",
EBoundSock: "The socket is currently bound and the required operation cannot be performed in this state",
EConnSock: "The socket is currently connected and therefore performing the required operation is not possible",
EInvParam: "Call parameters for API functions have some requirements that were not satisfied",
EInvSock: "The API function required an ID of an entity (socket or group) and it was invalid",
EUnboundSock: "The operation to be performed on a socket requires that it first be explicitly bound",
ENoListen: "The socket passed for the operation is required to be in the listen state",
ERdvNoServ: "The required operation cannot be performed when the socket is set to rendezvous mode",
ERdvUnbound: "An attempt was made to connect to a socket set to rendezvous mode that was not first bound",
EInvalMsgAPI: "The function was used incorrectly in the message API",
EInvalBufferAPI: "The function was used incorrectly in the stream (buffer) API",
EDupListen: "The port tried to be bound for listening is already busy",
ELargeMsg: "Size exceeded",
EInvPollID: "The epoll ID passed to an epoll function is invalid",
EPollEmpty: "The epoll container currently has no subscribed sockets",
//EBindConflict: "SRT_EBINDCONFLICT",
EAsyncFAIL: "General asynchronous failure (not in use currently)",
EAsyncSND: "Sending operation is not ready to perform",
EAsyncRCV: "Receiving operation is not ready to perform",
ETimeout: "The operation timed out",
ECongest: "With SRTO_TSBPDMODE and SRTO_TLPKTDROP set to true, some packets were dropped by sender",
EPeer: "Receiver peer is writing to a file that the agent is sending",
}

@ -0,0 +1,66 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
extern void srtLogCB(void* opaque, int level, const char* file, int line, const char* area, const char* message);
*/
import "C"
import (
"sync"
"unsafe"
gopointer "github.com/mattn/go-pointer"
)
type LogCallBackFunc func(level SrtLogLevel, file string, line int, area, message string)
type SrtLogLevel int
const (
// SrtLogLevelEmerg = int(C.LOG_EMERG)
// SrtLogLevelAlert = int(C.LOG_ALERT)
SrtLogLevelCrit SrtLogLevel = SrtLogLevel(C.LOG_CRIT)
SrtLogLevelErr SrtLogLevel = SrtLogLevel(C.LOG_ERR)
SrtLogLevelWarning SrtLogLevel = SrtLogLevel(C.LOG_WARNING)
SrtLogLevelNotice SrtLogLevel = SrtLogLevel(C.LOG_NOTICE)
SrtLogLevelInfo SrtLogLevel = SrtLogLevel(C.LOG_INFO)
SrtLogLevelDebug SrtLogLevel = SrtLogLevel(C.LOG_DEBUG)
SrtLogLevelTrace SrtLogLevel = SrtLogLevel(8)
)
var (
logCBPtr unsafe.Pointer = nil
logCBPtrLock sync.Mutex
)
//export srtLogCBWrapper
func srtLogCBWrapper(arg unsafe.Pointer, level C.int, file *C.char, line C.int, area, message *C.char) {
userCB := gopointer.Restore(arg).(LogCallBackFunc)
go userCB(SrtLogLevel(level), C.GoString(file), int(line), C.GoString(area), C.GoString(message))
}
func SrtSetLogLevel(level SrtLogLevel) {
C.srt_setloglevel(C.int(level))
}
func SrtSetLogHandler(cb LogCallBackFunc) {
ptr := gopointer.Save(cb)
C.srt_setloghandler(ptr, (*C.SRT_LOG_HANDLER_FN)(C.srtLogCB))
storeLogCBPtr(ptr)
}
func SrtUnsetLogHandler() {
C.srt_setloghandler(nil, nil)
storeLogCBPtr(nil)
}
func storeLogCBPtr(ptr unsafe.Pointer) {
logCBPtrLock.Lock()
defer logCBPtrLock.Unlock()
if logCBPtr != nil {
gopointer.Unref(logCBPtr)
}
logCBPtr = ptr
}

@ -0,0 +1,14 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
extern void srtLogCBWrapper (void* opaque, int level, char* file, int line, char* area, char* message);
void srtLogCB(void* opaque, int level, const char* file, int line, const char* area, const char* message)
{
srtLogCBWrapper(opaque, level, (char*)file, line, (char*)area,(char*) message);
}
*/
import "C"

@ -0,0 +1,87 @@
package srtgo
//#include <srt/srt.h>
import "C"
import (
"encoding/binary"
"fmt"
"net"
"syscall"
"unsafe"
)
func ntohs(val uint16) uint16 {
tmp := ((*[unsafe.Sizeof(val)]byte)(unsafe.Pointer(&val)))
return binary.BigEndian.Uint16((*tmp)[:])
}
func udpAddrFromSockaddr(addr *syscall.RawSockaddrAny) (*net.UDPAddr, error) {
var udpAddr net.UDPAddr
switch addr.Addr.Family {
case afINET6:
ptr := (*syscall.RawSockaddrInet6)(unsafe.Pointer(addr))
udpAddr.Port = int(ntohs(ptr.Port))
udpAddr.IP = ptr.Addr[:]
case afINET4:
ptr := (*syscall.RawSockaddrInet4)(unsafe.Pointer(addr))
udpAddr.Port = int(ntohs(ptr.Port))
udpAddr.IP = net.IPv4(
ptr.Addr[0],
ptr.Addr[1],
ptr.Addr[2],
ptr.Addr[3],
)
default:
return nil, fmt.Errorf("unknown address family: %v", addr.Addr.Family)
}
return &udpAddr, nil
}
func sockAddrFromIp4(ip net.IP, port uint16) (*C.struct_sockaddr, int, error) {
var raw syscall.RawSockaddrInet4
raw.Family = afINET4
p := (*[2]byte)(unsafe.Pointer(&raw.Port))
p[0] = byte(port >> 8)
p[1] = byte(port)
copy(raw.Addr[:], ip.To4())
return (*C.struct_sockaddr)(unsafe.Pointer(&raw)), int(sizeofSockAddrInet4), nil
}
func sockAddrFromIp6(ip net.IP, port uint16) (*C.struct_sockaddr, int, error) {
var raw syscall.RawSockaddrInet6
raw.Family = afINET6
p := (*[2]byte)(unsafe.Pointer(&raw.Port))
p[0] = byte(port >> 8)
p[1] = byte(port)
copy(raw.Addr[:], ip.To16())
return (*C.struct_sockaddr)(unsafe.Pointer(&raw)), int(sizeofSockAddrInet6), nil
}
func CreateAddrInet(name string, port uint16) (*C.struct_sockaddr, int, error) {
ip := net.ParseIP(name)
if ip == nil {
ips, err := net.LookupIP(name)
if err != nil {
return nil, 0, fmt.Errorf("Error in CreateAddrInet, LookupIP")
}
ip = ips[0]
}
if ip.To4() != nil {
return sockAddrFromIp4(ip, port)
} else if ip.To16() != nil {
return sockAddrFromIp6(ip, port)
}
return nil, 0, fmt.Errorf("Error in CreateAddrInet, LookupIP")
}

@ -0,0 +1,17 @@
//go:build !windows
package srtgo
import (
"syscall"
"golang.org/x/sys/unix"
)
const (
sizeofSockAddrInet4 = syscall.SizeofSockaddrInet4
sizeofSockAddrInet6 = syscall.SizeofSockaddrInet6
sizeofSockaddrAny = syscall.SizeofSockaddrAny
afINET4 = unix.AF_INET
afINET6 = unix.AF_INET6
)

@ -0,0 +1,29 @@
//go:build windows
package srtgo
import (
"unsafe"
"golang.org/x/sys/windows"
)
const (
afINET4 = windows.AF_INET
afINET6 = windows.AF_INET6
)
var (
sizeofSockAddrInet4 uint64 = 0
sizeofSockAddrInet6 uint64 = 0
sizeofSockaddrAny uint64 = 0
)
func init() {
inet4 := windows.RawSockaddrInet4{}
inet6 := windows.RawSockaddrInet6{}
any := windows.RawSockaddrAny{}
sizeofSockAddrInet4 = uint64(unsafe.Sizeof(inet4))
sizeofSockAddrInet6 = uint64(unsafe.Sizeof(inet6))
sizeofSockaddrAny = uint64(unsafe.Sizeof(any))
}

@ -0,0 +1,269 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
*/
import "C"
import (
"sync"
"sync/atomic"
"time"
)
const (
pollDefault = int32(iota)
pollReady = int32(iota)
pollWait = int32(iota)
)
type PollMode int
const (
ModeRead = PollMode(iota)
ModeWrite
)
/*
pollDesc contains the polling state for the associated SrtSocket
closing: socket is closing, reject all poll operations
pollErr: an error occured on the socket, indicates it's not useable anymore.
unblockRd: is used to unblock the poller when the socket becomes ready for io
rdState: polling state for read operations
rdDeadline: deadline in NS before poll operation times out, -1 means timedout (needs to be cleared), 0 is without timeout
rdSeq: sequence number protects against spurious signalling of timeouts when timer is reset.
rdTimer: timer used to enforce deadline.
*/
type pollDesc struct {
lock sync.Mutex
closing bool
fd C.SRTSOCKET
pollErr bool
unblockRd chan interface{}
rdState int32
rdLock sync.Mutex
rdDeadline int64
rdSeq int64
rdTimer *time.Timer
rtSeq int64
unblockWr chan interface{}
wrState int32
wrLock sync.Mutex
wdDeadline int64
wdSeq int64
wdTimer *time.Timer
wtSeq int64
pollS *pollServer
}
var pdPool = sync.Pool{
New: func() interface{} {
return &pollDesc{
unblockRd: make(chan interface{}, 1),
unblockWr: make(chan interface{}, 1),
rdTimer: time.NewTimer(0),
wdTimer: time.NewTimer(0),
}
},
}
func pollDescInit(s C.SRTSOCKET) *pollDesc {
pd := pdPool.Get().(*pollDesc)
pd.lock.Lock()
defer pd.lock.Unlock()
pd.fd = s
pd.rdState = pollDefault
pd.wrState = pollDefault
pd.pollS = pollServerCtx()
pd.closing = false
pd.pollErr = false
pd.rdSeq++
pd.wdSeq++
pd.pollS.pollOpen(pd)
return pd
}
func (pd *pollDesc) release() {
pd.lock.Lock()
defer pd.lock.Unlock()
if !pd.closing || pd.rdState == pollWait || pd.wrState == pollWait {
panic("returning open or blocked upon pollDesc")
}
pd.fd = 0
pdPool.Put(pd)
}
func (pd *pollDesc) wait(mode PollMode) error {
defer pd.reset(mode)
if err := pd.checkPollErr(mode); err != nil {
return err
}
state := &pd.rdState
unblockChan := pd.unblockRd
expiryChan := pd.rdTimer.C
timerSeq := int64(0)
pd.lock.Lock()
if mode == ModeRead {
timerSeq = pd.rtSeq
pd.rdLock.Lock()
defer pd.rdLock.Unlock()
} else if mode == ModeWrite {
timerSeq = pd.wtSeq
state = &pd.wrState
unblockChan = pd.unblockWr
expiryChan = pd.wdTimer.C
pd.wrLock.Lock()
defer pd.wrLock.Unlock()
}
for {
old := *state
if old == pollReady {
*state = pollDefault
pd.lock.Unlock()
return nil
}
if atomic.CompareAndSwapInt32(state, pollDefault, pollWait) {
break
}
}
pd.lock.Unlock()
wait:
for {
select {
case <-unblockChan:
break wait
case <-expiryChan:
pd.lock.Lock()
if mode == ModeRead {
if timerSeq == pd.rdSeq {
pd.rdDeadline = -1
pd.lock.Unlock()
break wait
}
timerSeq = pd.rtSeq
}
if mode == ModeWrite {
if timerSeq == pd.wdSeq {
pd.wdDeadline = -1
pd.lock.Unlock()
break wait
}
timerSeq = pd.wtSeq
}
pd.lock.Unlock()
}
}
err := pd.checkPollErr(mode)
return err
}
func (pd *pollDesc) close() {
pd.lock.Lock()
defer pd.lock.Unlock()
if pd.closing {
return
}
pd.closing = true
pd.pollS.pollClose(pd)
}
func (pd *pollDesc) checkPollErr(mode PollMode) error {
pd.lock.Lock()
defer pd.lock.Unlock()
if pd.closing {
return &SrtSocketClosed{}
}
if mode == ModeRead && pd.rdDeadline < 0 || mode == ModeWrite && pd.wdDeadline < 0 {
return &SrtEpollTimeout{}
}
if pd.pollErr {
return &SrtSocketClosed{}
}
return nil
}
func (pd *pollDesc) setDeadline(t time.Time, mode PollMode) {
pd.lock.Lock()
defer pd.lock.Unlock()
var d int64
if !t.IsZero() {
d = int64(time.Until(t))
if d == 0 {
d = -1
}
}
if mode == ModeRead || mode == ModeRead+ModeWrite {
pd.rdSeq++
pd.rtSeq = pd.rdSeq
if pd.rdDeadline > 0 {
pd.rdTimer.Stop()
}
pd.rdDeadline = d
if d > 0 {
pd.rdTimer.Reset(time.Duration(d))
}
if d < 0 {
pd.unblock(ModeRead, false, false)
}
}
if mode == ModeWrite || mode == ModeRead+ModeWrite {
pd.wdSeq++
pd.wtSeq = pd.wdSeq
if pd.wdDeadline > 0 {
pd.wdTimer.Stop()
}
pd.wdDeadline = d
if d > 0 {
pd.wdTimer.Reset(time.Duration(d))
}
if d < 0 {
pd.unblock(ModeWrite, false, false)
}
}
}
func (pd *pollDesc) unblock(mode PollMode, pollerr, ioready bool) {
if pollerr {
pd.lock.Lock()
pd.pollErr = pollerr
pd.lock.Unlock()
}
state := &pd.rdState
unblockChan := pd.unblockRd
if mode == ModeWrite {
state = &pd.wrState
unblockChan = pd.unblockWr
}
pd.lock.Lock()
old := atomic.LoadInt32(state)
if ioready {
atomic.StoreInt32(state, pollReady)
}
pd.lock.Unlock()
if old == pollWait {
//make sure we never block here
select {
case unblockChan <- struct{}{}:
//
default:
//
}
}
}
func (pd *pollDesc) reset(mode PollMode) {
if mode == ModeRead {
pd.rdLock.Lock()
pd.rdState = pollDefault
pd.rdLock.Unlock()
} else if mode == ModeWrite {
pd.wrLock.Lock()
pd.wrState = pollDefault
pd.wrLock.Unlock()
}
}

@ -0,0 +1,109 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
*/
import "C"
import (
"sync"
"unsafe"
)
var (
phctx *pollServer
once sync.Once
)
func pollServerCtx() *pollServer {
once.Do(pollServerCtxInit)
return phctx
}
func pollServerCtxInit() {
eid := C.srt_epoll_create()
C.srt_epoll_set(eid, C.SRT_EPOLL_ENABLE_EMPTY)
phctx = &pollServer{
srtEpollDescr: eid,
pollDescs: make(map[C.SRTSOCKET]*pollDesc),
}
go phctx.run()
}
type pollServer struct {
srtEpollDescr C.int
pollDescLock sync.Mutex
pollDescs map[C.SRTSOCKET]*pollDesc
}
func (p *pollServer) pollOpen(pd *pollDesc) {
//use uint because otherwise with ET it would overflow :/ (srt should accept an uint instead, or fix it's SRT_EPOLL_ET definition)
events := C.uint(C.SRT_EPOLL_IN | C.SRT_EPOLL_OUT | C.SRT_EPOLL_ERR | C.SRT_EPOLL_ET)
//via unsafe.Pointer because we cannot cast *C.uint to *C.int directly
//block poller
p.pollDescLock.Lock()
ret := C.srt_epoll_add_usock(p.srtEpollDescr, pd.fd, (*C.int)(unsafe.Pointer(&events)))
if ret == -1 {
panic("ERROR ADDING FD TO EPOLL")
}
p.pollDescs[pd.fd] = pd
p.pollDescLock.Unlock()
}
func (p *pollServer) pollClose(pd *pollDesc) {
sockstate := C.srt_getsockstate(pd.fd)
//Broken/closed sockets get removed internally by SRT lib
if sockstate == C.SRTS_BROKEN || sockstate == C.SRTS_CLOSING || sockstate == C.SRTS_CLOSED || sockstate == C.SRTS_NONEXIST {
return
}
ret := C.srt_epoll_remove_usock(p.srtEpollDescr, pd.fd)
if ret == -1 {
panic("ERROR REMOVING FD FROM EPOLL")
}
p.pollDescLock.Lock()
delete(p.pollDescs, pd.fd)
p.pollDescLock.Unlock()
}
func init() {
}
func (p *pollServer) run() {
timeoutMs := C.int64_t(-1)
fds := [128]C.SRT_EPOLL_EVENT{}
fdlen := C.int(128)
for {
res := C.srt_epoll_uwait(p.srtEpollDescr, &fds[0], fdlen, timeoutMs)
if res == 0 {
continue //Shouldn't happen with -1
} else if res == -1 {
panic("srt_epoll_error")
} else if res > 0 {
max := int(res)
if fdlen < res {
max = int(fdlen)
}
p.pollDescLock.Lock()
for i := 0; i < max; i++ {
s := fds[i].fd
events := fds[i].events
pd := p.pollDescs[s]
if events&C.SRT_EPOLL_ERR != 0 {
pd.unblock(ModeRead, true, false)
pd.unblock(ModeWrite, true, false)
continue
}
if events&C.SRT_EPOLL_IN != 0 {
pd.unblock(ModeRead, false, true)
}
if events&C.SRT_EPOLL_OUT != 0 {
pd.unblock(ModeWrite, false, true)
}
}
p.pollDescLock.Unlock()
}
}
}

@ -0,0 +1,54 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
int srt_recvmsg2_wrapped(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL *mctrl, int *srterror, int *syserror)
{
int ret = srt_recvmsg2(u, buf, len, mctrl);
if (ret < 0) {
*srterror = srt_getlasterror(syserror);
}
return ret;
}
*/
import "C"
import (
"errors"
"syscall"
"unsafe"
)
func srtRecvMsg2Impl(u C.SRTSOCKET, buf []byte, msgctrl *C.SRT_MSGCTRL) (n int, err error) {
srterr := C.int(0)
syserr := C.int(0)
n = int(C.srt_recvmsg2_wrapped(u, (*C.char)(unsafe.Pointer(&buf[0])), C.int(len(buf)), msgctrl, &srterr, &syserr))
if n < 0 {
srterror := SRTErrno(srterr)
if syserr < 0 {
srterror.wrapSysErr(syscall.Errno(syserr))
}
err = srterror
n = 0
}
return
}
// Read data from the SRT socket
func (s SrtSocket) Read(b []byte) (n int, err error) {
//Fastpath
if !s.blocking {
s.pd.reset(ModeRead)
}
n, err = srtRecvMsg2Impl(s.socket, b, nil)
for {
if !errors.Is(err, error(EAsyncRCV)) || s.blocking {
return
}
s.pd.wait(ModeRead)
n, err = srtRecvMsg2Impl(s.socket, b, nil)
}
}

@ -0,0 +1,578 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
#include <srt/access_control.h>
#include "callback.h"
static const SRTSOCKET get_srt_invalid_sock() { return SRT_INVALID_SOCK; };
static const int get_srt_error() { return SRT_ERROR; };
static const int get_srt_error_reject_predefined() { return SRT_REJC_PREDEFINED; };
static const int get_srt_error_reject_userdefined() { return SRT_REJC_USERDEFINED; };
*/
import "C"
import (
"errors"
"fmt"
"net"
"runtime"
"strconv"
"sync"
"syscall"
"time"
"unsafe"
gopointer "github.com/mattn/go-pointer"
)
// SRT Socket mode
const (
ModeFailure = iota
ModeListener
ModeCaller
ModeRendezvouz
)
// Binding ops
const (
bindingPre = 0
bindingPost = 1
)
// SrtSocket - SRT socket
type SrtSocket struct {
socket C.int
blocking bool
pd *pollDesc
host string
port uint16
options map[string]string
mode int
pktSize int
pollTimeout int64
}
var (
callbackMutex sync.Mutex
listenCallbackMap map[C.int]unsafe.Pointer = make(map[C.int]unsafe.Pointer)
connectCallbackMap map[C.int]unsafe.Pointer = make(map[C.int]unsafe.Pointer)
)
// Static consts from library
var (
SRT_INVALID_SOCK = C.get_srt_invalid_sock()
SRT_ERROR = C.get_srt_error()
SRTS_CONNECTED = C.SRTS_CONNECTED
)
const defaultPacketSize = 1456
// InitSRT - Initialize srt library
func InitSRT() {
C.srt_startup()
}
// CleanupSRT - Cleanup SRT lib
func CleanupSRT() {
C.srt_cleanup()
}
// NewSrtSocket - Create a new SRT Socket
func NewSrtSocket(host string, port uint16, options map[string]string) *SrtSocket {
s := new(SrtSocket)
s.socket = C.srt_create_socket()
if s.socket == SRT_INVALID_SOCK {
return nil
}
s.host = host
s.port = port
s.options = options
s.pollTimeout = -1
val, exists := options["pktsize"]
if exists {
pktSize, err := strconv.Atoi(val)
if err != nil {
s.pktSize = pktSize
}
}
if s.pktSize <= 0 {
s.pktSize = defaultPacketSize
}
val, exists = options["blocking"]
if exists && val != "0" {
s.blocking = true
}
if !s.blocking {
s.pd = pollDescInit(s.socket)
}
finalizer := func(obj interface{}) {
sf := obj.(*SrtSocket)
sf.Close()
if sf.pd != nil {
sf.pd.release()
}
}
//Cleanup SrtSocket if no references exist anymore
runtime.SetFinalizer(s, finalizer)
var err error
s.mode, err = s.preconfiguration()
if err != nil {
return nil
}
return s
}
func newFromSocket(acceptSocket *SrtSocket, socket C.SRTSOCKET) (*SrtSocket, error) {
s := new(SrtSocket)
s.socket = socket
s.pktSize = acceptSocket.pktSize
s.blocking = acceptSocket.blocking
s.pollTimeout = acceptSocket.pollTimeout
err := acceptSocket.postconfiguration(s)
if err != nil {
return nil, err
}
if !s.blocking {
s.pd = pollDescInit(s.socket)
}
finalizer := func(obj interface{}) {
sf := obj.(*SrtSocket)
sf.Close()
if sf.pd != nil {
sf.pd.release()
}
}
//Cleanup SrtSocket if no references exist anymore
runtime.SetFinalizer(s, finalizer)
return s, nil
}
func (s SrtSocket) GetSocket() C.int {
return s.socket
}
// Listen for incoming connections. The backlog setting defines how many sockets
// may be allowed to wait until they are accepted (excessive connection requests
// are rejected in advance)
func (s *SrtSocket) Listen(backlog int) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
nbacklog := C.int(backlog)
sa, salen, err := CreateAddrInet(s.host, s.port)
if err != nil {
return err
}
res := C.srt_bind(s.socket, sa, C.int(salen))
if res == SRT_ERROR {
C.srt_close(s.socket)
return fmt.Errorf("Error in srt_bind: %w", srtGetAndClearError())
}
res = C.srt_listen(s.socket, nbacklog)
if res == SRT_ERROR {
C.srt_close(s.socket)
return fmt.Errorf("Error in srt_listen: %w", srtGetAndClearError())
}
err = s.postconfiguration(s)
if err != nil {
return fmt.Errorf("Error setting post socket options")
}
return nil
}
// Connect to a remote endpoint
func (s *SrtSocket) Connect() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
sa, salen, err := CreateAddrInet(s.host, s.port)
if err != nil {
return err
}
res := C.srt_connect(s.socket, sa, C.int(salen))
if res == SRT_ERROR {
C.srt_close(s.socket)
return srtGetAndClearError()
}
if !s.blocking {
if err := s.pd.wait(ModeWrite); err != nil {
return err
}
}
err = s.postconfiguration(s)
if err != nil {
return fmt.Errorf("Error setting post socket options in connect")
}
return nil
}
// Stats - Retrieve stats from the SRT socket
func (s SrtSocket) Stats() (*SrtStats, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var stats C.SRT_TRACEBSTATS = C.SRT_TRACEBSTATS{}
var b C.int = 1
if C.srt_bstats(s.socket, &stats, b) == SRT_ERROR {
return nil, fmt.Errorf("Error getting stats, %w", srtGetAndClearError())
}
return newSrtStats(&stats), nil
}
// Mode - Return working mode of the SRT socket
func (s SrtSocket) Mode() int {
return s.mode
}
// PacketSize - Return packet size of the SRT socket
func (s SrtSocket) PacketSize() int {
return s.pktSize
}
// PollTimeout - Return polling max time, for connect/read/write operations.
// Only applied when socket is in non-blocking mode.
func (s SrtSocket) PollTimeout() time.Duration {
return time.Duration(s.pollTimeout) * time.Millisecond
}
// SetPollTimeout - Sets polling max time, for connect/read/write operations.
// Only applied when socket is in non-blocking mode.
func (s *SrtSocket) SetPollTimeout(pollTimeout time.Duration) {
s.pollTimeout = pollTimeout.Milliseconds()
}
func (s *SrtSocket) SetDeadline(deadline time.Time) {
s.pd.setDeadline(deadline, ModeRead+ModeWrite)
}
func (s *SrtSocket) SetReadDeadline(deadline time.Time) {
s.pd.setDeadline(deadline, ModeRead)
}
func (s *SrtSocket) SetWriteDeadline(deadline time.Time) {
s.pd.setDeadline(deadline, ModeWrite)
}
// Close the SRT socket
func (s *SrtSocket) Close() {
C.srt_close(s.socket)
s.socket = SRT_INVALID_SOCK
if !s.blocking {
s.pd.close()
}
callbackMutex.Lock()
if ptr, exists := listenCallbackMap[s.socket]; exists {
gopointer.Unref(ptr)
}
if ptr, exists := connectCallbackMap[s.socket]; exists {
gopointer.Unref(ptr)
}
callbackMutex.Unlock()
}
// ListenCallbackFunc specifies a function to be called before a connecting socket is passed to accept
type ListenCallbackFunc func(socket *SrtSocket, version int, addr *net.UDPAddr, streamid string) bool
//export srtListenCBWrapper
func srtListenCBWrapper(arg unsafe.Pointer, socket C.SRTSOCKET, hsVersion C.int, peeraddr *C.struct_sockaddr, streamid *C.char) C.int {
userCB := gopointer.Restore(arg).(ListenCallbackFunc)
s := new(SrtSocket)
s.socket = socket
udpAddr, _ := udpAddrFromSockaddr((*syscall.RawSockaddrAny)(unsafe.Pointer(peeraddr)))
if userCB(s, int(hsVersion), udpAddr, C.GoString(streamid)) {
return 0
}
return SRT_ERROR
}
// SetListenCallback - set a function to be called early in the handshake before a client
// is handed to accept on a listening socket.
// The connection can be rejected by returning false from the callback.
// See examples/echo-receiver for more details.
func (s SrtSocket) SetListenCallback(cb ListenCallbackFunc) {
ptr := gopointer.Save(cb)
C.srt_listen_callback(s.socket, (*C.srt_listen_callback_fn)(C.srtListenCB), ptr)
callbackMutex.Lock()
defer callbackMutex.Unlock()
if listenCallbackMap[s.socket] != nil {
gopointer.Unref(listenCallbackMap[s.socket])
}
listenCallbackMap[s.socket] = ptr
}
// ConnectCallbackFunc specifies a function to be called after a socket or connection in a group has failed.
type ConnectCallbackFunc func(socket *SrtSocket, err error, addr *net.UDPAddr, token int)
//export srtConnectCBWrapper
func srtConnectCBWrapper(arg unsafe.Pointer, socket C.SRTSOCKET, errcode C.int, peeraddr *C.struct_sockaddr, token C.int) {
userCB := gopointer.Restore(arg).(ConnectCallbackFunc)
s := new(SrtSocket)
s.socket = socket
udpAddr, _ := udpAddrFromSockaddr((*syscall.RawSockaddrAny)(unsafe.Pointer(peeraddr)))
userCB(s, SRTErrno(errcode), udpAddr, int(token))
}
// SetConnectCallback - set a function to be called after a socket or connection in a group has failed
// Note that the function is not guaranteed to be called if the socket is set to blocking mode.
func (s SrtSocket) SetConnectCallback(cb ConnectCallbackFunc) {
ptr := gopointer.Save(cb)
C.srt_connect_callback(s.socket, (*C.srt_connect_callback_fn)(C.srtConnectCB), ptr)
callbackMutex.Lock()
defer callbackMutex.Unlock()
if connectCallbackMap[s.socket] != nil {
gopointer.Unref(connectCallbackMap[s.socket])
}
connectCallbackMap[s.socket] = ptr
}
// Rejection reasons
var (
// Start of range for predefined rejection reasons
RejectionReasonPredefined = int(C.get_srt_error_reject_predefined())
// General syntax error in the SocketID specification (also a fallback code for undefined cases)
RejectionReasonBadRequest = RejectionReasonPredefined + 400
// Authentication failed, provided that the user was correctly identified and access to the required resource would be granted
RejectionReasonUnauthorized = RejectionReasonPredefined + 401
// The server is too heavily loaded, or you have exceeded credits for accessing the service and the resource.
RejectionReasonOverload = RejectionReasonPredefined + 402
// Access denied to the resource by any kind of reason
RejectionReasonForbidden = RejectionReasonPredefined + 403
// Resource not found at this time.
RejectionReasonNotFound = RejectionReasonPredefined + 404
// The mode specified in `m` key in StreamID is not supported for this request.
RejectionReasonBadMode = RejectionReasonPredefined + 405
// The requested parameters specified in SocketID cannot be satisfied for the requested resource. Also when m=publish and the data format is not acceptable.
RejectionReasonUnacceptable = RejectionReasonPredefined + 406
// Start of range for application defined rejection reasons
RejectionReasonUserDefined = int(C.get_srt_error_reject_predefined())
)
// SetRejectReason - set custom reason for connection reject
func (s SrtSocket) SetRejectReason(value int) error {
res := C.srt_setrejectreason(s.socket, C.int(value))
if res == SRT_ERROR {
return errors.New(C.GoString(C.srt_getlasterror_str()))
}
return nil
}
// GetSockOptByte - return byte value obtained with srt_getsockopt
func (s SrtSocket) GetSockOptByte(opt int) (byte, error) {
var v byte
l := 1
err := s.getSockOpt(opt, unsafe.Pointer(&v), &l)
return v, err
}
// GetSockOptBool - return bool value obtained with srt_getsockopt
func (s SrtSocket) GetSockOptBool(opt int) (bool, error) {
var v int32
l := 4
err := s.getSockOpt(opt, unsafe.Pointer(&v), &l)
if v == 1 {
return true, err
}
return false, err
}
// GetSockOptInt - return int value obtained with srt_getsockopt
func (s SrtSocket) GetSockOptInt(opt int) (int, error) {
var v int32
l := 4
err := s.getSockOpt(opt, unsafe.Pointer(&v), &l)
return int(v), err
}
// GetSockOptInt64 - return int64 value obtained with srt_getsockopt
func (s SrtSocket) GetSockOptInt64(opt int) (int64, error) {
var v int64
l := 8
err := s.getSockOpt(opt, unsafe.Pointer(&v), &l)
return v, err
}
// GetSockOptString - return string value obtained with srt_getsockopt
func (s SrtSocket) GetSockOptString(opt int) (string, error) {
buf := make([]byte, 256)
l := len(buf)
err := s.getSockOpt(opt, unsafe.Pointer(&buf[0]), &l)
if err != nil {
return "", err
}
return string(buf[:l]), nil
}
// SetSockOptByte - set byte value using srt_setsockopt
func (s SrtSocket) SetSockOptByte(opt int, value byte) error {
return s.setSockOpt(opt, unsafe.Pointer(&value), 1)
}
// SetSockOptBool - set bool value using srt_setsockopt
func (s SrtSocket) SetSockOptBool(opt int, value bool) error {
val := int(0)
if value {
val = 1
}
return s.setSockOpt(opt, unsafe.Pointer(&val), 4)
}
// SetSockOptInt - set int value using srt_setsockopt
func (s SrtSocket) SetSockOptInt(opt int, value int) error {
return s.setSockOpt(opt, unsafe.Pointer(&value), 4)
}
// SetSockOptInt64 - set int64 value using srt_setsockopt
func (s SrtSocket) SetSockOptInt64(opt int, value int64) error {
return s.setSockOpt(opt, unsafe.Pointer(&value), 8)
}
// SetSockOptString - set string value using srt_setsockopt
func (s SrtSocket) SetSockOptString(opt int, value string) error {
return s.setSockOpt(opt, unsafe.Pointer(&[]byte(value)[0]), len(value))
}
func (s SrtSocket) setSockOpt(opt int, data unsafe.Pointer, size int) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
res := C.srt_setsockopt(s.socket, 0, C.SRT_SOCKOPT(opt), data, C.int(size))
if res == -1 {
return fmt.Errorf("Error calling srt_setsockopt %w", srtGetAndClearError())
}
return nil
}
func (s SrtSocket) getSockOpt(opt int, data unsafe.Pointer, size *int) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
res := C.srt_getsockopt(s.socket, 0, C.SRT_SOCKOPT(opt), data, (*C.int)(unsafe.Pointer(size)))
if res == -1 {
return fmt.Errorf("Error calling srt_getsockopt %w", srtGetAndClearError())
}
return nil
}
func (s SrtSocket) preconfiguration() (int, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var blocking C.int
if s.blocking {
blocking = C.int(1)
} else {
blocking = C.int(0)
}
result := C.srt_setsockopt(s.socket, 0, C.SRTO_RCVSYN, unsafe.Pointer(&blocking), C.int(unsafe.Sizeof(blocking)))
if result == -1 {
return ModeFailure, fmt.Errorf("could not set SRTO_RCVSYN flag: %w", srtGetAndClearError())
}
var mode int
modeVal, ok := s.options["mode"]
if !ok {
modeVal = "default"
}
if modeVal == "client" || modeVal == "caller" {
mode = ModeCaller
} else if modeVal == "server" || modeVal == "listener" {
mode = ModeListener
} else if modeVal == "default" {
if s.host == "" {
mode = ModeListener
} else {
// Host is given, so check also "adapter"
if _, ok := s.options["adapter"]; ok {
mode = ModeRendezvouz
} else {
mode = ModeCaller
}
}
} else {
mode = ModeFailure
}
if linger, ok := s.options["linger"]; ok {
li, err := strconv.Atoi(linger)
if err == nil {
if err := setSocketLingerOption(s.socket, int32(li)); err != nil {
return ModeFailure, fmt.Errorf("could not set LINGER option %w", err)
}
} else {
return ModeFailure, fmt.Errorf("could not set LINGER option %w", err)
}
}
err := setSocketOptions(s.socket, bindingPre, s.options)
if err != nil {
return ModeFailure, fmt.Errorf("Error setting socket options: %w", err)
}
return mode, nil
}
func (s SrtSocket) postconfiguration(sck *SrtSocket) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var blocking C.int
if s.blocking {
blocking = 1
} else {
blocking = 0
}
res := C.srt_setsockopt(sck.socket, 0, C.SRTO_SNDSYN, unsafe.Pointer(&blocking), C.int(unsafe.Sizeof(blocking)))
if res == -1 {
return fmt.Errorf("Error in postconfiguration setting SRTO_SNDSYN: %w", srtGetAndClearError())
}
res = C.srt_setsockopt(sck.socket, 0, C.SRTO_RCVSYN, unsafe.Pointer(&blocking), C.int(unsafe.Sizeof(blocking)))
if res == -1 {
return fmt.Errorf("Error in postconfiguration setting SRTO_RCVSYN: %w", srtGetAndClearError())
}
err := setSocketOptions(sck.socket, bindingPost, s.options)
return err
}

@ -0,0 +1,191 @@
package srtgo
// #cgo LDFLAGS: -lsrt
// #include <srt/srt.h>
import "C"
import (
"errors"
"fmt"
"strconv"
"syscall"
"unsafe"
)
const (
transTypeLive = 0
transTypeFile = 1
)
const (
tInteger32 = 0
tInteger64 = 1
tString = 2
tBoolean = 3
tTransType = 4
SRTO_TRANSTYPE = C.SRTO_TRANSTYPE
SRTO_MAXBW = C.SRTO_MAXBW
SRTO_PBKEYLEN = C.SRTO_PBKEYLEN
SRTO_PASSPHRASE = C.SRTO_PASSPHRASE
SRTO_MSS = C.SRTO_MSS
SRTO_FC = C.SRTO_FC
SRTO_SNDBUF = C.SRTO_SNDBUF
SRTO_RCVBUF = C.SRTO_RCVBUF
SRTO_IPTTL = C.SRTO_IPTTL
SRTO_IPTOS = C.SRTO_IPTOS
SRTO_INPUTBW = C.SRTO_INPUTBW
SRTO_OHEADBW = C.SRTO_OHEADBW
SRTO_LATENCY = C.SRTO_LATENCY
SRTO_TSBPDMODE = C.SRTO_TSBPDMODE
SRTO_TLPKTDROP = C.SRTO_TLPKTDROP
SRTO_SNDDROPDELAY = C.SRTO_SNDDROPDELAY
SRTO_NAKREPORT = C.SRTO_NAKREPORT
SRTO_CONNTIMEO = C.SRTO_CONNTIMEO
SRTO_LOSSMAXTTL = C.SRTO_LOSSMAXTTL
SRTO_RCVLATENCY = C.SRTO_RCVLATENCY
SRTO_PEERLATENCY = C.SRTO_PEERLATENCY
SRTO_MINVERSION = C.SRTO_MINVERSION
SRTO_STREAMID = C.SRTO_STREAMID
SRTO_CONGESTION = C.SRTO_CONGESTION
SRTO_MESSAGEAPI = C.SRTO_MESSAGEAPI
SRTO_PAYLOADSIZE = C.SRTO_PAYLOADSIZE
SRTO_KMREFRESHRATE = C.SRTO_KMREFRESHRATE
SRTO_KMPREANNOUNCE = C.SRTO_KMPREANNOUNCE
SRTO_ENFORCEDENCRYPTION = C.SRTO_ENFORCEDENCRYPTION
SRTO_PEERIDLETIMEO = C.SRTO_PEERIDLETIMEO
SRTO_PACKETFILTER = C.SRTO_PACKETFILTER
SRTO_STATE = C.SRTO_STATE
)
type socketOption struct {
name string
level int
option int
binding int
dataType int
}
// List of possible srt socket options
var SocketOptions = []socketOption{
{"transtype", 0, SRTO_TRANSTYPE, bindingPre, tTransType},
{"maxbw", 0, SRTO_MAXBW, bindingPre, tInteger64},
{"pbkeylen", 0, SRTO_PBKEYLEN, bindingPre, tInteger32},
{"passphrase", 0, SRTO_PASSPHRASE, bindingPre, tString},
{"mss", 0, SRTO_MSS, bindingPre, tInteger32},
{"fc", 0, SRTO_FC, bindingPre, tInteger32},
{"sndbuf", 0, SRTO_SNDBUF, bindingPre, tInteger32},
{"rcvbuf", 0, SRTO_RCVBUF, bindingPre, tInteger32},
{"ipttl", 0, SRTO_IPTTL, bindingPre, tInteger32},
{"iptos", 0, SRTO_IPTOS, bindingPre, tInteger32},
{"inputbw", 0, SRTO_INPUTBW, bindingPost, tInteger64},
{"oheadbw", 0, SRTO_OHEADBW, bindingPost, tInteger32},
{"latency", 0, SRTO_LATENCY, bindingPre, tInteger32},
{"tsbpdmode", 0, SRTO_TSBPDMODE, bindingPre, tBoolean},
{"tlpktdrop", 0, SRTO_TLPKTDROP, bindingPre, tBoolean},
{"snddropdelay", 0, SRTO_SNDDROPDELAY, bindingPost, tInteger32},
{"nakreport", 0, SRTO_NAKREPORT, bindingPre, tBoolean},
{"conntimeo", 0, SRTO_CONNTIMEO, bindingPre, tInteger32},
{"lossmaxttl", 0, SRTO_LOSSMAXTTL, bindingPre, tInteger32},
{"rcvlatency", 0, SRTO_RCVLATENCY, bindingPre, tInteger32},
{"peerlatency", 0, SRTO_PEERLATENCY, bindingPre, tInteger32},
{"minversion", 0, SRTO_MINVERSION, bindingPre, tInteger32},
{"streamid", 0, SRTO_STREAMID, bindingPre, tString},
{"congestion", 0, SRTO_CONGESTION, bindingPre, tString},
{"messageapi", 0, SRTO_MESSAGEAPI, bindingPre, tBoolean},
{"payloadsize", 0, SRTO_PAYLOADSIZE, bindingPre, tInteger32},
{"kmrefreshrate", 0, SRTO_KMREFRESHRATE, bindingPre, tInteger32},
{"kmpreannounce", 0, SRTO_KMPREANNOUNCE, bindingPre, tInteger32},
{"enforcedencryption", 0, SRTO_ENFORCEDENCRYPTION, bindingPre, tBoolean},
{"peeridletimeo", 0, SRTO_PEERIDLETIMEO, bindingPre, tInteger32},
{"packetfilter", 0, SRTO_PACKETFILTER, bindingPre, tString},
}
func setSocketLingerOption(s C.int, li int32) error {
var lin syscall.Linger
lin.Linger = li
if lin.Linger > 0 {
lin.Onoff = 1
} else {
lin.Onoff = 0
}
res := C.srt_setsockopt(s, bindingPre, C.SRTO_LINGER, unsafe.Pointer(&lin), C.int(unsafe.Sizeof(lin)))
if res == SRT_ERROR {
return errors.New("failed to set linger")
}
return nil
}
func getSocketLingerOption(s *SrtSocket) (int32, error) {
var lin syscall.Linger
size := int(unsafe.Sizeof(lin))
err := s.getSockOpt(C.SRTO_LINGER, unsafe.Pointer(&lin), &size)
if err != nil {
return 0, err
}
if lin.Onoff == 0 {
return 0, nil
}
return lin.Linger, nil
}
// Set socket options for SRT
func setSocketOptions(s C.int, binding int, options map[string]string) error {
for _, so := range SocketOptions {
if val, ok := options[so.name]; ok {
if so.binding == binding {
if so.dataType == tInteger32 {
v, err := strconv.Atoi(val)
v32 := int32(v)
if err == nil {
result := C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(&v32), C.int32_t(unsafe.Sizeof(v32)))
if result == -1 {
return fmt.Errorf("warning - error setting option %s to %s, %w", so.name, val, srtGetAndClearError())
}
}
} else if so.dataType == tInteger64 {
v, err := strconv.ParseInt(val, 10, 64)
if err == nil {
result := C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(&v), C.int32_t(unsafe.Sizeof(v)))
if result == -1 {
return fmt.Errorf("warning - error setting option %s to %s, %w", so.name, val, srtGetAndClearError())
}
}
} else if so.dataType == tString {
sval := C.CString(val)
defer C.free(unsafe.Pointer(sval))
result := C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(sval), C.int32_t(len(val)))
if result == -1 {
return fmt.Errorf("warning - error setting option %s to %s, %w", so.name, val, srtGetAndClearError())
}
} else if so.dataType == tBoolean {
var result C.int
if val == "1" {
v := C.char(1)
result = C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(&v), C.int32_t(unsafe.Sizeof(v)))
} else if val == "0" {
v := C.char(0)
result = C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(&v), C.int32_t(unsafe.Sizeof(v)))
}
if result == -1 {
return fmt.Errorf("warning - error setting option %s to %s, %w", so.name, val, srtGetAndClearError())
}
} else if so.dataType == tTransType {
var result C.int
if val == "live" {
var v int32 = C.SRTT_LIVE
result = C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(&v), C.int32_t(unsafe.Sizeof(v)))
} else if val == "file" {
var v int32 = C.SRTT_FILE
result = C.srt_setsockflag(s, C.SRT_SOCKOPT(so.option), unsafe.Pointer(&v), C.int32_t(unsafe.Sizeof(v)))
}
if result == -1 {
return fmt.Errorf("warning - error setting option %s to %s: %w", so.name, val, srtGetAndClearError())
}
}
}
}
}
return nil
}

@ -0,0 +1,188 @@
package srtgo
// #cgo LDFLAGS: -lsrt
// #include <srt/srt.h>
import "C"
type SrtStats struct {
// Global measurements
MsTimeStamp int64 // time since the UDT entity is started, in milliseconds
PktSentTotal int64 // total number of sent data packets, including retransmissions
PktRecvTotal int64 // total number of received packets
PktSndLossTotal int // total number of lost packets (sender side)
PktRcvLossTotal int // total number of lost packets (receiver side)
PktRetransTotal int // total number of retransmitted packets
PktSentACKTotal int // total number of sent ACK packets
PktRecvACKTotal int // total number of received ACK packets
PktSentNAKTotal int // total number of sent NAK packets
PktRecvNAKTotal int // total number of received NAK packets
UsSndDurationTotal int64 // total time duration when UDT is sending data (idle time exclusive)
PktSndDropTotal int // number of too-late-to-send dropped packets
PktRcvDropTotal int // number of too-late-to play missing packets
PktRcvUndecryptTotal int // number of undecrypted packets
ByteSentTotal int64 // total number of sent data bytes, including retransmissions
ByteRecvTotal int64 // total number of received bytes
ByteRcvLossTotal int64 // total number of lost bytes
ByteRetransTotal int64 // total number of retransmitted bytes
ByteSndDropTotal int64 // number of too-late-to-send dropped bytes
ByteRcvDropTotal int64 // number of too-late-to play missing bytes (estimate based on average packet size)
ByteRcvUndecryptTotal int64 // number of undecrypted bytes
// Local measurements
PktSent int64 // number of sent data packets, including retransmissions
PktRecv int64 // number of received packets
PktSndLoss int // number of lost packets (sender side)
PktRcvLoss int // number of lost packets (receiver side)
PktRetrans int // number of retransmitted packets
PktRcvRetrans int // number of retransmitted packets received
PktSentACK int // number of sent ACK packets
PktRecvACK int // number of received ACK packets
PktSentNAK int // number of sent NAK packets
PktRecvNAK int // number of received NAK packets
MbpsSendRate float64 // sending rate in Mb/s
MbpsRecvRate float64 // receiving rate in Mb/s
UsSndDuration int64 // busy sending time (i.e., idle time exclusive)
PktReorderDistance int // size of order discrepancy in received sequences
PktRcvAvgBelatedTime float64 // average time of packet delay for belated packets (packets with sequence past the ACK)
PktRcvBelated int64 // number of received AND IGNORED packets due to having come too late
PktSndDrop int // number of too-late-to-send dropped packets
PktRcvDrop int // number of too-late-to play missing packets
PktRcvUndecrypt int // number of undecrypted packets
ByteSent int64 // number of sent data bytes, including retransmissions
ByteRecv int64 // number of received bytes
ByteRcvLoss int64 // number of retransmitted Bytes
ByteRetrans int64 // number of retransmitted Bytes
ByteSndDrop int64 // number of too-late-to-send dropped Bytes
ByteRcvDrop int64 // number of too-late-to play missing Bytes (estimate based on average packet size)
ByteRcvUndecrypt int64 // number of undecrypted bytes
// Instant measurements
UsPktSndPeriod float64 // packet sending period, in microseconds
PktFlowWindow int // flow window size, in number of packets
PktCongestionWindow int // congestion window size, in number of packets
PktFlightSize int // number of packets on flight
MsRTT float64 // RTT, in milliseconds
MbpsBandwidth float64 // estimated bandwidth, in Mb/s
ByteAvailSndBuf int // available UDT sender buffer size
ByteAvailRcvBuf int // available UDT receiver buffer size
MbpsMaxBW float64 // Transmit Bandwidth ceiling (Mbps)
ByteMSS int // MTU
PktSndBuf int // UnACKed packets in UDT sender
ByteSndBuf int // UnACKed bytes in UDT sender
MsSndBuf int // UnACKed timespan (msec) of UDT sender
MsSndTsbPdDelay int // Timestamp-based Packet Delivery Delay
PktRcvBuf int // Undelivered packets in UDT receiver
ByteRcvBuf int // Undelivered bytes of UDT receiver
MsRcvBuf int // Undelivered timespan (msec) of UDT receiver
MsRcvTsbPdDelay int // Timestamp-based Packet Delivery Delay
PktSndFilterExtraTotal int // number of control packets supplied by packet filter
PktRcvFilterExtraTotal int // number of control packets received and not supplied back
PktRcvFilterSupplyTotal int // number of packets that the filter supplied extra (e.g. FEC rebuilt)
PktRcvFilterLossTotal int // number of packet loss not coverable by filter
PktSndFilterExtra int // number of control packets supplied by packet filter
PktRcvFilterExtra int // number of control packets received and not supplied back
PktRcvFilterSupply int // number of packets that the filter supplied extra (e.g. FEC rebuilt)
PktRcvFilterLoss int // number of packet loss not coverable by filter
PktReorderTolerance int // packet reorder tolerance value
}
func newSrtStats(stats *C.SRT_TRACEBSTATS) *SrtStats {
s := new(SrtStats)
s.MsTimeStamp = int64(stats.msTimeStamp)
s.PktSentTotal = int64(stats.pktSentTotal)
s.PktRecvTotal = int64(stats.pktRecvTotal)
s.PktSndLossTotal = int(stats.pktSndLossTotal)
s.PktRcvLossTotal = int(stats.pktRcvLossTotal)
s.PktRetransTotal = int(stats.pktRetransTotal)
s.PktSentACKTotal = int(stats.pktSentACKTotal)
s.PktRecvACKTotal = int(stats.pktRecvACKTotal)
s.PktSentNAKTotal = int(stats.pktSentNAKTotal)
s.PktRecvNAKTotal = int(stats.pktRecvNAKTotal)
s.UsSndDurationTotal = int64(stats.usSndDurationTotal)
s.PktSndDropTotal = int(stats.pktSndDropTotal)
s.PktRcvDropTotal = int(stats.pktRcvDropTotal)
s.PktRcvUndecryptTotal = int(stats.pktRcvUndecryptTotal)
s.ByteSentTotal = int64(stats.byteSentTotal)
s.ByteRecvTotal = int64(stats.byteRecvTotal)
s.ByteRcvLossTotal = int64(stats.byteRcvLossTotal)
s.ByteRetransTotal = int64(stats.byteRetransTotal)
s.ByteSndDropTotal = int64(stats.byteSndDropTotal)
s.ByteRcvDropTotal = int64(stats.byteRcvDropTotal)
s.ByteRcvUndecryptTotal = int64(stats.byteRcvUndecryptTotal)
s.PktSent = int64(stats.pktSent)
s.PktRecv = int64(stats.pktRecv)
s.PktSndLoss = int(stats.pktSndLoss)
s.PktRcvLoss = int(stats.pktRcvLoss)
s.PktRetrans = int(stats.pktRetrans)
s.PktRcvRetrans = int(stats.pktRcvRetrans)
s.PktSentACK = int(stats.pktSentACK)
s.PktRecvACK = int(stats.pktRecvACK)
s.PktSentNAK = int(stats.pktSentNAK)
s.PktRecvNAK = int(stats.pktRecvNAK)
s.MbpsSendRate = float64(stats.mbpsSendRate)
s.MbpsRecvRate = float64(stats.mbpsRecvRate)
s.UsSndDuration = int64(stats.usSndDuration)
s.PktReorderDistance = int(stats.pktReorderDistance)
s.PktRcvAvgBelatedTime = float64(stats.pktRcvAvgBelatedTime)
s.PktRcvBelated = int64(stats.pktRcvBelated)
s.PktSndDrop = int(stats.pktSndDrop)
s.PktRcvDrop = int(stats.pktRcvDrop)
s.PktRcvUndecrypt = int(stats.pktRcvUndecrypt)
s.ByteSent = int64(stats.byteSent)
s.ByteRecv = int64(stats.byteRecv)
s.ByteRcvLoss = int64(stats.byteRcvLoss)
s.ByteRetrans = int64(stats.byteRetrans)
s.ByteSndDrop = int64(stats.byteSndDrop)
s.ByteRcvDrop = int64(stats.byteRcvDrop)
s.ByteRcvUndecrypt = int64(stats.byteRcvUndecrypt)
s.UsPktSndPeriod = float64(stats.usPktSndPeriod)
s.PktFlowWindow = int(stats.pktFlowWindow)
s.PktCongestionWindow = int(stats.pktCongestionWindow)
s.PktFlightSize = int(stats.pktFlightSize)
s.MsRTT = float64(stats.msRTT)
s.MbpsBandwidth = float64(stats.mbpsBandwidth)
s.ByteAvailSndBuf = int(stats.byteAvailSndBuf)
s.ByteAvailRcvBuf = int(stats.byteAvailRcvBuf)
s.MbpsMaxBW = float64(stats.mbpsMaxBW)
s.ByteMSS = int(stats.byteMSS)
s.PktSndBuf = int(stats.pktSndBuf)
s.ByteSndBuf = int(stats.byteSndBuf)
s.MsSndBuf = int(stats.msSndBuf)
s.MsSndTsbPdDelay = int(stats.msSndTsbPdDelay)
s.PktRcvBuf = int(stats.pktRcvBuf)
s.ByteRcvBuf = int(stats.byteRcvBuf)
s.MsRcvBuf = int(stats.msRcvBuf)
s.MsRcvTsbPdDelay = int(stats.msRcvTsbPdDelay)
s.PktSndFilterExtraTotal = int(stats.pktSndFilterExtraTotal)
s.PktRcvFilterExtraTotal = int(stats.pktRcvFilterExtraTotal)
s.PktRcvFilterSupplyTotal = int(stats.pktRcvFilterSupplyTotal)
s.PktRcvFilterLossTotal = int(stats.pktRcvFilterLossTotal)
s.PktSndFilterExtra = int(stats.pktSndFilterExtra)
s.PktRcvFilterExtra = int(stats.pktRcvFilterExtra)
s.PktRcvFilterSupply = int(stats.pktRcvFilterSupply)
s.PktRcvFilterLoss = int(stats.pktRcvFilterLoss)
s.PktReorderTolerance = int(stats.pktReorderTolerance)
return s
}

@ -0,0 +1,55 @@
package srtgo
/*
#cgo LDFLAGS: -lsrt
#include <srt/srt.h>
int srt_sendmsg2_wrapped(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL *mctrl, int *srterror, int *syserror)
{
int ret = srt_sendmsg2(u, buf, len, mctrl);
if (ret < 0) {
*srterror = srt_getlasterror(syserror);
}
return ret;
}
*/
import "C"
import (
"errors"
"syscall"
"unsafe"
)
func srtSendMsg2Impl(u C.SRTSOCKET, buf []byte, msgctrl *C.SRT_MSGCTRL) (n int, err error) {
srterr := C.int(0)
syserr := C.int(0)
n = int(C.srt_sendmsg2_wrapped(u, (*C.char)(unsafe.Pointer(&buf[0])), C.int(len(buf)), msgctrl, &srterr, &syserr))
if n < 0 {
srterror := SRTErrno(srterr)
if syserr < 0 {
srterror.wrapSysErr(syscall.Errno(syserr))
}
err = srterror
n = 0
}
return
}
// Write data to the SRT socket
func (s SrtSocket) Write(b []byte) (n int, err error) {
//Fastpath:
if !s.blocking {
s.pd.reset(ModeWrite)
}
n, err = srtSendMsg2Impl(s.socket, b, nil)
for {
if !errors.Is(err, error(EAsyncSND)) || s.blocking {
return
}
s.pd.wait(ModeWrite)
n, err = srtSendMsg2Impl(s.socket, b, nil)
}
}

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019 Yasuhiro Matsumoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,29 @@
# go-pointer
Utility for cgo
## Usage
https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md
In go 1.6, cgo argument can't be passed Go pointer.
```
var s string
C.pass_pointer(pointer.Save(&s))
v := *(pointer.Restore(C.get_from_pointer()).(*string))
```
## Installation
```
go get github.com/mattn/go-pointer
```
## License
MIT
## Author
Yasuhiro Matsumoto (a.k.a mattn)

@ -0,0 +1,57 @@
package pointer
// #include <stdlib.h>
import "C"
import (
"sync"
"unsafe"
)
var (
mutex sync.RWMutex
store = map[unsafe.Pointer]interface{}{}
)
func Save(v interface{}) unsafe.Pointer {
if v == nil {
return nil
}
// Generate real fake C pointer.
// This pointer will not store any data, but will bi used for indexing purposes.
// Since Go doest allow to cast dangling pointer to unsafe.Pointer, we do rally allocate one byte.
// Why we need indexing, because Go doest allow C code to store pointers to Go data.
var ptr unsafe.Pointer = C.malloc(C.size_t(1))
if ptr == nil {
panic("can't allocate 'cgo-pointer hack index pointer': ptr == nil")
}
mutex.Lock()
store[ptr] = v
mutex.Unlock()
return ptr
}
func Restore(ptr unsafe.Pointer) (v interface{}) {
if ptr == nil {
return nil
}
mutex.RLock()
v = store[ptr]
mutex.RUnlock()
return
}
func Unref(ptr unsafe.Pointer) {
if ptr == nil {
return
}
mutex.Lock()
delete(store, ptr)
mutex.Unlock()
C.free(ptr)
}

@ -28,6 +28,8 @@ var (
uint32Type = reflect.TypeOf(uint32(1))
uint64Type = reflect.TypeOf(uint64(1))
uintptrType = reflect.TypeOf(uintptr(1))
float32Type = reflect.TypeOf(float32(1))
float64Type = reflect.TypeOf(float64(1))
@ -308,11 +310,11 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
case reflect.Struct:
{
// All structs enter here. We're not interested in most types.
if !canConvert(obj1Value, timeType) {
if !obj1Value.CanConvert(timeType) {
break
}
// time.Time can compared!
// time.Time can be compared!
timeObj1, ok := obj1.(time.Time)
if !ok {
timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time)
@ -328,7 +330,7 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
case reflect.Slice:
{
// We only care about the []byte type.
if !canConvert(obj1Value, bytesType) {
if !obj1Value.CanConvert(bytesType) {
break
}
@ -345,6 +347,26 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
return CompareType(bytes.Compare(bytesObj1, bytesObj2)), true
}
case reflect.Uintptr:
{
uintptrObj1, ok := obj1.(uintptr)
if !ok {
uintptrObj1 = obj1Value.Convert(uintptrType).Interface().(uintptr)
}
uintptrObj2, ok := obj2.(uintptr)
if !ok {
uintptrObj2 = obj2Value.Convert(uintptrType).Interface().(uintptr)
}
if uintptrObj1 > uintptrObj2 {
return compareGreater, true
}
if uintptrObj1 == uintptrObj2 {
return compareEqual, true
}
if uintptrObj1 < uintptrObj2 {
return compareLess, true
}
}
}
return compareEqual, false

@ -1,16 +0,0 @@
//go:build go1.17
// +build go1.17
// TODO: once support for Go 1.16 is dropped, this file can be
// merged/removed with assertion_compare_go1.17_test.go and
// assertion_compare_legacy.go
package assert
import "reflect"
// Wrapper around reflect.Value.CanConvert, for compatibility
// reasons.
func canConvert(value reflect.Value, to reflect.Type) bool {
return value.CanConvert(to)
}

@ -1,16 +0,0 @@
//go:build !go1.17
// +build !go1.17
// TODO: once support for Go 1.16 is dropped, this file can be
// merged/removed with assertion_compare_go1.17_test.go and
// assertion_compare_can_convert.go
package assert
import "reflect"
// Older versions of Go does not have the reflect.Value.CanConvert
// method.
func canConvert(value reflect.Value, to reflect.Type) bool {
return false
}

@ -1,7 +1,4 @@
/*
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
package assert
@ -107,7 +104,7 @@ func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{},
return EqualExportedValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// EqualValuesf asserts that two objects are equal or convertable to the same types
// EqualValuesf asserts that two objects are equal or convertible to the same types
// and equal.
//
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
@ -616,6 +613,16 @@ func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interf
return NotErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
}
// NotImplementsf asserts that an object does not implement the specified interface.
//
// assert.NotImplementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
func NotImplementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotImplements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
}
// NotNilf asserts that the specified object is not nil.
//
// assert.NotNilf(t, err, "error message %s", "formatted")
@ -660,10 +667,12 @@ func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string,
return NotSame(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// NotSubsetf asserts that the specified list(array, slice...) contains not all
// elements given in the specified subset(array, slice...).
// NotSubsetf asserts that the specified list(array, slice...) or map does NOT
// contain all elements given in the specified subset list(array, slice...) or
// map.
//
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted")
// assert.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -747,10 +756,11 @@ func Samef(t TestingT, expected interface{}, actual interface{}, msg string, arg
return Same(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// Subsetf asserts that the specified list(array, slice...) contains all
// elements given in the specified subset(array, slice...).
// Subsetf asserts that the specified list(array, slice...) or map contains all
// elements given in the specified subset list(array, slice...) or map.
//
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
// assert.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted")
// assert.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()

@ -1,7 +1,4 @@
/*
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
package assert
@ -189,7 +186,7 @@ func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface
return EqualExportedValuesf(a.t, expected, actual, msg, args...)
}
// EqualValues asserts that two objects are equal or convertable to the same types
// EqualValues asserts that two objects are equal or convertible to the same types
// and equal.
//
// a.EqualValues(uint32(123), int32(123))
@ -200,7 +197,7 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn
return EqualValues(a.t, expected, actual, msgAndArgs...)
}
// EqualValuesf asserts that two objects are equal or convertable to the same types
// EqualValuesf asserts that two objects are equal or convertible to the same types
// and equal.
//
// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted")
@ -1221,6 +1218,26 @@ func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...in
return NotErrorIsf(a.t, err, target, msg, args...)
}
// NotImplements asserts that an object does not implement the specified interface.
//
// a.NotImplements((*MyInterface)(nil), new(MyObject))
func (a *Assertions) NotImplements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotImplements(a.t, interfaceObject, object, msgAndArgs...)
}
// NotImplementsf asserts that an object does not implement the specified interface.
//
// a.NotImplementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
func (a *Assertions) NotImplementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotImplementsf(a.t, interfaceObject, object, msg, args...)
}
// NotNil asserts that the specified object is not nil.
//
// a.NotNil(err)
@ -1309,10 +1326,12 @@ func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg stri
return NotSamef(a.t, expected, actual, msg, args...)
}
// NotSubset asserts that the specified list(array, slice...) contains not all
// elements given in the specified subset(array, slice...).
// NotSubset asserts that the specified list(array, slice...) or map does NOT
// contain all elements given in the specified subset list(array, slice...) or
// map.
//
// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
// a.NotSubset([1, 3, 4], [1, 2])
// a.NotSubset({"x": 1, "y": 2}, {"z": 3})
func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@ -1320,10 +1339,12 @@ func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs
return NotSubset(a.t, list, subset, msgAndArgs...)
}
// NotSubsetf asserts that the specified list(array, slice...) contains not all
// elements given in the specified subset(array, slice...).
// NotSubsetf asserts that the specified list(array, slice...) or map does NOT
// contain all elements given in the specified subset list(array, slice...) or
// map.
//
// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
// a.NotSubsetf([1, 3, 4], [1, 2], "error message %s", "formatted")
// a.NotSubsetf({"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@ -1483,10 +1504,11 @@ func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string,
return Samef(a.t, expected, actual, msg, args...)
}
// Subset asserts that the specified list(array, slice...) contains all
// elements given in the specified subset(array, slice...).
// Subset asserts that the specified list(array, slice...) or map contains all
// elements given in the specified subset list(array, slice...) or map.
//
// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
// a.Subset([1, 2, 3], [1, 2])
// a.Subset({"x": 1, "y": 2}, {"x": 1})
func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@ -1494,10 +1516,11 @@ func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...
return Subset(a.t, list, subset, msgAndArgs...)
}
// Subsetf asserts that the specified list(array, slice...) contains all
// elements given in the specified subset(array, slice...).
// Subsetf asserts that the specified list(array, slice...) or map contains all
// elements given in the specified subset list(array, slice...) or map.
//
// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
// a.Subsetf([1, 2, 3], [1, 2], "error message %s", "formatted")
// a.Subsetf({"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()

@ -19,7 +19,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/pmezard/go-difflib/difflib"
yaml "gopkg.in/yaml.v3"
"gopkg.in/yaml.v3"
)
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl"
@ -110,7 +110,12 @@ func copyExportedFields(expected interface{}) interface{} {
return result.Interface()
case reflect.Array, reflect.Slice:
result := reflect.MakeSlice(expectedType, expectedValue.Len(), expectedValue.Len())
var result reflect.Value
if expectedKind == reflect.Array {
result = reflect.New(reflect.ArrayOf(expectedValue.Len(), expectedType.Elem())).Elem()
} else {
result = reflect.MakeSlice(expectedType, expectedValue.Len(), expectedValue.Len())
}
for i := 0; i < expectedValue.Len(); i++ {
index := expectedValue.Index(i)
if isNil(index) {
@ -140,6 +145,8 @@ func copyExportedFields(expected interface{}) interface{} {
// structures.
//
// This function does no assertion of any kind.
//
// Deprecated: Use [EqualExportedValues] instead.
func ObjectsExportedFieldsAreEqual(expected, actual interface{}) bool {
expectedCleaned := copyExportedFields(expected)
actualCleaned := copyExportedFields(actual)
@ -153,17 +160,40 @@ func ObjectsAreEqualValues(expected, actual interface{}) bool {
return true
}
actualType := reflect.TypeOf(actual)
if actualType == nil {
expectedValue := reflect.ValueOf(expected)
actualValue := reflect.ValueOf(actual)
if !expectedValue.IsValid() || !actualValue.IsValid() {
return false
}
expectedValue := reflect.ValueOf(expected)
if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) {
expectedType := expectedValue.Type()
actualType := actualValue.Type()
if !expectedType.ConvertibleTo(actualType) {
return false
}
if !isNumericType(expectedType) || !isNumericType(actualType) {
// Attempt comparison after type conversion
return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual)
return reflect.DeepEqual(
expectedValue.Convert(actualType).Interface(), actual,
)
}
return false
// If BOTH values are numeric, there are chances of false positives due
// to overflow or underflow. So, we need to make sure to always convert
// the smaller type to a larger type before comparing.
if expectedType.Size() >= actualType.Size() {
return actualValue.Convert(expectedType).Interface() == expected
}
return expectedValue.Convert(actualType).Interface() == actual
}
// isNumericType returns true if the type is one of:
// int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64,
// float32, float64, complex64, complex128
func isNumericType(t reflect.Type) bool {
return t.Kind() >= reflect.Int && t.Kind() <= reflect.Complex128
}
/* CallerInfo is necessary because the assert functions use the testing object
@ -266,7 +296,7 @@ func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
// Aligns the provided message so that all lines after the first line start at the same location as the first line.
// Assumes that the first line starts at the correct location (after carriage return, tab, label, spacer and tab).
// The longestLabelLen parameter specifies the length of the longest label in the output (required becaues this is the
// The longestLabelLen parameter specifies the length of the longest label in the output (required because this is the
// basis on which the alignment occurs).
func indentMessageLines(message string, longestLabelLen int) string {
outBuf := new(bytes.Buffer)
@ -382,6 +412,25 @@ func Implements(t TestingT, interfaceObject interface{}, object interface{}, msg
return true
}
// NotImplements asserts that an object does not implement the specified interface.
//
// assert.NotImplements(t, (*MyInterface)(nil), new(MyObject))
func NotImplements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
interfaceType := reflect.TypeOf(interfaceObject).Elem()
if object == nil {
return Fail(t, fmt.Sprintf("Cannot check if nil does not implement %v", interfaceType), msgAndArgs...)
}
if reflect.TypeOf(object).Implements(interfaceType) {
return Fail(t, fmt.Sprintf("%T implements %v", object, interfaceType), msgAndArgs...)
}
return true
}
// IsType asserts that the specified objects are of the same type.
func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
@ -496,7 +545,7 @@ func samePointers(first, second interface{}) bool {
// representations appropriate to be presented to the user.
//
// If the values are not of like type, the returned strings will be prefixed
// with the type name, and the value will be enclosed in parenthesis similar
// with the type name, and the value will be enclosed in parentheses similar
// to a type conversion in the Go grammar.
func formatUnequalValues(expected, actual interface{}) (e string, a string) {
if reflect.TypeOf(expected) != reflect.TypeOf(actual) {
@ -523,7 +572,7 @@ func truncatingFormat(data interface{}) string {
return value
}
// EqualValues asserts that two objects are equal or convertable to the same types
// EqualValues asserts that two objects are equal or convertible to the same types
// and equal.
//
// assert.EqualValues(t, uint32(123), int32(123))
@ -566,12 +615,19 @@ func EqualExportedValues(t TestingT, expected, actual interface{}, msgAndArgs ..
return Fail(t, fmt.Sprintf("Types expected to match exactly\n\t%v != %v", aType, bType), msgAndArgs...)
}
if aType.Kind() == reflect.Ptr {
aType = aType.Elem()
}
if bType.Kind() == reflect.Ptr {
bType = bType.Elem()
}
if aType.Kind() != reflect.Struct {
return Fail(t, fmt.Sprintf("Types expected to both be struct \n\t%v != %v", aType.Kind(), reflect.Struct), msgAndArgs...)
return Fail(t, fmt.Sprintf("Types expected to both be struct or pointer to struct \n\t%v != %v", aType.Kind(), reflect.Struct), msgAndArgs...)
}
if bType.Kind() != reflect.Struct {
return Fail(t, fmt.Sprintf("Types expected to both be struct \n\t%v != %v", bType.Kind(), reflect.Struct), msgAndArgs...)
return Fail(t, fmt.Sprintf("Types expected to both be struct or pointer to struct \n\t%v != %v", bType.Kind(), reflect.Struct), msgAndArgs...)
}
expected = copyExportedFields(expected)
@ -620,17 +676,6 @@ func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return Fail(t, "Expected value not to be nil.", msgAndArgs...)
}
// containsKind checks if a specified kind in the slice of kinds.
func containsKind(kinds []reflect.Kind, kind reflect.Kind) bool {
for i := 0; i < len(kinds); i++ {
if kind == kinds[i] {
return true
}
}
return false
}
// isNil checks if a specified object is nil or not, without Failing.
func isNil(object interface{}) bool {
if object == nil {
@ -638,16 +683,13 @@ func isNil(object interface{}) bool {
}
value := reflect.ValueOf(object)
kind := value.Kind()
isNilableKind := containsKind(
[]reflect.Kind{
reflect.Chan, reflect.Func,
reflect.Interface, reflect.Map,
reflect.Ptr, reflect.Slice, reflect.UnsafePointer},
kind)
if isNilableKind && value.IsNil() {
return true
switch value.Kind() {
case
reflect.Chan, reflect.Func,
reflect.Interface, reflect.Map,
reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
return value.IsNil()
}
return false
@ -731,16 +773,14 @@ func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
}
// getLen try to get length of object.
// return (false, 0) if impossible.
func getLen(x interface{}) (ok bool, length int) {
// getLen tries to get the length of an object.
// It returns (0, false) if impossible.
func getLen(x interface{}) (length int, ok bool) {
v := reflect.ValueOf(x)
defer func() {
if e := recover(); e != nil {
ok = false
}
ok = recover() == nil
}()
return true, v.Len()
return v.Len(), true
}
// Len asserts that the specified object has specific length.
@ -751,13 +791,13 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{})
if h, ok := t.(tHelper); ok {
h.Helper()
}
ok, l := getLen(object)
l, ok := getLen(object)
if !ok {
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", object), msgAndArgs...)
return Fail(t, fmt.Sprintf("\"%v\" could not be applied builtin len()", object), msgAndArgs...)
}
if l != length {
return Fail(t, fmt.Sprintf("\"%s\" should have %d item(s), but has %d", object, length, l), msgAndArgs...)
return Fail(t, fmt.Sprintf("\"%v\" should have %d item(s), but has %d", object, length, l), msgAndArgs...)
}
return true
}
@ -919,10 +959,11 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{})
}
// Subset asserts that the specified list(array, slice...) contains all
// elements given in the specified subset(array, slice...).
// Subset asserts that the specified list(array, slice...) or map contains all
// elements given in the specified subset list(array, slice...) or map.
//
// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
// assert.Subset(t, [1, 2, 3], [1, 2])
// assert.Subset(t, {"x": 1, "y": 2}, {"x": 1})
func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -975,10 +1016,12 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok
return true
}
// NotSubset asserts that the specified list(array, slice...) contains not all
// elements given in the specified subset(array, slice...).
// NotSubset asserts that the specified list(array, slice...) or map does NOT
// contain all elements given in the specified subset list(array, slice...) or
// map.
//
// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
// assert.NotSubset(t, [1, 3, 4], [1, 2])
// assert.NotSubset(t, {"x": 1, "y": 2}, {"z": 3})
func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -1439,7 +1482,7 @@ func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAnd
h.Helper()
}
if math.IsNaN(epsilon) {
return Fail(t, "epsilon must not be NaN")
return Fail(t, "epsilon must not be NaN", msgAndArgs...)
}
actualEpsilon, err := calcRelativeError(expected, actual)
if err != nil {
@ -1458,19 +1501,26 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m
if h, ok := t.(tHelper); ok {
h.Helper()
}
if expected == nil || actual == nil ||
reflect.TypeOf(actual).Kind() != reflect.Slice ||
reflect.TypeOf(expected).Kind() != reflect.Slice {
if expected == nil || actual == nil {
return Fail(t, "Parameters must be slice", msgAndArgs...)
}
actualSlice := reflect.ValueOf(actual)
expectedSlice := reflect.ValueOf(expected)
actualSlice := reflect.ValueOf(actual)
for i := 0; i < actualSlice.Len(); i++ {
result := InEpsilon(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), epsilon)
if !result {
return result
if expectedSlice.Type().Kind() != reflect.Slice {
return Fail(t, "Expected value must be slice", msgAndArgs...)
}
expectedLen := expectedSlice.Len()
if !IsType(t, expected, actual) || !Len(t, actual, expectedLen) {
return false
}
for i := 0; i < expectedLen; i++ {
if !InEpsilon(t, expectedSlice.Index(i).Interface(), actualSlice.Index(i).Interface(), epsilon, "at index %d", i) {
return false
}
}
@ -1870,23 +1920,18 @@ func (c *CollectT) Errorf(format string, args ...interface{}) {
}
// FailNow panics.
func (c *CollectT) FailNow() {
func (*CollectT) FailNow() {
panic("Assertion failed")
}
// Reset clears the collected errors.
func (c *CollectT) Reset() {
c.errors = nil
// Deprecated: That was a method for internal usage that should not have been published. Now just panics.
func (*CollectT) Reset() {
panic("Reset() is deprecated")
}
// Copy copies the collected errors to the supplied t.
func (c *CollectT) Copy(t TestingT) {
if tt, ok := t.(tHelper); ok {
tt.Helper()
}
for _, err := range c.errors {
t.Errorf("%v", err)
}
// Deprecated: That was a method for internal usage that should not have been published. Now just panics.
func (*CollectT) Copy(TestingT) {
panic("Copy() is deprecated")
}
// EventuallyWithT asserts that given condition will be met in waitFor time,
@ -1912,8 +1957,8 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time
h.Helper()
}
collect := new(CollectT)
ch := make(chan bool, 1)
var lastFinishedTickErrs []error
ch := make(chan []error, 1)
timer := time.NewTimer(waitFor)
defer timer.Stop()
@ -1924,19 +1969,25 @@ func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time
for tick := ticker.C; ; {
select {
case <-timer.C:
collect.Copy(t)
for _, err := range lastFinishedTickErrs {
t.Errorf("%v", err)
}
return Fail(t, "Condition never satisfied", msgAndArgs...)
case <-tick:
tick = nil
collect.Reset()
go func() {
collect := new(CollectT)
defer func() {
ch <- collect.errors
}()
condition(collect)
ch <- len(collect.errors) == 0
}()
case v := <-ch:
if v {
case errs := <-ch:
if len(errs) == 0 {
return true
}
// Keep the errors from the last ended condition, so that they can be copied to t if timeout is reached.
lastFinishedTickErrs = errs
tick = ticker.C
}
}

@ -12,7 +12,7 @@ import (
// an error if building a new request fails.
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
w := httptest.NewRecorder()
req, err := http.NewRequest(method, url, nil)
req, err := http.NewRequest(method, url, http.NoBody)
if err != nil {
return -1, err
}
@ -32,12 +32,12 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
}
isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
if !isSuccessCode {
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code))
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code), msgAndArgs...)
}
return isSuccessCode
@ -54,12 +54,12 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
}
isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
if !isRedirectCode {
Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code))
Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code), msgAndArgs...)
}
return isRedirectCode
@ -76,12 +76,12 @@ func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
}
isErrorCode := code >= http.StatusBadRequest
if !isErrorCode {
Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code))
Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code), msgAndArgs...)
}
return isErrorCode
@ -98,12 +98,12 @@ func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, va
}
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
}
successful := code == statuscode
if !successful {
Fail(t, fmt.Sprintf("Expected HTTP status code %d for %q but received %d", statuscode, url+"?"+values.Encode(), code))
Fail(t, fmt.Sprintf("Expected HTTP status code %d for %q but received %d", statuscode, url+"?"+values.Encode(), code), msgAndArgs...)
}
return successful
@ -113,7 +113,10 @@ func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, va
// empty string if building a new request fails.
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
w := httptest.NewRecorder()
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
if len(values) > 0 {
url += "?" + values.Encode()
}
req, err := http.NewRequest(method, url, http.NoBody)
if err != nil {
return ""
}
@ -135,7 +138,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string,
contains := strings.Contains(body, fmt.Sprint(str))
if !contains {
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body), msgAndArgs...)
}
return contains
@ -155,7 +158,7 @@ func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url strin
contains := strings.Contains(body, fmt.Sprint(str))
if contains {
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body), msgAndArgs...)
}
return !contains

@ -1,7 +1,4 @@
/*
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
package require
@ -235,7 +232,7 @@ func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{},
t.FailNow()
}
// EqualValues asserts that two objects are equal or convertable to the same types
// EqualValues asserts that two objects are equal or convertible to the same types
// and equal.
//
// assert.EqualValues(t, uint32(123), int32(123))
@ -249,7 +246,7 @@ func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArg
t.FailNow()
}
// EqualValuesf asserts that two objects are equal or convertable to the same types
// EqualValuesf asserts that two objects are equal or convertible to the same types
// and equal.
//
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
@ -1546,6 +1543,32 @@ func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interf
t.FailNow()
}
// NotImplements asserts that an object does not implement the specified interface.
//
// assert.NotImplements(t, (*MyInterface)(nil), new(MyObject))
func NotImplements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.NotImplements(t, interfaceObject, object, msgAndArgs...) {
return
}
t.FailNow()
}
// NotImplementsf asserts that an object does not implement the specified interface.
//
// assert.NotImplementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
func NotImplementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.NotImplementsf(t, interfaceObject, object, msg, args...) {
return
}
t.FailNow()
}
// NotNil asserts that the specified object is not nil.
//
// assert.NotNil(t, err)
@ -1658,10 +1681,12 @@ func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string,
t.FailNow()
}
// NotSubset asserts that the specified list(array, slice...) contains not all
// elements given in the specified subset(array, slice...).
// NotSubset asserts that the specified list(array, slice...) or map does NOT
// contain all elements given in the specified subset list(array, slice...) or
// map.
//
// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
// assert.NotSubset(t, [1, 3, 4], [1, 2])
// assert.NotSubset(t, {"x": 1, "y": 2}, {"z": 3})
func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -1672,10 +1697,12 @@ func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...i
t.FailNow()
}
// NotSubsetf asserts that the specified list(array, slice...) contains not all
// elements given in the specified subset(array, slice...).
// NotSubsetf asserts that the specified list(array, slice...) or map does NOT
// contain all elements given in the specified subset list(array, slice...) or
// map.
//
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted")
// assert.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -1880,10 +1907,11 @@ func Samef(t TestingT, expected interface{}, actual interface{}, msg string, arg
t.FailNow()
}
// Subset asserts that the specified list(array, slice...) contains all
// elements given in the specified subset(array, slice...).
// Subset asserts that the specified list(array, slice...) or map contains all
// elements given in the specified subset list(array, slice...) or map.
//
// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
// assert.Subset(t, [1, 2, 3], [1, 2])
// assert.Subset(t, {"x": 1, "y": 2}, {"x": 1})
func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -1894,10 +1922,11 @@ func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...inte
t.FailNow()
}
// Subsetf asserts that the specified list(array, slice...) contains all
// elements given in the specified subset(array, slice...).
// Subsetf asserts that the specified list(array, slice...) or map contains all
// elements given in the specified subset list(array, slice...) or map.
//
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
// assert.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted")
// assert.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()

@ -1,7 +1,4 @@
/*
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
package require
@ -190,7 +187,7 @@ func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface
EqualExportedValuesf(a.t, expected, actual, msg, args...)
}
// EqualValues asserts that two objects are equal or convertable to the same types
// EqualValues asserts that two objects are equal or convertible to the same types
// and equal.
//
// a.EqualValues(uint32(123), int32(123))
@ -201,7 +198,7 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn
EqualValues(a.t, expected, actual, msgAndArgs...)
}
// EqualValuesf asserts that two objects are equal or convertable to the same types
// EqualValuesf asserts that two objects are equal or convertible to the same types
// and equal.
//
// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted")
@ -1222,6 +1219,26 @@ func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...in
NotErrorIsf(a.t, err, target, msg, args...)
}
// NotImplements asserts that an object does not implement the specified interface.
//
// a.NotImplements((*MyInterface)(nil), new(MyObject))
func (a *Assertions) NotImplements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
NotImplements(a.t, interfaceObject, object, msgAndArgs...)
}
// NotImplementsf asserts that an object does not implement the specified interface.
//
// a.NotImplementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
func (a *Assertions) NotImplementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
NotImplementsf(a.t, interfaceObject, object, msg, args...)
}
// NotNil asserts that the specified object is not nil.
//
// a.NotNil(err)
@ -1310,10 +1327,12 @@ func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg stri
NotSamef(a.t, expected, actual, msg, args...)
}
// NotSubset asserts that the specified list(array, slice...) contains not all
// elements given in the specified subset(array, slice...).
// NotSubset asserts that the specified list(array, slice...) or map does NOT
// contain all elements given in the specified subset list(array, slice...) or
// map.
//
// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
// a.NotSubset([1, 3, 4], [1, 2])
// a.NotSubset({"x": 1, "y": 2}, {"z": 3})
func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@ -1321,10 +1340,12 @@ func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs
NotSubset(a.t, list, subset, msgAndArgs...)
}
// NotSubsetf asserts that the specified list(array, slice...) contains not all
// elements given in the specified subset(array, slice...).
// NotSubsetf asserts that the specified list(array, slice...) or map does NOT
// contain all elements given in the specified subset list(array, slice...) or
// map.
//
// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
// a.NotSubsetf([1, 3, 4], [1, 2], "error message %s", "formatted")
// a.NotSubsetf({"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@ -1484,10 +1505,11 @@ func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string,
Samef(a.t, expected, actual, msg, args...)
}
// Subset asserts that the specified list(array, slice...) contains all
// elements given in the specified subset(array, slice...).
// Subset asserts that the specified list(array, slice...) or map contains all
// elements given in the specified subset list(array, slice...) or map.
//
// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
// a.Subset([1, 2, 3], [1, 2])
// a.Subset({"x": 1, "y": 2}, {"x": 1})
func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@ -1495,10 +1517,11 @@ func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...
Subset(a.t, list, subset, msgAndArgs...)
}
// Subsetf asserts that the specified list(array, slice...) contains all
// elements given in the specified subset(array, slice...).
// Subsetf asserts that the specified list(array, slice...) or map contains all
// elements given in the specified subset list(array, slice...) or map.
//
// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
// a.Subsetf([1, 2, 3], [1, 2], "error message %s", "formatted")
// a.Subsetf({"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()

@ -733,13 +733,14 @@ func (s *String) ReadOptionalASN1OctetString(out *[]byte, outPresent *bool, tag
return true
}
// ReadOptionalASN1Boolean sets *out to the value of the next ASN.1 BOOLEAN or,
// if the next bytes are not an ASN.1 BOOLEAN, to the value of defaultValue.
// It reports whether the operation was successful.
func (s *String) ReadOptionalASN1Boolean(out *bool, defaultValue bool) bool {
// ReadOptionalASN1Boolean attempts to read an optional ASN.1 BOOLEAN
// explicitly tagged with tag into out and advances. If no element with a
// matching tag is present, it sets "out" to defaultValue instead. It reports
// whether the read was successful.
func (s *String) ReadOptionalASN1Boolean(out *bool, tag asn1.Tag, defaultValue bool) bool {
var present bool
var child String
if !s.ReadOptionalASN1(&child, &present, asn1.BOOLEAN) {
if !s.ReadOptionalASN1(&child, &present, tag) {
return false
}
@ -748,7 +749,7 @@ func (s *String) ReadOptionalASN1Boolean(out *bool, defaultValue bool) bool {
return true
}
return s.ReadASN1Boolean(out)
return child.ReadASN1Boolean(out)
}
func (s *String) readASN1(out *String, outTag *asn1.Tag, skipHeader bool) bool {

@ -95,6 +95,11 @@ func (b *Builder) AddUint32(v uint32) {
b.add(byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
}
// AddUint48 appends a big-endian, 48-bit value to the byte string.
func (b *Builder) AddUint48(v uint64) {
b.add(byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
}
// AddUint64 appends a big-endian, 64-bit value to the byte string.
func (b *Builder) AddUint64(v uint64) {
b.add(byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v))

@ -81,6 +81,17 @@ func (s *String) ReadUint32(out *uint32) bool {
return true
}
// ReadUint48 decodes a big-endian, 48-bit value into out and advances over it.
// It reports whether the read was successful.
func (s *String) ReadUint48(out *uint64) bool {
v := s.read(6)
if v == nil {
return false
}
*out = uint64(v[0])<<40 | uint64(v[1])<<32 | uint64(v[2])<<24 | uint64(v[3])<<16 | uint64(v[4])<<8 | uint64(v[5])
return true
}
// ReadUint64 decodes a big-endian, 64-bit value into out and advances over it.
// It reports whether the read was successful.
func (s *String) ReadUint64(out *uint64) bool {

@ -1,7 +1,6 @@
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
//go:build amd64 && gc && !purego
// +build amd64,gc,!purego
package field

@ -1,7 +1,6 @@
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
//go:build amd64 && gc && !purego
// +build amd64,gc,!purego
#include "textflag.h"

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !amd64 || !gc || purego
// +build !amd64 !gc purego
package field

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build arm64 && gc && !purego
// +build arm64,gc,!purego
package field

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build arm64 && gc && !purego
// +build arm64,gc,!purego
#include "textflag.h"

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !arm64 || !gc || purego
// +build !arm64 !gc purego
package field

@ -260,9 +260,11 @@ var (
errReserved = errors.New("segment prefix is reserved")
errTooManyPtr = errors.New("too many pointers (>10)")
errInvalidPtr = errors.New("invalid pointer")
errInvalidName = errors.New("invalid dns name")
errNilResouceBody = errors.New("nil resource body")
errResourceLen = errors.New("insufficient data for resource body length")
errSegTooLong = errors.New("segment length too long")
errNameTooLong = errors.New("name too long")
errZeroSegLen = errors.New("zero length segment")
errResTooLong = errors.New("resource length too long")
errTooManyQuestions = errors.New("too many Questions to pack (>65535)")
@ -271,7 +273,6 @@ var (
errTooManyAdditionals = errors.New("too many Additionals to pack (>65535)")
errNonCanonicalName = errors.New("name is not in canonical format (it must end with a .)")
errStringTooLong = errors.New("character string exceeds maximum length (255)")
errCompressedSRV = errors.New("compressed name in SRV resource data")
)
// Internal constants.
@ -359,6 +360,8 @@ func (m *Header) GoString() string {
"Truncated: " + printBool(m.Truncated) + ", " +
"RecursionDesired: " + printBool(m.RecursionDesired) + ", " +
"RecursionAvailable: " + printBool(m.RecursionAvailable) + ", " +
"AuthenticData: " + printBool(m.AuthenticData) + ", " +
"CheckingDisabled: " + printBool(m.CheckingDisabled) + ", " +
"RCode: " + m.RCode.GoString() + "}"
}
@ -488,7 +491,7 @@ func (r *Resource) GoString() string {
// A ResourceBody is a DNS resource record minus the header.
type ResourceBody interface {
// pack packs a Resource except for its header.
pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error)
pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error)
// realType returns the actual type of the Resource. This is used to
// fill in the header Type field.
@ -499,7 +502,7 @@ type ResourceBody interface {
}
// pack appends the wire format of the Resource to msg.
func (r *Resource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (r *Resource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
if r.Body == nil {
return msg, errNilResouceBody
}
@ -525,22 +528,26 @@ func (r *Resource) pack(msg []byte, compression map[string]int, compressionOff i
// When parsing is started, the Header is parsed. Next, each Question can be
// either parsed or skipped. Alternatively, all Questions can be skipped at
// once. When all Questions have been parsed, attempting to parse Questions
// will return (nil, nil) and attempting to skip Questions will return
// (true, nil). After all Questions have been either parsed or skipped, all
// will return the [ErrSectionDone] error.
// After all Questions have been either parsed or skipped, all
// Answers, Authorities and Additionals can be either parsed or skipped in the
// same way, and each type of Resource must be fully parsed or skipped before
// proceeding to the next type of Resource.
//
// Parser is safe to copy to preserve the parsing state.
//
// Note that there is no requirement to fully skip or parse the message.
type Parser struct {
msg []byte
header header
section section
off int
index int
resHeaderValid bool
resHeader ResourceHeader
section section
off int
index int
resHeaderValid bool
resHeaderOffset int
resHeaderType Type
resHeaderLength uint16
}
// Start parses the header and enables the parsing of Questions.
@ -591,8 +598,9 @@ func (p *Parser) resource(sec section) (Resource, error) {
func (p *Parser) resourceHeader(sec section) (ResourceHeader, error) {
if p.resHeaderValid {
return p.resHeader, nil
p.off = p.resHeaderOffset
}
if err := p.checkAdvance(sec); err != nil {
return ResourceHeader{}, err
}
@ -602,14 +610,16 @@ func (p *Parser) resourceHeader(sec section) (ResourceHeader, error) {
return ResourceHeader{}, err
}
p.resHeaderValid = true
p.resHeader = hdr
p.resHeaderOffset = p.off
p.resHeaderType = hdr.Type
p.resHeaderLength = hdr.Length
p.off = off
return hdr, nil
}
func (p *Parser) skipResource(sec section) error {
if p.resHeaderValid {
newOff := p.off + int(p.resHeader.Length)
if p.resHeaderValid && p.section == sec {
newOff := p.off + int(p.resHeaderLength)
if newOff > len(p.msg) {
return errResourceLen
}
@ -740,6 +750,9 @@ func (p *Parser) AllAnswers() ([]Resource, error) {
}
// SkipAnswer skips a single Answer Resource.
//
// It does not perform a complete validation of the resource header, which means
// it may return a nil error when the [AnswerHeader] would actually return an error.
func (p *Parser) SkipAnswer() error {
return p.skipResource(sectionAnswers)
}
@ -790,6 +803,9 @@ func (p *Parser) AllAuthorities() ([]Resource, error) {
}
// SkipAuthority skips a single Authority Resource.
//
// It does not perform a complete validation of the resource header, which means
// it may return a nil error when the [AuthorityHeader] would actually return an error.
func (p *Parser) SkipAuthority() error {
return p.skipResource(sectionAuthorities)
}
@ -840,6 +856,9 @@ func (p *Parser) AllAdditionals() ([]Resource, error) {
}
// SkipAdditional skips a single Additional Resource.
//
// It does not perform a complete validation of the resource header, which means
// it may return a nil error when the [AdditionalHeader] would actually return an error.
func (p *Parser) SkipAdditional() error {
return p.skipResource(sectionAdditionals)
}
@ -860,14 +879,14 @@ func (p *Parser) SkipAllAdditionals() error {
// One of the XXXHeader methods must have been called before calling this
// method.
func (p *Parser) CNAMEResource() (CNAMEResource, error) {
if !p.resHeaderValid || p.resHeader.Type != TypeCNAME {
if !p.resHeaderValid || p.resHeaderType != TypeCNAME {
return CNAMEResource{}, ErrNotStarted
}
r, err := unpackCNAMEResource(p.msg, p.off)
if err != nil {
return CNAMEResource{}, err
}
p.off += int(p.resHeader.Length)
p.off += int(p.resHeaderLength)
p.resHeaderValid = false
p.index++
return r, nil
@ -878,14 +897,14 @@ func (p *Parser) CNAMEResource() (CNAMEResource, error) {
// One of the XXXHeader methods must have been called before calling this
// method.
func (p *Parser) MXResource() (MXResource, error) {
if !p.resHeaderValid || p.resHeader.Type != TypeMX {
if !p.resHeaderValid || p.resHeaderType != TypeMX {
return MXResource{}, ErrNotStarted
}
r, err := unpackMXResource(p.msg, p.off)
if err != nil {
return MXResource{}, err
}
p.off += int(p.resHeader.Length)
p.off += int(p.resHeaderLength)
p.resHeaderValid = false
p.index++
return r, nil
@ -896,14 +915,14 @@ func (p *Parser) MXResource() (MXResource, error) {
// One of the XXXHeader methods must have been called before calling this
// method.
func (p *Parser) NSResource() (NSResource, error) {
if !p.resHeaderValid || p.resHeader.Type != TypeNS {
if !p.resHeaderValid || p.resHeaderType != TypeNS {
return NSResource{}, ErrNotStarted
}
r, err := unpackNSResource(p.msg, p.off)
if err != nil {
return NSResource{}, err
}
p.off += int(p.resHeader.Length)
p.off += int(p.resHeaderLength)
p.resHeaderValid = false
p.index++
return r, nil
@ -914,14 +933,14 @@ func (p *Parser) NSResource() (NSResource, error) {
// One of the XXXHeader methods must have been called before calling this
// method.
func (p *Parser) PTRResource() (PTRResource, error) {
if !p.resHeaderValid || p.resHeader.Type != TypePTR {
if !p.resHeaderValid || p.resHeaderType != TypePTR {
return PTRResource{}, ErrNotStarted
}
r, err := unpackPTRResource(p.msg, p.off)
if err != nil {
return PTRResource{}, err
}
p.off += int(p.resHeader.Length)
p.off += int(p.resHeaderLength)
p.resHeaderValid = false
p.index++
return r, nil
@ -932,14 +951,14 @@ func (p *Parser) PTRResource() (PTRResource, error) {
// One of the XXXHeader methods must have been called before calling this
// method.
func (p *Parser) SOAResource() (SOAResource, error) {
if !p.resHeaderValid || p.resHeader.Type != TypeSOA {
if !p.resHeaderValid || p.resHeaderType != TypeSOA {
return SOAResource{}, ErrNotStarted
}
r, err := unpackSOAResource(p.msg, p.off)
if err != nil {
return SOAResource{}, err
}
p.off += int(p.resHeader.Length)
p.off += int(p.resHeaderLength)
p.resHeaderValid = false
p.index++
return r, nil
@ -950,14 +969,14 @@ func (p *Parser) SOAResource() (SOAResource, error) {
// One of the XXXHeader methods must have been called before calling this
// method.
func (p *Parser) TXTResource() (TXTResource, error) {
if !p.resHeaderValid || p.resHeader.Type != TypeTXT {
if !p.resHeaderValid || p.resHeaderType != TypeTXT {
return TXTResource{}, ErrNotStarted
}
r, err := unpackTXTResource(p.msg, p.off, p.resHeader.Length)
r, err := unpackTXTResource(p.msg, p.off, p.resHeaderLength)
if err != nil {
return TXTResource{}, err
}
p.off += int(p.resHeader.Length)
p.off += int(p.resHeaderLength)
p.resHeaderValid = false
p.index++
return r, nil
@ -968,14 +987,14 @@ func (p *Parser) TXTResource() (TXTResource, error) {
// One of the XXXHeader methods must have been called before calling this
// method.
func (p *Parser) SRVResource() (SRVResource, error) {
if !p.resHeaderValid || p.resHeader.Type != TypeSRV {
if !p.resHeaderValid || p.resHeaderType != TypeSRV {
return SRVResource{}, ErrNotStarted
}
r, err := unpackSRVResource(p.msg, p.off)
if err != nil {
return SRVResource{}, err
}
p.off += int(p.resHeader.Length)
p.off += int(p.resHeaderLength)
p.resHeaderValid = false
p.index++
return r, nil
@ -986,14 +1005,14 @@ func (p *Parser) SRVResource() (SRVResource, error) {
// One of the XXXHeader methods must have been called before calling this
// method.
func (p *Parser) AResource() (AResource, error) {
if !p.resHeaderValid || p.resHeader.Type != TypeA {
if !p.resHeaderValid || p.resHeaderType != TypeA {
return AResource{}, ErrNotStarted
}
r, err := unpackAResource(p.msg, p.off)
if err != nil {
return AResource{}, err
}
p.off += int(p.resHeader.Length)
p.off += int(p.resHeaderLength)
p.resHeaderValid = false
p.index++
return r, nil
@ -1004,14 +1023,14 @@ func (p *Parser) AResource() (AResource, error) {
// One of the XXXHeader methods must have been called before calling this
// method.
func (p *Parser) AAAAResource() (AAAAResource, error) {
if !p.resHeaderValid || p.resHeader.Type != TypeAAAA {
if !p.resHeaderValid || p.resHeaderType != TypeAAAA {
return AAAAResource{}, ErrNotStarted
}
r, err := unpackAAAAResource(p.msg, p.off)
if err != nil {
return AAAAResource{}, err
}
p.off += int(p.resHeader.Length)
p.off += int(p.resHeaderLength)
p.resHeaderValid = false
p.index++
return r, nil
@ -1022,14 +1041,14 @@ func (p *Parser) AAAAResource() (AAAAResource, error) {
// One of the XXXHeader methods must have been called before calling this
// method.
func (p *Parser) OPTResource() (OPTResource, error) {
if !p.resHeaderValid || p.resHeader.Type != TypeOPT {
if !p.resHeaderValid || p.resHeaderType != TypeOPT {
return OPTResource{}, ErrNotStarted
}
r, err := unpackOPTResource(p.msg, p.off, p.resHeader.Length)
r, err := unpackOPTResource(p.msg, p.off, p.resHeaderLength)
if err != nil {
return OPTResource{}, err
}
p.off += int(p.resHeader.Length)
p.off += int(p.resHeaderLength)
p.resHeaderValid = false
p.index++
return r, nil
@ -1043,11 +1062,11 @@ func (p *Parser) UnknownResource() (UnknownResource, error) {
if !p.resHeaderValid {
return UnknownResource{}, ErrNotStarted
}
r, err := unpackUnknownResource(p.resHeader.Type, p.msg, p.off, p.resHeader.Length)
r, err := unpackUnknownResource(p.resHeaderType, p.msg, p.off, p.resHeaderLength)
if err != nil {
return UnknownResource{}, err
}
p.off += int(p.resHeader.Length)
p.off += int(p.resHeaderLength)
p.resHeaderValid = false
p.index++
return r, nil
@ -1118,7 +1137,7 @@ func (m *Message) AppendPack(b []byte) ([]byte, error) {
// DNS messages can be a maximum of 512 bytes long. Without compression,
// many DNS response messages are over this limit, so enabling
// compression will help ensure compliance.
compression := map[string]int{}
compression := map[string]uint16{}
for i := range m.Questions {
var err error
@ -1209,7 +1228,7 @@ type Builder struct {
// compression is a mapping from name suffixes to their starting index
// in msg.
compression map[string]int
compression map[string]uint16
}
// NewBuilder creates a new builder with compression disabled.
@ -1246,7 +1265,7 @@ func NewBuilder(buf []byte, h Header) Builder {
//
// Compression should be enabled before any sections are added for best results.
func (b *Builder) EnableCompression() {
b.compression = map[string]int{}
b.compression = map[string]uint16{}
}
func (b *Builder) startCheck(s section) error {
@ -1662,7 +1681,7 @@ func (h *ResourceHeader) GoString() string {
// pack appends the wire format of the ResourceHeader to oldMsg.
//
// lenOff is the offset in msg where the Length field was packed.
func (h *ResourceHeader) pack(oldMsg []byte, compression map[string]int, compressionOff int) (msg []byte, lenOff int, err error) {
func (h *ResourceHeader) pack(oldMsg []byte, compression map[string]uint16, compressionOff int) (msg []byte, lenOff int, err error) {
msg = oldMsg
if msg, err = h.Name.pack(msg, compression, compressionOff); err != nil {
return oldMsg, 0, &nestedError{"Name", err}
@ -1728,7 +1747,7 @@ const (
//
// The provided extRCode must be an extended RCode.
func (h *ResourceHeader) SetEDNS0(udpPayloadLen int, extRCode RCode, dnssecOK bool) error {
h.Name = Name{Data: [nameLen]byte{'.'}, Length: 1} // RFC 6891 section 6.1.2
h.Name = Name{Data: [255]byte{'.'}, Length: 1} // RFC 6891 section 6.1.2
h.Type = TypeOPT
h.Class = Class(udpPayloadLen)
h.TTL = uint32(extRCode) >> 4 << 24
@ -1888,21 +1907,21 @@ func unpackBytes(msg []byte, off int, field []byte) (int, error) {
return newOff, nil
}
const nameLen = 255
const nonEncodedNameMax = 254
// A Name is a non-encoded domain name. It is used instead of strings to avoid
// A Name is a non-encoded and non-escaped domain name. It is used instead of strings to avoid
// allocations.
type Name struct {
Data [nameLen]byte // 255 bytes
Data [255]byte
Length uint8
}
// NewName creates a new Name from a string.
func NewName(name string) (Name, error) {
if len(name) > nameLen {
n := Name{Length: uint8(len(name))}
if len(name) > len(n.Data) {
return Name{}, errCalcLen
}
n := Name{Length: uint8(len(name))}
copy(n.Data[:], name)
return n, nil
}
@ -1917,6 +1936,8 @@ func MustNewName(name string) Name {
}
// String implements fmt.Stringer.String.
//
// Note: characters inside the labels are not escaped in any way.
func (n Name) String() string {
return string(n.Data[:n.Length])
}
@ -1933,9 +1954,13 @@ func (n *Name) GoString() string {
//
// The compression map will be updated with new domain suffixes. If compression
// is nil, compression will not be used.
func (n *Name) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (n *Name) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
oldMsg := msg
if n.Length > nonEncodedNameMax {
return nil, errNameTooLong
}
// Add a trailing dot to canonicalize name.
if n.Length == 0 || n.Data[n.Length-1] != '.' {
return oldMsg, errNonCanonicalName
@ -1946,6 +1971,8 @@ func (n *Name) pack(msg []byte, compression map[string]int, compressionOff int)
return append(msg, 0), nil
}
var nameAsStr string
// Emit sequence of counted strings, chopping at dots.
for i, begin := 0, 0; i < int(n.Length); i++ {
// Check for the end of the segment.
@ -1976,16 +2003,22 @@ func (n *Name) pack(msg []byte, compression map[string]int, compressionOff int)
// segment. A pointer is two bytes with the two most significant
// bits set to 1 to indicate that it is a pointer.
if (i == 0 || n.Data[i-1] == '.') && compression != nil {
if ptr, ok := compression[string(n.Data[i:])]; ok {
if ptr, ok := compression[string(n.Data[i:n.Length])]; ok {
// Hit. Emit a pointer instead of the rest of
// the domain.
return append(msg, byte(ptr>>8|0xC0), byte(ptr)), nil
}
// Miss. Add the suffix to the compression table if the
// offset can be stored in the available 14 bytes.
if len(msg) <= int(^uint16(0)>>2) {
compression[string(n.Data[i:])] = len(msg) - compressionOff
// offset can be stored in the available 14 bits.
newPtr := len(msg) - compressionOff
if newPtr <= int(^uint16(0)>>2) {
if nameAsStr == "" {
// allocate n.Data on the heap once, to avoid allocating it
// multiple times (for next labels).
nameAsStr = string(n.Data[:n.Length])
}
compression[nameAsStr[i:]] = uint16(newPtr)
}
}
}
@ -1994,10 +2027,6 @@ func (n *Name) pack(msg []byte, compression map[string]int, compressionOff int)
// unpack unpacks a domain name.
func (n *Name) unpack(msg []byte, off int) (int, error) {
return n.unpackCompressed(msg, off, true /* allowCompression */)
}
func (n *Name) unpackCompressed(msg []byte, off int, allowCompression bool) (int, error) {
// currOff is the current working offset.
currOff := off
@ -2029,13 +2058,19 @@ Loop:
if endOff > len(msg) {
return off, errCalcLen
}
// Reject names containing dots.
// See issue golang/go#56246
for _, v := range msg[currOff:endOff] {
if v == '.' {
return off, errInvalidName
}
}
name = append(name, msg[currOff:endOff]...)
name = append(name, '.')
currOff = endOff
case 0xC0: // Pointer
if !allowCompression {
return off, errCompressedSRV
}
if currOff >= len(msg) {
return off, errInvalidPtr
}
@ -2057,8 +2092,8 @@ Loop:
if len(name) == 0 {
name = append(name, '.')
}
if len(name) > len(n.Data) {
return off, errCalcLen
if len(name) > nonEncodedNameMax {
return off, errNameTooLong
}
n.Length = uint8(len(name))
if ptr == 0 {
@ -2116,7 +2151,7 @@ type Question struct {
}
// pack appends the wire format of the Question to msg.
func (q *Question) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (q *Question) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
msg, err := q.Name.pack(msg, compression, compressionOff)
if err != nil {
return msg, &nestedError{"Name", err}
@ -2212,7 +2247,7 @@ func (r *CNAMEResource) realType() Type {
}
// pack appends the wire format of the CNAMEResource to msg.
func (r *CNAMEResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (r *CNAMEResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
return r.CNAME.pack(msg, compression, compressionOff)
}
@ -2240,7 +2275,7 @@ func (r *MXResource) realType() Type {
}
// pack appends the wire format of the MXResource to msg.
func (r *MXResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (r *MXResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
oldMsg := msg
msg = packUint16(msg, r.Pref)
msg, err := r.MX.pack(msg, compression, compressionOff)
@ -2279,7 +2314,7 @@ func (r *NSResource) realType() Type {
}
// pack appends the wire format of the NSResource to msg.
func (r *NSResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (r *NSResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
return r.NS.pack(msg, compression, compressionOff)
}
@ -2306,7 +2341,7 @@ func (r *PTRResource) realType() Type {
}
// pack appends the wire format of the PTRResource to msg.
func (r *PTRResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (r *PTRResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
return r.PTR.pack(msg, compression, compressionOff)
}
@ -2343,7 +2378,7 @@ func (r *SOAResource) realType() Type {
}
// pack appends the wire format of the SOAResource to msg.
func (r *SOAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (r *SOAResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
oldMsg := msg
msg, err := r.NS.pack(msg, compression, compressionOff)
if err != nil {
@ -2415,7 +2450,7 @@ func (r *TXTResource) realType() Type {
}
// pack appends the wire format of the TXTResource to msg.
func (r *TXTResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (r *TXTResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
oldMsg := msg
for _, s := range r.TXT {
var err error
@ -2471,7 +2506,7 @@ func (r *SRVResource) realType() Type {
}
// pack appends the wire format of the SRVResource to msg.
func (r *SRVResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (r *SRVResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
oldMsg := msg
msg = packUint16(msg, r.Priority)
msg = packUint16(msg, r.Weight)
@ -2506,7 +2541,7 @@ func unpackSRVResource(msg []byte, off int) (SRVResource, error) {
return SRVResource{}, &nestedError{"Port", err}
}
var target Name
if _, err := target.unpackCompressed(msg, off, false /* allowCompression */); err != nil {
if _, err := target.unpack(msg, off); err != nil {
return SRVResource{}, &nestedError{"Target", err}
}
return SRVResource{priority, weight, port, target}, nil
@ -2522,7 +2557,7 @@ func (r *AResource) realType() Type {
}
// pack appends the wire format of the AResource to msg.
func (r *AResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (r *AResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
return packBytes(msg, r.A[:]), nil
}
@ -2556,7 +2591,7 @@ func (r *AAAAResource) GoString() string {
}
// pack appends the wire format of the AAAAResource to msg.
func (r *AAAAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (r *AAAAResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
return packBytes(msg, r.AAAA[:]), nil
}
@ -2596,7 +2631,7 @@ func (r *OPTResource) realType() Type {
return TypeOPT
}
func (r *OPTResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (r *OPTResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
for _, opt := range r.Options {
msg = packUint16(msg, opt.Code)
l := uint16(len(opt.Data))
@ -2654,7 +2689,7 @@ func (r *UnknownResource) realType() Type {
}
// pack appends the wire format of the UnknownResource to msg.
func (r *UnknownResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
func (r *UnknownResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) {
return packBytes(msg, r.Data[:]), nil
}

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || netbsd || openbsd
// +build aix darwin dragonfly freebsd netbsd openbsd
package socket

@ -3,8 +3,6 @@
// license that can be found in the LICENSE file.
//go:build (arm || mips || mipsle || 386 || ppc) && linux
// +build arm mips mipsle 386 ppc
// +build linux
package socket

@ -3,8 +3,6 @@
// license that can be found in the LICENSE file.
//go:build (arm64 || amd64 || loong64 || ppc64 || ppc64le || mips64 || mips64le || riscv64 || s390x) && linux
// +build arm64 amd64 loong64 ppc64 ppc64le mips64 mips64le riscv64 s390x
// +build linux
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build amd64 && solaris
// +build amd64,solaris
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !zos
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!zos
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || windows || zos
// +build aix windows zos
package socket

@ -3,6 +3,5 @@
// license that can be found in the LICENSE file.
//go:build darwin && go1.12
// +build darwin,go1.12
// This exists solely so we can linkname in symbols from syscall.

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
package socket

@ -3,8 +3,6 @@
// license that can be found in the LICENSE file.
//go:build (arm || mips || mipsle || 386 || ppc) && (darwin || dragonfly || freebsd || linux || netbsd || openbsd)
// +build arm mips mipsle 386 ppc
// +build darwin dragonfly freebsd linux netbsd openbsd
package socket

@ -3,8 +3,6 @@
// license that can be found in the LICENSE file.
//go:build (arm64 || amd64 || loong64 || ppc64 || ppc64le || mips64 || mips64le || riscv64 || s390x) && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || zos)
// +build arm64 amd64 loong64 ppc64 ppc64le mips64 mips64le riscv64 s390x
// +build aix darwin dragonfly freebsd linux netbsd openbsd zos
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build amd64 && solaris
// +build amd64,solaris
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !zos
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!zos
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !aix && !linux && !netbsd
// +build !aix,!linux,!netbsd
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || linux || netbsd
// +build aix linux netbsd
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || netbsd || openbsd
// +build aix darwin dragonfly freebsd netbsd openbsd
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || netbsd
// +build aix darwin dragonfly freebsd netbsd
package socket

@ -3,8 +3,6 @@
// license that can be found in the LICENSE file.
//go:build (arm || mips || mipsle || 386 || ppc) && linux
// +build arm mips mipsle 386 ppc
// +build linux
package socket

@ -3,8 +3,6 @@
// license that can be found in the LICENSE file.
//go:build (arm64 || amd64 || loong64 || ppc64 || ppc64le || mips64 || mips64le || riscv64 || s390x) && linux
// +build arm64 amd64 loong64 ppc64 ppc64le mips64 mips64le riscv64 s390x
// +build linux
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build amd64 && solaris
// +build amd64,solaris
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !zos
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!zos
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build s390x && zos
// +build s390x,zos
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !race
// +build !race
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build race
// +build race
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build linux
// +build linux
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows || zos
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris windows zos
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !linux
// +build !linux
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows && !zos
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows,!zos
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || openbsd || solaris
// +build aix darwin dragonfly freebsd openbsd solaris
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build linux && !s390x && !386
// +build linux,!s390x,!386
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build loong64
// +build loong64
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build riscv64
// +build riscv64
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows || zos
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris windows zos
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows && !zos
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows,!zos
package socket

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package socket

@ -3,7 +3,6 @@
// Added for go1.11 compatibility
//go:build aix
// +build aix
package socket

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save