diff --git a/common_description.go b/common_description.go index 0d523af..9db7903 100644 --- a/common_description.go +++ b/common_description.go @@ -5,7 +5,6 @@ package sdp import ( "strconv" - "strings" ) // Information describes the "i=" field which provides textual information @@ -13,7 +12,11 @@ import ( type Information string func (i Information) String() string { - return string(i) + return stringFromMarshal(i.marshalInto, i.marshalSize) +} + +func (i Information) marshalInto(b []byte) []byte { + return append(b, i...) } func (i Information) marshalSize() (size int) { @@ -29,11 +32,19 @@ type ConnectionInformation struct { } func (c ConnectionInformation) String() string { - parts := []string{c.NetworkType, c.AddressType} + return stringFromMarshal(c.marshalInto, c.marshalSize) +} + +func (c ConnectionInformation) marshalInto(b []byte) []byte { + b = append(append(b, c.NetworkType...), ' ') + b = append(b, c.AddressType...) + if c.Address != nil { - parts = append(parts, c.Address.String()) + b = append(b, ' ') + b = c.Address.marshalInto(b) } - return strings.Join(parts, " ") + + return b } func (c ConnectionInformation) marshalSize() (size int) { @@ -54,17 +65,21 @@ type Address struct { } func (c *Address) String() string { - var parts []string - parts = append(parts, c.Address) + return stringFromMarshal(c.marshalInto, c.marshalSize) +} + +func (c *Address) marshalInto(b []byte) []byte { + b = append(b, c.Address...) if c.TTL != nil { - parts = append(parts, strconv.Itoa(*c.TTL)) + b = append(b, '/') + b = strconv.AppendInt(b, int64(*c.TTL), 10) } - if c.Range != nil { - parts = append(parts, strconv.Itoa(*c.Range)) + b = append(b, '/') + b = strconv.AppendInt(b, int64(*c.Range), 10) } - return strings.Join(parts, "/") + return b } func (c Address) marshalSize() (size int) { @@ -88,12 +103,15 @@ type Bandwidth struct { } func (b Bandwidth) String() string { - var output string + return stringFromMarshal(b.marshalInto, b.marshalSize) +} + +func (b Bandwidth) marshalInto(d []byte) []byte { if b.Experimental { - output += "X-" + d = append(d, "X-"...) } - output += b.Type + ":" + strconv.FormatUint(b.Bandwidth, 10) - return output + d = append(append(d, b.Type...), ':') + return strconv.AppendUint(d, b.Bandwidth, 10) } func (b Bandwidth) marshalSize() (size int) { @@ -109,11 +127,15 @@ func (b Bandwidth) marshalSize() (size int) { type EncryptionKey string func (e EncryptionKey) String() string { - return string(e) + return stringFromMarshal(e.marshalInto, e.marshalSize) +} + +func (e EncryptionKey) marshalInto(b []byte) []byte { + return append(b, e...) } func (e EncryptionKey) marshalSize() (size int) { - return len(e.String()) + return len(e) } // Attribute describes the "a=" field which represents the primary means for @@ -139,11 +161,16 @@ func NewAttribute(key, value string) Attribute { } func (a Attribute) String() string { - output := a.Key + return stringFromMarshal(a.marshalInto, a.marshalSize) +} + +func (a Attribute) marshalInto(b []byte) []byte { + b = append(b, a.Key...) if len(a.Value) > 0 { - output += ":" + a.Value + b = append(append(b, ':'), a.Value...) } - return output + + return b } func (a Attribute) marshalSize() (size int) { diff --git a/marshal.go b/marshal.go index 965597f..43dd738 100644 --- a/marshal.go +++ b/marshal.go @@ -3,10 +3,6 @@ package sdp -import ( - "strings" -) - // Marshal takes a SDP struct to text // https://tools.ietf.org/html/rfc4566#section-5 // Session description @@ -44,81 +40,83 @@ import ( func (s *SessionDescription) Marshal() ([]byte, error) { m := make(marshaller, 0, s.MarshalSize()) - m.addKeyValue("v=", s.Version.String()) - m.addKeyValue("o=", s.Origin.String()) - m.addKeyValue("s=", s.SessionName.String()) + m.addKeyValue("v=", s.Version.marshalInto) + m.addKeyValue("o=", s.Origin.marshalInto) + m.addKeyValue("s=", s.SessionName.marshalInto) if s.SessionInformation != nil { - m.addKeyValue("i=", s.SessionInformation.String()) + m.addKeyValue("i=", s.SessionInformation.marshalInto) } if s.URI != nil { - m.addKeyValue("u=", s.URI.String()) + m = append(m, "u="...) + m = append(m, s.URI.String()...) + m = append(m, "\r\n"...) } if s.EmailAddress != nil { - m.addKeyValue("e=", s.EmailAddress.String()) + m.addKeyValue("e=", s.EmailAddress.marshalInto) } if s.PhoneNumber != nil { - m.addKeyValue("p=", s.PhoneNumber.String()) + m.addKeyValue("p=", s.PhoneNumber.marshalInto) } if s.ConnectionInformation != nil { - m.addKeyValue("c=", s.ConnectionInformation.String()) + m.addKeyValue("c=", s.ConnectionInformation.marshalInto) } for _, b := range s.Bandwidth { - m.addKeyValue("b=", b.String()) + m.addKeyValue("b=", b.marshalInto) } for _, td := range s.TimeDescriptions { - m.addKeyValue("t=", td.Timing.String()) + m.addKeyValue("t=", td.Timing.marshalInto) for _, r := range td.RepeatTimes { - m.addKeyValue("r=", r.String()) + m.addKeyValue("r=", r.marshalInto) } } if len(s.TimeZones) > 0 { - var b strings.Builder + m = append(m, "z="...) for i, z := range s.TimeZones { if i > 0 { - b.WriteString(" ") + m = append(m, ' ') } - b.WriteString(z.String()) + m = z.marshalInto(m) } - m.addKeyValue("z=", b.String()) + m = append(m, "\r\n"...) } if s.EncryptionKey != nil { - m.addKeyValue("k=", s.EncryptionKey.String()) + m.addKeyValue("k=", s.EncryptionKey.marshalInto) } for _, a := range s.Attributes { - m.addKeyValue("a=", a.String()) + m.addKeyValue("a=", a.marshalInto) } for _, md := range s.MediaDescriptions { - m.addKeyValue("m=", md.MediaName.String()) + m.addKeyValue("m=", md.MediaName.marshalInto) if md.MediaTitle != nil { - m.addKeyValue("i=", md.MediaTitle.String()) + m.addKeyValue("i=", md.MediaTitle.marshalInto) } if md.ConnectionInformation != nil { - m.addKeyValue("c=", md.ConnectionInformation.String()) + m.addKeyValue("c=", md.ConnectionInformation.marshalInto) } for _, b := range md.Bandwidth { - m.addKeyValue("b=", b.String()) + m.addKeyValue("b=", b.marshalInto) } if md.EncryptionKey != nil { - m.addKeyValue("k=", md.EncryptionKey.String()) + m.addKeyValue("k=", md.EncryptionKey.marshalInto) } for _, a := range md.Attributes { - m.addKeyValue("a=", a.String()) + m.addKeyValue("a=", a.marshalInto) } } @@ -212,13 +210,9 @@ func (s *SessionDescription) MarshalSize() (marshalSize int) { // marshaller contains state during marshaling. type marshaller []byte -func (m *marshaller) addKeyValue(key, value string) { - if value == "" { - return - } - +func (m *marshaller) addKeyValue(key string, value func([]byte) []byte) { *m = append(*m, key...) - *m = append(*m, value...) + *m = value(*m) *m = append(*m, "\r\n"...) } @@ -240,3 +234,7 @@ func lenInt(i int64) (count int) { } return lenUint(uint64(i)) } + +func stringFromMarshal(marshalFunc func([]byte) []byte, sizeFunc func() int) string { + return string(marshalFunc(make([]byte, 0, sizeFunc()))) +} diff --git a/media_description.go b/media_description.go index 801928e..d5d76e4 100644 --- a/media_description.go +++ b/media_description.go @@ -5,7 +5,6 @@ package sdp import ( "strconv" - "strings" ) // MediaDescription represents a media type. @@ -65,6 +64,15 @@ func (p *RangedPort) String() string { return output } +func (p RangedPort) marshalInto(b []byte) []byte { + b = strconv.AppendInt(b, int64(p.Value), 10) + if p.Range != nil { + b = append(b, '/') + b = strconv.AppendInt(b, int64(*p.Range), 10) + } + return b +} + func (p RangedPort) marshalSize() (size int) { size = lenInt(int64(p.Value)) if p.Range != nil { @@ -83,34 +91,38 @@ type MediaName struct { } func (m MediaName) String() string { - return strings.Join([]string{ - m.Media, - m.Port.String(), - strings.Join(m.Protos, "/"), - strings.Join(m.Formats, " "), - }, " ") + return stringFromMarshal(m.marshalInto, m.marshalSize) } -func (m MediaName) marshalSize() (size int) { - size = len(m.Media) - - size += 1 + m.Port.marshalSize() - - for i, p := range m.Protos { - if i != len(m.Protos) { - size++ +func (m MediaName) marshalInto(b []byte) []byte { + appendList := func(list []string, sep byte) { + for i, p := range list { + if i != 0 && i != len(list) { + b = append(b, sep) + } + b = append(b, p...) } - - size += len(p) } - for i, f := range m.Formats { - if i != len(m.Formats) { - size++ - } + b = append(append(b, m.Media...), ' ') + b = append(m.Port.marshalInto(b), ' ') + appendList(m.Protos, '/') + b = append(b, ' ') + appendList(m.Formats, ' ') + return b +} - size += len(f) +func (m MediaName) marshalSize() (size int) { + listSize := func(list []string) { + for _, p := range list { + size += 1 + len(p) + } } + size = len(m.Media) + size += 1 + m.Port.marshalSize() + listSize(m.Protos) + listSize(m.Formats) + return size } diff --git a/session_description.go b/session_description.go index 57a7da7..a9e25da 100644 --- a/session_description.go +++ b/session_description.go @@ -4,7 +4,6 @@ package sdp import ( - "fmt" "net/url" "strconv" ) @@ -85,7 +84,11 @@ func (s *SessionDescription) Attribute(key string) (string, bool) { type Version int func (v Version) String() string { - return strconv.Itoa(int(v)) + return stringFromMarshal(v.marshalInto, v.marshalSize) +} + +func (v Version) marshalInto(b []byte) []byte { + return strconv.AppendInt(b, int64(v), 10) } func (v Version) marshalSize() (size int) { @@ -104,15 +107,16 @@ type Origin struct { } func (o Origin) String() string { - return fmt.Sprintf( - "%v %d %d %v %v %v", - o.Username, - o.SessionID, - o.SessionVersion, - o.NetworkType, - o.AddressType, - o.UnicastAddress, - ) + return stringFromMarshal(o.marshalInto, o.marshalSize) +} + +func (o Origin) marshalInto(b []byte) []byte { + b = append(append(b, o.Username...), ' ') + b = append(strconv.AppendUint(b, o.SessionID, 10), ' ') + b = append(strconv.AppendUint(b, o.SessionVersion, 10), ' ') + b = append(append(b, o.NetworkType...), ' ') + b = append(append(b, o.AddressType...), ' ') + return append(b, o.UnicastAddress...) } func (o Origin) marshalSize() (size int) { @@ -130,7 +134,11 @@ func (o Origin) marshalSize() (size int) { type SessionName string func (s SessionName) String() string { - return string(s) + return stringFromMarshal(s.marshalInto, s.marshalSize) +} + +func (s SessionName) marshalInto(b []byte) []byte { + return append(b, s...) } func (s SessionName) marshalSize() (size int) { @@ -143,7 +151,11 @@ func (s SessionName) marshalSize() (size int) { type EmailAddress string func (e EmailAddress) String() string { - return string(e) + return stringFromMarshal(e.marshalInto, e.marshalSize) +} + +func (e EmailAddress) marshalInto(b []byte) []byte { + return append(b, e...) } func (e EmailAddress) marshalSize() (size int) { @@ -156,7 +168,11 @@ func (e EmailAddress) marshalSize() (size int) { type PhoneNumber string func (p PhoneNumber) String() string { - return string(p) + return stringFromMarshal(p.marshalInto, p.marshalSize) +} + +func (p PhoneNumber) marshalInto(b []byte) []byte { + return append(b, p...) } func (p PhoneNumber) marshalSize() (size int) { @@ -171,7 +187,13 @@ type TimeZone struct { } func (z TimeZone) String() string { - return strconv.FormatUint(z.AdjustmentTime, 10) + " " + strconv.FormatInt(z.Offset, 10) + return stringFromMarshal(z.marshalInto, z.marshalSize) +} + +func (z TimeZone) marshalInto(b []byte) []byte { + b = strconv.AppendUint(b, z.AdjustmentTime, 10) + b = append(b, ' ') + return strconv.AppendInt(b, z.Offset, 10) } func (z TimeZone) marshalSize() (size int) { diff --git a/time_description.go b/time_description.go index cada921..0a5baa4 100644 --- a/time_description.go +++ b/time_description.go @@ -5,7 +5,6 @@ package sdp import ( "strconv" - "strings" ) // TimeDescription describes "t=", "r=" fields of the session description @@ -29,9 +28,12 @@ type Timing struct { } func (t Timing) String() string { - output := strconv.FormatUint(t.StartTime, 10) - output += " " + strconv.FormatUint(t.StopTime, 10) - return output + return stringFromMarshal(t.marshalInto, t.marshalSize) +} + +func (t Timing) marshalInto(b []byte) []byte { + b = append(strconv.AppendUint(b, t.StartTime, 10), ' ') + return strconv.AppendUint(b, t.StopTime, 10) } func (t Timing) marshalSize() (size int) { @@ -47,14 +49,19 @@ type RepeatTime struct { } func (r RepeatTime) String() string { - fields := make([]string, 0) - fields = append(fields, strconv.FormatInt(r.Interval, 10)) - fields = append(fields, strconv.FormatInt(r.Duration, 10)) + return stringFromMarshal(r.marshalInto, r.marshalSize) +} + +func (r RepeatTime) marshalInto(b []byte) []byte { + b = strconv.AppendInt(b, r.Interval, 10) + b = append(b, ' ') + b = strconv.AppendInt(b, r.Duration, 10) for _, value := range r.Offsets { - fields = append(fields, strconv.FormatInt(value, 10)) + b = append(b, ' ') + b = strconv.AppendInt(b, value, 10) } - return strings.Join(fields, " ") + return b } func (r RepeatTime) marshalSize() (size int) {