blob: 61e44fd518d7c8df3d5b2bc6c99d469f841df408 [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2017 The ChromiumOS Authors
# 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 bytes to a ctypes ByteString pointer.
Args:
string: a Python bytes.
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()