libvisiontransfer  10.8.0
deviceenumeration.cpp
1 /*******************************************************************************
2  * Copyright (c) 2024 Allied Vision Technologies GmbH
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *******************************************************************************/
14 
15 #include <cstdint>
16 #include <cstring>
17 
18 #include "visiontransfer/deviceenumeration.h"
19 #include "visiontransfer/exceptions.h"
20 #include "visiontransfer/internal/networking.h"
21 #include "visiontransfer/internal/internalinformation.h"
22 
23 using namespace std;
24 using namespace visiontransfer;
25 using namespace visiontransfer::internal;
26 
27 namespace visiontransfer {
28 
29 /*************** Pimpl class containing all private members ***********/
30 
31 class DeviceEnumeration::Pimpl {
32 public:
33  Pimpl();
34  ~Pimpl();
35  DeviceInfo* getDevicesPointer(int* numDevices);
36 
37 private:
38  static constexpr int RESPONSE_WAIT_TIME_MS = 50;
39  SOCKET sock;
40  std::vector<DeviceInfo> deviceList;
41 
42  std::vector<sockaddr_in> findBroadcastAddresses();
43  void sendDiscoverBroadcast();
44  DeviceEnumeration::DeviceList collectDiscoverResponses();
45 };
46 
47 /******************** Stubs for all public members ********************/
48 
49 DeviceEnumeration::DeviceEnumeration():
50  pimpl(new Pimpl()) {
51  // All initialization in the pimpl class
52 }
53 
54 DeviceEnumeration::~DeviceEnumeration() {
55  delete pimpl;
56 }
57 
58 DeviceInfo* DeviceEnumeration::getDevicesPointer(int* numDevices) {
59  return pimpl->getDevicesPointer(numDevices);
60 }
61 
62 /******************** Implementation in pimpl class *******************/
63 
64 DeviceEnumeration::Pimpl::Pimpl() {
65  Networking::initNetworking();
66 
67  // Create socket
68  if((sock = ::socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) {
69  TransferException ex("Error creating broadcast socket: " + Networking::getLastErrorString());
70  throw ex;
71  }
72 
73  // Set broadcast flag
74  int broadcastPermission = 1;
75  if(setsockopt(sock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<char*>(&broadcastPermission),
76  sizeof(broadcastPermission)) < 0) {
77  TransferException ex("Error setting socket broadcast flag: " + Networking::getLastErrorString());
78  throw ex;
79  }
80 
81  // Set sending and receive timeouts
82 #ifdef _WIN32
83  unsigned int timeout = RESPONSE_WAIT_TIME_MS;
84 #else
85  struct timeval timeout;
86  timeout.tv_sec = 0;
87  timeout.tv_usec = RESPONSE_WAIT_TIME_MS*1000;
88 #endif
89 
90  setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<char*>(&timeout), sizeof(timeout));
91  setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<char*>(&timeout), sizeof(timeout));
92 }
93 
94 DeviceEnumeration::Pimpl::~Pimpl() {
95  close(sock);
96 }
97 
98 DeviceInfo* DeviceEnumeration::Pimpl::getDevicesPointer(int* numDevices) {
99  sendDiscoverBroadcast();
100  deviceList = collectDiscoverResponses();
101 
102  // Convert vector to simple pointer
103  *numDevices = (int) deviceList.size();
104  return deviceList.data();
105 }
106 
107 void DeviceEnumeration::Pimpl::sendDiscoverBroadcast() {
108  std::vector<sockaddr_in> addresses = findBroadcastAddresses();
109  for(sockaddr_in addr: addresses) {
110  addr.sin_port = htons(InternalInformation::DISCOVERY_BROADCAST_PORT);
111 
112  if (sendto(sock, InternalInformation::DISCOVERY_BROADCAST_MSG,
113  sizeof(InternalInformation::DISCOVERY_BROADCAST_MSG)-1, 0,
114  (struct sockaddr *) &addr, sizeof(addr))
115  != sizeof(InternalInformation::DISCOVERY_BROADCAST_MSG)-1) {
116  throw std::runtime_error("Error sending broadcast message");
117  }
118  }
119 }
120 
121 DeviceEnumeration::DeviceList DeviceEnumeration::Pimpl::collectDiscoverResponses() {
122  DeviceList ret;
123 
124  while(true) {
126  sockaddr_in senderAddress;
127  socklen_t senderLength = sizeof(senderAddress);
128 
129  int received = recvfrom(sock, reinterpret_cast<char*>(&msg), sizeof(msg),
130  0, (sockaddr *)&senderAddress, &senderLength);
131 
132  if(received < 0) {
133  // There are no more replies
134  break;
135  }
136  bool isLegacy = received == (int) sizeof(InternalInformation::DiscoveryMessageBasic);
137  bool isLegacyWithStatusInfo = received == (int) sizeof(InternalInformation::DiscoveryMessageWithStatus);
138  if(!(isLegacy||isLegacyWithStatusInfo)) {
139  if ( ((received < (int) sizeof(InternalInformation::DiscoveryMessageExtensibleV0)))
140  || ((received < (int) sizeof(InternalInformation::DiscoveryMessageExtensibleV1)) && (msg.discoveryExtensionVersion >= 0x01))
141  ) {
142  // Malformed message, truncated relative to reported format
143  continue;
144  }
145  }
146 
147  // Zero terminate version string
148  char fwVersion[sizeof(msg.firmwareVersion)+1];
149  memcpy(fwVersion, msg.firmwareVersion, sizeof(msg.firmwareVersion));
150  fwVersion[sizeof(msg.firmwareVersion)] = '\0';
151 
152  DeviceStatus status;
153  if (!isLegacy) {
154  // Construct health status report
155  status = DeviceStatus(msg.lastFps, msg.jumboSize, msg.currentCaptureSource);
156  }
157 
158  // Fallback for undefined fields
159  std::string serial = "N/A";
160 
161  if (!(isLegacy||isLegacyWithStatusInfo)) {
162  // Parse extension fields up to maximum supported reported version
163  if (msg.discoveryExtensionVersion >= 0x01) {
164  serial = std::string(msg.serialNumber);
165  }
166  // [Append subsequent extension levels here]
167 
168  if (msg.discoveryExtensionVersion > InternalInformation::CURRENT_DISCOVERY_EXTENSION_VERSION) {
169  // Device is sending more fields than we know of (library not up-to-date)
170  //
171  // Could warn here (but should continue)
172  }
173  }
174 
175  char* ip_addr = inet_ntoa(senderAddress.sin_addr);
176  // Add to result list
177  DeviceInfo info(
178  ip_addr,
179  msg.useTcp ? DeviceInfo::PROTOCOL_TCP : DeviceInfo::PROTOCOL_UDP,
180  fwVersion,
181  (DeviceInfo::DeviceModel)msg.model,
182  msg.protocolVersion == InternalInformation::CURRENT_PROTOCOL_VERSION,
183  serial,
184  status
185  );
186  ret.push_back(info);
187  }
188 
189  return ret;
190 }
191 
192 std::vector<sockaddr_in> DeviceEnumeration::Pimpl::findBroadcastAddresses() {
193  std::vector<sockaddr_in> ret;
194 
195 #ifndef _WIN32
196  // BSD-style implementation
197  struct ifaddrs * ifap;
198  if (getifaddrs(&ifap) == 0) {
199  struct ifaddrs * p = ifap;
200  while(p) {
201  if(p->ifa_dstaddr != nullptr && p->ifa_dstaddr->sa_family == AF_INET) {
202  ret.push_back(*reinterpret_cast<sockaddr_in*>(p->ifa_dstaddr));
203  }
204  p = p->ifa_next;
205  }
206  freeifaddrs(ifap);
207  }
208 #else
209  // Windows XP style implementation
210 
211  // Adapted from example code at http://msdn2.microsoft.com/en-us/library/aa365917.aspx
212  // Now get Windows' IPv4 addresses table. We gotta call GetIpAddrTable()
213  // multiple times in order to deal with potential race conditions properly.
214  MIB_IPADDRTABLE* ipTable = nullptr;
215  ULONG bufLen = 0;
216  for (int i=0; i<5; i++) {
217  DWORD ipRet = GetIpAddrTable(ipTable, &bufLen, false);
218  if (ipRet == ERROR_INSUFFICIENT_BUFFER) {
219  if(ipTable != nullptr) {
220  delete []reinterpret_cast<unsigned char*>(ipTable); // in case we had previously allocated it
221  }
222  ipTable = reinterpret_cast<MIB_IPADDRTABLE *>(new unsigned char[bufLen]);
223  memset(ipTable, 0, bufLen);
224  } else if (ipRet == NO_ERROR) {
225  break;
226  } else {
227  if(ipTable != nullptr) {
228  delete []reinterpret_cast<unsigned char*>(ipTable);
229  }
230  break;
231  }
232  }
233 
234  if (ipTable != nullptr) {
235  for (DWORD i=0; i<ipTable->dwNumEntries; i++) {
236  const MIB_IPADDRROW & row = ipTable->table[i];
237 
238  uint32_t ipAddr = row.dwAddr;
239  uint32_t netmask = row.dwMask;
240  uint32_t baddr = ipAddr & netmask;
241  if (row.dwBCastAddr) {
242  baddr |= ~netmask;
243  }
244 
245  sockaddr_in addr;
246  memset(&addr, 0, sizeof(addr));
247  addr.sin_family = AF_INET;
248  addr.sin_addr.s_addr = baddr;
249  ret.push_back(addr);
250  }
251 
252  delete []reinterpret_cast<unsigned char*>(ipTable);
253  }
254 #endif
255 
256  return ret;
257 }
258 
259 } // namespace
260 
visiontransfer::internal::InternalInformation::DiscoveryMessageExtensibleV1
Definition: internalinformation.h:79
visiontransfer::internal::InternalInformation::DiscoveryMessageWithStatus
Definition: internalinformation.h:69
visiontransfer::internal::InternalInformation::DiscoveryMessageExtensibleV0
Definition: internalinformation.h:75
visiontransfer::DeviceInfo
Aggregates information about a discovered device.
Definition: deviceinfo.h:59
visiontransfer::internal::InternalInformation::DiscoveryMessageBasic
Definition: internalinformation.h:63
visiontransfer::TransferException
Exception class that is used for all transfer exceptions.
Definition: exceptions.h:45
visiontransfer::DeviceStatus
Representation of the current device status / health. Useful for addressing issues with peripherals o...
Definition: deviceinfo.h:38
visiontransfer::internal::InternalInformation::DiscoveryMessage
Definition: internalinformation.h:83
Allied Vision