1# Copyright 2012, Google Inc.
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7#
8#     * Redistributions of source code must retain the above copyright
9# notice, this list of conditions and the following disclaimer.
10#     * Redistributions in binary form must reproduce the above
11# copyright notice, this list of conditions and the following disclaimer
12# in the documentation and/or other materials provided with the
13# distribution.
14#     * Neither the name of Google Inc. nor the names of its
15# contributors may be used to endorse or promote products derived from
16# this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31from mod_pywebsocket import common
32from mod_pywebsocket import util
33from mod_pywebsocket.http_header_util import quote_if_necessary
34
35
36# The list of available server side extension processor classes.
37_available_processors = {}
38_compression_extension_names = []
39
40
41class ExtensionProcessorInterface(object):
42
43    def __init__(self, request):
44        self._logger = util.get_class_logger(self)
45
46        self._request = request
47        self._active = True
48
49    def request(self):
50        return self._request
51
52    def name(self):
53        return None
54
55    def check_consistency_with_other_processors(self, processors):
56        pass
57
58    def set_active(self, active):
59        self._active = active
60
61    def is_active(self):
62        return self._active
63
64    def _get_extension_response_internal(self):
65        return None
66
67    def get_extension_response(self):
68        if not self._active:
69            self._logger.debug('Extension %s is deactivated', self.name())
70            return None
71
72        response = self._get_extension_response_internal()
73        if response is None:
74            self._active = False
75        return response
76
77    def _setup_stream_options_internal(self, stream_options):
78        pass
79
80    def setup_stream_options(self, stream_options):
81        if self._active:
82            self._setup_stream_options_internal(stream_options)
83
84
85def _log_outgoing_compression_ratio(
86        logger, original_bytes, filtered_bytes, average_ratio):
87    # Print inf when ratio is not available.
88    ratio = float('inf')
89    if original_bytes != 0:
90        ratio = float(filtered_bytes) / original_bytes
91
92    logger.debug('Outgoing compression ratio: %f (average: %f)' %
93            (ratio, average_ratio))
94
95
96def _log_incoming_compression_ratio(
97        logger, received_bytes, filtered_bytes, average_ratio):
98    # Print inf when ratio is not available.
99    ratio = float('inf')
100    if filtered_bytes != 0:
101        ratio = float(received_bytes) / filtered_bytes
102
103    logger.debug('Incoming compression ratio: %f (average: %f)' %
104            (ratio, average_ratio))
105
106
107def _parse_window_bits(bits):
108    """Return parsed integer value iff the given string conforms to the
109    grammar of the window bits extension parameters.
110    """
111
112    if bits is None:
113        raise ValueError('Value is required')
114
115    # For non integer values such as "10.0", ValueError will be raised.
116    int_bits = int(bits)
117
118    # First condition is to drop leading zero case e.g. "08".
119    if bits != str(int_bits) or int_bits < 8 or int_bits > 15:
120        raise ValueError('Invalid value: %r' % bits)
121
122    return int_bits
123
124
125class _AverageRatioCalculator(object):
126    """Stores total bytes of original and result data, and calculates average
127    result / original ratio.
128    """
129
130    def __init__(self):
131        self._total_original_bytes = 0
132        self._total_result_bytes = 0
133
134    def add_original_bytes(self, value):
135        self._total_original_bytes += value
136
137    def add_result_bytes(self, value):
138        self._total_result_bytes += value
139
140    def get_average_ratio(self):
141        if self._total_original_bytes != 0:
142            return (float(self._total_result_bytes) /
143                    self._total_original_bytes)
144        else:
145            return float('inf')
146
147
148class DeflateFrameExtensionProcessor(ExtensionProcessorInterface):
149    """deflate-frame extension processor.
150
151    Specification:
152    http://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate
153    """
154
155    _WINDOW_BITS_PARAM = 'max_window_bits'
156    _NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover'
157
158    def __init__(self, request):
159        ExtensionProcessorInterface.__init__(self, request)
160        self._logger = util.get_class_logger(self)
161
162        self._response_window_bits = None
163        self._response_no_context_takeover = False
164        self._bfinal = False
165
166        # Calculates
167        #     (Total outgoing bytes supplied to this filter) /
168        #     (Total bytes sent to the network after applying this filter)
169        self._outgoing_average_ratio_calculator = _AverageRatioCalculator()
170
171        # Calculates
172        #     (Total bytes received from the network) /
173        #     (Total incoming bytes obtained after applying this filter)
174        self._incoming_average_ratio_calculator = _AverageRatioCalculator()
175
176    def name(self):
177        return common.DEFLATE_FRAME_EXTENSION
178
179    def _get_extension_response_internal(self):
180        # Any unknown parameter will be just ignored.
181
182        window_bits = None
183        if self._request.has_parameter(self._WINDOW_BITS_PARAM):
184            window_bits = self._request.get_parameter_value(
185                self._WINDOW_BITS_PARAM)
186            try:
187                window_bits = _parse_window_bits(window_bits)
188            except ValueError, e:
189                return None
190
191        no_context_takeover = self._request.has_parameter(
192            self._NO_CONTEXT_TAKEOVER_PARAM)
193        if (no_context_takeover and
194            self._request.get_parameter_value(
195                self._NO_CONTEXT_TAKEOVER_PARAM) is not None):
196            return None
197
198        self._rfc1979_deflater = util._RFC1979Deflater(
199            window_bits, no_context_takeover)
200
201        self._rfc1979_inflater = util._RFC1979Inflater()
202
203        self._compress_outgoing = True
204
205        response = common.ExtensionParameter(self._request.name())
206
207        if self._response_window_bits is not None:
208            response.add_parameter(
209                self._WINDOW_BITS_PARAM, str(self._response_window_bits))
210        if self._response_no_context_takeover:
211            response.add_parameter(
212                self._NO_CONTEXT_TAKEOVER_PARAM, None)
213
214        self._logger.debug(
215            'Enable %s extension ('
216            'request: window_bits=%s; no_context_takeover=%r, '
217            'response: window_wbits=%s; no_context_takeover=%r)' %
218            (self._request.name(),
219             window_bits,
220             no_context_takeover,
221             self._response_window_bits,
222             self._response_no_context_takeover))
223
224        return response
225
226    def _setup_stream_options_internal(self, stream_options):
227
228        class _OutgoingFilter(object):
229
230            def __init__(self, parent):
231                self._parent = parent
232
233            def filter(self, frame):
234                self._parent._outgoing_filter(frame)
235
236        class _IncomingFilter(object):
237
238            def __init__(self, parent):
239                self._parent = parent
240
241            def filter(self, frame):
242                self._parent._incoming_filter(frame)
243
244        stream_options.outgoing_frame_filters.append(
245            _OutgoingFilter(self))
246        stream_options.incoming_frame_filters.insert(
247            0, _IncomingFilter(self))
248
249    def set_response_window_bits(self, value):
250        self._response_window_bits = value
251
252    def set_response_no_context_takeover(self, value):
253        self._response_no_context_takeover = value
254
255    def set_bfinal(self, value):
256        self._bfinal = value
257
258    def enable_outgoing_compression(self):
259        self._compress_outgoing = True
260
261    def disable_outgoing_compression(self):
262        self._compress_outgoing = False
263
264    def _outgoing_filter(self, frame):
265        """Transform outgoing frames. This method is called only by
266        an _OutgoingFilter instance.
267        """
268
269        original_payload_size = len(frame.payload)
270        self._outgoing_average_ratio_calculator.add_original_bytes(
271                original_payload_size)
272
273        if (not self._compress_outgoing or
274            common.is_control_opcode(frame.opcode)):
275            self._outgoing_average_ratio_calculator.add_result_bytes(
276                    original_payload_size)
277            return
278
279        frame.payload = self._rfc1979_deflater.filter(
280            frame.payload, bfinal=self._bfinal)
281        frame.rsv1 = 1
282
283        filtered_payload_size = len(frame.payload)
284        self._outgoing_average_ratio_calculator.add_result_bytes(
285                filtered_payload_size)
286
287        _log_outgoing_compression_ratio(
288                self._logger,
289                original_payload_size,
290                filtered_payload_size,
291                self._outgoing_average_ratio_calculator.get_average_ratio())
292
293    def _incoming_filter(self, frame):
294        """Transform incoming frames. This method is called only by
295        an _IncomingFilter instance.
296        """
297
298        received_payload_size = len(frame.payload)
299        self._incoming_average_ratio_calculator.add_result_bytes(
300                received_payload_size)
301
302        if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode):
303            self._incoming_average_ratio_calculator.add_original_bytes(
304                    received_payload_size)
305            return
306
307        frame.payload = self._rfc1979_inflater.filter(frame.payload)
308        frame.rsv1 = 0
309
310        filtered_payload_size = len(frame.payload)
311        self._incoming_average_ratio_calculator.add_original_bytes(
312                filtered_payload_size)
313
314        _log_incoming_compression_ratio(
315                self._logger,
316                received_payload_size,
317                filtered_payload_size,
318                self._incoming_average_ratio_calculator.get_average_ratio())
319
320
321_available_processors[common.DEFLATE_FRAME_EXTENSION] = (
322    DeflateFrameExtensionProcessor)
323_compression_extension_names.append(common.DEFLATE_FRAME_EXTENSION)
324
325_available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = (
326    DeflateFrameExtensionProcessor)
327_compression_extension_names.append(common.X_WEBKIT_DEFLATE_FRAME_EXTENSION)
328
329
330class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface):
331    """permessage-deflate extension processor.
332
333    Specification:
334    http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-08
335    """
336
337    _SERVER_MAX_WINDOW_BITS_PARAM = 'server_max_window_bits'
338    _SERVER_NO_CONTEXT_TAKEOVER_PARAM = 'server_no_context_takeover'
339    _CLIENT_MAX_WINDOW_BITS_PARAM = 'client_max_window_bits'
340    _CLIENT_NO_CONTEXT_TAKEOVER_PARAM = 'client_no_context_takeover'
341
342    def __init__(self, request, draft08=True):
343        """Construct PerMessageDeflateExtensionProcessor
344
345        Args:
346            draft08: Follow the constraints on the parameters that were not
347                specified for permessage-compress but are specified for
348                permessage-deflate as on
349                draft-ietf-hybi-permessage-compression-08.
350        """
351
352        ExtensionProcessorInterface.__init__(self, request)
353        self._logger = util.get_class_logger(self)
354
355        self._preferred_client_max_window_bits = None
356        self._client_no_context_takeover = False
357
358        self._draft08 = draft08
359
360    def name(self):
361        # This method returns "deflate" (not "permessage-deflate") for
362        # compatibility.
363        return 'deflate'
364
365    def _get_extension_response_internal(self):
366        if self._draft08:
367            for name in self._request.get_parameter_names():
368                if name not in [self._SERVER_MAX_WINDOW_BITS_PARAM,
369                                self._SERVER_NO_CONTEXT_TAKEOVER_PARAM,
370                                self._CLIENT_MAX_WINDOW_BITS_PARAM]:
371                    self._logger.debug('Unknown parameter: %r', name)
372                    return None
373        else:
374            # Any unknown parameter will be just ignored.
375            pass
376
377        server_max_window_bits = None
378        if self._request.has_parameter(self._SERVER_MAX_WINDOW_BITS_PARAM):
379            server_max_window_bits = self._request.get_parameter_value(
380                    self._SERVER_MAX_WINDOW_BITS_PARAM)
381            try:
382                server_max_window_bits = _parse_window_bits(
383                    server_max_window_bits)
384            except ValueError, e:
385                self._logger.debug('Bad %s parameter: %r',
386                                   self._SERVER_MAX_WINDOW_BITS_PARAM,
387                                   e)
388                return None
389
390        server_no_context_takeover = self._request.has_parameter(
391            self._SERVER_NO_CONTEXT_TAKEOVER_PARAM)
392        if (server_no_context_takeover and
393            self._request.get_parameter_value(
394                self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) is not None):
395            self._logger.debug('%s parameter must not have a value: %r',
396                               self._SERVER_NO_CONTEXT_TAKEOVER_PARAM,
397                               server_no_context_takeover)
398            return None
399
400        # client_max_window_bits from a client indicates whether the client can
401        # accept client_max_window_bits from a server or not.
402        client_client_max_window_bits = self._request.has_parameter(
403            self._CLIENT_MAX_WINDOW_BITS_PARAM)
404        if (self._draft08 and
405            client_client_max_window_bits and
406            self._request.get_parameter_value(
407                self._CLIENT_MAX_WINDOW_BITS_PARAM) is not None):
408            self._logger.debug('%s parameter must not have a value in a '
409                               'client\'s opening handshake: %r',
410                               self._CLIENT_MAX_WINDOW_BITS_PARAM,
411                               client_client_max_window_bits)
412            return None
413
414        self._rfc1979_deflater = util._RFC1979Deflater(
415            server_max_window_bits, server_no_context_takeover)
416
417        # Note that we prepare for incoming messages compressed with window
418        # bits upto 15 regardless of the client_max_window_bits value to be
419        # sent to the client.
420        self._rfc1979_inflater = util._RFC1979Inflater()
421
422        self._framer = _PerMessageDeflateFramer(
423            server_max_window_bits, server_no_context_takeover)
424        self._framer.set_bfinal(False)
425        self._framer.set_compress_outgoing_enabled(True)
426
427        response = common.ExtensionParameter(self._request.name())
428
429        if server_max_window_bits is not None:
430            response.add_parameter(
431                self._SERVER_MAX_WINDOW_BITS_PARAM,
432                str(server_max_window_bits))
433
434        if server_no_context_takeover:
435            response.add_parameter(
436                self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, None)
437
438        if self._preferred_client_max_window_bits is not None:
439            if self._draft08 and not client_client_max_window_bits:
440                self._logger.debug('Processor is configured to use %s but '
441                                   'the client cannot accept it',
442                                   self._CLIENT_MAX_WINDOW_BITS_PARAM)
443                return None
444            response.add_parameter(
445                self._CLIENT_MAX_WINDOW_BITS_PARAM,
446                str(self._preferred_client_max_window_bits))
447
448        if self._client_no_context_takeover:
449            response.add_parameter(
450                self._CLIENT_NO_CONTEXT_TAKEOVER_PARAM, None)
451
452        self._logger.debug(
453            'Enable %s extension ('
454            'request: server_max_window_bits=%s; '
455            'server_no_context_takeover=%r, '
456            'response: client_max_window_bits=%s; '
457            'client_no_context_takeover=%r)' %
458            (self._request.name(),
459             server_max_window_bits,
460             server_no_context_takeover,
461             self._preferred_client_max_window_bits,
462             self._client_no_context_takeover))
463
464        return response
465
466    def _setup_stream_options_internal(self, stream_options):
467        self._framer.setup_stream_options(stream_options)
468
469    def set_client_max_window_bits(self, value):
470        """If this option is specified, this class adds the
471        client_max_window_bits extension parameter to the handshake response,
472        but doesn't reduce the LZ77 sliding window size of its inflater.
473        I.e., you can use this for testing client implementation but cannot
474        reduce memory usage of this class.
475
476        If this method has been called with True and an offer without the
477        client_max_window_bits extension parameter is received,
478        - (When processing the permessage-deflate extension) this processor
479          declines the request.
480        - (When processing the permessage-compress extension) this processor
481          accepts the request.
482        """
483
484        self._preferred_client_max_window_bits = value
485
486    def set_client_no_context_takeover(self, value):
487        """If this option is specified, this class adds the
488        client_no_context_takeover extension parameter to the handshake
489        response, but doesn't reset inflater for each message. I.e., you can
490        use this for testing client implementation but cannot reduce memory
491        usage of this class.
492        """
493
494        self._client_no_context_takeover = value
495
496    def set_bfinal(self, value):
497        self._framer.set_bfinal(value)
498
499    def enable_outgoing_compression(self):
500        self._framer.set_compress_outgoing_enabled(True)
501
502    def disable_outgoing_compression(self):
503        self._framer.set_compress_outgoing_enabled(False)
504
505
506class _PerMessageDeflateFramer(object):
507    """A framer for extensions with per-message DEFLATE feature."""
508
509    def __init__(self, deflate_max_window_bits, deflate_no_context_takeover):
510        self._logger = util.get_class_logger(self)
511
512        self._rfc1979_deflater = util._RFC1979Deflater(
513            deflate_max_window_bits, deflate_no_context_takeover)
514
515        self._rfc1979_inflater = util._RFC1979Inflater()
516
517        self._bfinal = False
518
519        self._compress_outgoing_enabled = False
520
521        # True if a message is fragmented and compression is ongoing.
522        self._compress_ongoing = False
523
524        # Calculates
525        #     (Total outgoing bytes supplied to this filter) /
526        #     (Total bytes sent to the network after applying this filter)
527        self._outgoing_average_ratio_calculator = _AverageRatioCalculator()
528
529        # Calculates
530        #     (Total bytes received from the network) /
531        #     (Total incoming bytes obtained after applying this filter)
532        self._incoming_average_ratio_calculator = _AverageRatioCalculator()
533
534    def set_bfinal(self, value):
535        self._bfinal = value
536
537    def set_compress_outgoing_enabled(self, value):
538        self._compress_outgoing_enabled = value
539
540    def _process_incoming_message(self, message, decompress):
541        if not decompress:
542            return message
543
544        received_payload_size = len(message)
545        self._incoming_average_ratio_calculator.add_result_bytes(
546                received_payload_size)
547
548        message = self._rfc1979_inflater.filter(message)
549
550        filtered_payload_size = len(message)
551        self._incoming_average_ratio_calculator.add_original_bytes(
552                filtered_payload_size)
553
554        _log_incoming_compression_ratio(
555                self._logger,
556                received_payload_size,
557                filtered_payload_size,
558                self._incoming_average_ratio_calculator.get_average_ratio())
559
560        return message
561
562    def _process_outgoing_message(self, message, end, binary):
563        if not binary:
564            message = message.encode('utf-8')
565
566        if not self._compress_outgoing_enabled:
567            return message
568
569        original_payload_size = len(message)
570        self._outgoing_average_ratio_calculator.add_original_bytes(
571            original_payload_size)
572
573        message = self._rfc1979_deflater.filter(
574            message, end=end, bfinal=self._bfinal)
575
576        filtered_payload_size = len(message)
577        self._outgoing_average_ratio_calculator.add_result_bytes(
578            filtered_payload_size)
579
580        _log_outgoing_compression_ratio(
581                self._logger,
582                original_payload_size,
583                filtered_payload_size,
584                self._outgoing_average_ratio_calculator.get_average_ratio())
585
586        if not self._compress_ongoing:
587            self._outgoing_frame_filter.set_compression_bit()
588        self._compress_ongoing = not end
589        return message
590
591    def _process_incoming_frame(self, frame):
592        if frame.rsv1 == 1 and not common.is_control_opcode(frame.opcode):
593            self._incoming_message_filter.decompress_next_message()
594            frame.rsv1 = 0
595
596    def _process_outgoing_frame(self, frame, compression_bit):
597        if (not compression_bit or
598            common.is_control_opcode(frame.opcode)):
599            return
600
601        frame.rsv1 = 1
602
603    def setup_stream_options(self, stream_options):
604        """Creates filters and sets them to the StreamOptions."""
605
606        class _OutgoingMessageFilter(object):
607
608            def __init__(self, parent):
609                self._parent = parent
610
611            def filter(self, message, end=True, binary=False):
612                return self._parent._process_outgoing_message(
613                    message, end, binary)
614
615        class _IncomingMessageFilter(object):
616
617            def __init__(self, parent):
618                self._parent = parent
619                self._decompress_next_message = False
620
621            def decompress_next_message(self):
622                self._decompress_next_message = True
623
624            def filter(self, message):
625                message = self._parent._process_incoming_message(
626                    message, self._decompress_next_message)
627                self._decompress_next_message = False
628                return message
629
630        self._outgoing_message_filter = _OutgoingMessageFilter(self)
631        self._incoming_message_filter = _IncomingMessageFilter(self)
632        stream_options.outgoing_message_filters.append(
633            self._outgoing_message_filter)
634        stream_options.incoming_message_filters.append(
635            self._incoming_message_filter)
636
637        class _OutgoingFrameFilter(object):
638
639            def __init__(self, parent):
640                self._parent = parent
641                self._set_compression_bit = False
642
643            def set_compression_bit(self):
644                self._set_compression_bit = True
645
646            def filter(self, frame):
647                self._parent._process_outgoing_frame(
648                    frame, self._set_compression_bit)
649                self._set_compression_bit = False
650
651        class _IncomingFrameFilter(object):
652
653            def __init__(self, parent):
654                self._parent = parent
655
656            def filter(self, frame):
657                self._parent._process_incoming_frame(frame)
658
659        self._outgoing_frame_filter = _OutgoingFrameFilter(self)
660        self._incoming_frame_filter = _IncomingFrameFilter(self)
661        stream_options.outgoing_frame_filters.append(
662            self._outgoing_frame_filter)
663        stream_options.incoming_frame_filters.append(
664            self._incoming_frame_filter)
665
666        stream_options.encode_text_message_to_utf8 = False
667
668
669_available_processors[common.PERMESSAGE_DEFLATE_EXTENSION] = (
670        PerMessageDeflateExtensionProcessor)
671# TODO(tyoshino): Reorganize class names.
672_compression_extension_names.append('deflate')
673
674
675class MuxExtensionProcessor(ExtensionProcessorInterface):
676    """WebSocket multiplexing extension processor."""
677
678    _QUOTA_PARAM = 'quota'
679
680    def __init__(self, request):
681        ExtensionProcessorInterface.__init__(self, request)
682        self._quota = 0
683        self._extensions = []
684
685    def name(self):
686        return common.MUX_EXTENSION
687
688    def check_consistency_with_other_processors(self, processors):
689        before_mux = True
690        for processor in processors:
691            name = processor.name()
692            if name == self.name():
693                before_mux = False
694                continue
695            if not processor.is_active():
696                continue
697            if before_mux:
698                # Mux extension cannot be used after extensions
699                # that depend on frame boundary, extension data field, or any
700                # reserved bits which are attributed to each frame.
701                if (name == common.DEFLATE_FRAME_EXTENSION or
702                    name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION):
703                    self.set_active(False)
704                    return
705            else:
706                # Mux extension should not be applied before any history-based
707                # compression extension.
708                if (name == 'deflate' or
709                    name == common.DEFLATE_FRAME_EXTENSION or
710                    name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION):
711                    self.set_active(False)
712                    return
713
714    def _get_extension_response_internal(self):
715        self._active = False
716        quota = self._request.get_parameter_value(self._QUOTA_PARAM)
717        if quota is not None:
718            try:
719                quota = int(quota)
720            except ValueError, e:
721                return None
722            if quota < 0 or quota >= 2 ** 32:
723                return None
724            self._quota = quota
725
726        self._active = True
727        return common.ExtensionParameter(common.MUX_EXTENSION)
728
729    def _setup_stream_options_internal(self, stream_options):
730        pass
731
732    def set_quota(self, quota):
733        self._quota = quota
734
735    def quota(self):
736        return self._quota
737
738    def set_extensions(self, extensions):
739        self._extensions = extensions
740
741    def extensions(self):
742        return self._extensions
743
744
745_available_processors[common.MUX_EXTENSION] = MuxExtensionProcessor
746
747
748def get_extension_processor(extension_request):
749    """Given an ExtensionParameter representing an extension offer received
750    from a client, configures and returns an instance of the corresponding
751    extension processor class.
752    """
753
754    processor_class = _available_processors.get(extension_request.name())
755    if processor_class is None:
756        return None
757    return processor_class(extension_request)
758
759
760def is_compression_extension(extension_name):
761    return extension_name in _compression_extension_names
762
763
764# vi:sts=4 sw=4 et
765