Fix: config: check for node type before checking props
[deliverable/barectf.git] / barectf / config.py
1 # The MIT License (MIT)
2 #
3 # Copyright (c) 2015 Philippe Proulx <pproulx@efficios.com>
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 # THE SOFTWARE.
22
23 from barectf import metadata
24 import collections
25 import datetime
26 import barectf
27 import enum
28 import yaml
29 import uuid
30 import copy
31 import re
32
33
34 class ConfigError(RuntimeError):
35 def __init__(self, msg, prev=None):
36 super().__init__(msg)
37 self._prev = prev
38
39 @property
40 def prev(self):
41 return self._prev
42
43
44 class Config:
45 def __init__(self, version, prefix, metadata):
46 self.prefix = prefix
47 self.version = version
48 self.metadata = metadata
49
50 def _validate_metadata(self, meta):
51 try:
52 validator = _MetadataTypesHistologyValidator()
53 validator.validate(meta)
54 validator = _MetadataDynamicTypesValidator()
55 validator.validate(meta)
56 validator = _MetadataSpecialFieldsValidator()
57 validator.validate(meta)
58 except Exception as e:
59 raise ConfigError('metadata error', e)
60
61 try:
62 validator = _BarectfMetadataValidator()
63 validator.validate(meta)
64 except Exception as e:
65 raise ConfigError('barectf metadata error', e)
66
67 def _augment_metadata_env(self, meta):
68 env = meta.env
69
70 env['domain'] = 'bare'
71 env['tracer_name'] = 'barectf'
72 version_tuple = barectf.get_version_tuple()
73 env['tracer_major'] = version_tuple[0]
74 env['tracer_minor'] = version_tuple[1]
75 env['tracer_patch'] = version_tuple[2]
76 env['barectf_gen_date'] = str(datetime.datetime.now().isoformat())
77
78 @property
79 def version(self):
80 return self._version
81
82 @version.setter
83 def version(self, value):
84 self._version = value
85
86 @property
87 def metadata(self):
88 return self._metadata
89
90 @metadata.setter
91 def metadata(self, value):
92 self._validate_metadata(value)
93 self._augment_metadata_env(value)
94 self._metadata = value
95
96 @property
97 def prefix(self):
98 return self._prefix
99
100 @prefix.setter
101 def prefix(self, value):
102 if not is_valid_identifier(value):
103 raise ConfigError('prefix must be a valid C identifier')
104
105 self._prefix = value
106
107
108 def _is_assoc_array_prop(node):
109 return isinstance(node, dict)
110
111
112 def _is_array_prop(node):
113 return isinstance(node, list)
114
115
116 def _is_int_prop(node):
117 return type(node) is int
118
119
120 def _is_str_prop(node):
121 return type(node) is str
122
123
124 def _is_bool_prop(node):
125 return type(node) is bool
126
127
128 def _is_valid_alignment(align):
129 return ((align & (align - 1)) == 0) and align > 0
130
131
132 def _byte_order_str_to_bo(bo_str):
133 bo_str = bo_str.lower()
134
135 if bo_str == 'le':
136 return metadata.ByteOrder.LE
137 elif bo_str == 'be':
138 return metadata.ByteOrder.BE
139
140
141 def _encoding_str_to_encoding(encoding_str):
142 encoding_str = encoding_str.lower()
143
144 if encoding_str == 'utf-8' or encoding_str == 'utf8':
145 return metadata.Encoding.UTF8
146 elif encoding_str == 'ascii':
147 return metadata.Encoding.ASCII
148 elif encoding_str == 'none':
149 return metadata.Encoding.NONE
150
151
152 _re_iden = re.compile(r'^[a-zA-Z][a-zA-Z0-9_]*$')
153 _ctf_keywords = set([
154 'align',
155 'callsite',
156 'clock',
157 'enum',
158 'env',
159 'event',
160 'floating_point',
161 'integer',
162 'stream',
163 'string',
164 'struct',
165 'trace',
166 'typealias',
167 'typedef',
168 'variant',
169 ])
170
171
172 def is_valid_identifier(iden):
173 if not _re_iden.match(iden):
174 return False
175
176 if _re_iden in _ctf_keywords:
177 return False
178
179 return True
180
181
182 def _get_first_unknown_prop(node, known_props):
183 for prop_name in node:
184 if prop_name in known_props:
185 continue
186
187 return prop_name
188
189
190 # This validator validates the configured metadata for barectf specific
191 # needs.
192 #
193 # barectf needs:
194 #
195 # * all header/contexts are at least byte-aligned
196 # * all integer and floating point number sizes to be <= 64
197 # * no inner structures, arrays, or variants
198 class _BarectfMetadataValidator:
199 def __init__(self):
200 self._type_to_validate_type_func = {
201 metadata.Integer: self._validate_int_type,
202 metadata.FloatingPoint: self._validate_float_type,
203 metadata.Enum: self._validate_enum_type,
204 metadata.String: self._validate_string_type,
205 metadata.Struct: self._validate_struct_type,
206 metadata.Array: self._validate_array_type,
207 metadata.Variant: self._validate_variant_type,
208 }
209
210 def _validate_int_type(self, t, entity_root):
211 if t.size > 64:
212 raise ConfigError('integer type\'s size must be lesser than or equal to 64 bits')
213
214 def _validate_float_type(self, t, entity_root):
215 if t.size > 64:
216 raise ConfigError('floating point number type\'s size must be lesser than or equal to 64 bits')
217
218 def _validate_enum_type(self, t, entity_root):
219 if t.value_type.size > 64:
220 raise ConfigError('enumeration type\'s integer type\'s size must be lesser than or equal to 64 bits')
221
222 def _validate_string_type(self, t, entity_root):
223 pass
224
225 def _validate_struct_type(self, t, entity_root):
226 if not entity_root:
227 raise ConfigError('inner structure types are not supported as of this version')
228
229 for field_name, field_type in t.fields.items():
230 if entity_root and self._cur_entity is _Entity.TRACE_PACKET_HEADER:
231 if field_name == 'uuid':
232 # allow
233 continue
234
235 try:
236 self._validate_type(field_type, False)
237 except Exception as e:
238 raise ConfigError('in structure type\'s field "{}"'.format(field_name), e)
239
240 def _validate_array_type(self, t, entity_root):
241 raise ConfigError('array types are not supported as of this version')
242
243 def _validate_variant_type(self, t, entity_root):
244 raise ConfigError('variant types are not supported as of this version')
245
246 def _validate_type(self, t, entity_root):
247 self._type_to_validate_type_func[type(t)](t, entity_root)
248
249 def _validate_entity(self, t):
250 if t is None:
251 return
252
253 # make sure entity is byte-aligned
254 if t.align < 8:
255 raise ConfigError('type\'s alignment must be at least byte-aligned')
256
257 # make sure entity is a structure
258 if type(t) is not metadata.Struct:
259 raise ConfigError('expecting a structure type')
260
261 # validate types
262 self._validate_type(t, True)
263
264 def _validate_entities_and_names(self, meta):
265 self._cur_entity = _Entity.TRACE_PACKET_HEADER
266
267 try:
268 self._validate_entity(meta.trace.packet_header_type)
269 except Exception as e:
270 raise ConfigError('invalid trace packet header type', e)
271
272 for stream_name, stream in meta.streams.items():
273 if not is_valid_identifier(stream_name):
274 raise ConfigError('stream name "{}" is not a valid C identifier'.format(stream_name))
275
276 self._cur_entity = _Entity.STREAM_PACKET_CONTEXT
277
278 try:
279 self._validate_entity(stream.packet_context_type)
280 except Exception as e:
281 raise ConfigError('invalid packet context type in stream "{}"'.format(stream_name), e)
282
283 self._cur_entity = _Entity.STREAM_EVENT_HEADER
284
285 try:
286 self._validate_entity(stream.event_header_type)
287 except Exception as e:
288 raise ConfigError('invalid event header type in stream "{}"'.format(stream_name), e)
289
290 self._cur_entity = _Entity.STREAM_EVENT_CONTEXT
291
292 try:
293 self._validate_entity(stream.event_context_type)
294 except Exception as e:
295 raise ConfigError('invalid event context type in stream "{}"'.format(stream_name), e)
296
297 try:
298 for ev_name, ev in stream.events.items():
299 if not is_valid_identifier(ev_name):
300 raise ConfigError('event name "{}" is not a valid C identifier'.format(ev_name))
301
302 self._cur_entity = _Entity.EVENT_CONTEXT
303
304 try:
305 self._validate_entity(ev.context_type)
306 except Exception as e:
307 raise ConfigError('invalid context type in event "{}"'.format(ev_name), e)
308
309 self._cur_entity = _Entity.EVENT_PAYLOAD
310
311 if ev.payload_type is None:
312 raise ConfigError('missing payload type in event "{}"'.format(ev_name), e)
313
314 try:
315 self._validate_entity(ev.payload_type)
316 except Exception as e:
317 raise ConfigError('invalid payload type in event "{}"'.format(ev_name), e)
318
319 if not ev.payload_type.fields:
320 raise ConfigError('empty payload type in event "{}"'.format(ev_name), e)
321 except Exception as e:
322 raise ConfigError('invalid stream "{}"'.format(stream_name), e)
323
324 def validate(self, meta):
325 self._validate_entities_and_names(meta)
326
327
328 # This validator validates special fields of trace, stream, and event
329 # types. For example, if checks that the "stream_id" field exists in the
330 # trace packet header if there's more than one stream, and much more.
331 class _MetadataSpecialFieldsValidator:
332 def _validate_trace_packet_header_type(self, t):
333 # needs "stream_id" field?
334 if len(self._meta.streams) > 1:
335 # yes
336 if t is None:
337 raise ConfigError('need "stream_id" field in trace packet header type, but trace packet header type is missing')
338
339 if type(t) is not metadata.Struct:
340 raise ConfigError('need "stream_id" field in trace packet header type, but trace packet header type is not a structure type')
341
342 if 'stream_id' not in t.fields:
343 raise ConfigError('need "stream_id" field in trace packet header type')
344
345 # validate "magic" and "stream_id" types
346 if type(t) is not metadata.Struct:
347 return
348
349 for i, (field_name, field_type) in enumerate(t.fields.items()):
350 if field_name == 'magic':
351 if type(field_type) is not metadata.Integer:
352 raise ConfigError('"magic" field in trace packet header type must be an integer type')
353
354 if field_type.signed or field_type.size != 32:
355 raise ConfigError('"magic" field in trace packet header type must be a 32-bit unsigned integer type')
356
357 if i != 0:
358 raise ConfigError('"magic" field must be the first trace packet header type\'s field')
359 elif field_name == 'stream_id':
360 if type(field_type) is not metadata.Integer:
361 raise ConfigError('"stream_id" field in trace packet header type must be an integer type')
362
363 if field_type.signed:
364 raise ConfigError('"stream_id" field in trace packet header type must be an unsigned integer type')
365 elif field_name == 'uuid':
366 if self._meta.trace.uuid is None:
367 raise ConfigError('"uuid" field in trace packet header type specified, but no trace UUID provided')
368
369 if type(field_type) is not metadata.Array:
370 raise ConfigError('"uuid" field in trace packet header type must be an array')
371
372 if field_type.length != 16:
373 raise ConfigError('"uuid" field in trace packet header type must be an array of 16 bytes')
374
375 element_type = field_type.element_type
376
377 if type(element_type) is not metadata.Integer:
378 raise ConfigError('"uuid" field in trace packet header type must be an array of 16 bytes')
379
380 if element_type.size != 8:
381 raise ConfigError('"uuid" field in trace packet header type must be an array of 16 bytes')
382
383 if element_type.align != 8:
384 raise ConfigError('"uuid" field in trace packet header type must be an array of 16 byte-aligned bytes')
385
386 def _validate_trace(self, meta):
387 self._validate_trace_packet_header_type(meta.trace.packet_header_type)
388
389 def _validate_stream_packet_context(self, stream):
390 t = stream.packet_context_type
391
392 if type(t) is None:
393 return
394
395 if type(t) is not metadata.Struct:
396 return
397
398 # "timestamp_begin", if exists, is an unsigned integer type,
399 # mapped to a clock
400 if 'timestamp_begin' in t.fields:
401 ts_begin = t.fields['timestamp_begin']
402
403 if type(ts_begin) is not metadata.Integer:
404 raise ConfigError('"timestamp_begin" field in stream packet context type must be an integer type')
405
406 if ts_begin.signed:
407 raise ConfigError('"timestamp_begin" field in stream packet context type must be an unsigned integer type')
408
409 if not ts_begin.property_mappings:
410 raise ConfigError('"timestamp_begin" field in stream packet context type must be mapped to a clock')
411
412 # "timestamp_end", if exists, is an unsigned integer type,
413 # mapped to a clock
414 if 'timestamp_end' in t.fields:
415 ts_end = t.fields['timestamp_end']
416
417 if type(ts_end) is not metadata.Integer:
418 raise ConfigError('"timestamp_end" field in stream packet context type must be an integer type')
419
420 if ts_end.signed:
421 raise ConfigError('"timestamp_end" field in stream packet context type must be an unsigned integer type')
422
423 if not ts_end.property_mappings:
424 raise ConfigError('"timestamp_end" field in stream packet context type must be mapped to a clock')
425
426 # "timestamp_begin" and "timestamp_end" exist together
427 if (('timestamp_begin' in t.fields) ^ ('timestamp_end' in t.fields)):
428 raise ConfigError('"timestamp_begin" and "timestamp_end" fields must be defined together in stream packet context type')
429
430 # "events_discarded", if exists, is an unsigned integer type
431 if 'events_discarded' in t.fields:
432 events_discarded = t.fields['events_discarded']
433
434 if type(events_discarded) is not metadata.Integer:
435 raise ConfigError('"events_discarded" field in stream packet context type must be an integer type')
436
437 if events_discarded.signed:
438 raise ConfigError('"events_discarded" field in stream packet context type must be an unsigned integer type')
439
440 # "packet_size" and "content_size" must exist
441 if 'packet_size' not in t.fields:
442 raise ConfigError('missing "packet_size" field in stream packet context type')
443
444 packet_size = t.fields['packet_size']
445
446 # "content_size" and "content_size" must exist
447 if 'content_size' not in t.fields:
448 raise ConfigError('missing "content_size" field in stream packet context type')
449
450 content_size = t.fields['content_size']
451
452 # "packet_size" is an unsigned integer type
453 if type(packet_size) is not metadata.Integer:
454 raise ConfigError('"packet_size" field in stream packet context type must be an integer type')
455
456 if packet_size.signed:
457 raise ConfigError('"packet_size" field in stream packet context type must be an unsigned integer type')
458
459 # "content_size" is an unsigned integer type
460 if type(content_size) is not metadata.Integer:
461 raise ConfigError('"content_size" field in stream packet context type must be an integer type')
462
463 if content_size.signed:
464 raise ConfigError('"content_size" field in stream packet context type must be an unsigned integer type')
465
466 def _validate_stream_event_header(self, stream):
467 t = stream.event_header_type
468
469 # needs "id" field?
470 if len(stream.events) > 1:
471 # yes
472 if t is None:
473 raise ConfigError('need "id" field in stream event header type, but stream event header type is missing')
474
475 if type(t) is not metadata.Struct:
476 raise ConfigError('need "id" field in stream event header type, but stream event header type is not a structure type')
477
478 if 'id' not in t.fields:
479 raise ConfigError('need "id" field in stream event header type')
480
481 # validate "id" and "timestamp" types
482 if type(t) is not metadata.Struct:
483 return
484
485 # "timestamp", if exists, is an unsigned integer type,
486 # mapped to a clock
487 if 'timestamp' in t.fields:
488 ts = t.fields['timestamp']
489
490 if type(ts) is not metadata.Integer:
491 raise ConfigError('"ts" field in stream event header type must be an integer type')
492
493 if ts.signed:
494 raise ConfigError('"ts" field in stream event header type must be an unsigned integer type')
495
496 if not ts.property_mappings:
497 raise ConfigError('"ts" field in stream event header type must be mapped to a clock')
498
499 # "id" is an unsigned integer type
500 if 'id' in t.fields:
501 eid = t.fields['id']
502
503 if type(eid) is not metadata.Integer:
504 raise ConfigError('"id" field in stream event header type must be an integer type')
505
506 if eid.signed:
507 raise ConfigError('"id" field in stream event header type must be an unsigned integer type')
508
509 def _validate_stream(self, stream):
510 self._validate_stream_packet_context(stream)
511 self._validate_stream_event_header(stream)
512
513 def validate(self, meta):
514 self._meta = meta
515 self._validate_trace(meta)
516
517 for stream in meta.streams.values():
518 try:
519 self._validate_stream(stream)
520 except Exception as e:
521 raise ConfigError('invalid stream "{}"'.format(stream.name), e)
522
523
524 class _MetadataDynamicTypesValidatorStackEntry:
525 def __init__(self, base_t):
526 self._base_t = base_t
527 self._index = 0
528
529 @property
530 def index(self):
531 return self._index
532
533 @index.setter
534 def index(self, value):
535 self._index = value
536
537 @property
538 def base_t(self):
539 return self._base_t
540
541 @base_t.setter
542 def base_t(self, value):
543 self._base_t = value
544
545
546 # Entities. Order of values is important here.
547 @enum.unique
548 class _Entity(enum.IntEnum):
549 TRACE_PACKET_HEADER = 0
550 STREAM_PACKET_CONTEXT = 1
551 STREAM_EVENT_HEADER = 2
552 STREAM_EVENT_CONTEXT = 3
553 EVENT_CONTEXT = 4
554 EVENT_PAYLOAD = 5
555
556
557 # This validator validates dynamic metadata types, that is, it ensures
558 # variable-length array lengths and variant tags actually point to
559 # something that exists. It also checks that variable-length array
560 # lengths point to integer types and variant tags to enumeration types.
561 class _MetadataDynamicTypesValidator:
562 def __init__(self):
563 self._type_to_visit_type_func = {
564 metadata.Integer: None,
565 metadata.FloatingPoint: None,
566 metadata.Enum: None,
567 metadata.String: None,
568 metadata.Struct: self._visit_struct_type,
569 metadata.Array: self._visit_array_type,
570 metadata.Variant: self._visit_variant_type,
571 }
572
573 self._cur_trace = None
574 self._cur_stream = None
575 self._cur_event = None
576
577 def _lookup_path_from_base(self, path, parts, base, start_index,
578 base_is_current, from_t):
579 index = start_index
580 cur_t = base
581 found_path = []
582
583 while index < len(parts):
584 part = parts[index]
585 next_t = None
586
587 if type(cur_t) is metadata.Struct:
588 enumerated_items = enumerate(cur_t.fields.items())
589
590 # lookup each field
591 for i, (field_name, field_type) in enumerated_items:
592 if field_name == part:
593 next_t = field_type
594 found_path.append((i, field_type))
595
596 if next_t is None:
597 raise ConfigError('invalid path "{}": cannot find field "{}" in structure type'.format(path, part))
598 elif type(cur_t) is metadata.Variant:
599 enumerated_items = enumerate(cur_t.types.items())
600
601 # lookup each type
602 for i, (type_name, type_type) in enumerated_items:
603 if type_name == part:
604 next_t = type_type
605 found_path.append((i, type_type))
606
607 if next_t is None:
608 raise ConfigError('invalid path "{}": cannot find type "{}" in variant type'.format(path, part))
609 else:
610 raise ConfigError('invalid path "{}": requesting "{}" in a non-variant, non-structure type'.format(path, part))
611
612 cur_t = next_t
613 index += 1
614
615 # make sure that the pointed type is not the pointing type
616 if from_t is cur_t:
617 raise ConfigError('invalid path "{}": pointing to self'.format(path))
618
619 # if we're here, we found the type; however, it could be located
620 # _after_ the variant/VLA looking for it, if the pointing
621 # and pointed types are in the same entity, so compare the
622 # current stack entries indexes to our index path in that case
623 if not base_is_current:
624 return cur_t
625
626 for index, entry in enumerate(self._stack):
627 if index == len(found_path):
628 # end of index path; valid so far
629 break
630
631 if found_path[index][0] > entry.index:
632 raise ConfigError('invalid path "{}": pointed type is after pointing type'.format(path))
633
634 # also make sure that both pointed and pointing types share
635 # a common structure ancestor
636 for index, entry in enumerate(self._stack):
637 if index == len(found_path):
638 break
639
640 if entry.base_t is not found_path[index][1]:
641 # found common ancestor
642 if type(entry.base_t) is metadata.Variant:
643 raise ConfigError('invalid path "{}": type cannot be reached because pointed and pointing types are in the same variant type'.format(path))
644
645 return cur_t
646
647 def _lookup_path_from_top(self, path, parts):
648 if len(parts) != 1:
649 raise ConfigError('invalid path "{}": multipart relative path not supported'.format(path))
650
651 find_name = parts[0]
652 index = len(self._stack) - 1
653 got_struct = False
654
655 # check stack entries in reversed order
656 for entry in reversed(self._stack):
657 # structure base type
658 if type(entry.base_t) is metadata.Struct:
659 got_struct = True
660 enumerated_items = enumerate(entry.base_t.fields.items())
661
662 # lookup each field, until the current visiting index is met
663 for i, (field_name, field_type) in enumerated_items:
664 if i == entry.index:
665 break
666
667 if field_name == find_name:
668 return field_type
669
670 # variant base type
671 elif type(entry.base_t) is metadata.Variant:
672 enumerated_items = enumerate(entry.base_t.types.items())
673
674 # lookup each type, until the current visiting index is met
675 for i, (type_name, type_type) in enumerated_items:
676 if i == entry.index:
677 break
678
679 if type_name == find_name:
680 if not got_struct:
681 raise ConfigError('invalid path "{}": type cannot be reached because pointed and pointing types are in the same variant type'.format(path))
682
683 return type_type
684
685 # nothing returned here: cannot find type
686 raise ConfigError('invalid path "{}": cannot find type in current context'.format(path))
687
688 def _lookup_path(self, path, from_t):
689 parts = path.lower().split('.')
690 base = None
691 base_is_current = False
692
693 if len(parts) >= 3:
694 if parts[0] == 'trace':
695 if parts[1] == 'packet' and parts[2] == 'header':
696 # make sure packet header exists
697 if self._cur_trace.packet_header_type is None:
698 raise ConfigError('invalid path "{}": no defined trace packet header type'.format(path))
699
700 base = self._cur_trace.packet_header_type
701
702 if self._cur_entity == _Entity.TRACE_PACKET_HEADER:
703 base_is_current = True
704 else:
705 raise ConfigError('invalid path "{}": unknown names after "trace"'.format(path))
706 elif parts[0] == 'stream':
707 if parts[1] == 'packet' and parts[2] == 'context':
708 if self._cur_entity < _Entity.STREAM_PACKET_CONTEXT:
709 raise ConfigError('invalid path "{}": cannot access stream packet context here'.format(path))
710
711 if self._cur_stream.packet_context_type is None:
712 raise ConfigError('invalid path "{}": no defined stream packet context type'.format(path))
713
714 base = self._cur_stream.packet_context_type
715
716 if self._cur_entity == _Entity.STREAM_PACKET_CONTEXT:
717 base_is_current = True
718 elif parts[1] == 'event':
719 if parts[2] == 'header':
720 if self._cur_entity < _Entity.STREAM_EVENT_HEADER:
721 raise ConfigError('invalid path "{}": cannot access stream event header here'.format(path))
722
723 if self._cur_stream.event_header_type is None:
724 raise ConfigError('invalid path "{}": no defined stream event header type'.format(path))
725
726 base = self._cur_stream.event_header_type
727
728 if self._cur_entity == _Entity.STREAM_EVENT_HEADER:
729 base_is_current = True
730 elif parts[2] == 'context':
731 if self._cur_entity < _Entity.STREAM_EVENT_CONTEXT:
732 raise ConfigError('invalid path "{}": cannot access stream event context here'.format(path))
733
734 if self._cur_stream.event_context_type is None:
735 raise ConfigError('invalid path "{}": no defined stream event context type'.format(path))
736
737 base = self._cur_stream.event_context_type
738
739 if self._cur_entity == _Entity.STREAM_EVENT_CONTEXT:
740 base_is_current = True
741 else:
742 raise ConfigError('invalid path "{}": unknown names after "stream.event"'.format(path))
743 else:
744 raise ConfigError('invalid path "{}": unknown names after "stream"'.format(path))
745
746 if base is not None:
747 start_index = 3
748
749 if len(parts) >= 2 and base is None:
750 if parts[0] == 'event':
751 if parts[1] == 'context':
752 if self._cur_entity < _Entity.EVENT_CONTEXT:
753 raise ConfigError('invalid path "{}": cannot access event context here'.format(path))
754
755 if self._cur_event.context_type is None:
756 raise ConfigError('invalid path "{}": no defined event context type'.format(path))
757
758 base = self._cur_event.context_type
759
760 if self._cur_entity == _Entity.EVENT_CONTEXT:
761 base_is_current = True
762 elif parts[1] == 'payload' or parts[1] == 'fields':
763 if self._cur_entity < _Entity.EVENT_PAYLOAD:
764 raise ConfigError('invalid path "{}": cannot access event payload here'.format(path))
765
766 if self._cur_event.payload_type is None:
767 raise ConfigError('invalid path "{}": no defined event payload type'.format(path))
768
769 base = self._cur_event.payload_type
770
771 if self._cur_entity == _Entity.EVENT_PAYLOAD:
772 base_is_current = True
773 else:
774 raise ConfigError('invalid path "{}": unknown names after "event"'.format(path))
775
776 if base is not None:
777 start_index = 2
778
779 if base is not None:
780 return self._lookup_path_from_base(path, parts, base, start_index,
781 base_is_current, from_t)
782 else:
783 return self._lookup_path_from_top(path, parts)
784
785 def _stack_reset(self):
786 self._stack = []
787
788 def _stack_push(self, base_t):
789 entry = _MetadataDynamicTypesValidatorStackEntry(base_t)
790 self._stack.append(entry)
791
792 def _stack_pop(self):
793 self._stack.pop()
794
795 def _stack_incr_index(self):
796 self._stack[-1].index += 1
797
798 def _visit_struct_type(self, t):
799 self._stack_push(t)
800
801 for field_name, field_type in t.fields.items():
802 try:
803 self._visit_type(field_type)
804 except Exception as e:
805 raise ConfigError('in structure type\'s field "{}"'.format(field_name), e)
806
807 self._stack_incr_index()
808
809 self._stack_pop()
810
811 def _visit_array_type(self, t):
812 if not t.is_static:
813 # find length type
814 try:
815 length_type = self._lookup_path(t.length, t)
816 except Exception as e:
817 raise ConfigError('invalid array type\'s length', e)
818
819 # make sure length type an unsigned integer
820 if type(length_type) is not metadata.Integer:
821 raise ConfigError('array type\'s length does not point to an integer type')
822
823 if length_type.signed:
824 raise ConfigError('array type\'s length does not point to an unsigned integer type')
825
826 self._visit_type(t.element_type)
827
828 def _visit_variant_type(self, t):
829 # find tag type
830 try:
831 tag_type = self._lookup_path(t.tag, t)
832 except Exception as e:
833 raise ConfigError('invalid variant type\'s tag', e)
834
835 # make sure tag type is an enumeration
836 if type(tag_type) is not metadata.Enum:
837 raise ConfigError('variant type\'s tag does not point to an enumeration type')
838
839 # verify that each variant type's type exists as an enumeration member
840 for tag_name in t.types.keys():
841 if tag_name not in tag_type.members:
842 raise ConfigError('cannot find variant type\'s type "{}" in pointed tag type'.format(tag_name))
843
844 self._stack_push(t)
845
846 for type_name, type_type in t.types.items():
847 try:
848 self._visit_type(type_type)
849 except Exception as e:
850 raise ConfigError('in variant type\'s type "{}"'.format(type_name), e)
851
852 self._stack_incr_index()
853
854 self._stack_pop()
855
856 def _visit_type(self, t):
857 if t is None:
858 return
859
860 if type(t) in self._type_to_visit_type_func:
861 func = self._type_to_visit_type_func[type(t)]
862
863 if func is not None:
864 func(t)
865
866 def _visit_event(self, ev):
867 ev_name = ev.name
868
869 # set current event
870 self._cur_event = ev
871
872 # visit event context type
873 self._stack_reset()
874 self._cur_entity = _Entity.EVENT_CONTEXT
875
876 try:
877 self._visit_type(ev.context_type)
878 except Exception as e:
879 raise ConfigError('invalid context type in event "{}"'.format(ev_name), e)
880
881 # visit event payload type
882 self._stack_reset()
883 self._cur_entity = _Entity.EVENT_PAYLOAD
884
885 try:
886 self._visit_type(ev.payload_type)
887 except Exception as e:
888 raise ConfigError('invalid payload type in event "{}"'.format(ev_name), e)
889
890 def _visit_stream(self, stream):
891 stream_name = stream.name
892
893 # set current stream
894 self._cur_stream = stream
895
896 # reset current event
897 self._cur_event = None
898
899 # visit stream packet context type
900 self._stack_reset()
901 self._cur_entity = _Entity.STREAM_PACKET_CONTEXT
902
903 try:
904 self._visit_type(stream.packet_context_type)
905 except Exception as e:
906 raise ConfigError('invalid packet context type in stream "{}"'.format(stream_name), e)
907
908 # visit stream event header type
909 self._stack_reset()
910 self._cur_entity = _Entity.STREAM_EVENT_HEADER
911
912 try:
913 self._visit_type(stream.event_header_type)
914 except Exception as e:
915 raise ConfigError('invalid event header type in stream "{}"'.format(stream_name), e)
916
917 # visit stream event context type
918 self._stack_reset()
919 self._cur_entity = _Entity.STREAM_EVENT_CONTEXT
920
921 try:
922 self._visit_type(stream.event_context_type)
923 except Exception as e:
924 raise ConfigError('invalid event context type in stream "{}"'.format(stream_name), e)
925
926 # visit events
927 for ev in stream.events.values():
928 try:
929 self._visit_event(ev)
930 except Exception as e:
931 raise ConfigError('invalid stream "{}"'.format(stream_name))
932
933 def validate(self, meta):
934 # set current trace
935 self._cur_trace = meta.trace
936
937 # visit trace packet header type
938 self._stack_reset()
939 self._cur_entity = _Entity.TRACE_PACKET_HEADER
940
941 try:
942 self._visit_type(meta.trace.packet_header_type)
943 except Exception as e:
944 raise ConfigError('invalid packet header type in trace', e)
945
946 # visit streams
947 for stream in meta.streams.values():
948 self._visit_stream(stream)
949
950
951 # Since type inheritance allows types to be only partially defined at
952 # any place in the configuration, this validator validates that actual
953 # trace, stream, and event types are all complete and valid.
954 class _MetadataTypesHistologyValidator:
955 def __init__(self):
956 self._type_to_validate_type_histology_func = {
957 metadata.Integer: self._validate_integer_histology,
958 metadata.FloatingPoint: self._validate_float_histology,
959 metadata.Enum: self._validate_enum_histology,
960 metadata.String: self._validate_string_histology,
961 metadata.Struct: self._validate_struct_histology,
962 metadata.Array: self._validate_array_histology,
963 metadata.Variant: self._validate_variant_histology,
964 }
965
966 def _validate_integer_histology(self, t):
967 # size is set
968 if t.size is None:
969 raise ConfigError('missing integer type\'s size')
970
971 def _validate_float_histology(self, t):
972 # exponent digits is set
973 if t.exp_size is None:
974 raise ConfigError('missing floating point number type\'s exponent size')
975
976 # mantissa digits is set
977 if t.mant_size is None:
978 raise ConfigError('missing floating point number type\'s mantissa size')
979
980 # exponent and mantissa sum is a multiple of 8
981 if (t.exp_size + t.mant_size) % 8 != 0:
982 raise ConfigError('floating point number type\'s mantissa and exponent sizes sum must be a multiple of 8')
983
984 def _validate_enum_histology(self, t):
985 # integer type is set
986 if t.value_type is None:
987 raise ConfigError('missing enumeration type\'s integer type')
988
989 # there's at least one member
990 if not t.members:
991 raise ConfigError('enumeration type needs at least one member')
992
993 # no overlapping values
994 ranges = []
995
996 for label, value in t.members.items():
997 for rg in ranges:
998 if value[0] <= rg[1] and rg[0] <= value[1]:
999 raise ConfigError('enumeration type\'s member "{}" overlaps another member'.format(label))
1000
1001 ranges.append(value)
1002
1003 def _validate_string_histology(self, t):
1004 # always valid
1005 pass
1006
1007 def _validate_struct_histology(self, t):
1008 # all fields are valid
1009 for field_name, field_type in t.fields.items():
1010 try:
1011 self._validate_type_histology(field_type)
1012 except Exception as e:
1013 raise ConfigError('invalid structure type\'s field "{}"'.format(field_name), e)
1014
1015 def _validate_array_histology(self, t):
1016 # length is set
1017 if t.length is None:
1018 raise ConfigError('missing array type\'s length')
1019
1020 # element type is set
1021 if t.element_type is None:
1022 raise ConfigError('missing array type\'s element type')
1023
1024 # element type is valid
1025 try:
1026 self._validate_type_histology(t.element_type)
1027 except Exception as e:
1028 raise ConfigError('invalid array type\'s element type', e)
1029
1030 def _validate_variant_histology(self, t):
1031 # tag is set
1032 if t.tag is None:
1033 raise ConfigError('missing variant type\'s tag')
1034
1035 # there's at least one type
1036 if not t.types:
1037 raise ConfigError('variant type needs at least one type')
1038
1039 # all types are valid
1040 for type_name, type_t in t.types.items():
1041 try:
1042 self._validate_type_histology(type_t)
1043 except Exception as e:
1044 raise ConfigError('invalid variant type\'s type "{}"'.format(type_name), e)
1045
1046 def _validate_type_histology(self, t):
1047 if t is None:
1048 return
1049
1050 self._type_to_validate_type_histology_func[type(t)](t)
1051
1052 def _validate_entity_type_histology(self, t):
1053 if t is None:
1054 return
1055
1056 # entity cannot be an array
1057 if type(t) is metadata.Array:
1058 raise ConfigError('cannot use an array here')
1059
1060 self._validate_type_histology(t)
1061
1062 def _validate_event_types_histology(self, ev):
1063 ev_name = ev.name
1064
1065 # validate event context type
1066 try:
1067 self._validate_entity_type_histology(ev.context_type)
1068 except Exception as e:
1069 raise ConfigError('invalid event context type for event "{}"'.format(ev_name), e)
1070
1071 # validate event payload type
1072 if ev.payload_type is None:
1073 raise ConfigError('event payload type must exist in event "{}"'.format(ev_name))
1074
1075 # TODO: also check arrays, sequences, and variants
1076 if type(ev.payload_type) is metadata.Struct:
1077 if not ev.payload_type.fields:
1078 raise ConfigError('event payload type must have at least one field for event "{}"'.format(ev_name))
1079
1080 try:
1081 self._validate_entity_type_histology(ev.payload_type)
1082 except Exception as e:
1083 raise ConfigError('invalid event payload type for event "{}"'.format(ev_name), e)
1084
1085 def _validate_stream_types_histology(self, stream):
1086 stream_name = stream.name
1087
1088 # validate stream packet context type
1089 try:
1090 self._validate_entity_type_histology(stream.packet_context_type)
1091 except Exception as e:
1092 raise ConfigError('invalid stream packet context type for stream "{}"'.format(stream_name), e)
1093
1094 # validate stream event header type
1095 try:
1096 self._validate_entity_type_histology(stream.event_header_type)
1097 except Exception as e:
1098 raise ConfigError('invalid stream event header type for stream "{}"'.format(stream_name), e)
1099
1100 # validate stream event context type
1101 try:
1102 self._validate_entity_type_histology(stream.event_context_type)
1103 except Exception as e:
1104 raise ConfigError('invalid stream event context type for stream "{}"'.format(stream_name), e)
1105
1106 # validate events
1107 for ev in stream.events.values():
1108 try:
1109 self._validate_event_types_histology(ev)
1110 except Exception as e:
1111 raise ConfigError('invalid event in stream "{}"'.format(stream_name), e)
1112
1113 def validate(self, meta):
1114 # validate trace packet header type
1115 try:
1116 self._validate_entity_type_histology(meta.trace.packet_header_type)
1117 except Exception as e:
1118 raise ConfigError('invalid trace packet header type', e)
1119
1120 # validate streams
1121 for stream in meta.streams.values():
1122 self._validate_stream_types_histology(stream)
1123
1124
1125 class _YamlConfigParser:
1126 def __init__(self):
1127 self._class_name_to_create_type_func = {
1128 'int': self._create_integer,
1129 'integer': self._create_integer,
1130 'flt': self._create_float,
1131 'float': self._create_float,
1132 'floating-point': self._create_float,
1133 'enum': self._create_enum,
1134 'enumeration': self._create_enum,
1135 'str': self._create_string,
1136 'string': self._create_string,
1137 'struct': self._create_struct,
1138 'structure': self._create_struct,
1139 'array': self._create_array,
1140 'var': self._create_variant,
1141 'variant': self._create_variant,
1142 }
1143 self._type_to_create_type_func = {
1144 metadata.Integer: self._create_integer,
1145 metadata.FloatingPoint: self._create_float,
1146 metadata.Enum: self._create_enum,
1147 metadata.String: self._create_string,
1148 metadata.Struct: self._create_struct,
1149 metadata.Array: self._create_array,
1150 metadata.Variant: self._create_variant,
1151 }
1152
1153 def _set_byte_order(self, metadata_node):
1154 if 'trace' not in metadata_node:
1155 raise ConfigError('missing "trace" property (metadata)')
1156
1157 trace_node = metadata_node['trace']
1158
1159 if not _is_assoc_array_prop(trace_node):
1160 raise ConfigError('"trace" property (metadata) must be an associative array')
1161
1162 if 'byte-order' not in trace_node:
1163 raise ConfigError('missing "byte-order" property (trace)')
1164
1165 self._bo = _byte_order_str_to_bo(trace_node['byte-order'])
1166
1167 if self._bo is None:
1168 raise ConfigError('invalid "byte-order" property (trace): must be "le" or "be"')
1169
1170 def _lookup_type_alias(self, name):
1171 if name in self._tas:
1172 return copy.deepcopy(self._tas[name])
1173
1174 def _set_int_clock_prop_mapping(self, int_obj, prop_mapping_node):
1175 unk_prop = _get_first_unknown_prop(prop_mapping_node, ['type', 'name', 'property'])
1176
1177 if unk_prop:
1178 raise ConfigError('unknown property in integer type object\'s clock property mapping: "{}"'.format(unk_prop))
1179
1180 if 'name' not in prop_mapping_node:
1181 raise ConfigError('missing "name" property in integer type object\'s clock property mapping')
1182
1183 if 'property' not in prop_mapping_node:
1184 raise ConfigError('missing "property" property in integer type object\'s clock property mapping')
1185
1186 clock_name = prop_mapping_node['name']
1187 prop = prop_mapping_node['property']
1188
1189 if not _is_str_prop(clock_name):
1190 raise ConfigError('"name" property of integer type object\'s clock property mapping must be a string')
1191
1192 if not _is_str_prop(prop):
1193 raise ConfigError('"property" property of integer type object\'s clock property mapping must be a string')
1194
1195 if clock_name not in self._clocks:
1196 raise ConfigError('invalid clock name "{}" in integer type object\'s clock property mapping'.format(clock_name))
1197
1198 if prop != 'value':
1199 raise ConfigError('invalid "property" property in integer type object\'s clock property mapping: "{}"'.format(prop))
1200
1201 mapped_clock = self._clocks[clock_name]
1202 int_obj.property_mappings.append(metadata.PropertyMapping(mapped_clock, prop))
1203
1204 def _get_first_unknown_type_prop(self, type_node, known_props):
1205 kp = known_props + ['inherit', 'class']
1206
1207 if self._version >= 201:
1208 kp.append('$inherit')
1209
1210 return _get_first_unknown_prop(type_node, kp)
1211
1212 def _create_integer(self, obj, node):
1213 if obj is None:
1214 # create integer object
1215 obj = metadata.Integer()
1216
1217 unk_prop = self._get_first_unknown_type_prop(node, [
1218 'size',
1219 'align',
1220 'signed',
1221 'byte-order',
1222 'base',
1223 'encoding',
1224 'property-mappings',
1225 ])
1226
1227 if unk_prop:
1228 raise ConfigError('unknown integer type object property: "{}"'.format(unk_prop))
1229
1230 # size
1231 if 'size' in node:
1232 size = node['size']
1233
1234 if not _is_int_prop(size):
1235 raise ConfigError('"size" property of integer type object must be an integer')
1236
1237 if size < 1:
1238 raise ConfigError('invalid integer size: {}'.format(size))
1239
1240 obj.size = size
1241
1242 # align
1243 if 'align' in node:
1244 align = node['align']
1245
1246 if not _is_int_prop(align):
1247 raise ConfigError('"align" property of integer type object must be an integer')
1248
1249 if not _is_valid_alignment(align):
1250 raise ConfigError('invalid alignment: {}'.format(align))
1251
1252 obj.align = align
1253
1254 # signed
1255 if 'signed' in node:
1256 signed = node['signed']
1257
1258 if not _is_bool_prop(signed):
1259 raise ConfigError('"signed" property of integer type object must be a boolean')
1260
1261 obj.signed = signed
1262
1263 # byte order
1264 if 'byte-order' in node:
1265 byte_order = node['byte-order']
1266
1267 if not _is_str_prop(byte_order):
1268 raise ConfigError('"byte-order" property of integer type object must be a string ("le" or "be")')
1269
1270 byte_order = _byte_order_str_to_bo(byte_order)
1271
1272 if byte_order is None:
1273 raise ConfigError('invalid "byte-order" property in integer type object')
1274 else:
1275 byte_order = self._bo
1276
1277 obj.byte_order = byte_order
1278
1279 # base
1280 if 'base' in node:
1281 base = node['base']
1282
1283 if not _is_str_prop(base):
1284 raise ConfigError('"base" property of integer type object must be a string ("bin", "oct", "dec", or "hex")')
1285
1286 if base == 'bin':
1287 base = 2
1288 elif base == 'oct':
1289 base = 8
1290 elif base == 'dec':
1291 base = 10
1292 elif base == 'hex':
1293 base = 16
1294
1295 obj.base = base
1296
1297 # encoding
1298 if 'encoding' in node:
1299 encoding = node['encoding']
1300
1301 if not _is_str_prop(encoding):
1302 raise ConfigError('"encoding" property of integer type object must be a string ("none", "ascii", or "utf-8")')
1303
1304 encoding = _encoding_str_to_encoding(encoding)
1305
1306 if encoding is None:
1307 raise ConfigError('invalid "encoding" property in integer type object')
1308
1309 obj.encoding = encoding
1310
1311 # property mappings
1312 if 'property-mappings' in node:
1313 prop_mappings = node['property-mappings']
1314
1315 if not _is_array_prop(prop_mappings):
1316 raise ConfigError('"property-mappings" property of integer type object must be an array')
1317
1318 if len(prop_mappings) > 1:
1319 raise ConfigError('length of "property-mappings" array in integer type object must be 1')
1320
1321 del obj.property_mappings[:]
1322
1323 for index, prop_mapping in enumerate(prop_mappings):
1324 if not _is_assoc_array_prop(prop_mapping):
1325 raise ConfigError('elements of "property-mappings" property of integer type object must be associative arrays')
1326
1327 if 'type' not in prop_mapping:
1328 raise ConfigError('missing "type" property in integer type object\'s "property-mappings" array\'s element #{}'.format(index))
1329
1330 prop_type = prop_mapping['type']
1331
1332 if not _is_str_prop(prop_type):
1333 raise ConfigError('"type" property of integer type object\'s "property-mappings" array\'s element #{} must be a string'.format(index))
1334
1335 if prop_type == 'clock':
1336 self._set_int_clock_prop_mapping(obj, prop_mapping)
1337 else:
1338 raise ConfigError('unknown property mapping type "{}" in integer type object\'s "property-mappings" array\'s element #{}'.format(prop_type, index))
1339
1340 return obj
1341
1342 def _create_float(self, obj, node):
1343 if obj is None:
1344 # create floating point number object
1345 obj = metadata.FloatingPoint()
1346
1347 unk_prop = self._get_first_unknown_type_prop(node, [
1348 'size',
1349 'align',
1350 'byte-order',
1351 ])
1352
1353 if unk_prop:
1354 raise ConfigError('unknown floating point number type object property: "{}"'.format(unk_prop))
1355
1356 # size
1357 if 'size' in node:
1358 size = node['size']
1359
1360 if not _is_assoc_array_prop(size):
1361 raise ConfigError('"size" property of floating point number type object must be an associative array')
1362
1363 unk_prop = _get_first_unknown_prop(node, ['exp', 'mant'])
1364
1365 if 'exp' in size:
1366 exp = size['exp']
1367
1368 if not _is_int_prop(exp):
1369 raise ConfigError('"exp" property of floating point number type object\'s "size" property must be an integer')
1370
1371 if exp < 1:
1372 raise ConfigError('invalid floating point number exponent size: {}')
1373
1374 obj.exp_size = exp
1375
1376 if 'mant' in size:
1377 mant = size['mant']
1378
1379 if not _is_int_prop(mant):
1380 raise ConfigError('"mant" property of floating point number type object\'s "size" property must be an integer')
1381
1382 if mant < 1:
1383 raise ConfigError('invalid floating point number mantissa size: {}')
1384
1385 obj.mant_size = mant
1386
1387 # align
1388 if 'align' in node:
1389 align = node['align']
1390
1391 if not _is_int_prop(align):
1392 raise ConfigError('"align" property of floating point number type object must be an integer')
1393
1394 if not _is_valid_alignment(align):
1395 raise ConfigError('invalid alignment: {}'.format(align))
1396
1397 obj.align = align
1398
1399 # byte order
1400 if 'byte-order' in node:
1401 byte_order = node['byte-order']
1402
1403 if not _is_str_prop(byte_order):
1404 raise ConfigError('"byte-order" property of floating point number type object must be a string ("le" or "be")')
1405
1406 byte_order = _byte_order_str_to_bo(byte_order)
1407
1408 if byte_order is None:
1409 raise ConfigError('invalid "byte-order" property in floating point number type object')
1410 else:
1411 byte_order = self._bo
1412
1413 obj.byte_order = byte_order
1414
1415 return obj
1416
1417 def _create_enum(self, obj, node):
1418 if obj is None:
1419 # create enumeration object
1420 obj = metadata.Enum()
1421
1422 unk_prop = self._get_first_unknown_type_prop(node, [
1423 'value-type',
1424 'members',
1425 ])
1426
1427 if unk_prop:
1428 raise ConfigError('unknown enumeration type object property: "{}"'.format(unk_prop))
1429
1430 # value type
1431 if 'value-type' in node:
1432 try:
1433 obj.value_type = self._create_type(node['value-type'])
1434 except Exception as e:
1435 raise ConfigError('cannot create enumeration type\'s integer type', e)
1436
1437 # members
1438 if 'members' in node:
1439 members_node = node['members']
1440
1441 if not _is_array_prop(members_node):
1442 raise ConfigError('"members" property of enumeration type object must be an array')
1443
1444 cur = 0
1445
1446 for index, m_node in enumerate(members_node):
1447 if not _is_str_prop(m_node) and not _is_assoc_array_prop(m_node):
1448 raise ConfigError('invalid enumeration member #{}: expecting a string or an associative array'.format(index))
1449
1450 if _is_str_prop(m_node):
1451 label = m_node
1452 value = (cur, cur)
1453 cur += 1
1454 else:
1455 if 'label' not in m_node:
1456 raise ConfigError('missing "label" property in enumeration member #{}'.format(index))
1457
1458 label = m_node['label']
1459
1460 if not _is_str_prop(label):
1461 raise ConfigError('"label" property of enumeration member #{} must be a string'.format(index))
1462
1463 if 'value' not in m_node:
1464 raise ConfigError('missing "value" property in enumeration member ("{}")'.format(label))
1465
1466 value = m_node['value']
1467
1468 if not _is_int_prop(value) and not _is_array_prop(value):
1469 raise ConfigError('invalid enumeration member ("{}"): expecting an integer or an array'.format(label))
1470
1471 if _is_int_prop(value):
1472 cur = value + 1
1473 value = (value, value)
1474 else:
1475 if len(value) != 2:
1476 raise ConfigError('invalid enumeration member ("{}"): range must have exactly two items'.format(label))
1477
1478 mn = value[0]
1479 mx = value[1]
1480
1481 if mn > mx:
1482 raise ConfigError('invalid enumeration member ("{}"): invalid range ({} > {})'.format(label, mn, mx))
1483
1484 value = (mn, mx)
1485 cur = mx + 1
1486
1487 obj.members[label] = value
1488
1489 return obj
1490
1491 def _create_string(self, obj, node):
1492 if obj is None:
1493 # create string object
1494 obj = metadata.String()
1495
1496 unk_prop = self._get_first_unknown_type_prop(node, [
1497 'encoding',
1498 ])
1499
1500 if unk_prop:
1501 raise ConfigError('unknown string type object property: "{}"'.format(unk_prop))
1502
1503 # encoding
1504 if 'encoding' in node:
1505 encoding = node['encoding']
1506
1507 if not _is_str_prop(encoding):
1508 raise ConfigError('"encoding" property of string type object must be a string ("none", "ascii", or "utf-8")')
1509
1510 encoding = _encoding_str_to_encoding(encoding)
1511
1512 if encoding is None:
1513 raise ConfigError('invalid "encoding" property in string type object')
1514
1515 obj.encoding = encoding
1516
1517 return obj
1518
1519 def _create_struct(self, obj, node):
1520 if obj is None:
1521 # create structure object
1522 obj = metadata.Struct()
1523
1524 unk_prop = self._get_first_unknown_type_prop(node, [
1525 'min-align',
1526 'fields',
1527 ])
1528
1529 if unk_prop:
1530 raise ConfigError('unknown string type object property: "{}"'.format(unk_prop))
1531
1532 # minimum alignment
1533 if 'min-align' in node:
1534 min_align = node['min-align']
1535
1536 if not _is_int_prop(min_align):
1537 raise ConfigError('"min-align" property of structure type object must be an integer')
1538
1539 if not _is_valid_alignment(min_align):
1540 raise ConfigError('invalid minimum alignment: {}'.format(min_align))
1541
1542 obj.min_align = min_align
1543
1544 # fields
1545 if 'fields' in node:
1546 fields = node['fields']
1547
1548 if not _is_assoc_array_prop(fields):
1549 raise ConfigError('"fields" property of structure type object must be an associative array')
1550
1551 for field_name, field_node in fields.items():
1552 if not is_valid_identifier(field_name):
1553 raise ConfigError('"{}" is not a valid field name for structure type'.format(field_name))
1554
1555 try:
1556 obj.fields[field_name] = self._create_type(field_node)
1557 except Exception as e:
1558 raise ConfigError('cannot create structure type\'s field "{}"'.format(field_name), e)
1559
1560 return obj
1561
1562 def _create_array(self, obj, node):
1563 if obj is None:
1564 # create array object
1565 obj = metadata.Array()
1566
1567 unk_prop = self._get_first_unknown_type_prop(node, [
1568 'length',
1569 'element-type',
1570 ])
1571
1572 if unk_prop:
1573 raise ConfigError('unknown array type object property: "{}"'.format(unk_prop))
1574
1575 # length
1576 if 'length' in node:
1577 length = node['length']
1578
1579 if not _is_int_prop(length) and not _is_str_prop(length):
1580 raise ConfigError('"length" property of array type object must be an integer or a string')
1581
1582 if type(length) is int and length < 0:
1583 raise ConfigError('invalid static array length: {}'.format(length))
1584
1585 obj.length = length
1586
1587 # element type
1588 if 'element-type' in node:
1589 try:
1590 obj.element_type = self._create_type(node['element-type'])
1591 except Exception as e:
1592 raise ConfigError('cannot create array type\'s element type', e)
1593
1594 return obj
1595
1596 def _create_variant(self, obj, node):
1597 if obj is None:
1598 # create variant object
1599 obj = metadata.Variant()
1600
1601 unk_prop = self._get_first_unknown_type_prop(node, [
1602 'tag',
1603 'types',
1604 ])
1605
1606 if unk_prop:
1607 raise ConfigError('unknown variant type object property: "{}"'.format(unk_prop))
1608
1609 # tag
1610 if 'tag' in node:
1611 tag = node['tag']
1612
1613 if not _is_str_prop(tag):
1614 raise ConfigError('"tag" property of variant type object must be a string')
1615
1616 # do not validate variant tag for the moment; will be done in a
1617 # second phase
1618 obj.tag = tag
1619
1620 # element type
1621 if 'types' in node:
1622 types = node['types']
1623
1624 if not _is_assoc_array_prop(types):
1625 raise ConfigError('"types" property of variant type object must be an associative array')
1626
1627 # do not validate type names for the moment; will be done in a
1628 # second phase
1629 for type_name, type_node in types.items():
1630 if not is_valid_identifier(type_name):
1631 raise ConfigError('"{}" is not a valid type name for variant type'.format(type_name))
1632
1633 try:
1634 obj.types[type_name] = self._create_type(type_node)
1635 except Exception as e:
1636 raise ConfigError('cannot create variant type\'s type "{}"'.format(type_name), e)
1637
1638 return obj
1639
1640 def _create_type(self, type_node):
1641 if type(type_node) is str:
1642 t = self._lookup_type_alias(type_node)
1643
1644 if t is None:
1645 raise ConfigError('unknown type alias "{}"'.format(type_node))
1646
1647 return t
1648
1649 if not _is_assoc_array_prop(type_node):
1650 raise ConfigError('type objects must be associative arrays')
1651
1652 # inherit:
1653 # v2.0: "inherit"
1654 # v2.1+: "$inherit"
1655 inherit_node = None
1656
1657 if self._version >= 200:
1658 if 'inherit' in type_node:
1659 inherit_prop = 'inherit'
1660 inherit_node = type_node[inherit_prop]
1661
1662 if self._version >= 201:
1663 if '$inherit' in type_node:
1664 if inherit_node is not None:
1665 raise ConfigError('cannot specify both "inherit" and "$inherit" properties of type object: prefer "$inherit"')
1666
1667 inherit_prop = '$inherit'
1668 inherit_node = type_node[inherit_prop]
1669
1670 if inherit_node is not None and 'class' in type_node:
1671 raise ConfigError('cannot specify both "{}" and "class" properties in type object'.format(inherit_prop))
1672
1673 if inherit_node is not None:
1674 if not _is_str_prop(inherit_node):
1675 raise ConfigError('"{}" property of type object must be a string'.format(inherit_prop))
1676
1677 base = self._lookup_type_alias(inherit_node)
1678
1679 if base is None:
1680 raise ConfigError('cannot inherit from type alias "{}": type alias does not exist at this point'.format(inherit_node))
1681
1682 func = self._type_to_create_type_func[type(base)]
1683 else:
1684 if 'class' not in type_node:
1685 raise ConfigError('type objects which do not inherit must have a "class" property')
1686
1687 class_name = type_node['class']
1688
1689 if type(class_name) is not str:
1690 raise ConfigError('type objects\' "class" property must be a string')
1691
1692 if class_name not in self._class_name_to_create_type_func:
1693 raise ConfigError('unknown type class "{}"'.format(class_name))
1694
1695 base = None
1696 func = self._class_name_to_create_type_func[class_name]
1697
1698 return func(base, type_node)
1699
1700 def _register_type_aliases(self, metadata_node):
1701 self._tas = dict()
1702
1703 if 'type-aliases' not in metadata_node:
1704 return
1705
1706 ta_node = metadata_node['type-aliases']
1707
1708 if not _is_assoc_array_prop(ta_node):
1709 raise ConfigError('"type-aliases" property (metadata) must be an associative array')
1710
1711 for ta_name, ta_type in ta_node.items():
1712 if ta_name in self._tas:
1713 raise ConfigError('duplicate type alias "{}"'.format(ta_name))
1714
1715 try:
1716 t = self._create_type(ta_type)
1717 except Exception as e:
1718 raise ConfigError('cannot create type alias "{}"'.format(ta_name), e)
1719
1720 self._tas[ta_name] = t
1721
1722 def _create_clock(self, node):
1723 # create clock object
1724 clock = metadata.Clock()
1725
1726 if not _is_assoc_array_prop(env_node):
1727 raise ConfigError('clock objects must be associative arrays')
1728
1729 known_props = [
1730 'uuid',
1731 'description',
1732 'freq',
1733 'error-cycles',
1734 'offset',
1735 'absolute',
1736 'return-ctype',
1737 ]
1738
1739 if self._version >= 201:
1740 known_props.append('$return-ctype')
1741
1742 unk_prop = _get_first_unknown_prop(node, known_props)
1743
1744 if unk_prop:
1745 raise ConfigError('unknown clock object property: "{}"'.format(unk_prop))
1746
1747 # UUID
1748 if 'uuid' in node:
1749 uuidp = node['uuid']
1750
1751 if not _is_str_prop(uuidp):
1752 raise ConfigError('"uuid" property of clock object must be a string')
1753
1754 try:
1755 uuidp = uuid.UUID(uuidp)
1756 except:
1757 raise ConfigError('malformed UUID (clock object): "{}"'.format(uuidp))
1758
1759 clock.uuid = uuidp
1760
1761 # description
1762 if 'description' in node:
1763 desc = node['description']
1764
1765 if not _is_str_prop(desc):
1766 raise ConfigError('"description" property of clock object must be a string')
1767
1768 clock.description = desc
1769
1770 # frequency
1771 if 'freq' in node:
1772 freq = node['freq']
1773
1774 if not _is_int_prop(freq):
1775 raise ConfigError('"freq" property of clock object must be an integer')
1776
1777 if freq < 1:
1778 raise ConfigError('invalid clock frequency: {}'.format(freq))
1779
1780 clock.freq = freq
1781
1782 # error cycles
1783 if 'error-cycles' in node:
1784 error_cycles = node['error-cycles']
1785
1786 if not _is_int_prop(error_cycles):
1787 raise ConfigError('"error-cycles" property of clock object must be an integer')
1788
1789 if error_cycles < 0:
1790 raise ConfigError('invalid clock error cycles: {}'.format(error_cycles))
1791
1792 clock.error_cycles = error_cycles
1793
1794 # offset
1795 if 'offset' in node:
1796 offset = node['offset']
1797
1798 if not _is_assoc_array_prop(offset):
1799 raise ConfigError('"offset" property of clock object must be an associative array')
1800
1801 unk_prop = _get_first_unknown_prop(offset, ['cycles', 'seconds'])
1802
1803 if unk_prop:
1804 raise ConfigError('unknown clock object\'s offset property: "{}"'.format(unk_prop))
1805
1806 # cycles
1807 if 'cycles' in offset:
1808 offset_cycles = offset['cycles']
1809
1810 if not _is_int_prop(offset_cycles):
1811 raise ConfigError('"cycles" property of clock object\'s offset property must be an integer')
1812
1813 if offset_cycles < 0:
1814 raise ConfigError('invalid clock offset cycles: {}'.format(offset_cycles))
1815
1816 clock.offset_cycles = offset_cycles
1817
1818 # seconds
1819 if 'seconds' in offset:
1820 offset_seconds = offset['seconds']
1821
1822 if not _is_int_prop(offset_seconds):
1823 raise ConfigError('"seconds" property of clock object\'s offset property must be an integer')
1824
1825 if offset_seconds < 0:
1826 raise ConfigError('invalid clock offset seconds: {}'.format(offset_seconds))
1827
1828 clock.offset_seconds = offset_seconds
1829
1830 # absolute
1831 if 'absolute' in node:
1832 absolute = node['absolute']
1833
1834 if not _is_bool_prop(absolute):
1835 raise ConfigError('"absolute" property of clock object must be a boolean')
1836
1837 clock.absolute = absolute
1838
1839 # return C type:
1840 # v2.0: "return-ctype"
1841 # v2.1+: "$return-ctype"
1842 return_ctype_node = None
1843
1844 if self._version >= 200:
1845 if 'return-ctype' in node:
1846 return_ctype_prop = 'return-ctype'
1847 return_ctype_node = node[return_ctype_prop]
1848
1849 if self._version >= 201:
1850 if '$return-ctype' in node:
1851 if return_ctype_node is not None:
1852 raise ConfigError('cannot specify both "return-ctype" and "$return-ctype" properties of clock object: prefer "$return-ctype"')
1853
1854 return_ctype_prop = '$return-ctype'
1855 return_ctype_node = node[return_ctype_prop]
1856
1857 if return_ctype_node is not None:
1858 if not _is_str_prop(return_ctype_node):
1859 raise ConfigError('"{}" property of clock object must be a string'.format(return_ctype_prop))
1860
1861 clock.return_ctype = return_ctype_node
1862
1863 return clock
1864
1865 def _register_clocks(self, metadata_node):
1866 self._clocks = collections.OrderedDict()
1867
1868 if 'clocks' not in metadata_node:
1869 return
1870
1871 clocks_node = metadata_node['clocks']
1872
1873 if not _is_assoc_array_prop(clocks_node):
1874 raise ConfigError('"clocks" property (metadata) must be an associative array')
1875
1876 for clock_name, clock_node in clocks_node.items():
1877 if not is_valid_identifier(clock_name):
1878 raise ConfigError('invalid clock name: "{}"'.format(clock_name))
1879
1880 if clock_name in self._clocks:
1881 raise ConfigError('duplicate clock "{}"'.format(clock_name))
1882
1883 try:
1884 clock = self._create_clock(clock_node)
1885 except Exception as e:
1886 raise ConfigError('cannot create clock "{}"'.format(clock_name), e)
1887
1888 clock.name = clock_name
1889 self._clocks[clock_name] = clock
1890
1891 def _create_env(self, metadata_node):
1892 env = collections.OrderedDict()
1893
1894 if 'env' not in metadata_node:
1895 return env
1896
1897 env_node = metadata_node['env']
1898
1899 if not _is_assoc_array_prop(env_node):
1900 raise ConfigError('"env" property (metadata) must be an associative array')
1901
1902 for env_name, env_value in env_node.items():
1903 if env_name in env:
1904 raise ConfigError('duplicate environment variable "{}"'.format(env_name))
1905
1906 if not is_valid_identifier(env_name):
1907 raise ConfigError('invalid environment variable name: "{}"'.format(env_name))
1908
1909 if not _is_int_prop(env_value) and not _is_str_prop(env_value):
1910 raise ConfigError('invalid environment variable value ("{}"): expecting integer or string'.format(env_name))
1911
1912 env[env_name] = env_value
1913
1914 return env
1915
1916 def _register_log_levels(self, metadata_node):
1917 self._log_levels = dict()
1918
1919 if 'log-levels' not in metadata_node:
1920 return
1921
1922 log_levels_node = metadata_node['log-levels']
1923
1924 if not _is_assoc_array_prop(log_levels_node):
1925 raise ConfigError('"log-levels" property (metadata) must be an associative array')
1926
1927 for ll_name, ll_value in log_levels_node.items():
1928 if ll_name in self._log_levels:
1929 raise ConfigError('duplicate log level entry "{}"'.format(ll_name))
1930
1931 if not _is_int_prop(ll_value):
1932 raise ConfigError('invalid log level entry ("{}"): expecting an integer'.format(ll_name))
1933
1934 self._log_levels[ll_name] = ll_value
1935
1936 def _create_trace(self, metadata_node):
1937 # create trace object
1938 trace = metadata.Trace()
1939 trace_node = metadata_node['trace']
1940
1941 if not _is_assoc_array_prop(trace_node):
1942 raise ConfigError('"trace" property (metadata) must be an associative array')
1943
1944 unk_prop = _get_first_unknown_prop(trace_node, [
1945 'byte-order',
1946 'uuid',
1947 'packet-header-type',
1948 ])
1949
1950 if unk_prop:
1951 raise ConfigError('unknown trace object property: "{}"'.format(unk_prop))
1952
1953 # set byte order (already parsed)
1954 trace.byte_order = self._bo
1955
1956 # UUID
1957 if 'uuid' in trace_node:
1958 uuidp = trace_node['uuid']
1959
1960 if not _is_str_prop(uuidp):
1961 raise ConfigError('"uuid" property of trace object must be a string')
1962
1963 if uuidp == 'auto':
1964 uuidp = uuid.uuid1()
1965 else:
1966 try:
1967 uuidp = uuid.UUID(uuidp)
1968 except:
1969 raise ConfigError('malformed UUID (trace object): "{}"'.format(uuidp))
1970
1971 trace.uuid = uuidp
1972
1973 # packet header type
1974 if 'packet-header-type' in trace_node:
1975 try:
1976 ph_type = self._create_type(trace_node['packet-header-type'])
1977 except Exception as e:
1978 raise ConfigError('cannot create packet header type (trace)', e)
1979
1980 trace.packet_header_type = ph_type
1981
1982 return trace
1983
1984 def _lookup_log_level(self, ll):
1985 if _is_int_prop(ll):
1986 return ll
1987 elif _is_str_prop(ll) and ll in self._log_levels:
1988 return self._log_levels[ll]
1989
1990 def _create_event(self, event_node):
1991 event = metadata.Event()
1992
1993 if not _is_assoc_array_prop(event_node):
1994 raise ConfigError('event objects must be associative arrays')
1995
1996 unk_prop = _get_first_unknown_prop(event_node, [
1997 'log-level',
1998 'context-type',
1999 'payload-type',
2000 ])
2001
2002 if unk_prop:
2003 raise ConfigError('unknown event object property: "{}"'.format(unk_prop))
2004
2005 if 'log-level' in event_node:
2006 ll = self._lookup_log_level(event_node['log-level'])
2007
2008 if ll is None:
2009 raise ConfigError('invalid "log-level" property')
2010
2011 event.log_level = ll
2012
2013 if 'context-type' in event_node:
2014 try:
2015 t = self._create_type(event_node['context-type'])
2016 except Exception as e:
2017 raise ConfigError('cannot create event\'s context type object', e)
2018
2019 event.context_type = t
2020
2021 if 'payload-type' not in event_node:
2022 raise ConfigError('missing "payload-type" property in event object')
2023
2024 try:
2025 t = self._create_type(event_node['payload-type'])
2026 except Exception as e:
2027 raise ConfigError('cannot create event\'s payload type object', e)
2028
2029 event.payload_type = t
2030
2031 return event
2032
2033 def _create_stream(self, stream_node):
2034 stream = metadata.Stream()
2035
2036 if not _is_assoc_array_prop(stream_node):
2037 raise ConfigError('stream objects must be associative arrays')
2038
2039 unk_prop = _get_first_unknown_prop(stream_node, [
2040 'packet-context-type',
2041 'event-header-type',
2042 'event-context-type',
2043 'events',
2044 ])
2045
2046 if unk_prop:
2047 raise ConfigError('unknown stream object property: "{}"'.format(unk_prop))
2048
2049 if 'packet-context-type' in stream_node:
2050 try:
2051 t = self._create_type(stream_node['packet-context-type'])
2052 except Exception as e:
2053 raise ConfigError('cannot create stream\'s packet context type object', e)
2054
2055 stream.packet_context_type = t
2056
2057 if 'event-header-type' in stream_node:
2058 try:
2059 t = self._create_type(stream_node['event-header-type'])
2060 except Exception as e:
2061 raise ConfigError('cannot create stream\'s event header type object', e)
2062
2063 stream.event_header_type = t
2064
2065 if 'event-context-type' in stream_node:
2066 try:
2067 t = self._create_type(stream_node['event-context-type'])
2068 except Exception as e:
2069 raise ConfigError('cannot create stream\'s event context type object', e)
2070
2071 stream.event_context_type = t
2072
2073 if 'events' not in stream_node:
2074 raise ConfigError('missing "events" property in stream object')
2075
2076 events = stream_node['events']
2077
2078 if not _is_assoc_array_prop(events):
2079 raise ConfigError('"events" property of stream object must be an associative array')
2080
2081 if not events:
2082 raise ConfigError('at least one event is needed within a stream object')
2083
2084 cur_id = 0
2085
2086 for ev_name, ev_node in events.items():
2087 try:
2088 ev = self._create_event(ev_node)
2089 except Exception as e:
2090 raise ConfigError('cannot create event "{}"'.format(ev_name), e)
2091
2092 ev.id = cur_id
2093 ev.name = ev_name
2094 stream.events[ev_name] = ev
2095 cur_id += 1
2096
2097 return stream
2098
2099 def _create_streams(self, metadata_node):
2100 streams = collections.OrderedDict()
2101
2102 if 'streams' not in metadata_node:
2103 raise ConfigError('missing "streams" property (metadata)')
2104
2105 streams_node = metadata_node['streams']
2106
2107 if not _is_assoc_array_prop(streams_node):
2108 raise ConfigError('"streams" property (metadata) must be an associative array')
2109
2110 if not streams_node:
2111 raise ConfigError('at least one stream is needed (metadata)')
2112
2113 cur_id = 0
2114
2115 for stream_name, stream_node in streams_node.items():
2116 try:
2117 stream = self._create_stream(stream_node)
2118 except Exception as e:
2119 raise ConfigError('cannot create stream "{}"'.format(stream_name), e)
2120
2121 stream.id = cur_id
2122 stream.name = str(stream_name)
2123 streams[stream_name] = stream
2124 cur_id += 1
2125
2126 return streams
2127
2128 def _create_metadata(self, root):
2129 meta = metadata.Metadata()
2130
2131 if 'metadata' not in root:
2132 raise ConfigError('missing "metadata" property (root)')
2133
2134 if not _is_assoc_array_prop(metadata_node):
2135 raise ConfigError('"metadata" property (root) must be an associative array')
2136
2137 metadata_node = root['metadata']
2138 unk_prop = _get_first_unknown_prop(metadata_node, [
2139 'type-aliases',
2140 'log-levels',
2141 'trace',
2142 'env',
2143 'clocks',
2144 'streams',
2145 ])
2146
2147 if unk_prop:
2148 raise ConfigError('unknown metadata property: "{}"'.format(unk_prop))
2149
2150 self._set_byte_order(metadata_node)
2151 self._register_clocks(metadata_node)
2152 meta.clocks = self._clocks
2153 self._register_type_aliases(metadata_node)
2154 meta.env = self._create_env(metadata_node)
2155 meta.trace = self._create_trace(metadata_node)
2156 self._register_log_levels(metadata_node)
2157 meta.streams = self._create_streams(metadata_node)
2158
2159 return meta
2160
2161 def _get_version(self, root):
2162 if 'version' not in root:
2163 raise ConfigError('missing "version" property (root)')
2164
2165 version_node = root['version']
2166
2167 if not _is_str_prop(version_node):
2168 raise ConfigError('"version" property (root) must be a string')
2169
2170 version_node = version_node.strip()
2171
2172 if version_node not in ['2.0', '2.1']:
2173 raise ConfigError('unsupported version ({}): versions 2.0 and 2.1 are supported'.format(version_node))
2174
2175 # convert version string to comparable version integer
2176 parts = version_node.split('.')
2177 version = int(parts[0]) * 100 + int(parts[1])
2178
2179 return version
2180
2181 def _get_prefix(self, root):
2182 if 'prefix' not in root:
2183 return 'barectf_'
2184
2185 prefix_node = root['prefix']
2186
2187 if not _is_str_prop(prefix_node):
2188 raise ConfigError('"prefix" property (root) must be a string')
2189
2190 if not is_valid_identifier(prefix_node):
2191 raise ConfigError('"prefix" property (root) must be a valid C identifier')
2192
2193 return prefix_node
2194
2195 def _yaml_ordered_load(self, stream):
2196 class OLoader(yaml.Loader):
2197 pass
2198
2199 def construct_mapping(loader, node):
2200 loader.flatten_mapping(node)
2201
2202 return collections.OrderedDict(loader.construct_pairs(node))
2203
2204 OLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
2205 construct_mapping)
2206
2207 return yaml.load(stream, OLoader)
2208
2209 def parse(self, yml):
2210 try:
2211 root = self._yaml_ordered_load(yml)
2212 except Exception as e:
2213 raise ConfigError('cannot parse YAML input', e)
2214
2215 if not _is_assoc_array_prop(root):
2216 raise ConfigError('root must be an associative array')
2217
2218 self._version = self._get_version(root)
2219 meta = self._create_metadata(root)
2220 prefix = self._get_prefix(root)
2221
2222 return Config(self._version, prefix, meta)
2223
2224
2225 def from_yaml(yml):
2226 parser = _YamlConfigParser()
2227 cfg = parser.parse(yml)
2228
2229 return cfg
2230
2231
2232 def from_yaml_file(path):
2233 try:
2234 with open(path) as f:
2235 return from_yaml(f.read())
2236 except Exception as e:
2237 raise ConfigError('cannot create configuration from YAML file'.format(e), e)
This page took 0.077988 seconds and 5 git commands to generate.