AJA NTV2 SDK  18.0.0.2122
NTV2 SDK 18.0.0.2122
ntv2nubaccess.cpp
Go to the documentation of this file.
1 /* SPDX-License-Identifier: MIT */
7 #include "ajatypes.h"
8 #include "ntv2utils.h"
9 #include "ntv2nubaccess.h"
10 #include "ntv2publicinterface.h"
11 #include "ntv2version.h"
12 #include "ajabase/system/debug.h"
13 #include "ajabase/common/common.h"
15 #include "ajabase/system/atomic.h"
16 #include "ajabase/system/info.h" // for AJASystemInfo
17 #include <iomanip>
18 #if !defined(NTV2_PREVENT_PLUGIN_LOAD)
20  #include "ajabase/system/thread.h"
21  #include <fstream>
22  #include "mbedtls/x509.h"
23  #include "mbedtls/error.h"
24  #include "mbedtls/md.h"
25  #include "mbedtls/ssl.h"
26 #endif // defined(NTV2_PREVENT_PLUGIN_LOAD)
27 #if defined(AJAMac)
28  #include <CoreFoundation/CoreFoundation.h>
29  #include <dlfcn.h>
30  #define DLL_EXTENSION ".dylib"
31  #define PATH_DELIMITER "/"
32  #define FIRMWARE_FOLDER "Firmware"
33 #elif defined(AJALinux)
34  #include <dlfcn.h>
35  #define DLL_EXTENSION ".so"
36  #define PATH_DELIMITER "/"
37  #define FIRMWARE_FOLDER "firmware"
38 #elif defined(MSWindows)
39  #define DLL_EXTENSION ".dll"
40  #define PATH_DELIMITER "\\"
41  #define FIRMWARE_FOLDER "Firmware"
42 #elif defined(AJABareMetal)
43  #define DLL_EXTENSION ".so"
44  #define PATH_DELIMITER "/"
45  #define FIRMWARE_FOLDER "firmware"
46 #endif
47 #define SIG_EXTENSION ".sig"
48 
49 using namespace std;
50 
51 #define INSTP(_p_) xHEX0N(uint64_t(_p_),16)
52 #define NBFAIL(__x__) AJA_sERROR (AJA_DebugUnit_RPCClient, INSTP(this) << "::" << AJAFUNC << ": " << __x__)
53 #define NBWARN(__x__) AJA_sWARNING(AJA_DebugUnit_RPCClient, INSTP(this) << "::" << AJAFUNC << ": " << __x__)
54 #define NBNOTE(__x__) AJA_sNOTICE (AJA_DebugUnit_RPCClient, INSTP(this) << "::" << AJAFUNC << ": " << __x__)
55 #define NBINFO(__x__) AJA_sINFO (AJA_DebugUnit_RPCClient, INSTP(this) << "::" << AJAFUNC << ": " << __x__)
56 #define NBDBG(__x__) AJA_sDEBUG (AJA_DebugUnit_RPCClient, INSTP(this) << "::" << AJAFUNC << ": " << __x__)
57 #define NBCFAIL(__x__) AJA_sERROR (AJA_DebugUnit_RPCClient, AJAFUNC << ": " << __x__)
58 #define NBCWARN(__x__) AJA_sWARNING(AJA_DebugUnit_RPCClient, AJAFUNC << ": " << __x__)
59 #define NBCNOTE(__x__) AJA_sNOTICE (AJA_DebugUnit_RPCClient, AJAFUNC << ": " << __x__)
60 #define NBCINFO(__x__) AJA_sINFO (AJA_DebugUnit_RPCClient, AJAFUNC << ": " << __x__)
61 #define NBCDBG(__x__) AJA_sDEBUG (AJA_DebugUnit_RPCClient, AJAFUNC << ": " << __x__)
62 #define NBSFAIL(__x__) AJA_sERROR (AJA_DebugUnit_RPCServer, AJAFUNC << ": " << __x__)
63 #define NBSWARN(__x__) AJA_sWARNING(AJA_DebugUnit_RPCServer, AJAFUNC << ": " << __x__)
64 #define NBSNOTE(__x__) AJA_sNOTICE (AJA_DebugUnit_RPCServer, AJAFUNC << ": " << __x__)
65 #define NBSINFO(__x__) AJA_sINFO (AJA_DebugUnit_RPCServer, AJAFUNC << ": " << __x__)
66 #define NBSDBG(__x__) AJA_sDEBUG (AJA_DebugUnit_RPCServer, AJAFUNC << ": " << __x__)
67 
68 #define PLGFAIL(__x__) AJA_sERROR (AJA_DebugUnit_Plugins, AJAFUNC << ": " << __x__)
69 #define PLGWARN(__x__) AJA_sWARNING(AJA_DebugUnit_Plugins, AJAFUNC << ": " << __x__)
70 #define PLGNOTE(__x__) AJA_sNOTICE (AJA_DebugUnit_Plugins, AJAFUNC << ": " << __x__)
71 #define PLGINFO(__x__) AJA_sINFO (AJA_DebugUnit_Plugins, AJAFUNC << ": " << __x__)
72 #define PLGDBG(__x__) AJA_sDEBUG (AJA_DebugUnit_Plugins, AJAFUNC << ": " << __x__)
73 
74 #define P_FAIL(__x__) do \
75  { \
76  ostringstream _os_; \
77  _os_ << AJAFUNC << ": " << __x__; \
78  if (useStdout()) \
79  cout << "## ERROR: " << _os_.str() << endl; \
80  AJA_sERROR (AJA_DebugUnit_Plugins, _os_.str()); \
81  errMsg = _os_.str(); \
82  } while (false)
83 #define P_WARN(__x__) if (useStdout()) cout << "## WARNING: " << AJAFUNC << ": " << __x__ << endl; \
84  AJA_sWARNING(AJA_DebugUnit_Plugins, AJAFUNC << ": " << __x__)
85 #define P_NOTE(__x__) if (useStdout()) cout << "## NOTE: " << AJAFUNC << ": " << __x__ << endl; \
86  AJA_sNOTICE (AJA_DebugUnit_Plugins, AJAFUNC << ": " << __x__)
87 #define P_INFO(__x__) if (useStdout()) cout << "## INFO: " << AJAFUNC << ": " << __x__ << endl; \
88  AJA_sINFO (AJA_DebugUnit_Plugins, AJAFUNC << ": " << __x__)
89 #define P_DBG(__x__) if (useStdout()) cout << "## DEBUG: " << AJAFUNC << ": " << __x__ << endl; \
90  AJA_sDEBUG (AJA_DebugUnit_Plugins, AJAFUNC << ": " << __x__)
91 //#define _DEBUGSTATS_ // Define this to log above construct/destruct & open/close tallies
92 #if defined(_DEBUGSTATS_)
93  #define PDBGX(__x__) AJA_sDEBUG (AJA_DebugUnit_Plugins, INSTP(this) << "::" << AJAFUNC << ": " << __x__)
94 #else
95  #define PDBGX(__x__)
96 #endif
97 
98 
99 // Stats
100 uint32_t gBaseConstructCount(0); // Number of NTV2RPCBase constructor calls made
101 uint32_t gBaseDestructCount(0); // Number of NTV2RPCBase destructor calls made
102 uint32_t gClientConstructCount(0); // Number of NTV2RPCClient constructor calls made
103 uint32_t gClientDestructCount(0); // Number of NTV2RPCClient destructor calls made
104 uint32_t gServerConstructCount(0); // Number of NTV2RPCServer constructor calls made
105 uint32_t gServerDestructCount(0); // Number of NTV2RPCServer destructor calls made
106 uint32_t gLoaderConstructCount(0); // Number of NTV2PluginLoader constructor calls made
107 uint32_t gLoaderDestructCount(0); // Number of NTV2PluginLoader destructor calls made
108 uint32_t gPluginConstructCount(0); // Number of NTV2Plugin constructor calls made
109 uint32_t gPluginDestructCount(0); // Number of NTV2Plugin destructor calls made
110 
111 
112 string NTV2Dictionary::valueForKey (const string & inKey) const
113 {
114  DictConstIter it(mDict.find(inKey));
115  if (it == mDict.end())
116  return "";
117  return it->second;
118 }
119 
120 uint16_t NTV2Dictionary::u16ValueForKey (const string & inKey, const uint16_t inDefault) const
121 {
122  string str(valueForKey(inKey));
123  if (str.empty())
124  return inDefault;
125  if (str.find("0x") == 0 || str.find("0X") == 0)
126  {
127  str.erase(0,2);
128  if (str.empty())
129  return inDefault;
130  return uint16_t(aja::stoul(str, AJA_NULL, 16));
131  }
132  if (str.find("x") == 0 || str.find("X") == 0)
133  {
134  str.erase(0,1);
135  if (str.empty())
136  return inDefault;
137  return uint16_t(aja::stoul(str, AJA_NULL, 16));
138  }
139  if (str.find("o") == 0 || str.find("O") == 0)
140  {
141  str.erase(0,1);
142  if (str.empty())
143  return inDefault;
144  return uint16_t(aja::stoul(str, AJA_NULL, 8));
145  }
146  if (str.find("b") == 0 || str.find("B") == 0)
147  {
148  str.erase(0,1);
149  if (str.empty())
150  return inDefault;
151  return uint16_t(aja::stoul(str, AJA_NULL, 2));
152  }
153  return uint16_t(aja::stoul(str, AJA_NULL, 10));
154 }
155 
156 ostream & NTV2Dictionary::Print (ostream & oss, const bool inCompact) const
157 {
158  if (inCompact)
159  for (DictConstIter it(mDict.begin()); it != mDict.end(); )
160  {
161  const string & key(it->first), val(it->second), quote(val.find(' ') != string::npos ? "'" : "");
162  oss << key << "=" << quote << val << quote;
163  if (++it != mDict.end())
164  oss << " ";
165  }
166  else if (empty())
167  oss << "0 entries";
168  else
169  {
170  const int kyWdth(int(largestKeySize()+0)), valWdth(int(largestValueSize()+0));
171  oss << string(size_t(kyWdth), '-') << " " << string(size_t(valWdth), '-') << endl;
172  for (DictConstIter it(mDict.begin()); it != mDict.end(); )
173  {
174  const string & key(it->first), val(it->second);
175  oss << std::setw(kyWdth) << key << " : " << val;
176  if (++it != mDict.end())
177  oss << endl;
178  }
179  }
180  return oss;
181 }
182 
183 bool NTV2Dictionary::deserialize (const string & inStr)
184 {
185  size_t badKVPairs(0), insertFailures(0);
186  clear();
187  const NTV2StringList keyValPairs (aja::split(inStr, "\n"));
188  for (NTV2StringListConstIter it(keyValPairs.begin()); it != keyValPairs.end(); ++it)
189  {
190  const NTV2StringList keyValPair (aja::split(*it, "\t"));
191  if (keyValPair.size() != 2)
192  {badKVPairs++; continue;}
193  const string k(keyValPair.at(0)), v(keyValPair.at(1));
194  if (!insert(k, v))
195  insertFailures++;
196  }
197  return !empty() && !badKVPairs && !insertFailures;
198 }
199 
200 bool NTV2Dictionary::serialize (string & outStr) const
201 {
202  outStr.clear();
203  ostringstream oss;
204  for (DictConstIter it(mDict.begin()); it != mDict.end(); )
205  {
206  oss << it->first << "\t" << it->second;
207  if (++it != mDict.end())
208  oss << "\n";
209  }
210  outStr = oss.str();
211  return !outStr.empty();
212 }
213 
215 {
216  NTV2StringSet result;
217  for (DictConstIter it(mDict.begin()); it != mDict.end(); ++it)
218  result.insert(it->first);
219  return result;
220 }
221 
223 {
224  size_t result(0);
225  for (DictConstIter it(mDict.begin()); it != mDict.end(); ++it)
226  if (it->first.length() > result)
227  result = it->first.length();
228  return result;
229 }
230 
232 {
233  size_t result(0);
234  for (DictConstIter it(mDict.begin()); it != mDict.end(); ++it)
235  if (it->second.length() > result)
236  result = it->second.length();
237  return result;
238 }
239 
240 bool NTV2Dictionary::insert (const string & inKey, const string & inValue)
241 {
242  if (inKey.empty())
243  return false;
244  if (inKey.find("\t") != string::npos)
245  return false;
246  if (inKey.find("\n") != string::npos)
247  return false;
248  if (inValue.find("\t") != string::npos)
249  return false;
250  if (inValue.find("\n") != string::npos)
251  return false;
252  try {
253  mDict[inKey] = inValue; // Insert or update
254  } catch (bad_alloc) {
255  return false; // no memory
256  } catch (...) {
257  return false; // failed
258  }
259  return true;
260 }
261 
263 {
264  size_t numUpdated(0);
265  for (DictConstIter it(inDict.mDict.begin()); it != inDict.mDict.end(); ++it)
266  if (hasKey(it->first))
267  {mDict[it->first] = it->second; numUpdated++;}
268  return numUpdated;
269 }
270 
272 {
273  size_t numAdded(0);
274  for (DictConstIter it(inDict.mDict.begin()); it != inDict.mDict.end(); ++it)
275  if (!hasKey(it->first))
276  {mDict[it->first] = it->second; numAdded++;}
277  return numAdded;
278 }
279 
280 
282 {
283  Reset(inSpec);
284 }
285 
286 void NTV2DeviceSpecParser::Reset (const string inSpec)
287 {
288  mErrors.clear();
289  mResult.clear();
290  mQueryParams.clear();
291  mPos = 0;
292  mSpec = inSpec;
293  if (!mSpec.empty())
294  Parse(); // Go ahead and parse it
295 }
296 
297 string NTV2DeviceSpecParser::Resource (const bool inStripLeadSlash) const
298 {
299  string rsrc (Result(kConnectParamResource));
300  if (rsrc.empty())
301  return rsrc;
302  if (!inStripLeadSlash)
303  return rsrc;
304  if (rsrc.at(0) == '/')
305  rsrc.erase(0,1);
306  return rsrc;
307 }
308 
309 void NTV2DeviceSpecParser::Parse (void)
310 {
311  // A run of 3 consecutive letters that match "ntv" -- probably a scheme
312  // A run of 1 or 2 decimal digits -- probably a local device index number
313  // "0X" or "0x":
314  // - maybe a hexadecimal 32-bit value -- a local device ID
315  // - maybe a hexadecimal 64-bit value -- a local device serial number
316  // A run of 8 or 9 alphanumeric chars -- probably a local device serial number
317  ostringstream err;
318  string tokDevID, tokIndexNum, tokScheme, tokSerial, tokModelName, tokIPV4, tokPortNum;
319  size_t posDevID(0), posIndexNum(0), posScheme(0), posSerial(0), posModelName(0), posNetAddr(0);
320  bool isSerial(ParseSerialNum(posSerial, tokSerial)), isScheme(ParseScheme(posScheme, tokScheme));
321  bool isIndexNum(ParseDecNumber(posIndexNum, tokIndexNum)), isDeviceID(ParseDeviceID(posDevID, tokDevID));
322  bool isModelName(ParseModelName(posModelName, tokModelName));
323  bool isIPV4Port(ParseHostAddressAndPortNumber(posNetAddr, tokIPV4, tokPortNum));
324  if (isScheme && tokScheme == kLegalSchemeNTV2Local)
325  { // Re-parse serial#, index#, deviceID, modelName from just past "://"...
326  posDevID = posIndexNum = posSerial = posModelName = posScheme;
327  isSerial = ParseSerialNum(posSerial, tokSerial);
328  isIndexNum = ParseDecNumber(posIndexNum, tokIndexNum);
329  isDeviceID = ParseDeviceID(posDevID, tokDevID);
330  isModelName = ParseModelName(posModelName, tokModelName);
331  // Check for query...
332  size_t posQuery(0);
333  if (isDeviceID) posQuery = posDevID;
334  else if (isSerial) posQuery = posSerial;
335  else if (isModelName) posQuery = posModelName;
336  else if (isIndexNum) posQuery = posIndexNum;
337  if (posQuery)
338  {
339  NTV2Dictionary params;
340  if (ParseQuery(posQuery, params))
341  {
342  mResult.insert(kConnectParamQuery, DeviceSpec().substr(mPos, posQuery-mPos+1));
343  mQueryParams = params;
344  mPos = posQuery;
345  if (isDeviceID) posDevID = mPos;
346  else if (isSerial) posSerial = mPos;
347  else if (isModelName) posModelName = mPos;
348  else if (isIndexNum) posIndexNum = mPos;
349  }
350  }
351  }
352  do
353  {
354  if (isModelName)
355  {
356  mPos = posModelName;
358  mResult.insert(kConnectParamDevModel, tokModelName);
359  break;
360  }
361  if (isSerial)
362  { // Final serial number checks...
363  bool converted(false);
364  mPos = posSerial;
365  if (tokSerial.length() == 18) // 64-bit hex value?
366  {
367  // Convert numeric serial number into character string...
368  const bool hasLeading0X (tokSerial.find("0X") == 0 || tokSerial.find("0x") == 0);
369  const string hex64(tokSerial.substr(hasLeading0X ? 2 : 0, 16));
370  const ULWord64 serNum64(aja::stoull(hex64, AJA_NULL, 16));
371  string serTxt; // (CNTV2Card::SerialNum64ToString(serNum64));
372  for (size_t ndx(0); ndx < 8; ndx++)
373  serTxt += char(serNum64 >> ((7-ndx)*8));
374  //cerr << "Converted '" << tokSerial << "' into '" << serTxt << "'" << endl;
375  tokSerial = serTxt;
376  converted = true;
377  }
378  // Check for illegal characters in serial number:
379  for (size_t ndx(0); ndx < tokSerial.length(); ndx++)
380  { char ch(tokSerial.at(ndx));
381  if ( ! ( ( (ch >= '0') && (ch <= '9') ) ||
382  ( (ch >= 'A') && (ch <= 'Z') ) ||
383  ( (ch >= 'a') && (ch <= 'z') ) ||
384  (ch == ' ') || (ch == '-') ) )
385  {
386  err << "Illegal serial number character '" << (ch ? ch : '?') << "' (" << xHEX0N(UWord(ch),2) << ")";
387  AddError(err.str());
388  mPos -= converted ? 16 : 8; mPos += ndx * (converted ? 2 : 1) + (converted ? 1 : 0);
389  break;
390  }
391  }
392  mResult.insert(kConnectParamDevSerial, tokSerial);
394  break;
395  }
396  if (isDeviceID)
397  {
398  mPos = posDevID;
399  mResult.insert(kConnectParamDevID, tokDevID);
401  break;
402  }
403  if (isIndexNum)
404  {
405  if (posIndexNum < SpecLength())
406  { // Check if extra chars past index num is dotted quad:
407  if (isIPV4Port && !tokIPV4.empty())
408  {
409  mPos = posNetAddr;
410  mResult.insert(kConnectParamScheme, "ntv2nubrpclib");
411  mResult.insert(kConnectParamHost, tokIPV4);
412  if (!tokPortNum.empty())
413  mResult.insert(kConnectParamPort, tokPortNum);
414  break;
415  }
416  err << "Extra characters past index number";
417  AddError(err.str());
418  break;
419  }
420  mPos = posIndexNum;
421  mResult.insert(kConnectParamDevIndex, tokIndexNum);
423  break;
424  }
425  if (!isScheme || (isScheme && tokScheme == kLegalSchemeNTV2Local))
426  { // No such local device
427  err << "Invalid local device specification";
428  AddError(err.str());
429  mPos += isScheme ? 12 : 0;
430  break;
431  }
432  if (isScheme)
433  { // Continue parsing URLspec...
434  mPos = posScheme;
435  // "xxxx://swdevice/?"
436  // "nosharedmemory"
437  // "&supportlog=file%3A%2F%2F%2FUsers%2Fdemo%2FDesktop%2FAJAWatcherSupport.log"
438  // "&sdram=file%3A%2F%2F%2FUsers%2Fdemo%2FDesktop%2FSDRAMsnapshot.dat");
439  // Host[port]/[resource[?query]]
440  size_t posURL(posScheme), posRsrc(0);
441  string host, port, rsrcPath;
442  if (!ParseHostAddressAndPortNumber(posURL, host, port))
443  {mPos = posURL; AddError("Bad host address or port number"); break;}
444  mPos = posURL;
445  mResult.insert(kConnectParamScheme, tokScheme);
446  mResult.insert(kConnectParamHost, host);
447  if (!port.empty())
448  mResult.insert(kConnectParamPort, port);
449 
450  // Parse resource path...
451  posRsrc = mPos;
452  if (ParseResourcePath(posRsrc, rsrcPath))
453  {mPos = posRsrc; mResult.insert(kConnectParamResource, rsrcPath);}
454  // Parse query...
455  size_t posQuery(mPos);
456  NTV2Dictionary params;
457  if (ParseQuery(posQuery, params))
458  {
459  mResult.insert(kConnectParamQuery, DeviceSpec().substr(mPos, posQuery-mPos+1));
460  mQueryParams = params;
461  mPos = posQuery;
462  }
463  }
464  } while (false); // Once thru
465  if (mPos < SpecLength())
466  {err << "Parser failed at character position " << DEC(mPos); AddError(err.str());}
467  #if defined(_DEBUG)
468  ostringstream oss;
469  if (Successful())
470  { oss << "NTV2DeviceSpecParser::Parse success: '" << DeviceSpec() << "' -- ";
471  Print(oss);
473  }
474  else
475  { oss << "NTV2DeviceSpecParser::Parse failed: ";
476  PrintErrors(oss);
478  }
479  #endif // defined(_DEBUG)
480 } // Parse
481 
482 ostream & NTV2DeviceSpecParser::Print (ostream & oss, const bool inDumpResults) const
483 {
484  if (IsLocalDevice())
485  oss << "local device";
486  else if (HasScheme())
487  oss << "device '" << Scheme() << "'";
488  else
489  oss << "device";
490  if (HasResult(kConnectParamDevSerial))
491  oss << " serial '" << DeviceSerial() << "'";
492  else if (HasResult(kConnectParamDevModel))
493  oss << " model '" << DeviceModel() << "'";
494  else if (HasResult(kConnectParamDevID))
495  oss << " ID '" << DeviceID() << "'";
496  else if (HasResult(kConnectParamDevIndex))
497  oss << " " << DeviceIndex();
498  if (HasResult(kConnectParamHost))
499  oss << " host '" << Result(kConnectParamHost) << "'";
500  if (HasResult(kConnectParamPort))
501  oss << " port " << Result(kConnectParamPort);
502  if (HasResult(kConnectParamResource))
503  oss << " resource '" << Result(kConnectParamResource) << "'";
504  if (HasResult(kConnectParamQuery))
505  oss << " query '" << Result(kConnectParamQuery) << "'";
506  if (inDumpResults)
507  {oss << endl; Results().Print(oss, /*compact?*/false);}
508  return oss;
509 }
510 
511 string NTV2DeviceSpecParser::MakeDeviceSpec (const bool urlEncodeQuery) const
512 {
513  if (!Successful())
514  return "";
515  ostringstream result;
516  if (IsLocalDevice())
517  {
518  result << "ntv2local://";
519  if (HasResult(kConnectParamDevSerial))
520  result << DeviceSerial();
521  else if (HasResult(kConnectParamDevModel))
522  result << DeviceModel();
523  else if (HasResult(kConnectParamDevID))
524  result << DeviceID();
525  else if (HasResult(kConnectParamDevIndex))
526  result << DeviceIndex();
527  else
528  return "";
529  return result.str();
530  }
531  result << Scheme() << "://";
532  if (HasResult(kConnectParamHost))
533  result << Result(kConnectParamHost);
534  if (HasResult(kConnectParamPort))
535  result << ":" << Result(kConnectParamPort);
536  result << Result(kConnectParamResource);
537  if (HasQueryParams())
538  {
539  string q (MakeQueryString(urlEncodeQuery));
540  if (!q.empty())
541  result << "?" << q;
542  }
543  return result.str();
544 }
545 
546 string NTV2DeviceSpecParser::MakeQueryString (const bool urlEncode) const
547 {
548  if (!Successful())
549  return "";
550  if (!HasQueryParams())
551  return "";
552  NTV2StringList parms;
553  const NTV2StringSet ks (mQueryParams.keys());
554  for (NTV2StringSetConstIter it(ks.begin()); it != ks.end(); ++it)
555  {
556  ostringstream oss;
557  string k(*it), v(mQueryParams.valueForKey(k));
558  if (urlEncode)
559  oss << ::PercentEncode(k) << "=" << ::PercentEncode(v);
560  else
561  oss << k << "=" << v;
562  parms.push_back(oss.str());
563  }
564  return aja::join(parms, "&");
565 }
566 
568 {
569  ostringstream oss;
570  Print(oss);
571  return oss.str();
572 }
573 
575 {
576  string devIDStr (Result(kConnectParamDevID));
577  if (devIDStr.find("0X") != string::npos)
578  devIDStr.erase(0,2); // Delete "0x"
579  ULWord u32 = ULWord(aja::stoull(devIDStr, AJA_NULL, 16));
580  return NTV2DeviceID(u32);
581 }
582 
584 {
585  string devIDStr (Result(kConnectParamDevIndex));
586  UWord u16 = UWord(aja::stoul(devIDStr));
587  return u16;
588 }
589 
590 ostream & NTV2DeviceSpecParser::PrintErrors (ostream & oss) const
591 {
592  oss << DEC(ErrorCount()) << (ErrorCount() == 1 ? " error" : " errors") << (HasErrors() ? ":" : "");
593  if (HasErrors())
594  {
595  oss << endl
596  << DeviceSpec() << endl
597  << string(mPos ? mPos : 0,' ') << "^" << endl;
598  for (size_t num(0); num < ErrorCount(); )
599  {
600  oss << Error(num);
601  if (++num < ErrorCount())
602  oss << endl;
603  }
604  }
605  return oss;
606 }
607 
608 bool NTV2DeviceSpecParser::ParseHexNumber (size_t & pos, string & outToken)
609 {
610  outToken.clear();
611  string tokHexNum;
612  while (pos < SpecLength())
613  {
614  const char ch(CharAt(pos));
615  if (tokHexNum.length() == 0)
616  {
617  if (ch != '0')
618  break;
619  ++pos; tokHexNum = ch;
620  }
621  else if (tokHexNum.length() == 1)
622  {
623  if (ch != 'x' && ch != 'X')
624  break;
625  ++pos; tokHexNum += ch;
626  }
627  else
628  {
629  if (!IsHexDigit(ch))
630  break;
631  ++pos; tokHexNum += ch;
632  }
633  }
634  if (tokHexNum.length() > 2) // At least 3 chars
635  {aja::upper(tokHexNum); outToken = tokHexNum;} // Force upper-case hex
636  return !outToken.empty();
637 }
638 
639 bool NTV2DeviceSpecParser::ParseDecNumber (size_t & pos, string & outToken)
640 {
641  outToken.clear();
642  string tokDecNum;
643  while (pos < SpecLength())
644  {
645  const char ch(CharAt(pos));
646  if (!IsDecimalDigit(ch))
647  break;
648  ++pos;
649  if (ch != '0' || tokDecNum != "0") // This prevents accumulating more than one leading zero
650  tokDecNum += ch;
651  }
652  if (tokDecNum.length() > 0) // At least 1 char
653  outToken = tokDecNum;
654  return !outToken.empty();
655 }
656 
657 bool NTV2DeviceSpecParser::ParseAlphaNum (size_t & pos, string & outToken, const std::string & inOtherChars)
658 { // Run of letters and/or digits, but must start with letter
659  outToken.clear();
660  string tokAlphaNum;
661  while (pos < SpecLength())
662  {
663  const char ch(CharAt(pos));
664  if (!IsLetter(ch) && !IsDecimalDigit(ch) && inOtherChars.find(ch) == string::npos)
665  break; // Break if not letter/digit
666  if (tokAlphaNum.empty() && !IsLetter(ch))
667  break; // Didn't start with letter!
668  ++pos; tokAlphaNum += ch;
669  }
670  if (tokAlphaNum.length() > 0)
671  outToken = tokAlphaNum;
672  return !outToken.empty();
673 }
674 
675 bool NTV2DeviceSpecParser::ParseAlphaNumeric (size_t & pos, string & outToken, const std::string & inOtherChars)
676 { // Run of letters and/or digits (and can start with either)
677  outToken.clear();
678  string tokAlphaNum;
679  while (pos < SpecLength())
680  {
681  const char ch(CharAt(pos));
682  if (!IsLetter(ch) && !IsDecimalDigit(ch) && inOtherChars.find(ch) == string::npos)
683  break;
684  ++pos; tokAlphaNum += ch;
685  }
686  if (tokAlphaNum.length() > 0)
687  outToken = tokAlphaNum;
688  return !outToken.empty();
689 }
690 
691 bool NTV2DeviceSpecParser::ParseScheme (size_t & pos, string & outToken)
692 {
693  outToken.clear();
694  string rawScheme, tokScheme;
695  while (ParseAlphaNum(pos, rawScheme))
696  {
697  tokScheme = rawScheme;
698  char ch(CharAt(pos));
699  if (ch != ':')
700  break;
701  ++pos; tokScheme += ch;
702 
703  ch = CharAt(pos);
704  if (ch != '/')
705  break;
706  ++pos; tokScheme += ch;
707 
708  ch = CharAt(pos);
709  if (ch != '/')
710  break;
711  ++pos; tokScheme += ch;
712  break;
713  }
714  if (tokScheme.find("://") != string::npos) // Contains "://"
715  {aja::lower(rawScheme); outToken = rawScheme;} // Force lower-case
716  return !outToken.empty();
717 }
718 
719 bool NTV2DeviceSpecParser::ParseSerialNum (size_t & pos, string & outToken)
720 {
721  outToken.clear();
722  string tokAlphaNum, tokHexNum;
723  size_t origPos(pos), posAlphaNum(pos), posHexNum(pos);
724  do
725  {
726  while (posAlphaNum < SpecLength())
727  {
728  const char ch(CharAt(posAlphaNum));
729  if (!IsUpperLetter(ch) && !IsLowerLetter(ch) && !IsDecimalDigit(ch) && ch != '-' && ch != ' ')
730  break;
731  ++posAlphaNum; tokAlphaNum += ch;
732  }
733  if (tokAlphaNum.length() < 2) // At least 2 alphanum chars
734  {tokAlphaNum.clear(); break;}
735  if (tokAlphaNum.length() == 8 || tokAlphaNum.length() == 9)
736  {pos = posAlphaNum; outToken = tokAlphaNum; break;}
737 
738  if (ParseHexNumber(posHexNum, tokHexNum))
739  if (tokHexNum.length() == 18) // 64-bit value!
740  {pos = posHexNum; outToken = tokHexNum;}
741  } while (false);
742  if (tokAlphaNum == "ntv2kona1") // HACK! Can't open 'ntv2kona1' plugin without this hack!
743  {outToken.clear(); pos = origPos; return false;} // ('ntv2kona1' looks like a serial number!)
744  return !outToken.empty();
745 }
746 
747 bool NTV2DeviceSpecParser::ParseDeviceID (size_t & pos, string & outToken)
748 {
749  outToken.clear();
750  string tokHexNum;
751  if (!ParseHexNumber(pos, tokHexNum))
752  return false;
753  if (tokHexNum.length() != 10)
754  return false;
755  aja::upper(tokHexNum); // Fold to upper case
756 
757  // Check if it matches a known supported NTV2DeviceID...
759  NTV2StringSet devIDStrs;
760  for (NTV2DeviceIDSetConstIter it(allDevIDs.begin()); it != allDevIDs.end(); ++it)
761  {
762  ostringstream devID; devID << xHEX0N(*it,8);
763  string devIDStr(devID.str());
764  aja::upper(devIDStr);
765  devIDStrs.insert(devIDStr);
766  } // for each known/supported NTV2DeviceID
767  if (devIDStrs.find(tokHexNum) != devIDStrs.end())
768  outToken = tokHexNum; // Valid!
769  return !outToken.empty();
770 }
771 
772 bool NTV2DeviceSpecParser::ParseModelName (size_t & pos, string & outToken)
773 {
774  outToken.clear();
775  string tokName;
776  if (!ParseAlphaNum(pos, tokName, " "))
777  return false;
778  aja::lower(tokName); // Fold to lower case
779 
780  // Check if it matches a known supported device model name...
782  NTV2StringSet modelNames;
783  for (NTV2DeviceIDSetConstIter it(allDevIDs.begin()); it != allDevIDs.end(); ++it)
784  {
785  string modelName(::NTV2DeviceIDToString(*it));
786  aja::lower(modelName);
787  modelNames.insert(modelName);
788  } // for each known/supported NTV2DeviceID
789  if (modelNames.find(tokName) != modelNames.end())
790  outToken = tokName; // Valid!
791  return !outToken.empty();
792 }
793 
794 bool NTV2DeviceSpecParser::ParseDNSName (size_t & pos, string & outDNSName)
795 {
796  outDNSName.clear();
797  string dnsName, name;
798  size_t dnsPos(pos);
799  char ch(0);
800  while (ParseAlphaNum(dnsPos, name, "_-")) // also allow '_' and '-'
801  {
802  if (!dnsName.empty())
803  dnsName += '.';
804  dnsName += name;
805  ch = CharAt(dnsPos);
806  if (ch != '.')
807  break;
808  ++dnsPos;
809  }
810  if (!dnsName.empty())
811  pos = dnsPos;
812  outDNSName = dnsName;
813  return !outDNSName.empty();
814 }
815 
816 bool NTV2DeviceSpecParser::ParseIPv4Address (size_t & pos, string & outIPv4)
817 {
818  outIPv4.clear();
819  NTV2StringList ipv4Name;
820  string num;
821  size_t ipv4Pos(pos);
822  char ch(0);
823  while (ParseDecNumber(ipv4Pos, num))
824  {
825  ipv4Name.push_back(num);
826  ch = CharAt(ipv4Pos);
827  if (ch != '.')
828  break;
829  ++ipv4Pos;
830  }
831  if (ipv4Name.size() == 4)
832  pos = ipv4Pos;
833  outIPv4 = aja::join(ipv4Name, ".");
834  return ipv4Name.size() == 4;
835 }
836 
837 bool NTV2DeviceSpecParser::ParseHostAddressAndPortNumber (size_t & pos, string & outAddr, string & outPort)
838 {
839  outAddr.clear(); outPort.clear();
840  // Look for a DNSName or an IPv4 dotted quad...
841  string dnsName, ipv4, port;
842  size_t dnsPos(pos), ipv4Pos(pos), portPos(0);
843  bool isDNS(ParseDNSName(dnsPos, dnsName)), isIPv4(ParseIPv4Address(ipv4Pos, ipv4));
844  if (!isDNS && !isIPv4)
845  {pos = dnsPos < ipv4Pos ? ipv4Pos : dnsPos; return false;}
846  // NOTE: It's possible to have both isIPv4 && isDNS true -- in this case, isIPv4 takes precedence:
847  if (isIPv4)
848  {outAddr = ipv4; pos = portPos = ipv4Pos;}
849  else if (isDNS)
850  {outAddr = dnsName; pos = portPos = dnsPos;}
851 
852  // Check for optional port number
853  char ch (CharAt(portPos));
854  if (ch != ':')
855  return true;
856  ++portPos;
857  if (!ParseDecNumber(portPos, port))
858  {pos = portPos; return false;} // Bad port number!
859  outPort = port;
860  pos = portPos;
861  return true;
862 }
863 
864 bool NTV2DeviceSpecParser::ParseResourcePath (size_t & pos, string & outRsrc)
865 {
866  outRsrc.clear();
867  string rsrc, name;
868  size_t rsrcPos(pos);
869  char ch(CharAt(rsrcPos));
870  while (ch == '/')
871  {
872  ++rsrcPos;
873  rsrc += '/';
874  if (!ParseAlphaNumeric(rsrcPos, name, " "))
875  break;
876  rsrc += name;
877  ch = CharAt(rsrcPos);
878  }
879  if (!rsrc.empty())
880  pos = rsrcPos;
881  outRsrc = rsrc;
882  return !outRsrc.empty();
883 }
884 
885 bool NTV2DeviceSpecParser::ParseParamAssignment (size_t & pos, string & outKey, string & outValue)
886 {
887  outKey.clear(); outValue.clear();
888  string key, value;
889  size_t paramPos(pos);
890  char ch(CharAt(paramPos));
891  if (ch == '&')
892  ch = CharAt(++paramPos);
893  do
894  {
895  if (!ParseAlphaNumeric(paramPos, key))
896  break;
897  ch = CharAt(paramPos);
898  if (ch != '=')
899  break;
900  ch = CharAt(++paramPos);
901  while (ch != 0 && ch != '&')
902  {
903  value += ch;
904  ch = CharAt(++paramPos);
905  }
906  } while (false);
907  if (!key.empty())
908  {pos = paramPos; outKey = key; outValue = value;}
909  return !key.empty();
910 }
911 
912 bool NTV2DeviceSpecParser::ParseQuery (size_t & pos, NTV2Dictionary & outParams)
913 {
914  outParams.clear();
915  string key, value;
916  size_t queryPos(pos);
917  char ch(CharAt(queryPos));
918  if (ch != '?')
919  return false;
920  queryPos++;
921 
922  while (ParseParamAssignment(queryPos, key, value))
923  {
924  outParams.insert(key, value);
925  ch = CharAt(queryPos);
926  if (ch != '&')
927  break;
928  }
929  if (!outParams.empty())
930  pos = queryPos;
931  return !outParams.empty();
932 }
933 
934 bool NTV2DeviceSpecParser::IsUpperLetter (const char inChar)
935 { static const string sHexDigits("_ABCDEFGHIJKLMNOPQRSTUVWXYZ");
936  return sHexDigits.find(inChar) != string::npos;
937 }
938 
939 bool NTV2DeviceSpecParser::IsLowerLetter (const char inChar)
940 { static const string sHexDigits("abcdefghijklmnopqrstuvwxyz");
941  return sHexDigits.find(inChar) != string::npos;
942 }
943 
944 bool NTV2DeviceSpecParser::IsLetter (const char inChar, const bool inIncludeUnderscore)
945 { return (inIncludeUnderscore && inChar == '_') || IsUpperLetter(inChar) || IsLowerLetter(inChar);
946 }
947 
948 bool NTV2DeviceSpecParser::IsDecimalDigit (const char inChar)
949 { static const string sDecDigits("0123456789");
950  return sDecDigits.find(inChar) != string::npos;
951 }
952 
953 bool NTV2DeviceSpecParser::IsHexDigit (const char inChar)
954 { static const string sHexDigits("0123456789ABCDEFabcdef");
955  return sHexDigits.find(inChar) != string::npos;
956 }
957 
958 bool NTV2DeviceSpecParser::IsLegalSerialNumChar (const char inChar)
959 { return IsLetter(inChar) || IsDecimalDigit(inChar);
960 }
961 
962 #if defined(_DEBUG)
963  void NTV2DeviceSpecParser::test (void)
964  {
965  NTV2DeviceSpecParser specParser;
966  specParser.Reset("1");
967  specParser.Reset("00000000000000000000000000000000000000000000000000000000000000000000000000000000000001");
968  specParser.Reset("corvid24");
969  specParser.Reset("corvid88");
970  specParser.Reset("konalhi");
971  specParser.Reset("alpha");
972  specParser.Reset("00T64450");
973  specParser.Reset("00t6-450");
974  specParser.Reset("BLATZBE0");
975  specParser.Reset("0x424C41545A424530");
976  specParser.Reset("0x424C415425424530");
977 
978  specParser.Reset("badscheme://1");
979 
980  specParser.Reset("ntv2local://1");
981  specParser.Reset("NtV2lOcAl://00000000000000000000000000000000000000000000000000000000000000000000000000000000000001");
982  specParser.Reset("NTV2Local://corvid24");
983  specParser.Reset("ntv2local://corvid88");
984  specParser.Reset("ntv2local://konalhi");
985  specParser.Reset("ntv2local://alpha");
986  specParser.Reset("ntv2local://00T64450");
987  specParser.Reset("ntv2local://00t6-450");
988  specParser.Reset("ntv2local://BLATZBE0");
989 
990  specParser.Reset("ntv2nub://1.2.3.4");
991  specParser.Reset("ntv2nub://1.2.3.4/doc");
992  specParser.Reset("ntv2nub://1.2.3.4/doc/");
993  specParser.Reset("ntv2nub://1.2.3.4/doc/alpha?one&two=2&three=&four=4");
994  specParser.Reset("ntv2nub://1.2.3.4/doc/?one&two=2&three=&four=4");
995  specParser.Reset("ntv2nub://1.2.3.4:badport/doc?one&two=2&three=&four=4");
996  specParser.Reset("ntv2nub://1.2.3.4:200/doc?one&two=2&three=&four=4");
997  specParser.Reset("ntv2nub://1.2.3.4:200/doc/?one&two=2&three=&four=4");
998  specParser.Reset("ntv2nub://1.2.3.4:12345");
999  specParser.Reset("ntv2nub://1.2.3.4:65000/doc");
1000  specParser.Reset("ntv2nub://1.2.3.4:32767/doc/");
1001  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc/");
1002  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc/?");
1003  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc?");
1004  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc/?one");
1005  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc?one");
1006  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc/?one=");
1007  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc?one=");
1008  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc/?one=1");
1009  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc?one=1");
1010  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc/?one=1&two");
1011  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc?one=1&two");
1012  specParser.Reset("ntv2nub://50.200.250.300");
1013  specParser.Reset("ntv2nub://fully.qualified.domain.name.com/path/to/doc/?one=1&two");
1014  specParser.Reset("ntv2nub://fully.qualified.domain.name.edu:badport/path/to/doc/?one=1&two");
1015  specParser.Reset("ntv2nub://fully.qualified.domain.name.info:5544/path/to/doc/?one=1&two");
1016  specParser.Reset("ntv2nub://fully.qualified.domain.name.org/path/to/doc/?one=1&two");
1017  specParser.Reset("ntv2nub://fully.qualified.domain.name.nz:badport/path/to/doc/?one=1&two");
1018  specParser.Reset("ntv2nub://fully.qualified.domain.name.au:000004/path/to/doc/?one=1&two");
1019  specParser.Reset("ntv2nub://fully.qualified.domain.name.ch:4/corvid88");
1020  specParser.Reset("ntv2nub://fully.qualified.domain.name.cn:4/00T64450");
1021  specParser.Reset("ntv2nub://fully.qualified.domain.name.ru:4/2");
1022  specParser.Reset("ntv2nub://fully.qualified.domain.name.co.uk:4/00000000000000000000000000000001");
1023  specParser.Reset("ntv2nub://fully.qualified.domain.name.com:4/0000000000000000000000000000000001");
1024  specParser.Reset("ntv2://swdevice/?"
1025  "nosharedmemory"
1026  "&supportlog=file%3A%2F%2F%2FUsers%2Fdemo%2FDesktop%2FAJAWatcherSupport.log"
1027  "&sdram=file%3A%2F%2F%2FUsers%2Fdemo%2FDesktop%2FSDRAMsnapshot.dat");
1028  }
1029 #endif // defined(_DEBUG)
1030 
1031 #if defined(MSWindows)
1032  static string WinErrStr (const DWORD inErr)
1033  {
1034  string result("foo");
1035  LPVOID lpMsgBuf;
1036  const DWORD res(FormatMessage ( FORMAT_MESSAGE_ALLOCATE_BUFFER
1037  | FORMAT_MESSAGE_FROM_SYSTEM
1038  | FORMAT_MESSAGE_IGNORE_INSERTS, // dwFlags
1039  AJA_NULL, // lpSource: n/a
1040  inErr, // dwMessageId: n/a
1041  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // dwLanguageId
1042  (LPTSTR) &lpMsgBuf, // output buffer
1043  0, // output buffer size, in TCHARs
1044  AJA_NULL)); // user params
1045  if (lpMsgBuf)
1046  {
1047  result = reinterpret_cast<const char *>(lpMsgBuf);
1048  LocalFree(lpMsgBuf);
1049  }
1050  return result;
1051  }
1052 #endif // MSWindows
1053 
1054 
1055 #if !defined(NTV2_PREVENT_PLUGIN_LOAD)
1056 
1057 /*****************************************************************************************************************************************************
1058  NTV2Plugin
1059 *****************************************************************************************************************************************************/
1060 
1063 
1064 // Wraps handle returned from dlopen/LoadLibrary, calls dlclose/FreeLibrary upon destruction
1066 {
1067  public:
1068  static bool LoadPlugin (const string & path, const string & folderPath, NTV2PluginPtr & outPtr, string & outErrMsg, const bool inUseStdout);
1069 
1070  public:
1071  #if defined(MSWindows)
1072  NTV2Plugin (HMODULE handle, const string & path, const bool useStdout);
1073  inline operator HMODULE() const {return mHandle;}
1074  #else
1075  NTV2Plugin (void * handle, const string & path, const bool useStdout);
1076  inline operator void*() const {return mHandle;}
1077  #endif
1078  ~NTV2Plugin (void);
1079  inline bool isLoaded (void) const {return mHandle && !mPath.empty() ? true : false;}
1080  void * addressForSymbol (const string & inSymbol, string & outErrorMsg);
1081 
1082  private:
1083  NTV2Plugin();
1084  NTV2Plugin(const NTV2Plugin & rhs);
1085  NTV2Plugin & operator = (const NTV2Plugin & rhs);
1086  inline bool useStdout(void) {return mUseStdout;}
1087  private:
1088  #if defined(MSWindows)
1089  HMODULE mHandle;
1090  #else
1091  void * mHandle;
1092  #endif
1093  string mPath;
1094  bool mUseStdout;
1095 }; // NTV2Plugin
1096 
1097 // One-stop-shop to load a plugin & instantiate its NTV2Plugin instance
1098 bool NTV2Plugin::LoadPlugin (const string & path, const string & folderPath, NTV2PluginPtr & outPtr, string & outErrMsg, const bool inUseStdout)
1099 {
1100  ostringstream loadErr;
1101  #if defined(AJABareMetal)
1102  return false; // unimplemented
1103  #elif defined(MSWindows)
1104  // Open the DLL (Windows)...
1105  std::wstring dllsFolderW;
1106  aja::string_to_wstring(folderPath, dllsFolderW);
1107  if (!AddDllDirectory(dllsFolderW.c_str()))
1108  {
1109  loadErr << "AddDllDirectory '" << path << "' failed: " << WinErrStr(::GetLastError());
1110  return false;
1111  } // AddDllDirectory failed
1112  HMODULE h = ::LoadLibraryExA(LPCSTR(path.c_str()), AJA_NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
1113  if (!h)
1114  loadErr << "Unable to open '" << path << "': " << WinErrStr(::GetLastError());
1115  #else // MacOS or Linux
1116  // Open the .dylib (MacOS) or .so (Linux)...
1117  void * h = ::dlopen(path.c_str(), RTLD_LAZY);
1118  if (!h)
1119  {
1120  const char * pErrorStr(::dlerror());
1121  const string errStr (pErrorStr ? pErrorStr : "");
1122  loadErr << "Unable to open '" << path << "': " << errStr;
1123  } // dlopen failed
1124  #endif // MacOS or Linux
1125  if (!loadErr.str().empty())
1126  outErrMsg = loadErr.str();
1127  if (!h)
1128  return false;
1129  outPtr = new NTV2Plugin(h, path, inUseStdout);
1130  return outPtr;
1131 }
1132 
1133 NTV2Plugin::NTV2Plugin()
1134 {
1135  NTV2_ASSERT(false);
1137 }
1138 
1140 {
1141  NTV2_ASSERT(false);
1143 }
1144 
1145 NTV2Plugin & NTV2Plugin::operator = (const NTV2Plugin & rhs)
1146 {
1147  NTV2_ASSERT(false);
1148  return *this;
1149 }
1150 
1151 #if defined(MSWindows)
1152 NTV2Plugin::NTV2Plugin (HMODULE handle, const string & path, const bool inUseStdout)
1153 #else
1154 NTV2Plugin::NTV2Plugin (void * handle, const string & path, const bool inUseStdout)
1155 #endif
1156  : mHandle(handle),
1157  mPath(path)
1158 {
1159  NTV2_ASSERT(mHandle);
1160  NTV2_ASSERT(!mPath.empty());
1162  P_NOTE("Dynamic/shared library '" << mPath << "' (" << INSTP(mHandle) << ") loaded, "
1163  << DEC(gPluginConstructCount) << " created, " << DEC(gPluginDestructCount) << " destroyed");
1164 }
1165 
1167 {
1168  if (mHandle)
1169  #if defined(AJABareMetal)
1170  ; // unimplemented
1171  #elif !defined(MSWindows)
1172  ::dlclose(mHandle);
1173  #else // macOS or Linux
1174  ::FreeLibrary(mHandle);
1175  #endif
1177  P_NOTE("Dynamic/shared library '" << mPath << "' (" << INSTP(mHandle) << ") unloaded, "
1178  << DEC(gPluginConstructCount) << " created, " << DEC(gPluginDestructCount) << " destroyed");
1179  mHandle = AJA_NULL;
1180  mPath.clear();
1181 }
1182 
1183 void * NTV2Plugin::addressForSymbol (const string & inSymbolName, string & outErrorMsg)
1184 {
1185  outErrorMsg.clear();
1186  if (!mHandle)
1187  return AJA_NULL;
1188  if (inSymbolName.empty())
1189  return AJA_NULL;
1190  void * result(AJA_NULL);
1191  ostringstream err;
1192  #if defined(AJABareMetal)
1193  // TODO
1194  #elif defined(MSWindows)
1195  result = reinterpret_cast<void*>(::GetProcAddress(reinterpret_cast<HMODULE>(mHandle), inSymbolName.c_str()));
1196  if (!result)
1197  err << "'GetProcAddress' failed for '" << inSymbolName << "': " << WinErrStr(::GetLastError());
1198  #else // MacOS or Linux
1199  result = ::dlsym(mHandle, inSymbolName.c_str());
1200  if (!result)
1201  { const char * pErrorStr(::dlerror());
1202  const string errStr (pErrorStr ? pErrorStr : "");
1203  err << "'dlsym' failed for '" << inSymbolName << "': " << errStr;
1204  }
1205  #endif // MacOS or Linux
1206  outErrorMsg = err.str();
1207  return result;
1208 } // addressForSymbol
1209 
1210 
1211 /*****************************************************************************************************************************************************
1212  @brief A singleton that tracks and monitors loaded plugins, and frees them when no one is using them.
1213  @bug This doesn't work on some platforms when libajantv2 is statically linked into the plugin. When
1214  the plugin loads, the plugin gets its own separate libajantv2 static globals, and thus, a second
1215  set of libajantv2 singletons, including this one.
1216 *****************************************************************************************************************************************************/
1219 
1220 // Singleton that tracks plugin use
1222 {
1223  public: // Class Methods
1224  static PluginRegistry & Get (void);
1225  static void Terminate (void);
1226  static inline void EnableDebugging (const bool inEnable = true) {sDebugRegistry = inEnable;}
1227  static inline bool DebuggingEnabled (void) {return sDebugRegistry;}
1228 
1229  public: // Instance Methods
1230  PluginRegistry();
1231  ~PluginRegistry();
1232  bool loadPlugin (const string & path, const string & folderPath, NTV2PluginPtr & outPtr, string & errMsg, const bool useStdout);
1233  bool unloadPlugin (const string & path, string & errMsg);
1234  bool pluginIsLoaded (const string & path);
1235  bool pluginForPath (const string & path, NTV2PluginPtr & outHandle);
1236  NTV2StringList loadedPlugins (void);
1237  NTV2StringList pluginStats (void); // returns string list, each has pluginPath<tab>refCount
1238  bool hasPath (const string & path);
1239  bool indexForPath (const string & path, size_t & outIndex);
1240  ULWord countForPath (const string & path);
1241  uint32_t * refConForPath (const string & path);
1242  inline bool useStdout (void) const {return DebuggingEnabled();}
1243 
1244  private:
1245  typedef map<string, NTV2PluginPtr> NTV2PluginMap;
1246  NTV2StringList mPluginPaths; // List of unique known plugins; only grows, never shrinks
1247  ULWordSequence mPluginCounts; // Per-plugin instance counts: correlates to gPluginPaths
1248  ULWordSequence mCompareCounts; // For detecting changes to mPluginCounts
1249  NTV2PluginMap mPluginMap; // Maps each unique plugin path to its corresponding NTV2Plugin instance
1250  AJALock mPluginMapLock; // Mutex to serialize access to these registry globals
1251  AJAThread mMonitor; // Thread that monitors plugin utilization
1252  bool mQuitMonitor; // Set true to terminate monitor
1253  private:
1254  static void Monitor (AJAThread * pThread, void * pContext);
1255  void monitor (void); // Monitor thread function
1256  static PluginRegistryPtr sSingleton;
1257  static AJALock sMutex;
1258  static bool sDebugRegistry;
1259 }; // PluginRegistry
1260 
1261 PluginRegistryPtr PluginRegistry::sSingleton;
1262 AJALock PluginRegistry::sMutex;
1263 bool PluginRegistry::sDebugRegistry(false);
1264 
1266 {
1267  AJAAutoLock tmp(&sMutex);
1268  if (!sSingleton)
1269  sSingleton = new PluginRegistry;
1270  return *sSingleton;
1271 }
1272 
1274 {
1275  AJAAutoLock tmp(&sMutex);
1276  PLGWARN("");
1277  sSingleton = PluginRegistryPtr();
1278 }
1279 
1280 void PluginRegistry::Monitor (AJAThread * pThread, void * pContext)
1281 { (void) pThread;
1282  PluginRegistry * pObj (reinterpret_cast<PluginRegistry*>(pContext));
1283  if (pObj)
1284  pObj->monitor();
1285 }
1286 
1288  : mQuitMonitor(false)
1289 {
1290  P_NOTE ("PluginRegistry " << INSTP(this) << " constructed");
1291  mPluginCounts.reserve(256);
1292  for (size_t num(0); num < 256; num++)
1293  mPluginCounts.push_back(0);
1294  mCompareCounts = mPluginCounts;
1295  mMonitor.Attach(Monitor, this);
1297  mMonitor.Start();
1298 }
1299 
1301 {
1302  mQuitMonitor = true;
1303  while (mMonitor.Active())
1304  AJATime::Sleep(10);
1305  P_NOTE("PluginRegistry singleton " << INSTP(this) << " destroyed:" << endl << aja::join(pluginStats(), "\n"));
1306 }
1307 
1308 bool PluginRegistry::loadPlugin (const string & path, const string & folderPath, NTV2PluginPtr & outPtr, string & errMsg, const bool inUseStdout)
1309 {
1310  AJAAutoLock tmp(&mPluginMapLock);
1311  outPtr = NTV2PluginPtr();
1312  if (path.empty())
1313  {P_FAIL("empty path"); return false;}
1314  if (pluginForPath(path, outPtr))
1315  {
1316  NTV2_ASSERT(hasPath(path));
1317  return true;
1318  }
1319  if (hasPath(path))
1320  {P_WARN(INSTP(this) << ": '" << path << "': 'pluginForPath' returned false, but 'hasPath' returned true, count=" << countForPath(path));}
1321  string msg;
1322  if (!NTV2Plugin::LoadPlugin (path, folderPath, outPtr, msg, inUseStdout))
1323  {P_FAIL(msg); return false;}
1324  P_NOTE(INSTP(this) << ": Dynamic/shared library '" << path << "' loaded");
1325  mPluginMap[path] = outPtr;
1326  mPluginPaths.push_back(path);
1327  mPluginCounts.at(mPluginPaths.size()-1) = 0;
1328  return true;
1329 }
1330 
1331 bool PluginRegistry::unloadPlugin (const string & path, string & errMsg)
1332 {
1333  AJAAutoLock tmp(&mPluginMapLock);
1334  NTV2PluginPtr ptr;
1335  if (path.empty())
1336  return false;
1337  if (!pluginForPath(path, ptr))
1338  {P_FAIL(INSTP(this) << ": '" << path << "' requested to unload, but not loaded"); return false;}
1339  mPluginMap.erase(path); // This should cause NTV2Plugin destructor to be called
1340  P_NOTE(INSTP(this) << ": '" << path << "' unloaded");
1341  return true;
1342 }
1343 
1344 bool PluginRegistry::pluginIsLoaded (const string & path)
1345 {
1346  AJAAutoLock tmp(&mPluginMapLock);
1347  return mPluginMap.find(path) != mPluginMap.end();
1348 }
1349 
1350 bool PluginRegistry::pluginForPath (const string & path, NTV2PluginPtr & outHandle)
1351 {
1352  AJAAutoLock tmp(&mPluginMapLock);
1353  NTV2PluginMap::const_iterator it(mPluginMap.find(path));
1354  if (it == mPluginMap.end())
1355  outHandle = NTV2PluginPtr();
1356  else
1357  outHandle = it->second;
1358  return outHandle;
1359 }
1360 
1362 {
1363  NTV2StringList result;
1364  AJAAutoLock tmp(&mPluginMapLock);
1365  for (NTV2PluginMap::const_iterator it(mPluginMap.begin()); it != mPluginMap.end(); ++it)
1366  result.push_back(it->first);
1367  return result;
1368 }
1369 
1371 {
1372  NTV2StringList result;
1373  AJAAutoLock tmp(&mPluginMapLock);
1374  for (size_t ndx(0); ndx < mPluginPaths.size(); ndx++)
1375  {
1376  const string path (mPluginPaths.at(ndx));
1377  ostringstream oss; oss << path << "\t" << DEC(countForPath(path));
1378  NTV2PluginPtr p;
1379  if (pluginForPath(path, p))
1380  oss << "\t" << (p->isLoaded() ? "loaded" : "unloaded");
1381  else
1382  oss << "\t" << "---";
1383  result.push_back(oss.str());
1384  }
1385  return result;
1386 }
1387 
1388 bool PluginRegistry::hasPath (const string & path)
1389 {
1390  size_t ndx(0);
1391  return indexForPath(path,ndx);
1392 }
1393 
1394 bool PluginRegistry::indexForPath (const string & path, size_t & outIndex)
1395 {
1396  AJAAutoLock tmp(&mPluginMapLock);
1397  for (outIndex = 0; outIndex < mPluginPaths.size(); outIndex++)
1398  if (path == mPluginPaths.at(outIndex))
1399  return true;
1400  return false;
1401 }
1402 
1403 uint32_t * PluginRegistry::refConForPath (const string & path)
1404 {
1405  AJAAutoLock tmp(&mPluginMapLock);
1406  size_t ndx(0);
1407  if (indexForPath(path, ndx))
1408  return &mPluginCounts[ndx];
1409  return AJA_NULL;
1410 }
1411 
1413 {
1414  size_t ndx(0);
1415  if (indexForPath(path, ndx))
1416  return mPluginCounts.at(ndx);
1417  return 0;
1418 }
1419 
1420 void PluginRegistry::monitor (void)
1421 {
1422  P_NOTE("PluginRegistry " << INSTP(this) << " monitor started");
1423  ostringstream oss; oss << "PluginReg" << HEX0N(uint32_t(uint64_t(this)),8);
1424  mMonitor.SetThreadName(oss.str().c_str());
1425  while (!mQuitMonitor)
1426  {
1427  {
1428  AJAAutoLock tmp(&mPluginMapLock);
1429  for (size_t ndx(0); ndx < mPluginPaths.size(); ndx++)
1430  {
1431  const uint32_t oldCount(mCompareCounts.at(ndx)), newCount(mPluginCounts.at(ndx));
1432  if (newCount != oldCount)
1433  {
1434  string errMsg, path(mPluginPaths.at(ndx));
1435  if (newCount > oldCount)
1436  {P_NOTE("PluginRegistry " << INSTP(this) << ": Plugin '" << path << "' utilization "
1437  << "increased from " << DEC(oldCount) << " to " << DEC(newCount));}
1438  else
1439  {
1440  P_NOTE("PluginRegistry " << INSTP(this) << ": Plugin '" << path << "' utilization "
1441  << "decreased from " << DEC(oldCount) << " to " << DEC(newCount));
1442  if (newCount == 0)
1443  unloadPlugin(path, errMsg);
1444  } // else count decreased
1445  mCompareCounts.at(ndx) = newCount;
1446  } // something changed
1447  } // for each plugin
1448  }
1449  AJATime::Sleep(250);
1450  }
1451  P_NOTE("PluginRegistry " << INSTP(this) << " monitor stopped");
1452 }
1453 
1454 /*****************************************************************************************************************************************************
1455  @brief Knows how to load & validate a plugin
1456 *****************************************************************************************************************************************************/
1458 {
1459  public: // Instance Methods
1460  NTV2PluginLoader (NTV2Dictionary & params);
1461  ~NTV2PluginLoader ();
1462  void * getFunctionAddress (const string & inFuncName);
1463  inline string pluginPath (void) const {return mDict.valueForKey(kNTV2PluginInfoKey_PluginPath);}
1464  inline string pluginSigPath (void) const {return mDict.valueForKey(kNTV2PluginInfoKey_PluginSigPath);}
1465  inline string pluginsPath (void) const {return mDict.valueForKey(kNTV2PluginInfoKey_PluginsPath);}
1466  inline string pluginBaseName (void) const {return mDict.valueForKey(kNTV2PluginInfoKey_PluginBaseName);}
1467  bool isValidated (void) const;
1468  inline bool showParams (void) const {return mQueryParams.hasKey(kQParamShowParams);}
1469  void * refCon (void) const;
1470 
1471  protected: // Used internally
1472  bool validate (void);
1473  void * getSymbolAddress (const string & inSymbolName, string & outErrorMsg);
1474  bool getPluginsFolder (string & outPath) const;
1475  bool getBaseNameFromScheme (string & outName) const;
1476  inline bool isOpen (void) {return mpPlugin ? mpPlugin->isLoaded() : false;}
1477  inline bool useStdout (void) const {return mQueryParams.hasKey(kQParamLogToStdout);}
1478  inline bool isVerbose (void) const {return mQueryParams.hasKey(kQParamVerboseLogging);}
1479  inline bool showCertificate (void) const {return mQueryParams.hasKey(kQParamShowX509Cert);}
1480  bool fail (void);
1481 
1482  private: // Instance Data
1483  NTV2Dictionary & mDict;
1484  NTV2Dictionary mQueryParams;
1485  NTV2PluginPtr mpPlugin;
1486  bool mValidated;
1487  mutable string errMsg;
1488 
1489  protected: // Class Methods
1490  static bool ExtractCertInfo (NTV2Dictionary & outInfo, const string & inStr);
1491  static bool ExtractIssuerInfo (NTV2Dictionary & outInfo, const string & inStr, const string & inParentKey);
1492  static string mbedErrStr (const int mbedtlsReturnCode);
1493 }; // NTV2PluginLoader
1494 
1495 
1496 // Constructor -- peforms all preparatory work: determines which plugin to load, then loads & validates it
1498  : mDict(params),
1499  mValidated(false)
1500 {
1503  const NTV2Dictionary originalParams(mDict);
1504  if (NTV2DeviceSpecParser::ParseQueryParams (mDict, mQueryParams) && !mQueryParams.empty())
1505  mDict.addFrom(mQueryParams);
1507  mDict.erase(kNTV2PluginInfoKey_Fingerprint); // Be sure caller can't cheat
1508  P_INFO("Loader created for '" << mDict.valueForKey(kConnectParamScheme) << "', " << DEC(gLoaderConstructCount) << " created, "
1509  << DEC(gLoaderDestructCount) << " destroyed");
1510 
1511  // Determine plugin base name & where to find the dylib/dll/so...
1512  string pluginBaseName, pluginsFolder;
1513  if (getBaseNameFromScheme(pluginBaseName) && getPluginsFolder(pluginsFolder))
1514  {
1515  const string path (pluginsFolder + pluginBaseName);
1516  const string sigPath (path + SIG_EXTENSION), dllPath (path + DLL_EXTENSION);
1517  mDict.insert(kNTV2PluginInfoKey_PluginPath, dllPath);
1518  mDict.insert(kNTV2PluginInfoKey_PluginSigPath, sigPath);
1519  if (showParams())
1520  { cout << "## NOTE: Original params for '" << pluginPath() << "':" << endl;
1521  originalParams.Print(cout, false) << endl;
1522  }
1523  // Validate the plugin...
1524  validate();
1525  if (showParams())
1526  { cout << "## NOTE: Final params for '" << pluginPath() << "':" << endl;
1527  mDict.Print(cout, false) << endl;
1528  }
1529  }
1530 }
1531 
1533 {
1535  P_INFO("Loader destroyed for '" << pluginBaseName() << "', " << DEC(gLoaderConstructCount) << " created, "
1536  << DEC(gLoaderDestructCount) << " destroyed");
1537 }
1538 
1539 string NTV2PluginLoader::mbedErrStr (const int mbedtlsReturnCode)
1540 {
1541  NTV2Buffer errBuff(4096);
1542  string str;
1543  mbedtls_strerror (mbedtlsReturnCode, errBuff, errBuff);
1544  errBuff.GetString (str, /*U8Offset*/0, /*maxSize*/errBuff);
1545  return str;
1546 }
1547 
1548 bool NTV2PluginLoader::ExtractCertInfo (NTV2Dictionary & outInfo, const string & inStr)
1549 {
1550  outInfo.clear();
1551  if (inStr.empty())
1552  return false;
1553  string keyPrefix;
1554  NTV2StringList lines(aja::split(inStr, "\n"));
1555  for (size_t lineNdx(0); lineNdx < lines.size(); lineNdx++)
1556  {
1557  string line (lines.at(lineNdx));
1558  const bool indented (line.empty() ? false : line.at(0) == ' ');
1559  aja::strip(line);
1560  if (line.empty()) // there shouldn't be empty lines...
1561  continue; // ...but skip them anyway if they happen to appear
1562  NTV2StringList keyValPair (aja::split(line, " : "));
1563  if (keyValPair.size() != 2)
1564  {
1565  if (keyValPair.size() == 1)
1566  {
1567  keyPrefix = keyValPair.at(0);
1568  aja::replace(keyPrefix, ":", "");
1569  aja::strip(keyPrefix);
1570  continue; // next line
1571  }
1572  PLGFAIL("cert info line " << DEC(lineNdx+1) << " '" << line << "' has "
1573  << DEC(keyValPair.size()) << " column(s) -- expected 2");
1574  return false;
1575  }
1576  string key(keyValPair.at(0)), val(keyValPair.at(1));
1577  if (key.empty())
1578  {PLGFAIL("cert info line " << DEC(lineNdx+1) << " '" << line << "' empty key for value '" << val << "'"); continue;}
1579  if (indented && !keyPrefix.empty())
1580  {aja::strip(key); key = keyPrefix + ": " + key;}
1581  else
1582  {aja::strip(key); keyPrefix.clear();}
1583  if (outInfo.hasKey(key))
1584  val = outInfo.valueForKey(key) + ", " + val;
1585  outInfo.insert(key, aja::strip(val)); // ignore errors for now
1586  } // for each info line
1587  return true;
1588 } // ExtractCertInfo
1589 
1590 bool NTV2PluginLoader::ExtractIssuerInfo (NTV2Dictionary & outInfo, const string & inStr, const string & inParentKey)
1591 {
1592  outInfo.clear();
1593  if (inStr.empty())
1594  return false;
1595  string str(inStr);
1596  NTV2StringList pairs(aja::split(aja::replace(str, "\\,", ","), ", ")), normalized;
1597  string lastKey;
1598  for (size_t ndx(0); ndx < pairs.size(); ndx++)
1599  {
1600  string assignment (pairs.at(ndx));
1601  if (assignment.find('=') == string::npos)
1602  {
1603  if (!lastKey.empty())
1604  outInfo.insert (lastKey, outInfo.valueForKey(lastKey) + ", " + assignment);
1605  }
1606  else
1607  {
1608  NTV2StringList pieces (aja::split(assignment, "="));
1609  if (pieces.size() != 2)
1610  {PLGFAIL("'" << inParentKey << "' assignment '" << assignment << "' has " << pieces.size() << " component(s) -- expected 2"); continue;}
1611  lastKey = pieces.at(0);
1612  string val(pieces.at(1));
1613  outInfo.insert (aja::strip(lastKey), val);
1614  }
1615  } // for each key/val assignment
1616  return true;
1617 } // ExtractIssuerInfo
1618 
1620 {
1621  if (!inSrcDict.hasKey(kConnectParamQuery))
1622  return true; // It's not an error if there's no 'query' in srcDict
1623  string queryStr(inSrcDict.valueForKey(kConnectParamQuery));
1624  if (!queryStr.empty())
1625  if (queryStr[0] == '?')
1626  queryStr.erase(0,1); // Remove leading '?'
1627  PLGDBG("Query: '" << queryStr << "'");
1628  const NTV2StringList strs(aja::split(queryStr, "&"));
1629  for (NTV2StringListConstIter it(strs.begin()); it != strs.end(); ++it)
1630  {
1631  string str(*it), key, value;
1632  if (str.find("=") == string::npos)
1633  { // No assignment (i.e. no '=') --- just insert key with empty value...
1634  key = aja::lower(str);
1635  outQueryParams.insert(key, value);
1636  PLGDBG("'" << key << "' = ''");
1637  continue;
1638  }
1639  NTV2StringList pieces(aja::split(str,"="));
1640  if (pieces.empty())
1641  continue;
1642  key = aja::lower(pieces.at(0));
1643  if (pieces.size() > 1)
1644  value = pieces.at(1);
1645  if (key.empty())
1646  {PLGWARN("Empty key '" << key << "'"); continue;}
1647  if (outQueryParams.hasKey(key))
1648  PLGDBG("Param '" << key << "' value '" << outQueryParams.valueForKey(key) << "' to be replaced with '" << value << "'");
1649  outQueryParams.insert(key, ::PercentDecode(value));
1650  PLGDBG("'" << key << "' = '" << outQueryParams.valueForKey(key) << "'");
1651  } // for each &param
1652  return true;
1653 } // ParseQueryParams
1654 
1655 void * NTV2PluginLoader::getSymbolAddress (const string & inSymbolName, string & outErrorMsg)
1656 {
1657  outErrorMsg.clear();
1658  if (!mpPlugin)
1659  return AJA_NULL;
1660  return mpPlugin->addressForSymbol(inSymbolName, outErrorMsg);
1661 } // getSymbolAddress
1662 
1663 void * NTV2PluginLoader::refCon (void) const
1664 {
1666 }
1667 
1668 bool NTV2PluginLoader::getPluginsFolder (string & outPath) const
1669 {
1670  if (!pluginsPath().empty())
1671  {outPath = pluginsPath(); return true;} // already known, assumed to be good
1672 
1673  // Plugins are expected to be in the "aja" folder (the parent folder of the "aja/firmware" folder)...
1674  outPath = ::NTV2GetPluginsFolderPath(true/*include trailing slash*/);
1675  if (outPath.empty())
1676  return false;
1677  PLGDBG("AJA plugin path is '" << outPath << "'");
1678  mDict.insert(kNTV2PluginInfoKey_PluginsPath, outPath); // Store it in 'PluginsPath'
1679  return !outPath.empty(); // Success if not empty
1680 }
1681 
1682 bool NTV2PluginLoader::getBaseNameFromScheme (string & outName) const
1683 {
1684  if (!pluginBaseName().empty())
1685  {outName = pluginBaseName(); return true;} // already known, assumed to be good
1686 
1687  // URL scheme determines plugin base name...
1688  if (!mDict.hasKey(kConnectParamScheme))
1689  {P_FAIL("Missing scheme -- params: " << mDict); return false;} // No scheme
1690  string scheme(mDict.valueForKey(kConnectParamScheme));
1691  outName = scheme;
1692  mDict.insert(kNTV2PluginInfoKey_PluginBaseName, outName);
1693  return !outName.empty(); // Success if not empty
1694 }
1695 
1697 {
1698  if (mDict.hasKey(kNTV2PluginInfoKey_Errors))
1699  { const string v(mDict.valueForKey(kNTV2PluginInfoKey_Errors) + "\n" + errMsg);
1702  }
1703  else
1704  mDict.insert(kNTV2PluginInfoKey_Errors, errMsg);
1705  return false;
1706 }
1707 
1709 {
1710  // Load contents of plugin & sig files into sigContent & dllContent buffers...
1711  NTV2Buffer sigContent, dllContent;
1712  {
1713  // no more than 500MB
1714  const size_t maxBufSize = 512*1024*1024;
1715 
1716  ifstream dllF;
1717  dllF.open(pluginPath(), std::ios::in | std::ios::binary);
1718  if (dllF.fail())
1719  {P_FAIL("Could not open plugin file '" << pluginPath() << "'"); return fail();}
1720  if (!dllF.seekg(0, ios_base::end))
1721  {P_FAIL("Could not seek to end of plugin file '" << pluginPath() << "'"); return fail();}
1722  ifstream::pos_type curOffset(dllF.tellg());
1723  if (int(curOffset) == -1)
1724  {P_FAIL("Could not determine size of plugin file '" << pluginPath() << "'"); return fail();}
1725  size_t size = size_t(curOffset);
1726  if (size == 0)
1727  {P_FAIL("Plugin file '" << pluginPath() << "' is empty"); return fail();}
1728  if (size > maxBufSize)
1729  {P_FAIL("EOF not reached in plugin file '" << pluginPath() << "' -- over 500MB in size?"); return fail();}
1730  size += 1;
1731 
1732  NTV2Buffer tmp(size);
1733  if (!dllF.seekg(0, ios_base::beg))
1734  {P_FAIL("Could not seek back to start of plugin file '" << pluginPath() << "'");return fail();}
1735  if (!dllF.read(tmp, tmp.GetByteCount()).eof())
1736  {P_FAIL("EOF not reached in plugin file '" << pluginPath() << "' -- over 500MB in size?"); return fail();}
1737  tmp.Truncate(size_t(dllF.gcount()));
1738  dllContent = tmp;
1739 
1740  tmp.Allocate(size);
1741  ifstream sigF(pluginSigPath().c_str(), std::ios::in | std::ios::binary);
1742  if (!sigF.good())
1743  {P_FAIL("Signature file '" << pluginSigPath() << "' missing"); return fail();}
1744  if (!sigF.read(tmp, tmp.GetByteCount()).eof())
1745  {P_FAIL("EOF not reached in signature file '" << pluginSigPath() << "' -- over 500MB in size?"); return fail();}
1746  tmp.Truncate(size_t(sigF.gcount()));
1747  sigContent = tmp;
1748  }
1749 
1750  // Decode sigContent...
1751  NTV2Dictionary dict;
1752  { const string dictStr (reinterpret_cast<const char*>(sigContent.GetHostPointer()), size_t(sigContent.GetByteCount()));
1753  if (!dict.deserialize(dictStr))
1754  {P_FAIL("Unable to decode signature file '" << pluginSigPath() << "'"); return fail();}
1755  }
1756  P_DBG(DEC(dict.keys().size()) << " keys found in signature file '" << pluginSigPath() << "': " << dict.keys());
1757  NTV2Buffer checksumFromSigFile, x509CertFromSigFile, signature;
1759  {P_FAIL("Signature file '" << pluginSigPath() << "' missing '" << kNTV2PluginSigFileKey_X509Certificate << "' key"); return fail();}
1761  {P_FAIL("Signature file '" << pluginSigPath() << "' missing '" << kNTV2PluginSigFileKey_Signature << "' key"); return fail();}
1762  if (!x509CertFromSigFile.SetFromHexString(dict.valueForKey(kNTV2PluginSigFileKey_X509Certificate)))
1763  {P_FAIL("'SetFromHexString' failed to decode X509 certificate extracted from '" << pluginSigPath() << "' key '" << kNTV2PluginSigFileKey_X509Certificate << "'"); return fail();}
1764  if (!signature.SetFromHexString(dict.valueForKey(kNTV2PluginSigFileKey_Signature)))
1765  {P_FAIL("'SetFromHexString' failed to decode signature extracted from '" << pluginSigPath() << "' key '" << kNTV2PluginSigFileKey_Signature << "'"); return fail();}
1766 
1767  // Grab the signing certificate found in the .sig file...
1768  mbedtls_x509_crt crt; // Container for X509 certificate
1769  mbedtls_x509_crt_init(&crt); // Initialize it as empty
1770  int ret = mbedtls_x509_crt_parse(&crt, x509CertFromSigFile, x509CertFromSigFile);
1771  if (ret)
1772  { P_FAIL("'mbedtls_x509_crt_parse' returned " << ret << " (" << mbedErrStr(ret) << ") for X509 cert found in '" << pluginSigPath() << "'");
1773  mbedtls_x509_crt_free(&crt);
1774  return fail();
1775  }
1776 
1777  // Extract certificate info...
1778  NTV2Dictionary certInfo, issuerInfo, subjectInfo;
1779  {
1780  NTV2Buffer msgBuff(4096);
1781  int msgLength (mbedtls_x509_crt_info (msgBuff, msgBuff, /*prefixString*/"", &crt));
1782  string msg (msgBuff, size_t(msgLength));
1783  if (msg.empty())
1784  { P_FAIL("'mbedtls_x509_crt_info' returned no info for X509 cert found in '" << pluginSigPath() << "'");
1785  return fail();
1786  }
1787  if (showCertificate())
1788  cout << "## DEBUG: Raw X509 certificate info extracted from signature file '" << pluginSigPath() << "':" << endl
1789  << " " << msg << endl;
1790  if (!ExtractCertInfo (certInfo, msg))
1791  return false;
1792  if (isVerbose())
1793  { cout << "## NOTE: X509 certificate info extracted from signature file '" << pluginSigPath() << "':" << endl;
1794  certInfo.Print(cout, false) << endl;
1795  }
1796  if (certInfo.hasKey("issuer name"))
1797  if (!ExtractIssuerInfo (issuerInfo, certInfo.valueForKey("issuer name"), "issuer name"))
1798  return false;
1799  if (certInfo.hasKey("subject name"))
1800  if (!ExtractIssuerInfo (subjectInfo, certInfo.valueForKey("subject name"), "subject name"))
1801  return false;
1802  if (!certInfo.hasKey(kNTV2PluginInfoKey_Fingerprint))
1803  { P_FAIL("Missing key '" << kNTV2PluginInfoKey_Fingerprint << "' in X509 certificate from '" << pluginSigPath() << "'");
1804  return fail();
1805  }
1806  if (isVerbose() && !issuerInfo.empty())
1807  { cout << "## NOTE: 'issuer name' info:" << endl;
1808  issuerInfo.Print(cout, false) << endl;
1809  }
1810  if (isVerbose() && !subjectInfo.empty())
1811  { cout << "## NOTE: 'subject name' info:" << endl;
1812  subjectInfo.Print(cout, false) << endl;
1813  }
1814  if (!issuerInfo.hasKey(kNTV2PluginX500AttrKey_CommonName))
1815  { P_FAIL("Missing 'Issuer' key '" << kNTV2PluginX500AttrKey_CommonName << "' in X509 certificate from '" << pluginSigPath() << "'");
1816  return fail();
1817  }
1819  { P_FAIL("Missing 'Issuer' key '" << kNTV2PluginX500AttrKey_OrganizationName << "' in X509 certificate from '" << pluginSigPath() << "'");
1820  return fail();
1821  }
1823  { P_FAIL("Missing 'Subject' key '" << kNTV2PluginX500AttrKey_OrganizationalUnitName << "' in X509 certificate from '" << pluginSigPath() << "'");
1824  return fail();
1825  }
1826  mDict.addFrom(certInfo); // Store certInfo key/value pairs into client/server instance's params...
1827  }
1828 
1829  // Compute SHA256 hash of plugin...
1830  NTV2Buffer checksumFromDLL(32);
1831  ret = mbedtls_md_file (mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), pluginPath().c_str(), checksumFromDLL);
1832  if (ret)
1833  { P_FAIL("'mbedtls_md_file' returned " << ret << " (" << mbedErrStr(ret) << ") for '" << pluginPath() << "'");
1834  return fail();
1835  }
1836  if (isVerbose()) {string str; if (checksumFromDLL.toHexString(str)) cout << "## DEBUG: Digest: " << str << endl;}
1837 
1838  // Verify the dylib/DLL/so signature...
1839  ret = mbedtls_pk_verify (&crt.pk, MBEDTLS_MD_SHA256,
1840  /*msgHash*/checksumFromDLL, /*msgHashLength*/0,//checksumFromDLL,
1841  /*signatureToVerify*/signature, /*signatureLength*/signature);
1842  if (ret)
1843  { P_FAIL("'mbedtls_pk_verify' returned " << ret << " (" << mbedErrStr(ret) << ") for '" << pluginSigPath() << "'");
1844  return fail();
1845  }
1846  mbedtls_x509_crt_free(&crt); // Done using the mbedtls_x509_crt struct
1847  P_DBG("'mbedtls_pk_verify' succeeded for '" << pluginPath() << "' -- signature valid");
1848 
1849  // Load/open the shared library...
1850  if (!mpPlugin)
1851  if (!PluginRegistry::Get().loadPlugin (pluginPath(), pluginsPath(), mpPlugin, errMsg, useStdout()))
1852  return fail();
1853  PLGDBG("'" << pluginPath() << "' opened");
1854 
1855  // Obtain AJA Registration Info...
1856  NTV2Dictionary regInfo;
1857  string errGetInfo;
1858  void * pGetInfo = getSymbolAddress(kFuncNameGetRegInfo, errGetInfo);
1859  if (!pGetInfo)
1860  { P_FAIL("'" << pluginPath() << "': '" kFuncNameGetRegInfo "' failed: " << errGetInfo);
1861  return fail();
1862  }
1863  fpGetRegistrationInfo pGetRegInfo = reinterpret_cast<fpGetRegistrationInfo>(pGetInfo);
1864  if (!(*pGetRegInfo)(uint32_t(AJA_NTV2_SDK_VERSION), regInfo))
1865  {P_FAIL("'" << pluginPath() << "': '" << kFuncNameGetRegInfo << "' failed"); return fail();}
1866  PLGDBG("'" << pluginPath() << "': '" << kFuncNameGetRegInfo << "': returned " << regInfo.keys());
1867  if (regInfo.empty())
1868  {P_FAIL("'" << pluginPath() << "': no registration info (empty)"); return fail();}
1869 
1870  // Check for required registration info keys...
1871  NTV2StringList missingRegInfoKeys;
1876  for (size_t ndx(0); ndx < reqKeys.size(); ndx++)
1877  if (!regInfo.hasKey(reqKeys.at(ndx)))
1878  missingRegInfoKeys.push_back(reqKeys.at(ndx));
1879  if (!missingRegInfoKeys.empty())
1880  { P_FAIL("'" << pluginPath() << "': missing key(s) in registration info: '"
1881  << aja::join(missingRegInfoKeys, "','") << "'");
1882  return fail(); // fail
1883  }
1884  mDict.addFrom(regInfo); // Add registration info to plugin's dictionary
1885 
1886  // Check planet alignment...
1887  const string cnReg(regInfo.valueForKey(kNTV2PluginRegInfoKey_CommonName)),
1888  cnCert(issuerInfo.valueForKey(kNTV2PluginX500AttrKey_CommonName));
1889  const string onReg(regInfo.valueForKey(kNTV2PluginRegInfoKey_Vendor)),
1891  const string ouReg(regInfo.valueForKey(kNTV2PluginRegInfoKey_OrgUnit)),
1893  const string myVers(NTV2RPCBase::ShortSDKVersion()),
1895  const string ajaFingerprint(NTV2RPCBase::AJAFingerprint (/*lowerCase*/true, /*stripColons*/false));
1896  string fingerprint(mDict.valueForKey(kNTV2PluginInfoKey_Fingerprint));
1897  aja::lower(fingerprint); // since ajaFingerprint is lower case
1898  if (onReg != onCert)
1899  { P_FAIL("Vendor name (key='" << kNTV2PluginRegInfoKey_Vendor << "') \"" << onReg << "\" from plugin \""
1900  << pluginPath() << "\" doesn't match organization name (key='" << kNTV2PluginX500AttrKey_OrganizationName
1901  << "') \"" << onCert << "\" from X509 certificate 'Issuer' in '" << pluginSigPath() << "'");
1902  return fail();
1903  }
1904  if (cnReg != cnCert)
1905  { P_FAIL("Common name (key='" << kNTV2PluginRegInfoKey_CommonName << "') \"" << cnReg << "\" from plugin \""
1906  << pluginPath() << "\" doesn't match common name (key='" << kNTV2PluginX500AttrKey_CommonName
1907  << "') \"" << cnCert << "\" from X509 certificate 'Issuer' in '" << pluginSigPath() << "'");
1908  return fail();
1909  }
1910  if (ouReg != ouCert)
1911  { P_FAIL("Org unit (key='" << kNTV2PluginX500AttrKey_OrganizationalUnitName << "') \"" << ouReg << "\" from plugin \""
1912  << pluginPath() << "\" doesn't match org unit (key='" << kNTV2PluginX500AttrKey_OrganizationalUnitName
1913  << "') \"" << ouCert << "\" from X509 certificate 'Subject' in '" << pluginSigPath() << "'");
1914  return fail();
1915  }
1916  if (myVers != plVers)
1917  { P_FAIL("SDK version '" << plVers << "' from plugin \"" << pluginPath()
1918  << "\" doesn't match client SDK version '" << myVers << "'");
1919  return fail();
1920  }
1921 #if 1
1922  if (fingerprint != ajaFingerprint)
1923  { P_FAIL("'" << pluginPath() << "':|Plugin not authorized/signed by AJA:|"
1924  << "Issuer serial: " << fingerprint << "|AJA serial: " << ajaFingerprint);
1925  return fail(); // fail
1926  }
1927 #endif
1928  // Green light
1929  mValidated = true;
1930  return true;
1931 } // validate
1932 
1933 // Returns address of function having given name
1934 void * NTV2PluginLoader::getFunctionAddress (const string & inFuncName)
1935 {
1936  // Load/open the shared library...
1937  if (!isOpen())
1938  {P_FAIL("'" << inFuncName << "': '" << pluginPath() << "' not loaded"); return AJA_NULL;}
1939  if (!isValidated())
1940  {P_FAIL("'" << inFuncName << "': '" << pluginPath() << "' not validated"); return AJA_NULL;}
1941 
1942  // Finally, the last step ---- get address of requested function...
1943  string errStr;
1944  void * pResult = getSymbolAddress(inFuncName, errStr);
1945  if (!pResult)
1946  {P_FAIL("'" << inFuncName << "': '" << pluginPath() << "': " << errStr); return AJA_NULL;}
1947  P_DBG("Calling '" << inFuncName << "' in '" << pluginPath() << "'");
1948  return pResult;
1949 } // getFunctionAddress
1950 
1952 {
1953  return mpPlugin && mValidated;
1954 }
1955 
1957 {
1958  const NTV2StringList paths (PluginRegistry::Get().loadedPlugins());
1959  if (paths.empty())
1960  cout << "0 plugins" << endl;
1961  else if (paths.size() == 1)
1962  cout << "1 plugin: " << paths.at(0) << endl;
1963  else cout << DEC(paths.size()) << " plugins:" << endl << aja::join(paths, "\n") << endl;
1964 }
1965 #endif // !defined(NTV2_PREVENT_PLUGIN_LOAD)
1966 
1967 
1968 /*****************************************************************************************************************************************************
1969  NTV2RPCBase
1970 *****************************************************************************************************************************************************/
1971 
1973  : mParams(params),
1974  mpRefCon(pRefCon)
1975 {
1976  NTV2Dictionary queryParams;
1978  if (mpRefCon)
1980  PDBGX("refCnt=" << DEC(mpRefCon ? *mpRefCon : 0) << ", " << DEC(gBaseConstructCount) << " created, "
1981  << DEC(gBaseDestructCount) << " destroyed");
1983  {cout << __FILE__ << "(" << __LINE__ << "):" << AJAFUNC << ":" << endl; mParams.Print(cout, false) << endl;}
1984 }
1985 
1987 {
1989  if (mpRefCon)
1991  PDBGX("refCnt=" << DEC(mpRefCon ? *mpRefCon : 0) << ", " << DEC(gBaseConstructCount) << " created, "
1992  << DEC(gBaseDestructCount) << " destroyed");
1993 }
1994 
1995 bool NTV2RPCBase::SetParams (const NTV2ConnectParams & inNewParams, const bool inAugment)
1996 {
1997  AJAAutoLock tmp(&mParamLock);
1998  size_t oldCount(mParams.size()), updated(0), added(0);
1999  if (inAugment)
2000  {
2001  updated = mParams.updateFrom(inNewParams);
2002  added = mParams.addFrom(inNewParams);
2003  NBSDBG(DEC(updated) << " param(s) updated, " << DEC(added) << " added: " << mParams);
2004  }
2005  else
2006  {
2007  mParams = inNewParams;
2008  NBSDBG(DEC(oldCount) << " param(s) removed, replaced with " << inNewParams);
2009  }
2010  if (mParams.empty())
2011  NBSWARN("No params");
2012  return true;
2013 }
2014 
2016 {
2017  string result(::NTV2Version());
2018  const NTV2StringList halves(aja::split(result, " "));
2019  if (halves.empty())
2020  return result;
2021  NTV2StringList nums(aja::split(halves.front(), "."));
2022  while (nums.size() > 3)
2023  nums.pop_back();
2024  return aja::join(nums, ".");
2025 }
2026 
2027 
2028 string NTV2RPCBase::AJAFingerprint (const bool inLowerCase, const bool inStripColons)
2029 {
2030  static const string sAJAFingerprint ("70:1A:37:93:FA:4F:34:30:58:55:51:0C:01:4E:45:7C:BE:5B:41:65");
2031  string result(sAJAFingerprint);
2032  if (inStripColons)
2033  aja::replace(result, ":", "");
2034  if (inLowerCase)
2035  aja::lower(result);
2036  return result;
2037 }
2038 
2039 
2040 /*****************************************************************************************************************************************************
2041  NTV2RPCClientAPI
2042 *****************************************************************************************************************************************************/
2043 
2045  : NTV2RPCBase(inParams, reinterpret_cast<ULWord*>(pRefCon))
2046 {
2047  AJADebug::Open();
2049  PDBGX(DEC(gClientConstructCount) << " created, " << DEC(gClientDestructCount) << " destroyed");
2050 }
2051 
2053 {
2054  if (IsConnected())
2055  NTV2Disconnect();
2057  PDBGX(DEC(gClientConstructCount) << " created, " << DEC(gClientDestructCount) << " destroyed");
2058 }
2059 
2061 {
2062  AJAAutoLock tmp(&mParamLock);
2063  return mParams;
2064 }
2065 
2066 bool NTV2RPCClientAPI::HasConnectParam (const string & inParam) const
2067 {
2068  AJAAutoLock tmp(&mParamLock);
2069  return mParams.hasKey(inParam);
2070 }
2071 
2072 string NTV2RPCClientAPI::ConnectParam (const string & inParam) const
2073 {
2074  AJAAutoLock tmp(&mParamLock);
2075  return mParams.valueForKey(inParam);
2076 }
2077 
2079 {
2081 }
2082 
2083 ostream & NTV2RPCClientAPI::Print (ostream & oss) const
2084 {
2085  oss << (IsConnected() ? "Connected" : "Disconnected");
2086  if (IsConnected() && !Name().empty())
2087  oss << " to '" << Name() << "'";
2088  return oss;
2089 }
2090 
2092 {
2093  NTV2StringList strs;
2094  string fName(ConnectParam(kQParamVDevFileName)), hostName;
2095  if (!fName.empty())
2096  strs.push_back(string("from '") + fName + "'");
2098  sysInfo.GetValue(AJA_SystemInfoTag_System_Name, hostName);
2099  if (!hostName.empty())
2100  {strs.push_back("on"); strs.push_back(string("'") + hostName + "'");}
2101  return aja::join(strs, " ");
2102 }
2103 
2105 {
2106  if (IsConnected())
2107  NTV2Disconnect();
2108  return NTV2OpenRemote();
2109 }
2110 
2112 {
2113  return NTV2CloseRemote();
2114 }
2115 
2116 bool NTV2RPCClientAPI::NTV2ReadRegisterRemote (const ULWord regNum, ULWord & outRegValue, const ULWord regMask, const ULWord regShift)
2117 { (void) regNum; (void) outRegValue; (void) regMask; (void) regShift;
2118  return false; // UNIMPLEMENTED
2119 }
2120 
2121 bool NTV2RPCClientAPI::NTV2WriteRegisterRemote (const ULWord regNum, const ULWord regValue, const ULWord regMask, const ULWord regShift)
2122 { (void) regNum; (void) regValue; (void) regMask; (void) regShift;
2123  return false; // UNIMPLEMENTED
2124 }
2125 
2127 { (void) autoCircData;
2128  return false; // UNIMPLEMENTED
2129 }
2130 
2132 { (void) eInterrupt; (void) timeOutMs;
2133  return false; // UNIMPLEMENTED
2134 }
2135 
2136 #if !defined(NTV2_DEPRECATE_16_3)
2138  { (void) bitFileType;
2139  ::memset(&bitFileInfo, 0, sizeof(bitFileInfo));
2140  return false; // UNIMPLEMENTED
2141  }
2142 
2144  {
2145  ::memset(&buildInfo, 0, sizeof(buildInfo));
2146  return false; // UNIMPLEMENTED
2147  }
2148 
2150  const UWord signalMask, const bool testPatDMAEnb, const ULWord testPatNum)
2151  { (void) channel; (void) testPatternFBF; (void) signalMask; (void) testPatDMAEnb; (void) testPatNum;
2152  return false; // UNIMPLEMENTED
2153  }
2154 
2155  bool NTV2RPCClientAPI::NTV2ReadRegisterMultiRemote (const ULWord numRegs, ULWord & outFailedRegNum, NTV2RegInfo outRegs[])
2156  { (void) numRegs; (void) outFailedRegNum; (void) outRegs;
2157  return false; // UNIMPLEMENTED
2158  }
2159 
2161  {
2162  outDriverVersion = 0xFFFFFFFF;
2163  return false; // UNIMPLEMENTED
2164  }
2165 #endif // !defined(NTV2_DEPRECATE_16_3)
2166 
2167 bool NTV2RPCClientAPI::NTV2DMATransferRemote ( const NTV2DMAEngine inDMAEngine, const bool inIsRead, const ULWord inFrameNumber,
2168  NTV2Buffer & inOutFrameBuffer, const ULWord inCardOffsetBytes,
2169  const ULWord inNumSegments, const ULWord inSegmentHostPitch,
2170  const ULWord inSegmentCardPitch, const bool inSynchronous)
2171 { (void) inDMAEngine; (void) inIsRead; (void) inFrameNumber; (void) inOutFrameBuffer;
2172  (void) inCardOffsetBytes; (void) inNumSegments; (void) inSegmentHostPitch;
2173  (void) inSegmentCardPitch; (void) inSynchronous;
2174  return false; // UNIMPLEMENTED
2175 }
2176 
2178 { (void) pInMessage;
2179  return false; // UNIMPLEMENTED
2180 }
2181 
2182 bool NTV2RPCClientAPI::NTV2GetBoolParamRemote (const ULWord inParamID, ULWord & outValue)
2183 { (void) inParamID;
2184  outValue = 0;
2185  return false; // UNIMPLEMENTED
2186 }
2187 
2189 { (void) inParamID;
2190  outValue = 0;
2191  return false; // UNIMPLEMENTED
2192 }
2193 
2194 bool NTV2RPCClientAPI::NTV2GetSupportedRemote (const ULWord inEnumsID, ULWordSet & outSupported)
2195 { (void) inEnumsID;
2196  outSupported.clear();
2197  return false; // UNIMPLEMENTED
2198 }
2199 
2201 {
2202  return false; // UNIMPLEMENTED
2203 }
2204 
2206 {
2207 // AJAAutoLock tmp(&mParamLock);
2208 // mParams.clear();
2209  return true;
2210 }
2211 
2213 {
2214 #if defined(NTV2_PREVENT_PLUGIN_LOAD)
2215  return AJA_NULL;
2216 #else
2217  NTV2RPCClientAPI * pRPCObject(AJA_NULL);
2218  {
2219  NTV2PluginLoader loader(params);
2220  fpCreateClient pFunc (reinterpret_cast<fpCreateClient>(loader.getFunctionAddress(kFuncNameCreateClient)));
2221  if (!pFunc)
2222  return AJA_NULL;
2223 
2224  // Call plugin's Create function to instantiate the NTV2RPCClientAPI object...
2225  pRPCObject = (*pFunc) (loader.refCon(), params, AJA_NTV2_SDK_VERSION);
2226  if (!pRPCObject)
2227  NBCFAIL("'" << kFuncNameCreateClient << "' returned NULL client instance from: " << params);
2228  else
2229  NBCINFO("'" << kFuncNameCreateClient << "' created client instance " << xHEX0N(uint64_t(pRPCObject),16));
2230  } // loader freed here
2231  return pRPCObject;
2232 #endif
2233 } // CreateClient
2234 
2235 
2236 /*****************************************************************************************************************************************************
2237  NTV2RPCServerAPI
2238 *****************************************************************************************************************************************************/
2239 
2241 {
2242 #if defined(NTV2_PREVENT_PLUGIN_LOAD)
2243  return AJA_NULL;
2244 #else
2245  NTV2RPCServerAPI * pRPCObject(AJA_NULL);
2246  {
2247  NTV2PluginLoader loader(params);
2248  fpCreateServer pFunc = reinterpret_cast<fpCreateServer>(loader.getFunctionAddress(kFuncNameCreateServer));
2249  if (!pFunc)
2250  return AJA_NULL;
2251 
2252  // Call plugin's Create function to instantiate the NTV2RPCServerAPI object...
2253  pRPCObject = (*pFunc) (loader.refCon(), params, AJA_NTV2_SDK_VERSION);
2254  if (!pRPCObject)
2255  NBSFAIL("'" << kFuncNameCreateServer << "' returned NULL server instance from: " << params);
2256  else
2257  NBSINFO("'" << kFuncNameCreateServer << "' created server instance " << xHEX0N(uint64_t(pRPCObject),16));
2258  }
2259  return pRPCObject; // It's caller's responsibility to delete pRPCObject
2260 #endif
2261 } // CreateServer
2262 
2263 NTV2RPCServerAPI * NTV2RPCServerAPI::CreateServer (const string & inURL) // CLASS METHOD
2264 {
2265  NTV2DeviceSpecParser parser(inURL);
2266  if (parser.HasErrors())
2267  {
2268  NBSFAIL(parser.Error() << " in URL:\n" << inURL);
2269  parser.PrintErrors(cerr);
2270  return AJA_NULL;
2271  }
2272  NTV2ConfigParams parms(parser.Results());
2273  return CreateServer(parms);
2274 }
2275 
2277  : NTV2RPCBase(inParams, reinterpret_cast<ULWord*>(pRefCon))
2278 {
2279  NTV2Buffer spare(&mSpare, sizeof(mSpare)); spare.Fill(0ULL);
2280  AJADebug::Open();
2282  PDBGX(DEC(gServerConstructCount) << " created, " << DEC(gServerDestructCount) << " destroyed");
2283 }
2284 
2286 {
2287  Stop();
2288  while (IsRunning())
2289  AJATime::Sleep(50);
2291  PDBGX(DEC(gServerConstructCount) << " created, " << DEC(gServerDestructCount) << " destroyed");
2292 }
2293 
2295 { // This function normally should never be called;
2296  // It's usually overridden by a subclass
2297  NBSDBG("Started");
2298  while (!mTerminate)
2299  AJATime::Sleep(500);
2300  NBSDBG("Terminated");
2301 } // ServerFunction
2302 
2303 ostream & NTV2RPCServerAPI::Print (ostream & oss) const
2304 {
2305  oss << mParams;
2306  return oss;
2307 }
2308 
2310 {
2311  AJAAutoLock tmp(&mParamLock);
2312  return mParams;
2313 }
2314 
2315 bool NTV2RPCServerAPI::HasConfigParam (const string & inParam) const
2316 {
2317  AJAAutoLock tmp(&mParamLock);
2318  return mParams.hasKey(inParam);
2319 }
2320 
2321 string NTV2RPCServerAPI::ConfigParam (const string & inParam) const
2322 {
2323  AJAAutoLock tmp(&mParamLock);
2324  return mParams.valueForKey(inParam);
2325 }
static PluginRegistry & Get(void)
static bool ParseQueryParams(const NTV2Dictionary &inSrcDict, NTV2Dictionary &outQueryParams)
Parses the string found in the given source dictionary&#39;s &#39;query&#39; key (kConnectParamQuery), storing the key/value pairs of all parsed query parameters into "outQueryParams". Query parameter values are URL-decoded before storing in the "outQueryParams" dictionary.
void * getFunctionAddress(const string &inFuncName)
bool useStdout(void) const
Everything needed to call CNTV2Card::ReadRegister or CNTV2Card::WriteRegister functions.
NTV2StringList loadedPlugins(void)
NTV2DeviceID DeviceID(void) const
#define kNTV2PluginRegInfoKey_LongName
Plugin long name.
Definition: ntv2nubaccess.h:76
#define kConnectParamDevSerial
Device with this serial number.
Definition: ntv2nubaccess.h:28
bool loadPlugin(const string &path, const string &folderPath, NTV2PluginPtr &outPtr, string &errMsg, const bool useStdout)
uint32_t gBaseConstructCount(0)
bool Allocate(const size_t inByteCount, const bool inPageAligned=false)
Allocates (or re-allocates) my user-space storage using the given byte count. I assume full responsib...
#define NBSFAIL(__x__)
bool(* fpGetRegistrationInfo)(const uint32_t, NTV2Dictionary &)
Obtains a plugin&#39;s registration information. Starting in SDK 17.1, all plugins must implement this fu...
virtual bool ConnectHasScheme(void) const
virtual AJAStatus SetThreadName(const char *name)
Definition: thread.cpp:176
virtual bool NTV2ReadRegisterMultiRemote(const ULWord numRegs, ULWord &outFailedRegNum, NTV2RegInfo outRegs[])
uint32_t gClientConstructCount(0)
virtual bool NTV2GetNumericParamRemote(const ULWord inParamID, ULWord &outValue)
unsigned long long stoull(const std::string &str, std::size_t *idx, int base)
Definition: common.cpp:154
void DumpLoadedPlugins(void)
NTV2DeviceIDSet NTV2GetSupportedDevices(const NTV2DeviceKinds inKinds=NTV2_DEVICEKIND_ALL)
Returns an NTV2DeviceIDSet of devices supported by the SDK.
Definition: ntv2utils.cpp:7730
virtual bool NTV2Connect(void)
NTV2FrameBufferFormat
Identifies a particular video frame buffer pixel format. See Device Frame Buffer Formats for details...
Definition: ntv2enums.h:219
#define kConnectParamPort
Port number (optional)
Definition: ntv2nubaccess.h:26
NTV2RPCClientAPI *(* fpCreateClient)(void *, const NTV2ConnectParams &, const uint32_t)
Instantiates a new client instance to talk to a remote server.
#define P_WARN(__x__)
#define NBCINFO(__x__)
Declares the AJADebug class.
std::string valueForKey(const std::string &inKey) const
uint16_t u16ValueForKey(const std::string &inKey, const uint16_t inDefault=0) const
size_t size(void) const
#define kQParamShowX509Cert
Query parameter option that dumps X509 certificate info into message log.
Definition: ntv2nubaccess.h:37
#define AJAFUNC
Definition: ajatypes.h:293
bool pluginForPath(const string &path, NTV2PluginPtr &outHandle)
static AJAStatus Open(bool incrementRefCount=false)
Definition: debug.cpp:44
#define kNTV2PluginRegInfoKey_Copyright
Plugin copyright notice.
Definition: ntv2nubaccess.h:78
bool showParams(void) const
#define kNTV2PluginRegInfoKey_Vendor
Plugin vendor (manufacturer) name.
Definition: ntv2nubaccess.h:72
#define kQParamVerboseLogging
Query parameter option that enables verbose message logging.
Definition: ntv2nubaccess.h:35
NTV2Plugin(void *handle, const string &path, const bool useStdout)
#define kNTV2PluginX500AttrKey_OrganizationName
Definition: ntv2nubaccess.h:93
static int32_t Decrement(int32_t volatile *pTarget)
Definition: atomic.cpp:95
the parser read a key of a value in an object
static string mbedErrStr(const int mbedtlsReturnCode)
#define kLegalSchemeNTV2Local
Definition: ntv2nubaccess.h:56
ULWord GetByteCount(void) const
#define kNTV2PluginRegInfoKey_ShortName
Plugin short name.
Definition: ntv2nubaccess.h:75
bool pluginIsLoaded(const string &path)
One-stop shop for parsing device specifications. (New in SDK 16.3) I do very little in the way of val...
#define PDBGX(__x__)
Declares the AJATime class.
virtual AJAStatus SetPriority(AJAThreadPriority priority)
Definition: thread.cpp:133
uint32_t gLoaderConstructCount(0)
virtual bool NTV2GetSupportedRemote(const ULWord inEnumsID, ULWordSet &outSupported)
size_t erase(const std::string &inKey)
Erases the given key and its corresponding value from me, returns 1 if successful, 0 if not.
bool string_to_wstring(const std::string &str, std::wstring &wstr)
Definition: common.cpp:248
virtual bool HasConnectParam(const std::string &inParam) const
static bool ExtractCertInfo(NTV2Dictionary &outInfo, const string &inStr)
Definition: lock.h:28
UWord DeviceIndex(void) const
virtual std::string ConnectParam(const std::string &inParam) const
Dict::const_iterator DictConstIter
Common base class for NTV2RPCClientAPI and NTV2RPCServerAPI.
Defines the AJARefPtr template class.
Definition: json.hpp:5362
bool showCertificate(void) const
virtual AJAStatus Start()
Definition: thread.cpp:91
NTV2RPCServerAPI(NTV2ConnectParams inParams, void *pRefCon)
My constructor.
bool hasPath(const string &path)
bool serialize(std::string &outStr) const
Serializes my contents into the given string, returns true if string is not empty.
#define false
#define kQParamDebugRegistry
Query parameter option that enables debugging of PluginRegistry.
Definition: ntv2nubaccess.h:39
uint32_t ULWord
Definition: ajatypes.h:223
NTV2Channel
These enum values are mostly used to identify a specific widget_framestore. They&#39;re also commonly use...
Definition: ntv2enums.h:1357
std::ostream & Print(std::ostream &oss, const bool inDumpResults=false) const
bool toHexString(std::string &outStr, const size_t inLineBreakInterval=0) const
Converts my contents into a hex-encoded string.
Declares NTV2 "nub" client functions.
std::string MakeDeviceSpec(const bool urlEncodeQuery) const
bool hasKey(const std::string &inKey) const
#define kConnectParamDevModel
First device of this model (e.g. &#39;kona4&#39;)
Definition: ntv2nubaccess.h:29
static bool ExtractIssuerInfo(NTV2Dictionary &outInfo, const string &inStr, const string &inParentKey)
AJALock mParamLock
Mutex to protect mParams.
virtual std::string Name(void) const
NTV2Dictionary mParams
Copy of config params passed to my constructor.
#define NTV2_ASSERT(_expr_)
Definition: ajatypes.h:476
virtual bool NTV2DMATransferRemote(const NTV2DMAEngine inDMAEngine, const bool inIsRead, const ULWord inFrameNumber, NTV2Buffer &inOutBuffer, const ULWord inCardOffsetBytes, const ULWord inNumSegments, const ULWord inSegmentHostPitch, const ULWord inSegmentCardPitch, const bool inSynchronous)
#define kConnectParamDevID
First device having this ID (e.g. &#39;0x10518400&#39;)
Definition: ntv2nubaccess.h:30
#define kConnectParamHost
DNS name, IPv4 or sw device DLL name.
Definition: ntv2nubaccess.h:25
uint32_t gPluginConstructCount(0)
bool Truncate(const size_t inByteCount)
Truncates me to the given length. No reallocation takes place.
#define kNTV2PluginSigFileKey_X509Certificate
X509 certificate (encoded as hex string)
Definition: ntv2nubaccess.h:86
#define PLGFAIL(__x__)
unsigned long stoul(const std::string &str, std::size_t *idx, int base)
Definition: common.cpp:143
NTV2RPCServerAPI *(* fpCreateServer)(void *, const NTV2ConfigParams &, const uint32_t)
Instantiates a new server instance for talking to clients.
virtual bool NTV2GetBoolParamRemote(const ULWord inParamID, ULWord &outValue)
virtual std::ostream & Print(std::ostream &oss) const
virtual bool Active()
Definition: thread.cpp:116
void * addressForSymbol(const string &inSymbol, string &outErrorMsg)
bool Fill(const T &inValue)
Fills me with the given scalar value.
std::string PercentEncode(const std::string &inStr)
bool GetString(std::string &outString, const size_t inU8Offset=0, const size_t inMaxSize=128) const
Answers with my contents as a character string.
static std::string AJAFingerprint(const bool inLowerCase=false, const bool inStripColons=false)
#define NBSWARN(__x__)
#define NBSINFO(__x__)
An object that can connect to, and operate remote or fake devices. I have three general API groups: ...
NTV2StringSet keys(void) const
#define true
#define kFuncNameCreateServer
Create an NTV2RPCServerAPI instance.
Definition: ntv2nubaccess.h:60
std::string NTV2Version(const bool inDetailed=false)
Definition: ntv2version.cpp:41
bool insert(const std::string &inKey, const std::string &inValue)
Stores the given value using the given key; overwrites existing value if already present.
#define kNTV2PluginInfoKey_Fingerprint
Issuer cert fingerprint.
Definition: ntv2nubaccess.h:68
NTV2PluginLoader(NTV2Dictionary &params)
NTV2DeviceID
Identifies a specific AJA NTV2 device model number. The NTV2DeviceID is actually the PROM part number...
Definition: ntv2enums.h:20
static void EnableDebugging(const bool inEnable=true)
virtual bool NTV2Disconnect(void)
Disconnects me from the remote/fake host, closing the connection.
Declares the AJAThread class.
virtual void RunServer(void)
Principal server thread function, subclsses should override.
bool SetParams(const NTV2ConfigParams &inNewParams, const bool inAugment=false)
virtual void Stop(void)
Call this to request the server to stop.
virtual ~NTV2RPCClientAPI()
My destructor, automatically calls NTV2Disconnect.
virtual bool NTV2DriverGetBitFileInformationRemote(BITFILE_INFO_STRUCT &nfo, const NTV2BitFileType typ)
void Reset(const std::string inSpec="")
Resets me, then parses the given device specification.
virtual std::ostream & Print(std::ostream &oss) const
NTV2DeviceIDSet::const_iterator NTV2DeviceIDSetConstIter
A convenient const iterator for NTV2DeviceIDSet.
Definition: ntv2utils.h:1046
bool indexForPath(const string &path, size_t &outIndex)
size_t addFrom(const NTV2Dictionary &inDict)
Adds all values from inDict with non-matching keys, ignoring all matching keys.
uint32_t gServerDestructCount(0)
uint32_t gClientDestructCount(0)
virtual bool NTV2MessageRemote(NTV2_HEADER *pInMessage)
size_t largestKeySize(void) const
bool isLoaded(void) const
NTV2DMAEngine
Definition: ntv2enums.h:1856
#define AJA_NULL
Definition: ajatypes.h:167
NTV2StringSet::const_iterator NTV2StringSetConstIter
Definition: ntv2utils.h:1159
#define kNTV2PluginInfoKey_Errors
Plugin load or validation error(s), if any.
Definition: ntv2nubaccess.h:69
Declares the most fundamental data types used by NTV2. Since Windows NT was the first principal devel...
void clear(void)
Removes all of my key/value pairs.
Defines for the NTV2 SDK version number, used by ajantv2/includes/ntv2enums.h. See the ajantv2/includ...
enum _INTERRUPT_ENUMS_ INTERRUPT_ENUMS
static void Sleep(const int32_t inMilliseconds)
Suspends execution of the current thread for a given number of milliseconds.
Definition: systemtime.cpp:284
virtual bool NTV2CloseRemote(void)
std::ostream & Print(std::ostream &oss, const bool inCompact=true) const
Prints human-readable representation to ostream.
void split(const std::string &str, const char delim, std::vector< std::string > &elems)
Definition: common.cpp:350
#define kNTV2PluginX500AttrKey_OrganizationalUnitName
Definition: ntv2nubaccess.h:94
bool isVerbose(void) const
std::set< ULWord > ULWordSet
A collection of unique ULWord (uint32_t) values.
std::vector< std::string > NTV2StringList
Definition: ntv2utils.h:1155
bool getBaseNameFromScheme(string &outName) const
#define kNTV2PluginRegInfoKey_Description
Brief plugin description.
Definition: ntv2nubaccess.h:77
All new NTV2 structs start with this common header.
virtual bool IsConnected(void) const
the parser finished reading a JSON value
NTV2RPCClientAPI(NTV2ConnectParams inParams, void *pRefCon)
My constructor.
static bool DebuggingEnabled(void)
bool useStdout(void) const
#define kQParamVDevFileName
.vdev file name (with extension)
Definition: ntv2nubaccess.h:44
static int32_t Increment(int32_t volatile *pTarget)
Definition: atomic.cpp:82
std::string Resource(const bool inStripLeadSlash=true) const
virtual bool NTV2WaitForInterruptRemote(const INTERRUPT_ENUMS eInterrupt, const ULWord timeOutMs)
std::string NTV2GetPluginsFolderPath(const bool inAddTrailingPathDelim=false)
Definition: ntv2utils.cpp:7705
uint32_t * refConForPath(const string &path)
uint32_t mSpare[1024]
Reserved.
static NTV2RPCClientAPI * CreateClient(NTV2ConnectParams &inParams)
Instantiates a new NTV2RPCClientAPI instance using the given NTV2ConnectParams.
#define kNTV2PluginRegInfoKey_CommonName
Plugin vendor domain name.
Definition: ntv2nubaccess.h:73
uint64_t ULWord64
Definition: ajatypes.h:226
Base class of objects that can serve device operation RPCs with NTV2RPCClientAPI instances.
#define kNTV2PluginInfoKey_PluginPath
Local host full path to plugin file.
Definition: ntv2nubaccess.h:65
virtual bool IsRunning(void) const
#define INSTP(_p_)
NTV2BitFileType
Definition: ntv2enums.h:3349
virtual NTV2ConfigParams ConfigParams(void) const
static void Terminate(void)
virtual ~NTV2RPCBase()
virtual bool NTV2ReadRegisterRemote(const ULWord regNum, ULWord &outRegValue, const ULWord regMask, const ULWord regShift)
#define kQParamLogToStdout
Query parameter option that logs messages to standard output.
Definition: ntv2nubaccess.h:36
#define NBCFAIL(__x__)
#define P_INFO(__x__)
ULWord countForPath(const string &path)
void * refCon(void) const
bool mTerminate
Set true to stop server.
Describes a user-space buffer on the host computer. I have an address and a length, plus some optional attributes (allocated by SDK?, page-aligned? etc.).
bool deserialize(const std::string &inStr)
Resets me from the given string.
#define AJA_sERROR(_index_, _expr_)
Definition: debug.h:176
std::string MakeQueryString(const bool urlEncode) const
bool getPluginsFolder(string &outPath) const
string pluginSigPath(void) const
#define kNTV2PluginInfoKey_PluginBaseName
Plugin base name (i.e. without extension)
Definition: ntv2nubaccess.h:67
std::string NTV2DeviceIDToString(const NTV2DeviceID inValue, const bool inForRetailDisplay=false)
Definition: ntv2utils.cpp:4608
#define kFuncNameGetRegInfo
Answers with plugin registration info.
Definition: ntv2nubaccess.h:61
virtual bool NTV2DriverGetBuildInformationRemote(BUILD_INFO_STRUCT &buildInfo)
Declares the AJAAtomic class.
#define P_NOTE(__x__)
std::string & strip(std::string &str, const std::string &ws)
Definition: common.cpp:461
#define kConnectParamDevIndex
Device having this index number.
Definition: ntv2nubaccess.h:27
#define kNTV2PluginInfoKey_PluginsPath
Local host full path to folder containing plugins.
Definition: ntv2nubaccess.h:64
#define kQParamShowParams
Query parameter option that dumps parameters into message log.
Definition: ntv2nubaccess.h:38
virtual std::string Description(void) const
bool unloadPlugin(const string &path, string &errMsg)
std::string PercentDecode(const std::string &inStr)
bool isValidated(void) const
#define DEC(__x__)
virtual bool NTV2OpenRemote(void)
NTV2RPCBase(NTV2Dictionary params, uint32_t *pRefCon)
#define P_DBG(__x__)
#define kNTV2PluginX500AttrKey_CommonName
Definition: ntv2nubaccess.h:90
std::ostream & PrintErrors(std::ostream &oss) const
bool empty(void) const
AJARefPtr< NTV2Plugin > NTV2PluginPtr
#define kNTV2PluginRegInfoKey_OrgUnit
Plugin organization unit (to match certificate subject OU)
Definition: ntv2nubaccess.h:74
static bool LoadPlugin(const string &path, const string &folderPath, NTV2PluginPtr &outPtr, string &outErrMsg, const bool inUseStdout)
Declares numerous NTV2 utility functions.
virtual ~NTV2RPCServerAPI()
My destructor, automatically calls NTV2Disconnect.
uint32_t * mpRefCon
Reserved for internal use.
virtual AJAStatus Attach(AJAThreadFunction *pThreadFunction, void *pUserContext)
Definition: thread.cpp:169
virtual bool NTV2GetDriverVersionRemote(ULWord &vers)
Declares the AJASystemInfo class.
size_t updateFrom(const NTV2Dictionary &inDict)
Updates all values from inDict with matching keys, ignoring all non-matching keys.
uint32_t gLoaderDestructCount(0)
#define kConnectParamScheme
URL scheme.
Definition: ntv2nubaccess.h:24
virtual bool HasConfigParam(const std::string &inParam) const
AJARefPtr< PluginRegistry > PluginRegistryPtr
#define AJA_NTV2_SDK_VERSION
Definition: ntv2version.h:20
size_t largestValueSize(void) const
uint16_t UWord
Definition: ajatypes.h:221
#define kNTV2PluginRegInfoKey_Version
Plugin version (string)
Definition: ntv2nubaccess.h:80
#define PLGDBG(__x__)
NTV2DeviceSpecParser(const std::string inSpec="")
My constructor. If given device specification is non-empty, proceeds to Parse it. ...
#define xHEX0N(__x__, __n__)
A simple (not thread-safe) set of key/value pairs. (New in SDK 16.3)
#define SIG_EXTENSION
#define PLGWARN(__x__)
virtual bool NTV2WriteRegisterRemote(const ULWord regNum, const ULWord regValue, const ULWord regMask, const ULWord regShift)
Private include file for all ajabase sources.
#define DLL_EXTENSION
std::vector< uint32_t > ULWordSequence
An ordered sequence of ULWord (uint32_t) values.
#define kNTV2PluginSigFileKey_Signature
X509 digital signature (encoded as hex string)
Definition: ntv2nubaccess.h:87
std::string & upper(std::string &str)
Definition: common.cpp:442
#define kNTV2PluginRegInfoKey_NTV2SDKVersion
NTV2 SDK version that plugin was compiled with.
Definition: ntv2nubaccess.h:79
virtual AJAStatus GetValue(const AJASystemInfoTag inTag, std::string &outValue) const
Answers with the host system info value string for the given AJASystemInfoTag.
Definition: info.cpp:153
#define kFuncNameCreateClient
Create an NTV2RPCClientAPI instance.
Definition: ntv2nubaccess.h:59
std::string join(const std::vector< std::string > &parts, const std::string &delim)
Definition: common.cpp:468
#define P_FAIL(__x__)
std::set< NTV2DeviceID > NTV2DeviceIDSet
A set of NTV2DeviceIDs.
Definition: ntv2utils.h:1044
void * GetHostPointer(void) const
string pluginsPath(void) const
uint32_t gBaseDestructCount(0)
void * getSymbolAddress(const string &inSymbolName, string &outErrorMsg)
#define HEX0N(__x__, __n__)
Definition: debug.cpp:1175
uint32_t gPluginDestructCount(0)
string pluginPath(void) const
std::string & lower(std::string &str)
Definition: common.cpp:436
virtual NTV2ConnectParams ConnectParams(void) const
std::string InfoString(void) const
static NTV2RPCServerAPI * CreateServer(NTV2ConfigParams &inParams)
Factory method that instantiates a new NTV2RPCServerAPI instance using a plugin based on the specifie...
Declares enums and structs used by all platform drivers and the SDK.
virtual bool NTV2AutoCirculateRemote(AUTOCIRCULATE_DATA &autoCircData)
#define kConnectParamQuery
Query – everything past &#39;?&#39; in URL.
Definition: ntv2nubaccess.h:32
virtual std::string ConfigParam(const std::string &inParam) const
#define NBSDBG(__x__)
std::string & replace(std::string &str, const std::string &from, const std::string &to)
Definition: common.cpp:110
static std::string ShortSDKVersion(void)
uint32_t gServerConstructCount(0)
#define kConnectParamResource
Resource path – everything past URL [scheme://host[:port]/], excluding [?query]. ...
Definition: ntv2nubaccess.h:31
NTV2StringList::const_iterator NTV2StringListConstIter
Definition: ntv2utils.h:1157
std::set< std::string > NTV2StringSet
Definition: ntv2utils.h:1158
#define AJA_sDEBUG(_index_, _expr_)
Definition: debug.h:220
NTV2StringList pluginStats(void)
virtual bool NTV2DownloadTestPatternRemote(const NTV2Channel ch, const NTV2PixelFormat pf, const UWord msk, const bool dma, const ULWord tpNum)
string pluginBaseName(void) const
#define kNTV2PluginInfoKey_PluginSigPath
Local host full path to plugin signature file.
Definition: ntv2nubaccess.h:66