DeviceAtlas C++ Api documentation
devatlas_priv.h
1 #ifndef devatlas_priv_h
2 #define devatlas_priv_h
3 #include <mtld/devatlas.h>
4 #include <mtld/common_priv.h>
5 #include <limits>
6 
7 namespace Mobi { namespace Mtld { namespace Da {
8 
9 
10 struct LocalProperty {
11  Property property;
12  const Value *value;
13 };
14 
15 static constexpr size_t LOCAL_PROP_COUNT=16;
17  size_t count;
18  LocalProperty properties[LOCAL_PROP_COUNT];
19  LocalProperties *next;
20 public:
21  LocalProperty *get(size_t);
22  LocalProperty *get(const char *);
23  LocalProperty *allocNew(DeliveryContext *ctx, const char *, PropertyType);
25  : count(0)
26  , next(0)
27  {
28  }
29  size_t size() const;
30 };
31 
32 class NodeData;
33 
34 /*
35  * a set of properties
36  * This is only used if the mask and/or conflicts processing is in use
37  */
38 class propset {
39  typedef unsigned int I;
40  I *vals;
41  static const int mask = sizeof (I) * 8 - 1;
42 
43  static unsigned idxOf(unsigned item) {
44  return item / sizeof(I);
45  }
46 
47 public:
48  propset(size_t maxcount = 200) {
49  size_t count = idxOf(maxcount | mask) + 1;
50  vals = new I[count];
51  memset(vals, 0, sizeof (I) * count);
52  }
53  ~propset() {
54  delete[] vals;
55  }
56 
57  bool find(size_t v) const {
58  return vals[idxOf(v)] & (1 << (v & mask));
59  }
60 
61  bool end() const {
62  return false;
63  }
64 
65  void insert(unsigned v) {
66  vals[idxOf(v)] |= 1 << (v & mask);
67  }
68 };
69 
70 /*
71  * Node:
72  * Each character in the tree is represented by a node.
73  *
74  * Note that the empty nodes (those with null data fields) are represented in the JSON data by having
75  * children with arbitrary length characters, where all but the last
76  * character represents an empty child. We unfold these, so the lookups
77  * can be carried out more simply.
78  * For Example, the JSON data might have a node at "M" with a child at
79  * "ozilla". We represent this as a string of descendents
80  * M, o, z, i, l, l, a.
81  * The node objects are small enough that this makes the time/space
82  * tradeoff well worth it.
83  */
84 
85 class Node {
86  // The JSON parsers for this type are allowed at its internals.
87  friend class NodeParse;
88  friend struct ChildrenParse;
89 
90  // Children, indexed by the extra character at the end of their substring
91 public:
93  childmap children;
94  Node();
95  ~Node();
96  std::shared_ptr<NodeData> data;
97 
98  /* recursive search operations on the node: see below for details. */
99  template <typename T> void visitR(T &t, char key) const;
100 
101  // has this node any children?
102  bool isLeaf() const { return children.empty(); }
103 };
104 
105 class NodeData {
106  friend class NodeParse;
107  friend class Node;
108  attrvec attrs;
109  propset conflicts;
110  unsigned int nextTree;
111  unsigned int matchedDevices;
112  int resetWalkId;
113 public:
114  std::unique_ptr<std::vector<size_t> > replacementStrings;
115  std::unique_ptr<std::vector<size_t> > excls;
116  NodeData() : nextTree(0), matchedDevices(0), resetWalkId(-1) {}
117  const attrvec &getAttrs() const { return attrs; }
118  attrvec &getAttrs() { return attrs; }
119  const propset &getConflicts() const { return conflicts; }
120  propset &getConflicts() { return conflicts; }
121  unsigned getMatchedDeviceCount() const { return matchedDevices; }
122  unsigned getNextTreeMatchCount() const { return nextTree; }
123  int getResetWalkId() const { return resetWalkId; }
124 };
125 
126 /*
127  * visit this node, and all its children recursively, calling "pre"
128  * and "post" using a false return value to terminate the search.
129  * pre and post are passed the current node, and the character which
130  * identifies it in its parent.
131  */
132 template <typename T> void
133 Node::visitR(T &t, char key) const
134 {
135  if (!t.pre(key, this))
136  return;
137  for (childmap::const_iterator it = children.begin(); it != children.end(); ++it)
138  if ((*it).second)
139  (*it).second->visitR(t, (*it).first);
140  t.post(key, this);
141 }
142 
143 /*
144  * Searches for the substring of str from offset in the subtree of the
145  * current node.
146  * The search continues as follows:
147  * o call the supplied functor, and terminate the search if it returns false.
148  * o if we have reached the end of the string, return false.
149  * o find the child at the index specified for the character at str[offset]
150  * o if it exists, cal findR on it recursively, advancing offset.
151  */
152 
153 
154 
155 /*
156  * Utility to call find and visit on a specific tree in the atlas.
157  * Eliminates the "offset" parameter for clarity.
158  */
159 template <typename T, typename Allocator> size_t
160 dafind(const DeviceAtlas &atlas, T &t, TreeID tree, Allocator &a, const char *rostr, char **matched, char **unmatched)
161 {
162  const Node *n = atlas.roots[tree];
163  size_t len = strlen(rostr);
164  char *str;
165  a.allocate(str, len + 1);
166  strlcpy(str, rostr, len + 1);
167 
168  if (matched)
169  a.allocate(*matched, atlas.maxDepth + 1);
170 
171  Node::childmap::const_iterator it;
172  size_t offset;
173  size_t dataoff = 0;
174  char *pathp = matched ? *matched : 0;
175 
176  for (offset=0;;++offset) {
177  NodeData *data = n->data.get();
178  // Don't do any heavy lifting if the node has no associated data.
179  if (data) {
180  if (data->getResetWalkId() > -1) {
181  char *pstr;
182  std::string resetWalk = atlas.getResetWalkString((size_t)data->getResetWalkId());
183  const char *resetWalkString = resetWalk.c_str();
184  size_t resetWalkStringLen = resetWalk.size();
185  size_t ostrlen = strlen(str + offset);
186  size_t pcount = atlas.getPropertyCount();
187  a.allocate(pstr, resetWalkStringLen + ostrlen + 1);
188  memcpy(pstr, resetWalkString, resetWalkStringLen);
189  memcpy(pstr + resetWalkStringLen, str + offset, ostrlen + 1);
190  t.rfskp = 0;
191  if (matched)
192  *matched = 0;
193  if (unmatched)
194  *unmatched = 0;
195 
196  for (size_t i = 0; i < pcount; i ++)
197  t.setProperty(i, 0);
198 
199  return dafind(atlas, t, tree, a, pstr, matched, unmatched);
200  }
201  dataoff = offset;
202  if (atlas.getpropId()) {
203  if (t.rfskp && data->getAttrs().find(atlas.getpropId()->index) != data->getAttrs().end())
204  t.rfskp = 0;
205  if (data->excls)
206  t.rfskp = data->excls.get();
207  }
208  if (!t(*n, str, offset) || str[offset] == 0)
209  break;
210  if ((atlas.flags & DeviceAtlas::NoREFold) == 0) {
211  const auto repls = data->replacementStrings.get();
212  if (repls)
213  atlas.doReplacements(repls, str, offset);
214  }
215  }
216  if (pathp)
217  *pathp++ = str[offset];
218  it = n->children.find(str[offset]);
219  if (it == n->children.end())
220  break;
221  n = (*it).second;
222  if (n == 0)
223  break;
224  }
225  if (matched)
226  (*matched)[dataoff] = 0;
227  if (unmatched)
228  *unmatched = str + dataoff;
229 
230  return offset;
231 }
232 
233 template <typename T> void
234 visit(const DeviceAtlas &atlas, T &t, TreeID tree)
235 {
236  atlas.roots[tree]->visitR(t, '.');
237 }
238 
239 // 3.1 extensions: "UAR"
240 class Rule {
241 public:
242  int property;
243  int regex;
244  int matchpos;
245  int value;
246  Rule()
247  : property(-1)
248  , regex(-1)
249  , matchpos(-1)
250  , value(-1)
251  {
252  }
253 };
254 
255 enum REType {
256  RE_REFINE = 0,
257  RE_SEARCH = 1,
258  RE_TYPES = 2
259 };
260 
261 class RuleSet {
262 public:
263  int re[RE_TYPES];
264  std::vector<Rule *> rules;
265  RuleSet();
266  ~RuleSet();
267 };
268 
269 class RuleGroup {
270 // all these properties must be true for a UA to consider it for this RG
271 public:
272  std::unordered_map<int, int> selectionCriteria; // maps from property to value
273  std::vector<RuleSet *> ruleSets;
274  RuleGroup();
275  ~RuleGroup();
276  const RuleSet *findSet(const UAR *, int, const char *text) const;
277  bool matchProperties(const DeliveryContext *ctx) const;
278 };
279 
280 class UAR {
281 public:
282  TaggedRegexMap allRegexes;
283  const std::vector<REInfo *> *langRegexes;
284  const std::vector<REInfo *> *defaultRegexes;
285  std::vector<int> skipProps; // if these properties are true, then don't apply UAR rules
286  std::vector<RuleGroup *> groups;
287  const REInfo *getRE(size_t re) const;
288  UAR();
289  ~UAR();
290 };
291 
292 enum Operator {
293  Equal,
294  NotEqual
295 };
296 
297 template <typename T> bool
298 testop(Operator op, const T &lhs, const T &rhs)
299 {
300  switch (op) {
301  case Equal: return lhs == rhs;
302  case NotEqual: return lhs != rhs;
303  default: { Exception err; err << "\"unknown\""; throw err; }
304  }
305 }
306 
307 
308 struct CPRule {
309  size_t property;
310  Value *value;
311 
312  Operator op;
313  bool test(const DeviceAtlas &da, const Value *candidate) const {
314  const Property &p = da.getPropertyDescr(property);
315  switch (p.type) {
316  case Bool: return testop<bool>(op, *value, *candidate);
317  case Integer: return testop<long>(op, *value, *candidate);
318  case String: return testop<std::string>(op, *value, *candidate);
319  default: throw Exception("unhandled property type");
320  }
321  }
322 };
323 
324 struct CPRuleGroup {
325  const char *ua;
326  typedef std::vector<CPRule> Rules;
327  Rules rules;
328  attrvec properties;
329  attrvec extraproperties;
330  CPRuleGroup() : ua(0), rfskp(0) {}
331  void setProperty(size_t, const Value *) {}
332  bool hasProperty(const Property &) { return false; }
333  bool match(const DeliveryContext &ctx) const;
334  bool operator() (const Node &node, const char *, size_t);
335  std::vector<size_t> *rfskp;
336 };
337 
338 std::ostream &operator<<(std::ostream &os, const CPRuleGroup &);
339 std::ostream &operator<<(std::ostream &os, const CPRule &);
340 std::ostream &operator<<(std::ostream &os, const Operator &);
341 
342 class CPR;
344  std::vector<const Value *> props;
345  DeliveryContext &ctx;
346  ClientPropertiesReceiver(DeliveryContext &ctx_) : ctx(ctx_) {}
347  void operator () (PropertyType type, const char *, const char *);
348 };
349 
350 /*
351  * q's Accept Language preference handling
352  * The first encountered is taken
353  */
354 
356  const char *code;
357  size_t len;
358  size_t minuspos;
359  float q;
360 };
361 
362 /*
363  * language value sanity check
364  */
365 
366 inline size_t
367 decodeLanguage(char *language, size_t *minuspos) {
368  *minuspos = 0;
369  if (*language == '*')
370  return 1;
371  char *p = language;
372  char *m = NULL;
373  size_t mlen;
374  if ((m = strchr(language, '-')) == NULL)
375  m = strchr(language, '_');
376 
377  if (m) {
378  mlen = strlen(m);
379  if (mlen == 1 || mlen > 3)
380  return 0;
381  *minuspos = m - p;
382  char *wp = p;
383  wp[*minuspos] = 0;
384  if (strlen(wp) > 2)
385  return 0;
386  p[*minuspos] = '-';
387  } else {
388  if (strlen(language) > 2)
389  return 0;
390  }
391  size_t mp = 0;
392  for(; *p; p++, mp++) {
393  if ((*p != '-' && !isalpha(*p) && !isspace(*p)))
394  break;
395  if (*minuspos > 0 && mp > *minuspos)
396  *p = toupper(*p);
397  }
398 
399  return mp;
400 }
401 
402 /*
403  * language preference handling
404  * the one chosen is related to q ordering (implicity or explicity)
405  */
406 template <typename Allocator>
407 inline const char*
408 stripQlanguage(const char *language, size_t *minuspos, Allocator &a) {
409  char *cp;
410  size_t plen = strlen(language);
411  a.allocate(cp, plen + 1);
412  strlcpy(cp, language, plen + 1);
413 
414  char *token, *ptr;
415  AcceptLanguagePreference apmax = {0};
416 
417  while((token = strtok_r(cp, ",", &ptr)) != NULL) {
418  if (cp)
419  cp = 0;
420 
421  while (isspace(*token) && token ++);
422 
423  float q = 1.0f;
424  char *qstr, *code;
425  size_t codelen = 0;
426 
427  if ((qstr = strstr(token, ";")) != NULL) {
428  codelen = (qstr - token);
429  token[codelen] = 0;
430  if (qstr ++) {
431  while (isspace(*qstr) && qstr ++);
432  if (strncmp(qstr, "q=", 2) == 0) {
433  qstr += 2;
434  while (isspace(*qstr) && qstr ++);
435  if (*qstr != '*') {
436  float qtmp = strtod(qstr, 0);
437 
438  if (qtmp > 0.0f)
439  q = qtmp;
440  } else {
441  q = 0.0f;
442  }
443  }
444  }
445  }
446 
447  size_t tminuspos = 0;
448 
449  if ((codelen = decodeLanguage(token, &tminuspos)) > 0) {
450  a.allocate(code, codelen + 1);
451  strlcpy(code, token, codelen + 1);
452 
453  if (q > apmax.q) {
454  apmax.q = q;
455  apmax.len = codelen;
456  apmax.minuspos = tminuspos;
457  apmax.code = code;
458  } else if (q == apmax.q) {
459  if (apmax.code && tminuspos > 0 &&
460  strncmp(apmax.code, code, tminuspos) == 0 &&
461  codelen > apmax.len) {
462  apmax.len = codelen;
463  apmax.minuspos = tminuspos;
464  apmax.code = code;
465  }
466  }
467  }
468  }
469 
470  const char *finallanguage = (apmax.code ? (*apmax.code == '*' ? 0 : apmax.code) : 0);
471  *minuspos = (apmax.code ? apmax.minuspos : 0);
472 
473  return finallanguage;
474 }
475 
476 template <typename T, typename Allocator>
477 void decodeHeuristic(const char *p, T &cprec, Allocator &a) // XXX: template on ClientPropertiesReceiver
478 {
479  PropertyType type;
480  char c;
481  char *cp;
482  size_t plen = strlen(p);
483  a.allocate(cp, plen + 1);
484  strlcpy(cp, p, plen + 1);
485 
486  using namespace Mobi::Mtld::Util;
487 
488  char *token, *ptr;
489 
490  while ((token = strtok_r(cp, "|", &ptr)) != NULL) {
491  if (cp)
492  cp = 0;
493  char key[124], *kp = key;
494  char escapedvalue[124], *ep = escapedvalue;
495  switch ((c = nextchar((const char *&)token, Util::SKIPQUOTE | Util::NULLOK))) {
496  case 'b': type = Bool; break;
497  case 'i': type = Integer; break;
498  case 's': type = String; break;
499  case 0: return; // that's all folks.
500  default: cprec.props.clear(); return;
501  }
502 
503  char *sptr, *tmpkp, *tmpvp;
504  tmpkp = strtok_r(token, ":", &sptr);
505  tmpvp = strtok_r(NULL, ":", &sptr);
506  if (!tmpvp)
507  continue;
508 
509  size_t m = 0;
510  char *k = kp;
511  for (char *p = tmpkp; m++ < (sizeof(key)) - 1 && *p; ++p)
512  *k++ = *p;
513  *k = 0;
514 
515  m = 0;
516  for (const char *p = tmpvp; m++ < (sizeof(escapedvalue) - 1) && *p; ++p) {
517  switch (*p) {
518  case '\"': ep += snprintf(ep, sizeof("&quote;") + 1, "&quote;"); break;
519  case '&': ep += snprintf(ep, sizeof("&amp;") + 1, "&amp;"); break;
520  case '<': ep += snprintf(ep, sizeof("&lt;") + 1, "&lt;"); break;
521  case '>': ep += snprintf(ep, sizeof("&gt;") + 1, "&gt;"); break;
522  default: *ep++ = *p; break;
523  }
524  }
525  *ep = 0;
526 
527  const char * i;
528  for (i = kp; *i && (isalnum(*i) || *i == '.'); ++i);
529 
530  if (*i == 0 && i != kp)
531  cprec(type, kp, escapedvalue);
532  }
533 }
534 
535 template<typename T, typename Allocator>
536 void
537 dafindDesktopOsVer(T &t, Allocator &a, const char *oname, const char *plt, const char *oversion) {
538  const char *ovname = 0;
539  Value *ptroversion, *ptroname, *ptrovname;
540  auto olen = strlen(oversion);
541  if (strcmp(plt, "Windows") == 0) {
542  char *e;
543  char *foversion;
544  foversion = t.strdup(oversion);
545  while (foversion && *foversion == '0') foversion++;
546  if (!foversion)
547  return;
548  auto v = strtol(foversion, &e, 0);
549  if (*e != '\0' && *e != '.')
550  return;
551  switch (v) {
552  case 15:
553  ovname = "22H2";
554  break;
555  case 14:
556  case 10:
557  ovname = "21H2";
558  break;
559  case 13:
560  ovname = "Dev";
561  break;
562  case 8:
563  ovname = "1909";
564  break;
565  case 7:
566  ovname = "1809";
567  break;
568  case 6:
569  ovname = "1803";
570  break;
571  case 5:
572  ovname = "1709";
573  break;
574  case 4:
575  ovname = "1703";
576  break;
577  case 3:
578  ovname = "1607";
579  break;
580  case 2:
581  ovname = "1511";
582  break;
583  case 1:
584  ovname = "1507";
585  break;
586  }
587 
588  if (v >= 13) {
589  oname = "Windows 11";
590  } else if (v >= 1) {
591  oname = "Windows 10";
592  } else if (strcmp(oversion, "0.3.0") == 0) {
593  oname = "Windows 8.1";
594  ovname = "NT 6.3";
595  } else if (strcmp(oversion, "0.2.0") == 0) {
596  oname = "Windows 8";
597  ovname = "NT 6.2";
598  } else if (strcmp(oversion, "0.1.0") == 0) {
599  oname = "Windows 7";
600  ovname = "NT 6.1";
601  }
602  } else if (strcmp(plt, "macOS") == 0) {
603  oname = plt;
604  if (strncmp(oversion, "11", 2) == 0) {
605  ovname = "Big Sur";
606  } else if (strncmp(oversion, "12", 2) == 0) {
607  ovname = "Monterey";
608  } else if (strncmp(oversion, "13", 2) == 0) {
609  ovname = "Ventura";
610  } else if (strncmp(oversion, "10.12", 5) == 0) {
611  ovname = "Sierra";
612  } else if (strncmp(oversion, "10.13", 5) == 0) {
613  ovname = "High Sierra";
614  } else if (strncmp(oversion, "10.14", 5) == 0) {
615  ovname = "Mojave";
616  } else if (strncmp(oversion, "10.15", 5) == 0) {
617  ovname = "Catalina";
618  } else if (strncmp(oversion, "10.11", 5) == 0) {
619  ovname = "El Capitan";
620  oname = "OS X";
621  }
622  }
623 
624  a.allocate(ptroname);
625  ptroname->u.strval = t.strdup(oname);
626  ptroname->type = String;
627  t.setProperty(t.getAtlas().getpropOname()->index, ptroname);
628  if (ovname) {
629  a.allocate(ptrovname);
630  ptrovname->u.strval = t.strdup(ovname);
631  ptrovname->type = String;
632  t.setProperty(t.getAtlas().getpropOversionNm()->index, ptrovname);
633  }
634  if (olen > 0) {
635  a.allocate(ptroversion);
636  ptroversion->u.strval = t.strdup(oversion);
637  ptroversion->type = String;
638  t.setProperty(t.getAtlas().getpropOversion()->index, ptroversion);
639  }
640 }
641 
642 class CPR {
643 public:
644  typedef std::vector<CPRuleGroup> RuleGroups;
645  RuleGroups ruleGroups;
646  const CPRuleGroup *getMatchingCPRules(const DeliveryContext &ctx) const;
647 };
648 
649 
650 }}}
651 #endif // devatlas_priv_h
Definition: devatlas_priv.h:308
Definition: devatlas.h:108
Definition: devatlas_priv.h:642
Definition: exception.h:16
Definition: devatlas_priv.h:38
Definition: dajson.cpp:244
Definition: util.h:40
Definition: dajson.cpp:187
Definition: common.h:43
Definition: devatlas_priv.h:343
Definition: devatlas_priv.h:85
Definition: devatlas_priv.h:105
Definition: common.h:192
Definition: devatlas_priv.h:240
Definition: devatlas_priv.h:10
Definition: devatlas_priv.h:16
Definition: devatlas_priv.h:261
Definition: devatlas_priv.h:280
Definition: devatlas_priv.h:355
Definition: binary.h:13
Definition: devatlas.h:229
Definition: devatlas_priv.h:269
Definition: devatlas_priv.h:324
Definition: regex.h:10
Definition: common.h:142