commit - 52f2be3f00f9c63b286a9b49366dca4622ddc923
commit + f62ba4580683b9231f5608a097aa281961f16c1b
blob - 6e7dccc0c25d795b0587fd76fadd09f64ed3cd12
blob + 5eb9d57db83a496f81461b8a7b8ec0e94fc78360
--- internal/sip/sip.go
+++ internal/sip/sip.go
+// Package sip ... SIP protocol as specified in RFC 3261.
package sip
import (
"bufio"
+ "bytes"
"fmt"
"io"
"net/textproto"
Method string
URI string
- Proto string
- ProtoMajor int
- ProtoMinor int
-
Header textproto.MIMEHeader
ContentLength int64
+ ContentType string
Sequence int
+ Via Via
Body io.Reader
}
+const magicViaCookie = "z9hG4bK"
+
+type Via struct {
+ Address string
+ Branch string
+}
+
+func (v Via) String() string {
+ return fmt.Sprintf("SIP/2.0/UDP %s;branch=%s%s", v.Address, magicViaCookie, v.Branch)
+}
+
func ReadRequest(r io.Reader) (*Request, error) {
msg, err := readMessage(r)
if err != nil {
if msg.startLine[2] != version {
return &req, fmt.Errorf("unknown version %q", msg.startLine[2])
}
- req.Proto = version
- req.ProtoMajor = 2
req.Header = msg.header
if s := req.Header.Get("Content-Length"); s != "" {
return &req, nil
}
+func WriteRequest(w io.Writer, req *Request) (n int64, err error) {
+ // section 8.1.1. We can set Max-Forwards automatically.
+ required := []string{"To", "From", "CSeq", "Call-ID"}
+ for _, s := range required {
+ if req.Header.Get(s) == "" {
+ return 0, fmt.Errorf("missing field %s in header", s)
+ }
+ }
+ if req.Header.Get("Max-Forwards") == "" {
+ // TODO(otl): find section in RFC recommending 70.
+ // section x.x.x
+ req.Header.Set("Max-Forwards", strconv.Itoa(70))
+ }
+ if req.ContentLength > 0 {
+ req.Header.Set("Content-Length", strconv.Itoa(int(req.ContentLength)))
+ }
+ req.Header.Set("Via", req.Via.String())
+
+ buf := &bytes.Buffer{}
+ fmt.Fprintf(buf, "%s %s SIP/2.0\r\n", req.Method, req.URI)
+ for k := range req.Header {
+ for _, v := range req.Header.Values(k) {
+ fmt.Fprintf(buf, "%s: %s\r\n", k, v)
+ }
+ }
+ buf.WriteString("\r\n")
+ n, err = io.Copy(w, buf)
+ if err != nil {
+ return n, err
+ }
+
+ nn, err := io.Copy(w, req.Body)
+ n += nn
+ return n, err
+}
+
type message struct {
startLine [3]string
header textproto.MIMEHeader
Header textproto.MIMEHeader
ContentLength int64
- Sequence CommandSequence
+ // Sequence CommandSequence
- Body io.ReadCloser
+ Body io.Reader
}
+
+func parseResponse(msg *message) (*Response, error) {
+ if msg.startLine[0] != version {
+ return nil, fmt.Errorf("unknown version %s", msg.startLine[0])
+ }
+
+ var resp Response
+ var err error
+ resp.StatusCode, err = strconv.Atoi(msg.startLine[1])
+ if err != nil {
+ return nil, fmt.Errorf("bad status code %q: %v", msg.startLine[1], err)
+ }
+ resp.Status = msg.startLine[2]
+
+ resp.Header = msg.header
+ if s := resp.Header.Get("Content-Length"); s != "" {
+ n, err := strconv.Atoi(s)
+ if err != nil {
+ return &resp, fmt.Errorf("parse content-length: %w", err)
+ }
+ resp.ContentLength = int64(n)
+ }
+ resp.Body = msg.body
+ return &resp, nil
+}
blob - d4a8c7941abe026fe56be17d1a89b3dd8309c349
blob + ec1f4354adb5e24810959469771fb2258027319f
--- internal/sip/sip_test.go
+++ internal/sip/sip_test.go
package sip
import (
+ "fmt"
+ "os"
"strings"
"testing"
)
-func TestReadRequest(t *testing.T) {
- raw := `INVITE sip:bob@biloxi.com SIP/2.0
-Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds
-Max-Forwards: 70
-To: Bob <sip:bob@biloxi.com>
-From: Alice <sip:alice@atlanta.com>;tag=1928301774
-Call-ID: a84b4c76e66710@pc33.atlanta.com
+func TestRequest(t *testing.T) {
+ f, err := os.Open("testdata/invite")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+ _, err = ReadRequest(f)
+ if err != nil {
+ t.Fatal("read request:", err)
+ }
+}
+
+func TestResponse(t *testing.T) {
+ raw := `SIP/2.0 200 OK
+Via: SIP/2.0/UDP server10.example.com
+ ;branch=z9hG4bKnashds8;received=192.0.2.3
+Via: SIP/2.0/UDP bigbox3.site3.example.com
+ ;branch=z9hG4bK77ef4c2312983.1;received=192.0.2.2
+Via: SIP/2.0/UDP pc33.example.com
+ ;branch=z9hG4bK776asdhds ;received=192.0.2.1
+To: Bob <sip:bob@example.com>;tag=a6c85cf
+From: Alice <sip:alice@example.com>;tag=1928301774
+Call-ID: a84b4c76e66710@pc33.example.com
CSeq: 314159 INVITE
-Contact: <sip:alice@pc33.atlanta.com>
+Contact: <sip:bob@192.0.2.4>
Content-Type: application/sdp
-Content-Length: 328
+Content-Length: 131
...`
-
- _, err := ReadRequest(strings.NewReader(raw))
+ msg, err := readMessage(strings.NewReader(raw))
if err != nil {
- t.Fatal("read request:", err)
+ t.Fatal("read message:", err)
}
+ resp, err := parseResponse(msg)
+ if err != nil {
+ t.Fatalf("parse response: %v", err)
+ }
+ fmt.Printf("%+v\n", resp)
}