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