GCC Code Coverage Report


Directory: ./
File: channeloutput/KiNet.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 79 88 89.8%
Functions: 6 7 85.7%
Branches: 36 66 54.5%

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 <netinet/in.h>
19 #include <sys/socket.h>
20 #include <sys/time.h>
21 #include <sys/types.h>
22 #include <ctype.h>
23 #include <errno.h>
24 #include <netdb.h>
25 #include <signal.h>
26
27 #include "../Warnings.h"
28 #include "ChannelOutputSetup.h"
29 #include "../log.h"
30
31 #include "KiNet.h"
32
33 #define KINET_V1_PACKET_HEADERLEN 21
34 #define KINET_V2_PACKET_HEADERLEN 24
35
36 #define KINET_V1_TYPE 6
37 #define KINET_V2_TYPE 7
38
39 static const uint8_t V1_HEADER[KINET_V1_PACKET_HEADERLEN] = {
40 0x04, 0x01, 0xdc, 0x4a, // magic[4] 0401dc4a
41 0x01, 0x00, // ver[2] 0100
42 0x01, 0x01, // type[2]
43 0x00, 0x00, 0x00, 0x00, // seq[4]
44 0x00, // port
45 0x00, // padding
46 0x00, 0x00, // flags
47 0xFF, 0xFF, 0xFF, 0xFF, // time
48 0x00 // uni
49 };
50 static const uint8_t V2_HEADER[KINET_V2_PACKET_HEADERLEN] = {
51 0x04, 0x01, 0xdc, 0x4a, // magic[4] 0401dc4a
52 0x01, 0x00, // ver[2] 0100
53 0x08, 0x01, // type[2]
54 0x00, 0x00, 0x00, 0x00, // seq[4]
55 0xFF, 0xFF, 0xFF, 0xFF, // time
56 0x00, // port
57 0x00, // sequenceNum
58 0x00, 0x00, // flags?
59 0x00, 0x02,
60 0x00, 0x00
61 };
62
63 static const std::string KINETTYPE = "KiNet";
64
65 ✗ const std::string& KiNetOutputData::GetOutputTypeString() const {
66 ✗ return KINETTYPE;
67 }
68
69 2 KiNetOutputData::KiNetOutputData(const Json::Value& config) :
70 2 UDPOutputData(config) {
71 2 memset((char*)&kinetAddress, 0, sizeof(sockaddr_in));
72 2 kinetAddress.sin_family = AF_INET;
73 2 kinetAddress.sin_port = htons(KINET_PORT);
74
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (active) {
75
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 kinetAddress.sin_addr.s_addr = toInetAddr(ipAddress, valid);
76
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!valid) {
77 ✗ AddOutputConfigWarning(14, "Could not resolve host name " + ipAddress + " - disabling output");
78 ✗ active = false;
79 }
80 }
81
82
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 port = config["id"].asInt();
83
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (config.isMember("universeCount")) {
84
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 portCount = config["universeCount"].asInt();
85 }
86
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (portCount < 1) {
87 ✗ portCount = 1;
88 }
89
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (portCount > 255) {
90 ✗ portCount = 255;
91 }
92
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (channelCount > 512) {
93 ✗ channelCount = 512;
94 }
95
96
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 sentSequence.assign(portCount, 0);
97
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 pendingSequence.assign(portCount, 0);
98 2 kinetIovecs = (struct iovec*)calloc(portCount * 2, sizeof(struct iovec));
99 2 kinetBuffers = (unsigned char**)calloc(portCount, sizeof(unsigned char*));
100
101 2 int chan = startChannel - 1;
102
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 for (int x = 0; x < portCount; x++) {
103
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (type == KINET_V1_TYPE) {
104 3 kinetBuffers[x] = (unsigned char*)malloc(KINET_V1_PACKET_HEADERLEN);
105 3 memcpy(kinetBuffers[x], V1_HEADER, KINET_V1_PACKET_HEADERLEN);
106 3 kinetBuffers[x][12] = port + x;
107 } else {
108 3 kinetBuffers[x] = (unsigned char*)malloc(KINET_V2_PACKET_HEADERLEN);
109 3 memcpy(kinetBuffers[x], V2_HEADER, KINET_V2_PACKET_HEADERLEN);
110 3 kinetBuffers[x][16] = port + x;
111 }
112
113 // use scatter/gather for the packet. One IOV will contain
114 // the header, the second will point into the raw channel data
115 // and will be set at output time. This avoids any memcpy.
116 6 kinetIovecs[x * 2].iov_base = kinetBuffers[x];
117
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 kinetIovecs[x * 2].iov_len = type == KINET_V1_TYPE ? KINET_V1_PACKET_HEADERLEN : KINET_V2_PACKET_HEADERLEN;
118 6 kinetIovecs[x * 2 + 1].iov_base = nullptr;
119 6 kinetIovecs[x * 2 + 1].iov_len = channelCount;
120
121 6 chan += channelCount;
122 }
123 2 }
124 8 KiNetOutputData::~KiNetOutputData() {
125
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
16 for (int x = 0; x < portCount; x++) {
126 12 free(kinetBuffers[x]);
127 }
128 4 free(kinetBuffers);
129 4 free(kinetIovecs);
130 8 }
131
132 4 void KiNetOutputData::GetRequiredChannelRange(int& min, int& max) {
133 4 min = startChannel - 1;
134 4 max = startChannel + (channelCount * portCount) - 1;
135 4 }
136
137 138 void KiNetOutputData::PrepareData(unsigned char* channelData, UDPOutputMessages& msgs) {
138
3/6
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 138 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 138 times.
✗ Branch 5 not taken.
138 if (valid && active) {
139 138 int start = 0;
140 struct mmsghdr msg;
141 138 memset(&msg, 0, sizeof(msg));
142
143 138 msg.msg_hdr.msg_name = &kinetAddress;
144 138 msg.msg_hdr.msg_namelen = sizeof(sockaddr_in);
145 138 bool anySkipped = false;
146 138 bool allSkipped = true;
147
2/2
✓ Branch 0 taken 414 times.
✓ Branch 1 taken 138 times.
552 for (int p = 0; p < portCount; p++) {
148
1/2
✓ Branch 0 taken 414 times.
✗ Branch 1 not taken.
414 bool nto = NeedToOutputFrame(channelData, startChannel - 1, start, kinetIovecs[p * 2 + 1].iov_len);
149
1/2
✓ Branch 0 taken 414 times.
✗ Branch 1 not taken.
414 if (nto) {
150 414 msg.msg_hdr.msg_iov = &kinetIovecs[p * 2];
151 414 msg.msg_hdr.msg_iovlen = 2;
152 414 msg.msg_len = kinetIovecs[p * 2 + 1].iov_len + kinetIovecs[p * 2].iov_len;
153
154
2/4
✓ Branch 0 taken 414 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 414 times.
✗ Branch 3 not taken.
414 msgs[kinetAddress.sin_addr.s_addr].push_back(msg);
155
156
2/2
✓ Branch 0 taken 207 times.
✓ Branch 1 taken 207 times.
414 if (type == KINET_V2_TYPE) {
157 // Numbered from the last packet sent, not the last one
158 // prepared: a frame prepared twice before it went out (as
159 // one is when playback starts) skipped a number on the wire.
160 207 uint32_t seq = sentSequence[p] + 1;
161 207 pendingSequence[p] = seq;
162 207 kinetBuffers[p][8] = seq & 0xFF;
163 207 kinetBuffers[p][9] = (seq >> 8) & 0xFF;
164 207 kinetBuffers[p][10] = (seq >> 16) & 0xFF;
165 207 kinetBuffers[p][11] = (seq >> 24) & 0xFF;
166 }
167 // set the pointer to the channelData for the universe
168 414 kinetIovecs[p * 2 + 1].iov_base = (void*)(&channelData[startChannel - 1 + start]);
169 414 allSkipped = false;
170 } else {
171 ✗ anySkipped = true;
172 }
173 414 start += kinetIovecs[p * 2 + 1].iov_len;
174 }
175
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 138 times.
138 if (anySkipped) {
176 ✗ skippedFrames++;
177 } else {
178 138 skippedFrames = 0;
179 }
180
1/2
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
138 if (!allSkipped) {
181
1/2
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
138 SaveFrame(&channelData[startChannel - 1], start);
182 }
183 }
184 138 }
185 132 void KiNetOutputData::FrameSent() {
186 132 sentSequence = pendingSequence;
187 132 }
188
189 2 void KiNetOutputData::DumpConfig() {
190 2 LogDebug(VB_CHANNELOUT, "KiNet: %s %d:%d:%d:%d %s\n",
191 description.c_str(),
192 active,
193 startChannel,
194 channelCount,
195 type,
196 ipAddress.c_str());
197 2 }
198