esp32-sip-client-with-headset/main/sip/sip_parser.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;
}