• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OpenLightingProject / ola / 24635061330

19 Apr 2026 05:36PM UTC coverage: 44.862% (-0.002%) from 44.864%
24635061330

Pull #2046

github

web-flow
Merge 8d08f158d into c6196f753
Pull Request #2046: Plugfest discovered fixes

8554 of 19846 branches covered (43.1%)

11 of 32 new or added lines in 7 files covered. (34.38%)

2 existing lines in 2 files now uncovered.

22105 of 49273 relevant lines covered (44.86%)

47.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

33.97
/plugins/pathport/PathportNode.cpp
1
/*
2
 * This program is free software; you can redistribute it and/or modify
3
 * it under the terms of the GNU General Public License as published by
4
 * the Free Software Foundation; either version 2 of the License, or
5
 * (at your option) any later version.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU Library General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
15
 *
16
 * PathportNode.cpp
17
 * A SandNet node
18
 * Copyright (C) 2005 Simon Newton
19
 */
20

21
#include <string.h>
22
#include <algorithm>
23
#include <string>
24
#include <map>
25
#include <vector>
26
#include "ola/Logging.h"
27
#include "ola/Constants.h"
28
#include "ola/network/IPV4Address.h"
29
#include "ola/network/NetworkUtils.h"
30
#include "plugins/pathport/PathportNode.h"
31

32

33
namespace ola {
34
namespace plugin {
35
namespace pathport {
36

37
using std::string;
38
using std::map;
39
using std::vector;
40
using ola::network::HostToNetwork;
41
using ola::network::IPV4Address;
42
using ola::network::IPV4SocketAddress;
43
using ola::network::NetworkToHost;
44
using ola::network::UDPSocket;
45
using ola::Callback0;
46

47
/*
48
 * Create a new node
49
 * @param ip_address the IP address to prefer to listen on, if NULL we choose
50
 * one.
51
 */
52
PathportNode::PathportNode(const string &ip_address,
1✔
53
                           uint32_t device_id,
54
                           uint8_t dscp)
1✔
55
    : m_running(false),
1✔
56
      m_dscp(dscp),
1✔
57
      m_preferred_ip(ip_address),
1✔
58
      m_device_id(device_id),
1✔
59
      m_sequence_number(1) {
1✔
60
}
1✔
61

62

63
/*
64
 * Cleanup
65
 */
66
PathportNode::~PathportNode() {
1✔
67
  Stop();
1✔
68

69
  universe_handlers::iterator iter;
1✔
70
  for (iter = m_handlers.begin(); iter != m_handlers.end(); ++iter) {
1✔
71
    delete iter->second.closure;
×
72
  }
73
  m_handlers.clear();
1✔
74
}
1✔
75

76

77
/*
78
 * Start this node
79
 */
80
bool PathportNode::Start() {
1✔
81
  if (m_running) {
1✔
82
    return false;
83
  }
84

85
  ola::network::InterfacePicker *picker =
1✔
86
      ola::network::InterfacePicker::NewPicker();
1✔
87
  if (!picker->ChooseInterface(&m_interface, m_preferred_ip)) {
1✔
88
    delete picker;
×
89
    OLA_INFO << "Failed to find an interface";
×
90
    return false;
×
91
  }
92
  delete picker;
1✔
93

94
  m_config_addr = IPV4Address(HostToNetwork(PATHPORT_CONFIG_GROUP));
1✔
95
  m_status_addr = IPV4Address(HostToNetwork(PATHPORT_STATUS_GROUP));
1✔
96
  m_data_addr = IPV4Address(HostToNetwork(PATHPORT_DATA_GROUP));
1✔
97

98
  if (!InitNetwork()) {
1✔
99
    return false;
100
  }
101

102
  m_socket.SetTos(m_dscp);
1✔
103
  m_running = true;
1✔
104
  SendArpReply();
1✔
105

106
  return true;
1✔
107
}
108

109

110
/*
111
 * Stop this node
112
 */
113
bool PathportNode::Stop() {
2✔
114
  if (!m_running) {
2✔
115
    return false;
116
  }
117

118
  m_socket.Close();
1✔
119
  m_running = false;
1✔
120
  return true;
1✔
121
}
122

123

124
/*
125
 * Called when there is data on this socket
126
 */
127
void PathportNode::SocketReady(UDPSocket *socket) {
×
128
  pathport_packet_s packet;
×
129
  ssize_t packet_size = sizeof(packet);
×
130
  IPV4SocketAddress source;
×
131

132
  if (!socket->RecvFrom(reinterpret_cast<uint8_t*>(&packet),
×
133
                        &packet_size, &source))
134
    return;
135

136
  // skip packets sent by us
137
  if (source.Host() == m_interface.ip_address) {
×
138
    return;
139
  }
140

141
  if (packet_size < static_cast<ssize_t>(sizeof(packet.header))) {
×
142
    OLA_WARN << "Small pathport packet received, discarding";
×
143
    return;
×
144
  }
145
  packet_size -= static_cast<ssize_t>(sizeof(packet.header));
×
146

147
  // Validate header
148
  if (!ValidateHeader(packet.header)) {
×
149
    OLA_WARN << "Invalid pathport packet";
×
150
    return;
×
151
  }
152

153
  uint32_t destination = NetworkToHost(packet.header.destination);
×
154
  if (destination != m_device_id &&
×
155
      destination != PATHPORT_ID_BROADCAST &&
×
156
      destination != PATHPORT_STATUS_GROUP &&
×
157
      destination != PATHPORT_CONFIG_GROUP &&
×
158
      destination != PATHPORT_DATA_GROUP) {
159
    ola::network::IPV4Address addr(destination);
×
160
    OLA_WARN << "pathport destination not set to us: " << addr;
×
161
    return;
×
162
  }
163

164
  // TODO(simon): Handle multiple pdus here
165
  pathport_packet_pdu *pdu = &packet.d.pdu;
×
166

167
  if (packet_size < static_cast<ssize_t>(sizeof(pathport_pdu_header))) {
×
168
    OLA_WARN << "Pathport packet too small to fit a pdu header";
×
169
    return;
×
170
  }
171
  packet_size -= sizeof(pathport_pdu_header);
×
172

173
  switch (NetworkToHost(pdu->head.type)) {
×
174
    case PATHPORT_DATA:
×
175
      HandleDmxData(pdu->d.data, packet_size);
×
176
      break;
177
    case PATHPORT_ARP_REQUEST:
×
178
      SendArpReply();
×
179
      break;
180
    case PATHPORT_ARP_REPLY:
×
181
      OLA_DEBUG << "Got pathport arp reply";
×
182
      break;
×
183
    default:
×
184
      OLA_INFO << "Unhandled pathport packet with id: "
×
185
               << NetworkToHost(pdu->head.type);
×
186
  }
187
}
×
188

189

190
/*
191
 * Set the closure to be called when we receive data for this universe.
192
 * @param universe the universe to register the handler for
193
 * @param handler the Callback0 to call when there is data for this universe.
194
 * Ownership of the closure is transferred to the node.
195
 */
196
bool PathportNode::SetHandler(uint8_t universe,
×
197
                             DmxBuffer *buffer,
198
                             Callback0<void> *closure) {
199
  if (!closure) {
×
200
    return false;
201
  }
202

203
  universe_handlers::iterator iter = m_handlers.find(universe);
×
204

205
  if (iter == m_handlers.end()) {
×
206
    universe_handler handler;
×
207
    handler.buffer = buffer;
×
208
    handler.closure = closure;
×
209
    m_handlers[universe] = handler;
×
210
  } else {
211
    Callback0<void> *old_closure = iter->second.closure;
×
212
    iter->second.closure = closure;
×
213
    delete old_closure;
×
214
  }
215
  return true;
216
}
217

218

219
/*
220
 * Remove the handler for this universe
221
 * @param universe the universe handler to remove
222
 * @param true if removed, false if it didn't exist
223
 */
224
bool PathportNode::RemoveHandler(uint8_t universe) {
×
225
  universe_handlers::iterator iter = m_handlers.find(universe);
×
226

227
  if (iter != m_handlers.end()) {
×
228
    Callback0<void> *old_closure = iter->second.closure;
×
229
    m_handlers.erase(iter);
×
230
    delete old_closure;
×
231
    return true;
×
232
  }
233
  return false;
234
}
235

236

237
/*
238
 * Send an arp reply
239
 */
240
bool PathportNode::SendArpReply() {
1✔
241
  if (!m_running) {
1✔
242
    return false;
243
  }
244

245
  pathport_packet_s packet;
1✔
246

247
  // Should this go to status or config?
248
  PopulateHeader(&packet.header, PATHPORT_STATUS_GROUP);
1✔
249

250
  pathport_packet_pdu *pdu = &packet.d.pdu;
1✔
251
  pdu->head.type = HostToNetwork(static_cast<uint16_t>(PATHPORT_ARP_REPLY));
1✔
252
  pdu->head.len = HostToNetwork(
1✔
253
      static_cast<uint16_t>(sizeof(pathport_pdu_arp_reply)));
254
  pdu->d.arp_reply.id = HostToNetwork(m_device_id);
1✔
255
  m_interface.ip_address.Get(pdu->d.arp_reply.ip);
1✔
256
  pdu->d.arp_reply.manufacturer_code = NODE_MANUF_ZP_TECH;
1✔
257
  pdu->d.arp_reply.device_class = NODE_CLASS_DMX_NODE;
1✔
258
  pdu->d.arp_reply.device_type = NODE_DEVICE_PATHPORT;
1✔
259
  pdu->d.arp_reply.component_count = 1;
1✔
260

261
  unsigned int length = sizeof(pathport_packet_header) +
1✔
262
                        sizeof(pathport_pdu_header) +
263
                        sizeof(pathport_pdu_arp_reply);
264
  return SendPacket(packet, length, m_config_addr);
1✔
265
}
266

267

268
/*
269
 * Send some DMX data
270
 * @param buffer the DMX data
271
 * @return true if it was sent successfully, false otherwise
272
 */
273
bool PathportNode::SendDMX(unsigned int universe, const DmxBuffer &buffer) {
×
274
  if (!m_running) {
×
275
    return false;
276
  }
277

278
  if (universe > MAX_UNIVERSES) {
×
279
    OLA_WARN << "attempt to send to universe " << universe;
×
280
    return false;
×
281
  }
282

283
  pathport_packet_s packet;
×
284

285
  // pad to a multiple of 4 bytes
286
  unsigned int padded_size = (buffer.Size() + 3) & ~3;
×
287
  PopulateHeader(&packet.header, PATHPORT_DATA_GROUP);
×
288

289
  pathport_packet_pdu *pdu = &packet.d.pdu;
×
NEW
290
  pdu->head.type = HostToNetwork(static_cast<uint16_t>(PATHPORT_DATA));
×
291
  pdu->head.len = HostToNetwork(
×
NEW
292
      static_cast<uint16_t>(padded_size + sizeof(pathport_pdu_data)));
×
293

NEW
294
  pdu->d.data.type = HostToNetwork(static_cast<uint16_t>(XDMX_DATA_FLAT));
×
NEW
295
  pdu->d.data.channel_count = HostToNetwork(
×
NEW
296
      static_cast<uint16_t>(buffer.Size()));
×
297
  pdu->d.data.universe = 0;
×
298
  pdu->d.data.start_code = 0;
×
299
  pdu->d.data.offset = HostToNetwork(
×
300
      static_cast<uint16_t>(DMX_UNIVERSE_SIZE * universe));
301

302
  unsigned int length = padded_size;
×
303
  buffer.Get(pdu->d.data.data, &length);
×
304

305
  // pad data to multiple of 4 bytes
306
  length = sizeof(pathport_packet_header) +
×
307
           sizeof(pathport_pdu_header) +
308
           sizeof(pathport_pdu_data) + padded_size;
×
309

310
  return SendPacket(packet, length, m_data_addr);
×
311
}
312

313

314
/*
315
 * Setup the networking components.
316
 */
317
bool PathportNode::InitNetwork() {
1✔
318
  if (!m_socket.Init()) {
1✔
319
    OLA_WARN << "Socket init failed";
×
320
    return false;
×
321
  }
322

323
  if (!m_socket.Bind(IPV4SocketAddress(IPV4Address::WildCard(),
2✔
324
                                       PATHPORT_PORT))) {
1✔
325
    m_socket.Close();
×
326
    return false;
×
327
  }
328

329
  if (!m_socket.SetMulticastInterface(m_interface.ip_address)) {
1✔
330
    m_socket.Close();
×
331
    return false;
×
332
  }
333

334
  if (!m_socket.JoinMulticast(m_interface.ip_address, m_config_addr)) {
1✔
335
      OLA_WARN << "Failed to join multicast to: " << m_config_addr;
×
336
    m_socket.Close();
×
337
    return false;
×
338
  }
339

340
  if (!m_socket.JoinMulticast(m_interface.ip_address, m_data_addr)) {
1✔
341
      OLA_WARN << "Failed to join multicast to: " << m_data_addr;
×
342
    m_socket.Close();
×
343
    return false;
×
344
  }
345

346
  if (!m_socket.JoinMulticast(m_interface.ip_address, m_status_addr)) {
1✔
347
      OLA_WARN << "Failed to join multicast to: " << m_status_addr;
×
348
    m_socket.Close();
×
349
    return false;
×
350
  }
351

352
  m_socket.SetOnData(
1✔
353
      NewCallback(this, &PathportNode::SocketReady, &m_socket));
354
  return true;
1✔
355
}
356

357

358
/*
359
 * Fill in a pathport header structure
360
 */
361
void PathportNode::PopulateHeader(pathport_packet_header *header,
1✔
362
                                  uint32_t destination) {
363
  header->protocol = HostToNetwork(PATHPORT_PROTOCOL);
1✔
364
  header->version_major = MAJOR_VERSION;
1✔
365
  header->version_minor = MINOR_VERSION;
1✔
366
  header->sequence = HostToNetwork(m_sequence_number);
1✔
367
  memset(header->reserved, 0, sizeof(header->reserved));
1✔
368
  header->source = HostToNetwork(m_device_id);
1✔
369
  header->destination = HostToNetwork(destination);
1✔
370
}
1✔
371

372

373
/*
374
 * Check a pathport header structure is valid.
375
 */
376
bool PathportNode::ValidateHeader(const pathport_packet_header &header) {
×
377
  return (
×
378
      header.protocol == HostToNetwork(PATHPORT_PROTOCOL) &&
×
379
      header.version_major == MAJOR_VERSION &&
×
380
      header.version_minor == MINOR_VERSION);
×
381
}
382

383

384
/*
385
 * Handle new DMX data
386
 */
387
void PathportNode::HandleDmxData(const pathport_pdu_data &packet,
×
388
                                 unsigned int size) {
389
  if (size < sizeof(pathport_pdu_data)) {
×
390
    OLA_WARN << "Small pathport data packet received, ignoring";
×
391
    return;
×
392
  }
393

394
  // Don't handle release messages yet
395
  if (NetworkToHost(packet.type) != XDMX_DATA_FLAT) {
×
396
    return;
397
  }
398

399
  if (packet.start_code) {
×
400
    OLA_INFO << "Non-0 start code packet received, ignoring";
×
401
    return;
×
402
  }
403

404
  unsigned int offset = NetworkToHost(packet.offset) % DMX_UNIVERSE_SIZE;
×
405
  unsigned int universe = NetworkToHost(packet.offset) / DMX_UNIVERSE_SIZE;
×
406
  const uint8_t *dmx_data = packet.data;
×
407
  unsigned int data_size = std::min(
×
408
      NetworkToHost(packet.channel_count),
×
NEW
409
      static_cast<uint16_t>(size - sizeof(pathport_pdu_data)));
×
410

411
  while (data_size > 0 && universe <= MAX_UNIVERSES) {
×
412
    unsigned int channels_for_this_universe =
×
413
      std::min(data_size, DMX_UNIVERSE_SIZE - offset);
×
414

415
    universe_handlers::iterator iter = m_handlers.find(universe);
×
416
    if (iter != m_handlers.end()) {
×
417
      iter->second.buffer->SetRange(offset,
×
418
                                    dmx_data,
419
                                    channels_for_this_universe);
420
      iter->second.closure->Run();
×
421
    }
422
    data_size -= channels_for_this_universe;
×
423
    dmx_data += channels_for_this_universe;
×
424
    offset = 0;
×
425
    universe++;
×
426
  }
427
}
428

429

430
/*
431
 * @param destination the destination to target
432
 */
433
bool PathportNode::SendArpRequest(uint32_t destination) {
×
434
  if (!m_running) {
×
435
    return false;
436
  }
437

438
  pathport_packet_s packet;
×
439
  PopulateHeader(&packet.header, destination);
×
NEW
440
  packet.d.pdu.head.type = HostToNetwork(
×
441
      static_cast<uint16_t>(PATHPORT_ARP_REQUEST));
UNCOV
442
  packet.d.pdu.head.len = 0;
×
443

444
  unsigned int length = sizeof(pathport_packet_header) +
×
445
                        sizeof(pathport_pdu_header);
446
  return SendPacket(packet, length, m_status_addr);
×
447
}
448

449

450
/*
451
 * Send a packet
452
 */
453
bool PathportNode::SendPacket(const pathport_packet_s &packet,
1✔
454
                              unsigned int size,
455
                              IPV4Address destination) {
456
  ssize_t bytes_sent = m_socket.SendTo(
1✔
457
      reinterpret_cast<const uint8_t*>(&packet),
458
      size,
459
      IPV4SocketAddress(destination, PATHPORT_PORT));
1✔
460

461
  if (bytes_sent != static_cast<ssize_t>(size)) {
1✔
462
    OLA_INFO << "Only sent " << bytes_sent << " of " << size;
×
463
    return false;
×
464
  }
465
  return true;
466
}
467
}  // namespace pathport
468
}  // namespace plugin
469
}  // namespace ola
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc