Compare commits
3 Commits
33b01af78a
...
b056eb04ed
Author | SHA1 | Date | |
---|---|---|---|
b056eb04ed | |||
ee48d286c2 | |||
bde7828865 |
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user