mirror of
https://github.com/hypervortex/VH-Bombsquad-Modded-Server-Files
synced 2025-11-07 17:36:08 +00:00
244 lines
8.4 KiB
Python
244 lines
8.4 KiB
Python
# Released under the MIT License. See LICENSE for details.
|
|
#
|
|
"""Common errors and related functionality."""
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
import errno
|
|
|
|
if TYPE_CHECKING:
|
|
pass
|
|
|
|
|
|
class CleanError(Exception):
|
|
"""An error that can be presented to the user as a simple message.
|
|
|
|
These errors should be completely self-explanatory, to the point where
|
|
a traceback or other context would not be useful.
|
|
|
|
A CleanError with no message can be used to inform a script to fail
|
|
without printing any message.
|
|
|
|
This should generally be limited to errors that will *always* be
|
|
presented to the user (such as those in high level tool code).
|
|
Exceptions that may be caught and handled by other code should use
|
|
more descriptive exception types.
|
|
"""
|
|
|
|
def pretty_print(self, flush: bool = False) -> None:
|
|
"""Print the error to stdout, using red colored output if available.
|
|
|
|
If the error has an empty message, prints nothing (not even a newline).
|
|
"""
|
|
from efro.terminal import Clr
|
|
|
|
errstr = str(self)
|
|
if errstr:
|
|
print(f'{Clr.SRED}{errstr}{Clr.RST}', flush=flush)
|
|
|
|
|
|
class CommunicationError(Exception):
|
|
"""A communication related error has occurred.
|
|
|
|
This covers anything network-related going wrong in the sending
|
|
of data or receiving of a response. Basically anything that is out
|
|
of our control should get lumped in here. This error does not imply
|
|
that data was not received on the other end; only that a full
|
|
acknowledgement round trip was not completed.
|
|
|
|
These errors should be gracefully handled whenever possible, as
|
|
occasional network issues are unavoidable.
|
|
"""
|
|
|
|
|
|
class RemoteError(Exception):
|
|
"""An error occurred on the other end of some connection.
|
|
|
|
This occurs when communication succeeds but another type of error
|
|
occurs remotely. The error string can consist of a remote stack
|
|
trace or a simple message depending on the context.
|
|
|
|
Communication systems should raise more specific error types locally
|
|
when more introspection/control is needed; this is intended somewhat
|
|
as a catch-all.
|
|
"""
|
|
|
|
def __init__(self, msg: str, peer_desc: str):
|
|
super().__init__(msg)
|
|
self._peer_desc = peer_desc
|
|
|
|
def __str__(self) -> str:
|
|
s = ''.join(str(arg) for arg in self.args)
|
|
# Indent so we can more easily tell what is the remote part when
|
|
# this is in the middle of a long exception chain.
|
|
padding = ' '
|
|
s = ''.join(padding + line for line in s.splitlines(keepends=True))
|
|
return f'The following occurred on {self._peer_desc}:\n{s}'
|
|
|
|
|
|
class IntegrityError(ValueError):
|
|
"""Data has been tampered with or corrupted in some form."""
|
|
|
|
|
|
class AuthenticationError(Exception):
|
|
"""Authentication has failed for some operation.
|
|
|
|
This can be raised if server-side-verification does not match
|
|
client-supplied credentials, if an invalid password is supplied
|
|
for a sign-in attempt, etc.
|
|
"""
|
|
|
|
|
|
def is_urllib_communication_error(exc: BaseException, url: str | None) -> bool:
|
|
"""Is the provided exception from urllib a communication-related error?
|
|
|
|
Url, if provided can provide extra context for when to treat an error
|
|
as such an error.
|
|
|
|
This should be passed an exception which resulted from opening or
|
|
reading a urllib Request. It returns True for any errors that could
|
|
conceivably arise due to unavailable/poor network connections,
|
|
firewall/connectivity issues, or other issues out of our control.
|
|
These errors can often be safely ignored or presented to the user
|
|
as general 'network-unavailable' states.
|
|
"""
|
|
import urllib.error
|
|
import http.client
|
|
import socket
|
|
|
|
if isinstance(
|
|
exc,
|
|
(
|
|
urllib.error.URLError,
|
|
ConnectionError,
|
|
http.client.IncompleteRead,
|
|
http.client.BadStatusLine,
|
|
http.client.RemoteDisconnected,
|
|
socket.timeout,
|
|
),
|
|
):
|
|
|
|
# Special case: although an HTTPError is a subclass of URLError,
|
|
# we don't consider it a communication error. It generally means we
|
|
# have successfully communicated with the server but what we are asking
|
|
# for is not there/etc.
|
|
if isinstance(exc, urllib.error.HTTPError):
|
|
|
|
# Special sub-case: appspot.com hosting seems to give 403 errors
|
|
# (forbidden) to some countries. I'm assuming for legal reasons?..
|
|
# Let's consider that a communication error since its out of our
|
|
# control so we don't fill up logs with it.
|
|
if exc.code == 403 and url is not None and '.appspot.com' in url:
|
|
return True
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
if isinstance(exc, OSError):
|
|
if exc.errno == 10051: # Windows unreachable network error.
|
|
return True
|
|
if exc.errno in {
|
|
errno.ETIMEDOUT,
|
|
errno.EHOSTUNREACH,
|
|
errno.ENETUNREACH,
|
|
}:
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_requests_communication_error(exc: BaseException) -> bool:
|
|
"""Is the provided exception a communication-related error from requests?"""
|
|
import requests
|
|
|
|
# Looks like this maps pretty well onto requests' ConnectionError
|
|
return isinstance(exc, requests.ConnectionError)
|
|
|
|
|
|
def is_udp_communication_error(exc: BaseException) -> bool:
|
|
"""Should this udp-related exception be considered a communication error?
|
|
|
|
This should be passed an exception which resulted from creating and
|
|
using a socket.SOCK_DGRAM type socket. It should return True for any
|
|
errors that could conceivably arise due to unavailable/poor network
|
|
conditions, firewall/connectivity issues, etc. These issues can often
|
|
be safely ignored or presented to the user as general
|
|
'network-unavailable' states.
|
|
"""
|
|
if isinstance(exc, ConnectionRefusedError | TimeoutError):
|
|
return True
|
|
if isinstance(exc, OSError):
|
|
if exc.errno == 10051: # Windows unreachable network error.
|
|
return True
|
|
if exc.errno in {
|
|
errno.EADDRNOTAVAIL,
|
|
errno.ETIMEDOUT,
|
|
errno.EHOSTUNREACH,
|
|
errno.ENETUNREACH,
|
|
errno.EINVAL,
|
|
errno.EPERM,
|
|
errno.EACCES,
|
|
# Windows 'invalid argument' error.
|
|
10022,
|
|
# Windows 'a socket operation was attempted to'
|
|
# 'an unreachable network' error.
|
|
10051,
|
|
}:
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_asyncio_streams_communication_error(exc: BaseException) -> bool:
|
|
"""Should this streams error be considered a communication error?
|
|
|
|
This should be passed an exception which resulted from creating and
|
|
using asyncio streams. It should return True for any errors that could
|
|
conceivably arise due to unavailable/poor network connections,
|
|
firewall/connectivity issues, etc. These issues can often be safely
|
|
ignored or presented to the user as general 'connection-lost' events.
|
|
"""
|
|
# pylint: disable=too-many-return-statements
|
|
import ssl
|
|
|
|
if isinstance(
|
|
exc,
|
|
(
|
|
ConnectionError,
|
|
TimeoutError,
|
|
EOFError,
|
|
),
|
|
):
|
|
return True
|
|
|
|
# Also some specific errno ones.
|
|
if isinstance(exc, OSError):
|
|
if exc.errno == 10051: # Windows unreachable network error.
|
|
return True
|
|
if exc.errno in {
|
|
errno.ETIMEDOUT,
|
|
errno.EHOSTUNREACH,
|
|
errno.ENETUNREACH,
|
|
}:
|
|
return True
|
|
|
|
# Am occasionally getting a specific SSL error on shutdown which I
|
|
# believe is harmless (APPLICATION_DATA_AFTER_CLOSE_NOTIFY).
|
|
# It sounds like it may soon be ignored by Python (as of March 2022).
|
|
# Let's still complain, however, if we get any SSL errors besides
|
|
# this one. https://bugs.python.org/issue39951
|
|
if isinstance(exc, ssl.SSLError):
|
|
excstr = str(exc)
|
|
if 'APPLICATION_DATA_AFTER_CLOSE_NOTIFY' in excstr:
|
|
return True
|
|
|
|
# Also occasionally am getting WRONG_VERSION_NUMBER ssl errors;
|
|
# Assuming this just means client is attempting to connect from some
|
|
# outdated browser or whatnot.
|
|
if 'SSL: WRONG_VERSION_NUMBER' in excstr:
|
|
return True
|
|
|
|
# And seeing this very rarely; assuming its just data corruption?
|
|
if 'SSL: DECRYPTION_FAILED_OR_BAD_RECORD_MAC' in excstr:
|
|
return True
|
|
|
|
return False
|