2024-08-26 23:23:53 -04:00
|
|
|
import dataclasses
|
|
|
|
from typing import Literal
|
|
|
|
|
2023-11-08 12:34:11 -05:00
|
|
|
import bitstring
|
|
|
|
|
|
|
|
# Reference for H10301 card format:
|
|
|
|
# https://www.hidglobal.com/sites/default/files/hid-understanding_card_data_formats-wp-en.pdf
|
|
|
|
|
|
|
|
|
2024-08-26 23:23:53 -04:00
|
|
|
class InvalidHexCode(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Not26Bit(InvalidHexCode):
|
|
|
|
def __init__(self) -> None:
|
|
|
|
super().__init__("Card number > 26 bits")
|
|
|
|
|
|
|
|
|
|
|
|
class InvalidParity(InvalidHexCode):
|
|
|
|
def __init__(self, even_odd: Literal["even", "odd"]) -> None:
|
|
|
|
super().__init__(f"Bad {even_odd} parity")
|
|
|
|
|
|
|
|
|
2024-09-01 02:09:09 -04:00
|
|
|
@dataclasses.dataclass(frozen=True)
|
2023-11-08 12:34:11 -05:00
|
|
|
class Credential:
|
2024-08-26 23:23:53 -04:00
|
|
|
bits: bitstring.Bits
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_code(cls, facility=int, card_number=int) -> "Credential":
|
|
|
|
bits = bitstring.pack(
|
|
|
|
"0b000000, 0b0, uint:8=facility, uint:16=card_number, 0b0",
|
|
|
|
facility=facility,
|
|
|
|
card_number=card_number,
|
|
|
|
)
|
|
|
|
bits[6] = bits[7:19].count(1) % 2 # even parity
|
|
|
|
bits[31] = bits[19:31].count(0) % 2 # odd parity
|
2024-09-01 02:21:34 -04:00
|
|
|
return cls(bitstring.Bits(bits))
|
2024-08-26 23:23:53 -04:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_hex(cls, hex_code: str) -> "Credential":
|
|
|
|
bits = bitstring.Bits(hex=hex_code)
|
|
|
|
|
|
|
|
if bits[:6].any(1):
|
|
|
|
raise Not26Bit
|
|
|
|
if bits[6] != bits[7:19].count(1) % 2:
|
|
|
|
raise InvalidParity("even")
|
|
|
|
if bits[31] != (bits[19:31].count(0) % 2):
|
|
|
|
raise InvalidParity("odd")
|
|
|
|
|
|
|
|
return cls(bits)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def facility_code(self) -> int:
|
|
|
|
return self.bits[7:15].uint
|
2023-11-08 12:34:11 -05:00
|
|
|
|
|
|
|
@property
|
2024-08-26 23:23:53 -04:00
|
|
|
def card_number(self) -> int:
|
|
|
|
return self.bits[15:31].uint
|
2023-11-08 12:34:11 -05:00
|
|
|
|
|
|
|
@property
|
2024-08-26 23:23:53 -04:00
|
|
|
def hex(self) -> str:
|
2023-11-08 12:34:11 -05:00
|
|
|
return self.bits.hex.upper()
|