feat: Initial project structure with TUI, SSH, SIP parser, and capture modules
This commit is contained in:
257
internal/sip/parser.go
Normal file
257
internal/sip/parser.go
Normal file
@@ -0,0 +1,257 @@
|
||||
package sip
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
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 ""
|
||||
}
|
||||
Reference in New Issue
Block a user