commit 896f60249033051fe531557d83031f3d0e2fa1c1 from: Oliver Lowe date: Fri Jul 4 11:28:03 2025 UTC sdp: use encoding.TextMarshaler for Session encoding fmt.Stringer is not really meant for larger multi-line text. It sucks a bit that we may return an error, but this lets us put some validation logic in; we don't have any validation yet. commit - 035ab79449be5db836ae24e00d8bfe3317f14ea5 commit + 896f60249033051fe531557d83031f3d0e2fa1c1 blob - 5313912265ce515dae4646ee4ad514b2c8b77bf3 blob + b4570814a71b65d43f6f3cee77e4a6db39e7cd73 --- sdp/encode.go +++ sdp/encode.go @@ -1,21 +1,21 @@ package sdp import ( + "bytes" "fmt" "strings" ) -func (s Session) String() string { +func (s Session) MarshalText() ([]byte, error) { buf := &strings.Builder{} fmt.Fprintln(buf, "v=0") + if s.Origin.Username == "" { s.Origin.Username = NoUsername } - 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.Fprintln(buf, s.Origin) + fmt.Fprintf(buf, "s=%s\n", s.Name) if s.Info != "" { @@ -65,5 +65,5 @@ func (s Session) String() string { fmt.Fprintln(buf, m) } - return strings.TrimSpace(buf.String()) + return bytes.TrimSpace([]byte(buf.String())), nil } blob - d10ffc423a447ce6efdde01f4ab25e59de6225a4 blob + 913d63f5e7abd39ee8edc4b0886360022651c06f --- sdp/encode_test.go +++ sdp/encode_test.go @@ -12,18 +12,21 @@ func TestWriteSession(t *testing.T) { if err != nil { t.Fatal(err) } - b, err := io.ReadAll(f) + want, err := io.ReadAll(f) if err != nil { t.Fatal(err) } - want := string(b) - session, err := ReadSession(bytes.NewReader(b)) + session, err := ReadSession(bytes.NewReader(want)) if err != nil { t.Fatal(err) } - if want != session.String() { + got, err := session.MarshalText() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(want, got) { t.Errorf("mismatched sdp text") - t.Log("want", want) - t.Log("got", session.String()) + t.Log("want", string(want)) + t.Log("got", string(got)) } } blob - a2f6d52965ac0aa8362e39972003ca2ce9e7d658 blob + d20edbe5e9f6f46e74631c9cc31a88cfcd21d4b5 --- sdp/example_test.go +++ sdp/example_test.go @@ -12,8 +12,8 @@ import ( // audio in RTP starts by setting the mandatory fields Origin and Name. // The Media field contains information about the audio such as the sample rate // and the number of audio channels. -// The Session type implements fmt.Stringer; -// to encode a Session in the SDP text format, use Session.String(). +// Session implements encoding.TextMarshaler; +// to encode a Session in the SDP text format, use Session.MarshalText(). func Example() { session := sdp.Session{ Origin: sdp.Origin{ @@ -35,7 +35,8 @@ func Example() { }, }, } - fmt.Printf("%s", session) + text, _ := session.MarshalText() + fmt.Printf("%s", text) // Output: // v=0 // o=- 3930287268 3930287268 IN IP6 2001:db8::1 blob - 6031e234406a2f1fae937eec38a27f50b6436e6e blob + 8c78e45e3f32c9bfa9527df8f3d3377919b99f85 --- sdp/sdp.go +++ sdp/sdp.go @@ -60,6 +60,14 @@ type Origin struct { Address netip.Addr } +func (o Origin) String() string { + ipv := "IP6" + if o.Address.Is4() { + ipv = "IP4" + } + return fmt.Sprintf("o=%s %d %d IN %s %s", o.Username, o.ID, o.Version, ipv, o.Address) +} + func ReadSession(rd io.Reader) (*Session, error) { parser := &parser{Scanner: bufio.NewScanner(rd)} if err := parser.parse(); err != nil {