From 94c84c7542a90e778314919cf8da79b3bac9fb11 Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Fri, 8 Mar 2024 11:11:53 +0000
Subject: [PATCH] Refactor CircumventionProvider to use separate resources.

---
 .../onionwrapper/CircumventionProvider.java   |  74 +++++------
 .../CircumventionProviderImpl.java            | 121 +++++++-----------
 onionwrapper-core/src/main/resources/bridges  |  39 ------
 .../src/main/resources/bridges-d-zz           |  11 ++
 .../src/main/resources/bridges-m-zz           |   1 +
 .../src/main/resources/bridges-n-zz           |  17 +++
 .../src/main/resources/bridges-s-zz           |   4 +
 .../src/main/resources/bridges-v-zz           |   9 ++
 .../src/main/resources/snowflake-params       |   6 -
 .../CircumventionProviderImplTest.java        |  90 ++++---------
 .../briarproject/onionwrapper/BridgeTest.java |  57 ++++++---
 11 files changed, 182 insertions(+), 247 deletions(-)
 delete mode 100644 onionwrapper-core/src/main/resources/bridges
 create mode 100644 onionwrapper-core/src/main/resources/bridges-d-zz
 create mode 100644 onionwrapper-core/src/main/resources/bridges-m-zz
 create mode 100644 onionwrapper-core/src/main/resources/bridges-n-zz
 create mode 100644 onionwrapper-core/src/main/resources/bridges-s-zz
 create mode 100644 onionwrapper-core/src/main/resources/bridges-v-zz
 delete mode 100644 onionwrapper-core/src/main/resources/snowflake-params

diff --git a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/CircumventionProvider.java b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/CircumventionProvider.java
index 2c047ba..f95668c 100644
--- a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/CircumventionProvider.java
+++ b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/CircumventionProvider.java
@@ -11,66 +11,60 @@ import javax.annotation.concurrent.ThreadSafe;
 public interface CircumventionProvider {
 
 	enum BridgeType {
-		DEFAULT_OBFS4,
-		NON_DEFAULT_OBFS4,
-		VANILLA,
-		MEEK,
-		SNOWFLAKE
-	}
 
-	/**
-	 * Countries where Tor is blocked, i.e. vanilla Tor connection won't work.
-	 * <p>
-	 * See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
-	 * and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
-	 */
-	String[] BLOCKED = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
+		DEFAULT_OBFS4("d"),
+		NON_DEFAULT_OBFS4("n"),
+		VANILLA("v"),
+		MEEK("m"),
+		SNOWFLAKE("s");
+
+		final String letter;
+
+		BridgeType(String letter) {
+			this.letter = letter;
+		}
+	}
 
 	/**
-	 * Countries where bridge connections are likely to work.
-	 * Should be a subset of {@link #BLOCKED} and the union of
-	 * {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
-	 * {@link #DPI_BRIDGES}.
+	 * Countries where default obfs4 bridges should be used.
 	 */
-	String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
+	String[] COUNTRIES_DEFAULT_BRIDGES = {"BY"};
 
 	/**
-	 * Countries where default obfs4 or vanilla bridges are likely to work.
-	 * Should be a subset of {@link #BRIDGES}.
+	 * Countries where non-default obfs4 and vanilla bridges should be used.
 	 */
-	String[] DEFAULT_BRIDGES = {"EG", "VE"};
+	String[] COUNTRIES_NON_DEFAULT_BRIDGES = {"BY", "CN", "EG", "HK", "IR", "RU"};
 
 	/**
-	 * Countries where non-default obfs4 or vanilla bridges are likely to work.
-	 * Should be a subset of {@link #BRIDGES}.
+	 * Countries where meek bridges should be used.
 	 */
-	String[] NON_DEFAULT_BRIDGES = {"BY", "RU"};
+	String[] COUNTRIES_MEEK_BRIDGES = {"TM"};
 
 	/**
-	 * Countries where vanilla bridges are blocked via DPI but non-default
-	 * obfs4 bridges, meek and snowflake may work. Should be a subset of
-	 * {@link #BRIDGES}.
+	 * Countries where snowflake bridges should be used.
 	 */
-	String[] DPI_BRIDGES = {"CN", "IR", "TM"};
+	String[] COUNTRIES_SNOWFLAKE_BRIDGES = {"BY", "CN", "EG", "HK", "IR", "RU", "TM"};
 
 	/**
-	 * Returns true if vanilla Tor connections are blocked in the given country.
+	 * Returns the types of bridge connection that are suitable for the given country, or
+	 * {@link BridgeType#DEFAULT_OBFS4} and {@link BridgeType#VANILLA} if we don't have any
+	 * specific recommendations for the given country.
 	 */
-	boolean isTorProbablyBlocked(String countryCode);
+	List<BridgeType> getSuitableBridgeTypes(String countryCode);
 
 	/**
-	 * Returns true if bridge connections of some type work in the given
-	 * country.
+	 * Returns bridges of the given type that are usable in the given country.
+	 *
+	 * @param letsEncrypt Specifies whether the client is able to verify Let's Encrypt TLS
+	 * 		certificates signed with the IdentTrust DST Root X3 certificate. Versions of Android
+	 * 		older than 7.1 consider the certificate to have expired at the end of September 2021.
+	 * 		This parameter is currently ignored, as no domain-fronted bridges use Let's Encrypt
+	 * 		certificates.
 	 */
-	boolean doBridgesWork(String countryCode);
+	List<String> getBridges(BridgeType type, String countryCode, boolean letsEncrypt);
 
 	/**
-	 * Returns the types of bridge connection that are suitable for the given
-	 * country, or {@link #DEFAULT_BRIDGES} if no bridge type is known
-	 * to work.
+	 * Returns bridges of the given type that are usable in the given country.
 	 */
-	List<BridgeType> getSuitableBridgeTypes(String countryCode);
-
-	List<String> getBridges(BridgeType type, String countryCode,
-			boolean letsEncrypt);
+	List<String> getBridges(BridgeType type, String countryCode);
 }
diff --git a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/CircumventionProviderImpl.java b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/CircumventionProviderImpl.java
index 8f2af99..eef54fc 100644
--- a/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/CircumventionProviderImpl.java
+++ b/onionwrapper-core/src/main/java/org/briarproject/onionwrapper/CircumventionProviderImpl.java
@@ -6,15 +6,14 @@ import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Scanner;
 import java.util.Set;
-import java.util.TreeMap;
 
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
 import static java.util.Arrays.asList;
+import static java.util.Locale.US;
 import static org.briarproject.nullsafety.NullSafety.requireNonNull;
 import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
 import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.MEEK;
@@ -26,102 +25,68 @@ import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.VAN
 @NotNullByDefault
 class CircumventionProviderImpl implements CircumventionProvider {
 
-	private final static String BRIDGE_FILE_NAME = "bridges";
-	private final static String SNOWFLAKE_PARAMS_FILE_NAME = "snowflake-params";
-	private final static String DEFAULT_COUNTRY_CODE = "ZZ";
+	private static final String DEFAULT_COUNTRY_CODE = "ZZ";
 
-	private static final Set<String> BLOCKED_IN_COUNTRIES =
-			new HashSet<>(asList(BLOCKED));
-	private static final Set<String> BRIDGE_COUNTRIES =
-			new HashSet<>(asList(BRIDGES));
-	private static final Set<String> DEFAULT_OBFS4_BRIDGE_COUNTRIES =
-			new HashSet<>(asList(DEFAULT_BRIDGES));
-	private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
-			new HashSet<>(asList(NON_DEFAULT_BRIDGES));
-	private static final Set<String> DPI_COUNTRIES =
-			new HashSet<>(asList(DPI_BRIDGES));
+	private static final Set<String> DEFAULT_BRIDGES =
+			new HashSet<>(asList(COUNTRIES_DEFAULT_BRIDGES));
+	private static final Set<String> NON_DEFAULT_BRIDGES =
+			new HashSet<>(asList(COUNTRIES_NON_DEFAULT_BRIDGES));
+	private static final Set<String> MEEK_BRIDGES =
+			new HashSet<>(asList(COUNTRIES_MEEK_BRIDGES));
+	private static final Set<String> SNOWFLAKE_BRIDGES =
+			new HashSet<>(asList(COUNTRIES_SNOWFLAKE_BRIDGES));
 
 	@Inject
 	CircumventionProviderImpl() {
 	}
 
 	@Override
-	public boolean isTorProbablyBlocked(String countryCode) {
-		return BLOCKED_IN_COUNTRIES.contains(countryCode);
+	public List<BridgeType> getSuitableBridgeTypes(String countryCode) {
+		List<BridgeType> types = new ArrayList<>();
+		if (DEFAULT_BRIDGES.contains(countryCode)) {
+			types.add(DEFAULT_OBFS4);
+		}
+		if (NON_DEFAULT_BRIDGES.contains(countryCode)) {
+			types.add(NON_DEFAULT_OBFS4);
+			types.add(VANILLA);
+		}
+		if (MEEK_BRIDGES.contains(countryCode)) types.add(MEEK);
+		if (SNOWFLAKE_BRIDGES.contains(countryCode)) types.add(SNOWFLAKE);
+		// If we don't have any recommendations for this country then use the defaults
+		if (types.isEmpty()) {
+			types.add(DEFAULT_OBFS4);
+			types.add(VANILLA);
+		}
+		return types;
 	}
 
 	@Override
-	public boolean doBridgesWork(String countryCode) {
-		return BRIDGE_COUNTRIES.contains(countryCode);
+	public List<String> getBridges(BridgeType type, String countryCode, boolean letsEncrypt) {
+		// The `letsEncrypt` parameter is ignored, as no domain-fronted bridges use Let's Encrypt
+		return getBridges(type, countryCode);
 	}
 
 	@Override
-	public List<BridgeType> getSuitableBridgeTypes(String countryCode) {
-		if (DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) {
-			return asList(DEFAULT_OBFS4, VANILLA);
-		} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
-			return asList(NON_DEFAULT_OBFS4, VANILLA);
-		} else if (DPI_COUNTRIES.contains(countryCode)) {
-			return asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE);
-		} else {
-			return asList(DEFAULT_OBFS4, VANILLA);
+	public List<String> getBridges(BridgeType type, String countryCode) {
+		ClassLoader cl = getClass().getClassLoader();
+		// Try to load bridges that are specific to this country code
+		String filename = makeResourceFilename(type, countryCode);
+		InputStream is = cl.getResourceAsStream(filename);
+		if (is == null) {
+			// No resource for this country code - use the fallback resource
+			filename = makeResourceFilename(type, DEFAULT_COUNTRY_CODE);
+			is = requireNonNull(cl.getResourceAsStream(filename));
 		}
-	}
-
-	@Override
-	public List<String> getBridges(BridgeType type, String countryCode,
-			boolean letsEncrypt) {
-		InputStream is = requireNonNull(getClass().getClassLoader()
-				.getResourceAsStream(BRIDGE_FILE_NAME));
-		Scanner scanner = new Scanner(is);
-
 		List<String> bridges = new ArrayList<>();
-		while (scanner.hasNextLine()) {
-			String line = scanner.nextLine();
-			if ((type == DEFAULT_OBFS4 && line.startsWith("d ")) ||
-					(type == NON_DEFAULT_OBFS4 && line.startsWith("n ")) ||
-					(type == VANILLA && line.startsWith("v ")) ||
-					(type == MEEK && line.startsWith("m "))) {
-				bridges.add(line.substring(2));
-			} else if (type == SNOWFLAKE && line.startsWith("s ")) {
-				String params = getSnowflakeParams(countryCode, letsEncrypt);
-				bridges.add(line.substring(2) + " " + params);
-			}
-		}
-		scanner.close();
-		return bridges;
-	}
-
-	// Package access for testing
-	@SuppressWarnings("WeakerAccess")
-	String getSnowflakeParams(String countryCode, boolean letsEncrypt) {
-		Map<String, String> params = loadSnowflakeParams();
-		if (countryCode.isEmpty()) countryCode = DEFAULT_COUNTRY_CODE;
-		// If we have parameters for this country code, return them
-		String value = params.get(makeKey(countryCode, letsEncrypt));
-		if (value != null) return value;
-		// Return the default parameters
-		value = params.get(makeKey(DEFAULT_COUNTRY_CODE, letsEncrypt));
-		return requireNonNull(value);
-	}
-
-	private Map<String, String> loadSnowflakeParams() {
-		InputStream is = requireNonNull(getClass().getClassLoader()
-				.getResourceAsStream(SNOWFLAKE_PARAMS_FILE_NAME));
 		Scanner scanner = new Scanner(is);
-		Map<String, String> params = new TreeMap<>();
 		while (scanner.hasNextLine()) {
-			String line = scanner.nextLine();
-			if (line.length() < 5) continue;
-			String key = line.substring(0, 4); // Country code, space, digit
-			String value = line.substring(5);
-			params.put(key, value);
+			bridges.add("Bridge " + scanner.nextLine());
 		}
 		scanner.close();
-		return params;
+		return bridges;
 	}
 
-	private String makeKey(String countryCode, boolean letsEncrypt) {
-		return countryCode + " " + (letsEncrypt ? "1" : "0");
+	private String makeResourceFilename(BridgeType type, String countryCode) {
+		return "bridges-" + type.letter + "-" + countryCode.toLowerCase(US);
 	}
 }
diff --git a/onionwrapper-core/src/main/resources/bridges b/onionwrapper-core/src/main/resources/bridges
deleted file mode 100644
index 410ab7e..0000000
--- a/onionwrapper-core/src/main/resources/bridges
+++ /dev/null
@@ -1,39 +0,0 @@
-d Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
-d Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
-d Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
-d Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
-d Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
-d Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
-d Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
-d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
-d Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
-d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
-d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
-n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
-n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
-n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
-n Bridge obfs4 45.142.181.131:42069 6EBCF6B02DA2B982F4080A7119D737366AFB74FA cert=9HyWH/BCwWzNirZdTQtluCgJk+gFhpOqydIuyQ1iDvpnqsAynKF+zcPE/INZFefm86UlBg iat-mode=0
-n Bridge obfs4 94.142.246.132:8088 135C158527AA9FE9A2F26EC515EB6999D813D347 cert=wTUz0/5FhAZRkitil5MprGbSF3JzjxjxI1kAmxAdSeDy98NgcLr11f/qUXWDC76Y97RiSg iat-mode=0
-n Bridge obfs4 185.177.207.132:8443 4FB781F7A9DD39DA53A7996907817FC479874D19 cert=UL2gCAXWW5kEWY4TQ0lNeu6OAmzh40bXYVhMnTWVG8USnyy/zEKGSIPgmwTDMumWr9c1Pg iat-mode=0
-n Bridge obfs4 158.174.114.97:3456 32665CD4CBE19092CA47A53D317B8BFF5810441C cert=ne5Zt0TcMedSGmFwAs/AV6J6E9Hn7mG5mR6vQNpEfyuCZK1VRpQvU1LvvtesSu4CXqZtYQ iat-mode=0
-n Bridge obfs4 64.4.175.62:8000 8B72740D150795ACB5101AA5F95D1ACDA4FE6B3E cert=vduuNhJ5U/8hjZmllP6AFfXSlSZsnrimdR8Tm8DY9dxWS4n2j92fNc0qHihUwRqwcOfIcg iat-mode=0
-n Bridge obfs4 82.64.115.17:990 B08238781C2CD80DBD95AEABEB6F6C75F2E2CEB6 cert=1udeMlFNs3sJ20zwpPE6nShZqqwDb3F1ET4KzfSfD+fktkue9zNx9H3t+yLCPAsg+6UTUA iat-mode=1
-n Bridge obfs4 87.106.229.194:8006 0B6892C2DCD1FE8C1E7CBEC202BCEBECBDE47ECB cert=dZWWyGKA2x26+bHSMqPKSE81PGarid4FUHGYJMzs9onWMjUuZVotKRYlMIRStVdqKBdOVw iat-mode=0
-n Bridge obfs4 185.177.207.153:8443 6574D4D903FDE714F2759A3B3C31C0363A92DCDC cert=VAZd6bOJ6BKUZLLOYhMuaSPxjf+ZGAspvdQkf8C3naGk8r2b77WXWj9JF8+jLYb8l2fnUw iat-mode=0
-n Bridge obfs4 213.109.161.63:1790 7AC552E815FD43CEDE8E5E1768B305F61AC173B3 cert=9VwHJxHDvWBBHaLarwT5xPuCY0vVs6AWMsX4WuQ08Gj1JheSHYKZuSNiAXzZSukRlDKTDA iat-mode=0
-n Bridge obfs4 80.209.233.177:50901 1E8DB927ADDF9F0E529863ECEEAF801E43FFE355 cert=bDUl798foJHCXhkDYXLTog0El/tqx06DdZNo18nCDzKpElQyIeqU3wg6qggmbZycU6v4fw iat-mode=0
-n Bridge obfs4 78.159.118.224:19998 9735DAE37918DD9F0BA9CF56D336294BCB4207CC cert=MIBlPdg69nSskD9Id8bLzwQFJ1zICUMwwG9apMlvF35Y6Z9W8AVbSlahxlY17l8zLvwdEA iat-mode=0
-n Bridge obfs4 24.134.5.121:8989 6C11CE58EA4D4C3C7EC078EBDC9D7D8961A58699 cert=q9W7JYmdRvQqs9MEa7rAKU5iNiu5zKtkyJPVH5aH61QRgcTrjtom58EoDnTnnlc37xNZPQ iat-mode=0
-n Bridge obfs4 152.86.13.25:9025 3A3614A3C6F6BD6300B131E24D8C2A794B36156F cert=6KkUp19/U0GPSAMm9Qvw84Y3j1eJcpyitMB08Jc+NP79ztkuJUoK5/ukIxRntsi5aXOYEg iat-mode=0
-n Bridge obfs4 23.94.169.122:63000 1878E0F5DAFFBD4A0F8DC75820ADB9B10E8ABAF0 cert=iESu5/SAcFI8GPUXHaV4Yk1zraP1AjZySYSsKh0lT+Bj0q/pdwa4PjMjsOusMxiOuUQAOA iat-mode=0
-v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
-v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31
-v Bridge 87.100.193.2:9010 13FB63452AADFA082BD2BC3E1E320AD301F07877
-v Bridge 65.21.240.163:33245 20BD59649212CFE7412BFC9B94C3CCCFD8F807A8
-v Bridge 195.201.202.125:443 D44C0BC5AF900547704BCE5062E4B169672120E8
-v Bridge 80.67.179.20:143 D1B6871457A0AA01BF46EEB1989D89086E5E6825
-v Bridge 95.217.86.169:9001 7E0551FA74CF3C94C9CE3FB627AA6CA1066CECDC
-v Bridge 23.88.56.229:18680 0131800F17ACED0D6CF979F3D0910D63E5F6015A
-v Bridge 95.216.198.193:8080 23F35720E7DAC1F8A3DDBD5D40C90A552B31435A
-m Bridge meek_lite 192.0.2.18:80 BE776A53492E1E044A26F17306E1BC46A55A1625 url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com utls=hellochrome_auto
-s Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72
diff --git a/onionwrapper-core/src/main/resources/bridges-d-zz b/onionwrapper-core/src/main/resources/bridges-d-zz
new file mode 100644
index 0000000..c97c76c
--- /dev/null
+++ b/onionwrapper-core/src/main/resources/bridges-d-zz
@@ -0,0 +1,11 @@
+obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
+obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
+obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
+obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
+obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
+obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
+obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
+obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
+obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
+obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
+obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
diff --git a/onionwrapper-core/src/main/resources/bridges-m-zz b/onionwrapper-core/src/main/resources/bridges-m-zz
new file mode 100644
index 0000000..a7c2573
--- /dev/null
+++ b/onionwrapper-core/src/main/resources/bridges-m-zz
@@ -0,0 +1 @@
+meek_lite 192.0.2.18:80 BE776A53492E1E044A26F17306E1BC46A55A1625 url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com utls=hellochrome_auto
diff --git a/onionwrapper-core/src/main/resources/bridges-n-zz b/onionwrapper-core/src/main/resources/bridges-n-zz
new file mode 100644
index 0000000..030d97b
--- /dev/null
+++ b/onionwrapper-core/src/main/resources/bridges-n-zz
@@ -0,0 +1,17 @@
+obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
+obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
+obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
+obfs4 45.142.181.131:42069 6EBCF6B02DA2B982F4080A7119D737366AFB74FA cert=9HyWH/BCwWzNirZdTQtluCgJk+gFhpOqydIuyQ1iDvpnqsAynKF+zcPE/INZFefm86UlBg iat-mode=0
+obfs4 94.142.246.132:8088 135C158527AA9FE9A2F26EC515EB6999D813D347 cert=wTUz0/5FhAZRkitil5MprGbSF3JzjxjxI1kAmxAdSeDy98NgcLr11f/qUXWDC76Y97RiSg iat-mode=0
+obfs4 185.177.207.132:8443 4FB781F7A9DD39DA53A7996907817FC479874D19 cert=UL2gCAXWW5kEWY4TQ0lNeu6OAmzh40bXYVhMnTWVG8USnyy/zEKGSIPgmwTDMumWr9c1Pg iat-mode=0
+obfs4 158.174.114.97:3456 32665CD4CBE19092CA47A53D317B8BFF5810441C cert=ne5Zt0TcMedSGmFwAs/AV6J6E9Hn7mG5mR6vQNpEfyuCZK1VRpQvU1LvvtesSu4CXqZtYQ iat-mode=0
+obfs4 64.4.175.62:8000 8B72740D150795ACB5101AA5F95D1ACDA4FE6B3E cert=vduuNhJ5U/8hjZmllP6AFfXSlSZsnrimdR8Tm8DY9dxWS4n2j92fNc0qHihUwRqwcOfIcg iat-mode=0
+obfs4 82.64.115.17:990 B08238781C2CD80DBD95AEABEB6F6C75F2E2CEB6 cert=1udeMlFNs3sJ20zwpPE6nShZqqwDb3F1ET4KzfSfD+fktkue9zNx9H3t+yLCPAsg+6UTUA iat-mode=1
+obfs4 87.106.229.194:8006 0B6892C2DCD1FE8C1E7CBEC202BCEBECBDE47ECB cert=dZWWyGKA2x26+bHSMqPKSE81PGarid4FUHGYJMzs9onWMjUuZVotKRYlMIRStVdqKBdOVw iat-mode=0
+obfs4 185.177.207.153:8443 6574D4D903FDE714F2759A3B3C31C0363A92DCDC cert=VAZd6bOJ6BKUZLLOYhMuaSPxjf+ZGAspvdQkf8C3naGk8r2b77WXWj9JF8+jLYb8l2fnUw iat-mode=0
+obfs4 213.109.161.63:1790 7AC552E815FD43CEDE8E5E1768B305F61AC173B3 cert=9VwHJxHDvWBBHaLarwT5xPuCY0vVs6AWMsX4WuQ08Gj1JheSHYKZuSNiAXzZSukRlDKTDA iat-mode=0
+obfs4 80.209.233.177:50901 1E8DB927ADDF9F0E529863ECEEAF801E43FFE355 cert=bDUl798foJHCXhkDYXLTog0El/tqx06DdZNo18nCDzKpElQyIeqU3wg6qggmbZycU6v4fw iat-mode=0
+obfs4 78.159.118.224:19998 9735DAE37918DD9F0BA9CF56D336294BCB4207CC cert=MIBlPdg69nSskD9Id8bLzwQFJ1zICUMwwG9apMlvF35Y6Z9W8AVbSlahxlY17l8zLvwdEA iat-mode=0
+obfs4 24.134.5.121:8989 6C11CE58EA4D4C3C7EC078EBDC9D7D8961A58699 cert=q9W7JYmdRvQqs9MEa7rAKU5iNiu5zKtkyJPVH5aH61QRgcTrjtom58EoDnTnnlc37xNZPQ iat-mode=0
+obfs4 152.86.13.25:9025 3A3614A3C6F6BD6300B131E24D8C2A794B36156F cert=6KkUp19/U0GPSAMm9Qvw84Y3j1eJcpyitMB08Jc+NP79ztkuJUoK5/ukIxRntsi5aXOYEg iat-mode=0
+obfs4 23.94.169.122:63000 1878E0F5DAFFBD4A0F8DC75820ADB9B10E8ABAF0 cert=iESu5/SAcFI8GPUXHaV4Yk1zraP1AjZySYSsKh0lT+Bj0q/pdwa4PjMjsOusMxiOuUQAOA iat-mode=0
diff --git a/onionwrapper-core/src/main/resources/bridges-s-zz b/onionwrapper-core/src/main/resources/bridges-s-zz
new file mode 100644
index 0000000..f5d7d9d
--- /dev/null
+++ b/onionwrapper-core/src/main/resources/bridges-s-zz
@@ -0,0 +1,4 @@
+snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
+snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://1098762253.rsc.cdn77.org/ fronts=www.cdn77.com,www.phpmyadmin.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
+snowflake 192.0.2.5:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://snowflake-broker.azureedge.net/ fronts=ajax.aspnetcdn.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
+snowflake 192.0.2.6:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://snowflake-broker.azureedge.net/ fronts=ajax.aspnetcdn.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
diff --git a/onionwrapper-core/src/main/resources/bridges-v-zz b/onionwrapper-core/src/main/resources/bridges-v-zz
new file mode 100644
index 0000000..5e4df34
--- /dev/null
+++ b/onionwrapper-core/src/main/resources/bridges-v-zz
@@ -0,0 +1,9 @@
+92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
+213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31
+87.100.193.2:9010 13FB63452AADFA082BD2BC3E1E320AD301F07877
+65.21.240.163:33245 20BD59649212CFE7412BFC9B94C3CCCFD8F807A8
+195.201.202.125:443 D44C0BC5AF900547704BCE5062E4B169672120E8
+80.67.179.20:143 D1B6871457A0AA01BF46EEB1989D89086E5E6825
+95.217.86.169:9001 7E0551FA74CF3C94C9CE3FB627AA6CA1066CECDC
+23.88.56.229:18680 0131800F17ACED0D6CF979F3D0910D63E5F6015A
+95.216.198.193:8080 23F35720E7DAC1F8A3DDBD5D40C90A552B31435A
diff --git a/onionwrapper-core/src/main/resources/snowflake-params b/onionwrapper-core/src/main/resources/snowflake-params
deleted file mode 100644
index 86f4b5c..0000000
--- a/onionwrapper-core/src/main/resources/snowflake-params
+++ /dev/null
@@ -1,6 +0,0 @@
-ZZ 1 url=https://1098762253.rsc.cdn77.org/ front=www.cdn77.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
-ZZ 0 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
-TM 1 url=https://1098762253.rsc.cdn77.org/ front=www.cdn77.com ice=stun:206.53.159.130:3479,stun:94.23.17.185:3479,stun:217.74.179.29:3479,stun:83.125.8.47:3479,stun:23.253.102.137:3479,stun:52.26.251.34:3479,stun:154.73.34.8:3479,stun:185.125.180.70:3479,stun:195.35.115.37:3479 utls-imitate=hellochrome_auto
-TM 0 url=https://1098762253.rsc.cdn77.org/ front=www.cdn77.com ice=stun:206.53.159.130:3479,stun:94.23.17.185:3479,stun:217.74.179.29:3479,stun:83.125.8.47:3479,stun:23.253.102.137:3479,stun:52.26.251.34:3479,stun:154.73.34.8:3479,stun:185.125.180.70:3479,stun:195.35.115.37:3479 utls-imitate=hellochrome_auto
-IR 1 url=https://1098762253.rsc.cdn77.org/ front=www.cdn77.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
-IR 0 url=https://1098762253.rsc.cdn77.org/ front=www.cdn77.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
diff --git a/onionwrapper-core/src/test/java/org/briarproject/onionwrapper/CircumventionProviderImplTest.java b/onionwrapper-core/src/test/java/org/briarproject/onionwrapper/CircumventionProviderImplTest.java
index 187f396..3dd57ac 100644
--- a/onionwrapper-core/src/test/java/org/briarproject/onionwrapper/CircumventionProviderImplTest.java
+++ b/onionwrapper-core/src/test/java/org/briarproject/onionwrapper/CircumventionProviderImplTest.java
@@ -1,24 +1,18 @@
 package org.briarproject.onionwrapper;
 
+import org.briarproject.onionwrapper.CircumventionProvider.BridgeType;
 import org.junit.Test;
 
-import java.util.HashSet;
-import java.util.Set;
-
-import static java.util.Arrays.asList;
-import static org.briarproject.onionwrapper.CircumventionProvider.BLOCKED;
-import static org.briarproject.onionwrapper.CircumventionProvider.BRIDGES;
 import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
 import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.MEEK;
 import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
 import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
 import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.VANILLA;
-import static org.briarproject.onionwrapper.CircumventionProvider.DEFAULT_BRIDGES;
-import static org.briarproject.onionwrapper.CircumventionProvider.DPI_BRIDGES;
-import static org.briarproject.onionwrapper.CircumventionProvider.NON_DEFAULT_BRIDGES;
-import static org.junit.Assert.assertEquals;
+import static org.briarproject.onionwrapper.CircumventionProvider.COUNTRIES_DEFAULT_BRIDGES;
+import static org.briarproject.onionwrapper.CircumventionProvider.COUNTRIES_MEEK_BRIDGES;
+import static org.briarproject.onionwrapper.CircumventionProvider.COUNTRIES_NON_DEFAULT_BRIDGES;
+import static org.briarproject.onionwrapper.CircumventionProvider.COUNTRIES_SNOWFLAKE_BRIDGES;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 public class CircumventionProviderImplTest extends BaseTest {
@@ -27,65 +21,35 @@ public class CircumventionProviderImplTest extends BaseTest {
 			new CircumventionProviderImpl();
 
 	@Test
-	public void testInvariants() {
-		Set<String> blocked = new HashSet<>(asList(BLOCKED));
-		Set<String> bridges = new HashSet<>(asList(BRIDGES));
-		Set<String> defaultBridges = new HashSet<>(asList(DEFAULT_BRIDGES));
-		Set<String> nonDefaultBridges =
-				new HashSet<>(asList(NON_DEFAULT_BRIDGES));
-		Set<String> dpiBridges = new HashSet<>(asList(DPI_BRIDGES));
-		// BRIDGES should be a subset of BLOCKED
-		assertTrue(blocked.containsAll(bridges));
-		// BRIDGES should be the union of the bridge type sets
-		Set<String> union = new HashSet<>(defaultBridges);
-		union.addAll(nonDefaultBridges);
-		union.addAll(dpiBridges);
-		assertEquals(bridges, union);
-		// The bridge type sets should not overlap
-		assertEmptyIntersection(defaultBridges, nonDefaultBridges);
-		assertEmptyIntersection(defaultBridges, dpiBridges);
-		assertEmptyIntersection(nonDefaultBridges, dpiBridges);
-	}
-
-	@Test
-	public void testGetBestBridgeType() {
-		for (String country : DEFAULT_BRIDGES) {
-			assertEquals(asList(DEFAULT_OBFS4, VANILLA),
-					provider.getSuitableBridgeTypes(country));
+	public void testGetSuitableBridgeTypes() {
+		for (String countryCode : COUNTRIES_DEFAULT_BRIDGES) {
+			testBridgesAreSuitableAndExist(DEFAULT_OBFS4, countryCode);
 		}
-		for (String country : NON_DEFAULT_BRIDGES) {
-			assertEquals(asList(NON_DEFAULT_OBFS4, VANILLA),
-					provider.getSuitableBridgeTypes(country));
+		for (String countryCode : COUNTRIES_NON_DEFAULT_BRIDGES) {
+			testBridgesAreSuitableAndExist(NON_DEFAULT_OBFS4, countryCode);
+			testBridgesAreSuitableAndExist(VANILLA, countryCode);
 		}
-		for (String country : DPI_BRIDGES) {
-			assertEquals(asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE),
-					provider.getSuitableBridgeTypes(country));
+		for (String countryCode : COUNTRIES_MEEK_BRIDGES) {
+			testBridgesAreSuitableAndExist(MEEK, countryCode);
 		}
-		assertEquals(asList(DEFAULT_OBFS4, VANILLA),
-				provider.getSuitableBridgeTypes("ZZ"));
-	}
-
-	@Test
-	public void testHasSnowflakeParamsWithLetsEncrypt() {
-		testHasSnowflakeParams(true);
+		for (String countryCode : COUNTRIES_SNOWFLAKE_BRIDGES) {
+			testBridgesAreSuitableAndExist(SNOWFLAKE, countryCode);
+		}
+		// If bridges are enabled manually in a country with no specific bridge recommendations,
+		// we should use default obfs4 and vanilla
+		testBridgesAreSuitableAndExist(DEFAULT_OBFS4, "US");
+		testBridgesAreSuitableAndExist(VANILLA, "US");
 	}
 
 	@Test
-	public void testHasSnowflakeParamsWithoutLetsEncrypt() {
-		testHasSnowflakeParams(false);
-	}
-
-	private void testHasSnowflakeParams(boolean letsEncrypt) {
-		String tmParams = provider.getSnowflakeParams("TM", letsEncrypt);
-		String defaultParams = provider.getSnowflakeParams("ZZ", letsEncrypt);
-		assertFalse(tmParams.isEmpty());
-		assertFalse(defaultParams.isEmpty());
-		assertNotEquals(defaultParams, tmParams);
+	public void testIPv6BridgeTypes() {
+		// If we're on an IPv6-only network we'll use meek and snowflake in any country
+		assertFalse(provider.getBridges(MEEK, "US").isEmpty());
+		assertFalse(provider.getBridges(SNOWFLAKE, "US").isEmpty());
 	}
 
-	private <T> void assertEmptyIntersection(Set<T> a, Set<T> b) {
-		Set<T> intersection = new HashSet<>(a);
-		intersection.retainAll(b);
-		assertTrue(intersection.isEmpty());
+	private void testBridgesAreSuitableAndExist(BridgeType type, String countryCode) {
+		assertTrue(provider.getSuitableBridgeTypes(countryCode).contains(type));
+		assertFalse(provider.getBridges(type, countryCode).isEmpty());
 	}
 }
diff --git a/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/BridgeTest.java b/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/BridgeTest.java
index 34b6208..ab531d4 100644
--- a/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/BridgeTest.java
+++ b/onionwrapper-java/src/test/java/org/briarproject/onionwrapper/BridgeTest.java
@@ -19,6 +19,7 @@ import java.util.logging.Logger;
 
 import javax.annotation.concurrent.GuardedBy;
 
+import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static java.util.concurrent.Executors.newCachedThreadPool;
 import static java.util.concurrent.TimeUnit.MINUTES;
@@ -29,6 +30,10 @@ import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.MEE
 import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
 import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
 import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.VANILLA;
+import static org.briarproject.onionwrapper.CircumventionProvider.COUNTRIES_DEFAULT_BRIDGES;
+import static org.briarproject.onionwrapper.CircumventionProvider.COUNTRIES_MEEK_BRIDGES;
+import static org.briarproject.onionwrapper.CircumventionProvider.COUNTRIES_NON_DEFAULT_BRIDGES;
+import static org.briarproject.onionwrapper.CircumventionProvider.COUNTRIES_SNOWFLAKE_BRIDGES;
 import static org.briarproject.onionwrapper.TestUtils.deleteTestDirectory;
 import static org.briarproject.onionwrapper.TestUtils.getArchitectureForTorBinary;
 import static org.briarproject.onionwrapper.TestUtils.getTestDirectory;
@@ -44,7 +49,7 @@ public class BridgeTest extends BaseTest {
 
 	private final static Logger LOG = getLogger(BridgeTest.class.getName());
 
-	private static final String[] SNOWFLAKE_COUNTRY_CODES = {"IR", "TM", "ZZ"};
+	private static final List<BridgeType> ESSENTIAL_BRIDGE_TYPES = asList(MEEK, SNOWFLAKE);
 	private static final int SOCKS_PORT = 59060;
 	private static final int CONTROL_PORT = 59061;
 	private final static long TIMEOUT = MINUTES.toMillis(2);
@@ -56,27 +61,39 @@ public class BridgeTest extends BaseTest {
 	public static Iterable<Params> data() {
 		// Share stats among all the test instances
 		Stats stats = new Stats();
+		// Test all the unique bridge lines
+		Set<String> bridges = new TreeSet<>();
 		CircumventionProvider provider = new CircumventionProviderImpl();
 		List<Params> states = new ArrayList<>();
 		for (int i = 0; i < ATTEMPTS_PER_BRIDGE; i++) {
-			for (String bridge : provider.getBridges(DEFAULT_OBFS4, "", true)) {
-				states.add(new Params(bridge, DEFAULT_OBFS4, stats, false));
-			}
-			for (String bridge : provider.getBridges(NON_DEFAULT_OBFS4, "", true)) {
-				states.add(new Params(bridge, NON_DEFAULT_OBFS4, stats, false));
+			for (BridgeType type : BridgeType.values()) {
+				for (String bridge : provider.getBridges(type, "ZZ")) {
+					if (bridges.add(bridge)) states.add(new Params(bridge, type, stats));
+				}
 			}
-			for (String bridge : provider.getBridges(VANILLA, "", true)) {
-				states.add(new Params(bridge, VANILLA, stats, false));
+			for (String countryCode : COUNTRIES_DEFAULT_BRIDGES) {
+				for (String bridge : provider.getBridges(DEFAULT_OBFS4, countryCode)) {
+					if (bridges.add(bridge)) states.add(new Params(bridge, DEFAULT_OBFS4, stats));
+				}
 			}
-			for (String bridge : provider.getBridges(MEEK, "", true)) {
-				states.add(new Params(bridge, MEEK, stats, true));
+			for (String countryCode : COUNTRIES_NON_DEFAULT_BRIDGES) {
+				for (String bridge : provider.getBridges(NON_DEFAULT_OBFS4, countryCode)) {
+					if (bridges.add(bridge)) {
+						states.add(new Params(bridge, NON_DEFAULT_OBFS4, stats));
+					}
+				}
+				for (String bridge : provider.getBridges(VANILLA, countryCode)) {
+					if (bridges.add(bridge)) states.add(new Params(bridge, VANILLA, stats));
+				}
 			}
-			for (String countryCode : SNOWFLAKE_COUNTRY_CODES) {
-				for (String bridge : provider.getBridges(SNOWFLAKE, countryCode, true)) {
-					states.add(new Params(bridge, SNOWFLAKE, stats, true));
+			for (String countryCode : COUNTRIES_MEEK_BRIDGES) {
+				for (String bridge : provider.getBridges(MEEK, countryCode)) {
+					if (bridges.add(bridge)) states.add(new Params(bridge, MEEK, stats));
 				}
-				for (String bridge : provider.getBridges(SNOWFLAKE, countryCode, false)) {
-					states.add(new Params(bridge, SNOWFLAKE, stats, true));
+			}
+			for (String countryCode : COUNTRIES_SNOWFLAKE_BRIDGES) {
+				for (String bridge : provider.getBridges(SNOWFLAKE, countryCode)) {
+					if (bridges.add(bridge)) states.add(new Params(bridge, SNOWFLAKE, stats));
 				}
 			}
 		}
@@ -132,7 +149,7 @@ public class BridgeTest extends BaseTest {
 				params.stats.countSuccess(params.bridge);
 			} else {
 				LOG.warning("Could not connect to Tor within timeout: " + params.bridge);
-				params.stats.countFailure(params.bridge, params.essential);
+				params.stats.countFailure(params.bridge, params.bridgeType);
 			}
 		} finally {
 			tor.stop();
@@ -144,13 +161,11 @@ public class BridgeTest extends BaseTest {
 		private final String bridge;
 		private final BridgeType bridgeType;
 		private final Stats stats;
-		private final boolean essential;
 
-		private Params(String bridge, BridgeType bridgeType, Stats stats, boolean essential) {
+		private Params(String bridge, BridgeType bridgeType, Stats stats) {
 			this.bridge = bridge;
 			this.bridgeType = bridgeType;
 			this.stats = stats;
-			this.essential = essential;
 		}
 	}
 
@@ -171,7 +186,7 @@ public class BridgeTest extends BaseTest {
 			successes.add(bridge);
 		}
 
-		private synchronized void countFailure(String bridge, boolean essential) {
+		private synchronized void countFailure(String bridge, BridgeType bridgeType) {
 			if (failures.add(bridge) == ATTEMPTS_PER_BRIDGE) {
 				LOG.warning("Bridge is unreachable after "
 						+ ATTEMPTS_PER_BRIDGE + " attempts: " + bridge);
@@ -179,7 +194,7 @@ public class BridgeTest extends BaseTest {
 				if (unreachable.size() > UNREACHABLE_BRIDGES_ALLOWED) {
 					fail(unreachable.size() + " bridges are unreachable: " + unreachable);
 				}
-				if (essential) {
+				if (ESSENTIAL_BRIDGE_TYPES.contains(bridgeType)) {
 					fail("essential bridge is unreachable");
 				}
 			}
-- 
GitLab