Skip to content

Commit

Permalink
Merge pull request #20 from angaza/dev-qa-signoff-generator-tool
Browse files Browse the repository at this point in the history
Nexus Keycode/Channel integration test plan keycode generator scripts
  • Loading branch information
ricehornet authored Apr 21, 2022
2 parents f2d1817 + f3e402c commit d765127
Show file tree
Hide file tree
Showing 5 changed files with 495 additions and 38 deletions.
58 changes: 55 additions & 3 deletions nexus_keycode/test/tools/test_generate_full_keycode.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
from unittest import TestCase

import bitstring
from nexus_keycode.tools.generate_full_keycode import create_full_credit_message
import nexus_keycode.protocols.full as protocol
from nexus_keycode.tools.generate_full_keycode import (
create_full_channel_message,
create_full_credit_message,
)


class TestCreateFullCreditMessage(TestCase):
def test_invalid_msg_type__raises(self):
self.assertRaises(
ValueError, create_full_credit_message, 15, "INVALID_TYPE", b"\x12\xab" * 8, hours=168
ValueError,
create_full_credit_message,
15,
"INVALID_TYPE",
b"\x12\xab" * 8,
hours=168,
)

def test_valid_add_credit__returns_expected(self):
Expand All @@ -29,3 +36,48 @@ def test_valid_unlock__returns_expected(self):
# 'unlock' is a special case of set credit for full protocol
self.assertEqual(protocol.FullMessageType.SET_CREDIT, msg.message_type)
self.assertEqual(u"*425 687 269 124 32#", msg.to_keycode())


class TestCreateFullChannelMessage(TestCase):
def test_invalid_msg_type__raises(self):
self.assertRaises(
ValueError,
create_full_channel_message,
"INVALID_TYPE",
b"\x12\xab" * 8,
15,
b"\xab\x12" * 8,
3,
)

def test_valid_unlink__returns_expected(self):
msg = create_full_channel_message("UNLINK", b"\x12\xab" * 8, 15)
self.assertEqual(protocol.FullMessageType.PASSTHROUGH_COMMAND, msg.message_type)
self.assertEqual(u"*815 310 472 88#", msg.to_keycode())

def test_valid_link__returns_expected(self):
msg = create_full_channel_message(
"LINK", b"\x12\xab" * 8, 15, b"\xab\x12" * 8, 3
)
self.assertEqual(protocol.FullMessageType.PASSTHROUGH_COMMAND, msg.message_type)
self.assertEqual(u"*819 501 596 413 845#", msg.to_keycode())

def test_invalid_link__no_accessory_key__raises(self):
self.assertRaises(
ValueError,
create_full_channel_message,
"LINK",
b"\x12\xab" * 8,
15,
accessory_command_count=3,
)

def test_invalid_link__no_accessory_count__raises(self):
self.assertRaises(
ValueError,
create_full_channel_message,
"LINK",
b"\x12\xab" * 8,
15,
b"\xab\x12" * 8,
)
168 changes: 133 additions & 35 deletions nexus_keycode/tools/generate_full_keycode.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,87 @@
# Small Nexus Keycode Protocol Credit Keycode Generator
# Full Nexus Keycode Protocol Keycode Generator
# Convenience script to generate keycodes 'on the fly' for testing/QA purposes.

import argparse
import codecs
import nexus_keycode.protocols.full as protocol

import nexus_keycode.protocols.full as protocol
from nexus_keycode.protocols.channel_origin_commands import ChannelOriginAction

VALID_MESSAGE_TYPES = ["SET", "UNLOCK", "ADD"]
VALID_NXK_MESSAGE_TYPES = ["SET", "UNLOCK", "ADD"]
VALID_NXC_MESSAGE_TYPES = ["LINK", "UNLINK"]


def create_full_credit_message(msg_id, msg_type, secret_key, hours=None):

if msg_type not in VALID_MESSAGE_TYPES:
if msg_type not in VALID_NXK_MESSAGE_TYPES:
raise ValueError(
u"Invalid message type, supported values are {}".format(VALID_MESSAGE_TYPES)
u"Invalid message type, supported values are {}".format(
VALID_NXK_MESSAGE_TYPES
)
)

if msg_type == "SET":
msg = protocol.FullMessage.set_credit(msg_id, hours, secret_key)
elif msg_type == "ADD":
msg = protocol.FullMessage.add_credit(msg_id, hours, secret_key)
if msg_type == "UNLOCK":
return protocol.FullMessage.unlock(msg_id, secret_key)

if hours is None:
raise ValueError(
u"Expected non-null `hours` argument for message type {}".format(msg_type)
)

if msg_type == "ADD":
return protocol.FullMessage.add_credit(msg_id, hours, secret_key)
else:
assert msg_type == "UNLOCK"
msg = protocol.FullMessage.unlock(msg_id, secret_key)
return msg
assert msg_type == "SET"
return protocol.FullMessage.set_credit(msg_id, hours, secret_key)


def create_full_channel_message(
msg_type,
controller_sym_key,
controller_command_count,
accessory_sym_key=None,
accessory_command_count=None,
):

if msg_type not in VALID_NXC_MESSAGE_TYPES:
raise ValueError(
u"Invalid message type, supported values are {}".format(
VALID_NXC_MESSAGE_TYPES
)
)

if msg_type == "UNLINK":
return protocol.FactoryFullMessage.passthrough_channel_origin_command(
ChannelOriginAction.UNLINK_ALL_ACCESSORIES,
controller_sym_key=controller_sym_key,
controller_command_count=controller_command_count,
)
else:
assert msg_type == "LINK"
if accessory_sym_key is None or accessory_command_count is None:
raise ValueError(
u"Expected non-null accessory symmetric key and command count!"
)

return protocol.FactoryFullMessage.passthrough_channel_origin_command(
ChannelOriginAction.LINK_ACCESSORY_MODE_3,
controller_sym_key=controller_sym_key,
controller_command_count=controller_command_count,
accessory_sym_key=accessory_sym_key,
accessory_command_count=accessory_command_count,
)


if __name__ == "__main__":

def check_message_type(message_type):
if not isinstance(message_type, str) or str(message_type) not in VALID_MESSAGE_TYPES:
VALID_MESSAGE_TYPES_ALL = VALID_NXK_MESSAGE_TYPES + VALID_NXC_MESSAGE_TYPES
if (
not isinstance(message_type, str)
or str(message_type) not in VALID_MESSAGE_TYPES_ALL
):
raise argparse.ArgumentTypeError(
"'{}' is not in {}".format(message_type, VALID_MESSAGE_TYPES)
u"'{}' is not in {}".format(message_type, VALID_MESSAGE_TYPES_ALL)
)
return str(message_type)

Expand All @@ -40,62 +90,110 @@ def check_secret_key(secret_key):
int(secret_key, 16)
except ValueError:
raise argparse.ArgumentTypeError(
"'{}' contains characters that are not valid hexadecimal values (a-f, 0-9)".format(
secret_key
)
u"'{}' contains characters that are not valid hexadecimal values "
"(a-f, 0-9)".format(secret_key)
)

if len(secret_key) != 32:
raise argparse.ArgumentTypeError(
"'{}' is not a 16-byte secret key (must be 32 hex characters)".format(secret_key)
u"'{}' is not a 16-byte secret key (must be 32 hex characters)".format(
secret_key
)
)

decode_hex = codecs.getdecoder("hex_codec")
return decode_hex(secret_key)[0]

argparser = argparse.ArgumentParser(
description="Generate credit keycodes for Nexus Keycode 'small' protocol.",
epilog="python generate_small_keycode.py -t ADD -i 0 -k abcdef0011223344556677889900ffee -d 30",
description="Generate credit or Channel keycodes for Nexus Keycode 'full' protocol.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="python generate_full_keycode.py -t ADD -i 0 -k abcdef0011223344556677889900ffee -hr 30\n"
"python generate_full_keycode.py -t LINK -ck abcdef0011223344556677889900ffee -cc 15 -ak aabbccddeeff11223344556677889900 -ac 3",
)

# Note: 'UNLOCK' is implemented as a special case of "ADD", but for the purposes of this tool they are considered separate 'types'.
# Note: 'UNLOCK' is implemented as a special case of "SET", but for the purposes of this tool they are considered separate 'types'.
argparser.add_argument(
"-t",
"--message_type",
required=True,
type=check_message_type,
help="Credit keycode type. Valid options are: 'SET', 'ADD', 'UNLOCK'",
help="Keycode type. Valid options are: 'SET', 'ADD', 'UNLOCK', 'LINK', and 'UNLINK'",
)
argparser.add_argument(
"-i",
"--message_id",
required=True,
required=False,
type=int,
help="Keycode Message ID (e.g. 0, 1, 2, 3...)",
)
argparser.add_argument(
"-k",
"--secret_key",
required=True,
required=False,
type=check_secret_key,
help="Hex-encoded 16-byte secret key, 32 characters long (e.g. 'abcdef0011223344556677889900ffee'",
help="Hex-encoded 16-byte symmetric key for Nexus Keycode, 32 characters long (e.g. 'abcdef0011223344556677889900ffee')",
)
argparser.add_argument(
"-hr", "--hours", required=True, type=int, help="Hours of credit. Ignored for 'UNLOCK'"
"-hr", "--hours", required=False, type=int, help="Hours of credit."
)
argparser.add_argument(
"-ck",
"--controller_key",
required=False,
type=check_secret_key,
help="Hex-encoded 16-byte Nexus Channel Controller symmetric key, 32 characters long (e.g. 'abcdef0011223344556677889900ffee')",
)
argparser.add_argument(
"-cc",
"--controller_count",
required=False,
type=int,
help="Nexus Channel Controller Command Count",
)
argparser.add_argument(
"-ak",
"--accessory_key",
required=False,
type=check_secret_key,
help="Hex-encoded 16-byte Nexus Channel Accessory symmetric key, 32 characters long (e.g. 'abcdef0011223344556677889900ffee')",
)
argparser.add_argument(
"-ac",
"--accessory_count",
required=False,
type=int,
help="Nexus Channel Accessory Command Count",
)

# message_type, message_id, secret_key, days
args = argparser.parse_args()

msg_type = args.message_type
msg_id = args.message_id
key = args.secret_key
hours = args.hours
if msg_type in VALID_NXK_MESSAGE_TYPES:
msg_id = args.message_id
key = args.secret_key
hours = args.hours
msg = create_full_credit_message(msg_id, msg_type, key, hours)

print(
(u"{}\n" "Message Type={}\n" "Message ID={}\n" "Message Hours={}\n").format(
msg.to_keycode(), msg_type, msg_id, hours
)
)
else:
controller_key = args.controller_key
controller_count = args.controller_count
accessory_key = args.accessory_key
accessory_count = args.accessory_count

msg = create_full_credit_message(msg_id, msg_type, key, hours)
msg = create_full_channel_message(
msg_type, controller_key, controller_count, accessory_key, accessory_count
)

print(
("{}\n" "Message Type={}\n" "Message ID={}\n" "Message Hours={}\n").format(
msg.to_keycode(), msg_type, msg_id, hours
print(
(
u"{}\n"
"Message Type={}\n"
"Controller Count={}\n"
"Accessory Count={}\n"
).format(msg.to_keycode(), msg_type, controller_count, accessory_count)
)
)
Empty file.
Loading

0 comments on commit d765127

Please sign in to comment.