#!/usr/bin/env python
|
#
|
# Copyright (C) 2017 The Android Open Source Project
|
#
|
# 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.
|
|
"""Outputs HTML based on an input JSON file.
|
|
Outputs HTML tables suitable for inclusion in the Android documentation that
|
reflect the crypto algorithm support shown in the provided data file.
|
"""
|
|
import argparse
|
import operator
|
|
import crypto_docs
|
|
|
find_by_name = crypto_docs.find_by_name
|
|
|
def sort_by_name(seq):
|
return sorted(seq, key=lambda x: x['name'])
|
|
|
def has_notes(category):
|
for algorithm in category['algorithms']:
|
if 'note' in algorithm:
|
return True
|
return False
|
|
|
# Prevents the given value from being word-wrapped. This is mainly to ensure that
|
# long identifiers with hyphens, like OAEPwithSHA-1andMGF1Padding, don't get word-wrapped
|
# at the hyphen.
|
def nowrap(value):
|
return '<span style="white-space: nowrap">%s</span>' % value
|
|
|
def main():
|
parser = argparse.ArgumentParser(description='Output algorithm support HTML tables')
|
parser.add_argument('--for_javadoc',
|
action='store_true',
|
help='If specified, format for inclusion in class documentation')
|
parser.add_argument('--category',
|
action='append',
|
help='The category to display, may be specified multiple times')
|
parser.add_argument('file',
|
help='The JSON file to use for data')
|
args = parser.parse_args()
|
|
output = []
|
data = crypto_docs.load_json(args.file)
|
categories = sort_by_name(data['categories'])
|
output.append('<h2 id="SupportedAlgorithms">Supported Algorithms</h2>')
|
output.append('')
|
output.append('<ul>')
|
for category in categories:
|
if not category['name'].endswith('.Enabled'):
|
output.append(' <li><a href="#Supported{name}">'
|
'<code>{name}</code></a></li>'.format(**category))
|
output.append('</ul>')
|
for category in categories:
|
if args.category and category['name'] not in args.category:
|
continue
|
show_notes = has_notes(category)
|
if category['name'].endswith('.Enabled'):
|
# These are handled in the "Supported" section below
|
continue
|
if category['name'] == 'Cipher':
|
# We display ciphers in a four-column table to conserve space and
|
# so that it's more comprehensible. To do this, we have to
|
# collapse all our ciphers into "equivalence classes" of a sort.
|
|
# First, collect the relevant data for each algorithm into a tuple.
|
# The mode and padding are in lists because we are going to collapse
|
# multiple tuples with those in later steps.
|
algorithms = sort_by_name(category['algorithms'])
|
tuples = []
|
for algorithm in algorithms:
|
name, mode, padding = algorithm['name'].split('/')
|
tuples.append((
|
name,
|
[mode],
|
[padding],
|
algorithm['supported_api_levels'],
|
'deprecated' in algorithm and algorithm['deprecated'],
|
algorithm.get('note', '')))
|
# Sort the tuples by all items except padding, then collapse
|
# items with all non-padding values the same (which will always be
|
# neighboring items) into a single item.
|
tuples.sort(key=operator.itemgetter(0, 1, 3, 4))
|
i = 0
|
while i < len(tuples) - 1:
|
if (tuples[i][0] == tuples[i+1][0]
|
and tuples[i][1] == tuples[i+1][1]
|
and tuples[i][3] == tuples[i+1][3]
|
and tuples[i][4] == tuples[i+1][4]
|
and tuples[i][5] == tuples[i+1][5]):
|
tuples[i][2].extend(tuples[i+1][2])
|
del tuples[i+1]
|
else:
|
i += 1
|
# Do the same thing as above, but with modes.
|
tuples.sort(key=operator.itemgetter(0, 2, 3, 4))
|
i = 0
|
while i < len(tuples) - 1:
|
if (tuples[i][0] == tuples[i+1][0]
|
and tuples[i][2] == tuples[i+1][2]
|
and tuples[i][3] == tuples[i+1][3]
|
and tuples[i][4] == tuples[i+1][4]
|
and tuples[i][5] == tuples[i+1][5]):
|
tuples[i][1].extend(tuples[i+1][1])
|
del tuples[i+1]
|
else:
|
i += 1
|
# Display the table with rowspans for those entries where all the
|
# items have the same algorithm, mode, etc
|
output.append('<h3 id="Supported{name}">{name}</h3>'.format(**category))
|
output.append('<table>')
|
output.append(' <thead>')
|
output.append(' <tr>')
|
output.append(' <th>Algorithm</th>')
|
output.append(' <th>Modes</th>')
|
output.append(' <th>Paddings</th>')
|
output.append(' <th>Supported API Levels</th>')
|
if show_notes:
|
output.append(' <th>Notes</th>')
|
output.append(' </tr>')
|
output.append(' </thead>')
|
output.append(' <tbody>')
|
tuples.sort(key=operator.itemgetter(0, 4, 1, 2, 3))
|
i = 0
|
cur_deprecated = None
|
cur_algorithm = None
|
cur_mode = None
|
while i < len(tuples):
|
row = tuples[i]
|
if row[4] != cur_deprecated:
|
cur_deprecated = row[4]
|
cur_note = row[5]
|
cur_algorithm = None
|
cur_mode = None
|
if cur_deprecated:
|
output.append(' <tr class="deprecated">')
|
else:
|
output.append(' <tr>')
|
if row[0] != cur_algorithm:
|
cur_algorithm = row[0]
|
cur_mode = None
|
j = i + 1
|
while (j < len(tuples)
|
and tuples[j][4] == cur_deprecated
|
and tuples[j][5] == cur_note
|
and tuples[j][0] == cur_algorithm):
|
j += 1
|
rowspan = j - i
|
if rowspan > 1:
|
output.append(' <td rowspan="%d">%s</td>' % (rowspan, nowrap(cur_algorithm)))
|
else:
|
output.append(' <td>%s</td>' % nowrap(cur_algorithm))
|
if row[1] != cur_mode:
|
cur_mode = row[1]
|
j = i + 1
|
while (j < len(tuples)
|
and tuples[j][4] == cur_deprecated
|
and tuples[j][5] == cur_note
|
and tuples[j][0] == cur_algorithm
|
and tuples[j][1] == cur_mode):
|
j += 1
|
rowspan = j - i
|
modestring = '<br>'.join([nowrap(x) for x in cur_mode])
|
if rowspan > 1:
|
output.append(' <td rowspan="%d">%s</td>' % (rowspan, modestring))
|
else:
|
output.append(' <td>%s</td>' % modestring)
|
output.append(' <td>%s</td>' % '<br>'.join([nowrap(x) for x in row[2]]))
|
output.append(' <td>%s</td>' % nowrap(row[3]))
|
if show_notes:
|
output.append(' <td>%s</td>' % row[5])
|
output.append(' </tr>')
|
i += 1
|
output.append(' </tbody>')
|
output.append('</table>')
|
elif category['name'].endswith('.Supported'):
|
# Some categories come with a "Supported" and "Enabled" list, and we
|
# group those together in one table for display. Every entry that's enabled
|
# must be supported, so we can just look up the enabled version for each
|
# supported item
|
basename = category['name'][:-len('.Supported')]
|
supported = sort_by_name(category['algorithms'])
|
enabled = sort_by_name(find_by_name(categories, basename + '.Enabled')['algorithms'])
|
output.append('<h3 id="Supported{0}">{0}</h3>'.format(basename))
|
output.append('<table>')
|
output.append(' <thead>')
|
output.append(' <tr>')
|
output.append(' <th>Algorithm</th>')
|
output.append(' <th>Supported API Levels</th>')
|
output.append(' <th>Enabled By Default</th>')
|
if show_notes:
|
output.append(' <th>Notes</th>')
|
output.append(' </tr>')
|
output.append(' </thead>')
|
output.append(' <tbody>')
|
for algorithm in supported:
|
if 'deprecated' in algorithm and algorithm['deprecated']:
|
output.append(' <tr class="deprecated">')
|
else:
|
output.append(' <tr>')
|
output.append(' <td>%s</td>' % nowrap(algorithm['name']))
|
output.append(' <td>%s</td>' % nowrap(algorithm['supported_api_levels']))
|
enabled_alg = find_by_name(enabled, algorithm['name'])
|
if enabled_alg is None:
|
output.append(' <td></td>')
|
else:
|
output.append(' <td>%s</td>' % nowrap(enabled_alg['supported_api_levels']))
|
if show_notes:
|
if 'note' in algorithm:
|
output.append(' <td>%s</td>' % algorithm['note'])
|
else:
|
output.append(' <td></td>')
|
output.append(' </tr>')
|
output.append(' </tbody>')
|
output.append('</table>')
|
else:
|
output.append('<h3 id="Supported{name}">{name}</h3>'.format(**category))
|
output.append('<table>')
|
output.append(' <thead>')
|
output.append(' <tr>')
|
output.append(' <th>Algorithm</th>')
|
output.append(' <th>Supported API Levels</th>')
|
if show_notes:
|
output.append(' <th>Notes</th>')
|
output.append(' </tr>')
|
output.append(' </thead>')
|
output.append(' <tbody>')
|
algorithms = sort_by_name(category['algorithms'])
|
for algorithm in algorithms:
|
if 'deprecated' in algorithm and algorithm['deprecated']:
|
output.append(' <tr class="deprecated">')
|
else:
|
output.append(' <tr>')
|
output.append(' <td>%s</td>' % nowrap(algorithm['name']))
|
output.append(' <td>%s</td>' % nowrap(algorithm['supported_api_levels']))
|
if show_notes:
|
if 'note' in algorithm:
|
output.append(' <td>%s</td>' % algorithm['note'])
|
else:
|
output.append(' <td></td>')
|
output.append(' </tr>')
|
output.append(' </tbody>')
|
output.append('</table>')
|
if args.for_javadoc:
|
for i in range(len(output)):
|
output[i] = ' * ' + output[i]
|
print '\n'.join(output)
|
|
|
if __name__ == '__main__':
|
main()
|