Merge "Switch TLS Channel ID API from ECPrivateKey to PrivateKey."
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
index 8320ff6..e1038a6 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
@@ -226,6 +226,9 @@
public static native long EC_KEY_get_public_key(long keyRef);
+ public static native int ECDH_compute_key(
+ byte[] out, int outOffset, long publicKeyRef, long privateKeyRef);
+
// --- Message digest functions --------------
public static native long EVP_get_digestbyname(String name);
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPrivateKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPrivateKey.java
index 6ecd6ab..df62c6d 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPrivateKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLDSAPrivateKey.java
@@ -117,6 +117,10 @@
@Override
public BigInteger getX() {
+ if (key.isEngineBased()) {
+ throw new UnsupportedOperationException("private key value X cannot be extracted");
+ }
+
ensureReadParams();
return params.getX();
}
@@ -218,6 +222,7 @@
if (getOpenSSLKey().isEngineBased()) {
throw new NotSerializableException("engine-based keys can not be serialized");
}
+
stream.defaultWriteObject();
ensureReadParams();
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java
new file mode 100644
index 0000000..096e300
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECDHKeyAgreement.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2013 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 org.apache.harmony.xnet.provider.jsse;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.SecureRandom;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.KeyAgreementSpi;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Elliptic Curve Diffie-Hellman key agreement backed by the OpenSSL engine.
+ */
+public final class OpenSSLECDHKeyAgreement extends KeyAgreementSpi {
+
+ /** OpenSSL handle of the private key. Only available after the engine has been initialized. */
+ private OpenSSLKey mOpenSslPrivateKey;
+
+ /**
+ * Expected length (in bytes) of the agreed key ({@link #mResult}). Only available after the
+ * engine has been initialized.
+ */
+ private int mExpectedResultLength;
+
+ /** Agreed key. Only available after {@link #engineDoPhase(Key, boolean)} completes. */
+ private byte[] mResult;
+
+ @Override
+ public Key engineDoPhase(Key key, boolean lastPhase) throws InvalidKeyException {
+ if (mOpenSslPrivateKey == null) {
+ throw new IllegalStateException("Not initialized");
+ }
+ if (!lastPhase) {
+ throw new IllegalStateException("ECDH only has one phase");
+ }
+ if (key == null) {
+ throw new InvalidKeyException("key == null");
+ }
+ if (!(key instanceof ECPublicKey)) {
+ throw new InvalidKeyException("This phase requires an ECPublicKey. Actual key type: "
+ + key.getClass());
+ }
+ ECPublicKey publicKey = (ECPublicKey) key;
+
+ OpenSSLKey openSslPublicKey;
+ if (publicKey instanceof OpenSSLECPublicKey) {
+ // OpenSSL-backed key
+ openSslPublicKey = ((OpenSSLECPublicKey) publicKey).getOpenSSLKey();
+ } else {
+ // Not an OpenSSL-backed key -- create an OpenSSL-backed key from its X.509 encoding
+ if (!"X.509".equals(publicKey.getFormat())) {
+ throw new InvalidKeyException("Non-OpenSSL public key (" + publicKey.getClass()
+ + ") offers unsupported encoding format: " + publicKey.getFormat());
+ }
+ byte[] encoded = publicKey.getEncoded();
+ if (encoded == null) {
+ throw new InvalidKeyException("Non-OpenSSL public key (" + publicKey.getClass()
+ + ") does not provide encoded form");
+ }
+ try {
+ openSslPublicKey = new OpenSSLKey(NativeCrypto.d2i_PUBKEY(encoded));
+ } catch (Exception e) {
+ throw new InvalidKeyException("Failed to decode X.509 encoded public key", e);
+ }
+ }
+
+ byte[] buffer = new byte[mExpectedResultLength];
+ int actualResultLength = NativeCrypto.ECDH_compute_key(
+ buffer,
+ 0,
+ openSslPublicKey.getPkeyContext(),
+ mOpenSslPrivateKey.getPkeyContext());
+ byte[] result;
+ if (actualResultLength == -1) {
+ throw new RuntimeException("Engine returned " + actualResultLength);
+ } else if (actualResultLength == mExpectedResultLength) {
+ // The output is as long as expected -- use the whole buffer
+ result = buffer;
+ } else if (actualResultLength < mExpectedResultLength) {
+ // The output is shorter than expected -- use only what's produced by the engine
+ result = new byte[actualResultLength];
+ System.arraycopy(buffer, 0, mResult, 0, mResult.length);
+ } else {
+ // The output is longer than expected
+ throw new RuntimeException("Engine produced a longer than expected result. Expected: "
+ + mExpectedResultLength + ", actual: " + actualResultLength);
+ }
+ mResult = result;
+
+ return null; // No intermediate key
+ }
+
+ @Override
+ protected int engineGenerateSecret(byte[] sharedSecret, int offset)
+ throws ShortBufferException {
+ checkCompleted();
+ int available = sharedSecret.length - offset;
+ if (mResult.length > available) {
+ throw new ShortBufferException(
+ "Needed: " + mResult.length + ", available: " + available);
+ }
+
+ System.arraycopy(mResult, 0, sharedSecret, offset, mResult.length);
+ return mResult.length;
+ }
+
+ @Override
+ protected byte[] engineGenerateSecret() {
+ checkCompleted();
+ return mResult;
+ }
+
+ @Override
+ protected SecretKey engineGenerateSecret(String algorithm) {
+ checkCompleted();
+ return new SecretKeySpec(engineGenerateSecret(), algorithm);
+ }
+
+ @Override
+ protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException {
+ if (key == null) {
+ throw new InvalidKeyException("key == null");
+ }
+ if (!(key instanceof ECPrivateKey)) {
+ throw new InvalidKeyException("Not an EC private key: " + key.getClass());
+ }
+ ECPrivateKey privateKey = (ECPrivateKey) key;
+ mExpectedResultLength =
+ (privateKey.getParams().getCurve().getField().getFieldSize() + 7) / 8;
+
+ OpenSSLKey openSslPrivateKey;
+ if (privateKey instanceof OpenSSLECPrivateKey) {
+ // OpenSSL-backed key
+ openSslPrivateKey = ((OpenSSLECPrivateKey) privateKey).getOpenSSLKey();
+ } else {
+ // Not an OpenSSL-backed key -- create an OpenSSL-backed key from its PKCS#8 encoding
+ if (!"PKCS#8".equals(privateKey.getFormat())) {
+ throw new InvalidKeyException("Non-OpenSSL private key (" + privateKey.getClass()
+ + ") offers unsupported encoding format: " + privateKey.getFormat());
+ }
+ byte[] encoded = privateKey.getEncoded();
+ if (encoded == null) {
+ throw new InvalidKeyException("Non-OpenSSL private key (" + privateKey.getClass()
+ + ") does not provide encoded form");
+ }
+ try {
+ openSslPrivateKey = new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(encoded));
+ } catch (Exception e) {
+ throw new InvalidKeyException("Failed to decode PKCS#8 encoded private key", e);
+ }
+ }
+ mOpenSslPrivateKey = openSslPrivateKey;
+ }
+
+ @Override
+ protected void engineInit(Key key, AlgorithmParameterSpec params,
+ SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+ // ECDH doesn't need an AlgorithmParameterSpec
+ if (params != null) {
+ throw new InvalidAlgorithmParameterException("No algorithm parameters supported");
+ }
+ engineInit(key, random);
+ }
+
+ private void checkCompleted() {
+ if (mResult == null) {
+ throw new IllegalStateException("Key agreement not completed");
+ }
+ }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPrivateKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPrivateKey.java
index cc6c0a3..10ccf61 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPrivateKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLECPrivateKey.java
@@ -95,6 +95,10 @@
@Override
public BigInteger getS() {
+ if (key.isEngineBased()) {
+ throw new UnsupportedOperationException("private key value S cannot be extracted");
+ }
+
return getPrivateKey();
}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java
index 7ac4022..59e20d6 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLProvider.java
@@ -86,6 +86,9 @@
put("KeyFactory.EC", OpenSSLECKeyFactory.class.getName());
+ /* == KeyAgreement == */
+ put("KeyAgreement.ECDH", OpenSSLECDHKeyAgreement.class.getName());
+
/* == Signatures == */
put("Signature.MD5WithRSA", OpenSSLSignature.MD5RSA.class.getName());
put("Alg.Alias.Signature.MD5WithRSAEncryption", "MD5WithRSA");
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateCrtKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateCrtKey.java
index 862b201..c5254ad 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateCrtKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateCrtKey.java
@@ -322,6 +322,7 @@
if (getOpenSSLKey().isEngineBased()) {
throw new NotSerializableException("engine-based keys can not be serialized");
}
+
ensureReadParams();
stream.defaultWriteObject();
}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java
index 269c355..c0e0848 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java
@@ -139,6 +139,10 @@
@Override
public final BigInteger getPrivateExponent() {
+ if (key.isEngineBased()) {
+ throw new UnsupportedOperationException("private exponent cannot be extracted");
+ }
+
ensureReadParams();
return privateExponent;
}
@@ -260,6 +264,7 @@
if (getOpenSSLKey().isEngineBased()) {
throw new NotSerializableException("engine-based keys can not be serialized");
}
+
ensureReadParams();
stream.defaultWriteObject();
}
diff --git a/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp b/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
index ba68bd0..56b771a 100644
--- a/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
+++ b/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
@@ -356,6 +356,87 @@
jniThrowException(env, "java/security/NoSuchAlgorithmException", message);
}
+static void throwForAsn1Error(JNIEnv* env, int reason, const char *message) {
+ switch (reason) {
+ case ASN1_R_UNABLE_TO_DECODE_RSA_KEY:
+ case ASN1_R_UNABLE_TO_DECODE_RSA_PRIVATE_KEY:
+ case ASN1_R_UNKNOWN_PUBLIC_KEY_TYPE:
+ case ASN1_R_UNSUPPORTED_PUBLIC_KEY_TYPE:
+ case ASN1_R_WRONG_PUBLIC_KEY_TYPE:
+ throwInvalidKeyException(env, message);
+ break;
+ case ASN1_R_UNKNOWN_MESSAGE_DIGEST_ALGORITHM:
+ throwNoSuchAlgorithmException(env, message);
+ break;
+ default:
+ jniThrowRuntimeException(env, message);
+ break;
+ }
+}
+
+static void throwForEvpError(JNIEnv* env, int reason, const char *message) {
+ switch (reason) {
+ case EVP_R_BAD_DECRYPT:
+ throwBadPaddingException(env, message);
+ break;
+ case EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH:
+ throwIllegalBlockSizeException(env, message);
+ break;
+ case EVP_R_BAD_KEY_LENGTH:
+ case EVP_R_BN_DECODE_ERROR:
+ case EVP_R_BN_PUBKEY_ERROR:
+ case EVP_R_INVALID_KEY_LENGTH:
+ case EVP_R_MISSING_PARAMETERS:
+ case EVP_R_UNSUPPORTED_KEY_SIZE:
+ case EVP_R_UNSUPPORTED_KEYLENGTH:
+ throwInvalidKeyException(env, message);
+ break;
+ case EVP_R_WRONG_PUBLIC_KEY_TYPE:
+ throwSignatureException(env, message);
+ break;
+ default:
+ jniThrowRuntimeException(env, message);
+ break;
+ }
+}
+
+static void throwForRsaError(JNIEnv* env, int reason, const char *message) {
+ switch (reason) {
+ case RSA_R_BLOCK_TYPE_IS_NOT_01:
+ case RSA_R_BLOCK_TYPE_IS_NOT_02:
+ throwBadPaddingException(env, message);
+ break;
+ case RSA_R_ALGORITHM_MISMATCH:
+ case RSA_R_BAD_SIGNATURE:
+ case RSA_R_DATA_GREATER_THAN_MOD_LEN:
+ case RSA_R_INVALID_MESSAGE_LENGTH:
+ case RSA_R_WRONG_SIGNATURE_LENGTH:
+ throwSignatureException(env, message);
+ break;
+ case RSA_R_UNKNOWN_ALGORITHM_TYPE:
+ throwNoSuchAlgorithmException(env, message);
+ break;
+ case RSA_R_MODULUS_TOO_LARGE:
+ case RSA_R_NO_PUBLIC_EXPONENT:
+ throwInvalidKeyException(env, message);
+ break;
+ default:
+ jniThrowRuntimeException(env, message);
+ break;
+ }
+}
+
+static void throwForX509Error(JNIEnv* env, int reason, const char *message) {
+ switch (reason) {
+ case X509_R_UNSUPPORTED_ALGORITHM:
+ throwNoSuchAlgorithmException(env, message);
+ break;
+ default:
+ jniThrowRuntimeException(env, message);
+ break;
+ }
+}
+
/*
* Checks this thread's OpenSSL error queue and throws a RuntimeException if
* necessary.
@@ -378,26 +459,25 @@
JNI_TRACE("OpenSSL error in %s error=%lx library=%x reason=%x (%s:%d): %s %s",
location, error, library, reason, file, line, message,
(flags & ERR_TXT_STRING) ? data : "(no data)");
- if ((library == ERR_LIB_RSA)
- && ((reason == RSA_R_BLOCK_TYPE_IS_NOT_01)
- || (reason == RSA_R_BLOCK_TYPE_IS_NOT_02))) {
- throwBadPaddingException(env, message);
- } else if (library == ERR_LIB_RSA && reason == RSA_R_DATA_GREATER_THAN_MOD_LEN) {
- throwSignatureException(env, message);
- } else if (library == ERR_LIB_RSA && reason == RSA_R_WRONG_SIGNATURE_LENGTH) {
- throwSignatureException(env, message);
- } else if (library == ERR_LIB_ASN1 && reason == ASN1_R_WRONG_PUBLIC_KEY_TYPE) {
+ switch (library) {
+ case ERR_LIB_RSA:
+ throwForRsaError(env, reason, message);
+ break;
+ case ERR_LIB_ASN1:
+ throwForAsn1Error(env, reason, message);
+ break;
+ case ERR_LIB_EVP:
+ throwForEvpError(env, reason, message);
+ break;
+ case ERR_LIB_X509:
+ throwForX509Error(env, reason, message);
+ break;
+ case ERR_LIB_DSA:
throwInvalidKeyException(env, message);
- } else if (library == ERR_LIB_EVP && reason == EVP_R_BAD_DECRYPT) {
- throwBadPaddingException(env, message);
- } else if (library == ERR_LIB_EVP && reason == EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH) {
- throwIllegalBlockSizeException(env, message);
- } else if (library == ERR_LIB_X509 && reason == X509_R_UNSUPPORTED_ALGORITHM) {
- throwNoSuchAlgorithmException(env, message);
- } else if (library == ERR_LIB_DSA) {
- throwInvalidKeyException(env, message);
- } else {
+ break;
+ default:
jniThrowRuntimeException(env, message);
+ break;
}
result = true;
}
@@ -2672,6 +2752,60 @@
return reinterpret_cast<uintptr_t>(dup.release());
}
+static jint NativeCrypto_ECDH_compute_key(JNIEnv* env, jclass,
+ jbyteArray outArray, jint outOffset, jlong pubkeyRef, jlong privkeyRef)
+{
+ EVP_PKEY* pubPkey = reinterpret_cast<EVP_PKEY*>(pubkeyRef);
+ EVP_PKEY* privPkey = reinterpret_cast<EVP_PKEY*>(privkeyRef);
+ JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p)", outArray, outOffset, pubPkey, privPkey);
+
+ ScopedByteArrayRW out(env, outArray);
+ if (out.get() == NULL) {
+ JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p) can't get output buffer",
+ outArray, outOffset, pubPkey, privPkey);
+ return -1;
+ }
+
+ if ((outOffset < 0) || ((size_t) outOffset >= out.size())) {
+ jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
+ return -1;
+ }
+
+ Unique_EC_KEY pubkey(EVP_PKEY_get1_EC_KEY(pubPkey));
+ if (pubkey.get() == NULL) {
+ JNI_TRACE("ECDH_compute_key(%p) => can't get public key", pubPkey);
+ throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY public");
+ return -1;
+ }
+
+ const EC_POINT* pubkeyPoint = EC_KEY_get0_public_key(pubkey.get());
+ if (pubkeyPoint == NULL) {
+ JNI_TRACE("ECDH_compute_key(%p) => can't get public key point", pubPkey);
+ throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY public");
+ return -1;
+ }
+
+ Unique_EC_KEY privkey(EVP_PKEY_get1_EC_KEY(privPkey));
+ if (privkey.get() == NULL) {
+ throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY private");
+ return -1;
+ }
+
+ int outputLength = ECDH_compute_key(
+ &out[outOffset],
+ out.size() - outOffset,
+ pubkeyPoint,
+ privkey.get(),
+ NULL // No KDF
+ );
+ if (outputLength == -1) {
+ throwExceptionIfNecessary(env, "ECDH_compute_key");
+ return -1;
+ }
+
+ return outputLength;
+}
+
static jlong NativeCrypto_EVP_MD_CTX_create(JNIEnv* env, jclass) {
JNI_TRACE("EVP_MD_CTX_create()");
@@ -7681,6 +7815,7 @@
NATIVE_METHOD(NativeCrypto, EC_KEY_get0_group, "(J)J"),
NATIVE_METHOD(NativeCrypto, EC_KEY_get_private_key, "(J)[B"),
NATIVE_METHOD(NativeCrypto, EC_KEY_get_public_key, "(J)J"),
+ NATIVE_METHOD(NativeCrypto, ECDH_compute_key, "([BIJJ)I"),
NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_create, "()J"),
NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_init, "(J)V"),
NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_destroy, "(J)V"),
diff --git a/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java b/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java
index c1f73eb..5f34f3e 100644
--- a/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java
+++ b/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java
@@ -66,10 +66,10 @@
// Korean (sorts Korean, then English)
// …, ᄀ, ᄂ, ᄃ, ᄅ, ᄆ, ᄇ, ᄉ, ᄋ, ᄌ, ᄎ, ᄏ, ᄐ, ᄑ, ᄒ, …
AlphabeticIndex.ImmutableIndex ko = createIndex(Locale.KOREAN);
- assertHasLabel(ko, "\u1100", "\u1100");
- assertHasLabel(ko, "\u3131", "\u1100");
- assertHasLabel(ko, "\u1101", "\u1100");
- assertHasLabel(ko, "\u1161", "\u1112");
+ assertHasLabel(ko, "\u1100", "\u3131");
+ assertHasLabel(ko, "\u3131", "\u3131");
+ assertHasLabel(ko, "\u1101", "\u3131");
+ assertHasLabel(ko, "\u1161", "\u314e");
}
public void test_cs() throws Exception {
@@ -104,9 +104,12 @@
}
public void test_de() throws Exception {
- // German: [A-Z] (no ß or umlauted characters in standard alphabet)
+ // German: [A-S,Sch,St,T-Z] (no ß or umlauted characters in standard alphabet)
AlphabeticIndex.ImmutableIndex de = createIndex(Locale.GERMAN);
assertHasLabel(de, "ßind", "S");
+ assertHasLabel(de, "Sacher", "S");
+ assertHasLabel(de, "Schiller", "Sch");
+ assertHasLabel(de, "Steiff", "St");
}
public void test_th() throws Exception {
@@ -142,18 +145,18 @@
// Shen/Chen
assertHasLabel(zh_CN, "\u6c88", "C"); // icu4c 50 does not specialize for names.
// Shen/Chen (traditional)
- assertHasLabel(zh_CN, "\u700b", "S"); // icu4c 50 gets this wrong.
+ assertHasLabel(zh_CN, "\u700b", "S");
}
public void test_zh_TW() throws Exception {
// Traditional Chinese
- // …, 一, 丁, 丈, 不, 且, 丞, 串, 並, 亭, 乘, 乾, 傀, 亂, 僎, 僵, 儐, 償, 叢, 儳, 嚴, 儷, 儻, 囌, 囑, 廳, …
+ // …, [1-33, 35, 36, 39, 48]劃, …
// Shen/Chen
AlphabeticIndex.ImmutableIndex zh_TW = createIndex(new Locale("zh", "TW"));
- assertHasLabel(zh_TW, "\u6c88", "\u4e32");
- assertHasLabel(zh_TW, "\u700b", "\u53e2");
+ assertHasLabel(zh_TW, "\u6c88", "7\u5283");
+ assertHasLabel(zh_TW, "\u700b", "18\u5283");
// Jia/Gu
- assertHasLabel(zh_TW, "\u8d3e", "\u4e58");
+ assertHasLabel(zh_TW, "\u8d3e", "10\u5283");
}
public void test_constructor_NPE() throws Exception {