346 lines
11 KiB
C
346 lines
11 KiB
C
/**
|
|
* SIP Parser - Einfacher SIP Message Parser
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include "sip_parser.h"
|
|
|
|
// Hilfsfunktion: Zeile aus Buffer extrahieren
|
|
static int get_line(const char* buf, size_t buf_len, char* line, size_t line_len)
|
|
{
|
|
size_t i = 0;
|
|
while (i < buf_len && i < line_len - 1) {
|
|
if (buf[i] == '\r' || buf[i] == '\n') {
|
|
line[i] = '\0';
|
|
// Skip CRLF
|
|
if (buf[i] == '\r' && i + 1 < buf_len && buf[i + 1] == '\n') {
|
|
return i + 2;
|
|
}
|
|
return i + 1;
|
|
}
|
|
line[i] = buf[i];
|
|
i++;
|
|
}
|
|
line[i] = '\0';
|
|
return i;
|
|
}
|
|
|
|
// Hilfsfunktion: Header-Wert extrahieren
|
|
static int get_header_value(const char* line, const char* header_name, char* value, size_t value_len)
|
|
{
|
|
size_t name_len = strlen(header_name);
|
|
|
|
// Case-insensitive Vergleich
|
|
if (strncasecmp(line, header_name, name_len) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
// Skip Header Name und ':'
|
|
const char* p = line + name_len;
|
|
while (*p && (*p == ':' || *p == ' ' || *p == '\t')) {
|
|
p++;
|
|
}
|
|
|
|
strncpy(value, p, value_len - 1);
|
|
value[value_len - 1] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
int sip_parse_message(const char* data, size_t len, sip_message_t* msg)
|
|
{
|
|
if (!data || !msg || len == 0) {
|
|
return -1;
|
|
}
|
|
|
|
memset(msg, 0, sizeof(sip_message_t));
|
|
|
|
const char* p = data;
|
|
size_t remaining = len;
|
|
char line[512];
|
|
|
|
// Erste Zeile parsen (Request-Line oder Status-Line)
|
|
int line_len = get_line(p, remaining, line, sizeof(line));
|
|
if (line_len <= 0) return -1;
|
|
|
|
p += line_len;
|
|
remaining -= line_len;
|
|
|
|
// Status-Line: "SIP/2.0 200 OK"
|
|
if (strncmp(line, "SIP/2.0 ", 8) == 0) {
|
|
msg->is_request = false;
|
|
msg->status_code = atoi(line + 8);
|
|
|
|
// Reason Phrase
|
|
const char* reason = strchr(line + 8, ' ');
|
|
if (reason) {
|
|
strncpy(msg->reason_phrase, reason + 1, sizeof(msg->reason_phrase) - 1);
|
|
}
|
|
}
|
|
// Request-Line: "INVITE sip:user@host SIP/2.0"
|
|
else {
|
|
msg->is_request = true;
|
|
|
|
// Method
|
|
if (strncmp(line, "INVITE ", 7) == 0) {
|
|
msg->method = SIP_METHOD_INVITE;
|
|
} else if (strncmp(line, "ACK ", 4) == 0) {
|
|
msg->method = SIP_METHOD_ACK;
|
|
} else if (strncmp(line, "BYE ", 4) == 0) {
|
|
msg->method = SIP_METHOD_BYE;
|
|
} else if (strncmp(line, "CANCEL ", 7) == 0) {
|
|
msg->method = SIP_METHOD_CANCEL;
|
|
} else if (strncmp(line, "REGISTER ", 9) == 0) {
|
|
msg->method = SIP_METHOD_REGISTER;
|
|
} else if (strncmp(line, "OPTIONS ", 8) == 0) {
|
|
msg->method = SIP_METHOD_OPTIONS;
|
|
} else if (strncmp(line, "INFO ", 5) == 0) {
|
|
msg->method = SIP_METHOD_INFO;
|
|
} else {
|
|
msg->method = SIP_METHOD_UNKNOWN;
|
|
}
|
|
|
|
// Request URI
|
|
char* uri_start = strchr(line, ' ');
|
|
if (uri_start) {
|
|
uri_start++;
|
|
char* uri_end = strrchr(uri_start, ' ');
|
|
if (uri_end) {
|
|
size_t uri_len = uri_end - uri_start;
|
|
if (uri_len < sizeof(msg->request_uri)) {
|
|
strncpy(msg->request_uri, uri_start, uri_len);
|
|
msg->request_uri[uri_len] = '\0';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Headers parsen
|
|
while (remaining > 0) {
|
|
line_len = get_line(p, remaining, line, sizeof(line));
|
|
if (line_len <= 0) break;
|
|
|
|
p += line_len;
|
|
remaining -= line_len;
|
|
|
|
// Leere Zeile = Ende der Header, Body beginnt
|
|
if (line[0] == '\0') {
|
|
break;
|
|
}
|
|
|
|
// Header parsen
|
|
if (get_header_value(line, "Via", msg->via, sizeof(msg->via)) == 0) continue;
|
|
if (get_header_value(line, "v", msg->via, sizeof(msg->via)) == 0) continue;
|
|
|
|
if (get_header_value(line, "From", msg->from, sizeof(msg->from)) == 0) {
|
|
sip_extract_tag(msg->from, msg->from_tag, sizeof(msg->from_tag));
|
|
continue;
|
|
}
|
|
if (get_header_value(line, "f", msg->from, sizeof(msg->from)) == 0) {
|
|
sip_extract_tag(msg->from, msg->from_tag, sizeof(msg->from_tag));
|
|
continue;
|
|
}
|
|
|
|
if (get_header_value(line, "To", msg->to, sizeof(msg->to)) == 0) {
|
|
sip_extract_tag(msg->to, msg->to_tag, sizeof(msg->to_tag));
|
|
continue;
|
|
}
|
|
if (get_header_value(line, "t", msg->to, sizeof(msg->to)) == 0) {
|
|
sip_extract_tag(msg->to, msg->to_tag, sizeof(msg->to_tag));
|
|
continue;
|
|
}
|
|
|
|
if (get_header_value(line, "Call-ID", msg->call_id, sizeof(msg->call_id)) == 0) continue;
|
|
if (get_header_value(line, "i", msg->call_id, sizeof(msg->call_id)) == 0) continue;
|
|
|
|
if (strncasecmp(line, "CSeq:", 5) == 0 || strncasecmp(line, "CSeq :", 6) == 0) {
|
|
const char* cseq_val = strchr(line, ':');
|
|
if (cseq_val) {
|
|
cseq_val++;
|
|
while (*cseq_val == ' ') cseq_val++;
|
|
msg->cseq = atoi(cseq_val);
|
|
|
|
// Method aus CSeq
|
|
const char* method = strchr(cseq_val, ' ');
|
|
if (method) {
|
|
method++;
|
|
if (strncmp(method, "INVITE", 6) == 0) msg->cseq_method = SIP_METHOD_INVITE;
|
|
else if (strncmp(method, "REGISTER", 8) == 0) msg->cseq_method = SIP_METHOD_REGISTER;
|
|
else if (strncmp(method, "BYE", 3) == 0) msg->cseq_method = SIP_METHOD_BYE;
|
|
else if (strncmp(method, "ACK", 3) == 0) msg->cseq_method = SIP_METHOD_ACK;
|
|
else if (strncmp(method, "CANCEL", 6) == 0) msg->cseq_method = SIP_METHOD_CANCEL;
|
|
else if (strncmp(method, "OPTIONS", 7) == 0) msg->cseq_method = SIP_METHOD_OPTIONS;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (get_header_value(line, "Contact", msg->contact, sizeof(msg->contact)) == 0) continue;
|
|
if (get_header_value(line, "m", msg->contact, sizeof(msg->contact)) == 0) continue;
|
|
|
|
if (strncasecmp(line, "Content-Length:", 15) == 0) {
|
|
const char* cl = strchr(line, ':');
|
|
if (cl) msg->content_length = atoi(cl + 1);
|
|
continue;
|
|
}
|
|
if (strncasecmp(line, "l:", 2) == 0) {
|
|
msg->content_length = atoi(line + 2);
|
|
continue;
|
|
}
|
|
|
|
if (get_header_value(line, "Content-Type", msg->content_type, sizeof(msg->content_type)) == 0) continue;
|
|
if (get_header_value(line, "c", msg->content_type, sizeof(msg->content_type)) == 0) continue;
|
|
|
|
if (get_header_value(line, "WWW-Authenticate", msg->www_authenticate, sizeof(msg->www_authenticate)) == 0) continue;
|
|
if (get_header_value(line, "Proxy-Authenticate", msg->proxy_authenticate, sizeof(msg->proxy_authenticate)) == 0) continue;
|
|
}
|
|
|
|
// Body (SDP)
|
|
if (remaining > 0 && msg->content_length > 0) {
|
|
size_t body_len = remaining < sizeof(msg->sdp_body) - 1 ? remaining : sizeof(msg->sdp_body) - 1;
|
|
if (body_len > (size_t)msg->content_length) {
|
|
body_len = msg->content_length;
|
|
}
|
|
strncpy(msg->sdp_body, p, body_len);
|
|
msg->sdp_body[body_len] = '\0';
|
|
|
|
if (strstr(msg->content_type, "application/sdp") != NULL) {
|
|
msg->has_sdp = true;
|
|
sip_parse_sdp(msg);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sip_extract_uri(const char* header, char* uri, size_t uri_len)
|
|
{
|
|
// Suche <uri> oder sip:uri
|
|
const char* start = strchr(header, '<');
|
|
if (start) {
|
|
start++;
|
|
const char* end = strchr(start, '>');
|
|
if (end) {
|
|
size_t len = end - start;
|
|
if (len < uri_len) {
|
|
strncpy(uri, start, len);
|
|
uri[len] = '\0';
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback: sip: suchen
|
|
start = strstr(header, "sip:");
|
|
if (!start) start = strstr(header, "sips:");
|
|
if (start) {
|
|
const char* end = start;
|
|
while (*end && *end != '>' && *end != ';' && *end != ' ') {
|
|
end++;
|
|
}
|
|
size_t len = end - start;
|
|
if (len < uri_len) {
|
|
strncpy(uri, start, len);
|
|
uri[len] = '\0';
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int sip_extract_tag(const char* header, char* tag, size_t tag_len)
|
|
{
|
|
const char* tag_start = strstr(header, "tag=");
|
|
if (!tag_start) return -1;
|
|
|
|
tag_start += 4;
|
|
const char* tag_end = tag_start;
|
|
while (*tag_end && *tag_end != ';' && *tag_end != '>' && *tag_end != ' ') {
|
|
tag_end++;
|
|
}
|
|
|
|
size_t len = tag_end - tag_start;
|
|
if (len < tag_len) {
|
|
strncpy(tag, tag_start, len);
|
|
tag[len] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int sip_extract_display_name(const char* header, char* name, size_t name_len)
|
|
{
|
|
// "Display Name" <sip:user@host>
|
|
const char* quote_start = strchr(header, '"');
|
|
if (quote_start) {
|
|
quote_start++;
|
|
const char* quote_end = strchr(quote_start, '"');
|
|
if (quote_end) {
|
|
size_t len = quote_end - quote_start;
|
|
if (len < name_len) {
|
|
strncpy(name, quote_start, len);
|
|
name[len] = '\0';
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Display Name <sip:user@host>
|
|
const char* bracket = strchr(header, '<');
|
|
if (bracket && bracket > header) {
|
|
const char* start = header;
|
|
while (*start == ' ') start++;
|
|
size_t len = bracket - start;
|
|
while (len > 0 && start[len - 1] == ' ') len--;
|
|
if (len > 0 && len < name_len) {
|
|
strncpy(name, start, len);
|
|
name[len] = '\0';
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int sip_parse_sdp(sip_message_t* msg)
|
|
{
|
|
if (!msg->has_sdp || msg->sdp_body[0] == '\0') {
|
|
return -1;
|
|
}
|
|
|
|
// RTP Port und IP aus SDP extrahieren
|
|
// m=audio <port> RTP/AVP <payload_types>
|
|
// c=IN IP4 <ip>
|
|
|
|
const char* m_line = strstr(msg->sdp_body, "m=audio ");
|
|
if (m_line) {
|
|
m_line += 8;
|
|
msg->rtp_port = (uint16_t)atoi(m_line);
|
|
|
|
// Payload Type
|
|
const char* pt = strstr(m_line, " RTP/AVP ");
|
|
if (pt) {
|
|
pt += 9;
|
|
msg->rtp_payload_type = (uint8_t)atoi(pt);
|
|
}
|
|
}
|
|
|
|
const char* c_line = strstr(msg->sdp_body, "c=IN IP4 ");
|
|
if (c_line) {
|
|
c_line += 9;
|
|
const char* end = c_line;
|
|
while (*end && *end != '\r' && *end != '\n') end++;
|
|
size_t len = end - c_line;
|
|
if (len < sizeof(msg->rtp_ip)) {
|
|
strncpy(msg->rtp_ip, c_line, len);
|
|
msg->rtp_ip[len] = '\0';
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|