| # -*- coding: utf-8 -*- |
| # Copyright 2017 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """The python wrapper of the hammerd API.""" |
| |
| from __future__ import print_function |
| |
| import ctypes |
| import sys |
| |
| # Load hammerd-api library. |
| _DLL = ctypes.CDLL('libhammerd-api.so') |
| ENTROPY_SIZE = ctypes.c_int.in_dll(_DLL, 'kEntropySize').value |
| SHA256_DIGEST_LENGTH = ctypes.c_int.in_dll(_DLL, 'kSha256DigestLength').value |
| X25519_PUBLIC_VALUE_LEN = ctypes.c_int.in_dll( |
| _DLL, 'kX25519PublicValueLen').value |
| |
| |
| class Enum(object): |
| """Enumeration wrapper. ToStr converts enumerator to string.""" |
| @classmethod |
| def ToStr(cls, val): |
| for name, enum_value in cls.__dict__.items(): |
| if name.startswith('__'): |
| continue |
| if val == enum_value: |
| return name |
| return 'unknown(%d)' % val |
| |
| |
| class UpdateExtraCommand(Enum): |
| """The enumeration of extra vendor subcommands.""" |
| ImmediateReset = 0 |
| JumpToRW = 1 |
| StayInRO = 2 |
| UnlockRW = 3 |
| UnlockRollback = 4 |
| InjectEntropy = 5 |
| PairChallenge = 6 |
| TouchpadInfo = 7 |
| |
| |
| class SectionName(Enum): |
| """The enumeration of the image sections.""" |
| RO = 0 |
| RW = 1 |
| Invalid = 2 |
| |
| |
| class ChallengeStatus(Enum): |
| """The returned value of the PairChallenge.""" |
| ChallengePassed = 0 |
| ChallengeFailed = 1 |
| NeedInjectEntropy = 2 |
| UnknownError = 3 |
| |
| |
| class FirstResponsePdu(ctypes.Structure): |
| """Struct of response of first PDU from src/platform/ec/include/update_fw.h""" |
| _pack_ = 1 |
| _fields_ = [('return_value', ctypes.c_uint32), |
| ('header_type', ctypes.c_uint16), |
| ('protocol_version', ctypes.c_uint16), |
| ('maximum_pdu_size', ctypes.c_uint32), |
| ('flash_protection', ctypes.c_uint32), |
| ('offset', ctypes.c_uint32), |
| ('version', ctypes.c_ubyte * 32), |
| ('min_rollback', ctypes.c_int32), |
| ('key_version', ctypes.c_uint32)] |
| |
| def __str__(self): |
| ret = '' |
| for field_name, unused_field_type in self._fields_: |
| ret += '%s: %s\n' % (field_name, getattr(self, field_name)) |
| return ret |
| |
| |
| class TouchpadInfo(ctypes.Structure): |
| """touchpad_info struct from src/platform/ec/include/update_fw.h""" |
| _pack_ = 1 |
| _fields_ = [('status', ctypes.c_ubyte), |
| ('reserved', ctypes.c_ubyte), |
| ('vendor', ctypes.c_ushort), |
| ('fw_address', ctypes.c_uint), |
| ('fw_size', ctypes.c_uint), |
| ('allowed_fw_hash', ctypes.c_ubyte * SHA256_DIGEST_LENGTH), |
| ('id', ctypes.c_ushort), |
| ('fw_version', ctypes.c_ushort), |
| ('fw_checksum', ctypes.c_ushort)] |
| |
| |
| class ByteString(ctypes.Structure): |
| """Intermediary type between Python string to C++ string. |
| |
| Because ctypes doesn't support C++ std::string, we need to pass C-style |
| char pointer and the size of string first. Then convert it to std::string in |
| other side. |
| """ |
| _fields_ = [ |
| ('ptr', ctypes.c_char_p), |
| ('size', ctypes.c_size_t)] |
| |
| |
| class WrapperMetaclass(type): |
| """The metaclass of the wrapper class. |
| |
| Each wrapper class should declare the method signature in "METHODS" fields, |
| which is a list of (method name, [arg0 type, arg1 type, ...], return type). |
| Also, each class should initiate the instance to "self.object" field. |
| """ |
| def __new__(cls, name, bases, dct): |
| for method_name, argtypes, restype in dct['METHODS']: |
| dct[method_name] = WrapperMetaclass.GenerateMethod( |
| name, method_name, argtypes, restype) |
| return super(WrapperMetaclass, cls).__new__(cls, name, bases, dct) |
| |
| @staticmethod |
| def GenerateMethod(cls_name, method_name, argtypes, restype): |
| """Generates the wrapper function by the function signature.""" |
| def method(self, *args): |
| print('Call %s' % method_name, file=sys.stderr) |
| if len(args) != len(argtypes) - 1: # argtypes includes object itself. |
| raise TypeError('%s expected %d arguments, got %d.' % |
| (method_name, len(argtypes) - 1, len(args))) |
| # Convert Python string to ByteString. |
| args = list(args) # Convert args from tuple to list. |
| for idx, arg_type in enumerate(argtypes[1:]): |
| if arg_type == ctypes.POINTER(ByteString): |
| args[idx] = WrapperMetaclass.ConvertString(args[idx]) |
| elif arg_type == ctypes.c_char_p: |
| args[idx] = args[idx].encode('utf-8') |
| func = getattr(_DLL, '%s_%s' % (cls_name, method_name)) |
| func.argtypes = argtypes |
| func.restype = restype |
| |
| ret = func(self.object, *args) |
| if restype == ctypes.c_char_p: |
| ret = ret.decode('utf-8') |
| return ret |
| return method |
| |
| @staticmethod |
| def ConvertString(string): |
| """Converts Python string to a ctypes ByteString pointer. |
| |
| Args: |
| string: a Python string. |
| |
| Returns: |
| A ctypes pointer to ByteString. |
| """ |
| buffer_size = len(string) |
| buffer_ptr = ctypes.cast(ctypes.create_string_buffer(string, buffer_size), |
| ctypes.c_char_p) |
| return ctypes.byref(ByteString(buffer_ptr, buffer_size)) |
| |
| |
| class FirmwareUpdater(object, metaclass=WrapperMetaclass): |
| """The wrapper of FirmwareUpdater class.""" |
| |
| METHODS = [ |
| ('LoadEcImage', |
| [ctypes.c_voidp, ctypes.POINTER(ByteString)], ctypes.c_bool), |
| ('LoadTouchpadImage', |
| [ctypes.c_voidp, ctypes.POINTER(ByteString)], ctypes.c_bool), |
| ('TryConnectUsb', [ctypes.c_voidp], ctypes.c_bool), |
| ('CloseUsb', [ctypes.c_voidp], None), |
| ('SendFirstPdu', [ctypes.c_voidp], ctypes.c_bool), |
| ('SendDone', [ctypes.c_voidp], None), |
| ('InjectEntropy', [ctypes.c_voidp], ctypes.c_bool), |
| ('InjectEntropyWithPayload', |
| [ctypes.c_voidp, ctypes.POINTER(ByteString)], ctypes.c_bool), |
| ('SendSubcommand', [ctypes.c_voidp, ctypes.c_uint16], ctypes.c_bool), |
| ('SendSubcommandWithPayload', |
| [ctypes.c_voidp, ctypes.c_uint16, ctypes.POINTER(ByteString)], |
| ctypes.c_bool), |
| ('SendSubcommandReceiveResponse', |
| [ctypes.c_voidp, ctypes.c_uint16, ctypes.POINTER(ByteString), |
| ctypes.c_voidp, ctypes.c_size_t], ctypes.c_bool), |
| ('TransferImage', [ctypes.c_voidp, ctypes.c_int], ctypes.c_bool), |
| ('TransferTouchpadFirmware', |
| [ctypes.c_voidp, ctypes.c_uint32, ctypes.c_size_t], ctypes.c_bool), |
| ('CurrentSection', [ctypes.c_voidp], ctypes.c_int), |
| ('UpdatePossible', [ctypes.c_voidp, ctypes.c_int], ctypes.c_bool), |
| ('VersionMismatch', [ctypes.c_voidp, ctypes.c_int], ctypes.c_bool), |
| ('IsSectionLocked', [ctypes.c_voidp, ctypes.c_int], ctypes.c_bool), |
| ('UnlockRW', [ctypes.c_voidp], ctypes.c_bool), |
| ('IsRollbackLocked', [ctypes.c_voidp], ctypes.c_bool), |
| ('UnlockRollback', [ctypes.c_voidp], ctypes.c_bool), |
| ('GetFirstResponsePdu', |
| [ctypes.c_voidp], ctypes.POINTER(FirstResponsePdu)), |
| ('GetSectionVersion', [ctypes.c_voidp, ctypes.c_int], ctypes.c_char_p), |
| ] |
| |
| def __init__(self, vendor_id, product_id, path=None): |
| func = _DLL.FirmwareUpdater_New |
| func.argtypes = [ctypes.c_uint16, ctypes.c_uint16, ctypes.c_char_p] |
| func.restype = ctypes.c_void_p |
| if path is not None: |
| path = path.encode('utf-8') |
| self.object = func(vendor_id, product_id, path) |
| |
| |
| class PairManager(object, metaclass=WrapperMetaclass): |
| """The wrapper of FirmwareUpdater class.""" |
| |
| METHODS = [ |
| ('PairChallenge', [ctypes.c_voidp, ctypes.c_voidp, |
| ctypes.POINTER(ctypes.c_uint8)], ctypes.c_int), |
| ] |
| |
| def __init__(self): |
| func = _DLL.PairManager_New |
| func.argtypes = [] |
| func.restype = ctypes.c_void_p |
| self.object = func() |