Loading services/core/java/com/android/server/signedconfig/InvalidConfigException.java 0 → 100644 +33 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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 com.android.server.signedconfig; /** * Thrown when there is a problem parsing the config embedded in an APK. */ public class InvalidConfigException extends Exception { public InvalidConfigException(String message) { super(message); } public InvalidConfigException(String message, Exception cause) { super(message, cause); } } services/core/java/com/android/server/signedconfig/SignedConfig.java 0 → 100644 +137 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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 com.android.server.signedconfig; import com.android.internal.annotations.VisibleForTesting; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Represents signed configuration. * * <p>This configuration should only be used if the signature has already been verified. */ public class SignedConfig { private static final String KEY_VERSION = "version"; private static final String KEY_CONFIG = "config"; private static final String CONFIG_KEY_MIN_SDK = "minSdk"; private static final String CONFIG_KEY_MAX_SDK = "maxSdk"; private static final String CONFIG_KEY_VALUES = "values"; // TODO it may be better to use regular key/value pairs in a JSON object, rather than an array // of objects with the 2 keys below. private static final String CONFIG_KEY_KEY = "key"; private static final String CONFIG_KEY_VALUE = "value"; /** * Represents config values targetting to an SDK range. */ public static class PerSdkConfig { public final int minSdk; public final int maxSdk; public final Map<String, String> values; public PerSdkConfig(int minSdk, int maxSdk, Map<String, String> values) { this.minSdk = minSdk; this.maxSdk = maxSdk; this.values = Collections.unmodifiableMap(values); } } public final int version; public final List<PerSdkConfig> perSdkConfig; public SignedConfig(int version, List<PerSdkConfig> perSdkConfig) { this.version = version; this.perSdkConfig = Collections.unmodifiableList(perSdkConfig); } /** * Find matching sdk config for a given SDK level. * * @param sdkVersion SDK version of device. * @return Matching config, of {@code null} if there is none. */ public PerSdkConfig getMatchingConfig(int sdkVersion) { for (PerSdkConfig config : perSdkConfig) { if (config.minSdk <= sdkVersion && sdkVersion <= config.maxSdk) { return config; } } // nothing matching return null; } /** * Parse configuration from an APK. * * @param config config as read from the APK metadata. * @return Parsed configuration. * @throws InvalidConfigException If there's a problem parsing the config. */ public static SignedConfig parse(String config, Set<String> allowedKeys) throws InvalidConfigException { try { JSONObject json = new JSONObject(config); int version = json.getInt(KEY_VERSION); JSONArray perSdkConfig = json.getJSONArray(KEY_CONFIG); List<PerSdkConfig> parsedConfigs = new ArrayList<>(); for (int i = 0; i < perSdkConfig.length(); ++i) { parsedConfigs.add(parsePerSdkConfig(perSdkConfig.getJSONObject(i), allowedKeys)); } return new SignedConfig(version, parsedConfigs); } catch (JSONException e) { throw new InvalidConfigException("Could not parse JSON", e); } } @VisibleForTesting static PerSdkConfig parsePerSdkConfig(JSONObject json, Set<String> allowedKeys) throws JSONException, InvalidConfigException { int minSdk = json.getInt(CONFIG_KEY_MIN_SDK); int maxSdk = json.getInt(CONFIG_KEY_MAX_SDK); JSONArray valueArray = json.getJSONArray(CONFIG_KEY_VALUES); Map<String, String> values = new HashMap<>(); for (int i = 0; i < valueArray.length(); ++i) { JSONObject keyValuePair = valueArray.getJSONObject(i); String key = keyValuePair.getString(CONFIG_KEY_KEY); String value = keyValuePair.has(CONFIG_KEY_VALUE) ? keyValuePair.getString(CONFIG_KEY_VALUE) : null; if (!allowedKeys.contains(key)) { throw new InvalidConfigException("Config key " + key + " is not allowed"); } values.put(key, value); } return new PerSdkConfig(minSdk, maxSdk, values); } } services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java 0 → 100644 +328 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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 com.android.server.signedconfig; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static java.util.Collections.emptySet; import androidx.test.runner.AndroidJUnit4; import com.google.common.collect.Sets; import org.json.JSONException; import org.json.JSONObject; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Arrays; import java.util.Collections; import java.util.Set; /** * Tests for {@link SignedConfig} */ @RunWith(AndroidJUnit4.class) public class SignedConfigTest { private static Set<String> setOf(String... values) { return Sets.newHashSet(values); } @Test public void testParsePerSdkConfigSdkMinMax() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject("{\"minSdk\":2, \"maxSdk\": 3, \"values\": []}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, emptySet()); assertThat(config.minSdk).isEqualTo(2); assertThat(config.maxSdk).isEqualTo(3); } @Test public void testParsePerSdkConfigNoMinSdk() throws JSONException { JSONObject json = new JSONObject("{\"maxSdk\": 3, \"values\": []}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigNoMaxSdk() throws JSONException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"values\": []}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigNoValues() throws JSONException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigSdkNullMinSdk() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject("{\"minSdk\":null, \"maxSdk\": 3, \"values\": []}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigSdkNullMaxSdk() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject("{\"minSdk\":1, \"maxSdk\": null, \"values\": []}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigNullValues() throws JSONException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3, \"values\": null}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigZeroValues() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3, \"values\": []}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b")); assertThat(config.values).hasSize(0); } @Test public void testParsePerSdkConfigSingleKey() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject( "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b")); assertThat(config.values).containsExactly("a", "1"); } @Test public void testParsePerSdkConfigMultiKeys() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject( "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}, " + "{\"key\":\"c\", \"value\": \"2\"}]}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig( json, setOf("a", "b", "c")); assertThat(config.values).containsExactly("a", "1", "c", "2"); } @Test public void testParsePerSdkConfigSingleKeyNotAllowed() throws JSONException { JSONObject json = new JSONObject( "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}"); try { SignedConfig.parsePerSdkConfig(json, setOf("b")); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigSingleKeyNoValue() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject( "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\"}]}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b")); assertThat(config.values).containsExactly("a", null); } @Test public void testParsePerSdkConfigValuesInvalid() throws JSONException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": \"foo\"}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigConfigEntryInvalid() throws JSONException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [1, 2]}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigConfigEntryNull() throws JSONException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [null]}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParseVersion() throws InvalidConfigException { SignedConfig config = SignedConfig.parse( "{\"version\": 1, \"config\": []}", emptySet()); assertThat(config.version).isEqualTo(1); } @Test public void testParseVersionInvalid() { try { SignedConfig.parse("{\"version\": \"notanint\", \"config\": []}", emptySet()); fail("Expected InvalidConfigException"); } catch (InvalidConfigException e) { //expected } } @Test public void testParseNoVersion() { try { SignedConfig.parse("{\"config\": []}", emptySet()); fail("Expected InvalidConfigException"); } catch (InvalidConfigException e) { //expected } } @Test public void testParseNoConfig() { try { SignedConfig.parse("{\"version\": 1}", emptySet()); fail("Expected InvalidConfigException"); } catch (InvalidConfigException e) { //expected } } @Test public void testParseConfigNull() { try { SignedConfig.parse("{\"version\": 1, \"config\": null}", emptySet()); fail("Expected InvalidConfigException"); } catch (InvalidConfigException e) { //expected } } @Test public void testParseVersionNull() { try { SignedConfig.parse("{\"version\": null, \"config\": []}", emptySet()); fail("Expected InvalidConfigException"); } catch (InvalidConfigException e) { //expected } } @Test public void testParseConfigInvalidEntry() { try { SignedConfig.parse("{\"version\": 1, \"config\": [{}]}", emptySet()); fail("Expected InvalidConfigException"); } catch (InvalidConfigException e) { //expected } } @Test public void testParseSdkConfigSingle() throws InvalidConfigException { SignedConfig config = SignedConfig.parse( "{\"version\": 1, \"config\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}]}", emptySet()); assertThat(config.perSdkConfig).hasSize(1); } @Test public void testParseSdkConfigMultiple() throws InvalidConfigException { SignedConfig config = SignedConfig.parse( "{\"version\": 1, \"config\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}, " + "{\"minSdk\": 2, \"maxSdk\": 2, \"values\": []}]}", emptySet()); assertThat(config.perSdkConfig).hasSize(2); } @Test public void testGetMatchingConfigFirst() { SignedConfig.PerSdkConfig sdk1 = new SignedConfig.PerSdkConfig( 1, 1, Collections.emptyMap()); SignedConfig.PerSdkConfig sdk2 = new SignedConfig.PerSdkConfig( 2, 2, Collections.emptyMap()); SignedConfig config = new SignedConfig(0, Arrays.asList(sdk1, sdk2)); assertThat(config.getMatchingConfig(1)).isEqualTo(sdk1); } @Test public void testGetMatchingConfigSecond() { SignedConfig.PerSdkConfig sdk1 = new SignedConfig.PerSdkConfig( 1, 1, Collections.emptyMap()); SignedConfig.PerSdkConfig sdk2 = new SignedConfig.PerSdkConfig( 2, 2, Collections.emptyMap()); SignedConfig config = new SignedConfig(0, Arrays.asList(sdk1, sdk2)); assertThat(config.getMatchingConfig(2)).isEqualTo(sdk2); } @Test public void testGetMatchingConfigInRange() { SignedConfig.PerSdkConfig sdk13 = new SignedConfig.PerSdkConfig( 1, 3, Collections.emptyMap()); SignedConfig.PerSdkConfig sdk46 = new SignedConfig.PerSdkConfig( 4, 6, Collections.emptyMap()); SignedConfig config = new SignedConfig(0, Arrays.asList(sdk13, sdk46)); assertThat(config.getMatchingConfig(2)).isEqualTo(sdk13); } @Test public void testGetMatchingConfigNoMatch() { SignedConfig.PerSdkConfig sdk1 = new SignedConfig.PerSdkConfig( 1, 1, Collections.emptyMap()); SignedConfig.PerSdkConfig sdk2 = new SignedConfig.PerSdkConfig( 2, 2, Collections.emptyMap()); SignedConfig config = new SignedConfig(0, Arrays.asList(sdk1, sdk2)); assertThat(config.getMatchingConfig(3)).isNull(); } } Loading
services/core/java/com/android/server/signedconfig/InvalidConfigException.java 0 → 100644 +33 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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 com.android.server.signedconfig; /** * Thrown when there is a problem parsing the config embedded in an APK. */ public class InvalidConfigException extends Exception { public InvalidConfigException(String message) { super(message); } public InvalidConfigException(String message, Exception cause) { super(message, cause); } }
services/core/java/com/android/server/signedconfig/SignedConfig.java 0 → 100644 +137 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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 com.android.server.signedconfig; import com.android.internal.annotations.VisibleForTesting; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Represents signed configuration. * * <p>This configuration should only be used if the signature has already been verified. */ public class SignedConfig { private static final String KEY_VERSION = "version"; private static final String KEY_CONFIG = "config"; private static final String CONFIG_KEY_MIN_SDK = "minSdk"; private static final String CONFIG_KEY_MAX_SDK = "maxSdk"; private static final String CONFIG_KEY_VALUES = "values"; // TODO it may be better to use regular key/value pairs in a JSON object, rather than an array // of objects with the 2 keys below. private static final String CONFIG_KEY_KEY = "key"; private static final String CONFIG_KEY_VALUE = "value"; /** * Represents config values targetting to an SDK range. */ public static class PerSdkConfig { public final int minSdk; public final int maxSdk; public final Map<String, String> values; public PerSdkConfig(int minSdk, int maxSdk, Map<String, String> values) { this.minSdk = minSdk; this.maxSdk = maxSdk; this.values = Collections.unmodifiableMap(values); } } public final int version; public final List<PerSdkConfig> perSdkConfig; public SignedConfig(int version, List<PerSdkConfig> perSdkConfig) { this.version = version; this.perSdkConfig = Collections.unmodifiableList(perSdkConfig); } /** * Find matching sdk config for a given SDK level. * * @param sdkVersion SDK version of device. * @return Matching config, of {@code null} if there is none. */ public PerSdkConfig getMatchingConfig(int sdkVersion) { for (PerSdkConfig config : perSdkConfig) { if (config.minSdk <= sdkVersion && sdkVersion <= config.maxSdk) { return config; } } // nothing matching return null; } /** * Parse configuration from an APK. * * @param config config as read from the APK metadata. * @return Parsed configuration. * @throws InvalidConfigException If there's a problem parsing the config. */ public static SignedConfig parse(String config, Set<String> allowedKeys) throws InvalidConfigException { try { JSONObject json = new JSONObject(config); int version = json.getInt(KEY_VERSION); JSONArray perSdkConfig = json.getJSONArray(KEY_CONFIG); List<PerSdkConfig> parsedConfigs = new ArrayList<>(); for (int i = 0; i < perSdkConfig.length(); ++i) { parsedConfigs.add(parsePerSdkConfig(perSdkConfig.getJSONObject(i), allowedKeys)); } return new SignedConfig(version, parsedConfigs); } catch (JSONException e) { throw new InvalidConfigException("Could not parse JSON", e); } } @VisibleForTesting static PerSdkConfig parsePerSdkConfig(JSONObject json, Set<String> allowedKeys) throws JSONException, InvalidConfigException { int minSdk = json.getInt(CONFIG_KEY_MIN_SDK); int maxSdk = json.getInt(CONFIG_KEY_MAX_SDK); JSONArray valueArray = json.getJSONArray(CONFIG_KEY_VALUES); Map<String, String> values = new HashMap<>(); for (int i = 0; i < valueArray.length(); ++i) { JSONObject keyValuePair = valueArray.getJSONObject(i); String key = keyValuePair.getString(CONFIG_KEY_KEY); String value = keyValuePair.has(CONFIG_KEY_VALUE) ? keyValuePair.getString(CONFIG_KEY_VALUE) : null; if (!allowedKeys.contains(key)) { throw new InvalidConfigException("Config key " + key + " is not allowed"); } values.put(key, value); } return new PerSdkConfig(minSdk, maxSdk, values); } }
services/tests/servicestests/src/com/android/server/signedconfig/SignedConfigTest.java 0 → 100644 +328 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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 com.android.server.signedconfig; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static java.util.Collections.emptySet; import androidx.test.runner.AndroidJUnit4; import com.google.common.collect.Sets; import org.json.JSONException; import org.json.JSONObject; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Arrays; import java.util.Collections; import java.util.Set; /** * Tests for {@link SignedConfig} */ @RunWith(AndroidJUnit4.class) public class SignedConfigTest { private static Set<String> setOf(String... values) { return Sets.newHashSet(values); } @Test public void testParsePerSdkConfigSdkMinMax() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject("{\"minSdk\":2, \"maxSdk\": 3, \"values\": []}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, emptySet()); assertThat(config.minSdk).isEqualTo(2); assertThat(config.maxSdk).isEqualTo(3); } @Test public void testParsePerSdkConfigNoMinSdk() throws JSONException { JSONObject json = new JSONObject("{\"maxSdk\": 3, \"values\": []}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigNoMaxSdk() throws JSONException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"values\": []}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigNoValues() throws JSONException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigSdkNullMinSdk() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject("{\"minSdk\":null, \"maxSdk\": 3, \"values\": []}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigSdkNullMaxSdk() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject("{\"minSdk\":1, \"maxSdk\": null, \"values\": []}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigNullValues() throws JSONException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3, \"values\": null}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigZeroValues() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 3, \"values\": []}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b")); assertThat(config.values).hasSize(0); } @Test public void testParsePerSdkConfigSingleKey() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject( "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b")); assertThat(config.values).containsExactly("a", "1"); } @Test public void testParsePerSdkConfigMultiKeys() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject( "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}, " + "{\"key\":\"c\", \"value\": \"2\"}]}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig( json, setOf("a", "b", "c")); assertThat(config.values).containsExactly("a", "1", "c", "2"); } @Test public void testParsePerSdkConfigSingleKeyNotAllowed() throws JSONException { JSONObject json = new JSONObject( "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\", \"value\": \"1\"}]}"); try { SignedConfig.parsePerSdkConfig(json, setOf("b")); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigSingleKeyNoValue() throws JSONException, InvalidConfigException { JSONObject json = new JSONObject( "{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [{\"key\":\"a\"}]}"); SignedConfig.PerSdkConfig config = SignedConfig.parsePerSdkConfig(json, setOf("a", "b")); assertThat(config.values).containsExactly("a", null); } @Test public void testParsePerSdkConfigValuesInvalid() throws JSONException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": \"foo\"}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigConfigEntryInvalid() throws JSONException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [1, 2]}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParsePerSdkConfigConfigEntryNull() throws JSONException { JSONObject json = new JSONObject("{\"minSdk\": 1, \"maxSdk\": 1, \"values\": [null]}"); try { SignedConfig.parsePerSdkConfig(json, emptySet()); fail("Expected InvalidConfigException or JSONException"); } catch (JSONException | InvalidConfigException e) { // expected } } @Test public void testParseVersion() throws InvalidConfigException { SignedConfig config = SignedConfig.parse( "{\"version\": 1, \"config\": []}", emptySet()); assertThat(config.version).isEqualTo(1); } @Test public void testParseVersionInvalid() { try { SignedConfig.parse("{\"version\": \"notanint\", \"config\": []}", emptySet()); fail("Expected InvalidConfigException"); } catch (InvalidConfigException e) { //expected } } @Test public void testParseNoVersion() { try { SignedConfig.parse("{\"config\": []}", emptySet()); fail("Expected InvalidConfigException"); } catch (InvalidConfigException e) { //expected } } @Test public void testParseNoConfig() { try { SignedConfig.parse("{\"version\": 1}", emptySet()); fail("Expected InvalidConfigException"); } catch (InvalidConfigException e) { //expected } } @Test public void testParseConfigNull() { try { SignedConfig.parse("{\"version\": 1, \"config\": null}", emptySet()); fail("Expected InvalidConfigException"); } catch (InvalidConfigException e) { //expected } } @Test public void testParseVersionNull() { try { SignedConfig.parse("{\"version\": null, \"config\": []}", emptySet()); fail("Expected InvalidConfigException"); } catch (InvalidConfigException e) { //expected } } @Test public void testParseConfigInvalidEntry() { try { SignedConfig.parse("{\"version\": 1, \"config\": [{}]}", emptySet()); fail("Expected InvalidConfigException"); } catch (InvalidConfigException e) { //expected } } @Test public void testParseSdkConfigSingle() throws InvalidConfigException { SignedConfig config = SignedConfig.parse( "{\"version\": 1, \"config\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}]}", emptySet()); assertThat(config.perSdkConfig).hasSize(1); } @Test public void testParseSdkConfigMultiple() throws InvalidConfigException { SignedConfig config = SignedConfig.parse( "{\"version\": 1, \"config\":[{\"minSdk\": 1, \"maxSdk\": 1, \"values\": []}, " + "{\"minSdk\": 2, \"maxSdk\": 2, \"values\": []}]}", emptySet()); assertThat(config.perSdkConfig).hasSize(2); } @Test public void testGetMatchingConfigFirst() { SignedConfig.PerSdkConfig sdk1 = new SignedConfig.PerSdkConfig( 1, 1, Collections.emptyMap()); SignedConfig.PerSdkConfig sdk2 = new SignedConfig.PerSdkConfig( 2, 2, Collections.emptyMap()); SignedConfig config = new SignedConfig(0, Arrays.asList(sdk1, sdk2)); assertThat(config.getMatchingConfig(1)).isEqualTo(sdk1); } @Test public void testGetMatchingConfigSecond() { SignedConfig.PerSdkConfig sdk1 = new SignedConfig.PerSdkConfig( 1, 1, Collections.emptyMap()); SignedConfig.PerSdkConfig sdk2 = new SignedConfig.PerSdkConfig( 2, 2, Collections.emptyMap()); SignedConfig config = new SignedConfig(0, Arrays.asList(sdk1, sdk2)); assertThat(config.getMatchingConfig(2)).isEqualTo(sdk2); } @Test public void testGetMatchingConfigInRange() { SignedConfig.PerSdkConfig sdk13 = new SignedConfig.PerSdkConfig( 1, 3, Collections.emptyMap()); SignedConfig.PerSdkConfig sdk46 = new SignedConfig.PerSdkConfig( 4, 6, Collections.emptyMap()); SignedConfig config = new SignedConfig(0, Arrays.asList(sdk13, sdk46)); assertThat(config.getMatchingConfig(2)).isEqualTo(sdk13); } @Test public void testGetMatchingConfigNoMatch() { SignedConfig.PerSdkConfig sdk1 = new SignedConfig.PerSdkConfig( 1, 1, Collections.emptyMap()); SignedConfig.PerSdkConfig sdk2 = new SignedConfig.PerSdkConfig( 2, 2, Collections.emptyMap()); SignedConfig config = new SignedConfig(0, Arrays.asList(sdk1, sdk2)); assertThat(config.getMatchingConfig(3)).isNull(); } }