<filename>hammerd/hammerd_api.py
# -*- 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()

<filename>hammerd/hammerd_api.py
# -*- 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()

# -*- 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. # Load hammerd-api library. Enumeration wrapper. ToStr converts enumerator to string. The enumeration of extra vendor subcommands. The enumeration of the image sections. The returned value of the PairChallenge. Struct of response of first PDU from src/platform/ec/include/update_fw.h touchpad_info struct from src/platform/ec/include/update_fw.h 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. 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. Generates the wrapper function by the function signature. # argtypes includes object itself. # Convert Python string to ByteString. # Convert args from tuple to list. Converts Python string to a ctypes ByteString pointer.      Args:       string: a Python string.      Returns:       A ctypes pointer to ByteString. The wrapper of FirmwareUpdater class. The wrapper of FirmwareUpdater class.