Adam Vartanian | 9ae0b40 | 2017-03-08 16:39:31 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
| 3 | # Copyright (C) 2017 The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | |
| 17 | """Outputs HTML based on an input JSON file. |
| 18 | |
| 19 | Outputs HTML tables suitable for inclusion in the Android documentation that |
| 20 | reflect the crypto algorithm support shown in the provided data file. |
| 21 | """ |
| 22 | |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 23 | import argparse |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 24 | import operator |
Adam Vartanian | 9ae0b40 | 2017-03-08 16:39:31 +0000 | [diff] [blame] | 25 | |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 26 | import crypto_docs |
| 27 | |
Adam Vartanian | 9ae0b40 | 2017-03-08 16:39:31 +0000 | [diff] [blame] | 28 | |
Adam Vartanian | 2205113 | 2017-05-04 12:11:30 +0100 | [diff] [blame] | 29 | find_by_name = crypto_docs.find_by_name |
| 30 | |
| 31 | |
Adam Vartanian | 9ae0b40 | 2017-03-08 16:39:31 +0000 | [diff] [blame] | 32 | def sort_by_name(seq): |
| 33 | return sorted(seq, key=lambda x: x['name']) |
| 34 | |
| 35 | |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 36 | def has_notes(category): |
| 37 | for algorithm in category['algorithms']: |
| 38 | if 'note' in algorithm: |
| 39 | return True |
| 40 | return False |
| 41 | |
| 42 | |
| 43 | # Prevents the given value from being word-wrapped. This is mainly to ensure that |
| 44 | # long identifiers with hyphens, like OAEPwithSHA-1andMGF1Padding, don't get word-wrapped |
| 45 | # at the hyphen. |
| 46 | def nowrap(value): |
| 47 | return '<span style="white-space: nowrap">%s</span>' % value |
| 48 | |
| 49 | |
Adam Vartanian | 9ae0b40 | 2017-03-08 16:39:31 +0000 | [diff] [blame] | 50 | def main(): |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 51 | parser = argparse.ArgumentParser(description='Output algorithm support HTML tables') |
| 52 | parser.add_argument('--for_javadoc', |
| 53 | action='store_true', |
| 54 | help='If specified, format for inclusion in class documentation') |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 55 | parser.add_argument('--category', |
| 56 | action='append', |
| 57 | help='The category to display, may be specified multiple times') |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 58 | parser.add_argument('file', |
| 59 | help='The JSON file to use for data') |
| 60 | args = parser.parse_args() |
| 61 | |
| 62 | output = [] |
| 63 | data = crypto_docs.load_json(args.file) |
Adam Vartanian | 9ae0b40 | 2017-03-08 16:39:31 +0000 | [diff] [blame] | 64 | categories = sort_by_name(data['categories']) |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 65 | output.append('<h2 id="SupportedAlgorithms">Supported Algorithms</h2>') |
| 66 | output.append('') |
| 67 | output.append('<ul>') |
Adam Vartanian | 9ae0b40 | 2017-03-08 16:39:31 +0000 | [diff] [blame] | 68 | for category in categories: |
Adam Vartanian | 2205113 | 2017-05-04 12:11:30 +0100 | [diff] [blame] | 69 | if not category['name'].endswith('.Enabled'): |
| 70 | output.append(' <li><a href="#Supported{name}">' |
| 71 | '<code>{name}</code></a></li>'.format(**category)) |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 72 | output.append('</ul>') |
Adam Vartanian | 9ae0b40 | 2017-03-08 16:39:31 +0000 | [diff] [blame] | 73 | for category in categories: |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 74 | if args.category and category['name'] not in args.category: |
| 75 | continue |
| 76 | show_notes = has_notes(category) |
Adam Vartanian | 2205113 | 2017-05-04 12:11:30 +0100 | [diff] [blame] | 77 | if category['name'].endswith('.Enabled'): |
| 78 | # These are handled in the "Supported" section below |
| 79 | continue |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 80 | if category['name'] == 'Cipher': |
| 81 | # We display ciphers in a four-column table to conserve space and |
| 82 | # so that it's more comprehensible. To do this, we have to |
| 83 | # collapse all our ciphers into "equivalence classes" of a sort. |
| 84 | |
| 85 | # First, collect the relevant data for each algorithm into a tuple. |
| 86 | # The mode and padding are in lists because we are going to collapse |
| 87 | # multiple tuples with those in later steps. |
| 88 | algorithms = sort_by_name(category['algorithms']) |
| 89 | tuples = [] |
| 90 | for algorithm in algorithms: |
| 91 | name, mode, padding = algorithm['name'].split('/') |
| 92 | tuples.append(( |
| 93 | name, |
| 94 | [mode], |
| 95 | [padding], |
| 96 | algorithm['supported_api_levels'], |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 97 | 'deprecated' in algorithm and algorithm['deprecated'], |
| 98 | algorithm.get('note', ''))) |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 99 | # Sort the tuples by all items except padding, then collapse |
| 100 | # items with all non-padding values the same (which will always be |
| 101 | # neighboring items) into a single item. |
| 102 | tuples.sort(key=operator.itemgetter(0, 1, 3, 4)) |
| 103 | i = 0 |
| 104 | while i < len(tuples) - 1: |
| 105 | if (tuples[i][0] == tuples[i+1][0] |
| 106 | and tuples[i][1] == tuples[i+1][1] |
| 107 | and tuples[i][3] == tuples[i+1][3] |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 108 | and tuples[i][4] == tuples[i+1][4] |
| 109 | and tuples[i][5] == tuples[i+1][5]): |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 110 | tuples[i][2].extend(tuples[i+1][2]) |
| 111 | del tuples[i+1] |
| 112 | else: |
| 113 | i += 1 |
| 114 | # Do the same thing as above, but with modes. |
| 115 | tuples.sort(key=operator.itemgetter(0, 2, 3, 4)) |
| 116 | i = 0 |
| 117 | while i < len(tuples) - 1: |
| 118 | if (tuples[i][0] == tuples[i+1][0] |
| 119 | and tuples[i][2] == tuples[i+1][2] |
| 120 | and tuples[i][3] == tuples[i+1][3] |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 121 | and tuples[i][4] == tuples[i+1][4] |
| 122 | and tuples[i][5] == tuples[i+1][5]): |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 123 | tuples[i][1].extend(tuples[i+1][1]) |
| 124 | del tuples[i+1] |
| 125 | else: |
| 126 | i += 1 |
| 127 | # Display the table with rowspans for those entries where all the |
| 128 | # items have the same algorithm, mode, etc |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 129 | output.append('<h3 id="Supported{name}">{name}</h3>'.format(**category)) |
| 130 | output.append('<table>') |
| 131 | output.append(' <thead>') |
| 132 | output.append(' <tr>') |
| 133 | output.append(' <th>Algorithm</th>') |
| 134 | output.append(' <th>Modes</th>') |
| 135 | output.append(' <th>Paddings</th>') |
| 136 | output.append(' <th>Supported API Levels</th>') |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 137 | if show_notes: |
| 138 | output.append(' <th>Notes</th>') |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 139 | output.append(' </tr>') |
| 140 | output.append(' </thead>') |
| 141 | output.append(' <tbody>') |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 142 | tuples.sort(key=operator.itemgetter(0, 4, 1, 2, 3)) |
| 143 | i = 0 |
| 144 | cur_deprecated = None |
| 145 | cur_algorithm = None |
| 146 | cur_mode = None |
| 147 | while i < len(tuples): |
| 148 | row = tuples[i] |
| 149 | if row[4] != cur_deprecated: |
| 150 | cur_deprecated = row[4] |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 151 | cur_note = row[5] |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 152 | cur_algorithm = None |
| 153 | cur_mode = None |
| 154 | if cur_deprecated: |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 155 | output.append(' <tr class="deprecated">') |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 156 | else: |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 157 | output.append(' <tr>') |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 158 | if row[0] != cur_algorithm: |
| 159 | cur_algorithm = row[0] |
| 160 | cur_mode = None |
| 161 | j = i + 1 |
| 162 | while (j < len(tuples) |
| 163 | and tuples[j][4] == cur_deprecated |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 164 | and tuples[j][5] == cur_note |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 165 | and tuples[j][0] == cur_algorithm): |
| 166 | j += 1 |
| 167 | rowspan = j - i |
| 168 | if rowspan > 1: |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 169 | output.append(' <td rowspan="%d">%s</td>' % (rowspan, nowrap(cur_algorithm))) |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 170 | else: |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 171 | output.append(' <td>%s</td>' % nowrap(cur_algorithm)) |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 172 | if row[1] != cur_mode: |
| 173 | cur_mode = row[1] |
| 174 | j = i + 1 |
| 175 | while (j < len(tuples) |
| 176 | and tuples[j][4] == cur_deprecated |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 177 | and tuples[j][5] == cur_note |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 178 | and tuples[j][0] == cur_algorithm |
| 179 | and tuples[j][1] == cur_mode): |
| 180 | j += 1 |
| 181 | rowspan = j - i |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 182 | modestring = '<br>'.join([nowrap(x) for x in cur_mode]) |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 183 | if rowspan > 1: |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 184 | output.append(' <td rowspan="%d">%s</td>' % (rowspan, modestring)) |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 185 | else: |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 186 | output.append(' <td>%s</td>' % modestring) |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 187 | output.append(' <td>%s</td>' % '<br>'.join([nowrap(x) for x in row[2]])) |
| 188 | output.append(' <td>%s</td>' % nowrap(row[3])) |
| 189 | if show_notes: |
| 190 | output.append(' <td>%s</td>' % row[5]) |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 191 | output.append(' </tr>') |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 192 | i += 1 |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 193 | output.append(' </tbody>') |
| 194 | output.append('</table>') |
Adam Vartanian | 2205113 | 2017-05-04 12:11:30 +0100 | [diff] [blame] | 195 | elif category['name'].endswith('.Supported'): |
| 196 | # Some categories come with a "Supported" and "Enabled" list, and we |
| 197 | # group those together in one table for display. Every entry that's enabled |
| 198 | # must be supported, so we can just look up the enabled version for each |
| 199 | # supported item |
| 200 | basename = category['name'][:-len('.Supported')] |
| 201 | supported = sort_by_name(category['algorithms']) |
| 202 | enabled = sort_by_name(find_by_name(categories, basename + '.Enabled')['algorithms']) |
| 203 | output.append('<h3 id="Supported{0}">{0}</h3>'.format(basename)) |
| 204 | output.append('<table>') |
| 205 | output.append(' <thead>') |
| 206 | output.append(' <tr>') |
| 207 | output.append(' <th>Algorithm</th>') |
| 208 | output.append(' <th>Supported API Levels</th>') |
| 209 | output.append(' <th>Enabled By Default</th>') |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 210 | if show_notes: |
| 211 | output.append(' <th>Notes</th>') |
Adam Vartanian | 2205113 | 2017-05-04 12:11:30 +0100 | [diff] [blame] | 212 | output.append(' </tr>') |
| 213 | output.append(' </thead>') |
| 214 | output.append(' <tbody>') |
| 215 | for algorithm in supported: |
| 216 | if 'deprecated' in algorithm and algorithm['deprecated']: |
| 217 | output.append(' <tr class="deprecated">') |
| 218 | else: |
| 219 | output.append(' <tr>') |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 220 | output.append(' <td>%s</td>' % nowrap(algorithm['name'])) |
| 221 | output.append(' <td>%s</td>' % nowrap(algorithm['supported_api_levels'])) |
Adam Vartanian | 2205113 | 2017-05-04 12:11:30 +0100 | [diff] [blame] | 222 | enabled_alg = find_by_name(enabled, algorithm['name']) |
| 223 | if enabled_alg is None: |
| 224 | output.append(' <td></td>') |
| 225 | else: |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 226 | output.append(' <td>%s</td>' % nowrap(enabled_alg['supported_api_levels'])) |
| 227 | if show_notes: |
| 228 | if 'note' in algorithm: |
| 229 | output.append(' <td>%s</td>' % algorithm['note']) |
| 230 | else: |
| 231 | output.append(' <td></td>') |
Adam Vartanian | 2205113 | 2017-05-04 12:11:30 +0100 | [diff] [blame] | 232 | output.append(' </tr>') |
| 233 | output.append(' </tbody>') |
| 234 | output.append('</table>') |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 235 | else: |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 236 | output.append('<h3 id="Supported{name}">{name}</h3>'.format(**category)) |
| 237 | output.append('<table>') |
| 238 | output.append(' <thead>') |
| 239 | output.append(' <tr>') |
| 240 | output.append(' <th>Algorithm</th>') |
| 241 | output.append(' <th>Supported API Levels</th>') |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 242 | if show_notes: |
| 243 | output.append(' <th>Notes</th>') |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 244 | output.append(' </tr>') |
| 245 | output.append(' </thead>') |
| 246 | output.append(' <tbody>') |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 247 | algorithms = sort_by_name(category['algorithms']) |
| 248 | for algorithm in algorithms: |
Adam Vartanian | befc86a | 2017-03-16 14:26:08 +0000 | [diff] [blame] | 249 | if 'deprecated' in algorithm and algorithm['deprecated']: |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 250 | output.append(' <tr class="deprecated">') |
| 251 | else: |
| 252 | output.append(' <tr>') |
Adam Vartanian | 1179672 | 2018-02-02 14:09:14 +0000 | [diff] [blame] | 253 | output.append(' <td>%s</td>' % nowrap(algorithm['name'])) |
| 254 | output.append(' <td>%s</td>' % nowrap(algorithm['supported_api_levels'])) |
| 255 | if show_notes: |
| 256 | if 'note' in algorithm: |
| 257 | output.append(' <td>%s</td>' % algorithm['note']) |
| 258 | else: |
| 259 | output.append(' <td></td>') |
Adam Vartanian | 748190c | 2017-03-20 11:16:44 +0000 | [diff] [blame] | 260 | output.append(' </tr>') |
| 261 | output.append(' </tbody>') |
| 262 | output.append('</table>') |
| 263 | if args.for_javadoc: |
| 264 | for i in range(len(output)): |
| 265 | output[i] = ' * ' + output[i] |
| 266 | print '\n'.join(output) |
Adam Vartanian | 9ae0b40 | 2017-03-08 16:39:31 +0000 | [diff] [blame] | 267 | |
| 268 | |
| 269 | if __name__ == '__main__': |
| 270 | main() |