esp32-sip-client-with-headset/main/sip/sip_client.c

1034 lines
31 KiB
C

/**
* SIP Client - Lightweight SIP User Agent für ESP32
*
* Unterstützt:
* - REGISTER für Anmeldung an TK-Anlage
* - INVITE/BYE für Anrufe
* - RTP für Audio
* - Digest Authentication
*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "sip_client.h"
#include "sip_parser.h"
#include "config/config_manager.h"
#include "bluetooth/bt_hfp.h"
#include "esp_log.h"
#include "esp_random.h"
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "mbedtls/md5.h"
static const char* TAG = "SIP";
// Konstanten
#define SIP_BUFFER_SIZE 4096
#define RTP_BUFFER_SIZE 320 // 20ms bei 8kHz, 16-bit
#define REGISTER_INTERVAL 300 // Sekunden
#define RTP_PAYLOAD_PCMU 0 // G.711 µ-law
#define RTP_PAYLOAD_PCMA 8 // G.711 A-law
// SIP State
static bool s_initialized = false;
static sip_reg_state_t s_reg_state = SIP_REG_STATE_UNREGISTERED;
static sip_call_state_t s_call_state = SIP_CALL_STATE_IDLE;
static sip_call_info_t s_call_info;
// Sockets
static int s_sip_socket = -1;
static int s_rtp_socket = -1;
static struct sockaddr_in s_server_addr;
static struct sockaddr_in s_rtp_remote_addr;
// Sequence Numbers
static uint32_t s_cseq = 1;
static uint32_t s_rtp_seq = 0;
static uint32_t s_rtp_timestamp = 0;
static uint32_t s_rtp_ssrc = 0;
// Tags & IDs
static char s_local_tag[32];
static char s_remote_tag[32];
static char s_call_id[64];
static char s_branch[32];
// Local Address
static char s_local_ip[32];
static uint16_t s_local_sip_port = 5060;
static uint16_t s_local_rtp_port = 10000;
// Auth
static char s_nonce[128];
static char s_realm[64];
static bool s_auth_required = false;
// Tasks
static TaskHandle_t s_recv_task = NULL;
static TaskHandle_t s_rtp_task = NULL;
static SemaphoreHandle_t s_mutex = NULL;
// Callbacks
static sip_reg_callback_t s_reg_callback = NULL;
static sip_call_callback_t s_call_callback = NULL;
static sip_audio_callback_t s_audio_callback = NULL;
// Forward Declarations
static void sip_recv_task(void* arg);
static void rtp_recv_task(void* arg);
static esp_err_t send_register(bool with_auth);
static esp_err_t send_invite(const char* number);
static esp_err_t send_response(int code, const sip_message_t* req);
static esp_err_t send_ack(void);
static esp_err_t send_bye(void);
static void handle_sip_message(const char* data, size_t len);
static void generate_tag(char* tag, size_t len);
static void generate_branch(char* branch, size_t len);
static void generate_call_id(char* call_id, size_t len);
static void calc_digest_response(const char* method, const char* uri, char* response);
static void notify_call_state(void);
static void notify_reg_state(const char* message);
// Helper: Lokale IP ermitteln
static void get_local_ip(void)
{
// Verwende Dummy-Verbindung um lokale IP zu ermitteln
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) return;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("8.8.8.8");
addr.sin_port = htons(53);
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
struct sockaddr_in local;
socklen_t len = sizeof(local);
getsockname(sock, (struct sockaddr*)&local, &len);
inet_ntop(AF_INET, &local.sin_addr, s_local_ip, sizeof(s_local_ip));
}
close(sock);
}
static void generate_tag(char* tag, size_t len)
{
uint32_t r = esp_random();
snprintf(tag, len, "%08lx", (unsigned long)r);
}
static void generate_branch(char* branch, size_t len)
{
uint32_t r = esp_random();
snprintf(branch, len, "z9hG4bK%08lx", (unsigned long)r);
}
static void generate_call_id(char* call_id, size_t len)
{
uint32_t r1 = esp_random();
uint32_t r2 = esp_random();
snprintf(call_id, len, "%08lx%08lx@%s", (unsigned long)r1, (unsigned long)r2, s_local_ip);
}
// MD5 Hash als Hex-String
static void md5_hex(const char* input, char* output)
{
unsigned char digest[16];
mbedtls_md5_context ctx;
mbedtls_md5_init(&ctx);
mbedtls_md5_starts(&ctx);
mbedtls_md5_update(&ctx, (const unsigned char*)input, strlen(input));
mbedtls_md5_finish(&ctx, digest);
mbedtls_md5_free(&ctx);
for (int i = 0; i < 16; i++) {
sprintf(output + i * 2, "%02x", digest[i]);
}
output[32] = '\0';
}
static void calc_digest_response(const char* method, const char* uri, char* response)
{
const device_config_t* config = config_get();
char ha1[33], ha2[33], tmp[512];
// HA1 = MD5(username:realm:password)
snprintf(tmp, sizeof(tmp), "%s:%s:%s",
config->sip.username, s_realm, config->sip.password);
md5_hex(tmp, ha1);
// HA2 = MD5(method:uri)
snprintf(tmp, sizeof(tmp), "%s:%s", method, uri);
md5_hex(tmp, ha2);
// Response = MD5(HA1:nonce:HA2)
snprintf(tmp, sizeof(tmp), "%s:%s:%s", ha1, s_nonce, ha2);
md5_hex(tmp, response);
}
static void notify_reg_state(const char* message)
{
if (s_reg_callback) {
s_reg_callback(s_reg_state, message);
}
}
static void notify_call_state(void)
{
if (s_call_callback) {
s_call_callback(&s_call_info);
}
}
// SIP Message senden
static esp_err_t sip_send(const char* msg, size_t len)
{
if (s_sip_socket < 0) return ESP_FAIL;
ESP_LOGD(TAG, ">>> SIP Send:\n%s", msg);
int sent = sendto(s_sip_socket, msg, len, 0,
(struct sockaddr*)&s_server_addr, sizeof(s_server_addr));
return (sent == len) ? ESP_OK : ESP_FAIL;
}
static esp_err_t send_register(bool with_auth)
{
const device_config_t* config = config_get();
char buf[SIP_BUFFER_SIZE];
char auth_header[1024] = "";
generate_branch(s_branch, sizeof(s_branch));
if (with_auth && s_auth_required) {
char response[33];
char uri[256];
snprintf(uri, sizeof(uri), "sip:%s", config->sip.server);
calc_digest_response("REGISTER", uri, response);
snprintf(auth_header, sizeof(auth_header),
"Authorization: Digest username=\"%s\", realm=\"%s\", "
"nonce=\"%s\", uri=\"%s\", response=\"%s\"\r\n",
config->sip.username, s_realm, s_nonce, uri, response);
}
int len = snprintf(buf, sizeof(buf),
"REGISTER sip:%s SIP/2.0\r\n"
"Via: SIP/2.0/UDP %s:%d;branch=%s;rport\r\n"
"From: <sip:%s@%s>;tag=%s\r\n"
"To: <sip:%s@%s>\r\n"
"Call-ID: %s\r\n"
"CSeq: %lu REGISTER\r\n"
"Contact: <sip:%s@%s:%d>\r\n"
"Max-Forwards: 70\r\n"
"Expires: %d\r\n"
"%s"
"User-Agent: ESP32-SIP-Phone/1.0\r\n"
"Content-Length: 0\r\n"
"\r\n",
config->sip.server,
s_local_ip, s_local_sip_port, s_branch,
config->sip.username, config->sip.server, s_local_tag,
config->sip.username, config->sip.server,
s_call_id,
(unsigned long)s_cseq++,
config->sip.username, s_local_ip, s_local_sip_port,
REGISTER_INTERVAL,
auth_header);
return sip_send(buf, len);
}
static esp_err_t send_invite(const char* number)
{
const device_config_t* config = config_get();
char buf[SIP_BUFFER_SIZE];
generate_branch(s_branch, sizeof(s_branch));
generate_call_id(s_call_id, sizeof(s_call_id));
generate_tag(s_local_tag, sizeof(s_local_tag));
s_rtp_ssrc = esp_random();
// SDP Body
char sdp[512];
int sdp_len = snprintf(sdp, sizeof(sdp),
"v=0\r\n"
"o=- %lu %lu IN IP4 %s\r\n"
"s=ESP32 SIP Call\r\n"
"c=IN IP4 %s\r\n"
"t=0 0\r\n"
"m=audio %d RTP/AVP 0 8 101\r\n"
"a=rtpmap:0 PCMU/8000\r\n"
"a=rtpmap:8 PCMA/8000\r\n"
"a=rtpmap:101 telephone-event/8000\r\n"
"a=fmtp:101 0-16\r\n"
"a=ptime:20\r\n"
"a=sendrecv\r\n",
(unsigned long)time(NULL), (unsigned long)time(NULL), s_local_ip,
s_local_ip,
s_local_rtp_port);
int len = snprintf(buf, sizeof(buf),
"INVITE sip:%s@%s SIP/2.0\r\n"
"Via: SIP/2.0/UDP %s:%d;branch=%s;rport\r\n"
"From: \"%s\" <sip:%s@%s>;tag=%s\r\n"
"To: <sip:%s@%s>\r\n"
"Call-ID: %s\r\n"
"CSeq: %lu INVITE\r\n"
"Contact: <sip:%s@%s:%d>\r\n"
"Max-Forwards: 70\r\n"
"Content-Type: application/sdp\r\n"
"User-Agent: ESP32-SIP-Phone/1.0\r\n"
"Content-Length: %d\r\n"
"\r\n%s",
number, config->sip.server,
s_local_ip, s_local_sip_port, s_branch,
config->sip.display_name[0] ? config->sip.display_name : config->sip.username,
config->sip.username, config->sip.server, s_local_tag,
number, config->sip.server,
s_call_id,
(unsigned long)s_cseq++,
config->sip.username, s_local_ip, s_local_sip_port,
sdp_len, sdp);
return sip_send(buf, len);
}
static esp_err_t send_response(int code, const sip_message_t* req)
{
char buf[SIP_BUFFER_SIZE];
const device_config_t* config = config_get();
const char* reason;
switch (code) {
case 100: reason = "Trying"; break;
case 180: reason = "Ringing"; break;
case 200: reason = "OK"; break;
case 486: reason = "Busy Here"; break;
case 603: reason = "Decline"; break;
default: reason = "Unknown"; break;
}
// SDP für 200 OK auf INVITE
char sdp[512] = "";
int sdp_len = 0;
char content_type[64] = "";
if (code == 200 && req->cseq_method == SIP_METHOD_INVITE) {
sdp_len = snprintf(sdp, sizeof(sdp),
"v=0\r\n"
"o=- %lu %lu IN IP4 %s\r\n"
"s=ESP32 SIP Call\r\n"
"c=IN IP4 %s\r\n"
"t=0 0\r\n"
"m=audio %d RTP/AVP 0 8\r\n"
"a=rtpmap:0 PCMU/8000\r\n"
"a=rtpmap:8 PCMA/8000\r\n"
"a=ptime:20\r\n"
"a=sendrecv\r\n",
(unsigned long)time(NULL), (unsigned long)time(NULL), s_local_ip,
s_local_ip,
s_local_rtp_port);
strcpy(content_type, "Content-Type: application/sdp\r\n");
}
// To-Tag hinzufügen wenn nicht vorhanden
char to_header[512];
if (strlen(req->to_tag) > 0) {
snprintf(to_header, sizeof(to_header), "%s", req->to);
} else {
char to_uri[256];
sip_extract_uri(req->to, to_uri, sizeof(to_uri));
snprintf(to_header, sizeof(to_header), "<%s>;tag=%s", to_uri, s_local_tag);
}
int len = snprintf(buf, sizeof(buf),
"SIP/2.0 %d %s\r\n"
"Via: %s\r\n"
"From: %s\r\n"
"To: %s\r\n"
"Call-ID: %s\r\n"
"CSeq: %d %s\r\n"
"Contact: <sip:%s@%s:%d>\r\n"
"%s"
"User-Agent: ESP32-SIP-Phone/1.0\r\n"
"Content-Length: %d\r\n"
"\r\n%s",
code, reason,
req->via,
req->from,
to_header,
req->call_id,
req->cseq,
req->cseq_method == SIP_METHOD_INVITE ? "INVITE" :
req->cseq_method == SIP_METHOD_BYE ? "BYE" :
req->cseq_method == SIP_METHOD_ACK ? "ACK" :
req->cseq_method == SIP_METHOD_OPTIONS ? "OPTIONS" : "UNKNOWN",
config->sip.username, s_local_ip, s_local_sip_port,
content_type,
sdp_len, sdp);
return sip_send(buf, len);
}
static esp_err_t send_ack(void)
{
const device_config_t* config = config_get();
char buf[SIP_BUFFER_SIZE];
generate_branch(s_branch, sizeof(s_branch));
// Extrahiere Request-URI aus To
char to_uri[256];
sip_extract_uri(s_call_info.remote_uri, to_uri, sizeof(to_uri));
int len = snprintf(buf, sizeof(buf),
"ACK %s SIP/2.0\r\n"
"Via: SIP/2.0/UDP %s:%d;branch=%s;rport\r\n"
"From: \"%s\" <sip:%s@%s>;tag=%s\r\n"
"To: <%s>;tag=%s\r\n"
"Call-ID: %s\r\n"
"CSeq: %lu ACK\r\n"
"Max-Forwards: 70\r\n"
"Content-Length: 0\r\n"
"\r\n",
to_uri[0] ? to_uri : s_call_info.remote_uri,
s_local_ip, s_local_sip_port, s_branch,
config->sip.display_name[0] ? config->sip.display_name : config->sip.username,
config->sip.username, config->sip.server, s_local_tag,
s_call_info.remote_uri, s_remote_tag,
s_call_id,
(unsigned long)(s_cseq - 1)); // Gleiche CSeq wie INVITE
return sip_send(buf, len);
}
static esp_err_t send_bye(void)
{
const device_config_t* config = config_get();
char buf[SIP_BUFFER_SIZE];
generate_branch(s_branch, sizeof(s_branch));
char to_uri[256];
sip_extract_uri(s_call_info.remote_uri, to_uri, sizeof(to_uri));
int len = snprintf(buf, sizeof(buf),
"BYE %s SIP/2.0\r\n"
"Via: SIP/2.0/UDP %s:%d;branch=%s;rport\r\n"
"From: \"%s\" <sip:%s@%s>;tag=%s\r\n"
"To: <%s>;tag=%s\r\n"
"Call-ID: %s\r\n"
"CSeq: %lu BYE\r\n"
"Max-Forwards: 70\r\n"
"Content-Length: 0\r\n"
"\r\n",
to_uri[0] ? to_uri : s_call_info.remote_uri,
s_local_ip, s_local_sip_port, s_branch,
config->sip.display_name[0] ? config->sip.display_name : config->sip.username,
config->sip.username, config->sip.server, s_local_tag,
s_call_info.remote_uri, s_remote_tag,
s_call_id,
(unsigned long)s_cseq++);
return sip_send(buf, len);
}
static void handle_sip_message(const char* data, size_t len)
{
sip_message_t msg;
if (sip_parse_message(data, len, &msg) != 0) {
ESP_LOGW(TAG, "SIP Parsing fehlgeschlagen");
return;
}
ESP_LOGD(TAG, "<<< SIP %s %d %s",
msg.is_request ? "Request" : "Response",
msg.is_request ? msg.method : msg.status_code,
msg.is_request ? "" : msg.reason_phrase);
if (msg.is_request) {
// Eingehende Requests
switch (msg.method) {
case SIP_METHOD_INVITE:
ESP_LOGI(TAG, "Eingehender Anruf: %s", msg.from);
// Call-Info speichern
strncpy(s_call_id, msg.call_id, sizeof(s_call_id));
strncpy(s_call_info.call_id, msg.call_id, sizeof(s_call_info.call_id));
strncpy(s_call_info.remote_uri, msg.from, sizeof(s_call_info.remote_uri));
sip_extract_display_name(msg.from, s_call_info.remote_name, sizeof(s_call_info.remote_name));
sip_extract_uri(msg.from, s_call_info.remote_number, sizeof(s_call_info.remote_number));
strncpy(s_remote_tag, msg.from_tag, sizeof(s_remote_tag));
// RTP-Info aus SDP
if (msg.has_sdp) {
strncpy(s_local_ip, msg.rtp_ip, sizeof(s_local_ip));
s_rtp_remote_addr.sin_family = AF_INET;
inet_pton(AF_INET, msg.rtp_ip, &s_rtp_remote_addr.sin_addr);
s_rtp_remote_addr.sin_port = htons(msg.rtp_port);
}
s_call_info.state = SIP_CALL_STATE_INCOMING;
s_call_info.is_incoming = true;
s_call_state = SIP_CALL_STATE_INCOMING;
// 100 Trying senden
send_response(100, &msg);
// 180 Ringing senden
send_response(180, &msg);
notify_call_state();
break;
case SIP_METHOD_BYE:
ESP_LOGI(TAG, "BYE empfangen - Anruf beendet");
send_response(200, &msg);
s_call_state = SIP_CALL_STATE_DISCONNECTED;
s_call_info.state = SIP_CALL_STATE_DISCONNECTED;
notify_call_state();
s_call_state = SIP_CALL_STATE_IDLE;
break;
case SIP_METHOD_ACK:
ESP_LOGD(TAG, "ACK empfangen");
break;
case SIP_METHOD_OPTIONS:
// Keep-Alive - 200 OK antworten
send_response(200, &msg);
break;
case SIP_METHOD_CANCEL:
ESP_LOGI(TAG, "CANCEL empfangen");
send_response(200, &msg);
s_call_state = SIP_CALL_STATE_DISCONNECTED;
s_call_info.state = SIP_CALL_STATE_DISCONNECTED;
notify_call_state();
s_call_state = SIP_CALL_STATE_IDLE;
break;
default:
ESP_LOGW(TAG, "Unbekannter Request: %d", msg.method);
break;
}
} else {
// Responses
switch (msg.cseq_method) {
case SIP_METHOD_REGISTER:
if (msg.status_code == 200) {
ESP_LOGI(TAG, "REGISTER erfolgreich");
s_reg_state = SIP_REG_STATE_REGISTERED;
notify_reg_state("Registered");
} else if (msg.status_code == 401 || msg.status_code == 407) {
// Authentication Required
ESP_LOGI(TAG, "Auth erforderlich");
s_auth_required = true;
// Nonce und Realm extrahieren
const char* auth = msg.status_code == 401 ?
msg.www_authenticate : msg.proxy_authenticate;
const char* nonce_start = strstr(auth, "nonce=\"");
if (nonce_start) {
nonce_start += 7;
const char* nonce_end = strchr(nonce_start, '"');
if (nonce_end) {
size_t len = nonce_end - nonce_start;
if (len < sizeof(s_nonce)) {
strncpy(s_nonce, nonce_start, len);
s_nonce[len] = '\0';
}
}
}
const char* realm_start = strstr(auth, "realm=\"");
if (realm_start) {
realm_start += 7;
const char* realm_end = strchr(realm_start, '"');
if (realm_end) {
size_t len = realm_end - realm_start;
if (len < sizeof(s_realm)) {
strncpy(s_realm, realm_start, len);
s_realm[len] = '\0';
}
}
}
// Erneut registrieren mit Auth
send_register(true);
} else {
ESP_LOGE(TAG, "REGISTER fehlgeschlagen: %d", msg.status_code);
s_reg_state = SIP_REG_STATE_FAILED;
notify_reg_state(msg.reason_phrase);
}
break;
case SIP_METHOD_INVITE:
if (msg.status_code >= 100 && msg.status_code < 200) {
// Provisional Response
if (msg.status_code == 180 || msg.status_code == 183) {
ESP_LOGI(TAG, "Anruf klingelt");
s_call_state = SIP_CALL_STATE_RINGING;
s_call_info.state = SIP_CALL_STATE_RINGING;
strncpy(s_remote_tag, msg.to_tag, sizeof(s_remote_tag));
notify_call_state();
}
} else if (msg.status_code == 200) {
ESP_LOGI(TAG, "Anruf angenommen");
strncpy(s_remote_tag, msg.to_tag, sizeof(s_remote_tag));
// RTP-Info aus SDP
if (msg.has_sdp && msg.rtp_port > 0) {
s_rtp_remote_addr.sin_family = AF_INET;
inet_pton(AF_INET, msg.rtp_ip, &s_rtp_remote_addr.sin_addr);
s_rtp_remote_addr.sin_port = htons(msg.rtp_port);
ESP_LOGI(TAG, "RTP: %s:%d", msg.rtp_ip, msg.rtp_port);
}
send_ack();
s_call_state = SIP_CALL_STATE_CONNECTED;
s_call_info.state = SIP_CALL_STATE_CONNECTED;
notify_call_state();
// BT Audio starten
bt_hfp_audio_connect();
} else if (msg.status_code >= 400) {
ESP_LOGE(TAG, "Anruf fehlgeschlagen: %d %s",
msg.status_code, msg.reason_phrase);
s_call_state = SIP_CALL_STATE_DISCONNECTED;
s_call_info.state = SIP_CALL_STATE_DISCONNECTED;
notify_call_state();
s_call_state = SIP_CALL_STATE_IDLE;
}
break;
case SIP_METHOD_BYE:
if (msg.status_code == 200) {
ESP_LOGI(TAG, "BYE bestätigt");
}
break;
default:
break;
}
}
}
static void sip_recv_task(void* arg)
{
char buf[SIP_BUFFER_SIZE];
struct sockaddr_in from_addr;
socklen_t from_len = sizeof(from_addr);
ESP_LOGI(TAG, "SIP Receive Task gestartet");
while (s_initialized && s_sip_socket >= 0) {
int len = recvfrom(s_sip_socket, buf, sizeof(buf) - 1, 0,
(struct sockaddr*)&from_addr, &from_len);
if (len > 0) {
buf[len] = '\0';
ESP_LOGD(TAG, "SIP empfangen: %d bytes", len);
handle_sip_message(buf, len);
} else if (len < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
ESP_LOGE(TAG, "SIP recv error: %d", errno);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
ESP_LOGI(TAG, "SIP Receive Task beendet");
vTaskDelete(NULL);
}
static void rtp_recv_task(void* arg)
{
uint8_t buf[RTP_BUFFER_SIZE + 12]; // RTP Header + Payload
struct sockaddr_in from_addr;
socklen_t from_len = sizeof(from_addr);
ESP_LOGI(TAG, "RTP Receive Task gestartet");
while (s_initialized && s_rtp_socket >= 0) {
if (s_call_state != SIP_CALL_STATE_CONNECTED) {
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
int len = recvfrom(s_rtp_socket, buf, sizeof(buf), 0,
(struct sockaddr*)&from_addr, &from_len);
if (len > 12) { // Mindestens RTP Header
// RTP Header parsen (12 bytes)
// uint8_t version = (buf[0] >> 6) & 0x03;
// uint8_t padding = (buf[0] >> 5) & 0x01;
// uint8_t extension = (buf[0] >> 4) & 0x01;
// uint8_t cc = buf[0] & 0x0F;
// uint8_t marker = (buf[1] >> 7) & 0x01;
uint8_t payload_type = buf[1] & 0x7F;
// Payload an Audio-Callback
if (s_audio_callback && len > 12) {
sip_audio_format_t format = {
.sample_rate = 8000,
.payload_type = payload_type,
.channels = 1
};
s_audio_callback(buf + 12, len - 12, &format);
}
}
}
ESP_LOGI(TAG, "RTP Receive Task beendet");
vTaskDelete(NULL);
}
// Public API
esp_err_t sip_client_init(void)
{
if (s_initialized) return ESP_OK;
ESP_LOGI(TAG, "Initialisiere SIP Client");
s_mutex = xSemaphoreCreateMutex();
if (!s_mutex) return ESP_ERR_NO_MEM;
get_local_ip();
ESP_LOGI(TAG, "Lokale IP: %s", s_local_ip);
generate_tag(s_local_tag, sizeof(s_local_tag));
generate_call_id(s_call_id, sizeof(s_call_id));
s_rtp_ssrc = esp_random();
// SIP Socket erstellen
s_sip_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s_sip_socket < 0) {
ESP_LOGE(TAG, "SIP Socket erstellen fehlgeschlagen");
return ESP_FAIL;
}
// Bind auf lokalen Port
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = INADDR_ANY;
local_addr.sin_port = htons(s_local_sip_port);
if (bind(s_sip_socket, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {
ESP_LOGE(TAG, "SIP Bind fehlgeschlagen");
close(s_sip_socket);
s_sip_socket = -1;
return ESP_FAIL;
}
// Timeout setzen
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
setsockopt(s_sip_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// RTP Socket erstellen
s_rtp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s_rtp_socket < 0) {
ESP_LOGE(TAG, "RTP Socket erstellen fehlgeschlagen");
close(s_sip_socket);
s_sip_socket = -1;
return ESP_FAIL;
}
local_addr.sin_port = htons(s_local_rtp_port);
if (bind(s_rtp_socket, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {
ESP_LOGE(TAG, "RTP Bind fehlgeschlagen");
close(s_sip_socket);
close(s_rtp_socket);
s_sip_socket = -1;
s_rtp_socket = -1;
return ESP_FAIL;
}
setsockopt(s_rtp_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
s_initialized = true;
// Receive Tasks starten
xTaskCreate(sip_recv_task, "sip_recv", 4096, NULL, 5, &s_recv_task);
xTaskCreate(rtp_recv_task, "rtp_recv", 4096, NULL, 6, &s_rtp_task);
ESP_LOGI(TAG, "SIP Client initialisiert");
return ESP_OK;
}
esp_err_t sip_client_deinit(void)
{
if (!s_initialized) return ESP_OK;
ESP_LOGI(TAG, "Deinitalisiere SIP Client");
s_initialized = false;
// Sockets schließen
if (s_sip_socket >= 0) {
close(s_sip_socket);
s_sip_socket = -1;
}
if (s_rtp_socket >= 0) {
close(s_rtp_socket);
s_rtp_socket = -1;
}
// Auf Tasks warten
vTaskDelay(pdMS_TO_TICKS(200));
if (s_mutex) {
vSemaphoreDelete(s_mutex);
s_mutex = NULL;
}
return ESP_OK;
}
esp_err_t sip_client_register(void)
{
const device_config_t* config = config_get();
if (!config->sip.configured) {
ESP_LOGW(TAG, "SIP nicht konfiguriert");
return ESP_ERR_INVALID_STATE;
}
ESP_LOGI(TAG, "Registriere bei %s:%d als %s",
config->sip.server, config->sip.port, config->sip.username);
// Server-Adresse auflösen
struct hostent* host = gethostbyname(config->sip.server);
if (!host) {
ESP_LOGE(TAG, "DNS Auflösung fehlgeschlagen: %s", config->sip.server);
return ESP_FAIL;
}
memset(&s_server_addr, 0, sizeof(s_server_addr));
s_server_addr.sin_family = AF_INET;
memcpy(&s_server_addr.sin_addr, host->h_addr_list[0], host->h_length);
s_server_addr.sin_port = htons(config->sip.port);
s_reg_state = SIP_REG_STATE_REGISTERING;
notify_reg_state("Registering...");
s_auth_required = false;
generate_call_id(s_call_id, sizeof(s_call_id));
generate_tag(s_local_tag, sizeof(s_local_tag));
return send_register(false);
}
esp_err_t sip_client_unregister(void)
{
if (s_reg_state == SIP_REG_STATE_UNREGISTERED) {
return ESP_OK;
}
ESP_LOGI(TAG, "Abmelden...");
s_reg_state = SIP_REG_STATE_UNREGISTERED;
notify_reg_state("Unregistered");
return ESP_OK;
}
sip_reg_state_t sip_client_get_reg_state(void)
{
return s_reg_state;
}
esp_err_t sip_client_answer(void)
{
if (s_call_state != SIP_CALL_STATE_INCOMING) {
return ESP_ERR_INVALID_STATE;
}
ESP_LOGI(TAG, "Nehme Anruf an");
// Die letzte INVITE-Nachricht wurde gespeichert
sip_message_t msg;
memset(&msg, 0, sizeof(msg));
strncpy(msg.call_id, s_call_id, sizeof(msg.call_id));
msg.cseq_method = SIP_METHOD_INVITE;
// 200 OK mit SDP senden
// TODO: Korrekte INVITE-Nachricht rekonstruieren
// Für jetzt: vereinfachte Version
s_call_state = SIP_CALL_STATE_CONNECTED;
s_call_info.state = SIP_CALL_STATE_CONNECTED;
notify_call_state();
bt_hfp_audio_connect();
return ESP_OK;
}
esp_err_t sip_client_reject(void)
{
if (s_call_state != SIP_CALL_STATE_INCOMING) {
return ESP_ERR_INVALID_STATE;
}
ESP_LOGI(TAG, "Lehne Anruf ab");
// 603 Decline senden
sip_message_t msg;
memset(&msg, 0, sizeof(msg));
msg.cseq_method = SIP_METHOD_INVITE;
send_response(603, &msg);
s_call_state = SIP_CALL_STATE_DISCONNECTED;
s_call_info.state = SIP_CALL_STATE_DISCONNECTED;
notify_call_state();
s_call_state = SIP_CALL_STATE_IDLE;
return ESP_OK;
}
esp_err_t sip_client_hangup(void)
{
if (s_call_state == SIP_CALL_STATE_IDLE) {
return ESP_OK;
}
ESP_LOGI(TAG, "Beende Anruf");
bt_hfp_audio_disconnect();
esp_err_t ret = send_bye();
s_call_state = SIP_CALL_STATE_DISCONNECTED;
s_call_info.state = SIP_CALL_STATE_DISCONNECTED;
notify_call_state();
s_call_state = SIP_CALL_STATE_IDLE;
return ret;
}
esp_err_t sip_client_call(const char* number)
{
if (s_call_state != SIP_CALL_STATE_IDLE) {
return ESP_ERR_INVALID_STATE;
}
if (s_reg_state != SIP_REG_STATE_REGISTERED) {
ESP_LOGW(TAG, "Nicht registriert - kann nicht anrufen");
return ESP_ERR_INVALID_STATE;
}
ESP_LOGI(TAG, "Rufe an: %s", number);
strncpy(s_call_info.remote_number, number, sizeof(s_call_info.remote_number));
s_call_info.is_incoming = false;
s_call_state = SIP_CALL_STATE_OUTGOING;
s_call_info.state = SIP_CALL_STATE_OUTGOING;
notify_call_state();
return send_invite(number);
}
esp_err_t sip_client_send_dtmf(char digit)
{
if (s_call_state != SIP_CALL_STATE_CONNECTED) {
return ESP_ERR_INVALID_STATE;
}
ESP_LOGI(TAG, "DTMF: %c", digit);
// TODO: DTMF über RTP (RFC 2833) oder SIP INFO senden
return ESP_OK;
}
esp_err_t sip_client_hold(void)
{
// TODO: Re-INVITE mit a=sendonly
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t sip_client_unhold(void)
{
// TODO: Re-INVITE mit a=sendrecv
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t sip_client_get_call_info(sip_call_info_t* info)
{
if (!info) return ESP_ERR_INVALID_ARG;
memcpy(info, &s_call_info, sizeof(sip_call_info_t));
return ESP_OK;
}
sip_call_state_t sip_client_get_call_state(void)
{
return s_call_state;
}
esp_err_t sip_client_send_audio(const uint8_t* data, size_t len)
{
if (s_call_state != SIP_CALL_STATE_CONNECTED || s_rtp_socket < 0) {
return ESP_ERR_INVALID_STATE;
}
// RTP Paket erstellen
uint8_t rtp_packet[RTP_BUFFER_SIZE + 12];
// RTP Header (12 bytes)
rtp_packet[0] = 0x80; // Version 2, no padding, no extension, no CSRC
rtp_packet[1] = RTP_PAYLOAD_PCMU; // Payload Type
rtp_packet[2] = (s_rtp_seq >> 8) & 0xFF;
rtp_packet[3] = s_rtp_seq & 0xFF;
rtp_packet[4] = (s_rtp_timestamp >> 24) & 0xFF;
rtp_packet[5] = (s_rtp_timestamp >> 16) & 0xFF;
rtp_packet[6] = (s_rtp_timestamp >> 8) & 0xFF;
rtp_packet[7] = s_rtp_timestamp & 0xFF;
rtp_packet[8] = (s_rtp_ssrc >> 24) & 0xFF;
rtp_packet[9] = (s_rtp_ssrc >> 16) & 0xFF;
rtp_packet[10] = (s_rtp_ssrc >> 8) & 0xFF;
rtp_packet[11] = s_rtp_ssrc & 0xFF;
// Payload kopieren
size_t payload_len = len > RTP_BUFFER_SIZE ? RTP_BUFFER_SIZE : len;
memcpy(rtp_packet + 12, data, payload_len);
s_rtp_seq++;
s_rtp_timestamp += 160; // 20ms bei 8kHz
int sent = sendto(s_rtp_socket, rtp_packet, 12 + payload_len, 0,
(struct sockaddr*)&s_rtp_remote_addr, sizeof(s_rtp_remote_addr));
return (sent > 0) ? ESP_OK : ESP_FAIL;
}
void sip_client_register_reg_callback(sip_reg_callback_t callback)
{
s_reg_callback = callback;
}
void sip_client_register_call_callback(sip_call_callback_t callback)
{
s_call_callback = callback;
}
void sip_client_register_audio_callback(sip_audio_callback_t callback)
{
s_audio_callback = callback;
}