diff --git a/sdp/codec.go b/sdp/codec.go index 27a065c..a7360a6 100644 --- a/sdp/codec.go +++ b/sdp/codec.go @@ -2,7 +2,6 @@ package sdp import ( "bytes" - "errors" "strconv" ) @@ -17,25 +16,23 @@ type Codec struct { Fmtp string // some extra info; i.e. dtmf might set as "0-16" } -func (codec *Codec) Append(b *bytes.Buffer) error { - if codec.Name == "" { - return errors.New("Codec.Name is blank") - } - if codec.Rate < 8000 { - return errors.New("Codec.Rate < 8000") - } +func (codec *Codec) Append(b *bytes.Buffer) { b.WriteString("a=rtpmap:") - b.WriteString(strconv.Itoa(int(codec.PT)) + " ") - b.WriteString(codec.Name + "/") + b.WriteString(strconv.FormatInt(int64(codec.PT), 10)) + b.WriteString(" ") + b.WriteString(codec.Name) + b.WriteString("/") b.WriteString(strconv.Itoa(codec.Rate)) if codec.Param != "" { - b.WriteString("/" + codec.Param) + b.WriteString("/") + b.WriteString(codec.Param) } b.WriteString("\r\n") if codec.Fmtp != "" { b.WriteString("a=fmtp:") - b.WriteString(strconv.Itoa(int(codec.PT)) + " ") - b.WriteString(codec.Fmtp + "\r\n") + b.WriteString(strconv.FormatInt(int64(codec.PT), 10)) + b.WriteString(" ") + b.WriteString(codec.Fmtp) + b.WriteString("\r\n") } - return nil } diff --git a/sdp/codecs.go b/sdp/codecs.go index e89a800..e598d13 100755 --- a/sdp/codecs.go +++ b/sdp/codecs.go @@ -19,44 +19,33 @@ package sdp var ( - ULAWCodec = &Codec{PT: 0, Name: "PCMU", Rate: 8000} - DTMFCodec = &Codec{PT: 101, Name: "telephone-event", Rate: 8000, Fmtp: "0-16"} + ULAWCodec = Codec{PT: 0, Name: "PCMU", Rate: 8000} + DTMFCodec = Codec{PT: 101, Name: "telephone-event", Rate: 8000, Fmtp: "0-16"} - StandardCodecs = map[uint8]*Codec{ - // G.711 μ-Law is the de-facto codec (SpanDSP g711.h) - 0: ULAWCodec, - // Uncool codec asterisk ppl like (SpanDSP gsm0610.h) - 3: &Codec{PT: 3, Name: "GSM", Rate: 8000}, - 4: &Codec{PT: 4, Name: "G723", Rate: 8000}, - // adaptive pulse code modulation (SpanDSP ima_adpcm.h) - 5: &Codec{PT: 5, Name: "DVI4", Rate: 8000}, - 6: &Codec{PT: 6, Name: "DVI4", Rate: 16000}, - // chat with your friends ww2 field marshall style (SpanDSP lpc10.h) - 7: &Codec{PT: 7, Name: "LPC", Rate: 8000}, - // G.711 variant of μ-Law used in yurop (SpanDSP g711.h) - 8: &Codec{PT: 8, Name: "PCMA", Rate: 8000}, - // used for Polycom HD Voice; rate actually 16khz lol (SpanDSP g722.h) - 9: &Codec{PT: 9, Name: "G722", Rate: 8000}, - // 16-bit signed PCM stereo/mono (mind your MTU; adjust ptime) - 10: &Codec{PT: 10, Name: "L16", Rate: 44100, Param: "2"}, - 11: &Codec{PT: 11, Name: "L16", Rate: 44100}, - 12: &Codec{PT: 12, Name: "QCELP", Rate: 8000}, - // RFC3389 comfort noise - 13: &Codec{PT: 13, Name: "CN", Rate: 8000}, - 14: &Codec{PT: 14, Name: "MPA", Rate: 90000}, - 15: &Codec{PT: 15, Name: "G728", Rate: 8000}, - 16: &Codec{PT: 16, Name: "DVI4", Rate: 11025}, - 17: &Codec{PT: 17, Name: "DVI4", Rate: 22050}, - // best voice codec (if you got $10 bucks) - 18: &Codec{PT: 18, Name: "G729", Rate: 8000}, - 25: &Codec{PT: 25, Name: "CelB", Rate: 90000}, - 26: &Codec{PT: 26, Name: "JPEG", Rate: 90000}, - 28: &Codec{PT: 28, Name: "nv", Rate: 90000}, - // RFC4587 video - 31: &Codec{PT: 31, Name: "H261", Rate: 90000}, - 32: &Codec{PT: 32, Name: "MPV", Rate: 90000}, - 33: &Codec{PT: 33, Name: "MP2T", Rate: 90000}, - // $$$ video - 34: &Codec{PT: 34, Name: "H263", Rate: 90000}, + StandardCodecs = map[uint8]Codec{ + 0: ULAWCodec, // G.711 μ-Law is the de-facto codec (SpanDSP g711.h) + 3: Codec{PT: 3, Name: "GSM", Rate: 8000}, // Uncool codec asterisk ppl like (SpanDSP gsm0610.h) + 4: Codec{PT: 4, Name: "G723", Rate: 8000}, // Worthless. + 5: Codec{PT: 5, Name: "DVI4", Rate: 8000}, // Adaptive pulse code modulation (SpanDSP ima_adpcm.h) + 6: Codec{PT: 6, Name: "DVI4", Rate: 16000}, // Adaptive pulse code modulation 16khz (SpanDSP ima_adpcm.h) + 7: Codec{PT: 7, Name: "LPC", Rate: 8000}, // Chat with your friends ww2 field marshall style (SpanDSP lpc10.h) + 8: Codec{PT: 8, Name: "PCMA", Rate: 8000}, // G.711 variant of μ-Law used in yurop (SpanDSP g711.h) + 9: Codec{PT: 9, Name: "G722", Rate: 8000}, // Used for Polycom HD Voice; Rate actually 16khz LOL (SpanDSP g722.h) + 10: Codec{PT: 10, Name: "L16", Rate: 44100, Param: "2"}, // 16-bit signed PCM stereo/mono (mind your MTU; adjust ptime) + 11: Codec{PT: 11, Name: "L16", Rate: 44100}, + 12: Codec{PT: 12, Name: "QCELP", Rate: 8000}, + 13: Codec{PT: 13, Name: "CN", Rate: 8000}, // RFC3389 comfort noise + 14: Codec{PT: 14, Name: "MPA", Rate: 90000}, + 15: Codec{PT: 15, Name: "G728", Rate: 8000}, + 16: Codec{PT: 16, Name: "DVI4", Rate: 11025}, + 17: Codec{PT: 17, Name: "DVI4", Rate: 22050}, + 18: Codec{PT: 18, Name: "G729", Rate: 8000}, // Best telephone voice codec (if you got $10 bucks) + 25: Codec{PT: 25, Name: "CelB", Rate: 90000}, + 26: Codec{PT: 26, Name: "JPEG", Rate: 90000}, + 28: Codec{PT: 28, Name: "nv", Rate: 90000}, + 31: Codec{PT: 31, Name: "H261", Rate: 90000}, // RFC4587 Video + 32: Codec{PT: 32, Name: "MPV", Rate: 90000}, + 33: Codec{PT: 33, Name: "MP2T", Rate: 90000}, + 34: Codec{PT: 34, Name: "H263", Rate: 90000}, // $$$ video } ) diff --git a/sdp/media.go b/sdp/media.go index 44e73f9..34b3634 100644 --- a/sdp/media.go +++ b/sdp/media.go @@ -2,44 +2,34 @@ package sdp import ( "bytes" - "errors" "strconv" ) // Media is a high level representation of the c=/m=/a= lines for describing a // specific type of media. Only "audio" and "video" are supported at this time. type Media struct { - Type string // "audio" or "video" Proto string // RTP, SRTP, UDP, UDPTL, TCP, TLS, etc. - Port int // port number (0 - 2^16-1) - Codecs []Codec // never nil with at least one codec + Port uint16 // Port number (0 - 2^16-1) + Codecs []Codec // Collection of codecs of a specific type. } -func (media *Media) Append(b *bytes.Buffer) error { - if media.Type != "audio" && media.Type != "video" { - return errors.New("Media.Type not audio/video: " + media.Type) - } - if media.Codecs == nil || len(media.Codecs) == 0 { - return errors.New("Media.Codecs not set") - } - if media.Port == 0 { - return errors.New("Media.Port not set") - } +func (media *Media) Append(type_ string, b *bytes.Buffer) { + b.WriteString("m=") + b.WriteString(type_) + b.WriteString(" ") + b.WriteString(strconv.FormatUint(uint64(media.Port), 10)) + b.WriteString(" ") if media.Proto == "" { - media.Proto = "RTP/AVP" + b.WriteString("RTP/AVP") + } else { + b.WriteString(media.Proto) } - b.WriteString("m=") - b.WriteString(media.Type + " ") - b.WriteString(strconv.Itoa(int(media.Port)) + " ") - b.WriteString(media.Proto) for _, codec := range media.Codecs { - b.WriteString(" " + strconv.Itoa(int(codec.PT))) + b.WriteString(" ") + b.WriteString(strconv.FormatInt(int64(codec.PT), 10)) } b.WriteString("\r\n") for _, codec := range media.Codecs { - if err := codec.Append(b); err != nil { - return err - } + codec.Append(b) } - return nil } diff --git a/sdp/origin.go b/sdp/origin.go index 0109165..8a0bd47 100644 --- a/sdp/origin.go +++ b/sdp/origin.go @@ -2,40 +2,47 @@ package sdp import ( "bytes" - "errors" "github.com/jart/gosip/util" ) // Origin represents the session origin (o=) line of an SDP. Who knows what // this is supposed to do. type Origin struct { - User string // first value in o= line - ID string // second value in o= line - Version string // third value in o= line - Addr string // tracks ip of original user-agent + User string // First value in o= line + ID string // Second value in o= line + Version string // Third value in o= line + Addr string // Tracks IP of original user-agent } -func (origin *Origin) Append(b *bytes.Buffer) error { - if origin.ID == "" { - return errors.New("sdp missing origin id") - } - if origin.Version == "" { - return errors.New("sdp missing origin version") - } - if origin.Addr == "" { - return errors.New("sdp missing origin address") +func (origin *Origin) Append(b *bytes.Buffer) { + id := origin.ID + if id == "" { + id = util.GenerateOriginID() } + b.WriteString("o=") if origin.User == "" { - origin.User = "-" + b.WriteString("-") + } else { + b.WriteString(origin.User) + } + b.WriteString(" ") + b.WriteString(id) + b.WriteString(" ") + if origin.Version == "" { + b.WriteString(id) + } else { + b.WriteString(origin.Version) } - b.WriteString("o=") - b.WriteString(origin.User + " ") - b.WriteString(origin.ID + " ") - b.WriteString(origin.Version) if util.IsIPv6(origin.Addr) { - b.WriteString(" IN IP6 " + origin.Addr + "\r\n") + b.WriteString(" IN IP6 ") + } else { + b.WriteString(" IN IP4 ") + } + if origin.Addr == "" { + // In case of bugs, keep calm and DDOS NASA. + b.WriteString("69.28.157.198") } else { - b.WriteString(" IN IP4 " + origin.Addr + "\r\n") + b.WriteString(origin.Addr) } - return nil + b.WriteString("\r\n") } diff --git a/sdp/sdp.go b/sdp/sdp.go index 831de89..a678085 100755 --- a/sdp/sdp.go +++ b/sdp/sdp.go @@ -57,7 +57,6 @@ import ( "errors" "github.com/jart/gosip/util" "log" - "math/rand" "net" "strconv" "strings" @@ -70,32 +69,31 @@ const ( // SDP represents a Session Description Protocol SIP payload. type SDP struct { - SendOnly bool // true if 'a=sendonly' was specified in sdp - RecvOnly bool // true if 'a=recvonly' was specified in sdp - Ptime int // transmit every X millisecs (0 means not set) - Addr string // connect to this ip; never blank (from c=) - Audio *Media // non nil if we can establish audio - Video *Media // non nil if we can establish video - Origin Origin // this must always be present - Session string // s= Session Name (defaults to "-") - Time string // t= Active Time (defaults to "0 0") - Attrs [][2]string // a= lines we don't recognize (never nil) + Origin Origin // This must always be present + Addr string // Connect to this IP; never blank (from c=) + Audio *Media // Non-nil if we can establish audio + Video *Media // Non-nil if we can establish video + Session string // s= Session Name (default "-") + Time string // t= Active Time (default "0 0") + Ptime int // Transmit frame every N milliseconds (default 20) + SendOnly bool // True if 'a=sendonly' was specified in SDP + RecvOnly bool // True if 'a=recvonly' was specified in SDP + Attrs [][2]string // a= lines we don't recognize } // Easy way to create a basic, everyday SDP for VoIP. -func New(addr *net.UDPAddr, codecs ...*Codec) *SDP { +func New(addr *net.UDPAddr, codecs ...Codec) *SDP { sdp := new(SDP) sdp.Addr = addr.IP.String() - sdp.Origin.ID = strconv.FormatInt(int64(rand.Uint32()), 10) + sdp.Origin.ID = util.GenerateOriginID() sdp.Origin.Version = sdp.Origin.ID sdp.Origin.Addr = sdp.Addr sdp.Audio = new(Media) - sdp.Audio.Type = "audio" sdp.Audio.Proto = "RTP/AVP" - sdp.Audio.Port = addr.Port + sdp.Audio.Port = uint16(addr.Port) sdp.Audio.Codecs = make([]Codec, len(codecs)) for i := 0; i < len(codecs); i++ { - sdp.Audio.Codecs[i] = *codecs[i] + sdp.Audio.Codecs[i] = codecs[i] } sdp.Attrs = make([][2]string, 0, 8) return sdp @@ -216,7 +214,6 @@ func Parse(s string) (sdp *SDP, err error) { if audioinfo != "" { sdp.Audio = new(Media) - sdp.Audio.Type = "audio" sdp.Audio.Port, sdp.Audio.Proto, pts, err = parseMediaInfo(audioinfo) if err != nil { return nil, err @@ -231,7 +228,6 @@ func Parse(s string) (sdp *SDP, err error) { if videoinfo != "" { sdp.Video = new(Media) - sdp.Video.Type = "video" sdp.Video.Port, sdp.Video.Proto, pts, err = parseMediaInfo(videoinfo) if err != nil { return nil, err @@ -256,58 +252,62 @@ func (sdp *SDP) String() string { return "" } var b bytes.Buffer - if err := sdp.Append(&b); err != nil { - log.Println("Bad SDP!", err) - return "" - } + sdp.Append(&b) return b.String() } -func (sdp *SDP) Append(b *bytes.Buffer) error { - if sdp.Audio == nil && sdp.Video == nil { - return errors.New("sdp lonely no media :[") - } +func (sdp *SDP) Append(b *bytes.Buffer) { + b.WriteString("v=0\r\n") + sdp.Origin.Append(b) + b.WriteString("s=") if sdp.Session == "" { - sdp.Session = "my people call themselves dark angels" + b.WriteString("my people call themselves dark angels") + } else { + b.WriteString(sdp.Session) } - if sdp.Time == "" { - sdp.Time = "0 0" + b.WriteString("\r\n") + if util.IsIPv6(sdp.Addr) { + b.WriteString("c=IN IP6 ") + } else { + b.WriteString("c=IN IP4 ") } - b.WriteString("v=0\r\n") - if err := sdp.Origin.Append(b); err != nil { - return err + if sdp.Addr == "" { + // In case of bugs, keep calm and DDOS NASA. + b.WriteString("69.28.157.198") + } else { + b.WriteString(sdp.Addr) } - b.WriteString("s=" + sdp.Session + "\r\n") - if util.IsIPv6(sdp.Addr) { - b.WriteString("c=IN IP6 " + sdp.Addr + "\r\n") + b.WriteString("\r\n") + b.WriteString("t=") + if sdp.Time == "" { + b.WriteString("0 0") } else { - b.WriteString("c=IN IP4 " + sdp.Addr + "\r\n") + b.WriteString(sdp.Time) } - b.WriteString("t=" + sdp.Time + "\r\n") + b.WriteString("\r\n") if sdp.Audio != nil { - if err := sdp.Audio.Append(b); err != nil { - return err - } + sdp.Audio.Append("audio", b) } if sdp.Video != nil { - if err := sdp.Video.Append(b); err != nil { - return err - } - } - if sdp.Attrs != nil { - for _, attr := range sdp.Attrs { - if attr[0] == "" { - return errors.New("SDP.Attrs key empty!") - } - if attr[1] == "" { - b.WriteString("a=" + attr[0] + "\r\n") - } else { - b.WriteString("a=" + attr[0] + ":" + attr[1] + "\r\n") - } + sdp.Video.Append("video", b) + } + for _, attr := range sdp.Attrs { + if attr[1] == "" { + b.WriteString("a=") + b.WriteString(attr[0]) + b.WriteString("\r\n") + } else { + b.WriteString("a=") + b.WriteString(attr[0]) + b.WriteString(":") + b.WriteString(attr[1]) + b.WriteString("\r\n") } } if sdp.Ptime > 0 { - b.WriteString("a=ptime:" + strconv.Itoa(sdp.Ptime) + "\r\n") + b.WriteString("a=ptime:") + b.WriteString(strconv.Itoa(sdp.Ptime)) + b.WriteString("\r\n") } if sdp.SendOnly { b.WriteString("a=sendonly\r\n") @@ -316,7 +316,6 @@ func (sdp *SDP) Append(b *bytes.Buffer) error { } else { b.WriteString("a=sendrecv\r\n") } - return nil } // Here we take the list of payload types from the m= line (e.g. 9 18 0 101) @@ -344,7 +343,7 @@ func populateCodecs(media *Media, pts []uint8, rtpmaps, fmtps []string) (err err return errors.New("dynamic codec missing rtpmap") } else { if v, ok := StandardCodecs[pt]; ok { - *codec = *v + *codec = v } else { return errors.New("unknown iana codec id: " + strconv.Itoa(int(pt))) @@ -386,7 +385,7 @@ func parseRtpmapInfo(codec *Codec, s string) (err error) { } // Give me the part of an "m=" line that looks like: "30126 RTP/AVP 0 101". -func parseMediaInfo(s string) (port int, proto string, pts []uint8, err error) { +func parseMediaInfo(s string) (port uint16, proto string, pts []uint8, err error) { toks := strings.Split(s, " ") if toks == nil || len(toks) < 3 { return 0, "", nil, errors.New("invalid m= line") @@ -400,10 +399,11 @@ func parseMediaInfo(s string) (port int, proto string, pts []uint8, err error) { } // Convert port to int and check range. - port, err = strconv.Atoi(portS) + portU, err := strconv.ParseUint(portS, 10, 16) if err != nil || !(0 <= port && port <= 65535) { return 0, "", nil, errors.New("invalid m= port") } + port = uint16(portU) // Is it rtp? srtp? udp? tcp? etc. (must be 3+ chars) proto = toks[1] diff --git a/sdp/sdp_test.go b/sdp/sdp_test.go index 64b9ee0..dd86b01 100755 --- a/sdp/sdp_test.go +++ b/sdp/sdp_test.go @@ -41,7 +41,6 @@ var sdpTests = []sdpTest{ Time: "0 0", Addr: "10.0.0.38", Audio: &sdp.Media{ - Type: "audio", Proto: "RTP/AVP", Port: 30126, Codecs: []sdp.Codec{ @@ -86,7 +85,6 @@ var sdpTests = []sdpTest{ Session: "pokémon", Time: "0 0", Audio: &sdp.Media{ - Type: "audio", Proto: "RTP/AVP", Port: 32898, Codecs: []sdp.Codec{ @@ -94,7 +92,6 @@ var sdpTests = []sdpTest{ }, }, Video: &sdp.Media{ - Type: "video", Proto: "RTP/AVP", Port: 32900, Codecs: []sdp.Codec{ @@ -138,7 +135,6 @@ var sdpTests = []sdpTest{ Time: "0 0", Addr: "1.2.3.4", Audio: &sdp.Media{ - Type: "audio", Proto: "RTP/AVP", Port: 32898, Codecs: []sdp.Codec{ @@ -186,7 +182,6 @@ var sdpTests = []sdpTest{ Time: "0 0", Addr: "dead:beef::666", Audio: &sdp.Media{ - Type: "audio", Proto: "RTP/AVP", Port: 32898, Codecs: []sdp.Codec{ @@ -235,7 +230,6 @@ var sdpTests = []sdpTest{ Time: "0 0", Addr: "10.11.34.37", Audio: &sdp.Media{ - Type: "audio", Proto: "RTP/AVP", Port: 4000, Codecs: []sdp.Codec{ @@ -280,7 +274,6 @@ var sdpTests = []sdpTest{ Addr: "dead:beef::666", SendOnly: true, Audio: &sdp.Media{ - Type: "audio", Proto: "TCP/IP", Port: 80, Codecs: []sdp.Codec{ @@ -356,9 +349,6 @@ func sdpCompareMedia(t *testing.T, name string, correct, media *sdp.Media) { return } - if correct.Type != media.Type { - t.Error(name, "Type", correct.Type, "!=", media.Type) - } if correct.Proto != media.Proto { t.Error(name, "Proto", correct.Proto, "!=", media.Proto) } diff --git a/util/util.go b/util/util.go index b50370f..e6ce0ac 100755 --- a/util/util.go +++ b/util/util.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "math/rand" "net" + "strconv" "strings" "syscall" ) @@ -78,6 +79,11 @@ func GenerateCallID() string { return uuid4 } +// Generates a random ID for an SDP. +func GenerateOriginID() string { + return strconv.FormatUint(uint64(rand.Uint32()), 10) +} + func randomBytes(l int) (b []byte) { b = make([]byte, l) for i := 0; i < l; i++ {