streaming

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

sip.go (7078B)


      1 // Package sip ... SIP protocol as specified in RFC 3261.
      2 package sip
      3 
      4 import (
      5 	"bufio"
      6 	"bytes"
      7 	"fmt"
      8 	"io"
      9 	"net/textproto"
     10 	"net/url"
     11 	"strconv"
     12 	"strings"
     13 	"unicode"
     14 )
     15 
     16 const (
     17 	MethodRegister = "REGISTER"
     18 	MethodInvite   = "INVITE"
     19 	MethodAck      = "ACK"
     20 	MethodCancel   = "CANCEL"
     21 	MethodBye      = "BYE"
     22 	MethodOptions  = "OPTIONS"
     23 )
     24 
     25 const version = "SIP/2.0"
     26 
     27 type Request struct {
     28 	Method string
     29 	URI    string
     30 
     31 	Header        textproto.MIMEHeader
     32 	ContentLength int64
     33 	ContentType   string
     34 	Sequence      int
     35 	To            Address
     36 	From          Address
     37 	Via           Via
     38 
     39 	Body io.Reader
     40 }
     41 
     42 type URI url.URL
     43 
     44 func (u URI) String() string {
     45 	return "<" + (*url.URL)(&u).String() + ">"
     46 }
     47 
     48 type Address struct {
     49 	Name string
     50 	URI
     51 	Tag string
     52 }
     53 
     54 func (a Address) String() string {
     55 	var tag string
     56 	if a.Tag != "" {
     57 		tag = ";tag=" + a.Tag
     58 	}
     59 	if a.Name != "" {
     60 		return fmt.Sprintf("%s %s%s", a.Name, a.URI, tag)
     61 	}
     62 	return a.URI.String() + tag
     63 }
     64 
     65 func ParseAddress(s string) (Address, error) {
     66 	s = strings.TrimSpace(s)
     67 
     68 	// TODO(otl): we're parsing header parameters - should we generalise somewhere?
     69 	// See section 20.
     70 	before, tag, found := strings.Cut(s, ";")
     71 	if found {
     72 		if !strings.HasPrefix(tag, "tag=") {
     73 			return Address{}, fmt.Errorf("bad tag: missing %q prefix", "tag=")
     74 		}
     75 		tag = tag[4:]
     76 	}
     77 	addr := Address{Tag: tag}
     78 
     79 	// bare URI without angle brackets
     80 	// e.g. "sip:test@example.com"
     81 	u, err := url.Parse(before)
     82 	if err == nil {
     83 		addr.URI = URI(*u)
     84 		return addr, nil
     85 	}
     86 
     87 	// URI without name
     88 	// e.g. "<sip:test@example.com>"
     89 	if strings.HasPrefix(before, "<") && strings.HasSuffix(before, ">") {
     90 		trimmed := strings.Trim(before, "<>")
     91 		u, err := url.Parse(trimmed)
     92 		if err != nil {
     93 			return addr, err
     94 		}
     95 		addr.URI = URI(*u)
     96 		return addr, nil
     97 	}
     98 
     99 	i := strings.Index(before, "<")
    100 	if i < 0 {
    101 		return addr, fmt.Errorf("missing angle bracket after name")
    102 	}
    103 	j := strings.Index(before, ">")
    104 	if j < 0 {
    105 		return addr, fmt.Errorf("missing closing angle bracket")
    106 	}
    107 	addr.Name = strings.TrimSpace(before[:i])
    108 
    109 	u, err = url.Parse(before[i+1 : j])
    110 	if err != nil {
    111 		return addr, fmt.Errorf("parse uri: %w", err)
    112 	}
    113 	addr.URI = URI(*u)
    114 	return addr, nil
    115 }
    116 
    117 const magicViaCookie = "z9hG4bK"
    118 
    119 const (
    120 	TransportUDP int = iota
    121 	TransportTCP
    122 )
    123 
    124 // Via represents the Via field in the header of requests.
    125 type Via struct {
    126 	// Transport indicates whether TCP or UDP should be used in
    127 	// subsequent transactions.
    128 	Transport int
    129 	// Address is a hostname or IP address to which responses
    130 	// should be sent.
    131 	Address string
    132 	// Branch uniquely identifies transactions from a particular user-agent.
    133 	Branch string
    134 }
    135 
    136 func (v Via) String() string {
    137 	tport := "unknown"
    138 	switch v.Transport {
    139 	case TransportUDP:
    140 		tport = "UDP"
    141 	case TransportTCP:
    142 		tport = "TCP"
    143 	}
    144 	return fmt.Sprintf("SIP/2.0/%s %s;branch=%s%s", tport, v.Address, magicViaCookie, v.Branch)
    145 }
    146 
    147 func ReadRequest(r io.Reader) (*Request, error) {
    148 	msg, err := readMessage(r)
    149 	if err != nil {
    150 		return nil, err
    151 	}
    152 	return parseRequest(msg)
    153 }
    154 
    155 func parseRequest(msg *message) (*Request, error) {
    156 	var req Request
    157 	req.Method = msg.startLine[0]
    158 	req.URI = msg.startLine[1]
    159 	if msg.startLine[2] != version {
    160 		return &req, fmt.Errorf("unknown version %q", msg.startLine[2])
    161 	}
    162 
    163 	req.Header = msg.header
    164 	if s := req.Header.Get("Content-Length"); s != "" {
    165 		n, err := strconv.Atoi(s)
    166 		if err != nil {
    167 			return &req, fmt.Errorf("parse content-length: %w", err)
    168 		}
    169 		req.ContentLength = int64(n)
    170 	}
    171 	req.Body = msg.body
    172 	return &req, nil
    173 }
    174 
    175 func WriteRequest(w io.Writer, req *Request) (n int64, err error) {
    176 	// section 8.1.1. We can set Max-Forwards automatically.
    177 	required := []string{"CSeq", "Call-ID"}
    178 	for _, s := range required {
    179 		if req.Header.Get(s) == "" {
    180 			return 0, fmt.Errorf("missing field %s in header", s)
    181 		}
    182 	}
    183 
    184 	if req.To.URI.String() == "" {
    185 		return 0, fmt.Errorf("empty uri in to header field")
    186 	}
    187 	if req.From.URI.String() == "" {
    188 		return 0, fmt.Errorf("empty uri in from header field")
    189 	}
    190 	req.Header.Set("To", req.To.String())
    191 	req.Header.Set("From", req.From.String())
    192 
    193 	if req.Via.Address == "" {
    194 		return 0, fmt.Errorf("empty address in via header field")
    195 	} else if req.Via.Branch == "" {
    196 		return 0, fmt.Errorf("empty branch in via header field")
    197 	}
    198 
    199 	req.Header.Set("Via", req.Via.String())
    200 	if req.Header.Get("Max-Forwards") == "" {
    201 		// TODO(otl): find section in RFC recommending 70.
    202 		// section x.x.x
    203 		req.Header.Set("Max-Forwards", strconv.Itoa(70))
    204 	}
    205 	if req.ContentLength > 0 {
    206 		req.Header.Set("Content-Length", strconv.Itoa(int(req.ContentLength)))
    207 	}
    208 
    209 	buf := &bytes.Buffer{}
    210 	fmt.Fprintf(buf, "%s %s SIP/2.0\r\n", req.Method, req.URI)
    211 	for k := range req.Header {
    212 		for _, v := range req.Header.Values(k) {
    213 			fmt.Fprintf(buf, "%s: %s\r\n", k, v)
    214 		}
    215 	}
    216 	buf.WriteString("\r\n")
    217 	n, err = io.Copy(w, buf)
    218 	if err != nil {
    219 		return n, err
    220 	}
    221 
    222 	if req.Body != nil {
    223 		var nn int64
    224 		nn, err = io.Copy(w, req.Body)
    225 		n += nn
    226 	}
    227 	return n, err
    228 }
    229 
    230 type message struct {
    231 	startLine [3]string
    232 	header    textproto.MIMEHeader
    233 	body      *bufio.Reader
    234 }
    235 
    236 func readMessage(rd io.Reader) (*message, error) {
    237 	r := textproto.NewReader(bufio.NewReader(rd))
    238 	line, err := r.ReadLine()
    239 	if err != nil {
    240 		return nil, fmt.Errorf("read start line: %w", err)
    241 	}
    242 	sline, err := parseStartLine(line)
    243 	if err != nil {
    244 		return nil, fmt.Errorf("parse start line: %w", err)
    245 	}
    246 
    247 	header, err := r.ReadMIMEHeader()
    248 	if err != nil {
    249 		return nil, fmt.Errorf("read header: %w", err)
    250 	}
    251 	return &message{startLine: sline, header: header, body: r.R}, nil
    252 }
    253 
    254 // Request-Line  =  Method SP Request-URI SP SIP-Version CRLF
    255 // Status-Line  =  SIP-Version SP Status-Code SP Reason-Phrase CRLF
    256 func parseStartLine(text string) (line [3]string, err error) {
    257 	fields := strings.Fields(text)
    258 	if len(fields) != 3 {
    259 		return line, fmt.Errorf("expected 3 fields, read %d", len(fields))
    260 	}
    261 	for i, s := range fields {
    262 		if containsSpace(s) {
    263 			return line, fmt.Errorf("illegal space character in field %d", i)
    264 		}
    265 	}
    266 	return [3]string{fields[0], fields[1], fields[2]}, nil
    267 }
    268 
    269 func containsSpace(s string) bool {
    270 	for _, r := range s {
    271 		if unicode.IsSpace(r) {
    272 			return true
    273 		}
    274 	}
    275 	return false
    276 }
    277 
    278 type CommandSequence struct {
    279 	Number int
    280 	Method string
    281 }
    282 
    283 type Response struct {
    284 	Status     string
    285 	StatusCode int
    286 
    287 	Header        textproto.MIMEHeader
    288 	ContentLength int64
    289 	// Sequence      CommandSequence
    290 
    291 	Body io.Reader
    292 }
    293 
    294 func parseResponse(msg *message) (*Response, error) {
    295 	if msg.startLine[0] != version {
    296 		return nil, fmt.Errorf("unknown version %s", msg.startLine[0])
    297 	}
    298 
    299 	var resp Response
    300 	var err error
    301 	resp.StatusCode, err = strconv.Atoi(msg.startLine[1])
    302 	if err != nil {
    303 		return nil, fmt.Errorf("bad status code %q: %v", msg.startLine[1], err)
    304 	}
    305 	resp.Status = msg.startLine[2]
    306 
    307 	resp.Header = msg.header
    308 	if s := resp.Header.Get("Content-Length"); s != "" {
    309 		n, err := strconv.Atoi(s)
    310 		if err != nil {
    311 			return &resp, fmt.Errorf("parse content-length: %w", err)
    312 		}
    313 		resp.ContentLength = int64(n)
    314 	}
    315 	resp.Body = msg.body
    316 	return &resp, nil
    317 }