first commit

This commit is contained in:
Stefan Hacker
2026-01-29 20:31:37 +01:00
commit 0b09765013
30 changed files with 6865 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+139
View File
@@ -0,0 +1,139 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
// SIP-Registrierungsstatus
typedef enum {
SIP_REG_STATE_UNREGISTERED = 0,
SIP_REG_STATE_REGISTERING,
SIP_REG_STATE_REGISTERED,
SIP_REG_STATE_FAILED
} sip_reg_state_t;
// Anrufstatus
typedef enum {
SIP_CALL_STATE_IDLE = 0,
SIP_CALL_STATE_INCOMING, // Eingehender Anruf
SIP_CALL_STATE_OUTGOING, // Ausgehender Anruf
SIP_CALL_STATE_RINGING, // Klingelt
SIP_CALL_STATE_CONNECTED, // Verbunden
SIP_CALL_STATE_HOLD, // Gehalten
SIP_CALL_STATE_DISCONNECTING,
SIP_CALL_STATE_DISCONNECTED
} sip_call_state_t;
// Anrufinformationen
typedef struct {
char call_id[64];
char remote_uri[128]; // SIP URI des Gegenübers
char remote_name[64]; // Display Name
char remote_number[32]; // Telefonnummer
sip_call_state_t state;
uint32_t duration_sec; // Anrufdauer in Sekunden
bool is_incoming;
} sip_call_info_t;
// RTP Audio Format
typedef struct {
uint32_t sample_rate; // Typisch 8000 oder 16000 Hz
uint8_t payload_type; // 0=PCMU, 8=PCMA, etc.
uint8_t channels;
} sip_audio_format_t;
// Callbacks
typedef void (*sip_reg_callback_t)(sip_reg_state_t state, const char* message);
typedef void (*sip_call_callback_t)(const sip_call_info_t* call_info);
typedef void (*sip_audio_callback_t)(const uint8_t* data, size_t len, const sip_audio_format_t* format);
/**
* Initialisiert den SIP-Client
*/
esp_err_t sip_client_init(void);
/**
* Deinitalisiert den SIP-Client
*/
esp_err_t sip_client_deinit(void);
/**
* Registriert bei der TK-Anlage
* Verwendet Konfiguration aus config_manager
*/
esp_err_t sip_client_register(void);
/**
* Meldet sich von der TK-Anlage ab
*/
esp_err_t sip_client_unregister(void);
/**
* Gibt den Registrierungsstatus zurück
*/
sip_reg_state_t sip_client_get_reg_state(void);
/**
* Nimmt einen eingehenden Anruf an
*/
esp_err_t sip_client_answer(void);
/**
* Lehnt einen eingehenden Anruf ab
*/
esp_err_t sip_client_reject(void);
/**
* Beendet den aktuellen Anruf
*/
esp_err_t sip_client_hangup(void);
/**
* Tätigt einen ausgehenden Anruf
*/
esp_err_t sip_client_call(const char* number);
/**
* Sendet DTMF-Töne
*/
esp_err_t sip_client_send_dtmf(char digit);
/**
* Setzt den Anruf auf Hold
*/
esp_err_t sip_client_hold(void);
/**
* Holt den Anruf von Hold zurück
*/
esp_err_t sip_client_unhold(void);
/**
* Gibt Informationen über den aktuellen Anruf zurück
*/
esp_err_t sip_client_get_call_info(sip_call_info_t* info);
/**
* Gibt den aktuellen Anrufstatus zurück
*/
sip_call_state_t sip_client_get_call_state(void);
/**
* Sendet Audio-Daten (RTP)
*/
esp_err_t sip_client_send_audio(const uint8_t* data, size_t len);
/**
* Registriert Callbacks
*/
void sip_client_register_reg_callback(sip_reg_callback_t callback);
void sip_client_register_call_callback(sip_call_callback_t callback);
void sip_client_register_audio_callback(sip_audio_callback_t callback);
#ifdef __cplusplus
}
#endif
+345
View File
@@ -0,0 +1,345 @@
/**
* 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;
}
+92
View File
@@ -0,0 +1,92 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
// SIP Methods
typedef enum {
SIP_METHOD_UNKNOWN = 0,
SIP_METHOD_INVITE,
SIP_METHOD_ACK,
SIP_METHOD_BYE,
SIP_METHOD_CANCEL,
SIP_METHOD_REGISTER,
SIP_METHOD_OPTIONS,
SIP_METHOD_INFO,
SIP_METHOD_UPDATE,
SIP_METHOD_PRACK
} sip_method_t;
// Geparste SIP-Nachricht
typedef struct {
bool is_request; // true = Request, false = Response
// Request Line
sip_method_t method;
char request_uri[128];
// Status Line (Response)
int status_code;
char reason_phrase[64];
// Headers
char via[256];
char from[256];
char from_tag[64];
char to[256];
char to_tag[64];
char call_id[128];
int cseq;
sip_method_t cseq_method;
char contact[256];
int content_length;
char content_type[64];
// Authorization
char www_authenticate[512];
char proxy_authenticate[512];
// SDP Body
char sdp_body[2048];
bool has_sdp;
// RTP Info aus SDP
char rtp_ip[32];
uint16_t rtp_port;
uint8_t rtp_payload_type;
} sip_message_t;
/**
* Parst eine SIP-Nachricht
*/
int sip_parse_message(const char* data, size_t len, sip_message_t* msg);
/**
* Extrahiert URI aus Header
*/
int sip_extract_uri(const char* header, char* uri, size_t uri_len);
/**
* Extrahiert Tag aus Header
*/
int sip_extract_tag(const char* header, char* tag, size_t tag_len);
/**
* Extrahiert Display-Name aus Header
*/
int sip_extract_display_name(const char* header, char* name, size_t name_len);
/**
* Parst SDP und extrahiert RTP-Info
*/
int sip_parse_sdp(sip_message_t* msg);
#ifdef __cplusplus
}
#endif