#!/usr/bin/env python

#
#    Copyright (c) 2020-2022 Project CHIP Authors
#    Copyright (c) 2019 Google LLC.
#    Copyright (c) 2013-2017 Nest Labs, Inc.
#    All rights reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
#

#
#    @file
#      This file implements a Python script to generate a C/C++ header
#      for individual ASN1 Object IDs (OIDs) that are used in Matter
#      TLV encodings (notably the Matter Certificate object).
#

from __future__ import absolute_import, print_function

import optparse
import sys


def identity(n):
    return n


# OID labels
ansi_X9_62 = identity
certicom = identity
characteristicTwo = identity
matter = identity
curve = identity
curves = identity
digest_algorithm = identity
dod = identity
ds = identity
enterprise = identity
organization = identity
internet = identity
iso = identity
itu_t = identity
joint_iso_ccitt = identity
keyType = identity
mechanisms = identity
member_body = identity
pkcs1 = identity
pkcs = identity
pkix = identity
prime = identity
private = identity
rsadsi = identity
schemes = identity
security = identity
signatures = identity
us = identity
zigbee = identity

# OID Categories
oidCategories = [
    ("PubKeyAlgo",         0x0100),
    ("SigAlgo",            0x0200),
    ("AttributeType",      0x0300),
    ("EllipticCurve",      0x0400),
    ("Extension",          0x0500),
    ("KeyPurpose",         0x0600)
]

# Table of well-known ASN.1 object IDs
#
oids = [

    # !!! WARNING !!!
    #
    # The enumerated values associated with individual object IDs are used in Matter TLV encodings (notably the Matter Certificate object).
    # Because of this, the Enum Values assigned to object IDs in this table MUST NOT BE CHANGED once in use.


    #                                              Enum
    # Category          Name                       Value    Object ID
    # ----------------- -------------------------- -------- ------------------------------------------------------------------------------------------------

    # Public Key Algorithms
    ("PubKeyAlgo",     "ECPublicKey",             1,       [
     iso(1), member_body(2), us(840), ansi_X9_62(10045), keyType(2), 1]),

    # Signature Algorithms
    # RFC 3279
    ("SigAlgo",        "ECDSAWithSHA256",         1,       [
     iso(1), member_body(2), us(840), ansi_X9_62(10045), signatures(4), 3, 2]),

    # X.509 Distinguished Name Attribute Types
    #   WARNING -- Assign no values higher than 127.
    ("AttributeType",  "CommonName",              1,
     [joint_iso_ccitt(2), ds(5), 4, 3]),
    ("AttributeType",  "Surname",                 2,
     [joint_iso_ccitt(2), ds(5), 4, 4]),
    ("AttributeType",  "SerialNumber",
     3,       [joint_iso_ccitt(2), ds(5), 4, 5]),
    ("AttributeType",  "CountryName",
     4,       [joint_iso_ccitt(2), ds(5), 4, 6]),
    ("AttributeType",  "LocalityName",
     5,       [joint_iso_ccitt(2), ds(5), 4, 7]),
    ("AttributeType",  "StateOrProvinceName",
     6,       [joint_iso_ccitt(2), ds(5), 4, 8]),
    ("AttributeType",  "OrganizationName",
     7,       [joint_iso_ccitt(2), ds(5), 4, 10]),
    ("AttributeType",  "OrganizationalUnitName",
     8,       [joint_iso_ccitt(2), ds(5), 4, 11]),
    ("AttributeType",  "Title",                   9,
     [joint_iso_ccitt(2), ds(5), 4, 12]),
    ("AttributeType",  "Name",                    10,
     [joint_iso_ccitt(2), ds(5), 4, 41]),
    ("AttributeType",  "GivenName",               11,
     [joint_iso_ccitt(2), ds(5), 4, 42]),
    ("AttributeType",  "Initials",                12,
     [joint_iso_ccitt(2), ds(5), 4, 43]),
    ("AttributeType",  "GenerationQualifier",
     13,      [joint_iso_ccitt(2), ds(5), 4, 44]),
    ("AttributeType",  "DNQualifier",             14,
     [joint_iso_ccitt(2), ds(5), 4, 46]),
    ("AttributeType",  "Pseudonym",               15,
     [joint_iso_ccitt(2), ds(5), 4, 65]),
    ("AttributeType",  "DomainComponent",         16,
     [itu_t(0), 9, 2342, 19200300, 100, 1, 25]),
    ("AttributeType",  "MatterNodeId",              17,      [iso(1), organization(
        3), dod(6), internet(1), private(4), enterprise(1), zigbee(37244), matter(1), 1]),
    ("AttributeType",  "MatterFirmwareSigningId",   18,      [iso(1), organization(
        3), dod(6), internet(1), private(4), enterprise(1), zigbee(37244), matter(1), 2]),
    ("AttributeType",  "MatterICACId",              19,      [iso(1), organization(
        3), dod(6), internet(1), private(4), enterprise(1), zigbee(37244), matter(1), 3]),
    ("AttributeType",  "MatterRCACId",              20,      [iso(1), organization(
        3), dod(6), internet(1), private(4), enterprise(1), zigbee(37244), matter(1), 4]),
    ("AttributeType",  "MatterFabricId",            21,      [iso(1), organization(
        3), dod(6), internet(1), private(4), enterprise(1), zigbee(37244), matter(1), 5]),
    ("AttributeType",  "MatterCASEAuthTag",         22,      [iso(1), organization(
        3), dod(6), internet(1), private(4), enterprise(1), zigbee(37244), matter(1), 6]),

    # Elliptic Curves
    ("EllipticCurve",  "prime256v1",              1,       [
     iso(1), member_body(2), us(840), ansi_X9_62(10045), curves(3), prime(1), 7]),

    # Certificate Extensions
    ("Extension",      "BasicConstraints",
     1,       [joint_iso_ccitt(2), ds(5), 29, 19]),
    ("Extension",      "KeyUsage",                2,
     [joint_iso_ccitt(2), ds(5), 29, 15]),
    ("Extension",      "ExtendedKeyUsage",
     3,       [joint_iso_ccitt(2), ds(5), 29, 37]),
    ("Extension",      "SubjectKeyIdentifier",
     4,       [joint_iso_ccitt(2), ds(5), 29, 14]),
    ("Extension",      "AuthorityKeyIdentifier",
     5,       [joint_iso_ccitt(2), ds(5), 29, 35]),
    ("Extension",      "CSRRequest",
     6,       [iso(1), member_body(2), us(840), rsadsi(113549), pkcs(1), 9, 14]),

    # Key Purposes
    ("KeyPurpose",     "ServerAuth",              1,       [iso(1), organization(
        3), dod(6), internet(1), security(5), mechanisms(5), pkix(7), 3, 1]),
    ("KeyPurpose",     "ClientAuth",              2,       [iso(1), organization(
        3), dod(6), internet(1), security(5), mechanisms(5), pkix(7), 3, 2]),
    ("KeyPurpose",     "CodeSigning",             3,       [iso(1), organization(
        3), dod(6), internet(1), security(5), mechanisms(5), pkix(7), 3, 3]),
    ("KeyPurpose",     "EmailProtection",         4,       [iso(1), organization(
        3), dod(6), internet(1), security(5), mechanisms(5), pkix(7), 3, 4]),
    ("KeyPurpose",     "TimeStamping",            5,       [iso(1), organization(
        3), dod(6), internet(1), security(5), mechanisms(5), pkix(7), 3, 8]),
    ("KeyPurpose",     "OCSPSigning",             6,       [iso(1), organization(
        3), dod(6), internet(1), security(5), mechanisms(5), pkix(7), 3, 9]),
]


def encodeOID(oid):

    assert len(oid) >= 2

    oid = [(oid[0]*40 + oid[1])] + oid[2:]

    encodedOID = []
    for val in oid:
        val, byte = divmod(val, 128)
        seg = [byte]
        while val > 0:
            val, byte = divmod(val, 128)
            seg.insert(0, byte + 0x80)
        encodedOID += (seg)

    return encodedOID


TEMPLATE = '''/*
 *
 *    Copyright (c) 2020-2022 Project CHIP Authors
 *    Copyright (c) 2019 Google LLC.
 *    Copyright (c) 2013-2017 Nest Labs, Inc.
 *    All rights reserved.
 *
 *    Licensed under the Apache License, Version 2.0 (the \"License\");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an \"AS IS\" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 *
 */

/**
 *    @file
 *      ASN.1 Object ID Definitions
 *
 *      !!! WARNING !!! WARNING !!! WARNING !!!
 *
 *      DO NOT EDIT THIS FILE! This file is generated by the
 *      gen-oid-table.py script.
 *
 *      To make changes, edit the script and re-run it to generate
 *      this file.
 *
 */

#pragma once

#include <cstdint>
#include <cstddef>

namespace chip {
namespace ASN1 {

enum OIDCategory
%(oid_category_enums)s

typedef uint16_t OID;

enum
%(oid_enums)s

struct OIDTableEntry
{
    OID EnumVal;
    const uint8_t *EncodedOID;
    uint16_t EncodedOIDLen;
};

struct OIDNameTableEntry
{
    OID EnumVal;
    const char *Name;
};

extern const OIDTableEntry sOIDTable[];
extern const OIDNameTableEntry sOIDNameTable[];
extern const size_t sOIDTableSize;

#ifdef ASN1_DEFINE_OID_TABLE
%(oid_utf8_strings)s

const OIDTableEntry sOIDTable[] =
%(oid_table)s

const size_t sOIDTableSize = sizeof(sOIDTable) / sizeof(OIDTableEntry);

#endif // ASN1_DEFINE_OID_TABLE

#ifdef ASN1_DEFINE_OID_NAME_TABLE

const OIDNameTableEntry sOIDNameTable[] =
%(oid_name_table)s

#endif // ASN1_DEFINE_OID_NAME_TABLE

} // namespace ASN1
} // namespace chip
'''

oid_category_enums = "{\n"
for (catName, catEnum) in oidCategories:
    oid_category_enums += "    kOIDCategory_%s = 0x%04X,\n" % (
        catName, catEnum)
oid_category_enums += '''
    kOIDCategory_NotSpecified = 0,
    kOIDCategory_Unknown = 0x0F00,
    kOIDCategory_Mask = 0x0F00
};'''

oid_enums = "{\n"
for (catName, catEnum) in oidCategories:
    for (oidCatName, oidName, oidEnum, oid) in oids:
        if (oidCatName == catName):
            oid_enums += "    kOID_%s_%s = 0x%04X,\n" % (
                catName, oidName, catEnum + oidEnum)
    oid_enums += "\n"
oid_enums += '''    kOID_NotSpecified = 0,
    kOID_Unknown = 0xFFFF,
    kOID_EnumMask = 0x00FF
};'''

oid_utf8_strings = "\n"
for (catName, oidName, oidEnum, oid) in oids:
    oid_utf8_strings += "static const uint8_t sOID_%s_%s[] = { %s };\n" % (
        catName, oidName, ", ".join(["0x%02X" % (x) for x in encodeOID(oid)]))

oid_table = "{\n"
for (catName, oidName, oidEnum, oid) in oids:
    oid_table += "    { kOID_%s_%s, sOID_%s_%s, sizeof(sOID_%s_%s) },\n" % (
        catName, oidName, catName, oidName, catName, oidName)
oid_table += "    { kOID_NotSpecified, NULL, 0 }\n};"

oid_name_table = "{\n"
for (catName, oidName, oidEnum, oid) in oids:
    oid_name_table += "    { kOID_%s_%s, \"%s\" },\n" % (
        catName, oidName, oidName)
oid_name_table += "    { kOID_NotSpecified, NULL }\n};"


def main(argv):
    parser = optparse.OptionParser()

    parser.add_option('--output_file')

    options, _ = parser.parse_args(argv)

    template_args = {
        'oid_category_enums': oid_category_enums,
        'oid_enums': oid_enums,
        'oid_utf8_strings': oid_utf8_strings,
        'oid_table': oid_table,
        'oid_name_table': oid_name_table,
    }

    with open(options.output_file, 'w') as asn1oid_file:
        asn1oid_file.write(TEMPLATE % template_args)

    return 0


if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))
