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 19Outputs HTML tables suitable for inclusion in the Android documentation that 20reflect the crypto algorithm support shown in the provided data file. 21""" 22 23import argparse 24import operator 25 26import crypto_docs 27 28 29find_by_name = crypto_docs.find_by_name 30 31 32def sort_by_name(seq): 33 return sorted(seq, key=lambda x: x['name']) 34 35 36def 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. 46def nowrap(value): 47 return '<span style="white-space: nowrap">%s</span>' % value 48 49 50def main(): 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') 55 parser.add_argument('--category', 56 action='append', 57 help='The category to display, may be specified multiple times') 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) 64 categories = sort_by_name(data['categories']) 65 output.append('<h2 id="SupportedAlgorithms">Supported Algorithms</h2>') 66 output.append('') 67 output.append('<ul>') 68 for category in categories: 69 if not category['name'].endswith('.Enabled'): 70 output.append(' <li><a href="#Supported{name}">' 71 '<code>{name}</code></a></li>'.format(**category)) 72 output.append('</ul>') 73 for category in categories: 74 if args.category and category['name'] not in args.category: 75 continue 76 show_notes = has_notes(category) 77 if category['name'].endswith('.Enabled'): 78 # These are handled in the "Supported" section below 79 continue 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'], 97 'deprecated' in algorithm and algorithm['deprecated'], 98 algorithm.get('note', ''))) 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] 108 and tuples[i][4] == tuples[i+1][4] 109 and tuples[i][5] == tuples[i+1][5]): 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] 121 and tuples[i][4] == tuples[i+1][4] 122 and tuples[i][5] == tuples[i+1][5]): 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 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>') 137 if show_notes: 138 output.append(' <th>Notes</th>') 139 output.append(' </tr>') 140 output.append(' </thead>') 141 output.append(' <tbody>') 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] 151 cur_note = row[5] 152 cur_algorithm = None 153 cur_mode = None 154 if cur_deprecated: 155 output.append(' <tr class="deprecated">') 156 else: 157 output.append(' <tr>') 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 164 and tuples[j][5] == cur_note 165 and tuples[j][0] == cur_algorithm): 166 j += 1 167 rowspan = j - i 168 if rowspan > 1: 169 output.append(' <td rowspan="%d">%s</td>' % (rowspan, nowrap(cur_algorithm))) 170 else: 171 output.append(' <td>%s</td>' % nowrap(cur_algorithm)) 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 177 and tuples[j][5] == cur_note 178 and tuples[j][0] == cur_algorithm 179 and tuples[j][1] == cur_mode): 180 j += 1 181 rowspan = j - i 182 modestring = '<br>'.join([nowrap(x) for x in cur_mode]) 183 if rowspan > 1: 184 output.append(' <td rowspan="%d">%s</td>' % (rowspan, modestring)) 185 else: 186 output.append(' <td>%s</td>' % modestring) 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]) 191 output.append(' </tr>') 192 i += 1 193 output.append(' </tbody>') 194 output.append('</table>') 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>') 210 if show_notes: 211 output.append(' <th>Notes</th>') 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>') 220 output.append(' <td>%s</td>' % nowrap(algorithm['name'])) 221 output.append(' <td>%s</td>' % nowrap(algorithm['supported_api_levels'])) 222 enabled_alg = find_by_name(enabled, algorithm['name']) 223 if enabled_alg is None: 224 output.append(' <td></td>') 225 else: 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>') 232 output.append(' </tr>') 233 output.append(' </tbody>') 234 output.append('</table>') 235 else: 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>') 242 if show_notes: 243 output.append(' <th>Notes</th>') 244 output.append(' </tr>') 245 output.append(' </thead>') 246 output.append(' <tbody>') 247 algorithms = sort_by_name(category['algorithms']) 248 for algorithm in algorithms: 249 if 'deprecated' in algorithm and algorithm['deprecated']: 250 output.append(' <tr class="deprecated">') 251 else: 252 output.append(' <tr>') 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>') 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) 267 268 269if __name__ == '__main__': 270 main() 271