upload android base code part6
This commit is contained in:
parent
421e214c7d
commit
4e516ec6ed
35396 changed files with 9188716 additions and 0 deletions
42
android/libcore/tools/docs/crypto/README
Normal file
42
android/libcore/tools/docs/crypto/README
Normal file
|
@ -0,0 +1,42 @@
|
|||
The tools in this directory are a scheme for autogenerating crypto doc support.
|
||||
|
||||
The central file is data/crypto_support.json, which is a JSON file that contains
|
||||
the supported API levels for each crypto algorithm that has ever been supported
|
||||
by Android. The categories are identified as described in the JCA, along with
|
||||
a couple extra classes.
|
||||
|
||||
Each algorithm has up to three properties:
|
||||
|
||||
name - The name of the algorithm. These are currently normalized to
|
||||
all-uppercase versions, but this produces substandard results in some cases
|
||||
(eg, PBEWITHHMACSHA versus PBEWithHmacSHA), so it could be improved.
|
||||
|
||||
supported_api_levels: The set of Android API levels that this algorithm was/is
|
||||
supported for. It should be a sequence of zero or more "N" values or "N-M"
|
||||
ranges separated by commas followed by a "N+" value if the algorithm is
|
||||
currently supported. For example: "17+", "1-8,22+", "1-10,15,22-24".
|
||||
|
||||
deprecated: Whether the algorithm is unsupported at the current API level. This
|
||||
is equivalent to supported_api_levels.endswith('+'), but is included for
|
||||
clarity's sake.
|
||||
|
||||
Updating the documentation is a three-step process: get the set of supported
|
||||
algorithms, update the data file, then generate HTML based on the data file.
|
||||
|
||||
Getting the set of supported algorithms is done using
|
||||
src/java/libcore/java/security/ListProviders.java. It's important that this is
|
||||
run using vogar in mode=activity rather than the default of mode=device, as
|
||||
the Android frameworks (in particular, Android Keystore) can add additional
|
||||
support.
|
||||
|
||||
The data from ListProviders is fed into update_crypto_support.py, which rewrites
|
||||
the data file. Any newly-added algorithms will be added to the file's data,
|
||||
and any newly-removed algorithms will have their supported API levels updated.
|
||||
|
||||
The preceding two steps can be performed together by running
|
||||
run_update_crypto_support.sh.
|
||||
|
||||
Finally, format_supported_algorithm_table.py reads the data file and outputs
|
||||
a collection of HTML tables suitable for copying-and-pasting into the docs
|
||||
to stdout. Add --javadoc to produce output appropriate for inclusion in
|
||||
Javadoc.
|
39
android/libcore/tools/docs/crypto/crypto_docs.py
Normal file
39
android/libcore/tools/docs/crypto/crypto_docs.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
# 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.
|
||||
|
||||
'''Utility functions for crypto doc updating tools.'''
|
||||
|
||||
import json
|
||||
|
||||
|
||||
def load_json(filename):
|
||||
'''Returns an object containing the JSON data from the provided file.'''
|
||||
f = open(filename)
|
||||
# JSON doesn't allow comments, but we have some header docs in our file,
|
||||
# so strip comments out before parsing
|
||||
stripped_contents = ''
|
||||
for line in f:
|
||||
if not line.strip().startswith('#'):
|
||||
stripped_contents += line
|
||||
data = json.loads(stripped_contents)
|
||||
f.close()
|
||||
return data
|
||||
|
||||
|
||||
def find_by_name(seq, name):
|
||||
"""Returns the first element in seq with the given name."""
|
||||
for item in seq:
|
||||
if item['name'] == name:
|
||||
return item
|
||||
return None
|
2725
android/libcore/tools/docs/crypto/data/crypto_support.json
Normal file
2725
android/libcore/tools/docs/crypto/data/crypto_support.json
Normal file
File diff suppressed because it is too large
Load diff
226
android/libcore/tools/docs/crypto/format_supported_algorithm_table.py
Executable file
226
android/libcore/tools/docs/crypto/format_supported_algorithm_table.py
Executable file
|
@ -0,0 +1,226 @@
|
|||
#!/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 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('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 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']))
|
||||
# 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]):
|
||||
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]):
|
||||
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>')
|
||||
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_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][0] == cur_algorithm):
|
||||
j += 1
|
||||
rowspan = j - i
|
||||
if rowspan > 1:
|
||||
output.append(' <td rowspan="%d">%s</td>' % (rowspan, cur_algorithm))
|
||||
else:
|
||||
output.append(' <td>%s</td>' % 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][0] == cur_algorithm
|
||||
and tuples[j][1] == cur_mode):
|
||||
j += 1
|
||||
rowspan = j - i
|
||||
modestring = '<br>'.join(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(row[2]))
|
||||
output.append(' <td>%s</td>' % row[3])
|
||||
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>')
|
||||
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>{name}</td>'.format(**algorithm))
|
||||
output.append(' <td>{supported_api_levels}</td>'.format(**algorithm))
|
||||
enabled_alg = find_by_name(enabled, algorithm['name'])
|
||||
if enabled_alg is None:
|
||||
output.append(' <td></td>')
|
||||
else:
|
||||
output.append(' <td>{supported_api_levels}</td>'.format(**enabled_alg))
|
||||
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>')
|
||||
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>{name}</td>'.format(**algorithm))
|
||||
output.append(' <td>{supported_api_levels}</td>'.format(**algorithm))
|
||||
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()
|
17
android/libcore/tools/docs/crypto/run_update_crypto_support.sh
Executable file
17
android/libcore/tools/docs/crypto/run_update_crypto_support.sh
Executable file
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Updates the crypto support JSON data file by running the appropriate tools.
|
||||
|
||||
# Ensure that the files we need are in the place we expect
|
||||
if [ ! -f libcore/tools/docs/crypto/src/java/libcore/java/security/ListProviders.java -o ! -f libcore/tools/docs/crypto/data/crypto_support.json ]; then
|
||||
echo "This command must be run from the repo root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "The current API level must be specified as an argument."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
make -j48 vogar dx
|
||||
vogar --mode=activity --toolchain=jdk --multidex=false libcore/tools/docs/crypto/src/java/libcore/java/security/ListProviders.java | libcore/tools/docs/crypto/update_crypto_support.py --api_level=$1 --rewrite_file libcore/tools/docs/crypto/data/crypto_support.json
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package libcore.java.security;
|
||||
|
||||
import android.net.PskKeyManager;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
|
||||
/**
|
||||
* Prints a list of all algorithms provided by security providers. Intended to be run
|
||||
* via vogar as part of the algorithm documentation update process.
|
||||
* <p>
|
||||
* {@code vogar libcore/tools/src/java/libcore/java/security/ListProviders.java}
|
||||
*/
|
||||
public class ListProviders {
|
||||
|
||||
private static final boolean SHOW_PROVIDER = false;
|
||||
|
||||
// These algorithms were previously provided, but now are aliases for a different
|
||||
// algorithm. For documentation purposes, we want to continue having them show up
|
||||
// as supported.
|
||||
private static final Set<String> KNOWN_ALIASES = new TreeSet<>(Arrays.asList(new String[]{
|
||||
"Alg.Alias.Signature.DSA",
|
||||
"Alg.Alias.Signature.DSAwithSHA1",
|
||||
"Alg.Alias.Signature.ECDSA",
|
||||
"Alg.Alias.Signature.ECDSAwithSHA1",
|
||||
}));
|
||||
|
||||
// Ciphers come in algorithm/mode/padding combinations, and not all combinations are explicitly
|
||||
// registered by the providers (sometimes only the base algorithm is registered). While there
|
||||
// is a mechanism for providers to specify which modes and/or paddings are supported for a
|
||||
// given algorithm, none of our providers use it. Thus, when a base algorithm is seen, all
|
||||
// combinations of modes and paddings will be tried to see which ones are supported.
|
||||
private static final Set<String> CIPHER_MODES = new TreeSet<>(Arrays.asList(new String[]{
|
||||
"CBC",
|
||||
"CFB",
|
||||
"CTR",
|
||||
"CTS",
|
||||
"ECB",
|
||||
"GCM",
|
||||
"OFB",
|
||||
"NONE",
|
||||
}));
|
||||
private static final Set<String> CIPHER_PADDINGS = new TreeSet<>(Arrays.asList(new String[]{
|
||||
"NoPadding",
|
||||
"OAEPPadding",
|
||||
"OAEPwithSHA-1andMGF1Padding",
|
||||
"OAEPwithSHA-224andMGF1Padding",
|
||||
"OAEPwithSHA-256andMGF1Padding",
|
||||
"OAEPwithSHA-384andMGF1Padding",
|
||||
"OAEPwithSHA-512andMGF1Padding",
|
||||
"PKCS1Padding",
|
||||
"PKCS5Padding",
|
||||
"ISO10126Padding",
|
||||
}));
|
||||
|
||||
private static void print(Provider p, String type, String algorithm) {
|
||||
System.out.println((SHOW_PROVIDER ? p.getName() + ": " : "") + type + " " + algorithm);
|
||||
}
|
||||
|
||||
public static void main(String[] argv) throws Exception {
|
||||
System.out.println("BEGIN ALGORITHM LIST");
|
||||
for (Provider p : Security.getProviders()) {
|
||||
Set<Provider.Service> services = new TreeSet<Provider.Service>(
|
||||
new Comparator<Provider.Service>() {
|
||||
public int compare(Provider.Service a, Provider.Service b) {
|
||||
int typeCompare = a.getType().compareTo(b.getType());
|
||||
if (typeCompare != 0) {
|
||||
return typeCompare;
|
||||
}
|
||||
return a.getAlgorithm().compareTo(b.getAlgorithm());
|
||||
}
|
||||
});
|
||||
services.addAll(p.getServices());
|
||||
for (Provider.Service s : services) {
|
||||
if (s.getType().equals("Cipher") && s.getAlgorithm().startsWith("PBE")) {
|
||||
// PBE ciphers are a mess and generally don't do anything but delegate
|
||||
// to the underlying cipher. We don't want to document them.
|
||||
continue;
|
||||
}
|
||||
if (s.getType().equals("Cipher") && s.getAlgorithm().indexOf('/') == -1) {
|
||||
for (String mode : CIPHER_MODES) {
|
||||
for (String padding : CIPHER_PADDINGS) {
|
||||
try {
|
||||
String name = s.getAlgorithm() + "/" + mode + "/" + padding;
|
||||
Cipher.getInstance(name, p);
|
||||
print(p, s.getType(), name);
|
||||
} catch (NoSuchAlgorithmException
|
||||
|NoSuchPaddingException
|
||||
|IllegalArgumentException e) {
|
||||
// This combination doesn't work
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print(p, s.getType(), s.getAlgorithm());
|
||||
}
|
||||
}
|
||||
for (String alias : KNOWN_ALIASES) {
|
||||
if (p.containsKey(alias)) {
|
||||
String[] elements = alias.split("\\."); // Split takes a regex
|
||||
print(p, elements[2], elements[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// SSLEngine and SSLSocket algorithms are handled outside the default provider system
|
||||
SSLContext defaultContext = SSLContext.getDefault();
|
||||
// PSK cipher suites are only enabled when a PskKeyManager is available, but some other
|
||||
// suites are disabled in that case, so check for both
|
||||
SSLContext pskContext = SSLContext.getInstance("TLS");
|
||||
pskContext.init(
|
||||
new KeyManager[] {new PskKeyManager(){}},
|
||||
new TrustManager[0],
|
||||
null);
|
||||
for (SSLContext sslContext : new SSLContext[] {defaultContext, pskContext}) {
|
||||
SSLEngine engine = sslContext.createSSLEngine();
|
||||
for (String suite : engine.getSupportedCipherSuites()) {
|
||||
print(sslContext.getProvider(), "SSLEngine.Supported", suite);
|
||||
}
|
||||
for (String suite : engine.getEnabledCipherSuites()) {
|
||||
print(sslContext.getProvider(), "SSLEngine.Enabled", suite);
|
||||
}
|
||||
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
|
||||
for (String suite : socketFactory.getSupportedCipherSuites()) {
|
||||
print(sslContext.getProvider(), "SSLSocket.Supported", suite);
|
||||
}
|
||||
for (String suite : socketFactory.getDefaultCipherSuites()) {
|
||||
print(sslContext.getProvider(), "SSLSocket.Enabled", suite);
|
||||
}
|
||||
}
|
||||
System.out.println("END ALGORITHM LIST");
|
||||
}
|
||||
}
|
279
android/libcore/tools/docs/crypto/update_crypto_support.py
Executable file
279
android/libcore/tools/docs/crypto/update_crypto_support.py
Executable file
|
@ -0,0 +1,279 @@
|
|||
#!/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.
|
||||
|
||||
"""Updates a JSON data file of supported algorithms.
|
||||
|
||||
Takes input on stdin a list of provided algorithms as produced by
|
||||
ListProviders.java along with a JSON file of the previous set of algorithm
|
||||
support and what the current API level is, and produces an updated JSON
|
||||
record of algorithm support.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
import crypto_docs
|
||||
|
||||
SUPPORTED_CATEGORIES = [
|
||||
'AlgorithmParameterGenerator',
|
||||
'AlgorithmParameters',
|
||||
'CertificateFactory',
|
||||
'CertPathBuilder',
|
||||
'CertPathValidator',
|
||||
'CertStore',
|
||||
'Cipher',
|
||||
'KeyAgreement',
|
||||
'KeyFactory',
|
||||
'KeyGenerator',
|
||||
'KeyManagerFactory',
|
||||
'KeyPairGenerator',
|
||||
'KeyStore',
|
||||
'Mac',
|
||||
'MessageDigest',
|
||||
'SecretKeyFactory',
|
||||
'SecureRandom',
|
||||
'Signature',
|
||||
'SSLContext',
|
||||
'SSLEngine.Enabled',
|
||||
'SSLEngine.Supported',
|
||||
'SSLSocket.Enabled',
|
||||
'SSLSocket.Supported',
|
||||
'TrustManagerFactory',
|
||||
]
|
||||
|
||||
# For these categories, we really want to maintain the casing that was in the
|
||||
# original data, so avoid changing it.
|
||||
CASE_SENSITIVE_CATEGORIES = [
|
||||
'SSLEngine.Enabled',
|
||||
'SSLEngine.Supported',
|
||||
'SSLSocket.Enabled',
|
||||
'SSLSocket.Supported',
|
||||
]
|
||||
|
||||
|
||||
find_by_name = crypto_docs.find_by_name
|
||||
|
||||
|
||||
def find_by_normalized_name(seq, name):
|
||||
"""Returns the first element in seq with the given normalized name."""
|
||||
for item in seq:
|
||||
if normalize_name(item['name']) == name:
|
||||
return item
|
||||
return None
|
||||
|
||||
|
||||
def sort_by_name(seq):
|
||||
"""Returns a copy of the input sequence sorted by name."""
|
||||
return sorted(seq, key=lambda x: x['name'])
|
||||
|
||||
|
||||
def normalize_name(name):
|
||||
"""Returns a normalized version of the given algorithm name."""
|
||||
name = name.upper()
|
||||
# BouncyCastle uses X.509 with an alias of X509, Conscrypt does the
|
||||
# reverse. X.509 is the official name of the standard, so use that.
|
||||
if name == "X509":
|
||||
name = "X.509"
|
||||
# PKCS5PADDING and PKCS7PADDING are the same thing (more accurately, PKCS#5
|
||||
# is a special case of PKCS#7), but providers are inconsistent in their
|
||||
# naming. Use PKCS5PADDING because that's what our docs have used
|
||||
# historically.
|
||||
if name.endswith("/PKCS7PADDING"):
|
||||
name = name[:-1 * len("/PKCS7PADDING")] + "/PKCS5PADDING"
|
||||
return name
|
||||
|
||||
|
||||
def fix_name_caps_for_output(name):
|
||||
"""Returns a version of the given algorithm name with capitalization fixed."""
|
||||
# It's important that this must only change the capitalization of the
|
||||
# name, not any of its text, otherwise future runs won't be able to
|
||||
# match this name with the name coming from the device.
|
||||
|
||||
# We current make the following capitalization fixes
|
||||
# DESede (not DESEDE)
|
||||
# FOOwithBAR (not FOOWITHBAR or FOOWithBAR)
|
||||
# Hmac (not HMAC)
|
||||
name = re.sub('WITH', 'with', name, flags=re.I)
|
||||
name = re.sub('DESEDE', 'DESede', name, flags=re.I)
|
||||
name = re.sub('HMAC', 'Hmac', name, flags=re.I)
|
||||
return name
|
||||
|
||||
|
||||
def get_current_data(f):
|
||||
"""Returns a map of the algorithms in the given input.
|
||||
|
||||
The input file-like object must supply a "BEGIN ALGORITHM LIST" line
|
||||
followed by any number of lines of an algorithm category and algorithm name
|
||||
separated by whitespace followed by a "END ALGORITHM LIST" line. The
|
||||
input can supply arbitrary values outside of the BEGIN and END lines, it
|
||||
will be ignored.
|
||||
|
||||
The returned algorithms will have their names normalized.
|
||||
|
||||
Returns:
|
||||
A dict of categories to lists of normalized algorithm names and a
|
||||
dict of normalized algorithm names to original algorithm names.
|
||||
|
||||
Raises:
|
||||
EOFError: If either the BEGIN or END sentinel lines are not present.
|
||||
ValueError: If a line between the BEGIN and END sentinel lines is not
|
||||
made up of two identifiers separated by whitespace.
|
||||
"""
|
||||
current_data = collections.defaultdict(list)
|
||||
name_dict = {}
|
||||
|
||||
saw_begin = False
|
||||
saw_end = False
|
||||
for line in f.readlines():
|
||||
line = line.strip()
|
||||
if not saw_begin:
|
||||
if line.strip() == 'BEGIN ALGORITHM LIST':
|
||||
saw_begin = True
|
||||
continue
|
||||
if line == 'END ALGORITHM LIST':
|
||||
saw_end = True
|
||||
break
|
||||
category, algorithm = line.split()
|
||||
if category not in SUPPORTED_CATEGORIES:
|
||||
continue
|
||||
normalized_name = normalize_name(algorithm)
|
||||
current_data[category].append(normalized_name)
|
||||
name_dict[normalized_name] = algorithm
|
||||
|
||||
if not saw_begin:
|
||||
raise EOFError(
|
||||
'Reached the end of input without encountering the begin sentinel')
|
||||
if not saw_end:
|
||||
raise EOFError(
|
||||
'Reached the end of input without encountering the end sentinel')
|
||||
return dict(current_data), name_dict
|
||||
|
||||
|
||||
def update_data(prev_data, current_data, name_dict, api_level, date):
|
||||
"""Returns a copy of prev_data, modified to take into account current_data.
|
||||
|
||||
Updates the algorithm support metadata structure by starting with the
|
||||
information in prev_data and updating it to take into account the algorithms
|
||||
listed in current_data. Algorithms not present in current_data will still
|
||||
be present in the return value, but their supported_api_levels may be
|
||||
modified to indicate that they are no longer supported.
|
||||
|
||||
Args:
|
||||
prev_data: The data on algorithm support from the previous API level.
|
||||
current_data: The algorithms supported in the current API level, as a map
|
||||
from algorithm category to list of algorithm names.
|
||||
api_level: An integer representing the current API level.
|
||||
date: A datetime object containing the time of update.
|
||||
"""
|
||||
new_data = {'categories': []}
|
||||
|
||||
for category in SUPPORTED_CATEGORIES:
|
||||
prev_category = find_by_name(prev_data['categories'], category)
|
||||
if prev_category is None:
|
||||
prev_category = {'name': category, 'algorithms': []}
|
||||
current_category = (
|
||||
current_data[category] if category in current_data else [])
|
||||
new_category = {'name': category, 'algorithms': []}
|
||||
prev_algorithms = [normalize_name(x['name']) for x in prev_category['algorithms']]
|
||||
alg_union = set(prev_algorithms) | set(current_category)
|
||||
for alg in alg_union:
|
||||
prev_alg = find_by_normalized_name(prev_category['algorithms'], alg)
|
||||
if alg in name_dict:
|
||||
new_algorithm = {'name': name_dict[alg]}
|
||||
elif prev_alg is not None:
|
||||
new_algorithm = {'name': prev_alg['name']}
|
||||
else:
|
||||
new_algorithm = {'name': alg}
|
||||
if category not in CASE_SENSITIVE_CATEGORIES:
|
||||
new_algorithm['name'] = fix_name_caps_for_output(new_algorithm['name'])
|
||||
new_level = None
|
||||
if alg in current_category and alg in prev_algorithms:
|
||||
# Both old and new have it, just ensure the API level is right
|
||||
if prev_alg['supported_api_levels'].endswith('+'):
|
||||
new_level = prev_alg['supported_api_levels']
|
||||
else:
|
||||
new_level = (prev_alg['supported_api_levels']
|
||||
+ ',%d+' % api_level)
|
||||
elif alg in prev_algorithms:
|
||||
# Only in the old set, so ensure the API level is marked
|
||||
# as ending
|
||||
if prev_alg['supported_api_levels'].endswith('+'):
|
||||
# The algorithm is newly missing, so modify the support
|
||||
# to end at the previous level
|
||||
new_level = prev_alg['supported_api_levels'][:-1]
|
||||
if not new_level.endswith(str(api_level - 1)):
|
||||
new_level += '-%d' % (api_level - 1)
|
||||
else:
|
||||
new_level = prev_alg['supported_api_levels']
|
||||
new_algorithm['deprecated'] = 'true'
|
||||
else:
|
||||
# Only in the new set, so add it
|
||||
new_level = '%d+' % api_level
|
||||
new_algorithm['supported_api_levels'] = new_level
|
||||
new_category['algorithms'].append(new_algorithm)
|
||||
if new_category['algorithms']:
|
||||
new_category['algorithms'] = sort_by_name(
|
||||
new_category['algorithms'])
|
||||
new_data['categories'].append(new_category)
|
||||
new_data['categories'] = sort_by_name(new_data['categories'])
|
||||
new_data['api_level'] = str(api_level)
|
||||
new_data['last_updated'] = date.strftime('%Y-%m-%d %H:%M:%S UTC')
|
||||
|
||||
return new_data
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Update JSON support file')
|
||||
parser.add_argument('--api_level',
|
||||
required=True,
|
||||
type=int,
|
||||
help='The current API level')
|
||||
parser.add_argument('--rewrite_file',
|
||||
action='store_true',
|
||||
help='If specified, rewrite the'
|
||||
' input file with the result')
|
||||
parser.add_argument('file',
|
||||
help='The JSON file to update')
|
||||
args = parser.parse_args()
|
||||
|
||||
prev_data = crypto_docs.load_json(args.file)
|
||||
|
||||
current_data, name_dict = get_current_data(sys.stdin)
|
||||
|
||||
new_data = update_data(prev_data,
|
||||
current_data,
|
||||
name_dict,
|
||||
args.api_level,
|
||||
datetime.datetime.utcnow())
|
||||
|
||||
if args.rewrite_file:
|
||||
f = open(args.file, 'w')
|
||||
f.write('# This file is autogenerated.'
|
||||
' See libcore/tools/docs/crypto/README for details.\n')
|
||||
json.dump(
|
||||
new_data, f, indent=2, sort_keys=True, separators=(',', ': '))
|
||||
f.close()
|
||||
else:
|
||||
print json.dumps(
|
||||
new_data, indent=2, sort_keys=True, separators=(',', ': '))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
417
android/libcore/tools/docs/crypto/update_crypto_support_test.py
Executable file
417
android/libcore/tools/docs/crypto/update_crypto_support_test.py
Executable file
|
@ -0,0 +1,417 @@
|
|||
#!/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.
|
||||
|
||||
import datetime
|
||||
import StringIO
|
||||
import unittest
|
||||
import update_crypto_support
|
||||
|
||||
|
||||
def do_update_data(prev_data, current_data, name_dict={}):
|
||||
return update_crypto_support.update_data(
|
||||
prev_data,
|
||||
current_data,
|
||||
name_dict,
|
||||
72,
|
||||
datetime.datetime.utcfromtimestamp(1234567890))
|
||||
|
||||
|
||||
# The timestamp 1234567890 in our text format
|
||||
LAST_UPDATED_TEXT = '2009-02-13 23:31:30 UTC'
|
||||
|
||||
|
||||
class TestUpdateData(unittest.TestCase):
|
||||
def test_find_by_name(self):
|
||||
self.assertIsNone(update_crypto_support.find_by_name([], 'foo'))
|
||||
self.assertIsNone(
|
||||
update_crypto_support.find_by_name([{'name': 'foo'}], 'bar'))
|
||||
self.assertEqual(
|
||||
update_crypto_support.find_by_name(
|
||||
[{'name': 'foo', 'value': 'foo_value'}],
|
||||
'foo'),
|
||||
{'name': 'foo', 'value': 'foo_value'})
|
||||
self.assertEqual(
|
||||
update_crypto_support.find_by_name(
|
||||
[{'name': 'foo', 'value': 'foo_value'},
|
||||
{'name': 'bar', 'value': 'bar_value'}],
|
||||
'bar'),
|
||||
{'name': 'bar', 'value': 'bar_value'})
|
||||
self.assertEqual(
|
||||
update_crypto_support.find_by_name(
|
||||
[{'name': 'foo', 'value': 'foo_value'},
|
||||
{'name': 'bar', 'value': 'bar_value'},
|
||||
{'name': 'foo', 'value': 'foo2_value'}],
|
||||
'foo'),
|
||||
{'name': 'foo', 'value': 'foo_value'})
|
||||
self.assertEqual(
|
||||
update_crypto_support.find_by_name(
|
||||
[{'name': 'foot', 'value': 'foot_value'},
|
||||
{'name': 'bar', 'value': 'bar_value'},
|
||||
{'name': 'foo', 'value': 'foo_value'}],
|
||||
'foo'),
|
||||
{'name': 'foo', 'value': 'foo_value'})
|
||||
|
||||
def test_sort_by_name(self):
|
||||
self.assertEqual(update_crypto_support.sort_by_name([]), [])
|
||||
self.assertEqual(
|
||||
update_crypto_support.sort_by_name(
|
||||
[{'name': 'foot', 'value': 'foot_value'},
|
||||
{'name': 'bar', 'value': 'bar_value'},
|
||||
{'name': 'foo', 'value': 'foo_value'}]),
|
||||
[{'name': 'bar', 'value': 'bar_value'},
|
||||
{'name': 'foo', 'value': 'foo_value'},
|
||||
{'name': 'foot', 'value': 'foot_value'}])
|
||||
with self.assertRaises(KeyError):
|
||||
update_crypto_support.sort_by_name([{'not_name': 'foo'}])
|
||||
|
||||
def test_get_current_data(self):
|
||||
self.assertEqual(update_crypto_support.get_current_data(
|
||||
StringIO.StringIO(
|
||||
'''
|
||||
BEGIN ALGORITHM LIST
|
||||
Mac Bob
|
||||
Mac Jones
|
||||
MessageDigest Jim
|
||||
Mac Amy
|
||||
OtherThing Mary
|
||||
END ALGORITHM LIST
|
||||
''')),
|
||||
({'Mac': ['BOB', 'JONES', 'AMY'],
|
||||
'MessageDigest': ['JIM']},
|
||||
{'AMY': 'Amy',
|
||||
'BOB': 'Bob',
|
||||
'JONES': 'Jones',
|
||||
'JIM': 'Jim'}))
|
||||
self.assertEqual(update_crypto_support.get_current_data(
|
||||
StringIO.StringIO(
|
||||
'''
|
||||
BEGIN ALGORITHM LIST
|
||||
Mac Dupe
|
||||
Mac Jones
|
||||
MessageDigest Jim
|
||||
Mac Amy
|
||||
Mac Dupe
|
||||
OtherThing Mary
|
||||
END ALGORITHM LIST
|
||||
''')),
|
||||
({'Mac': ['DUPE', 'JONES', 'AMY', 'DUPE'],
|
||||
'MessageDigest': ['JIM']},
|
||||
{'AMY': 'Amy',
|
||||
'DUPE': 'Dupe',
|
||||
'JONES': 'Jones',
|
||||
'JIM': 'Jim'}))
|
||||
self.assertEqual(update_crypto_support.get_current_data(
|
||||
StringIO.StringIO(
|
||||
'''
|
||||
Mac NotAValue
|
||||
BEGIN ALGORITHM LIST
|
||||
Mac Bob
|
||||
Mac Jones
|
||||
MessageDigest Jim
|
||||
Mac Amy
|
||||
OtherThing Mary
|
||||
END ALGORITHM LIST
|
||||
Mac AlsoNotAValue
|
||||
''')),
|
||||
({'Mac': ['BOB', 'JONES', 'AMY'],
|
||||
'MessageDigest': ['JIM']},
|
||||
{'AMY': 'Amy',
|
||||
'BOB': 'Bob',
|
||||
'JONES': 'Jones',
|
||||
'JIM': 'Jim'}))
|
||||
self.assertEqual(update_crypto_support.get_current_data(
|
||||
StringIO.StringIO(
|
||||
'''
|
||||
BEGIN ALGORITHM LIST OF LISTS
|
||||
Mac NotAValue
|
||||
BEGIN ALGORITHM LIST
|
||||
Mac Bob
|
||||
Mac Jones
|
||||
MessageDigest Jim
|
||||
Mac Amy
|
||||
OtherThing Mary
|
||||
END ALGORITHM LIST
|
||||
''')),
|
||||
({'Mac': ['BOB', 'JONES', 'AMY'],
|
||||
'MessageDigest': ['JIM']},
|
||||
{'AMY': 'Amy',
|
||||
'BOB': 'Bob',
|
||||
'JONES': 'Jones',
|
||||
'JIM': 'Jim'}))
|
||||
with self.assertRaises(EOFError):
|
||||
update_crypto_support.get_current_data(StringIO.StringIO(
|
||||
'''
|
||||
NOTBEGIN ALGORITHM LIST
|
||||
Mac Bob
|
||||
Mac Jones
|
||||
MessageDigest Jim
|
||||
Mac Amy
|
||||
OtherThing Mary
|
||||
END ALGORITHM LIST
|
||||
'''))
|
||||
with self.assertRaises(EOFError):
|
||||
update_crypto_support.get_current_data(StringIO.StringIO(
|
||||
'''
|
||||
BEGIN ALGORITHM LIST
|
||||
Mac Bob
|
||||
Mac Jones
|
||||
MessageDigest Jim
|
||||
Mac Amy
|
||||
OtherThing Mary'''))
|
||||
with self.assertRaises(ValueError):
|
||||
update_crypto_support.get_current_data(StringIO.StringIO(
|
||||
'''
|
||||
BEGIN ALGORITHM LIST
|
||||
Mac Bob
|
||||
Mac Jones
|
||||
MessageDigest Jim OneTooManyItems
|
||||
Mac Amy
|
||||
OtherThing Mary
|
||||
END ALGORITHM LIST
|
||||
'''))
|
||||
with self.assertRaises(ValueError):
|
||||
update_crypto_support.get_current_data(StringIO.StringIO(
|
||||
'''
|
||||
BEGIN ALGORITHM LIST
|
||||
Mac Bob
|
||||
Mac Jones
|
||||
TooFewItems
|
||||
MessageDigest Jim
|
||||
Mac Amy
|
||||
OtherThing Mary
|
||||
END ALGORITHM LIST
|
||||
'''))
|
||||
|
||||
def test_update_data_no_data(self):
|
||||
self.assertEqual(
|
||||
do_update_data(
|
||||
{'categories': []},
|
||||
{}),
|
||||
{'categories': [],
|
||||
'api_level': '72',
|
||||
'last_updated': LAST_UPDATED_TEXT})
|
||||
|
||||
self.assertEqual(
|
||||
do_update_data(
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': []}]},
|
||||
{}),
|
||||
{'categories': [],
|
||||
'api_level': '72',
|
||||
'last_updated': LAST_UPDATED_TEXT})
|
||||
|
||||
self.assertEqual(
|
||||
do_update_data(
|
||||
{'categories': []},
|
||||
{'MessageDigest': []}),
|
||||
{'categories': [],
|
||||
'api_level': '72',
|
||||
'last_updated': LAST_UPDATED_TEXT})
|
||||
|
||||
def test_update_data_no_updates(self):
|
||||
self.assertEqual(
|
||||
do_update_data(
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'SHA-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'SHA-2',
|
||||
'supported_api_levels': '1-22',
|
||||
'deprecated': 'true'}]}]},
|
||||
{'MessageDigest': ['SHA-1']}),
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'SHA-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'SHA-2',
|
||||
'supported_api_levels': '1-22',
|
||||
'deprecated': 'true'}]}],
|
||||
'api_level': '72',
|
||||
'last_updated': LAST_UPDATED_TEXT})
|
||||
|
||||
def test_update_data_new_item(self):
|
||||
self.assertEqual(
|
||||
do_update_data(
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'SHA-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'SHA-2',
|
||||
'supported_api_levels': '1-22',
|
||||
'deprecated': 'true'},
|
||||
{'name': 'SHA-384',
|
||||
'supported_api_levels': '17-32'}]}]},
|
||||
{'MessageDigest': ['SHA-1', 'SHA-256', 'SHA-384']}),
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'SHA-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'SHA-2',
|
||||
'supported_api_levels': '1-22',
|
||||
'deprecated': 'true'},
|
||||
{'name': 'SHA-256',
|
||||
'supported_api_levels': '72+'},
|
||||
{'name': 'SHA-384',
|
||||
'supported_api_levels': '17-32,72+'}]}],
|
||||
'api_level': '72',
|
||||
'last_updated': LAST_UPDATED_TEXT})
|
||||
|
||||
def test_update_data_removed_item(self):
|
||||
self.assertEqual(
|
||||
do_update_data(
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'SHA-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'SHA-2',
|
||||
'supported_api_levels': '1-22',
|
||||
'deprecated': 'true'},
|
||||
{'name': 'SHA-256',
|
||||
'supported_api_levels': '70+'},
|
||||
{'name': 'SHA-384',
|
||||
'supported_api_levels': '71+'},
|
||||
{'name': 'SHA-512',
|
||||
'supported_api_levels': '1-3,17-32,47+'}]}]},
|
||||
{'MessageDigest': ['SHA-1']}),
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'SHA-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'SHA-2',
|
||||
'supported_api_levels': '1-22',
|
||||
'deprecated': 'true'},
|
||||
{'name': 'SHA-256',
|
||||
'supported_api_levels': '70-71',
|
||||
'deprecated': 'true'},
|
||||
{'name': 'SHA-384',
|
||||
'supported_api_levels': '71',
|
||||
'deprecated': 'true'},
|
||||
{'name': 'SHA-512',
|
||||
'supported_api_levels': '1-3,17-32,47-71',
|
||||
'deprecated': 'true'}]}],
|
||||
'api_level': '72',
|
||||
'last_updated': LAST_UPDATED_TEXT})
|
||||
|
||||
def test_update_data_duplicates(self):
|
||||
self.assertEqual(
|
||||
do_update_data(
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'SHA-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'SHA-2',
|
||||
'supported_api_levels': '1-22',
|
||||
'deprecated': 'true'},
|
||||
{'name': 'SHA-1',
|
||||
'supported_api_levels': '7+'}]}]},
|
||||
{'MessageDigest': ['SHA-1']}),
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'SHA-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'SHA-2',
|
||||
'supported_api_levels': '1-22',
|
||||
'deprecated': 'true'}]}],
|
||||
'api_level': '72',
|
||||
'last_updated': LAST_UPDATED_TEXT})
|
||||
|
||||
self.assertEqual(
|
||||
do_update_data(
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'SHA-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'SHA-2',
|
||||
'supported_api_levels': '1-22',
|
||||
'deprecated': 'true'}]}]},
|
||||
{'MessageDigest': ['SHA-1', 'SHA-1']}),
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'SHA-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'SHA-2',
|
||||
'supported_api_levels': '1-22',
|
||||
'deprecated': 'true'}]}],
|
||||
'api_level': '72',
|
||||
'last_updated': LAST_UPDATED_TEXT})
|
||||
|
||||
def test_update_name_matching(self):
|
||||
self.assertEqual(
|
||||
do_update_data(
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'sha-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'Sha-2',
|
||||
'supported_api_levels': '1-22',
|
||||
'deprecated': 'true'},
|
||||
{'name': 'SHA-3',
|
||||
'supported_api_levels': '7+'}]}]},
|
||||
{'MessageDigest': ['SHA-1', 'SHA-2', 'SHA-3']},
|
||||
{'SHA-1': 'Sha-1', 'SHA-2': 'Sha-2', 'SHA-3': 'Sha-3'}),
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'Sha-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'Sha-2',
|
||||
'supported_api_levels': '1-22,72+'},
|
||||
{'name': 'Sha-3',
|
||||
'supported_api_levels': '7+'}]}],
|
||||
'api_level': '72',
|
||||
'last_updated': LAST_UPDATED_TEXT})
|
||||
self.assertEqual(
|
||||
do_update_data(
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'sha-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'Sha-2',
|
||||
'supported_api_levels': '1-22',
|
||||
'deprecated': 'true'},
|
||||
{'name': 'SHA-3',
|
||||
'supported_api_levels': '7+'}]}]},
|
||||
{'MessageDigest': ['SHA-1', 'SHA-3']},
|
||||
{'SHA-1': 'Sha-1', 'SHA-3': 'Sha-3'}),
|
||||
{'categories': [
|
||||
{'name': 'MessageDigest',
|
||||
'algorithms': [
|
||||
{'name': 'Sha-1',
|
||||
'supported_api_levels': '1+'},
|
||||
{'name': 'Sha-2',
|
||||
'supported_api_levels': '1-22',
|
||||
'deprecated': 'true'},
|
||||
{'name': 'Sha-3',
|
||||
'supported_api_levels': '7+'}]}],
|
||||
'api_level': '72',
|
||||
'last_updated': LAST_UPDATED_TEXT})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Add table
Add a link
Reference in a new issue