GCC Code Coverage Report


Directory: ./
File: channeloutput/E131.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 156 161 96.9%
Functions: 9 9 100.0%
Branches: 84 131 64.1%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the GPL v2 as described in the
10 * included LICENSE.GPL file.
11 */
12
13 #include "fpp-pch.h"
14
15 #include "fpp-json.h"
16
17 #include <arpa/inet.h>
18 #include <net/if.h>
19 #include <netinet/in.h>
20 #include <sys/ioctl.h>
21 #include <sys/socket.h>
22 #include <sys/time.h>
23 #include <sys/types.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <math.h>
27 #include <netdb.h>
28 #include <signal.h>
29 #include <unistd.h>
30
31 #include "../Warnings.h"
32 #include "../log.h"
33
34 #include "ChannelOutputSetup.h"
35 #include "E131.h"
36 #include "Universe.h"
37 #include "channeloutputthread.h"
38 #include "e131defs.h"
39 #include "fpp.h"
40 #include "ping.h"
41
42 const unsigned char E131header[] = {
43 0x00, 0x10, 0x00, 0x00, 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00,
44 0x72, 0x6e, 0x00, 0x00, 0x00, 0x04, 'F', 'A', 'L', 'C', 'O', 'N', ' ', 'F', 'P', 'P',
45 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x58, 0x00, 0x00, 0x00, 0x02, 'F', 'A', 'L', 'C',
46 'O', 'N', 'C', 'H', 'R', 'I', 'S', 'T', 'M', 'A', 'S', '.', 'C', 'O', 'M', ' ',
47 'B', 'Y', ' ', 'D', 'P', 'I', 'T', 'T', 'S', ' ', 'A', 'N', 'D', ' ', 'M', 'Y',
48 'K', 'R', 'O', 'F', 'T', ' ', 'F', 'A', 'L', 'C', 'O', 'N', ' ', 'P', 'I', ' ',
49 'P', 'L', 'A', 'Y', 'E', 'R', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
50 0x00, 0x00, 0x01, 0x72, 0x0b, 0x02, 0xa1, 0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0x00
51 };
52
53 const unsigned char E131SyncHeader[] = {
54 // Root Layer (38 bytes)
55 0x00, 0x10, // Preamble Size
56 0x00, 0x00, // Post-amble Size
57 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00, // "ASC-E1.17\0\0\0"
58 0x70, 0x21, // RLP Flags + Length (49-16=33)
59 0x00, 0x00, 0x00, 0x08, // Root Vector = VECTOR_ROOT_E131_EXTENDED
60 'F', 'A', 'L', 'C', 'O', 'N', ' ', 'F', 'P', 'P', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // CID
61 // Framing Layer (11 bytes)
62 0x70, 0x0b, // Framing Flags + Length (49-38=11)
63 0x00, 0x00, 0x00, 0x01, // Framing Vector = VECTOR_E131_EXTENDED_SYNCHRONIZATION
64 0x00, // Sequence Number
65 0x00, 0x00, // Synchronization Address
66 0x00, 0x00 // Reserved
67 };
68
69 static const std::string E131TYPE = "e1.31";
70
71 8 const std::string& E131OutputData::GetOutputTypeString() const {
72 8 return E131TYPE;
73 }
74
75 158 E131OutputData::E131OutputData(const Json::Value& config) :
76 UDPOutputData(config),
77 158 universeCount(1),
78 158 syncUniverse(0),
79 158 e131SyncPacket(nullptr) {
80 sockaddr_in e131Address;
81 158 memset((char*)&e131Address, 0, sizeof(sockaddr_in));
82 158 e131Address.sin_family = AF_INET;
83 158 e131Address.sin_port = htons(E131_DEST_PORT);
84
85
2/4
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 158 times.
✗ Branch 3 not taken.
158 universe = config["id"].asInt();
86
2/4
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 158 times.
✗ Branch 3 not taken.
158 priority = config["priority"].asInt();
87
3/4
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 138 times.
158 if (config.isMember("universeCount")) {
88
2/4
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
20 universeCount = config["universeCount"].asInt();
89 }
90
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 158 times.
158 if (universeCount < 1) {
91 ✗ universeCount = 1;
92 }
93
3/4
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 155 times.
158 if (config.isMember("syncUniverse")) {
94
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 syncUniverse = config["syncUniverse"].asInt();
95 }
96
2/4
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 158 times.
158 if (syncUniverse < 0 || syncUniverse > 63999) {
97 ✗ syncUniverse = 0;
98 }
99
2/3
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 154 times.
✗ Branch 2 not taken.
158 switch (type) {
100 4 case 0: // Multicast
101
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 ipAddress = "";
102 4 break;
103 154 case 1: //UnicastAddress
104
2/4
✓ Branch 0 taken 154 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 154 times.
✗ Branch 3 not taken.
154 ipAddress = config["address"].asString();
105 154 break;
106 }
107
108
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 154 times.
158 if (type == E131_TYPE_MULTICAST) {
109 int UniverseOctet[2];
110 char sAddress[32];
111
112 4 UniverseOctet[0] = universe / 256;
113 4 UniverseOctet[1] = universe % 256;
114 4 snprintf(sAddress, sizeof(sAddress), "239.255.%d.%d", UniverseOctet[0], UniverseOctet[1]);
115 4 e131Address.sin_addr.s_addr = inet_addr(sAddress);
116
2/2
✓ Branch 0 taken 153 times.
✓ Branch 1 taken 1 times.
154 } else if (active) {
117
1/2
✓ Branch 0 taken 153 times.
✗ Branch 1 not taken.
153 e131Address.sin_addr.s_addr = toInetAddr(ipAddress, valid);
118
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 142 times.
153 if (!valid) {
119
3/6
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
11 AddOutputConfigWarning(14, "Could not resolve host name " + ipAddress + " - disabling output");
120 11 active = false;
121 }
122 }
123
124
1/2
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
158 e131Iovecs.resize(universeCount * 2);
125
1/2
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
158 e131Headers.resize(universeCount);
126
1/2
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
158 e131SentSequence.assign(universeCount, 0);
127
1/2
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
158 e131Pending.assign(universeCount, false);
128
1/2
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
158 e131Addresses.resize(universeCount);
129
2/2
✓ Branch 0 taken 2692 times.
✓ Branch 1 taken 158 times.
2850 for (int x = 0; x < universeCount; x++) {
130
2/2
✓ Branch 0 taken 1503 times.
✓ Branch 1 taken 1189 times.
2692 if (type == E131_TYPE_MULTICAST) {
131 int UniverseOctet[2];
132 char sAddress[32];
133
134 1503 int u = universe + x;
135 1503 UniverseOctet[0] = u / 256;
136 1503 UniverseOctet[1] = u % 256;
137 1503 snprintf(sAddress, sizeof(sAddress), "239.255.%d.%d", UniverseOctet[0], UniverseOctet[1]);
138 1503 e131Address.sin_addr.s_addr = inet_addr(sAddress);
139 }
140 2692 e131Addresses[x] = e131Address;
141
142 2692 unsigned char* e131Buffer = (unsigned char*)malloc(E131_HEADER_LENGTH);
143 2692 e131Headers[x] = e131Buffer;
144 2692 memcpy(e131Buffer, E131header, E131_HEADER_LENGTH);
145
146 2692 int uni = universe + x;
147 2692 e131Buffer[E131_PRIORITY_INDEX] = priority;
148 2692 e131Buffer[E131_SYNC_ADDRESS_INDEX] = (char)(syncUniverse / 256);
149 2692 e131Buffer[E131_SYNC_ADDRESS_INDEX + 1] = (char)(syncUniverse % 256);
150 2692 e131Buffer[E131_UNIVERSE_INDEX] = (char)(uni / 256);
151 2692 e131Buffer[E131_UNIVERSE_INDEX + 1] = (char)(uni % 256);
152
153 // Property Value Count
154 2692 e131Buffer[E131_COUNT_INDEX] = ((channelCount + 1) / 256);
155 2692 e131Buffer[E131_COUNT_INDEX + 1] = ((channelCount + 1) % 256);
156
157 // RLP Protocol flags and length
158 2692 int count = 638 - 16 - (512 - (channelCount));
159 2692 e131Buffer[E131_RLP_COUNT_INDEX] = (count / 256) + 0x70;
160 2692 e131Buffer[E131_RLP_COUNT_INDEX + 1] = count % 256;
161
162 // Framing Protocol flags and length
163 2692 count = 638 - 38 - (512 - (channelCount));
164 2692 e131Buffer[E131_FRAMING_COUNT_INDEX] = (count / 256) + 0x70;
165 2692 e131Buffer[E131_FRAMING_COUNT_INDEX + 1] = count % 256;
166
167 // DMP Protocol flags and length
168 2692 count = 638 - 115 - (512 - (channelCount));
169 2692 e131Buffer[E131_DMP_COUNT_INDEX] = (count / 256) + 0x70;
170 2692 e131Buffer[E131_DMP_COUNT_INDEX + 1] = count % 256;
171
172 2692 e131Buffer[E131_SEQUENCE_INDEX] = 0;
173
174 // use scatter/gather for the packet. One IOV will contain
175 // the header, the second will point into the raw channel data
176 // and will be set at output time. This avoids any memcpy.
177 2692 e131Iovecs[x * 2].iov_base = e131Buffer;
178 2692 e131Iovecs[x * 2].iov_len = E131_HEADER_LENGTH;
179 2692 e131Iovecs[x * 2 + 1].iov_base = nullptr;
180 2692 e131Iovecs[x * 2 + 1].iov_len = channelCount;
181 }
182
183
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 155 times.
158 if (syncUniverse > 0) {
184 3 e131SyncPacket = (unsigned char*)malloc(E131_SYNC_PACKET_LENGTH);
185 3 memcpy(e131SyncPacket, E131SyncHeader, E131_SYNC_PACKET_LENGTH);
186 3 e131SyncPacket[E131_SYNC_PACKET_UNIVERSE_INDEX] = (unsigned char)(syncUniverse / 256);
187 3 e131SyncPacket[E131_SYNC_PACKET_UNIVERSE_INDEX + 1] = (unsigned char)(syncUniverse % 256);
188
189 3 e131SyncIovec.iov_base = e131SyncPacket;
190 3 e131SyncIovec.iov_len = E131_SYNC_PACKET_LENGTH;
191
192 3 memset((char*)&e131SyncAddress, 0, sizeof(sockaddr_in));
193 3 e131SyncAddress.sin_family = AF_INET;
194 3 e131SyncAddress.sin_port = htons(E131_DEST_PORT);
195 char sAddress[32];
196 3 snprintf(sAddress, sizeof(sAddress), "239.255.%d.%d",
197 3 syncUniverse / 256, syncUniverse % 256);
198 3 e131SyncAddress.sin_addr.s_addr = inet_addr(sAddress);
199 }
200 158 }
201
202 632 E131OutputData::~E131OutputData() {
203
2/2
✓ Branch 0 taken 2692 times.
✓ Branch 1 taken 158 times.
5700 for (auto a : e131Headers) {
204 5384 free(a);
205 }
206
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 155 times.
316 if (e131SyncPacket) {
207 6 free(e131SyncPacket);
208 }
209 632 }
210
211 316 bool E131OutputData::IsPingable() {
212 316 return type == 1;
213 }
214
215 20082 void E131OutputData::PrepareData(unsigned char* channelData, UDPOutputMessages& msgs) {
216
3/6
✓ Branch 0 taken 20082 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20082 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 20082 times.
✗ Branch 5 not taken.
20082 if (valid && active) {
217 20082 unsigned char* cur = channelData + startChannel - 1;
218 20082 int start = 0;
219 20082 bool anySkipped = false;
220 20082 bool allSkipped = true;
221
2/2
✓ Branch 0 taken 702051 times.
✓ Branch 1 taken 20082 times.
722133 for (int x = 0; x < universeCount; x++) {
222
2/2
✓ Branch 0 taken 701984 times.
✓ Branch 1 taken 67 times.
702051 if (NeedToOutputFrame(channelData, startChannel - 1, start, channelCount)) {
223 struct mmsghdr msg;
224 701984 memset(&msg, 0, sizeof(msg));
225
226 701984 msg.msg_hdr.msg_name = &e131Addresses[x];
227 701984 msg.msg_hdr.msg_namelen = sizeof(sockaddr_in);
228 701984 msg.msg_hdr.msg_iov = &e131Iovecs[x * 2];
229 701984 msg.msg_hdr.msg_iovlen = 2;
230 701984 msg.msg_len = channelCount + E131_HEADER_LENGTH;
231
2/2
✓ Branch 0 taken 312142 times.
✓ Branch 1 taken 389842 times.
701984 if (type == E131_TYPE_MULTICAST) {
232
2/4
✓ Branch 0 taken 312142 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 312142 times.
✗ Branch 3 not taken.
312142 msgs[MULTICAST_MESSAGES_KEY].push_back(msg);
233 } else {
234
2/4
✓ Branch 0 taken 389842 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 389842 times.
✗ Branch 3 not taken.
389842 msgs[e131Addresses[x].sin_addr.s_addr].push_back(msg);
235 }
236
237 701984 e131Headers[x][E131_SEQUENCE_INDEX] = e131SentSequence[x] + 1;
238
1/2
✓ Branch 0 taken 701984 times.
✗ Branch 1 not taken.
701984 e131Pending[x] = true;
239 701984 e131Iovecs[x * 2 + 1].iov_base = (void*)cur;
240 701984 allSkipped = false;
241 } else {
242
1/2
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
67 e131Pending[x] = false;
243 67 anySkipped = true;
244 }
245 702051 cur += channelCount;
246 702051 start += channelCount;
247 }
248
2/2
✓ Branch 0 taken 67 times.
✓ Branch 1 taken 20015 times.
20082 if (anySkipped) {
249 67 skippedFrames++;
250 } else {
251 20015 skippedFrames = 0;
252 }
253
2/2
✓ Branch 0 taken 20015 times.
✓ Branch 1 taken 67 times.
20082 if (!allSkipped) {
254 20015 SaveFrame(&channelData[startChannel - 1], start);
255 }
256 }
257 20082 }
258
259 20082 void E131OutputData::PostPrepareData(unsigned char* channelData, UDPOutputMessages& msgs) {
260 20082 e131SyncPending = false;
261
7/10
✓ Branch 0 taken 20082 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20082 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 146 times.
✓ Branch 5 taken 19936 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 146 times.
✓ Branch 8 taken 19936 times.
✓ Branch 9 taken 146 times.
20082 if (!valid || !active || syncUniverse <= 0 || !e131SyncPacket) {
262 19936 return;
263 }
264
265 // If another output already queued a sync packet for this same sync
266 // universe (same destination multicast address), skip to avoid duplicates.
267
1/2
✓ Branch 0 taken 146 times.
✗ Branch 1 not taken.
146 auto& syncMsgs = msgs[E131_SYNC_KEY];
268
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 146 times.
146 for (const auto& existing : syncMsgs) {
269 ✗ sockaddr_in* addr = (sockaddr_in*)existing.msg_hdr.msg_name;
270 ✗ if (addr && addr->sin_addr.s_addr == e131SyncAddress.sin_addr.s_addr) {
271 ✗ return;
272 }
273 }
274
275 146 e131SyncPacket[E131_SYNC_PACKET_SEQ_INDEX] = e131SyncSentSequence + 1;
276 146 e131SyncPending = true;
277
278 struct mmsghdr msg;
279 146 memset(&msg, 0, sizeof(msg));
280 146 msg.msg_hdr.msg_name = &e131SyncAddress;
281 146 msg.msg_hdr.msg_namelen = sizeof(sockaddr_in);
282 146 msg.msg_hdr.msg_iov = &e131SyncIovec;
283 146 msg.msg_hdr.msg_iovlen = 1;
284 146 msg.msg_len = E131_SYNC_PACKET_LENGTH;
285
1/2
✓ Branch 0 taken 146 times.
✗ Branch 1 not taken.
146 syncMsgs.push_back(msg);
286 }
287
288 20742 void E131OutputData::FrameSent() {
289
2/2
✓ Branch 0 taken 699737 times.
✓ Branch 1 taken 20742 times.
720479 for (int x = 0; x < universeCount; x++) {
290
3/4
✓ Branch 0 taken 699737 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 698775 times.
✓ Branch 3 taken 962 times.
699737 if (e131Pending[x]) {
291 698775 e131SentSequence[x] = e131Headers[x][E131_SEQUENCE_INDEX];
292
1/2
✓ Branch 0 taken 698775 times.
✗ Branch 1 not taken.
698775 e131Pending[x] = false;
293 }
294 }
295
2/2
✓ Branch 0 taken 138 times.
✓ Branch 1 taken 20604 times.
20742 if (e131SyncPending) {
296 138 e131SyncSentSequence = e131SyncPacket[E131_SYNC_PACKET_SEQ_INDEX];
297 138 e131SyncPending = false;
298 }
299 20742 }
300
301 281 void E131OutputData::GetRequiredChannelRange(int& min, int& max) {
302 281 min = startChannel - 1;
303 281 max = startChannel + (channelCount * universeCount) - 1;
304 281 }
305
306 158 void E131OutputData::DumpConfig() {
307 158 LogDebug(VB_CHANNELOUT, "E1.31 Universe: %s %d:%d:%d:%d:%d:%d sync:%d %s\n",
308 description.c_str(),
309 active,
310 universe,
311 startChannel,
312 channelCount,
313 type,
314 universeCount,
315 syncUniverse,
316 ipAddress.c_str());
317 158 }
318