1# SPDX-License-Identifier: GPL-2.0-only 2# This file is part of Scapy 3# See https://scapy.net/ for more information 4 5""" 6Python 2 and 3 link classes. 7""" 8 9import base64 10import binascii 11import struct 12import sys 13 14from typing import ( 15 Any, 16 AnyStr, 17 Callable, 18 Optional, 19 TypeVar, 20 TYPE_CHECKING, 21 Union, 22) 23 24# Very important: will issue typing errors otherwise 25__all__ = [ 26 # typing 27 'DecoratorCallable', 28 'Literal', 29 'Protocol', 30 'Self', 31 'UserDict', 32 # compat 33 'base64_bytes', 34 'bytes_base64', 35 'bytes_encode', 36 'bytes_hex', 37 'chb', 38 'hex_bytes', 39 'orb', 40 'plain_str', 41 'raw', 42] 43 44# Typing compatibility 45 46# Note: 47# supporting typing on multiple python versions is a nightmare. 48# we provide a FakeType class to be able to use types added on 49# later Python versions (since we run mypy on 3.12), on older 50# ones. 51 52 53# Import or create fake types 54 55def _FakeType(name, cls=object): 56 # type: (str, Optional[type]) -> Any 57 class _FT(object): 58 def __init__(self, name): 59 # type: (str) -> None 60 self.name = name 61 62 # make the objects subscriptable indefinitely 63 def __getitem__(self, item): # type: ignore 64 return cls 65 66 def __call__(self, *args, **kargs): 67 # type: (*Any, **Any) -> Any 68 if isinstance(args[0], str): 69 self.name = args[0] 70 return self 71 72 def __repr__(self): 73 # type: () -> str 74 return "<Fake typing.%s>" % self.name 75 return _FT(name) 76 77 78# Python 3.8 Only 79if sys.version_info >= (3, 8): 80 from typing import Literal 81 from typing import Protocol 82else: 83 Literal = _FakeType("Literal") 84 85 class Protocol: 86 pass 87 88 89# Python 3.9 Only 90if sys.version_info >= (3, 9): 91 from collections import UserDict 92else: 93 from collections import UserDict as _UserDict 94 UserDict = _FakeType("_UserDict", _UserDict) 95 96 97# Python 3.11 Only 98if sys.version_info >= (3, 11): 99 from typing import Self 100else: 101 Self = _FakeType("Self") 102 103########### 104# Python3 # 105########### 106 107# https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators 108DecoratorCallable = TypeVar("DecoratorCallable", bound=Callable[..., Any]) 109 110 111# This is ugly, but we don't want to move raw() out of compat.py 112# and it makes it much clearer 113if TYPE_CHECKING: 114 from scapy.packet import Packet 115 116 117def raw(x): 118 # type: (Packet) -> bytes 119 """ 120 Builds a packet and returns its bytes representation. 121 This function is and will always be cross-version compatible 122 """ 123 return bytes(x) 124 125 126def bytes_encode(x): 127 # type: (Any) -> bytes 128 """Ensure that the given object is bytes. If the parameter is a 129 packet, raw() should be preferred. 130 131 """ 132 if isinstance(x, str): 133 return x.encode() 134 return bytes(x) 135 136 137def plain_str(x): 138 # type: (Any) -> str 139 """Convert basic byte objects to str""" 140 if isinstance(x, bytes): 141 return x.decode(errors="backslashreplace") 142 return str(x) 143 144 145def chb(x): 146 # type: (int) -> bytes 147 """Same than chr() but encode as bytes.""" 148 return struct.pack("!B", x) 149 150 151def orb(x): 152 # type: (Union[int, str, bytes]) -> int 153 """Return ord(x) when not already an int.""" 154 if isinstance(x, int): 155 return x 156 return ord(x) 157 158 159def bytes_hex(x): 160 # type: (AnyStr) -> bytes 161 """Hexify a str or a bytes object""" 162 return binascii.b2a_hex(bytes_encode(x)) 163 164 165def hex_bytes(x): 166 # type: (AnyStr) -> bytes 167 """De-hexify a str or a byte object""" 168 return binascii.a2b_hex(bytes_encode(x)) 169 170 171def int_bytes(x, size): 172 # type: (int, int) -> bytes 173 """Convert an int to an arbitrary sized bytes string""" 174 return x.to_bytes(size, byteorder='big') 175 176 177def bytes_int(x): 178 # type: (bytes) -> int 179 """Convert an arbitrary sized bytes string to an int""" 180 return int.from_bytes(x, "big") 181 182 183def base64_bytes(x): 184 # type: (AnyStr) -> bytes 185 """Turn base64 into bytes""" 186 return base64.decodebytes(bytes_encode(x)) 187 188 189def bytes_base64(x): 190 # type: (AnyStr) -> bytes 191 """Turn bytes into base64""" 192 return base64.encodebytes(bytes_encode(x)).replace(b'\n', b'') 193