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 }