tdaq-develop-2025-02-12
ots_mm_udp_interface.cpp
1 
2 #include "test/ots_mm_udp_interface.h"
3 
4 #define MAXBUFLEN 5000
5 
6 //==============================================================================
8 void* get_in_addr(struct sockaddr* sa)
9 {
10  if(sa->sa_family == AF_INET)
11  {
12  return &(((struct sockaddr_in*)sa)->sin_addr);
13  }
14 
15  return &(((struct sockaddr_in6*)sa)->sin6_addr);
16 }
17 
18 //==============================================================================
19 int makeSocket(const char* ip, int port, struct sockaddr_in& mm_ai_addr)
20 {
21  int sockfd;
22  struct addrinfo hints, *servinfo, *p;
23  int rv;
24  // int numbytes;
25  // struct sockaddr_storage their_addr;
26  // socklen_t addr_len;
27  // char s[INET6_ADDRSTRLEN];
28 
29  memset(&hints, 0, sizeof hints);
30  hints.ai_family = AF_UNSPEC;
31  hints.ai_socktype = SOCK_DGRAM;
32 
33  if((rv = getaddrinfo(ip, std::to_string(port).c_str(), &hints, &servinfo)) != 0)
34  {
35  __SS__ << "getaddrinfo: " << gai_strerror(rv) << __E__;
36  __SS_THROW__;
37  }
38 
39  // loop through all the results and make a socket
40  for(p = servinfo; p != NULL; p = p->ai_next)
41  {
42  if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
43  {
44  __COUT_WARN__ << "sw: socket failed, trying other address..." << __E__;
45  continue;
46  }
47 
48  break;
49  }
50 
51  if(p == NULL)
52  {
53  __SS__ << "sw: failed to create socket" << __E__;
54  __SS_THROW__;
55  }
56 
57  //copy ai_addr, which is needed for sendto
58  memcpy(&mm_ai_addr, p->ai_addr, sizeof(mm_ai_addr));
59 
60  freeaddrinfo(servinfo);
61  return sockfd;
62 } //end makeSocket()
63 
64 //==============================================================================
67 fd_set fileDescriptor_;
68 struct timeval timeout_;
69 struct sockaddr_in fromAddress_;
70 socklen_t addressLength_ = sizeof(fromAddress_);
71 int receive(int sockfd,
72  std::string& buffer,
73  unsigned int timeoutSeconds,
74  unsigned int timeoutUSeconds,
75  bool verbose)
76 {
77  unsigned long fromIPAddress;
78  unsigned short fromPort;
79 
80  // set timeout period for select()
81  timeout_.tv_sec = timeoutSeconds;
82  timeout_.tv_usec = timeoutUSeconds;
83 
84  FD_ZERO(&fileDescriptor_);
85  FD_SET(sockfd, &fileDescriptor_);
86  select(sockfd + 1, &fileDescriptor_, 0, 0, &timeout_);
87 
88  int numberOfBytes;
89  if(FD_ISSET(sockfd, &fileDescriptor_))
90  {
91  buffer.resize(MAXBUFLEN); // NOTE: this is inexpensive according to
92  // Lorenzo/documentation in C++11 (only increases
93  // size once and doesn't decrease size)
94  if((numberOfBytes = recvfrom(sockfd,
95  &buffer[0],
96  MAXBUFLEN,
97  0,
98  (struct sockaddr*)&fromAddress_,
99  &addressLength_)) == -1)
100  {
101  __SS__ << "Error reading buffer from\tIP:\t";
102  std::string fromIP = inet_ntoa(fromAddress_.sin_addr);
103  fromIPAddress = fromAddress_.sin_addr.s_addr;
104  fromPort = fromAddress_.sin_port;
105 
106  for(int i = 0; i < 4; i++)
107  {
108  ss << ((fromIPAddress << (i * 8)) & 0xff);
109  if(i < 3)
110  ss << ".";
111  }
112  ss << "\tPort\t" << ntohs(fromPort) << " IP " << fromIP << std::endl;
113  __COUT__ << "\n" << ss.str();
114  return -1;
115  }
116  // char address[INET_ADDRSTRLEN];
117  // inet_ntop(AF_INET, &(fromAddress.sin_addr), address, INET_ADDRSTRLEN);
118  fromIPAddress = fromAddress_.sin_addr.s_addr;
119  fromPort = fromAddress_.sin_port;
120 
121  //__COUT__ << __PRETTY_FUNCTION__ << "IP: " << std::hex << fromIPAddress <<
122  // std::dec << " port: " << fromPort << std::endl;
123  //__COUT__ << "Socket Number: " << socketNumber_ << " number of bytes: " <<
124  // nOfBytes << std::endl; gettimeofday(&tvend,NULL);
125  //__COUT__ << "started at" << tvbegin.tv_sec << ":" <<tvbegin.tv_usec <<
126  // std::endl;
127  //__COUT__ << "ended at" << tvend.tv_sec << ":" <<tvend.tv_usec << std::endl;
128 
129  // NOTE: this is inexpensive according to Lorenzo/documentation in C++11 (only
130  // increases size once and doesn't decrease size)
131  buffer.resize(numberOfBytes);
132 
133  if(verbose) // debug
134  {
135  std::string fromIP = inet_ntoa(fromAddress_.sin_addr);
136 
137  __COUT__ << "Receiving from: " << fromIP << ":" << ntohs(fromPort)
138  << " size: " << buffer.size() << std::endl;
139 
140  // std::stringstream ss;
141  // ss << "\tRx";
142  // uint32_t begin = 0;
143  // for(uint32_t i=begin; i<buffer.size(); i++)
144  // {
145  // if(i==begin+2) ss << ":::";
146  // else if(i==begin+10) ss << ":::";
147  // ss << std::setfill('0') << std::setw(2) << std::hex <<
148  //(((int16_t) buffer[i]) &0xFF) << "-" << std::dec;
149  // }
150  // ss << std::endl;
151  // std::cout << ss.str();
152  }
153  }
154  else
155  {
156  if(verbose)
157  __COUT__ << "No new messages for "
158  << timeoutSeconds + timeoutUSeconds / 1000000.
159  << "s. Read request timed out." << std::endl;
160  return -1;
161  }
162 
163  return 0;
164 } //end receive()
165 
166 //==============================================================================
167 ots_mm_udp_interface::ots_mm_udp_interface(const char* mm_ip, int mm_port) : mm_sock_(-1)
170 {
171  __COUT__ << "Constructor" << __E__;
172 
173  mm_sock_ = makeSocket(mm_ip, mm_port, mm_ai_addr); // mm_p_);
174 } //end constructor()
175 
176 //==============================================================================
177 ots_mm_udp_interface::~ots_mm_udp_interface()
178 {
179  __COUT__ << "Destructor" << __E__;
180 
181  if(mm_sock_ != -1)
182  close(mm_sock_);
183 
184  // if(mm_servinfo_)
185  // freeaddrinfo(mm_servinfo_);
186 } //end destructor()
187 
188 //=========================================================================
191 std::string extractXmlField(const std::string& xml,
192  const std::string& field,
193  uint32_t occurrence,
194  size_t after,
195  size_t* returnAfter)
196 {
197  size_t lo = after, hi;
198  for(uint32_t i = 0; i <= occurrence; ++i)
199  if((lo = xml.find("<" + field + " ", lo)) == std::string::npos)
200  return "";
201  if((hi = xml.find("'/>", lo)) == std::string::npos)
202  return "";
203 
204  lo = xml.find("value='", lo) + 7;
205 
206  if(returnAfter)
207  *returnAfter = hi + 3;
208 
209  return xml.substr(lo, hi - lo);
210 } //end extractXmlField()
211 
212 //=========================================================================
215 std::string rextractXmlField(const std::string& xml,
216  const std::string& field,
217  uint32_t occurrence,
218  size_t before)
219 {
220  size_t lo = 0, hi = before;
221  for(uint32_t i = 0; i <= occurrence; ++i)
222  if((lo = xml.rfind("<" + field + " ", hi)) == std::string::npos)
223  return "";
224  if((hi = xml.rfind("'/>", hi)) == std::string::npos)
225  return "";
226 
227  lo = xml.find("value='", lo) + 7;
228 
229  return xml.substr(lo, hi - lo);
230 } //end rextractXmlField()
231 
232 //==============================================================================
244 void getVectorFromString(const std::string& inputString,
245  std::vector<std::string>& listToReturn,
246  const std::set<char>& delimiter,
247  const std::set<char>& whitespace,
248  std::vector<char>* listOfDelimiters,
249  bool decodeURIComponents)
250 {
251  unsigned int i = 0;
252  unsigned int j = 0;
253  unsigned int c = 0;
254  std::set<char>::iterator delimeterSearchIt;
255  char lastDelimiter = 0;
256  bool isDelimiter;
257  // bool foundLeadingDelimiter = false;
258 
259  //__COUT__ << inputString << __E__;
260  //__COUTV__(inputString.length());
261 
262  // go through the full string extracting elements
263  // add each found element to set
264  for(; c < inputString.size(); ++c)
265  {
266  //__COUT__ << (char)inputString[c] << __E__;
267 
268  delimeterSearchIt = delimiter.find(inputString[c]);
269  isDelimiter = delimeterSearchIt != delimiter.end();
270 
271  //__COUT__ << (char)inputString[c] << " " << isDelimiter <<
272  //__E__;//char)lastDelimiter << __E__;
273 
274  if(whitespace.find(inputString[c]) !=
275  whitespace.end() // ignore leading white space
276  && i == j)
277  {
278  ++i;
279  ++j;
280  // if(isDelimiter)
281  // foundLeadingDelimiter = true;
282  }
283  else if(whitespace.find(inputString[c]) != whitespace.end() &&
284  i != j) // trailing white space, assume possible end of element
285  {
286  // do not change j or i
287  }
288  else if(isDelimiter) // delimiter is end of element
289  {
290  // __COUT__ << "Set element found: " << i << "-" << j << " of " << inputString.size() << __E__;
291  // inputString.substr(i,j-i) << std::endl;
292 
293  if(listOfDelimiters && listToReturn.size()) // || foundLeadingDelimiter))
294  // //accept leading delimiter
295  // (especially for case of
296  // leading negative in math
297  // parsing)
298  {
299  //__COUTV__(lastDelimiter);
300  listOfDelimiters->push_back(lastDelimiter);
301  }
302 
303  listToReturn.push_back(decodeURIComponents
305  inputString.substr(i, j - i))
306  : inputString.substr(i, j - i));
307 
308  // setup i and j for next find
309  i = c + 1;
310  j = c + 1;
311  }
312  else // part of element, so move j, not i
313  j = c + 1;
314 
315  if(isDelimiter)
316  lastDelimiter = *delimeterSearchIt;
317  //__COUTV__(lastDelimiter);
318  }
319 
320  if(1) // i != j) //last element check (for case when no concluding ' ' or delimiter)
321  {
322  //__COUT__ << "Last element found: " <<
323  // inputString.substr(i,j-i) << std::endl;
324 
325  if(listOfDelimiters && listToReturn.size()) // || foundLeadingDelimiter))
326  // //accept leading delimiter
327  // (especially for case of leading
328  // negative in math parsing)
329  {
330  //__COUTV__(lastDelimiter);
331  listOfDelimiters->push_back(lastDelimiter);
332  }
333  listToReturn.push_back(
334  decodeURIComponents
335  ? ots_mm_udp_interface::decodeURIComponent(inputString.substr(i, j - i))
336  : inputString.substr(i, j - i));
337  }
338 
339  // assert that there is one less delimiter than values
340  if(listOfDelimiters && listToReturn.size() - 1 != listOfDelimiters->size() &&
341  listToReturn.size() != listOfDelimiters->size())
342  {
343  __SS__
344  << "There is a mismatch in delimiters to entries (should be equal or one "
345  "less delimiter): "
346  << listOfDelimiters->size() << " vs " << listToReturn.size()
347  << __E__; // << "Entries: " << StringMacros::vectorToString(listToReturn) << __E__
348  //<< "Delimiters: " << StringMacros::vectorToString(*listOfDelimiters) << __E__;
349  __SS_THROW__;
350  }
351 
352 } // end getVectorFromString()
353 
354 //==============================================================================
366 std::vector<std::string> getVectorFromString(const std::string& inputString,
367  const std::set<char>& delimiter,
368  const std::set<char>& whitespace,
369  std::vector<char>* listOfDelimiters,
370  bool decodeURIComponents)
371 {
372  std::vector<std::string> listToReturn;
373 
374  getVectorFromString(inputString,
375  listToReturn,
376  delimiter,
377  whitespace,
378  listOfDelimiters,
379  decodeURIComponents);
380  return listToReturn;
381 } // end getVectorFromString()
382 
383 //==============================================================================
384 std::string vectorToString(const std::vector<std::string>& setToReturn,
385  const std::string& delimeter /*= ", "*/)
386 {
387  std::stringstream ss;
388  bool first = true;
389  for(auto& setValue : setToReturn)
390  {
391  if(first)
392  first = false;
393  else
394  ss << delimeter;
395  ss << setValue;
396  }
397  return ss.str();
398 } // end vectorToString()
399 
400 //==============================================================================
403 std::string ots_mm_udp_interface::decodeURIComponent(const std::string& data)
404 {
405  std::string decodeURIString(data.size(), 0); // init to same size
406  unsigned int j = 0;
407  for(unsigned int i = 0; i < data.size(); ++i, ++j)
408  {
409  if(data[i] == '%')
410  {
411  // high order hex nibble digit
412  if(data[i + 1] > '9') // then ABCDEF
413  decodeURIString[j] += (data[i + 1] - 55) * 16;
414  else
415  decodeURIString[j] += (data[i + 1] - 48) * 16;
416 
417  // low order hex nibble digit
418  if(data[i + 2] > '9') // then ABCDEF
419  decodeURIString[j] += (data[i + 2] - 55);
420  else
421  decodeURIString[j] += (data[i + 2] - 48);
422 
423  i += 2; // skip to next char
424  }
425  else
426  decodeURIString[j] = data[i];
427  }
428  decodeURIString.resize(j);
429  return decodeURIString;
430 } // end decodeURIComponent()
431 
432 //==============================================================================
433 std::string ots_mm_udp_interface::encodeURIComponent(const std::string& sourceStr)
434 {
435  std::string retStr = "";
436  char encodeStr[4];
437  for(const auto& c : sourceStr)
438  if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
439  retStr += c;
440  else
441  {
442  sprintf(encodeStr, "%%%2.2X", (uint8_t)c);
443  retStr += encodeStr;
444  }
445  return retStr;
446 } // end encodeURIComponent()
447 
448 //==============================================================================
449 std::string ots_mm_udp_interface::decodeHTMLEntities(const std::string& sourceStr)
450 {
451  std::string retStr = sourceStr;
452  std::vector<std::string> htmlSubstrings(
453  {"&lt;", "&gt;", "&amp;", "&quot;", "&apos;", "&#160;", "&#013;"});
454  std::vector<std::string> htmlReplaces({"<", ">", "&", "\"", "'", " ", "\n"});
455  for(size_t i = 0; i < htmlSubstrings.size(); ++i)
456  {
457  size_t index = 0;
458  while((index = retStr.find(htmlSubstrings[i], index)) != std::string::npos)
459  {
460  retStr.replace(index, htmlSubstrings[i].size(), htmlReplaces[i]);
461  index += htmlReplaces[i].size(); //advance search location
462  }
463  }
464  return retStr;
465 } // end decodeHTMLEntities()
466 
467 //==============================================================================
470 {
471  __COUT__ << "getFrontendMacroInfo()" << __E__;
472  if(fullXML_.size())
473  return fullXML_;
474 
475  int numbytes;
476 
477  std::string sendMessage = "GetFrontendMacroInfo";
478 
479  if((numbytes = sendto(mm_sock_,
480  &(sendMessage.c_str()[0]),
481  sendMessage.size(),
482  0,
483  (struct sockaddr*)&mm_ai_addr,
484  sizeof(mm_ai_addr))) == -1)
485  {
486  __SS__ << "Error on getFrontendMacroInfo() sendto!" << __E__;
487  __SS_THROW__;
488  }
489 
490  // __COUTV__(numbytes);
491 
492  // read response ///////////////////////////////////////////////////////////
493  fullXML_ = ""; //clear just in case
494  while(receive(mm_sock_,
495  buffer_,
496  0 /*timeoutSeconds*/,
497  200000 /*timeoutUSeconds*/,
498  true /*verbose*/) == 0)
499  {
500  // __COUT__ << "Appending " << buffer_.size() << " received bytes" << __E__;
501  fullXML_ += buffer_;
502  }
503 
504  if(fullXML_.size() == 0)
505  {
506  __SS__ << "FE Macro Info receive failed! Check that a MacroMaker Supervisor is "
507  "running with UDP Remote Control enabled."
508  << __E__;
509  __SS_THROW__;
510  }
511 
512  // __COUTV__(fullXML_);
513 
514  //Format: each line:
515  // <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
516  // ;<macro name>;<macro permissions req>;<macro num of inputs>;...<input names ;
517  // separated>...
518  // ;<macro num of outputs>;...<output names ; separated>...
519  // do not use :-separator because of the : in user permissions strings
520 
521  return fullXML_;
522 } //end getFrontendMacroInfo()
523 
524 //==============================================================================
527 {
528  __COUT__ << "getFrontendList()" << __E__;
529  getFrontendMacroInfo(); //init fullXML_
530 
531  //Format: each line:
532  // <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
533  // ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
534  // separated>...
535  // ;<macro num of outputs>;...<output names ; separated>...
536  // do not use :-separator because of the : in user permissions strings
537 
538  std::string value;
539  size_t after = 0;
540 
541  std::string retStr;
542  while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
543  {
544  __COUTV__(fullXML_.size());
545  __COUTV__(after);
546  // __COUTV__(value);
547 
548  std::vector<std::string> fields = getVectorFromString(value, {';'});
549 
550  // __COUTV__(vectorToString(fields));
551  if(fields.size() < 6)
552  continue;
553 
554  //0 = supervisor name
555  //1 = supervisor lid
556  //2 = type
557  //3 = FE UID
558 
559  if(retStr.size())
560  retStr += ';';
561  retStr += fields[3]; //append FE UID
562  // __COUTV__(retStr);
563  } //end primary loop
564 
565  return retStr;
566 } //end getFrontendList()
567 
568 //==============================================================================
569 std::string ots_mm_udp_interface::getCommandList(const std::string& targetFE)
570 {
571  __COUT__ << "getCommandList()" << __E__;
572  getFrontendMacroInfo(); //init fullXML_
573 
574  //Format: each line:
575  // <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
576  // ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
577  // separated>...
578  // ;<macro num of outputs>;...<output names ; separated>...
579  // do not use :-separator because of the : in user permissions strings
580 
581  std::string value;
582  size_t after = 0;
583  std::string retStr;
584  while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
585  {
586  // __COUTV__(after);
587  // __COUTV__(value);
588  std::vector<std::string> fields = getVectorFromString(value, {';'});
589 
590  if(fields.size() < 6)
591  continue;
592 
593  uint32_t i = 4; //start at first Macro Name, then...
594  //i+0 = Macro Name
595  //i+1 = Macro Permissions
596  //i+2 = Macro Tooltip
597  //i+3 = number of inputs N
598  //i+4+N = number of outputs M
599  //i+4+N+M = Macro Name...
600 
601  if(fields[3] == targetFE)
602  {
603  // __COUTV__(vectorToString(fields));
604 
605  //scan through all Macro Names
606 
607  while(fields.size() >
608  i + 5) //need type, uid, input count, output count (at least)
609  {
610  if(retStr.size())
611  retStr += ';';
612  retStr += fields[i + 0]; //append FE Macro Name
613 
614  // __COUTV__(fields[i+0]);
615  // __COUTV__(fields[i+3]);
616 
617  //now navigate to next FE UID, by jumping input and output counts
618  i += 3 + 1 + atoi(fields[i + 3].c_str());
619 
620  if(fields.size() < i)
621  {
622  __SS__ << "Illegal FE Macro info! " << i << " vs " << fields.size()
623  << __E__;
624  __SS_THROW__;
625  }
626  // __COUTV__(fields[i]);
627  i += 1 + atoi(fields[i].c_str());
628  } //primary FE UID search with fields from one FE Supervisor
629 
630  break; //found targetFE, so end search
631  }
632 
633  } //end FE search loop
634 
635  return retStr;
636 } //end getCommandList()
637 
638 //==============================================================================
639 int ots_mm_udp_interface::getCommandInputCount(const std::string& targetFE,
640  const std::string& command)
641 {
642  __COUT__ << "getCommandInputCount()" << __E__;
643  getFrontendMacroInfo(); //init fullXML_
644 
645  //Format: each line:
646  // <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
647  // ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
648  // separated>...
649  // ;<macro num of outputs>;...<output names ; separated>...
650  // do not use :-separator because of the : in user permissions strings
651 
652  std::string value;
653  size_t after = 0;
654  std::string retStr;
655  while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
656  {
657  // __COUTV__(after);
658  // __COUTV__(value);
659  std::vector<std::string> fields = getVectorFromString(value, {';'});
660 
661  if(fields.size() < 6)
662  continue;
663 
664  uint32_t i = 4; //start at first Macro Name, then...
665  //i+0 = Macro Name
666  //i+1 = Macro Permissions
667  //i+2 = Macro Tooltip
668  //i+3 = number of inputs N
669  //i+4+N = number of outputs M
670  //i+4+N+M = Macro Name...
671 
672  if(fields[3] == targetFE)
673  {
674  // __COUTV__(vectorToString(fields));
675 
676  //scan through all Macro Names
677 
678  while(fields.size() >
679  i + 5) //need type, uid, input count, output count (at least)
680  {
681  if(fields[i + 0] == command) //if found uid/command pair, return
682  {
683  return atoi(fields[i + 3].c_str()); //return input count
684  }
685  //else keep looking for macro name
686 
687  //now navigate to next FE UID, by jumping input and output counts
688  i += 3 + 1 + atoi(fields[i + 3].c_str());
689 
690  if(fields.size() < i)
691  {
692  __SS__ << "Illegal FE Macro info! " << i << " vs " << fields.size()
693  << __E__;
694  __SS_THROW__;
695  }
696  // __COUTV__(fields[i]);
697  i += 1 + atoi(fields[i].c_str());
698  } //primary FE UID search with fields from one FE Supervisor
699 
700  break; //found targetFE, so end search
701  }
702  } //end FE search loop
703 
704  __SS__ << "Count not find FE '" << targetFE << "' Command '" << command
705  << "' in FE Macro Info! Check UID and Macro name." << __E__;
706  __SS_THROW__;
707 } //end getCommandInputCount()
708 
709 //==============================================================================
710 int ots_mm_udp_interface::getCommandOutputCount(const std::string& targetFE,
711  const std::string& command)
712 {
713  __COUT__ << "getCommandOutputCount()" << __E__;
714  getFrontendMacroInfo(); //init fullXML_
715 
716  //Format: each line:
717  // <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
718  // ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
719  // separated>...
720  // ;<macro num of outputs>;...<output names ; separated>...
721  // do not use :-separator because of the : in user permissions strings
722 
723  std::string value;
724  size_t after = 0;
725  std::string retStr;
726  while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
727  {
728  // __COUTV__(after);
729  // __COUTV__(value);
730  std::vector<std::string> fields = getVectorFromString(value, {';'});
731 
732  if(fields.size() < 6)
733  continue;
734 
735  uint32_t i = 4; //start at first Macro Name, then...
736  //i+0 = Macro Name
737  //i+1 = Macro Permissions
738  //i+2 = Macro Tooltip
739  //i+3 = number of inputs N
740  //i+4+N = number of outputs M
741  //i+4+N+M = Macro Name...
742 
743  if(fields[3] == targetFE)
744  {
745  // __COUTV__(vectorToString(fields));
746 
747  //scan through all Macro Names
748 
749  while(fields.size() >
750  i + 5) //need type, uid, input count, output count (at least)
751  {
752  bool found = false;
753  if(fields[i + 0] == command) //if found uid/command pair, return
754  {
755  found = true;
756  }
757  //else keep looking for macro name
758 
759  //now navigate to next FE UID, by jumping input and output counts
760  i += 3 + 1 + atoi(fields[i + 3].c_str());
761 
762  if(fields.size() < i)
763  {
764  __SS__ << "Illegal FE Macro info! " << i << " vs " << fields.size()
765  << __E__;
766  __SS_THROW__;
767  }
768  // __COUTV__(fields[i]);
769 
770  if(found)
771  {
772  return atoi(fields[i].c_str()); //return output count
773  }
774  //else keep looking for macro name
775  i += 1 + atoi(fields[i].c_str());
776  } //primary FE UID search with fields from one FE Supervisor
777 
778  break; //found targetFE, so end search
779  }
780  } //end FE search loop
781 
782  __SS__ << "Count not find FE '" << targetFE << "' Command '" << command
783  << "' in FE Macro Info! Check UID and Macro name." << __E__;
784  __SS_THROW__;
785 } //end getCommandOutputCount()
786 
787 //==============================================================================
788 std::string ots_mm_udp_interface::getCommandInputName(const std::string& targetFE,
789  const std::string& command,
790  int inputIndex)
791 {
792  // __COUT__ << "getCommandInputName()" << __E__;
793  getFrontendMacroInfo(); //init fullXML_
794 
795  //Format: each line:
796  // <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
797  // ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
798  // separated>...
799  // ;<macro num of outputs>;...<output names ; separated>...
800  // do not use :-separator because of the : in user permissions strings
801 
802  std::string value;
803  size_t after = 0;
804  std::string retStr;
805  while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
806  {
807  // __COUTV__(after);
808  // __COUTV__(value);
809  std::vector<std::string> fields = getVectorFromString(value, {';'});
810 
811  if(fields.size() < 6)
812  continue;
813 
814  uint32_t i = 4; //start at first Macro Name, then...
815  //i+0 = Macro Name
816  //i+1 = Macro Permissions
817  //i+2 = Macro Tooltip
818  //i+3 = number of inputs N
819  //i+4+N = number of outputs M
820  //i+4+N+M = Macro Name...
821 
822  if(fields[3] == targetFE)
823  {
824  // __COUTV__(vectorToString(fields));
825 
826  //scan through all Macro Names
827 
828  while(fields.size() >
829  i + 5) //need type, uid, input count, output count (at least)
830  {
831  if(fields[i + 0] == command) //if found uid/command pair, return
832  {
833  if(i + 3 + 1 + inputIndex >= fields.size() ||
834  inputIndex >= atoi(fields[i + 3].c_str()))
835  {
836  __SS__ << "Illegal input arg index " << inputIndex << " vs "
837  << fields[i + 3] << " count" << __E__;
838  __SS_THROW__;
839  }
840  return decodeURIComponent(
841  fields[i + 3 + 1 + inputIndex]); //return input arg name
842  }
843  //else keep looking for macro name
844 
845  //now navigate to next FE UID, by jumping input and output counts
846  i += 3 + 1 + atoi(fields[i + 3].c_str());
847 
848  if(fields.size() < i)
849  {
850  __SS__ << "Illegal FE Macro info! " << i << " vs " << fields.size()
851  << __E__;
852  __SS_THROW__;
853  }
854  // __COUTV__(fields[i]);
855  i += 1 + atoi(fields[i].c_str());
856  } //primary FE UID search with fields from one FE Supervisor
857 
858  break; //found targetFE, so end search
859  }
860  } //end FE search loop
861 
862  __SS__ << "Count not find FE '" << targetFE << "' Command '" << command
863  << "' in FE Macro Info! Check UID and Macro name." << __E__;
864  __SS_THROW__;
865 } //end getCommandInputName()
866 
867 //==============================================================================
869 std::string ots_mm_udp_interface::getCommandOutputName(const std::string& targetFE,
870  const std::string& command,
871  int outputIndex)
872 {
873  // // __COUT__ << "getCommandOutputName()" << __E__;
874  // if(feCache_.find(targetFE + "|" + command) != feCache_.end())
875  // {
876  // if(feCache_.at(targetFE + "|" + command).find("outputNames-" + std::to_string(outputIndex)) != feCache_.at(targetFE + "|" + command).end())
877  // {
878  // __COUT__ << "Found direct cache" << __E__;
879  // return feCache_.at(targetFE + "|" + command).at("outputNames-" + std::to_string(outputIndex));
880  // }
881  // else if(feCache_.at(targetFE + "|" + command).find("outputNames") != feCache_.at(targetFE + "|" + command).end())
882  // {
883  // __COUT__ << "Found cache" << __E__;
884  // std::vector<std::string> outputs = getVectorFromString(
885  // feCache_.at(targetFE + "|" + command).at("outputNames"), {';'});
886  // return outputs[outputIndex];
887  // }
888  // }
889  getFrontendMacroInfo(); //init fullXML_
890 
891  //Format: each line:
892  // <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
893  // ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
894  // separated>...
895  // ;<macro num of outputs>;...<output names ; separated>...
896  // do not use :-separator because of the : in user permissions strings
897 
898  std::string value;
899  size_t after = 0;
900  std::string retStr;
901  while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
902  {
903  // __COUTV__(after);
904  // __COUTV__(value);
905  std::vector<std::string> fields = getVectorFromString(value, {';'});
906 
907  if(fields.size() < 6)
908  continue;
909 
910  uint32_t i = 4; //start at first Macro Name, then...
911  //i+0 = Macro Name
912  //i+1 = Macro Permissions
913  //i+2 = Macro Tooltip
914  //i+3 = number of inputs N
915  //i+4+N = number of outputs M
916  //i+4+N+M = Macro Name...
917 
918  if(fields[3] == targetFE)
919  {
920  // __COUTV__(vectorToString(fields));
921 
922  //scan through all Macro Names
923 
924  while(fields.size() >
925  i + 5) //need type, uid, input count, output count (at least)
926  {
927  bool found = false;
928  if(fields[i + 0] == command) //if found uid/command pair, return
929  {
930  found = true;
931  }
932  //else keep looking for macro name
933 
934  //now navigate to next FE UID, by jumping input and output counts
935  i += 3 + 1 + atoi(fields[i + 3].c_str());
936 
937  if(fields.size() < i)
938  {
939  __SS__ << "Illegal FE Macro info! " << i << " vs " << fields.size()
940  << __E__;
941  __SS_THROW__;
942  }
943  // __COUTV__(fields[i]);
944 
945  if(found)
946  {
947  if(i + 1 + outputIndex >= fields.size() ||
948  outputIndex >= atoi(fields[i].c_str()))
949  {
950  __SS__ << "Illegal output arg index " << outputIndex << " vs "
951  << fields[i] << " count" << __E__;
952  __SS_THROW__;
953  }
954 
955  // feCache_[targetFE + "|" + command]["outputNames-" + std::to_string(outputIndex)] = decodeURIComponent(fields[i+1+outputIndex]);
956  // return feCache_.at(targetFE + "|" + command).at("outputNames-" + std::to_string(outputIndex)); //return output arg name
957  return decodeURIComponent(fields[i + 1 + outputIndex]);
958  }
959  //else keep looking for macro name
960  i += 1 + atoi(fields[i].c_str());
961  } //primary FE UID search with fields from one FE Supervisor
962 
963  break; //found targetFE, so end search
964  }
965  } //end FE search loop
966 
967  __SS__ << "Count not find FE '" << targetFE << "' Command '" << command
968  << "' in FE Macro Info! Check UID and Macro name." << __E__;
969  __SS_THROW__;
970 } //end getCommandOutputName()
971 
972 //==============================================================================
975 std::string ots_mm_udp_interface::runCommand(const std::string& targetFE,
976  const std::string& command,
977  const std::string& inputs)
978 {
979  __COUT__ << "On '" << targetFE << "' runCommand: " << command << __E__;
980 
981  __COUTV__(inputs);
982 
983  //extract FE Type from FE Macro info...
984  getFrontendMacroInfo(); //init fullXML_
985  std::string feType;
986 
987  //Format: each line:
988  // <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
989  // ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
990  // separated>...
991  // ;<macro num of outputs>;...<output names ; separated>...
992  // do not use :-separator because of the : in user permissions strings
993 
994  std::string value;
995  size_t after = 0;
996 
997  std::string retStr;
998  while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
999  {
1000  __COUTV__(fullXML_.size());
1001  __COUTV__(after);
1002  // __COUTV__(value);
1003 
1004  std::vector<std::string> fields = getVectorFromString(value, {';'});
1005 
1006  // __COUTV__(vectorToString(fields));
1007  if(fields.size() < 6)
1008  continue;
1009 
1010  //0 = supervisor name
1011  //1 = supervisor lid
1012  //2 = type
1013  //3 = FE UID
1014 
1015  if(fields[3] == targetFE)
1016  {
1017  feType = fields[2];
1018  __COUTV__(feType);
1019  break;
1020  }
1021  } //end primary loop
1022 
1023  if(feType.size() == 0)
1024  {
1025  __SS__ << "Could not find front-end type for FE UID '" << targetFE << __E__;
1026  __SS_THROW__;
1027  }
1028 
1029  int numbytes;
1030 
1031  std::string sendMessage = "RunFrontendMacro";
1032  //create ;-separated arguments to satisfy this:
1033  // std::string feClassSelected = bufferFields[1];
1034  // std::string feUIDSelected = bufferFields[2]; // allow CSV multi-selection
1035  // std::string macroType = bufferFields[3]; // "fe", "public", "private"
1036  // std::string macroName = bufferFields[4];
1037  // std::string inputArgs = StringMacros::decodeURIComponent(bufferFields[5]); //two level ;- and ,- separated
1038  // std::string outputArgs = StringMacros::decodeURIComponent(bufferFields[6]); // ,- separated
1039  // bool saveOutputs = bufferFields[7] == "1";
1040  sendMessage += ";" + feType; // std::string feClassSelected = bufferFields[1];
1041  sendMessage +=
1042  ";" +
1043  targetFE; // std::string feUIDSelected = bufferFields[2]; // allow CSV multi-selection
1044  sendMessage +=
1045  ";" +
1046  std::string(
1047  "fe"); // std::string macroType = bufferFields[3]; // "fe", "public", "private"
1048  sendMessage +=
1049  ";" + encodeURIComponent(command); // std::string macroName = bufferFields[4];
1050 
1051  //handle inputs
1052  {
1053  std::string inputStr;
1054  uint32_t numberOfInputs = getCommandInputCount(targetFE, command);
1055 
1056  std::vector<std::string> inputVec = getVectorFromString(inputs, {';'});
1057  uint32_t countOfNonEmptyInputs = 0;
1058  for(uint32_t i = 0; i < inputVec.size(); ++i)
1059  {
1060  if(inputVec[i] == "")
1061  continue; //skip empty inputs
1062  if(countOfNonEmptyInputs)
1063  inputStr += ";";
1064  inputStr += encodeURIComponent(getCommandInputName(
1065  targetFE, command, countOfNonEmptyInputs)) +
1066  "," + inputVec[i]; //already encoded (hopefully)
1067 
1068  ++countOfNonEmptyInputs;
1069  }
1070  if(numberOfInputs != countOfNonEmptyInputs)
1071  {
1072  __SS__ << "Input argument mismatch: " << countOfNonEmptyInputs << " vs "
1073  << numberOfInputs << " expected." << __E__;
1074  __SS_THROW__;
1075  }
1076  __COUTV__(inputStr);
1077  sendMessage += ";" + encodeURIComponent(inputStr); //double encoded
1078  }
1079 
1080  //handle outputs, which are ,- separated
1081  uint32_t numberOfOutputs = getCommandOutputCount(targetFE, command);
1082  {
1083  std::string outputStr;
1084 
1085  for(uint32_t i = 0; i < numberOfOutputs; ++i)
1086  {
1087  if(i)
1088  outputStr += ",";
1089  outputStr += encodeURIComponent(getCommandOutputName(targetFE, command, i));
1090  }
1091 
1092  sendMessage += ";" + encodeURIComponent(outputStr); //double encoded
1093  }
1094 
1095  sendMessage +=
1096  ";" + std::string("0"); // bool saveOutputs = bufferFields[7] == "1";
1097 
1098  __COUTV__(sendMessage.size());
1099  __COUTV__(sendMessage);
1100 
1101  if((numbytes = sendto(mm_sock_,
1102  &(sendMessage.c_str()[0]),
1103  sendMessage.size(),
1104  0,
1105  (struct sockaddr*)&mm_ai_addr,
1106  sizeof(mm_ai_addr))) == -1)
1107  {
1108  __SS__ << "Error on runCommand() sendto! Error: " << strerror(errno) << __E__;
1109 
1110  { //check for more error info
1111  int error = 0;
1112  socklen_t len = sizeof(error);
1113  int retval = getsockopt(mm_sock_, SOL_SOCKET, SO_ERROR, &error, &len);
1114  if(retval != 0 || error != 0)
1115  {
1116  ss << "Socket is closed or in error state: " << strerror(error)
1117  << std::endl;
1118  }
1119  }
1120 
1121  close(mm_sock_);
1122  mm_sock_ = -1;
1123 
1124  __SS_THROW__;
1125  }
1126 
1127  // read response ///////////////////////////////////////////////////////////
1128  std::string runXML = ""; //clear just in case
1129  //give first response a long time for execution propagation to/from device, but following receives should be short because just from UDP packet splitting
1130  if(receive(mm_sock_,
1131  buffer_,
1132  10 /*timeoutSeconds*/,
1133  0 /*timeoutUSeconds*/,
1134  true /*verbose*/) == 0)
1135  {
1136  // __COUT__ << "Appending " << buffer_.size() << " received bytes" << __E__;
1137  runXML += buffer_;
1138  while(receive(mm_sock_,
1139  buffer_,
1140  0 /*timeoutSeconds*/,
1141  200000 /*timeoutUSeconds*/,
1142  true /*verbose*/) == 0)
1143  runXML += buffer_;
1144  }
1145 
1146  __COUTV__(runXML);
1147 
1148  if(runXML.size() == 0 || runXML.find("Error") == 0)
1149  {
1150  __SS__ << "Error running the command. Received buffer: "
1151  << (runXML.size() == 0 ? "<empty>" : runXML) << __E__;
1152  __SS_THROW__;
1153  }
1154 
1155  //example response:
1156  // <ROOT>
1157  // <DATA>
1158  // <feMacroExec value='Check Firefly Loss-of-Light'>
1159  // <exec_time value='Fri Aug 16 18:59:15 2024 CDT'/>
1160  // <fe_uid value='CFO0'/>
1161  // <fe_type value='CFOFrontEndInterface'/>
1162  // <fe_context value='ots::FESupervisor'/>
1163  // <fe_supervisor value='LID-280'/>
1164  // <fe_hostname value='mu2e-calo-03.fnal.gov'/>
1165  // <outputArgs_name value='Link Status'/>
1166  // <outputArgs_value value='%7B0%3ADEAD%2C%201%3A%20DEAD%2C%202%3ADEAD%2C%203%3ADEAD%2C%204%3A%20DEAD%2C%205%3A%20DEAD%2C%206%2FCFO%3A%20OK%2C%207%2FEVB%3A%20DEAD%7D'/>
1167  // </feMacroExec>
1168  // </DATA>
1169  // </ROOT>
1170 
1171  //verify outputs
1172  {
1173  std::string outputStr;
1174 
1175  size_t after = 0;
1176  std::string error = extractXmlField(runXML, "Error", 0, after, &after);
1177 
1178  if(error != "")
1179  {
1180  //error has html entities in it
1181  error = decodeHTMLEntities(error);
1182  __SS__ << "Error message received after command execution attempt: \n"
1183  << error << __E__;
1184  __SS_THROW__;
1185  }
1186 
1187  for(uint32_t i = 0; i < numberOfOutputs; ++i)
1188  {
1189  std::string expectedOutputName = getCommandOutputName(targetFE, command, i);
1190 
1191  std::string outputName =
1192  extractXmlField(runXML, "outputArgs_name", 0, after, &after);
1193  std::string outputValue =
1194  extractXmlField(runXML, "outputArgs_value", 0, after, &after);
1195  __COUT_INFO__ << "Command Result output #" << i << ": " << outputName << " = "
1196  << decodeURIComponent(outputValue) << __E__;
1197 
1198  if(i)
1199  outputStr += ';';
1200  outputStr += outputValue;
1201  }
1202 
1203  return outputStr;
1204  }
1205 
1206 } //end runCommand()
std::string getFrontendList(void)
returns CSV list of Front-end interface UIDs
ots_mm_udp_interface(const char *mm_ip, int mm_port)
static std::string decodeURIComponent(const std::string &data)
std::string runCommand(const std::string &targetFE, const std::string &command, const std::string &inputs)
const std::string & getFrontendMacroInfo(void)
returns CSV list of Front-end interface UIDs
std::string getCommandOutputName(const std::string &targetFE, const std::string &command, int outputIndex)
Note: if std::map does not complicate interface too much for ROOT/pyton, could make this const std::s...