1# Copyright (c) 2017, The MITRE Corporation
2# For license information, see the LICENSE.txt file
3
4"""
5Creating, handling, and parsing TAXII 1.0 messages.
6"""
7
8import six
9
10try:
11    import simplejson as json
12except ImportError:
13    import json
14import os
15import warnings
16
17from lxml import etree
18
19from .common import (parse, parse_datetime_string, append_any_content_etree, TAXIIBase,
20                     get_required, get_optional, get_optional_text, parse_xml_string,
21                     stringify_content)
22from .validation import do_check, uri_regex, check_timestamp_label, message_id_regex_10
23from .constants import *
24
25
26def validate_xml(xml_string):
27    """
28    Note that this function has been deprecated. Please see
29    libtaxii.validators.SchemaValidator.
30
31    Validate XML with the TAXII XML Schema 1.0.
32
33    Args:
34        xml_string (str): The XML to validate.
35
36    Example:
37        .. code-block:: python
38
39            is_valid = tm10.validate_xml(message.to_xml())
40    """
41
42    warnings.warn('Call to deprecated function: libtaxii.messages_10.validate_xml()',
43                  category=DeprecationWarning)
44
45    etree_xml = parse_xml_string(xml_string)
46    package_dir, package_filename = os.path.split(__file__)
47    schema_file = os.path.join(package_dir, "xsd", "TAXII_XMLMessageBinding_Schema.xsd")
48    taxii_schema_doc = parse(schema_file, allow_file=True)
49    xml_schema = etree.XMLSchema(taxii_schema_doc)
50    valid = xml_schema.validate(etree_xml)
51    if not valid:
52        return xml_schema.error_log.last_error
53    return valid
54
55
56def get_message_from_xml(xml_string, encoding='utf_8'):
57    """Create a TAXIIMessage object from an XML string.
58
59    This function automatically detects which type of Message should be created
60    based on the XML.
61
62    Args:
63        xml_string (str): The XML to parse into a TAXII message.
64
65    Example:
66        .. code-block:: python
67
68            message_xml = message.to_xml()
69            new_message = tm10.get_message_from_xml(message_xml)
70    """
71    if isinstance(xml_string, six.binary_type):
72        xml_string = xml_string.decode(encoding, 'replace')
73    etree_xml = parse_xml_string(xml_string)
74    qn = etree.QName(etree_xml)
75    if qn.namespace != ns_map['taxii']:
76        raise ValueError('Unsupported namespace: %s' % qn.namespace)
77
78    message_type = qn.localname
79
80    if message_type == MSG_DISCOVERY_REQUEST:
81        return DiscoveryRequest.from_etree(etree_xml)
82    if message_type == MSG_DISCOVERY_RESPONSE:
83        return DiscoveryResponse.from_etree(etree_xml)
84    if message_type == MSG_FEED_INFORMATION_REQUEST:
85        return FeedInformationRequest.from_etree(etree_xml)
86    if message_type == MSG_FEED_INFORMATION_RESPONSE:
87        return FeedInformationResponse.from_etree(etree_xml)
88    if message_type == MSG_POLL_REQUEST:
89        return PollRequest.from_etree(etree_xml)
90    if message_type == MSG_POLL_RESPONSE:
91        return PollResponse.from_etree(etree_xml)
92    if message_type == MSG_STATUS_MESSAGE:
93        return StatusMessage.from_etree(etree_xml)
94    if message_type == MSG_INBOX_MESSAGE:
95        return InboxMessage.from_etree(etree_xml)
96    if message_type == MSG_MANAGE_FEED_SUBSCRIPTION_REQUEST:
97        return ManageFeedSubscriptionRequest.from_etree(etree_xml)
98    if message_type == MSG_MANAGE_FEED_SUBSCRIPTION_RESPONSE:
99        return ManageFeedSubscriptionResponse.from_etree(etree_xml)
100
101    raise ValueError('Unknown message_type: %s' % message_type)
102
103
104def get_message_from_dict(d):
105    """Create a TAXIIMessage object from a dictonary.
106
107    This function automatically detects which type of Message should be created
108    based on the 'message_type' key in the dictionary.
109
110    Args:
111        d (dict): The dictionary to build the TAXII message from.
112
113    Example:
114        .. code-block:: python
115
116            message_dict = message.to_dict()
117            new_message = tm10.get_message_from_dict(message_dict)
118    """
119    if 'message_type' not in d:
120        raise ValueError('message_type is a required field!')
121
122    message_type = d['message_type']
123    if message_type == MSG_DISCOVERY_REQUEST:
124        return DiscoveryRequest.from_dict(d)
125    if message_type == MSG_DISCOVERY_RESPONSE:
126        return DiscoveryResponse.from_dict(d)
127    if message_type == MSG_FEED_INFORMATION_REQUEST:
128        return FeedInformationRequest.from_dict(d)
129    if message_type == MSG_FEED_INFORMATION_RESPONSE:
130        return FeedInformationResponse.from_dict(d)
131    if message_type == MSG_POLL_REQUEST:
132        return PollRequest.from_dict(d)
133    if message_type == MSG_POLL_RESPONSE:
134        return PollResponse.from_dict(d)
135    if message_type == MSG_STATUS_MESSAGE:
136        return StatusMessage.from_dict(d)
137    if message_type == MSG_INBOX_MESSAGE:
138        return InboxMessage.from_dict(d)
139    if message_type == MSG_MANAGE_FEED_SUBSCRIPTION_REQUEST:
140        return ManageFeedSubscriptionRequest.from_dict(d)
141    if message_type == MSG_MANAGE_FEED_SUBSCRIPTION_RESPONSE:
142        return ManageFeedSubscriptionResponse.from_dict(d)
143
144    raise ValueError('Unknown message_type: %s' % message_type)
145
146
147def get_message_from_json(json_string, encoding='utf_8'):
148    """Create a TAXIIMessage object from a JSON string.
149
150    This function automatically detects which type of Message should be created
151    based on the JSON.
152
153    Args:
154        json_string (str): The JSON to parse into a TAXII message.
155    """
156    decoded_string = json_string.decode(encoding, 'replace')
157    return get_message_from_dict(json.loads(decoded_string))
158
159
160class TAXIIBase10(TAXIIBase):
161    version = VID_TAXII_XML_10
162
163
164class DeliveryParameters(TAXIIBase10):
165
166    """Delivery Parameters.
167
168    Args:
169        inbox_protocol (str): identifies the protocol to be used when pushing
170            TAXII Data Feed content to a Consumer's TAXII Inbox Service
171            implementation. **Required**
172        inbox_address (str): identifies the address of the TAXII Daemon hosting
173            the Inbox Service to which the Consumer requests content for this
174            TAXII Data Feed to be delivered. **Required**
175        delivery_message_binding (str): identifies the message binding to be
176             used to send pushed content for this subscription. **Required**
177        content_bindings (list of str): contains Content Binding IDs
178            indicating which types of contents the Consumer requests to
179            receive for this TAXII Data Feed. **Optional**
180    """
181
182    # TODO: Should the default arguments of these change? I'm not sure these are
183    # actually optional
184
185    def __init__(self, inbox_protocol=None, inbox_address=None,
186                 delivery_message_binding=None, content_bindings=None):
187        self.inbox_protocol = inbox_protocol
188        self.inbox_address = inbox_address
189        self.delivery_message_binding = delivery_message_binding
190        self.content_bindings = content_bindings or []
191
192    @property
193    def sort_key(self):
194        return self.inbox_address
195
196    @property
197    def inbox_protocol(self):
198        return self._inbox_protocol
199
200    @inbox_protocol.setter
201    def inbox_protocol(self, value):
202        do_check(value, 'inbox_protocol', regex_tuple=uri_regex)
203        self._inbox_protocol = value
204
205    @property
206    def inbox_address(self):
207        return self._inbox_address
208
209    @inbox_address.setter
210    def inbox_address(self, value):
211        # TODO: Can inbox_address be validated?
212        self._inbox_address = value
213
214    @property
215    def delivery_message_binding(self):
216        return self._delivery_message_binding
217
218    @delivery_message_binding.setter
219    def delivery_message_binding(self, value):
220        do_check(value, 'delivery_message_binding', regex_tuple=uri_regex)
221        self._delivery_message_binding = value
222
223    @property
224    def content_bindings(self):
225        return self._content_bindings
226
227    @content_bindings.setter
228    def content_bindings(self, value):
229        do_check(value, 'content_bindings', regex_tuple=uri_regex)
230        self._content_bindings = value
231
232    def to_etree(self):
233        xml = etree.Element('{%s}Push_Parameters' % ns_map['taxii'])
234
235        if self.inbox_protocol is not None:
236            pb = etree.SubElement(xml, '{%s}Protocol_Binding' % ns_map['taxii'])
237            pb.text = self.inbox_protocol
238
239        if self.inbox_address is not None:
240            a = etree.SubElement(xml, '{%s}Address' % ns_map['taxii'])
241            a.text = self.inbox_address
242
243        if self.delivery_message_binding is not None:
244            mb = etree.SubElement(xml, '{%s}Message_Binding' % ns_map['taxii'])
245            mb.text = self.delivery_message_binding
246
247        for binding in self.content_bindings:
248            cb = etree.SubElement(xml, '{%s}Content_Binding' % ns_map['taxii'])
249            cb.text = binding
250
251        return xml
252
253    def to_dict(self):
254        d = {}
255
256        if self.inbox_protocol is not None:
257            d['inbox_protocol'] = self.inbox_protocol
258
259        if self.inbox_address is not None:
260            d['inbox_address'] = self.inbox_address
261
262        if self.delivery_message_binding is not None:
263            d['delivery_message_binding'] = self.delivery_message_binding
264
265        d['content_bindings'] = []
266        for binding in self.content_bindings:
267            d['content_bindings'].append(binding)
268
269        return d
270
271    def to_text(self, line_prepend=''):
272        s = line_prepend + "=== Push Parameters ===\n"
273        s += line_prepend + "  Inbox Protocol: %s\n" % self.inbox_protocol
274        s += line_prepend + "  Address: %s\n" % self.inbox_address
275        s += line_prepend + "  Message Binding: %s\n" % self.delivery_message_binding
276        if len(self.content_bindings) > 0:
277            s += line_prepend + "  Content Bindings: Any Content\n"
278        for cb in self.content_bindings:
279            s += line_prepend + "  Content Binding: %s\n" % str(cb)
280
281        return s
282
283    @staticmethod
284    def from_etree(etree_xml):
285
286        inbox_protocol = get_optional_text(etree_xml, './taxii:Protocol_Binding', ns_map)
287        inbox_address = get_optional_text(etree_xml, './taxii:Address', ns_map)
288        delivery_message_binding = get_optional_text(etree_xml, './taxii:Message_Binding', ns_map)
289
290        content_bindings = []
291        for binding in etree_xml.xpath('./taxii:Content_Binding', namespaces=ns_map):
292            content_bindings.append(binding.text)
293
294        return DeliveryParameters(inbox_protocol, inbox_address, delivery_message_binding, content_bindings)
295
296    @staticmethod
297    def from_dict(d):
298        return DeliveryParameters(**d)
299
300
301class TAXIIMessage(TAXIIBase10):
302
303    """Encapsulate properties common to all TAXII Messages (such as headers).
304
305    This class is extended by each Message Type (e.g., DiscoveryRequest), with
306    each subclass containing subclass-specific information
307    """
308
309    message_type = 'TAXIIMessage'
310
311    def __init__(self, message_id, in_response_to=None, extended_headers=None):
312        """Create a new TAXIIMessage
313
314        Arguments:
315        - message_id (string) - A value identifying this message.
316        - in_response_to (string) - Contains the Message ID of the message to
317          which this is a response.
318        - extended_headers (dictionary) - A dictionary of name/value pairs for
319          use as Extended Headers
320        """
321        self.message_id = message_id
322        self.in_response_to = in_response_to
323        if extended_headers is None:
324            self.extended_headers = {}
325        else:
326            self.extended_headers = extended_headers
327
328    @property
329    def message_id(self):
330        return self._message_id
331
332    @message_id.setter
333    def message_id(self, value):
334        do_check(value, 'message_id', regex_tuple=message_id_regex_10)
335        self._message_id = value
336
337    @property
338    def in_response_to(self):
339        return self._in_response_to
340
341    @in_response_to.setter
342    def in_response_to(self, value):
343        do_check(value, 'in_response_to', regex_tuple=message_id_regex_10, can_be_none=True)
344        self._in_response_to = value
345
346    @property
347    def extended_headers(self):
348        return self._extended_headers
349
350    @extended_headers.setter
351    def extended_headers(self, value):
352        do_check(list(value.keys()), 'extended_headers.keys()', regex_tuple=uri_regex)
353        self._extended_headers = value
354
355    def to_etree(self):
356        """Creates the base etree for the TAXII Message.
357
358        Message-specific constructs must be added by each Message class. In
359        general, when converting to XML, subclasses should call this method
360        first, then create their specific XML constructs.
361        """
362        root_elt = etree.Element('{%s}%s' % (ns_map['taxii'], self.message_type), nsmap=ns_map)
363        root_elt.attrib['message_id'] = str(self.message_id)
364
365        if self.in_response_to is not None:
366            root_elt.attrib['in_response_to'] = str(self.in_response_to)
367
368        if len(self.extended_headers) > 0:
369            eh = etree.SubElement(root_elt, '{%s}Extended_Headers' % ns_map['taxii'])
370
371            for name, value in list(self.extended_headers.items()):
372                h = etree.SubElement(eh, '{%s}Extended_Header' % ns_map['taxii'])
373                h.attrib['name'] = name
374                append_any_content_etree(h, value)
375                # h.text = value
376        return root_elt
377
378    def to_xml(self, pretty_print=False):
379        """Convert a message to XML.
380
381        Subclasses shouldn't implement this method, as it is mainly a wrapper
382        for cls.to_etree.
383        """
384        return etree.tostring(self.to_etree(), pretty_print=pretty_print, encoding='utf-8')
385
386    def to_dict(self):
387        """Create the base dictionary for the TAXII Message.
388
389        Message-specific constructs must be added by each Message class. In
390        general, when converting to dictionary, subclasses should call this
391        method first, then create their specific dictionary constructs.
392        """
393        d = {}
394        d['message_type'] = self.message_type
395        d['message_id'] = self.message_id
396        if self.in_response_to is not None:
397            d['in_response_to'] = self.in_response_to
398        d['extended_headers'] = {}
399        for k, v in six.iteritems(self.extended_headers):
400            if isinstance(v, etree._Element) or isinstance(v, etree._ElementTree):
401                v = etree.tostring(v, encoding='utf-8')
402            elif not isinstance(v, six.string_types):
403                v = str(v)
404            d['extended_headers'][k] = v
405
406        return d
407
408    def to_text(self, line_prepend=''):
409        s = line_prepend + "Message Type: %s\n" % self.message_type
410        s += line_prepend + "Message ID: %s" % self.message_id
411        if self.in_response_to:
412            s += "; In Response To: %s" % self.in_response_to
413        s += "\n"
414        for k, v in six.iteritems(self.extended_headers):
415            s += line_prepend + "Extended Header: %s = %s" % (k, v)
416
417        return s
418
419    @classmethod
420    def from_etree(cls, src_etree, **kwargs):
421        """Pulls properties of a TAXII Message from an etree.
422
423        Message-specific constructs must be pulled by each Message class. In
424        general, when converting from etree, subclasses should call this method
425        first, then parse their specific XML constructs.
426        """
427
428        # Check namespace and element name of the root element
429        expected_tag = '{%s}%s' % (ns_map['taxii'], cls.message_type)
430        tag = src_etree.tag
431        if tag != expected_tag:
432            raise ValueError('%s != %s' % (tag, expected_tag))
433
434        # Get the message ID
435        message_id = get_required(src_etree, '/taxii:*/@message_id', ns_map)
436
437        # Get in response to, if present
438        in_response_to = get_optional(src_etree, '/taxii:*/@in_response_to', ns_map)
439        if in_response_to is not None:
440            kwargs['in_response_to'] = in_response_to
441
442        # Get the Extended headers
443        extended_header_list = src_etree.xpath('/taxii:*/taxii:Extended_Headers/taxii:Extended_Header', namespaces=ns_map)
444        extended_headers = {}
445        for header in extended_header_list:
446            eh_name = header.xpath('./@name')[0]
447            # eh_value = header.text
448            if len(header) == 0:  # This has string content
449                eh_value = header.text
450            else:  # This has XML content
451                eh_value = header[0]
452
453            extended_headers[eh_name] = eh_value
454
455        return cls(message_id, extended_headers=extended_headers, **kwargs)
456
457    @classmethod
458    def from_xml(cls, xml):
459        """Parse a Message from XML.
460
461        Subclasses shouldn't implemnet this method, as it is mainly a wrapper
462        for cls.from_etree.
463        """
464        etree_xml = parse_xml_string(xml)
465        return cls.from_etree(etree_xml)
466
467    @classmethod
468    def from_dict(cls, d, **kwargs):
469        """Pulls properties of a TAXII Message from a dictionary.
470
471        Message-specific constructs must be pulled by each Message class. In
472        general, when converting from dictionary, subclasses should call this
473        method first, then parse their specific dictionary constructs.
474        """
475        message_type = d['message_type']
476        if message_type != cls.message_type:
477            raise ValueError('%s != %s' % (message_type, cls.message_type))
478        message_id = d['message_id']
479        extended_headers = {}
480        for k, v in six.iteritems(d['extended_headers']):
481            try:
482                v = parse(v, allow_file=False)
483            except etree.XMLSyntaxError:
484                pass
485            extended_headers[k] = v
486
487        in_response_to = d.get('in_response_to')
488        if in_response_to:
489            kwargs['in_response_to'] = in_response_to
490
491        return cls(message_id, extended_headers=extended_headers, **kwargs)
492
493    @classmethod
494    def from_json(cls, json_string):
495        return cls.from_dict(json.loads(json_string))
496
497
498class ContentBlock(TAXIIBase10):
499
500    """A TAXII Content Block.
501
502    Args:
503        content_binding (str): a Content Binding ID or nesting expression
504            indicating the type of content contained in the Content field of this
505            Content Block. **Required**
506        content (string or etree): a piece of content of the type specified
507            by the Content Binding. **Required**
508        timestamp_label (datetime): the Timestamp Label associated with this
509            Content Block. **Optional**
510        padding (string): an arbitrary amount of padding for this Content
511            Block. **Optional**
512    """
513
514    NAME = 'Content_Block'
515
516    def __init__(self, content_binding, content, timestamp_label=None, padding=None):
517        self.content_binding = content_binding
518        self.content = content
519        self.timestamp_label = timestamp_label
520        self.padding = padding
521
522    @property
523    def sort_key(self):
524        return self.content[:25]
525
526    @property
527    def content_binding(self):
528        return self._content_binding
529
530    @content_binding.setter
531    def content_binding(self, value):
532        do_check(value, 'content_binding', regex_tuple=uri_regex)
533        self._content_binding = value
534
535    @property
536    def content(self):
537        if self.content_is_xml:
538            return etree.tostring(self._content, encoding='utf-8')
539        else:
540            return self._content
541
542    @content.setter
543    def content(self, value):
544        do_check(value, 'content')  # Just check for not None
545        self._content, self.content_is_xml = stringify_content(value)
546
547    @property
548    def content_is_xml(self):
549        return self._content_is_xml
550
551    @content_is_xml.setter
552    def content_is_xml(self, value):
553        do_check(value, 'content_is_xml', value_tuple=(True, False))
554        self._content_is_xml = value
555
556    @property
557    def timestamp_label(self):
558        return self._timestamp_label
559
560    @timestamp_label.setter
561    def timestamp_label(self, value):
562        value = check_timestamp_label(value, 'timestamp_label', can_be_none=True)
563        self._timestamp_label = value
564
565    def to_etree(self):
566        block = etree.Element('{%s}Content_Block' % ns_map['taxii'], nsmap=ns_map)
567        cb = etree.SubElement(block, '{%s}Content_Binding' % ns_map['taxii'])
568        cb.text = self.content_binding
569        c = etree.SubElement(block, '{%s}Content' % ns_map['taxii'])
570
571        if self.content_is_xml:
572            c.append(self._content)
573        else:
574            c.text = self._content
575
576        if self.timestamp_label:
577            tl = etree.SubElement(block, '{%s}Timestamp_Label' % ns_map['taxii'])
578            tl.text = self.timestamp_label.isoformat()
579
580        if self.padding is not None:
581            p = etree.SubElement(block, '{%s}Padding' % ns_map['taxii'])
582            p.text = self.padding
583
584        return block
585
586    def to_dict(self):
587        block = {}
588        block['content_binding'] = self.content_binding
589
590        if self.content_is_xml:
591            block['content'] = etree.tostring(self._content, encoding='utf-8')
592        else:
593            block['content'] = self._content
594        block['content_is_xml'] = self.content_is_xml
595
596        if self.timestamp_label:
597            block['timestamp_label'] = self.timestamp_label.isoformat()
598
599        if self.padding is not None:
600            block['padding'] = self.padding
601
602        return block
603
604    def to_text(self, line_prepend=''):
605        s = line_prepend + "=== Content Block ===\n"
606        s += line_prepend + "  Content Binding: %s\n" % self.content_binding
607        s += line_prepend + "  Content Length: %s\n" % len(self.content)
608        s += line_prepend + "  (Only content length is shown for brevity)\n"
609        if self.timestamp_label:
610            s += line_prepend + "  Timestamp Label: %s\n" % self.timestamp_label.isoformat()
611        s += line_prepend + "  Padding: %s\n" % self.padding
612
613        return s
614
615    @staticmethod
616    def from_etree(etree_xml):
617        kwargs = {}
618
619        kwargs['content_binding'] = get_required(etree_xml, './taxii:Content_Binding', ns_map).text
620
621        kwargs['padding'] = get_optional_text(etree_xml, './taxii:Padding', ns_map)
622
623        ts_text = get_optional_text(etree_xml, './taxii:Timestamp_Label', ns_map)
624        if ts_text:
625            kwargs['timestamp_label'] = parse_datetime_string(ts_text)
626
627        content = get_required(etree_xml, './taxii:Content', ns_map)
628
629        if len(content) == 0:  # This has string content
630            kwargs['content'] = content.text
631        else:  # This has XML content
632            kwargs['content'] = content[0]
633
634        return ContentBlock(**kwargs)
635
636
637    @staticmethod
638    def from_dict(d):
639        kwargs = {}
640        kwargs['content_binding'] = d['content_binding']
641        kwargs['padding'] = d.get('padding')
642
643        if d.get('timestamp_label'):
644            kwargs['timestamp_label'] = parse_datetime_string(d['timestamp_label'])
645
646        is_xml = d.get('content_is_xml', False)
647        if is_xml:
648            #FIXME: to parse or not to parse the content - this should be configurable
649            kwargs['content'] = parse(d['content'], allow_file=False)
650        else:
651            kwargs['content'] = d['content']
652
653        cb = ContentBlock(**kwargs)
654        return cb
655
656    @classmethod
657    def from_json(cls, json_string):
658        return cls.from_dict(json.loads(json_string))
659
660
661# TAXII Message Classes #
662
663class DiscoveryRequest(TAXIIMessage):
664
665    """
666    A TAXII Discovery Request message.
667
668    Args:
669        message_id (str): A value identifying this message. **Required**
670        extended_headers (dict): A dictionary of name/value pairs for
671            use as Extended Headers. **Optional**
672    """
673
674    message_type = MSG_DISCOVERY_REQUEST
675
676    @TAXIIMessage.in_response_to.setter
677    def in_response_to(self, value):
678        if value:
679            raise ValueError('in_response_to must be None')
680        self._in_response_to = value
681
682
683class DiscoveryResponse(TAXIIMessage):
684
685    """
686    A TAXII Discovery Response message.
687
688    Args:
689        message_id (str): A value identifying this message. **Required**
690        in_response_to (str): Contains the Message ID of the message to
691            which this is a response. **Optional**
692        extended_headers (dict): A dictionary of name/value pairs for
693            use as Extended Headers. **Optional**
694        service_instances (list of `ServiceInstance`): a list of
695            service instances that this response contains. **Optional**
696    """
697    message_type = MSG_DISCOVERY_RESPONSE
698
699    def __init__(self, message_id, in_response_to, extended_headers=None, service_instances=None):
700        super(DiscoveryResponse, self).__init__(message_id, in_response_to, extended_headers)
701        self.service_instances = service_instances or []
702
703    @TAXIIMessage.in_response_to.setter
704    def in_response_to(self, value):
705        do_check(value, 'in_response_to', regex_tuple=uri_regex)
706        self._in_response_to = value
707
708    @property
709    def service_instances(self):
710        return self._service_instances
711
712    @service_instances.setter
713    def service_instances(self, value):
714        do_check(value, 'service_instances', type=ServiceInstance)
715        self._service_instances = value
716
717    def to_etree(self):
718        xml = super(DiscoveryResponse, self).to_etree()
719        for service_instance in self.service_instances:
720            xml.append(service_instance.to_etree())
721        return xml
722
723    def to_dict(self):
724        d = super(DiscoveryResponse, self).to_dict()
725        d['service_instances'] = []
726        for service_instance in self.service_instances:
727            d['service_instances'].append(service_instance.to_dict())
728        return d
729
730    def to_text(self, line_prepend=''):
731        s = super(DiscoveryResponse, self).to_text(line_prepend)
732        for si in self.service_instances:
733            s += si.to_text(line_prepend + STD_INDENT)
734
735        return s
736
737    @classmethod
738    def from_etree(cls, etree_xml):
739        msg = super(DiscoveryResponse, cls).from_etree(etree_xml)
740        msg.service_instances = []
741        for service_instance in etree_xml.xpath('./taxii:Service_Instance', namespaces=ns_map):
742            si = ServiceInstance.from_etree(service_instance)
743            msg.service_instances.append(si)
744        return msg
745
746    @classmethod
747    def from_dict(cls, d):
748        msg = super(DiscoveryResponse, cls).from_dict(d)
749        msg.service_instances = []
750        for service_instance in d['service_instances']:
751            si = ServiceInstance.from_dict(service_instance)
752            msg.service_instances.append(si)
753        return msg
754
755
756class ServiceInstance(TAXIIBase10):
757
758    """
759    The Service Instance component of a TAXII Discovery Response Message.
760
761    Args:
762        service_type (string): identifies the Service Type of this
763            Service Instance. **Required**
764        services_version (string): identifies the TAXII Services
765            Specification to which this Service conforms. **Required**
766        protocol_binding (string): identifies the protocol binding
767            supported by this Service. **Required**
768        service_address (string): identifies the network address of the
769            TAXII Daemon that hosts this Service. **Required**
770        message_bindings (list of strings): identifies the message
771            bindings supported by this Service instance. **Required**
772        inbox_service_accepted_content (list of strings): identifies
773            content bindings that this Inbox Service is willing to accept.
774            **Optional**
775        available (boolean): indicates whether the identity of the
776            requester (authenticated or otherwise) is allowed to access this
777            TAXII Service. **Optional**
778        message (string): contains a message regarding this Service
779            instance. **Optional**
780
781    The ``message_bindings`` list must contain at least one value.
782    """
783
784    def __init__(self, service_type, services_version, protocol_binding,
785                 service_address, message_bindings,
786                 inbox_service_accepted_content=None, available=None,
787                 message=None):
788        self.service_type = service_type
789        self.services_version = services_version
790        self.protocol_binding = protocol_binding
791        self.service_address = service_address
792        self.message_bindings = message_bindings
793        self.inbox_service_accepted_content = inbox_service_accepted_content or []
794        self.available = available
795        self.message = message
796
797    @property
798    def sort_key(self):
799        return self.service_address
800
801    @property
802    def service_type(self):
803        return self._service_type
804
805    @service_type.setter
806    def service_type(self, value):
807        do_check(value, 'service_type', value_tuple=SVC_TYPES)
808        self._service_type = value
809
810    @property
811    def services_version(self):
812        return self._services_version
813
814    @services_version.setter
815    def services_version(self, value):
816        do_check(value, 'services_version', regex_tuple=uri_regex)
817        self._services_version = value
818
819    @property
820    def protocol_binding(self):
821        return self._protocol_binding
822
823    @protocol_binding.setter
824    def protocol_binding(self, value):
825        do_check(value, 'protocol_binding', regex_tuple=uri_regex)
826        self._protocol_binding = value
827
828    @property
829    def service_address(self):
830        return self._service_address
831
832    @service_address.setter
833    def service_address(self, value):
834        self._service_address = value
835
836    @property
837    def message_bindings(self):
838        return self._message_bindings
839
840    @message_bindings.setter
841    def message_bindings(self, value):
842        do_check(value, 'message_bindings', regex_tuple=uri_regex)
843        self._message_bindings = value
844
845    @property
846    def inbox_service_accepted_content(self):
847        return self._inbox_service_accepted_content
848
849    @inbox_service_accepted_content.setter
850    def inbox_service_accepted_content(self, value):
851        do_check(value, 'inbox_service_accepted_content', regex_tuple=uri_regex)
852        self._inbox_service_accepted_content = value
853
854    @property
855    def available(self):
856        return self._available
857
858    @available.setter
859    def available(self, value):
860        do_check(value, 'available', value_tuple=(True, False), can_be_none=True)
861        self._available = value
862
863    def to_etree(self):
864        si = etree.Element('{%s}Service_Instance' % ns_map['taxii'])
865        si.attrib['service_type'] = self.service_type
866        si.attrib['service_version'] = self.services_version
867        if self.available:
868            si.attrib['available'] = str(self.available).lower()
869
870        protocol_binding = etree.SubElement(si, '{%s}Protocol_Binding' % ns_map['taxii'])
871        protocol_binding.text = self.protocol_binding
872
873        service_address = etree.SubElement(si, '{%s}Address' % ns_map['taxii'])
874        service_address.text = self.service_address
875
876        for mb in self.message_bindings:
877            message_binding = etree.SubElement(si, '{%s}Message_Binding' % ns_map['taxii'])
878            message_binding.text = mb
879
880        for cb in self.inbox_service_accepted_content:
881            content_binding = etree.SubElement(si, '{%s}Content_Binding' % ns_map['taxii'])
882            content_binding.text = cb
883
884        if self.message is not None:
885            message = etree.SubElement(si, '{%s}Message' % ns_map['taxii'])
886            message.text = self.message
887
888        return si
889
890    def to_dict(self):
891        d = {}
892        d['service_type'] = self.service_type
893        d['services_version'] = self.services_version
894        d['protocol_binding'] = self.protocol_binding
895        d['service_address'] = self.service_address
896        d['message_bindings'] = self.message_bindings
897        d['inbox_service_accepted_content'] = self.inbox_service_accepted_content
898        d['available'] = self.available
899        d['message'] = self.message
900        return d
901
902    def to_text(self, line_prepend=''):
903        s = line_prepend + "=== Service Instance===\n"
904        s += line_prepend + "  Service Type: %s\n" % self.service_type
905        s += line_prepend + "  Services Version: %s\n" % self.services_version
906        s += line_prepend + "  Protocol Binding: %s\n" % self.protocol_binding
907        s += line_prepend + "  Address: %s\n" % self.service_address
908        for mb in self.message_bindings:
909            s += line_prepend + "  Message Binding: %s\n" % mb
910        if len(self.inbox_service_accepted_content) == 0:
911            s += line_prepend + "  Inbox Service Accepts: %s\n" % None
912        for isac in self.inbox_service_accepted_content:
913            s += line_prepend + "  Inbox Service Accepts: %s\n" % isac
914        s += line_prepend + "  Available: %s\n" % self.available
915        s += line_prepend + "  Message: %s\n" % self.message
916
917        return s
918
919    @classmethod
920    def from_etree(cls, etree_xml):  # Expects a taxii:Service_Instance element
921        service_type = etree_xml.attrib['service_type']
922        services_version = etree_xml.attrib['service_version']
923        available = None
924        if etree_xml.attrib.get('available'):
925            tmp_available = etree_xml.attrib['available']
926            available = tmp_available.lower() == 'true'
927
928        protocol_binding = get_required(etree_xml, './taxii:Protocol_Binding', ns_map).text
929        service_address = get_required(etree_xml, './taxii:Address', ns_map).text
930
931        message_bindings = []
932        for mb in etree_xml.xpath('./taxii:Message_Binding', namespaces=ns_map):
933            message_bindings.append(mb.text)
934
935        inbox_service_accepted_contents = []
936        for cb in etree_xml.xpath('./taxii:Content_Binding', namespaces=ns_map):
937            inbox_service_accepted_contents.append(cb.text)
938
939        message = get_optional_text(etree_xml, './taxii:Message', ns_map)
940
941        return ServiceInstance(service_type, services_version, protocol_binding,
942                service_address, message_bindings, inbox_service_accepted_contents,
943                available, message)
944
945    @staticmethod
946    def from_dict(d):
947        return ServiceInstance(**d)
948
949
950class FeedInformationRequest(TAXIIMessage):
951
952    """
953    A TAXII Feed Information Request message.
954
955    Args:
956        message_id (str): A value identifying this message. **Required**
957        extended_headers (dict): A dictionary of name/value pairs for
958            use as Extended Headers. **Optional**
959    """
960
961    message_type = MSG_FEED_INFORMATION_REQUEST
962
963    @TAXIIMessage.in_response_to.setter
964    def in_response_to(self, value):
965        if value:
966            raise ValueError('in_response_to must be None')
967        self._in_response_to = value
968
969
970class FeedInformationResponse(TAXIIMessage):
971
972    """
973    A TAXII Feed Information Response message.
974
975    Args:
976        message_id (str): A value identifying this message. **Required**
977        in_response_to (str): Contains the Message ID of the message to
978            which this is a response. **Required**
979        extended_headers (dict): A dictionary of name/value pairs for
980            use as Extended Headers. **Optional**
981        feed_informations (list of FeedInformation): A list
982            of FeedInformation objects to be contained in this response.
983            **Optional**
984    """
985    message_type = MSG_FEED_INFORMATION_RESPONSE
986
987    def __init__(self, message_id, in_response_to, extended_headers=None, feed_informations=None):
988        super(FeedInformationResponse, self).__init__(message_id, in_response_to, extended_headers=extended_headers)
989        self.feed_informations = feed_informations or []
990
991    @TAXIIMessage.in_response_to.setter
992    def in_response_to(self, value):
993        do_check(value, 'in_response_to', regex_tuple=message_id_regex_10)
994        self._in_response_to = value
995
996    @property
997    def feed_informations(self):
998        return self._feed_informations
999
1000    @feed_informations.setter
1001    def feed_informations(self, value):
1002        do_check(value, 'feed_informations', type=FeedInformation)
1003        self._feed_informations = value
1004
1005    def to_etree(self):
1006        xml = super(FeedInformationResponse, self).to_etree()
1007        for feed in self.feed_informations:
1008            xml.append(feed.to_etree())
1009        return xml
1010
1011    def to_dict(self):
1012        d = super(FeedInformationResponse, self).to_dict()
1013        d['feed_informations'] = []
1014        for feed in self.feed_informations:
1015            d['feed_informations'].append(feed.to_dict())
1016        return d
1017
1018    def to_text(self, line_prepend=''):
1019        s = super(FeedInformationResponse, self).to_text(line_prepend)
1020        for feed in self.feed_informations:
1021            s += feed.to_text(line_prepend + STD_INDENT)
1022
1023        return s
1024
1025    @classmethod
1026    def from_etree(cls, etree_xml):
1027        msg = super(FeedInformationResponse, cls).from_etree(etree_xml)
1028        msg.feed_informations = []
1029        feed_informations = etree_xml.xpath('./taxii:Feed', namespaces=ns_map)
1030        for feed in feed_informations:
1031            msg.feed_informations.append(FeedInformation.from_etree(feed))
1032        return msg
1033
1034    @classmethod
1035    def from_dict(cls, d):
1036        msg = super(FeedInformationResponse, cls).from_dict(d)
1037        msg.feed_informations = []
1038        for feed in d['feed_informations']:
1039            msg.feed_informations.append(FeedInformation.from_dict(feed))
1040        return msg
1041
1042
1043class FeedInformation(TAXIIBase10):
1044
1045    """
1046    The Feed Information component of a TAXII Feed Information Response
1047    Message.
1048
1049    Arguments:
1050        feed_name (str): the name by which this TAXII Data Feed is
1051            identified. **Required**
1052        feed_description (str): a prose description of this TAXII
1053            Data Feed. **Required**
1054        supported_contents (list of str): Content Binding IDs
1055            indicating which types of content are currently expressed in this
1056            TAXII Data Feed. **Required**
1057        available (boolean): whether the identity of the requester
1058            (authenticated or otherwise) is allowed to access this TAXII
1059            Service. **Optional** Default: ``None``, indicating "unknown"
1060        push_methods (list of PushMethod objects): the protocols that
1061            can be used to push content via a subscription. **Optional**
1062        polling_service_instances (list of PollingServiceInstance objects):
1063            the bindings and address a Consumer can use to interact with a
1064            Poll Service instance that supports this TAXII Data Feed.
1065            **Optional**
1066        subscription_methods (list of SubscriptionMethod objects): the
1067            protocol and address of the TAXII Daemon hosting the Feed
1068            Management Service that can process subscriptions for this TAXII
1069            Data Feed. **Optional**
1070
1071    The absense of ``push_methods`` indicates no push methods.  The absense
1072    of ``polling_service_instances`` indicates no polling services.  At
1073    least one of ``push_methods`` and ``polling_service_instances`` must not
1074    be empty. The absense of ``subscription_methods`` indicates no
1075    subscription services.
1076    """
1077
1078    def __init__(self, feed_name, feed_description, supported_contents,
1079                 available=None, push_methods=None,
1080                 polling_service_instances=None, subscription_methods=None):
1081
1082        self.feed_name = feed_name
1083        self.available = available
1084        self.feed_description = feed_description
1085        self.supported_contents = supported_contents
1086        self.push_methods = push_methods or []
1087        self.polling_service_instances = polling_service_instances or []
1088        self.subscription_methods = subscription_methods or []
1089
1090    @property
1091    def sort_key(self):
1092        return self.feed_name
1093
1094    @property
1095    def feed_name(self):
1096        return self._feed_name
1097
1098    @feed_name.setter
1099    def feed_name(self, value):
1100        do_check(value, 'feed_name', regex_tuple=uri_regex)
1101        self._feed_name = value
1102
1103    @property
1104    def available(self):
1105        return self._available
1106
1107    @available.setter
1108    def available(self, value):
1109        do_check(value, 'available', value_tuple=(True, False), can_be_none=True)
1110        self._available = value
1111
1112    @property
1113    def supported_contents(self):
1114        return self._supported_contents
1115
1116    @supported_contents.setter
1117    def supported_contents(self, value):
1118        do_check(value, 'supported_contents', regex_tuple=uri_regex)
1119        self._supported_contents = value
1120
1121    @property
1122    def push_methods(self):
1123        return self._push_methods
1124
1125    @push_methods.setter
1126    def push_methods(self, value):
1127        do_check(value, 'push_methods', type=PushMethod)
1128        self._push_methods = value
1129
1130    @property
1131    def polling_service_instances(self):
1132        return self._polling_service_instances
1133
1134    @polling_service_instances.setter
1135    def polling_service_instances(self, value):
1136        do_check(value, 'polling_service_instances', type=PollingServiceInstance)
1137        self._polling_service_instances = value
1138
1139    @property
1140    def subscription_methods(self):
1141        return self._subscription_methods
1142
1143    @subscription_methods.setter
1144    def subscription_methods(self, value):
1145        do_check(value, 'subscription_methods', type=SubscriptionMethod)
1146        self._subscription_methods = value
1147
1148    def to_etree(self):
1149        f = etree.Element('{%s}Feed' % ns_map['taxii'])
1150        f.attrib['feed_name'] = self.feed_name
1151        if self.available:
1152            f.attrib['available'] = str(self.available).lower()
1153        feed_description = etree.SubElement(f, '{%s}Description' % ns_map['taxii'])
1154        feed_description.text = self.feed_description
1155
1156        for binding in self.supported_contents:
1157            cb = etree.SubElement(f, '{%s}Content_Binding' % ns_map['taxii'])
1158            cb.text = binding
1159
1160        for push_method in self.push_methods:
1161            f.append(push_method.to_etree())
1162
1163        for polling_service in self.polling_service_instances:
1164            f.append(polling_service.to_etree())
1165
1166        for subscription_method in self.subscription_methods:
1167            f.append(subscription_method.to_etree())
1168
1169        return f
1170
1171    def to_dict(self):
1172        d = {}
1173        d['feed_name'] = self.feed_name
1174        if self.available:
1175            d['available'] = self.available
1176        d['feed_description'] = self.feed_description
1177        d['supported_contents'] = self.supported_contents
1178        d['push_methods'] = []
1179        for push_method in self.push_methods:
1180            d['push_methods'].append(push_method.to_dict())
1181        d['polling_service_instances'] = []
1182        for polling_service in self.polling_service_instances:
1183            d['polling_service_instances'].append(polling_service.to_dict())
1184        d['subscription_methods'] = []
1185        for subscription_method in self.subscription_methods:
1186            d['subscription_methods'].append(subscription_method.to_dict())
1187        return d
1188
1189    def to_text(self, line_prepend=''):
1190        s = line_prepend + "=== Data Feed ===\n"
1191        s += line_prepend + "  Feed Name: %s\n" % self.feed_name
1192        if self.available:
1193            s += line_prepend + "  Available: %s\n" % self.available
1194        s += line_prepend + "  Feed Description: %s\n" % self.feed_description
1195        for sc in self.supported_contents:
1196            s += line_prepend + "  Supported Content: %s\n" % sc
1197        for pm in self.push_methods:
1198            s += pm.to_text(line_prepend + STD_INDENT)
1199        for ps in self.polling_service_instances:
1200            s += ps.to_text(line_prepend + STD_INDENT)
1201        for sm in self.subscription_methods:
1202            s += sm.to_text(line_prepend + STD_INDENT)
1203
1204        return s
1205
1206    @staticmethod
1207    def from_etree(etree_xml):
1208        kwargs = {}
1209        kwargs['feed_name'] = etree_xml.attrib['feed_name']
1210        kwargs['available'] = None
1211        if 'available' in etree_xml.attrib:
1212            tmp = etree_xml.attrib['available']
1213            kwargs['available'] = tmp.lower() == 'true'
1214
1215        kwargs['feed_description'] = get_required(etree_xml, './taxii:Description', ns_map).text
1216
1217        kwargs['supported_contents'] = []
1218        for binding_elt in etree_xml.xpath('./taxii:Content_Binding', namespaces=ns_map):
1219            kwargs['supported_contents'].append(binding_elt.text)
1220
1221        kwargs['push_methods'] = []
1222        for push_method_elt in etree_xml.xpath('./taxii:Push_Method', namespaces=ns_map):
1223            kwargs['push_methods'].append(PushMethod.from_etree(push_method_elt))
1224
1225        kwargs['polling_service_instances'] = []
1226        for polling_elt in etree_xml.xpath('./taxii:Polling_Service', namespaces=ns_map):
1227            kwargs['polling_service_instances'].append(PollingServiceInstance.from_etree(polling_elt))
1228
1229        kwargs['subscription_methods'] = []
1230        for subscription_elt in etree_xml.xpath('./taxii:Subscription_Service', namespaces=ns_map):
1231            kwargs['subscription_methods'].append(SubscriptionMethod.from_etree(subscription_elt))
1232
1233        return FeedInformation(**kwargs)
1234
1235    @staticmethod
1236    def from_dict(d):
1237        kwargs = {}
1238        kwargs['feed_name'] = d['feed_name']
1239        kwargs['available'] = d.get('available')
1240
1241        kwargs['feed_description'] = d['feed_description']
1242        kwargs['supported_contents'] = []
1243        for binding in d.get('supported_contents', []):
1244            kwargs['supported_contents'].append(binding)
1245
1246        kwargs['push_methods'] = []
1247        for push_method in d.get('push_methods', []):
1248            kwargs['push_methods'].append(PushMethod.from_dict(push_method))
1249
1250        kwargs['polling_service_instances'] = []
1251        for polling in d.get('polling_service_instances', []):
1252            kwargs['polling_service_instances'].append(PollingServiceInstance.from_dict(polling))
1253
1254        kwargs['subscription_methods'] = []
1255        for subscription_method in d.get('subscription_methods', []):
1256            kwargs['subscription_methods'].append(SubscriptionMethod.from_dict(subscription_method))
1257
1258        return FeedInformation(**kwargs)
1259
1260
1261class PushMethod(TAXIIBase10):
1262
1263    """
1264    The Push Method component of a TAXII Feed Information
1265    component.
1266
1267    Args:
1268        push_protocol (str): a protocol binding that can be used
1269            to push content to an Inbox Service instance. **Required**
1270        push_message_bindings (list of str): the message bindings that
1271            can be used to push content to an Inbox Service instance
1272            using the protocol identified in the Push Protocol field.
1273            **Required**
1274    """
1275
1276    def __init__(self, push_protocol, push_message_bindings):
1277        self.push_protocol = push_protocol
1278        self.push_message_bindings = push_message_bindings
1279
1280    @property
1281    def sort_key(self):
1282        return self.push_protocol
1283
1284    @property
1285    def push_protocol(self):
1286        return self._push_protocol
1287
1288    @push_protocol.setter
1289    def push_protocol(self, value):
1290        do_check(value, 'push_protocol', regex_tuple=uri_regex)
1291        self._push_protocol = value
1292
1293    @property
1294    def push_message_bindings(self):
1295        return self._push_message_bindings
1296
1297    @push_message_bindings.setter
1298    def push_message_bindings(self, value):
1299        do_check(value, 'push_message_bindings', regex_tuple=uri_regex)
1300        self._push_message_bindings = value
1301
1302    def to_etree(self):
1303        x = etree.Element('{%s}Push_Method' % ns_map['taxii'])
1304        proto_bind = etree.SubElement(x, '{%s}Protocol_Binding' % ns_map['taxii'])
1305        proto_bind.text = self.push_protocol
1306        for binding in self.push_message_bindings:
1307            b = etree.SubElement(x, '{%s}Message_Binding' % ns_map['taxii'])
1308            b.text = binding
1309        return x
1310
1311    def to_dict(self):
1312        d = {}
1313        d['push_protocol'] = self.push_protocol
1314        d['push_message_bindings'] = []
1315        for binding in self.push_message_bindings:
1316            d['push_message_bindings'].append(binding)
1317        return d
1318
1319    def to_text(self, line_prepend=''):
1320        s = line_prepend + "=== Push Method ===\n"
1321        s += line_prepend + "  Protocol Binding: %s\n" % self.push_protocol
1322        for mb in self.push_message_bindings:
1323            s += line_prepend + "  Message Binding: %s\n" % mb
1324
1325        return s
1326
1327    @staticmethod
1328    def from_etree(etree_xml):
1329        kwargs = {}
1330        kwargs['push_protocol'] = get_required(etree_xml, './taxii:Protocol_Binding', ns_map).text
1331        kwargs['push_message_bindings'] = []
1332        for message_binding in etree_xml.xpath('./taxii:Message_Binding', namespaces=ns_map):
1333            kwargs['push_message_bindings'].append(message_binding.text)
1334        return PushMethod(**kwargs)
1335
1336    @staticmethod
1337    def from_dict(d):
1338        return PushMethod(**d)
1339
1340
1341class PollingServiceInstance(TAXIIBase10):
1342
1343    """
1344    The Polling Service Instance component of a TAXII Feed
1345    Information component.
1346
1347    Args:
1348        poll_protocol (str): the protocol binding supported by
1349            this Poll Service instance. **Required**
1350        poll_address (str): the address of the TAXII Daemon
1351            hosting this Poll Service instance. **Required**
1352        poll_message_bindings (list of str): the message bindings
1353            supported by this Poll Service instance. **Required**
1354    """
1355    NAME = 'Polling_Service'
1356
1357    def __init__(self, poll_protocol, poll_address, poll_message_bindings):
1358        self.poll_protocol = poll_protocol
1359        self.poll_address = poll_address
1360        self.poll_message_bindings = poll_message_bindings
1361
1362    @property
1363    def sort_key(self):
1364        return self.poll_address
1365
1366    @property
1367    def poll_protocol(self):
1368        return self._poll_protocol
1369
1370    @poll_protocol.setter
1371    def poll_protocol(self, value):
1372        do_check(value, 'poll_protocol', regex_tuple=uri_regex)
1373        self._poll_protocol = value
1374
1375    @property
1376    def poll_message_bindings(self):
1377        return self._poll_message_bindings
1378
1379    @poll_message_bindings.setter
1380    def poll_message_bindings(self, value):
1381        do_check(value, 'poll_message_bindings', regex_tuple=uri_regex)
1382        self._poll_message_bindings = value
1383
1384    def to_etree(self):
1385        x = etree.Element('{%s}Polling_Service' % ns_map['taxii'])
1386        proto_bind = etree.SubElement(x, '{%s}Protocol_Binding' % ns_map['taxii'])
1387        proto_bind.text = self.poll_protocol
1388        address = etree.SubElement(x, '{%s}Address' % ns_map['taxii'])
1389        address.text = self.poll_address
1390        for binding in self.poll_message_bindings:
1391            b = etree.SubElement(x, '{%s}Message_Binding' % ns_map['taxii'])
1392            b.text = binding
1393        return x
1394
1395    def to_dict(self):
1396        d = {}
1397        d['poll_protocol'] = self.poll_protocol
1398        d['poll_address'] = self.poll_address
1399        d['poll_message_bindings'] = []
1400        for binding in self.poll_message_bindings:
1401            d['poll_message_bindings'].append(binding)
1402        return d
1403
1404    def to_text(self, line_prepend=''):
1405        s = line_prepend + "=== Poll Service Instance ===\n"
1406        s += line_prepend + "  Protocol Binding: %s\n" % self.poll_protocol
1407        s += line_prepend + "  Address: %s\n" % self.poll_address
1408        for mb in self.poll_message_bindings:
1409            s += line_prepend + "  Message Binding: %s\n" % mb
1410
1411        return s
1412
1413    @classmethod
1414    def from_etree(cls, etree_xml):
1415        protocol = get_required(etree_xml, './taxii:Protocol_Binding', ns_map).text
1416        addr = get_required(etree_xml, './taxii:Address', ns_map).text
1417
1418        bindings = []
1419        for message_binding in etree_xml.xpath('./taxii:Message_Binding', namespaces=ns_map):
1420            bindings.append(message_binding.text)
1421        return cls(protocol, addr, bindings)
1422
1423    @classmethod
1424    def from_dict(cls, d):
1425        return cls(**d)
1426
1427
1428class SubscriptionMethod(TAXIIBase10):
1429
1430    """
1431    The Subscription Method component of a TAXII Feed Information
1432    component.
1433
1434    Args:
1435        subscription_protocol (str): the protocol binding supported by
1436            this Feed Management Service instance. **Required**
1437        subscription_address (str): the address of the TAXII Daemon
1438            hosting this Feed Management Service instance.
1439            **Required**.
1440        subscription_message_bindings (list of str): the message
1441            bindings supported by this Feed Management Service
1442            Instance. **Required**
1443    """
1444    NAME = 'Subscription_Service'
1445
1446    def __init__(self, subscription_protocol, subscription_address,
1447                 subscription_message_bindings):
1448        self.subscription_protocol = subscription_protocol
1449        self.subscription_address = subscription_address
1450        self.subscription_message_bindings = subscription_message_bindings
1451
1452    @property
1453    def sort_key(self):
1454        return self.subscription_address
1455
1456    @property
1457    def subscription_protocol(self):
1458        return self._subscription_protocol
1459
1460    @subscription_protocol.setter
1461    def subscription_protocol(self, value):
1462        do_check(value, 'subscription_protocol', regex_tuple=uri_regex)
1463        self._subscription_protocol = value
1464
1465    @property
1466    def subscription_message_bindings(self):
1467        return self._subscription_message_bindings
1468
1469    @subscription_message_bindings.setter
1470    def subscription_message_bindings(self, value):
1471        do_check(value, 'subscription_message_bindings', regex_tuple=uri_regex)
1472        self._subscription_message_bindings = value
1473
1474    def to_etree(self):
1475        x = etree.Element('{%s}%s' % (ns_map['taxii'], self.NAME))
1476        proto_bind = etree.SubElement(x, '{%s}Protocol_Binding' % ns_map['taxii'])
1477        proto_bind.text = self.subscription_protocol
1478        address = etree.SubElement(x, '{%s}Address' % ns_map['taxii'])
1479        address.text = self.subscription_address
1480        for binding in self.subscription_message_bindings:
1481            b = etree.SubElement(x, '{%s}Message_Binding' % ns_map['taxii'])
1482            b.text = binding
1483        return x
1484
1485    def to_dict(self):
1486        d = {}
1487        d['subscription_protocol'] = self.subscription_protocol
1488        d['subscription_address'] = self.subscription_address
1489        d['subscription_message_bindings'] = []
1490        for binding in self.subscription_message_bindings:
1491            d['subscription_message_bindings'].append(binding)
1492        return d
1493
1494    def to_text(self, line_prepend=''):
1495        s = line_prepend + "=== Subscription Method ===\n"
1496        s += line_prepend + "  Protocol Binding: %s\n" % self.subscription_protocol
1497        s += line_prepend + "  Address: %s\n" % self.subscription_address
1498        for mb in self.subscription_message_bindings:
1499            s += line_prepend + "  Message Binding: %s\n" % mb
1500
1501        return s
1502
1503    @classmethod
1504    def from_etree(cls, etree_xml):
1505        protocol = get_required(etree_xml, './taxii:Protocol_Binding', ns_map).text
1506        addr = get_required(etree_xml, './taxii:Address', ns_map).text
1507        bindings = []
1508        for message_binding in etree_xml.xpath('./taxii:Message_Binding', namespaces=ns_map):
1509            bindings.append(message_binding.text)
1510        return cls(protocol, addr, bindings)
1511
1512    @classmethod
1513    def from_dict(cls, d):
1514        return cls(**d)
1515
1516
1517class PollRequest(TAXIIMessage):
1518
1519    """
1520    A TAXII Poll Request message.
1521
1522    Arguments:
1523        message_id (str): A value identifying this message. **Required**
1524        extended_headers (dict): A dictionary of name/value pairs for
1525            use as Extended Headers. **Optional**
1526        feed_name (str): the name of the TAXII Data Feed that is being
1527            polled. **Required**
1528        exclusive_begin_timestamp_label (datetime): a Timestamp Label
1529            indicating the beginning of the range of TAXII Data Feed content the
1530            requester wishes to receive. **Optional**
1531        inclusive_end_timestamp_label (datetime): a Timestamp Label
1532            indicating the end of the range of TAXII Data Feed content the
1533            requester wishes to receive. **Optional**
1534        subscription_id (str): the existing subscription the Consumer
1535            wishes to poll. **Optional**
1536        content_bindings (list of str): the type of content that is
1537            requested in the response to this poll. **Optional**, defaults to
1538            accepting all content bindings.
1539    """
1540    message_type = MSG_POLL_REQUEST
1541
1542    def __init__(self, message_id, extended_headers=None,
1543                 feed_name=None, exclusive_begin_timestamp_label=None,
1544                 inclusive_end_timestamp_label=None, subscription_id=None,
1545                 content_bindings=None):
1546        super(PollRequest, self).__init__(message_id, extended_headers=extended_headers)
1547        self.feed_name = feed_name
1548        self.exclusive_begin_timestamp_label = exclusive_begin_timestamp_label
1549        self.inclusive_end_timestamp_label = inclusive_end_timestamp_label
1550        self.subscription_id = subscription_id
1551        self.content_bindings = content_bindings or []
1552
1553    @TAXIIMessage.in_response_to.setter
1554    def in_response_to(self, value):
1555        if value:
1556            raise ValueError('in_response_to must be None')
1557        self._in_response_to = value
1558
1559    @property
1560    def feed_name(self):
1561        return self._feed_name
1562
1563    @feed_name.setter
1564    def feed_name(self, value):
1565        do_check(value, 'feed_name', regex_tuple=uri_regex)
1566        self._feed_name = value
1567
1568    @property
1569    def exclusive_begin_timestamp_label(self):
1570        return self._exclusive_begin_timestamp_label
1571
1572    @exclusive_begin_timestamp_label.setter
1573    def exclusive_begin_timestamp_label(self, value):
1574        value = check_timestamp_label(value, 'exclusive_begin_timestamp_label', can_be_none=True)
1575        self._exclusive_begin_timestamp_label = value
1576
1577    @property
1578    def inclusive_end_timestamp_label(self):
1579        return self._inclusive_end_timestamp_label
1580
1581    @inclusive_end_timestamp_label.setter
1582    def inclusive_end_timestamp_label(self, value):
1583        value = check_timestamp_label(value, 'inclusive_end_timestamp_label', can_be_none=True)
1584        self._inclusive_end_timestamp_label = value
1585
1586    @property
1587    def subscription_id(self):
1588        return self._subscription_id
1589
1590    @subscription_id.setter
1591    def subscription_id(self, value):
1592        do_check(value, 'subscription_id', regex_tuple=uri_regex, can_be_none=True)
1593        self._subscription_id = value
1594
1595    @property
1596    def content_bindings(self):
1597        return self._content_bindings
1598
1599    @content_bindings.setter
1600    def content_bindings(self, value):
1601        do_check(value, 'content_bindings', regex_tuple=uri_regex)
1602        self._content_bindings = value
1603
1604    def to_etree(self):
1605        xml = super(PollRequest, self).to_etree()
1606        xml.attrib['feed_name'] = self.feed_name
1607        if self.subscription_id is not None:
1608            xml.attrib['subscription_id'] = self.subscription_id
1609
1610        if self.exclusive_begin_timestamp_label:
1611            ebt = etree.SubElement(xml, '{%s}Exclusive_Begin_Timestamp' % ns_map['taxii'])
1612            # TODO: Add TZ Info
1613            ebt.text = self.exclusive_begin_timestamp_label.isoformat()
1614
1615        if self.inclusive_end_timestamp_label:
1616            iet = etree.SubElement(xml, '{%s}Inclusive_End_Timestamp' % ns_map['taxii'])
1617            # TODO: Add TZ Info
1618            iet.text = self.inclusive_end_timestamp_label.isoformat()
1619
1620        for binding in self.content_bindings:
1621            b = etree.SubElement(xml, '{%s}Content_Binding' % ns_map['taxii'])
1622            b.text = binding
1623
1624        return xml
1625
1626    def to_dict(self):
1627        d = super(PollRequest, self).to_dict()
1628        d['feed_name'] = self.feed_name
1629        if self.subscription_id is not None:
1630            d['subscription_id'] = self.subscription_id
1631        if self.exclusive_begin_timestamp_label:  # TODO: Add TZ Info
1632            d['exclusive_begin_timestamp_label'] = self.exclusive_begin_timestamp_label.isoformat()
1633        if self.inclusive_end_timestamp_label:  # TODO: Add TZ Info
1634            d['inclusive_end_timestamp_label'] = self.inclusive_end_timestamp_label.isoformat()
1635        d['content_bindings'] = []
1636        for bind in self.content_bindings:
1637            d['content_bindings'].append(bind)
1638        return d
1639
1640    def to_text(self, line_prepend=''):
1641        s = super(PollRequest, self).to_text(line_prepend)
1642        s += line_prepend + "  Feed Name: %s\n" % self.feed_name
1643        if self.subscription_id:
1644            s += line_prepend + "  Subscription ID: %s\n" % self.subscription_id
1645
1646        if self.exclusive_begin_timestamp_label:
1647            s += line_prepend + "  Excl. Begin Timestamp Label: %s\n" % self.exclusive_begin_timestamp_label.isoformat()
1648        else:
1649            s += line_prepend + "  Excl. Begin Timestamp Label: %s\n" % None
1650
1651        if self.inclusive_end_timestamp_label:
1652            s += line_prepend + "  Incl. End Timestamp Label: %s\n" % self.inclusive_end_timestamp_label.isoformat()
1653        else:
1654            s += line_prepend + "  Incl. End Timestamp Label: %s\n" % None
1655
1656        if len(self.content_bindings) == 0:
1657            s += line_prepend + "  Content Binding: Any Content\n"
1658
1659        for cb in self.content_bindings:
1660            s += line_prepend + "  Content Binding: %s\n" % cb
1661
1662        return s
1663
1664    @classmethod
1665    def from_etree(cls, etree_xml):
1666        kwargs = {}
1667        kwargs['feed_name'] = get_required(etree_xml, './@feed_name', ns_map)
1668        kwargs['subscription_id'] = get_optional(etree_xml, './@subscription_id', ns_map)
1669
1670        ebt_text = get_optional_text(etree_xml, './taxii:Exclusive_Begin_Timestamp', ns_map)
1671        if ebt_text:
1672            kwargs['exclusive_begin_timestamp_label'] = parse_datetime_string(ebt_text)
1673
1674        iet_text = get_optional_text(etree_xml, './taxii:Inclusive_End_Timestamp', ns_map)
1675        if iet_text:
1676            kwargs['inclusive_end_timestamp_label'] = parse_datetime_string(iet_text)
1677
1678        kwargs['content_bindings'] = []
1679        for binding in etree_xml.xpath('./taxii:Content_Binding', namespaces=ns_map):
1680            kwargs['content_bindings'].append(binding.text)
1681
1682        msg = super(PollRequest, cls).from_etree(etree_xml, **kwargs)
1683        return msg
1684
1685    @classmethod
1686    def from_dict(cls, d):
1687        kwargs = {}
1688        kwargs['feed_name'] = d['feed_name']
1689
1690        kwargs['subscription_id'] = d.get('subscription_id')
1691
1692        kwargs['exclusive_begin_timestamp_label'] = None
1693        if d.get('exclusive_begin_timestamp_label'):
1694            kwargs['exclusive_begin_timestamp_label'] = parse_datetime_string(d['exclusive_begin_timestamp_label'])
1695
1696        kwargs['inclusive_end_timestamp_label'] = None
1697        if d.get('inclusive_end_timestamp_label'):
1698            kwargs['inclusive_end_timestamp_label'] = parse_datetime_string(d['inclusive_end_timestamp_label'])
1699
1700        kwargs['content_bindings'] = d.get('content_bindings', [])
1701
1702        msg = super(PollRequest, cls).from_dict(d, **kwargs)
1703        return msg
1704
1705
1706class PollResponse(TAXIIMessage):
1707
1708    """
1709    A TAXII Poll Response message.
1710
1711    Args:
1712        message_id (str): A value identifying this message. **Required**
1713        in_response_to (str): Contains the Message ID of the message to
1714            which this is a response. **Required**
1715        extended_headers (dict): A dictionary of name/value pairs for
1716            use as Extended Headers. **Optional**
1717        feed_name (str): the name of the TAXII Data Feed that was polled.
1718            **Required**
1719        inclusive_begin_timestamp_label (datetime): a Timestamp Label
1720            indicating the beginning of the range this response covers.
1721            **Optional**
1722        inclusive_end_timestamp_label (datetime): a Timestamp Label
1723            indicating the end of the range this response covers. **Required**
1724        subscription_id (str): the Subscription ID for which this content
1725            is being provided. **Optional**
1726        message (str): additional information for the message recipient.
1727            **Optional**
1728        content_blocks (list of ContentBlock): piece of content
1729            and additional information related to the content. **Optional**
1730    """
1731    message_type = MSG_POLL_RESPONSE
1732
1733    def __init__(self, message_id, in_response_to, extended_headers=None,
1734                 feed_name=None, inclusive_begin_timestamp_label=None,
1735                 inclusive_end_timestamp_label=None, subscription_id=None,
1736                 message=None, content_blocks=None):
1737        super(PollResponse, self).__init__(message_id, in_response_to, extended_headers)
1738        self.feed_name = feed_name
1739        self.inclusive_end_timestamp_label = inclusive_end_timestamp_label
1740        self.inclusive_begin_timestamp_label = inclusive_begin_timestamp_label
1741        self.subscription_id = subscription_id
1742        self.message = message
1743        self.content_blocks = content_blocks or []
1744
1745    @TAXIIMessage.in_response_to.setter
1746    def in_response_to(self, value):
1747        do_check(value, 'in_response_to', regex_tuple=uri_regex)
1748        self._in_response_to = value
1749
1750    @property
1751    def feed_name(self):
1752        return self._feed_name
1753
1754    @feed_name.setter
1755    def feed_name(self, value):
1756        do_check(value, 'feed_name', regex_tuple=uri_regex)
1757        self._feed_name = value
1758
1759    @property
1760    def inclusive_end_timestamp_label(self):
1761        return self._inclusive_end_timestamp_label
1762
1763    @inclusive_end_timestamp_label.setter
1764    def inclusive_end_timestamp_label(self, value):
1765        value = check_timestamp_label(value, 'inclusive_end_timestamp_label')
1766        self._inclusive_end_timestamp_label = value
1767
1768    @property
1769    def inclusive_begin_timestamp_label(self):
1770        return self._inclusive_begin_timestamp_label
1771
1772    @inclusive_begin_timestamp_label.setter
1773    def inclusive_begin_timestamp_label(self, value):
1774        value = check_timestamp_label(value, 'inclusive_begin_timestamp_label', can_be_none=True)
1775        self._inclusive_begin_timestamp_label = value
1776
1777    @property
1778    def subscription_id(self):
1779        return self._subscription_id
1780
1781    @subscription_id.setter
1782    def subscription_id(self, value):
1783        do_check(value, 'subscription_id', regex_tuple=uri_regex, can_be_none=True)
1784        self._subscription_id = value
1785
1786    @property
1787    def content_blocks(self):
1788        return self._content_blocks
1789
1790    @content_blocks.setter
1791    def content_blocks(self, value):
1792        do_check(value, 'content_blocks', type=ContentBlock)
1793        self._content_blocks = value
1794
1795    def to_etree(self):
1796        xml = super(PollResponse, self).to_etree()
1797        xml.attrib['feed_name'] = self.feed_name
1798        if self.subscription_id is not None:
1799            xml.attrib['subscription_id'] = self.subscription_id
1800
1801        if self.message is not None:
1802            m = etree.SubElement(xml, '{%s}Message' % ns_map['taxii'])
1803            m.text = self.message
1804
1805        if self.inclusive_begin_timestamp_label:
1806            ibt = etree.SubElement(xml, '{%s}Inclusive_Begin_Timestamp' % ns_map['taxii'])
1807            ibt.text = self.inclusive_begin_timestamp_label.isoformat()
1808
1809        iet = etree.SubElement(xml, '{%s}Inclusive_End_Timestamp' % ns_map['taxii'])
1810        iet.text = self.inclusive_end_timestamp_label.isoformat()
1811
1812        for block in self.content_blocks:
1813            xml.append(block.to_etree())
1814
1815        return xml
1816
1817    def to_dict(self):
1818        d = super(PollResponse, self).to_dict()
1819
1820        d['feed_name'] = self.feed_name
1821        if self.subscription_id is not None:
1822            d['subscription_id'] = self.subscription_id
1823        if self.message is not None:
1824            d['message'] = self.message
1825        if self.inclusive_begin_timestamp_label:
1826            d['inclusive_begin_timestamp_label'] = self.inclusive_begin_timestamp_label.isoformat()
1827        d['inclusive_end_timestamp_label'] = self.inclusive_end_timestamp_label.isoformat()
1828        d['content_blocks'] = []
1829        for block in self.content_blocks:
1830            d['content_blocks'].append(block.to_dict())
1831
1832        return d
1833
1834    def to_text(self, line_prepend=''):
1835        s = super(PollResponse, self).to_text(line_prepend)
1836        s += line_prepend + "  Feed Name: %s\n" % self.feed_name
1837        if self.subscription_id:
1838            s += line_prepend + "  Subscription ID: %s\n" % self.subscription_id
1839        s += line_prepend + "  Message: %s\n" % self.message
1840
1841        if self.inclusive_begin_timestamp_label:
1842            s += line_prepend + "  Incl. Begin Timestamp Label: %s\n" % self.inclusive_begin_timestamp_label.isoformat()
1843        else:
1844            s += line_prepend + "  Incl. Begin Timestamp Label: %s\n" % None
1845
1846        s += line_prepend + "  Incl. End Timestamp Label: %s\n" % self.inclusive_end_timestamp_label.isoformat()
1847
1848        for cb in self.content_blocks:
1849            s += cb.to_text(line_prepend + STD_INDENT)
1850
1851        return s
1852
1853    @classmethod
1854    def from_etree(cls, etree_xml):
1855        kwargs = {}
1856
1857        kwargs['feed_name'] = get_required(etree_xml, './@feed_name', ns_map)
1858        kwargs['subscription_id'] = get_optional(etree_xml, './@subscription_id', ns_map)
1859        kwargs['message'] = get_optional_text(etree_xml, './taxii:Message', ns_map)
1860
1861        ibts_text = get_optional_text(etree_xml, './taxii:Inclusive_Begin_Timestamp', ns_map)
1862        if ibts_text:
1863            kwargs['inclusive_begin_timestamp_label'] = parse_datetime_string(ibts_text)
1864
1865        iets_text = get_required(etree_xml, './taxii:Inclusive_End_Timestamp', ns_map).text
1866        kwargs['inclusive_end_timestamp_label'] = parse_datetime_string(iets_text)
1867
1868        kwargs['content_blocks'] = []
1869        blocks = etree_xml.xpath('./taxii:Content_Block', namespaces=ns_map)
1870        for block in blocks:
1871            kwargs['content_blocks'].append(ContentBlock.from_etree(block))
1872
1873        msg = super(PollResponse, cls).from_etree(etree_xml, **kwargs)
1874        return msg
1875
1876    @classmethod
1877    def from_dict(cls, d):
1878        kwargs = {}
1879        kwargs['feed_name'] = d['feed_name']
1880
1881        kwargs['message'] = d.get('message')
1882        kwargs['subscription_id'] = d.get('subscription_id')
1883
1884        kwargs['inclusive_begin_timestamp_label'] = None
1885        if d.get('inclusive_begin_timestamp_label'):
1886            kwargs['inclusive_begin_timestamp_label'] = parse_datetime_string(d['inclusive_begin_timestamp_label'])
1887
1888        kwargs['inclusive_end_timestamp_label'] = parse_datetime_string(d['inclusive_end_timestamp_label'])
1889
1890        kwargs['content_blocks'] = []
1891        for block in d['content_blocks']:
1892            kwargs['content_blocks'].append(ContentBlock.from_dict(block))
1893        msg = super(PollResponse, cls).from_dict(d, **kwargs)
1894        return msg
1895
1896
1897class StatusMessage(TAXIIMessage):
1898
1899    """
1900    A TAXII Status message.
1901
1902    Args:
1903        message_id (str): A value identifying this message. **Required**
1904        in_response_to (str): Contains the Message ID of the message to
1905            which this is a response. **Required**
1906        extended_headers (dict): A dictionary of name/value pairs for
1907            use as Extended Headers. **Optional**
1908        status_type (str): One of the defined Status Types or a third-party-
1909            defined Status Type. **Required**
1910        status_detail (str): A field for additional information about
1911            this status in a machine-readable format. **Optional or Prohibited**
1912            depending on ``status_type``. See TAXII Specification for details.
1913        message (str): Additional information for the status. There is no
1914            expectation that this field be interpretable by a machine; it is
1915            instead targeted to a human operator. **Optional**
1916    """
1917    message_type = MSG_STATUS_MESSAGE
1918
1919    def __init__(self, message_id, in_response_to, extended_headers=None,
1920                 status_type=None, status_detail=None, message=None):
1921        super(StatusMessage, self).__init__(message_id, in_response_to, extended_headers=extended_headers)
1922        self.status_type = status_type
1923        self.status_detail = status_detail
1924        self.message = message
1925
1926    @TAXIIMessage.in_response_to.setter
1927    def in_response_to(self, value):
1928        do_check(value, 'in_response_to', regex_tuple=uri_regex)
1929        self._in_response_to = value
1930
1931    @property
1932    def status_type(self):
1933        return self._status_type
1934
1935    @status_type.setter
1936    def status_type(self, value):
1937        do_check(value, 'status_type')
1938        self._status_type = value
1939
1940    # TODO: is it possible to check the status detail?
1941
1942    def to_etree(self):
1943        xml = super(StatusMessage, self).to_etree()
1944        xml.attrib['status_type'] = self.status_type
1945
1946        if self.status_detail is not None:
1947            sd = etree.SubElement(xml, '{%s}Status_Detail' % ns_map['taxii'])
1948            sd.text = self.status_detail
1949
1950        if self.message is not None:
1951            m = etree.SubElement(xml, '{%s}Message' % ns_map['taxii'])
1952            m.text = self.message
1953
1954        return xml
1955
1956    def to_dict(self):
1957        d = super(StatusMessage, self).to_dict()
1958        d['status_type'] = self.status_type
1959        if self.status_detail is not None:
1960            d['status_detail'] = self.status_detail
1961        if self.message is not None:
1962            d['message'] = self.message
1963
1964        return d
1965
1966    def to_text(self, line_prepend=''):
1967        s = super(StatusMessage, self).to_text(line_prepend)
1968        s += line_prepend + "  Status Type: %s\n" % self.status_type
1969        if self.status_detail:
1970            s += line_prepend + "  Status Detail: %s\n" % self.status_detail
1971        s += line_prepend + "  Status Message: %s\n" % self.message
1972        return s
1973
1974    @classmethod
1975    def from_etree(cls, etree_xml):
1976        kwargs = dict(
1977            status_type=etree_xml.attrib['status_type'],
1978            status_detail=get_optional_text(etree_xml, './taxii:Status_Detail', ns_map),
1979            message=get_optional_text(etree_xml, './taxii:Message', ns_map),
1980        )
1981
1982        msg = super(StatusMessage, cls).from_etree(etree_xml, **kwargs)
1983        return msg
1984
1985    @classmethod
1986    def from_dict(cls, d):
1987        kwargs = dict(
1988            status_type=d['status_type'],
1989            status_detail=d.get('status_detail'),
1990            message=d.get('message')
1991        )
1992
1993        msg = super(StatusMessage, cls).from_dict(d, **kwargs)
1994        return msg
1995
1996
1997class InboxMessage(TAXIIMessage):
1998
1999    """
2000    A TAXII Inbox message.
2001
2002    Args:
2003        message_id (str): A value identifying this message. **Required**
2004        extended_headers (dict): A dictionary of name/value pairs for
2005            use as Extended Headers. **Optional**
2006        message (str): prose information for the message recipient. **Optional**
2007        subscription_information (libtaxii.messages_10.SubscriptionInformation): This
2008            field is only present if this message is being sent to provide
2009            content in accordance with an existing TAXII Data Feed
2010            subscription. **Optional**
2011        content_blocks (list of ContentBlock): Inbox content. **Optional**
2012    """
2013
2014    message_type = MSG_INBOX_MESSAGE
2015
2016    def __init__(self, message_id, in_response_to=None, extended_headers=None,
2017                 message=None, subscription_information=None,
2018                 content_blocks=None):
2019
2020        super(InboxMessage, self).__init__(message_id, extended_headers=extended_headers)
2021        self.subscription_information = subscription_information
2022        self.message = message
2023        self.content_blocks = content_blocks or []
2024
2025    @TAXIIMessage.in_response_to.setter
2026    def in_response_to(self, value):
2027        if value:
2028            raise ValueError('in_response_to must be None')
2029        self._in_response_to = value
2030
2031    @property
2032    def subscription_information(self):
2033        return self._subscription_information
2034
2035    @subscription_information.setter
2036    def subscription_information(self, value):
2037        do_check(value, 'subscription_information', type=SubscriptionInformation, can_be_none=True)
2038        self._subscription_information = value
2039
2040    @property
2041    def content_blocks(self):
2042        return self._content_blocks
2043
2044    @content_blocks.setter
2045    def content_blocks(self, value):
2046        do_check(value, 'content_blocks', type=ContentBlock)
2047        self._content_blocks = value
2048
2049    def to_etree(self):
2050        xml = super(InboxMessage, self).to_etree()
2051        if self.message is not None:
2052            m = etree.SubElement(xml, '{%s}Message' % ns_map['taxii'])
2053            m.text = self.message
2054
2055        if self.subscription_information:
2056            xml.append(self.subscription_information.to_etree())
2057
2058        for block in self.content_blocks:
2059            xml.append(block.to_etree())
2060
2061        return xml
2062
2063    def to_dict(self):
2064        d = super(InboxMessage, self).to_dict()
2065        if self.message is not None:
2066            d['message'] = self.message
2067
2068        if self.subscription_information:
2069            d['subscription_information'] = self.subscription_information.to_dict()
2070
2071        d['content_blocks'] = []
2072        for block in self.content_blocks:
2073            d['content_blocks'].append(block.to_dict())
2074
2075        return d
2076
2077    def to_text(self, line_prepend=''):
2078        s = super(InboxMessage, self).to_text(line_prepend)
2079        s += line_prepend + "  Message: %s\n" % self.message
2080        if self.subscription_information:
2081            s += self.subscription_information.to_text(line_prepend + STD_INDENT)
2082        s += line_prepend + "  Message has %s Content Blocks\n" % len(self.content_blocks)
2083        for cb in self.content_blocks:
2084            s += cb.to_text(line_prepend + STD_INDENT)
2085
2086        return s
2087
2088    @classmethod
2089    def from_etree(cls, etree_xml):
2090        msg = super(InboxMessage, cls).from_etree(etree_xml)
2091
2092        msg.message = get_optional_text(etree_xml, './taxii:Message', ns_map)
2093
2094        subs_info = get_optional(etree_xml, './taxii:Source_Subscription', ns_map)
2095        if subs_info is not None:
2096            msg.subscription_information = SubscriptionInformation.from_etree(subs_info)
2097
2098        content_blocks = etree_xml.xpath('./taxii:Content_Block', namespaces=ns_map)
2099        msg.content_blocks = []
2100        for block in content_blocks:
2101            msg.content_blocks.append(ContentBlock.from_etree(block))
2102
2103        return msg
2104
2105    @classmethod
2106    def from_dict(cls, d):
2107        msg = super(InboxMessage, cls).from_dict(d)
2108
2109        msg.message = d.get('message')
2110
2111        msg.subscription_information = None
2112        if 'subscription_information' in d:
2113            msg.subscription_information = SubscriptionInformation.from_dict(d['subscription_information'])
2114
2115        msg.content_blocks = []
2116        for block in d['content_blocks']:
2117            msg.content_blocks.append(ContentBlock.from_dict(block))
2118
2119        return msg
2120
2121
2122class SubscriptionInformation(TAXIIBase10):
2123
2124    """
2125    The Subscription Information component of a TAXII Inbox message.
2126
2127    Arguments:
2128        feed_name (str): the name of the TAXII Data Feed from
2129            which this content is being provided. **Required**
2130        subscription_id (str): the Subscription ID for which this
2131            content is being provided. **Required**
2132        inclusive_begin_timestamp_label (datetime): a Timestamp Label
2133            indicating the beginning of the time range this Inbox Message
2134            covers. **Optional**
2135        inclusive_end_timestamp_label (datetime): a Timestamp Label
2136            indicating the end of the time range this Inbox Message covers.
2137            **Optional**
2138    """
2139
2140    def __init__(self, feed_name, subscription_id,
2141                 inclusive_begin_timestamp_label,
2142                 inclusive_end_timestamp_label):
2143        self.feed_name = feed_name
2144        self.subscription_id = subscription_id
2145        self.inclusive_begin_timestamp_label = inclusive_begin_timestamp_label
2146        self.inclusive_end_timestamp_label = inclusive_end_timestamp_label
2147
2148    @property
2149    def feed_name(self):
2150        return self._feed_name
2151
2152    @feed_name.setter
2153    def feed_name(self, value):
2154        do_check(value, 'feed_name', regex_tuple=uri_regex)
2155        self._feed_name = value
2156
2157    @property
2158    def subscription_id(self):
2159        return self._subscription_id
2160
2161    @subscription_id.setter
2162    def subscription_id(self, value):
2163        do_check(value, 'subscription_id', regex_tuple=uri_regex)
2164        self._subscription_id = value
2165
2166    @property
2167    def inclusive_begin_timestamp_label(self):
2168        return self._inclusive_begin_timestamp_label
2169
2170    @inclusive_begin_timestamp_label.setter
2171    def inclusive_begin_timestamp_label(self, value):
2172        value = check_timestamp_label(value, 'inclusive_begin_timestamp_label')
2173        self._inclusive_begin_timestamp_label = value
2174
2175    @property
2176    def inclusive_end_timestamp_label(self):
2177        return self._inclusive_end_timestamp_label
2178
2179    @inclusive_end_timestamp_label.setter
2180    def inclusive_end_timestamp_label(self, value):
2181        value = check_timestamp_label(value, 'inclusive_end_timestamp_label')
2182        self._inclusive_end_timestamp_label = value
2183
2184    def to_etree(self):
2185        xml = etree.Element('{%s}Source_Subscription' % ns_map['taxii'])
2186        xml.attrib['feed_name'] = self.feed_name
2187        xml.attrib['subscription_id'] = self.subscription_id
2188
2189        ibtl = etree.SubElement(xml, '{%s}Inclusive_Begin_Timestamp' % ns_map['taxii'])
2190        ibtl.text = self.inclusive_begin_timestamp_label.isoformat()
2191
2192        ietl = etree.SubElement(xml, '{%s}Inclusive_End_Timestamp' % ns_map['taxii'])
2193        ietl.text = self.inclusive_end_timestamp_label.isoformat()
2194
2195        return xml
2196
2197    def to_dict(self):
2198        d = {}
2199        d['feed_name'] = self.feed_name
2200        d['subscription_id'] = self.subscription_id
2201        d['inclusive_begin_timestamp_label'] = self.inclusive_begin_timestamp_label.isoformat()
2202        d['inclusive_end_timestamp_label'] = self.inclusive_end_timestamp_label.isoformat()
2203        return d
2204
2205    def to_text(self, line_prepend=''):
2206        s = line_prepend + "=== Subscription Information ===\n"
2207        s += line_prepend + "  Feed Name: %s\n" % self.feed_name
2208        s += line_prepend + "  Subscription ID: %s\n" % self.subscription_id
2209        s += line_prepend + "  Incl. Begin TS Label: %s\n" % self.inclusive_begin_timestamp_label.isoformat()
2210        s += line_prepend + "  Incl. End TS Label: %s\n" % self.inclusive_end_timestamp_label.isoformat()
2211        return s
2212
2213    @staticmethod
2214    def from_etree(etree_xml):
2215        feed_name = etree_xml.attrib['feed_name']
2216        subscription_id = etree_xml.attrib['subscription_id']
2217
2218        ibtl = parse_datetime_string(get_required(etree_xml, './taxii:Inclusive_Begin_Timestamp', ns_map).text)
2219        ietl = parse_datetime_string(get_required(etree_xml, './taxii:Inclusive_End_Timestamp', ns_map).text)
2220
2221        return SubscriptionInformation(feed_name, subscription_id, ibtl, ietl)
2222
2223    @staticmethod
2224    def from_dict(d):
2225        feed_name = d['feed_name']
2226        subscription_id = d['subscription_id']
2227
2228        ibtl = parse_datetime_string(d['inclusive_begin_timestamp_label'])
2229        ietl = parse_datetime_string(d['inclusive_end_timestamp_label'])
2230
2231        return SubscriptionInformation(feed_name, subscription_id, ibtl, ietl)
2232
2233
2234class ManageFeedSubscriptionRequest(TAXIIMessage):
2235
2236    """
2237    A TAXII Manage Feed Subscription Request message.
2238
2239    Args:
2240        message_id (str): A value identifying this message. **Required**
2241        extended_headers (dict): A dictionary of name/value pairs for
2242            use as Extended Headers. **Optional**
2243        feed_name (str): the name of the TAXII Data Feed to which the
2244            action applies. **Required**
2245        action (str): the requested action to take. **Required**
2246        subscription_id (str): the ID of a previously created subscription.
2247            **Required** if ``action==``:py:data:`ACT_UNSUBSCRIBE`, else
2248            **Prohibited**.
2249        delivery_parameters (list of DeliveryParameters): the delivery parameters
2250            for this request. **Optional** Absence means delivery is not requested.
2251    """
2252
2253    message_type = MSG_MANAGE_FEED_SUBSCRIPTION_REQUEST
2254
2255    def __init__(self, message_id, extended_headers=None,
2256                 feed_name=None, action=None, subscription_id=None,
2257                 delivery_parameters=None):
2258        super(ManageFeedSubscriptionRequest, self).__init__(message_id, extended_headers=extended_headers)
2259        self.feed_name = feed_name
2260        self.action = action
2261        self.subscription_id = subscription_id
2262        self.delivery_parameters = delivery_parameters
2263
2264    @TAXIIMessage.in_response_to.setter
2265    def in_response_to(self, value):
2266        if value:
2267            raise ValueError('in_response_to must be None')
2268        self._in_response_to = value
2269
2270    @property
2271    def feed_name(self):
2272        return self._feed_name
2273
2274    @feed_name.setter
2275    def feed_name(self, value):
2276        do_check(value, 'feed_name', regex_tuple=uri_regex)
2277        self._feed_name = value
2278
2279    @property
2280    def action(self):
2281        return self._action
2282
2283    @action.setter
2284    def action(self, value):
2285        do_check(value, 'action', value_tuple=ACT_TYPES)
2286        self._action = value
2287
2288    @property
2289    def subscription_id(self):
2290        return self._subscription_id
2291
2292    @subscription_id.setter
2293    def subscription_id(self, value):
2294        do_check(value, 'subscription_id', regex_tuple=uri_regex, can_be_none=True)
2295        self._subscription_id = value
2296
2297    @property
2298    def delivery_parameters(self):
2299        return self._delivery_parameters
2300
2301    @delivery_parameters.setter
2302    def delivery_parameters(self, value):
2303        do_check(value, 'delivery_parameters', type=DeliveryParameters, can_be_none=True)
2304        self._delivery_parameters = value
2305
2306    def to_etree(self):
2307        xml = super(ManageFeedSubscriptionRequest, self).to_etree()
2308        xml.attrib['feed_name'] = self.feed_name
2309        xml.attrib['action'] = self.action
2310        if self.subscription_id is not None:
2311            xml.attrib['subscription_id'] = self.subscription_id
2312
2313        if self.delivery_parameters:
2314            xml.append(self.delivery_parameters.to_etree())
2315        return xml
2316
2317    def to_dict(self):
2318        d = super(ManageFeedSubscriptionRequest, self).to_dict()
2319        d['feed_name'] = self.feed_name
2320        d['action'] = self.action
2321        d['subscription_id'] = self.subscription_id
2322        d['delivery_parameters'] = None
2323        if self.delivery_parameters:
2324            d['delivery_parameters'] = self.delivery_parameters.to_dict()
2325        return d
2326
2327    def to_text(self, line_prepend=''):
2328        s = super(ManageFeedSubscriptionRequest, self).to_text(line_prepend)
2329        s += line_prepend + "  Feed Name: %s\n" % self.feed_name
2330        s += line_prepend + "  Action: %s\n" % self.action
2331        s += line_prepend + "  Subscription ID: %s\n" % self.subscription_id
2332        if self.delivery_parameters:
2333            s += self.delivery_parameters.to_text(line_prepend + STD_INDENT)
2334        return s
2335
2336    @classmethod
2337    def from_etree(cls, etree_xml):
2338        kwargs = dict(
2339            feed_name=get_required(etree_xml, './@feed_name', ns_map),
2340            action=get_required(etree_xml, './@action', ns_map),
2341
2342            # subscription_id is not required for action 'SUBSCRIBE'
2343            subscription_id=get_optional(etree_xml, './@subscription_id', ns_map),
2344        )
2345
2346        # marked as required in spec but as optional is XSD
2347        delivery = get_optional(etree_xml, './taxii:Push_Parameters', ns_map)
2348        if delivery is not None:
2349            kwargs['delivery_parameters'] = DeliveryParameters.from_etree(delivery)
2350
2351        msg = super(ManageFeedSubscriptionRequest, cls).from_etree(etree_xml, **kwargs)
2352        return msg
2353
2354    @classmethod
2355    def from_dict(cls, d):
2356        kwargs = dict(
2357            feed_name=d['feed_name'],
2358            action=d['action'],
2359            subscription_id=d['subscription_id'],
2360            delivery_parameters=DeliveryParameters.from_dict(d['delivery_parameters'])
2361        )
2362
2363        msg = super(ManageFeedSubscriptionRequest, cls).from_dict(d, **kwargs)
2364        return msg
2365
2366
2367class ManageFeedSubscriptionResponse(TAXIIMessage):
2368
2369    """
2370    A TAXII Manage Feed Subscription Response message.
2371
2372    Args:
2373        message_id (str): A value identifying this message. **Required**
2374        in_response_to (str): Contains the Message ID of the message to
2375            which this is a response. **Required**
2376        extended_headers (dict): A dictionary of name/value pairs for
2377            use as Extended Headers. **Optional**
2378        feed_name (str): the name of the TAXII Data Feed to which
2379            the action applies. **Required**
2380        message (str): additional information for the message recipient.
2381            **Optional**
2382        subscription_instances (list of SubscriptionInstance): **Optional**
2383    """
2384
2385    message_type = MSG_MANAGE_FEED_SUBSCRIPTION_RESPONSE
2386
2387    def __init__(self, message_id, in_response_to, extended_headers=None,
2388                 feed_name=None, message=None, subscription_instances=None):
2389        super(ManageFeedSubscriptionResponse, self).__init__(message_id, in_response_to, extended_headers=extended_headers)
2390        self.feed_name = feed_name
2391        self.message = message
2392        self.subscription_instances = subscription_instances or []
2393
2394    @TAXIIMessage.in_response_to.setter
2395    def in_response_to(self, value):
2396        do_check(value, 'in_response_to', regex_tuple=uri_regex)
2397        self._in_response_to = value
2398
2399    @property
2400    def feed_name(self):
2401        return self._feed_name
2402
2403    @feed_name.setter
2404    def feed_name(self, value):
2405        do_check(value, 'feed_name', regex_tuple=uri_regex)
2406        self._feed_name = value
2407
2408    @property
2409    def subscription_instances(self):
2410        return self._subscription_instances
2411
2412    @subscription_instances.setter
2413    def subscription_instances(self, value):
2414        do_check(value, 'subscription_instances', type=SubscriptionInstance)
2415        self._subscription_instances = value
2416
2417    def to_etree(self):
2418        xml = super(ManageFeedSubscriptionResponse, self).to_etree()
2419        xml.attrib['feed_name'] = self.feed_name
2420        if self.message is not None:
2421            m = etree.SubElement(xml, '{%s}Message' % ns_map['taxii'])
2422            m.text = self.message
2423
2424        for subscription_instance in self.subscription_instances:
2425            xml.append(subscription_instance.to_etree())
2426
2427        return xml
2428
2429    def to_dict(self):
2430        d = super(ManageFeedSubscriptionResponse, self).to_dict()
2431        d['feed_name'] = self.feed_name
2432        if self.message is not None:
2433            d['message'] = self.message
2434        d['subscription_instances'] = []
2435        for subscription_instance in self.subscription_instances:
2436            d['subscription_instances'].append(subscription_instance.to_dict())
2437
2438        return d
2439
2440    def to_text(self, line_prepend=''):
2441        s = super(ManageFeedSubscriptionResponse, self).to_text(line_prepend)
2442        s += line_prepend + "  Feed Name: %s\n" % self.feed_name
2443        s += line_prepend + "  Message: %s\n" % self.message
2444        for si in self.subscription_instances:
2445            s += si.to_text(line_prepend + STD_INDENT)
2446        return s
2447
2448    @classmethod
2449    def from_etree(cls, etree_xml):
2450        kwargs = {}
2451        kwargs['feed_name'] = etree_xml.attrib['feed_name']
2452
2453        kwargs['message'] = get_optional_text(etree_xml, './taxii:Message', ns_map)
2454
2455        kwargs['subscription_instances'] = []
2456        for si in etree_xml.xpath('./taxii:Subscription', namespaces=ns_map):
2457            kwargs['subscription_instances'].append(SubscriptionInstance.from_etree(si))
2458
2459        msg = super(ManageFeedSubscriptionResponse, cls).from_etree(etree_xml, **kwargs)
2460        return msg
2461
2462    @classmethod
2463    def from_dict(cls, d):
2464        kwargs = {}
2465        kwargs['feed_name'] = d['feed_name']
2466        kwargs['message'] = d.get('message')
2467
2468        kwargs['subscription_instances'] = []
2469        for instance in d['subscription_instances']:
2470            kwargs['subscription_instances'].append(SubscriptionInstance.from_dict(instance))
2471
2472        msg = super(ManageFeedSubscriptionResponse, cls).from_dict(d, **kwargs)
2473        return msg
2474
2475
2476class SubscriptionInstance(TAXIIBase10):
2477
2478    """
2479    The Subscription Instance component of the Manage Feed Subscription
2480    Response message.
2481
2482    Args:
2483        subscription_id (str): the id of the subscription. **Required**
2484        delivery_parameters (libtaxii.messages_10.DeliveryParameters): the parameters
2485            for this subscription. **Required** if responding to message
2486            with ``action==``:py:data:`ACT_STATUS`, otherwise **Prohibited**
2487        poll_instances (list of PollInstance): Each Poll
2488            Instance represents an instance of a Poll Service that can be
2489            contacted to retrieve content associated with the new
2490            Subscription. **Optional**
2491    """
2492
2493    def __init__(self, subscription_id, delivery_parameters=None,
2494                 poll_instances=None):
2495        self.subscription_id = subscription_id
2496        self.delivery_parameters = delivery_parameters
2497        self.poll_instances = poll_instances or []
2498
2499    @property
2500    def sort_key(self):
2501        return self.subscription_id
2502
2503    @property
2504    def subscription_id(self):
2505        return self._subscription_id
2506
2507    @subscription_id.setter
2508    def subscription_id(self, value):
2509        do_check(value, 'subscription_id', regex_tuple=uri_regex)
2510        self._subscription_id = value
2511
2512    @property
2513    def delivery_parameters(self):
2514        return self._delivery_parameters
2515
2516    @delivery_parameters.setter
2517    def delivery_parameters(self, value):
2518        do_check(value, 'delivery_parameters', type=DeliveryParameters, can_be_none=True)
2519        self._delivery_parameters = value
2520
2521    @property
2522    def poll_instances(self):
2523        return self._poll_instances
2524
2525    @poll_instances.setter
2526    def poll_instances(self, value):
2527        do_check(value, 'poll_instances', type=PollInstance, can_be_none=False)
2528        self._poll_instances = value
2529
2530    def to_etree(self):
2531        xml = etree.Element('{%s}Subscription' % ns_map['taxii'])
2532        xml.attrib['subscription_id'] = self.subscription_id
2533
2534        if self.delivery_parameters:
2535            xml.append(self.delivery_parameters.to_etree())
2536
2537        for poll_instance in self.poll_instances:
2538            xml.append(poll_instance.to_etree())
2539
2540        return xml
2541
2542    def to_dict(self):
2543        d = {}
2544        d['subscription_id'] = self.subscription_id
2545
2546        if self.delivery_parameters:
2547            d['delivery_parameters'] = self.delivery_parameters.to_dict()
2548        else:
2549            d['delivery_parameters'] = None
2550
2551        d['poll_instances'] = []
2552        for poll_instance in self.poll_instances:
2553            d['poll_instances'].append(poll_instance.to_dict())
2554
2555        return d
2556
2557    def to_text(self, line_indent=''):
2558        s = line_indent + "=== Subscription Instance ===\n"
2559        s += line_indent + "  Subscription ID: %s\n" % self.subscription_id
2560        if self.delivery_parameters:
2561            s += self.delivery_parameters.to_text(line_indent + STD_INDENT)
2562        for pi in self.poll_instances:
2563            s += pi.to_text(line_indent + STD_INDENT)
2564        return s
2565
2566    @staticmethod
2567    def from_etree(etree_xml):
2568        subscription_id = etree_xml.attrib['subscription_id']
2569
2570        _delivery_parameters = get_optional(etree_xml, './taxii:Push_Parameters', ns_map)
2571        if _delivery_parameters is not None:
2572            delivery_parameters = DeliveryParameters.from_etree(_delivery_parameters)
2573        else:
2574            delivery_parameters = None
2575
2576        poll_instances = []
2577        for poll_instance in etree_xml.xpath('./taxii:Poll_Instance', namespaces=ns_map):
2578            poll_instances.append(PollInstance.from_etree(poll_instance))
2579
2580        return SubscriptionInstance(subscription_id, delivery_parameters, poll_instances)
2581
2582    @staticmethod
2583    def from_dict(d):
2584        subscription_id = d['subscription_id']
2585
2586        if d.get('delivery_parameters'):
2587            delivery_parameters = DeliveryParameters.from_dict(d['delivery_parameters'])
2588        else:
2589            delivery_parameters = None
2590
2591        poll_instances = []
2592        for poll_instance in d['poll_instances']:
2593            poll_instances.append(PollInstance.from_dict(poll_instance))
2594
2595        return SubscriptionInstance(subscription_id, delivery_parameters, poll_instances)
2596
2597
2598class PollInstance(TAXIIBase10):
2599
2600    """
2601    The Poll Instance component of the Manage Feed Subscription
2602    Response message.
2603
2604    Args:
2605        poll_protocol (str): The protocol binding supported by this
2606            instance of a Polling Service. **Required**
2607        poll_address (str): the address of the TAXII Daemon hosting
2608            this Poll Service. **Required**
2609        poll_message_bindings (list of str): one or more message bindings
2610            that can be used when interacting with this Poll Service
2611            instance. **Required**
2612    """
2613
2614    def __init__(self, poll_protocol, poll_address, poll_message_bindings=None):
2615        self.poll_protocol = poll_protocol
2616        self.poll_address = poll_address
2617        self._poll_message_bindings = poll_message_bindings or []
2618
2619    @property
2620    def sort_key(self):
2621        return self.poll_address
2622
2623    @property
2624    def poll_protocol(self):
2625        return self._poll_protocol
2626
2627    @poll_protocol.setter
2628    def poll_protocol(self, value):
2629        do_check(value, 'poll_protocol', regex_tuple=uri_regex)
2630        self._poll_protocol = value
2631
2632    @property
2633    def poll_message_bindings(self):
2634        return self._poll_message_bindings
2635
2636    @poll_message_bindings.setter
2637    def poll_message_bindings(self, value):
2638        do_check(value, 'poll_message_bindings', regex_tuple=uri_regex)
2639        self._poll_message_bindings = value
2640
2641    def to_etree(self):
2642        xml = etree.Element('{%s}Poll_Instance' % ns_map['taxii'])
2643
2644        pb = etree.SubElement(xml, '{%s}Protocol_Binding' % ns_map['taxii'])
2645        pb.text = self.poll_protocol
2646
2647        a = etree.SubElement(xml, '{%s}Address' % ns_map['taxii'])
2648        a.text = self.poll_address
2649
2650        for binding in self.poll_message_bindings:
2651            b = etree.SubElement(xml, '{%s}Message_Binding' % ns_map['taxii'])
2652            b.text = binding
2653
2654        return xml
2655
2656    def to_dict(self):
2657        d = {}
2658
2659        d['poll_protocol'] = self.poll_protocol
2660        d['poll_address'] = self.poll_address
2661        d['poll_message_bindings'] = []
2662        for binding in self.poll_message_bindings:
2663            d['poll_message_bindings'].append(binding)
2664
2665        return d
2666
2667    def to_text(self, line_prepend=''):
2668        s = line_prepend + "=== Poll Instance ===\n"
2669        s += line_prepend + "  Protocol Binding: %s\n" % self.poll_protocol
2670        s += line_prepend + "  Address: %s\n" % self.poll_address
2671        for mb in self.poll_message_bindings:
2672            s += line_prepend + "  Message Binding: %s\n" % mb
2673        return s
2674
2675    @staticmethod
2676    def from_etree(etree_xml):
2677        poll_protocol = get_required(etree_xml, './taxii:Protocol_Binding', ns_map).text
2678        address = get_required(etree_xml, './taxii:Address', ns_map).text
2679
2680        poll_message_bindings = []
2681        for b in etree_xml.xpath('./taxii:Message_Binding', namespaces=ns_map):
2682            poll_message_bindings.append(b.text)
2683
2684        return PollInstance(poll_protocol, address, poll_message_bindings)
2685
2686    @staticmethod
2687    def from_dict(d):
2688        return PollInstance(**d)
2689
2690########################################################
2691# EVERYTHING BELOW HERE IS FOR BACKWARDS COMPATIBILITY #
2692########################################################
2693
2694# Add top-level classes as nested classes for backwards compatibility
2695DiscoveryResponse.ServiceInstance = ServiceInstance
2696FeedInformationResponse.FeedInformation = FeedInformation
2697FeedInformation.PushMethod = PushMethod
2698FeedInformation.PollingServiceInstance = PollingServiceInstance
2699FeedInformation.SubscriptionMethod = SubscriptionMethod
2700ManageFeedSubscriptionResponse.PollInstance = PollInstance
2701ManageFeedSubscriptionResponse.SubscriptionInstance = SubscriptionInstance
2702InboxMessage.SubscriptionInformation = SubscriptionInformation
2703
2704# Constants not imported in `from constants import *`
2705MSG_TYPES = MSG_TYPES_10
2706ST_TYPES = ST_TYPES_10
2707ACT_TYPES = ACT_TYPES_10
2708SVC_TYPES = SVC_TYPES_10
2709
2710from .common import (generate_message_id)
2711