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