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 : {"<", ">", "&", """, "'", " ", "
"});
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()
|