Skip to content
Snippets Groups Projects
Unverified Commit dfc06633 authored by Jean-Michel Picod's avatar Jean-Michel Picod Committed by GitHub
Browse files

Merge pull request #45 from jmichelp/master

Improve deploy script.
parents ef414bc2 f345a753
Branches
No related tags found
No related merge requests found
{
"recommendations": [
"davidanson.vscode-markdownlint",
"rust-lang.rust"
"rust-lang.rust",
"ms-python.python"
]
}
{
"editor.detectIndentation": true,
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.insertSpaces": true,
"editor.tabSize": 4,
"rust-client.channel": "nightly",
// The toolchain is updated from time to time so let's make sure that RLS is updated too
"rust-client.updateOnStartup": true,
"rust.clippy_preference": "on"
"rust.clippy_preference": "on",
// Try to make VSCode formating as close as possible to the Google style.
"python.formatting.provider": "yapf",
"python.formatting.yapfArgs": [
"--style=chromium"
],
"[python]": {
"editor.tabSize": 2
},
}
......@@ -27,7 +27,6 @@ import subprocess
import sys
from tockloader import tab, tbfh, tockloader
# This structure allows us in the future to also support out-of-tree boards.
SUPPORTED_BOARDS = {
"nrf52840_dk": "third_party/tock/boards/nordic/nrf52840dk",
......@@ -45,386 +44,366 @@ APP_HEAP_SIZE = 90000
def get_supported_boards():
boards = []
for name, root in SUPPORTED_BOARDS.items():
if all((os.path.exists(os.path.join(root, "Cargo.toml")),
os.path.exists(os.path.join(root, "Makefile")))):
boards.append(name)
return tuple(set(boards))
boards = []
for name, root in SUPPORTED_BOARDS.items():
if all((os.path.exists(os.path.join(root, "Cargo.toml")),
os.path.exists(os.path.join(root, "Makefile")))):
boards.append(name)
return tuple(set(boards))
def fatal(msg):
print("{style_begin}fatal:{style_end} {message}".format(
style_begin=colorama.Fore.RED + colorama.Style.BRIGHT,
style_end=colorama.Style.RESET_ALL,
message=msg))
sys.exit(1)
print("{style_begin}fatal:{style_end} {message}".format(
style_begin=colorama.Fore.RED + colorama.Style.BRIGHT,
style_end=colorama.Style.RESET_ALL,
message=msg))
sys.exit(1)
def error(msg):
print("{style_begin}error:{style_end} {message}".format(
style_begin=colorama.Fore.RED,
style_end=colorama.Style.RESET_ALL,
message=msg))
print("{style_begin}error:{style_end} {message}".format(
style_begin=colorama.Fore.RED,
style_end=colorama.Style.RESET_ALL,
message=msg))
def info(msg):
print("{style_begin}info:{style_end} {message}".format(
style_begin=colorama.Fore.GREEN + colorama.Style.BRIGHT,
style_end=colorama.Style.RESET_ALL,
message=msg))
print("{style_begin}info:{style_end} {message}".format(
style_begin=colorama.Fore.GREEN + colorama.Style.BRIGHT,
style_end=colorama.Style.RESET_ALL,
message=msg))
class RemoveConstAction(argparse.Action):
def __init__(self,
option_strings,
dest,
const,
default=None,
required=False,
help=None,
metavar=None):
super(RemoveConstAction, self).__init__(
option_strings=option_strings,
dest=dest,
nargs=0,
const=const,
default=default,
required=required,
help=help,
metavar=metavar)
def __call__(self, parser, namespace, values, option_string=None):
# Code is simply a modified version of the AppendConstAction from argparse
# https://github.com/python/cpython/blob/master/Lib/argparse.py#L138-L147
# https://github.com/python/cpython/blob/master/Lib/argparse.py#L1028-L1052
items = getattr(namespace, self.dest, [])
if type(items) is list:
items = items[:]
else:
items = copy.copy(items)
if self.const in items:
self.remove(self.const)
setattr(namespace, self.dest, items)
def __init__(self,
option_strings,
dest,
const,
default=None,
required=False,
help=None,
metavar=None):
super(RemoveConstAction, self).__init__(
option_strings=option_strings,
dest=dest,
nargs=0,
const=const,
default=default,
required=required,
help=help,
metavar=metavar)
def __call__(self, parser, namespace, values, option_string=None):
# Code is simply a modified version of the AppendConstAction from argparse
# https://github.com/python/cpython/blob/master/Lib/argparse.py#L138-L147
# https://github.com/python/cpython/blob/master/Lib/argparse.py#L1028-L1052
items = getattr(namespace, self.dest, [])
if type(items) is list:
items = items[:]
else:
items = copy.copy(items)
if self.const in items:
self.remove(self.const)
setattr(namespace, self.dest, items)
class OpenSKInstaller(object):
def __init__(self, args):
colorama.init()
self.args = args
# Where all the TAB files should go
self.tab_folder = os.path.join("target", "tab")
# This is the filename that elf2tab command expects in order
# to create a working TAB file.
self.target_elf_filename = os.path.join(
self.tab_folder, "cortex-m4.elf")
self.tockloader_default_args = argparse.Namespace(
arch="cortex-m4",
board=getattr(self.args, "board", "nrf52840"),
debug=False,
force=False,
jlink=True,
jlink_device="nrf52840_xxaa",
jlink_if="swd",
jlink_speed=1200,
jtag=False,
no_bootloader_entry=False,
page_size=4096,
port=None,
)
def checked_command_output(self, cmd):
cmd_output = ""
try:
cmd_output = subprocess.check_output(cmd)
except subprocess.CalledProcessError as e:
fatal("Failed to execute {}: {}".format(cmd[0], str(e)))
# Unreachable because fatal() will exit
return cmd_output.decode()
def update_rustc_if_needed(self):
target_toolchain_fullstring = "stable"
with open("rust-toolchain", "r") as f:
target_toolchain_fullstring = f.readline().strip()
target_toolchain = target_toolchain_fullstring.split("-", maxsplit=1)
if len(target_toolchain) == 1:
# If we target the stable version of rust, we won't have a date
# associated to the version and split will only return 1 item.
# To avoid failing later when accessing the date, we insert an
# empty value.
target_toolchain.append('')
current_version = self.checked_command_output(["rustc", "--version"])
if not all(
(target_toolchain[0] in current_version,
target_toolchain[1] in current_version)):
info("Updating rust toolchain to {}".format(
"-".join(target_toolchain)))
# Need to update
self.checked_command_output(
["rustup", "install", target_toolchain_fullstring])
self.checked_command_output(
["rustup", "target", "add", "thumbv7em-none-eabi"])
info("Rust toolchain up-to-date")
def build_and_install_tockos(self):
self.checked_command_output(
["make", "-C", SUPPORTED_BOARDS[self.args.board], "flash"]
)
def build_and_install_example(self):
assert(self.args.application)
self.checked_command_output([
"cargo",
"build",
"--release",
"--target=thumbv7em-none-eabi",
"--features={}".format(",".join(self.args.features)),
"--example",
self.args.application
])
self.install_elf_file(os.path.join(
"target/thumbv7em-none-eabi/release/examples",
self.args.application))
def build_and_install_opensk(self):
assert(self.args.application)
info("Building OpenSK application")
self.checked_command_output([
"cargo",
"build",
"--release",
"--target=thumbv7em-none-eabi",
"--features={}".format(",".join(self.args.features)),
])
self.install_elf_file(os.path.join(
"target/thumbv7em-none-eabi/release", self.args.application))
def generate_crypto_materials(self, force_regenerate):
has_error = subprocess.call([
os.path.join("tools", "gen_key_materials.sh"),
"Y" if force_regenerate else "N",
])
if has_error:
error((
"Something went wrong while trying to generate ECC "
"key and/or certificate for OpenSK"))
def install_elf_file(self, elf_path):
assert(self.args.application)
package_parameter = "-n"
elf2tab_ver = self.checked_command_output(
["elf2tab", "--version"]).split(' ', maxsplit=1)[1]
# Starting from v0.5.0-dev the parameter changed.
# Current pyblished crate is 0.4.0 but we don't want developers
# running the HEAD from github to be stuck
if "0.5.0-dev" in elf2tab_ver:
package_parameter = "--package-name"
os.makedirs(self.tab_folder, exist_ok=True)
tab_filename = os.path.join(
self.tab_folder,
"{}.tab".format(self.args.application))
shutil.copyfile(elf_path, self.target_elf_filename)
self.checked_command_output([
"elf2tab",
package_parameter,
self.args.application,
"-o",
tab_filename,
self.target_elf_filename,
"--stack={}".format(STACK_SIZE),
"--app-heap={}".format(APP_HEAP_SIZE),
"--kernel-heap=1024",
"--protected-region-size=64"
])
self.install_padding()
info("Installing Tock application {}".format(self.args.application))
args = copy.copy(self.tockloader_default_args)
setattr(args, "app_address", 0x40000)
setattr(args, "erase", self.args.clear_apps)
setattr(args, "make", False)
setattr(args, "no_replace", False)
setattr(args, "sticky", False)
tock = tockloader.TockLoader(args)
tock.open(args)
tabs = [tab.TAB(tab_filename)]
try:
tock.install(tabs, replace="yes",
erase=args.erase, sticky=args.sticky)
except tockloader.exceptions.TockLoaderException as e:
fatal("Couldn't install Tock application {}: {}".format(
self.args.application, str(e)))
def install_padding(self):
fake_header = tbfh.TBFHeader("")
fake_header.version = 2
fake_header.fields["header_size"] = 0x10
fake_header.fields["total_size"] = 0x10000
fake_header.fields["flags"] = 0
padding = fake_header.get_binary()
info("Flashing padding application")
args = copy.copy(self.tockloader_default_args)
setattr(args, "address", 0x30000)
tock = tockloader.TockLoader(args)
tock.open(args)
try:
tock.flash_binary(padding, args.address)
except tockloader.exceptions.TockLoaderException as e:
fatal("Couldn't install padding: {}".format(str(e)))
def clear_apps(self):
args = copy.copy(self.tockloader_default_args)
setattr(args, "app_address", 0x40000)
info("Erasing all installed applications")
tock = tockloader.TockLoader(args)
tock.open(args)
try:
tock.erase_apps(False)
except tockloader.exceptions.TockLoaderException as e:
# Erasing apps is not critical
info(("A non-critical error occured while erasing "
"apps: {}".format(str(e))))
def run(self):
if self.args.action is None:
# Nothing to do
return
self.update_rustc_if_needed()
if self.args.action == "os":
info("Installing Tock on board {}".format(self.args.board))
self.build_and_install_tockos()
if self.args.action == "app":
if self.args.application is None:
fatal("Unspecified application")
if self.args.clear_apps:
self.clear_apps()
if self.args.application == "ctap2":
self.generate_crypto_materials(self.args.regenerate_keys)
self.build_and_install_opensk()
else:
self.build_and_install_example()
class OpenSKInstaller(object):
def main(args):
# Make sure the current working directory is the right one before running
os.chdir(os.path.realpath(os.path.dirname(__file__)))
# Check for pre-requisite executable files.
if not shutil.which("JLinkExe"):
fatal(("Couldn't find JLinkExe binary. Make sure Segger JLink tools "
"are installed and correctly set up."))
def __init__(self, args):
colorama.init()
self.args = args
# Where all the TAB files should go
self.tab_folder = os.path.join("target", "tab")
# This is the filename that elf2tab command expects in order
# to create a working TAB file.
self.target_elf_filename = os.path.join(self.tab_folder, "cortex-m4.elf")
self.tockloader_default_args = argparse.Namespace(
arch="cortex-m4",
board=getattr(self.args, "board", "nrf52840"),
debug=False,
force=False,
jlink=True,
jlink_device="nrf52840_xxaa",
jlink_if="swd",
jlink_speed=1200,
jtag=False,
no_bootloader_entry=False,
page_size=4096,
port=None,
)
OpenSKInstaller(args).run()
def checked_command_output(self, cmd):
cmd_output = ""
try:
cmd_output = subprocess.check_output(cmd)
except subprocess.CalledProcessError as e:
fatal("Failed to execute {}: {}".format(cmd[0], str(e)))
# Unreachable because fatal() will exit
return cmd_output.decode()
def update_rustc_if_needed(self):
target_toolchain_fullstring = "stable"
with open("rust-toolchain", "r") as f:
target_toolchain_fullstring = f.readline().strip()
target_toolchain = target_toolchain_fullstring.split("-", maxsplit=1)
if len(target_toolchain) == 1:
# If we target the stable version of rust, we won't have a date
# associated to the version and split will only return 1 item.
# To avoid failing later when accessing the date, we insert an
# empty value.
target_toolchain.append('')
current_version = self.checked_command_output(["rustc", "--version"])
if not all((target_toolchain[0] in current_version,
target_toolchain[1] in current_version)):
info("Updating rust toolchain to {}".format("-".join(target_toolchain)))
# Need to update
self.checked_command_output(
["rustup", "install", target_toolchain_fullstring])
self.checked_command_output(
["rustup", "target", "add", "thumbv7em-none-eabi"])
info("Rust toolchain up-to-date")
def build_and_install_tockos(self):
self.checked_command_output(
["make", "-C", SUPPORTED_BOARDS[self.args.board], "flash"])
def build_and_install_example(self):
assert (self.args.application)
self.checked_command_output([
"cargo", "build", "--release", "--target=thumbv7em-none-eabi",
"--features={}".format(",".join(self.args.features)), "--example",
self.args.application
])
self.install_elf_file(
os.path.join("target/thumbv7em-none-eabi/release/examples",
self.args.application))
def build_and_install_opensk(self):
assert (self.args.application)
info("Building OpenSK application")
self.checked_command_output([
"cargo",
"build",
"--release",
"--target=thumbv7em-none-eabi",
"--features={}".format(",".join(self.args.features)),
])
self.install_elf_file(
os.path.join("target/thumbv7em-none-eabi/release",
self.args.application))
def generate_crypto_materials(self, force_regenerate):
has_error = subprocess.call([
os.path.join("tools", "gen_key_materials.sh"),
"Y" if force_regenerate else "N",
])
if has_error:
error(("Something went wrong while trying to generate ECC "
"key and/or certificate for OpenSK"))
def install_elf_file(self, elf_path):
assert (self.args.application)
package_parameter = "-n"
elf2tab_ver = self.checked_command_output(["elf2tab", "--version"]).split(
' ', maxsplit=1)[1]
# Starting from v0.5.0-dev the parameter changed.
# Current pyblished crate is 0.4.0 but we don't want developers
# running the HEAD from github to be stuck
if "0.5.0-dev" in elf2tab_ver:
package_parameter = "--package-name"
os.makedirs(self.tab_folder, exist_ok=True)
tab_filename = os.path.join(self.tab_folder,
"{}.tab".format(self.args.application))
shutil.copyfile(elf_path, self.target_elf_filename)
self.checked_command_output([
"elf2tab", package_parameter, self.args.application, "-o", tab_filename,
self.target_elf_filename, "--stack={}".format(STACK_SIZE),
"--app-heap={}".format(APP_HEAP_SIZE), "--kernel-heap=1024",
"--protected-region-size=64"
])
self.install_padding()
info("Installing Tock application {}".format(self.args.application))
args = copy.copy(self.tockloader_default_args)
setattr(args, "app_address", 0x40000)
setattr(args, "erase", self.args.clear_apps)
setattr(args, "make", False)
setattr(args, "no_replace", False)
tock = tockloader.TockLoader(args)
tock.open(args)
tabs = [tab.TAB(tab_filename)]
try:
tock.install(tabs, replace="yes", erase=args.erase)
except tockloader.exceptions.TockLoaderException as e:
fatal("Couldn't install Tock application {}: {}".format(
self.args.application, str(e)))
def install_padding(self):
fake_header = tbfh.TBFHeader("")
fake_header.version = 2
fake_header.fields["header_size"] = 0x10
fake_header.fields["total_size"] = 0x10000
fake_header.fields["flags"] = 0
padding = fake_header.get_binary()
info("Flashing padding application")
args = copy.copy(self.tockloader_default_args)
setattr(args, "address", 0x30000)
tock = tockloader.TockLoader(args)
tock.open(args)
try:
tock.flash_binary(padding, args.address)
except tockloader.exceptions.TockLoaderException as e:
fatal("Couldn't install padding: {}".format(str(e)))
def clear_apps(self):
args = copy.copy(self.tockloader_default_args)
setattr(args, "app_address", 0x40000)
info("Erasing all installed applications")
tock = tockloader.TockLoader(args)
tock.open(args)
try:
tock.erase_apps(False)
except tockloader.exceptions.TockLoaderException as e:
# Erasing apps is not critical
info(("A non-critical error occured while erasing "
"apps: {}".format(str(e))))
def verify_flashed_app(self, expected_app):
args = copy.copy(self.tockloader_default_args)
tock = tockloader.TockLoader(args)
app_found = False
with tock._start_communication_with_board():
apps = [app.name for app in tock._extract_all_app_headers()]
app_found = expected_app in apps
return app_found
def run(self):
if self.args.action is None:
# Nothing to do
return 0
self.update_rustc_if_needed()
if self.args.action == "os":
info("Installing Tock on board {}".format(self.args.board))
self.build_and_install_tockos()
return 0
if self.args.action == "app":
if self.args.application is None:
fatal("Unspecified application")
if self.args.clear_apps:
self.clear_apps()
if self.args.application == "ctap2":
self.generate_crypto_materials(self.args.regenerate_keys)
self.build_and_install_opensk()
else:
self.build_and_install_example()
if self.verify_flashed_app(self.args.application):
info("You're all set!")
return 0
error(("It seems that something went wrong. "
"App/example not found on your board."))
return 1
if __name__ == '__main__':
shared_parser = argparse.ArgumentParser(add_help=False)
shared_parser.add_argument(
"--dont-clear-apps",
action="store_false",
default=True,
dest="clear_apps",
help=(
"When installing an application, previously installed "
"applications won't be erased from the board."
),
)
def main(args):
# Make sure the current working directory is the right one before running
os.chdir(os.path.realpath(os.path.dirname(__file__)))
# Check for pre-requisite executable files.
if not shutil.which("JLinkExe"):
fatal(("Couldn't find JLinkExe binary. Make sure Segger JLink tools "
"are installed and correctly set up."))
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(
dest="action",
help=(
"Indicates which part of the firmware should be compiled and "
"flashed to the connected board."
)
)
OpenSKInstaller(args).run()
os_commands = commands.add_parser(
"os",
parents=[shared_parser],
help=(
"Compiles and installs Tock OS. The target board must be "
"specified by setting the --board argument."
),
)
os_commands.add_argument(
"--board",
metavar="BOARD_NAME",
dest="board",
choices=get_supported_boards(),
help="Indicates which board Tock OS will be compiled for.",
required=True
)
app_commands = commands.add_parser(
"app",
parents=[shared_parser],
help="compiles and installs an application."
)
app_commands.add_argument(
"--panic-console",
action="append_const",
const="panic_console",
dest="features",
help=(
"In case of application panic, the console will be used to "
if __name__ == '__main__':
shared_parser = argparse.ArgumentParser(add_help=False)
shared_parser.add_argument(
"--dont-clear-apps",
action="store_false",
default=True,
dest="clear_apps",
help=("When installing an application, previously installed "
"applications won't be erased from the board."),
)
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(
dest="action",
help=("Indicates which part of the firmware should be compiled and "
"flashed to the connected board."))
os_commands = commands.add_parser(
"os",
parents=[shared_parser],
help=("Compiles and installs Tock OS. The target board must be "
"specified by setting the --board argument."),
)
os_commands.add_argument(
"--board",
metavar="BOARD_NAME",
dest="board",
choices=get_supported_boards(),
help="Indicates which board Tock OS will be compiled for.",
required=True)
app_commands = commands.add_parser(
"app",
parents=[shared_parser],
help="compiles and installs an application.")
app_commands.add_argument(
"--panic-console",
action="append_const",
const="panic_console",
dest="features",
help=("In case of application panic, the console will be used to "
"output messages before starting blinking the LEDs on the "
"board."
),
)
app_commands.add_argument(
"--no-u2f",
action=RemoveConstAction,
const="with_ctap1",
dest="features",
help=(
"Compiles the OpenSK application without backward compatible "
"support for U2F/CTAP1 protocol."
),
)
app_commands.add_argument(
"--regen-keys",
action="store_true",
default=False,
dest="regenerate_keys",
help=(
"Forces the generation of files (certificates and private keys) "
"board."),
)
app_commands.add_argument(
"--no-u2f",
action=RemoveConstAction,
const="with_ctap1",
dest="features",
help=("Compiles the OpenSK application without backward compatible "
"support for U2F/CTAP1 protocol."),
)
app_commands.add_argument(
"--regen-keys",
action="store_true",
default=False,
dest="regenerate_keys",
help=("Forces the generation of files (certificates and private keys) "
"under the crypto_data/ directory. "
"This is useful to allow flashing multiple OpenSK authenticators "
"in a row without them being considered clones."
),
)
app_commands.add_argument(
"--debug",
action="append_const",
const="debug_ctap",
dest="features",
help=(
"Compiles and installs the OpenSK application in debug mode "
"in a row without them being considered clones."),
)
app_commands.add_argument(
"--debug",
action="append_const",
const="debug_ctap",
dest="features",
help=("Compiles and installs the OpenSK application in debug mode "
"(i.e. more debug messages will be sent over the console port "
"such as hexdumps of packets)."
),
)
apps = app_commands.add_mutually_exclusive_group()
apps.add_argument(
"--opensk",
dest="application",
action="store_const",
const="ctap2",
help="Compiles and installs the OpenSK application."
)
apps.add_argument(
"--crypto_bench",
dest="application",
action="store_const",
const="crypto_bench",
help=(
"Compiles and installs the crypto_bench example that tests "
"the performance of the cryptographic algorithms on the board."
)
)
app_commands.set_defaults(features=["with_ctap1"])
main(parser.parse_args())
"such as hexdumps of packets)."),
)
apps = app_commands.add_mutually_exclusive_group()
apps.add_argument(
"--opensk",
dest="application",
action="store_const",
const="ctap2",
help="Compiles and installs the OpenSK application.")
apps.add_argument(
"--crypto_bench",
dest="application",
action="store_const",
const="crypto_bench",
help=("Compiles and installs the crypto_bench example that tests "
"the performance of the cryptographic algorithms on the board."))
app_commands.set_defaults(features=["with_ctap1"])
main(parser.parse_args())
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment