1######################################################################### 2# Copyright 2013-2019 by Carnegie Mellon University 3# 4# @OPENSOURCE_HEADER_START@ 5# Use of the Network Situational Awareness Python support library and 6# related source code is subject to the terms of the following licenses: 7# 8# GNU Lesser Public License (LGPL) Rights pursuant to Version 2.1, February 1999 9# Government Purpose License Rights (GPLR) pursuant to DFARS 252.227.7013 10# 11# NO WARRANTY 12# 13# ANY INFORMATION, MATERIALS, SERVICES, INTELLECTUAL PROPERTY OR OTHER 14# PROPERTY OR RIGHTS GRANTED OR PROVIDED BY CARNEGIE MELLON UNIVERSITY 15# PURSUANT TO THIS LICENSE (HEREINAFTER THE "DELIVERABLES") ARE ON AN 16# "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY 17# KIND, EITHER EXPRESS OR IMPLIED AS TO ANY MATTER INCLUDING, BUT NOT 18# LIMITED TO, WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE, 19# MERCHANTABILITY, INFORMATIONAL CONTENT, NONINFRINGEMENT, OR ERROR-FREE 20# OPERATION. CARNEGIE MELLON UNIVERSITY SHALL NOT BE LIABLE FOR INDIRECT, 21# SPECIAL OR CONSEQUENTIAL DAMAGES, SUCH AS LOSS OF PROFITS OR INABILITY 22# TO USE SAID INTELLECTUAL PROPERTY, UNDER THIS LICENSE, REGARDLESS OF 23# WHETHER SUCH PARTY WAS AWARE OF THE POSSIBILITY OF SUCH DAMAGES. 24# LICENSEE AGREES THAT IT WILL NOT MAKE ANY WARRANTY ON BEHALF OF 25# CARNEGIE MELLON UNIVERSITY, EXPRESS OR IMPLIED, TO ANY PERSON 26# CONCERNING THE APPLICATION OF OR THE RESULTS TO BE OBTAINED WITH THE 27# DELIVERABLES UNDER THIS LICENSE. 28# 29# Licensee hereby agrees to defend, indemnify, and hold harmless Carnegie 30# Mellon University, its trustees, officers, employees, and agents from 31# all claims or demands made against them (and any related losses, 32# expenses, or attorney's fees) arising out of, or relating to Licensee's 33# and/or its sub licensees' negligent use or willful misuse of or 34# negligent conduct or willful misconduct regarding the Software, 35# facilities, or other rights or assistance granted by Carnegie Mellon 36# University under this License, including, but not limited to, any 37# claims of product liability, personal injury, death, damage to 38# property, or violation of any laws or regulations. 39# 40# Carnegie Mellon University Software Engineering Institute authored 41# documents are sponsored by the U.S. Department of Defense under 42# Contract FA8721-05-C-0003. Carnegie Mellon University retains 43# copyrights in all material produced under this contract. The U.S. 44# Government retains a non-exclusive, royalty-free license to publish or 45# reproduce these documents, or allow others to do so, for U.S. 46# Government purposes only pursuant to the copyright license under the 47# contract clause at 252.227.7013. 48# 49# @OPENSOURCE_HEADER_END@ 50# 51####################################################################### 52 53from __future__ import absolute_import 54import warnings 55from . import _pyfixbuf 56from . import version 57import binascii 58import ipaddress 59import itertools 60import re 61import struct 62import sys 63import collections 64 65__all__ = ['InfoElement', 'InfoModel', 'Buffer', 66 'Session', 'Exporter', 'Collector', 67 'Template', 'InfoElementSpec', 'Record', 68 'STML', 'STL', 'BL', 'CERT_PEN', 'VARLEN', 69 'BASICLIST', 'Listener', 70 'SUBTEMPLATELIST', 71 'SUBTEMPLATEMULTILIST', 'ENDIAN', 72 'REVERSIBLE', 'DataType', 'Units', 73 'Semantic', '__version__'] 74 75CERT_PEN = 6871 76VARLEN = 65535 77ENDIAN = 1 78REVERSIBLE = 64 79IPV6 = 6 80__version__ = version.pyfixbuf_version 81 82if sys.hexversion >= 0x03000000: 83 long = int 84 zip_longest = itertools.zip_longest 85else: 86 # Allow Python3 syntax to invoke Python2 methods 87 range = xrange 88 zip = itertools.izip 89 zip_longest = itertools.izip_longest 90 91 92class Enum(object): 93 def __new__(cls): 94 raise Exception("Class %s may not be instantiated" % cls.__name__) 95 96 @classmethod 97 def get_name(cls, value): 98 """ 99 Returns the string name associated with *value* in this enumeration. 100 101 Raises :exc:`KeyError` if *value* is not known to this enumeration. 102 Raises :exc:`TypeError` if *value* is not an instance of an `int`. 103 """ 104 if isinstance(value, int): 105 return cls._names[value] 106 raise TypeError("Integer argument required") 107 108 @classmethod 109 def to_string(cls, value): 110 """ 111 Returns a string that includes the type name and the name associated 112 with *value* in this enumeration; e.g., `DataType(UINT8)` or 113 `Units(PACKETS)`. 114 115 Returns a string even when *value* is not known to this enumeration. 116 """ 117 try: 118 return "%s(%s)" % (cls.__name__, cls._names[value]) 119 except: 120 return "%s(%s)" % (cls.__name__, str(value)) 121 122 @classmethod 123 def by_name(cls, name): 124 """ 125 Returns the value associated with the name *name* in this enumeration. 126 127 Raises :exc:`KeyError` if this enumeration does not have a value 128 asociated with *name*. Raises :exc:`TypeError` if *name* is not a 129 :class:`str`. 130 """ 131 if name in cls._names: 132 return cls.__dict__[name] 133 if isinstance(name, str): 134 raise KeyError("Invalid %s '%s'" % (cls.__name__, str(name))) 135 raise TypeError("String argument required") 136 137 @classmethod 138 def _init(cls): 139 for i,name in enumerate(cls._names): 140 setattr(cls, name, i) 141 142 143def enum(name, seq, **named): 144 named['_names'] = seq 145 enums = dict(list(zip(seq, list(range(len(seq))))), **named) 146 return type(name, (Enum,), enums) 147 148class DataType(Enum): 149 """ 150 An enumeration containing the DataTypes supported by pyfixbuf. 151 152 In the following table, the RLE column indicates whether the type supports 153 reduced length encoding. 154 155 .. list-table:: 156 :header-rows: 1 157 :widths: 20, 8, 8, 4, 20 158 159 * - Type 160 - Integer Value 161 - Length 162 - RLE 163 - Python Return Type 164 * - DataType.OCTET_ARRAY 165 - 0 166 - VARLEN 167 - No 168 - bytearray 169 * - DataType.UINT8 170 - 1 171 - 1 172 - No 173 - int 174 * - DataType.UINT16 175 - 2 176 - 2 177 - Yes 178 - long 179 * - DataType.UINT32 180 - 3 181 - 4 182 - Yes 183 - long 184 * - DataType.UINT64 185 - 4 186 - 8 187 - Yes 188 - long 189 * - DataType.INT8 190 - 5 191 - 1 192 - No 193 - long 194 * - DataType.INT16 195 - 6 196 - 2 197 - Yes 198 - long 199 * - DataType.INT32 200 - 7 201 - 4 202 - Yes 203 - long 204 * - DataType.INT64 205 - 8 206 - 8 207 - Yes 208 - long 209 * - DataType.FLOAT32 210 - 9 211 - 4 212 - No 213 - float 214 * - DataType.FLOAT64 215 - 10 216 - 8 217 - No 218 - float 219 * - DataType.BOOL 220 - 11 221 - 1 222 - No 223 - bool 224 * - DataType.MAC_ADDR 225 - 12 226 - 6 227 - No 228 - string 229 * - DataType.STRING 230 - 13 231 - VARLEN 232 - No 233 - string 234 * - DataType.SECONDS 235 - 14 236 - 4 237 - No 238 - long 239 * - DataType.MILLISECONDS 240 - 15 241 - 8 242 - No 243 - long 244 * - DataType.MICROSECONDS 245 - 16 246 - 8 247 - No 248 - long 249 * - DataType.NANOSECONDS 250 - 17 251 - 8 252 - No 253 - long 254 * - DataType.IP4ADDR 255 - 18 256 - 4 257 - No 258 - ipaddress.IPv4Address 259 * - DataType.IP6ADDR 260 - 19 261 - 16 262 - No 263 - ipaddress.IPv6Address 264 * - DataType.BASIC_LIST 265 - 20 266 - VARLEN 267 - No 268 - pyfixbuf.BL 269 * - DataType.SUB_TMPL_LIST 270 - 21 271 - VARLEN 272 - No 273 - pyfixbuf.STL 274 * - DataType.SUB_TMPL_MULTI_LIST 275 - 22 276 - VARLEN 277 - No 278 - pyfixbuf.STML 279 """ 280 _names = ("OCTET_ARRAY", "UINT8", "UINT16", "UINT32", "UINT64", "INT8", 281 "INT16", "INT32", "INT64", "FLOAT32", "FLOAT64", "BOOL", 282 "MAC_ADDR", "STRING", "SECONDS", "MILLISECONDS", 283 "MICROSECONDS", "NANOSECONDS", "IP4ADDR", "IP6ADDR", 284 "BASIC_LIST", "SUB_TMPL_LIST", "SUB_TMPL_MULTI_LIST") 285 286 @staticmethod 287 def check_type(dt, value): 288 """ 289 Returns ``True`` if a field whose :class:`InfoElement`'s DataType is 290 `dt` may be set to `value` given it the Pythonic type of `value`. 291 """ 292 if type(value) is int: 293 if dt in [DataType.BOOL, DataType.UINT8, DataType.UINT16, 294 DataType.UINT32, DataType.UINT64, 295 DataType.INT8, DataType.INT16, 296 DataType.INT32, DataType.INT64, 297 DataType.SECONDS, DataType.MILLISECONDS, 298 DataType.MICROSECONDS, DataType.NANOSECONDS]: 299 return True 300 if type(value) is long: 301 if dt in [DataType.UINT32, DataType.UINT64, 302 DataType.INT32, DataType.INT64, 303 DataType.SECONDS, DataType.MICROSECONDS, 304 DataType.NANOSECONDS, DataType.MILLISECONDS]: 305 return True 306 if type(value) is str: 307 if (dt in [DataType.OCTET_ARRAY, DataType.STRING] or (dt==VARLEN)): 308 return True 309 if type(value) is bytearray: 310 if dt in [DataType.OCTET_ARRAY, DataType.MAC_ADDR]: 311 return True 312 if type(value) is float and dt in [DataType.FLOAT32, DataType.FLOAT64]: 313 return True 314 if type(value) is bool and dt in [DataType.BOOL]: 315 return True 316 if type(value) is ipaddress.IPv4Address and dt in [DataType.IP4ADDR]: 317 return True 318 if type(value) is ipaddress.IPv6Address and dt in [DataType.IP46DDR]: 319 return True 320 return False 321 322 @staticmethod 323 def get_length(dt): 324 """Returns the standard length of a DataType""" 325 if dt in [DataType.OCTET_ARRAY, DataType.STRING, DataType.BASIC_LIST, 326 DataType.SUB_TMPL_LIST, DataType.SUB_TMPL_MULTI_LIST]: 327 return VARLEN 328 elif dt in [DataType.UINT8, DataType.INT8, DataType.BOOL]: 329 return 1 330 elif dt in [DataType.UINT16, DataType.INT16]: 331 return 2 332 elif dt in [DataType.UINT32, DataType.INT32, DataType.SECONDS, 333 DataType.FLOAT32, DataType.IP4ADDR]: 334 return 4 335 elif (dt == DataType.MAC_ADDR): 336 return 6 337 elif dt in [DataType.UINT64, DataType.INT64, DataType.FLOAT64, 338 DataType.MILLISECONDS, DataType.MICROSECONDS, 339 DataType.NANOSECONDS]: 340 return 8 341 elif (dt == DataType.IP6ADDR): 342 return 16 343 else: 344 return VARLEN 345 346 @staticmethod 347 def refine_type_for_length(dt, len): 348 """Chooses a DataType given a DataType and a length""" 349 # FIXME: Move this function onto DataType class 350 signed = False 351 float = False 352 if dt in [DataType.INT8,DataType.INT16,DataType.INT32,DataType.INT64]: 353 signed = True 354 elif dt in [DataType.FLOAT32, DataType.FLOAT64]: 355 float = True 356 if (len == 1): 357 if signed: 358 return DataType.INT8 359 else: 360 return DataType.UINT8 361 elif (len == 2): 362 if signed: 363 return DataType.INT16 364 else: 365 return DataType.UINT16 366 elif (len <= 4): 367 if signed: 368 return DataType.INT32 369 elif float: 370 return DataType.FLOAT32 371 else: 372 return DataType.UINT32 373 elif (len <= 8): 374 if signed: 375 return DataType.INT64 376 elif float: 377 return DataType.FLOAT64 378 else: 379 return DataType.UINT64 380 381 @staticmethod 382 def supports_RLE(dt): 383 """Returns True if the DataType supports reduced length encoding""" 384 return dt not in [DataType.BOOL, DataType.UINT8, DataType.INT8, 385 DataType.IP4ADDR, DataType.IP6ADDR, 386 DataType.SECONDS, DataType.MILLISECONDS, 387 DataType.MICROSECONDS, DataType.NANOSECONDS, 388 DataType.MAC_ADDR, DataType.FLOAT32, 389 DataType.BASIC_LIST, DataType.SUB_TMPL_LIST, 390 DataType.SUB_TMPL_MULTI_LIST] 391DataType._init() 392 393 394class Units(Enum): 395 """ 396 An enumeration containing the Units supported by pyfixbuf. 397 398 ============================ ============= 399 Units Integer Value 400 ============================ ============= 401 Units.NONE 0 402 Units.BITS 1 403 Units.OCTETS 2 404 Units.PACKETS 3 405 Units.FLOWS 4 406 Units.SECONDS 5 407 Units.MILLISECONDS 6 408 Units.MICROSECONDS 7 409 Units.NANOSECONDS 8 410 Units.WORDS 9 411 Units.MESSAGES 10 412 Units.HOPS 11 413 Units.ENTRIES 12 414 Units.FRAMES 13 415 Units.PORTS 14 416 UNITS.INFERRED 15 417 ============================ ============= 418 """ 419 _names = ("NONE", "BITS", "OCTETS", "PACKETS", "FLOWS", "SECONDS", 420 "MILLISECONDS", "MICROSECONDS", "NANOSECONDS", "WORDS", 421 "MESSAGES", "HOPS", "ENTRIES", "FRAMES", "PORTS", "INFERRED") 422Units._init() 423 424 425class Semantic(Enum): 426 """ 427 An enumeration containg the available Semantic values. 428 429 ============================ ============= 430 Semantic Integer Value 431 ============================ ============= 432 Semantic.DEFAULT 0 433 Semantic.QUANTITY 1 434 Semantic.TOTALCOUNTER 2 435 Semantic.DELTACOUNTER 3 436 Semantic.IDENTIFIER 4 437 Semantic.FLAGS 5 438 Semantic.LIST 6 439 Semantic.SNMPCOUNTER 7 440 Semantic.SNMPGAUGE 8 441 ============================ ============= 442 """ 443 _names = ("DEFAULT", "QUANTITY", "TOTALCOUNTER", "DELTACOUNTER", 444 "IDENTIFIER", "FLAGS", "LIST", "SNMPCOUNTER", "SNMPGAUGE") 445Semantic._init() 446 447 448BASICLIST = DataType.BASIC_LIST 449SUBTEMPLATELIST = DataType.SUB_TMPL_LIST 450SUBTEMPLATEMULTILIST = DataType.SUB_TMPL_MULTI_LIST 451 452 453def assert_type(typ, arg): 454 if not isinstance(arg, typ): 455 raise TypeError("Expected %s but got %s instead" % 456 (typ.__name__, type(arg).__name__)) 457 458def yield_record_subrecords(record, tmpl_id): 459 assert_type(Record, record) 460 if tmpl_id == 0: 461 yield record 462 elif tmpl_id == record.template.template_id: 463 yield record 464 for infoelem in record: 465 if (type(infoelem) == STL) or (type(infoelem) == STML): 466 for sub_record in infoelem.iter_records(tmpl_id): 467 yield sub_record 468 469 470# class InfoElement(object): 471# defined entirely in C 472InfoElement = _pyfixbuf.InfoElement 473 474# class InfoElementSpec(object): 475# defined entirely in C 476InfoElementSpec = _pyfixbuf.InfoElementSpec 477 478 479class _SessionCallbacks(object): 480 def __init__(self, session): 481 self.session = session 482 self.callbacks = [] 483 484 def add(self, callback): 485 if not callable(callback): 486 raise ValueError("Callbacks must be callable") 487 self.callbacks.append(callback) 488 489 def __call__(self, template): 490 ctx = None 491 for cb in self.callbacks: 492 nctx = cb(self.session, Template._wrap(template), ctx) 493 if nctx is not None: 494 ctx = nctx 495 return ctx 496 497class _IgnoreList(object): 498 def __init__(self, values): 499 self.list = values 500 501 def __call__(self, session, template, ctx): 502 if template.template_id in self.list: 503 session.add_template_pair(template.template_id, 0) 504 505 506class Session(_pyfixbuf.fbSessionBase): 507 """ 508 Creates an empty :class:`Session` given an :class:`InfoModel`. 509 510 Args: 511 model (:class:`InfoModel`): The Information Model to assocate with the 512 Session. 513 """ 514 def __init__(self, model): 515 assert_type(InfoModel, model) 516 self.model = model 517 _pyfixbuf.fbSessionBase.__init__(self, model._unwrap()) 518 self.internal_templates = dict() 519 self.external_templates = dict() 520 self._callbacks = _SessionCallbacks(self) 521 _pyfixbuf.fbSessionBase.setTemplateCallback(self, self._callbacks) 522 self._ignorelist = None 523 524 def _add_template(self, template, template_id, internal): 525 """ 526 Adds the given Template to the Session. Adds only as internal 527 template when `internal` is True, only as external when `internal` is 528 False, and as both when `internal` is None. 529 """ 530 assert_type(Template, template) 531 if (template_id < 0 or template_id > 65535): 532 raise Exception("Invalid ID: " + str(template_id) + 533 " Template ID must be a 16 bit integer") 534 template._finalize() 535 if internal is not True: 536 # `internal` is either False or None; add external 537 template_id = _pyfixbuf.fbSessionBase.addTemplate( 538 self, template._unwrap(), template_id, False) 539 self.external_templates[template_id] = template 540 if internal is not False: 541 # `internal` is either True or None; add internal 542 template_id = _pyfixbuf.fbSessionBase.addTemplate( 543 self, template._unwrap(), template_id, True) 544 self.internal_templates[template_id] = template 545 return template_id 546 547 def add_template(self, template, template_id=0): 548 """ 549 Adds the given :class:`Template` *template* to the :class:`Session` 550 with the optional *template_id*. This template may be used as both an 551 internal and an external template. Use :meth:`add_internal_template` 552 or :meth:`add_external_template` to be more selective on template 553 usage. 554 555 If a *template_id* is not given or 0, libfixbuf will automatically 556 choose one. *template_id* will be used for representing both the 557 internal and external template. The valid range for *template_id* is 558 256 to 65535 (0x100 to 0xFFFF). 559 560 Returns the *template_id* of the added template. 561 """ 562 return self._add_template(template, template_id, None) 563 564 def add_internal_template(self, template, template_id=0): 565 """ 566 Adds the given *template* as an internal template to the session with 567 the optionally given *template_id*. An internal template determines 568 how the data will be presented when transcoded. See also 569 :meth:`add_template`. 570 571 If *template_id* is not set or 0, libfixbuf will automatically 572 choose one. 573 574 Returns the *template_id* of the added template. 575 """ 576 return self._add_template(template, template_id, True) 577 578 def add_external_template(self, template, template_id=0): 579 """ 580 Adds the given *template* as an external template to the session with 581 the optionally given *template_id*. An external template is used when 582 exporting IPFIX data. (libfixbuf automatically creates external 583 templates when collecting IPFIX.) See also :meth:`add_template`. 584 585 If *template_id* is not set or 0, libfixbuf will automatically 586 choose one. 587 588 Returns the *template_id* of the added template. 589 """ 590 return self._add_template(template, template_id, False) 591 592 def get_template(self, template_id, internal=False): 593 """ 594 Returns the :class:`Template` with the given *template_id*. By 595 default, the external template is returned. Set *internal* to ``True`` 596 to retrieve the internal template with the given *template_id*. The 597 returned :class:`Template` may not be modified. 598 599 Raises an Exception if a :class:`Template` with the given 600 *template_id* does not exist on the :class:`Session`. 601 """ 602 template = _pyfixbuf.fbSessionBase.getTemplate(self, template_id, 603 internal) 604 if (template == None): 605 if internal: 606 raise Exception("Internal template with ID " + str(template_id) 607 + " does not belong to this Session.") 608 else: 609 raise Exception("External template with ID " + str(template_id) 610 + " has not been added to this Session.") 611 new_template = Template._wrap(template) 612 return new_template 613 614 def decode_only(self, id_list): 615 """ 616 When decoding, causes this :class:`Session` to ignore a record in a 617 subTemplateList (:class:`STL`) or subTemplateMultiList (:class:`STML`) 618 **unless** the record has a template ID in the given *id_list* of 619 ``int`` s. The method is only used when the :class:`Session` is bound 620 to a :class:`Collector` or :class:`Listener`. 621 622 This method has no effect on records that are not in a subTemplateList 623 or subTemplateMultiList. See also :meth:`ignore_templates`. 624 """ 625 for item in id_list: 626 if not isinstance(item, int): 627 raise ValueError("Invalid Template ID: " + str(item) + 628 ". Template ID in List must be an Integer"); 629 _pyfixbuf.fbSessionBase.addTemplatePair(self, item, item) 630 631 def ignore_templates(self, id_list): 632 """ 633 When decoding, causes this :class:`Session` to ignore a record in a 634 subTemplateList (:class:`STL`) or subTemplateMultiList (:class:`STML`) 635 **when** the record has a template ID in the given *id_list* of 636 ``int`` s. The method is only used when the :class:`Session` is bound 637 to a :class:`Collector` or :class:`Listener`. 638 639 This method has no effect on records that are not in a subTemplateList 640 or subTemplateMultiList. See also :meth:`decode_only`. 641 """ 642 for item in id_list: 643 if not isinstance(item, int): 644 raise ValueError("Invalid Template ID: " + str(item) + 645 ". Template ID in List must be an Integer"); 646 if self._ignorelist is None: 647 self._ignorelist = _IgnoreList(id_list) 648 else: 649 self._ignorelist.list = id_list 650 self.add_template_callback(self._ignorelist) 651 652 def add_template_pair(self, external_template_id, internal_template_id): 653 """ 654 Specifies how to transcode data within structured data (a list). 655 656 That is, tells a :class:`Collector` or :class:`Listener` that data in 657 a :class:`STML` or :class:`STL` whose :class:`Template` is 658 *external_template_id* is to be transcoded into the :class:`Template` 659 given by *internal_template_id*. 660 661 By default, libfixbuf transcodes each entry in the :class:`STML` or 662 :class:`STL` with the external template it received, requiring the 663 collector to free or clear any memory allocated for the list 664 elements. The collector can change this behavior by adding a "template 665 pair." For each entry in the STL or STML, if the entry has the given 666 *external_template_id*, it will use the given *internal_template_id* 667 to transcode the record. The *internal_template_id* must reference an 668 internal template that was previously added to the :class:`Session`. 669 If *internal_template_id* is 0, the entry will not be transcoded and 670 will be ignored by libfixbuf. 671 672 Once a template pair has been added - the default is to ONLY decode 673 entries that have an external_template_id in this template pair table. 674 Therefore, any entries in a STML or STL that reference a template id 675 not in this table will be dropped. 676 """ 677 if (internal_template_id in self.internal_templates or 678 internal_template_id == 0): 679 _pyfixbuf.fbSessionBase.addTemplatePair(self, external_template_id, 680 internal_template_id) 681 else: 682 raise Exception("An internal template with id: " + 683 str(internal_template_id) + 684 " has not been added to this session") 685 686 def add_template_callback(self, callback): 687 """ 688 Adds the callable *callback* to be called whenever a new external 689 template is read from a :class:`Collector` or :class:`Listener`. 690 691 Multiple callbacks may be added to a :class:`Session`. The callbacks 692 may specify a **context** object that is stored with the 693 :class:`Template`. 694 695 After setting the callback(s), when a new template is read the 696 callbacks will be called in the order they are added, and each may 697 modify the context object. After all callbacks are called, the 698 :class:`Template`'s context object will be set to the result. A 699 :class:`Template`'s context may be retrieved via 700 :meth:`Template.get_context`. 701 702 The *callback* must be a callable and it will be called with three 703 arguments: *session*, *template*, and *context*. *session* is the 704 :class:`Session` from which the callback was added. *template* is the 705 new :class:`Template` and will have its `template_id` property set to 706 the external template ID. 707 708 The *context* argument is the current object that is to be stored as 709 the Template's context. The callback should either return a new 710 object to use as the context or ``None``. A return value of ``None`` 711 indicates that the context object should not change. 712 713 To avoid undefined behavior, the callback functions should be added 714 before the :class:`Session` is bound to a :class:`Buffer`. 715 """ 716 self._callbacks.add(callback) 717 718 719class InfoModel(object): 720 """ 721 An IPFIX Information Model stores all of the Information Elements 722 (:class:`InfoElement`) that can be collected or exported by a 723 Collecting or Exporting Process. 724 725 The constructor creates a new Information Model and 726 adds the default IANA-managed Information Elements. 727 """ 728 def __init__(self): 729 self._parent = _pyfixbuf.fbInfoModelBase() 730 731 @classmethod 732 def _wrap(cls, base): 733 obj = cls.__new__(cls) 734 obj._parent = base 735 return obj 736 737 def _unwrap(self): 738 return self._parent 739 740 def add_element(self, element): 741 """Adds the given :class:`InfoElement` to the 742 :class:`InfoModel`. 743 """ 744 self._unwrap().addElement(element) 745 746 def add_element_list(self, elements): 747 """Adds each of the :class:`InfoElement` objects in *elements* to the 748 :class:`InfoModel`.""" 749 if isinstance(elements, str): 750 raise TypeError("Expected a non-str iterable") 751 for item in elements: 752 if item != None: 753 self.add_element(item) 754 755 def get_element_length(self, *args, **kwds): 756 """ 757 Returns the default length of the Information Element with the 758 given *name* in the Information Model. If *type* (BASICLIST, 759 SUBTEMPLATELIST, SUBTEMPLATEMULTILIST) is given, assumes 760 this element will be used in a list and the length of the list 761 structure in libfixbuf will be returned. If the Information 762 Element is a variable length (VARLEN) element, the length that 763 will be returned is the length of the fbVarfield_t structure 764 in libfixbuf. To get the length of the Information Element as 765 it is defined in the Information Model, use 766 :meth:`get_element` to return the :class:`InfoElement` and get 767 its length attribute. 768 """ 769 return self._unwrap().getElementLength(*args, **kwds) 770 771 def get_element_type(self, name): 772 """ 773 Returns the type of the Information Element as defined in the 774 :class:`InfoModel` given the :class:`InfoElement` *name*. 775 """ 776 return self._unwrap().getElementTrueType(name) 777 778 def get_element(self, *args, **kwds): 779 """ 780 Returns the :class:`InfoElement` given the *name* or *id* and *ent*. 781 Raises :exc:`KeyError` when the element is not found. 782 783 Example: 784 785 >>> ie = model.get_element('protocolIdentifier') 786 >>> ie.name 787 'protocolIdentifier' 788 >>> ie = model.get_element(id = 4) 789 >>> ie.name 790 'protocolIdentifier' 791 >>> ie = m.get_element(id = 4, ent = CERT_PEN) 792 Traceback ... 793 KeyError: 'No element 6871/4' 794 """ 795 return self._unwrap().getElement(*args, **kwds) 796 797 def add_options_element(self, rec): 798 """ 799 Adds the information element contained in the Options :class:`Record`. 800 Use this method for incoming Options Records that contain Information 801 Element Type Information. 802 """ 803 if (not isinstance(rec, dict)): 804 rec.as_dict() 805 dt = rec["informationElementDataType"] 806 len = DataType.get_length(dt) 807 return self.add_element( 808 InfoElement(rec["informationElementName"], 809 rec["privateEnterpriseNumber"], 810 rec["informationElementId"], len, type=dt, 811 min=rec["informationElementRangeBegin"], 812 max=rec["informationElementRangeEnd"], 813 units=rec["informationElementUnits"], 814 semantic=rec["informationElementSemantics"], 815 description=rec["informationElementDescription"])) 816 817 def read_from_xml_file(self, filename): 818 """ 819 Adds to this :class:`InfoModel` the Information Elements found in the 820 XML file in `filename`. See also :meth:`read_from_xml_data`. 821 """ 822 file = open(filename, "r") 823 data = file.read() 824 file.close() 825 return self._unwrap().readXMLData(data) 826 827 def read_from_xml_data(self, xml_data): 828 """ 829 Adds to this :class:`InfoModel` the Information Elements found in the 830 XML data stored in `xml_data`, which is an object that supports the 831 buffer call interface. See also :meth:`read_from_xml_file`. 832 """ 833 return self._unwrap().readXMLData(xml_data) 834 835 836class Exporter(_pyfixbuf.fbExporterBase): 837 """ 838 Creates an empty :class:`Exporter`. Initialize the exporter using 839 :meth:`init_file` or :meth:`init_net`. 840 """ 841 def __init__(self): 842 self.initialized = 0 843 _pyfixbuf.fbExporterBase.__init__(self) 844 845 def init_file(self, filename): 846 """ 847 Initializes the :class:`Exporter` to write to the given *filename*. 848 """ 849 self.initialized = 1 850 _pyfixbuf.fbExporterBase.allocFile(self, filename) 851 852 def init_net(self, hostname, transport="tcp", port=4739): 853 """ 854 Initializes the :class:`Exporter` to write to the given *hostname*, 855 *port* over the given *transport*. 856 857 Given *hostname* may be a hostname or IP address. 858 859 Acceptable values for *transport* are ``tcp`` and ``udp``. Default is 860 ``tcp``. 861 862 Given *port* must be greater than 1024. Default is 4739. 863 """ 864 if not isinstance(port, int): 865 port = int(port) 866 if (port < 1024 or port == 0): 867 raise Exception("Invalid Port. Port must be greater than 1024.") 868 self.initialized = 1 869 _pyfixbuf.fbExporterBase.allocNet(self, host=hostname, 870 transport=transport, port=str(port)) 871 872 873class Collector(_pyfixbuf.fbCollectorBase): 874 """ 875 Creates an uninitialized :class:`Collector`. An IPFIX Collector manages 876 the file it is reading from. 877 Initialize the collector using :meth:`init_file`. 878 """ 879 def __init__(self): 880 self.initialized = 0 881 _pyfixbuf.fbCollectorBase.__init__(self) 882 883 def init_file(self, filename): 884 """ 885 Initialize the :class:`Collector` to read from the given *filename*. 886 *filename* should be the path to a valid IPFIX File or the string ``-`` 887 to read from the standard input. 888 """ 889 self.initialized = 1 890 return _pyfixbuf.fbCollectorBase.allocCollectorFile(self, filename) 891 892 893class Template(object): 894 """ 895 Creates a new Template using the given *model*, an :class:`InfoModel`. 896 An IPFIX Template 897 is an ordered list of the Information Elements that are to be 898 collected or exported. For export, the order of Information Elements 899 in the Templates determines how the data will be exported. 900 901 If *type* is given, an Information Element Type Information Options 902 Template will be created. The appropriate elements will automatically 903 be added to the template and the scope will be sent. See :rfc:`5610` 904 for more information. 905 906 Once a Template has been added to a :class:`Session`, it cannot be 907 altered. 908 909 A :class:`Template` can be accessed like a dictionary or a list to 910 retrieve a specific :class:`InfoElementSpec`. 911 """ 912 def __init__(self, model, type=False): 913 assert_type(InfoModel, model) 914 self._parent = _pyfixbuf.fbTemplateBase(model._unwrap(), type) 915 self.infomodel = model 916 # the InfoElements as stored on the Template (len is the len_override, 917 # name is in _templatename, midx may be non-zero, ref.canon should be 918 # followed) 919 self._elements = [] 920 self._scope = self._unwrap().scope or None 921 if type: 922 self._build_element_list() 923 924 @classmethod 925 def _wrap(cls, base): 926 """Wraps a TemplateBase object as a Template.""" 927 obj = cls.__new__(cls) 928 obj._parent = base 929 obj.infomodel = InfoModel._wrap(base.infomodel) 930 obj._elements = [] 931 obj._build_element_list() 932 obj._scope = base.scope or None 933 return obj 934 935 def _unwrap(self): 936 """Returns the TemplateBase object.""" 937 return self._parent 938 939 def _finalize(self): 940 """Called before adding to a session.""" 941 if (not self.read_only and self._unwrap().scope == 0 and 942 self.scope is not None): 943 self._unwrap().scope = self._scope 944 945 @property 946 def read_only(self): 947 """ 948 Returns ``True`` if this :class:`Template` has been added to a 949 :class:`Session`. 950 """ 951 return self._unwrap().read_only 952 953 @property 954 def template_id(self): 955 """Returns the template ID for this :class:`Template`.""" 956 return self._unwrap().template_id 957 958 @property 959 def type(self): 960 """ 961 Returns ``True`` if this :class:`Template` is an Information Element 962 Type Information Options Template. 963 """ 964 return self._unwrap().type 965 966 @property 967 def scope(self): 968 """ 969 Returns the number of scoped elements in this :class:`Template`. A 970 scope of ``None`` means that this :class:`Template` is not an Options 971 template. 972 """ 973 return self._scope 974 975 @scope.setter 976 def scope(self, value): 977 """ 978 Sets the scope for this :class:`Template`. ``None`` means no scope, 979 and an argument of zero means that all elements currently in the 980 :class:`Template` will be made part of the scope. 981 """ 982 if self.read_only: 983 raise AttributeError( 984 "Scope cannot be changed; Template is owned by a Session") 985 if value is not None and (value < 0 or value > len(self)): 986 raise AttributeError( 987 "Too few or too many scoped elements") 988 if value == 0: 989 value = len(self) 990 self._scope = value 991 992 def copy(self): 993 """Returns a copy of this :class:`Template`.""" 994 copy = Template(self.infomodel) 995 copy.add_spec_list(iter(self)) 996 copy._scope = self.scope 997 return copy 998 999 def _add_spec(self, spec): 1000 """Adds the given :class:`InfoElementSpec` *spec* to the Template.""" 1001 assert_type(InfoElementSpec, spec) 1002 ie = self.infomodel.get_element(spec.name) 1003 if spec.length and spec.length != ie.length and ie.length != VARLEN: 1004 if (spec.length > self.infomodel.get_element_length(spec.name)): 1005 raise Exception("InfoElementSpec " + spec.name + " uses " 1006 "invalid reduced-length. Must be smaller than " 1007 "the default InfoElement length.") 1008 if (not(DataType.supports_RLE(ie.type))): 1009 raise Exception("Data Type of InfoElement " + spec.name + 1010 " does not support reduced-length encoding.") 1011 idx = self._unwrap().addSpec(spec) 1012 self._elements.append(self._unwrap().getIndexedIE(idx)) 1013 1014 def add_spec(self, spec): 1015 """Appends a given :class:`InfoElementSpec` *spec* to the Template. 1016 1017 Once the Template has been added to a :class:`Session`, it 1018 cannot be altered. 1019 """ 1020 if (self.read_only): 1021 raise Exception("Invalid add: Template has already been added " 1022 "to the session.") 1023 self._add_spec(spec) 1024 1025 def add_spec_list(self, specs): 1026 """Appends each of the :class:`InfoElementSpec` items in *specs* to 1027 the :class:`Template`. 1028 1029 Once the :class:`Template` has been added to the :class:`Session`, 1030 it can not be altered. 1031 """ 1032 if (self.read_only): 1033 raise Exception("Invalid add: Template has already been added" 1034 " to the session.") 1035 if isinstance(specs, str): 1036 raise TypeError("Expected a non-str iterable") 1037 for spec in specs: 1038 self._add_spec(spec) 1039 1040 def add_element(self, name): 1041 """Appends an Information Element with the given *name* to this 1042 :class:`Template`. This function may be used as an alternative to 1043 :meth:`add_spec`. 1044 1045 This function creates an :class:`InfoElementSpec` with the given 1046 element *name* and default length and adds it to the Template. 1047 1048 Once the Template has been added to the :class:`Session`, it 1049 can not be altered. 1050 """ 1051 self.add_spec(InfoElementSpec(name)) 1052 1053 def get_indexed_ie(self, index): 1054 """ 1055 Returns the :class:`InfoElement` at the given positional *index* in 1056 the :class:`Template`. Unlike the :meth:`__getitem__` method which 1057 returns the :class:`InfoElementSpec`, this method 1058 returns the :class:`InfoElement` at a particular index. 1059 """ 1060 return self._unwrap().getIndexedIE(index, True) 1061 1062 def __contains__(self, element): 1063 """ 1064 Implements the ``in`` operator for :class:`Template` instances: Tests 1065 whether *element* is in the :class:`Template`. 1066 1067 If *element* is an `int`, return ``True`` if it is non-negative and 1068 less than the length of the :class:`Template`. 1069 1070 If *element* is a `str`, return ``True`` if an Information Element 1071 with the name *element* is included in the :class:`Template`. 1072 1073 If *element* is an :class:`InfoElement` or :class:`InfoElementSpec`, 1074 return ``True`` if the element exists in the :class:`Template`, 1075 ``False`` otherwise. 1076 1077 Examples: 1078 1079 >>> t = Template(InfoModel()) 1080 >>> t.add_element("protocolIdentifier") 1081 >>> "protocolIdentifier" in t 1082 True 1083 >>> InfoElementSpec("protocolIdentifier") in t 1084 True 1085 """ 1086 if isinstance(element, int): 1087 return element >= 0 and element < len(self) 1088 return self._unwrap().containsElement(element) 1089 1090 def __len__(self): 1091 """ 1092 Implements the built-in `len()` method for :class:`Template` 1093 instances: Returns the number of :class:`InfoElementSpec` objects in 1094 the :class:`Template`. 1095 1096 Examples: 1097 1098 >>> t = Template(InfoModel()) 1099 >>> t.add_element("protocolIdentifier") 1100 >>> len(t) 1101 1 1102 """ 1103 return self._unwrap().elementCount() 1104 1105 def __getitem__(self, key): 1106 """ 1107 Implements the evaluation of ``template[key]`` for :class:`Template` 1108 instances: Returns the :class:`InfoElementSpec` represented by *key*. 1109 1110 If *key* is an :class:`int`, it is treated as a positional index and 1111 an :exc:`IndexError` exception is raised when it is out of range. 1112 1113 If *key* is a :class:`str`, it is treated as the name of the 1114 :class:`InfoElementSpec` to find and :exc:`KeyError` Exception is 1115 raised when *key* is not the name of an :class:`InfoElementSpec` on 1116 the :class:`Template`. If an :class:`InfoElementSpec` is repeated in 1117 the :class:`Template`, querying by name always returns the first 1118 element. 1119 1120 Any other type for *key* raises a :exc:`TypeError`. 1121 1122 Examples: 1123 1124 >>> t = Template(InfoModel()) 1125 >>> t.add_element("protocolIdentifier") 1126 >>> t[0] 1127 <pyfixbuf.InfoElementSpec object at 0x107a0fc00> 1128 >>> t["protocolIdentifier"] 1129 <pyfixbuf.InfoElementSpec object at 0x107a0fc00> 1130 """ 1131 if (isinstance(key, str)): 1132 for item in self._elements: 1133 if item._templatename == key: 1134 return InfoElementSpec(item._templatename, item.length) 1135 raise KeyError("No InfoElementSpec with name " + key) 1136 elif (isinstance(key, int)): 1137 if key < len(self._elements): 1138 item = self._elements[key] 1139 return InfoElementSpec(item._templatename, item.length) 1140 raise IndexError("No element at index " + str(key)) 1141 else: 1142 raise TypeError("Expected a str or an int") 1143 1144 def __iter__(self): 1145 """ 1146 Implements the method to return an Iterator over the 1147 :class:`InfoElementSpec` objects in this :class:`Template`. See also 1148 :meth:`ie_iter`. 1149 """ 1150 for i in range(len(self)): 1151 yield self[i] 1152 1153 def ie_iter(self): 1154 """ 1155 Returns an Iterator over the :class:`InfoElement` objects in this 1156 :class:`Template`. See also :meth:`__iter__`. 1157 1158 Example: 1159 1160 >>> for ie in template: 1161 ... print ie.name, DataType.get_name(ie.type) 1162 ... 1163 """ 1164 for i in range(len(self)): 1165 yield self.get_indexed_ie(i) 1166 1167 def get_context(self): 1168 """ 1169 Returns the :class:`Template`'s context object as set by the callables 1170 registered with the :meth:`Session.add_template_callback` function. 1171 """ 1172 return self._unwrap().getContext() 1173 1174 def _build_element_list(self): 1175 """ 1176 Updates the _elements list with the IEs from the base object. 1177 """ 1178 for i in range(len(self)): 1179 self._elements.append(self._unwrap().getIndexedIE(i)) 1180 1181 1182class Record(_pyfixbuf.fbRecordBase): 1183 """ 1184 Creates an empty Record given an :class:`InfoModel`, *model*, and 1185 optionally a *template* or a *record*. 1186 1187 The :class:`Record` is returned from a collection :class:`Buffer` or is 1188 added to an exporting :class:`Buffer`. 1189 1190 When adding elements to a :class:`Record`, the :class:`Record` should 1191 match a :class:`Template`. If the process is collecting, the 1192 :class:`Record` should match the Internal Template. For an Exporting 1193 process, the :class:`Record` should match the External Template, and there 1194 should be one :class:`Record` for each External Template. A 1195 :class:`Record` can not contain more Information Elements than it's 1196 associated *template*. Information Elements should be added to the 1197 :class:`Record` in the same order as the :class:`Template`. 1198 1199 If a *template* is given to the constructor, all Information Elements that 1200 exist in the *template* are added to the :class:`Record` in the same order 1201 as they exist in the Template. The *record* argument is ignored. 1202 1203 If a *record* is given, all Information Elements that exist in the 1204 *record* are added to the :class:`Record` in the same order as they exist 1205 in the *record.* 1206 1207 One element must exist in the :class:`Record` before exporting any data. 1208 1209 A :class:`Record` maintains internal dictionaries for the elements that it 1210 contains. For this reason, if a template contains more than 1 of the same 1211 Information Element, elements must be added using the :meth:`add_element` 1212 method in order to give alternate key names to elements that are the same. 1213 1214 A :class:`Record` may also be accessed similar to a list. 1215 """ 1216 1217 Field = collections.namedtuple('Field', 'name instance ie length value') 1218 1219 _Elem = collections.namedtuple('_Elem', 'key ie wire_len offset mem_len') 1220 # 1221 # The _Elem class maintains internal information about each field 1222 # 1223 # key: a (name, instance) pair, where instance is 0 for the first 1224 # occurance of 'name', 1 for the sencond, etc 1225 # ie: the canonical InfoElement 1226 # wire_len: the length of this value in a string---VARLEN for lists and 1227 # variable length strings, octetArrays 1228 # offset: the offset of this value in the byte array 1229 # mem_len: the length of this value in memory, sizeof(struct) for lists 1230 # and variable length strings, octetArrays 1231 1232 def __init__(self, model, template=None, record=None): 1233 assert_type(InfoModel, model) 1234 # References to other pyfixbuf classes 1235 self.model = model 1236 self.template = None 1237 self.session = None 1238 # Information about each field, a list of _Elem objects, in the order 1239 # in which they were appended 1240 self._fields = [] 1241 # A mapping from (key -> _Elem), where each _Elem appears two or 1242 # maybe three times: once keyed by its numeric position, once keyed by 1243 # a (key_name,instance_count) tuple, and when "instance_count" is 1244 # zero, again by just the key_name. 1245 self._field_dict = dict() 1246 # Information about basicLists, keyed by _Elem 1247 self._basic_list = dict() 1248 # Once STML/STL elements are initialzed, no more elements may be added 1249 self.list_init = False 1250 # Current byte offset in C structure 1251 offset = 0 1252 if template: 1253 assert_type(Template, template) 1254 self.template = template 1255 for spec in template: 1256 ie = self.model.get_element(spec.name) 1257 if (spec.length and spec.length != VARLEN): 1258 wire_len = spec.length 1259 mem_len = wire_len 1260 else: 1261 wire_len = ie.length 1262 mem_len = model.get_element_length(spec.name) 1263 self._add_field(spec.name, ie, wire_len, offset, mem_len) 1264 offset += mem_len 1265 if ie.type == DataType.BASIC_LIST: 1266 field = self._fields[-1] 1267 self._basic_list[field] = None 1268 elif isinstance(record, Record): 1269 self.template = record.template 1270 for field in record._fields: 1271 # namedtuples are immutatable, so add it directly 1272 self._fields.append(field) 1273 offset += field.mem_len 1274 for key,field in record._field_dict.items(): 1275 self._field_dict[key] = field 1276 for field,value in record._basic_list.items(): 1277 self._basic_list[field] = value 1278 _pyfixbuf.fbRecordBase.__init__(self, offset) 1279 self.length = offset 1280 1281 def _get_field_offset(self, key): 1282 """ 1283 Returns the byte offset of the field *key* in the data array for this 1284 :class:`Record`. 1285 """ 1286 return self._field_dict[key].offset 1287 1288 def _add_field(self, key_name, ie, wire_len, offset, mem_len): 1289 """ 1290 Creates a _Elem() and adds it to this :class:`Record`. 1291 """ 1292 # location of the new field 1293 pos = len(self._fields) 1294 # make a unique key 1295 key = (key_name, 0) 1296 while key in self._field_dict: 1297 key = (key_name, key[1]+1) 1298 # create the field and add to record 1299 field = Record._Elem(key, ie, wire_len, offset, mem_len) 1300 self._fields.append(field) 1301 # key the field two or maybe three ways 1302 self._field_dict[pos] = field 1303 self._field_dict[key] = field 1304 if key[1] == 0: 1305 self._field_dict[key[0]] = field 1306 1307 def add_element(self, key_name, type=0, element_name=None, length=0): 1308 """ 1309 Appends a field named *key_name* to this :class:`Record`, where 1310 *key_name* represents the value of an Information Element. 1311 1312 The :class:`InfoElement` may be specified in three ways: The 1313 *key_name* may match the name of an :class:`InfoElement`, the 1314 *element_name* may name the :class:`InfoElement`, or *type* may be 1315 specified as BASICLIST, SUBTEMPLATELIST, SUBTEMPLATEMULTILIST, or 1316 VARLEN for octetArray. 1317 1318 When the :class:`Record` contains duplicate Information Elements, the 1319 *key_name* should be unique to ease reference to the elements and the 1320 :class:`InfoElement` should be specified using the *element_name* or 1321 *type* parameters. 1322 1323 The *length* parameter specifies the reduced-length encoding length 1324 for the Information Element, similar to the `length` attribute of 1325 :class:`InfoElementSpec`. This may only be applied to certain data 1326 types and must be smaller than the default length. When 0, the 1327 default length specified by the :class:`InfoModel` is used. 1328 1329 When *type* is BASICLIST, the *element_name* parameter is used as the 1330 content type for the elements in the basicList. See 1331 :meth:`init_basic_list` and the :class:`BL` constructor. 1332 1333 Elements must be added in the same order as they exist in the template. 1334 1335 Examples:: 1336 1337 >>> my_rec = pyfixbuf.Record(model) 1338 >>> my_rec.add_element("sourceTransportPort") 1339 >>> my_rec.add_element("sourceTransportPort2", 0, "sourceTransportPort") 1340 >>> my_rec.add_element("basicList") 1341 >>> my_rec.add_element("basicList2", BASICLIST) 1342 >>> my_rec.add_element("octetTotalCount", length=4) 1343 1344 In the above example, an empty :class:`Record` was created. 1345 The corresponding template 1346 to the above :class:`Record` would look something like: 1347 1348 >>> tmpl = Template(model) 1349 >>> tmpl.add_spec_list([ 1350 ... pyfixbuf.InfoElementSpec("sourceTransportPort"), 1351 ... pyfixbuf.InfoElementSpec("sourceTransportPort"), 1352 ... pyfixbuf.InfoElementSpec("basicList"), 1353 ... pyfixbuf.InfoElementSpec("basicList"), 1354 ... pyfixbuf.InfoElementSpec("octetTotalCount", 4)]) 1355 1356 As you can see, we have two sourceTransportPort elements and 1357 two basicList elements. A basicList is a list 1358 of one or more of the same Information Element. 1359 The Information Element in the basicList does not have to be 1360 initialized until data is added to the :class:`Record`. 1361 1362 Since we have two sourceTransportPort fields, we must give a *key_name* 1363 to one of the elements, in this case, sourceTransport2. 1364 Since sourceTransportPort2 is not a defined Information Element in the 1365 Information Model, the *element_name* must be given to the method. 1366 1367 Similarly, in order to access the dictionary of elements 1368 in the :class:`Record`, we had to give the second basicList a 1369 *key_name*, basicList2. Since basicList2 is not a defined Information 1370 Element, it needs to be given the *type*, BASICLIST. 1371 Since *type* is not 0, it does not need an *element_name*. 1372 """ 1373 if self.list_init: 1374 raise Exception("Information Elements may not be added to the " 1375 "Record once the Record's lists have been " 1376 "initialized.") 1377 if self.template: 1378 raise Exception("Information Elements may not be added to the " 1379 "Record once the Record's Template has been set.") 1380 assert_type(str, key_name) 1381 if element_name == None: 1382 element_name = key_name 1383 if type == BASICLIST: 1384 ie = self.model.get_element("basicList") 1385 elif type == SUBTEMPLATELIST: 1386 ie = self.model.get_element("subTemplateList") 1387 elif type == SUBTEMPLATEMULTILIST: 1388 ie = self.model.get_element("subTemplateMultiList") 1389 elif type != 0 and type != VARLEN: 1390 raise Exception("Invalid Type " + str(type)) 1391 else: 1392 try: 1393 ie = self.model.get_element(element_name) 1394 except Exception: 1395 raise Exception("Information Element " + element_name + 1396 " does Not Exist in Information Model") 1397 try: 1398 mem_len = self.model.get_element_length(element_name, type) 1399 except Exception: 1400 raise Exception("Information Element " + element_name + 1401 " does Not Exist in Information Model") 1402 # use modified length if requested and type is not a list/varlen 1403 wire_len = ie.length 1404 if (ie.type in [BASICLIST, SUBTEMPLATELIST, SUBTEMPLATEMULTILIST] or 1405 length == 0 or length == VARLEN or type == VARLEN): 1406 mem_len = self.model.get_element_length(element_name, type) 1407 elif not DataType.supports_RLE(ie.type): 1408 raise Exception("Information Element " + element_name + 1409 " does not support reduced-length encoding.") 1410 elif length > wire_len: 1411 raise Exception("Length value must be smaller than default " 1412 "Information Element Length (default: " + 1413 ie.length + ").") 1414 else: 1415 type = DataType.refine_type_for_length(ie.type, length) 1416 wire_len = length 1417 mem_len = wire_len 1418 self._add_field(key_name, ie, wire_len, self.length, mem_len) 1419 self.length += mem_len 1420 if ie.type == DataType.BASIC_LIST: 1421 field = self._fields[-1] 1422 try: 1423 ie = self.model.get_element(element_name) 1424 self._basic_list[field] = element_name 1425 except KeyError: 1426 self._basic_list[field] = None 1427 1428 def add_element_list(self, name_list): 1429 """ 1430 Treats each string in *name_list* as the name of an 1431 :class:`InfoElement` and appends that information element to the 1432 :class:`Record`. See the above method :meth:`add_element`. 1433 """ 1434 if isinstance(name_list, str): 1435 raise TypeError("Expected a non-str iterable") 1436 for name in name_list: 1437 self.add_element(name) 1438 1439 def clear_all_lists(self): 1440 """ 1441 Clears all the lists in the top level of the :class:`Record`. 1442 1443 Any nested lists must be accessed and cleared manually. 1444 1445 This is useful for a :class:`Record` 1446 that contains mostly one level list items, such as YAF_HTTP_LIST. 1447 """ 1448 for field in self._fields: 1449 ty = field.ie.type 1450 if ty == DataType.BASIC_LIST: 1451 self.clear_basic_list(field.key) 1452 elif ty == DataType.SUB_TMPL_LIST: 1453 stl = self._get_value(field) 1454 stl.clear() 1455 elif ty == DataType.SUB_TMPL_MULTI_LIST: 1456 stml = self._get_value(field) 1457 stml._clear_stml(self, self._off_dict[key]) 1458 1459 def clear(self): 1460 """ 1461 Clears any memory allocated for the :class:`Record`. 1462 """ 1463 _pyfixbuf.fbRecordBase.clear(self) 1464 1465 def init_basic_list(self, basic_list_key, count=0, element_name=None): 1466 """Initializes a basicList for export with the given *basic_list_key* 1467 name to a list of *count* elements. If a name is not given to 1468 the *element_name* keyword, it assumes 1469 the *basic_list_key* is a valid Information Element Name. 1470 1471 Examples:: 1472 1473 >>> my_rec.add_element("bL", BASICLIST, "octetTotalCount") 1474 >>> my_rec.add_element("basicList") 1475 >>> my_rec.add_element("basicList2", BASICLIST) 1476 >>> my_rec.init_basic_list("bL", 4) 1477 >>> my_rec.init_basic_list("basicList", 3, "destinationTransportPort") 1478 >>> my_rec.init_basic_list("basicList2", 2, "souceIPv4Address") 1479 1480 In the above example, we have initialized three basicLists. 1481 The first initializes a basicList of octetTotalCounts by 1482 adding the element as as basicList to the record. Later we 1483 initialize the basicList to 4 items. The second does the 1484 initialization of the type, destintationTransportPort, when 1485 calling :meth:`init_basic_list` as opposed to the first, which 1486 is done when the basicList is added to the record. The third, 1487 basicList2, is initialized to two sourceIPv4Addresses. 1488 1489 It is perfectly acceptable to initialize a list to 0 elements. 1490 All basicLists in the :class:`Record` must be initialized before 1491 appending the :class:`Record` to the :class:`Buffer`. 1492 1493 A basicList may be initialized via this method, or by using the 1494 :class:`BL` and setting the basicList element in the 1495 :class:`Record` to the :class:`BL`. 1496 """ 1497 field = self._field_dict[basic_list_key] 1498 if field.ie.type != BASICLIST: 1499 raise Exception("The type of field %s is not BASICLIST" % 1500 str(basic_list_key)) 1501 bl = self._basic_list[field] 1502 if isinstance(bl, BL): 1503 return bl 1504 if element_name: 1505 ie = element_name 1506 elif bl is None: 1507 raise Exception("Field " + basic_list_key + " must be" 1508 " initialized to an information element.") 1509 elif bl == "basicList": 1510 raise Exception("Nested basicLists are not supported") 1511 else: 1512 ie = self._basic_list[field] 1513 self._basic_list[field] = BL(self.model, ie, count) 1514 1515 def clear_basic_list(self, basic_list_key): 1516 """ 1517 Clears the basicList on this :class:`Record` identified by 1518 *basic_list_key*, freeing any memory allocated for the list. This 1519 should be called after the :class:`Record` has been appended to the 1520 :class:`Buffer`. Does nothing if the type of the field identified by 1521 *basic_list_key* is not BASICLIST. Raises :exc:`KeyError` if 1522 *basic_list_key* is not known on this :class:`Record`. 1523 """ 1524 field = self._field_dict[basic_list_key] 1525 if field.ie.type == BASICLIST: 1526 _pyfixbuf.fbRecordBase.basicListClear(self, field.offset) 1527 1528 def __getitem__(self, key): 1529 """ 1530 Implements the evaluation of ``record[key]`` for :class:`Record` 1531 instances: Returns the value of the element with the given *key*. 1532 1533 If *key* is a string, it may the name of an :class:`InfoElement` or 1534 the *key_name* specified to the :meth:`add_element` method. *key* may 1535 also be an integer corresponding to the positional index in the 1536 :class:`Record`. 1537 1538 Raises an :exc:`Exception` when *key* is not in the :class:`Record`. 1539 Use :meth:`get` for a similar function that does not raise an 1540 Exception. See also :meth:`get_field`. 1541 1542 The return type depends on the Information Element type which was 1543 defined when initializing the :class:`InfoElement`. 1544 1545 ============================ ============= 1546 Element Type Return Type 1547 ============================ ============= 1548 UINT*, INT* long 1549 FLOAT* float 1550 MILLISECONDS, MICROSECONDS long 1551 NANOSECONDS, SECONDS long 1552 OCTET_ARRAY bytearray 1553 BASICLIST :class:`BL` 1554 STRING string 1555 IP (v4 or v6) ipaddress object 1556 MAC_ADDR MAC Address String xx:xx:xx:xx:xx:xx 1557 SUBTEMPLATELIST :class:`STL` 1558 SUBTEMPLATEMULTILIST :class:`STML` 1559 Default (Undefined Type) bytearray 1560 ============================ ============= 1561 1562 Example: 1563 1564 >>> rec 1565 <pyfixbuf.Record object at 0x10d0f49f0> 1566 >>> rec['protocolIdentifier'] 1567 6 1568 >>> rec[1] 1569 80 1570 >>> rec.template[1].name 1571 'sourceTransportPort' 1572 """ 1573 field = self._field_dict.get(key) 1574 if field is None: 1575 if isinstance(key, int): 1576 ex = IndexError 1577 else: 1578 ex = KeyError 1579 raise ex("Key %s does not exist on this Record" % str(key)) 1580 return self._get_value(field) 1581 1582 def __setitem__(self, key, value): 1583 """ 1584 Implements assignment to ``record[key]`` for :class:`Record` 1585 instances: Sets the value of the element having the given *key* to 1586 *value*. 1587 1588 If *key* is a string, it may the name of an :class:`InfoElement` or 1589 the *key_name* specified to the :meth:`add_element` method. *key* may 1590 also be an integer corresponding to the positional index in the 1591 :class:`Record`. 1592 1593 """ 1594 field = self._field_dict.get(key) 1595 if field is None: 1596 if isinstance(key, int): 1597 ex = IndexError 1598 else: 1599 ex = KeyError 1600 raise ex("Key %s does not exist on this Record" % str(key)) 1601 return self._set_value(field, value) 1602 1603 def _get_value(self, field): 1604 """ 1605 Returns the value for a field. For internal use. 1606 """ 1607 #assert_type(Record._Elem, field) 1608 ty = field.ie.type 1609 if ty == DataType.BASIC_LIST: 1610 bl = BL(self.model, self._basic_list[field]) 1611 _pyfixbuf.fbRecordBase.getBL(self, bl, field.offset) 1612 bl._fill_bl_list() 1613 return bl 1614 if ty == DataType.SUB_TMPL_MULTI_LIST: 1615 stml = STML(self, field.key) 1616 stml.session = self.session 1617 return stml 1618 if ty == DataType.SUB_TMPL_LIST: 1619 stl = STL(self, field.key) 1620 stl.session = self.session 1621 return stl 1622 data = _pyfixbuf.fbRecordBase.getOffset(self, field.offset, 1623 field.mem_len, ty, 1624 (VARLEN == field.wire_len)) 1625 if ty == DataType.IP4ADDR: 1626 return ipaddress.IPv4Address(data) 1627 if ty == DataType.IP6ADDR: 1628 return ipaddress.IPv6Address(bytes(data)) 1629 if ty == DataType.MAC_ADDR: 1630 return bytes_to_macaddr(data) 1631 return data 1632 1633 def _set_value(self, field, value): 1634 """ 1635 Sets a field to a value. For internal use. 1636 """ 1637 #assert_type(Record._Elem, field) 1638 ty = field.ie.type 1639 if ty == BASICLIST: 1640 if isinstance(value, BL): 1641 bl = value 1642 elif self._basic_list[field] == None: 1643 raise Exception("Basic List \"" + str(field.key) + "\" has not" 1644 " been initialized, call init_basic_list()") 1645 else: 1646 if not isinstance(value, list): 1647 value = [value] 1648 if not isinstance(self._basic_list[field], BL): 1649 self._basic_list[field] = BL( 1650 self.model, self._basic_list[field], len(value)) 1651 self._basic_list[field].copy(value) 1652 bl = self._basic_list[field] 1653 _pyfixbuf.fbRecordBase.setOffset(self, bl, field.offset, 1654 field.mem_len, ty, 0) 1655 return 1656 # Handle anything that is not a basicList 1657 if ty == DataType.IP4ADDR: 1658 value = ip4addr_to_int(value) 1659 elif ty == DataType.IP6ADDR: 1660 value = ip6addr_to_bytes(value) 1661 elif ty == DataType.MAC_ADDR: 1662 value = macaddr_to_bytes(value) 1663 elif ty == DataType.SUB_TMPL_LIST: 1664 self.list_init = True 1665 if isinstance(value, STL): 1666 value.inrec = self 1667 elif isinstance(value, list): 1668 template = None 1669 for rec in value: 1670 if not isinstance(rec, Record): 1671 raise Exception("Value must be a list of Records.") 1672 if template == None and rec.template != None: 1673 template = rec.template 1674 if (template == None): 1675 raise Exception("At least one Record in list must " 1676 "be associated with template.") 1677 stl = STL(self, field.key) 1678 stl.entry_init(value[0], template, len(value)) 1679 for i,rec in enumerate(value): 1680 stl[i] = rec 1681 value = stl 1682 elif ty == DataType.SUB_TMPL_MULTI_LIST: 1683 self.list_init = True 1684 if isinstance(value, STML): 1685 value._put_stml_rec(self, field.offset) 1686 elif isinstance(value, list): 1687 stml = create_stml_from_list(value) 1688 value = stml 1689 else: 1690 raise Exception("Value is type of " + str(type(value)) + 1691 " but key " + str(field.key) + " has type " + 1692 DataType.to_string(ty)) 1693 elif not DataType.check_type(ty, value): 1694 raise Exception("Value is type of " + str(type(value)) + 1695 " but key " + str(field.key) + " has type " + 1696 DataType.to_string(ty)) 1697 _pyfixbuf.fbRecordBase.setOffset(self, value, field.offset, 1698 field.mem_len, ty, 1699 (VARLEN == field.wire_len)) 1700 1701 def get(self, key, default=None): 1702 """ 1703 Returns ``record[key]`` if *key* exists on this :class:`Record`; 1704 otherwise returns *default*. 1705 """ 1706 field = self._field_dict.get(key) 1707 if field is None: 1708 return default 1709 return self._get_value(field) 1710 1711 def get_field(self, key): 1712 """ 1713 Returns a :class:`Record.Field` object for *key* on this 1714 :class:`Record`. Raises :exc:`KeyError` when *key* is not present. 1715 """ 1716 field = self._field_dict.get(key) 1717 return Record.Field(field.key[0], field.key[1], field.ie, 1718 field.wire_len, self._get_value(field)) 1719 1720 def copy(self, other): 1721 """ 1722 Copies all the matching elements from the *other* :class:`Record` to 1723 this :class:`Record`. 1724 """ 1725 assert_type(Record, other) 1726 for field in self._fields: 1727 other_field = other._field_dict.get(field.key) 1728 if other_field is not None: 1729 self._set_value(field, other._get_value(other_field)) 1730 1731 def is_list(self, key): 1732 """ 1733 Returns ``True`` or ``False`` depending on the type of the given 1734 *key*. Raises :exc:`KeyError` when *key* is not on this 1735 :class:`Record`. 1736 """ 1737 return (self._field_dict[key].ie.type in 1738 [BASICLIST, SUBTEMPLATELIST, SUBTEMPLATEMULTILIST]) 1739 1740 def get_field_type(self, key): 1741 """ 1742 Returns the :class:`DataType` for the given *key*. Raises 1743 :exc:`KeyError` when *key* is not on this :class:`Record`. 1744 """ 1745 return self._field_dict[key].ie.type 1746 1747 def get_stl_list_entry(self, key): 1748 """ 1749 Gets the subTemplateList from this :class:`Record` with the given 1750 *key* and returns a newly allocated :class:`STL`. Returns None if the 1751 type of *key* is not SUBTEMPLATELIST. Raises :exc:`KeyError` if 1752 *key* is not a known key. 1753 1754 A :class:`STL` may also be accessed by using :meth:`__getitem__`. 1755 """ 1756 field = self._field_dict[key] 1757 if field.ie.type == SUBTEMPLATELIST: 1758 return self._get_value(field) 1759 1760 def get_stml_list_entry(self, key): 1761 """ 1762 Gets the subTemplateMultiList from this :class:`Record` with the given 1763 *key* and returns a newly allocated :class:`STML`. Returns None if 1764 the type of *key* is not SUBTEMPLATELIST. Raises :exc:`KeyError` if 1765 *key* is not a known key. 1766 1767 A :class:`STML` may also be retrieved by using :meth:`__getitem__`. 1768 """ 1769 field = self._field_dict[key] 1770 if field.ie.type == SUBTEMPLATELIST: 1771 return self._get_value(field) 1772 1773 def as_dict(self): 1774 """ 1775 Returns a :class:`dict` that represents the :class:`Record`. 1776 1777 The keys of the dictionary are normally strings, but if the 1778 :class:`Record`'s :class:`Template` contains duplicate a 1779 :class:`InfoElement`, the key for the **second** such element is a 1780 couple containing the name and the :class:`int` 1, the third would be 1781 the name and the :class:`int` 2, et cetera. 1782 """ 1783 recdict = dict() 1784 for field in self._fields: 1785 if field.key[1] == 0: 1786 recdict[field.key[0]] = self._get_value(field) 1787 else: 1788 recdict[field.key] = self._get_value(field) 1789 return recdict 1790 1791 def __len__(self): 1792 """ 1793 Implements the built-in `len()` method for :class:`Record` instances: 1794 Returns the number of elements in the :class:`Record`. 1795 """ 1796 return len(self._fields) 1797 1798 def __contains__(self, element): 1799 """ 1800 Implements the ``in`` operator for :class:`Record` instances: Tests 1801 whether :meth:`add_element` was called with a `key_name` that is equal 1802 to *element*. If the :class:`Record` was initialized with a 1803 :class:`Template`, tests whether the :class:`Template` included an 1804 :class:`InfoElement` having the name *element*. 1805 1806 Example: 1807 1808 >>> rec 1809 <pyfixbuf.Record object at 0x10d0f49f0> 1810 >>> 'protocolIdentifier' in rec 1811 True 1812 """ 1813 return element in self._field_dict 1814 1815 def __iter__(self): 1816 """ 1817 Implements the method to return an Iterator over the values in the 1818 :class:`Record`. See also :meth:`iterfields`. 1819 1820 Example: 1821 1822 >>> for value in record: 1823 ... print value 1824 """ 1825 for field in self._fields: 1826 yield self._get_value(field) 1827 1828 def iterfields(self): 1829 """ 1830 Returns an Iterator over the :class:`Record`'s values where each 1831 iteration returns a :class:`Record.Field` object. To get a 1832 :class:`Record.Field` for a single field, use :meth:`get_field`. 1833 1834 Example: 1835 1836 >>> for field in record.iterfields(): 1837 ... print field.ie.name, DataType.get_name(field.ie.type), field.value 1838 ... 1839 """ 1840 for field in self._fields: 1841 yield Record.Field(field.key[0], field.key[1], field.ie, 1842 field.wire_len, self._get_value(field)) 1843 1844 def matches_template(self, template, exact=False): 1845 """ 1846 Returns ``True`` if this :class:`Record` matches :class:`Template` 1847 using the checks specified by :meth:`check_template`. Returns 1848 ``False`` otherwise. 1849 """ 1850 assert_type(Template, template) 1851 try: 1852 self.check_template(template, exact) 1853 return True 1854 except Exception: 1855 return False 1856 1857 def check_template(self, template, exact=False): 1858 """ 1859 Checks whether :class:`Template` has exactly the same 1860 :class:`InfoElement`\s as this :class:`Record` in the same order with 1861 the same reduced-length encodings. When *exact* is ``False``, the 1862 :class:`Template` may have additional Elements. Raises an 1863 :exc:`Exception` if there is a mismatch; returns no result otherwise. 1864 1865 """ 1866 assert_type(Template, template) 1867 for field, t_ie, spec in zip_longest( 1868 self._fields, template.ie_iter(), template): 1869 if field is None or t_ie is None: 1870 if not exact and field is None: 1871 return 1872 raise Exception("The Record and Template contain a" 1873 " different number of elements") 1874 if (t_ie.id != field.ie.id or 1875 t_ie.enterprise_number != field.ie.enterprise_number): 1876 raise Exception("The Record and Template do not contain the" 1877 " same elements") 1878 if spec.length != field.wire_len: 1879 raise Exception("The element lengths in the Record and" 1880 " Template differ") 1881 1882 def set_template(self, template): 1883 """ 1884 If this :class:`Record` was not initialized with a :class:`Template`, 1885 this method may be used to set the :class:`Record`'s 1886 :class:`Template`. A :class:`Record` must have a :class:`Template` 1887 associated with it when assigning a :class:`Record` to a 1888 subTemplateList element. 1889 1890 Examples: 1891 1892 >>> tmpl = pyfixbuf.Template(model) 1893 >>> tmpl.add_spec_list([ 1894 ... pyfixbuf.InfoElementSpec("sourceTransportPort"), 1895 ... pyfixbuf.InfoElementSpec("destinationTransportPort")] 1896 >>> my_rec = pyfixbuf.Record(model) 1897 >>> my_rec.add_element("sourceTransportPort", "destinationTransportPort") 1898 >>> my_rec["sourceTransportPort"] = 13 1899 >>> my_rec["destinationTransportPort"] = 15 1900 >>> my_rec.set_template(tmpl) 1901 >>> other_rec["subTemplateList"] = [my_rec] 1902 """ 1903 self.check_template(template, True) 1904 self.template = template 1905 1906 def count(self, element_name): 1907 """Counts the occurrences of the *element_name* in the 1908 :class:`Record`. 1909 1910 Examples: 1911 1912 >>> rec.add_element_list(["basicList", "basicList", "basicList"]) 1913 >>> rec.count("basicList") 1914 3 1915 >>> rec.count("sourceTransportPort") 1916 0 1917 """ 1918 for field in reversed(self._fields): 1919 if field.key[0] == element_name: 1920 return 1 + field.key[1] 1921 return 0 1922 1923 1924class Buffer(_pyfixbuf.fBufBase): 1925 """Creates an uninitialized :class:`Buffer` given a :class:`Record`, 1926 *record*. 1927 1928 A :class:`Record` must be associated with the :class:`Buffer` 1929 before retrieving or appending data to the Buffer. If *auto* is 1930 set, a :class:`Template` will be auto-generated from the external 1931 template that is set on the :class:`Buffer`. A :class:`Record` 1932 will then be auto-generated to match the new :class:`Template` 1933 that was set on the :class:`Buffer`. 1934 1935 The :class:`Buffer` must also be initialized for collection using 1936 :meth:`init_collection` or exporting :meth:`init_export` prior to 1937 calling next(). 1938 """ 1939 def __init__(self, record=None, auto=False): 1940 self.session = None 1941 self.int_tmpl = None 1942 self.ext_tmpl = None 1943 self.model = None 1944 #set 1 for collection, 2 for export 1945 self.mode = 0 1946 self.auto = auto 1947 if record: 1948 assert_type(Record, record) 1949 self.rec = record 1950 _pyfixbuf.fBufBase.__init__(self) 1951 1952 def init_collection(self, session, collector): 1953 """ 1954 Initializes the :class:`Buffer` for collection given the 1955 :class:`Session`, *session*, and :class:`Collector`, *collector*. 1956 """ 1957 assert_type(Session, session) 1958 assert_type(Collector, collector) 1959 self.session = session 1960 self.model = session.model 1961 self.mode = 1 1962 if (collector.initialized == 0): 1963 raise Exception("Collector has not been initialized for file " 1964 "collection") 1965 return _pyfixbuf.fBufBase.allocForCollection(self, session, collector) 1966 1967 def init_export(self, session, exporter): 1968 """ 1969 Initializes the :class:`Buffer` for Export given the :class:`Session`, 1970 *session*, and :class:`Exporter`, *exporter*. 1971 """ 1972 assert_type(Session, session) 1973 assert_type(Exporter, exporter) 1974 self.session = session 1975 if (exporter.initialized == 0): 1976 raise Exception("Exporter has not been initialized for file " 1977 " or transport.") 1978 self.mode = 2 1979 return _pyfixbuf.fBufBase.allocForExport(self, session, exporter) 1980 1981 def _init_listener(self, session): 1982 """ 1983 Internal function to set session on buffer. 1984 """ 1985 self.session = session 1986 self.model = session.model 1987 self.mode = 1 1988 1989 def set_internal_template(self, template_id): 1990 """ 1991 Sets the internal :class:`Template` to the one whose ID is 1992 *template_id*. The :class:`Buffer` must have an internal template set 1993 on it before collecting or exporting. 1994 """ 1995 if (self.session == None): 1996 raise Exception("Buffer needs to be initialized for collection " 1997 "or export before setting templates.") 1998 if template_id == 0: 1999 raise Exception("Invalid Template Id [0]") 2000 if (template_id not in self.session.internal_templates): 2001 raise Exception("Template %d has not been added to Session's " 2002 "Internal Templates." % template_id) 2003 self.int_tmpl = template_id 2004 _pyfixbuf.fBufBase.setInternalTemplate(self, template_id) 2005 2006 def set_export_template(self, template_id): 2007 """ 2008 Sets the external :class:`Template` to the one whose ID is 2009 *template_id*. The :class:`Buffer` must have an export template set 2010 before appending (:meth:`append`) a :class:`Record` to the 2011 :class:`Buffer`. The export :class:`Template` describes how fixbuf 2012 will write the given :class:`Record` to the output stream. 2013 """ 2014 if (self.session == None): 2015 raise Exception("Buffer needs to be initialized for collection " 2016 "or export before setting templates.") 2017 if template_id == 0: 2018 raise Exception("Invalid Template Id [0]") 2019 if (self.session.external_templates.get(template_id) == None): 2020 raise Exception("Template %d has not been added to Session's " 2021 "External Templates." % template_id) 2022 self.ext_tmpl = template_id 2023 _pyfixbuf.fBufBase.setExportTemplate(self, template_id) 2024 2025 def __iter__(self): 2026 """ 2027 Implements the method to return an Iterator over the :class:`Record` 2028 objects in the :class:`Buffer`. 2029 2030 Example: 2031 2032 >>> for record in buffer: 2033 ... print record.as_dict() 2034 """ 2035 return self 2036 2037 def next(self): 2038 """ 2039 Returns the next :class:`Record` in the :class:`Buffer`. 2040 """ 2041 return self.__next__() 2042 2043 def __next__(self): 2044 """ 2045 Returns the next :class:`Record` in the :class:`Buffer`. Raises a 2046 :exc:`StopIteration` Exception when there are no more 2047 :class:`Record` objects. 2048 """ 2049 if (self.auto): 2050 self._auto_generate_template() 2051 2052 if self.int_tmpl == None: 2053 raise Exception("No Internal Template Set on Buffer") 2054 if self.rec == None: 2055 tmpl = self.session.get_template(self.int_tmpl, True) 2056 self.rec = Record(self.model, tmpl) 2057 if (self.rec.length == 0): 2058 raise Exception("No Information Elements in Record") 2059 self.rec.session = self.session 2060 _pyfixbuf.fBufBase.nextRecord(self, self.rec) 2061 return self.rec 2062 2063 def next_record(self, record=None): 2064 """ 2065 Gets the next record on this :class:`Buffer` in the form of the given 2066 :class:`Record`, *record*. Returns ``None`` if the :class:`Buffer` is 2067 empty. 2068 """ 2069 if (self.auto): 2070 try: 2071 self._auto_generate_template() 2072 except StopIteration: 2073 return None 2074 2075 if self.int_tmpl == None: 2076 raise Exception("No internal template set on buffer") 2077 if record == None: 2078 tmpl = self.session.get_template(self.int_tmpl, True) 2079 record = Record(self.model, tmpl) 2080 else: 2081 assert_type(Record, record) 2082 if (record.length == 0): 2083 raise Exception("No Information Elements in Record") 2084 try: 2085 _pyfixbuf.fBufBase.nextRecord(self, record) 2086 record.session = self.session 2087 return record 2088 except StopIteration: 2089 return None 2090 2091 def set_record(self, record): 2092 """ 2093 Sets the :class:`Record` on this :class:`Buffer` to *record*. If 2094 *record* is associated with a :class:`Template`, also calls 2095 :meth:`set_internal_template` with the template_id of that 2096 :class:`Template`. 2097 """ 2098 assert_type(Record, record) 2099 self.rec = record 2100 if (record.template): 2101 if (record.template.template_id): 2102 self.set_internal_template(record.template.template_id) 2103 2104 def next_template(self): 2105 """ 2106 Retrieves the external :class:`Template` that **will** be used to read 2107 the **next** :class:`Record` from this :class:`Buffer`. If no next 2108 :class:`Record` is available, raises :exc:`StopIteration`. 2109 2110 See also :meth:`get_template`. 2111 """ 2112 template = _pyfixbuf.fBufBase.nextTemplate(self) 2113 new_template = Template._wrap(template) 2114 return new_template 2115 2116 def get_template(self): 2117 """ 2118 Retrieves the external :class:`Template` that **was** used to read the 2119 **current** record from the buffer. If no :class:`Record` has been 2120 read, returns ``None``. 2121 2122 See also :meth:`next_template`. 2123 """ 2124 template = _pyfixbuf.fBufBase.getTemplate(self) 2125 new_template = Template._wrap(template) 2126 return new_template 2127 2128 def append(self, *args): 2129 """ 2130 Appends the first argument, a :class:`Record`, to the :class:`Buffer`. 2131 If a second argument, an :class:`int` representing a length, is given, 2132 appends only the first *length* number of bytes to the 2133 :class:`Buffer`. 2134 2135 An internal and external :class:`Template` must be set on the 2136 :class:`Buffer` prior to appending a :class:`Record`. 2137 """ 2138 if self.ext_tmpl == None: 2139 raise Exception("No External Template Set on Buffer") 2140 if self.int_tmpl == None: 2141 raise Exception("No Internal Template Set on Buffer") 2142 return _pyfixbuf.fBufBase.append(self, *args) 2143 2144 def write_ie_options_record(self, name, template): 2145 """ 2146 Appends an Information Element Type Information Record 2147 on the :class:`Buffer`. An Options Record will be written with 2148 information about the Information Element with the given 2149 *name*. *template* is the Information 2150 Element Type Options Template that was created by giving 2151 ``type=True`` to the :class:`Template` constructor. 2152 """ 2153 inttid = self.int_tmpl 2154 exttid = self.ext_tmpl 2155 if (self.mode == 0): 2156 raise Exception("This buffer has not been initialized for export.") 2157 elif (self.mode == 1): 2158 raise Exception("This buffer is for collection only. It cannot " 2159 "write options records.") 2160 if (not(template.scope)): 2161 raise Exception("Given Template is not an Options Template.") 2162 if (template.template_id not in self.session.internal_templates): 2163 tid = self.session.add_template(template) 2164 _pyfixbuf.fBufBase.writeOptionsRecord(self,template.infomodel._unwrap(), 2165 name, template.template_id) 2166 #set templates back to where they were 2167 if inttid: 2168 self.set_internal_template(inttid) 2169 if exttid: 2170 self.set_export_template(exttid) 2171 2172 def auto_insert(self): 2173 """ 2174 Tells the :class:`Buffer` to watch the stream for Information Element 2175 Option Records and automatically create and insert an 2176 :class:`InfoElement` to the :class:`InfoModel` for each one seen. 2177 Information elements that have an enterprise number of 0 are ignored. 2178 """ 2179 _pyfixbuf.fBufBase.setAutoInsert(self) 2180 2181 2182 def ignore_options(self, ignore): 2183 """ 2184 Tells the :class:`Buffer` how to handle Options Records. 2185 2186 If *ignore* is set to True, the :class:`Buffer` ignores Options 2187 Templates and Records. By default, *ignore* is False, and the 2188 :class:`Buffer` returns Options Records that application must handle. 2189 2190 The application may use :meth:`next_template` to retrieve the 2191 :class:`Template` and determine if it is an Options Template. 2192 """ 2193 if (ignore < 0): 2194 raise Exception("Invalid value given to ignore_options. " 2195 "Should be True/False") 2196 _pyfixbuf.fBufBase.ignoreOptions(self, ignore) 2197 2198 2199 def _auto_generate_template(self): 2200 """ 2201 If *auto* is set to True, the buffer will auto-generate internal 2202 templates from the external templates it receives and set them 2203 on the buffer before decoding the next IPFIX message. The 2204 :class:`Record` returned from the :class:`Buffer` will match 2205 the external template retrieved from the exporting message. 2206 2207 Raises StopIteration if no more data is available. 2208 """ 2209 if (self.session == None): 2210 raise Exception("Buffer must be initialized for collection " 2211 "before template can be received.") 2212 if (self.mode != 1): 2213 raise Exception("Auto generation for templates only applies " 2214 "to Buffers in collection mode.") 2215 # allow this to raise StopIteration if no data 2216 tmpl_next = self.next_template() 2217 if tmpl_next.template_id != self.int_tmpl: 2218 self.rec = None 2219 try: 2220 tmpl = self.session.get_template(tmpl_next.template_id, True) 2221 except: 2222 tmpl = self.session.get_template(tmpl_next.template_id) 2223 self.session.add_internal_template(tmpl, tmpl_next.template_id) 2224 self.set_internal_template(tmpl_next.template_id) 2225 2226 2227class STML(_pyfixbuf.fbSTMLBase): 2228 """A :class:`STML` object represents a subTemplateMultiList. 2229 2230 If a :class:`Record` object, *record*, and *key_name*, a string, 2231 are provided, the :class:`STML` object with *key_name* will be 2232 initialized in the given :class:`Record`. It is only necessary to 2233 initialize and give a *type_count* if the subTemplateMultiList 2234 will be exported. All subTemplateMultiLists in an exported 2235 :class:`Record` must be initialized. It is acceptable to 2236 initialize an STML to 0 list entries. 2237 2238 A :class:`STML` must be initialized with *record* and *key_name* 2239 OR a *type_count*. This object can be used to set a 2240 subTemplateMultiList element in a :class:`Record`. 2241 2242 The subTemplateMultiList is initialized to ``None`` unless it is 2243 given a *type_count*, in which case it will intialize the list and 2244 allocate memory in the given record. 2245 2246 *type_count* is the number of different templates that the 2247 :class:`STML` will contain. For example, if you plan to have an 2248 STML with entries of type Template ID 999 and 888, *type_count* 2249 would be 2. *type_count* would also be 2 even if both instances 2250 will use Template ID 999. 2251 2252 Examples:: 2253 2254 >>> stml = my_rec["subTemplateMultiList"] # sufficient for collection 2255 >>> stml = pyfixbuf.STML(rec, "subTemplateMultiList", 3) # STML with 3 entries for export 2256 >>> stml = pyfixbuf.STML(type_count=2) 2257 >>> stml = [record1, record2] 2258 >>> stml2 = pyfixbuf.STML(type_count=3) 2259 >>> stml2[0] = [record1, record2] 2260 >>> stml2[1][0] = record3 2261 >>> stml2[2].entry_init(record3, tmpl3, 0) #all entries must be init'd - even to 0. 2262 >>> rec["subTemplateMultiList"] = stml 2263 """ 2264 def __init__(self, record=None, key_name=None, type_count=-1): 2265 self.info_model = None 2266 self.offset = 0 2267 self.entries = [] 2268 # The record that owns this STML 2269 self.rec = None 2270 self.session = None 2271 if record is None and type_count >= 0: 2272 _pyfixbuf.fbSTMLBase.__init__(self, None, 0, type_count) 2273 elif record is not None and key_name is not None: 2274 assert_type(Record, record) 2275 if (record.get_field_type(key_name) != SUBTEMPLATEMULTILIST): 2276 raise Exception("DataType of " + str(key_name) + 2277 " is not SUBTEMPLATEMULTILIST") 2278 self.offset = record._get_field_offset(key_name) 2279 _pyfixbuf.fbSTMLBase.__init__(self, record, self.offset) 2280 record.list_init = True 2281 self.rec = record 2282 self.info_model = record.model 2283 self.session = record.session 2284 else: 2285 raise Exception("STML must be intialized with either" 2286 " a type_count or a Record and key_name") 2287 2288 def __iter__(self): 2289 """ 2290 Implements the method to return an Iterator over the 2291 :class:`STMLEntry` objects in the :class:`STML`. 2292 2293 Example: 2294 2295 >>> for entry in stml: 2296 ... for record in entry: 2297 ... print record.as_dict() 2298 """ 2299 return self 2300 2301 def next(self): 2302 """Returns the next :class:`STMLEntry` in the :class:`STML`. 2303 """ 2304 return self.__next__() 2305 2306 def __next__(self): 2307 """Returns the next :class:`STMLEntry` in the :class:`STML`. 2308 """ 2309 _pyfixbuf.fbSTMLBase.getNextEntry(self, self.rec, self.offset) 2310 stmlentry = STMLEntry(self) 2311 return stmlentry 2312 2313 def clear(self): 2314 """ 2315 Clears the entries in the subTemplateMultiList and frees any 2316 memory allocated. 2317 """ 2318 _pyfixbuf.fbSTMLBase.clear(self, self.rec, self.offset) 2319 2320 def _clear_stml(self, record, offset): 2321 _pyfixbuf.fbSTMLBase.clear(self, record, offset) 2322 2323 def _put_stml_rec(self, record, offset): 2324 self.rec = record 2325 self.offset = offset 2326 2327 def __len__(self): 2328 """ 2329 Implements the built-in `len()` method for :class:`STML` instances: 2330 Returns the number of :class:`STMLEntry` objects in the :class:`STML`. 2331 """ 2332 return self.count 2333 2334 def __contains__(self, name): 2335 """ 2336 Implements the ``in`` operator for :class:`STML` instances: Tests 2337 whether the :class:`Template` used by the first :class:`STMLEntry` in 2338 this :class:`STML` contains an :class:`InfoElement` having the name 2339 *name*. 2340 """ 2341 _pyfixbuf.fbSTMLBase.getFirstEntry(self, self.rec, self.offset) 2342 stmlentry = STMLEntry(self) 2343 if name in stmlentry: 2344 rv = True 2345 else: 2346 rv = False 2347 _pyfixbuf.fbSTMLBase.rewind(self) 2348 return rv 2349 2350 def __getitem__(self, index): 2351 """ 2352 Implements the evaluation of ``stml[index]`` for :class:`STML` 2353 instances: Returns the :class:`STMLEntry` at position *index*. 2354 2355 Examples:: 2356 2357 >>> entry = stml[0] 2358 >>> stml[0].entry_init[record, template, 3] 2359 """ 2360 if (not(isinstance(index, int))): 2361 raise TypeError 2362 if (len(self.entries) == 0): 2363 for i in range(len(self)): 2364 _pyfixbuf.fbSTMLBase.getIndex(self, i) 2365 stmlentry = STMLEntry(self) 2366 self.entries.append(stmlentry) 2367 return self.entries[index] 2368 2369 def __setitem__(self, index, value): 2370 """ 2371 Implements assignment to ``stml[index]`` for :class:`STML` instances: 2372 Sets the :class:`STMLEntry` at position *index* in the :class:`STML` 2373 to the list of :class:`Record` objects in *value*. *value* must be a 2374 list. All :class:`Records` in the list should have the same 2375 :class:Template. 2376 2377 Examples: 2378 2379 >>> stml[0] = [rec1, rec2, rec3, rec4] 2380 """ 2381 if (not(isinstance(value, list))): 2382 raise TypeError 2383 if (len(value) == 0): 2384 raise Exception("List must not be empty. Use entry_init to " 2385 "initialze STMLEntry to empty list.") 2386 entry = self[index] 2387 if (value[0].template): 2388 entry.entry_init(value[0], value[0].template, len(value)) 2389 else: 2390 raise Exception("Records in list must have Template " 2391 "associated with them, or STMLEntry must " 2392 "be initialized with entry_init()") 2393 for i,rec in enumerate(value): 2394 entry[i] = rec 2395 2396 def iter_records(self, tmpl_id=0): 2397 """ 2398 Returns an Iterator over the :class:`STML`'s records, including records 2399 in any nested :class:`STML` or :class:`STL`. If a template ID is 2400 passed, returns an iterator over all the :class:`Record` objects with a 2401 template ID matching the passed value. 2402 2403 Example: 2404 2405 >>> for record in stml.iter_records(): 2406 ... print record.template.template_id 2407 ... 2408 """ 2409 for entry in self: 2410 for record in entry: 2411 for sub_rec in yield_record_subrecords(record, tmpl_id): 2412 yield sub_rec 2413 2414 2415class STMLEntry(_pyfixbuf.fbSTMLEntryBase): 2416 """ 2417 Creates an empty :class:`STMLEntry` and associates it to the 2418 given :class:`STML`, *stml*. There should be one 2419 :class:`STMLEntry` for each different :class:`Template` in the 2420 :class:`STML`. 2421 2422 Each :class:`STMLEntry` should be initialized using :meth:`entry_init` 2423 to associate a :class:`Record` and :class:`Template` with the entry. 2424 """ 2425 def __init__(self, stml): 2426 assert_type(STML, stml) 2427 _pyfixbuf.fbSTMLEntryBase.__init__(self, stml) 2428 self.info_model = stml.info_model 2429 self.stml = stml 2430 self.rec = None 2431 self.initialized = False 2432 self.items = 0 2433 self._exp_template = None 2434 # this variable keeps track if getNextDatPtr or getNextEntry was called 2435 # to set the record to the appropriate data buffer for __getitem__ 2436 self.recset = False 2437 2438 def entry_init(self, record, template, count=0): 2439 """ 2440 Initializes the :class:`STMLEntry` to the given :class:`Record`, 2441 *record*, :class:`Template`, *template*, and *count* instances of the 2442 *record* it will contain. 2443 2444 This should only be used for exporting a subTemplateMultiList. 2445 Entries in the :class:`STML` must all be initialized, even if it 2446 is initialized to 0. This method is not necessary if a :class:`Record` 2447 has a template associated with it. The application can simply set 2448 the :class:`STMLEntry` to a list of :class:`Record` objects and the 2449 :class:`STMLEntry` will automatically be initialized. 2450 2451 Raises an :exc:`Exception` when `template` has a template_id of 0. You 2452 should add the `template` to a :class:`Session` before using it for 2453 the :class:`STMLEntry`. 2454 2455 Examples:: 2456 2457 >>> stml = pyfixbuf.STML(my_rec, "subTemplateMultiList", 1) 2458 >>> stml[0].entry_init(my_rec, template, 2) 2459 >>> my_rec["sourceTransportPort"] = 3 2460 >>> stml[0][0] = my_rec 2461 >>> my_rec["sourceTransportPort"] = 5 2462 >>> stml[0][1] = my_rec 2463 """ 2464 assert_type(Record, record) 2465 assert_type(Template, template) 2466 if template.template_id == 0: 2467 raise Exception("Template has a template_id of 0") 2468 record.check_template(template, True) 2469 self.set_record(record) 2470 _pyfixbuf.fbSTMLEntryBase.entryInit(self, record, template._unwrap(), 2471 template.template_id, count) 2472 self.initialized = True 2473 self.items = count 2474 self.info_model = record.model 2475 self._exp_template = template 2476 2477 def set_record(self, record): 2478 """ 2479 Sets the :class:`Record` on the :class:`STMLEntry` to *record* in 2480 order to access its elements. 2481 """ 2482 assert_type(Record, record) 2483 tid = self.template_id 2484 if (tid and self.stml.session): 2485 tmpl = self.stml.session.get_template(tid, False) 2486 record.check_template(tmpl) 2487 elif self._exp_template: 2488 record.check_template(self._exp_template) 2489 self.rec = record 2490 2491 def set_template(self, template): 2492 """ 2493 Assigns a :class:`Template` to the :class:`STMLEntry`. The 2494 :class:`Template` must be valid. 2495 2496 This method may be used as an alternative to :meth:`entry_init`. This 2497 is only required if the :class:`Record` that will be assigned to the 2498 :class:`STMLEntry` was not created with a :class:`Template`. Using 2499 this method instead of :meth:`entry_init` results in only allocating 2500 one item for the :class:`STMLEntry`. 2501 2502 Raises an :exc:`Exception` when `template` has a template_id of 0. You 2503 should add the `template` to a :class:`Session` before using it for 2504 the :class:`STMLEntry`. 2505 """ 2506 assert_type(Template, template) 2507 if template.template_id == 0: 2508 raise Exception("Template has a template_id of 0") 2509 _pyfixbuf.fbSTMLEntryBase.entryInit(self, None, template._unwrap(), 2510 template.template_id, 1) 2511 self.initialized = True 2512 self.items = 1 2513 self.info_model = template.infomodel 2514 self._exp_template = template 2515 2516 def __iter__(self): 2517 """ 2518 Implements the method to return an Iterator over the :class:`Record` 2519 objects in the :class:`STMLEntry`. 2520 2521 Example: 2522 2523 >>> for entry in stml: 2524 ... for record in entry: 2525 ... print record.as_dict() 2526 """ 2527 return self 2528 2529 def next(self): 2530 """Retrieves the next :class:`Record` in the :class:`STMLEntry`. 2531 """ 2532 return self.__next__() 2533 2534 def __next__(self): 2535 """Retrieves the next :class:`Record` in the :class:`STMLEntry`. 2536 2537 If a :class:`Record` has not been associated with this 2538 :class:`STMLEntry` a :class:`Record` will be auto generated 2539 using the current :class:`Template` set on this 2540 :class:`STMLEntry`. 2541 """ 2542 if (self.rec == None): 2543 tid = self.template_id 2544 tmpl = self.stml.session.get_template(tid) 2545 self.rec = Record(self.info_model, tmpl) 2546 self.rec.session = self.stml.session 2547 #raise Exception("No record set on STML Entry") 2548 _pyfixbuf.fbSTMLEntryBase.getNextRecord(self, self.rec) 2549 self.recset = True 2550 return self.rec 2551 2552 def __contains__(self, name): 2553 """ 2554 Implements the ``in`` operator for :class:`STMLEntry` instances: Tests 2555 whether the :class:`Template` associated with this :class:`STMLEntry` 2556 contains an :class:`InfoElement` having the name *name*. 2557 2558 Alternatively, you can access the :class:`Template` ID associated with 2559 this :class:`STMLEntry` to determine the type of :class:`Record` that 2560 should be used to access the elements. 2561 """ 2562 return _pyfixbuf.fbSTMLEntryBase.containsElement( 2563 self, self.info_model._unwrap(), name) 2564 2565 def __len__(self): 2566 """ 2567 Implements the built-in `len()` method for :class:`STMLEntry` 2568 instances: Returns the number of :class:`Record` objects in the 2569 :class:`STMLEntry`. 2570 """ 2571 return self.count 2572 2573 def __getitem__(self, item): 2574 """ 2575 Implements the evaluation of ``stmlEntry[item]`` for :class:`STMLEntry` 2576 instances: 2577 2578 If *item* is an :class:`int`, returns the :class:`Record` at that 2579 positional index. 2580 2581 If *item* is a :class:`str`, finds the :class:`InfoElement` with the 2582 name *item* in the :class:`STMLEntry`'s :class:`Template` and returns 2583 the value for that element in the first :class:`Record` in the 2584 :class:`STMLEntry`. 2585 2586 Returns None if *item* has an unexpected type. 2587 """ 2588 if (self.rec == None): 2589 if (self.info_model): 2590 tid = self.template_id 2591 tmpl = self.stml.session.get_template(tid) 2592 self.rec = Record(self.info_model, tmpl) 2593 else: 2594 raise Exception("No record set on STMLEntry") 2595 elif (self.info_model == None): 2596 self.info_model = self.rec.model 2597 2598 newRecord = Record(self.info_model, record=self.rec) 2599 newRecord.session=self.stml.session 2600 if (isinstance(item, str) or isinstance(item, tuple)): 2601 _pyfixbuf.fbSTMLEntryBase.getIndexedEntry(self, newRecord, 0) 2602 return newRecord[item] 2603 elif (isinstance(item, int)): 2604 _pyfixbuf.fbSTMLEntryBase.getIndexedEntry(self, newRecord, item) 2605 self.recset=True 2606 return newRecord 2607 2608 def __setitem__(self, index, value): 2609 """ 2610 Implements assignment to ``stmlentry[index]`` for :class:`STMLEntry` 2611 instances: Sets the :class:`Record` at position *index* in this 2612 :class:`STMLEntry` to *value*. 2613 2614 If this :class:`STMLEntry` has not been previously initialized, it is 2615 initialized with the :class:`Record`'s :class:`Template` and a length 2616 of 1. 2617 """ 2618 assert_type(Record, value) 2619 if (self.initialized == False): 2620 if (self.items == 0): 2621 if (value.template): 2622 self.entry_init(value, value.template, 1) 2623 else: 2624 raise Exception( 2625 "STMLEntry must be initialized for some number of items.") 2626 if self.stml.session and self.template_id: 2627 tmpl = self.stml.session.get_template(tid) 2628 value.check_template(tmpl) 2629 elif self._exp_template: 2630 value.check_template(self._exp_template) 2631 self.rec = value 2632 _pyfixbuf.fbSTMLEntryBase.setIndexedEntry(self, index, value) 2633 2634 2635class STL(_pyfixbuf.fbSTLBase): 2636 """ 2637 A :class:`STL` represents a subTemplateList. 2638 2639 If *record*, a :class:`Record` object, and *key_name*, a string, are 2640 provided, the subTemplateList for *key_name* in the given *record* is 2641 initialized, otherwise a generic :class:`STL` is initialized. Eventually 2642 a :class:`Template` must be associated with the :class:`STL` for encoding. 2643 2644 For decoding, a :class:`Record` must be associated with the :class:`STL`. 2645 """ 2646 def __init__(self, record=None, key_name=None): 2647 self.initialized = False 2648 self.info_model = None 2649 self.session = None 2650 # The record that gets filled and matches the STL 2651 self.rec = None 2652 # The template to use for export 2653 self._exp_template = None 2654 # The record that owns this STL 2655 self.inrec = None 2656 if (record != None and key_name != None): 2657 assert_type(Record, record) 2658 if (record.get_field_type(key_name) != SUBTEMPLATELIST): 2659 raise Exception("DataType of " + str(key_name) + 2660 " is not SUBTEMPLATELIST") 2661 _pyfixbuf.fbSTLBase.__init__(self, record, 2662 record._get_field_offset(key_name)) 2663 record.list_init = True 2664 self.inrec = record 2665 self.info_model = record.model 2666 self.session = record.session 2667 else: 2668 _pyfixbuf.fbSTLBase.__init__(self) 2669 2670 def set_record(self, record): 2671 """ 2672 Sets the :class:`Record` on this :class:`STL` to *record*. 2673 """ 2674 assert_type(Record, record) 2675 tid = self.template_id 2676 if (tid and self.session): 2677 tmpl = self.session.get_template(tid, False) 2678 record.check_template(tmpl) 2679 elif self._exp_template: 2680 value.check_template(self._exp_template) 2681 self.rec = record 2682 2683 def __contains__(self, name): 2684 """ 2685 Implements the ``in`` operator for :class:`STL` instances: Tests 2686 whether the :class:`Template` associated with this :class:`STL` 2687 contains an :class:`InfoElement` having the name *name*. 2688 """ 2689 return _pyfixbuf.fbSTLBase.containsElement( 2690 self, self.info_model._unwrap(), name) 2691 2692 def entry_init(self, record, template, count=0): 2693 """ 2694 Initializes the :class:`STL` to *count* entries of the given 2695 :class:`Record` and :class:`Template`. 2696 2697 This method should only be used to export a :class:`STL`. 2698 2699 Each :class:`STL` should be initialized before appending 2700 the :class:`Record` to the :class:`Buffer` even if it 2701 is initialized to 0. 2702 2703 Raises an :exc:`Exception` when `template` has a template_id of 0. You 2704 should add the `template` to a :class:`Session` before using it for 2705 the :class:`STL`. 2706 2707 The record that contains the :class:`STL` should not be modified 2708 after calling entry_init(). 2709 """ 2710 assert_type(Record, record) 2711 assert_type(Template, template) 2712 if template.template_id == 0: 2713 raise Exception("Template has a template_id of 0.") 2714 record.check_template(template, True) 2715 self.set_record(record) 2716 _pyfixbuf.fbSTLBase.entryInit( 2717 self, template._unwrap(), template.template_id, count) 2718 self.initialized = True 2719 self.info_model = record.model 2720 self._exp_template = template 2721 2722 def __iter__(self): 2723 """ 2724 Implements the method to return an Iterator over the :class:`Record` 2725 objects in the :class:`STL`. 2726 2727 Example: 2728 2729 >>> for record in stl: 2730 ... print record.as_dict() 2731 """ 2732 return self 2733 2734 def next(self): 2735 """Returns the next :class:`Record` in the :class:`STL`""" 2736 return self.__next__() 2737 2738 def __next__(self): 2739 """Returns the next :class:`Record` in the :class:`STL`""" 2740 if self.rec == None: 2741 if self.session: 2742 tid = self.template_id 2743 tmpl = self.session.get_template(tid) 2744 self.rec = Record(self.info_model, tmpl) 2745 self.rec.session = self.session 2746 else: 2747 raise Exception("No Record or Session set for STL") 2748 _pyfixbuf.fbSTLBase.getNext(self, self.rec) 2749 return self.rec 2750 2751 def clear(self): 2752 """ 2753 Clears all entries in the list. Nested elements should be accessed 2754 and freed before calling this method. Frees any memory previously 2755 allocated for the list. 2756 """ 2757 _pyfixbuf.fbSTLBase.clear(self) 2758 2759 def __len__(self): 2760 """ 2761 Implements the built-in `len()` method for :class:`STL` instances: 2762 Returns the number of :class:`Record` objects in the :class:`STL`. 2763 """ 2764 return self.count 2765 2766 def __getitem__(self, item): 2767 """ 2768 Implements the evaluation of ``stl[item]`` for :class:`STL` instances: 2769 2770 If *item* is an :class:`int`, returns the :class:`Record` at that 2771 positional index. 2772 2773 If *item* is a :class:`str`, finds the :class:`InfoElement` with the 2774 name *item* in the :class:`STL`'s :class:`Template` and returns the 2775 value for that element in the most recently accessed :class:`Record` 2776 in the :class:`STL`. 2777 2778 Returns None if *item* has an unexpected type. 2779 """ 2780 if self.rec == None: 2781 if self.session: 2782 tid = self.template_id 2783 tmpl = self.session.get_template(tid) 2784 self.rec = Record(self.info_model, tmpl) 2785 self.rec.session = self.session 2786 else: 2787 raise Exception("No Record or Session set for STL") 2788 if (isinstance(item, str) or isinstance(item, tuple)): 2789 return self.rec[item] 2790 elif (isinstance(item, int)): 2791 newRecord = Record(self.rec.model, record=self.rec) 2792 newRecord.session=self.session 2793 _pyfixbuf.fbSTLBase.getIndexedEntry(self, newRecord, item) 2794 return newRecord 2795 2796 def __setitem__(self, index, value): 2797 """ 2798 Implements assignment to ``stl[index]`` for :class:`STL` instances: 2799 Sets the :class:`Record` at position *index* in this :class:`STL` to 2800 *value*. 2801 2802 If this :class:`STL` was not previously initialized via 2803 :meth:`entry_init`, it is initialized with the given :class:`Record`'s 2804 :class:`Template` and a count of 1. 2805 """ 2806 assert_type(Record, value) 2807 if (self.initialized == False): 2808 if (value.template): 2809 self.entry_init(value, value.template, 1) 2810 else: 2811 raise Exception( 2812 "STL has not been initialized. Use entry_init().") 2813 if self.session and self.template_id: 2814 tmpl = self.session.get_template(tid) 2815 value.check_template(tmpl) 2816 elif self._exp_template: 2817 value.check_template(self._exp_template) 2818 self.rec = value 2819 _pyfixbuf.fbSTLBase.setIndexedEntry(self, index, value) 2820 2821 def iter_records(self, tmpl_id=0): 2822 """ 2823 Returns an Iterator over the :class:`STL`'s records, including records 2824 in any nested :class:`STML` or :class:`STL`. If a template ID is 2825 passed, returns an iterator over all the :class:`Record` objects with a 2826 template ID matching the passed value. 2827 2828 Example: 2829 2830 >>> for record in stl.iter_records(): 2831 ... print record.template.template_id 2832 ... 2833 """ 2834 for record in self: 2835 for sub_rec in yield_record_subrecords(record, tmpl_id): 2836 yield sub_rec 2837 2838 2839class BL(_pyfixbuf.fbBLBase): 2840 """A :class:`BL` represents a basicList. 2841 2842 A basicList is a list of zero or more instances of an Information Element. 2843 2844 A basicList can be initialized through a :class:`Record` via 2845 init_basic_list(), or by creating a :class:`BL` object. 2846 2847 The constructor requires an :class:`InfoModel` *model*, and a 2848 :class:`InfoElementSpec`, :class:`InfoElement`, or string 2849 *element*. Additionally, it takes an optional integer *count* 2850 which represents the number of elements in the list, and an 2851 optional integer *semantic* to express the relationship among the 2852 list items. 2853 2854 All basicLists in a :class:`Record` must be initialized (even to 0) before 2855 appending a :class:`Record` to a :class:`Buffer`. 2856 2857 Examples:: 2858 2859 >>> rec.add_element("basicList", BASICLIST) 2860 >>> rec.add_element("basicList2", BASICLIST) 2861 >>> bl = BL(model, "sourceTransportPort", 2) 2862 >>> bl[0] = 80 2863 >>> bl[1] = 23 2864 >>> rec["basicList"] = bl 2865 >>> rec.init_basic_list("basicList2", 4, "octetTotalCount") 2866 >>> rec["basicList2"] = [99, 101, 104, 23] 2867 """ 2868 def __init__(self, model, element, count=0, semantic=0): 2869 assert_type(InfoModel, model) 2870 self.model = model 2871 self.list = [] 2872 assert_type(InfoModel, model) 2873 if (isinstance(element, InfoElement)): 2874 _pyfixbuf.fbBLBase.__init__(self, element, count, semantic) 2875 elif (isinstance(element, str)): 2876 ie = model.get_element(element) 2877 _pyfixbuf.fbBLBase.__init__(self, ie, count, semantic) 2878 elif (isinstance(element, InfoElementSpec)): 2879 ie = model.get_element(element.name) 2880 _pyfixbuf.fbBLBase.__init__(self, ie, count, semantic) 2881 else: 2882 _pyfixbuf.fbBLBase.__init__(self) 2883 2884 def _fill_bl_list(self): 2885 if (len(self) and len(self.list) == 0): 2886 blist = _pyfixbuf.fbBLBase.getitems(self) 2887 ty = self.element.type 2888 tlist = [] 2889 if ty == DataType.IP4ADDR: 2890 for item in blist: 2891 tlist.append(ipaddress.IPv4Address(item)) 2892 elif ty == DataType.IP6ADDR: 2893 for item in blist: 2894 tlist.append(ipaddress.IPv6Address(bytes(item))) 2895 elif ty == DataType.MAC_ADDR: 2896 for item in blist: 2897 tlist.append(bytes_to_macaddr(item)) 2898 else: 2899 self.list = blist 2900 if (len(tlist)): 2901 self.list = tlist 2902 2903 def __len__(self): 2904 """ 2905 Implements the built-in `len()` method for :class:`BL` instances: 2906 Returns the number of entries in the basicList. 2907 2908 Example: 2909 2910 >>> bl = BL(model, "sourceTransportPort", 5) 2911 >>> len(bl) 2912 5 2913 """ 2914 return self.count 2915 2916 def __iter__(self): 2917 """ 2918 Implements the method to return an Iterator over the items in the 2919 :class:`BL`. 2920 2921 Example: 2922 2923 >>> bl = record['basicList'] 2924 >>> bl.element.name 2925 'httpContentType' 2926 >>> for v in bl: 2927 ... print v 2928 ... 2929 'text/xml' 2930 'text/plain' 2931 """ 2932 self._fill_bl_list() 2933 for item in self.list: 2934 yield item 2935 2936 def __getitem__(self, index): 2937 """ 2938 Implements the evaluation of ``bl[index]`` for :class:`BL` instances: 2939 Returns the value at position *index* in the basicList. 2940 2941 Example: 2942 2943 >>> bl = record['basicList'] 2944 >>> bl.element.name 2945 'httpContentType' 2946 >>> print bl[0] 2947 'text/xml' 2948 """ 2949 self._fill_bl_list() 2950 return self.list[index] 2951 2952 def __setitem__(self, index, value): 2953 """ 2954 Implements assignment to ``bl[index]`` for :class:`BL` instances: Sets 2955 the value at position *index* in the basicList to *value*. 2956 """ 2957 if (index > len(self)): 2958 raise IndexError("Index %d is out of range" % index) 2959 if (self.element == None): 2960 raise Exception("BL must be initialized with InfoElement" 2961 " before setting items.") 2962 ty = self.element.type 2963 if ty == DataType.IP6ADDR: 2964 value = ip6addr_to_bytes(value) 2965 if ty == DataType.IP4ADDR: 2966 value = ip4addr_to_int(value) 2967 if ty == DataType.MAC_ADDR: 2968 value = macaddr_to_bytes(value) 2969 _pyfixbuf.fbBLBase.setitems(self, index, value) 2970 2971 def copy(self, other): 2972 """ 2973 Copies the items in the list *other* to this :class:`BL`, stopping 2974 when *other* is exhausted or the length of the :class:`BL` is reached. 2975 2976 Raises :exc:`TypeError` when *other* does not support iteration. 2977 """ 2978 for i,value in zip(range(len(self)), other): 2979 self[i] = value 2980 2981 def __contains__(self, item): 2982 """ 2983 Implements the ``in`` operator for :class:`BL` instances: Tests 2984 whether this :class:`BL` contains an element whose value is *item*. 2985 2986 Example: 2987 2988 >>> bl = record['basicList'] 2989 >>> bl.element.name 2990 'httpContentType' 2991 >>> 'text/plain' in bl 2992 True 2993 """ 2994 self._fill_bl_list() 2995 return item in self.list 2996 2997 def __str__(self): 2998 self._fill_bl_list() 2999 return str(self.list) 3000 3001 def __eq__(self, other): 3002 """ 3003 Determines whether *other* is equal to this :class:`BL`. 3004 3005 Returns ``True`` when *other* and this basicList contain the same 3006 elements in the same order. Returns ``False`` otherwise. Raises 3007 :exc:`TypeError` when *other* does not support iteration. 3008 3009 Example: 3010 3011 >>> bl = record['basicList'] 3012 >>> bl.element.name 3013 'httpContentType' 3014 >>> bl == ['text/xml', 'text/plain'] 3015 True 3016 """ 3017 if (other == None): 3018 return False 3019 self._fill_bl_list() 3020 for x,y in zip_longest(iter(self.list), other): 3021 if x != y or x is None: 3022 return False 3023 return True 3024 3025 3026class Listener(_pyfixbuf.fbListenerBase): 3027 """ 3028 Creates a :class:`Listener` given the *session*, *hostname*, *transport*, 3029 and *port*. 3030 3031 *session* must be a valid instance of :class:`Session`. 3032 3033 *hostname* is a string containing the address to bind to, a hostname or an 3034 IP Address. If *hostname* is None, all addresses are used. 3035 3036 *transport* is the transport protocol; it may contain "tcp" (the default) 3037 "udp", or "sctp". (Using "sctp" raises an exception unless libfixbuf 3038 has been compiled with SCTP support.) 3039 3040 *port* is the port number to listen on, and it should be greater than 3041 1024. The default is 4739. 3042 3043 Examples:: 3044 3045 >>> listener = Listener(session, hostname="localhost", port=18000) 3046 """ 3047 def __init__(self, session, hostname, transport="tcp", port=4739): 3048 assert_type(Session, session) 3049 self.session = session 3050 if (port < 1024 or port == 0): 3051 raise Exception("Invalid Port: Port should be greater than 1024") 3052 _pyfixbuf.fbListenerBase.__init__(self) 3053 _pyfixbuf.fbListenerBase.allocListener(self, transport, hostname, 3054 str(port), session) 3055 3056 def wait(self, record=None): 3057 """ 3058 Waits for a connection on the set host and port. Once a connection 3059 is made, returns a newly allocated :class:`Buffer`. 3060 3061 If a :class:`Record` is given to :meth:`wait` then the returned 3062 :class:`Buffer` will already be associated with a :class:`Record`. 3063 3064 If no :class:`Record` is given, you must use :meth:`set_record` on 3065 the :class:`Buffer` before 3066 accessing the elements. 3067 3068 After receiving the :class:`Buffer` you must set the internal template 3069 on the returned :class:`Buffer` using 3070 :meth:`Buffer.set_internal_template` before accessing the data. 3071 3072 Examples:: 3073 3074 >>> buf = listener.wait() 3075 >>> buf.set_record(my_rec) 3076 >>> buf.set_internal_template(999) 3077 >>> for data in buf: 3078 >>> ... 3079 """ 3080 try: 3081 buf = Buffer(record) 3082 buf._init_listener(self.session) 3083 _pyfixbuf.fbListenerBase.listenerWait(self, buf, self.session) 3084 return buf 3085 except (KeyboardInterrupt, SystemExit): 3086 raise Exception("Stopped By User") 3087 3088 3089################### 3090# Misc Functions 3091################### 3092 3093# Takes a user's value for an IPv4 address and converts it to int 3094def ip4addr_to_int(x): 3095 if isinstance(x, ipaddress.IPv4Address): 3096 x = int(x) 3097 elif isinstance(x, (int, long)): 3098 if x < 0 or x > 0xffffffff: 3099 raise Exception("Integer IP4ADDR value falls outside valid range: " 3100 + str(x)) 3101 elif isinstance(x, bytearray): 3102 if len(x) != 4: 3103 raise Exception( 3104 "Bytearray IP4ADDR value has length %d, expected 4: %s" % 3105 (len(x), str(x))) 3106 x = int(ipaddress.IPv4Address(bytes(x))) 3107 elif isinstance(b"python2.7", str): 3108 # python 2.x 3109 if isinstance(x, (str, unicode, bytes)): 3110 x = int(ipaddress.IPv4Address(x.decode())) 3111 elif isinstance(x, (str, bytes)): 3112 x = int(ipaddress.IPv4Address(x)) 3113 return x 3114 3115# Takes a user's value for an IPv6 address and converts it to bytearray 3116def ip6addr_to_bytes(x): 3117 if isinstance(x, ipaddress.IPv6Address): 3118 x = x.packed 3119 elif isinstance(x, (int, long)): 3120 if x < 0: 3121 raise Exception("Integer IP6ADDR is negative: " + str(x)) 3122 x = ipaddress.IPv6Address(x).packed 3123 elif isinstance(x, bytearray): 3124 if len(x) != 16: 3125 raise Exception( 3126 "Bytearray IP6ADDR value has length %d, expected 16: %s" % 3127 (len(x), str(x))) 3128 x = bytes(x) 3129 elif isinstance(b"python2.7", str): 3130 # python 2.x 3131 if isinstance(x, (str, unicode, bytes)): 3132 x = ipaddress.IPv6Address(x.decode()).packed 3133 elif isinstance(x, (str, bytes)): 3134 x = ipaddress.IPv6Address(x).packed 3135 return bytearray(x) 3136 3137re_mac_address = re.compile(r"\A" + ":".join(["[0-9a-f][0-9a-f]"] * 6) + r"\Z", 3138 re.IGNORECASE) 3139 3140# Takes a user's value for a MAC address and converts it to bytearray 3141def macaddr_to_bytes(value): 3142 if isinstance(value, bytearray): 3143 if len(value) != 6: 3144 raise Exception("Bytearray MacAddress value has incorrect " 3145 "length (6 expected, got %d): %s" % 3146 (len(value), str(value))) 3147 elif isinstance(value, (int, long)): 3148 if value < 0 or value > 0xffffffffffff: 3149 raise Exception("Integer MAC_ADDR value falls outside " 3150 "valid range: %s" % str(value)) 3151 value = bytearray(binascii.a2b_hex('%012x' % value)) 3152 elif isinstance(b"python2.7", str): 3153 # python 2.x 3154 if isinstance(value, (str, unicode, bytes)): 3155 if not re_mac_address.match(value): 3156 raise Exception("String MAC_ADDR value has wrong form:" + value) 3157 value = bytearray(binascii.a2b_hex(value.replace(':', ''))) 3158 elif isinstance(value, bytes): 3159 if len(value) != 6: 3160 raise Exception("Bytes MacAddress value has incorrect " 3161 "length (6 expected, got %d): %s" % 3162 (len(value), str(value))) 3163 value = bytearray(value) 3164 elif isinstance(value, str): 3165 if not re_mac_address.match(value): 3166 raise Exception("String MAC_ADDR value has wrong form:" + value) 3167 value = bytearray(binascii.a2b_hex(value.replace(':', ''))) 3168 return value 3169 3170# Prints the "xx:xx:xx:xx:xx:xx" format for a MAC addr bytearray 3171def bytes_to_macaddr(packed): 3172 return ':'.join('%02x' % b for b in packed) 3173 3174def create_stml_from_list(item_list): 3175 listlen = len(item_list) 3176 tid = dict() 3177 tid2list=[] 3178 difflist = 0 3179 for i,item in enumerate(item_list): 3180 assert_type(Record, item) 3181 template = item.template 3182 if template: 3183 if template not in tid: 3184 newlist = [] 3185 newlist.append(item) 3186 tid[template] = difflist 3187 tid2list.append(newlist) 3188 difflist += 1 3189 else: 3190 elist = tid2list[tid[template]] 3191 elist.append(item) 3192 tid2list[tid[template]] = elist 3193 else: 3194 raise Exception("No template associated with item " + str(i+1)) 3195 stml = STML(type_count=len(tid)) 3196 for i,lt in enumerate(tid2list): 3197 stml[i] = lt 3198 return stml 3199 3200 3201FILTER = ''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) 3202def pyfix_hex_dump(src, length=8): 3203 newstr=[] 3204 for i in range(0, len(src), length): 3205 s = src[i:i+length] 3206 hex = ' '.join(["%02X" % ord(x) for x in s]) 3207 printchar = s.translate(FILTER) 3208 newstr.append("%04X %-*s %s\n" % (i, length*3, hex, printchar)) 3209 return ''.join(newstr) 3210 3211 3212from .yaflists import YAF_LIST, YAF_DNS_LIST, YAF_DPI_LIST, YAF_FLOW_STATS_LIST, YAF_FTP_LIST, YAF_HTTP_LIST, YAF_IMAP_LIST, YAF_RTSP_LIST, YAF_SIP_LIST, YAF_SLP_LIST, YAF_SMTP_LIST, YAF_SSL_LIST, YAF_STATS_LIST 3213 3214 3215# Local Variables: 3216# indent-tabs-mode:nil 3217# fill-column:78 3218# End: 3219