Compare commits

...

3 Commits

Author SHA1 Message Date
b056eb04ed doorcontrol: Add some more debug logging in update_doors task
All checks were successful
Ruff / ruff (push) Successful in 1m14s
Test / test (push) Successful in 3m48s
2024-09-01 21:36:24 -04:00
ee48d286c2 doorcontrol: Fix Credential odd parity calculation
whoops!
2024-09-01 21:36:11 -04:00
bde7828865 doorcontrol: Show hex value in hid.Credential exceptions 2024-09-01 09:47:50 -04:00
3 changed files with 35 additions and 12 deletions

View File

@ -12,28 +12,36 @@ class InvalidHexCode(Exception):
class Not26Bit(InvalidHexCode): class Not26Bit(InvalidHexCode):
def __init__(self) -> None: def __init__(self, bits: bitstring.Bits) -> None:
super().__init__("Card number > 26 bits") super().__init__(f"Card number > 26 bits [{bits.hex}]")
class InvalidParity(InvalidHexCode): class InvalidParity(InvalidHexCode):
def __init__(self, even_odd: Literal["even", "odd"]) -> None: def __init__(self, bits: bitstring.Bits, even_odd: Literal["even", "odd"]) -> None:
super().__init__(f"Bad {even_odd} parity") super().__init__(f"Bad {even_odd} parity [{bits.hex}]")
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class Credential: class Credential:
bits: bitstring.Bits bits: bitstring.Bits
@staticmethod
def even_parity(bits: bitstring.Bits) -> bool:
return bool(bits[7:19].count(1) % 2)
@staticmethod
def odd_parity(bits: bitstring.Bits) -> bool:
return (bits[19:31].count(1) % 2) == 0
@classmethod @classmethod
def from_code(cls, facility=int, card_number=int) -> "Credential": def from_code(cls, facility: int, card_number: int) -> "Credential":
bits = bitstring.pack( bits = bitstring.pack(
"0b000000, 0b0, uint:8=facility, uint:16=card_number, 0b0", "0b000000, 0b0, uint:8=facility, uint:16=card_number, 0b0",
facility=facility, facility=facility,
card_number=card_number, card_number=card_number,
) )
bits[6] = bits[7:19].count(1) % 2 # even parity bits[6] = cls.even_parity(bits)
bits[31] = bits[19:31].count(0) % 2 # odd parity bits[31] = cls.odd_parity(bits)
return cls(bitstring.Bits(bits)) return cls(bitstring.Bits(bits))
@classmethod @classmethod
@ -41,11 +49,11 @@ class Credential:
bits = bitstring.Bits(hex=hex_code) bits = bitstring.Bits(hex=hex_code)
if bits[:6].any(1): if bits[:6].any(1):
raise Not26Bit raise Not26Bit(bits)
if bits[6] != bits[7:19].count(1) % 2: if bits[6] != cls.even_parity(bits):
raise InvalidParity("even") raise InvalidParity(bits, "even")
if bits[31] != (bits[19:31].count(0) % 2): if bits[31] != (cls.odd_parity(bits)):
raise InvalidParity("odd") raise InvalidParity(bits, "odd")
return cls(bits) return cls(bits)

View File

@ -18,3 +18,13 @@ class CredentialTestCase(TestCase):
cred = Credential.from_code(facility_code, card_number) cred = Credential.from_code(facility_code, card_number)
hex_cred = Credential.from_hex(cred.hex) hex_cred = Credential.from_hex(cred.hex)
self.assertEqual(cred, hex_cred) self.assertEqual(cred, hex_cred)
def test_example_numbers(self):
examples = [
(100, 12345, "02C86073"),
]
for facility_code, card_number, hex_number in examples:
code_cred = Credential.from_code(facility_code, card_number)
hex_cred = Credential.from_hex(hex_number)
self.assertEqual(code_cred, hex_cred)

View File

@ -208,11 +208,14 @@ class DoorMember:
def update_door(door: Door, dry_run: bool = False): def update_door(door: Door, dry_run: bool = False):
logger.info(f"Updating {door}")
logger.debug(f"Fetching members from database for {door}")
members = [ members = [
DoorMember.from_membershipworks_member(membershipworks_member, door) DoorMember.from_membershipworks_member(membershipworks_member, door)
for membershipworks_member in (Member.objects.with_is_active()).all() for membershipworks_member in (Member.objects.with_is_active()).all()
] ]
logger.debug(f"Fetching cardholders from {door}")
cardholders = { cardholders = {
member.membershipworks_id: member member.membershipworks_id: member
for member in [ for member in [
@ -221,11 +224,13 @@ def update_door(door: Door, dry_run: bool = False):
] ]
} }
logger.debug(f"Fetching credentials from {door}")
existing_door_credentials = { existing_door_credentials = {
Credential.from_hex(c.attrib["rawCardNumber"]) Credential.from_hex(c.attrib["rawCardNumber"])
for c in door.controller.get_credentials() for c in door.controller.get_credentials()
} }
logger.debug(f"Syncing members with {door}")
# TODO: can I combine requests? # TODO: can I combine requests?
for member in members: for member in members:
# cardholder did not exist, so add them # cardholder did not exist, so add them