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