ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
mdns.c
Go to the documentation of this file.
1
9#include "network/mdns/mdns.h"
10#include "common.h"
11#include "platform/socket.h"
12#include <string.h>
13#include <stdio.h>
14#include <mdns.h>
15#include <netinet/in.h> /* For struct sockaddr_in, sockaddr_in6 */
16
26
28#define MDNS_BUFFER_SIZE (4 * 1024)
29
32 if (!mdns) {
33 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mDNS context");
34 return NULL;
35 }
36
37 memset(mdns, 0, sizeof(asciichat_mdns_t));
38
39 /* Allocate I/O buffer for mDNS packets */
41 if (!mdns->buffer) {
42 SAFE_FREE(mdns);
43 SET_ERRNO(ERROR_MEMORY, "Failed to allocate mDNS buffer");
44 return NULL;
45 }
47
48 /* Open IPv4 mDNS socket */
49 mdns->socket_fd = mdns_socket_open_ipv4(NULL);
50 if (mdns->socket_fd < 0) {
51 SAFE_FREE(mdns->buffer);
52 SAFE_FREE(mdns);
53 SET_ERRNO(ERROR_NETWORK_BIND, "Failed to open mDNS socket");
54 return NULL;
55 }
56
57 log_info("mDNS context initialized (socket: %d, buffer: %zu bytes)", mdns->socket_fd, mdns->buffer_capacity);
58 return mdns;
59}
60
62 if (!mdns) {
63 return;
64 }
65
66 if (mdns->socket_fd >= 0) {
67 mdns_socket_close(mdns->socket_fd);
68 }
69
70 SAFE_FREE(mdns->buffer);
71 SAFE_FREE(mdns);
72 log_info("mDNS context shutdown");
73}
74
76 if (!mdns || !service) {
77 return SET_ERRNO(ERROR_INVALID_PARAM, "mDNS context or service is NULL");
78 }
79
80 if (!service->name || !service->type || !service->host) {
81 return SET_ERRNO(ERROR_INVALID_PARAM, "Service name, type, or host is NULL");
82 }
83
84 log_info("Advertising mDNS service: %s (%s:%d)", service->name, service->host, service->port);
85
86 /* TODO: Implement actual advertisement using mdns library
87 * This will involve creating service records and sending announcements
88 * The mdns library provides mdns_announce_* functions for this
89 */
90 return ASCIICHAT_OK;
91}
92
94 if (!mdns || !service_name) {
95 return SET_ERRNO(ERROR_INVALID_PARAM, "mDNS context or service name is NULL");
96 }
97
98 log_info("Stopped advertising service: %s", service_name);
99
100 /* TODO: Implement actual unadvertisement
101 * This will involve sending goodbye records with TTL=0
102 */
103 return ASCIICHAT_OK;
104}
105
112static int mdns_record_callback(int sock, const struct sockaddr *from, size_t addrlen, mdns_entry_type_t entry,
113 uint16_t query_id, uint16_t rtype, uint16_t rclass, uint32_t ttl, const void *data,
114 size_t size, size_t name_offset, size_t name_length, size_t record_offset,
115 size_t record_length, void *user_data) {
116 asciichat_mdns_t *mdns = (asciichat_mdns_t *)user_data;
117
118 if (!mdns || !mdns->callback) {
119 return 0;
120 }
121
122 /* Only process answers */
123 if (entry != MDNS_ENTRYTYPE_ANSWER) {
124 return 0;
125 }
126
128 memset(&discovery, 0, sizeof(discovery));
129 discovery.ttl = ttl;
130
131 /* Extract service type and name from DNS name format
132 * For PTR records, data contains the target name (service instance name)
133 */
134 size_t offset = name_offset;
135 mdns_string_extract(data, size, &offset, discovery.type, sizeof(discovery.type));
136
137 /* Parse record based on type */
138 switch (rtype) {
139 case MDNS_RECORDTYPE_PTR: {
140 /* PTR record: points to a service instance (e.g., "MyChat" for "_ascii-chat._tcp.local")
141 * The record data contains the target name (the service instance name)
142 */
143 offset = record_offset;
144 mdns_string_extract(data, size, &offset, discovery.name, sizeof(discovery.name));
145 log_debug("mDNS PTR: %s -> %s (TTL: %u)", discovery.type, discovery.name, ttl);
146 break;
147 }
148
149 case MDNS_RECORDTYPE_SRV: {
150 /* SRV record: service details (port, target host)
151 * mdns_record_parse_srv extracts the target hostname into a buffer
152 * and also contains priority, weight, port in the returned structure
153 */
154 mdns_record_srv_t srv =
155 mdns_record_parse_srv(data, size, record_offset, record_length, discovery.host, sizeof(discovery.host));
156 discovery.port = srv.port;
157 log_debug("mDNS SRV: %s -> %s:%u (TTL: %u)", discovery.type, discovery.host, srv.port, ttl);
158 break;
159 }
160
161 case MDNS_RECORDTYPE_A: {
162 /* A record: IPv4 address
163 * mdns_record_parse_a fills a sockaddr_in structure with the address
164 */
165 struct sockaddr_in addr_in;
166 mdns_record_parse_a(data, size, record_offset, record_length, &addr_in);
167
168 /* Convert network address to dotted decimal notation */
169 uint8_t *bytes = (uint8_t *)&addr_in.sin_addr;
170 snprintf(discovery.ipv4, sizeof(discovery.ipv4), "%d.%d.%d.%d", bytes[0], bytes[1], bytes[2], bytes[3]);
171 log_debug("mDNS A: %s -> %s (TTL: %u)", discovery.type, discovery.ipv4, ttl);
172 break;
173 }
174
175 case MDNS_RECORDTYPE_AAAA: {
176 /* AAAA record: IPv6 address
177 * mdns_record_parse_aaaa fills a sockaddr_in6 structure with the address
178 */
179 struct sockaddr_in6 addr_in6;
180 mdns_record_parse_aaaa(data, size, record_offset, record_length, &addr_in6);
181
182 /* Convert IPv6 address to colon-separated hex notation */
183 const uint8_t *bytes = (const uint8_t *)&addr_in6.sin6_addr;
184 snprintf(discovery.ipv6, sizeof(discovery.ipv6),
185 "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", bytes[0], bytes[1], bytes[2],
186 bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12],
187 bytes[13], bytes[14], bytes[15]);
188 log_debug("mDNS AAAA: %s -> %s (TTL: %u)", discovery.type, discovery.ipv6, ttl);
189 break;
190 }
191
192 case MDNS_RECORDTYPE_TXT: {
193 /* TXT record: service properties (key=value pairs)
194 * For simplicity, we skip individual TXT parsing for now
195 * Could be extended to extract and store specific properties
196 */
197 log_debug("mDNS TXT: %s (TTL: %u)", discovery.type, ttl);
198 discovery.txt[0] = '\0';
199 break;
200 }
201
202 default:
203 log_debug("mDNS unknown record type: %u", rtype);
204 return 0;
205 }
206
207 /* Call the user callback with discovered service */
208 mdns->callback(&discovery, mdns->callback_data);
209
210 (void)sock;
211 (void)query_id;
212 (void)rclass;
213 (void)addrlen;
214 (void)from;
215 (void)name_length;
216 return 0;
217}
218
220 asciichat_mdns_discovery_callback_fn callback, void *user_data) {
221 if (!mdns || !service_type || !callback) {
222 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid mDNS query parameters");
223 }
224
225 /* Validate service type is non-empty and has minimum length
226 * Prevents underflow bug in mdns library when processing empty strings
227 */
228 size_t service_type_len = strlen(service_type);
229 if (service_type_len == 0) {
230 return SET_ERRNO(ERROR_INVALID_PARAM, "Service type cannot be empty");
231 }
232
233 mdns->callback = callback;
234 mdns->callback_data = user_data;
235
236 log_info("Starting mDNS query for: %s", service_type);
237
238 /* Send PTR query for service type (one-shot query)
239 * PTR query discovers all instances of a service type
240 * mdns_query_send returns the query ID for response filtering
241 */
242 int query_id = mdns_query_send(mdns->socket_fd, MDNS_RECORDTYPE_PTR, service_type, service_type_len, mdns->buffer,
243 mdns->buffer_capacity, 0);
244
245 if (query_id <= 0) {
246 return SET_ERRNO(ERROR_NETWORK, "mDNS query send failed for %s (query_id=%d)", service_type, query_id);
247 }
248
249 mdns->query_id = (uint16_t)query_id;
250 log_debug("mDNS query sent for service type: %s (query_id: %d)", service_type, query_id);
251
252 return ASCIICHAT_OK;
253}
254
256 if (!mdns) {
257 return SET_ERRNO(ERROR_INVALID_PARAM, "mDNS context is NULL");
258 }
259
260 /* Process incoming mDNS packets (responses from previous queries)
261 * mdns_query_recv processes all records received since last call
262 * The callback function (mdns_record_callback) is invoked for each record
263 */
264 int num_records =
265 mdns_query_recv(mdns->socket_fd, mdns->buffer, mdns->buffer_capacity, mdns_record_callback, mdns, mdns->query_id);
266
267 if (num_records < 0) {
268 return SET_ERRNO(ERROR_NETWORK, "Failed to receive mDNS query responses");
269 }
270
271 if (num_records > 0) {
272 log_debug("Processed %d mDNS records", num_records);
273 }
274
275 (void)timeout_ms;
276 return ASCIICHAT_OK;
277}
278
280 if (!mdns) {
281 return -1;
282 }
283 return mdns->socket_fd;
284}
unsigned short uint16_t
Definition common.h:57
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_NETWORK_BIND
Definition error_codes.h:70
@ ERROR_NETWORK
Definition error_codes.h:69
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INVALID_PARAM
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
asciichat_error_t asciichat_mdns_update(asciichat_mdns_t *mdns, int timeout_ms)
Process pending mDNS events (must be called regularly)
Definition mdns.c:255
asciichat_error_t asciichat_mdns_unadvertise(asciichat_mdns_t *mdns, const char *service_name)
Stop advertising a service.
Definition mdns.c:93
#define MDNS_BUFFER_SIZE
mDNS packet buffer size (4KB should handle most service records)
Definition mdns.c:28
void asciichat_mdns_shutdown(asciichat_mdns_t *mdns)
Shutdown mDNS context and cleanup.
Definition mdns.c:61
asciichat_mdns_t * asciichat_mdns_init(void)
Initialize mDNS context.
Definition mdns.c:30
int asciichat_mdns_get_socket(asciichat_mdns_t *mdns)
Get the socket file descriptor for integration with select/poll.
Definition mdns.c:279
asciichat_error_t asciichat_mdns_query(asciichat_mdns_t *mdns, const char *service_type, asciichat_mdns_discovery_callback_fn callback, void *user_data)
Query for services on the local network.
Definition mdns.c:219
asciichat_error_t asciichat_mdns_advertise(asciichat_mdns_t *mdns, const asciichat_mdns_service_t *service)
Advertise a service on the local network.
Definition mdns.c:75
void(* asciichat_mdns_discovery_callback_fn)(const asciichat_mdns_discovery_t *service, void *user_data)
Callback for discovered services.
Definition mdns.h:107
Cross-platform socket interface for ascii-chat.
Discovered service information.
Definition mdns.h:82
Service information for advertisement.
Definition mdns.h:64
const char * type
Definition mdns.h:70
const char * host
Definition mdns.h:72
const char * name
Definition mdns.h:66
Internal mDNS context structure.
Definition mdns.c:18
size_t buffer_capacity
Definition mdns.c:23
int socket_fd
Definition mdns.c:19
asciichat_mdns_discovery_callback_fn callback
Definition mdns.c:20
uint16_t query_id
Definition mdns.c:24
void * callback_data
Definition mdns.c:21
uint8_t * buffer
Definition mdns.c:22