From 159807e2faa50a2940f09d57dfa6132220508cce Mon Sep 17 00:00:00 2001 From: Juan Castillo Date: Tue, 15 Dec 2015 16:37:57 +0000 Subject: [PATCH] cert_create: update help message The help message printed by the cert_create tool using the command line option -h (or --help) does not correctly list all the available command line options. This patch reworks the print_help() function to print the help messages in a data driven approach. For each command line option registered, an optional help message can be specified, which will be printed by print_help(). Help messages for the TBBR options (certificates, keys and images) are also provided. Fix a small bug in the short options string passed to getopt_long: the ':' was missing in the '-a' option (this option must take an argument). Fixes ARM-software/tf-issues#337 Change-Id: I9d08c2dfd349022808fcc884724f677eefdc1452 --- tools/cert_create/include/cert.h | 1 + tools/cert_create/include/cmd_opt.h | 9 ++- tools/cert_create/include/ext.h | 1 + tools/cert_create/include/key.h | 1 + tools/cert_create/src/cert.c | 14 ++-- tools/cert_create/src/cmd_opt.c | 32 ++++++-- tools/cert_create/src/ext.c | 11 ++- tools/cert_create/src/key.c | 12 +-- tools/cert_create/src/main.c | 108 ++++++++++++++------------ tools/cert_create/src/tbbr/tbb_cert.c | 13 +++- tools/cert_create/src/tbbr/tbb_ext.c | 8 ++ tools/cert_create/src/tbbr/tbb_key.c | 7 ++ 12 files changed, 145 insertions(+), 72 deletions(-) diff --git a/tools/cert_create/include/cert.h b/tools/cert_create/include/cert.h index 11381c93..d38353a1 100644 --- a/tools/cert_create/include/cert.h +++ b/tools/cert_create/include/cert.h @@ -57,6 +57,7 @@ struct cert_s { const char *opt; /* Command line option to pass filename */ const char *fn; /* Filename to save the certificate */ const char *cn; /* Subject CN (Company Name) */ + const char *help_msg; /* Help message */ /* These fields must be defined statically */ int key; /* Key to be signed */ diff --git a/tools/cert_create/include/cmd_opt.h b/tools/cert_create/include/cmd_opt.h index ca48d7ca..389aa233 100644 --- a/tools/cert_create/include/cmd_opt.h +++ b/tools/cert_create/include/cmd_opt.h @@ -42,9 +42,16 @@ enum { CMD_OPT_EXT }; +/* Structure to define a command line option */ +typedef struct cmd_opt_s { + struct option long_opt; + const char *help_msg; +} cmd_opt_t; + /* Exported API*/ -int cmd_opt_add(const char *name, int has_arg, int val); +void cmd_opt_add(const cmd_opt_t *cmd_opt); const struct option *cmd_opt_get_array(void); const char *cmd_opt_get_name(int idx); +const char *cmd_opt_get_help_msg(int idx); #endif /* CMD_OPT_H_ */ diff --git a/tools/cert_create/include/ext.h b/tools/cert_create/include/ext.h index 0ede3651..52092b50 100644 --- a/tools/cert_create/include/ext.h +++ b/tools/cert_create/include/ext.h @@ -50,6 +50,7 @@ typedef struct ext_s { const char *oid; /* OID of the extension */ const char *sn; /* Short name */ const char *ln; /* Long description */ + const char *help_msg; /* Help message */ int asn1_type; /* OpenSSL ASN1 type of the extension data. * Supported types are: * - V_ASN1_INTEGER diff --git a/tools/cert_create/include/key.h b/tools/cert_create/include/key.h index 6995a063..2171679c 100644 --- a/tools/cert_create/include/key.h +++ b/tools/cert_create/include/key.h @@ -64,6 +64,7 @@ enum { typedef struct key_s { int id; /* Key id */ const char *opt; /* Command line option to specify a key */ + const char *help_msg; /* Help message */ const char *desc; /* Key description (debug purposes) */ char *fn; /* Filename to load/store the key */ EVP_PKEY *key; /* Key container */ diff --git a/tools/cert_create/src/cert.c b/tools/cert_create/src/cert.c index bf526451..a559832e 100644 --- a/tools/cert_create/src/cert.c +++ b/tools/cert_create/src/cert.c @@ -183,19 +183,21 @@ int cert_new(cert_t *cert, int days, int ca, STACK_OF(X509_EXTENSION) * sk) int cert_init(void) { + cmd_opt_t cmd_opt; cert_t *cert; - int rc = 0; unsigned int i; for (i = 0; i < num_certs; i++) { cert = &certs[i]; - rc = cmd_opt_add(cert->opt, required_argument, CMD_OPT_CERT); - if (rc != 0) { - break; - } + cmd_opt.long_opt.name = cert->opt; + cmd_opt.long_opt.has_arg = required_argument; + cmd_opt.long_opt.flag = NULL; + cmd_opt.long_opt.val = CMD_OPT_CERT; + cmd_opt.help_msg = cert->help_msg; + cmd_opt_add(&cmd_opt); } - return rc; + return 0; } cert_t *cert_get_by_opt(const char *opt) diff --git a/tools/cert_create/src/cmd_opt.c b/tools/cert_create/src/cmd_opt.c index 3847b98d..ecf84ab9 100644 --- a/tools/cert_create/src/cmd_opt.c +++ b/tools/cert_create/src/cmd_opt.c @@ -28,26 +28,35 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include +#include #include +#include "debug.h" /* Command line options */ static struct option long_opt[CMD_OPT_MAX_NUM+1]; +static const char *help_msg[CMD_OPT_MAX_NUM+1]; static int num_reg_opt; -int cmd_opt_add(const char *name, int has_arg, int val) +void cmd_opt_add(const cmd_opt_t *cmd_opt) { + assert(cmd_opt != NULL); + if (num_reg_opt >= CMD_OPT_MAX_NUM) { - return -1; + ERROR("Out of memory. Please increase CMD_OPT_MAX_NUM\n"); + exit(1); } - long_opt[num_reg_opt].name = name; - long_opt[num_reg_opt].has_arg = has_arg; + + long_opt[num_reg_opt].name = cmd_opt->long_opt.name; + long_opt[num_reg_opt].has_arg = cmd_opt->long_opt.has_arg; long_opt[num_reg_opt].flag = 0; - long_opt[num_reg_opt].val = val; - num_reg_opt++; + long_opt[num_reg_opt].val = cmd_opt->long_opt.val; - return 0; + help_msg[num_reg_opt] = cmd_opt->help_msg; + + num_reg_opt++; } const struct option *cmd_opt_get_array(void) @@ -63,3 +72,12 @@ const char *cmd_opt_get_name(int idx) return long_opt[idx].name; } + +const char *cmd_opt_get_help_msg(int idx) +{ + if (idx >= num_reg_opt) { + return NULL; + } + + return help_msg[idx]; +} diff --git a/tools/cert_create/src/ext.c b/tools/cert_create/src/ext.c index 14aef661..3f56edb7 100644 --- a/tools/cert_create/src/ext.c +++ b/tools/cert_create/src/ext.c @@ -69,6 +69,7 @@ IMPLEMENT_ASN1_FUNCTIONS(HASH) */ int ext_init(void) { + cmd_opt_t cmd_opt; ext_t *ext; X509V3_EXT_METHOD *m; int nid, ret; @@ -78,10 +79,12 @@ int ext_init(void) ext = &extensions[i]; /* Register command line option */ if (ext->opt) { - if (cmd_opt_add(ext->opt, required_argument, - CMD_OPT_EXT)) { - return 1; - } + cmd_opt.long_opt.name = ext->opt; + cmd_opt.long_opt.has_arg = required_argument; + cmd_opt.long_opt.flag = NULL; + cmd_opt.long_opt.val = CMD_OPT_EXT; + cmd_opt.help_msg = ext->help_msg; + cmd_opt_add(&cmd_opt); } /* Register the extension OID in OpenSSL */ if (ext->oid == NULL) { diff --git a/tools/cert_create/src/key.c b/tools/cert_create/src/key.c index 76d528b9..a7ee7596 100644 --- a/tools/cert_create/src/key.c +++ b/tools/cert_create/src/key.c @@ -194,6 +194,7 @@ int key_store(key_t *key) int key_init(void) { + cmd_opt_t cmd_opt; key_t *key; int rc = 0; unsigned int i; @@ -201,11 +202,12 @@ int key_init(void) for (i = 0; i < num_keys; i++) { key = &keys[i]; if (key->opt != NULL) { - rc = cmd_opt_add(key->opt, required_argument, - CMD_OPT_KEY); - if (rc != 0) { - break; - } + cmd_opt.long_opt.name = key->opt; + cmd_opt.long_opt.has_arg = required_argument; + cmd_opt.long_opt.flag = NULL; + cmd_opt.long_opt.val = CMD_OPT_KEY; + cmd_opt.help_msg = key->help_msg; + cmd_opt_add(&cmd_opt); } } diff --git a/tools/cert_create/src/main.c b/tools/cert_create/src/main.c index 3e4f8c57..3d2b4ba2 100644 --- a/tools/cert_create/src/main.c +++ b/tools/cert_create/src/main.c @@ -28,6 +28,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include +#include #include #include #include @@ -81,36 +83,7 @@ #define VAL_DAYS 7300 #define ID_TO_BIT_MASK(id) (1 << id) #define NUM_ELEM(x) ((sizeof(x)) / (sizeof(x[0]))) - -/* Files */ -enum { - /* Image file names (inputs) */ - BL2_ID = 0, - SCP_BL2_ID, - BL31_ID, - BL32_ID, - BL33_ID, - /* Certificate file names (outputs) */ - TRUSTED_BOOT_FW_CERT_ID, - TRUSTED_KEY_CERT_ID, - SCP_FW_KEY_CERT_ID, - SCP_FW_CONTENT_CERT_ID, - SOC_FW_KEY_CERT_ID, - SOC_FW_CONTENT_CERT_ID, - TRUSTED_OS_FW_KEY_CERT_ID, - TRUSTED_OS_FW_CONTENT_CERT_ID, - NON_TRUSTED_FW_KEY_CERT_ID, - NON_TRUSTED_FW_CONTENT_CERT_ID, - /* Key file names (input/output) */ - ROT_KEY_ID, - TRUSTED_WORLD_KEY_ID, - NON_TRUSTED_WORLD_KEY_ID, - SCP_BL2_KEY_ID, - BL31_KEY_ID, - BL32_KEY_ID, - BL33_KEY_ID, - NUM_OPTS -}; +#define HELP_OPT_MAX_LEN 128 /* Global options */ static int key_alg; @@ -142,7 +115,14 @@ static const char *key_algs_str[] = { static void print_help(const char *cmd, const struct option *long_opt) { - int i = 0; + int rem, i = 0; + const struct option *opt; + char line[HELP_OPT_MAX_LEN]; + char *p; + + assert(cmd != NULL); + assert(long_opt != NULL); + printf("\n\n"); printf("The certificate generation tool loads the binary images and\n" "optionally the RSA keys, and outputs the key and content\n" @@ -150,18 +130,28 @@ static void print_help(const char *cmd, const struct option *long_opt) "If keys are provided, they must be in PEM format.\n" "Certificates are generated in DER format.\n"); printf("\n"); - printf("Usage:\n\n"); - printf(" %s [-hknp] \\\n", cmd); - for (i = 0; i < NUM_OPTS; i++) { - printf(" --%s \\\n", long_opt[i].name); + printf("Usage:\n"); + printf("\t%s [OPTIONS]\n\n", cmd); + + printf("Available options:\n"); + i = 0; + opt = long_opt; + while (opt->name) { + p = line; + rem = HELP_OPT_MAX_LEN; + if (isalpha(opt->val)) { + /* Short format */ + sprintf(p, "-%c,", (char)opt->val); + p += 3; + rem -= 3; + } + snprintf(p, rem, "--%s %s", opt->name, + (opt->has_arg == required_argument) ? "" : ""); + printf("\t%-32s %s\n", line, cmd_opt_get_help_msg(i)); + opt++; + i++; } printf("\n"); - printf("-a Key algorithm: rsa (default), ecdsa\n"); - printf("-h Print help and exit\n"); - printf("-k Save key pairs into files. Filenames must be provided\n"); - printf("-n Generate new key pairs if no key files are provided\n"); - printf("-p Print the certificates in the standard output\n"); - printf("\n"); exit(0); } @@ -237,6 +227,30 @@ static void check_cmd_params(void) } } +/* Common command line options */ +static const cmd_opt_t common_cmd_opt[] = { + { + { "help", no_argument, NULL, 'h' }, + "Print this message and exit" + }, + { + { "key-alg", required_argument, NULL, 'a' }, + "Key algorithm: 'rsa' (default), 'ecdsa'" + }, + { + { "save-keys", no_argument, NULL, 'k' }, + "Save key pairs into files. Filenames must be provided" + }, + { + { "new-keys", no_argument, NULL, 'n' }, + "Generate new key pairs if no key files are provided" + }, + { + { "print-cert", no_argument, NULL, 'p' }, + "Print the certificates in the standard output" + } +}; + int main(int argc, char *argv[]) { STACK_OF(X509_EXTENSION) * sk = NULL; @@ -260,11 +274,9 @@ int main(int argc, char *argv[]) key_alg = KEY_ALG_RSA; /* Add common command line options */ - cmd_opt_add("key-alg", required_argument, 'a'); - cmd_opt_add("help", no_argument, 'h'); - cmd_opt_add("save-keys", no_argument, 'k'); - cmd_opt_add("new-chain", no_argument, 'n'); - cmd_opt_add("print-cert", no_argument, 'p'); + for (i = 0; i < NUM_ELEM(common_cmd_opt); i++) { + cmd_opt_add(&common_cmd_opt[i]); + } /* Initialize the certificates */ if (cert_init() != 0) { @@ -289,7 +301,7 @@ int main(int argc, char *argv[]) while (1) { /* getopt_long stores the option index here. */ - c = getopt_long(argc, argv, "ahknp", cmd_opt, &opt_idx); + c = getopt_long(argc, argv, "a:hknp", cmd_opt, &opt_idx); /* Detect the end of the options. */ if (c == -1) { @@ -333,7 +345,7 @@ int main(int argc, char *argv[]) break; case '?': default: - printf("%s\n", optarg); + print_help(argv[0], cmd_opt); exit(1); } } diff --git a/tools/cert_create/src/tbbr/tbb_cert.c b/tools/cert_create/src/tbbr/tbb_cert.c index 20be59f7..7a50ab35 100644 --- a/tools/cert_create/src/tbbr/tbb_cert.c +++ b/tools/cert_create/src/tbbr/tbb_cert.c @@ -43,6 +43,7 @@ static cert_t tbb_certs[] = { [TRUSTED_BOOT_FW_CERT] = { .id = TRUSTED_BOOT_FW_CERT, .opt = "tb-fw-cert", + .help_msg = "Trusted Boot FW Certificate (output file)", .fn = NULL, .cn = "Trusted Boot FW Certificate", .key = ROT_KEY, @@ -55,6 +56,7 @@ static cert_t tbb_certs[] = { [TRUSTED_KEY_CERT] = { .id = TRUSTED_KEY_CERT, .opt = "trusted-key-cert", + .help_msg = "Trusted Key Certificate (output file)", .fn = NULL, .cn = "Trusted Key Certificate", .key = ROT_KEY, @@ -68,6 +70,7 @@ static cert_t tbb_certs[] = { [SCP_FW_KEY_CERT] = { .id = SCP_FW_KEY_CERT, .opt = "scp-fw-key-cert", + .help_msg = "SCP Firmware Key Certificate (output file)", .fn = NULL, .cn = "SCP Firmware Key Certificate", .key = TRUSTED_WORLD_KEY, @@ -80,6 +83,7 @@ static cert_t tbb_certs[] = { [SCP_FW_CONTENT_CERT] = { .id = SCP_FW_CONTENT_CERT, .opt = "scp-fw-cert", + .help_msg = "SCP Firmware Content Certificate (output file)", .fn = NULL, .cn = "SCP Firmware Content Certificate", .key = SCP_FW_CONTENT_CERT_KEY, @@ -92,6 +96,7 @@ static cert_t tbb_certs[] = { [SOC_FW_KEY_CERT] = { .id = SOC_FW_KEY_CERT, .opt = "soc-fw-key-cert", + .help_msg = "SoC Firmware Key Certificate (output file)", .fn = NULL, .cn = "SoC Firmware Key Certificate", .key = TRUSTED_WORLD_KEY, @@ -104,6 +109,7 @@ static cert_t tbb_certs[] = { [SOC_FW_CONTENT_CERT] = { .id = SOC_FW_CONTENT_CERT, .opt = "soc-fw-cert", + .help_msg = "SoC Firmware Content Certificate (output file)", .fn = NULL, .cn = "SoC Firmware Content Certificate", .key = SOC_FW_CONTENT_CERT_KEY, @@ -116,6 +122,7 @@ static cert_t tbb_certs[] = { [TRUSTED_OS_FW_KEY_CERT] = { .id = TRUSTED_OS_FW_KEY_CERT, .opt = "tos-fw-key-cert", + .help_msg = "Trusted OS Firmware Key Certificate (output file)", .fn = NULL, .cn = "Trusted OS Firmware Key Certificate", .key = TRUSTED_WORLD_KEY, @@ -128,6 +135,7 @@ static cert_t tbb_certs[] = { [TRUSTED_OS_FW_CONTENT_CERT] = { .id = TRUSTED_OS_FW_CONTENT_CERT, .opt = "tos-fw-cert", + .help_msg = "Trusted OS Firmware Content Certificate (output file)", .fn = NULL, .cn = "Trusted OS Firmware Content Certificate", .key = TRUSTED_OS_FW_CONTENT_CERT_KEY, @@ -140,6 +148,7 @@ static cert_t tbb_certs[] = { [NON_TRUSTED_FW_KEY_CERT] = { .id = NON_TRUSTED_FW_KEY_CERT, .opt = "nt-fw-key-cert", + .help_msg = "Non-Trusted Firmware Key Certificate (output file)", .fn = NULL, .cn = "Non-Trusted Firmware Key Certificate", .key = NON_TRUSTED_WORLD_KEY, @@ -152,6 +161,7 @@ static cert_t tbb_certs[] = { [NON_TRUSTED_FW_CONTENT_CERT] = { .id = NON_TRUSTED_FW_CONTENT_CERT, .opt = "nt-fw-cert", + .help_msg = "Non-Trusted Firmware Content Certificate (output file)", .fn = NULL, .cn = "Non-Trusted Firmware Content Certificate", .key = NON_TRUSTED_FW_CONTENT_CERT_KEY, @@ -164,8 +174,9 @@ static cert_t tbb_certs[] = { [FWU_CERT] = { .id = FWU_CERT, .opt = "fwu-cert", + .help_msg = "Firmware Update Certificate (output file)", .fn = NULL, - .cn = "FWU Certificate", + .cn = "Firmware Update Certificate", .key = ROT_KEY, .issuer = FWU_CERT, .ext = { diff --git a/tools/cert_create/src/tbbr/tbb_ext.c b/tools/cert_create/src/tbbr/tbb_ext.c index 1400fbfd..8bcb0704 100644 --- a/tools/cert_create/src/tbbr/tbb_ext.c +++ b/tools/cert_create/src/tbbr/tbb_ext.c @@ -61,6 +61,7 @@ static ext_t tbb_ext[] = { [TRUSTED_BOOT_FW_HASH_EXT] = { .oid = TRUSTED_BOOT_FW_HASH_OID, .opt = "tb-fw", + .help_msg = "Trusted Boot Firmware image file", .sn = "TrustedBootFirmwareHash", .ln = "Trusted Boot Firmware hash (SHA256)", .asn1_type = V_ASN1_OCTET_STRING, @@ -93,6 +94,7 @@ static ext_t tbb_ext[] = { [SCP_FW_HASH_EXT] = { .oid = SCP_FW_HASH_OID, .opt = "scp-fw", + .help_msg = "SCP Firmware image file", .sn = "SCPFirmwareHash", .ln = "SCP Firmware hash (SHA256)", .asn1_type = V_ASN1_OCTET_STRING, @@ -109,6 +111,7 @@ static ext_t tbb_ext[] = { [SOC_AP_FW_HASH_EXT] = { .oid = SOC_AP_FW_HASH_OID, .opt = "soc-fw", + .help_msg = "SoC AP Firmware image file", .sn = "SoCAPFirmwareHash", .ln = "SoC AP Firmware hash (SHA256)", .asn1_type = V_ASN1_OCTET_STRING, @@ -125,6 +128,7 @@ static ext_t tbb_ext[] = { [TRUSTED_OS_FW_HASH_EXT] = { .oid = TRUSTED_OS_FW_HASH_OID, .opt = "tos-fw", + .help_msg = "Trusted OS image file", .sn = "TrustedOSHash", .ln = "Trusted OS hash (SHA256)", .asn1_type = V_ASN1_OCTET_STRING, @@ -141,6 +145,7 @@ static ext_t tbb_ext[] = { [NON_TRUSTED_WORLD_BOOTLOADER_HASH_EXT] = { .oid = NON_TRUSTED_WORLD_BOOTLOADER_HASH_OID, .opt = "nt-fw", + .help_msg = "Non-Trusted World Bootloader image file", .sn = "NonTrustedWorldBootloaderHash", .ln = "Non-Trusted World hash (SHA256)", .asn1_type = V_ASN1_OCTET_STRING, @@ -149,6 +154,7 @@ static ext_t tbb_ext[] = { [SCP_FWU_CFG_HASH_EXT] = { .oid = SCP_FWU_CFG_HASH_OID, .opt = "scp-fwu-cfg", + .help_msg = "SCP Firmware Update Config image file", .sn = "SCPFWUpdateConfig", .ln = "SCP Firmware Update Config hash (SHA256)", .asn1_type = V_ASN1_OCTET_STRING, @@ -158,6 +164,7 @@ static ext_t tbb_ext[] = { [AP_FWU_CFG_HASH_EXT] = { .oid = AP_FWU_CFG_HASH_OID, .opt = "ap-fwu-cfg", + .help_msg = "AP Firmware Update Config image file", .sn = "APFWUpdateConfig", .ln = "AP Firmware Update Config hash (SHA256)", .asn1_type = V_ASN1_OCTET_STRING, @@ -167,6 +174,7 @@ static ext_t tbb_ext[] = { [FWU_HASH_EXT] = { .oid = FWU_HASH_OID, .opt = "fwu", + .help_msg = "Firmware Updater image file", .sn = "FWUpdaterHash", .ln = "Firmware Updater hash (SHA256)", .asn1_type = V_ASN1_OCTET_STRING, diff --git a/tools/cert_create/src/tbbr/tbb_key.c b/tools/cert_create/src/tbbr/tbb_key.c index 089425a9..1d2f7891 100644 --- a/tools/cert_create/src/tbbr/tbb_key.c +++ b/tools/cert_create/src/tbbr/tbb_key.c @@ -39,36 +39,43 @@ static key_t tbb_keys[] = { [ROT_KEY] = { .id = ROT_KEY, .opt = "rot-key", + .help_msg = "Root Of Trust key (input/output file)", .desc = "Root Of Trust key" }, [TRUSTED_WORLD_KEY] = { .id = TRUSTED_WORLD_KEY, .opt = "trusted-world-key", + .help_msg = "Trusted World key (input/output file)", .desc = "Trusted World key" }, [NON_TRUSTED_WORLD_KEY] = { .id = NON_TRUSTED_WORLD_KEY, .opt = "non-trusted-world-key", + .help_msg = "Non Trusted World key (input/output file)", .desc = "Non Trusted World key" }, [SCP_FW_CONTENT_CERT_KEY] = { .id = SCP_FW_CONTENT_CERT_KEY, .opt = "scp-fw-key", + .help_msg = "SCP Firmware Content Certificate key (input/output file)", .desc = "SCP Firmware Content Certificate key" }, [SOC_FW_CONTENT_CERT_KEY] = { .id = SOC_FW_CONTENT_CERT_KEY, .opt = "soc-fw-key", + .help_msg = "SoC Firmware Content Certificate key (input/output file)", .desc = "SoC Firmware Content Certificate key" }, [TRUSTED_OS_FW_CONTENT_CERT_KEY] = { .id = TRUSTED_OS_FW_CONTENT_CERT_KEY, .opt = "tos-fw-key", + .help_msg = "Trusted OS Firmware Content Certificate key (input/output file)", .desc = "Trusted OS Firmware Content Certificate key" }, [NON_TRUSTED_FW_CONTENT_CERT_KEY] = { .id = NON_TRUSTED_FW_CONTENT_CERT_KEY, .opt = "nt-fw-key", + .help_msg = "Non Trusted Firmware Content Certificate key (input/output file)", .desc = "Non Trusted Firmware Content Certificate key" } }; -- 2.30.2