package sip import ( "regexp" "strings" "time" ) // Method represents a SIP method type Method string const ( MethodINVITE Method = "INVITE" MethodACK Method = "ACK" MethodBYE Method = "BYE" MethodCANCEL Method = "CANCEL" MethodREGISTER Method = "REGISTER" MethodOPTIONS Method = "OPTIONS" MethodPRACK Method = "PRACK" MethodSUBSCRIBE Method = "SUBSCRIBE" MethodNOTIFY Method = "NOTIFY" MethodPUBLISH Method = "PUBLISH" MethodINFO Method = "INFO" MethodREFER Method = "REFER" MethodMESSAGE Method = "MESSAGE" MethodUPDATE Method = "UPDATE" ) // Packet represents a parsed SIP packet type Packet struct { Timestamp time.Time Raw string IsRequest bool Method Method StatusCode int StatusText string RequestURI string // Common headers CallID string From string To string Via []string CSeq string Contact string ContentType string // Network info SourceIP string SourcePort int DestIP string DestPort int // SDP body if present SDP *SDP } // SDP represents a parsed SDP body type SDP struct { Raw string SessionName string Origin string Connection string // c= line (IP for media) MediaSections []MediaSection } // MediaSection represents an m= line in SDP type MediaSection struct { MediaType string // audio, video, etc Port int Protocol string // RTP/AVP, etc Formats []string Attributes map[string]string } // Parse parses a raw SIP message func Parse(raw string) (*Packet, error) { p := &Packet{Raw: raw} lines := strings.Split(raw, "\r\n") if len(lines) == 0 { lines = strings.Split(raw, "\n") } if len(lines) == 0 { return nil, nil } // Parse first line (request or response) firstLine := lines[0] if strings.HasPrefix(firstLine, "SIP/2.0") { p.IsRequest = false parts := strings.SplitN(firstLine, " ", 3) if len(parts) >= 2 { p.StatusCode = parseStatusCode(parts[1]) } if len(parts) >= 3 { p.StatusText = parts[2] } } else { p.IsRequest = true parts := strings.SplitN(firstLine, " ", 3) if len(parts) >= 1 { p.Method = Method(parts[0]) } if len(parts) >= 2 { p.RequestURI = parts[1] } } // Parse headers headerEnd := 0 for i, line := range lines[1:] { if line == "" { headerEnd = i + 2 break } p.parseHeader(line) } // Parse SDP body if present if headerEnd > 0 && headerEnd < len(lines) && strings.Contains(p.ContentType, "application/sdp") { body := strings.Join(lines[headerEnd:], "\r\n") p.SDP = ParseSDP(body) } return p, nil } func (p *Packet) parseHeader(line string) { idx := strings.Index(line, ":") if idx < 0 { return } name := strings.TrimSpace(line[:idx]) value := strings.TrimSpace(line[idx+1:]) switch strings.ToLower(name) { case "call-id", "i": p.CallID = value case "from", "f": p.From = value case "to", "t": p.To = value case "via", "v": p.Via = append(p.Via, value) case "cseq": p.CSeq = value case "contact", "m": p.Contact = value case "content-type", "c": p.ContentType = value } } func parseStatusCode(s string) int { var code int for _, c := range s { if c >= '0' && c <= '9' { code = code*10 + int(c-'0') } else { break } } return code } // ParseSDP parses an SDP body func ParseSDP(body string) *SDP { sdp := &SDP{Raw: body} lines := strings.Split(body, "\r\n") if len(lines) <= 1 { lines = strings.Split(body, "\n") } var currentMedia *MediaSection for _, line := range lines { if len(line) < 2 || line[1] != '=' { continue } typ := line[0] value := line[2:] switch typ { case 'o': sdp.Origin = value case 's': sdp.SessionName = value case 'c': sdp.Connection = value case 'm': if currentMedia != nil { sdp.MediaSections = append(sdp.MediaSections, *currentMedia) } currentMedia = parseMediaLine(value) case 'a': if currentMedia != nil { parseAttribute(currentMedia, value) } } } if currentMedia != nil { sdp.MediaSections = append(sdp.MediaSections, *currentMedia) } return sdp } func parseMediaLine(value string) *MediaSection { parts := strings.Fields(value) if len(parts) < 3 { return &MediaSection{} } m := &MediaSection{ MediaType: parts[0], Protocol: parts[2], Attributes: make(map[string]string), } if port := parsePort(parts[1]); port > 0 { m.Port = port } if len(parts) > 3 { m.Formats = parts[3:] } return m } func parsePort(s string) int { var port int for _, c := range s { if c >= '0' && c <= '9' { port = port*10 + int(c-'0') } else { break } } return port } func parseAttribute(m *MediaSection, value string) { if idx := strings.Index(value, ":"); idx > 0 { m.Attributes[value[:idx]] = value[idx+1:] } else { m.Attributes[value] = "" } } // Regular expressions for extracting IP addresses var ipRegex = regexp.MustCompile(`\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`) // GetSDPMediaIP extracts the media IP from SDP func (s *SDP) GetSDPMediaIP() string { if s.Connection != "" { matches := ipRegex.FindString(s.Connection) if matches != "" { return matches } } return "" }