1# Copyright (c) 2017, The MITRE Corporation
2# For license information, see the LICENSE.txt file
3
4"""
5Creating, handling, and parsing TAXII 1.1 messages.
6"""
7
8
9import collections
10import six
11try:
12    import simplejson as json
13except ImportError:
14    import json
15import os
16import warnings
17
18from lxml import etree
19
20from .common import (parse, parse_datetime_string, append_any_content_etree, TAXIIBase,
21                     get_required, get_optional, get_optional_text, parse_xml_string,
22                     stringify_content)
23from .validation import do_check, uri_regex, check_timestamp_label
24from .constants import *
25
26
27def validate_xml(xml_string):
28    """
29    Note that this function has been deprecated. Please see
30    libtaxii.validators.SchemaValidator.
31
32    Validate XML with the TAXII XML Schema 1.1.
33
34    Args:
35        xml_string (str): The XML to validate.
36
37    Example:
38        .. code-block:: python
39
40            is_valid = tm11.validate_xml(message.to_xml())
41    """
42
43    warnings.warn('Call to deprecated function: libtaxii.messages_11.validate_xml()',
44                  category=DeprecationWarning)
45
46    etree_xml = parse_xml_string(xml_string)
47    package_dir, package_filename = os.path.split(__file__)
48    schema_file = os.path.join(package_dir, "xsd", "TAXII_XMLMessageBinding_Schema_11.xsd")
49    taxii_schema_doc = parse(schema_file, allow_file=True)
50    xml_schema = etree.XMLSchema(taxii_schema_doc)
51    valid = xml_schema.validate(etree_xml)
52    # TODO: Additionally, validate the Query stuff
53    if not valid:
54        return xml_schema.error_log.last_error
55    return valid
56
57
58def get_message_from_xml(xml_string, encoding='utf_8'):
59    """Create a TAXIIMessage object from an XML string.
60
61    This function automatically detects which type of Message should be created
62    based on the XML.
63
64    Args:
65        xml_string (str): The XML to parse into a TAXII message.
66        encoding (str): The encoding of the string; defaults to UTF-8
67
68    Example:
69        .. code-block:: python
70
71            message_xml = message.to_xml()
72            new_message = tm11.get_message_from_xml(message_xml)
73    """
74    if isinstance(xml_string, six.binary_type):
75        xml_string = xml_string.decode(encoding, 'replace')
76    etree_xml = parse_xml_string(xml_string)
77    qn = etree.QName(etree_xml)
78    if qn.namespace != ns_map['taxii_11']:
79        raise ValueError('Unsupported namespace: %s' % qn.namespace)
80
81    message_type = qn.localname
82
83    if message_type == MSG_DISCOVERY_REQUEST:
84        return DiscoveryRequest.from_etree(etree_xml)
85    if message_type == MSG_DISCOVERY_RESPONSE:
86        return DiscoveryResponse.from_etree(etree_xml)
87    if message_type == MSG_COLLECTION_INFORMATION_REQUEST:
88        return CollectionInformationRequest.from_etree(etree_xml)
89    if message_type == MSG_COLLECTION_INFORMATION_RESPONSE:
90        return CollectionInformationResponse.from_etree(etree_xml)
91    if message_type == MSG_POLL_REQUEST:
92        return PollRequest.from_etree(etree_xml)
93    if message_type == MSG_POLL_RESPONSE:
94        return PollResponse.from_etree(etree_xml)
95    if message_type == MSG_STATUS_MESSAGE:
96        return StatusMessage.from_etree(etree_xml)
97    if message_type == MSG_INBOX_MESSAGE:
98        return InboxMessage.from_etree(etree_xml)
99    if message_type == MSG_MANAGE_COLLECTION_SUBSCRIPTION_REQUEST:
100        return ManageCollectionSubscriptionRequest.from_etree(etree_xml)
101    if message_type == MSG_MANAGE_COLLECTION_SUBSCRIPTION_RESPONSE:
102        return ManageCollectionSubscriptionResponse.from_etree(etree_xml)
103    if message_type == MSG_POLL_FULFILLMENT_REQUEST:
104        return PollFulfillmentRequest.from_etree(etree_xml)
105
106    raise ValueError('Unknown message_type: %s' % message_type)
107
108
109def get_message_from_dict(d):
110    """Create a TAXIIMessage object from a dictonary.
111
112    This function automatically detects which type of Message should be created
113    based on the 'message_type' key in the dictionary.
114
115    Args:
116        d (dict): The dictionary to build the TAXII message from.
117
118    Example:
119        .. code-block:: python
120
121            message_dict = message.to_dict()
122            new_message = tm11.get_message_from_dict(message_dict)
123    """
124    if 'message_type' not in d:
125        raise ValueError('message_type is a required field!')
126
127    message_type = d['message_type']
128    if message_type == MSG_DISCOVERY_REQUEST:
129        return DiscoveryRequest.from_dict(d)
130    if message_type == MSG_DISCOVERY_RESPONSE:
131        return DiscoveryResponse.from_dict(d)
132    if message_type == MSG_COLLECTION_INFORMATION_REQUEST:
133        return CollectionInformationRequest.from_dict(d)
134    if message_type == MSG_COLLECTION_INFORMATION_RESPONSE:
135        return CollectionInformationResponse.from_dict(d)
136    if message_type == MSG_POLL_REQUEST:
137        return PollRequest.from_dict(d)
138    if message_type == MSG_POLL_RESPONSE:
139        return PollResponse.from_dict(d)
140    if message_type == MSG_STATUS_MESSAGE:
141        return StatusMessage.from_dict(d)
142    if message_type == MSG_INBOX_MESSAGE:
143        return InboxMessage.from_dict(d)
144    if message_type == MSG_MANAGE_COLLECTION_SUBSCRIPTION_REQUEST:
145        return ManageCollectionSubscriptionRequest.from_dict(d)
146    if message_type == MSG_MANAGE_COLLECTION_SUBSCRIPTION_RESPONSE:
147        return ManageCollectionSubscriptionResponse.from_dict(d)
148    if message_type == MSG_POLL_FULFILLMENT_REQUEST:
149        return PollFulfillmentRequest.from_dict(d)
150
151    raise ValueError('Unknown message_type: %s' % message_type)
152
153
154def get_message_from_json(json_string, encoding='utf_8'):
155    """Create a TAXIIMessage object from a JSON string.
156
157    This function automatically detects which type of Message should be created
158    based on the JSON.
159
160    Args:
161        json_string (str): The JSON to parse into a TAXII message.
162    """
163    decoded_string = json_string.decode(encoding, 'replace')
164    return get_message_from_dict(json.loads(decoded_string))
165
166
167def _sanitize_content_binding(binding):
168    """
169    Takes in one of:
170    1. ContentBinding object
171    2. string
172    3. dict
173    and returns a ContentBinding object.
174
175    This supports function calls where a string or ContentBinding can be
176    used to specify a content binding.
177    """
178    if isinstance(binding, ContentBinding):  # It's already good to go
179        return binding
180    elif isinstance(binding, six.string_types):  # Convert it to a ContentBinding
181        return ContentBinding.from_string(binding)
182    elif isinstance(binding, dict):  # Convert it to a ContentBinding
183        return ContentBinding.from_dict(binding)
184    else:  # Don't know what to do with it.
185        raise ValueError('Type cannot be converted to ContentBinding: %s' % binding.__class__.__name__)
186
187
188def _sanitize_content_bindings(binding_list):
189    bindings = []
190    for item in binding_list:
191        bindings.append(_sanitize_content_binding(item))
192
193    return bindings
194
195
196class UnsupportedQueryException(Exception):
197
198    def __init__(self, value):
199        self.value = value
200
201    def __str__(self):
202        return repr(self.value)
203
204
205# Start with the 'default' deserializer
206query_deserializers = {}
207
208
209def register_query_format(format_id, query, query_info, schema=None):
210    """
211    This function registers a query format with libtaxii.messages_11.
212    Arguments:
213        format_id (string) - The format ID of the query
214        query (messages_11.Query subclass) - the Query object associated with the format_id
215        query_info (messages_11.SupportedQuery subclass) - the SupportedQuery object associated with the format_id
216        schema (xml schema) - The XML schema for validating the query
217    """
218    query_deserializers[format_id] = {'query': query, 'query_info': query_info, 'schema': schema}
219
220
221def get_deserializer(format_id, type):
222    do_check(type, 'type', value_tuple=('query', 'query_info'))
223
224    if format_id not in query_deserializers:
225        raise UnsupportedQueryException('A deserializer for the query format \'%s\' is not registered.' % format_id)
226
227    return query_deserializers[format_id][type]
228
229# TODO: Consider using this
230# def _create_element(name, namespace=ns_map['taxii_11'], value=None, attrs=None, parent=None):
231    # """
232    # Helper method for appending a new element to an existing element.
233
234    # Assumes the namespace is TAXII 1.1
235
236    # Arguments:
237    #     name (string) - The name of the element
238    #     namespace (string) - The namespace of the element
239    #     value (string) - The text value of the element
240    #     attrs (dict) - A dictionary of attributes
241    #     parent (Element) - The parent element
242    # """
243    # if value is None and attrs is None:
244    #     return
245
246    # if parent is None:
247    #     elt = etree.Element('{%s}%s' % (namespace, name), nsmap=ns_map)
248    # else:
249    #     elt = etree.SubElement(parent, '{%s}%s' % (namespace, name), nsmap=ns_map)
250
251    # if value is not None:
252    #     elt.text = value
253
254    # if attrs is not None:
255    #     for k, v in attrs.items():
256    #         elt.attrib[k] = v
257
258    # return elt
259
260
261class TAXIIBase11(TAXIIBase):
262    version = VID_TAXII_XML_11
263
264
265class SupportedQuery(TAXIIBase11):
266
267    """
268    This class contains an instance of a supported query. It
269    is expected that, generally, messages_11.SupportedQuery
270    subclasses will be used in place of this class
271    to represent a query
272    """
273
274    def __init__(self, format_id):
275        """
276        Arguments:
277            format_id (string) - The format_id of this supported query
278        """
279        self.format_id = format_id
280
281    @property
282    def sort_key(self):
283        return self.format_id
284
285    @property
286    def format_id(self):
287        return self._format_id
288
289    @format_id.setter
290    def format_id(self, value):
291        do_check(value, 'format_id', regex_tuple=uri_regex)
292        self._format_id = value
293
294    def to_etree(self):
295        q = etree.Element('{%s}Supported_Query' % ns_map['taxii_11'], nsmap=ns_map)
296        q.attrib['format_id'] = self.format_id
297        return q
298
299    def to_dict(self):
300        return {'format_id': self.format_id}
301
302    def to_text(self, line_prepend=''):
303        s = line_prepend + "=== Supported Query Information ===\n"
304        s += line_prepend + "  Query Format: %s\n" % self.format_id
305        return s
306
307    @staticmethod
308    def from_etree(etree_xml):
309        format_id = get_required(etree_xml, './@format_id', ns_map)
310        return SupportedQuery(format_id)
311
312    @staticmethod
313    def from_dict(d):
314        return SupportedQuery(**d)
315
316
317class Query(TAXIIBase11):
318
319    """This class contains an instance of a query.
320
321    It is expected that, generally, messages_11.Query subclasses will be used
322    in place of this class to represent a query.
323    """
324
325    def __init__(self, format_id):
326        """
327        Arguments:
328            format_id (string) - The format_id of this query
329        """
330        self.format_id = format_id
331
332    @property
333    def format_id(self):
334        return self._format_id
335
336    @format_id.setter
337    def format_id(self, value):
338        do_check(value, 'format_id', regex_tuple=uri_regex)
339        self._format_id = value
340
341    def to_etree(self):
342        q = etree.Element('{%s}Query' % ns_map['taxii_11'], nsmap=ns_map)
343        q.attrib['format_id'] = self.format_id
344        return q
345
346    def to_dict(self):
347        return {'format_id': self.format_id}
348
349    def to_text(self, line_prepend=''):
350        s = line_prepend + "=== Query ===\n"
351        s += line_prepend + "  Query Format: %s\n" % self.format_id
352
353        return s
354
355    @classmethod
356    def from_etree(cls, etree_xml, kwargs):
357        format_id = get_required(etree_xml, './@format_id', ns_map)
358        return cls(format_id, **kwargs)
359
360    @classmethod
361    def from_dict(cls, d, kwargs):
362        return cls(d, **kwargs)
363
364
365# A value can be one of:
366# - a dictionary, where each key is a content_binding_id and each value is a list of subtypes
367#   (This is the default representation)
368# - a "content_binding_id[>subtype]" structure
369# - a list of "content_binding_id[>subtype]" structures
370
371
372class ContentBinding(TAXIIBase11):
373
374    """TAXII Content Binding component
375
376    Args:
377        binding_id (str): The content binding ID. **Required**
378        subtype_ids (list of str): the subtype IDs. **Required**
379    """
380
381    def __init__(self, binding_id, subtype_ids=None):
382        self.binding_id = binding_id
383        self.subtype_ids = subtype_ids or []
384
385    def __str__(self):
386        s = self.binding_id
387        if len(self.subtype_ids) > 0:
388            s += '>' + ','.join(self.subtype_ids)
389
390        return s
391
392    @staticmethod
393    def from_string(s):
394        if '>' not in s:
395            return ContentBinding(s)
396
397        parts = s.split('>')
398        binding_id = parts[0]
399        subtype_ids = parts[1].split(',')
400        return ContentBinding(binding_id, subtype_ids)
401
402    @property
403    def sort_key(self):
404        return str(self)
405
406    @property
407    def binding_id(self):
408        return self._binding_id
409
410    @binding_id.setter
411    def binding_id(self, value):
412        do_check(value, 'binding_id', regex_tuple=uri_regex)
413        self._binding_id = value
414
415    @property
416    def subtype_ids(self):
417        return self._subtype_ids
418
419    @subtype_ids.setter
420    def subtype_ids(self, value):
421        do_check(value, 'subtype_ids', regex_tuple=uri_regex)
422        self._subtype_ids = value
423
424    def to_etree(self):
425        cb = etree.Element('{%s}Content_Binding' % ns_map['taxii_11'], nsmap=ns_map)
426        cb.attrib['binding_id'] = self.binding_id
427        for subtype_id in self.subtype_ids:
428            s = etree.SubElement(cb, '{%s}Subtype' % ns_map['taxii_11'], nsmap=ns_map)
429            s.attrib['subtype_id'] = subtype_id
430        return cb
431
432    def to_dict(self):
433        return {'binding_id': self.binding_id, 'subtype_ids': self.subtype_ids}
434
435    def to_text(self, line_prepend=''):
436        return line_prepend + str(self)
437
438    def __hash__(self):
439        return hash(str(self.to_dict()))
440
441    @classmethod
442    def from_etree(self, etree_xml):
443        binding_id = etree_xml.attrib['binding_id']
444        subtype_ids = []
445        subtype_elts = etree_xml.xpath('./taxii_11:Subtype', namespaces=ns_map)
446        for elt in subtype_elts:
447            subtype_ids.append(elt.attrib['subtype_id'])
448        return ContentBinding(binding_id, subtype_ids)
449
450    @classmethod
451    def from_dict(self, d):
452        return ContentBinding(**d)
453
454
455class RecordCount(TAXIIBase11):
456
457    """
458    Information summarizing the number of records.
459
460    Args:
461        record_count (int): The number of records
462        partial_count (bool): Whether the number of records is a partial count
463    """
464
465    def __init__(self, record_count, partial_count=False):
466        self.record_count = record_count
467        self.partial_count = partial_count
468
469    @property
470    def record_count(self):
471        return self._record_count
472
473    @record_count.setter
474    def record_count(self, value):
475        do_check(value, 'record_count', type=int)
476        self._record_count = value
477
478    @property
479    def partial_count(self):
480        return self._partial_count
481
482    @partial_count.setter
483    def partial_count(self, value):
484        do_check(value, 'partial_count', value_tuple=(True, False), can_be_none=True)
485        self._partial_count = value
486
487    def to_etree(self):
488        xml = etree.Element('{%s}Record_Count' % ns_map['taxii_11'], nsmap=ns_map)
489        xml.text = str(self.record_count)
490
491        if self.partial_count is not None:
492            xml.attrib['partial_count'] = str(self.partial_count).lower()
493
494        return xml
495
496    def to_dict(self):
497        d = {}
498        d['record_count'] = self.record_count
499        if self.partial_count is not None:
500            d['partial_count'] = self.partial_count
501
502        return d
503
504    def to_text(self, line_prepend=''):
505        s = line_prepend + "=== Record Count ===\n"
506        s += line_prepend + "  Record Count: %s\n" % self.record_count
507        if self.partial_count:
508            s += line_prepend + "  Partial Count: %s\n" % self.partial_count
509
510        return s
511
512    @staticmethod
513    def from_etree(etree_xml):
514        record_count = int(etree_xml.text)
515        partial_count = etree_xml.attrib.get('partial_count', 'false') == 'true'
516
517        return RecordCount(record_count, partial_count)
518
519    @staticmethod
520    def from_dict(d):
521        return RecordCount(**d)
522
523
524class _GenericParameters(TAXIIBase11):
525    name = 'Generic_Parameters'
526
527    def __init__(self, response_type=RT_FULL, content_bindings=None, query=None):
528        self.response_type = response_type
529        self.content_bindings = content_bindings or []
530        self.query = query
531
532    @property
533    def response_type(self):
534        return self._response_type
535
536    @response_type.setter
537    def response_type(self, value):
538        do_check(value, 'response_type', value_tuple=(RT_FULL, RT_COUNT_ONLY), can_be_none=True)
539        self._response_type = value
540
541    @property
542    def content_bindings(self):
543        return self._content_bindings
544
545    @content_bindings.setter
546    def content_bindings(self, value):
547        value = _sanitize_content_bindings(value)
548        do_check(value, 'content_bindings', type=ContentBinding)
549        self._content_bindings = value
550
551    @property
552    def query(self):
553        return self._query
554
555    @query.setter
556    def query(self, value):
557        # TODO: Can i do more validation?
558        do_check(value, 'query', type=Query, can_be_none=True)
559        self._query = value
560
561    def to_etree(self):
562        xml = etree.Element('{%s}%s' % (ns_map['taxii_11'], self.name), nsmap=ns_map)
563        if self.response_type is not None:
564            rt = etree.SubElement(xml, '{%s}Response_Type' % ns_map['taxii_11'], nsmap=ns_map)
565            rt.text = self.response_type
566
567        for binding in self.content_bindings:
568            xml.append(binding.to_etree())
569
570        if self.query is not None:
571            xml.append(self.query.to_etree())
572
573        return xml
574
575    def to_dict(self):
576        d = {}
577        if self.response_type is not None:
578            d['response_type'] = self.response_type
579
580        d['content_bindings'] = []
581        for binding in self.content_bindings:
582            d['content_bindings'].append(binding.to_dict())
583
584        d['query'] = None
585        if self.query is not None:
586            d['query'] = self.query.to_dict()
587
588        return d
589
590    def to_text(self, line_prepend=''):
591        s = line_prepend + "=== %s ===\n" % self.name
592        for binding in self.content_bindings:
593            s += "  Content Binding: %s\n" % str(binding)
594
595        if self.query:
596            s += self.query.to_text(line_prepend + STD_INDENT)
597
598        if self.response_type:
599            s += line_prepend + "  Response type: %s\n" % str(self.response_type)
600
601        return s
602
603    @classmethod
604    def from_etree(cls, etree_xml, **kwargs):
605
606        response_type = get_optional_text(etree_xml, './taxii_11:Response_Type', ns_map)
607        if response_type is None:
608            response_type = RT_FULL
609
610        content_bindings = []
611        for binding in etree_xml.xpath('./taxii_11:Content_Binding', namespaces=ns_map):
612            content_bindings.append(ContentBinding.from_etree(binding))
613
614        query = None
615        query_el = get_optional(etree_xml, './taxii_11:Query', ns_map)
616        if query_el is not None:
617            format_id = query_el.attrib['format_id']
618            query = get_deserializer(format_id, 'query').from_etree(query_el)
619
620        return cls(response_type, content_bindings, query, **kwargs)
621
622    @classmethod
623    def from_dict(cls, d, **kwargs):
624        response_type = d.get('response_type', RT_FULL)
625        content_bindings = []
626        for binding in d['content_bindings']:
627            content_bindings.append(ContentBinding.from_dict(binding))
628
629        query = None
630        if 'query' in d and d['query'] is not None:
631            format_id = d['query']['format_id']
632            query = get_deserializer(format_id, 'query').from_dict(d['query'])
633
634        return cls(response_type, content_bindings, query, **kwargs)
635
636
637class SubscriptionParameters(_GenericParameters):
638
639    """
640    TAXII Subscription Parameters.
641
642    Args:
643        response_type (str): The requested response type. Must be either
644            :py:data:`RT_FULL` or :py:data:`RT_COUNT_ONLY`. **Optional**,
645            defaults to :py:data:`RT_FULL`
646        content_bindings (list of ContentBinding objects): A list of Content
647            Bindings acceptable in response. **Optional**
648        query (Query): The query for this poll parameters. **Optional**
649    """
650    name = 'Subscription_Parameters'
651
652
653class ContentBlock(TAXIIBase11):
654
655    """A TAXII Content Block.
656
657    Args:
658        content_binding (ContentBinding): a Content Binding ID or nesting expression
659            indicating the type of content contained in the Content field of this
660            Content Block. **Required**
661        content (string or etree): a piece of content of the type specified
662            by the Content Binding. **Required**
663        timestamp_label (datetime): the Timestamp Label associated with this
664            Content Block. **Optional**
665        padding (string): an arbitrary amount of padding for this Content
666            Block. **Optional**
667        message (string): a message associated with this ContentBlock. **Optional**
668    """
669    NAME = 'Content_Block'
670
671    def __init__(self, content_binding, content, timestamp_label=None,
672                 padding=None, message=None):
673        self.content_binding = content_binding
674        self.content = content
675        self.timestamp_label = timestamp_label
676        self.message = message
677        self.padding = padding
678
679    @property
680    def sort_key(self):
681        return self.content[:25]
682
683    @property
684    def content_binding(self):
685        return self._content_binding
686
687    @content_binding.setter
688    def content_binding(self, value):
689        value = _sanitize_content_binding(value)
690        do_check(value, 'content_binding', type=ContentBinding)
691        self._content_binding = value
692
693    @property
694    def content(self):
695        if self.content_is_xml:
696            return etree.tostring(self._content, encoding='utf-8')
697        else:
698            return self._content
699
700    @content.setter
701    def content(self, value):
702        do_check(value, 'content')  # Just check for not None
703        self._content, self.content_is_xml = stringify_content(value)
704
705    @property
706    def content_is_xml(self):
707        return self._content_is_xml
708
709    @content_is_xml.setter
710    def content_is_xml(self, value):
711        do_check(value, 'content_is_xml', value_tuple=(True, False))
712        self._content_is_xml = value
713
714    @property
715    def timestamp_label(self):
716        return self._timestamp_label
717
718    @timestamp_label.setter
719    def timestamp_label(self, value):
720        value = check_timestamp_label(value, 'timestamp_label', can_be_none=True)
721        self._timestamp_label = value
722
723    @property
724    def message(self):
725        return self._message
726
727    @message.setter
728    def message(self, value):
729        do_check(value, 'message', type=six.string_types, can_be_none=True)
730        self._message = value
731
732    def to_etree(self):
733        block = etree.Element('{%s}Content_Block' % ns_map['taxii_11'], nsmap=ns_map)
734        block.append(self.content_binding.to_etree())
735        c = etree.SubElement(block, '{%s}Content' % ns_map['taxii_11'])
736
737        if self.content_is_xml:
738            c.append(self._content)
739        else:
740            c.text = self._content
741
742        if self.timestamp_label is not None:
743            tl = etree.SubElement(block, '{%s}Timestamp_Label' % ns_map['taxii_11'])
744            tl.text = self.timestamp_label.isoformat()
745
746        if self.message is not None:
747            m = etree.SubElement(block, '{%s}Message' % ns_map['taxii_11'])
748            m.text = self.message
749
750        if self.padding is not None:
751            p = etree.SubElement(block, '{%s}Padding' % ns_map['taxii_11'])
752            p.text = self.padding
753
754        return block
755
756    def to_dict(self):
757        block = {}
758        block['content_binding'] = self.content_binding.to_dict()
759
760        if self.content_is_xml:
761            block['content'] = etree.tostring(self._content, encoding='utf-8')
762        else:
763            block['content'] = self._content
764        block['content_is_xml'] = self.content_is_xml
765
766        if self.timestamp_label is not None:
767            block['timestamp_label'] = self.timestamp_label.isoformat()
768
769        if self.message is not None:
770            block['message'] = self.message
771
772        if self.padding is not None:
773            block['padding'] = self.padding
774
775        return block
776
777    def to_text(self, line_prepend=''):
778        s = line_prepend + "=== Content Block ===\n"
779        s += line_prepend + "  Content Binding: %s\n" % str(self.content_binding)
780        s += line_prepend + "  Content length: %s\n" % len(self.content)
781        s += line_prepend + "  (Content not printed for brevity)\n"
782        if self.timestamp_label:
783            s += line_prepend + "  Timestamp Label: %s\n" % self.timestamp_label
784        s += line_prepend + "  Message: %s\n" % self.message
785        s += line_prepend + "  Padding: %s\n" % self.padding
786        return s
787
788    @staticmethod
789    def from_etree(etree_xml):
790        kwargs = {}
791
792        kwargs['content_binding'] = ContentBinding.from_etree(
793                get_required(etree_xml, './taxii_11:Content_Binding', ns_map))
794
795        kwargs['padding'] = get_optional_text(etree_xml, './taxii_11:Padding', ns_map)
796
797        ts_text = get_optional_text(etree_xml, './taxii_11:Timestamp_Label', ns_map)
798        if ts_text:
799            kwargs['timestamp_label'] = parse_datetime_string(ts_text)
800
801        kwargs['message'] = get_optional_text(etree_xml, './taxii_11:Message', ns_map)
802
803        content = get_required(etree_xml, './taxii_11:Content', ns_map)
804        if len(content) == 0:  # This has string content
805            kwargs['content'] = content.text
806        else:  # This has XML content
807            kwargs['content'] = content[0]
808
809        return ContentBlock(**kwargs)
810
811    @staticmethod
812    def from_dict(d):
813        kwargs = {}
814        kwargs['content_binding'] = ContentBinding.from_dict(d['content_binding'])
815        kwargs['padding'] = d.get('padding')
816        if 'timestamp_label' in d:
817            kwargs['timestamp_label'] = parse_datetime_string(d['timestamp_label'])
818        kwargs['message'] = d.get('message')
819        is_xml = d.get('content_is_xml', False)
820        if is_xml:
821            kwargs['content'] = parse(d['content'], allow_file=False)
822        else:
823            kwargs['content'] = d['content']
824
825        cb = ContentBlock(**kwargs)
826        return cb
827
828    @classmethod
829    def from_json(cls, json_string):
830        return cls.from_dict(json.loads(json_string))
831
832
833class PushParameters(TAXIIBase11):
834
835    """Set up Push Parameters.
836
837    Args:
838        inbox_protocol (str): identifies the protocol to be used when pushing
839            TAXII Data Collection content to a Consumer's TAXII Inbox Service
840            implementation. **Required**
841        inbox_address (str): identifies the address of the TAXII Daemon hosting
842            the Inbox Service to which the Consumer requests content for this
843            TAXII Data Collection to be delivered. **Required**
844        delivery_message_binding (str): identifies the message binding to be
845             used to send pushed content for this subscription. **Required**
846    """
847
848    name = 'Push_Parameters'
849
850    def __init__(self, inbox_protocol, inbox_address, delivery_message_binding):
851        self.inbox_protocol = inbox_protocol
852        self.inbox_address = inbox_address
853        self.delivery_message_binding = delivery_message_binding
854
855    @property
856    def sort_key(self):
857        return self.inbox_address
858
859    @property
860    def inbox_protocol(self):
861        return self._inbox_protocol
862
863    @inbox_protocol.setter
864    def inbox_protocol(self, value):
865        do_check(value, 'inbox_protocol', regex_tuple=uri_regex)
866        self._inbox_protocol = value
867
868    @property
869    def inbox_address(self):
870        return self._inbox_address
871
872    @inbox_address.setter
873    def inbox_address(self, value):
874        self._inbox_address = value
875
876    @property
877    def delivery_message_binding(self):
878        return self._delivery_message_binding
879
880    @delivery_message_binding.setter
881    def delivery_message_binding(self, value):
882        do_check(value, 'delivery_message_binding', regex_tuple=uri_regex)
883        self._delivery_message_binding = value
884
885    def to_etree(self):
886        xml = etree.Element('{%s}%s' % (ns_map['taxii_11'], self.name))
887
888        pb = etree.SubElement(xml, '{%s}Protocol_Binding' % ns_map['taxii_11'])
889        pb.text = self.inbox_protocol
890
891        a = etree.SubElement(xml, '{%s}Address' % ns_map['taxii_11'])
892        a.text = self.inbox_address
893
894        mb = etree.SubElement(xml, '{%s}Message_Binding' % ns_map['taxii_11'])
895        mb.text = self.delivery_message_binding
896
897        return xml
898
899    def to_dict(self):
900        d = {}
901
902        if self.inbox_protocol is not None:
903            d['inbox_protocol'] = self.inbox_protocol
904
905        if self.inbox_address is not None:
906            d['inbox_address'] = self.inbox_address
907
908        if self.delivery_message_binding is not None:
909            d['delivery_message_binding'] = self.delivery_message_binding
910
911        return d
912
913    def to_text(self, line_prepend=''):
914        s = line_prepend + "=== Push Parameters ===\n"
915        s += line_prepend + "  Protocol Binding: %s\n" % self.inbox_protocol
916        s += line_prepend + "  Inbox Address: %s\n" % self.inbox_address
917        s += line_prepend + "  Message Binding: %s\n" % self.delivery_message_binding
918        return s
919
920    @classmethod
921    def from_etree(cls, etree_xml):
922        inbox_protocol = get_optional_text(etree_xml, './taxii_11:Protocol_Binding', ns_map)
923        inbox_address = get_optional_text(etree_xml, './taxii_11:Address', ns_map)
924        delivery_message_binding = get_optional_text(etree_xml, './taxii_11:Message_Binding', ns_map)
925
926        return cls(inbox_protocol, inbox_address, delivery_message_binding)
927
928    @classmethod
929    def from_dict(cls, d):
930        return cls(**d)
931
932
933# TODO: Check docstring
934class DeliveryParameters(PushParameters):
935
936    """Set up Delivery Parameters.
937
938    Args:
939        inbox_protocol (str): identifies the protocol to be used when pushing
940            TAXII Data Collection content to a Consumer's TAXII Inbox Service
941            implementation. **Required**
942        inbox_address (str): identifies the address of the TAXII Daemon hosting
943            the Inbox Service to which the Consumer requests content for this
944            TAXII Data Collection to be delivered. **Required**
945        delivery_message_binding (str): identifies the message binding to be
946             used to send pushed content for this subscription. **Required**
947    """
948
949    name = 'Delivery_Parameters'
950
951
952class TAXIIMessage(TAXIIBase11):
953
954    """Encapsulate properties common to all TAXII Messages (such as headers).
955
956    This class is extended by each Message Type (e.g., DiscoveryRequest), with
957    each subclass containing subclass-specific information
958    """
959
960    message_type = 'TAXIIMessage'
961
962    def __init__(self, message_id, in_response_to=None, extended_headers=None):
963        """Create a new TAXIIMessage
964
965        Args:
966            message_id (str): A value identifying this message.
967            in_response_to (str): Contains the Message ID of the message to
968                which this is a response.
969            extended_headers (dict): A dictionary of name/value pairs for
970                use as Extended Headers
971        """
972        self.message_id = message_id
973        self.in_response_to = in_response_to
974        self.extended_headers = extended_headers or {}
975
976    @property
977    def message_id(self):
978        return self._message_id
979
980    @message_id.setter
981    def message_id(self, value):
982        do_check(value, 'message_id', regex_tuple=uri_regex)
983        self._message_id = value
984
985    @property
986    def in_response_to(self):
987        return self._in_response_to
988
989    @in_response_to.setter
990    def in_response_to(self, value):
991        do_check(value, 'in_response_to', regex_tuple=uri_regex)
992        self._in_response_to = value
993
994    @property
995    def extended_headers(self):
996        return self._extended_headers
997
998    @extended_headers.setter
999    def extended_headers(self, value):
1000        do_check(list(value.keys()), 'extended_headers.keys()', regex_tuple=uri_regex)
1001        self._extended_headers = value
1002
1003    def to_etree(self):
1004        """Creates the base etree for the TAXII Message.
1005
1006        Message-specific constructs must be added by each Message class. In
1007        general, when converting to XML, subclasses should call this method
1008        first, then create their specific XML constructs.
1009        """
1010        root_elt = etree.Element('{%s}%s' % (ns_map['taxii_11'], self.message_type), nsmap=ns_map)
1011        root_elt.attrib['message_id'] = str(self.message_id)
1012
1013        if self.in_response_to is not None:
1014            root_elt.attrib['in_response_to'] = str(self.in_response_to)
1015
1016        if len(self.extended_headers) > 0:
1017            eh = etree.SubElement(root_elt, '{%s}Extended_Headers' % ns_map['taxii_11'], nsmap=ns_map)
1018
1019            for name, value in list(self.extended_headers.items()):
1020                h = etree.SubElement(eh, '{%s}Extended_Header' % ns_map['taxii_11'], nsmap=ns_map)
1021                h.attrib['name'] = name
1022                append_any_content_etree(h, value)
1023                # h.text = value
1024        return root_elt
1025
1026    def to_dict(self):
1027        """Create the base dictionary for the TAXII Message.
1028
1029        Message-specific constructs must be added by each Message class. In
1030        general, when converting to dictionary, subclasses should call this
1031        method first, then create their specific dictionary constructs.
1032        """
1033        d = {}
1034        d['message_type'] = self.message_type
1035        d['message_id'] = self.message_id
1036        if self.in_response_to is not None:
1037            d['in_response_to'] = self.in_response_to
1038        d['extended_headers'] = {}
1039        for k, v in six.iteritems(self.extended_headers):
1040            if isinstance(v, etree._Element) or isinstance(v, etree._ElementTree):
1041                v = etree.tostring(v, encoding='utf-8')
1042            elif not isinstance(v, six.string_types):
1043                v = str(v)
1044            d['extended_headers'][k] = v
1045
1046        return d
1047
1048    def to_text(self, line_prepend=''):
1049        s = line_prepend + "Message Type: %s\n" % self.message_type
1050        s += line_prepend + "Message ID: %s" % self.message_id
1051        if self.in_response_to:
1052            s += "; In Response To: %s" % self.in_response_to
1053        s += "\n"
1054        for k, v in six.iteritems(self.extended_headers):
1055            s += line_prepend + "Extended Header: %s = %s\n" % (k, v)
1056
1057        return s
1058
1059    @classmethod
1060    def from_etree(cls, src_etree, **kwargs):
1061        """Pulls properties of a TAXII Message from an etree.
1062
1063        Message-specific constructs must be pulled by each Message class. In
1064        general, when converting from etree, subclasses should call this method
1065        first, then parse their specific XML constructs.
1066        """
1067
1068        # Check namespace and element name of the root element
1069        expected_tag = '{%s}%s' % (ns_map['taxii_11'], cls.message_type)
1070        tag = src_etree.tag
1071        if tag != expected_tag:
1072            raise ValueError('%s != %s' % (tag, expected_tag))
1073
1074        # Get the message ID
1075        message_id = get_required(src_etree, '/taxii_11:*/@message_id', ns_map)
1076
1077        # Get in response to, if present
1078        in_response_to = get_optional(src_etree, '/taxii_11:*/@in_response_to', ns_map)
1079        if in_response_to is not None:
1080            kwargs['in_response_to'] = in_response_to
1081
1082        # Get the Extended headers
1083        extended_header_list = src_etree.xpath('/taxii_11:*/taxii_11:Extended_Headers/taxii_11:Extended_Header', namespaces=ns_map)
1084        extended_headers = {}
1085        for header in extended_header_list:
1086            eh_name = header.xpath('./@name')[0]
1087            if len(header) == 0:  # This has string content
1088                eh_value = header.text
1089            else:  # This has XML content
1090                eh_value = header[0]
1091
1092            extended_headers[eh_name] = eh_value
1093
1094        return cls(message_id, extended_headers=extended_headers, **kwargs)
1095
1096
1097    @classmethod
1098    def from_dict(cls, d, **kwargs):
1099        """Pulls properties of a TAXII Message from a dictionary.
1100
1101        Message-specific constructs must be pulled by each Message class. In
1102        general, when converting from dictionary, subclasses should call this
1103        method first, then parse their specific dictionary constructs.
1104        """
1105        message_type = d['message_type']
1106        if message_type != cls.message_type:
1107            raise ValueError('%s != %s' % (message_type, cls.message_type))
1108        message_id = d['message_id']
1109        extended_headers = {}
1110        for k, v in six.iteritems(d['extended_headers']):
1111            try:
1112                v = parse(v, allow_file=False)
1113            except etree.XMLSyntaxError:
1114                pass
1115            extended_headers[k] = v
1116
1117        in_response_to = d.get('in_response_to')
1118        if in_response_to:
1119            kwargs['in_response_to'] = in_response_to
1120
1121        return cls(message_id, extended_headers=extended_headers, **kwargs)
1122
1123    @classmethod
1124    def from_json(cls, json_string):
1125        return cls.from_dict(json.loads(json_string))
1126
1127
1128class TAXIIRequestMessage(TAXIIMessage):
1129
1130    @TAXIIMessage.in_response_to.setter
1131    def in_response_to(self, value):
1132        if value is not None:
1133            raise ValueError('in_response_to must be None')
1134        self._in_response_to = value
1135
1136
1137class DiscoveryRequest(TAXIIRequestMessage):
1138
1139    """
1140    A TAXII Discovery Request message.
1141
1142    Args:
1143        message_id (str): A value identifying this message. **Required**
1144        extended_headers (dict): A dictionary of name/value pairs for
1145            use as Extended Headers. **Optional**
1146    """
1147
1148    message_type = MSG_DISCOVERY_REQUEST
1149
1150
1151class DiscoveryResponse(TAXIIMessage):
1152
1153    """
1154    A TAXII Discovery Response message.
1155
1156    Args:
1157        message_id (str): A value identifying this message. **Required**
1158        in_response_to (str): Contains the Message ID of the message to
1159            which this is a response. **Optional**
1160        extended_headers (dict): A dictionary of name/value pairs for
1161            use as Extended Headers. **Optional**
1162        service_instances (list of `ServiceInstance`): a list of
1163            service instances that this response contains. **Optional**
1164    """
1165
1166    message_type = MSG_DISCOVERY_RESPONSE
1167
1168    def __init__(self, message_id, in_response_to, extended_headers=None, service_instances=None):
1169        super(DiscoveryResponse, self).__init__(message_id, in_response_to, extended_headers)
1170        self.service_instances = service_instances or []
1171
1172    @TAXIIMessage.in_response_to.setter
1173    def in_response_to(self, value):
1174        do_check(value, 'in_response_to', regex_tuple=uri_regex)
1175        self._in_response_to = value
1176
1177    @property
1178    def service_instances(self):
1179        return self._service_instances
1180
1181    @service_instances.setter
1182    def service_instances(self, value):
1183        do_check(value, 'service_instances', type=ServiceInstance)
1184        self._service_instances = value
1185
1186    def to_etree(self):
1187        xml = super(DiscoveryResponse, self).to_etree()
1188        for service_instance in self.service_instances:
1189            xml.append(service_instance.to_etree())
1190        return xml
1191
1192    def to_dict(self):
1193        d = super(DiscoveryResponse, self).to_dict()
1194        d['service_instances'] = []
1195        for service_instance in self.service_instances:
1196            d['service_instances'].append(service_instance.to_dict())
1197        return d
1198
1199    def to_text(self, line_prepend=''):
1200        s = super(DiscoveryResponse, self).to_text()
1201        for si in self.service_instances:
1202            s += si.to_text(line_prepend + STD_INDENT)
1203
1204        return s
1205
1206    @classmethod
1207    def from_etree(cls, etree_xml):
1208        kwargs = {}
1209        kwargs['service_instances'] = []
1210        service_instance_set = etree_xml.xpath('./taxii_11:Service_Instance', namespaces=ns_map)
1211        for service_instance in service_instance_set:
1212            si = ServiceInstance.from_etree(service_instance)
1213            kwargs['service_instances'].append(si)
1214
1215        return super(DiscoveryResponse, cls).from_etree(etree_xml, **kwargs)
1216
1217    @classmethod
1218    def from_dict(cls, d):
1219        msg = super(DiscoveryResponse, cls).from_dict(d)
1220        msg.service_instances = []
1221        service_instance_set = d['service_instances']
1222        for service_instance in service_instance_set:
1223            si = ServiceInstance.from_dict(service_instance)
1224            msg.service_instances.append(si)
1225        return msg
1226
1227
1228class ServiceInstance(TAXIIBase11):
1229
1230    """
1231    The Service Instance component of a TAXII Discovery Response Message.
1232
1233    Args:
1234        service_type (string): identifies the Service Type of this
1235            Service Instance. **Required**
1236        services_version (string): identifies the TAXII Services
1237            Specification to which this Service conforms. **Required**
1238        protocol_binding (string): identifies the protocol binding
1239            supported by this Service. **Required**
1240        service_address (string): identifies the network address of the
1241            TAXII Daemon that hosts this Service. **Required**
1242        message_bindings (list of strings): identifies the message
1243            bindings supported by this Service instance. **Required**
1244        inbox_service_accepted_content (list of ContentBinding objects): identifies
1245            content bindings that this Inbox Service is willing to accept.
1246            **Optional**
1247        available (boolean): indicates whether the identity of the
1248            requester (authenticated or otherwise) is allowed to access this
1249            TAXII Service. **Optional**
1250        message (string): contains a message regarding this Service
1251            instance. **Optional**
1252        supported_query (SupportedQuery): contains a structure indicating a
1253            supported query. **Optional**
1254
1255    The ``message_bindings`` list must contain at least one value. The
1256    ``supported_query`` parameter is optional when
1257    ``service_type`` is :py:data:`SVC_POLL`.
1258    """
1259
1260    def __init__(self, service_type, services_version, protocol_binding,
1261                 service_address, message_bindings,
1262                 inbox_service_accepted_content=None, available=None,
1263                 message=None, supported_query=None):
1264        self.service_type = service_type
1265        self.services_version = services_version
1266        self.protocol_binding = protocol_binding
1267        self.service_address = service_address
1268        self.message_bindings = message_bindings
1269        self.inbox_service_accepted_content = inbox_service_accepted_content or []
1270        self.available = available
1271        self.message = message
1272        self.supported_query = supported_query or []
1273
1274    @property
1275    def sort_key(self):
1276        return self.service_address
1277
1278    @property
1279    def service_type(self):
1280        return self._service_type
1281
1282    @service_type.setter
1283    def service_type(self, value):
1284        do_check(value, 'service_type', value_tuple=SVC_TYPES)
1285        self._service_type = value
1286
1287    @property
1288    def services_version(self):
1289        return self._services_version
1290
1291    @services_version.setter
1292    def services_version(self, value):
1293        do_check(value, 'services_version', regex_tuple=uri_regex)
1294        self._services_version = value
1295
1296    @property
1297    def protocol_binding(self):
1298        return self._protocol_binding
1299
1300    @protocol_binding.setter
1301    def protocol_binding(self, value):
1302        do_check(value, 'protocol_binding', regex_tuple=uri_regex)
1303        self._protocol_binding = value
1304
1305    @property
1306    def service_address(self):
1307        return self._service_address
1308
1309    @service_address.setter
1310    def service_address(self, value):
1311        self._service_address = value
1312
1313    @property
1314    def message_bindings(self):
1315        return self._message_bindings
1316
1317    @message_bindings.setter
1318    def message_bindings(self, value):
1319        do_check(value, 'message_bindings', regex_tuple=uri_regex)
1320        self._message_bindings = value
1321
1322    @property
1323    def supported_query(self):
1324        return self._supported_query
1325
1326    @supported_query.setter
1327    def supported_query(self, value):
1328        do_check(value, 'supported_query', type=SupportedQuery)
1329        self._supported_query = value
1330
1331    @property
1332    def inbox_service_accepted_content(self):
1333        return self._inbox_service_accepted_content
1334
1335    @inbox_service_accepted_content.setter
1336    def inbox_service_accepted_content(self, value):
1337        value = _sanitize_content_bindings(value)
1338        do_check(value, 'inbox_service_accepted_content', type=ContentBinding)
1339        self._inbox_service_accepted_content = value
1340
1341    @property
1342    def available(self):
1343        return self._available
1344
1345    @available.setter
1346    def available(self, value):
1347        do_check(value, 'available', value_tuple=(True, False), can_be_none=True)
1348        self._available = value
1349
1350    def to_etree(self):
1351        si = etree.Element('{%s}Service_Instance' % ns_map['taxii_11'], nsmap=ns_map)
1352        si.attrib['service_type'] = self.service_type
1353        si.attrib['service_version'] = self.services_version
1354        if self.available is not None:
1355            si.attrib['available'] = str(self.available).lower()
1356
1357        protocol_binding = etree.SubElement(si, '{%s}Protocol_Binding' % ns_map['taxii_11'], nsmap=ns_map)
1358        protocol_binding.text = self.protocol_binding
1359
1360        service_address = etree.SubElement(si, '{%s}Address' % ns_map['taxii_11'], nsmap=ns_map)
1361        service_address.text = self.service_address
1362
1363        for mb in self.message_bindings:
1364            message_binding = etree.SubElement(si, '{%s}Message_Binding' % ns_map['taxii_11'], nsmap=ns_map)
1365            message_binding.text = mb
1366
1367        for sq in self.supported_query:
1368            si.append(sq.to_etree())
1369
1370        for cb in self.inbox_service_accepted_content:
1371            content_binding = cb.to_etree()
1372            si.append(content_binding)
1373
1374        if self.message is not None:
1375            message = etree.SubElement(si, '{%s}Message' % ns_map['taxii_11'], nsmap=ns_map)
1376            message.text = self.message
1377
1378        return si
1379
1380    def to_dict(self):
1381        d = {}
1382        d['service_type'] = self.service_type
1383        d['services_version'] = self.services_version
1384        d['protocol_binding'] = self.protocol_binding
1385        d['service_address'] = self.service_address
1386        d['message_bindings'] = self.message_bindings
1387        d['supported_query'] = []
1388        for sq in self.supported_query:
1389            d['supported_query'].append(sq.to_dict())
1390        d['inbox_service_accepted_content'] = self.inbox_service_accepted_content
1391        d['available'] = self.available
1392        d['message'] = self.message
1393        return d
1394
1395    def to_text(self, line_prepend=''):
1396        s = line_prepend + "=== Service Instance ===\n"
1397        s += line_prepend + "  Service Type: %s\n" % self.service_type
1398        s += line_prepend + "  Service Version: %s\n" % self.services_version
1399        s += line_prepend + "  Protocol Binding: %s\n" % self.protocol_binding
1400        s += line_prepend + "  Service Address: %s\n" % self.service_address
1401        for mb in self.message_bindings:
1402            s += line_prepend + "  Message Binding: %s\n" % mb
1403        if self.service_type == SVC_INBOX:
1404            s += line_prepend + "  Inbox Service AC: %s\n" % [ac.to_text() for ac in self.inbox_service_accepted_content]
1405        s += line_prepend + "  Available: %s\n" % self.available
1406        s += line_prepend + "  Message: %s\n" % self.message
1407        for q in self.supported_query:
1408            s += q.to_text(line_prepend + STD_INDENT)
1409
1410        return s
1411
1412    @staticmethod
1413    def from_etree(etree_xml):  # Expects a taxii_11:Service_Instance element
1414        service_type = etree_xml.attrib['service_type']
1415        services_version = etree_xml.attrib['service_version']
1416        available = None
1417        if etree_xml.attrib.get('available'):
1418            tmp_available = etree_xml.attrib['available']
1419            available = tmp_available == 'true'
1420
1421        protocol_binding = get_required(etree_xml, './taxii_11:Protocol_Binding', ns_map).text
1422        service_address = get_required(etree_xml, './taxii_11:Address', ns_map).text
1423
1424        message_bindings = []
1425        message_binding_set = etree_xml.xpath('./taxii_11:Message_Binding', namespaces=ns_map)
1426        for mb in message_binding_set:
1427            message_bindings.append(mb.text)
1428
1429        inbox_service_accepted_content = []
1430        inbox_service_accepted_content_set = etree_xml.xpath('./taxii_11:Content_Binding', namespaces=ns_map)
1431        for cb in inbox_service_accepted_content_set:
1432            inbox_service_accepted_content.append(ContentBinding.from_etree(cb))
1433
1434        supported_query = []
1435        supported_query_set = etree_xml.xpath('./taxii_11:Supported_Query', namespaces=ns_map)
1436        for sq in supported_query_set:
1437            format_id = sq.xpath('./@format_id')[0]
1438            query_obj = get_deserializer(format_id, 'query_info').from_etree(sq)
1439            supported_query.append(query_obj)
1440
1441        message = get_optional_text(etree_xml, './taxii_11:Message', ns_map)
1442
1443        return ServiceInstance(service_type,
1444                               services_version,
1445                               protocol_binding,
1446                               service_address,
1447                               message_bindings,
1448                               inbox_service_accepted_content,
1449                               available,
1450                               message,
1451                               supported_query)
1452
1453    @staticmethod
1454    def from_dict(d):
1455        service_type = d['service_type']
1456        services_version = d['services_version']
1457        protocol_binding = d['protocol_binding']
1458        service_address = d['service_address']
1459        message_bindings = d['message_bindings']
1460        supported_query = []
1461        sq_list = d.get('supported_query')
1462        if sq_list is not None:
1463            for sq in sq_list:
1464                format_id = sq['format_id']
1465                query_obj = get_deserializer(format_id, 'query_info').from_dict(sq)
1466                supported_query.append(query_obj)
1467        inbox_service_accepted_content = d.get('inbox_service_accepted_content')
1468        available = d.get('available')
1469        message = d.get('message')
1470
1471        return ServiceInstance(service_type,
1472                               services_version,
1473                               protocol_binding,
1474                               service_address,
1475                               message_bindings,
1476                               inbox_service_accepted_content,
1477                               available,
1478                               message,
1479                               supported_query)
1480
1481
1482class CollectionInformationRequest(TAXIIRequestMessage):
1483
1484    """
1485    A TAXII Collection Information Request message.
1486
1487    Args:
1488        message_id (str): A value identifying this message. **Required**
1489        extended_headers (dict): A dictionary of name/value pairs for
1490            use as Extended Headers. **Optional**
1491    """
1492
1493    message_type = MSG_COLLECTION_INFORMATION_REQUEST
1494
1495
1496class CollectionInformationResponse(TAXIIMessage):
1497
1498    """
1499    A TAXII Collection Information Response message.
1500
1501    Args:
1502        message_id (str): A value identifying this message. **Required**
1503        in_response_to (str): Contains the Message ID of the message to
1504            which this is a response. **Optional**
1505        extended_headers (dict): A dictionary of name/value pairs for
1506            use as Extended Headers. **Optional**
1507        collection_informations (list of CollectionInformation objects): A list
1508            of CollectionInformation objects to be contained in this response.
1509            **Optional**
1510    """
1511    message_type = MSG_COLLECTION_INFORMATION_RESPONSE
1512
1513    def __init__(self, message_id, in_response_to, extended_headers=None, collection_informations=None):
1514        super(CollectionInformationResponse, self).__init__(message_id, in_response_to, extended_headers=extended_headers)
1515        self.collection_informations = collection_informations or []
1516
1517    @TAXIIMessage.in_response_to.setter
1518    def in_response_to(self, value):
1519        do_check(value, 'in_response_to', regex_tuple=uri_regex)
1520        self._in_response_to = value
1521
1522    @property
1523    def collection_informations(self):
1524        return self._collection_informations
1525
1526    @collection_informations.setter
1527    def collection_informations(self, value):
1528        do_check(value, 'collection_informations', type=CollectionInformation)
1529        self._collection_informations = value
1530
1531    def to_etree(self):
1532        xml = super(CollectionInformationResponse, self).to_etree()
1533        for collection in self.collection_informations:
1534            xml.append(collection.to_etree())
1535        return xml
1536
1537    def to_dict(self):
1538        d = super(CollectionInformationResponse, self).to_dict()
1539        d['collection_informations'] = []
1540        for collection in self.collection_informations:
1541            d['collection_informations'].append(collection.to_dict())
1542        return d
1543
1544    def to_text(self, line_prepend=''):
1545        s = super(CollectionInformationResponse, self).to_text(line_prepend)
1546        s += line_prepend + "Contains %s Collection Informations\n" % len(self.collection_informations)
1547        for collection in self.collection_informations:
1548            s += collection.to_text(line_prepend + STD_INDENT)
1549
1550        return s
1551
1552    @classmethod
1553    def from_etree(cls, etree_xml):
1554        msg = super(CollectionInformationResponse, cls).from_etree(etree_xml)
1555        msg.collection_informations = []
1556        collection_informations = etree_xml.xpath('./taxii_11:Collection', namespaces=ns_map)
1557        for collection in collection_informations:
1558            msg.collection_informations.append(CollectionInformation.from_etree(collection))
1559        return msg
1560
1561    @classmethod
1562    def from_dict(cls, d):
1563        msg = super(CollectionInformationResponse, cls).from_dict(d)
1564        msg.collection_informations = []
1565        for collection in d['collection_informations']:
1566            msg.collection_informations.append(CollectionInformation.from_dict(collection))
1567        return msg
1568
1569
1570class CollectionInformation(TAXIIBase11):
1571
1572    """
1573    The Collection Information component of a TAXII Collection Information
1574    Response Message.
1575
1576    Arguments:
1577        collection_name (str): the name by which this TAXII Data Collection is
1578            identified. **Required**
1579        collection_description (str): a prose description of this TAXII
1580            Data Collection. **Required**
1581        supported_contents (list of str): Content Binding IDs
1582            indicating which types of content are currently expressed in this
1583            TAXII Data Collection. **Optional**
1584        available (boolean): whether the identity of the requester
1585            (authenticated or otherwise) is allowed to access this TAXII
1586            Service. **Optional** Default: ``None``, indicating "unknown"
1587        push_methods (list of PushMethod objects): the protocols that
1588            can be used to push content via a subscription. **Optional**
1589        polling_service_instances (list of PollingServiceInstance objects):
1590            the bindings and address a Consumer can use to interact with a
1591            Poll Service instance that supports this TAXII Data Collection.
1592            **Optional**
1593        subscription_methods (list of SubscriptionMethod objects): the
1594            protocol and address of the TAXII Daemon hosting the Collection
1595            Management Service that can process subscriptions for this TAXII
1596            Data Collection. **Optional**
1597        collection_volume (int): the typical number of messages per day.
1598            **Optional**
1599        collection_type (str): the type ofo this collection. **Optional**,
1600            defaults to :py:data:`CT_DATA_FEED`.
1601        receiving_inbox_services (list of ReceivingInboxService objects):
1602            TODO: FILL THIS IN. **Optional**
1603
1604    If ``supported_contents`` is omitted, then the collection supports all
1605    content bindings.  The absense of ``push_methods`` indicates no push
1606    methods.  The absense of ``polling_service_instances`` indicates no
1607    polling services.  The absense of ``subscription_methods`` indicates no
1608    subscription services.  The absense of ``receiving_inbox_services``
1609    indicates no receiving inbox services.
1610    """
1611
1612    def __init__(self, collection_name, collection_description,
1613                 supported_contents=None, available=None, push_methods=None,
1614                 polling_service_instances=None, subscription_methods=None,
1615                 collection_volume=None, collection_type=CT_DATA_FEED,
1616                 receiving_inbox_services=None):
1617        self.collection_name = collection_name
1618        self.available = available
1619        self.collection_description = collection_description
1620        self.supported_contents = supported_contents or []
1621        self.push_methods = push_methods or []
1622        self.polling_service_instances = polling_service_instances or []
1623        self.subscription_methods = subscription_methods or []
1624        self.receiving_inbox_services = receiving_inbox_services or []
1625        self.collection_volume = collection_volume
1626        self.collection_type = collection_type
1627
1628    @property
1629    def sort_key(self):
1630        return self.collection_name
1631
1632    @property
1633    def collection_name(self):
1634        return self._collection_name
1635
1636    @collection_name.setter
1637    def collection_name(self, value):
1638        do_check(value, 'collection_name', regex_tuple=uri_regex)
1639        self._collection_name = value
1640
1641    @property
1642    def available(self):
1643        return self._available
1644
1645    @available.setter
1646    def available(self, value):
1647        do_check(value, 'available', value_tuple=(True, False), can_be_none=True)
1648        self._available = value
1649
1650    @property
1651    def supported_contents(self):
1652        return self._supported_contents
1653
1654    @supported_contents.setter
1655    def supported_contents(self, value):
1656        value = _sanitize_content_bindings(value)
1657        do_check(value, 'supported_contents', type=ContentBinding)
1658        self._supported_contents = value
1659
1660    @property
1661    def push_methods(self):
1662        return self._push_methods
1663
1664    @push_methods.setter
1665    def push_methods(self, value):
1666        do_check(value, 'push_methods', type=PushMethod)
1667        self._push_methods = value
1668
1669    @property
1670    def polling_service_instances(self):
1671        return self._polling_service_instances
1672
1673    @polling_service_instances.setter
1674    def polling_service_instances(self, value):
1675        do_check(value, 'polling_service_instances', type=PollingServiceInstance)
1676        self._polling_service_instances = value
1677
1678    @property
1679    def subscription_methods(self):
1680        return self._subscription_methods
1681
1682    @subscription_methods.setter
1683    def subscription_methods(self, value):
1684        do_check(value, 'subscription_methods', type=SubscriptionMethod)
1685        self._subscription_methods = value
1686
1687    @property
1688    def receiving_inbox_services(self):
1689        return self._receiving_inbox_services
1690
1691    @receiving_inbox_services.setter
1692    def receiving_inbox_services(self, value):
1693        do_check(value, 'receiving_inbox_services', type=ReceivingInboxService)
1694        self._receiving_inbox_services = value
1695
1696    @property
1697    def collection_volume(self):
1698        return self._collection_volume
1699
1700    @collection_volume.setter
1701    def collection_volume(self, value):
1702        do_check(value, 'collection_volume', type=int, can_be_none=True)
1703        self._collection_volume = value
1704
1705    @property
1706    def collection_type(self):
1707        return self._collection_type
1708
1709    @collection_type.setter
1710    def collection_type(self, value):
1711        do_check(value, 'collection_type', value_tuple=CT_TYPES, can_be_none=True)
1712        self._collection_type = value
1713
1714    def to_etree(self):
1715        c = etree.Element('{%s}Collection' % ns_map['taxii_11'], nsmap=ns_map)
1716        c.attrib['collection_name'] = self.collection_name
1717        if self.collection_type is not None:
1718            c.attrib['collection_type'] = self.collection_type
1719        if self.available is not None:
1720            c.attrib['available'] = str(self.available).lower()
1721        collection_description = etree.SubElement(c, '{%s}Description' % ns_map['taxii_11'], nsmap=ns_map)
1722        collection_description.text = self.collection_description
1723
1724        if self.collection_volume is not None:
1725            collection_volume = etree.SubElement(c, '{%s}Collection_Volume' % ns_map['taxii_11'], nsmap=ns_map)
1726            collection_volume.text = str(self.collection_volume)
1727
1728        for binding in self.supported_contents:
1729            c.append(binding.to_etree())
1730
1731        for push_method in self.push_methods:
1732            c.append(push_method.to_etree())
1733
1734        for polling_service in self.polling_service_instances:
1735            c.append(polling_service.to_etree())
1736
1737        for subscription_method in self.subscription_methods:
1738            c.append(subscription_method.to_etree())
1739
1740        for receiving_inbox_service in self.receiving_inbox_services:
1741            c.append(receiving_inbox_service.to_etree())
1742
1743        return c
1744
1745    def to_dict(self):
1746        d = {}
1747        d['collection_name'] = self.collection_name
1748        if self.collection_type is not None:
1749            d['collection_type'] = self.collection_type
1750        if self.available is not None:
1751            d['available'] = self.available
1752        d['collection_description'] = self.collection_description
1753        if self.collection_volume is not None:
1754            d['collection_volume'] = self.collection_volume
1755        # TODO: I think this isn't a good serialization, I think a for loop is necessary
1756        # This is probably a bug
1757        d['supported_contents'] = self.supported_contents
1758
1759        d['push_methods'] = []
1760        for push_method in self.push_methods:
1761            d['push_methods'].append(push_method.to_dict())
1762
1763        d['polling_service_instances'] = []
1764        for polling_service in self.polling_service_instances:
1765            d['polling_service_instances'].append(polling_service.to_dict())
1766
1767        d['subscription_methods'] = []
1768        for subscription_method in self.subscription_methods:
1769            d['subscription_methods'].append(subscription_method.to_dict())
1770
1771        d['receiving_inbox_services'] = []
1772        for receiving_inbox_service in self.receiving_inbox_services:
1773            d['receiving_inbox_services'].append(receiving_inbox_service.to_dict())
1774
1775        return d
1776
1777    def to_text(self, line_prepend=''):
1778        s = line_prepend + "=== Data Collection Information ===\n"
1779        s += line_prepend + "  Collection Name: %s\n" % self.collection_name
1780        s += line_prepend + "  Collection Type: %s\n" % (self.collection_type if (None != self.collection_type) else CT_DATA_FEED)
1781        s += line_prepend + "  Available: %s\n" % self.available
1782        s += line_prepend + "  Collection Description: %s\n" % self.collection_description
1783        if self.collection_volume:
1784            s += line_prepend + "  Volume: %s\n" % self.collection_volume
1785        if len(self.supported_contents) == 0:  # All contents supported:
1786            s += line_prepend + "  Supported Content: %s\n" % "All"
1787        for contents in self.supported_contents:
1788            s += line_prepend + "  Supported Content: %s\n" % contents.to_text(line_prepend + STD_INDENT)
1789        for psi in self.polling_service_instances:
1790            s += psi.to_text(line_prepend + STD_INDENT)
1791        for sm in self.subscription_methods:
1792            s += sm.to_text(line_prepend + STD_INDENT)
1793        for ris in self.receiving_inbox_services:
1794            s += ris.to_text(line_prepend + STD_INDENT)
1795        s += line_prepend + "==================================\n\n"
1796        return s
1797
1798    @staticmethod
1799    def from_etree(etree_xml):
1800        kwargs = {}
1801        kwargs['collection_name'] = etree_xml.attrib['collection_name']
1802        kwargs['collection_type'] = etree_xml.attrib.get('collection_type', None)
1803
1804        kwargs['available'] = None
1805        if 'available' in etree_xml.attrib:
1806            tmp = etree_xml.attrib['available']
1807            kwargs['available'] = tmp.lower() == 'true'
1808
1809        kwargs['collection_description'] = get_required(etree_xml, './taxii_11:Description', ns_map).text
1810
1811        collection_volume_text = get_optional_text(etree_xml, './taxii_11:Collection_Volume', ns_map)
1812        if collection_volume_text:
1813            kwargs['collection_volume'] = int(collection_volume_text)
1814
1815        kwargs['supported_contents'] = []
1816        supported_content_set = etree_xml.xpath('./taxii_11:Content_Binding', namespaces=ns_map)
1817        for binding_elt in supported_content_set:
1818            kwargs['supported_contents'].append(ContentBinding.from_etree(binding_elt))
1819
1820        kwargs['push_methods'] = []
1821        push_method_set = etree_xml.xpath('./taxii_11:Push_Method', namespaces=ns_map)
1822        for push_method_elt in push_method_set:
1823            kwargs['push_methods'].append(PushMethod.from_etree(push_method_elt))
1824
1825        kwargs['polling_service_instances'] = []
1826        polling_service_set = etree_xml.xpath('./taxii_11:Polling_Service', namespaces=ns_map)
1827        for polling_elt in polling_service_set:
1828            kwargs['polling_service_instances'].append(PollingServiceInstance.from_etree(polling_elt))
1829
1830        kwargs['subscription_methods'] = []
1831        subscription_method_set = etree_xml.xpath('./taxii_11:Subscription_Service', namespaces=ns_map)
1832        for subscription_elt in subscription_method_set:
1833            kwargs['subscription_methods'].append(SubscriptionMethod.from_etree(subscription_elt))
1834
1835        kwargs['receiving_inbox_services'] = []
1836        receiving_inbox_services_set = etree_xml.xpath('./taxii_11:Receiving_Inbox_Service', namespaces=ns_map)
1837        for receiving_inbox_service in receiving_inbox_services_set:
1838            kwargs['receiving_inbox_services'].append(ReceivingInboxService.from_etree(receiving_inbox_service))
1839
1840        return CollectionInformation(**kwargs)
1841
1842
1843    @staticmethod
1844    def from_dict(d):
1845        kwargs = {}
1846        kwargs['collection_name'] = d['collection_name']
1847        kwargs['collection_type'] = d.get('collection_type')
1848        kwargs['available'] = d.get('available')
1849        kwargs['collection_description'] = d['collection_description']
1850        kwargs['collection_volume'] = d.get('collection_volume')
1851
1852        kwargs['supported_contents'] = d.get('supported_contents', [])
1853
1854        kwargs['push_methods'] = []
1855        for push_method in d.get('push_methods', []):
1856            kwargs['push_methods'].append(PushMethod.from_dict(push_method))
1857
1858        kwargs['polling_service_instances'] = []
1859        for polling in d.get('polling_service_instances', []):
1860            kwargs['polling_service_instances'].append(PollingServiceInstance.from_dict(polling))
1861
1862        kwargs['subscription_methods'] = []
1863        for subscription_method in d.get('subscription_methods', []):
1864            kwargs['subscription_methods'].append(SubscriptionMethod.from_dict(subscription_method))
1865
1866        kwargs['receiving_inbox_services'] = []
1867        receiving_inbox_services_set = d.get('receiving_inbox_services', [])
1868        for receiving_inbox_service in receiving_inbox_services_set:
1869            kwargs['receiving_inbox_services'].append(ReceivingInboxService.from_dict(receiving_inbox_service))
1870
1871        return CollectionInformation(**kwargs)
1872
1873
1874class PushMethod(TAXIIBase11):
1875
1876    """
1877    The Push Method component of a TAXII Collection Information
1878    component.
1879
1880    Args:
1881        push_protocol (str): a protocol binding that can be used
1882            to push content to an Inbox Service instance. **Required**
1883        push_message_bindings (list of str): the message bindings that
1884            can be used to push content to an Inbox Service instance
1885            using the protocol identified in the Push Protocol field.
1886            **Required**
1887    """
1888
1889    def __init__(self, push_protocol, push_message_bindings):
1890        self.push_protocol = push_protocol
1891        self.push_message_bindings = push_message_bindings
1892
1893    @property
1894    def sort_key(self):
1895        return self.push_protocol
1896
1897    @property
1898    def push_protocol(self):
1899        return self._push_protocol
1900
1901    @push_protocol.setter
1902    def push_protocol(self, value):
1903        do_check(value, 'push_protocol', regex_tuple=uri_regex)
1904        self._push_protocol = value
1905
1906    @property
1907    def push_message_bindings(self):
1908        return self._push_message_bindings
1909
1910    @push_message_bindings.setter
1911    def push_message_bindings(self, value):
1912        do_check(value, 'push_message_bindings', regex_tuple=uri_regex)
1913        self._push_message_bindings = value
1914
1915    def to_etree(self):
1916        x = etree.Element('{%s}Push_Method' % ns_map['taxii_11'], nsmap=ns_map)
1917        proto_bind = etree.SubElement(x, '{%s}Protocol_Binding' % ns_map['taxii_11'], nsmap=ns_map)
1918        proto_bind.text = self.push_protocol
1919        for binding in self.push_message_bindings:
1920            b = etree.SubElement(x, '{%s}Message_Binding' % ns_map['taxii_11'], nsmap=ns_map)
1921            b.text = binding
1922        return x
1923
1924    def to_dict(self):
1925        d = {}
1926        d['push_protocol'] = self.push_protocol
1927        d['push_message_bindings'] = []
1928        for binding in self.push_message_bindings:
1929            d['push_message_bindings'].append(binding)
1930        return d
1931
1932    def to_text(self, line_prepend=''):
1933        s = line_prepend + "=== Push Method ===\n"
1934        s += line_prepend + "  Protocol Binding: %s\n" % self.push_protocol
1935        for mb in self.push_message_bindings:
1936            s += line_prepend + "  Message Binding: %s\n" % mb
1937        return s
1938
1939    @staticmethod
1940    def from_etree(etree_xml):
1941        kwargs = {}
1942        kwargs['push_protocol'] = get_required(etree_xml, './taxii_11:Protocol_Binding', ns_map).text
1943
1944        kwargs['push_message_bindings'] = []
1945        message_binding_set = etree_xml.xpath('./taxii_11:Message_Binding', namespaces=ns_map)
1946        for message_binding in message_binding_set:
1947            kwargs['push_message_bindings'].append(message_binding.text)
1948        return PushMethod(**kwargs)
1949
1950    @staticmethod
1951    def from_dict(d):
1952        return PushMethod(**d)
1953
1954
1955class PollingServiceInstance(TAXIIBase11):
1956
1957    """
1958    The Polling Service Instance component of a TAXII Collection
1959    Information component.
1960
1961    Args:
1962        poll_protocol (str): the protocol binding supported by
1963            this Poll Service instance. **Required**
1964        poll_address (str): the address of the TAXII Daemon
1965            hosting this Poll Service instance. **Required**
1966        poll_message_bindings (list of str): the message bindings
1967            supported by this Poll Service instance. **Required**
1968    """
1969    NAME = 'Polling_Service'
1970
1971    def __init__(self, poll_protocol, poll_address, poll_message_bindings):
1972        self.poll_protocol = poll_protocol
1973        self.poll_address = poll_address
1974        self.poll_message_bindings = poll_message_bindings
1975
1976    @property
1977    def sort_key(self):
1978        return self.poll_address
1979
1980    @property
1981    def poll_protocol(self):
1982        return self._poll_protocol
1983
1984    @poll_protocol.setter
1985    def poll_protocol(self, value):
1986        do_check(value, 'poll_protocol', regex_tuple=uri_regex)
1987        self._poll_protocol = value
1988
1989    @property
1990    def poll_message_bindings(self):
1991        return self._poll_message_bindings
1992
1993    @poll_message_bindings.setter
1994    def poll_message_bindings(self, value):
1995        do_check(value, 'poll_message_bindings', regex_tuple=uri_regex)
1996        self._poll_message_bindings = value
1997
1998    def to_etree(self):
1999        x = etree.Element('{%s}Polling_Service' % ns_map['taxii_11'], nsmap=ns_map)
2000        proto_bind = etree.SubElement(x, '{%s}Protocol_Binding' % ns_map['taxii_11'], nsmap=ns_map)
2001        proto_bind.text = self.poll_protocol
2002        address = etree.SubElement(x, '{%s}Address' % ns_map['taxii_11'], nsmap=ns_map)
2003        address.text = self.poll_address
2004        for binding in self.poll_message_bindings:
2005            b = etree.SubElement(x, '{%s}Message_Binding' % ns_map['taxii_11'], nsmap=ns_map)
2006            b.text = binding
2007        return x
2008
2009    def to_dict(self):
2010        d = {}
2011        d['poll_protocol'] = self.poll_protocol
2012        d['poll_address'] = self.poll_address
2013        d['poll_message_bindings'] = []
2014        for binding in self.poll_message_bindings:
2015            d['poll_message_bindings'].append(binding)
2016        return d
2017
2018    def to_text(self, line_prepend=''):
2019        s = line_prepend + "=== Polling Service Instance ===\n"
2020        s += line_prepend + "  Poll Protocol: %s\n" % self.poll_protocol
2021        s += line_prepend + "  Poll Address: %s\n" % self.poll_address
2022        for binding in self.poll_message_bindings:
2023            s += line_prepend + "  Message Binding: %s\n" % binding
2024        return s
2025
2026    @classmethod
2027    def from_etree(cls, etree_xml):
2028        protocol = get_required(etree_xml, './taxii_11:Protocol_Binding', ns_map).text
2029        addr = get_required(etree_xml, './taxii_11:Address', ns_map).text
2030
2031        bindings = []
2032        message_binding_set = etree_xml.xpath('./taxii_11:Message_Binding', namespaces=ns_map)
2033        for message_binding in message_binding_set:
2034            bindings.append(message_binding.text)
2035        return cls(protocol, addr, bindings)
2036
2037    @classmethod
2038    def from_dict(cls, d):
2039        return cls(**d)
2040
2041
2042class SubscriptionMethod(TAXIIBase11):
2043
2044    """
2045    The Subscription Method component of a TAXII Collection Information
2046    component.
2047
2048    Args:
2049        subscription_protocol (str): the protocol binding supported by
2050            this Collection Management Service instance. **Required**
2051        subscription_address (str): the address of the TAXII Daemon
2052            hosting this Collection Management Service instance.
2053            **Required**.
2054        subscription_message_bindings (list of str): the message
2055            bindings supported by this Collection Management Service
2056            Instance. **Required**
2057    """
2058    NAME = 'Subscription_Service'
2059
2060    def __init__(self, subscription_protocol, subscription_address,
2061                 subscription_message_bindings):
2062        self.subscription_protocol = subscription_protocol
2063        self.subscription_address = subscription_address
2064        self.subscription_message_bindings = subscription_message_bindings
2065
2066    @property
2067    def sort_key(self):
2068        return self.subscription_address
2069
2070    @property
2071    def subscription_protocol(self):
2072        return self._subscription_protocol
2073
2074    @subscription_protocol.setter
2075    def subscription_protocol(self, value):
2076        do_check(value, 'subscription_protocol', regex_tuple=uri_regex)
2077        self._subscription_protocol = value
2078
2079    @property
2080    def subscription_message_bindings(self):
2081        return self._subscription_message_bindings
2082
2083    @subscription_message_bindings.setter
2084    def subscription_message_bindings(self, value):
2085        do_check(value, 'subscription_message_bindings', regex_tuple=uri_regex)
2086        self._subscription_message_bindings = value
2087
2088    def to_etree(self):
2089        x = etree.Element('{%s}%s' % (ns_map['taxii_11'], self.NAME))
2090        proto_bind = etree.SubElement(x, '{%s}Protocol_Binding' % ns_map['taxii_11'], nsmap=ns_map)
2091        proto_bind.text = self.subscription_protocol
2092        address = etree.SubElement(x, '{%s}Address' % ns_map['taxii_11'], nsmap=ns_map)
2093        address.text = self.subscription_address
2094        for binding in self.subscription_message_bindings:
2095            b = etree.SubElement(x, '{%s}Message_Binding' % ns_map['taxii_11'], nsmap=ns_map)
2096            b.text = binding
2097        return x
2098
2099    def to_dict(self):
2100        d = {}
2101        d['subscription_protocol'] = self.subscription_protocol
2102        d['subscription_address'] = self.subscription_address
2103        d['subscription_message_bindings'] = []
2104        for binding in self.subscription_message_bindings:
2105            d['subscription_message_bindings'].append(binding)
2106        return d
2107
2108    def to_text(self, line_prepend=''):
2109        s = line_prepend + "=== Subscription Service ===\n"
2110        s += line_prepend + "  Protocol Binding: %s\n" % self.subscription_protocol
2111        s += line_prepend + "  Address: %s\n" % self.subscription_address
2112        for mb in self.subscription_message_bindings:
2113            s += line_prepend + "  Message Binding: %s\n" % mb
2114        return s
2115
2116    @classmethod
2117    def from_etree(cls, etree_xml):
2118        protocol = get_required(etree_xml, './taxii_11:Protocol_Binding', ns_map).text
2119        addr = get_required(etree_xml, './taxii_11:Address', ns_map).text
2120        bindings = []
2121        message_binding_set = etree_xml.xpath('./taxii_11:Message_Binding', namespaces=ns_map)
2122        for message_binding in message_binding_set:
2123            bindings.append(message_binding.text)
2124        return cls(protocol, addr, bindings)
2125
2126    @classmethod
2127    def from_dict(cls, d):
2128        return cls(**d)
2129
2130
2131class ReceivingInboxService(TAXIIBase11):
2132
2133    """
2134    The Receiving Inbox Service component of a TAXII Collection
2135    Information component.
2136
2137    Args:
2138        inbox_protocol (str): Indicates the protocol this Inbox Service
2139            uses. **Required**
2140        inbox address (str): Indicates the address of this Inbox Service.
2141            **Required**
2142        inbox_message_bindings (list of str): Each string indicates a
2143            message binding that this inbox service uses. **Required**
2144        supported_contents (list of ContentBinding objects): Each object
2145            indicates a Content Binding this inbox service can receive.
2146            **Optional**.  Setting to ``None`` means that all Content
2147            Bindings are supported.
2148    """
2149
2150    def __init__(self, inbox_protocol, inbox_address,
2151                 inbox_message_bindings, supported_contents=None):
2152        self.inbox_protocol = inbox_protocol
2153        self.inbox_address = inbox_address
2154        self.inbox_message_bindings = inbox_message_bindings
2155        self.supported_contents = supported_contents or []
2156
2157    @property
2158    def sort_key(self):
2159        return self.inbox_address
2160
2161    @property
2162    def inbox_protocol(self):
2163        return self._inbox_protocol
2164
2165    @inbox_protocol.setter
2166    def inbox_protocol(self, value):
2167        do_check(value, 'inbox_protocol', type=six.string_types, regex_tuple=uri_regex)
2168        self._inbox_protocol = value
2169
2170    @property
2171    def inbox_address(self):
2172        return self._inbox_address
2173
2174    @inbox_address.setter
2175    def inbox_address(self, value):
2176        self._inbox_address = value
2177
2178    @property
2179    def inbox_message_bindings(self):
2180        return self._inbox_message_bindings
2181
2182    @inbox_message_bindings.setter
2183    def inbox_message_bindings(self, value):
2184        do_check(value, 'inbox_message_bindings', regex_tuple=uri_regex)
2185        self._inbox_message_bindings = value
2186
2187    @property
2188    def supported_contents(self):
2189        return self._supported_contents
2190
2191    @supported_contents.setter
2192    def supported_contents(self, value):
2193        value = _sanitize_content_bindings(value)
2194        do_check(value, 'supported_contents', type=ContentBinding)
2195        self._supported_contents = value
2196
2197    def to_etree(self):
2198        xml = etree.Element('{%s}Receiving_Inbox_Service' % ns_map['taxii_11'], nsmap=ns_map)
2199
2200        pb = etree.SubElement(xml, '{%s}Protocol_Binding' % ns_map['taxii_11'])
2201        pb.text = self.inbox_protocol
2202
2203        a = etree.SubElement(xml, '{%s}Address' % ns_map['taxii_11'])
2204        a.text = self.inbox_address
2205
2206        for binding in self.inbox_message_bindings:
2207            mb = etree.SubElement(xml, '{%s}Message_Binding' % ns_map['taxii_11'])
2208            mb.text = binding
2209
2210        for binding in self.supported_contents:
2211            xml.append(binding.to_etree())
2212
2213        return xml
2214
2215    def to_dict(self):
2216        d = {}
2217
2218        d['inbox_protocol'] = self.inbox_protocol
2219        d['inbox_address'] = self.inbox_address
2220        d['inbox_message_bindings'] = self.inbox_message_bindings
2221        d['supported_contents'] = []
2222        for supported_content in self.supported_contents:
2223            d['supported_contents'].append(supported_content.to_dict())
2224
2225        return d
2226
2227    def to_text(self, line_prepend=''):
2228        s = line_prepend + "=== Receiving Inbox Service ===\n"
2229        s += line_prepend + "  Protocol Binding: %s\n" % self.inbox_protocol
2230        s += line_prepend + "  Address: %s\n" % self.inbox_address
2231        for mb in self.inbox_message_bindings:
2232            s += line_prepend + "  Message Binding: %s\n" % mb
2233        if len(self.supported_contents) == 0:
2234            s += line_prepend + "  Supported Contents: All\n"
2235        for sc in self.supported_contents:
2236            s += line_prepend + "  Supported Content: %s\n" % str(sc)
2237
2238        return s
2239
2240    @staticmethod
2241    def from_etree(etree_xml):
2242        proto = get_required(etree_xml, './taxii_11:Protocol_Binding', ns_map).text
2243        addr = get_required(etree_xml, './taxii_11:Address', ns_map).text
2244
2245        message_bindings = []
2246        message_binding_set = etree_xml.xpath('./taxii_11:Message_Binding', namespaces=ns_map)
2247        for mb in message_binding_set:
2248            message_bindings.append(mb.text)
2249
2250        supported_contents = []
2251        supported_contents_set = etree_xml.xpath('./taxii_11:Content_Binding', namespaces=ns_map)
2252        for cb in supported_contents_set:
2253            supported_contents.append(ContentBinding.from_etree(cb))
2254
2255        return ReceivingInboxService(proto, addr, message_bindings, supported_contents)
2256
2257    @staticmethod
2258    def from_dict(d):
2259        kwargs = {}
2260        kwargs['inbox_protocol'] = d['inbox_protocol']
2261        kwargs['inbox_address'] = d['inbox_address']
2262        kwargs['inbox_message_bindings'] = d['inbox_message_bindings']
2263        kwargs['supported_contents'] = []
2264        for binding in d['supported_contents']:
2265            kwargs['supported_contents'].append(ContentBinding.from_dict(binding))
2266
2267        return ReceivingInboxService(**kwargs)
2268
2269
2270class PollRequest(TAXIIRequestMessage):
2271
2272    """
2273    A TAXII Poll Request message.
2274
2275    Arguments:
2276        message_id (str): A value identifying this message. **Required**
2277        extended_headers (dict): A dictionary of name/value pairs for
2278            use as Extended Headers. **Optional**
2279        collection_name (str): the name of the TAXII Data Collection that is being
2280            polled. **Required**
2281        exclusive_begin_timestamp_label (datetime): a Timestamp Label
2282            indicating the beginning of the range of TAXII Data Feed content the
2283            requester wishes to receive. **Optional for a Data Feed, Prohibited
2284            for a Data Set**
2285        inclusive_end_timestamp_label (datetime): a Timestamp Label
2286            indicating the end of the range of TAXII Data Feed content the
2287            requester wishes to receive. **Optional for a Data Feed, Probited
2288            for a Data Set**
2289        subscription_id (str): the existing subscription the Consumer
2290            wishes to poll. **Optional**
2291        poll_parameters (list of PollParameters objects): the poll parameters
2292            for this request. **Optional**
2293
2294    Exactly one of ``subscription_id`` and ``poll_parameters`` is **Required**.
2295    """
2296    message_type = MSG_POLL_REQUEST
2297
2298    def __init__(self, message_id, extended_headers=None,
2299                 collection_name=None, exclusive_begin_timestamp_label=None,
2300                 inclusive_end_timestamp_label=None, subscription_id=None,
2301                 poll_parameters=None):
2302        super(PollRequest, self).__init__(message_id, extended_headers=extended_headers)
2303        self.collection_name = collection_name
2304        self.exclusive_begin_timestamp_label = exclusive_begin_timestamp_label
2305        self.inclusive_end_timestamp_label = inclusive_end_timestamp_label
2306        self.subscription_id = subscription_id
2307        self.poll_parameters = poll_parameters
2308
2309        if subscription_id is None and poll_parameters is None:
2310            raise ValueError('One of subscription_id or poll_parameters must not be None')
2311        if subscription_id is not None and poll_parameters is not None:
2312            raise ValueError('Only one of subscription_id and poll_parameters can be present')
2313
2314    @TAXIIMessage.in_response_to.setter
2315    def in_response_to(self, value):
2316        do_check(value, 'in_response_to', value_tuple=(None, None), can_be_none=True)
2317        self._in_response_to = value
2318
2319    @property
2320    def collection_name(self):
2321        return self._collection_name
2322
2323    @collection_name.setter
2324    def collection_name(self, value):
2325        do_check(value, 'collection_name', regex_tuple=uri_regex)
2326        self._collection_name = value
2327
2328    @property
2329    def exclusive_begin_timestamp_label(self):
2330        return self._exclusive_begin_timestamp_label
2331
2332    @exclusive_begin_timestamp_label.setter
2333    def exclusive_begin_timestamp_label(self, value):
2334        value = check_timestamp_label(value, 'exclusive_begin_timestamp_label', can_be_none=True)
2335        self._exclusive_begin_timestamp_label = value
2336
2337    @property
2338    def inclusive_end_timestamp_label(self):
2339        return self._inclusive_end_timestamp_label
2340
2341    @inclusive_end_timestamp_label.setter
2342    def inclusive_end_timestamp_label(self, value):
2343        value = check_timestamp_label(value, 'inclusive_end_timestamp_label', can_be_none=True)
2344        self._inclusive_end_timestamp_label = value
2345
2346    @property
2347    def subscription_id(self):
2348        return self._subscription_id
2349
2350    @subscription_id.setter
2351    def subscription_id(self, value):
2352        do_check(value, 'subscription_id', regex_tuple=uri_regex, can_be_none=True)
2353        self._subscription_id = value
2354
2355    @property
2356    def poll_parameters(self):
2357        return self._poll_parameters
2358
2359    @poll_parameters.setter
2360    def poll_parameters(self, value):
2361        do_check(value, 'poll_parameters', type=PollParameters, can_be_none=True)
2362        self._poll_parameters = value
2363
2364    def to_etree(self):
2365        xml = super(PollRequest, self).to_etree()
2366        xml.attrib['collection_name'] = self.collection_name
2367
2368        if self.exclusive_begin_timestamp_label is not None:
2369            ebt = etree.SubElement(xml, '{%s}Exclusive_Begin_Timestamp' % ns_map['taxii_11'], nsmap=ns_map)
2370            # TODO: Add TZ Info
2371            ebt.text = self.exclusive_begin_timestamp_label.isoformat()
2372
2373        if self.inclusive_end_timestamp_label is not None:
2374            iet = etree.SubElement(xml, '{%s}Inclusive_End_Timestamp' % ns_map['taxii_11'], nsmap=ns_map)
2375            # TODO: Add TZ Info
2376            iet.text = self.inclusive_end_timestamp_label.isoformat()
2377
2378        if self.subscription_id is not None:
2379            si = etree.SubElement(xml, '{%s}Subscription_ID' % ns_map['taxii_11'], nsmap=ns_map)
2380            si.text = self.subscription_id
2381
2382        if self.poll_parameters is not None:
2383            xml.append(self.poll_parameters.to_etree())
2384
2385        return xml
2386
2387    def to_dict(self):
2388        d = super(PollRequest, self).to_dict()
2389        d['collection_name'] = self.collection_name
2390        if self.subscription_id is not None:
2391            d['subscription_id'] = self.subscription_id
2392        if self.exclusive_begin_timestamp_label is not None:  # TODO: Add TZ Info
2393            d['exclusive_begin_timestamp_label'] = self.exclusive_begin_timestamp_label.isoformat()
2394        if self.inclusive_end_timestamp_label is not None:  # TODO: Add TZ Info
2395            d['inclusive_end_timestamp_label'] = self.inclusive_end_timestamp_label.isoformat()
2396        d['poll_parameters'] = None
2397        if self.poll_parameters is not None:
2398            d['poll_parameters'] = self.poll_parameters.to_dict()
2399        return d
2400
2401    def to_text(self, line_prepend=''):
2402        s = super(PollRequest, self).to_text(line_prepend)
2403        s += line_prepend + "  Collection Name: %s\n" % self.collection_name
2404        if self.subscription_id:
2405            s += line_prepend + "  Subscription ID: %s\n" % self.subscription_id
2406        s += line_prepend + "  Excl. Begin TS Label: %s\n" % self.exclusive_begin_timestamp_label
2407        s += line_prepend + "  Incl. End TS Label: %s\n" % self.inclusive_end_timestamp_label
2408        if self.poll_parameters:
2409            s += self.poll_parameters.to_text(line_prepend + STD_INDENT)
2410
2411        return s
2412
2413    @classmethod
2414    def from_etree(cls, etree_xml):
2415        kwargs = {}
2416        kwargs['collection_name'] = get_required(etree_xml, './@collection_name', ns_map)
2417
2418        kwargs['exclusive_begin_timestamp_label'] = None
2419
2420        begin_ts_text = get_optional_text(etree_xml, './taxii_11:Exclusive_Begin_Timestamp', ns_map)
2421        if begin_ts_text:
2422            kwargs['exclusive_begin_timestamp_label'] = parse_datetime_string(begin_ts_text)
2423
2424        end_ts_text = get_optional_text(etree_xml, './taxii_11:Inclusive_End_Timestamp', ns_map)
2425        if end_ts_text:
2426            kwargs['inclusive_end_timestamp_label'] = parse_datetime_string(end_ts_text)
2427
2428        poll_parameter_el = get_optional(etree_xml, './taxii_11:Poll_Parameters', ns_map)
2429        if poll_parameter_el is not None:
2430            kwargs['poll_parameters'] = PollParameters.from_etree(poll_parameter_el)
2431
2432        kwargs['subscription_id'] = get_optional_text(etree_xml, './taxii_11:Subscription_ID', ns_map)
2433
2434        msg = super(PollRequest, cls).from_etree(etree_xml, **kwargs)
2435        return msg
2436
2437    @classmethod
2438    def from_dict(cls, d):
2439        kwargs = {}
2440        kwargs['collection_name'] = d['collection_name']
2441
2442        kwargs['subscription_id'] = d.get('subscription_id')
2443
2444        kwargs['exclusive_begin_timestamp_label'] = None
2445        if 'exclusive_begin_timestamp_label' in d:
2446            kwargs['exclusive_begin_timestamp_label'] = parse_datetime_string(d['exclusive_begin_timestamp_label'])
2447
2448        kwargs['inclusive_end_timestamp_label'] = None
2449        if 'inclusive_end_timestamp_label' in d:
2450            kwargs['inclusive_end_timestamp_label'] = parse_datetime_string(d['inclusive_end_timestamp_label'])
2451
2452        kwargs['poll_parameters'] = None
2453        if 'poll_parameters' in d and d['poll_parameters'] is not None:
2454            kwargs['poll_parameters'] = PollParameters.from_dict(d['poll_parameters'])
2455
2456        msg = super(PollRequest, cls).from_dict(d, **kwargs)
2457        return msg
2458
2459
2460class PollParameters(_GenericParameters):
2461
2462    """
2463    The Poll Parameters component of a TAXII Poll Request message.
2464
2465    Args:
2466        response_type (str): The requested response type. Must be either
2467            :py:data:`RT_FULL` or :py:data:`RT_COUNT_ONLY`. **Optional**,
2468            defaults to :py:data:`RT_FULL`
2469        content_bindings (list of ContentBinding objects): A list of Content
2470            Bindings acceptable in response. **Optional**
2471        query (Query): The query for this poll parameters. **Optional**
2472        allow_asynch (bool): Indicates whether the client supports
2473            asynchronous polling. **Optional**, defaults to ``False``
2474        delivery_parameters (libtaxii.messages_11.DeliveryParameters): The requested delivery
2475            parameters for this object. **Optional**
2476
2477    If ``content_bindings`` in not provided, this indicates that all
2478    bindings are accepted as a response.
2479    """
2480    name = 'Poll_Parameters'
2481
2482    def __init__(self, response_type=RT_FULL, content_bindings=None,
2483                 query=None, allow_asynch=False, delivery_parameters=None):
2484        super(PollParameters, self).__init__(response_type, content_bindings, query)
2485        self.allow_asynch = allow_asynch
2486        self.delivery_parameters = delivery_parameters
2487
2488    @property
2489    def delivery_parameters(self):
2490        return self._delivery_parameters
2491
2492    @delivery_parameters.setter
2493    def delivery_parameters(self, value):
2494        do_check(value, 'delivery_parameters', type=DeliveryParameters, can_be_none=True)
2495        self._delivery_parameters = value
2496
2497    @property
2498    def allow_asynch(self):
2499        return self._allow_asynch
2500
2501    @allow_asynch.setter
2502    def allow_asynch(self, value):
2503        do_check(value, 'allow_asynch', value_tuple=(True, False), can_be_none=True)
2504        self._allow_asynch = value
2505
2506    def to_etree(self):
2507        xml = super(PollParameters, self).to_etree()
2508
2509        if self.allow_asynch is not None:
2510            xml.attrib['allow_asynch'] = str(self.allow_asynch).lower()
2511
2512        if self.delivery_parameters is not None:
2513            xml.append(self.delivery_parameters.to_etree())
2514        return xml
2515
2516    def to_dict(self):
2517        d = super(PollParameters, self).to_dict()
2518        if self.allow_asynch is not None:
2519            d['allow_asynch'] = str(self.allow_asynch).lower()
2520        d['delivery_parameters'] = None
2521        if self.delivery_parameters is not None:
2522            d['delivery_parameters'] = self.delivery_parameters.to_dict()
2523        return d
2524
2525    def to_text(self, line_prepend=''):
2526        s = super(PollParameters, self).to_text(line_prepend)
2527        if self.allow_asynch:
2528            s += line_prepend + "  Allow Asynch: %s\n" % self.allow_asynch
2529        if self.delivery_parameters:
2530            s += self.delivery_parameters.to_text(line_prepend + STD_INDENT)
2531        return s
2532
2533    @classmethod
2534    def from_etree(cls, etree_xml):
2535        poll_parameters = super(PollParameters, cls).from_etree(etree_xml)
2536
2537        allow_asynch_el = get_optional(etree_xml, './@allow_asynch', ns_map)
2538        poll_parameters.allow_asynch = allow_asynch_el == 'true'
2539
2540        delivery_parameters_el = get_optional(etree_xml, './taxii_11:Delivery_Parameters', ns_map)
2541        if delivery_parameters_el is not None:
2542            poll_parameters.delivery_parameters = DeliveryParameters.from_etree(delivery_parameters_el)
2543
2544        return poll_parameters
2545
2546    @classmethod
2547    def from_dict(cls, d):
2548        poll_parameters = super(PollParameters, cls).from_dict(d)
2549
2550        aa = d.get('allow_asynch')
2551        if aa is not None:
2552            poll_parameters.allow_asynch = aa == 'true'
2553
2554        delivery_parameters = d.get('delivery_parameters')
2555        if delivery_parameters is not None:
2556            poll_parameters.delivery_parameters = DeliveryParameters.from_dict(delivery_parameters)
2557
2558        return poll_parameters
2559
2560
2561class PollResponse(TAXIIMessage):
2562
2563    """
2564    A TAXII Poll Response message.
2565
2566    Args:
2567        message_id (str): A value identifying this message. **Required**
2568        in_response_to (str): Contains the Message ID of the message to
2569            which this is a response. **Optional**
2570        extended_headers (dict): A dictionary of name/value pairs for
2571            use as Extended Headers. **Optional**
2572        collection_name (str): the name of the TAXII Data Collection that was
2573            polled. **Required**
2574        exclusive_begin_timestamp_label (datetime): a Timestamp Label
2575            indicating the beginning of the range this response covers.
2576            **Optional for a Data Feed, Prohibited for a Data Set**
2577        inclusive_end_timestamp_label (datetime): a Timestamp Label
2578            indicating the end of the range this response covers. **Optional
2579            for a Data Feed, Prohibited for a Data Set**
2580        subscription_id (str): the Subscription ID for which this content
2581            is being provided. **Optional**
2582        message (str): additional information for the message recipient.
2583            **Optional**
2584        content_blocks (list of ContentBlock): piece of content
2585            and additional information related to the content. **Optional**
2586        more (bool): Whether there are more result parts. **Optional**, defaults
2587            to ``False``
2588        result_id (str): The ID of this result. **Optional**
2589        result_part_number (int): The result part number of this response.
2590             **Optional**
2591        record_count (RecordCount): The number of records and whether
2592             the count is a lower bound. **Optional**
2593    """
2594    message_type = MSG_POLL_RESPONSE
2595
2596    def __init__(self, message_id, in_response_to, extended_headers=None,
2597                 collection_name=None, exclusive_begin_timestamp_label=None,
2598                 inclusive_end_timestamp_label=None, subscription_id=None,
2599                 message=None, content_blocks=None, more=False, result_id=None,
2600                 result_part_number=1, record_count=None):
2601        super(PollResponse, self).__init__(message_id, in_response_to, extended_headers)
2602        self.collection_name = collection_name
2603        self.exclusive_begin_timestamp_label = exclusive_begin_timestamp_label
2604        self.inclusive_end_timestamp_label = inclusive_end_timestamp_label
2605        self.subscription_id = subscription_id
2606        self.message = message
2607        self.content_blocks = content_blocks or []
2608        self.more = more
2609        self.result_part_number = result_part_number
2610        self.result_id = result_id
2611        self.record_count = record_count
2612
2613    @TAXIIMessage.in_response_to.setter
2614    def in_response_to(self, value):
2615        do_check(value, 'in_response_to', regex_tuple=uri_regex)
2616        self._in_response_to = value
2617
2618    @property
2619    def collection_name(self):
2620        return self._collection_name
2621
2622    @collection_name.setter
2623    def collection_name(self, value):
2624        do_check(value, 'collection_name', regex_tuple=uri_regex)
2625        self._collection_name = value
2626
2627    @property
2628    def inclusive_end_timestamp_label(self):
2629        return self._inclusive_end_timestamp_label
2630
2631    @inclusive_end_timestamp_label.setter
2632    def inclusive_end_timestamp_label(self, value):
2633        value = check_timestamp_label(value, 'inclusive_end_timestamp_label', can_be_none=True)
2634        self._inclusive_end_timestamp_label = value
2635
2636    @property
2637    def inclusive_begin_timestamp_label(self):
2638        return self._inclusive_begin_timestamp_label
2639
2640    @inclusive_begin_timestamp_label.setter
2641    def inclusive_begin_timestamp_label(self, value):
2642        value = check_timestamp_label(value, 'inclusive_begin_timestamp_label', can_be_none=True)
2643        self._inclusive_begin_timestamp_label = value
2644
2645    @property
2646    def subscription_id(self):
2647        return self._subscription_id
2648
2649    @subscription_id.setter
2650    def subscription_id(self, value):
2651        do_check(value, 'subscription_id', regex_tuple=uri_regex, can_be_none=True)
2652        self._subscription_id = value
2653
2654    @property
2655    def content_blocks(self):
2656        return self._content_blocks
2657
2658    @content_blocks.setter
2659    def content_blocks(self, value):
2660        do_check(value, 'content_blocks', type=ContentBlock)
2661        self._content_blocks = value
2662
2663    @property
2664    def more(self):
2665        return self._more
2666
2667    @more.setter
2668    def more(self, value):
2669        do_check(value, 'more', value_tuple=(True, False))
2670        self._more = value
2671
2672    @property
2673    def result_id(self):
2674        return self._result_id
2675
2676    @result_id.setter
2677    def result_id(self, value):
2678        do_check(value, 'result_id', regex_tuple=uri_regex, can_be_none=True)
2679        self._result_id = value
2680
2681    @property
2682    def result_part_number(self):
2683        return self._result_part_number
2684
2685    @result_part_number.setter
2686    def result_part_number(self, value):
2687        do_check(value, 'result_part_number', type=int, can_be_none=True)
2688        self._result_part_number = value
2689
2690    @property
2691    def record_count(self):
2692        return self._record_count
2693
2694    @record_count.setter
2695    def record_count(self, value):
2696        do_check(value, 'record_count', type=RecordCount, can_be_none=True)
2697        self._record_count = value
2698
2699    def to_etree(self):
2700        xml = super(PollResponse, self).to_etree()
2701        xml.attrib['collection_name'] = self.collection_name
2702        if self.result_id is not None:
2703            xml.attrib['result_id'] = self.result_id
2704
2705        if self.more is not None:
2706            xml.attrib['more'] = str(self.more).lower()
2707
2708        if self.result_part_number is not None:
2709            xml.attrib['result_part_number'] = str(self.result_part_number)
2710
2711        if self.subscription_id is not None:
2712            si = etree.SubElement(xml, '{%s}Subscription_ID' % ns_map['taxii_11'])
2713            si.text = self.subscription_id
2714
2715        if self.exclusive_begin_timestamp_label:
2716            ibt = etree.SubElement(xml, '{%s}Exclusive_Begin_Timestamp' % ns_map['taxii_11'])
2717            ibt.text = self.exclusive_begin_timestamp_label.isoformat()
2718
2719        if self.inclusive_end_timestamp_label:
2720            iet = etree.SubElement(xml, '{%s}Inclusive_End_Timestamp' % ns_map['taxii_11'])
2721            iet.text = self.inclusive_end_timestamp_label.isoformat()
2722
2723        if self.record_count:
2724            xml.append(self.record_count.to_etree())
2725
2726        if self.message is not None:
2727            m = etree.SubElement(xml, '{%s}Message' % ns_map['taxii_11'])
2728            m.text = self.message
2729
2730        for block in self.content_blocks:
2731            xml.append(block.to_etree())
2732
2733        return xml
2734
2735    def to_dict(self):
2736        d = super(PollResponse, self).to_dict()
2737
2738        d['collection_name'] = self.collection_name
2739        d['more'] = self.more
2740        d['result_id'] = self.result_id
2741        d['result_part_number'] = self.result_part_number
2742        if self.record_count is not None:
2743            d['record_count'] = self.record_count.to_dict()
2744        if self.subscription_id is not None:
2745            d['subscription_id'] = self.subscription_id
2746        if self.message is not None:
2747            d['message'] = self.message
2748        if self.exclusive_begin_timestamp_label is not None:
2749            d['exclusive_begin_timestamp_label'] = self.exclusive_begin_timestamp_label.isoformat()
2750        if self.inclusive_end_timestamp_label is not None:
2751            d['inclusive_end_timestamp_label'] = self.inclusive_end_timestamp_label.isoformat()
2752        d['content_blocks'] = []
2753        for block in self.content_blocks:
2754            d['content_blocks'].append(block.to_dict())
2755
2756        return d
2757
2758    def to_text(self, line_prepend=''):
2759        s = super(PollResponse, self).to_text(line_prepend)
2760        s += line_prepend + "  Collection Name: %s\n" % self.collection_name
2761        s += line_prepend + "  More: %s\n" % self.more
2762        s += line_prepend + "  Result ID: %s\n" % self.result_id
2763        if self.result_part_number:
2764            s += line_prepend + "  Result Part Num: %s\n" % self.result_part_number
2765        if self.record_count:
2766            s += self.record_count.to_text(line_prepend + STD_INDENT)
2767        if self.subscription_id:
2768            s += line_prepend + "  Subscription ID: %s\n" % self.subscription_id
2769        if self.message:
2770            s += line_prepend + "  Message: %s\n" % self.message
2771        if self.exclusive_begin_timestamp_label:
2772            s += line_prepend + "  Excl. Begin TS Label: %s\n" % self.exclusive_begin_timestamp_label.isoformat()
2773        if self.inclusive_end_timestamp_label:
2774            s += line_prepend + "  Incl. End TS Label: %s\n" % self.inclusive_end_timestamp_label.isoformat()
2775        for cb in self.content_blocks:
2776            s += cb.to_text(line_prepend + STD_INDENT)
2777        return s
2778
2779    @classmethod
2780    def from_etree(cls, etree_xml):
2781        kwargs = {}
2782
2783        kwargs['collection_name'] = get_required(etree_xml, './@collection_name', ns_map)
2784        kwargs['more'] = etree_xml.attrib.get('more', 'false') == 'true'
2785        kwargs['subscription_id'] = None
2786        kwargs['result_id'] = etree_xml.attrib.get('result_id')
2787        rpn = etree_xml.attrib.get('result_part_number', None)
2788        if rpn:
2789            kwargs['result_part_number'] = int(rpn)
2790
2791        kwargs['subscription_id'] = get_optional_text(etree_xml, './taxii_11:Subscription_ID', ns_map)
2792        kwargs['message'] = get_optional_text(etree_xml, './taxii_11:Message', ns_map)
2793
2794        ebts_text = get_optional_text(etree_xml, './taxii_11:Exclusive_Begin_Timestamp', ns_map)
2795        if ebts_text:
2796            kwargs['exclusive_begin_timestamp_label'] = parse_datetime_string(ebts_text)
2797
2798        iets_text = get_optional_text(etree_xml, './taxii_11:Inclusive_End_Timestamp', ns_map)
2799        if iets_text:
2800            kwargs['inclusive_end_timestamp_label'] = parse_datetime_string(iets_text)
2801
2802        kwargs['content_blocks'] = []
2803        for block in etree_xml.xpath('./taxii_11:Content_Block', namespaces=ns_map):
2804            kwargs['content_blocks'].append(ContentBlock.from_etree(block))
2805
2806        record_count_el = get_optional(etree_xml, './taxii_11:Record_Count', ns_map)
2807        if record_count_el is not None:
2808            kwargs['record_count'] = RecordCount.from_etree(record_count_el)
2809
2810        msg = super(PollResponse, cls).from_etree(etree_xml, **kwargs)
2811        return msg
2812
2813    @classmethod
2814    def from_dict(cls, d):
2815        kwargs = {}
2816        kwargs['collection_name'] = d['collection_name']
2817        kwargs['result_id'] = d.get('result_id')
2818        kwargs['result_part_number'] = d.get('result_part_number')
2819        kwargs['message'] = None
2820        if 'message' in d:
2821            kwargs['message'] = d['message']
2822
2823        kwargs['subscription_id'] = d.get('subscription_id')
2824        kwargs['more'] = d.get('more', False)
2825
2826        kwargs['exclusive_begin_timestamp_label'] = None
2827        if 'exclusive_begin_timestamp_label' in d:
2828            kwargs['exclusive_begin_timestamp_label'] = parse_datetime_string(d['exclusive_begin_timestamp_label'])
2829
2830        kwargs['record_count'] = None
2831        if 'record_count' in d:
2832            kwargs['record_count'] = RecordCount.from_dict(d['record_count'])
2833
2834        kwargs['inclusive_end_timestamp_label'] = None
2835        if 'inclusive_end_timestamp_label' in d:
2836            kwargs['inclusive_end_timestamp_label'] = parse_datetime_string(d['inclusive_end_timestamp_label'])
2837
2838        kwargs['content_blocks'] = []
2839        for block in d['content_blocks']:
2840            kwargs['content_blocks'].append(ContentBlock.from_dict(block))
2841        msg = super(PollResponse, cls).from_dict(d, **kwargs)
2842        return msg
2843
2844
2845_StatusDetail = collections.namedtuple('_StatusDetail', ['name', 'required', 'type', 'multiple'])
2846_DCE_AcceptableDestination = _StatusDetail(SD_ACCEPTABLE_DESTINATION, False, str, True)
2847_IRP_MaxPartNumber = _StatusDetail(SD_MAX_PART_NUMBER, True, int, False)
2848_NF_Item = _StatusDetail(SD_ITEM, False, str, False)
2849_P_EstimatedWait = _StatusDetail(SD_ESTIMATED_WAIT, True, int, False)
2850_P_ResultId = _StatusDetail(SD_RESULT_ID, True, str, False)
2851_P_WillPush = _StatusDetail(SD_WILL_PUSH, True, bool, False)
2852_R_EstimatedWait = _StatusDetail(SD_ESTIMATED_WAIT, False, int, False)
2853_UM_SupportedBinding = _StatusDetail(SD_SUPPORTED_BINDING, False, str, True)
2854_UC_SupportedContent = _StatusDetail(SD_SUPPORTED_CONTENT, False, ContentBinding, True)
2855_UP_SupportedProtocol = _StatusDetail(SD_SUPPORTED_PROTOCOL, False, str, True)
2856_UQ_SupportedQuery = _StatusDetail(SD_SUPPORTED_QUERY, False, str, True)
2857
2858
2859status_details = {
2860    ST_ASYNCHRONOUS_POLL_ERROR: {},
2861    ST_BAD_MESSAGE: {},
2862    ST_DENIED: {},
2863    ST_DESTINATION_COLLECTION_ERROR: {SD_ACCEPTABLE_DESTINATION: _DCE_AcceptableDestination},
2864    ST_FAILURE: {},
2865    ST_INVALID_RESPONSE_PART: {SD_MAX_PART_NUMBER: _IRP_MaxPartNumber},
2866    ST_NETWORK_ERROR: {},
2867    ST_NOT_FOUND: {SD_ITEM: _NF_Item},
2868    ST_PENDING: {SD_ESTIMATED_WAIT: _P_EstimatedWait,
2869                 SD_RESULT_ID: _P_ResultId,
2870                 SD_WILL_PUSH: _P_WillPush},
2871    ST_POLLING_UNSUPPORTED: {},
2872    ST_RETRY: {SD_ESTIMATED_WAIT: _R_EstimatedWait},
2873    ST_SUCCESS: {},
2874    ST_UNAUTHORIZED: {},
2875    ST_UNSUPPORTED_MESSAGE_BINDING: {SD_SUPPORTED_BINDING: _UM_SupportedBinding},
2876    ST_UNSUPPORTED_CONTENT_BINDING: {SD_SUPPORTED_CONTENT: _UC_SupportedContent},
2877    ST_UNSUPPORTED_PROTOCOL: {SD_SUPPORTED_PROTOCOL: _UP_SupportedProtocol},
2878    ST_UNSUPPORTED_QUERY: {SD_SUPPORTED_QUERY: _UQ_SupportedQuery}
2879}
2880
2881
2882class StatusMessage(TAXIIMessage):
2883
2884    """
2885    A TAXII Status message.
2886
2887    Args:
2888        message_id (str): A value identifying this message. **Required**
2889        in_response_to (str): Contains the Message ID of the message to
2890            which this is a response. **Required**
2891        extended_headers (dict): A dictionary of name/value pairs for
2892            use as Extended Headers. **Optional**
2893        status_type (str): One of the defined Status Types or a third-party-
2894            defined Status Type. **Required**
2895        status_detail (dict): A field for additional information about
2896            this status in a machine-readable format. **Required or Optional**
2897            depending on ``status_type``. See TAXII Specification for details.
2898        message (str): Additional information for the status. There is no
2899            expectation that this field be interpretable by a machine; it is
2900            instead targeted to a human operator. **Optional**
2901    """
2902    message_type = MSG_STATUS_MESSAGE
2903
2904    def __init__(self, message_id, in_response_to, extended_headers=None,
2905                 status_type=None, status_detail=None, message=None):
2906        super(StatusMessage, self).__init__(message_id, in_response_to, extended_headers=extended_headers)
2907        self.status_type = status_type
2908        self.status_detail = status_detail or {}
2909        self.message = message
2910
2911    @TAXIIMessage.in_response_to.setter
2912    def in_response_to(self, value):
2913        do_check(value, 'in_response_to', regex_tuple=uri_regex)
2914        self._in_response_to = value
2915
2916    @property
2917    def status_type(self):
2918        return self._status_type
2919
2920    @status_type.setter
2921    def status_type(self, value):
2922        do_check(value, 'status_type')
2923        self._status_type = value
2924
2925    @property
2926    def status_detail(self):
2927        return self._status_detail
2928
2929    @status_detail.setter
2930    def status_detail(self, value):
2931        do_check(list(value.keys()), 'status_detail.keys()', regex_tuple=uri_regex)
2932        detail_rules = status_details.get(self.status_type, {})
2933        # Check defined status types for conformance
2934        for sd_name, rules in six.iteritems(detail_rules):
2935            do_check(value.get(sd_name, None),
2936                     'status_detail[\'%s\']' % sd_name,
2937                     type=rules.type,
2938                     can_be_none=(not rules.required))
2939
2940        self._status_detail = value
2941
2942    @property
2943    def message(self):
2944        return self._message
2945
2946    @message.setter
2947    def message(self, value):
2948        do_check(value, 'message', type=six.string_types, can_be_none=True)
2949        self._message = value
2950
2951    def to_etree(self):
2952        xml = super(StatusMessage, self).to_etree()
2953        xml.attrib['status_type'] = self.status_type
2954
2955        if len(self.status_detail) > 0:
2956            sd = etree.SubElement(xml, '{%s}Status_Detail' % ns_map['taxii_11'])
2957            for k, v in six.iteritems(self.status_detail):
2958                if not isinstance(v, list):
2959                    v = [v]
2960                for item in v:
2961                    d = etree.SubElement(sd, '{%s}Detail' % ns_map['taxii_11'])
2962                    d.attrib['name'] = k
2963                    if item in (True, False):
2964                        d.text = str(item).lower()
2965                    else:
2966                        d.text = str(item)
2967
2968        if self.message is not None:
2969            m = etree.SubElement(xml, '{%s}Message' % ns_map['taxii_11'])
2970            m.text = self.message
2971
2972        return xml
2973
2974    def to_dict(self):
2975        d = super(StatusMessage, self).to_dict()
2976        d['status_type'] = self.status_type
2977        if self.status_detail is not None:
2978            d['status_detail'] = self.status_detail
2979        if self.message is not None:
2980            d['message'] = self.message
2981        return d
2982
2983    def to_text(self, line_prepend=''):
2984        s = super(StatusMessage, self).to_text(line_prepend)
2985        s += line_prepend + "Status Type: %s\n" % self.status_type
2986        for k, v in six.iteritems(self.status_detail):
2987            s += line_prepend + "Status Detail: %s = %s\n" % (k, v)
2988        if self.message:
2989            s += line_prepend + "Message: %s\n" % self.message
2990        return s
2991
2992    @classmethod
2993    def from_etree(cls, etree_xml):
2994        kwargs = {}
2995
2996        status_type = etree_xml.attrib['status_type']
2997        kwargs['status_type'] = status_type
2998
2999        kwargs['status_detail'] = {}
3000        detail_set = etree_xml.xpath('./taxii_11:Status_Detail/taxii_11:Detail', namespaces=ns_map)
3001        for detail in detail_set:
3002            # TODO: This seems kind of hacky and should probably be improved
3003            name = detail.attrib['name']
3004
3005            if status_type in status_details and name in status_details[status_type]:  # We have information for this status detail
3006                detail_info = status_details[status_type][name]
3007            else:  # We don't have information, so make something up
3008                detail_info = _StatusDetail('PlaceholderDetail', False, str, True)
3009
3010            if detail_info.type == bool:
3011                v = detail.text.lower() == 'true'
3012            else:
3013                v = detail_info.type(detail.text)
3014            if detail_info.multiple:  # There can be multiple instances of this item
3015                if name not in kwargs['status_detail']:
3016                    kwargs['status_detail'][name] = v
3017                else:  # It already exists
3018                    if not isinstance(kwargs['status_detail'][name], list):
3019                        kwargs['status_detail'][name] = [kwargs['status_detail'][name]]  # Make it a list
3020                    kwargs['status_detail'][name].append(v)
3021            else:
3022                kwargs['status_detail'][name] = v
3023
3024        kwargs['message'] = None
3025        m_set = etree_xml.xpath('./taxii_11:Message', namespaces=ns_map)
3026        if len(m_set) > 0:
3027            kwargs['message'] = m_set[0].text
3028
3029        msg = super(StatusMessage, cls).from_etree(etree_xml, **kwargs)
3030        return msg
3031
3032    @classmethod
3033    def from_dict(cls, d):
3034        kwargs = {}
3035        kwargs['status_type'] = d['status_type']
3036        kwargs['status_detail'] = d.get('status_detail')
3037        kwargs['message'] = d.get('message')
3038
3039        msg = super(StatusMessage, cls).from_dict(d, **kwargs)
3040        return msg
3041
3042
3043class InboxMessage(TAXIIMessage):
3044
3045    """
3046    A TAXII Inbox message.
3047
3048    Args:
3049        message_id (str): A value identifying this message. **Required**
3050        extended_headers (dict): A dictionary of name/value pairs for
3051            use as Extended Headers. **Optional**
3052        message (str): prose information for the message recipient. **Optional**
3053        result_id (str): the result id. **Optional**
3054        destination_collection_names (list of str): Each string indicates a
3055             destination collection name. **Optional**
3056        subscription_information (libtaxii.messages_11.SubscriptionInformation): This
3057            field is only present if this message is being sent to provide
3058            content in accordance with an existing TAXII Data Collection
3059            subscription. **Optional**
3060        record_count (RecordCount): The number of records and whether
3061             the count is a lower bound. **Optional**
3062        content_blocks (list of ContentBlock): Inbox content. **Optional**
3063    """
3064    message_type = MSG_INBOX_MESSAGE
3065
3066    def __init__(self, message_id, in_response_to=None, extended_headers=None,
3067                 message=None, result_id=None, destination_collection_names=None,
3068                 subscription_information=None, record_count=None,
3069                 content_blocks=None):
3070
3071        super(InboxMessage, self).__init__(message_id, extended_headers=extended_headers)
3072        self.subscription_information = subscription_information
3073        self.message = message
3074        self.result_id = result_id
3075        self.destination_collection_names = destination_collection_names or []
3076        self.subscription_information = subscription_information
3077        self.record_count = record_count
3078        self.content_blocks = content_blocks or []
3079
3080    @TAXIIMessage.in_response_to.setter
3081    def in_response_to(self, value):
3082        if value is not None:
3083            raise ValueError('in_response_to must be None')
3084        self._in_response_to = value
3085
3086    @property
3087    def subscription_information(self):
3088        return self._subscription_information
3089
3090    @subscription_information.setter
3091    def subscription_information(self, value):
3092        do_check(value, 'subscription_information', type=SubscriptionInformation, can_be_none=True)
3093        self._subscription_information = value
3094
3095    @property
3096    def content_blocks(self):
3097        return self._content_blocks
3098
3099    @content_blocks.setter
3100    def content_blocks(self, value):
3101        do_check(value, 'content_blocks', type=ContentBlock)
3102        self._content_blocks = value
3103
3104    @property
3105    def result_id(self):
3106        return self._result_id
3107
3108    @result_id.setter
3109    def result_id(self, value):
3110        do_check(value, 'result_id', regex_tuple=uri_regex, can_be_none=True)
3111        self._result_id = value
3112
3113    @property
3114    def destination_collection_names(self):
3115        return self._destination_collection_names
3116
3117    @destination_collection_names.setter
3118    def destination_collection_names(self, value):
3119        do_check(value, 'destination_collection_names', regex_tuple=uri_regex)
3120        self._destination_collection_names = value
3121
3122    @property
3123    def record_count(self):
3124        return self._record_count
3125
3126    @record_count.setter
3127    def record_count(self, value):
3128        do_check(value, 'record_count', type=RecordCount, can_be_none=True)
3129        self._record_count = value
3130
3131    def to_etree(self):
3132        xml = super(InboxMessage, self).to_etree()
3133
3134        if self.result_id is not None:
3135            xml.attrib['result_id'] = self.result_id
3136
3137        for dcn in self.destination_collection_names:
3138            d = etree.SubElement(xml, '{%s}Destination_Collection_Name' % ns_map['taxii_11'], nsmap=ns_map)
3139            d.text = dcn
3140
3141        if self.message is not None:
3142            m = etree.SubElement(xml, '{%s}Message' % ns_map['taxii_11'])
3143            m.text = self.message
3144
3145        if self.subscription_information is not None:
3146            xml.append(self.subscription_information.to_etree())
3147
3148        if self.record_count is not None:
3149            xml.append(self.record_count.to_etree())
3150
3151        for block in self.content_blocks:
3152            xml.append(block.to_etree())
3153
3154        return xml
3155
3156    def to_dict(self):
3157        d = super(InboxMessage, self).to_dict()
3158
3159        if self.result_id is not None:
3160            d['result_id'] = self.result_id
3161
3162        d['destination_collection_names'] = []
3163        for dcn in self.destination_collection_names:
3164            d['destination_collection_names'].append(dcn)
3165
3166        if self.message is not None:
3167            d['message'] = self.message
3168
3169        if self.subscription_information is not None:
3170            d['subscription_information'] = self.subscription_information.to_dict()
3171
3172        if self.record_count is not None:
3173            d['record_count'] = self.record_count.to_dict()
3174
3175        d['content_blocks'] = []
3176        for block in self.content_blocks:
3177            d['content_blocks'].append(block.to_dict())
3178
3179        return d
3180
3181    def to_text(self, line_prepend=''):
3182        s = super(InboxMessage, self).to_text(line_prepend)
3183        if self.result_id:
3184            s += line_prepend + "  Result ID: %s\n" % self.result_id
3185        for dcn in self.destination_collection_names:
3186            s += line_prepend + "  Destination Collection Name: %s\n" % dcn
3187        s += line_prepend + "  Message: %s\n" % self.message
3188        if self.subscription_information:
3189            s += self.subscription_information.to_text(line_prepend + STD_INDENT)
3190        if self.record_count:
3191            s += self.record_count.to_text(line_prepend + STD_INDENT)
3192        s += line_prepend + "  Message has %s Content Blocks\n" % len(self.content_blocks)
3193        for cb in self.content_blocks:
3194            s += cb.to_text(line_prepend + STD_INDENT)
3195
3196        return s
3197
3198    @classmethod
3199    def from_etree(cls, etree_xml):
3200        kwargs = {}
3201
3202        result_id_set = etree_xml.xpath('./@result_id')
3203        if len(result_id_set) > 0:
3204            kwargs['result_id'] = result_id_set[0]
3205
3206        kwargs['destination_collection_names'] = []
3207        dcn_set = etree_xml.xpath('./taxii_11:Destination_Collection_Name', namespaces=ns_map)
3208        for dcn in dcn_set:
3209            kwargs['destination_collection_names'].append(dcn.text)
3210
3211        msg_set = etree_xml.xpath('./taxii_11:Message', namespaces=ns_map)
3212        if len(msg_set) > 0:
3213            kwargs['message'] = msg_set[0].text
3214
3215        subs_infos = etree_xml.xpath('./taxii_11:Source_Subscription', namespaces=ns_map)
3216        if len(subs_infos) > 0:
3217            kwargs['subscription_information'] = SubscriptionInformation.from_etree(subs_infos[0])
3218
3219        record_count_set = etree_xml.xpath('./taxii_11:Record_Count', namespaces=ns_map)
3220        if len(record_count_set) > 0:
3221            kwargs['record_count'] = RecordCount.from_etree(record_count_set[0])
3222
3223        content_blocks = etree_xml.xpath('./taxii_11:Content_Block', namespaces=ns_map)
3224        kwargs['content_blocks'] = []
3225        for block in content_blocks:
3226            kwargs['content_blocks'].append(ContentBlock.from_etree(block))
3227
3228        msg = super(InboxMessage, cls).from_etree(etree_xml, **kwargs)
3229        return msg
3230
3231    @classmethod
3232    def from_dict(cls, d):
3233
3234        kwargs = {}
3235
3236        kwargs['result_id'] = d.get('result_id')
3237
3238        kwargs['destination_collection_names'] = []
3239        if 'destination_collection_names' in d:
3240            for dcn in d['destination_collection_names']:
3241                kwargs['destination_collection_names'].append(dcn)
3242
3243        kwargs['message'] = d.get('message')
3244
3245        kwargs['subscription_information'] = None
3246        if 'subscription_information' in d:
3247            kwargs['subscription_information'] = SubscriptionInformation.from_dict(d['subscription_information'])
3248
3249        if 'record_count' in d:
3250            kwargs['record_count'] = RecordCount.from_dict(d['record_count'])
3251
3252        kwargs['content_blocks'] = []
3253        for block in d['content_blocks']:
3254            kwargs['content_blocks'].append(ContentBlock.from_dict(block))
3255
3256        msg = super(InboxMessage, cls).from_dict(d, **kwargs)
3257        return msg
3258
3259
3260class SubscriptionInformation(TAXIIBase11):
3261
3262    """
3263    The Subscription Information component of a TAXII Inbox message.
3264
3265    Arguments:
3266        collection_name (str): the name of the TAXII Data Collection from
3267            which this content is being provided. **Required**
3268        subscription_id (str): the Subscription ID for which this
3269            content is being provided. **Required**
3270        exclusive_begin_timestamp_label (datetime): a Timestamp Label
3271            indicating the beginning of the time range this Inbox Message
3272            covers. **Optional for a Data Feed, Prohibited for a Data Set**
3273        inclusive_end_timestamp_label (datetime): a Timestamp Label
3274            indicating the end of the time range this Inbox Message covers.
3275            **Optional for a Data Feed, Prohibited for a Data Set**
3276    """
3277
3278    def __init__(self, collection_name, subscription_id, exclusive_begin_timestamp_label=None, inclusive_end_timestamp_label=None):
3279        self.collection_name = collection_name
3280        self.subscription_id = subscription_id
3281        self.exclusive_begin_timestamp_label = exclusive_begin_timestamp_label
3282        self.inclusive_end_timestamp_label = inclusive_end_timestamp_label
3283
3284    @property
3285    def collection_name(self):
3286        return self._collection_name
3287
3288    @collection_name.setter
3289    def collection_name(self, value):
3290        do_check(value, 'collection_name', regex_tuple=uri_regex)
3291        self._collection_name = value
3292
3293    @property
3294    def subscription_id(self):
3295        return self._subscription_id
3296
3297    @subscription_id.setter
3298    def subscription_id(self, value):
3299        do_check(value, 'subscription_id', regex_tuple=uri_regex)
3300        self._subscription_id = value
3301
3302    @property
3303    def exclusive_begin_timestamp_label(self):
3304        return self._exclusive_begin_timestamp_label
3305
3306    @exclusive_begin_timestamp_label.setter
3307    def exclusive_begin_timestamp_label(self, value):
3308        value = check_timestamp_label(value, 'exclusive_begin_timestamp_label', can_be_none=True)
3309        self._exclusive_begin_timestamp_label = value
3310
3311    @property
3312    def inclusive_end_timestamp_label(self):
3313        return self._inclusive_end_timestamp_label
3314
3315    @inclusive_end_timestamp_label.setter
3316    def inclusive_end_timestamp_label(self, value):
3317        value = check_timestamp_label(value, 'inclusive_end_timestamp_label', can_be_none=True)
3318        self._inclusive_end_timestamp_label = value
3319
3320    def to_etree(self):
3321        xml = etree.Element('{%s}Source_Subscription' % ns_map['taxii_11'])
3322        xml.attrib['collection_name'] = self.collection_name
3323        si = etree.SubElement(xml, '{%s}Subscription_ID' % ns_map['taxii_11'])
3324        si.text = self.subscription_id
3325
3326        if self.exclusive_begin_timestamp_label:
3327            ebtl = etree.SubElement(xml, '{%s}Exclusive_Begin_Timestamp' % ns_map['taxii_11'])
3328            ebtl.text = self.exclusive_begin_timestamp_label.isoformat()
3329
3330        if self.inclusive_end_timestamp_label:
3331            ietl = etree.SubElement(xml, '{%s}Inclusive_End_Timestamp' % ns_map['taxii_11'])
3332            ietl.text = self.inclusive_end_timestamp_label.isoformat()
3333
3334        return xml
3335
3336    def to_dict(self):
3337        d = {}
3338        d['collection_name'] = self.collection_name
3339        d['subscription_id'] = self.subscription_id
3340        if self.exclusive_begin_timestamp_label:
3341            d['exclusive_begin_timestamp_label'] = self.exclusive_begin_timestamp_label.isoformat()
3342        if self.inclusive_end_timestamp_label:
3343            d['inclusive_end_timestamp_label'] = self.inclusive_end_timestamp_label.isoformat()
3344        return d
3345
3346    def to_text(self, line_prepend=''):
3347        s = line_prepend + "=== Source Subscription ===\n"
3348        s += line_prepend + "  Collection Name: %s\n" % self.collection_name
3349        s += line_prepend + "  Subscription ID: %s\n" % self.subscription_id
3350
3351        if self.exclusive_begin_timestamp_label:
3352            s += line_prepend + "  Excl. Begin TS Label: %s\n" % self.exclusive_begin_timestamp_label.isoformat()
3353        else:
3354            s += line_prepend + "  Excl. Begin TS Label: %s\n" % None
3355
3356        if self.inclusive_end_timestamp_label:
3357            s += line_prepend + "  Incl. End TS Label: %s\n" % self.inclusive_end_timestamp_label.isoformat()
3358        else:
3359            s += line_prepend + "  Incl. End TS Label: %s\n" % None
3360        return s
3361
3362    @staticmethod
3363    def from_etree(etree_xml):
3364        collection_name = etree_xml.attrib['collection_name']
3365        subscription_id = get_required(etree_xml, './taxii_11:Subscription_ID', ns_map).text
3366
3367        begin_ts_text = get_optional_text(etree_xml, './taxii_11:Exclusive_Begin_Timestamp', ns_map)
3368        ebtl = parse_datetime_string(begin_ts_text) if begin_ts_text else None
3369
3370        end_ts_text = get_optional_text(etree_xml, './taxii_11:Inclusive_End_Timestamp', ns_map)
3371        ietl = parse_datetime_string(end_ts_text) if end_ts_text else None
3372
3373        return SubscriptionInformation(collection_name, subscription_id, ebtl, ietl)
3374
3375    @staticmethod
3376    def from_dict(d):
3377        collection_name = d['collection_name']
3378        subscription_id = d['subscription_id']
3379
3380        ebtl = parse_datetime_string(d.get('exclusive_begin_timestamp_label'))
3381        ietl = parse_datetime_string(d.get('inclusive_end_timestamp_label'))
3382
3383        return SubscriptionInformation(collection_name, subscription_id, ebtl, ietl)
3384
3385
3386class ManageCollectionSubscriptionRequest(TAXIIRequestMessage):
3387
3388    """
3389    A TAXII Manage Collection Subscription Request message.
3390
3391    Args:
3392        message_id (str): A value identifying this message. **Required**
3393        extended_headers (dict): A dictionary of name/value pairs for
3394            use as Extended Headers. **Optional**
3395        collection_name (str): the name of the TAXII Data Collection to which the
3396            action applies. **Required**
3397        action (str): the requested action to take. **Required**
3398        subscription_id (str): the ID of a previously created subscription.
3399            **Probibited** if ``action==``:py:data:`ACT_SUBSCRIBE`, else
3400            **Required**
3401        subscription_parameters (SubscriptionParameters): The parameters for
3402            this subscription. **Optional**
3403        push_parameters (list of PushParameter): the push parameters for this
3404            request. **Optional** Absence means push is not requested.
3405    """
3406
3407    message_type = MSG_MANAGE_COLLECTION_SUBSCRIPTION_REQUEST
3408
3409    def __init__(self, message_id, extended_headers=None,
3410                 collection_name=None, action=None, subscription_id=None,
3411                 subscription_parameters=None, push_parameters=None):
3412
3413        super(ManageCollectionSubscriptionRequest, self).__init__(message_id, extended_headers=extended_headers)
3414        self.collection_name = collection_name
3415        self.action = action
3416        self.subscription_id = subscription_id
3417        self.subscription_parameters = subscription_parameters or SubscriptionParameters()
3418        self.push_parameters = push_parameters
3419
3420    @TAXIIMessage.in_response_to.setter
3421    def in_response_to(self, value):
3422        if value is not None:
3423            raise ValueError('in_response_to must be None')
3424        self._in_response_to = value
3425
3426    @property
3427    def collection_name(self):
3428        return self._collection_name
3429
3430    @collection_name.setter
3431    def collection_name(self, value):
3432        do_check(value, 'collection_name', regex_tuple=uri_regex)
3433        self._collection_name = value
3434
3435    @property
3436    def action(self):
3437        return self._action
3438
3439    @action.setter
3440    def action(self, value):
3441        do_check(value, 'action', value_tuple=ACT_TYPES)
3442        self._action = value
3443
3444    @property
3445    def subscription_id(self):
3446        return self._subscription_id
3447
3448    @subscription_id.setter
3449    def subscription_id(self, value):
3450        do_check(value, 'subscription_id', regex_tuple=uri_regex, can_be_none=True)
3451        self._subscription_id = value
3452
3453    @property
3454    def subscription_parameters(self):
3455        return self._subscription_parameters
3456
3457    @subscription_parameters.setter
3458    def subscription_parameters(self, value):
3459        do_check(value, 'subscription_parameters', type=SubscriptionParameters, can_be_none=True)
3460        self._subscription_parameters = value
3461
3462    @property
3463    def push_parameters(self):
3464        return self._push_parameters
3465
3466    @push_parameters.setter
3467    def push_parameters(self, value):
3468        do_check(value, 'push_parameters', type=PushParameters, can_be_none=True)
3469        self._push_parameters = value
3470
3471    def to_etree(self):
3472        xml = super(ManageCollectionSubscriptionRequest, self).to_etree()
3473        xml.attrib['collection_name'] = self.collection_name
3474        xml.attrib['action'] = self.action
3475        if self.subscription_id is not None:
3476            si = etree.SubElement(xml, '{%s}Subscription_ID' % ns_map['taxii_11'])
3477            si.text = self.subscription_id
3478
3479        if self.action == ACT_SUBSCRIBE:
3480            xml.append(self.subscription_parameters.to_etree())
3481
3482        if self.action == ACT_SUBSCRIBE and self.push_parameters:
3483            xml.append(self.push_parameters.to_etree())
3484        return xml
3485
3486    def to_dict(self):
3487        d = super(ManageCollectionSubscriptionRequest, self).to_dict()
3488        d['collection_name'] = self.collection_name
3489        d['action'] = self.action
3490
3491        if self.subscription_id is not None:
3492            d['subscription_id'] = self.subscription_id
3493
3494        if self.action == ACT_SUBSCRIBE:
3495            d['subscription_parameters'] = self.subscription_parameters.to_dict()
3496
3497        if self.action == ACT_SUBSCRIBE and self.push_parameters:
3498            d['push_parameters'] = self.push_parameters.to_dict()
3499
3500        return d
3501
3502    def to_text(self, line_prepend=''):
3503        s = super(ManageCollectionSubscriptionRequest, self).to_text(line_prepend)
3504        s += line_prepend + "  Collection Name: %s\n" % self.collection_name
3505        s += line_prepend + "  Action: %s\n" % self.action
3506        s += line_prepend + "  Subscription ID: %s\n" % self.subscription_id
3507
3508        if self.action == ACT_SUBSCRIBE:
3509            s += self.subscription_parameters.to_text(line_prepend + STD_INDENT)
3510
3511        if self.action == ACT_SUBSCRIBE and self.push_parameters:
3512            s += self.push_parameters.to_text(line_prepend + STD_INDENT)
3513
3514        return s
3515
3516    @classmethod
3517    def from_etree(cls, etree_xml):
3518
3519        kwargs = {}
3520        kwargs['collection_name'] = get_required(etree_xml, './@collection_name', ns_map)
3521        kwargs['action'] = get_required(etree_xml, './@action', ns_map)
3522
3523        kwargs['subscription_id'] = get_optional_text(etree_xml, './taxii_11:Subscription_ID', ns_map)
3524
3525        subscription_parameters_el = get_optional(etree_xml, './taxii_11:Subscription_Parameters', ns_map)
3526        if subscription_parameters_el is not None:
3527            kwargs['subscription_parameters'] = SubscriptionParameters.from_etree(subscription_parameters_el)
3528
3529        push_parameters_el = get_optional(etree_xml, './taxii_11:Push_Parameters', ns_map)
3530        if push_parameters_el is not None:
3531            kwargs['push_parameters'] = PushParameters.from_etree(push_parameters_el)
3532
3533        msg = super(ManageCollectionSubscriptionRequest, cls).from_etree(etree_xml, **kwargs)
3534        return msg
3535
3536    @classmethod
3537    def from_dict(cls, d):
3538        kwargs = {}
3539        kwargs['collection_name'] = d['collection_name']
3540        kwargs['action'] = d['action']
3541        kwargs['subscription_id'] = d.get('subscription_id')
3542
3543        kwargs['subscription_parameters'] = None
3544        if 'subscription_parameters' in d:
3545            kwargs['subscription_parameters'] = SubscriptionParameters.from_dict(d['subscription_parameters'])
3546
3547        kwargs['push_parameters'] = None
3548        if 'push_parameters' in d:
3549            kwargs['push_parameters'] = PushParameters.from_dict(d['push_parameters'])
3550
3551        msg = super(ManageCollectionSubscriptionRequest, cls).from_dict(d, **kwargs)
3552        return msg
3553
3554
3555class ManageCollectionSubscriptionResponse(TAXIIMessage):
3556
3557    """
3558    A TAXII Manage Collection Subscription Response message.
3559
3560    Args:
3561        message_id (str): A value identifying this message. **Required**
3562        in_response_to (str): Contains the Message ID of the message to
3563            which this is a response. **Required**
3564        extended_headers (dict): A dictionary of name/value pairs for
3565            use as Extended Headers. **Optional**
3566        collection_name (str): the name of the TAXII Data Collection to which
3567            the action applies. **Required**
3568        message (str): additional information for the message recipient.
3569            **Optional**
3570        subscription_instances (list of SubscriptionInstance): **Optional**
3571    """
3572    message_type = MSG_MANAGE_COLLECTION_SUBSCRIPTION_RESPONSE
3573
3574    def __init__(self, message_id, in_response_to, extended_headers=None, collection_name=None, message=None, subscription_instances=None):
3575        super(ManageCollectionSubscriptionResponse, self).__init__(message_id, in_response_to, extended_headers=extended_headers)
3576        self.collection_name = collection_name
3577        self.message = message
3578        self.subscription_instances = subscription_instances or []
3579
3580    @TAXIIMessage.in_response_to.setter
3581    def in_response_to(self, value):
3582        do_check(value, 'in_response_to', regex_tuple=uri_regex)
3583        self._in_response_to = value
3584
3585    @property
3586    def collection_name(self):
3587        return self._collection_name
3588
3589    @collection_name.setter
3590    def collection_name(self, value):
3591        do_check(value, 'collection_name', regex_tuple=uri_regex)
3592        self._collection_name = value
3593
3594    @property
3595    def subscription_instances(self):
3596        return self._subscription_instances
3597
3598    @subscription_instances.setter
3599    def subscription_instances(self, value):
3600        do_check(value, 'subscription_instances', type=SubscriptionInstance)
3601        self._subscription_instances = value
3602
3603    def to_etree(self):
3604        xml = super(ManageCollectionSubscriptionResponse, self).to_etree()
3605        xml.attrib['collection_name'] = self.collection_name
3606        if self.message is not None:
3607            m = etree.SubElement(xml, '{%s}Message' % ns_map['taxii_11'])
3608            m.text = self.message
3609
3610        for subscription_instance in self.subscription_instances:
3611            xml.append(subscription_instance.to_etree())
3612
3613        return xml
3614
3615    def to_dict(self):
3616        d = super(ManageCollectionSubscriptionResponse, self).to_dict()
3617        d['collection_name'] = self.collection_name
3618        if self.message is not None:
3619            d['message'] = self.message
3620        d['subscription_instances'] = []
3621        for subscription_instance in self.subscription_instances:
3622            d['subscription_instances'].append(subscription_instance.to_dict())
3623
3624        return d
3625
3626    def to_text(self, line_prepend=''):
3627        s = super(ManageCollectionSubscriptionResponse, self).to_text(line_prepend)
3628        s += line_prepend + "  Collection Name: %s\n" % self.collection_name
3629        s += line_prepend + "  Message: %s\n" % self.message
3630        for si in self.subscription_instances:
3631            s += si.to_text(line_prepend + STD_INDENT)
3632
3633        return s
3634
3635    @classmethod
3636    def from_etree(cls, etree_xml):
3637        kwargs = {}
3638        kwargs['collection_name'] = etree_xml.attrib['collection_name']
3639
3640        kwargs['message'] = get_optional_text(etree_xml, './taxii_11:Message', ns_map)
3641
3642        kwargs['subscription_instances'] = []
3643        for si in etree_xml.xpath('./taxii_11:Subscription', namespaces=ns_map):
3644            kwargs['subscription_instances'].append(SubscriptionInstance.from_etree(si))
3645
3646        msg = super(ManageCollectionSubscriptionResponse, cls).from_etree(etree_xml, **kwargs)
3647        return msg
3648
3649    @classmethod
3650    def from_dict(cls, d):
3651        kwargs = {}
3652        kwargs['collection_name'] = d['collection_name']
3653
3654        kwargs['message'] = d.get('message')
3655
3656        kwargs['subscription_instances'] = []
3657        for instance in d['subscription_instances']:
3658            kwargs['subscription_instances'].append(SubscriptionInstance.from_dict(instance))
3659
3660        msg = super(ManageCollectionSubscriptionResponse, cls).from_dict(d, **kwargs)
3661        return msg
3662
3663
3664class SubscriptionInstance(TAXIIBase11):
3665
3666    """
3667    The Subscription Instance component of the Manage Collection Subscription
3668    Response message.
3669
3670    Args:
3671        subscription_id (str): the id of the subscription. **Required**
3672        status (str): One of :py:data:`SS_ACTIVE`, :py:data:`SS_PAUSED`, or
3673                :py:data:`SS_UNSUBSCRIBED`. **Optional**, defaults to "ACTIVE"
3674        subscription_parameters (SubscriptionParameters): the parameters
3675            for this subscription. **Optional** If provided, should match
3676            the request.
3677        push_parameters (PushParameters): the push parameters for this
3678            subscription. **Optional** If provided, should match the request.
3679        poll_instances (list of PollInstance): The Poll Services that can be
3680                polled to fulfill this subscription. **Optional**
3681    """
3682
3683    def __init__(self, subscription_id, status=SS_ACTIVE,
3684                 subscription_parameters=None, push_parameters=None,
3685                 poll_instances=None):
3686        self.subscription_id = subscription_id
3687        self.status = status
3688        self.subscription_parameters = subscription_parameters
3689        self.push_parameters = push_parameters
3690        self.poll_instances = poll_instances or []
3691
3692    @property
3693    def sort_key(self):
3694        return self.subscription_id
3695
3696    @property
3697    def subscription_id(self):
3698        return self._subscription_id
3699
3700    @subscription_id.setter
3701    def subscription_id(self, value):
3702        do_check(value, 'subscription_id', regex_tuple=uri_regex)
3703        self._subscription_id = value
3704
3705    @property
3706    def status(self):
3707        return self._status
3708
3709    @status.setter
3710    def status(self, value):
3711        do_check(value, 'status', value_tuple=SS_TYPES, can_be_none=True)
3712        self._status = value
3713
3714    @property
3715    def subscription_parameters(self):
3716        return self._subscription_parameters
3717
3718    @subscription_parameters.setter
3719    def subscription_parameters(self, value):
3720        do_check(value, 'subscription_parameters', type=SubscriptionParameters, can_be_none=True)
3721        self._subscription_parameters = value
3722
3723    @property
3724    def push_parameters(self):
3725        return self._push_parameters
3726
3727    @push_parameters.setter
3728    def push_parameters(self, value):
3729        do_check(value, 'push_parameters', type=PushParameters, can_be_none=True)
3730        self._push_parameters = value
3731
3732    @property
3733    def poll_instances(self):
3734        return self._poll_instances
3735
3736    @poll_instances.setter
3737    def poll_instances(self, value):
3738        do_check(value, 'poll_instances', type=PollInstance)
3739        self._poll_instances = value
3740
3741    def to_etree(self):
3742        si = etree.Element('{%s}Subscription' % ns_map['taxii_11'], nsmap=ns_map)
3743        if self.status is not None:
3744            si.attrib['status'] = self.status
3745
3746        subs_id = etree.SubElement(si, '{%s}Subscription_ID' % ns_map['taxii_11'])
3747        subs_id.text = self.subscription_id
3748
3749        if self.subscription_parameters is not None:
3750            si.append(self.subscription_parameters.to_etree())
3751
3752        if self.push_parameters is not None:
3753            si.append(self.push_parameters.to_etree())
3754
3755        for pi in self.poll_instances:
3756            si.append(pi.to_etree())
3757
3758        return si
3759
3760    def to_dict(self):
3761        d = {}
3762        d['status'] = self.status
3763        d['subscription_id'] = self.subscription_id
3764        d['subscription_parameters'] = None
3765        if self.subscription_parameters is not None:
3766            d['subscription_parameters'] = self.subscription_parameters.to_dict()
3767
3768        d['push_parameters'] = None
3769        if self.push_parameters is not None:
3770            d['push_parameters'] = self.push_parameters.to_dict()
3771
3772        d['poll_instances'] = []
3773        for pi in self.poll_instances:
3774            d['poll_instances'].append(pi.to_dict())
3775
3776        return d
3777
3778    def to_text(self, line_prepend=''):
3779        s = line_prepend + "=== Subscription Instance ===\n"
3780        s += line_prepend + "  Status: %s\n" % self.status
3781        s += line_prepend + "  Subscription ID: %s\n" % self.subscription_id
3782        if self.subscription_parameters:
3783            s += self.subscription_parameters.to_text(line_prepend + STD_INDENT)
3784        if self.push_parameters:
3785            s += self.push_parameters.to_text(line_prepend + STD_INDENT)
3786        for pi in self.poll_instances:
3787            s += pi.to_text(line_prepend + STD_INDENT)
3788
3789        return s
3790
3791    @staticmethod
3792    def from_etree(etree_xml):
3793
3794        status = get_optional(etree_xml, './@status', ns_map)
3795
3796        subscription_id = get_required(etree_xml, './taxii_11:Subscription_ID', ns_map).text
3797
3798        subscription_parameters = None
3799        subscription_parameters_el = get_optional(etree_xml, './taxii_11:Subscription_Parameters', ns_map)
3800        if subscription_parameters_el is not None:
3801            subscription_parameters = SubscriptionParameters.from_etree(subscription_parameters_el)
3802
3803        push_parameters = None
3804        push_parameters_el = get_optional(etree_xml, './taxii_11:Push_Parameters', ns_map)
3805        if push_parameters_el is not None:
3806            push_parameters = PushParameters.from_etree(push_parameters_el)
3807
3808        poll_instances = []
3809        for pi in etree_xml.xpath('./taxii_11:Poll_Instance', namespaces=ns_map):
3810            poll_instances.append(PollInstance.from_etree(pi))
3811
3812        return SubscriptionInstance(subscription_id, status, subscription_parameters, push_parameters, poll_instances)
3813
3814    @staticmethod
3815    def from_dict(d):
3816        subscription_id = d['subscription_id']
3817        status = d.get('status')
3818
3819        subscription_parameters = None
3820        if d.get('subscription_parameters') is not None:
3821            subscription_parameters = SubscriptionParameters.from_dict(d['subscription_parameters'])
3822
3823        push_parameters = None
3824        if d.get('push_parameters') is not None:
3825            push_parameters = PushParameters.from_dict(d['push_parameters'])
3826
3827        poll_instances = []
3828        if 'poll_instances' in d:
3829            for pi in d['poll_instances']:
3830                poll_instances.append(PollInstance.from_dict(pi))
3831
3832        return SubscriptionInstance(subscription_id, status, subscription_parameters, push_parameters, poll_instances)
3833
3834
3835class PollInstance(TAXIIBase11):
3836
3837    """
3838    The Poll Instance component of the Manage Collection Subscription
3839    Response message.
3840
3841    Args:
3842        poll_protocol (str): The protocol binding supported by this
3843            instance of a Polling Service. **Required**
3844        poll_address (str): the address of the TAXII Daemon hosting
3845            this Poll Service. **Required**
3846        poll_message_bindings (list of str): one or more message bindings
3847            that can be used when interacting with this Poll Service
3848            instance. **Required**
3849    """
3850
3851    def __init__(self, poll_protocol, poll_address, poll_message_bindings=None):
3852        self.poll_protocol = poll_protocol
3853        self.poll_address = poll_address
3854        self.poll_message_bindings = poll_message_bindings or []
3855
3856    @property
3857    def sort_key(self):
3858        return self.poll_address
3859
3860    @property
3861    def poll_protocol(self):
3862        return self._poll_protocol
3863
3864    @poll_protocol.setter
3865    def poll_protocol(self, value):
3866        do_check(value, 'poll_protocol', regex_tuple=uri_regex)
3867        self._poll_protocol = value
3868
3869    @property
3870    def poll_message_bindings(self):
3871        return self._poll_message_bindings
3872
3873    @poll_message_bindings.setter
3874    def poll_message_bindings(self, value):
3875        do_check(value, 'poll_message_bindings', regex_tuple=uri_regex)
3876        self._poll_message_bindings = value
3877
3878    def to_etree(self):
3879        xml = etree.Element('{%s}Poll_Instance' % ns_map['taxii_11'])
3880
3881        pb = etree.SubElement(xml, '{%s}Protocol_Binding' % ns_map['taxii_11'])
3882        pb.text = self.poll_protocol
3883
3884        a = etree.SubElement(xml, '{%s}Address' % ns_map['taxii_11'])
3885        a.text = self.poll_address
3886
3887        for binding in self.poll_message_bindings:
3888            b = etree.SubElement(xml, '{%s}Message_Binding' % ns_map['taxii_11'])
3889            b.text = binding
3890
3891        return xml
3892
3893    def to_dict(self):
3894        d = {}
3895
3896        d['poll_protocol'] = self.poll_protocol
3897        d['poll_address'] = self.poll_address
3898        d['poll_message_bindings'] = []
3899        for binding in self.poll_message_bindings:
3900            d['poll_message_bindings'].append(binding)
3901
3902        return d
3903
3904    def to_text(self, line_prepend=''):
3905        s = line_prepend + "=== Poll Instance ===\n"
3906        s += line_prepend + "  Protocol Binding: %s\n" % self.poll_protocol
3907        s += line_prepend + "  Address: %s\n" % self.poll_address
3908        for mb in self.poll_message_bindings:
3909            s += line_prepend + "  Message Binding: %s\n" % mb
3910        return s
3911
3912    @staticmethod
3913    def from_etree(etree_xml):
3914        poll_protocol = get_required(etree_xml, './taxii_11:Protocol_Binding', ns_map).text
3915        address = get_required(etree_xml, './taxii_11:Address', ns_map).text
3916
3917        poll_message_bindings = []
3918        for b in etree_xml.xpath('./taxii_11:Message_Binding', namespaces=ns_map):
3919            poll_message_bindings.append(b.text)
3920
3921        return PollInstance(poll_protocol, address, poll_message_bindings)
3922
3923    @staticmethod
3924    def from_dict(d):
3925        return PollInstance(**d)
3926
3927
3928class PollFulfillmentRequest(TAXIIRequestMessage):
3929
3930    """
3931    A TAXII Poll Fulfillment Request message.
3932
3933    Args:
3934        message_id (str): A value identifying this message. **Required**
3935        extended_headers (dict): A dictionary of name/value pairs for
3936            use as Extended Headers. **Optional**
3937        collection_name (str): the name of the TAXII Data Collection to which the
3938            action applies. **Required**
3939        result_id (str): The result id of the requested result. **Required**
3940        result_part_number (int): The part number being requested. **Required**
3941    """
3942    message_type = MSG_POLL_FULFILLMENT_REQUEST
3943
3944    def __init__(self, message_id, extended_headers=None,
3945                 collection_name=None, result_id=None, result_part_number=None):
3946        super(PollFulfillmentRequest, self).__init__(message_id, extended_headers=extended_headers)
3947        self.collection_name = collection_name
3948        self.result_id = result_id
3949        self.result_part_number = result_part_number
3950
3951    @property
3952    def collection_name(self):
3953        return self._collection_name
3954
3955    @collection_name.setter
3956    def collection_name(self, value):
3957        do_check(value, 'collection_name', regex_tuple=uri_regex)
3958        self._collection_name = value
3959
3960    @property
3961    def result_id(self):
3962        return self._result_id
3963
3964    @result_id.setter
3965    def result_id(self, value):
3966        do_check(value, 'result_id', regex_tuple=uri_regex)
3967        self._result_id = value
3968
3969    @property
3970    def result_part_number(self):
3971        return self._result_part_number
3972
3973    @result_part_number.setter
3974    def result_part_number(self, value):
3975        do_check(value, 'result_part_number', type=int)
3976        self._result_part_number = value
3977
3978    def to_etree(self):
3979        xml = super(PollFulfillmentRequest, self).to_etree()
3980        xml.attrib['collection_name'] = self.collection_name
3981        xml.attrib['result_id'] = self.result_id
3982        xml.attrib['result_part_number'] = str(self.result_part_number)
3983        return xml
3984
3985    def to_dict(self):
3986        d = super(PollFulfillmentRequest, self).to_dict()
3987        d['collection_name'] = self.collection_name
3988        d['result_id'] = self.result_id
3989        d['result_part_number'] = self.result_part_number
3990        return d
3991
3992    def to_text(self, line_prepend=''):
3993        s = super(PollFulfillmentRequest, self).to_text(line_prepend)
3994        s += line_prepend + "  Collection Name: %s\n" % self.collection_name
3995        s += line_prepend + "  Result ID: %s\n" % self.result_id
3996        s += line_prepend + "  Result Part Number: %s\n" % self.result_part_number
3997        return s
3998
3999    @classmethod
4000    def from_etree(cls, etree_xml):
4001
4002        kwargs = {}
4003        kwargs['collection_name'] = etree_xml.attrib['collection_name']
4004        kwargs['result_id'] = etree_xml.attrib['result_id']
4005        kwargs['result_part_number'] = int(etree_xml.attrib['result_part_number'])
4006
4007        return super(PollFulfillmentRequest, cls).from_etree(etree_xml, **kwargs)
4008
4009    @classmethod
4010    def from_dict(cls, d):
4011
4012        kwargs = {}
4013        kwargs['collection_name'] = d['collection_name']
4014        kwargs['result_id'] = d['result_id']
4015        kwargs['result_part_number'] = int(d['result_part_number'])
4016
4017        return super(PollFulfillmentRequest, cls).from_dict(d, **kwargs)
4018
4019
4020########################################################
4021# EVERYTHING BELOW HERE IS FOR BACKWARDS COMPATIBILITY #
4022########################################################
4023
4024# Add top-level classes as nested classes for backwards compatibility
4025DiscoveryResponse.ServiceInstance = ServiceInstance
4026CollectionInformationResponse.CollectionInformation = CollectionInformation
4027CollectionInformation.PushMethod = PushMethod
4028CollectionInformation.PollingServiceInstance = PollingServiceInstance
4029CollectionInformation.SubscriptionMethod = SubscriptionMethod
4030CollectionInformation.ReceivingInboxService = ReceivingInboxService
4031ManageCollectionSubscriptionResponse.PollInstance = PollInstance
4032ManageCollectionSubscriptionResponse.SubscriptionInstance = SubscriptionInstance
4033PollRequest.PollParameters = PollParameters
4034InboxMessage.SubscriptionInformation = SubscriptionInformation
4035
4036# Constants not imported in `from constants import *`
4037
4038MSG_TYPES = MSG_TYPES_11
4039ST_TYPES = ST_TYPES_11
4040ACT_TYPES = ACT_TYPES_11
4041SS_TYPES = SS_TYPES_11
4042RT_TYPES = RT_TYPES_11
4043CT_TYPES = CT_TYPES_11
4044SVC_TYPES = SVC_TYPES_11
4045SD_TYPES = SD_TYPES_11
4046
4047from .common import (generate_message_id)
4048