streaming

Media streaming and broadcast systems in Go
Log | Files | Refs | README | LICENSE

commit 2019fa0504b90a8b3609d4bb69731c916de2c272
parent 6c87f537549ca5125fbe7faa7cd4e9353e031e8f
Author: Oliver Lowe <o@olowe.co>
Date:   Sat, 10 Aug 2024 16:46:36 +1000

sdp: parse IP addresses as netip.Addr

Using Addr, we don't have to keep track of the address type ourselves.
We also get a guarantee that the values are actually valid IP
addresses.

Diffstat:
Msdp/encode.go | 6+++++-
Msdp/example_test.go | 8++++----
Msdp/sdp.go | 46+++++++++++++++++++++++++---------------------
Msdp/sdp_test.go | 20++++++++++----------
4 files changed, 44 insertions(+), 36 deletions(-)

diff --git a/sdp/encode.go b/sdp/encode.go @@ -11,7 +11,11 @@ func (s Session) String() string { if s.Origin.Username == "" { s.Origin.Username = NoUsername } - fmt.Fprintf(buf, "o=%s %d %d IN %s %s\n", s.Origin.Username, s.Origin.ID, s.Origin.Version, s.Origin.AddressType, s.Origin.Address) + ipv := "IP6" + if s.Origin.Address.Is4() { + ipv = "IP4" + } + fmt.Fprintf(buf, "o=%s %d %d IN %s %s\n", s.Origin.Username, s.Origin.ID, s.Origin.Version, ipv, s.Origin.Address) fmt.Fprintf(buf, "s=%s\n", s.Name) if s.Info != "" { diff --git a/sdp/example_test.go b/sdp/example_test.go @@ -2,6 +2,7 @@ package sdp_test import ( "fmt" + "net/netip" "github.com/untangledco/streaming/sdp" ) @@ -12,10 +13,9 @@ import ( func Example() { session := sdp.Session{ Origin: sdp.Origin{ - ID: 3930287268, // example only; use sdp.Now() - Version: 3930287268, // example only; use sdp.Now() - AddressType: "IP6", // or "IP4" - Address: "2001:db8::1", + ID: 3930287268, // example only; use sdp.Now() + Version: 3930287268, // example only; use sdp.Now() + Address: netip.MustParseAddr("2001:db8::1"), }, Name: "A call from me to you", } diff --git a/sdp/sdp.go b/sdp/sdp.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net/mail" + "net/netip" "net/url" "strconv" "strings" @@ -55,15 +56,8 @@ type Origin struct { // value is a timestamp from Now(). Version int - // AddressType describes the type of Address. This is almost - // always "IP4" or "IP6" for IPv4 and IPv6 respectively. - AddressType string // TODO(otl): only "IP4", "IP6" valid... new int type? - - // Address is the originating address of the session. This is - // almost always a literal IPv4 or IPv6 address such as - // "192.0.2.1" or "2001:db8::1". - // TODO(otl): is a hostname valid? if not, use netip.Addr, then can drop AddressType. - Address string + // Address is the originating address of the session. + Address netip.Addr } func ReadSession(rd io.Reader) (*Session, error) { @@ -101,8 +95,13 @@ func parseOrigin(line string) (Origin, error) { if fields[3] != "IN" { return o, fmt.Errorf("unknown network class %q", fields[3]) } - o.AddressType = fields[4] - o.Address = fields[5] + + // skip IP4/IP6 in fields[4]; netip handles the IP version for us. + addr, err := netip.ParseAddr(fields[5]) + if err != nil { + return o, fmt.Errorf("parse address: %w", err) + } + o.Address = addr return o, nil } @@ -311,16 +310,18 @@ func parseMedia(s string) (Media, error) { // ConnInfo represents connection information. type ConnInfo struct { - Type string // TODO(otl): only "IP4", "IP6" valid... new int type? - // TODO(otl): can this be a hostname? if not, maybe use netip.Addr - Address string // IPv4, IPv6 literal + Address netip.Addr // TODO(otl): what are these units? seconds? TTL uint8 // time to live Count int // number of addresses after Address } func (c *ConnInfo) String() string { - s := fmt.Sprintf("c=%s %s %s", "IN", c.Type, c.Address) + ipv := "IP6" + if c.Address.Is4() { + ipv = "IP4" + } + s := fmt.Sprintf("c=%s %s %s", "IN", ipv, c.Address) if c.TTL > 0 { s += fmt.Sprintf("/%d", c.TTL) } @@ -339,13 +340,16 @@ func parseConnInfo(s string) (ConnInfo, error) { return ConnInfo{}, fmt.Errorf("unsupported class %q, expected IN", fields[0]) } - conn := ConnInfo{Type: fields[1]} + var conn ConnInfo if fields[1] != "IP4" && fields[1] != "IP6" { return conn, fmt.Errorf("unsupported network type %s", fields[2]) } - conn.Type = fields[1] addr := strings.Split(fields[2], "/") - conn.Address = addr[0] + var err error + conn.Address, err = netip.ParseAddr(addr[0]) + if err != nil { + return conn, fmt.Errorf("parse address: %w", err) + } if len(addr) == 1 { return conn, nil } @@ -359,7 +363,7 @@ func parseConnInfo(s string) (ConnInfo, error) { } } - if conn.Type == "IP4" { + if conn.Address.Is4() { if subfields[0] < 0 || subfields[0] > 255 { return conn, fmt.Errorf("ttl: %d is outside uint8 range", subfields[0]) } @@ -369,9 +373,9 @@ func parseConnInfo(s string) (ConnInfo, error) { } } - if conn.Type == "IP6" && len(subfields) > 1 { + if conn.Address.Is6() && len(subfields) > 1 { return conn, fmt.Errorf("parse address: only 1 subfield allowed, read %d", len(subfields)) - } else if conn.Type == "IP6" && len(subfields) == 1 { + } else if conn.Address.Is6() && len(subfields) == 1 { conn.Count = subfields[0] } return conn, nil diff --git a/sdp/sdp_test.go b/sdp/sdp_test.go @@ -2,6 +2,7 @@ package sdp import ( "net/mail" + "net/netip" "net/url" "os" "reflect" @@ -18,7 +19,7 @@ func TestReadSession(t *testing.T) { name: "good.sdp", want: Session{ Name: "Call to John Smith", - Origin: Origin{"jdoe", 3724394400, 3724394405, "IP4", "198.51.100.1"}, + Origin: Origin{"jdoe", 3724394400, 3724394405, netip.MustParseAddr("198.51.100.1")}, Info: "SDP Offer #1", URI: &url.URL{ Scheme: "http", @@ -31,8 +32,7 @@ func TestReadSession(t *testing.T) { }, Phone: "+16175556011", Connection: &ConnInfo{ - Type: "IP4", - Address: "198.51.100.1", + Address: netip.MustParseAddr("198.51.100.1"), }, Media: []Media{ Media{ @@ -52,7 +52,7 @@ func TestReadSession(t *testing.T) { Port: 51372, Transport: ProtoRTP, Format: []string{"99"}, - Connection: &ConnInfo{"IP6", "2001:db8::2", 0, 0}, + Connection: &ConnInfo{netip.MustParseAddr("2001:db8::2"), 0, 0}, Attributes: []string{"rtpmap:99", "h263-1998/90000"}, }, }, @@ -61,7 +61,7 @@ func TestReadSession(t *testing.T) { { name: "some_optional.sdp", want: Session{ - Origin: Origin{"jdoe", 3724394400, 3724394405, "IP4", "198.51.100.1"}, + Origin: Origin{"jdoe", 3724394400, 3724394405, netip.MustParseAddr("198.51.100.1")}, Name: "Call to John Smith", Email: &mail.Address{ Name: "Jane Doe", @@ -134,11 +134,11 @@ func TestConnInfo(t *testing.T) { line string want ConnInfo }{ - {"ipv4", "IN IP4 192.0.2.1", ConnInfo{"IP4", "192.0.2.1", 0, 0}}, - {"ipv4 ttl", "IN IP4 233.252.0.1/127", ConnInfo{"IP4", "233.252.0.1", 127, 0}}, - {"ipv4 ttl count", "IN IP4 233.252.0.1/127/3", ConnInfo{"IP4", "233.252.0.1", 127, 3}}, - {"ipv6", "IN IP6 2001:db8::1", ConnInfo{"IP6", "2001:db8::1", 0, 0}}, - {"ipv6 count", "IN IP6 ff00::db8:0:101/3", ConnInfo{"IP6", "ff00::db8:0:101", 0, 3}}, + {"ipv4", "IN IP4 192.0.2.1", ConnInfo{netip.MustParseAddr("192.0.2.1"), 0, 0}}, + {"ipv4 ttl", "IN IP4 233.252.0.1/127", ConnInfo{netip.MustParseAddr("233.252.0.1"), 127, 0}}, + {"ipv4 ttl count", "IN IP4 233.252.0.1/127/3", ConnInfo{netip.MustParseAddr("233.252.0.1"), 127, 3}}, + {"ipv6", "IN IP6 2001:db8::1", ConnInfo{netip.MustParseAddr("2001:db8::1"), 0, 0}}, + {"ipv6 count", "IN IP6 ff00::db8:0:101/3", ConnInfo{netip.MustParseAddr("ff00::db8:0:101"), 0, 3}}, } for _, tt := range cases {