first commit
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user