package srt import ( "fmt" "net/url" "strconv" "time" ) const ( UDP_HEADER_SIZE = 28 SRT_HEADER_SIZE = 16 MIN_MSS_SIZE = 76 MAX_MSS_SIZE = 1500 MIN_PAYLOAD_SIZE = MIN_MSS_SIZE - UDP_HEADER_SIZE - SRT_HEADER_SIZE MAX_PAYLOAD_SIZE = MAX_MSS_SIZE - UDP_HEADER_SIZE - SRT_HEADER_SIZE MIN_PASSPHRASE_SIZE = 10 MAX_PASSPHRASE_SIZE = 79 MAX_STREAMID_SIZE = 512 SRT_VERSION = 0x010401 ) // Config is the configuration for a SRT connection type Config struct { // Type of congestion control. 'live' or 'file' // SRTO_CONGESTION Congestion string // Connection timeout. // SRTO_CONNTIMEO ConnectionTimeout time.Duration // Enable drift tracer. // SRTO_DRIFTTRACER DriftTracer bool // Reject connection if parties set different passphrase. // SRTO_ENFORCEDENCRYPTION EnforcedEncryption bool // Flow control window size. Packets. // SRTO_FC FC uint32 // Accept group connections. // SRTO_GROUPCONNECT GroupConnect bool // Group stability timeout. // SRTO_GROUPSTABTIMEO GroupStabilityTimeout time.Duration // Input bandwidth. Bytes. // SRTO_INPUTBW InputBW int64 // IP socket type of service // SRTO_IPTOS IPTOS int // Defines IP socket "time to live" option. // SRTO_IPTTL IPTTL int // Allow only IPv6. // SRTO_IPV6ONLY IPv6Only int // Duration of Stream Encryption key switchover. Packets. // SRTO_KMPREANNOUNCE KMPreAnnounce uint64 // Stream encryption key refresh rate. Packets. // SRTO_KMREFRESHRATE KMRefreshRate uint64 // Defines the maximum accepted transmission latency. // SRTO_LATENCY Latency time.Duration // Packet reorder tolerance. // SRTO_LOSSMAXTTL LossMaxTTL uint32 // Bandwidth limit in bytes/s. // SRTO_MAXBW MaxBW int64 // Enable SRT message mode. // SRTO_MESSAGEAPI MessageAPI bool // Minimum input bandwidth // This option is effective only if both SRTO_MAXBW and SRTO_INPUTBW are set to 0. It controls the minimum allowed value of the input bitrate estimate. // SRTO_MININPUTBW MinInputBW int64 // Minimum SRT library version of a peer. // SRTO_MINVERSION MinVersion uint32 // MTU size // SRTO_MSS MSS uint32 // Enable periodic NAK reports // SRTO_NAKREPORT NAKReport bool // Limit bandwidth overhead, percents // SRTO_OHEADBW OverheadBW int64 // Set up the packet filter. // SRTO_PACKETFILTER PacketFilter string // Password for the encrypted transmission. // SRTO_PASSPHRASE Passphrase string // Maximum payload size. Bytes. // SRTO_PAYLOADSIZE PayloadSize uint32 // Crypto key length in bytes. // SRTO_PBKEYLEN PBKeylen int // Peer idle timeout. // SRTO_PEERIDLETIMEO PeerIdleTimeout time.Duration // Minimum receiver latency to be requested by sender. // SRTO_PEERLATENCY PeerLatency time.Duration // Receiver buffer size. Bytes. // SRTO_RCVBUF ReceiverBufferSize uint32 // Receiver-side latency. // SRTO_RCVLATENCY ReceiverLatency time.Duration // Sender buffer size. Bytes. // SRTO_SNDBUF SendBufferSize uint32 // Sender's delay before dropping packets. // SRTO_SNDDROPDELAY SendDropDelay time.Duration // Stream ID (settable in caller mode only, visible on the listener peer) // SRTO_STREAMID StreamId string // Drop too late packets. // SRTO_TLPKTDROP TooLatePacketDrop bool // Transmission type. 'live' or 'file'. // SRTO_TRANSTYPE TransmissionType string // Timestamp-based packet delivery mode. // SRTO_TSBPDMODE TSBPDMode bool // An implementation of the Logger interface Logger Logger } // DefaultConfig is the default configuration for a SRT connection // if no individual configuration has been provided. var defaultConfig Config = Config{ Congestion: "live", ConnectionTimeout: 3 * time.Second, DriftTracer: true, EnforcedEncryption: true, FC: 25600, GroupConnect: false, GroupStabilityTimeout: 0, InputBW: 0, IPTOS: 0, IPTTL: 0, IPv6Only: -1, KMPreAnnounce: 1 << 12, KMRefreshRate: 1 << 24, Latency: -1, LossMaxTTL: 0, MaxBW: -1, MessageAPI: false, MinVersion: SRT_VERSION, MSS: MAX_MSS_SIZE, NAKReport: true, OverheadBW: 25, PacketFilter: "", Passphrase: "", PayloadSize: MAX_PAYLOAD_SIZE, PBKeylen: 16, PeerIdleTimeout: 2 * time.Second, PeerLatency: 120 * time.Millisecond, ReceiverBufferSize: 0, ReceiverLatency: 120 * time.Millisecond, SendBufferSize: 0, SendDropDelay: 1 * time.Second, StreamId: "", TooLatePacketDrop: true, TransmissionType: "live", TSBPDMode: true, } // DefaultConfig returns the default configuration for Dial and Listen. func DefaultConfig() Config { return defaultConfig } // UnmarshalURL takes a SRT URL and parses out the configuration. A SRT URL is // srt://[host]:[port]?[key1]=[value1]&[key2]=[value2]... func (c *Config) UnmarshalURL(addr string) (string, error) { u, err := url.Parse(addr) if err != nil { return "", err } if u.Scheme != "srt" { return "", fmt.Errorf("the URL doesn't seem to be an srt:// URL") } return u.Host, c.UnmarshalQuery(u.RawQuery) } // UnmarshalQuery parses a query string and interprets it as a configuration // for a SRT connection. The key in each key/value pair corresponds to the // respective field in the Config type, but with only lower case letters. Bool // values can be represented as "true"/"false", "on"/"off", "yes"/"no", or "0"/"1". func (c *Config) UnmarshalQuery(query string) error { v, err := url.ParseQuery(query) if err != nil { return err } // https://github.com/Haivision/srt/blob/master/docs/apps/srt-live-transmit.md if s := v.Get("congestion"); len(s) != 0 { c.Congestion = s } if s := v.Get("conntimeo"); len(s) != 0 { if d, err := strconv.Atoi(s); err == nil { c.ConnectionTimeout = time.Duration(d) * time.Millisecond } } if s := v.Get("drifttracer"); len(s) != 0 { switch s { case "yes", "on", "true", "1": c.DriftTracer = true case "no", "off", "false", "0": c.DriftTracer = false } } if s := v.Get("enforcedencryption"); len(s) != 0 { switch s { case "yes", "on", "true", "1": c.EnforcedEncryption = true case "no", "off", "false", "0": c.EnforcedEncryption = false } } if s := v.Get("fc"); len(s) != 0 { if d, err := strconv.ParseUint(s, 10, 32); err == nil { c.FC = uint32(d) } } if s := v.Get("groupconnect"); len(s) != 0 { switch s { case "yes", "on", "true", "1": c.GroupConnect = true case "no", "off", "false", "0": c.GroupConnect = false } } if s := v.Get("groupstabtimeo"); len(s) != 0 { if d, err := strconv.Atoi(s); err == nil { c.GroupStabilityTimeout = time.Duration(d) * time.Millisecond } } if s := v.Get("inputbw"); len(s) != 0 { if d, err := strconv.ParseInt(s, 10, 64); err == nil { c.InputBW = d } } if s := v.Get("iptos"); len(s) != 0 { if d, err := strconv.Atoi(s); err == nil { c.IPTOS = d } } if s := v.Get("ipttl"); len(s) != 0 { if d, err := strconv.Atoi(s); err == nil { c.IPTTL = d } } if s := v.Get("ipv6only"); len(s) != 0 { if d, err := strconv.Atoi(s); err == nil { c.IPv6Only = d } } if s := v.Get("kmpreannounce"); len(s) != 0 { if d, err := strconv.ParseUint(s, 10, 64); err == nil { c.KMPreAnnounce = d } } if s := v.Get("kmrefreshrate"); len(s) != 0 { if d, err := strconv.ParseUint(s, 10, 64); err == nil { c.KMRefreshRate = d } } if s := v.Get("latency"); len(s) != 0 { if d, err := strconv.Atoi(s); err == nil { c.Latency = time.Duration(d) * time.Millisecond } } if s := v.Get("lossmaxttl"); len(s) != 0 { if d, err := strconv.ParseUint(s, 10, 32); err == nil { c.LossMaxTTL = uint32(d) } } if s := v.Get("maxbw"); len(s) != 0 { if d, err := strconv.ParseInt(s, 10, 64); err == nil { c.MaxBW = d } } if s := v.Get("mininputbw"); len(s) != 0 { if d, err := strconv.ParseInt(s, 10, 64); err == nil { c.MinInputBW = d } } if s := v.Get("messageapi"); len(s) != 0 { switch s { case "yes", "on", "true", "1": c.MessageAPI = true case "no", "off", "false", "0": c.MessageAPI = false } } // minversion is ignored if s := v.Get("mss"); len(s) != 0 { if d, err := strconv.ParseUint(s, 10, 32); err == nil { c.MSS = uint32(d) } } if s := v.Get("nakreport"); len(s) != 0 { switch s { case "yes", "on", "true", "1": c.NAKReport = true case "no", "off", "false", "0": c.NAKReport = false } } if s := v.Get("oheadbw"); len(s) != 0 { if d, err := strconv.ParseInt(s, 10, 64); err == nil { c.OverheadBW = d } } if s := v.Get("packetfilter"); len(s) != 0 { c.PacketFilter = s } if s := v.Get("passphrase"); len(s) != 0 { c.Passphrase = s } if s := v.Get("payloadsize"); len(s) != 0 { if d, err := strconv.ParseUint(s, 10, 32); err == nil { c.PayloadSize = uint32(d) } } if s := v.Get("pbkeylen"); len(s) != 0 { if d, err := strconv.Atoi(s); err == nil { c.PBKeylen = d } } if s := v.Get("peeridletimeo"); len(s) != 0 { if d, err := strconv.Atoi(s); err == nil { c.PeerIdleTimeout = time.Duration(d) * time.Millisecond } } if s := v.Get("peerlatency"); len(s) != 0 { if d, err := strconv.Atoi(s); err == nil { c.PeerLatency = time.Duration(d) * time.Millisecond } } if s := v.Get("rcvbuf"); len(s) != 0 { if d, err := strconv.ParseUint(s, 10, 32); err == nil { c.ReceiverBufferSize = uint32(d) } } if s := v.Get("rcvlatency"); len(s) != 0 { if d, err := strconv.Atoi(s); err == nil { c.ReceiverLatency = time.Duration(d) * time.Millisecond } } // retransmitalgo not implemented (there's only one) if s := v.Get("sndbuf"); len(s) != 0 { if d, err := strconv.ParseUint(s, 10, 32); err == nil { c.SendBufferSize = uint32(d) } } if s := v.Get("snddropdelay"); len(s) != 0 { if d, err := strconv.Atoi(s); err == nil { c.SendDropDelay = time.Duration(d) * time.Millisecond } } if s := v.Get("streamid"); len(s) != 0 { c.StreamId = s } if s := v.Get("tlpktdrop"); len(s) != 0 { switch s { case "yes", "on", "true", "1": c.TooLatePacketDrop = true case "no", "off", "false", "0": c.TooLatePacketDrop = false } } if s := v.Get("transtype"); len(s) != 0 { c.TransmissionType = s } if s := v.Get("tsbpdmode"); len(s) != 0 { switch s { case "yes", "on", "true", "1": c.TSBPDMode = true case "no", "off", "false", "0": c.TSBPDMode = false } } return nil } // MarshalURL returns the SRT URL for this config and the given host and port. func (c *Config) MarshalURL(host string, port uint) string { return "srt://" + host + ":" + strconv.FormatUint(uint64(port), 10) + "?" + c.MarshalQuery() } // MarshalQuery returns the corresponding query string for a configuration. func (c *Config) MarshalQuery() string { q := url.Values{} if c.Congestion != defaultConfig.Congestion { q.Set("congestion", c.Congestion) } if c.ConnectionTimeout != defaultConfig.ConnectionTimeout { q.Set("conntimeo", strconv.FormatInt(c.ConnectionTimeout.Milliseconds(), 10)) } if c.DriftTracer != defaultConfig.DriftTracer { q.Set("drifttracer", strconv.FormatBool(c.DriftTracer)) } if c.EnforcedEncryption != defaultConfig.EnforcedEncryption { q.Set("enforcedencryption", strconv.FormatBool(c.EnforcedEncryption)) } if c.FC != defaultConfig.FC { q.Set("fc", strconv.FormatUint(uint64(c.FC), 10)) } if c.GroupConnect != defaultConfig.GroupConnect { q.Set("groupconnect", strconv.FormatBool(c.GroupConnect)) } if c.GroupStabilityTimeout != defaultConfig.GroupStabilityTimeout { q.Set("groupstabtimeo", strconv.FormatInt(c.GroupStabilityTimeout.Milliseconds(), 10)) } if c.InputBW != defaultConfig.InputBW { q.Set("inputbw", strconv.FormatInt(c.InputBW, 10)) } if c.IPTOS != defaultConfig.IPTOS { q.Set("iptos", strconv.FormatInt(int64(c.IPTOS), 10)) } if c.IPTTL != defaultConfig.IPTTL { q.Set("ipttl", strconv.FormatInt(int64(c.IPTTL), 10)) } if c.IPv6Only != defaultConfig.IPv6Only { q.Set("ipv6only", strconv.FormatInt(int64(c.IPv6Only), 10)) } if len(c.Passphrase) != 0 { if c.KMPreAnnounce != defaultConfig.KMPreAnnounce { q.Set("kmpreannounce", strconv.FormatUint(c.KMPreAnnounce, 10)) } if c.KMRefreshRate != defaultConfig.KMRefreshRate { q.Set("kmrefreshrate", strconv.FormatUint(c.KMRefreshRate, 10)) } } if c.Latency != defaultConfig.Latency { q.Set("latency", strconv.FormatInt(c.Latency.Milliseconds(), 10)) } if c.LossMaxTTL != defaultConfig.LossMaxTTL { q.Set("lossmaxttl", strconv.FormatInt(int64(c.LossMaxTTL), 10)) } if c.MaxBW != defaultConfig.MaxBW { q.Set("maxbw", strconv.FormatInt(c.MaxBW, 10)) } if c.MinInputBW != defaultConfig.InputBW { q.Set("mininputbw", strconv.FormatInt(c.MinInputBW, 10)) } if c.MessageAPI != defaultConfig.MessageAPI { q.Set("messageapi", strconv.FormatBool(c.MessageAPI)) } if c.MSS != defaultConfig.MSS { q.Set("mss", strconv.FormatUint(uint64(c.MSS), 10)) } if c.NAKReport != defaultConfig.NAKReport { q.Set("nakreport", strconv.FormatBool(c.NAKReport)) } if c.OverheadBW != defaultConfig.OverheadBW { q.Set("oheadbw", strconv.FormatInt(c.OverheadBW, 10)) } if c.PacketFilter != defaultConfig.PacketFilter { q.Set("packetfilter", c.PacketFilter) } if len(c.Passphrase) != 0 { q.Set("passphrase", c.Passphrase) } if c.PayloadSize != defaultConfig.PayloadSize { q.Set("payloadsize", strconv.FormatUint(uint64(c.PayloadSize), 10)) } if c.PBKeylen != defaultConfig.PBKeylen { q.Set("pbkeylen", strconv.FormatInt(int64(c.PBKeylen), 10)) } if c.PeerIdleTimeout != defaultConfig.PeerIdleTimeout { q.Set("peeridletimeo", strconv.FormatInt(c.PeerIdleTimeout.Milliseconds(), 10)) } if c.PeerLatency != defaultConfig.PeerLatency { q.Set("peerlatency", strconv.FormatInt(c.PeerLatency.Milliseconds(), 10)) } if c.ReceiverBufferSize != defaultConfig.ReceiverBufferSize { q.Set("rcvbuf", strconv.FormatInt(int64(c.ReceiverBufferSize), 10)) } if c.ReceiverLatency != defaultConfig.ReceiverLatency { q.Set("rcvlatency", strconv.FormatInt(c.ReceiverLatency.Milliseconds(), 10)) } if c.SendBufferSize != defaultConfig.SendBufferSize { q.Set("sndbuf", strconv.FormatInt(int64(c.SendBufferSize), 10)) } if c.SendDropDelay != defaultConfig.SendDropDelay { q.Set("snddropdelay", strconv.FormatInt(c.SendDropDelay.Milliseconds(), 10)) } if len(c.StreamId) != 0 { q.Set("streamid", c.StreamId) } if c.TooLatePacketDrop != defaultConfig.TooLatePacketDrop { q.Set("tlpktdrop", strconv.FormatBool(c.TooLatePacketDrop)) } if c.TransmissionType != defaultConfig.TransmissionType { q.Set("transtype", c.TransmissionType) } if c.TSBPDMode != defaultConfig.TSBPDMode { q.Set("tsbpdmode", strconv.FormatBool(c.TSBPDMode)) } return q.Encode() } // Validate validates a configuration or returns an error if a field // has an invalid value. func (c Config) Validate() error { if c.TransmissionType != "live" { return fmt.Errorf("config: TransmissionType must be 'live'") } c.Congestion = "live" c.NAKReport = true c.TooLatePacketDrop = true c.TSBPDMode = true if c.Congestion != "live" { return fmt.Errorf("config: Congestion mode must be 'live'") } if c.ConnectionTimeout <= 0 { return fmt.Errorf("config: ConnectionTimeout must be greater than 0") } if c.GroupConnect { return fmt.Errorf("config: GroupConnect is not supported") } if c.IPTOS > 0 && c.IPTOS > 255 { return fmt.Errorf("config: IPTOS must be lower than 255") } if c.IPTTL > 0 && c.IPTTL > 255 { return fmt.Errorf("config: IPTTL must be between 1 and 255") } if c.IPv6Only > 0 { return fmt.Errorf("config: IPv6Only is not supported") } if c.KMRefreshRate != 0 { if c.KMPreAnnounce < 1 || c.KMPreAnnounce > c.KMRefreshRate/2 { return fmt.Errorf("config: KMPreAnnounce must be greater than 1 and smaller than KMRefreshRate/2") } } if c.Latency >= 0 { c.PeerLatency = c.Latency c.ReceiverLatency = c.Latency } if c.MinVersion != SRT_VERSION { return fmt.Errorf("config: MinVersion must be %#06x", SRT_VERSION) } if c.MSS < MIN_MSS_SIZE || c.MSS > MAX_MSS_SIZE { return fmt.Errorf("config: MSS must be between %d and %d (both inclusive)", MIN_MSS_SIZE, MAX_MSS_SIZE) } if !c.NAKReport { return fmt.Errorf("config: NAKReport must be enabled") } if c.OverheadBW < 10 || c.OverheadBW > 100 { return fmt.Errorf("config: OverheadBW must be between 10 and 100") } if len(c.PacketFilter) != 0 { return fmt.Errorf("config: PacketFilter are not supported") } if len(c.Passphrase) != 0 { if len(c.Passphrase) < MIN_PASSPHRASE_SIZE || len(c.Passphrase) > MAX_PASSPHRASE_SIZE { return fmt.Errorf("config: Passphrase must be between %d and %d bytes long", MIN_PASSPHRASE_SIZE, MAX_PASSPHRASE_SIZE) } } if c.PayloadSize < MIN_PAYLOAD_SIZE || c.PayloadSize > MAX_PAYLOAD_SIZE { return fmt.Errorf("config: PayloadSize must be between %d and %d (both inclusive)", MIN_PAYLOAD_SIZE, MAX_PAYLOAD_SIZE) } if c.PayloadSize > c.MSS-uint32(SRT_HEADER_SIZE+UDP_HEADER_SIZE) { return fmt.Errorf("config: PayloadSize must not be larger than %d (MSS - %d)", c.MSS-uint32(SRT_HEADER_SIZE+UDP_HEADER_SIZE), SRT_HEADER_SIZE-UDP_HEADER_SIZE) } if c.PBKeylen != 16 && c.PBKeylen != 24 && c.PBKeylen != 32 { return fmt.Errorf("config: PBKeylen must be 16, 24, or 32 bytes") } if c.PeerLatency < 0 { return fmt.Errorf("config: PeerLatency must be greater than 0") } if c.ReceiverLatency < 0 { return fmt.Errorf("config: ReceiverLatency must be greater than 0") } if c.SendDropDelay < 0 { return fmt.Errorf("config: SendDropDelay must be greater than 0") } if len(c.StreamId) > MAX_STREAMID_SIZE { return fmt.Errorf("config: StreamId must be shorter than or equal to %d bytes", MAX_STREAMID_SIZE) } if !c.TooLatePacketDrop { return fmt.Errorf("config: TooLatePacketDrop must be enabled") } if c.TransmissionType != "live" { return fmt.Errorf("config: TransmissionType must be 'live'") } if !c.TSBPDMode { return fmt.Errorf("config: TSBPDMode must be enabled") } return nil }