AJA NTV2 SDK  18.1.0.2262
NTV2 SDK 18.1.0.2262
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 (const 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  // "&fbinit=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" || tokAlphaNum == "ntv2vkona") // 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::ParseQueryParams (const NTV2Dictionary & inSrcDict, NTV2Dictionary & outQueryParams)
935 {
936  if (!inSrcDict.hasKey(kConnectParamQuery))
937  return true; // It's not an error if there's no 'query' in srcDict
938  string queryStr(inSrcDict.valueForKey(kConnectParamQuery));
939  if (!queryStr.empty())
940  if (queryStr[0] == '?')
941  queryStr.erase(0,1); // Remove leading '?'
942  PLGDBG("Query: '" << queryStr << "'");
943  const NTV2StringList strs(aja::split(queryStr, "&"));
944  for (NTV2StringListConstIter it(strs.begin()); it != strs.end(); ++it)
945  {
946  string str(*it), key, value;
947  if (str.find("=") == string::npos)
948  { // No assignment (i.e. no '=') --- just insert key with empty value...
949  key = aja::lower(str);
950  outQueryParams.insert(key, value);
951  PLGDBG("'" << key << "' = ''");
952  continue;
953  }
954  NTV2StringList pieces(aja::split(str,"="));
955  if (pieces.empty())
956  continue;
957  key = aja::lower(pieces.at(0));
958  if (pieces.size() > 1)
959  value = pieces.at(1);
960  if (key.empty())
961  {PLGWARN("Empty key '" << key << "'"); continue;}
962  if (outQueryParams.hasKey(key))
963  PLGDBG("Param '" << key << "' value '" << outQueryParams.valueForKey(key) << "' to be replaced with '" << value << "'");
964  outQueryParams.insert(key, ::PercentDecode(value));
965  PLGDBG("'" << key << "' = '" << outQueryParams.valueForKey(key) << "'");
966  } // for each &param
967  return true;
968 } // ParseQueryParams
969 
970 bool NTV2DeviceSpecParser::IsUpperLetter (const char inChar)
971 { static const string sHexDigits("_ABCDEFGHIJKLMNOPQRSTUVWXYZ");
972  return sHexDigits.find(inChar) != string::npos;
973 }
974 
975 bool NTV2DeviceSpecParser::IsLowerLetter (const char inChar)
976 { static const string sHexDigits("abcdefghijklmnopqrstuvwxyz");
977  return sHexDigits.find(inChar) != string::npos;
978 }
979 
980 bool NTV2DeviceSpecParser::IsLetter (const char inChar, const bool inIncludeUnderscore)
981 { return (inIncludeUnderscore && inChar == '_') || IsUpperLetter(inChar) || IsLowerLetter(inChar);
982 }
983 
984 bool NTV2DeviceSpecParser::IsDecimalDigit (const char inChar)
985 { static const string sDecDigits("0123456789");
986  return sDecDigits.find(inChar) != string::npos;
987 }
988 
989 bool NTV2DeviceSpecParser::IsHexDigit (const char inChar)
990 { static const string sHexDigits("0123456789ABCDEFabcdef");
991  return sHexDigits.find(inChar) != string::npos;
992 }
993 
994 bool NTV2DeviceSpecParser::IsLegalSerialNumChar (const char inChar)
995 { return IsLetter(inChar) || IsDecimalDigit(inChar);
996 }
997 
998 #if defined(_DEBUG)
999  void NTV2DeviceSpecParser::test (void)
1000  {
1001  NTV2DeviceSpecParser specParser;
1002  specParser.Reset("1");
1003  specParser.Reset("00000000000000000000000000000000000000000000000000000000000000000000000000000000000001");
1004  specParser.Reset("corvid24");
1005  specParser.Reset("corvid88");
1006  specParser.Reset("konalhi");
1007  specParser.Reset("alpha");
1008  specParser.Reset("00T64450");
1009  specParser.Reset("00t6-450");
1010  specParser.Reset("BLATZBE0");
1011  specParser.Reset("0x424C41545A424530");
1012  specParser.Reset("0x424C415425424530");
1013 
1014  specParser.Reset("badscheme://1");
1015 
1016  specParser.Reset("ntv2local://1");
1017  specParser.Reset("NtV2lOcAl://00000000000000000000000000000000000000000000000000000000000000000000000000000000000001");
1018  specParser.Reset("NTV2Local://corvid24");
1019  specParser.Reset("ntv2local://corvid88");
1020  specParser.Reset("ntv2local://konalhi");
1021  specParser.Reset("ntv2local://alpha");
1022  specParser.Reset("ntv2local://00T64450");
1023  specParser.Reset("ntv2local://00t6-450");
1024  specParser.Reset("ntv2local://BLATZBE0");
1025 
1026  specParser.Reset("ntv2nub://1.2.3.4");
1027  specParser.Reset("ntv2nub://1.2.3.4/doc");
1028  specParser.Reset("ntv2nub://1.2.3.4/doc/");
1029  specParser.Reset("ntv2nub://1.2.3.4/doc/alpha?one&two=2&three=&four=4");
1030  specParser.Reset("ntv2nub://1.2.3.4/doc/?one&two=2&three=&four=4");
1031  specParser.Reset("ntv2nub://1.2.3.4:badport/doc?one&two=2&three=&four=4");
1032  specParser.Reset("ntv2nub://1.2.3.4:200/doc?one&two=2&three=&four=4");
1033  specParser.Reset("ntv2nub://1.2.3.4:200/doc/?one&two=2&three=&four=4");
1034  specParser.Reset("ntv2nub://1.2.3.4:12345");
1035  specParser.Reset("ntv2nub://1.2.3.4:65000/doc");
1036  specParser.Reset("ntv2nub://1.2.3.4:32767/doc/");
1037  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc/");
1038  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc/?");
1039  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc?");
1040  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc/?one");
1041  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc?one");
1042  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc/?one=");
1043  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc?one=");
1044  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc/?one=1");
1045  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc?one=1");
1046  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc/?one=1&two");
1047  specParser.Reset("ntv2nub://1.2.3.4/path/to/doc?one=1&two");
1048  specParser.Reset("ntv2nub://50.200.250.300");
1049  specParser.Reset("ntv2nub://fully.qualified.domain.name.com/path/to/doc/?one=1&two");
1050  specParser.Reset("ntv2nub://fully.qualified.domain.name.edu:badport/path/to/doc/?one=1&two");
1051  specParser.Reset("ntv2nub://fully.qualified.domain.name.info:5544/path/to/doc/?one=1&two");
1052  specParser.Reset("ntv2nub://fully.qualified.domain.name.org/path/to/doc/?one=1&two");
1053  specParser.Reset("ntv2nub://fully.qualified.domain.name.nz:badport/path/to/doc/?one=1&two");
1054  specParser.Reset("ntv2nub://fully.qualified.domain.name.au:000004/path/to/doc/?one=1&two");
1055  specParser.Reset("ntv2nub://fully.qualified.domain.name.ch:4/corvid88");
1056  specParser.Reset("ntv2nub://fully.qualified.domain.name.cn:4/00T64450");
1057  specParser.Reset("ntv2nub://fully.qualified.domain.name.ru:4/2");
1058  specParser.Reset("ntv2nub://fully.qualified.domain.name.co.uk:4/00000000000000000000000000000001");
1059  specParser.Reset("ntv2nub://fully.qualified.domain.name.com:4/0000000000000000000000000000000001");
1060  specParser.Reset("ntv2://swdevice/?"
1061  "nosharedmemory"
1062  "&supportlog=file%3A%2F%2F%2FUsers%2Fdemo%2FDesktop%2FAJAWatcherSupport.log"
1063  "&fbinit=file%3A%2F%2F%2FUsers%2Fdemo%2FDesktop%2FSDRAMsnapshot.dat");
1064  }
1065 #endif // defined(_DEBUG)
1066 
1067 #if defined(MSWindows)
1068  static string WinErrStr (const DWORD inErr)
1069  {
1070  string result("foo");
1071  LPVOID lpMsgBuf;
1072  const DWORD res(FormatMessage ( FORMAT_MESSAGE_ALLOCATE_BUFFER
1073  | FORMAT_MESSAGE_FROM_SYSTEM
1074  | FORMAT_MESSAGE_IGNORE_INSERTS, // dwFlags
1075  AJA_NULL, // lpSource: n/a
1076  inErr, // dwMessageId: n/a
1077  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // dwLanguageId
1078  (LPTSTR) &lpMsgBuf, // output buffer
1079  0, // output buffer size, in TCHARs
1080  AJA_NULL)); // user params
1081  if (lpMsgBuf)
1082  {
1083  if (res)
1084  result = reinterpret_cast<const char *>(lpMsgBuf);
1085  LocalFree(lpMsgBuf);
1086  }
1087  return result;
1088  }
1089 #endif // MSWindows
1090 
1091 
1092 #if !defined(NTV2_PREVENT_PLUGIN_LOAD)
1093 
1094 /*****************************************************************************************************************************************************
1095  NTV2Plugin
1096 *****************************************************************************************************************************************************/
1097 
1100 
1101 // Wraps handle returned from dlopen/LoadLibrary, calls dlclose/FreeLibrary upon destruction
1103 {
1104  public:
1105  static bool LoadPlugin (const string & path, const string & folderPath, NTV2PluginPtr & outPtr, string & outErrMsg, const bool inUseStdout);
1106 
1107  public:
1108  #if defined(MSWindows)
1109  NTV2Plugin (HMODULE handle, const string & path, const bool useStdout);
1110  inline operator HMODULE() const {return mHandle;}
1111  #else
1112  NTV2Plugin (void * handle, const string & path, const bool useStdout);
1113  inline operator void*() const {return mHandle;}
1114  #endif
1115  ~NTV2Plugin (void);
1116  inline bool isLoaded (void) const {return mHandle && !mPath.empty() ? true : false;}
1117  void * addressForSymbol (const string & inSymbol, string & outErrorMsg);
1118 
1119  private:
1120  NTV2Plugin();
1121  NTV2Plugin(const NTV2Plugin & rhs);
1122  NTV2Plugin & operator = (const NTV2Plugin & rhs);
1123  inline bool useStdout(void) {return mUseStdout;}
1124  private:
1125  #if defined(MSWindows)
1126  HMODULE mHandle;
1127  #else
1128  void * mHandle;
1129  #endif
1130  string mPath;
1131  bool mUseStdout;
1132 }; // NTV2Plugin
1133 
1134 // One-stop-shop to load a plugin & instantiate its NTV2Plugin instance
1135 bool NTV2Plugin::LoadPlugin (const string & path, const string & folderPath, NTV2PluginPtr & outPtr, string & outErrMsg, const bool inUseStdout)
1136 {
1137  ostringstream loadErr;
1138  #if defined(AJABareMetal)
1139  return false; // unimplemented
1140  #elif defined(MSWindows)
1141  // Open the DLL (Windows)...
1142  std::wstring dllsFolderW;
1143  aja::string_to_wstring(folderPath, dllsFolderW);
1144  if (!AddDllDirectory(dllsFolderW.c_str()))
1145  {
1146  loadErr << "AddDllDirectory '" << path << "' failed: " << WinErrStr(::GetLastError());
1147  return false;
1148  } // AddDllDirectory failed
1149  HMODULE h = ::LoadLibraryExA(LPCSTR(path.c_str()), AJA_NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
1150  if (!h)
1151  loadErr << "Unable to open '" << path << "': " << WinErrStr(::GetLastError());
1152  #else // MacOS or Linux
1153  // Open the .dylib (MacOS) or .so (Linux)...
1154  void * h = ::dlopen(path.c_str(), RTLD_LAZY);
1155  if (!h)
1156  {
1157  const char * pErrorStr(::dlerror());
1158  const string errStr (pErrorStr ? pErrorStr : "");
1159  loadErr << "Unable to open '" << path << "': " << errStr;
1160  } // dlopen failed
1161  #endif // MacOS or Linux
1162  if (!loadErr.str().empty())
1163  outErrMsg = loadErr.str();
1164  if (!h)
1165  return false;
1166  outPtr = new NTV2Plugin(h, path, inUseStdout);
1167  return outPtr;
1168 }
1169 
1170 NTV2Plugin::NTV2Plugin()
1171 {
1172  NTV2_ASSERT(false);
1174 }
1175 
1177 {
1178  NTV2_ASSERT(false);
1180 }
1181 
1182 NTV2Plugin & NTV2Plugin::operator = (const NTV2Plugin & rhs)
1183 {
1184  NTV2_ASSERT(false);
1185  return *this;
1186 }
1187 
1188 #if defined(MSWindows)
1189 NTV2Plugin::NTV2Plugin (HMODULE handle, const string & path, const bool inUseStdout)
1190 #else
1191 NTV2Plugin::NTV2Plugin (void * handle, const string & path, const bool inUseStdout)
1192 #endif
1193  : mHandle(handle),
1194  mPath(path)
1195 {
1196  NTV2_ASSERT(mHandle);
1197  NTV2_ASSERT(!mPath.empty());
1199  P_NOTE("Dynamic/shared library '" << mPath << "' (" << INSTP(mHandle) << ") loaded, "
1200  << DEC(gPluginConstructCount) << " created, " << DEC(gPluginDestructCount) << " destroyed");
1201 }
1202 
1204 {
1205  if (mHandle)
1206  #if defined(AJABareMetal)
1207  ; // unimplemented
1208  #elif !defined(MSWindows)
1209  ::dlclose(mHandle);
1210  #else // macOS or Linux
1211  ::FreeLibrary(mHandle);
1212  #endif
1214  P_NOTE("Dynamic/shared library '" << mPath << "' (" << INSTP(mHandle) << ") unloaded, "
1215  << DEC(gPluginConstructCount) << " created, " << DEC(gPluginDestructCount) << " destroyed");
1216  mHandle = AJA_NULL;
1217  mPath.clear();
1218 }
1219 
1220 void * NTV2Plugin::addressForSymbol (const string & inSymbolName, string & outErrorMsg)
1221 {
1222  outErrorMsg.clear();
1223  if (!mHandle)
1224  return AJA_NULL;
1225  if (inSymbolName.empty())
1226  return AJA_NULL;
1227  void * result(AJA_NULL);
1228  ostringstream err;
1229  #if defined(AJABareMetal)
1230  // TODO
1231  #elif defined(MSWindows)
1232  result = reinterpret_cast<void*>(::GetProcAddress(reinterpret_cast<HMODULE>(mHandle), inSymbolName.c_str()));
1233  if (!result)
1234  err << "'GetProcAddress' failed for '" << inSymbolName << "': " << WinErrStr(::GetLastError());
1235  #else // MacOS or Linux
1236  result = ::dlsym(mHandle, inSymbolName.c_str());
1237  if (!result)
1238  { const char * pErrorStr(::dlerror());
1239  const string errStr (pErrorStr ? pErrorStr : "");
1240  err << "'dlsym' failed for '" << inSymbolName << "': " << errStr;
1241  }
1242  #endif // MacOS or Linux
1243  outErrorMsg = err.str();
1244  return result;
1245 } // addressForSymbol
1246 
1247 
1248 /*****************************************************************************************************************************************************
1249  @brief A singleton that tracks and monitors loaded plugins, and frees them when no one is using them.
1250  @bug This doesn't work on some platforms when libajantv2 is statically linked into the plugin. When
1251  the plugin loads, the plugin gets its own separate libajantv2 static globals, and thus, a second
1252  set of libajantv2 singletons, including this one.
1253 *****************************************************************************************************************************************************/
1256 
1257 // Singleton that tracks plugin use
1259 {
1260  public: // Class Methods
1261  static PluginRegistry & Get (void);
1262  static void Terminate (void);
1263  static inline void EnableDebugging (const bool inEnable = true) {sDebugRegistry = inEnable;}
1264  static inline bool DebuggingEnabled (void) {return sDebugRegistry;}
1265 
1266  public: // Instance Methods
1267  PluginRegistry();
1268  ~PluginRegistry();
1269  bool loadPlugin (const string & path, const string & folderPath, NTV2PluginPtr & outPtr, string & errMsg, const bool useStdout);
1270  bool unloadPlugin (const string & path, string & errMsg);
1271  bool pluginIsLoaded (const string & path);
1272  bool pluginForPath (const string & path, NTV2PluginPtr & outHandle);
1273  NTV2StringList loadedPlugins (void);
1274  NTV2StringList pluginStats (void); // returns string list, each has pluginPath<tab>refCount
1275  bool hasPath (const string & path);
1276  bool indexForPath (const string & path, size_t & outIndex);
1277  ULWord countForPath (const string & path);
1278  uint32_t * refConForPath (const string & path);
1279  inline bool useStdout (void) const {return DebuggingEnabled();}
1280 
1281  private:
1282  typedef map<string, NTV2PluginPtr> NTV2PluginMap;
1283  NTV2StringList mPluginPaths; // List of unique known plugins; only grows, never shrinks
1284  ULWordSequence mPluginCounts; // Per-plugin instance counts: correlates to gPluginPaths
1285  ULWordSequence mCompareCounts; // For detecting changes to mPluginCounts
1286  NTV2PluginMap mPluginMap; // Maps each unique plugin path to its corresponding NTV2Plugin instance
1287  AJALock mPluginMapLock; // Mutex to serialize access to these registry globals
1288  AJAThread mMonitor; // Thread that monitors plugin utilization
1289  bool mQuitMonitor; // Set true to terminate monitor
1290  private:
1291  static void Monitor (AJAThread * pThread, void * pContext);
1292  void monitor (void); // Monitor thread function
1293  static PluginRegistryPtr sSingleton;
1294  static AJALock sMutex;
1295  static bool sDebugRegistry;
1296 }; // PluginRegistry
1297 
1298 PluginRegistryPtr PluginRegistry::sSingleton;
1299 AJALock PluginRegistry::sMutex;
1300 bool PluginRegistry::sDebugRegistry(false);
1301 
1303 {
1304  AJAAutoLock tmp(&sMutex);
1305  if (!sSingleton)
1306  sSingleton = new PluginRegistry;
1307  return *sSingleton;
1308 }
1309 
1311 {
1312  AJAAutoLock tmp(&sMutex);
1313  PLGWARN("");
1314  sSingleton = PluginRegistryPtr();
1315 }
1316 
1317 void PluginRegistry::Monitor (AJAThread * pThread, void * pContext)
1318 { (void) pThread;
1319  PluginRegistry * pObj (reinterpret_cast<PluginRegistry*>(pContext));
1320  if (pObj)
1321  pObj->monitor();
1322 }
1323 
1325  : mQuitMonitor(false)
1326 {
1327  P_NOTE ("PluginRegistry " << INSTP(this) << " constructed");
1328  mPluginCounts.reserve(256);
1329  for (size_t num(0); num < 256; num++)
1330  mPluginCounts.push_back(0);
1331  mCompareCounts = mPluginCounts;
1332  mMonitor.Attach(Monitor, this);
1334  mMonitor.Start();
1335 }
1336 
1338 {
1339  mQuitMonitor = true;
1340  while (mMonitor.Active())
1341  AJATime::Sleep(10);
1342  P_NOTE("PluginRegistry singleton " << INSTP(this) << " destroyed:" << endl << aja::join(pluginStats(), "\n"));
1343 }
1344 
1345 bool PluginRegistry::loadPlugin (const string & path, const string & folderPath, NTV2PluginPtr & outPtr, string & errMsg, const bool inUseStdout)
1346 {
1347  AJAAutoLock tmp(&mPluginMapLock);
1348  outPtr = NTV2PluginPtr();
1349  if (path.empty())
1350  {P_FAIL("empty path"); return false;}
1351  if (pluginForPath(path, outPtr))
1352  {
1353  NTV2_ASSERT(hasPath(path));
1354  return true;
1355  }
1356  if (hasPath(path))
1357  {P_WARN(INSTP(this) << ": '" << path << "': 'pluginForPath' returned false, but 'hasPath' returned true, count=" << countForPath(path));}
1358  string msg;
1359  if (!NTV2Plugin::LoadPlugin (path, folderPath, outPtr, msg, inUseStdout))
1360  {P_FAIL(msg); return false;}
1361  P_NOTE(INSTP(this) << ": Dynamic/shared library '" << path << "' loaded");
1362  mPluginMap[path] = outPtr;
1363  mPluginPaths.push_back(path);
1364  mPluginCounts.at(mPluginPaths.size()-1) = 0;
1365  return true;
1366 }
1367 
1368 bool PluginRegistry::unloadPlugin (const string & path, string & errMsg)
1369 {
1370  AJAAutoLock tmp(&mPluginMapLock);
1371  NTV2PluginPtr ptr;
1372  if (path.empty())
1373  return false;
1374  if (!pluginForPath(path, ptr))
1375  {P_FAIL(INSTP(this) << ": '" << path << "' requested to unload, but not loaded"); return false;}
1376  mPluginMap.erase(path); // This should cause NTV2Plugin destructor to be called
1377  P_NOTE(INSTP(this) << ": '" << path << "' unloaded");
1378  return true;
1379 }
1380 
1381 bool PluginRegistry::pluginIsLoaded (const string & path)
1382 {
1383  AJAAutoLock tmp(&mPluginMapLock);
1384  return mPluginMap.find(path) != mPluginMap.end();
1385 }
1386 
1387 bool PluginRegistry::pluginForPath (const string & path, NTV2PluginPtr & outHandle)
1388 {
1389  AJAAutoLock tmp(&mPluginMapLock);
1390  NTV2PluginMap::const_iterator it(mPluginMap.find(path));
1391  if (it == mPluginMap.end())
1392  outHandle = NTV2PluginPtr();
1393  else
1394  outHandle = it->second;
1395  return outHandle;
1396 }
1397 
1399 {
1400  NTV2StringList result;
1401  AJAAutoLock tmp(&mPluginMapLock);
1402  for (NTV2PluginMap::const_iterator it(mPluginMap.begin()); it != mPluginMap.end(); ++it)
1403  result.push_back(it->first);
1404  return result;
1405 }
1406 
1408 {
1409  NTV2StringList result;
1410  AJAAutoLock tmp(&mPluginMapLock);
1411  for (size_t ndx(0); ndx < mPluginPaths.size(); ndx++)
1412  {
1413  const string path (mPluginPaths.at(ndx));
1414  ostringstream oss; oss << path << "\t" << DEC(countForPath(path));
1415  NTV2PluginPtr p;
1416  if (pluginForPath(path, p))
1417  oss << "\t" << (p->isLoaded() ? "loaded" : "unloaded");
1418  else
1419  oss << "\t" << "---";
1420  result.push_back(oss.str());
1421  }
1422  return result;
1423 }
1424 
1425 bool PluginRegistry::hasPath (const string & path)
1426 {
1427  size_t ndx(0);
1428  return indexForPath(path,ndx);
1429 }
1430 
1431 bool PluginRegistry::indexForPath (const string & path, size_t & outIndex)
1432 {
1433  AJAAutoLock tmp(&mPluginMapLock);
1434  for (outIndex = 0; outIndex < mPluginPaths.size(); outIndex++)
1435  if (path == mPluginPaths.at(outIndex))
1436  return true;
1437  return false;
1438 }
1439 
1440 uint32_t * PluginRegistry::refConForPath (const string & path)
1441 {
1442  AJAAutoLock tmp(&mPluginMapLock);
1443  size_t ndx(0);
1444  if (indexForPath(path, ndx))
1445  return &mPluginCounts[ndx];
1446  return AJA_NULL;
1447 }
1448 
1450 {
1451  size_t ndx(0);
1452  if (indexForPath(path, ndx))
1453  return mPluginCounts.at(ndx);
1454  return 0;
1455 }
1456 
1457 void PluginRegistry::monitor (void)
1458 {
1459  P_NOTE("PluginRegistry " << INSTP(this) << " monitor started");
1460  ostringstream oss; oss << "PluginReg" << HEX0N(uint32_t(uint64_t(this)),8);
1461  mMonitor.SetThreadName(oss.str().c_str());
1462  while (!mQuitMonitor)
1463  {
1464  {
1465  AJAAutoLock tmp(&mPluginMapLock);
1466  for (size_t ndx(0); ndx < mPluginPaths.size(); ndx++)
1467  {
1468  const uint32_t oldCount(mCompareCounts.at(ndx)), newCount(mPluginCounts.at(ndx));
1469  if (newCount != oldCount)
1470  {
1471  string errMsg, path(mPluginPaths.at(ndx));
1472  if (newCount > oldCount)
1473  {P_NOTE("PluginRegistry " << INSTP(this) << ": Plugin '" << path << "' utilization "
1474  << "increased from " << DEC(oldCount) << " to " << DEC(newCount));}
1475  else
1476  {
1477  P_NOTE("PluginRegistry " << INSTP(this) << ": Plugin '" << path << "' utilization "
1478  << "decreased from " << DEC(oldCount) << " to " << DEC(newCount));
1479  if (newCount == 0)
1480  unloadPlugin(path, errMsg);
1481  } // else count decreased
1482  mCompareCounts.at(ndx) = newCount;
1483  } // something changed
1484  } // for each plugin
1485  }
1486  AJATime::Sleep(250);
1487  }
1488  P_NOTE("PluginRegistry " << INSTP(this) << " monitor stopped");
1489 }
1490 
1491 /*****************************************************************************************************************************************************
1492  @brief Knows how to load & validate a plugin
1493 *****************************************************************************************************************************************************/
1495 {
1496  public: // Instance Methods
1497  NTV2PluginLoader (NTV2Dictionary & params);
1498  ~NTV2PluginLoader ();
1499  void * getFunctionAddress (const string & inFuncName);
1500  inline string pluginPath (void) const {return mDict.valueForKey(kNTV2PluginInfoKey_PluginPath);}
1501  inline string pluginSigPath (void) const {return mDict.valueForKey(kNTV2PluginInfoKey_PluginSigPath);}
1502  inline string pluginsPath (void) const {return mDict.valueForKey(kNTV2PluginInfoKey_PluginsPath);}
1503  inline string pluginBaseName (void) const {return mDict.valueForKey(kNTV2PluginInfoKey_PluginBaseName);}
1504  bool isValidated (void) const;
1505  inline bool showParams (void) const {return mQueryParams.hasKey(kQParamShowParams);}
1506  void * refCon (void) const;
1507 
1508  protected: // Used internally
1509  bool validate (void);
1510  void * getSymbolAddress (const string & inSymbolName, string & outErrorMsg);
1511  bool getPluginsFolder (string & outPath) const;
1512  bool getBaseNameFromScheme (string & outName) const;
1513  inline bool isOpen (void) {return mpPlugin ? mpPlugin->isLoaded() : false;}
1514  inline bool useStdout (void) const {return mQueryParams.hasKey(kQParamLogToStdout);}
1515  inline bool isVerbose (void) const {return mQueryParams.hasKey(kQParamVerboseLogging);}
1516  inline bool showCertificate (void) const {return mQueryParams.hasKey(kQParamShowX509Cert);}
1517  bool fail (void);
1518 
1519  private: // Instance Data
1520  NTV2Dictionary & mDict;
1521  NTV2Dictionary mQueryParams;
1522  NTV2PluginPtr mpPlugin;
1523  bool mValidated;
1524  mutable string errMsg;
1525 
1526  protected: // Class Methods
1527  static bool ExtractCertInfo (NTV2Dictionary & outInfo, const string & inStr);
1528  static bool ExtractIssuerInfo (NTV2Dictionary & outInfo, const string & inStr, const string & inParentKey);
1529  static string mbedErrStr (const int mbedtlsReturnCode);
1530 }; // NTV2PluginLoader
1531 
1532 
1533 // Constructor -- peforms all preparatory work: determines which plugin to load, then loads & validates it
1535  : mDict(params),
1536  mValidated(false)
1537 {
1540  const NTV2Dictionary originalParams(mDict);
1541  if (NTV2DeviceSpecParser::ParseQueryParams (mDict, mQueryParams) && !mQueryParams.empty())
1542  mDict.addFrom(mQueryParams);
1544  mDict.erase(kNTV2PluginInfoKey_Fingerprint); // Be sure caller can't cheat
1545  P_INFO("Loader created for '" << mDict.valueForKey(kConnectParamScheme) << "', " << DEC(gLoaderConstructCount) << " created, "
1546  << DEC(gLoaderDestructCount) << " destroyed");
1547 
1548  // Determine plugin base name & where to find the dylib/dll/so...
1549  string pluginBaseName, pluginsFolder;
1550  if (getBaseNameFromScheme(pluginBaseName) && getPluginsFolder(pluginsFolder))
1551  {
1552  const string path (pluginsFolder + pluginBaseName);
1553  const string sigPath (path + SIG_EXTENSION), dllPath (path + DLL_EXTENSION);
1554  mDict.insert(kNTV2PluginInfoKey_PluginPath, dllPath);
1555  mDict.insert(kNTV2PluginInfoKey_PluginSigPath, sigPath);
1556  if (showParams())
1557  { cout << "## NOTE: Original params for '" << pluginPath() << "':" << endl;
1558  originalParams.Print(cout, false) << endl;
1559  }
1560  // Validate the plugin...
1561  validate();
1562  if (showParams())
1563  { cout << "## NOTE: Final params for '" << pluginPath() << "':" << endl;
1564  mDict.Print(cout, false) << endl;
1565  }
1566  }
1567 }
1568 
1570 {
1572  P_INFO("Loader destroyed for '" << pluginBaseName() << "', " << DEC(gLoaderConstructCount) << " created, "
1573  << DEC(gLoaderDestructCount) << " destroyed");
1574 }
1575 
1576 string NTV2PluginLoader::mbedErrStr (const int mbedtlsReturnCode)
1577 {
1578  NTV2Buffer errBuff(4096);
1579  string str;
1580  mbedtls_strerror (mbedtlsReturnCode, errBuff, errBuff);
1581  errBuff.GetString (str, /*U8Offset*/0, /*maxSize*/errBuff);
1582  return str;
1583 }
1584 
1585 bool NTV2PluginLoader::ExtractCertInfo (NTV2Dictionary & outInfo, const string & inStr)
1586 {
1587  outInfo.clear();
1588  if (inStr.empty())
1589  return false;
1590  string keyPrefix;
1591  NTV2StringList lines(aja::split(inStr, "\n"));
1592  for (size_t lineNdx(0); lineNdx < lines.size(); lineNdx++)
1593  {
1594  string line (lines.at(lineNdx));
1595  const bool indented (line.empty() ? false : line.at(0) == ' ');
1596  aja::strip(line);
1597  if (line.empty()) // there shouldn't be empty lines...
1598  continue; // ...but skip them anyway if they happen to appear
1599  NTV2StringList keyValPair (aja::split(line, " : "));
1600  if (keyValPair.size() != 2)
1601  {
1602  if (keyValPair.size() == 1)
1603  {
1604  keyPrefix = keyValPair.at(0);
1605  aja::replace(keyPrefix, ":", "");
1606  aja::strip(keyPrefix);
1607  continue; // next line
1608  }
1609  PLGFAIL("cert info line " << DEC(lineNdx+1) << " '" << line << "' has "
1610  << DEC(keyValPair.size()) << " column(s) -- expected 2");
1611  return false;
1612  }
1613  string key(keyValPair.at(0)), val(keyValPair.at(1));
1614  if (key.empty())
1615  {PLGFAIL("cert info line " << DEC(lineNdx+1) << " '" << line << "' empty key for value '" << val << "'"); continue;}
1616  if (indented && !keyPrefix.empty())
1617  {aja::strip(key); key = keyPrefix + ": " + key;}
1618  else
1619  {aja::strip(key); keyPrefix.clear();}
1620  if (outInfo.hasKey(key))
1621  val = outInfo.valueForKey(key) + ", " + val;
1622  outInfo.insert(key, aja::strip(val)); // ignore errors for now
1623  } // for each info line
1624  return true;
1625 } // ExtractCertInfo
1626 
1627 bool NTV2PluginLoader::ExtractIssuerInfo (NTV2Dictionary & outInfo, const string & inStr, const string & inParentKey)
1628 {
1629  outInfo.clear();
1630  if (inStr.empty())
1631  return false;
1632  string str(inStr);
1633  NTV2StringList pairs(aja::split(aja::replace(str, "\\,", ","), ", ")), normalized;
1634  string lastKey;
1635  for (size_t ndx(0); ndx < pairs.size(); ndx++)
1636  {
1637  string assignment (pairs.at(ndx));
1638  if (assignment.find('=') == string::npos)
1639  {
1640  if (!lastKey.empty())
1641  outInfo.insert (lastKey, outInfo.valueForKey(lastKey) + ", " + assignment);
1642  }
1643  else
1644  {
1645  NTV2StringList pieces (aja::split(assignment, "="));
1646  if (pieces.size() != 2)
1647  {PLGFAIL("'" << inParentKey << "' assignment '" << assignment << "' has " << pieces.size() << " component(s) -- expected 2"); continue;}
1648  lastKey = pieces.at(0);
1649  string val(pieces.at(1));
1650  outInfo.insert (aja::strip(lastKey), val);
1651  }
1652  } // for each key/val assignment
1653  return true;
1654 } // ExtractIssuerInfo
1655 
1656 void * NTV2PluginLoader::getSymbolAddress (const string & inSymbolName, string & outErrorMsg)
1657 {
1658  outErrorMsg.clear();
1659  if (!mpPlugin)
1660  return AJA_NULL;
1661  return mpPlugin->addressForSymbol(inSymbolName, outErrorMsg);
1662 } // getSymbolAddress
1663 
1664 void * NTV2PluginLoader::refCon (void) const
1665 {
1667 }
1668 
1669 bool NTV2PluginLoader::getPluginsFolder (string & outPath) const
1670 {
1671  if (!pluginsPath().empty())
1672  {outPath = pluginsPath(); return true;} // already known, assumed to be good
1673 
1674  // Plugins are expected to be in the "aja" folder (the parent folder of the "aja/firmware" folder)...
1675  outPath = ::NTV2GetPluginsFolderPath(true/*include trailing slash*/);
1676  if (outPath.empty())
1677  return false;
1678  PLGDBG("AJA plugin path is '" << outPath << "'");
1679  mDict.insert(kNTV2PluginInfoKey_PluginsPath, outPath); // Store it in 'PluginsPath'
1680  return !outPath.empty(); // Success if not empty
1681 }
1682 
1683 bool NTV2PluginLoader::getBaseNameFromScheme (string & outName) const
1684 {
1685  if (!pluginBaseName().empty())
1686  {outName = pluginBaseName(); return true;} // already known, assumed to be good
1687 
1688  // URL scheme determines plugin base name...
1689  if (!mDict.hasKey(kConnectParamScheme))
1690  {P_FAIL("Missing scheme -- params: " << mDict); return false;} // No scheme
1691  string scheme(mDict.valueForKey(kConnectParamScheme));
1692  outName = scheme;
1693  mDict.insert(kNTV2PluginInfoKey_PluginBaseName, outName);
1694  return !outName.empty(); // Success if not empty
1695 }
1696 
1698 {
1699  if (mDict.hasKey(kNTV2PluginInfoKey_Errors))
1700  { const string v(mDict.valueForKey(kNTV2PluginInfoKey_Errors) + "\n" + errMsg);
1703  }
1704  else
1705  mDict.insert(kNTV2PluginInfoKey_Errors, errMsg);
1706  return false;
1707 }
1708 
1710 {
1711  // Load contents of plugin & sig files into sigContent & dllContent buffers...
1712  NTV2Buffer sigContent, dllContent;
1713  {
1714  // no more than 500MB
1715  const size_t maxBufSize = 512*1024*1024;
1716 
1717  ifstream dllF;
1718  dllF.open(pluginPath(), std::ios::in | std::ios::binary);
1719  if (dllF.fail())
1720  {P_FAIL("Could not open plugin file '" << pluginPath() << "'"); return fail();}
1721  if (!dllF.seekg(0, ios_base::end))
1722  {P_FAIL("Could not seek to end of plugin file '" << pluginPath() << "'"); return fail();}
1723  ifstream::pos_type curOffset(dllF.tellg());
1724  if (int(curOffset) == -1)
1725  {P_FAIL("Could not determine size of plugin file '" << pluginPath() << "'"); return fail();}
1726  size_t size = size_t(curOffset);
1727  if (size == 0)
1728  {P_FAIL("Plugin file '" << pluginPath() << "' is empty"); return fail();}
1729  if (size > maxBufSize)
1730  {P_FAIL("EOF not reached in plugin file '" << pluginPath() << "' -- over 500MB in size?"); return fail();}
1731  size += 1;
1732 
1733  NTV2Buffer tmp(size);
1734  if (!dllF.seekg(0, ios_base::beg))
1735  {P_FAIL("Could not seek back to start of plugin file '" << pluginPath() << "'");return fail();}
1736  if (!dllF.read(tmp, tmp.GetByteCount()).eof())
1737  {P_FAIL("EOF not reached in plugin file '" << pluginPath() << "' -- over 500MB in size?"); return fail();}
1738  tmp.Truncate(size_t(dllF.gcount()));
1739  dllContent = tmp;
1740 
1741  tmp.Allocate(size);
1742  ifstream sigF(pluginSigPath().c_str(), std::ios::in | std::ios::binary);
1743  if (!sigF.good())
1744  {P_FAIL("Signature file '" << pluginSigPath() << "' missing"); return fail();}
1745  if (!sigF.read(tmp, tmp.GetByteCount()).eof())
1746  {P_FAIL("EOF not reached in signature file '" << pluginSigPath() << "' -- over 500MB in size?"); return fail();}
1747  tmp.Truncate(size_t(sigF.gcount()));
1748  sigContent = tmp;
1749  }
1750 
1751  // Decode sigContent...
1752  NTV2Dictionary dict;
1753  { const string dictStr (reinterpret_cast<const char*>(sigContent.GetHostPointer()), size_t(sigContent.GetByteCount()));
1754  if (!dict.deserialize(dictStr))
1755  {P_FAIL("Unable to decode signature file '" << pluginSigPath() << "'"); return fail();}
1756  }
1757  P_DBG(DEC(dict.keys().size()) << " keys found in signature file '" << pluginSigPath() << "': " << dict.keys());
1758  NTV2Buffer checksumFromSigFile, x509CertFromSigFile, signature;
1760  {P_FAIL("Signature file '" << pluginSigPath() << "' missing '" << kNTV2PluginSigFileKey_X509Certificate << "' key"); return fail();}
1762  {P_FAIL("Signature file '" << pluginSigPath() << "' missing '" << kNTV2PluginSigFileKey_Signature << "' key"); return fail();}
1763  if (!x509CertFromSigFile.SetFromHexString(dict.valueForKey(kNTV2PluginSigFileKey_X509Certificate)))
1764  {P_FAIL("'SetFromHexString' failed to decode X509 certificate extracted from '" << pluginSigPath() << "' key '" << kNTV2PluginSigFileKey_X509Certificate << "'"); return fail();}
1765  if (!signature.SetFromHexString(dict.valueForKey(kNTV2PluginSigFileKey_Signature)))
1766  {P_FAIL("'SetFromHexString' failed to decode signature extracted from '" << pluginSigPath() << "' key '" << kNTV2PluginSigFileKey_Signature << "'"); return fail();}
1767  if (isVerbose()) {string s; if (signature.toHexString(s)) cout << "## DEBUG1: signature: " << s << endl;}
1768 
1769  // Grab the signing certificate found in the .sig file...
1770  mbedtls_x509_crt crt; // Container for X509 certificate
1771  mbedtls_x509_crt_init(&crt); // Initialize it as empty
1772  int ret = mbedtls_x509_crt_parse(&crt, x509CertFromSigFile, x509CertFromSigFile);
1773  if (ret)
1774  { P_FAIL("'mbedtls_x509_crt_parse' returned " << ret << " (" << mbedErrStr(ret) << ") for X509 cert found in '" << pluginSigPath() << "'");
1775  mbedtls_x509_crt_free(&crt);
1776  return fail();
1777  }
1778 
1779  // Extract certificate info...
1780  NTV2Dictionary certInfo, issuerInfo, subjectInfo;
1781  {
1782  NTV2Buffer msgBuff(4096);
1783  int msgLength (mbedtls_x509_crt_info (msgBuff, msgBuff, /*prefixString*/"", &crt));
1784  string msg (msgBuff, size_t(msgLength));
1785  if (msg.empty())
1786  { P_FAIL("'mbedtls_x509_crt_info' returned no info for X509 cert found in '" << pluginSigPath() << "'");
1787  mbedtls_x509_crt_free(&crt);
1788  return fail();
1789  }
1790  if (showCertificate())
1791  cout << "## DEBUG: Raw X509 certificate info extracted from signature file '" << pluginSigPath() << "':" << endl
1792  << " " << msg << endl;
1793  if (!ExtractCertInfo (certInfo, msg))
1794  { mbedtls_x509_crt_free(&crt);
1795  return false;
1796  }
1797  if (isVerbose())
1798  { cout << "## NOTE: X509 certificate info extracted from signature file '" << pluginSigPath() << "':" << endl;
1799  certInfo.Print(cout, false) << endl;
1800  }
1801  if (certInfo.hasKey("issuer name"))
1802  if (!ExtractIssuerInfo (issuerInfo, certInfo.valueForKey("issuer name"), "issuer name"))
1803  { mbedtls_x509_crt_free(&crt);
1804  return false;
1805  }
1806  if (certInfo.hasKey("subject name"))
1807  if (!ExtractIssuerInfo (subjectInfo, certInfo.valueForKey("subject name"), "subject name"))
1808  { mbedtls_x509_crt_free(&crt);
1809  return false;
1810  }
1811  if (!certInfo.hasKey(kNTV2PluginInfoKey_Fingerprint))
1812  { P_FAIL("Missing key '" << kNTV2PluginInfoKey_Fingerprint << "' in X509 certificate from '" << pluginSigPath() << "'");
1813  mbedtls_x509_crt_free(&crt);
1814  return fail();
1815  }
1816  if (isVerbose() && !issuerInfo.empty())
1817  { cout << "## NOTE: 'issuer name' info:" << endl;
1818  issuerInfo.Print(cout, false) << endl;
1819  }
1820  if (isVerbose() && !subjectInfo.empty())
1821  { cout << "## NOTE: 'subject name' info:" << endl;
1822  subjectInfo.Print(cout, false) << endl;
1823  }
1824  if (!issuerInfo.hasKey(kNTV2PluginX500AttrKey_CommonName))
1825  { P_FAIL("Missing 'Issuer' key '" << kNTV2PluginX500AttrKey_CommonName << "' in X509 certificate from '" << pluginSigPath() << "'");
1826  mbedtls_x509_crt_free(&crt);
1827  return fail();
1828  }
1830  { P_FAIL("Missing 'Issuer' key '" << kNTV2PluginX500AttrKey_OrganizationName << "' in X509 certificate from '" << pluginSigPath() << "'");
1831  mbedtls_x509_crt_free(&crt);
1832  return fail();
1833  }
1835  { P_FAIL("Missing 'Subject' key '" << kNTV2PluginX500AttrKey_OrganizationalUnitName << "' in X509 certificate from '" << pluginSigPath() << "'");
1836  mbedtls_x509_crt_free(&crt);
1837  return fail();
1838  }
1839  mDict.addFrom(certInfo); // Store certInfo key/value pairs into client/server instance's params...
1840  }
1841 
1842  // Compute SHA256 hash of plugin...
1843  NTV2Buffer checksumFromDLL(32);
1844  ret = mbedtls_md_file (mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), pluginPath().c_str(), checksumFromDLL);
1845  if (ret)
1846  { P_FAIL("'mbedtls_md_file' returned " << ret << " (" << mbedErrStr(ret) << ") for '" << pluginPath() << "'");
1847  mbedtls_x509_crt_free(&crt);
1848  return fail();
1849  }
1850  if (isVerbose()) {string s; if (checksumFromDLL.toHexString(s)) cout << "## DEBUG: checksumFromDLL: " << s << endl;}
1851  if (isVerbose()) {string s; if (signature.toHexString(s)) cout << "## DEBUG2: signature: " << s << endl;}
1852 
1853  // Verify the dylib/DLL/so signature...
1854  ret = mbedtls_pk_verify (&crt.pk, // PK context
1855  MBEDTLS_MD_SHA256, // msg digest type (hash algorithm used)
1856  checksumFromDLL, // Ptr to hash of signed msg
1857  0, // Byte length of hash --- zero means "use length associated with msg digest type instead"
1858  signature, // Ptr to signature being verified
1859  signature.GetByteCount()); // Length of signature being verified (in bytes)
1860  if (ret)
1861  { P_FAIL("'mbedtls_pk_verify' returned " << ret << " (" << mbedErrStr(ret) << ") for '" << pluginSigPath() << "'");
1862  mbedtls_x509_crt_free(&crt);
1863  return fail();
1864  }
1865  mbedtls_x509_crt_free(&crt); // Done using the mbedtls_x509_crt struct
1866  P_DBG("'mbedtls_pk_verify' succeeded for '" << pluginPath() << "' -- signature valid");
1867 
1868  // Load/open the shared library...
1869  if (!mpPlugin)
1870  if (!PluginRegistry::Get().loadPlugin (pluginPath(), pluginsPath(), mpPlugin, errMsg, useStdout()))
1871  return fail();
1872  PLGDBG("'" << pluginPath() << "' opened");
1873 
1874  // Obtain AJA Registration Info...
1875  NTV2Dictionary regInfo;
1876  string errGetInfo;
1877  void * pGetInfo = getSymbolAddress(kFuncNameGetRegInfo, errGetInfo);
1878  if (!pGetInfo)
1879  { P_FAIL("'" << pluginPath() << "': '" kFuncNameGetRegInfo "' failed: " << errGetInfo);
1880  return fail();
1881  }
1882  fpGetRegistrationInfo pGetRegInfo = reinterpret_cast<fpGetRegistrationInfo>(pGetInfo);
1883  if (!(*pGetRegInfo)(uint32_t(AJA_NTV2_SDK_VERSION), regInfo))
1884  {P_FAIL("'" << pluginPath() << "': '" << kFuncNameGetRegInfo << "' failed"); return fail();}
1885  PLGDBG("'" << pluginPath() << "': '" << kFuncNameGetRegInfo << "': returned " << regInfo.keys());
1886  if (regInfo.empty())
1887  {P_FAIL("'" << pluginPath() << "': no registration info (empty)"); return fail();}
1888 
1889  // Check for required registration info keys...
1890  NTV2StringList missingRegInfoKeys;
1895  for (size_t ndx(0); ndx < reqKeys.size(); ndx++)
1896  if (!regInfo.hasKey(reqKeys.at(ndx)))
1897  missingRegInfoKeys.push_back(reqKeys.at(ndx));
1898  if (!missingRegInfoKeys.empty())
1899  { P_FAIL("'" << pluginPath() << "': missing key(s) in registration info: '"
1900  << aja::join(missingRegInfoKeys, "','") << "'");
1901  return fail(); // fail
1902  }
1903  mDict.addFrom(regInfo); // Add registration info to plugin's dictionary
1904 
1905  // Check planet alignment...
1906  const string cnReg(regInfo.valueForKey(kNTV2PluginRegInfoKey_CommonName)),
1907  cnCert(issuerInfo.valueForKey(kNTV2PluginX500AttrKey_CommonName));
1908  const string onReg(regInfo.valueForKey(kNTV2PluginRegInfoKey_Vendor)),
1910  const string ouReg(regInfo.valueForKey(kNTV2PluginRegInfoKey_OrgUnit)),
1912  const string myVers(NTV2RPCBase::ShortSDKVersion()),
1914  const string ajaFingerprint(NTV2RPCBase::AJAFingerprint (/*lowerCase*/true, /*stripColons*/false));
1915  string fingerprint(mDict.valueForKey(kNTV2PluginInfoKey_Fingerprint));
1916  aja::lower(fingerprint); // since ajaFingerprint is lower case
1917  if (onReg != onCert)
1918  { P_FAIL("Vendor name (key='" << kNTV2PluginRegInfoKey_Vendor << "') \"" << onReg << "\" from plugin \""
1919  << pluginPath() << "\" doesn't match organization name (key='" << kNTV2PluginX500AttrKey_OrganizationName
1920  << "') \"" << onCert << "\" from X509 certificate 'Issuer' in '" << pluginSigPath() << "'");
1921  return fail();
1922  }
1923  if (cnReg != cnCert)
1924  { P_FAIL("Common name (key='" << kNTV2PluginRegInfoKey_CommonName << "') \"" << cnReg << "\" from plugin \""
1925  << pluginPath() << "\" doesn't match common name (key='" << kNTV2PluginX500AttrKey_CommonName
1926  << "') \"" << cnCert << "\" from X509 certificate 'Issuer' in '" << pluginSigPath() << "'");
1927  return fail();
1928  }
1929  if (ouReg != ouCert)
1930  { P_FAIL("Org unit (key='" << kNTV2PluginX500AttrKey_OrganizationalUnitName << "') \"" << ouReg << "\" from plugin \""
1931  << pluginPath() << "\" doesn't match org unit (key='" << kNTV2PluginX500AttrKey_OrganizationalUnitName
1932  << "') \"" << ouCert << "\" from X509 certificate 'Subject' in '" << pluginSigPath() << "'");
1933  return fail();
1934  }
1935  if (myVers != plVers)
1936  { P_FAIL("SDK version '" << plVers << "' from plugin \"" << pluginPath()
1937  << "\" doesn't match client SDK version '" << myVers << "'");
1938  return fail();
1939  }
1940 #if 1
1941  if (fingerprint != ajaFingerprint)
1942  { P_FAIL("'" << pluginPath() << "':|Plugin not authorized/signed by AJA:|"
1943  << "Issuer serial: " << fingerprint << "|AJA serial: " << ajaFingerprint);
1944  return fail(); // fail
1945  }
1946 #endif
1947  // Green light
1948  mValidated = true;
1949  return true;
1950 } // validate
1951 
1952 // Returns address of function having given name
1953 void * NTV2PluginLoader::getFunctionAddress (const string & inFuncName)
1954 {
1955  // Load/open the shared library...
1956  if (!isOpen())
1957  {P_FAIL("'" << inFuncName << "': '" << pluginPath() << "' not loaded"); return AJA_NULL;}
1958  if (!isValidated())
1959  {P_FAIL("'" << inFuncName << "': '" << pluginPath() << "' not validated"); return AJA_NULL;}
1960 
1961  // Finally, the last step ---- get address of requested function...
1962  string errStr;
1963  void * pResult = getSymbolAddress(inFuncName, errStr);
1964  if (!pResult)
1965  {P_FAIL("'" << inFuncName << "': '" << pluginPath() << "': " << errStr); return AJA_NULL;}
1966  P_DBG("Calling '" << inFuncName << "' in '" << pluginPath() << "'");
1967  return pResult;
1968 } // getFunctionAddress
1969 
1971 {
1972  return mpPlugin && mValidated;
1973 }
1974 
1976 {
1977  const NTV2StringList paths (PluginRegistry::Get().loadedPlugins());
1978  if (paths.empty())
1979  cout << "0 plugins" << endl;
1980  else if (paths.size() == 1)
1981  cout << "1 plugin: " << paths.at(0) << endl;
1982  else cout << DEC(paths.size()) << " plugins:" << endl << aja::join(paths, "\n") << endl;
1983 }
1984 #endif // !defined(NTV2_PREVENT_PLUGIN_LOAD)
1985 
1986 
1987 /*****************************************************************************************************************************************************
1988  NTV2RPCBase
1989 *****************************************************************************************************************************************************/
1990 
1992  : mParams(params),
1993  mpRefCon(pRefCon)
1994 {
1995  NTV2Dictionary queryParams;
1997  if (mpRefCon)
1999  PDBGX("refCnt=" << DEC(mpRefCon ? *mpRefCon : 0) << ", " << DEC(gBaseConstructCount) << " created, "
2000  << DEC(gBaseDestructCount) << " destroyed");
2002  {cout << __FILE__ << "(" << __LINE__ << "):" << AJAFUNC << ":" << endl; mParams.Print(cout, false) << endl;}
2003 }
2004 
2006 {
2008  if (mpRefCon)
2010  PDBGX("refCnt=" << DEC(mpRefCon ? *mpRefCon : 0) << ", " << DEC(gBaseConstructCount) << " created, "
2011  << DEC(gBaseDestructCount) << " destroyed");
2012 }
2013 
2014 bool NTV2RPCBase::SetParams (const NTV2ConnectParams & inNewParams, const bool inAugment)
2015 {
2016  AJAAutoLock tmp(&mParamLock);
2017  size_t oldCount(mParams.size()), updated(0), added(0);
2018  if (inAugment)
2019  {
2020  updated = mParams.updateFrom(inNewParams);
2021  added = mParams.addFrom(inNewParams);
2022  NBSDBG(DEC(updated) << " param(s) updated, " << DEC(added) << " added: " << mParams);
2023  }
2024  else
2025  {
2026  mParams = inNewParams;
2027  NBSDBG(DEC(oldCount) << " param(s) removed, replaced with " << inNewParams);
2028  }
2029  if (mParams.empty())
2030  NBSWARN("No params");
2031  return true;
2032 }
2033 
2035 {
2036  string result(::NTV2Version());
2037  const NTV2StringList halves(aja::split(result, " "));
2038  if (halves.empty())
2039  return result;
2040  NTV2StringList nums(aja::split(halves.front(), "."));
2041  while (nums.size() > 3)
2042  nums.pop_back();
2043  return aja::join(nums, ".");
2044 }
2045 
2046 
2047 string NTV2RPCBase::AJAFingerprint (const bool inLowerCase, const bool inStripColons)
2048 {
2049  static const string sAJAFingerprint ("70:1A:37:93:FA:4F:34:30:58:55:51:0C:01:4E:45:7C:BE:5B:41:65");
2050  string result(sAJAFingerprint);
2051  if (inStripColons)
2052  aja::replace(result, ":", "");
2053  if (inLowerCase)
2054  aja::lower(result);
2055  return result;
2056 }
2057 
2058 
2059 /*****************************************************************************************************************************************************
2060  NTV2RPCClientAPI
2061 *****************************************************************************************************************************************************/
2062 
2064  : NTV2RPCBase(inParams, reinterpret_cast<ULWord*>(pRefCon))
2065 {
2066  AJADebug::Open();
2068  PDBGX(DEC(gClientConstructCount) << " created, " << DEC(gClientDestructCount) << " destroyed");
2069 }
2070 
2072 {
2073  if (IsConnected())
2074  NTV2Disconnect();
2076  PDBGX(DEC(gClientConstructCount) << " created, " << DEC(gClientDestructCount) << " destroyed");
2077 }
2078 
2080 {
2081  AJAAutoLock tmp(&mParamLock);
2082  return mParams;
2083 }
2084 
2085 bool NTV2RPCClientAPI::HasConnectParam (const string & inParam) const
2086 {
2087  AJAAutoLock tmp(&mParamLock);
2088  return mParams.hasKey(inParam);
2089 }
2090 
2091 string NTV2RPCClientAPI::ConnectParam (const string & inParam) const
2092 {
2093  AJAAutoLock tmp(&mParamLock);
2094  return mParams.valueForKey(inParam);
2095 }
2096 
2098 {
2100 }
2101 
2102 ostream & NTV2RPCClientAPI::Print (ostream & oss) const
2103 {
2104  oss << (IsConnected() ? "Connected" : "Disconnected");
2105  if (IsConnected() && !Name().empty())
2106  oss << " to '" << Name() << "'";
2107  return oss;
2108 }
2109 
2111 {
2112  NTV2StringList strs;
2113  string fName(ConnectParam(kQParamVDevFileName)), hostName;
2114  if (!fName.empty())
2115  strs.push_back(string("from '") + fName + "'");
2117  sysInfo.GetValue(AJA_SystemInfoTag_System_Name, hostName);
2118  if (!hostName.empty())
2119  {strs.push_back("on"); strs.push_back(string("'") + hostName + "'");}
2120  return aja::join(strs, " ");
2121 }
2122 
2124 {
2125  if (IsConnected())
2126  NTV2Disconnect();
2127  return NTV2OpenRemote();
2128 }
2129 
2131 {
2132  return NTV2CloseRemote();
2133 }
2134 
2135 bool NTV2RPCClientAPI::NTV2ReadRegisterRemote (const ULWord regNum, ULWord & outRegValue, const ULWord regMask, const ULWord regShift)
2136 { (void) regNum; (void) outRegValue; (void) regMask; (void) regShift;
2137  return false; // UNIMPLEMENTED
2138 }
2139 
2140 bool NTV2RPCClientAPI::NTV2WriteRegisterRemote (const ULWord regNum, const ULWord regValue, const ULWord regMask, const ULWord regShift)
2141 { (void) regNum; (void) regValue; (void) regMask; (void) regShift;
2142  return false; // UNIMPLEMENTED
2143 }
2144 
2146 { (void) autoCircData;
2147  return false; // UNIMPLEMENTED
2148 }
2149 
2151 { (void) eInterrupt; (void) timeOutMs;
2152  return false; // UNIMPLEMENTED
2153 }
2154 
2155 #if !defined(NTV2_DEPRECATE_16_3)
2157  { (void) bitFileType;
2158  ::memset(&bitFileInfo, 0, sizeof(bitFileInfo));
2159  return false; // UNIMPLEMENTED
2160  }
2161 
2163  {
2164  ::memset(&buildInfo, 0, sizeof(buildInfo));
2165  return false; // UNIMPLEMENTED
2166  }
2167 
2169  const UWord signalMask, const bool testPatDMAEnb, const ULWord testPatNum)
2170  { (void) channel; (void) testPatternFBF; (void) signalMask; (void) testPatDMAEnb; (void) testPatNum;
2171  return false; // UNIMPLEMENTED
2172  }
2173 
2174  bool NTV2RPCClientAPI::NTV2ReadRegisterMultiRemote (const ULWord numRegs, ULWord & outFailedRegNum, NTV2RegInfo outRegs[])
2175  { (void) numRegs; (void) outFailedRegNum; (void) outRegs;
2176  return false; // UNIMPLEMENTED
2177  }
2178 
2180  {
2181  outDriverVersion = 0xFFFFFFFF;
2182  return false; // UNIMPLEMENTED
2183  }
2184 #endif // !defined(NTV2_DEPRECATE_16_3)
2185 
2186 bool NTV2RPCClientAPI::NTV2DMATransferRemote ( const NTV2DMAEngine inDMAEngine, const bool inIsRead, const ULWord inFrameNumber,
2187  NTV2Buffer & inOutFrameBuffer, const ULWord inCardOffsetBytes,
2188  const ULWord inNumSegments, const ULWord inSegmentHostPitch,
2189  const ULWord inSegmentCardPitch, const bool inSynchronous)
2190 { (void) inDMAEngine; (void) inIsRead; (void) inFrameNumber; (void) inOutFrameBuffer;
2191  (void) inCardOffsetBytes; (void) inNumSegments; (void) inSegmentHostPitch;
2192  (void) inSegmentCardPitch; (void) inSynchronous;
2193  return false; // UNIMPLEMENTED
2194 }
2195 
2197 { (void) pInMessage;
2198  return false; // UNIMPLEMENTED
2199 }
2200 
2201 bool NTV2RPCClientAPI::NTV2GetBoolParamRemote (const ULWord inParamID, ULWord & outValue)
2202 { (void) inParamID;
2203  outValue = 0;
2204  return false; // UNIMPLEMENTED
2205 }
2206 
2208 { (void) inParamID;
2209  outValue = 0;
2210  return false; // UNIMPLEMENTED
2211 }
2212 
2213 bool NTV2RPCClientAPI::NTV2GetSupportedRemote (const ULWord inEnumsID, ULWordSet & outSupported)
2214 { (void) inEnumsID;
2215  outSupported.clear();
2216  return false; // UNIMPLEMENTED
2217 }
2218 
2220 {
2221  return false; // UNIMPLEMENTED
2222 }
2223 
2225 {
2226 // AJAAutoLock tmp(&mParamLock);
2227 // mParams.clear();
2228  return true;
2229 }
2230 
2232 {
2233 #if defined(NTV2_PREVENT_PLUGIN_LOAD)
2234  return AJA_NULL;
2235 #else
2236  NTV2RPCClientAPI * pRPCObject(AJA_NULL);
2237  {
2238  NTV2PluginLoader loader(params);
2239  fpCreateClient pFunc (reinterpret_cast<fpCreateClient>(loader.getFunctionAddress(kFuncNameCreateClient)));
2240  if (!pFunc)
2241  {NBCFAIL("'" << kFuncNameCreateClient << "' has NULL function address: " << params); return AJA_NULL;}
2242 
2243  // Call plugin's Create function to instantiate the NTV2RPCClientAPI object...
2244  try {
2245  pRPCObject = (*pFunc) (loader.refCon(), params, AJA_NTV2_SDK_VERSION);
2246  } catch (std::bad_alloc &) {
2247  pRPCObject = nullptr;
2248  NBCFAIL("'" << kFuncNameCreateClient << "' bad_alloc exception in CreateClient function");
2249  } catch (...) {
2250  pRPCObject = nullptr;
2251  NBCFAIL("'" << kFuncNameCreateClient << "' exception in CreateClient function");
2252  }
2253  if (!pRPCObject)
2254  NBCFAIL("'" << kFuncNameCreateClient << "' returned NULL client instance from: " << params);
2255  else
2256  NBCINFO("'" << kFuncNameCreateClient << "' created client instance " << xHEX0N(uint64_t(pRPCObject),16));
2257  } // loader freed here
2258  return pRPCObject;
2259 #endif
2260 } // CreateClient
2261 
2262 
2263 /*****************************************************************************************************************************************************
2264  NTV2RPCServerAPI
2265 *****************************************************************************************************************************************************/
2266 
2268 {
2269 #if defined(NTV2_PREVENT_PLUGIN_LOAD)
2270  return AJA_NULL;
2271 #else
2272  NTV2RPCServerAPI * pRPCObject(AJA_NULL);
2273  {
2274  NTV2PluginLoader loader(params);
2275  fpCreateServer pFunc = reinterpret_cast<fpCreateServer>(loader.getFunctionAddress(kFuncNameCreateServer));
2276  if (!pFunc)
2277  {NBCFAIL("'" << kFuncNameCreateServer << "' has NULL function address: " << params); return AJA_NULL;}
2278 
2279  // Call plugin's Create function to instantiate the NTV2RPCServerAPI object...
2280  try {
2281  pRPCObject = (*pFunc) (loader.refCon(), params, AJA_NTV2_SDK_VERSION);
2282  } catch (std::bad_alloc &) {
2283  pRPCObject = nullptr;
2284  NBCFAIL("'" << kFuncNameCreateServer << "' bad_alloc exception in CreateServer function");
2285  } catch (...) {
2286  pRPCObject = nullptr;
2287  NBCFAIL("'" << kFuncNameCreateServer << "' exception in CreateServer function");
2288  }
2289  if (!pRPCObject)
2290  NBCFAIL("'" << kFuncNameCreateServer << "' returned NULL server instance from: " << params);
2291  else
2292  NBCINFO("'" << kFuncNameCreateServer << "' created server instance " << xHEX0N(uint64_t(pRPCObject),16));
2293  }
2294  return pRPCObject; // It's caller's responsibility to delete pRPCObject
2295 #endif
2296 } // CreateServer
2297 
2298 NTV2RPCServerAPI * NTV2RPCServerAPI::CreateServer (const string & inURL) // CLASS METHOD
2299 {
2300  NTV2DeviceSpecParser parser(inURL);
2301  if (parser.HasErrors())
2302  {
2303  NBSFAIL(parser.Error() << " in URL:\n" << inURL);
2304  parser.PrintErrors(cerr);
2305  return AJA_NULL;
2306  }
2307  NTV2ConfigParams parms(parser.Results());
2308  return CreateServer(parms);
2309 }
2310 
2312  : NTV2RPCBase(inParams, reinterpret_cast<ULWord*>(pRefCon))
2313 {
2314  mRunning = mTerminate = false;
2315  NTV2Buffer spare(&mSpare, sizeof(mSpare)); spare.Fill(0ULL);
2316  AJADebug::Open();
2318  PDBGX(DEC(gServerConstructCount) << " created, " << DEC(gServerDestructCount) << " destroyed");
2319 }
2320 
2322 {
2323  Stop();
2324  while (IsRunning())
2325  AJATime::Sleep(50);
2327  PDBGX(DEC(gServerConstructCount) << " created, " << DEC(gServerDestructCount) << " destroyed");
2328 }
2329 
2331 { // This function normally should never be called;
2332  // It's usually overridden by a subclass
2333  NBSDBG("Started");
2334  while (!mTerminate)
2335  AJATime::Sleep(500);
2336  NBSDBG("Terminated");
2337 } // ServerFunction
2338 
2339 ostream & NTV2RPCServerAPI::Print (ostream & oss) const
2340 {
2341  oss << mParams;
2342  return oss;
2343 }
2344 
2346 {
2347  AJAAutoLock tmp(&mParamLock);
2348  return mParams;
2349 }
2350 
2351 bool NTV2RPCServerAPI::HasConfigParam (const string & inParam) const
2352 {
2353  AJAAutoLock tmp(&mParamLock);
2354  return mParams.hasKey(inParam);
2355 }
2356 
2357 string NTV2RPCServerAPI::ConfigParam (const string & inParam) const
2358 {
2359  AJAAutoLock tmp(&mParamLock);
2360  return mParams.valueForKey(inParam);
2361 }
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:77
#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:7396
virtual bool NTV2Connect(void)
NTV2FrameBufferFormat
Identifies a particular video frame buffer pixel format. See Device Frame Buffer Formats for details...
Definition: ntv2enums.h:221
#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__)
NTV2StringSet::const_iterator NTV2StringSetConstIter
#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:306
bool pluginForPath(const string &path, NTV2PluginPtr &outHandle)
static AJAStatus Open(bool incrementRefCount=false)
Definition: debug.cpp:44
size_t GetByteCount(void) const
#define kNTV2PluginRegInfoKey_Copyright
Plugin copyright notice.
Definition: ntv2nubaccess.h:79
bool showParams(void) const
#define kNTV2PluginRegInfoKey_Vendor
Plugin vendor (manufacturer) name.
Definition: ntv2nubaccess.h:73
#define kQParamVerboseLogging
Query parameter option that enables verbose message logging.
Definition: ntv2nubaccess.h:35
NTV2StringList::const_iterator NTV2StringListConstIter
NTV2Plugin(void *handle, const string &path, const bool useStdout)
#define kNTV2PluginX500AttrKey_OrganizationName
Definition: ntv2nubaccess.h:94
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:57
#define kNTV2PluginRegInfoKey_ShortName
Plugin short name.
Definition: ntv2nubaccess.h:76
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:236
NTV2Channel
These enum values are mostly used to identify a specific widget_framestore. They&#39;re also commonly use...
Definition: ntv2enums.h:1359
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:489
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:87
#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__)
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:61
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:69
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:1045
bool indexForPath(const string &path, size_t &outIndex)
std::set< std::string > NTV2StringSet
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:1859
#define AJA_NULL
Definition: ajatypes.h:180
#define kNTV2PluginInfoKey_Errors
Plugin load or validation error(s), if any.
Definition: ntv2nubaccess.h:70
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:95
bool isVerbose(void) const
std::set< ULWord > ULWordSet
A collection of unique ULWord (uint32_t) values.
bool getBaseNameFromScheme(string &outName) const
#define kNTV2PluginRegInfoKey_Description
Brief plugin description.
Definition: ntv2nubaccess.h:78
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:45
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:7370
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:74
uint64_t ULWord64
Definition: ajatypes.h:239
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:66
virtual bool IsRunning(void) const
#define INSTP(_p_)
NTV2BitFileType
Definition: ntv2enums.h:3352
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:68
std::string NTV2DeviceIDToString(const NTV2DeviceID inValue, const bool inForRetailDisplay=false)
Definition: ntv2utils.cpp:4608
#define kFuncNameGetRegInfo
Answers with plugin registration info.
Definition: ntv2nubaccess.h:62
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:65
#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:91
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:75
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:234
#define kNTV2PluginRegInfoKey_Version
Plugin version (string)
Definition: ntv2nubaccess.h:81
#define PLGDBG(__x__)
bool mRunning
Running?
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:88
std::string & upper(std::string &str)
Definition: common.cpp:442
#define kNTV2PluginRegInfoKey_NTV2SDKVersion
NTV2 SDK version that plugin was compiled with.
Definition: ntv2nubaccess.h:80
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:60
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:1043
void * GetHostPointer(void) const
string pluginsPath(void) const
uint32_t gBaseDestructCount(0)
void * getSymbolAddress(const string &inSymbolName, string &outErrorMsg)
std::vector< std::string > NTV2StringList
#define HEX0N(__x__, __n__)
Definition: debug.cpp:1181
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
#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:67