예제 #1
0
 def test_dont_truncate(self):
     """
     Tests that truncation is not performed if unnecessary.
     """
     # This shouldn't need to be truncated
     txt = simplestring(20)
     aps = {"alert": txt}
     self.assertEqual(txt, truncate(payload_for_aps(aps), 256)["aps"]["alert"])
예제 #2
0
 def test_truncate_alert(self):
     """
     Tests that the 'alert' string field will be truncated when needed.
     """
     overhead = len(json_encode(payload_for_aps({"alert": ""})))
     txt = simplestring(10)
     aps = {"alert": txt}
     self.assertEquals(
         txt[:5],
         truncate(payload_for_aps(aps), overhead + 5)["aps"]["alert"])
예제 #3
0
 def test_truncate_loc_arg(self):
     """
     Tests that the 'alert' 'loc-args' field will be truncated when needed.
     (Tests with one loc arg)
     """
     overhead = len(json_encode(payload_for_aps({"alert": {"loc-args": [""]}})))
     txt = simplestring(10)
     aps = {"alert": {"loc-args": [txt]}}
     self.assertEqual(
         txt[:5],
         truncate(payload_for_aps(aps), overhead + 5)["aps"]["alert"]["loc-args"][0],
     )
예제 #4
0
 def test_truncate_string_with_multibyte(self):
     """
     Tests that truncation works as expected on strings containing one
     multibyte character.
     """
     overhead = len(json_encode(payload_for_aps({"alert": ""})))
     txt = u"\U0001F430" + simplestring(30)
     aps = {"alert": txt}
     # NB. The number of characters of the string we get is dependent
     # on the json encoding used.
     self.assertEquals(
         txt[:17],
         truncate(payload_for_aps(aps), overhead + 20)["aps"]["alert"])
예제 #5
0
 def test_truncate_multibyte(self):
     """
     Tests that truncation works as expected on strings containing only
     multibyte characters.
     """
     overhead = len(json_encode(payload_for_aps({"alert": ""})))
     txt = sillystring(30)
     aps = {"alert": txt}
     trunc = truncate(payload_for_aps(aps), overhead + 30)
     # The string is all 4 byte characters so the trunctaed UTF-8 string
     # should be a multiple of 4 bytes long
     self.assertEquals(len(trunc["aps"]["alert"].encode()) % 4, 0)
     # NB. The number of characters of the string we get is dependent
     # on the json encoding used.
     self.assertEquals(txt[:7], trunc["aps"]["alert"])
예제 #6
0
    async def _dispatch_notification_unlimited(
            self, n: Notification, device: Device,
            context: NotificationContext) -> List[str]:
        log = NotificationLoggerAdapter(logger,
                                        {"request_id": context.request_id})

        # The pushkey is kind of secret because you can use it to send push
        # to someone.
        # span_tags = {"pushkey": device.pushkey}
        span_tags: Dict[str, int] = {}

        with self.sygnal.tracer.start_span(
                "apns_dispatch", tags=span_tags,
                child_of=context.opentracing_span) as span_parent:
            # Before we build the payload, check that the default_payload is not
            # malformed and reject the pushkey if it is

            default_payload = {}

            if device.data:
                default_payload = device.data.get("default_payload", {})
                if not isinstance(default_payload, dict):
                    log.error(
                        "default_payload is malformed, this value must be a dict."
                    )
                    return [device.pushkey]

            if n.event_id and not n.type:
                payload: Optional[Dict[str,
                                       Any]] = self._get_payload_event_id_only(
                                           n, default_payload)
            else:
                payload = self._get_payload_full(n, device, log)

            if payload is None:
                # Nothing to do
                span_parent.log_kv({logs.EVENT: "apns_no_payload"})
                return []
            prio = 10
            if n.prio == "low":
                prio = 5

            shaved_payload = apnstruncate.truncate(
                payload, max_length=self.MAX_JSON_BODY_SIZE)

            for retry_number in range(self.MAX_TRIES):
                try:
                    span_tags = {"retry_num": retry_number}

                    with self.sygnal.tracer.start_span(
                            "apns_dispatch_try",
                            tags=span_tags,
                            child_of=span_parent) as span:
                        assert shaved_payload is not None
                        return await self._dispatch_request(
                            log, span, device, shaved_payload, prio)
                except TemporaryNotificationDispatchException as exc:
                    retry_delay = self.RETRY_DELAY_BASE * (2**retry_number)
                    if exc.custom_retry_delay is not None:
                        retry_delay = exc.custom_retry_delay

                    log.warning(
                        "Temporary failure, will retry in %d seconds",
                        retry_delay,
                        exc_info=True,
                    )

                    span_parent.log_kv({
                        "event": "temporary_fail",
                        "retrying_in": retry_delay
                    })
                    if retry_number < self.MAX_TRIES - 1:
                        await twisted_sleep(
                            retry_delay, twisted_reactor=self.sygnal.reactor)

            raise NotificationDispatchException("Retried too many times.")
예제 #7
0
    async def _dispatch_notification_unlimited(self, n, device, context):
        log = NotificationLoggerAdapter(logger,
                                        {"request_id": context.request_id})

        # The pushkey is kind of secret because you can use it to send push
        # to someone.
        # span_tags = {"pushkey": device.pushkey}
        span_tags: Dict[str, int] = {}

        with self.sygnal.tracer.start_span(
                "apns_dispatch", tags=span_tags,
                child_of=context.opentracing_span) as span_parent:

            if n.event_id and not n.type:
                payload = self._get_payload_event_id_only(n, device)
            else:
                payload = self._get_payload_full(n, device, log)

            if payload is None:
                # Nothing to do
                span_parent.log_kv({logs.EVENT: "apns_no_payload"})
                return
            prio = 10
            if n.prio == "low":
                prio = 5

            shaved_payload = apnstruncate.truncate(
                payload, max_length=self.MAX_JSON_BODY_SIZE)

            for retry_number in range(self.MAX_TRIES):
                try:
                    log.debug("Trying")

                    span_tags = {"retry_num": retry_number}

                    with self.sygnal.tracer.start_span(
                            "apns_dispatch_try",
                            tags=span_tags,
                            child_of=span_parent) as span:
                        return await self._dispatch_request(
                            log, span, device, shaved_payload, prio)
                except TemporaryNotificationDispatchException as exc:
                    retry_delay = self.RETRY_DELAY_BASE * (2**retry_number)
                    if exc.custom_retry_delay is not None:
                        retry_delay = exc.custom_retry_delay

                    log.warning(
                        "Temporary failure, will retry in %d seconds",
                        retry_delay,
                        exc_info=True,
                    )

                    span_parent.log_kv({
                        "event": "temporary_fail",
                        "retrying_in": retry_delay
                    })

                    if retry_number == self.MAX_TRIES - 1:
                        raise NotificationDispatchException(
                            "Retried too many times.") from exc
                    else:
                        await twisted_sleep(
                            retry_delay, twisted_reactor=self.sygnal.reactor)