"""
helper functions to work with OATH HOTP (RFC4226) OTP's and YubiHSM
"""

# Copyright (c) 2011 Yubico AB
# See the file COPYING for licence statement.

import string
import struct

__all__ = [
    # constants
    # functions
    'validate_oath_hotp_with_aead',
    # classes
 ]

import pyhsm.exception
import pyhsm.aead_cmd

def search_for_oath_code(hsm, key_handle, nonce, aead, counter, user_code, look_ahead=1):
    """
    Try to validate an OATH HOTP OTP generated by a token whose secret key is
    available to the YubiHSM through the AEAD.

    The parameter `aead' is either a string, or an instance of YHSM_GeneratedAEAD.

    Returns next counter value on successful auth, and None otherwise.
    """
    key_handle = pyhsm.util.input_validate_key_handle(key_handle)
    nonce = pyhsm.util.input_validate_nonce(nonce, pad = False)
    aead = pyhsm.util.input_validate_aead(aead)
    counter = pyhsm.util.input_validate_int(counter, 'counter')
    user_code = pyhsm.util.input_validate_int(user_code, 'user_code')
    hsm.load_temp_key(nonce, key_handle, aead)
    # User might have produced codes never sent to us, so we support trying look_ahead
    # codes to see if we find the user's current code.
    for j in xrange(look_ahead):
        this_counter = counter + j
        secret = struct.pack("> Q", this_counter)
        hmac_result = hsm.hmac_sha1(pyhsm.defines.YSM_TEMP_KEY_HANDLE, secret).get_hash()
        this_code = truncate(hmac_result)
        if this_code == user_code:
            return this_counter + 1
    return None

def truncate(hmac_result, length=6):
    """ Perform the truncating. """
    assert(len(hmac_result) == 20)
    offset   =  ord(hmac_result[19]) & 0xf
    bin_code = (ord(hmac_result[offset]) & 0x7f) << 24 \
        | (ord(hmac_result[offset+1]) & 0xff) << 16 \
        | (ord(hmac_result[offset+2]) & 0xff) <<  8 \
        | (ord(hmac_result[offset+3]) & 0xff)
    return bin_code % (10 ** length)
