#!/usr/bin/env python3

# Copyright 2023 Sergey Stolyarov <sergei@regolit.com>
#
# Distributed under New BSD License.
#
# https://opensource.org/license/bsd-3-clause/


from smartcard.System import readers
from smartcard.CardRequest import CardRequest
from smartcard.CardConnection import CardConnection
from smartcard.util import toHexString, toBytes, PACK

def main() -> int:
    reader = readers()[0]
    print('Connected reader: {0}'.format(reader))
    cardrequest = CardRequest(timeout=None, readers=[reader])
    print('Waiting for Mifare Ultralight card ...')
    cardservice = cardrequest.waitforcard()
    cardservice.connection.connect()
    print('Card connected, checking card, please wait ... ', end='')

    page = 0
    pages = []

    # read first page to check type
    #       CLA INS P1  P2      Lc
    apdu = 'FF  B0  00  {:02X}  04'.format(page)
    response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
    if (sw1,sw2) != (0x90,0x00):
        print('Read Binary failed, probably not Mifare Ultralight card, terminating.')
        return 1
    pages.append(response)
    page += 1
    print('done')

    print('Reading pages ', end='')

    # read all pages
    while True:
        #       CLA INS P1  P2      Lc
        apdu = 'FF  B0  00  {:02X}  04'.format(page)
        response, sw1, sw2 = cardservice.connection.transmit(toBytes(apdu))
        if (sw1,sw2) != (0x90,0x00):
            break
        pages.append(response)
        print('.', end='', flush=True)
        page += 1
    print(' {} pages'.format(page))

    # print card details: UID, locking bits
    uid_bytes = pages[0][0:3] + pages[1]
    lock_0 = byte_to_bin(pages[2][2])
    lock_1 = byte_to_bin(pages[2][3])
    locked_pages = {}
    for x in range(5):
        if lock_0[x] == 1:
            locked_pages[7-x] = True
    for x in range(8):
        if lock_1[x] == 1:
            locked_pages[15-x] = True

    print('UID:', toHexString(uid_bytes, PACK))
    print('OTP bits:', byte_to_bin(pages[3][0]), byte_to_bin(pages[3][1]), byte_to_bin(pages[3][2]), byte_to_bin(pages[3][3]))
    print('Lock bits:', lock_0, lock_1)

    # pretty print card memory
    print('Page | Memory bytes | State ')
    print('-----+--------------+---------')
    for i,p in enumerate(pages):
        print('  {:02X} | {}  | '.format(i, toHexString(p)), end='')
        if locked_pages.get(i, False) == True:
            print('locked')
        else:
            print('')


def byte_to_bin(b):
    regs = []
    for i in range(8):
        if b & 1 == 1:
            regs.append(1)
        else:
            regs.append(0)
        b >>= 1
    regs.reverse()
    return regs

if __name__ == '__main__':
    main()