libvisiontransfer  10.6.0
deviceenumeration.cpp
1 /*******************************************************************************
2  * Copyright (c) 2023 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/networking.h"
21 #include "visiontransfer/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 = 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 == sizeof(InternalInformation::DiscoveryMessageBasic);
137  if((received != sizeof(msg)) && !isLegacy ) {
138  // Invalid message
139  continue;
140  }
141 
142  // Zero terminate version string
143  char fwVersion[sizeof(msg.firmwareVersion)+1];
144  memcpy(fwVersion, msg.firmwareVersion, sizeof(msg.firmwareVersion));
145  fwVersion[sizeof(msg.firmwareVersion)] = '\0';
146 
147  DeviceStatus status;
148  if (!isLegacy) {
149  // Construct health status report
150  status = DeviceStatus(msg.lastFps, msg.jumboSize, msg.currentCaptureSource);
151  }
152 
153  // Add to result list
154  DeviceInfo info(
155  inet_ntoa(senderAddress.sin_addr),
156  msg.useTcp ? DeviceInfo::PROTOCOL_TCP : DeviceInfo::PROTOCOL_UDP,
157  fwVersion,
158  (DeviceInfo::DeviceModel)msg.model,
159  msg.protocolVersion == InternalInformation::CURRENT_PROTOCOL_VERSION,
160  status
161  );
162  ret.push_back(info);
163  }
164 
165  return ret;
166 }
167 
168 std::vector<sockaddr_in> DeviceEnumeration::Pimpl::findBroadcastAddresses() {
169  std::vector<sockaddr_in> ret;
170 
171 #ifndef _WIN32
172  // BSD-style implementation
173  struct ifaddrs * ifap;
174  if (getifaddrs(&ifap) == 0) {
175  struct ifaddrs * p = ifap;
176  while(p) {
177  if(p->ifa_dstaddr != nullptr && p->ifa_dstaddr->sa_family == AF_INET) {
178  ret.push_back(*reinterpret_cast<sockaddr_in*>(p->ifa_dstaddr));
179  }
180  p = p->ifa_next;
181  }
182  freeifaddrs(ifap);
183  }
184 #else
185  // Windows XP style implementation
186 
187  // Adapted from example code at http://msdn2.microsoft.com/en-us/library/aa365917.aspx
188  // Now get Windows' IPv4 addresses table. We gotta call GetIpAddrTable()
189  // multiple times in order to deal with potential race conditions properly.
190  MIB_IPADDRTABLE* ipTable = nullptr;
191  ULONG bufLen = 0;
192  for (int i=0; i<5; i++) {
193  DWORD ipRet = GetIpAddrTable(ipTable, &bufLen, false);
194  if (ipRet == ERROR_INSUFFICIENT_BUFFER) {
195  if(ipTable != nullptr) {
196  delete []reinterpret_cast<unsigned char*>(ipTable); // in case we had previously allocated it
197  }
198  ipTable = reinterpret_cast<MIB_IPADDRTABLE *>(new unsigned char[bufLen]);
199  memset(ipTable, 0, bufLen);
200  } else if (ipRet == NO_ERROR) {
201  break;
202  } else {
203  if(ipTable != nullptr) {
204  delete []reinterpret_cast<unsigned char*>(ipTable);
205  }
206  break;
207  }
208  }
209 
210  if (ipTable != nullptr) {
211  for (DWORD i=0; i<ipTable->dwNumEntries; i++) {
212  const MIB_IPADDRROW & row = ipTable->table[i];
213 
214  uint32_t ipAddr = row.dwAddr;
215  uint32_t netmask = row.dwMask;
216  uint32_t baddr = ipAddr & netmask;
217  if (row.dwBCastAddr) {
218  baddr |= ~netmask;
219  }
220 
221  sockaddr_in addr;
222  memset(&addr, 0, sizeof(addr));
223  addr.sin_family = AF_INET;
224  addr.sin_addr.s_addr = baddr;
225  ret.push_back(addr);
226  }
227 
228  delete []reinterpret_cast<unsigned char*>(ipTable);
229  }
230 #endif
231 
232  return ret;
233 }
234 
235 } // namespace
236 
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:69
Allied Vision