Branch data Line data Source code
1 : : /* SPDX-FileCopyrightText: Copyright 2026 Cozens Software Solutions Limited
2 : : * SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial
3 : : */
4 : :
5 : : #include <stdbool.h>
6 : : #include <stddef.h>
7 : : #include <stdint.h>
8 : :
9 : : #include "SolidSyslogDatagram.h"
10 : : #include "SolidSyslogEndpoint.h"
11 : : #include "SolidSyslogEndpointHostPrivate.h"
12 : : #include "SolidSyslogError.h"
13 : : #include "SolidSyslogFormatter.h"
14 : : #include "SolidSyslogNullSender.h"
15 : : #include "SolidSyslogPrival.h"
16 : : #include "SolidSyslogErrorCategory.h"
17 : : #include "SolidSyslogResolver.h"
18 : : #include "SolidSyslogSenderCategories.h"
19 : : #include "SolidSyslogSenderDefinition.h"
20 : : #include "SolidSyslogSenderHealth.h"
21 : : #include "SolidSyslogTransport.h"
22 : : #include "SolidSyslogUdpPayload.h"
23 : : #include "SolidSyslogUdpSender.h"
24 : : #include "SolidSyslogUdpSenderErrors.h"
25 : : #include "SolidSyslogUdpSenderPrivate.h"
26 : :
27 : : struct SolidSyslogFormatter;
28 : :
29 : : const struct SolidSyslogErrorSource SolidSyslogUdpSenderErrorSource = {"UdpSender", SolidSyslogUdpSenderErrors_Name};
30 : :
31 : : static bool UdpSender_Send(struct SolidSyslogSender* base, const void* buffer, size_t size);
32 : : static void UdpSender_Disconnect(struct SolidSyslogSender* base);
33 : :
34 : : static inline struct SolidSyslogUdpSender* UdpSender_SelfFromBase(struct SolidSyslogSender* base);
35 : : static inline enum SolidSyslogDatagramSendResult UdpSender_Deliver(
36 : : struct SolidSyslogUdpSender* self,
37 : : const void* buffer,
38 : : size_t size
39 : : );
40 : : static inline bool UdpSender_WasDiscarded(enum SolidSyslogDatagramSendResult outcome);
41 : : static inline void UdpSender_UpdateDeliveryHealth(struct SolidSyslogUdpSender* self, bool delivered);
42 : : static inline bool UdpSender_Reconcile(struct SolidSyslogUdpSender* self);
43 : : static inline void UdpSender_DisconnectIfStale(struct SolidSyslogUdpSender* self);
44 : : static inline bool UdpSender_EnsureConnected(struct SolidSyslogUdpSender* self);
45 : : static inline bool UdpSender_Connected(const struct SolidSyslogUdpSender* self);
46 : : static bool UdpSender_Connect(struct SolidSyslogUdpSender* self);
47 : : static inline uint16_t UdpSender_QueryEndpointPort(
48 : : struct SolidSyslogUdpSender* self,
49 : : struct SolidSyslogFormatter* hostFormatter
50 : : );
51 : : static inline bool UdpSender_OpenSocket(struct SolidSyslogUdpSender* self);
52 : : static bool UdpSender_ResolveDestination(struct SolidSyslogUdpSender* self, const char* host, uint16_t port);
53 : : static inline struct SolidSyslogAddress* UdpSender_Address(struct SolidSyslogUdpSender* self);
54 : : static inline void UdpSender_CloseSocket(struct SolidSyslogUdpSender* self);
55 : : static inline enum SolidSyslogDatagramSendResult UdpSender_TransmitDatagram(
56 : : struct SolidSyslogUdpSender* self,
57 : : const void* buffer,
58 : : size_t size
59 : : );
60 : : static inline enum SolidSyslogDatagramSendResult UdpSender_SendTo(
61 : : struct SolidSyslogUdpSender* self,
62 : : const void* buffer,
63 : : size_t size
64 : : );
65 : : static inline bool UdpSender_FailureWasOversize(struct SolidSyslogUdpSender* self, size_t size);
66 : : static inline enum SolidSyslogDatagramSendResult UdpSender_RetryAfterOversize(
67 : : struct SolidSyslogUdpSender* self,
68 : : const void* buffer,
69 : : size_t size
70 : : );
71 : : static inline void UdpSender_ReportRecordTooLarge(struct SolidSyslogUdpSender* self);
72 : : static uint32_t UdpSender_NilEndpointVersion(void* context);
73 : :
74 : 129 : void SolidSyslogUdpSender_Initialise(struct SolidSyslogSender* base, const struct SolidSyslogUdpSenderConfig* config)
75 : : {
76 : 129 : struct SolidSyslogUdpSender* self = UdpSender_SelfFromBase(base);
77 : 129 : self->Base.Send = UdpSender_Send;
78 : 129 : self->Base.Disconnect = UdpSender_Disconnect;
79 : 129 : self->Config = *config;
80 [ + + ]: 129 : if (self->Config.EndpointVersion == NULL)
81 : : {
82 : 1 : self->Config.EndpointVersion = UdpSender_NilEndpointVersion;
83 : : }
84 : 129 : self->Connected = false;
85 : 129 : self->DeliveryHealthy = true;
86 : 129 : self->RecordTooLargeReported = false;
87 : 129 : self->LastEndpointVersion = 0;
88 : 129 : }
89 : :
90 : 129 : void SolidSyslogUdpSender_Cleanup(struct SolidSyslogSender* base)
91 : : {
92 : : /* Disconnect first so the live Config.Datagram is still reachable; then overwrite the
93 : : * abstract base with the shared NullSender vtable so use-after-destroy is a safe
94 : : * no-op rather than a NULL-fn-pointer crash. Derived fields are private to this TU
95 : : * so the next SolidSyslogUdpSender_Initialise overwrites them; no need to wipe here. */
96 : 129 : UdpSender_Disconnect(base);
97 : 129 : *base = *SolidSyslogNullSender_Get();
98 : 129 : }
99 : :
100 : 104 : static bool UdpSender_Send(struct SolidSyslogSender* base, const void* buffer, size_t size)
101 : : {
102 : 104 : bool result = false;
103 [ + + ]: 104 : if (buffer == NULL)
104 : : {
105 : 2 : UdpSender_Report(
106 : : SOLIDSYSLOG_BAD_ARGUMENT_SEVERITY,
107 : : SOLIDSYSLOG_CAT_BAD_ARGUMENT,
108 : : SOLIDSYSLOG_UDP_SENDER_ERROR_SEND_NULL_BUFFER
109 : : );
110 : : }
111 : : else
112 : : {
113 : 102 : struct SolidSyslogUdpSender* self = UdpSender_SelfFromBase(base);
114 : 102 : enum SolidSyslogDatagramSendResult outcome = UdpSender_Deliver(self, buffer, size);
115 [ + + ]: 102 : if (!UdpSender_WasDiscarded(outcome))
116 : : {
117 : 89 : UdpSender_UpdateDeliveryHealth(self, outcome == SOLIDSYSLOG_DATAGRAM_SEND_RESULT_SENT);
118 : : }
119 : 102 : result = outcome != SOLIDSYSLOG_DATAGRAM_SEND_RESULT_FAILED;
120 : : }
121 : 104 : return result;
122 : : }
123 : :
124 : 102 : static inline enum SolidSyslogDatagramSendResult UdpSender_Deliver(
125 : : struct SolidSyslogUdpSender* self,
126 : : const void* buffer,
127 : : size_t size
128 : : )
129 : : {
130 : 102 : enum SolidSyslogDatagramSendResult outcome = SOLIDSYSLOG_DATAGRAM_SEND_RESULT_FAILED;
131 [ + + ]: 102 : if (UdpSender_Reconcile(self))
132 : : {
133 : 97 : outcome = UdpSender_TransmitDatagram(self, buffer, size);
134 : : }
135 : 102 : return outcome;
136 : : }
137 : :
138 : : /* A record still refused for size once trimmed has been discarded: it reached
139 : : * nobody, so it says nothing about delivery health either way. */
140 : 102 : static inline bool UdpSender_WasDiscarded(enum SolidSyslogDatagramSendResult outcome)
141 : : {
142 : 102 : return outcome == SOLIDSYSLOG_DATAGRAM_SEND_RESULT_OVERSIZE;
143 : : }
144 : :
145 : : /* Driven only from the genuine delivery path - a NULL-buffer argument error
146 : : * never touches the delivery-health edge. */
147 : 89 : static inline void UdpSender_UpdateDeliveryHealth(struct SolidSyslogUdpSender* self, bool delivered)
148 : : {
149 : : static const struct SolidSyslogSenderHealthReporter reporter = {
150 : : .Source = &SolidSyslogUdpSenderErrorSource,
151 : : .FailedDetail = (int32_t) SOLIDSYSLOG_UDP_SENDER_ERROR_DELIVERY_FAILED,
152 : : .RestoredDetail = (int32_t) SOLIDSYSLOG_UDP_SENDER_ERROR_DELIVERY_RESTORED
153 : : };
154 : 89 : SolidSyslogSenderHealth_Update(&self->DeliveryHealthy, delivered, &reporter);
155 : 89 : }
156 : :
157 : 155 : static void UdpSender_Disconnect(struct SolidSyslogSender* base)
158 : : {
159 : 155 : struct SolidSyslogUdpSender* self = UdpSender_SelfFromBase(base);
160 : :
161 [ + + ]: 155 : if (UdpSender_Connected(self))
162 : : {
163 : 83 : UdpSender_CloseSocket(self);
164 : : }
165 : 155 : }
166 : :
167 : 386 : static inline struct SolidSyslogUdpSender* UdpSender_SelfFromBase(struct SolidSyslogSender* base)
168 : : {
169 : 386 : return (struct SolidSyslogUdpSender*) base;
170 : : }
171 : :
172 : 102 : static inline bool UdpSender_Reconcile(struct SolidSyslogUdpSender* self)
173 : : {
174 : 102 : UdpSender_DisconnectIfStale(self);
175 : 102 : return UdpSender_EnsureConnected(self);
176 : : }
177 : :
178 : 102 : static inline void UdpSender_DisconnectIfStale(struct SolidSyslogUdpSender* self)
179 : : {
180 : 102 : uint32_t version = self->Config.EndpointVersion(self->Config.EndpointContext);
181 : :
182 [ + + ]: 102 : if (version != self->LastEndpointVersion)
183 : : {
184 : 19 : UdpSender_Disconnect(&self->Base);
185 : 19 : self->LastEndpointVersion = version;
186 : : }
187 : 102 : }
188 : :
189 : 102 : static inline bool UdpSender_EnsureConnected(struct SolidSyslogUdpSender* self)
190 : : {
191 [ + + + + ]: 102 : return UdpSender_Connected(self) || UdpSender_Connect(self);
192 : : }
193 : :
194 : 345 : static inline bool UdpSender_Connected(const struct SolidSyslogUdpSender* self)
195 : : {
196 : 345 : return self->Connected;
197 : : }
198 : :
199 : 88 : static bool UdpSender_Connect(struct SolidSyslogUdpSender* self)
200 : : {
201 : : SolidSyslogFormatterStorage hostStorage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(SOLIDSYSLOG_MAX_HOST_SIZE)];
202 : 88 : struct SolidSyslogFormatter* hostFormatter = SolidSyslogFormatter_Create(hostStorage, SOLIDSYSLOG_MAX_HOST_SIZE);
203 : 88 : uint16_t port = UdpSender_QueryEndpointPort(self, hostFormatter);
204 : 88 : const char* host = SolidSyslogFormatter_AsFormattedBuffer(hostFormatter);
205 : :
206 [ + + + + ]: 88 : if (UdpSender_OpenSocket(self) && UdpSender_ResolveDestination(self, host, port))
207 : : {
208 : 83 : self->Connected = true;
209 : : }
210 : : else
211 : : {
212 : 5 : UdpSender_CloseSocket(self);
213 : : }
214 : 88 : return UdpSender_Connected(self);
215 : : }
216 : :
217 : 88 : static inline uint16_t UdpSender_QueryEndpointPort(
218 : : struct SolidSyslogUdpSender* self,
219 : : struct SolidSyslogFormatter* hostFormatter
220 : : )
221 : : {
222 : : struct SolidSyslogEndpointHost hostSink;
223 : 88 : SolidSyslogEndpointHost_FromFormatter(&hostSink, hostFormatter);
224 : 88 : struct SolidSyslogEndpoint endpoint = {.Host = &hostSink, .Port = 0};
225 : 88 : self->Config.Endpoint(&endpoint, self->Config.EndpointContext);
226 : 88 : return endpoint.Port;
227 : : }
228 : :
229 : 88 : static inline bool UdpSender_OpenSocket(struct SolidSyslogUdpSender* self)
230 : : {
231 : 88 : return SolidSyslogDatagram_Open(self->Config.Datagram);
232 : : }
233 : :
234 : 86 : static bool UdpSender_ResolveDestination(struct SolidSyslogUdpSender* self, const char* host, uint16_t port)
235 : : {
236 : 86 : return SolidSyslogResolver_Resolve(
237 : : self->Config.Resolver,
238 : : SOLIDSYSLOG_TRANSPORT_UDP,
239 : : host,
240 : : port,
241 : : UdpSender_Address(self)
242 : : );
243 : : }
244 : :
245 : 195 : static inline struct SolidSyslogAddress* UdpSender_Address(struct SolidSyslogUdpSender* self)
246 : : {
247 : 195 : return self->Config.Address;
248 : : }
249 : :
250 : 88 : static inline void UdpSender_CloseSocket(struct SolidSyslogUdpSender* self)
251 : : {
252 : 88 : SolidSyslogDatagram_Close(self->Config.Datagram);
253 : 88 : self->Connected = false;
254 : 88 : }
255 : :
256 : : /* SENT and FAILED as the datagram answered; OVERSIZE only for a record that
257 : : * could not be sent even trimmed, and has been discarded. */
258 : 97 : static inline enum SolidSyslogDatagramSendResult UdpSender_TransmitDatagram(
259 : : struct SolidSyslogUdpSender* self,
260 : : const void* buffer,
261 : : size_t size
262 : : )
263 : : {
264 : 97 : enum SolidSyslogDatagramSendResult result = UdpSender_SendTo(self, buffer, size);
265 [ + + + + ]: 97 : if ((result == SOLIDSYSLOG_DATAGRAM_SEND_RESULT_FAILED) && UdpSender_FailureWasOversize(self, size))
266 : : {
267 : 1 : result = SOLIDSYSLOG_DATAGRAM_SEND_RESULT_OVERSIZE;
268 : : }
269 [ + + ]: 97 : if (result == SOLIDSYSLOG_DATAGRAM_SEND_RESULT_OVERSIZE)
270 : : {
271 : 22 : result = UdpSender_RetryAfterOversize(self, buffer, size);
272 : : }
273 : 97 : return result;
274 : : }
275 : :
276 : : /* A datagram handed to the network, whole or trimmed, ends a run of records
277 : : * too large to send. */
278 : 109 : static inline enum SolidSyslogDatagramSendResult UdpSender_SendTo(
279 : : struct SolidSyslogUdpSender* self,
280 : : const void* buffer,
281 : : size_t size
282 : : )
283 : : {
284 : : enum SolidSyslogDatagramSendResult result =
285 : 109 : SolidSyslogDatagram_SendTo(self->Config.Datagram, buffer, size, UdpSender_Address(self));
286 [ + + ]: 109 : if (result == SOLIDSYSLOG_DATAGRAM_SEND_RESULT_SENT)
287 : : {
288 : 73 : self->RecordTooLargeReported = false;
289 : : }
290 : 109 : return result;
291 : : }
292 : :
293 : : /* An implementation that cannot distinguish an over-large datagram reports
294 : : * FAILED for one, which SolidSyslogDatagram.h permits, so a failure is
295 : : * reclassified as OVERSIZE where the record could not have fitted. Asking
296 : : * MaxPayload only once a send has failed keeps the query off the success
297 : : * path, and the size test leaves a genuine failure on a record that does fit
298 : : * to be retried whole. Zero says the implementation cannot report what the
299 : : * path carries, so nothing is inferred from it. */
300 : 10 : static inline bool UdpSender_FailureWasOversize(struct SolidSyslogUdpSender* self, size_t size)
301 : : {
302 : 10 : size_t maxPayload = SolidSyslogDatagram_MaxPayload(self->Config.Datagram);
303 [ + + + + ]: 10 : return (maxPayload > 0U) && (size > maxPayload);
304 : : }
305 : :
306 : 22 : static inline enum SolidSyslogDatagramSendResult UdpSender_RetryAfterOversize(
307 : : struct SolidSyslogUdpSender* self,
308 : : const void* buffer,
309 : : size_t size
310 : : )
311 : : {
312 : 22 : size_t maxPayload = SolidSyslogDatagram_MaxPayload(self->Config.Datagram);
313 : 22 : size_t clipLimit = (size < maxPayload) ? size : maxPayload;
314 : 22 : size_t trimmed = SolidSyslogUdpPayload_TrimToCodepointBoundary((const uint8_t*) buffer, clipLimit);
315 : : /* A trim that leaves nothing stands for a refusal: the path cannot carry
316 : : * the record at all. */
317 : 22 : enum SolidSyslogDatagramSendResult result = SOLIDSYSLOG_DATAGRAM_SEND_RESULT_OVERSIZE;
318 [ + + ]: 22 : if (trimmed > 0U)
319 : : {
320 : 12 : result = UdpSender_SendTo(self, buffer, trimmed);
321 : : }
322 [ + + ]: 22 : if (result == SOLIDSYSLOG_DATAGRAM_SEND_RESULT_OVERSIZE)
323 : : {
324 : : /* Still refused for size, so trying again cannot help: the record is
325 : : * discarded, so Service does not loop on an undeliverable, and the
326 : : * loss is reported. */
327 : 13 : UdpSender_ReportRecordTooLarge(self);
328 : : }
329 : 22 : return result;
330 : : }
331 : :
332 : : /* One report stands for a run of records the path would not carry. */
333 : 13 : static inline void UdpSender_ReportRecordTooLarge(struct SolidSyslogUdpSender* self)
334 : : {
335 [ + + ]: 13 : if (!self->RecordTooLargeReported)
336 : : {
337 : 12 : UdpSender_Report(
338 : : SOLIDSYSLOG_SEVERITY_ERROR,
339 : : SOLIDSYSLOG_CAT_SENDER_RECORDS_LOST,
340 : : SOLIDSYSLOG_UDP_SENDER_ERROR_RECORD_TOO_LARGE
341 : : );
342 : 12 : self->RecordTooLargeReported = true;
343 : : }
344 : 13 : }
345 : :
346 : 1 : static uint32_t UdpSender_NilEndpointVersion(void* context)
347 : : {
348 : : (void) context;
349 : 1 : return 0;
350 : : }
|