LCOV - code coverage report
Current view: top level - otsdaq-utilities/test - ots_mm_udp_interface.cpp (source / functions) Coverage Total Hit
Test: mu2edaq.info.cleaned Lines: 0.0 % 1208 0
Test Date: 2026-10-29 01:38:59 Functions: 0.0 % 42 0

            Line data    Source code
       1              : 
       2              : #include "test/ots_mm_udp_interface.h"
       3              : 
       4              : #include <chrono>
       5              : #include <iomanip>
       6              : #include <limits>
       7              : 
       8              : #define MAXBUFLEN 5000
       9              : 
      10              : //==============================================================================
      11            0 : bool hasCompleteXmlRoot(const std::string& xml)
      12              : {
      13            0 :         return xml.size() >= 10 &&
      14            0 :                xml.substr(xml.size() - 10).find("</ROOT>") != std::string::npos;
      15              : }  //end hasCompleteXmlRoot()
      16              : 
      17              : //==============================================================================
      18              : /// get sockaddr, IPv4 or IPv6:
      19            0 : void* get_in_addr(struct sockaddr* sa)
      20              : {
      21            0 :         if(sa->sa_family == AF_INET)
      22              :         {
      23            0 :                 return &(((struct sockaddr_in*)sa)->sin_addr);
      24              :         }
      25              : 
      26            0 :         return &(((struct sockaddr_in6*)sa)->sin6_addr);
      27              : }
      28              : 
      29              : //==============================================================================
      30            0 : int makeSocket(const char* ip, int port, struct sockaddr_in& mm_ai_addr)
      31              : {
      32              :         int             sockfd;
      33              :         struct addrinfo hints, *servinfo, *p;
      34              :         int             rv;
      35              :         // int                     numbytes;
      36              :         // struct sockaddr_storage their_addr;
      37              :         // socklen_t               addr_len;
      38              :         // char                    s[INET6_ADDRSTRLEN];
      39              : 
      40            0 :         memset(&hints, 0, sizeof hints);
      41            0 :         hints.ai_family   = AF_UNSPEC;
      42            0 :         hints.ai_socktype = SOCK_DGRAM;
      43              : 
      44            0 :         if((rv = getaddrinfo(ip, std::to_string(port).c_str(), &hints, &servinfo)) != 0)
      45              :         {
      46            0 :                 __SS__ << "getaddrinfo: " << gai_strerror(rv) << __E__;
      47            0 :                 __SS_THROW__;
      48            0 :         }
      49              : 
      50              :         // loop through all the results and make a socket
      51            0 :         for(p = servinfo; p != NULL; p = p->ai_next)
      52              :         {
      53            0 :                 if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
      54              :                 {
      55            0 :                         __COUT_WARN__ << "sw: socket failed, trying other address..." << __E__;
      56            0 :                         continue;
      57              :                 }
      58              : 
      59            0 :                 break;
      60              :         }
      61              : 
      62            0 :         if(p == NULL)
      63              :         {
      64            0 :                 __SS__ << "sw: failed to create socket" << __E__;
      65            0 :                 __SS_THROW__;
      66            0 :         }
      67              : 
      68              :         //copy ai_addr, which is needed for sendto
      69            0 :         memcpy(&mm_ai_addr, p->ai_addr, sizeof(mm_ai_addr));
      70              : 
      71            0 :         freeaddrinfo(servinfo);
      72              : 
      73              :         // increase socket buffer size
      74            0 :         unsigned int socketReceiveBufferSize = 0x1400000;
      75            0 :         if(setsockopt(sockfd,
      76              :                       SOL_SOCKET,
      77              :                       SO_RCVBUF,
      78              :                       (char*)&socketReceiveBufferSize,
      79            0 :                       sizeof(socketReceiveBufferSize)) < 0)
      80            0 :                 __COUT_ERR__ << "Failed to set socket receive size to 0x" << std::hex
      81            0 :                              << socketReceiveBufferSize << std::dec << "." << std::endl;
      82              :         else
      83              :                 __COUT__ << "set socket receive size to 0x" << std::hex << socketReceiveBufferSize
      84              :                          << std::dec << "." << __E__;
      85              : 
      86            0 :         return sockfd;
      87              : }  //end makeSocket()
      88              : 
      89              : //==============================================================================
      90              : /// receive ~~
      91              : ///     returns numberOfBytes on success, -1 on failure
      92              : fd_set             fileDescriptor_;
      93              : struct timeval     timeout_;
      94              : struct sockaddr_in fromAddress_;
      95              : socklen_t          addressLength_ = sizeof(fromAddress_);
      96            0 : int                receive(int          sockfd,
      97              :                            std::string& buffer,
      98              :                            unsigned int timeoutSeconds,
      99              :                            unsigned int timeoutUSeconds,
     100              :                            bool         verbose)
     101              : {
     102              :         unsigned long  fromIPAddress;
     103              :         unsigned short fromPort;
     104              : 
     105              :         // set timeout period for select()
     106            0 :         timeout_.tv_sec  = timeoutSeconds;
     107            0 :         timeout_.tv_usec = timeoutUSeconds;
     108              : 
     109            0 :         FD_ZERO(&fileDescriptor_);
     110            0 :         FD_SET(sockfd, &fileDescriptor_);
     111            0 :         select(sockfd + 1, &fileDescriptor_, 0, 0, &timeout_);
     112              : 
     113              :         int numberOfBytes;
     114            0 :         if(FD_ISSET(sockfd, &fileDescriptor_))
     115              :         {
     116            0 :                 buffer.resize(MAXBUFLEN);  // NOTE: this is inexpensive according to
     117              :                                            // Lorenzo/documentation in C++11 (only increases
     118              :                                            // size once and doesn't decrease size)
     119            0 :                 if((numberOfBytes = recvfrom(sockfd,
     120            0 :                                              &buffer[0],
     121              :                                              MAXBUFLEN,
     122              :                                              0,
     123              :                                              (struct sockaddr*)&fromAddress_,
     124            0 :                                              &addressLength_)) == -1)
     125              :                 {
     126            0 :                         __SS__ << "Error reading buffer from\tIP:\t";
     127            0 :                         std::string fromIP = inet_ntoa(fromAddress_.sin_addr);
     128            0 :                         fromIPAddress      = fromAddress_.sin_addr.s_addr;
     129            0 :                         fromPort           = fromAddress_.sin_port;
     130              : 
     131            0 :                         for(int i = 0; i < 4; i++)
     132              :                         {
     133            0 :                                 ss << ((fromIPAddress << (i * 8)) & 0xff);
     134            0 :                                 if(i < 3)
     135            0 :                                         ss << ".";
     136              :                         }
     137            0 :                         ss << "\tPort\t" << ntohs(fromPort) << " IP " << fromIP << std::endl;
     138              :                         __COUT__ << "\n" << ss.str();
     139            0 :                         return -1;
     140            0 :                 }
     141              :                 // char address[INET_ADDRSTRLEN];
     142              :                 // inet_ntop(AF_INET, &(fromAddress.sin_addr), address, INET_ADDRSTRLEN);
     143            0 :                 fromIPAddress = fromAddress_.sin_addr.s_addr;
     144            0 :                 fromPort      = fromAddress_.sin_port;
     145              : 
     146              :                 //__COUT__ << __PRETTY_FUNCTION__ << "IP: " << std::hex << fromIPAddress <<
     147              :                 // std::dec << " port: " << fromPort << std::endl;
     148              :                 //__COUT__ << "Socket Number: " << socketNumber_ << " number of bytes: " <<
     149              :                 // nOfBytes << std::endl;  gettimeofday(&tvend,NULL);
     150              :                 //__COUT__ << "started at" << tvbegin.tv_sec << ":" <<tvbegin.tv_usec <<
     151              :                 // std::endl;
     152              :                 //__COUT__ << "ended at" << tvend.tv_sec << ":" <<tvend.tv_usec << std::endl;
     153              : 
     154              :                 // NOTE: this is inexpensive according to Lorenzo/documentation in C++11 (only
     155              :                 // increases size once and doesn't decrease size)
     156            0 :                 buffer.resize(numberOfBytes);
     157              : 
     158            0 :                 if(verbose)  // debug
     159              :                 {
     160            0 :                         std::string fromIP = inet_ntoa(fromAddress_.sin_addr);
     161              : 
     162              :                         __COUT__ << "Receiving from: " << fromIP << ":" << ntohs(fromPort)
     163              :                                  << " size: " << buffer.size() << std::endl;
     164              : 
     165              :                         //                      std::stringstream ss;
     166              :                         //                      ss << "\tRx";
     167              :                         //                      uint32_t begin = 0;
     168              :                         //                      for(uint32_t i=begin; i<buffer.size(); i++)
     169              :                         //                      {
     170              :                         //                              if(i==begin+2) ss << ":::";
     171              :                         //                              else if(i==begin+10) ss << ":::";
     172              :                         //                              ss << std::setfill('0') << std::setw(2) << std::hex <<
     173              :                         //(((int16_t)  buffer[i]) &0xFF) << "-" << std::dec;
     174              :                         //                      }
     175              :                         //                      ss << std::endl;
     176              :                         //                      std::cout << ss.str();
     177            0 :                 }
     178              :         }
     179              :         else
     180              :         {
     181              :                 if(verbose)
     182              :                         __COUT__ << "No new messages for "
     183              :                                  << timeoutSeconds + timeoutUSeconds / 1000000.
     184              :                                  << "s. Read request timed out." << std::endl;
     185            0 :                 return -1;
     186              :         }
     187              : 
     188            0 :         return 0;
     189              : }  //end receive()
     190              : 
     191              : //==============================================================================
     192            0 : ots_mm_udp_interface::ots_mm_udp_interface(const char* mm_ip, int mm_port) : mm_sock_(-1)
     193              : /// , mm_p_(nullptr)
     194              : /// , mm_servinfo_(nullptr)
     195              : {
     196              :         __COUT__ << "Constructor" << __E__;
     197              : 
     198            0 :         mm_sock_ = makeSocket(mm_ip, mm_port, mm_ai_addr);  // mm_p_);
     199              : 
     200            0 :         selfIPandPort_ = mm_ip;
     201            0 :         selfIPandPort_ += ":";
     202            0 :         selfIPandPort_ += std::to_string(mm_port);
     203            0 : }  //end constructor()
     204              : 
     205              : //==============================================================================
     206            0 : ots_mm_udp_interface::~ots_mm_udp_interface()
     207              : {
     208              :         __COUT__ << "Destructor" << __E__;
     209              : 
     210            0 :         if(mm_sock_ != -1)
     211            0 :                 close(mm_sock_);
     212              : 
     213              :         // if(mm_servinfo_)
     214              :         //      freeaddrinfo(mm_servinfo_);
     215            0 : }  //end destructor()
     216              : 
     217              : //==============================================================================
     218            0 : void ots_mm_udp_interface::receiveXmlResponse(std::string&       response,
     219              :                                               const std::string& waitDescription,
     220              :                                               int                inactivityTimeoutSeconds)
     221              : {
     222            0 :         response.clear();
     223            0 :         auto    startTime           = std::chrono::steady_clock::now();
     224            0 :         auto    lastUpdateTime      = std::chrono::steady_clock::now();
     225            0 :         int64_t lastWarnElapsedTime = 0;
     226              : 
     227              :         while(true)
     228              :         {
     229            0 :                 if(receive(mm_sock_,
     230            0 :                            buffer_,
     231              :                            0 /*timeoutSeconds*/,
     232              :                            200000 /*timeoutUSeconds*/,
     233            0 :                            false /*verbose*/) == 0)
     234              :                 {
     235            0 :                         lastUpdateTime = std::chrono::steady_clock::now();
     236            0 :                         if(buffer_.find("<progress>") == 0)
     237              :                         {
     238            0 :                                 __COUT_INFO__ << "Progress update: " << buffer_ << __E__;
     239            0 :                                 continue;
     240              :                         }
     241              : 
     242            0 :                         response += buffer_;
     243              :                         // __COUT_INFO__ << "Received: " << buffer_ << __E__;
     244            0 :                         if(hasCompleteXmlRoot(response))
     245            0 :                                 break;
     246              :                 }
     247              : 
     248            0 :                 auto currentTime = std::chrono::steady_clock::now();
     249              :                 auto elapsed =
     250            0 :                     std::chrono::duration_cast<std::chrono::seconds>(currentTime - startTime);
     251            0 :                 if(lastWarnElapsedTime != elapsed.count() && elapsed.count() > 2 &&
     252            0 :                    elapsed.count() % 3 == 0)
     253              :                 {
     254            0 :                         __COUT_WARN__ << "Still waiting for " << waitDescription << " after "
     255            0 :                                       << elapsed.count() << " seconds... " << response.size()
     256            0 :                                       << " bytes received so far." << __E__;
     257            0 :                         lastWarnElapsedTime = elapsed.count();
     258              :                 }
     259              : 
     260            0 :                 elapsed = std::chrono::duration_cast<std::chrono::seconds>(currentTime -
     261            0 :                                                                            lastUpdateTime);
     262            0 :                 if(elapsed.count() > inactivityTimeoutSeconds)
     263            0 :                         break;
     264            0 :         }
     265            0 : }  //end receiveXmlResponse()
     266              : 
     267              : //=========================================================================
     268              : ///extract value for field from xml looking forwards from after
     269              : /// occurence = 0 is first occurence
     270            0 : std::string extractXmlField(const std::string& xml,
     271              :                             const std::string& field,
     272              :                             uint32_t           occurrence,
     273              :                             size_t             after,
     274              :                             size_t*            returnAfter)
     275              : {
     276            0 :         size_t lo = after, hi;
     277            0 :         for(uint32_t i = 0; i <= occurrence; ++i)
     278            0 :                 if((lo = xml.find("<" + field + " ", lo)) == std::string::npos)
     279            0 :                         return "";
     280            0 :         if((hi = xml.find("'/>", lo)) == std::string::npos)
     281            0 :                 return "";
     282              : 
     283            0 :         lo = xml.find("value='", lo) + 7;
     284              : 
     285            0 :         if(returnAfter)
     286            0 :                 *returnAfter = hi + 3;
     287              : 
     288            0 :         return xml.substr(lo, hi - lo);
     289              : }  //end extractXmlField()
     290              : 
     291              : //=========================================================================
     292              : ///extract value for field from xml looking backwards from before
     293              : /// occurence = 0 is first occurence
     294            0 : std::string rextractXmlField(const std::string& xml,
     295              :                              const std::string& field,
     296              :                              uint32_t           occurrence,
     297              :                              size_t             before)
     298              : {
     299            0 :         size_t lo = 0, hi = before;
     300            0 :         for(uint32_t i = 0; i <= occurrence; ++i)
     301            0 :                 if((lo = xml.rfind("<" + field + " ", hi)) == std::string::npos)
     302            0 :                         return "";
     303            0 :         if((hi = xml.rfind("'/>", hi)) == std::string::npos)
     304            0 :                 return "";
     305              : 
     306            0 :         lo = xml.find("value='", lo) + 7;
     307              : 
     308            0 :         return xml.substr(lo, hi - lo);
     309              : }  //end rextractXmlField()
     310              : 
     311              : //==============================================================================
     312              : /// getVectorFromString
     313              : ///     extracts the list of elements from string that uses a delimiter
     314              : ///             ignoring whitespace
     315              : ///     optionally returns the list of delimiters encountered, which may be useful
     316              : ///             for extracting which operator was used.
     317              : ///
     318              : ///
     319              : ///     Note: lists are returned as vectors
     320              : ///     Note: the size() of delimiters will be one less than the size() of the returned values
     321              : ///             unless there is a leading delimiter, in which case vectors will have the same
     322              : /// size.
     323            0 : void getVectorFromString(const std::string&        inputString,
     324              :                          std::vector<std::string>& listToReturn,
     325              :                          const std::set<char>&     delimiter,
     326              :                          const std::set<char>&     whitespace,
     327              :                          std::vector<char>*        listOfDelimiters,
     328              :                          bool                      decodeURIComponents)
     329              : {
     330            0 :         unsigned int             i = 0;
     331            0 :         unsigned int             j = 0;
     332            0 :         unsigned int             c = 0;
     333            0 :         std::set<char>::iterator delimeterSearchIt;
     334            0 :         char                     lastDelimiter = 0;
     335              :         bool                     isDelimiter;
     336              :         // bool foundLeadingDelimiter = false;
     337              : 
     338              :         //__COUT__ << inputString << __E__;
     339              :         //__COUTV__(inputString.length());
     340              : 
     341              :         // go through the full string extracting elements
     342              :         // add each found element to set
     343            0 :         for(; c < inputString.size(); ++c)
     344              :         {
     345              :                 //__COUT__ << (char)inputString[c] << __E__;
     346              : 
     347            0 :                 delimeterSearchIt = delimiter.find(inputString[c]);
     348            0 :                 isDelimiter       = delimeterSearchIt != delimiter.end();
     349              : 
     350              :                 //__COUT__ << (char)inputString[c] << " " << isDelimiter <<
     351              :                 //__E__;//char)lastDelimiter << __E__;
     352              : 
     353            0 :                 if(whitespace.find(inputString[c]) !=
     354            0 :                        whitespace.end()  // ignore leading white space
     355            0 :                    && i == j)
     356              :                 {
     357            0 :                         ++i;
     358            0 :                         ++j;
     359              :                         // if(isDelimiter)
     360              :                         //      foundLeadingDelimiter = true;
     361              :                 }
     362            0 :                 else if(whitespace.find(inputString[c]) != whitespace.end() &&
     363              :                         i != j)  // trailing white space, assume possible end of element
     364              :                 {
     365              :                         // do not change j or i
     366              :                 }
     367            0 :                 else if(isDelimiter)  // delimiter is end of element
     368              :                 {
     369              :                         // __COUT__ << "Set element found: " << i << "-" << j << " of " << inputString.size() << __E__;
     370              :                         //              inputString.substr(i,j-i) << std::endl;
     371              : 
     372            0 :                         if(listOfDelimiters && listToReturn.size())  // || foundLeadingDelimiter))
     373              :                                                                      // //accept leading delimiter
     374              :                                                                      // (especially for case of
     375              :                                                                      // leading negative in math
     376              :                                                                      // parsing)
     377              :                         {
     378              :                                 //__COUTV__(lastDelimiter);
     379            0 :                                 listOfDelimiters->push_back(lastDelimiter);
     380              :                         }
     381              : 
     382            0 :                         listToReturn.push_back(decodeURIComponents
     383            0 :                                                    ? ots_mm_udp_interface::decodeURIComponent(
     384            0 :                                                          inputString.substr(i, j - i))
     385            0 :                                                    : inputString.substr(i, j - i));
     386              : 
     387              :                         // setup i and j for next find
     388            0 :                         i = c + 1;
     389            0 :                         j = c + 1;
     390              :                 }
     391              :                 else  // part of element, so move j, not i
     392            0 :                         j = c + 1;
     393              : 
     394            0 :                 if(isDelimiter)
     395            0 :                         lastDelimiter = *delimeterSearchIt;
     396              :                 //__COUTV__(lastDelimiter);
     397              :         }
     398              : 
     399              :         if(1)  // i != j) //last element check (for case when no concluding ' ' or delimiter)
     400              :         {
     401              :                 //__COUT__ << "Last element found: " <<
     402              :                 //              inputString.substr(i,j-i) << std::endl;
     403              : 
     404            0 :                 if(listOfDelimiters && listToReturn.size())  // || foundLeadingDelimiter))
     405              :                                                              // //accept leading delimiter
     406              :                                                              // (especially for case of leading
     407              :                                                              // negative in math parsing)
     408              :                 {
     409              :                         //__COUTV__(lastDelimiter);
     410            0 :                         listOfDelimiters->push_back(lastDelimiter);
     411              :                 }
     412            0 :                 listToReturn.push_back(
     413              :                     decodeURIComponents
     414            0 :                         ? ots_mm_udp_interface::decodeURIComponent(inputString.substr(i, j - i))
     415            0 :                         : inputString.substr(i, j - i));
     416              :         }
     417              : 
     418              :         // assert that there is one less delimiter than values
     419            0 :         if(listOfDelimiters && listToReturn.size() - 1 != listOfDelimiters->size() &&
     420            0 :            listToReturn.size() != listOfDelimiters->size())
     421              :         {
     422            0 :                 __SS__
     423              :                     << "There is a mismatch in delimiters to entries (should be equal or one "
     424            0 :                        "less delimiter): "
     425            0 :                     << listOfDelimiters->size() << " vs " << listToReturn.size()
     426            0 :                     << __E__;  // << "Entries: " << StringMacros::vectorToString(listToReturn) << __E__
     427              :                 //<< "Delimiters: " << StringMacros::vectorToString(*listOfDelimiters) << __E__;
     428            0 :                 __SS_THROW__;
     429            0 :         }
     430              : 
     431            0 : }  // end getVectorFromString()
     432              : 
     433              : //==============================================================================
     434              : /// getVectorFromString
     435              : ///     extracts the list of elements from string that uses a delimiter
     436              : ///             ignoring whitespace
     437              : ///     optionally returns the list of delimiters encountered, which may be useful
     438              : ///             for extracting which operator was used.
     439              : ///
     440              : ///
     441              : ///     Note: lists are returned as vectors
     442              : ///     Note: the size() of delimiters will be one less than the size() of the returned values
     443              : ///             unless there is a leading delimiter, in which case vectors will have the same
     444              : /// size.
     445            0 : std::vector<std::string> getVectorFromString(const std::string&    inputString,
     446              :                                              const std::set<char>& delimiter,
     447              :                                              const std::set<char>& whitespace,
     448              :                                              std::vector<char>*    listOfDelimiters,
     449              :                                              bool                  decodeURIComponents)
     450              : {
     451            0 :         std::vector<std::string> listToReturn;
     452              : 
     453            0 :         getVectorFromString(inputString,
     454              :                             listToReturn,
     455              :                             delimiter,
     456              :                             whitespace,
     457              :                             listOfDelimiters,
     458              :                             decodeURIComponents);
     459            0 :         return listToReturn;
     460            0 : }  // end getVectorFromString()
     461              : 
     462              : //==============================================================================
     463            0 : std::string vectorToString(const std::vector<std::string>& setToReturn,
     464              :                            const std::string&              delimeter /*= ", "*/)
     465              : {
     466            0 :         std::stringstream ss;
     467            0 :         bool              first = true;
     468            0 :         for(auto& setValue : setToReturn)
     469              :         {
     470            0 :                 if(first)
     471            0 :                         first = false;
     472              :                 else
     473            0 :                         ss << delimeter;
     474            0 :                 ss << setValue;
     475              :         }
     476            0 :         return ss.str();
     477            0 : }  // end vectorToString()
     478              : 
     479              : //==============================================================================
     480              : /// decodeURIComponent
     481              : ///     converts all %## to the ascii character
     482            0 : std::string ots_mm_udp_interface::decodeURIComponent(const std::string& data)
     483              : {
     484            0 :         std::string  decodeURIString(data.size(), 0);  // init to same size
     485            0 :         unsigned int j = 0;
     486            0 :         for(unsigned int i = 0; i < data.size(); ++i, ++j)
     487              :         {
     488            0 :                 if(data[i] == '%')
     489              :                 {
     490              :                         // high order hex nibble digit
     491            0 :                         if(data[i + 1] > '9')  // then ABCDEF
     492            0 :                                 decodeURIString[j] += (data[i + 1] - 55) * 16;
     493              :                         else
     494            0 :                                 decodeURIString[j] += (data[i + 1] - 48) * 16;
     495              : 
     496              :                         // low order hex nibble digit
     497            0 :                         if(data[i + 2] > '9')  // then ABCDEF
     498            0 :                                 decodeURIString[j] += (data[i + 2] - 55);
     499              :                         else
     500            0 :                                 decodeURIString[j] += (data[i + 2] - 48);
     501              : 
     502            0 :                         i += 2;  // skip to next char
     503              :                 }
     504              :                 else
     505            0 :                         decodeURIString[j] = data[i];
     506              :         }
     507            0 :         decodeURIString.resize(j);
     508            0 :         return decodeURIString;
     509            0 : }  // end decodeURIComponent()
     510              : 
     511              : //==============================================================================
     512            0 : std::string ots_mm_udp_interface::encodeURIComponent(const std::string& sourceStr)
     513              : {
     514            0 :         std::string retStr = "";
     515              :         char        encodeStr[4];
     516            0 :         for(const auto& c : sourceStr)
     517            0 :                 if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
     518            0 :                         retStr += c;
     519              :                 else
     520              :                 {
     521            0 :                         sprintf(encodeStr, "%%%2.2X", (uint8_t)c);
     522            0 :                         retStr += encodeStr;
     523              :                 }
     524            0 :         return retStr;
     525            0 : }  // end encodeURIComponent()
     526              : 
     527              : //==============================================================================
     528            0 : std::string ots_mm_udp_interface::decodeHTMLEntities(const std::string& sourceStr)
     529              : {
     530            0 :         std::string              retStr = sourceStr;
     531              :         std::vector<std::string> htmlSubstrings(
     532            0 :             {"&lt;", "&gt;", "&amp;", "&quot;", "&apos;", "&#160;", "&#013;"});
     533            0 :         std::vector<std::string> htmlReplaces({"<", ">", "&", "\"", "'", " ", "\n"});
     534            0 :         for(size_t i = 0; i < htmlSubstrings.size(); ++i)
     535              :         {
     536            0 :                 size_t index = 0;
     537            0 :                 while((index = retStr.find(htmlSubstrings[i], index)) != std::string::npos)
     538              :                 {
     539            0 :                         retStr.replace(index, htmlSubstrings[i].size(), htmlReplaces[i]);
     540            0 :                         index += htmlReplaces[i].size();  //advance search location
     541              :                 }
     542              :         }
     543            0 :         return retStr;
     544            0 : }  // end decodeHTMLEntities()
     545              : 
     546              : //==============================================================================
     547              : ///escapes a string for embedding in a JSON string literal, using only the
     548              : ///escape sequences understood by the sequence .dat decoder
     549            0 : std::string ots_mm_udp_interface::escapeJsonString(const std::string& sourceStr)
     550              : {
     551            0 :         std::string escapedStr;
     552            0 :         escapedStr.reserve(sourceStr.size());
     553            0 :         for(size_t i = 0; i < sourceStr.size(); ++i)
     554              :         {
     555            0 :                 char sourceChar = sourceStr[i];
     556            0 :                 if(sourceChar == '"')
     557            0 :                         escapedStr += "\\\"";
     558            0 :                 else if(sourceChar == '\\')
     559            0 :                         escapedStr += "\\\\";
     560            0 :                 else if(sourceChar == '\n')
     561            0 :                         escapedStr += "\\n";
     562            0 :                 else if(sourceChar == '\t')
     563            0 :                         escapedStr += "\\t";
     564            0 :                 else if(sourceChar == '\r')
     565            0 :                         escapedStr += "\\r";
     566              :                 else
     567            0 :                         escapedStr += sourceChar;
     568              :         }
     569            0 :         return escapedStr;
     570            0 : }  // end escapeJsonString()
     571              : 
     572              : //==============================================================================
     573              : ///accepts only a digits-only positive count that fits in unsigned int
     574            0 : bool ots_mm_udp_interface::parseIterationCount(const std::string& text,
     575              :                                                unsigned int&      iterationCount)
     576              : {
     577            0 :         if(text.empty())
     578            0 :                 return false;
     579            0 :         unsigned long long accumulatedCount = 0;
     580            0 :         for(size_t i = 0; i < text.size(); ++i)
     581              :         {
     582            0 :                 if(text[i] < '0' || text[i] > '9')
     583            0 :                         return false;
     584            0 :                 accumulatedCount = accumulatedCount * 10 + (text[i] - '0');
     585            0 :                 if(accumulatedCount > std::numeric_limits<unsigned int>::max())
     586            0 :                         return false;
     587              :         }
     588            0 :         if(accumulatedCount == 0)
     589            0 :                 return false;
     590            0 :         iterationCount = static_cast<unsigned int>(accumulatedCount);
     591            0 :         return true;
     592              : }  // end parseIterationCount()
     593              : 
     594              : //==============================================================================
     595              : ///returns CSV list of Front-end interface UIDs
     596            0 : const std::string& ots_mm_udp_interface::getFrontendMacroInfo()
     597              : {
     598              :         __COUT__ << "getFrontendMacroInfo()" << __E__;
     599            0 :         if(fullXML_.size())
     600            0 :                 return fullXML_;
     601              : 
     602              :         int numbytes;
     603              : 
     604            0 :         std::string sendMessage = "GetFrontendMacroInfo";
     605              : 
     606            0 :         if((numbytes = sendto(mm_sock_,
     607            0 :                               &(sendMessage.c_str()[0]),
     608              :                               sendMessage.size(),
     609              :                               0,
     610            0 :                               (struct sockaddr*)&mm_ai_addr,
     611            0 :                               sizeof(mm_ai_addr))) == -1)
     612              :         {
     613            0 :                 __SS__ << "Error on getFrontendMacroInfo() sendto!" << __E__;
     614            0 :                 __SS_THROW__;
     615            0 :         }
     616              : 
     617              :         // __COUTV__(numbytes);
     618              : 
     619              :         // read response ///////////////////////////////////////////////////////////
     620            0 :         receiveXmlResponse(fullXML_, "FE Macro Info response", 5);
     621              : 
     622            0 :         if(fullXML_.size() == 0)
     623              :         {
     624            0 :                 __SS__ << "FE Macro Info receive failed! Check that a MacroMaker Supervisor is "
     625              :                           "configured with UDP Remote Control enabled and accessible at "
     626            0 :                        << selfIPandPort_ << "." << __E__;
     627            0 :                 __SS_THROW__;
     628            0 :         }
     629              : 
     630              :         // __COUTV__(fullXML_);
     631              : 
     632              :         //Format:       each line:
     633              :         //              <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
     634              :         //              ;<macro name>;<macro permissions req>;<macro num of inputs>;...<input names ;
     635              :         // separated>...
     636              :         //              ;<macro num of outputs>;...<output names ; separated>...
     637              :         //      do not use :-separator because of the : in user permissions strings
     638              : 
     639            0 :         return fullXML_;
     640            0 : }  //end getFrontendMacroInfo()
     641              : 
     642              : //==============================================================================
     643              : ///returns CSV list of Front-end interface UIDs
     644            0 : std::string ots_mm_udp_interface::getFrontendList()
     645              : {
     646              :         __COUT__ << "getFrontendList()" << __E__;
     647            0 :         getFrontendMacroInfo();  //init fullXML_
     648              : 
     649              :         //Format:       each line:
     650              :         //              <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
     651              :         //              ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
     652              :         // separated>...
     653              :         //              ;<macro num of outputs>;...<output names ; separated>...
     654              :         //      do not use :-separator because of the : in user permissions strings
     655              : 
     656            0 :         std::string value;
     657            0 :         size_t      after = 0;
     658              : 
     659            0 :         std::string retStr;
     660            0 :         while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
     661              :         {
     662              :                 __COUTV__(fullXML_.size());
     663              :                 __COUTV__(after);
     664              :                 // __COUTV__(value);
     665              : 
     666            0 :                 std::vector<std::string> fields = getVectorFromString(value, {';'});
     667              : 
     668              :                 // __COUTV__(vectorToString(fields));
     669            0 :                 if(fields.size() < 6)
     670            0 :                         continue;
     671              : 
     672              :                 //0 = supervisor name
     673              :                 //1 = supervisor lid
     674              :                 //2 = type
     675              :                 //3 = FE UID
     676              : 
     677            0 :                 if(retStr.size())
     678            0 :                         retStr += ';';
     679            0 :                 retStr += fields[3];  //append FE UID
     680              :                                       // __COUTV__(retStr);
     681            0 :         }                         //end primary loop
     682              : 
     683            0 :         return retStr;
     684            0 : }  //end getFrontendList()
     685              : 
     686              : //==============================================================================
     687            0 : std::string ots_mm_udp_interface::getCommandList(const std::string& targetFE)
     688              : {
     689              :         __COUT__ << "getCommandList()" << __E__;
     690            0 :         getFrontendMacroInfo();  //init fullXML_
     691              : 
     692              :         //Format:       each line:
     693              :         //              <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
     694              :         //              ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
     695              :         // separated>...
     696              :         //              ;<macro num of outputs>;...<output names ; separated>...
     697              :         //      do not use :-separator because of the : in user permissions strings
     698              : 
     699            0 :         std::string value;
     700            0 :         size_t      after = 0;
     701            0 :         std::string retStr;
     702            0 :         while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
     703              :         {
     704              :                 // __COUTV__(after);
     705              :                 // __COUTV__(value);
     706            0 :                 std::vector<std::string> fields = getVectorFromString(value, {';'});
     707              : 
     708            0 :                 if(fields.size() < 6)
     709            0 :                         continue;
     710              : 
     711            0 :                 uint32_t i = 4;  //start at first Macro Name, then...
     712              :                 //i+0 = Macro Name
     713              :                 //i+1 = Macro Permissions
     714              :                 //i+2 = Macro Tooltip
     715              :                 //i+3 = number of inputs N
     716              :                 //i+4+N = number of outputs M
     717              :                 //i+4+N+M = Macro Name...
     718              : 
     719            0 :                 if(fields[3] == targetFE)
     720              :                 {
     721              :                         // __COUTV__(vectorToString(fields));
     722              : 
     723              :                         //scan through all Macro Names
     724              : 
     725            0 :                         while(fields.size() >
     726            0 :                               i + 5)  //need type, uid, input count, output count (at least)
     727              :                         {
     728            0 :                                 if(retStr.size())
     729            0 :                                         retStr += ';';
     730            0 :                                 retStr += fields[i + 0];  //append FE Macro Name
     731              : 
     732              :                                 // __COUTV__(fields[i+0]);
     733              :                                 // __COUTV__(fields[i+3]);
     734              : 
     735              :                                 //now navigate to next FE UID, by jumping input and output counts
     736            0 :                                 i += 3 + 1 + atoi(fields[i + 3].c_str());
     737              : 
     738            0 :                                 if(fields.size() < i)
     739              :                                 {
     740            0 :                                         __SS__ << "Illegal FE Macro info! " << i << " vs " << fields.size()
     741            0 :                                                << __E__;
     742            0 :                                         __SS_THROW__;
     743            0 :                                 }
     744              :                                 // __COUTV__(fields[i]);
     745            0 :                                 i += 1 + atoi(fields[i].c_str());
     746              :                         }  //primary FE UID search with fields from one FE Supervisor
     747              : 
     748            0 :                         break;  //found targetFE, so end search
     749              :                 }
     750              : 
     751            0 :         }  //end FE search loop
     752              : 
     753            0 :         return retStr;
     754            0 : }  //end getCommandList()
     755              : 
     756              : //==============================================================================
     757            0 : int ots_mm_udp_interface::getCommandInputCount(const std::string& targetFE,
     758              :                                                const std::string& command)
     759              : {
     760              :         __COUT__ << "getCommandInputCount()" << __E__;
     761            0 :         getFrontendMacroInfo();  //init fullXML_
     762              : 
     763              :         //Format:       each line:
     764              :         //              <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
     765              :         //              ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
     766              :         // separated>...
     767              :         //              ;<macro num of outputs>;...<output names ; separated>...
     768              :         //      do not use :-separator because of the : in user permissions strings
     769              : 
     770            0 :         std::string value;
     771            0 :         size_t      after = 0;
     772            0 :         std::string retStr;
     773            0 :         while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
     774              :         {
     775              :                 // __COUTV__(after);
     776              :                 // __COUTV__(value);
     777            0 :                 std::vector<std::string> fields = getVectorFromString(value, {';'});
     778              : 
     779            0 :                 if(fields.size() < 6)
     780            0 :                         continue;
     781              : 
     782            0 :                 uint32_t i = 4;  //start at first Macro Name, then...
     783              :                 //i+0 = Macro Name
     784              :                 //i+1 = Macro Permissions
     785              :                 //i+2 = Macro Tooltip
     786              :                 //i+3 = number of inputs N
     787              :                 //i+4+N = number of outputs M
     788              :                 //i+4+N+M = Macro Name...
     789              : 
     790            0 :                 if(fields[3] == targetFE)
     791              :                 {
     792              :                         // __COUTV__(vectorToString(fields));
     793              : 
     794              :                         //scan through all Macro Names
     795              : 
     796            0 :                         while(fields.size() >
     797            0 :                               i + 5)  //need type, uid, input count, output count (at least)
     798              :                         {
     799            0 :                                 if(fields[i + 0] == command)  //if found uid/command pair, return
     800              :                                 {
     801            0 :                                         return atoi(fields[i + 3].c_str());  //return input count
     802              :                                 }
     803              :                                 //else keep looking for macro name
     804              : 
     805              :                                 //now navigate to next FE UID, by jumping input and output counts
     806            0 :                                 i += 3 + 1 + atoi(fields[i + 3].c_str());
     807              : 
     808            0 :                                 if(fields.size() < i)
     809              :                                 {
     810            0 :                                         __SS__ << "Illegal FE Macro info! " << i << " vs " << fields.size()
     811            0 :                                                << __E__;
     812            0 :                                         __SS_THROW__;
     813            0 :                                 }
     814              :                                 // __COUTV__(fields[i]);
     815            0 :                                 i += 1 + atoi(fields[i].c_str());
     816              :                         }  //primary FE UID search with fields from one FE Supervisor
     817              : 
     818            0 :                         break;  //found targetFE, so end search
     819              :                 }
     820            0 :         }  //end FE search loop
     821              : 
     822            0 :         __SS__ << "Count not find FE '" << targetFE << "' Command '" << command
     823            0 :                << "' in FE Macro Info! Check UID and Macro name." << __E__;
     824            0 :         __SS_THROW__;
     825            0 : }  //end getCommandInputCount()
     826              : 
     827              : //==============================================================================
     828            0 : int ots_mm_udp_interface::getCommandOutputCount(const std::string& targetFE,
     829              :                                                 const std::string& command)
     830              : {
     831              :         __COUT__ << "getCommandOutputCount()" << __E__;
     832            0 :         getFrontendMacroInfo();  //init fullXML_
     833              : 
     834              :         //Format:       each line:
     835              :         //              <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
     836              :         //              ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
     837              :         // separated>...
     838              :         //              ;<macro num of outputs>;...<output names ; separated>...
     839              :         //      do not use :-separator because of the : in user permissions strings
     840              : 
     841            0 :         std::string value;
     842            0 :         size_t      after = 0;
     843            0 :         std::string retStr;
     844            0 :         while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
     845              :         {
     846              :                 // __COUTV__(after);
     847              :                 // __COUTV__(value);
     848            0 :                 std::vector<std::string> fields = getVectorFromString(value, {';'});
     849              : 
     850            0 :                 if(fields.size() < 6)
     851            0 :                         continue;
     852              : 
     853            0 :                 uint32_t i = 4;  //start at first Macro Name, then...
     854              :                 //i+0 = Macro Name
     855              :                 //i+1 = Macro Permissions
     856              :                 //i+2 = Macro Tooltip
     857              :                 //i+3 = number of inputs N
     858              :                 //i+4+N = number of outputs M
     859              :                 //i+4+N+M = Macro Name...
     860              : 
     861            0 :                 if(fields[3] == targetFE)
     862              :                 {
     863              :                         // __COUTV__(vectorToString(fields));
     864              : 
     865              :                         //scan through all Macro Names
     866              : 
     867            0 :                         while(fields.size() >
     868            0 :                               i + 5)  //need type, uid, input count, output count (at least)
     869              :                         {
     870            0 :                                 bool found = false;
     871            0 :                                 if(fields[i + 0] == command)  //if found uid/command pair, return
     872              :                                 {
     873            0 :                                         found = true;
     874              :                                 }
     875              :                                 //else keep looking for macro name
     876              : 
     877              :                                 //now navigate to next FE UID, by jumping input and output counts
     878            0 :                                 i += 3 + 1 + atoi(fields[i + 3].c_str());
     879              : 
     880            0 :                                 if(fields.size() < i)
     881              :                                 {
     882            0 :                                         __SS__ << "Illegal FE Macro info! " << i << " vs " << fields.size()
     883            0 :                                                << __E__;
     884            0 :                                         __SS_THROW__;
     885            0 :                                 }
     886              :                                 // __COUTV__(fields[i]);
     887              : 
     888            0 :                                 if(found)
     889              :                                 {
     890            0 :                                         return atoi(fields[i].c_str());  //return output count
     891              :                                 }
     892              :                                 //else keep looking for macro name
     893            0 :                                 i += 1 + atoi(fields[i].c_str());
     894              :                         }  //primary FE UID search with fields from one FE Supervisor
     895              : 
     896            0 :                         break;  //found targetFE, so end search
     897              :                 }
     898            0 :         }  //end FE search loop
     899              : 
     900            0 :         __SS__ << "Count not find FE '" << targetFE << "' Command '" << command
     901            0 :                << "' in FE Macro Info! Check UID and Macro name." << __E__;
     902            0 :         __SS_THROW__;
     903            0 : }  //end getCommandOutputCount()
     904              : 
     905              : //==============================================================================
     906            0 : std::string ots_mm_udp_interface::getCommandInputName(const std::string& targetFE,
     907              :                                                       const std::string& command,
     908              :                                                       int                inputIndex)
     909              : {
     910              :         // __COUT__ << "getCommandInputName()" << __E__;
     911            0 :         getFrontendMacroInfo();  //init fullXML_
     912              : 
     913              :         //Format:       each line:
     914              :         //              <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
     915              :         //              ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
     916              :         // separated>...
     917              :         //              ;<macro num of outputs>;...<output names ; separated>...
     918              :         //      do not use :-separator because of the : in user permissions strings
     919              : 
     920            0 :         std::string value;
     921            0 :         size_t      after = 0;
     922            0 :         std::string retStr;
     923            0 :         while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
     924              :         {
     925              :                 // __COUTV__(after);
     926              :                 // __COUTV__(value);
     927            0 :                 std::vector<std::string> fields = getVectorFromString(value, {';'});
     928              : 
     929            0 :                 if(fields.size() < 6)
     930            0 :                         continue;
     931              : 
     932            0 :                 uint32_t i = 4;  //start at first Macro Name, then...
     933              :                 //i+0 = Macro Name
     934              :                 //i+1 = Macro Permissions
     935              :                 //i+2 = Macro Tooltip
     936              :                 //i+3 = number of inputs N
     937              :                 //i+4+N = number of outputs M
     938              :                 //i+4+N+M = Macro Name...
     939              : 
     940            0 :                 if(fields[3] == targetFE)
     941              :                 {
     942              :                         // __COUTV__(vectorToString(fields));
     943              : 
     944              :                         //scan through all Macro Names
     945              : 
     946            0 :                         while(fields.size() >
     947            0 :                               i + 5)  //need type, uid, input count, output count (at least)
     948              :                         {
     949            0 :                                 if(fields[i + 0] == command)  //if found uid/command pair, return
     950              :                                 {
     951            0 :                                         if(i + 3 + 1 + inputIndex >= fields.size() ||
     952            0 :                                            inputIndex >= atoi(fields[i + 3].c_str()))
     953              :                                         {
     954            0 :                                                 __SS__ << "Illegal input arg index " << inputIndex << " vs "
     955            0 :                                                        << fields[i + 3] << " count" << __E__;
     956            0 :                                                 __SS_THROW__;
     957            0 :                                         }
     958              :                                         return decodeURIComponent(
     959            0 :                                             fields[i + 3 + 1 + inputIndex]);  //return input arg name
     960              :                                 }
     961              :                                 //else keep looking for macro name
     962              : 
     963              :                                 //now navigate to next FE UID, by jumping input and output counts
     964            0 :                                 i += 3 + 1 + atoi(fields[i + 3].c_str());
     965              : 
     966            0 :                                 if(fields.size() < i)
     967              :                                 {
     968            0 :                                         __SS__ << "Illegal FE Macro info! " << i << " vs " << fields.size()
     969            0 :                                                << __E__;
     970            0 :                                         __SS_THROW__;
     971            0 :                                 }
     972              :                                 // __COUTV__(fields[i]);
     973            0 :                                 i += 1 + atoi(fields[i].c_str());
     974              :                         }  //primary FE UID search with fields from one FE Supervisor
     975              : 
     976            0 :                         break;  //found targetFE, so end search
     977              :                 }
     978            0 :         }  //end FE search loop
     979              : 
     980            0 :         __SS__ << "Count not find FE '" << targetFE << "' Command '" << command
     981            0 :                << "' in FE Macro Info! Check UID and Macro name." << __E__;
     982            0 :         __SS_THROW__;
     983            0 : }  //end getCommandInputName()
     984              : 
     985              : //==============================================================================
     986              : ///Note: if std::map does not complicate interface too much for ROOT/pyton, could make this const std::string& and leverage cache solution
     987            0 : std::string ots_mm_udp_interface::getCommandOutputName(const std::string& targetFE,
     988              :                                                        const std::string& command,
     989              :                                                        int                outputIndex)
     990              : {
     991              :         // // __COUT__ << "getCommandOutputName()" << __E__;
     992              :         // if(feCache_.find(targetFE + "|" + command) != feCache_.end())
     993              :         // {
     994              :         //      if(feCache_.at(targetFE + "|" + command).find("outputNames-" + std::to_string(outputIndex)) != feCache_.at(targetFE + "|" + command).end())
     995              :         //      {
     996              :         //              __COUT__ << "Found direct cache" << __E__;
     997              :         //              return feCache_.at(targetFE + "|" + command).at("outputNames-" + std::to_string(outputIndex));
     998              :         //      }
     999              :         //      else if(feCache_.at(targetFE + "|" + command).find("outputNames") != feCache_.at(targetFE + "|" + command).end())
    1000              :         //      {
    1001              :         //              __COUT__ << "Found cache" << __E__;
    1002              :         //              std::vector<std::string> outputs = getVectorFromString(
    1003              :         //                      feCache_.at(targetFE + "|" + command).at("outputNames"), {';'});
    1004              :         //              return outputs[outputIndex];
    1005              :         //      }
    1006              :         // }
    1007            0 :         getFrontendMacroInfo();  //init fullXML_
    1008              : 
    1009              :         //Format:       each line:
    1010              :         //              <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
    1011              :         //              ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
    1012              :         // separated>...
    1013              :         //              ;<macro num of outputs>;...<output names ; separated>...
    1014              :         //      do not use :-separator because of the : in user permissions strings
    1015              : 
    1016            0 :         std::string value;
    1017            0 :         size_t      after = 0;
    1018            0 :         std::string retStr;
    1019            0 :         while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
    1020              :         {
    1021              :                 // __COUTV__(after);
    1022              :                 // __COUTV__(value);
    1023            0 :                 std::vector<std::string> fields = getVectorFromString(value, {';'});
    1024              : 
    1025            0 :                 if(fields.size() < 6)
    1026            0 :                         continue;
    1027              : 
    1028            0 :                 uint32_t i = 4;  //start at first Macro Name, then...
    1029              :                 //i+0 = Macro Name
    1030              :                 //i+1 = Macro Permissions
    1031              :                 //i+2 = Macro Tooltip
    1032              :                 //i+3 = number of inputs N
    1033              :                 //i+4+N = number of outputs M
    1034              :                 //i+4+N+M = Macro Name...
    1035              : 
    1036            0 :                 if(fields[3] == targetFE)
    1037              :                 {
    1038              :                         // __COUTV__(vectorToString(fields));
    1039              : 
    1040              :                         //scan through all Macro Names
    1041              : 
    1042            0 :                         while(fields.size() >
    1043            0 :                               i + 5)  //need type, uid, input count, output count (at least)
    1044              :                         {
    1045            0 :                                 bool found = false;
    1046            0 :                                 if(fields[i + 0] == command)  //if found uid/command pair, return
    1047              :                                 {
    1048            0 :                                         found = true;
    1049              :                                 }
    1050              :                                 //else keep looking for macro name
    1051              : 
    1052              :                                 //now navigate to next FE UID, by jumping input and output counts
    1053            0 :                                 i += 3 + 1 + atoi(fields[i + 3].c_str());
    1054              : 
    1055            0 :                                 if(fields.size() < i)
    1056              :                                 {
    1057            0 :                                         __SS__ << "Illegal FE Macro info! " << i << " vs " << fields.size()
    1058            0 :                                                << __E__;
    1059            0 :                                         __SS_THROW__;
    1060            0 :                                 }
    1061              :                                 // __COUTV__(fields[i]);
    1062              : 
    1063            0 :                                 if(found)
    1064              :                                 {
    1065            0 :                                         if(i + 1 + outputIndex >= fields.size() ||
    1066            0 :                                            outputIndex >= atoi(fields[i].c_str()))
    1067              :                                         {
    1068            0 :                                                 __SS__ << "Illegal output arg index " << outputIndex << " vs "
    1069            0 :                                                        << fields[i] << " count" << __E__;
    1070            0 :                                                 __SS_THROW__;
    1071            0 :                                         }
    1072              : 
    1073              :                                         // feCache_[targetFE + "|" + command]["outputNames-" + std::to_string(outputIndex)] = decodeURIComponent(fields[i+1+outputIndex]);
    1074              :                                         // return feCache_.at(targetFE + "|" + command).at("outputNames-" + std::to_string(outputIndex)); //return output arg name
    1075            0 :                                         return decodeURIComponent(fields[i + 1 + outputIndex]);
    1076              :                                 }
    1077              :                                 //else keep looking for macro name
    1078            0 :                                 i += 1 + atoi(fields[i].c_str());
    1079              :                         }  //primary FE UID search with fields from one FE Supervisor
    1080              : 
    1081            0 :                         break;  //found targetFE, so end search
    1082              :                 }
    1083            0 :         }  //end FE search loop
    1084              : 
    1085            0 :         __SS__ << "Count not find FE '" << targetFE << "' Command '" << command
    1086            0 :                << "' in FE Macro Info! Check UID and Macro name." << __E__;
    1087            0 :         __SS_THROW__;
    1088            0 : }  //end getCommandOutputName()
    1089              : 
    1090              : //==============================================================================
    1091              : ///     inputs should be ;-separated and URI encoded (to avoid commas and semicolons)
    1092              : ///     outputs will be ;-separated and URI encoded
    1093            0 : std::string ots_mm_udp_interface::runCommand(const std::string& targetFE,
    1094              :                                              const std::string& command,
    1095              :                                              const std::string& inputs)
    1096              : {
    1097              :         __COUT__ << "On '" << targetFE << "' runCommand: " << command << __E__;
    1098              : 
    1099              :         __COUTV__(inputs);
    1100              : 
    1101              :         //extract FE Type from FE Macro info...
    1102            0 :         getFrontendMacroInfo();  //init fullXML_
    1103            0 :         std::string feType;
    1104              : 
    1105              :         //Format:       each line:
    1106              :         //              <parent supervisor name>;<parent supervisor lid>;<interface type>;<interface UID>
    1107              :         //              ;<macro name>;<macro permissions req>;<macro tooltip>;<macro num of inputs>;...<input names ;
    1108              :         // separated>...
    1109              :         //              ;<macro num of outputs>;...<output names ; separated>...
    1110              :         //      do not use :-separator because of the : in user permissions strings
    1111              : 
    1112            0 :         std::string value;
    1113            0 :         size_t      after = 0;
    1114              : 
    1115            0 :         std::string retStr;
    1116            0 :         while((value = extractXmlField(fullXML_, "FEMacros", 0, after, &after)) != "")
    1117              :         {
    1118              :                 __COUTV__(fullXML_.size());
    1119              :                 __COUTV__(after);
    1120              :                 // __COUTV__(value);
    1121              : 
    1122            0 :                 std::vector<std::string> fields = getVectorFromString(value, {';'});
    1123              : 
    1124              :                 // __COUTV__(vectorToString(fields));
    1125            0 :                 if(fields.size() < 6)
    1126            0 :                         continue;
    1127              : 
    1128              :                 //0 = supervisor name
    1129              :                 //1 = supervisor lid
    1130              :                 //2 = type
    1131              :                 //3 = FE UID
    1132              : 
    1133            0 :                 if(fields[3] == targetFE)
    1134              :                 {
    1135            0 :                         feType = fields[2];
    1136              :                         __COUTV__(feType);
    1137            0 :                         break;
    1138              :                 }
    1139            0 :         }  //end primary loop
    1140              : 
    1141            0 :         if(feType.size() == 0)
    1142              :         {
    1143            0 :                 __SS__ << "Could not find front-end type for FE UID '" << targetFE << __E__;
    1144            0 :                 __SS_THROW__;
    1145            0 :         }
    1146              : 
    1147              :         int numbytes;
    1148              : 
    1149            0 :         std::string sendMessage = "RunFrontendMacro";
    1150              :         //create ;-separated arguments to satisfy this:
    1151              :         // std::string feClassSelected = bufferFields[1];
    1152              :         // std::string feUIDSelected = bufferFields[2];  // allow CSV multi-selection
    1153              :         // std::string macroType =  bufferFields[3]; // "fe", "public", "private"
    1154              :         // std::string macroName = bufferFields[4];
    1155              :         // std::string inputArgs   = StringMacros::decodeURIComponent(bufferFields[5]); //two level ;- and ,- separated
    1156              :         // std::string outputArgs  =  StringMacros::decodeURIComponent(bufferFields[6]); // ,- separated
    1157              :         // bool        saveOutputs = bufferFields[7] == "1";
    1158            0 :         sendMessage += ";" + feType;  // std::string feClassSelected = bufferFields[1];
    1159              :         sendMessage +=
    1160            0 :             ";" +
    1161            0 :             targetFE;  // std::string feUIDSelected = bufferFields[2];  // allow CSV multi-selection
    1162              :         sendMessage +=
    1163            0 :             ";" +
    1164            0 :             std::string(
    1165            0 :                 "fe");  // std::string macroType =  bufferFields[3]; // "fe", "public", "private"
    1166              :         sendMessage +=
    1167            0 :             ";" + encodeURIComponent(command);  // std::string macroName = bufferFields[4];
    1168              : 
    1169              :         //handle inputs
    1170              :         {
    1171            0 :                 std::string inputStr;
    1172            0 :                 uint32_t    numberOfInputs = getCommandInputCount(targetFE, command);
    1173              : 
    1174            0 :                 std::vector<std::string> inputVec = getVectorFromString(inputs, {';'});
    1175            0 :                 uint32_t                 countOfNonEmptyInputs = 0;
    1176            0 :                 for(uint32_t i = 0; i < inputVec.size(); ++i)
    1177              :                 {
    1178            0 :                         if(inputVec[i] == "")
    1179            0 :                                 continue;  //skip empty inputs
    1180            0 :                         if(countOfNonEmptyInputs)
    1181            0 :                                 inputStr += ";";
    1182            0 :                         inputStr += encodeURIComponent(getCommandInputName(
    1183            0 :                                         targetFE, command, countOfNonEmptyInputs)) +
    1184            0 :                                     "," + inputVec[i];  //already encoded (hopefully)
    1185              : 
    1186            0 :                         ++countOfNonEmptyInputs;
    1187              :                 }
    1188            0 :                 if(numberOfInputs != countOfNonEmptyInputs)
    1189              :                 {
    1190            0 :                         __SS__ << "Input argument mismatch: " << countOfNonEmptyInputs << " vs "
    1191            0 :                                << numberOfInputs << " expected." << __E__;
    1192            0 :                         __SS_THROW__;
    1193            0 :                 }
    1194              :                 __COUTV__(inputStr);
    1195            0 :                 sendMessage += ";" + encodeURIComponent(inputStr);  //double encoded
    1196            0 :         }
    1197              : 
    1198              :         //handle outputs, which are ,- separated
    1199            0 :         uint32_t numberOfOutputs = getCommandOutputCount(targetFE, command);
    1200              :         {
    1201            0 :                 std::string outputStr;
    1202              : 
    1203            0 :                 for(uint32_t i = 0; i < numberOfOutputs; ++i)
    1204              :                 {
    1205            0 :                         if(i)
    1206            0 :                                 outputStr += ",";
    1207            0 :                         outputStr += encodeURIComponent(getCommandOutputName(targetFE, command, i));
    1208              :                 }
    1209              : 
    1210            0 :                 sendMessage += ";" + encodeURIComponent(outputStr);  //double encoded
    1211            0 :         }
    1212              : 
    1213              :         sendMessage +=
    1214            0 :             ";" + std::string("0");  // bool        saveOutputs = bufferFields[7] == "1";
    1215              : 
    1216              :         __COUTV__(sendMessage.size());
    1217              :         __COUTV__(sendMessage);
    1218              : 
    1219            0 :         if((numbytes = sendto(mm_sock_,
    1220            0 :                               &(sendMessage.c_str()[0]),
    1221              :                               sendMessage.size(),
    1222              :                               0,
    1223            0 :                               (struct sockaddr*)&mm_ai_addr,
    1224            0 :                               sizeof(mm_ai_addr))) == -1)
    1225              :         {
    1226            0 :                 __SS__ << "Error on runCommand() sendto! Error: " << strerror(errno) << __E__;
    1227              : 
    1228              :                 {  //check for more error info
    1229            0 :                         int       error  = 0;
    1230            0 :                         socklen_t len    = sizeof(error);
    1231            0 :                         int       retval = getsockopt(mm_sock_, SOL_SOCKET, SO_ERROR, &error, &len);
    1232            0 :                         if(retval != 0 || error != 0)
    1233              :                         {
    1234            0 :                                 ss << "Socket is closed or in error state: " << strerror(error)
    1235            0 :                                    << std::endl;
    1236              :                         }
    1237              :                 }
    1238              : 
    1239            0 :                 close(mm_sock_);
    1240            0 :                 mm_sock_ = -1;
    1241              : 
    1242            0 :                 __SS_THROW__;
    1243            0 :         }
    1244              : 
    1245              :         // read response ///////////////////////////////////////////////////////////
    1246            0 :         std::string runXML;
    1247            0 :         receiveXmlResponse(runXML, "command run response", 10);
    1248              : 
    1249              :         // At this point, runXML should contain the complete response assembled by the current receive logic.
    1250            0 :         if(runXML.size() == 0 || runXML.find("Error") == 0 || !hasCompleteXmlRoot(runXML))
    1251              :         {
    1252            0 :                 __SS__ << "Error running the command. Received error or incomplete buffer: "
    1253            0 :                        << (runXML.size() == 0 ? "<empty>" : runXML) << __E__;
    1254            0 :                 __SS_THROW__;
    1255            0 :         }
    1256              : 
    1257              :         //example response:
    1258              :         // <ROOT>
    1259              :         //      <DATA>
    1260              :         //              <feMacroExec value='Check Firefly Loss-of-Light'>
    1261              :         //                      <exec_time value='Fri Aug 16 18:59:15 2024 CDT'/>
    1262              :         //                      <fe_uid value='CFO0'/>
    1263              :         //                      <fe_type value='CFOFrontEndInterface'/>
    1264              :         //                      <fe_context value='ots::FESupervisor'/>
    1265              :         //                      <fe_supervisor value='LID-280'/>
    1266              :         //                      <fe_hostname value='mu2e-calo-03.fnal.gov'/>
    1267              :         //                      <outputArgs_name value='Link Status'/>
    1268              :         //                      <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'/>
    1269              :         //              </feMacroExec>
    1270              :         //      </DATA>
    1271              :         // </ROOT>
    1272              : 
    1273              :         //verify outputs
    1274              :         {
    1275            0 :                 std::string outputStr;
    1276              : 
    1277            0 :                 size_t      after = 0;
    1278            0 :                 std::string error = extractXmlField(runXML, "Error", 0, after, &after);
    1279              : 
    1280            0 :                 if(error != "")
    1281              :                 {
    1282              :                         //error has html entities in it
    1283            0 :                         error = decodeHTMLEntities(error);
    1284            0 :                         __SS__ << "Error message received after command execution attempt: \n"
    1285            0 :                                << error << __E__;
    1286            0 :                         __SS_THROW__;
    1287            0 :                 }
    1288              : 
    1289            0 :                 for(uint32_t i = 0; i < numberOfOutputs; ++i)
    1290              :                 {
    1291            0 :                         std::string expectedOutputName = getCommandOutputName(targetFE, command, i);
    1292              : 
    1293              :                         std::string outputName =
    1294            0 :                             extractXmlField(runXML, "outputArgs_name", 0, after, &after);
    1295              :                         std::string outputValue =
    1296            0 :                             extractXmlField(runXML, "outputArgs_value", 0, after, &after);
    1297            0 :                         __COUT_INFO__ << "Command Result output #" << i << ": " << outputName << " = "
    1298            0 :                                       << decodeURIComponent(outputValue) << __E__;
    1299              : 
    1300            0 :                         if(i)
    1301            0 :                                 outputStr += ';';
    1302            0 :                         outputStr += outputValue;
    1303            0 :                 }
    1304              : 
    1305            0 :                 return outputStr;
    1306            0 :         }
    1307              : 
    1308            0 : }  //end runCommand()
    1309              : 
    1310              : //==============================================================================
    1311            0 : std::string ots_mm_udp_interface::getSequenceList()
    1312              : {
    1313              :         __COUT__ << "getSequenceList()" << __E__;
    1314              : 
    1315            0 :         std::string sendMessage = "GetSequenceList";
    1316              : 
    1317              :         int numbytes;
    1318            0 :         if((numbytes = sendto(mm_sock_,
    1319            0 :                               &(sendMessage.c_str()[0]),
    1320              :                               sendMessage.size(),
    1321              :                               0,
    1322            0 :                               (struct sockaddr*)&mm_ai_addr,
    1323            0 :                               sizeof(mm_ai_addr))) == -1)
    1324              :         {
    1325            0 :                 __SS__ << "Error on getSequenceList() sendto!" << __E__;
    1326            0 :                 __SS_THROW__;
    1327            0 :         }
    1328              : 
    1329            0 :         std::string responseXML;
    1330            0 :         receiveXmlResponse(responseXML, "Sequence List response", 5);
    1331              : 
    1332            0 :         if(responseXML.size() == 0)
    1333              :         {
    1334            0 :                 __SS__ << "Sequence List receive failed! Check that a MacroMaker Supervisor is "
    1335              :                           "configured with UDP Remote Control enabled and accessible at "
    1336            0 :                        << selfIPandPort_ << "." << __E__;
    1337            0 :                 __SS_THROW__;
    1338            0 :         }
    1339              : 
    1340            0 :         size_t      after = 0;
    1341              :         std::string sequenceListValue =
    1342            0 :             extractXmlField(responseXML, "FEsequences", 0, after, &after);
    1343              : 
    1344            0 :         std::string              cleanedSequenceList;
    1345            0 :         std::vector<std::string> rawEntries = getVectorFromString(sequenceListValue, {';'});
    1346            0 :         for(size_t i = 0; i < rawEntries.size(); ++i)
    1347              :         {
    1348            0 :                 std::string entry = rawEntries[i];
    1349            0 :                 if(entry.size() > 4 && entry.substr(entry.size() - 4) == ".dat")
    1350            0 :                         entry = entry.substr(0, entry.size() - 4);
    1351            0 :                 std::string baseName  = entry;
    1352            0 :                 size_t      lastSlash = baseName.rfind('/');
    1353            0 :                 if(lastSlash != std::string::npos)
    1354            0 :                         baseName = baseName.substr(lastSlash + 1);
    1355            0 :                 if(baseName.empty() || baseName == "." || baseName == "..")
    1356            0 :                         continue;
    1357            0 :                 if(!cleanedSequenceList.empty())
    1358            0 :                         cleanedSequenceList += ";";
    1359            0 :                 cleanedSequenceList += entry;
    1360            0 :         }
    1361            0 :         return cleanedSequenceList;
    1362            0 : }  //end getSequenceList()
    1363              : 
    1364              : //==============================================================================
    1365            0 : std::string ots_mm_udp_interface::getSequenceInfo(const std::string& sequenceName)
    1366              : {
    1367              :         __COUT__ << "getSequenceInfo() for '" << sequenceName << "'" << __E__;
    1368              : 
    1369            0 :         std::string sendMessage = "GetSequenceInfo;" + encodeURIComponent(sequenceName);
    1370              : 
    1371              :         int numbytes;
    1372            0 :         if((numbytes = sendto(mm_sock_,
    1373            0 :                               &(sendMessage.c_str()[0]),
    1374              :                               sendMessage.size(),
    1375              :                               0,
    1376            0 :                               (struct sockaddr*)&mm_ai_addr,
    1377            0 :                               sizeof(mm_ai_addr))) == -1)
    1378              :         {
    1379            0 :                 __SS__ << "Error on getSequenceInfo() sendto!" << __E__;
    1380            0 :                 __SS_THROW__;
    1381            0 :         }
    1382              : 
    1383            0 :         std::string responseXML;
    1384            0 :         receiveXmlResponse(responseXML, "Sequence Info response", 5);
    1385              : 
    1386            0 :         if(responseXML.size() == 0 || responseXML.find("Error") == 0 ||
    1387            0 :            !hasCompleteXmlRoot(responseXML))
    1388              :         {
    1389            0 :                 __SS__ << "Error loading sequence '" << sequenceName
    1390            0 :                        << "'. Received: " << (responseXML.size() == 0 ? "<empty>" : responseXML)
    1391            0 :                        << __E__;
    1392            0 :                 __SS_THROW__;
    1393            0 :         }
    1394              : 
    1395            0 :         size_t      after        = 0;
    1396            0 :         std::string errorMessage = extractXmlField(responseXML, "Error", 0, after, &after);
    1397            0 :         if(!errorMessage.empty())
    1398              :         {
    1399            0 :                 errorMessage = decodeHTMLEntities(errorMessage);
    1400            0 :                 __SS__ << "Error loading sequence '" << sequenceName << "': " << errorMessage
    1401            0 :                        << __E__;
    1402            0 :                 __SS_THROW__;
    1403            0 :         }
    1404              : 
    1405            0 :         after = 0;
    1406              :         std::string sequenceJSON =
    1407            0 :             extractXmlField(responseXML, "FEsequence", 0, after, &after);
    1408            0 :         return decodeHTMLEntities(sequenceJSON);
    1409            0 : }  //end getSequenceInfo()
    1410              : 
    1411              : //==============================================================================
    1412              : // Lightweight JSON helpers for parsing sequence .dat content.
    1413              : // Supports both old format (string values) and new format (object values).
    1414              : 
    1415              : namespace
    1416              : {
    1417              : 
    1418            0 : size_t findMatchingBrace(const std::string& jsonString, size_t openBracePosition)
    1419              : {
    1420            0 :         int  depth       = 1;
    1421            0 :         bool insideQuote = false;
    1422            0 :         for(size_t i = openBracePosition + 1; i < jsonString.size(); ++i)
    1423              :         {
    1424            0 :                 char currentChar = jsonString[i];
    1425            0 :                 if(currentChar == '\\' && insideQuote)
    1426              :                 {
    1427            0 :                         ++i;
    1428            0 :                         continue;
    1429              :                 }
    1430            0 :                 if(currentChar == '"')
    1431            0 :                         insideQuote = !insideQuote;
    1432            0 :                 if(!insideQuote)
    1433              :                 {
    1434            0 :                         if(currentChar == '{')
    1435            0 :                                 ++depth;
    1436            0 :                         else if(currentChar == '}')
    1437              :                         {
    1438            0 :                                 --depth;
    1439            0 :                                 if(depth == 0)
    1440            0 :                                         return i;
    1441              :                         }
    1442              :                 }
    1443              :         }
    1444            0 :         return std::string::npos;
    1445              : }
    1446              : 
    1447            0 : std::string extractJsonStringValue(const std::string& jsonString,
    1448              :                                    size_t             openQuotePosition)
    1449              : {
    1450            0 :         std::string result;
    1451            0 :         for(size_t i = openQuotePosition + 1; i < jsonString.size(); ++i)
    1452              :         {
    1453            0 :                 char currentChar = jsonString[i];
    1454            0 :                 if(currentChar == '\\' && i + 1 < jsonString.size())
    1455              :                 {
    1456            0 :                         char nextChar = jsonString[i + 1];
    1457            0 :                         if(nextChar == '"')
    1458            0 :                                 result += '"';
    1459            0 :                         else if(nextChar == '\\')
    1460            0 :                                 result += '\\';
    1461            0 :                         else if(nextChar == '/')
    1462            0 :                                 result += '/';
    1463            0 :                         else if(nextChar == 'n')
    1464            0 :                                 result += '\n';
    1465            0 :                         else if(nextChar == 't')
    1466            0 :                                 result += '\t';
    1467            0 :                         else if(nextChar == 'r')
    1468            0 :                                 result += '\r';
    1469              :                         else
    1470              :                         {
    1471            0 :                                 result += currentChar;
    1472            0 :                                 result += nextChar;
    1473              :                         }
    1474            0 :                         ++i;
    1475            0 :                         continue;
    1476            0 :                 }
    1477            0 :                 if(currentChar == '"')
    1478            0 :                         break;
    1479            0 :                 result += currentChar;
    1480              :         }
    1481            0 :         return result;
    1482            0 : }
    1483              : 
    1484            0 : size_t findClosingQuote(const std::string& jsonString, size_t openQuotePosition)
    1485              : {
    1486            0 :         for(size_t i = openQuotePosition + 1; i < jsonString.size(); ++i)
    1487              :         {
    1488            0 :                 if(jsonString[i] == '\\')
    1489              :                 {
    1490            0 :                         ++i;
    1491            0 :                         continue;
    1492              :                 }
    1493            0 :                 if(jsonString[i] == '"')
    1494            0 :                         return i;
    1495              :         }
    1496            0 :         return std::string::npos;
    1497              : }
    1498              : 
    1499            0 : std::string extractFieldFromObject(const std::string& objectJSON,
    1500              :                                    const std::string& fieldName)
    1501              : {
    1502            0 :         std::string searchPattern = "\"" + fieldName + "\"";
    1503            0 :         size_t      fieldPosition = objectJSON.find(searchPattern);
    1504            0 :         if(fieldPosition == std::string::npos)
    1505            0 :                 return "";
    1506              : 
    1507            0 :         size_t colonPosition = objectJSON.find(':', fieldPosition + searchPattern.size());
    1508            0 :         if(colonPosition == std::string::npos)
    1509            0 :                 return "";
    1510              : 
    1511            0 :         size_t valueStart = colonPosition + 1;
    1512            0 :         while(valueStart < objectJSON.size() && objectJSON[valueStart] == ' ')
    1513            0 :                 ++valueStart;
    1514            0 :         if(valueStart >= objectJSON.size())
    1515            0 :                 return "";
    1516              : 
    1517            0 :         if(objectJSON[valueStart] == '"')
    1518            0 :                 return extractJsonStringValue(objectJSON, valueStart);
    1519              : 
    1520            0 :         size_t valueEnd = objectJSON.find_first_of(",}", valueStart);
    1521            0 :         if(valueEnd == std::string::npos)
    1522            0 :                 valueEnd = objectJSON.size();
    1523            0 :         return objectJSON.substr(valueStart, valueEnd - valueStart);
    1524            0 : }
    1525              : 
    1526              : struct SequenceCommand
    1527              : {
    1528              :         std::string feClass;
    1529              :         std::string feUID;
    1530              :         std::string macroType;
    1531              :         std::string macroName;
    1532              :         std::string inputArgs;
    1533              :         std::string outputArgs;
    1534              :         std::string saveOutputs;
    1535              : };
    1536              : 
    1537              : struct SequenceVariable
    1538              : {
    1539              :         std::string value;
    1540              :         std::string step;
    1541              :         std::string max;
    1542              : };
    1543              : 
    1544            0 : SequenceCommand parseCommandObject(const std::string& objectJSON)
    1545              : {
    1546            0 :         SequenceCommand command;
    1547            0 :         command.feClass     = extractFieldFromObject(objectJSON, "feClass");
    1548            0 :         command.feUID       = extractFieldFromObject(objectJSON, "feUID");
    1549            0 :         command.macroType   = extractFieldFromObject(objectJSON, "macroType");
    1550            0 :         command.macroName   = extractFieldFromObject(objectJSON, "macroName");
    1551            0 :         command.inputArgs   = extractFieldFromObject(objectJSON, "inputArgs");
    1552            0 :         command.outputArgs  = extractFieldFromObject(objectJSON, "outputArgs");
    1553            0 :         command.saveOutputs = extractFieldFromObject(objectJSON, "saveOutputs");
    1554            0 :         if(command.saveOutputs.empty())
    1555            0 :                 command.saveOutputs = "0";
    1556            0 :         return command;
    1557            0 : }
    1558              : 
    1559            0 : void parseSequenceJSON(
    1560              :     const std::string&                                     sequenceJSON,
    1561              :     std::map<unsigned int, SequenceCommand>&               commandMap,
    1562              :     std::vector<std::pair<std::string, SequenceVariable>>& variableList)
    1563              : {
    1564            0 :         if(sequenceJSON.empty())
    1565            0 :                 return;
    1566              : 
    1567            0 :         size_t outerOpen = sequenceJSON.find('{');
    1568            0 :         if(outerOpen == std::string::npos)
    1569            0 :                 return;
    1570              : 
    1571            0 :         size_t position = outerOpen + 1;
    1572            0 :         while(position < sequenceJSON.size())
    1573              :         {
    1574            0 :                 size_t keyStart = sequenceJSON.find('"', position);
    1575            0 :                 if(keyStart == std::string::npos)
    1576            0 :                         break;
    1577            0 :                 size_t keyEnd = findClosingQuote(sequenceJSON, keyStart);
    1578            0 :                 if(keyEnd == std::string::npos)
    1579            0 :                         break;
    1580              : 
    1581            0 :                 std::string key = sequenceJSON.substr(keyStart + 1, keyEnd - keyStart - 1);
    1582              : 
    1583            0 :                 size_t colonPosition = sequenceJSON.find(':', keyEnd + 1);
    1584            0 :                 if(colonPosition == std::string::npos)
    1585            0 :                         break;
    1586              : 
    1587            0 :                 size_t valueStart = colonPosition + 1;
    1588            0 :                 while(valueStart < sequenceJSON.size() && sequenceJSON[valueStart] == ' ')
    1589            0 :                         ++valueStart;
    1590              : 
    1591            0 :                 if(key == "__variables__")
    1592              :                 {
    1593            0 :                         if(sequenceJSON[valueStart] == '{')
    1594              :                         {
    1595            0 :                                 size_t variablesEnd = findMatchingBrace(sequenceJSON, valueStart);
    1596            0 :                                 if(variablesEnd == std::string::npos)
    1597            0 :                                         break;
    1598              :                                 std::string variablesBlock =
    1599            0 :                                     sequenceJSON.substr(valueStart, variablesEnd - valueStart + 1);
    1600              : 
    1601            0 :                                 size_t variablePosition = 1;
    1602            0 :                                 while(variablePosition < variablesBlock.size())
    1603              :                                 {
    1604            0 :                                         size_t variableNameStart = variablesBlock.find('"', variablePosition);
    1605            0 :                                         if(variableNameStart == std::string::npos)
    1606            0 :                                                 break;
    1607              :                                         size_t variableNameEnd =
    1608            0 :                                             findClosingQuote(variablesBlock, variableNameStart);
    1609            0 :                                         if(variableNameEnd == std::string::npos)
    1610            0 :                                                 break;
    1611              :                                         std::string variableName = variablesBlock.substr(
    1612            0 :                                             variableNameStart + 1, variableNameEnd - variableNameStart - 1);
    1613              : 
    1614            0 :                                         size_t variableColon = variablesBlock.find(':', variableNameEnd + 1);
    1615            0 :                                         if(variableColon == std::string::npos)
    1616            0 :                                                 break;
    1617              :                                         size_t variableObjectStart =
    1618            0 :                                             variablesBlock.find('{', variableColon + 1);
    1619            0 :                                         if(variableObjectStart == std::string::npos)
    1620            0 :                                                 break;
    1621              :                                         size_t variableObjectEnd =
    1622            0 :                                             findMatchingBrace(variablesBlock, variableObjectStart);
    1623            0 :                                         if(variableObjectEnd == std::string::npos)
    1624            0 :                                                 break;
    1625              : 
    1626              :                                         std::string variableObjectJSON = variablesBlock.substr(
    1627            0 :                                             variableObjectStart, variableObjectEnd - variableObjectStart + 1);
    1628              : 
    1629            0 :                                         SequenceVariable sequenceVariable;
    1630              :                                         sequenceVariable.value =
    1631            0 :                                             extractFieldFromObject(variableObjectJSON, "value");
    1632              :                                         sequenceVariable.step =
    1633            0 :                                             extractFieldFromObject(variableObjectJSON, "step");
    1634              :                                         sequenceVariable.max =
    1635            0 :                                             extractFieldFromObject(variableObjectJSON, "max");
    1636            0 :                                         variableList.push_back({variableName, sequenceVariable});
    1637              : 
    1638            0 :                                         variablePosition = variableObjectEnd + 1;
    1639            0 :                                 }
    1640            0 :                                 position = variablesEnd + 1;
    1641            0 :                         }
    1642              :                         else
    1643              :                         {
    1644            0 :                                 position = valueStart + 1;
    1645              :                         }
    1646            0 :                         continue;
    1647            0 :                 }
    1648              : 
    1649            0 :                 bool isNumericKey = !key.empty();
    1650            0 :                 for(size_t i = 0; i < key.size() && isNumericKey; ++i)
    1651            0 :                         if(key[i] < '0' || key[i] > '9')
    1652            0 :                                 isNumericKey = false;
    1653              : 
    1654            0 :                 if(!isNumericKey)
    1655              :                 {
    1656            0 :                         if(sequenceJSON[valueStart] == '{')
    1657              :                         {
    1658            0 :                                 size_t skipEnd = findMatchingBrace(sequenceJSON, valueStart);
    1659            0 :                                 position = (skipEnd != std::string::npos) ? skipEnd + 1 : valueStart + 1;
    1660              :                         }
    1661            0 :                         else if(sequenceJSON[valueStart] == '"')
    1662              :                         {
    1663            0 :                                 size_t skipEnd = findClosingQuote(sequenceJSON, valueStart);
    1664            0 :                                 position = (skipEnd != std::string::npos) ? skipEnd + 1 : valueStart + 1;
    1665              :                         }
    1666              :                         else
    1667              :                         {
    1668            0 :                                 position = sequenceJSON.find_first_of(",}", valueStart);
    1669            0 :                                 if(position == std::string::npos)
    1670            0 :                                         break;
    1671              :                         }
    1672            0 :                         continue;
    1673            0 :                 }
    1674              : 
    1675            0 :                 unsigned int stepIndex = std::stoul(key);
    1676              : 
    1677            0 :                 if(sequenceJSON[valueStart] == '"')
    1678              :                 {
    1679              :                         std::string commandObjectJSON =
    1680            0 :                             extractJsonStringValue(sequenceJSON, valueStart);
    1681            0 :                         commandMap[stepIndex] = parseCommandObject(commandObjectJSON);
    1682            0 :                         size_t closingQuote   = findClosingQuote(sequenceJSON, valueStart);
    1683            0 :                         position =
    1684            0 :                             (closingQuote != std::string::npos) ? closingQuote + 1 : valueStart + 1;
    1685            0 :                 }
    1686            0 :                 else if(sequenceJSON[valueStart] == '{')
    1687              :                 {
    1688            0 :                         size_t objectEnd = findMatchingBrace(sequenceJSON, valueStart);
    1689            0 :                         if(objectEnd == std::string::npos)
    1690            0 :                                 break;
    1691              :                         std::string commandObjectJSON =
    1692            0 :                             sequenceJSON.substr(valueStart, objectEnd - valueStart + 1);
    1693            0 :                         commandMap[stepIndex] = parseCommandObject(commandObjectJSON);
    1694            0 :                         position              = objectEnd + 1;
    1695            0 :                 }
    1696              :                 else
    1697              :                 {
    1698            0 :                         position = sequenceJSON.find_first_of(",}", valueStart);
    1699            0 :                         if(position == std::string::npos)
    1700            0 :                                 break;
    1701              :                 }
    1702            0 :         }
    1703              : }  //end parseSequenceJSON()
    1704              : 
    1705              : struct SplitNumericResult
    1706              : {
    1707              :         double      numericPart;
    1708              :         std::string unitSuffix;
    1709              :         bool        isValid;
    1710              : };
    1711              : 
    1712            0 : SplitNumericResult splitNumericValue(const std::string& valueString)
    1713              : {
    1714              :         static const std::regex numericPattern(
    1715            0 :             "^\\s*([-+]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][-+]?\\d+)?)\\s*([a-zA-Z]*)\\s*$");
    1716            0 :         std::smatch        matchResult;
    1717            0 :         SplitNumericResult result;
    1718            0 :         result.isValid = false;
    1719            0 :         if(std::regex_match(valueString, matchResult, numericPattern))
    1720              :         {
    1721            0 :                 result.numericPart = std::stod(matchResult[1].str());
    1722            0 :                 result.unitSuffix  = matchResult[2].str();
    1723            0 :                 result.isValid     = true;
    1724              :         }
    1725            0 :         return result;
    1726            0 : }
    1727              : 
    1728            0 : std::string substituteSequenceVariables(
    1729              :     const std::string&                                           inputArgs,
    1730              :     const std::vector<std::pair<std::string, SequenceVariable>>& variableList,
    1731              :     unsigned int                                                 currentIteration)
    1732              : {
    1733            0 :         if(variableList.empty())
    1734            0 :                 return inputArgs;
    1735              : 
    1736              :         std::vector<std::string> pairs =
    1737            0 :             getVectorFromString(inputArgs, {';'}, {} /*whitespace*/);
    1738            0 :         std::string result;
    1739              : 
    1740            0 :         for(size_t pairIndex = 0; pairIndex < pairs.size(); ++pairIndex)
    1741              :         {
    1742            0 :                 if(pairIndex > 0)
    1743            0 :                         result += ";";
    1744              : 
    1745              :                 std::vector<std::string> nameAndValue =
    1746            0 :                     getVectorFromString(pairs[pairIndex], {','}, {} /*whitespace*/);
    1747            0 :                 if(nameAndValue.size() < 2)
    1748              :                 {
    1749            0 :                         result += pairs[pairIndex];
    1750            0 :                         continue;
    1751              :                 }
    1752              : 
    1753              :                 std::string decodedValue =
    1754            0 :                     ots_mm_udp_interface::decodeURIComponent(nameAndValue[1]);
    1755              : 
    1756            0 :                 for(size_t variableIndex = 0; variableIndex < variableList.size();
    1757              :                     ++variableIndex)
    1758              :                 {
    1759            0 :                         const std::string&      variableName     = variableList[variableIndex].first;
    1760            0 :                         const SequenceVariable& sequenceVariable = variableList[variableIndex].second;
    1761              : 
    1762            0 :                         size_t searchPosition = 0;
    1763            0 :                         while((searchPosition = decodedValue.find(variableName, searchPosition)) !=
    1764              :                               std::string::npos)
    1765              :                         {
    1766            0 :                                 std::string effectiveValue = sequenceVariable.value;
    1767              : 
    1768            0 :                                 if(currentIteration > 0)
    1769              :                                 {
    1770              :                                         SplitNumericResult valueParts =
    1771            0 :                                             splitNumericValue(sequenceVariable.value);
    1772            0 :                                         double stepValue = 0;
    1773              :                                         try
    1774              :                                         {
    1775            0 :                                                 stepValue = std::stod(sequenceVariable.step);
    1776              :                                         }
    1777            0 :                                         catch(...)
    1778              :                                         {
    1779            0 :                                         }
    1780              : 
    1781            0 :                                         if(valueParts.isValid && stepValue != 0)
    1782              :                                         {
    1783            0 :                                                 double computedValue =
    1784            0 :                                                     valueParts.numericPart + currentIteration * stepValue;
    1785              :                                                 SplitNumericResult maxParts =
    1786            0 :                                                     splitNumericValue(sequenceVariable.max);
    1787            0 :                                                 if(maxParts.isValid)
    1788              :                                                 {
    1789            0 :                                                         if(stepValue > 0 && computedValue > maxParts.numericPart)
    1790            0 :                                                                 computedValue = maxParts.numericPart;
    1791            0 :                                                         else if(stepValue < 0 && computedValue < maxParts.numericPart)
    1792            0 :                                                                 computedValue = maxParts.numericPart;
    1793              :                                                 }
    1794              : 
    1795            0 :                                                 std::ostringstream precisionStream;
    1796            0 :                                                 precisionStream << std::setprecision(12) << computedValue;
    1797            0 :                                                 effectiveValue = precisionStream.str() + valueParts.unitSuffix;
    1798            0 :                                         }
    1799            0 :                                 }
    1800              : 
    1801            0 :                                 decodedValue.replace(searchPosition, variableName.size(), effectiveValue);
    1802            0 :                                 searchPosition += effectiveValue.size();
    1803            0 :                         }
    1804              :                 }
    1805              : 
    1806            0 :                 result += nameAndValue[0] + "," +
    1807            0 :                           ots_mm_udp_interface::encodeURIComponent(decodedValue);
    1808            0 :         }
    1809            0 :         return result;
    1810            0 : }  //end substituteSequenceVariables()
    1811              : 
    1812            0 : std::string serializeSequenceVariables(
    1813              :     const std::vector<std::pair<std::string, SequenceVariable>>& variableList)
    1814              : {
    1815            0 :         std::string variablesJSON = "{";
    1816            0 :         for(size_t variableIndex = 0; variableIndex < variableList.size(); ++variableIndex)
    1817              :         {
    1818            0 :                 if(variableIndex > 0)
    1819            0 :                         variablesJSON += ",";
    1820              :                 variablesJSON +=
    1821            0 :                     "\"" +
    1822            0 :                     ots_mm_udp_interface::escapeJsonString(variableList[variableIndex].first) +
    1823            0 :                     "\":{\"value\":\"" +
    1824            0 :                     ots_mm_udp_interface::escapeJsonString(
    1825            0 :                         variableList[variableIndex].second.value) +
    1826            0 :                     "\",\"step\":\"" +
    1827            0 :                     ots_mm_udp_interface::escapeJsonString(
    1828            0 :                         variableList[variableIndex].second.step) +
    1829            0 :                     "\",\"max\":\"" +
    1830            0 :                     ots_mm_udp_interface::escapeJsonString(
    1831            0 :                         variableList[variableIndex].second.max) +
    1832            0 :                     "\"}";
    1833              :         }
    1834            0 :         variablesJSON += "}";
    1835            0 :         return variablesJSON;
    1836            0 : }  //end serializeSequenceVariables()
    1837              : 
    1838              : struct ActiveSequenceChainGuard
    1839              : {
    1840              :         std::vector<std::string>& chain_;
    1841            0 :         ActiveSequenceChainGuard(std::vector<std::string>& chain,
    1842              :                                  const std::string&        sequenceName)
    1843            0 :             : chain_(chain)
    1844              :         {
    1845            0 :                 chain_.push_back(sequenceName);
    1846            0 :         }
    1847            0 :         ~ActiveSequenceChainGuard() { chain_.pop_back(); }
    1848              : };
    1849              : 
    1850              : }  //end anonymous namespace
    1851              : 
    1852              : //==============================================================================
    1853            0 : std::string ots_mm_udp_interface::runSequence(const std::string& sequenceName,
    1854              :                                               const std::string& variablesJSON,
    1855              :                                               unsigned int       iterationCount)
    1856              : {
    1857            0 :         std::vector<std::string> activeSequenceChain;
    1858              :         return runSequenceRecursive(sequenceName,
    1859              :                                     variablesJSON,
    1860              :                                     iterationCount,
    1861              :                                     false /*isNestedCall*/,
    1862              :                                     0 /*outerIterationIndex*/,
    1863            0 :                                     activeSequenceChain);
    1864            0 : }  //end runSequence()
    1865              : 
    1866              : //==============================================================================
    1867              : ///Expands the saved FE target (exact UID, CSV list of UIDs, or '*' filtered by
    1868              : ///FE class) to every front-end that defines the macro, requires all of them to
    1869              : ///agree on the argument lists, and returns that single signature.
    1870            0 : void ots_mm_udp_interface::resolveMacroSignature(const std::string&        feClass,
    1871              :                                                  const std::string&        feUID,
    1872              :                                                  const std::string&        macroName,
    1873              :                                                  std::vector<std::string>& inputNames,
    1874              :                                                  std::vector<std::string>& outputNames)
    1875              : {
    1876            0 :         getFrontendMacroInfo();  //init fullXML_
    1877              : 
    1878            0 :         const bool            isWildcardTarget = (feUID == "*");
    1879            0 :         std::set<std::string> explicitTargetUIDs;
    1880            0 :         if(!isWildcardTarget)
    1881              :         {
    1882            0 :                 std::vector<std::string> targetFields = getVectorFromString(feUID, {','});
    1883            0 :                 for(size_t targetIndex = 0; targetIndex < targetFields.size(); ++targetIndex)
    1884              :                 {
    1885            0 :                         size_t firstNonSpace = targetFields[targetIndex].find_first_not_of(" \t");
    1886            0 :                         size_t lastNonSpace  = targetFields[targetIndex].find_last_not_of(" \t");
    1887            0 :                         if(firstNonSpace == std::string::npos)
    1888            0 :                                 continue;
    1889            0 :                         explicitTargetUIDs.insert(targetFields[targetIndex].substr(
    1890            0 :                             firstNonSpace, lastNonSpace - firstNonSpace + 1));
    1891              :                 }
    1892            0 :         }
    1893              : 
    1894              :         struct MacroSignature
    1895              :         {
    1896              :                 std::vector<std::string> inputNames;
    1897              :                 std::vector<std::string> outputNames;
    1898              :                 std::vector<std::string> definingTargets;
    1899              :         };
    1900            0 :         std::vector<MacroSignature> distinctSignatures;
    1901              : 
    1902            0 :         std::string xmlValue;
    1903            0 :         size_t      xmlAfter = 0;
    1904            0 :         while((xmlValue = extractXmlField(fullXML_, "FEMacros", 0, xmlAfter, &xmlAfter)) !=
    1905            0 :               "")
    1906              :         {
    1907            0 :                 std::vector<std::string> xmlFields = getVectorFromString(xmlValue, {';'});
    1908            0 :                 if(xmlFields.size() < 6)
    1909            0 :                         continue;
    1910            0 :                 const std::string& candidateClass = xmlFields[2];
    1911            0 :                 const std::string& candidateUID   = xmlFields[3];
    1912              : 
    1913            0 :                 if(isWildcardTarget)
    1914              :                 {
    1915            0 :                         if(feClass != "*" && candidateClass != feClass)
    1916            0 :                                 continue;
    1917              :                 }
    1918            0 :                 else if(!explicitTargetUIDs.count(candidateUID))
    1919            0 :                         continue;
    1920              : 
    1921              :                 //i+0 = Macro Name, i+1 = Permissions, i+2 = Tooltip, i+3 = input count N,
    1922              :                 //i+4..i+3+N = input names, i+4+N = output count M, then M output names
    1923            0 :                 size_t fieldIndex = 4;
    1924            0 :                 while(xmlFields.size() > fieldIndex + 5)
    1925              :                 {
    1926            0 :                         size_t inputCount       = atoi(xmlFields[fieldIndex + 3].c_str());
    1927            0 :                         size_t outputCountIndex = fieldIndex + 4 + inputCount;
    1928            0 :                         if(outputCountIndex >= xmlFields.size())
    1929            0 :                                 break;
    1930            0 :                         size_t outputCount = atoi(xmlFields[outputCountIndex].c_str());
    1931            0 :                         if(outputCountIndex + outputCount >= xmlFields.size())
    1932            0 :                                 break;
    1933              : 
    1934            0 :                         if(xmlFields[fieldIndex] == macroName)
    1935              :                         {
    1936              :                                 std::vector<std::string> candidateInputs(
    1937            0 :                                     xmlFields.begin() + fieldIndex + 4,
    1938            0 :                                     xmlFields.begin() + outputCountIndex);
    1939              :                                 std::vector<std::string> candidateOutputs(
    1940            0 :                                     xmlFields.begin() + outputCountIndex + 1,
    1941            0 :                                     xmlFields.begin() + outputCountIndex + 1 + outputCount);
    1942              :                                 std::string targetDescription =
    1943            0 :                                     candidateUID + " (" + candidateClass + ")";
    1944              : 
    1945            0 :                                 bool matchedExisting = false;
    1946            0 :                                 for(size_t signatureIndex = 0; signatureIndex < distinctSignatures.size();
    1947              :                                     ++signatureIndex)
    1948              :                                 {
    1949            0 :                                         if(distinctSignatures[signatureIndex].inputNames == candidateInputs &&
    1950            0 :                                            distinctSignatures[signatureIndex].outputNames == candidateOutputs)
    1951              :                                         {
    1952            0 :                                                 distinctSignatures[signatureIndex].definingTargets.push_back(
    1953              :                                                     targetDescription);
    1954            0 :                                                 matchedExisting = true;
    1955            0 :                                                 break;
    1956              :                                         }
    1957              :                                 }
    1958            0 :                                 if(!matchedExisting)
    1959              :                                 {
    1960            0 :                                         MacroSignature newSignature;
    1961            0 :                                         newSignature.inputNames  = candidateInputs;
    1962            0 :                                         newSignature.outputNames = candidateOutputs;
    1963            0 :                                         newSignature.definingTargets.push_back(targetDescription);
    1964            0 :                                         distinctSignatures.push_back(newSignature);
    1965            0 :                                 }
    1966            0 :                                 break;  //one definition per FE
    1967            0 :                         }
    1968            0 :                         fieldIndex = outputCountIndex + 1 + outputCount;
    1969              :                 }
    1970            0 :         }  //end FE search loop
    1971              : 
    1972            0 :         if(distinctSignatures.empty())
    1973              :         {
    1974            0 :                 __SS__ << "No front-end matching UID '" << feUID << "' of class '" << feClass
    1975              :                        << "' defines FE Macro '" << macroName
    1976            0 :                        << "'. Check the saved sequence against the current FE Macro info."
    1977            0 :                        << __E__;
    1978            0 :                 __SS_THROW__;
    1979            0 :         }
    1980            0 :         if(distinctSignatures.size() > 1)
    1981              :         {
    1982            0 :                 __SS__ << "FE Macro '" << macroName << "' targets FE UID '" << feUID
    1983              :                        << "' of FE class '" << feClass
    1984            0 :                        << "', and those front-ends define this macro with different arguments:";
    1985            0 :                 for(size_t signatureIndex = 0; signatureIndex < distinctSignatures.size();
    1986              :                     ++signatureIndex)
    1987              :                 {
    1988            0 :                         ss << "\n  - ";
    1989            0 :                         for(size_t targetIndex = 0;
    1990            0 :                             targetIndex < distinctSignatures[signatureIndex].definingTargets.size();
    1991              :                             ++targetIndex)
    1992              :                                 ss << (targetIndex ? ", " : "")
    1993            0 :                                    << distinctSignatures[signatureIndex].definingTargets[targetIndex];
    1994            0 :                         ss << ": " << distinctSignatures[signatureIndex].inputNames.size()
    1995            0 :                            << " input(s)";
    1996            0 :                         if(distinctSignatures[signatureIndex].inputNames.size())
    1997              :                         {
    1998            0 :                                 ss << " (";
    1999            0 :                                 for(size_t inputIndex = 0;
    2000            0 :                                     inputIndex < distinctSignatures[signatureIndex].inputNames.size();
    2001              :                                     ++inputIndex)
    2002              :                                         ss << (inputIndex ? ", " : "")
    2003            0 :                                            << distinctSignatures[signatureIndex].inputNames[inputIndex];
    2004            0 :                                 ss << ")";
    2005              :                         }
    2006              :                 }
    2007              :                 ss << "\nRecord it again with a specific FE class or FE, so there is one "
    2008            0 :                       "argument list to send."
    2009            0 :                    << __E__;
    2010            0 :                 __SS_THROW__;
    2011            0 :         }
    2012              : 
    2013            0 :         inputNames  = distinctSignatures[0].inputNames;
    2014            0 :         outputNames = distinctSignatures[0].outputNames;
    2015            0 : }  //end resolveMacroSignature()
    2016              : 
    2017              : //==============================================================================
    2018            0 : std::string ots_mm_udp_interface::runSequenceRecursive(
    2019              :     const std::string&        sequenceName,
    2020              :     const std::string&        variablesJSON,
    2021              :     unsigned int              iterationCount,
    2022              :     bool                      isNestedCall,
    2023              :     unsigned int              outerIterationIndex,
    2024              :     std::vector<std::string>& activeSequenceChain)
    2025              : {
    2026            0 :         __COUT_INFO__ << "runSequence() '" << sequenceName << "' for " << iterationCount
    2027            0 :                       << " iteration(s)" << __E__;
    2028              : 
    2029            0 :         for(size_t chainIndex = 0; chainIndex < activeSequenceChain.size(); ++chainIndex)
    2030              :         {
    2031            0 :                 if(activeSequenceChain[chainIndex] == sequenceName)
    2032              :                 {
    2033            0 :                         __SS__ << "Nested MoM cycle detected, refusing to run: ";
    2034            0 :                         for(size_t describeIndex = 0; describeIndex < activeSequenceChain.size();
    2035              :                             ++describeIndex)
    2036            0 :                                 ss << activeSequenceChain[describeIndex] << " -> ";
    2037            0 :                         ss << sequenceName << __E__;
    2038            0 :                         __SS_THROW__;
    2039            0 :                 }
    2040              :         }
    2041            0 :         ActiveSequenceChainGuard activeSequenceChainGuard(activeSequenceChain, sequenceName);
    2042              : 
    2043            0 :         std::string sequenceJSON = getSequenceInfo(sequenceName);
    2044              :         __COUTV__(sequenceJSON.size());
    2045              : 
    2046            0 :         std::map<unsigned int, SequenceCommand>               commandMap;
    2047            0 :         std::vector<std::pair<std::string, SequenceVariable>> variableList;
    2048            0 :         parseSequenceJSON(sequenceJSON, commandMap, variableList);
    2049              : 
    2050            0 :         __COUT_INFO__ << "Parsed " << commandMap.size() << " step(s) and "
    2051            0 :                       << variableList.size() << " variable(s)" << __E__;
    2052              : 
    2053            0 :         std::string resolvedVariablesJSON;
    2054            0 :         if(isNestedCall)
    2055              :         {
    2056              :                 // match the browser runner: a nested MoM's own variable definitions are
    2057              :                 // discarded and the outer sequence's resolved variables apply instead
    2058            0 :                 variableList.clear();
    2059            0 :                 resolvedVariablesJSON = variablesJSON.empty() ? "{}" : variablesJSON;
    2060            0 :                 std::map<unsigned int, SequenceCommand> unusedCommands;
    2061            0 :                 parseSequenceJSON("{\"__variables__\":" + resolvedVariablesJSON + "}",
    2062              :                                   unusedCommands,
    2063              :                                   variableList);
    2064            0 :         }
    2065              :         else
    2066              :         {
    2067            0 :                 if(!variablesJSON.empty() && variablesJSON != "{}")
    2068              :                 {
    2069            0 :                         std::map<unsigned int, SequenceCommand>               unusedCommands;
    2070            0 :                         std::vector<std::pair<std::string, SequenceVariable>> overrideVariables;
    2071            0 :                         parseSequenceJSON("{\"__variables__\":" + variablesJSON + "}",
    2072              :                                           unusedCommands,
    2073              :                                           overrideVariables);
    2074            0 :                         for(size_t overrideIndex = 0; overrideIndex < overrideVariables.size();
    2075              :                             ++overrideIndex)
    2076              :                         {
    2077            0 :                                 for(size_t variableIndex = 0; variableIndex < variableList.size();
    2078              :                                     ++variableIndex)
    2079              :                                 {
    2080            0 :                                         if(variableList[variableIndex].first ==
    2081            0 :                                            overrideVariables[overrideIndex].first)
    2082              :                                         {
    2083            0 :                                                 variableList[variableIndex].second =
    2084            0 :                                                     overrideVariables[overrideIndex].second;
    2085            0 :                                                 break;
    2086              :                                         }
    2087              :                                 }
    2088              :                         }
    2089            0 :                 }
    2090            0 :                 resolvedVariablesJSON = serializeSequenceVariables(variableList);
    2091              :         }
    2092              : 
    2093            0 :         std::string  accumulatedOutput;
    2094            0 :         unsigned int totalSteps     = commandMap.size() * iterationCount;
    2095            0 :         unsigned int completedSteps = 0;
    2096              : 
    2097            0 :         for(unsigned int iterationIndex = 0; iterationIndex < iterationCount;
    2098              :             ++iterationIndex)
    2099              :         {
    2100            0 :                 if(iterationCount > 1)
    2101            0 :                         __COUT_INFO__ << "\n=== Iteration " << (iterationIndex + 1) << " of "
    2102            0 :                                       << iterationCount << " ===" << __E__;
    2103              : 
    2104              :                 // nested MoMs step variables with the outer run, as the browser runner does
    2105            0 :                 unsigned int substitutionIterationIndex =
    2106            0 :                     isNestedCall ? outerIterationIndex : iterationIndex;
    2107              : 
    2108            0 :                 for(auto& commandEntry : commandMap)
    2109              :                 {
    2110            0 :                         unsigned int     stepIndex = commandEntry.first;
    2111            0 :                         SequenceCommand& command   = commandEntry.second;
    2112            0 :                         ++completedSteps;
    2113              : 
    2114            0 :                         if(command.macroType == "__delay__")
    2115              :                         {
    2116              :                                 std::vector<std::string> delayParts =
    2117            0 :                                     getVectorFromString(command.inputArgs, {','}, {} /*whitespace*/);
    2118            0 :                                 double delaySeconds = 0;
    2119            0 :                                 if(delayParts.size() > 1)
    2120              :                                 {
    2121              :                                         try
    2122              :                                         {
    2123            0 :                                                 delaySeconds = std::stod(decodeURIComponent(delayParts[1]));
    2124              :                                         }
    2125            0 :                                         catch(...)
    2126              :                                         {
    2127            0 :                                         }
    2128              :                                 }
    2129              : 
    2130            0 :                                 __COUT_INFO__ << "  [" << completedSteps << "/" << totalSteps
    2131            0 :                                               << "] Delay " << delaySeconds << "s..." << __E__;
    2132            0 :                                 if(delaySeconds > 0)
    2133            0 :                                         usleep(static_cast<useconds_t>(delaySeconds * 1000000));
    2134            0 :                                 continue;
    2135            0 :                         }
    2136              : 
    2137            0 :                         if(command.macroType == "__mom__")
    2138              :                         {
    2139            0 :                                 std::string              innerSequenceName = command.macroName;
    2140              :                                 std::vector<std::string> momIterParts =
    2141            0 :                                     getVectorFromString(command.inputArgs, {','}, {} /*whitespace*/);
    2142            0 :                                 unsigned int innerIterationCount = 1;
    2143            0 :                                 if(momIterParts.size() > 1)
    2144              :                                 {
    2145            0 :                                         std::string innerIterationText = decodeURIComponent(momIterParts[1]);
    2146            0 :                                         if(!parseIterationCount(innerIterationText, innerIterationCount))
    2147              :                                         {
    2148            0 :                                                 __SS__ << "Invalid iteration count '" << innerIterationText
    2149              :                                                        << "' saved for nested MoM '" << innerSequenceName
    2150            0 :                                                        << "' at step " << stepIndex
    2151            0 :                                                        << ". Must be a positive integer." << __E__;
    2152            0 :                                                 __SS_THROW__;
    2153            0 :                                         }
    2154            0 :                                 }
    2155              : 
    2156            0 :                                 __COUT_INFO__ << "  [" << completedSteps << "/" << totalSteps
    2157            0 :                                               << "] Nested MoM: " << innerSequenceName << " x"
    2158            0 :                                               << innerIterationCount << __E__;
    2159              : 
    2160              :                                 std::string innerResult = runSequenceRecursive(innerSequenceName,
    2161              :                                                                                resolvedVariablesJSON,
    2162              :                                                                                innerIterationCount,
    2163              :                                                                                true /*isNestedCall*/,
    2164              :                                                                                substitutionIterationIndex,
    2165            0 :                                                                                activeSequenceChain);
    2166            0 :                                 if(!innerResult.empty())
    2167              :                                 {
    2168            0 :                                         if(!accumulatedOutput.empty())
    2169            0 :                                                 accumulatedOutput += "\n";
    2170            0 :                                         accumulatedOutput += innerResult;
    2171              :                                 }
    2172            0 :                                 continue;
    2173            0 :                         }
    2174              : 
    2175            0 :                         __COUT_INFO__ << "  [" << completedSteps << "/" << totalSteps << "] "
    2176            0 :                                       << command.macroName << " on " << command.feUID << " ("
    2177            0 :                                       << command.feClass << ")" << __E__;
    2178              : 
    2179            0 :                         std::string reconciledInputArgs  = command.inputArgs;
    2180            0 :                         std::string reconciledOutputArgs = command.outputArgs;
    2181              : 
    2182              :                         // MacroMaker public/private macros are not described by the FE Macro info,
    2183              :                         // so only FE macros can be reconciled against their current signature
    2184            0 :                         if(command.macroType == "fe")
    2185              :                         {
    2186            0 :                                 std::vector<std::string> currentInputNames, currentOutputNames;
    2187            0 :                                 resolveMacroSignature(command.feClass,
    2188            0 :                                                       command.feUID,
    2189            0 :                                                       command.macroName,
    2190              :                                                       currentInputNames,
    2191              :                                                       currentOutputNames);
    2192              : 
    2193              :                                 // always rebuild from the current names so renames are honored too;
    2194              :                                 // saved values are kept by name and new inputs receive "Default"
    2195            0 :                                 std::map<std::string, std::string> savedInputsByName;
    2196              :                                 std::vector<std::string>           savedInputPairs =
    2197            0 :                                     getVectorFromString(command.inputArgs, {';'}, {} /*whitespace*/);
    2198            0 :                                 for(size_t savedIndex = 0; savedIndex < savedInputPairs.size();
    2199              :                                     ++savedIndex)
    2200              :                                 {
    2201              :                                         std::vector<std::string> nameAndValue = getVectorFromString(
    2202            0 :                                             savedInputPairs[savedIndex], {','}, {} /*whitespace*/);
    2203            0 :                                         if(nameAndValue.size() >= 2)
    2204            0 :                                                 savedInputsByName[decodeURIComponent(nameAndValue[0])] =
    2205            0 :                                                     nameAndValue[1];
    2206            0 :                                 }
    2207              : 
    2208            0 :                                 reconciledInputArgs.clear();
    2209            0 :                                 size_t reusedSavedInputCount = 0;
    2210            0 :                                 for(size_t inputIndex = 0; inputIndex < currentInputNames.size();
    2211              :                                     ++inputIndex)
    2212              :                                 {
    2213            0 :                                         if(inputIndex > 0)
    2214            0 :                                                 reconciledInputArgs += ";";
    2215              :                                         reconciledInputArgs +=
    2216            0 :                                             encodeURIComponent(currentInputNames[inputIndex]) + ",";
    2217              :                                         auto savedEntry =
    2218            0 :                                             savedInputsByName.find(currentInputNames[inputIndex]);
    2219            0 :                                         if(savedEntry != savedInputsByName.end())
    2220              :                                         {
    2221            0 :                                                 reconciledInputArgs += savedEntry->second;
    2222            0 :                                                 ++reusedSavedInputCount;
    2223              :                                         }
    2224              :                                         else
    2225            0 :                                                 reconciledInputArgs += encodeURIComponent("Default");
    2226              :                                 }
    2227            0 :                                 if(reusedSavedInputCount != savedInputsByName.size() ||
    2228            0 :                                    reusedSavedInputCount != currentInputNames.size())
    2229            0 :                                         __COUT_INFO__ << "    Reconciled inputs: sequence saved "
    2230            0 :                                                       << savedInputsByName.size() << ", FE expects "
    2231            0 :                                                       << currentInputNames.size() << ", matched by name "
    2232            0 :                                                       << reusedSavedInputCount
    2233            0 :                                                       << " (unmatched FE inputs set to 'Default')" << __E__;
    2234              : 
    2235            0 :                                 reconciledOutputArgs.clear();
    2236            0 :                                 for(size_t outputIndex = 0; outputIndex < currentOutputNames.size();
    2237              :                                     ++outputIndex)
    2238              :                                 {
    2239            0 :                                         if(outputIndex > 0)
    2240            0 :                                                 reconciledOutputArgs += ",";
    2241              :                                         reconciledOutputArgs +=
    2242            0 :                                             encodeURIComponent(currentOutputNames[outputIndex]);
    2243              :                                 }
    2244            0 :                         }
    2245              : 
    2246              :                         std::string substitutedInputArgs = substituteSequenceVariables(
    2247            0 :                             reconciledInputArgs, variableList, substitutionIterationIndex);
    2248              : 
    2249            0 :                         std::string sendMessage = "RunFrontendMacro";
    2250            0 :                         sendMessage += ";" + command.feClass;
    2251            0 :                         sendMessage += ";" + command.feUID;
    2252            0 :                         sendMessage += ";" + command.macroType;
    2253            0 :                         sendMessage += ";" + encodeURIComponent(command.macroName);
    2254            0 :                         sendMessage += ";" + encodeURIComponent(substitutedInputArgs);
    2255            0 :                         sendMessage += ";" + encodeURIComponent(reconciledOutputArgs);
    2256            0 :                         sendMessage += std::string(";") + (command.saveOutputs == "1" ? "1" : "0");
    2257              : 
    2258              :                         __COUTV__(sendMessage.size());
    2259              : 
    2260              :                         int numbytes;
    2261            0 :                         if((numbytes = sendto(mm_sock_,
    2262            0 :                                               &(sendMessage.c_str()[0]),
    2263              :                                               sendMessage.size(),
    2264              :                                               0,
    2265            0 :                                               (struct sockaddr*)&mm_ai_addr,
    2266            0 :                                               sizeof(mm_ai_addr))) == -1)
    2267              :                         {
    2268            0 :                                 __SS__ << "Error on runSequence() sendto for step " << stepIndex << " ("
    2269            0 :                                        << command.macroName << ")!" << __E__;
    2270            0 :                                 __SS_THROW__;
    2271            0 :                         }
    2272              : 
    2273            0 :                         std::string runXML;
    2274            0 :                         receiveXmlResponse(runXML, "sequence step response", 120);
    2275              : 
    2276            0 :                         if(runXML.size() == 0 || runXML.find("Error") == 0 ||
    2277            0 :                            !hasCompleteXmlRoot(runXML))
    2278              :                         {
    2279            0 :                                 __SS__ << "Error running sequence step " << stepIndex << " ("
    2280            0 :                                        << command.macroName
    2281            0 :                                        << "): " << (runXML.size() == 0 ? "<empty>" : runXML) << __E__;
    2282            0 :                                 __SS_THROW__;
    2283            0 :                         }
    2284              : 
    2285            0 :                         size_t      parseAfter = 0;
    2286              :                         std::string errorMessage =
    2287            0 :                             extractXmlField(runXML, "Error", 0, parseAfter, &parseAfter);
    2288            0 :                         if(!errorMessage.empty())
    2289              :                         {
    2290            0 :                                 errorMessage = decodeHTMLEntities(errorMessage);
    2291            0 :                                 __SS__ << "Error at sequence step " << stepIndex << " ("
    2292            0 :                                        << command.macroName << "): " << errorMessage << __E__;
    2293            0 :                                 __SS_THROW__;
    2294            0 :                         }
    2295              : 
    2296            0 :                         parseAfter = 0;
    2297            0 :                         std::string outputName;
    2298            0 :                         while((outputName = extractXmlField(
    2299            0 :                                    runXML, "outputArgs_name", 0, parseAfter, &parseAfter)) != "")
    2300              :                         {
    2301            0 :                                 std::string outputValue = extractXmlField(
    2302            0 :                                     runXML, "outputArgs_value", 0, parseAfter, &parseAfter);
    2303            0 :                                 __COUT_INFO__ << "    Output: " << outputName << " = "
    2304            0 :                                               << decodeURIComponent(outputValue) << __E__;
    2305            0 :                                 if(!accumulatedOutput.empty())
    2306            0 :                                         accumulatedOutput += ";";
    2307            0 :                                 accumulatedOutput += outputValue;
    2308            0 :                         }
    2309            0 :                 }
    2310              :         }
    2311              : 
    2312            0 :         __COUT_INFO__ << "Sequence '" << sequenceName << "' completed." << __E__;
    2313            0 :         return accumulatedOutput;
    2314            0 : }  //end runSequenceRecursive()
    2315              : 
    2316              : //==============================================================================
    2317            0 : std::string ots_mm_udp_interface::describeSequence(const std::string& sequenceJSON)
    2318              : {
    2319            0 :         std::map<unsigned int, SequenceCommand>               commandMap;
    2320            0 :         std::vector<std::pair<std::string, SequenceVariable>> variableList;
    2321            0 :         parseSequenceJSON(sequenceJSON, commandMap, variableList);
    2322              : 
    2323            0 :         std::ostringstream description;
    2324              : 
    2325            0 :         description << "Steps (" << commandMap.size() << "):\n";
    2326              : 
    2327            0 :         for(auto& commandEntry : commandMap)
    2328              :         {
    2329            0 :                 unsigned int           stepIndex = commandEntry.first;
    2330            0 :                 const SequenceCommand& command   = commandEntry.second;
    2331              : 
    2332            0 :                 description << "  Step " << stepIndex << ": ";
    2333              : 
    2334            0 :                 if(command.macroType == "__delay__")
    2335              :                 {
    2336              :                         std::vector<std::string> delayParts =
    2337            0 :                             getVectorFromString(command.inputArgs, {','}, {} /*whitespace*/);
    2338            0 :                         std::string delayValue = "?";
    2339            0 :                         if(delayParts.size() > 1)
    2340            0 :                                 delayValue = decodeURIComponent(delayParts[1]);
    2341            0 :                         description << "[Delay " << delayValue << "s]\n";
    2342            0 :                         continue;
    2343            0 :                 }
    2344              : 
    2345            0 :                 if(command.macroType == "__mom__")
    2346              :                 {
    2347              :                         std::vector<std::string> momParts =
    2348            0 :                             getVectorFromString(command.inputArgs, {','}, {} /*whitespace*/);
    2349            0 :                         std::string innerIterations = "1";
    2350            0 :                         if(momParts.size() > 1)
    2351            0 :                                 innerIterations = decodeURIComponent(momParts[1]);
    2352            0 :                         description << "[Nested MoM: " << command.macroName << " x" << innerIterations
    2353            0 :                                     << "]\n";
    2354            0 :                         continue;
    2355            0 :                 }
    2356              : 
    2357            0 :                 description << command.macroName << "  on  " << command.feUID << " ("
    2358            0 :                             << command.feClass << ")\n";
    2359              : 
    2360              :                 std::vector<std::string> inputPairs =
    2361            0 :                     getVectorFromString(command.inputArgs, {';'}, {} /*whitespace*/);
    2362            0 :                 for(size_t inputIndex = 0; inputIndex < inputPairs.size(); ++inputIndex)
    2363              :                 {
    2364              :                         std::vector<std::string> nameAndValue =
    2365            0 :                             getVectorFromString(inputPairs[inputIndex], {','}, {} /*whitespace*/);
    2366            0 :                         if(nameAndValue.size() >= 2)
    2367            0 :                                 description << "      Input:  " << decodeURIComponent(nameAndValue[0])
    2368            0 :                                             << " = " << decodeURIComponent(nameAndValue[1]) << "\n";
    2369            0 :                         else if(nameAndValue.size() == 1 && !nameAndValue[0].empty())
    2370            0 :                                 description << "      Input:  " << decodeURIComponent(nameAndValue[0])
    2371            0 :                                             << "\n";
    2372            0 :                 }
    2373              : 
    2374              :                 std::vector<std::string> outputPairs =
    2375            0 :                     getVectorFromString(command.outputArgs, {';'}, {} /*whitespace*/);
    2376            0 :                 for(size_t outputIndex = 0; outputIndex < outputPairs.size(); ++outputIndex)
    2377              :                 {
    2378              :                         std::vector<std::string> nameAndValue =
    2379            0 :                             getVectorFromString(outputPairs[outputIndex], {','}, {} /*whitespace*/);
    2380            0 :                         if(!nameAndValue.empty() && !nameAndValue[0].empty())
    2381            0 :                                 description << "      Output: " << decodeURIComponent(nameAndValue[0])
    2382            0 :                                             << (command.saveOutputs == "1" ? " (saved)" : "") << "\n";
    2383            0 :                 }
    2384            0 :         }
    2385              : 
    2386            0 :         if(!variableList.empty())
    2387              :         {
    2388            0 :                 description << "\nInput Variables (" << variableList.size() << "):\n";
    2389              : 
    2390            0 :                 size_t maxNameWidth  = 0;
    2391            0 :                 size_t maxValueWidth = 0;
    2392            0 :                 size_t maxStepWidth  = 0;
    2393            0 :                 for(size_t variableIndex = 0; variableIndex < variableList.size();
    2394              :                     ++variableIndex)
    2395              :                 {
    2396            0 :                         if(variableList[variableIndex].first.size() > maxNameWidth)
    2397            0 :                                 maxNameWidth = variableList[variableIndex].first.size();
    2398            0 :                         if(variableList[variableIndex].second.value.size() > maxValueWidth)
    2399            0 :                                 maxValueWidth = variableList[variableIndex].second.value.size();
    2400            0 :                         if(variableList[variableIndex].second.step.size() > maxStepWidth)
    2401            0 :                                 maxStepWidth = variableList[variableIndex].second.step.size();
    2402              :                 }
    2403              : 
    2404            0 :                 for(size_t variableIndex = 0; variableIndex < variableList.size();
    2405              :                     ++variableIndex)
    2406              :                 {
    2407            0 :                         const std::string&      variableName     = variableList[variableIndex].first;
    2408            0 :                         const SequenceVariable& sequenceVariable = variableList[variableIndex].second;
    2409            0 :                         description << "  #" << variableIndex << "  " << std::left
    2410              :                                     << std::setw(maxNameWidth) << variableName
    2411              :                                     << ":  value=" << std::setw(maxValueWidth)
    2412            0 :                                     << sequenceVariable.value << "  step=" << std::setw(maxStepWidth)
    2413            0 :                                     << sequenceVariable.step << "  max=" << sequenceVariable.max
    2414            0 :                                     << "\n";
    2415              :                 }
    2416              :         }
    2417              :         else
    2418              :         {
    2419            0 :                 description << "\nNo Input Variables.\n";
    2420              :         }
    2421              : 
    2422            0 :         return description.str();
    2423            0 : }  //end describeSequence()
        

Generated by: LCOV version 2.0-1