auto import from //depot/cupcake/@137055
diff --git a/icu/src/main/native/DecimalFormatInterface.cpp b/icu/src/main/native/DecimalFormatInterface.cpp
index fb5cf9f..6221826 100644
--- a/icu/src/main/native/DecimalFormatInterface.cpp
+++ b/icu/src/main/native/DecimalFormatInterface.cpp
@@ -50,13 +50,13 @@
default :
exception = env->FindClass("java/lang/RuntimeException");
}
-
+
return (env->ThrowNew(exception, emsg) != 0);
}
return 0;
}
-static jint openDecimalFormatImpl(JNIEnv *env, jclass clazz, jstring locale,
+static jint openDecimalFormatImpl(JNIEnv *env, jclass clazz, jstring locale,
jstring pattern) {
// the errorcode returned by unum_open
@@ -70,9 +70,9 @@
const char *localeChars = env->GetStringUTFChars(locale, NULL);
// open a default type number format
- UNumberFormat *fmt = unum_open(UNUM_PATTERN_DECIMAL, pattChars, pattLen,
+ UNumberFormat *fmt = unum_open(UNUM_PATTERN_DECIMAL, pattChars, pattLen,
localeChars, NULL, &status);
-
+
// release the allocated strings
env->ReleaseStringChars(pattern, pattChars);
env->ReleaseStringUTFChars(locale, localeChars);
@@ -88,20 +88,20 @@
static void closeDecimalFormatImpl(JNIEnv *env, jclass clazz, jint addr) {
- // get the pointer to the number format
+ // get the pointer to the number format
UNumberFormat *fmt = (UNumberFormat *)(int)addr;
// close this number format
unum_close(fmt);
}
-static void setSymbol(JNIEnv *env, jclass clazz, jint addr, jint symbol,
+static void setSymbol(JNIEnv *env, jclass clazz, jint addr, jint symbol,
jstring text) {
-
+
// the errorcode returned by unum_setSymbol
UErrorCode status = U_ZERO_ERROR;
- // get the pointer to the number format
+ // get the pointer to the number format
UNumberFormat *fmt = (UNumberFormat *)(int)addr;
// prepare the symbol string for the call to unum_setSymbol
@@ -109,9 +109,9 @@
int textLen = env->GetStringLength(text);
// set the symbol
- unum_setSymbol(fmt, (UNumberFormatSymbol) symbol, textChars, textLen,
+ unum_setSymbol(fmt, (UNumberFormatSymbol) symbol, textChars, textLen,
&status);
-
+
// release previously allocated space
env->ReleaseStringChars(text, textChars);
@@ -126,14 +126,14 @@
// the errorcode returned by unum_getSymbol
UErrorCode status = U_ZERO_ERROR;
- // get the pointer to the number format
+ // get the pointer to the number format
UNumberFormat *fmt = (UNumberFormat *)(int)addr;
UChar* result = NULL;
resultlength=0;
// find out how long the result will be
- reslenneeded=unum_getSymbol(fmt, (UNumberFormatSymbol) symbol, result,
+ reslenneeded=unum_getSymbol(fmt, (UNumberFormatSymbol) symbol, result,
resultlength, &status);
result = NULL;
@@ -141,7 +141,7 @@
status=U_ZERO_ERROR;
resultlength=reslenneeded+1;
result=(UChar*)malloc(sizeof(UChar) * resultlength);
- reslenneeded=unum_getSymbol(fmt, (UNumberFormatSymbol) symbol, result,
+ reslenneeded=unum_getSymbol(fmt, (UNumberFormatSymbol) symbol, result,
resultlength, &status);
}
if (icuError(env, status) != FALSE) {
@@ -154,17 +154,17 @@
return res;
}
-
-static void setAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol,
+
+static void setAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol,
jint value) {
-
+
UNumberFormat *fmt = (UNumberFormat *)(int)addr;
unum_setAttribute(fmt, (UNumberFormatAttribute) symbol, value);
}
-
+
static jint getAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol) {
-
+
UNumberFormat *fmt = (UNumberFormat *)(int)addr;
int res = unum_getAttribute(fmt, (UNumberFormatAttribute) symbol);
@@ -172,27 +172,27 @@
return res;
}
-static void setTextAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol,
+static void setTextAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol,
jstring text) {
// the errorcode returned by unum_setTextAttribute
UErrorCode status = U_ZERO_ERROR;
- // get the pointer to the number format
+ // get the pointer to the number format
UNumberFormat *fmt = (UNumberFormat *)(int)addr;
const UChar *textChars = env->GetStringChars(text, NULL);
int textLen = env->GetStringLength(text);
- unum_setTextAttribute(fmt, (UNumberFormatTextAttribute) symbol, textChars,
+ unum_setTextAttribute(fmt, (UNumberFormatTextAttribute) symbol, textChars,
textLen, &status);
-
+
env->ReleaseStringChars(text, textChars);
icuError(env, status);
}
-static jstring getTextAttribute(JNIEnv *env, jclass clazz, jint addr,
+static jstring getTextAttribute(JNIEnv *env, jclass clazz, jint addr,
jint symbol) {
uint32_t resultlength, reslenneeded;
@@ -200,14 +200,14 @@
// the errorcode returned by unum_getTextAttribute
UErrorCode status = U_ZERO_ERROR;
- // get the pointer to the number format
+ // get the pointer to the number format
UNumberFormat *fmt = (UNumberFormat *)(int)addr;
UChar* result = NULL;
resultlength=0;
// find out how long the result will be
- reslenneeded=unum_getTextAttribute(fmt, (UNumberFormatTextAttribute) symbol,
+ reslenneeded=unum_getTextAttribute(fmt, (UNumberFormatTextAttribute) symbol,
result, resultlength, &status);
result = NULL;
@@ -215,8 +215,8 @@
status=U_ZERO_ERROR;
resultlength=reslenneeded+1;
result=(UChar*)malloc(sizeof(UChar) * resultlength);
- reslenneeded=unum_getTextAttribute(fmt,
- (UNumberFormatTextAttribute) symbol, result, resultlength,
+ reslenneeded=unum_getTextAttribute(fmt,
+ (UNumberFormatTextAttribute) symbol, result, resultlength,
&status);
}
if (icuError(env, status) != FALSE) {
@@ -230,13 +230,13 @@
return res;
}
-static void applyPatternImpl(JNIEnv *env, jclass clazz, jint addr,
+static void applyPatternImpl(JNIEnv *env, jclass clazz, jint addr,
jboolean localized, jstring pattern) {
// the errorcode returned by unum_applyPattern
UErrorCode status = U_ZERO_ERROR;
- // get the pointer to the number format
+ // get the pointer to the number format
UNumberFormat *fmt = (UNumberFormat *)(int)addr;
const UChar *pattChars = env->GetStringChars(pattern, NULL);
@@ -249,7 +249,7 @@
icuError(env, status);
}
-static jstring toPatternImpl(JNIEnv *env, jclass clazz, jint addr,
+static jstring toPatternImpl(JNIEnv *env, jclass clazz, jint addr,
jboolean localized) {
uint32_t resultlength, reslenneeded;
@@ -257,7 +257,7 @@
// the errorcode returned by unum_toPattern
UErrorCode status = U_ZERO_ERROR;
- // get the pointer to the number format
+ // get the pointer to the number format
UNumberFormat *fmt = (UNumberFormat *)(int)addr;
UChar* result = NULL;
@@ -271,7 +271,7 @@
status=U_ZERO_ERROR;
resultlength=reslenneeded+1;
result=(UChar*)malloc(sizeof(UChar) * resultlength);
- reslenneeded=unum_toPattern(fmt, localized, result, resultlength,
+ reslenneeded=unum_toPattern(fmt, localized, result, resultlength,
&status);
}
if (icuError(env, status) != FALSE) {
@@ -284,19 +284,19 @@
return res;
}
-
-static jstring formatLong(JNIEnv *env, jclass clazz, jint addr, jlong value,
+
+static jstring formatLong(JNIEnv *env, jclass clazz, jint addr, jlong value,
jobject field, jstring fieldType, jobject attributes) {
const char * fieldPositionClassName = "java/text/FieldPosition";
const char * stringBufferClassName = "java/lang/StringBuffer";
jclass fieldPositionClass = env->FindClass(fieldPositionClassName);
jclass stringBufferClass = env->FindClass(stringBufferClassName);
- jmethodID setBeginIndexMethodID = env->GetMethodID(fieldPositionClass,
+ jmethodID setBeginIndexMethodID = env->GetMethodID(fieldPositionClass,
"setBeginIndex", "(I)V");
- jmethodID setEndIndexMethodID = env->GetMethodID(fieldPositionClass,
+ jmethodID setEndIndexMethodID = env->GetMethodID(fieldPositionClass,
"setEndIndex", "(I)V");
- jmethodID appendMethodID = env->GetMethodID(stringBufferClass,
+ jmethodID appendMethodID = env->GetMethodID(stringBufferClass,
"append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
const char * fieldName = NULL;
@@ -331,7 +331,7 @@
if(status==U_BUFFER_OVERFLOW_ERROR) {
status=U_ZERO_ERROR;
- result = (UChar*)malloc(sizeof(UChar) * (reslenneeded + 1));
+ result = (UChar*)malloc(sizeof(UChar) * (reslenneeded + 1));
res->extract(result, reslenneeded + 1, status);
}
@@ -393,18 +393,18 @@
return resulting;
}
-static jstring formatDouble(JNIEnv *env, jclass clazz, jint addr, jdouble value,
+static jstring formatDouble(JNIEnv *env, jclass clazz, jint addr, jdouble value,
jobject field, jstring fieldType, jobject attributes) {
const char * fieldPositionClassName = "java/text/FieldPosition";
const char * stringBufferClassName = "java/lang/StringBuffer";
jclass fieldPositionClass = env->FindClass(fieldPositionClassName);
jclass stringBufferClass = env->FindClass(stringBufferClassName);
- jmethodID setBeginIndexMethodID = env->GetMethodID(fieldPositionClass,
+ jmethodID setBeginIndexMethodID = env->GetMethodID(fieldPositionClass,
"setBeginIndex", "(I)V");
- jmethodID setEndIndexMethodID = env->GetMethodID(fieldPositionClass,
+ jmethodID setEndIndexMethodID = env->GetMethodID(fieldPositionClass,
"setEndIndex", "(I)V");
- jmethodID appendMethodID = env->GetMethodID(stringBufferClass,
+ jmethodID appendMethodID = env->GetMethodID(stringBufferClass,
"append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
const char * fieldName = NULL;
@@ -439,7 +439,7 @@
if(status==U_BUFFER_OVERFLOW_ERROR) {
status=U_ZERO_ERROR;
- result = (UChar*)malloc(sizeof(UChar) * (reslenneeded + 1));
+ result = (UChar*)malloc(sizeof(UChar) * (reslenneeded + 1));
res->extract(result, reslenneeded + 1, status);
@@ -502,7 +502,7 @@
return resulting;
}
-static jstring formatDigitList(JNIEnv *env, jclass clazz, jint addr, jstring value,
+static jstring formatDigitList(JNIEnv *env, jclass clazz, jint addr, jstring value,
jobject field, jstring fieldType, jobject attributes, jint scale) {
// const char * valueUTF = env->GetStringUTFChars(value, NULL);
@@ -521,21 +521,36 @@
uint32_t reslenneeded;
- bool isInteger = (scale == 0);
-
// prepare digit list
- const char *digits = env->GetStringUTFChars(value, NULL);
+ const char *valueChars = env->GetStringUTFChars(value, NULL);
- // length must be string lengt + 2 because there's an additional
- // character in front of the string ("+" or "-") and a \0 at the end
- DigitList digitList(strlen(digits) + 2);
- digitList.fCount = strlen(digits);
+ bool isInteger = (scale == 0);
+ bool isPositive = (*valueChars != '-');
+
+ // skip the '-' if the number is negative
+ const char *digits = (isPositive ? valueChars : valueChars + 1);
+ int length = strlen(digits);
+
+ // The length of our digit list buffer must be the actual string length + 3,
+ // because ICU will append some additional characters at the head and at the
+ // tail of the string, in order to keep strtod() happy:
+ //
+ // - The sign "+" or "-" is appended at the head
+ // - The exponent "e" and the "\0" terminator is appended at the tail
+ //
+ // In retrospect, the changes to ICU's DigitList that were necessary for
+ // big numbers look a bit hacky. It would make sense to rework all this
+ // once ICU 4.x has been integrated into Android. Ideally, big number
+ // support would make it into ICU itself, so we don't need our private
+ // fix anymore.
+ DigitList digitList(length + 3);
+ digitList.fCount = length;
strcpy(digitList.fDigits, digits);
- env->ReleaseStringUTFChars(value, digits);
+ env->ReleaseStringUTFChars(value, valueChars);
digitList.fDecimalAt = digitList.fCount - scale;
- digitList.fIsPositive = (*digits != '-');
+ digitList.fIsPositive = isPositive;
digitList.fRoundingMode = DecimalFormat::kRoundHalfUp;
UChar *result = NULL;
@@ -548,10 +563,9 @@
UErrorCode status = U_ZERO_ERROR;
DecimalFormat::AttributeBuffer *attrBuffer = NULL;
- attrBuffer = (DecimalFormat::AttributeBuffer *) malloc(sizeof(DecimalFormat::AttributeBuffer));
+ attrBuffer = (DecimalFormat::AttributeBuffer *) calloc(sizeof(DecimalFormat::AttributeBuffer), 1);
attrBuffer->bufferSize = 128;
- attrBuffer->buffer = (char *) malloc(129 * sizeof(char));
- attrBuffer->buffer[0] = '\0';
+ attrBuffer->buffer = (char *) calloc(129 * sizeof(char), 1);
DecimalFormat *fmt = (DecimalFormat *)(int)addr;
@@ -564,7 +578,7 @@
if(status==U_BUFFER_OVERFLOW_ERROR) {
status=U_ZERO_ERROR;
- result = (UChar*)malloc(sizeof(UChar) * (reslenneeded + 1));
+ result = (UChar*)malloc(sizeof(UChar) * (reslenneeded + 1));
res.extract(result, reslenneeded + 1, status);
@@ -584,7 +598,7 @@
}
free(attrBuffer->buffer);
free(attrBuffer);
- return NULL;
+ return NULL;
}
int attrLength = (strlen(attrBuffer->buffer) + 1 );
@@ -596,7 +610,7 @@
// prepare the classes and method ids
const char * stringBufferClassName = "java/lang/StringBuffer";
jclass stringBufferClass = env->FindClass(stringBufferClassName);
- jmethodID appendMethodID = env->GetMethodID(stringBufferClass,
+ jmethodID appendMethodID = env->GetMethodID(stringBufferClass,
"append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
jstring attrString = env->NewStringUTF(attrBuffer->buffer + 1); // cut off the leading ';'
@@ -655,7 +669,7 @@
return resulting;
}
-static jobject parse(JNIEnv *env, jclass clazz, jint addr, jstring text,
+static jobject parse(JNIEnv *env, jclass clazz, jint addr, jstring text,
jobject position) {
const char * textUTF = env->GetStringUTFChars(text, NULL);
@@ -680,11 +694,11 @@
jclass bigDecimalClass = env->FindClass(bigDecimalClassName);
jclass bigIntegerClass = env->FindClass(bigIntegerClassName);
- jmethodID getIndexMethodID = env->GetMethodID(parsePositionClass,
+ jmethodID getIndexMethodID = env->GetMethodID(parsePositionClass,
"getIndex", "()I");
- jmethodID setIndexMethodID = env->GetMethodID(parsePositionClass,
+ jmethodID setIndexMethodID = env->GetMethodID(parsePositionClass,
"setIndex", "(I)V");
- jmethodID setErrorIndexMethodID = env->GetMethodID(parsePositionClass,
+ jmethodID setErrorIndexMethodID = env->GetMethodID(parsePositionClass,
"setErrorIndex", "(I)V");
jmethodID longInitMethodID = env->GetMethodID(longClass, "<init>", "(J)V");
@@ -696,8 +710,8 @@
bool resultAssigned;
int parsePos = env->CallIntMethod(position, getIndexMethodID, NULL);
- // make sure the ParsePosition is valid. Actually icu4c would parse a number
- // correctly even if the parsePosition is set to -1, but since the RI fails
+ // make sure the ParsePosition is valid. Actually icu4c would parse a number
+ // correctly even if the parsePosition is set to -1, but since the RI fails
// for that case we have to fail too
if(parsePos < 0 || parsePos > strlength) {
return NULL;
@@ -707,9 +721,9 @@
const UnicodeString src((UChar*)str, strlength, strlength);
ParsePosition pp;
-
+
pp.setIndex(parsePos);
-
+
DigitList digits;
((const DecimalFormat*)fmt)->parse(src, resultAssigned, res, pp, FALSE, digits);
@@ -719,8 +733,8 @@
if(pp.getErrorIndex() == -1) {
parsePos = pp.getIndex();
} else {
- env->CallVoidMethod(position, setErrorIndexMethodID,
- (jint) pp.getErrorIndex());
+ env->CallVoidMethod(position, setErrorIndexMethodID,
+ (jint) pp.getErrorIndex());
return NULL;
}
@@ -745,17 +759,17 @@
case Formattable::kDouble:
resultDouble = res.getDouble();
env->CallVoidMethod(position, setIndexMethodID, (jint) parsePos);
- return env->NewObject(doubleClass, dblInitMethodID,
+ return env->NewObject(doubleClass, dblInitMethodID,
(jdouble) resultDouble);
case Formattable::kLong:
resultLong = res.getLong();
env->CallVoidMethod(position, setIndexMethodID, (jint) parsePos);
- return env->NewObject(longClass, longInitMethodID,
+ return env->NewObject(longClass, longInitMethodID,
(jlong) resultLong);
case Formattable::kInt64:
resultInt64 = res.getInt64();
env->CallVoidMethod(position, setIndexMethodID, (jint) parsePos);
- return env->NewObject(longClass, longInitMethodID,
+ return env->NewObject(longClass, longInitMethodID,
(jlong) resultInt64);
default:
return NULL;
@@ -805,7 +819,7 @@
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
- {"openDecimalFormatImpl", "(Ljava/lang/String;Ljava/lang/String;)I",
+ {"openDecimalFormatImpl", "(Ljava/lang/String;Ljava/lang/String;)I",
(void*) openDecimalFormatImpl},
{"closeDecimalFormatImpl", "(I)V", (void*) closeDecimalFormatImpl},
{"setSymbol", "(IILjava/lang/String;)V", (void*) setSymbol},
@@ -816,22 +830,22 @@
{"getTextAttribute", "(II)Ljava/lang/String;", (void*) getTextAttribute},
{"applyPatternImpl", "(IZLjava/lang/String;)V", (void*) applyPatternImpl},
{"toPatternImpl", "(IZ)Ljava/lang/String;", (void*) toPatternImpl},
- {"format",
- "(IJLjava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;)Ljava/lang/String;",
+ {"format",
+ "(IJLjava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;)Ljava/lang/String;",
(void*) formatLong},
- {"format",
- "(IDLjava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;)Ljava/lang/String;",
+ {"format",
+ "(IDLjava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;)Ljava/lang/String;",
(void*) formatDouble},
- {"format",
- "(ILjava/lang/String;Ljava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;I)Ljava/lang/String;",
+ {"format",
+ "(ILjava/lang/String;Ljava/text/FieldPosition;Ljava/lang/String;Ljava/lang/StringBuffer;I)Ljava/lang/String;",
(void*) formatDigitList},
- {"parse",
- "(ILjava/lang/String;Ljava/text/ParsePosition;)Ljava/lang/Number;",
+ {"parse",
+ "(ILjava/lang/String;Ljava/text/ParsePosition;)Ljava/lang/Number;",
(void*) parse},
{"cloneImpl", "(I)I", (void*) cloneImpl}
};
int register_com_ibm_icu4jni_text_NativeDecimalFormat(JNIEnv* env) {
- return jniRegisterNativeMethods(env,
- "com/ibm/icu4jni/text/NativeDecimalFormat", gMethods,
+ return jniRegisterNativeMethods(env,
+ "com/ibm/icu4jni/text/NativeDecimalFormat", gMethods,
NELEM(gMethods));
}
diff --git a/luni/src/main/java/java/io/BufferedInputStream.java b/luni/src/main/java/java/io/BufferedInputStream.java
index 1720366..0b9afc3 100644
--- a/luni/src/main/java/java/io/BufferedInputStream.java
+++ b/luni/src/main/java/java/io/BufferedInputStream.java
@@ -108,7 +108,7 @@
Logger.global.info(
"Default buffer size used in BufferedInputStream " +
"constructor. It would be " +
- "better to be explicit if a 8k buffer is required.");
+ "better to be explicit if an 8k buffer is required.");
// END android-added
}
diff --git a/luni/src/main/java/java/io/BufferedOutputStream.java b/luni/src/main/java/java/io/BufferedOutputStream.java
index 55419d1..835d13f 100644
--- a/luni/src/main/java/java/io/BufferedOutputStream.java
+++ b/luni/src/main/java/java/io/BufferedOutputStream.java
@@ -80,7 +80,7 @@
Logger.global.info(
"Default buffer size used in BufferedOutputStream " +
"constructor. It would be " +
- "better to be explicit if a 8k buffer is required.");
+ "better to be explicit if an 8k buffer is required.");
// END android-added
}
diff --git a/luni/src/main/java/java/io/BufferedReader.java b/luni/src/main/java/java/io/BufferedReader.java
index c490b89..e82e538 100644
--- a/luni/src/main/java/java/io/BufferedReader.java
+++ b/luni/src/main/java/java/io/BufferedReader.java
@@ -80,7 +80,7 @@
Logger.global.info(
"Default buffer size used in BufferedReader " +
"constructor. It would be " +
- "better to be explicit if a 8k-char buffer is required.");
+ "better to be explicit if an 8k-char buffer is required.");
// END android-added
}
diff --git a/luni/src/main/java/java/io/BufferedWriter.java b/luni/src/main/java/java/io/BufferedWriter.java
index 66bfcc9..761b9c5 100644
--- a/luni/src/main/java/java/io/BufferedWriter.java
+++ b/luni/src/main/java/java/io/BufferedWriter.java
@@ -80,7 +80,7 @@
Logger.global.info(
"Default buffer size used in BufferedWriter " +
"constructor. It would be " +
- "better to be explicit if a 8k-char buffer is required.");
+ "better to be explicit if an 8k-char buffer is required.");
// END android-added
}
diff --git a/luni/src/main/java/java/util/ArrayList.java b/luni/src/main/java/java/util/ArrayList.java
index 5ea316a..3bae372 100644
--- a/luni/src/main/java/java/util/ArrayList.java
+++ b/luni/src/main/java/java/util/ArrayList.java
@@ -112,7 +112,7 @@
* @param object
* the object to add.
* @throws IndexOutOfBoundsException
- * when {@code location < 0 || >= size()}
+ * when {@code location < 0 || > size()}
* @since Android 1.0
*/
@Override
diff --git a/luni/src/test/java/org/apache/harmony/luni/tests/java/lang/StringTest.java b/luni/src/test/java/org/apache/harmony/luni/tests/java/lang/StringTest.java
index 6c995bb..30510c2 100644
--- a/luni/src/test/java/org/apache/harmony/luni/tests/java/lang/StringTest.java
+++ b/luni/src/test/java/org/apache/harmony/luni/tests/java/lang/StringTest.java
@@ -17,15 +17,14 @@
package org.apache.harmony.luni.tests.java.lang;
-import dalvik.annotation.TestTargets;
import dalvik.annotation.TestLevel;
-import dalvik.annotation.TestTargetNew;
import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
import junit.framework.TestCase;
import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Constructor;
+import java.math.BigDecimal;
@TestTargetClass(String.class)
public class StringTest extends TestCase {
@@ -701,4 +700,40 @@
} catch (IndexOutOfBoundsException e) {
}
}
+
+ @TestTargetNew(
+ level = TestLevel.ADDITIONAL,
+ notes = "Regression test for some existing bugs and crashes",
+ method = "format",
+ args = { String.class, Object.class }
+ )
+ public void testProblemCases() {
+ BigDecimal[] input = new BigDecimal[] {
+ new BigDecimal("20.00000"),
+ new BigDecimal("20.000000"),
+ new BigDecimal(".2"),
+ new BigDecimal("2"),
+ new BigDecimal("-2"),
+ new BigDecimal("200000000000000000000000"),
+ new BigDecimal("20000000000000000000000000000000000000000000000000")
+ };
+
+ String[] output = new String[] {
+ "20.00",
+ "20.00",
+ "0.20",
+ "2.00",
+ "-2.00",
+ "200000000000000000000000.00",
+ "20000000000000000000000000000000000000000000000000.00"
+ };
+
+ for (int i = 0; i < input.length; i++) {
+ String result = String.format("%.2f", input[i]);
+ assertEquals("Format test for \"" + input[i] + "\" failed, " +
+ "expected=" + output[i] + ", " +
+ "actual=" + result, output[i], result);
+ }
+ }
+
}
diff --git a/run-core-tests b/run-core-tests
index 3656e2d..25e53ee 100755
--- a/run-core-tests
+++ b/run-core-tests
@@ -26,6 +26,5 @@
chmod 777 $tmp
exec dalvikvm -Duser.language=en -Duser.region=US -Djava.io.tmpdir=$tmp \
- -Xbootclasspath:$BOOTCLASSPATH \
- -classpath /system/framework/core-tests.jar \
+ -Xbootclasspath:$BOOTCLASSPATH:/system/framework/core-tests.jar \
com.google.coretests.Main "$@"
diff --git a/security/src/main/java/org/bouncycastle/asn1/x509/X509NameTokenizer.java b/security/src/main/java/org/bouncycastle/asn1/x509/X509NameTokenizer.java
index 035e924..8f0d08b 100644
--- a/security/src/main/java/org/bouncycastle/asn1/x509/X509NameTokenizer.java
+++ b/security/src/main/java/org/bouncycastle/asn1/x509/X509NameTokenizer.java
@@ -66,6 +66,17 @@
{
if (escaped || quoted)
{
+ // BEGIN android-added
+ // copied from a newer version of BouncyCastle
+ if (c == '#' && buf.charAt(buf.length() - 1) == '=')
+ {
+ buf.append('\\');
+ }
+ else if (c == '+' && seperator != '+')
+ {
+ buf.append('\\');
+ }
+ // END android-added
buf.append(c);
escaped = false;
}
@@ -88,4 +99,4 @@
index = end;
return buf.toString().trim();
}
-}
+}
\ No newline at end of file
diff --git a/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java b/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java
index 6469c93..6adca42 100644
--- a/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java
+++ b/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java
@@ -15,22 +15,352 @@
* limitations under the License.
*/
+// BEGIN android-added
+// Copied and condensed code taken from the Apache HttpClient. Also slightly
+// modified, so it matches the package/class structure of the core libraries.
+// This HostnameVerifier does checking similar to what the RI and popular
+// browsers do.
+// END android-added
+
package javax.net.ssl;
+import org.apache.harmony.luni.util.Inet6Util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
/**
- * Default implementation of javax.net.ssl.HostnameVerifier
+ * A HostnameVerifier that works the same way as Curl and Firefox.
+ * <p/>
+ * The hostname must match either the first CN, or any of the subject-alts.
+ * A wildcard can occur in the CN, and in any of the subject-alts.
+ * <p/>
+ * The only difference between BROWSER_COMPATIBLE and STRICT is that a wildcard
+ * (such as "*.foo.com") with BROWSER_COMPATIBLE matches all subdomains,
+ * including "a.b.foo.com".
*
- * @since Android 1.0
+ * @author Julius Davies
*/
class DefaultHostnameVerifier implements HostnameVerifier {
/**
- * DefaultHostnameVerifier assumes the connection should not be permitted.
- *
- * @param hostname
- * @param session
+ * This contains a list of 2nd-level domains that aren't allowed to
+ * have wildcards when combined with country-codes.
+ * For example: [*.co.uk].
+ * <p/>
+ * The [*.co.uk] problem is an interesting one. Should we just hope
+ * that CA's would never foolishly allow such a certificate to happen?
+ * Looks like we're the only implementation guarding against this.
+ * Firefox, Curl, Sun Java 1.4, 5, 6 don't bother with this check.
*/
- public boolean verify(String hostname, SSLSession session) {
- return false;
+ private final static String[] BAD_COUNTRY_2LDS =
+ { "ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info",
+ "lg", "ne", "net", "or", "org" };
+
+ static {
+ // Just in case developer forgot to manually sort the array. :-)
+ Arrays.sort(BAD_COUNTRY_2LDS);
}
+
+ public DefaultHostnameVerifier() {
+ super();
+ }
+
+ public final void verify(String host, SSLSocket ssl)
+ throws IOException {
+ if(host == null) {
+ throw new NullPointerException("host to verify is null");
+ }
+
+ ssl.startHandshake();
+ SSLSession session = ssl.getSession();
+ if(session == null) {
+ // In our experience this only happens under IBM 1.4.x when
+ // spurious (unrelated) certificates show up in the server'
+ // chain. Hopefully this will unearth the real problem:
+ InputStream in = ssl.getInputStream();
+ in.available();
+ /*
+ If you're looking at the 2 lines of code above because
+ you're running into a problem, you probably have two
+ options:
+
+ #1. Clean up the certificate chain that your server
+ is presenting (e.g. edit "/etc/apache2/server.crt"
+ or wherever it is your server's certificate chain
+ is defined).
+
+ OR
+
+ #2. Upgrade to an IBM 1.5.x or greater JVM, or switch
+ to a non-IBM JVM.
+ */
+
+ // If ssl.getInputStream().available() didn't cause an
+ // exception, maybe at least now the session is available?
+ session = ssl.getSession();
+ if(session == null) {
+ // If it's still null, probably a startHandshake() will
+ // unearth the real problem.
+ ssl.startHandshake();
+
+ // Okay, if we still haven't managed to cause an exception,
+ // might as well go for the NPE. Or maybe we're okay now?
+ session = ssl.getSession();
+ }
+ }
+
+ Certificate[] certs = session.getPeerCertificates();
+ X509Certificate x509 = (X509Certificate) certs[0];
+ verify(host, x509);
+ }
+
+ public final boolean verify(String host, SSLSession session) {
+ try {
+ Certificate[] certs = session.getPeerCertificates();
+ X509Certificate x509 = (X509Certificate) certs[0];
+ verify(host, x509);
+ return true;
+ }
+ catch(SSLException e) {
+ return false;
+ }
+ }
+
+ public final void verify(String host, X509Certificate cert)
+ throws SSLException {
+ String[] cns = getCNs(cert);
+ String[] subjectAlts = getDNSSubjectAlts(cert);
+ verify(host, cns, subjectAlts);
+ }
+
+ public final void verify(final String host, final String[] cns,
+ final String[] subjectAlts,
+ final boolean strictWithSubDomains)
+ throws SSLException {
+
+ // Build the list of names we're going to check. Our DEFAULT and
+ // STRICT implementations of the HostnameVerifier only use the
+ // first CN provided. All other CNs are ignored.
+ // (Firefox, wget, curl, Sun Java 1.4, 5, 6 all work this way).
+ LinkedList<String> names = new LinkedList<String>();
+ if(cns != null && cns.length > 0 && cns[0] != null) {
+ names.add(cns[0]);
+ }
+ if(subjectAlts != null) {
+ for (String subjectAlt : subjectAlts) {
+ if (subjectAlt != null) {
+ names.add(subjectAlt);
+ }
+ }
+ }
+
+ if(names.isEmpty()) {
+ String msg = "Certificate for <" + host +
+ "> doesn't contain CN or DNS subjectAlt";
+ throw new SSLException(msg);
+ }
+
+ // StringBuffer for building the error message.
+ StringBuffer buf = new StringBuffer();
+
+ // We're can be case-insensitive when comparing the host we used to
+ // establish the socket to the hostname in the certificate.
+ String hostName = host.trim().toLowerCase(Locale.ENGLISH);
+ boolean match = false;
+ for(Iterator<String> it = names.iterator(); it.hasNext();) {
+ // Don't trim the CN, though!
+ String cn = it.next();
+ cn = cn.toLowerCase(Locale.ENGLISH);
+ // Store CN in StringBuffer in case we need to report an error.
+ buf.append(" <");
+ buf.append(cn);
+ buf.append('>');
+ if(it.hasNext()) {
+ buf.append(" OR");
+ }
+
+ // The CN better have at least two dots if it wants wildcard
+ // action. It also can't be [*.co.uk] or [*.co.jp] or
+ // [*.org.uk], etc...
+ boolean doWildcard = cn.startsWith("*.") &&
+ cn.lastIndexOf('.') >= 0 &&
+ acceptableCountryWildcard(cn) &&
+ !Inet6Util.isValidIPV4Address(host);
+
+ if(doWildcard) {
+ match = hostName.endsWith(cn.substring(1));
+ if(match && strictWithSubDomains) {
+ // If we're in strict mode, then [*.foo.com] is not
+ // allowed to match [a.b.foo.com]
+ match = countDots(hostName) == countDots(cn);
+ }
+ } else {
+ match = hostName.equals(cn);
+ }
+ if(match) {
+ break;
+ }
+ }
+ if(!match) {
+ throw new SSLException("hostname in certificate didn't match: <" +
+ host + "> !=" + buf);
+ }
+ }
+
+ public static boolean acceptableCountryWildcard(String cn) {
+ int cnLen = cn.length();
+ if(cnLen >= 7 && cnLen <= 9) {
+ // Look for the '.' in the 3rd-last position:
+ if(cn.charAt(cnLen - 3) == '.') {
+ // Trim off the [*.] and the [.XX].
+ String s = cn.substring(2, cnLen - 3);
+ // And test against the sorted array of bad 2lds:
+ int x = Arrays.binarySearch(BAD_COUNTRY_2LDS, s);
+ return x < 0;
+ }
+ }
+ return true;
+ }
+
+ public static String[] getCNs(X509Certificate cert) {
+ LinkedList<String> cnList = new LinkedList<String>();
+ /*
+ Sebastian Hauer's original StrictSSLProtocolSocketFactory used
+ getName() and had the following comment:
+
+ Parses a X.500 distinguished name for the value of the
+ "Common Name" field. This is done a bit sloppy right
+ now and should probably be done a bit more according to
+ <code>RFC 2253</code>.
+
+ I've noticed that toString() seems to do a better job than
+ getName() on these X500Principal objects, so I'm hoping that
+ addresses Sebastian's concern.
+
+ For example, getName() gives me this:
+ 1.2.840.113549.1.9.1=#16166a756c6975736461766965734063756362632e636f6d
+
+ whereas toString() gives me this:
+ EMAILADDRESS=juliusdavies@cucbc.com
+
+ Looks like toString() even works with non-ascii domain names!
+ I tested it with "花子.co.jp" and it worked fine.
+ */
+ String subjectPrincipal = cert.getSubjectX500Principal().toString();
+ StringTokenizer st = new StringTokenizer(subjectPrincipal, ",");
+ while(st.hasMoreTokens()) {
+ String tok = st.nextToken();
+ int x = tok.indexOf("CN=");
+ if(x >= 0) {
+ cnList.add(tok.substring(x + 3));
+ }
+ }
+ if(!cnList.isEmpty()) {
+ String[] cns = new String[cnList.size()];
+ cnList.toArray(cns);
+ return cns;
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Extracts the array of SubjectAlt DNS names from an X509Certificate.
+ * Returns null if there aren't any.
+ * <p/>
+ * Note: Java doesn't appear able to extract international characters
+ * from the SubjectAlts. It can only extract international characters
+ * from the CN field.
+ * <p/>
+ * (Or maybe the version of OpenSSL I'm using to test isn't storing the
+ * international characters correctly in the SubjectAlts?).
+ *
+ * @param cert X509Certificate
+ * @return Array of SubjectALT DNS names stored in the certificate.
+ */
+ public static String[] getDNSSubjectAlts(X509Certificate cert) {
+ LinkedList<String> subjectAltList = new LinkedList<String>();
+ Collection<List<?>> c = null;
+ try {
+ c = cert.getSubjectAlternativeNames();
+ }
+ catch(CertificateParsingException cpe) {
+ Logger.getLogger(DefaultHostnameVerifier.class.getName())
+ .log(Level.FINE, "Error parsing certificate.", cpe);
+ }
+ if(c != null) {
+ for (List<?> aC : c) {
+ List<?> list = aC;
+ int type = ((Integer) list.get(0)).intValue();
+ // If type is 2, then we've got a dNSName
+ if (type == 2) {
+ String s = (String) list.get(1);
+ subjectAltList.add(s);
+ }
+ }
+ }
+ if(!subjectAltList.isEmpty()) {
+ String[] subjectAlts = new String[subjectAltList.size()];
+ subjectAltList.toArray(subjectAlts);
+ return subjectAlts;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Counts the number of dots "." in a string.
+ * @param s string to count dots from
+ * @return number of dots
+ */
+ public static int countDots(final String s) {
+ int count = 0;
+ for(int i = 0; i < s.length(); i++) {
+ if(s.charAt(i) == '.') {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Checks to see if the supplied hostname matches any of the supplied CNs
+ * or "DNS" Subject-Alts. Most implementations only look at the first CN,
+ * and ignore any additional CNs. Most implementations do look at all of
+ * the "DNS" Subject-Alts. The CNs or Subject-Alts may contain wildcards
+ * according to RFC 2818.
+ *
+ * @param cns CN fields, in order, as extracted from the X.509
+ * certificate.
+ * @param subjectAlts Subject-Alt fields of type 2 ("DNS"), as extracted
+ * from the X.509 certificate.
+ * @param host The hostname to verify.
+ * @throws SSLException If verification failed.
+ */
+ public final void verify(
+ final String host,
+ final String[] cns,
+ final String[] subjectAlts) throws SSLException {
+ verify(host, cns, subjectAlts, false);
+ }
+
}
diff --git a/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java b/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java
index cb821c2..2b7c03e 100644
--- a/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java
+++ b/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java
@@ -24,6 +24,7 @@
// BEGIN android-added
import java.lang.reflect.Method;
import java.net.UnknownHostException;
+import java.util.logging.Logger;
// END android-added
import javax.net.SocketFactory;
@@ -57,60 +58,56 @@
* @since Android 1.0
*/
public static SocketFactory getDefault() {
- if (defaultSocketFactory != null) {
+ synchronized (SSLSocketFactory.class) {
+ if (defaultSocketFactory != null) {
+ // BEGIN android-added
+ log("SSLSocketFactory", "Using factory " + defaultSocketFactory);
+ // END android-added
+ return defaultSocketFactory;
+ }
+ if (defaultName == null) {
+ AccessController.doPrivileged(new java.security.PrivilegedAction(){
+ public Object run() {
+ defaultName = Security.getProperty("ssl.SocketFactory.provider");
+ if (defaultName != null) {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ if (cl == null) {
+ cl = ClassLoader.getSystemClassLoader();
+ }
+ try {
+ defaultSocketFactory = (SocketFactory) Class.forName(
+ defaultName, true, cl).newInstance();
+ } catch (Exception e) {
+ return e;
+ }
+ }
+ return null;
+ }
+ });
+ }
+
+ if (defaultSocketFactory == null) {
+ // Try to find in providers
+ SSLContext context = DefaultSSLContext.getContext();
+ if (context != null) {
+ defaultSocketFactory = context.getSocketFactory();
+ }
+ }
+ if (defaultSocketFactory == null) {
+ // Use internal implementation
+ defaultSocketFactory = new DefaultSSLSocketFactory("No SSLSocketFactory installed");
+ }
// BEGIN android-added
log("SSLSocketFactory", "Using factory " + defaultSocketFactory);
// END android-added
return defaultSocketFactory;
}
- if (defaultName == null) {
- AccessController.doPrivileged(new java.security.PrivilegedAction(){
- public Object run() {
- defaultName = Security.getProperty("ssl.SocketFactory.provider");
- if (defaultName != null) {
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
- if (cl == null) {
- cl = ClassLoader.getSystemClassLoader();
- }
- try {
- defaultSocketFactory = (SocketFactory) Class.forName(
- defaultName, true, cl).newInstance();
- } catch (Exception e) {
- return e;
- }
- }
- return null;
- }
- });
- }
-
- if (defaultSocketFactory == null) {
- // Try to find in providers
- SSLContext context = DefaultSSLContext.getContext();
- if (context != null) {
- defaultSocketFactory = context.getSocketFactory();
- }
- }
- if (defaultSocketFactory == null) {
- // Use internal implementation
- defaultSocketFactory = new DefaultSSLSocketFactory("No SSLSocketFactory installed");
- }
- // BEGIN android-added
- log("SSLSocketFactory", "Using factory " + defaultSocketFactory);
- // END android-added
- return defaultSocketFactory;
}
// BEGIN android-added
@SuppressWarnings("unchecked")
private static void log(String tag, String msg) {
- try {
- Class clazz = Class.forName("android.util.Log");
- Method method = clazz.getMethod("d", new Class[] { String.class, String.class });
- method.invoke(null, new Object[] { tag, msg });
- } catch (Exception ex) {
- // Silently ignore.
- }
+ Logger.getLogger(tag).info(msg);
}
// END android-added
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java
new file mode 100644
index 0000000..a95d38f
--- /dev/null
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2009 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.util.*;
+import java.util.logging.Level;
+import java.io.*;
+
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
+import javax.security.cert.X509Certificate;
+import javax.security.cert.CertificateEncodingException;
+import javax.security.cert.CertificateException;
+
+/**
+ * Supports SSL session caches.
+ */
+abstract class AbstractSessionContext implements SSLSessionContext {
+
+ volatile int maximumSize;
+ volatile int timeout;
+
+ final SSLParameters parameters;
+
+ /** Identifies OpenSSL sessions. */
+ static final int OPEN_SSL = 1;
+
+ /**
+ * Constructs a new session context.
+ *
+ * @param parameters
+ * @param maximumSize of cache
+ * @param timeout for cache entries
+ */
+ AbstractSessionContext(SSLParameters parameters, int maximumSize,
+ int timeout) {
+ this.parameters = parameters;
+ this.maximumSize = maximumSize;
+ this.timeout = timeout;
+ }
+
+ /**
+ * Returns the collection of sessions ordered by least-recently-used first.
+ */
+ abstract Iterator<SSLSession> sessionIterator();
+
+ public final Enumeration getIds() {
+ final Iterator<SSLSession> iterator = sessionIterator();
+ return new Enumeration<byte[]>() {
+ public boolean hasMoreElements() {
+ return iterator.hasNext();
+ }
+ public byte[] nextElement() {
+ return iterator.next().getId();
+ }
+ };
+ }
+
+ public final int getSessionCacheSize() {
+ return maximumSize;
+ }
+
+ public final int getSessionTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Makes sure cache size is < maximumSize.
+ */
+ abstract void trimToSize();
+
+ public final void setSessionCacheSize(int size)
+ throws IllegalArgumentException {
+ if (size < 0) {
+ throw new IllegalArgumentException("size < 0");
+ }
+
+ int oldMaximum = maximumSize;
+ maximumSize = size;
+
+ // Trim cache to size if necessary.
+ if (size < oldMaximum) {
+ trimToSize();
+ }
+ }
+
+ /**
+ * Converts the given session to bytes.
+ *
+ * @return session data as bytes or null if the session can't be converted
+ */
+ byte[] toBytes(SSLSession session) {
+ // TODO: Support SSLSessionImpl, too.
+ if (!(session instanceof OpenSSLSessionImpl)) {
+ return null;
+ }
+
+ OpenSSLSessionImpl sslSession = (OpenSSLSessionImpl) session;
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream daos = new DataOutputStream(baos);
+
+ daos.writeInt(OPEN_SSL); // session type ID
+
+ // Session data.
+ byte[] data = sslSession.getEncoded();
+ daos.writeInt(data.length);
+ daos.write(data);
+
+ // Certificates.
+ X509Certificate[] certs = session.getPeerCertificateChain();
+ daos.writeInt(certs.length);
+
+ // TODO: Call nativegetpeercertificates()
+ for (X509Certificate cert : certs) {
+ data = cert.getEncoded();
+ daos.writeInt(data.length);
+ daos.write(data);
+ }
+
+ return baos.toByteArray();
+ } catch (IOException e) {
+ log(e);
+ return null;
+ } catch (CertificateEncodingException e) {
+ log(e);
+ return null;
+ }
+ }
+
+ /**
+ * Creates a session from the given bytes.
+ *
+ * @return a session or null if the session can't be converted
+ */
+ SSLSession toSession(byte[] data, String host, int port) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(data);
+ DataInputStream dais = new DataInputStream(bais);
+ try {
+ int type = dais.readInt();
+ if (type != OPEN_SSL) {
+ log(new AssertionError("Unexpected type ID: " + type));
+ return null;
+ }
+
+ int length = dais.readInt();
+ byte[] sessionData = new byte[length];
+ dais.readFully(sessionData);
+
+ int count = dais.readInt();
+ X509Certificate[] certs = new X509Certificate[count];
+ for (int i = 0; i < count; i++) {
+ length = dais.readInt();
+ byte[] certData = new byte[length];
+ dais.readFully(certData);
+ certs[i] = X509Certificate.getInstance(certData);
+ }
+
+ return new OpenSSLSessionImpl(sessionData, parameters, host, port,
+ certs, this);
+ } catch (IOException e) {
+ log(e);
+ return null;
+ } catch (CertificateException e) {
+ log(e);
+ return null;
+ }
+ }
+
+ static void log(Throwable t) {
+ java.util.logging.Logger.global.log(Level.WARNING,
+ "Error converting session.", t);
+ }
+
+ /**
+ * Byte array wrapper. Implements equals() and hashCode().
+ */
+ static class ByteArray {
+
+ private final byte[] bytes;
+
+ ByteArray(byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(bytes);
+ }
+
+ @Override
+ @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
+ public boolean equals(Object o) {
+ ByteArray other = (ByteArray) o;
+ return Arrays.equals(bytes, other.bytes);
+ }
+ }
+}
\ No newline at end of file
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java
index 8419096..55a06f5 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java
@@ -613,22 +613,18 @@
host = engineOwner.getPeerHost();
port = engineOwner.getPeerPort();
}
- // END android-changed
if (host == null || port == -1) {
return null; // starts new session
}
- byte[] id;
- SSLSession ses;
- SSLSessionContext context = parameters.getClientSessionContext();
- for (Enumeration en = context.getIds(); en.hasMoreElements();) {
- id = (byte[])en.nextElement();
- ses = context.getSession(id);
- if (host.equals(ses.getPeerHost()) && port == ses.getPeerPort()) {
- return (SSLSessionImpl)((SSLSessionImpl)ses).clone(); // resume
- }
+ ClientSessionContext context = parameters.getClientSessionContext();
+ SSLSessionImpl session
+ = (SSLSessionImpl) context.getSession(host, port);
+ if (session != null) {
+ session = (SSLSessionImpl) session.clone();
}
- return null; // starts new session
+ return session;
+ // END android-changed
}
}
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java
new file mode 100644
index 0000000..2c8738f
--- /dev/null
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2009 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.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.net.ssl.SSLSession;
+
+/**
+ * Caches client sessions. Indexes by host and port. Users are typically
+ * looking to reuse any session for a given host and port. Users of the
+ * standard API are forced to iterate over the sessions semi-linearly as
+ * opposed to in constant time.
+ */
+public class ClientSessionContext extends AbstractSessionContext {
+
+ /*
+ * We don't care about timeouts in the client implementation. Trying
+ * to reuse an expired session and having to start a new one requires no
+ * more effort than starting a new one, so you might as well try to reuse
+ * one on the off chance it's still valid.
+ */
+
+ /** Sessions indexed by host and port in access order. */
+ final Map<HostAndPort, SSLSession> sessions
+ = new LinkedHashMap<HostAndPort, SSLSession>() {
+ @Override
+ protected boolean removeEldestEntry(
+ Map.Entry<HostAndPort, SSLSession> eldest) {
+ // Called while lock is held on sessions.
+ boolean remove = maximumSize > 0 && size() > maximumSize;
+ if (remove) {
+ removeById(eldest.getValue());
+ }
+ return remove;
+ }
+ };
+
+ /**
+ * Sessions indexed by ID. Initialized on demand. Protected from concurrent
+ * access by holding a lock on sessions.
+ */
+ Map<ByteArray, SSLSession> sessionsById;
+
+ final SSLClientSessionCache persistentCache;
+
+ public ClientSessionContext(SSLParameters parameters,
+ SSLClientSessionCache persistentCache) {
+ super(parameters, 10, 0);
+ this.persistentCache = persistentCache;
+ }
+
+ public final void setSessionTimeout(int seconds)
+ throws IllegalArgumentException {
+ if (seconds < 0) {
+ throw new IllegalArgumentException("seconds < 0");
+ }
+ timeout = seconds;
+ }
+
+ Iterator<SSLSession> sessionIterator() {
+ synchronized (sessions) {
+ SSLSession[] array = sessions.values().toArray(
+ new SSLSession[sessions.size()]);
+ return Arrays.asList(array).iterator();
+ }
+ }
+
+ void trimToSize() {
+ synchronized (sessions) {
+ int size = sessions.size();
+ if (size > maximumSize) {
+ int removals = size - maximumSize;
+ Iterator<SSLSession> i = sessions.values().iterator();
+ do {
+ removeById(i.next());
+ i.remove();
+ } while (--removals > 0);
+ }
+ }
+ }
+
+ void removeById(SSLSession session) {
+ if (sessionsById != null) {
+ sessionsById.remove(new ByteArray(session.getId()));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see #getSession(String, int) for an implementation-specific but more
+ * efficient approach
+ */
+ public SSLSession getSession(byte[] sessionId) {
+ /*
+ * This method is typically used in conjunction with getIds() to
+ * iterate over the sessions linearly, so it doesn't make sense for
+ * it to impact access order.
+ *
+ * It also doesn't load sessions from the persistent cache as doing
+ * so would likely force every session to load.
+ */
+
+ ByteArray id = new ByteArray(sessionId);
+ synchronized (sessions) {
+ indexById();
+ return sessionsById.get(id);
+ }
+ }
+
+ /**
+ * Ensures that the ID-based index is initialized.
+ */
+ private void indexById() {
+ if (sessionsById == null) {
+ sessionsById = new HashMap<ByteArray, SSLSession>();
+ for (SSLSession session : sessions.values()) {
+ sessionsById.put(new ByteArray(session.getId()), session);
+ }
+ }
+ }
+
+ /**
+ * Adds the given session to the ID-based index if the index has already
+ * been initialized.
+ */
+ private void indexById(SSLSession session) {
+ if (sessionsById != null) {
+ sessionsById.put(new ByteArray(session.getId()), session);
+ }
+ }
+
+ /**
+ * Finds a cached session for the given host name and port.
+ *
+ * @param host of server
+ * @param port of server
+ * @return cached session or null if none found
+ */
+ public SSLSession getSession(String host, int port) {
+ synchronized (sessions) {
+ SSLSession session = sessions.get(new HostAndPort(host, port));
+ if (session != null) {
+ return session;
+ }
+ }
+
+ // Look in persistent cache.
+ if (persistentCache != null) {
+ byte[] data = persistentCache.getSessionData(host, port);
+ if (data != null) {
+ SSLSession session = toSession(data, host, port);
+ if (session != null) {
+ synchronized (sessions) {
+ sessions.put(new HostAndPort(host, port), session);
+ indexById(session);
+ }
+ return session;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ void putSession(SSLSession session) {
+ HostAndPort key = new HostAndPort(session.getPeerHost(),
+ session.getPeerPort());
+ synchronized (sessions) {
+ sessions.put(key, session);
+ indexById(session);
+ }
+
+ // TODO: This in a background thread.
+ if (persistentCache != null) {
+ byte[] data = toBytes(session);
+ if (data != null) {
+ persistentCache.putSessionData(session, data);
+ }
+ }
+ }
+
+ static class HostAndPort {
+ final String host;
+ final int port;
+
+ HostAndPort(String host, int port) {
+ this.host = host;
+ this.port = port;
+ }
+
+ @Override
+ public int hashCode() {
+ return host.hashCode() * 31 + port;
+ }
+
+ @Override
+ @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
+ public boolean equals(Object o) {
+ HostAndPort other = (HostAndPort) o;
+ return host.equals(other.host) && port == other.port;
+ }
+ }
+}
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/FileClientSessionCache.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/FileClientSessionCache.java
new file mode 100644
index 0000000..ab097e4
--- /dev/null
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/FileClientSessionCache.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2009 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 javax.net.ssl.SSLSession;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.Iterator;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * File-based cache implementation. Only one process should access the
+ * underlying directory at a time.
+ */
+public class FileClientSessionCache {
+
+ static final int MAX_SIZE = 20;
+
+ static final java.util.logging.Logger logger
+ = java.util.logging.Logger.getLogger(
+ FileClientSessionCache.class.getName());
+
+ private FileClientSessionCache() {}
+
+ /**
+ * This cache creates one file per SSL session using "host.port" for
+ * the file name. Files are created or replaced when session data is put
+ * in the cache (see {@link #putSessionData}). Files are read on
+ * cache hits, but not on cache misses.
+ *
+ * <p>When the number of session files exceeds MAX_SIZE, we delete the
+ * least-recently-used file. We don't current persist the last access time,
+ * so the ordering actually ends up being least-recently-modified in some
+ * cases and even just "not accessed in this process" if the filesystem
+ * doesn't track last modified times.
+ */
+ static class Impl implements SSLClientSessionCache {
+
+ /** Directory to store session files in. */
+ final File directory;
+
+ /**
+ * Map of name -> File. Keeps track of the order files were accessed in.
+ */
+ Map<String, File> accessOrder = newAccessOrder();
+
+ /** The number of files on disk. */
+ int size;
+
+ /**
+ * The initial set of files. We use this to defer adding information
+ * about all files to accessOrder until necessary.
+ */
+ String[] initialFiles;
+
+ /**
+ * Constructs a new cache backed by the given directory.
+ */
+ Impl(File directory) throws IOException {
+ boolean exists = directory.exists();
+ if (exists && !directory.isDirectory()) {
+ throw new IOException(directory
+ + " exists but is not a directory.");
+ }
+
+ if (exists) {
+ // Read and sort initial list of files. We defer adding
+ // information about these files to accessOrder until necessary
+ // (see indexFiles()). Sorting the list enables us to detect
+ // cache misses in getSessionData().
+ // Note: Sorting an array here was faster than creating a
+ // HashSet on Dalvik.
+ initialFiles = directory.list();
+ Arrays.sort(initialFiles);
+ size = initialFiles.length;
+ } else {
+ // Create directory.
+ if (!directory.mkdirs()) {
+ throw new IOException("Creation of " + directory
+ + " directory failed.");
+ }
+ size = 0;
+ }
+
+ this.directory = directory;
+ }
+
+ /**
+ * Creates a new access-ordered linked hash map.
+ */
+ private static Map<String, File> newAccessOrder() {
+ return new LinkedHashMap<String, File>(
+ MAX_SIZE, 0.75f, true /* access order */);
+ }
+
+ /**
+ * Gets the file name for the given host and port.
+ */
+ private static String fileName(String host, int port) {
+ if (host == null) {
+ throw new NullPointerException("host");
+ }
+ return host + "." + port;
+ }
+
+ public synchronized byte[] getSessionData(String host, int port) {
+ /*
+ * Note: This method is only called when the in-memory cache
+ * in SSLSessionContext misses, so it would be unnecesarily
+ * rendundant for this cache to store data in memory.
+ */
+
+ String name = fileName(host, port);
+ File file = accessOrder.get(name);
+
+ if (file == null) {
+ // File wasn't in access order. Check initialFiles...
+ if (initialFiles == null) {
+ // All files are in accessOrder, so it doesn't exist.
+ return null;
+ }
+
+ // Look in initialFiles.
+ if (Arrays.binarySearch(initialFiles, name) < 0) {
+ // Not found.
+ return null;
+ }
+
+ // The file is on disk but not in accessOrder yet.
+ file = new File(directory, name);
+ accessOrder.put(name, file);
+ }
+
+ FileInputStream in;
+ try {
+ in = new FileInputStream(file);
+ } catch (FileNotFoundException e) {
+ logReadError(host, e);
+ return null;
+ }
+ try {
+ int size = (int) file.length();
+ byte[] data = new byte[size];
+ new DataInputStream(in).readFully(data);
+ logger.log(Level.FINE, "Read session for " + host + ".");
+ return data;
+ } catch (IOException e) {
+ logReadError(host, e);
+ return null;
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e) { /* ignore */ }
+ }
+ }
+
+ static void logReadError(String host, Throwable t) {
+ logger.log(Level.INFO, "Error reading session data for " + host
+ + ".", t);
+ }
+
+ public synchronized void putSessionData(SSLSession session,
+ byte[] sessionData) {
+ String host = session.getPeerHost();
+ if (sessionData == null) {
+ throw new NullPointerException("sessionData");
+ }
+
+ String name = fileName(host, session.getPeerPort());
+ File file = new File(directory, name);
+
+ // Used to keep track of whether or not we're expanding the cache.
+ boolean existedBefore = file.exists();
+
+ FileOutputStream out;
+ try {
+ out = new FileOutputStream(file);
+ } catch (FileNotFoundException e) {
+ // We can't write to the file.
+ logWriteError(host, e);
+ return;
+ }
+
+ // If we expanded the cache (by creating a new file)...
+ if (!existedBefore) {
+ size++;
+
+ // Delete an old file if necessary.
+ makeRoom();
+ }
+
+ boolean writeSuccessful = false;
+ try {
+ out.write(sessionData);
+ writeSuccessful = true;
+ } catch (IOException e) {
+ logWriteError(host, e);
+ } finally {
+ boolean closeSuccessful = false;
+ try {
+ out.close();
+ closeSuccessful = true;
+ } catch (IOException e) {
+ logWriteError(host, e);
+ } finally {
+ if (!writeSuccessful || !closeSuccessful) {
+ // Storage failed. Clean up.
+ delete(file);
+ } else {
+ // Success!
+ accessOrder.put(name, file);
+ logger.log(Level.FINE, "Stored session for " + host
+ + ".");
+ }
+ }
+ }
+ }
+
+ /**
+ * Deletes old files if necessary.
+ */
+ private void makeRoom() {
+ if (size <= MAX_SIZE) {
+ return;
+ }
+
+ indexFiles();
+
+ // Delete LRUed files.
+ int removals = size - MAX_SIZE;
+ Iterator<File> i = accessOrder.values().iterator();
+ do {
+ delete(i.next());
+ i.remove();
+ } while (--removals > 0);
+ }
+
+ /**
+ * Lazily updates accessOrder to know about all files as opposed to
+ * just the files accessed since this process started.
+ */
+ private void indexFiles() {
+ String[] initialFiles = this.initialFiles;
+ if (initialFiles != null) {
+ this.initialFiles = null;
+
+ // Files on disk only, sorted by last modified time.
+ // TODO: Use last access time.
+ Set<CacheFile> diskOnly = new TreeSet<CacheFile>();
+ for (String name : initialFiles) {
+ // If the file hasn't been accessed in this process...
+ if (!accessOrder.containsKey(name)) {
+ diskOnly.add(new CacheFile(directory, name));
+ }
+ }
+
+ if (!diskOnly.isEmpty()) {
+ // Add files not accessed in this process to the beginning
+ // of accessOrder.
+ Map<String, File> newOrder = newAccessOrder();
+ for (CacheFile cacheFile : diskOnly) {
+ newOrder.put(cacheFile.name, cacheFile);
+ }
+ newOrder.putAll(accessOrder);
+
+ this.accessOrder = newOrder;
+ }
+ }
+ }
+
+ @SuppressWarnings("ThrowableInstanceNeverThrown")
+ private void delete(File file) {
+ if (!file.delete()) {
+ logger.log(Level.INFO, "Failed to delete " + file + ".",
+ new IOException());
+ }
+ size--;
+ }
+
+ static void logWriteError(String host, Throwable t) {
+ logger.log(Level.INFO, "Error writing session data for "
+ + host + ".", t);
+ }
+ }
+
+ /**
+ * Maps directories to the cache instances that are backed by those
+ * directories. We synchronize access using the cache instance, so it's
+ * important that everyone shares the same instance.
+ */
+ static final Map<File, FileClientSessionCache.Impl> caches
+ = new HashMap<File, FileClientSessionCache.Impl>();
+
+ /**
+ * Returns a cache backed by the given directory. Creates the directory
+ * (including parent directories) if necessary. This cache should have
+ * exclusive access to the given directory.
+ *
+ * @param directory to store files in
+ * @return a cache backed by the given directory
+ * @throws IOException if the file exists and is not a directory or if
+ * creating the directories fails
+ */
+ public static synchronized SSLClientSessionCache usingDirectory(
+ File directory) throws IOException {
+ FileClientSessionCache.Impl cache = caches.get(directory);
+ if (cache == null) {
+ cache = new FileClientSessionCache.Impl(directory);
+ caches.put(directory, cache);
+ }
+ return cache;
+ }
+
+ /** For testing. */
+ static synchronized void reset() {
+ caches.clear();
+ }
+
+ /** A file containing a piece of cached data. */
+ static class CacheFile extends File {
+
+ final String name;
+
+ CacheFile(File dir, String name) {
+ super(dir, name);
+ this.name = name;
+ }
+
+ long lastModified = -1;
+
+ @Override
+ public long lastModified() {
+ long lastModified = this.lastModified;
+ if (lastModified == -1) {
+ lastModified = this.lastModified = super.lastModified();
+ }
+ return lastModified;
+ }
+
+ @Override
+ public int compareTo(File another) {
+ // Sort by last modified time.
+ long result = lastModified() - another.lastModified();
+ if (result == 0) {
+ return super.compareTo(another);
+ }
+ return result < 0 ? -1 : 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java
index 4fc6e99..e985908 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java
@@ -302,7 +302,8 @@
@Override
public Socket accept() throws IOException {
- OpenSSLSocketImpl socket = new OpenSSLSocketImpl(sslParameters, ssl_op_no);
+ OpenSSLSocketImpl socket
+ = new OpenSSLSocketImpl(sslParameters, ssl_op_no);
implAccept(socket);
socket.accept(ssl_ctx, client_mode);
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
index 475d388..ca7d6f8 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
@@ -23,6 +23,7 @@
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Iterator;
+import java.util.UnknownFormatConversionException;
import java.util.Vector;
import javax.net.ssl.SSLPeerUnverifiedException;
@@ -55,21 +56,41 @@
private SSLParameters sslParameters;
private String peerHost;
private int peerPort;
+ private final SSLSessionContext sessionContext;
/**
* Class constructor creates an SSL session context given the appropriate
* SSL parameters.
+ *
+ * @param session the Identifier for SSL session
* @param sslParameters the SSL parameters like ciphers' suites etc.
- * @param ssl the Identifier for SSL session
*/
- protected OpenSSLSessionImpl(int session, SSLParameters sslParameters, String peerHost, int peerPort) {
+ protected OpenSSLSessionImpl(int session, SSLParameters sslParameters,
+ String peerHost, int peerPort, SSLSessionContext sessionContext) {
this.session = session;
this.sslParameters = sslParameters;
this.peerHost = peerHost;
this.peerPort = peerPort;
+ this.sessionContext = sessionContext;
}
/**
+ * Constructs a session from a byte[].
+ */
+ OpenSSLSessionImpl(byte[] derData, SSLParameters sslParameters,
+ String peerHost, int peerPort,
+ javax.security.cert.X509Certificate[] peerCertificateChain,
+ SSLSessionContext sessionContext)
+ throws IOException {
+ this.sslParameters = sslParameters;
+ this.peerHost = peerHost;
+ this.peerPort = peerPort;
+ this.peerCertificateChain = peerCertificateChain;
+ this.sessionContext = sessionContext;
+ initializeNative(derData);
+ }
+
+ /**
* Returns the identifier of the actual OpenSSL session.
*/
private native byte[] nativegetid();
@@ -90,6 +111,40 @@
private native long nativegetcreationtime();
/**
+ * Serialize the native state of the session ( ID, cypher, keys - but
+ * not certs ), using openSSL i2d_SSL_SESSION()
+ *
+ * @return the DER encoding of the session.
+ */
+ private native byte[] nativeserialize();
+
+ /**
+ * Create a SSL_SESSION object using d2i_SSL_SESSION.
+ */
+ private native int nativedeserialize(byte[] data, int size);
+
+ /**
+ * Get the session object in DER format. This allows saving the session
+ * data or sharing it with other processes.
+ */
+ byte[] getEncoded() {
+ return nativeserialize();
+ }
+
+ /**
+ * Init the underlying native object from DER data. This
+ * allows loading the saved session.
+ * @throws IOException
+ */
+ private void initializeNative(byte[] derData) throws IOException {
+ this.session = nativedeserialize(derData, derData.length);
+ if (this.session == 0) {
+ throw new IOException("Invalid session data");
+ }
+ }
+
+
+ /**
* Gets the creation time of the SSL session.
* @return the session's creation time in milli seconds since 12.00 PM,
* January 1st, 1970
@@ -337,7 +392,7 @@
if (sm != null) {
sm.checkPermission(new SSLPermission("getSSLSessionContext"));
}
- return sslParameters.getClientSessionContext();
+ return sessionContext;
}
/**
@@ -347,7 +402,7 @@
* @return true if this session may be resumed.
*/
public boolean isValid() {
- SSLSessionContextImpl context = sslParameters.getClientSessionContext();
+ SSLSessionContext context = sessionContext;
if (isValid
&& context != null
&& context.getSessionTimeout() != 0
@@ -372,7 +427,7 @@
* of security, by the full machinery of the <code>AccessController</code>
* class.
*
- * @param <code>String name</code> the name of the binding to find.
+ * @param name the name of the binding to find.
* @return the value bound to that name, or null if the binding does not
* exist.
* @throws <code>IllegalArgumentException</code> if the argument is null.
@@ -416,9 +471,9 @@
* -data bounds are monitored, as a matter of security, by the full
* machinery of the <code>AccessController</code> class.
*
- * @param <code>String name</code> the name of the link (no null are
+ * @param name the name of the link (no null are
* accepted!)
- * @param <code>Object value</code> data object that shall be bound to
+ * @param value data object that shall be bound to
* name.
* @throws <code>IllegalArgumentException</code> if one or both
* argument(s) is null.
@@ -444,7 +499,7 @@
* monitored, as a matter of security, by the full machinery of the
* <code>AccessController</code> class.
*
- * @param <code>String name</code> the name of the link (no null are
+ * @param name the name of the link (no null are
* accepted!)
* @throws <code>IllegalArgumentException</code> if the argument is null.
*/
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
index 1d38ca9..8779736 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
@@ -21,7 +21,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
-import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
@@ -31,7 +30,6 @@
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
-import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -58,7 +56,7 @@
private int ssl;
private InputStream is;
private OutputStream os;
- private Object handshakeLock = new Object();
+ private final Object handshakeLock = new Object();
private Object readLock = new Object();
private Object writeLock = new Object();
private SSLParameters sslParameters;
@@ -66,7 +64,7 @@
private Socket socket;
private boolean autoClose;
private boolean handshakeStarted = false;
- private ArrayList listeners;
+ private ArrayList<HandshakeCompletedListener> listeners;
private long ssl_op_no = 0x00000000L;
private int timeout = 0;
private InetSocketAddress address;
@@ -137,11 +135,11 @@
/**
* Class constructor with 2 parameters
*
- * @param <code>SSLParameters sslParameters</code> Parameters for the SSL
+ * @param sslParameters Parameters for the SSL
* context
- * @param <code>long ssl_op_no</code> Parameter to set the enabled
+ * @param ssl_op_no Parameter to set the enabled
* protocols
- * @throws <code>IOException</code> if network fails
+ * @throws IOException if network fails
*/
protected OpenSSLSocketImpl(SSLParameters sslParameters, long ssl_op_no) throws IOException {
super();
@@ -153,9 +151,9 @@
/**
* Class constructor with 1 parameter
*
- * @param <code>SSLParameters sslParameters</code> Parameters for the SSL
+ * @param sslParameters Parameters for the SSL
* context
- * @throws <code>IOException</code> if network fails
+ * @throws IOException if network fails
*/
protected OpenSSLSocketImpl(SSLParameters sslParameters) throws IOException {
super();
@@ -167,11 +165,8 @@
/**
* Class constructor with 3 parameters
*
- * @param <code> String host</code>
- * @param <code>int port</code>
- * @param <code>SSLParameters sslParameters</code>
- * @throws <code>IOException</code> if network fails
- * @throws <code>UnknownHostException</code> host not defined
+ * @throws IOException if network fails
+ * @throws java.net.UnknownHostException host not defined
*/
protected OpenSSLSocketImpl(String host, int port,
SSLParameters sslParameters)
@@ -186,11 +181,8 @@
/**
* Class constructor with 3 parameters: 1st is InetAddress
*
- * @param <code>InetAddress address</code>
- * @param <code>int port</code>
- * @param <code>SSLParameters sslParameters</code>
- * @throws <code>IOException</code> if network fails
- * @throws <code>UnknownHostException</code> host not defined
+ * @throws IOException if network fails
+ * @throws java.net.UnknownHostException host not defined
*/
protected OpenSSLSocketImpl(InetAddress address, int port,
SSLParameters sslParameters)
@@ -205,13 +197,8 @@
/**
* Class constructor with 5 parameters: 1st is host
*
- * @param <code>String host</code>
- * @param <code>int port</code>
- * @param <code>InetAddress localHost</code>
- * @param <code>int localPort</code>
- * @param <code>SSLParameters sslParameters</code>
- * @throws <code>IOException</code> if network fails
- * @throws <code>UnknownHostException</code> host not defined
+ * @throws IOException if network fails
+ * @throws java.net.UnknownHostException host not defined
*/
protected OpenSSLSocketImpl(String host, int port, InetAddress clientAddress,
int clientPort, SSLParameters sslParameters)
@@ -225,13 +212,8 @@
/**
* Class constructor with 5 parameters: 1st is InetAddress
*
- * @param <code>InetAddress address</code>
- * @param <code>int port</code>
- * @param <code>InetAddress localAddress</code>
- * @param <code>int localPort</code>
- * @param <code>SSLParameters sslParameters</code>
- * @throws <code>IOException</code> if network fails
- * @throws <code>UnknownHostException</code> host not defined
+ * @throws IOException if network fails
+ * @throws java.net.UnknownHostException host not defined
*/
protected OpenSSLSocketImpl(InetAddress address, int port,
InetAddress clientAddress, int clientPort, SSLParameters sslParameters)
@@ -246,12 +228,7 @@
* Constructor with 5 parameters: 1st is socket. Enhances an existing socket
* with SSL functionality.
*
- * @param <code>Socket socket</code>
- * @param <code>String host</code>
- * @param <code>int port</code>
- * @param <code>boolean autoClose</code>
- * @param <code>SSLParameters sslParameters</code>
- * @throws <code>IOException</code> if network fails
+ * @throws IOException if network fails
*/
protected OpenSSLSocketImpl(Socket socket, String host, int port,
boolean autoClose, SSLParameters sslParameters) throws IOException {
@@ -278,26 +255,26 @@
*
* @return OpenSSLSessionImpl
*/
- private OpenSSLSessionImpl getOpenSSLSessionImpl() {
- try {
- byte[] id;
- SSLSession ses;
- for (Enumeration<byte[]> en = sslParameters.getClientSessionContext().getIds(); en.hasMoreElements();) {
- id = en.nextElement();
- ses = sslParameters.getClientSessionContext().getSession(id);
- if (ses instanceof OpenSSLSessionImpl && ses.isValid() &&
- super.getInetAddress() != null &&
- super.getInetAddress().getHostAddress() != null &&
- super.getInetAddress().getHostName().equals(ses.getPeerHost()) &&
- super.getPort() == ses.getPeerPort()) {
- return (OpenSSLSessionImpl) ses;
- }
- }
- } catch (Exception ex) {
- // It's not clear to me under what circumstances the above code
- // might fail. I also can't reproduce it.
+ private OpenSSLSessionImpl getCachedClientSession() {
+ if (super.getInetAddress() == null ||
+ super.getInetAddress().getHostAddress() == null ||
+ super.getInetAddress().getHostName() == null) {
+ return null;
}
- return null;
+ ClientSessionContext sessionContext
+ = sslParameters.getClientSessionContext();
+ return (OpenSSLSessionImpl) sessionContext.getSession(
+ super.getInetAddress().getHostName(),
+ super.getPort());
+ }
+
+ /**
+ * Ensures that logger is lazily loaded. The outer class seems to load
+ * before logging is ready.
+ */
+ static class LoggerHolder {
+ static final Logger logger = Logger.getLogger(
+ OpenSSLSocketImpl.class.getName());
}
/**
@@ -317,38 +294,60 @@
return;
}
}
-
- {
- // Debug
- int size = 0;
- for (Enumeration<byte[]> en = sslParameters.getClientSessionContext().getIds();
- en.hasMoreElements(); en.nextElement()) { size++; };
- }
- OpenSSLSessionImpl session = getOpenSSLSessionImpl();
+
+ OpenSSLSessionImpl session = getCachedClientSession();
// Check if it's allowed to create a new session (default is true)
- if (!sslParameters.getEnableSessionCreation() && session == null) {
+ if (session == null && !sslParameters.getEnableSessionCreation()) {
throw new SSLHandshakeException("SSL Session may not be created");
} else {
- if (nativeconnect(ssl_ctx, this.socket != null ?
- this.socket : this, sslParameters.getUseClientMode(), session != null ? session.session : 0)) {
+ Socket socket = this.socket != null ? this.socket : this;
+ int sessionId = session != null ? session.session : 0;
+ if (nativeconnect(ssl_ctx, socket, sslParameters.getUseClientMode(),
+ sessionId)) {
+ // nativeconnect shouldn't return true if the session is not
+ // done
session.lastAccessedTime = System.currentTimeMillis();
sslSession = session;
- } else {
- if (address == null) sslSession = new OpenSSLSessionImpl(nativegetsslsession(ssl),
- sslParameters, super.getInetAddress().getHostName(), super.getPort());
- else sslSession = new OpenSSLSessionImpl(nativegetsslsession(ssl),
- sslParameters, address.getHostName(), address.getPort());
- try {
- X509Certificate[] peerCertificates = (X509Certificate[]) sslSession.getPeerCertificates();
- if (peerCertificates == null || peerCertificates.length == 0) {
+ LoggerHolder.logger.fine("Reused cached session for "
+ + getInetAddress().getHostName() + ".");
+ } else {
+ if (session != null) {
+ LoggerHolder.logger.fine("Reuse of cached session for "
+ + getInetAddress().getHostName() + " failed.");
+ } else {
+ LoggerHolder.logger.fine("Created new session for "
+ + getInetAddress().getHostName() + ".");
+ }
+
+ ClientSessionContext sessionContext
+ = sslParameters.getClientSessionContext();
+ if (address == null) {
+ sslSession = new OpenSSLSessionImpl(
+ nativegetsslsession(ssl), sslParameters,
+ super.getInetAddress().getHostName(),
+ super.getPort(), sessionContext);
+ } else {
+ sslSession = new OpenSSLSessionImpl(
+ nativegetsslsession(ssl), sslParameters,
+ address.getHostName(), address.getPort(),
+ sessionContext);
+ }
+
+ try {
+ X509Certificate[] peerCertificates = (X509Certificate[])
+ sslSession.getPeerCertificates();
+
+ if (peerCertificates == null
+ || peerCertificates.length == 0) {
throw new SSLException("Server sends no certificate");
}
- sslParameters.getTrustManager().checkServerTrusted(peerCertificates,
- nativecipherauthenticationmethod());
- sslParameters.getClientSessionContext().putSession(sslSession);
+ sslParameters.getTrustManager().checkServerTrusted(
+ peerCertificates,
+ nativecipherauthenticationmethod());
+ sessionContext.putSession(sslSession);
} catch (CertificateException e) {
throw new SSLException("Not trusted server certificate", e);
}
@@ -360,9 +359,8 @@
HandshakeCompletedEvent event =
new HandshakeCompletedEvent(this, sslSession);
int size = listeners.size();
- for (int i=0; i<size; i++) {
- ((HandshakeCompletedListener)listeners.get(i))
- .handshakeCompleted(event);
+ for (int i = 0; i < size; i++) {
+ listeners.get(i).handshakeCompleted(event);
}
}
}
@@ -381,22 +379,27 @@
nativeaccept(this, m_ctx, client_mode);
+ ServerSessionContext sessionContext
+ = sslParameters.getServerSessionContext();
sslSession = new OpenSSLSessionImpl(nativegetsslsession(ssl),
- sslParameters, super.getInetAddress().getHostName(), super.getPort());
+ sslParameters, super.getInetAddress().getHostName(),
+ super.getPort(), sessionContext);
sslSession.lastAccessedTime = System.currentTimeMillis();
+ sessionContext.putSession(sslSession);
}
/**
* Callback methode for the OpenSSL native certificate verification process.
*
- * @param <code>byte[][] bytes</code> Byte array containing the cert's
+ * @param bytes Byte array containing the cert's
* information.
* @return 0 if the certificate verification fails or 1 if OK
*/
@SuppressWarnings("unused")
private int verify_callback(byte[][] bytes) {
try {
- X509Certificate[] peerCertificateChain = new X509Certificate[bytes.length];
+ X509Certificate[] peerCertificateChain
+ = new X509Certificate[bytes.length];
for(int i = 0; i < bytes.length; i++) {
peerCertificateChain[i] =
new X509CertImpl(javax.security.cert.X509Certificate.getInstance(bytes[i]).getEncoded());
@@ -582,7 +585,6 @@
/**
* Registers a listener to be notified that a SSL handshake
* was successfully completed on this connection.
- * @param <code>HandShakeCompletedListener listener</code>
* @throws <code>IllegalArgumentException</code> if listener is null.
*/
public void addHandshakeCompletedListener(
@@ -598,7 +600,6 @@
/**
* The method removes a registered listener.
- * @param <code>HandShakeCompletedListener listener</code>
* @throws IllegalArgumentException if listener is null or not registered
*/
public void removeHandshakeCompletedListener(
@@ -631,7 +632,7 @@
* SSL sessions. If the flag is set to false, and there are no actual
* sessions to resume, then there will be no successful handshaking.
*
- * @param <code>boolean flag</code> true if session may be created; false
+ * @param flag true if session may be created; false
* if a session already exists and must be resumed.
*/
public void setEnableSessionCreation(boolean flag) {
@@ -683,9 +684,9 @@
* This method enables the cipher suites listed by
* getSupportedCipherSuites().
*
- * @param <code> String[] suites</code> names of all the cipher suites to
+ * @param suites names of all the cipher suites to
* put on use
- * @throws <code>IllegalArgumentException</code> when one or more of the
+ * @throws IllegalArgumentException when one or more of the
* ciphers in array suites are not supported, or when the array
* is null.
*/
@@ -781,9 +782,9 @@
/**
* This method set the actual SSL socket to client mode.
*
- * @param <code>boolean mode</code> true if the socket starts in client
+ * @param mode true if the socket starts in client
* mode
- * @throws <code>IllegalArgumentException</code> if mode changes during
+ * @throws IllegalArgumentException if mode changes during
* handshake.
*/
public synchronized void setUseClientMode(boolean mode) {
@@ -818,7 +819,7 @@
* Sets the SSL socket to use client's authentication. Relevant only for
* server sockets!
*
- * @param <code>boolean need</code> true if client authentication is
+ * @param need true if client authentication is
* desired, false if not.
*/
public void setNeedClientAuth(boolean need) {
@@ -831,7 +832,7 @@
* method will continue the negotiation if the client decide not to send
* authentication credentials.
*
- * @param <code>boolean want</code> true if client authentication is
+ * @param want true if client authentication is
* desired, false if not.
*/
public void setWantClientAuth(boolean want) {
@@ -878,6 +879,9 @@
* socket's closure.
*/
public void close() throws IOException {
+ // TODO: Close SSL sockets using a background thread so they close
+ // gracefully.
+
synchronized (handshakeLock) {
if (!handshakeStarted) {
handshakeStarted = true;
@@ -987,10 +991,11 @@
}
/**
- * Helper class for a thread that knows how to call {@link #close} on behalf
- * of instances being finalized, since that call can take arbitrarily long
- * (e.g., due to a slow network), and an overly long-running finalizer will
- * cause the process to be totally aborted.
+ * Helper class for a thread that knows how to call
+ * {@link OpenSSLSocketImpl#close} on behalf of instances being finalized,
+ * since that call can take arbitrarily long (e.g., due to a slow network),
+ * and an overly long-running finalizer will cause the process to be
+ * totally aborted.
*/
private class Finalizer extends Thread {
public void run() {
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLClientSessionCache.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLClientSessionCache.java
new file mode 100644
index 0000000..8a73fa5
--- /dev/null
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLClientSessionCache.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2009 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 javax.net.ssl.SSLSession;
+
+/**
+ * A persistent {@link javax.net.ssl.SSLSession} cache used by
+ * {@link javax.net.ssl.SSLSessionContext} to share client-side SSL sessions
+ * across processes. For example, this cache enables applications to
+ * persist and reuse sessions across restarts.
+ *
+ * <p>The {@code SSLSessionContext} implementation converts
+ * {@code SSLSession}s into raw bytes and vice versa. The exact makeup of the
+ * session data is dependent upon the caller's implementation and is opaque to
+ * the {@code SSLClientSessionCache} implementation.
+ */
+public interface SSLClientSessionCache {
+
+ /**
+ * Gets data from a pre-existing session for a given server host and port.
+ *
+ * @param host from {@link javax.net.ssl.SSLSession#getPeerHost()}
+ * @param port from {@link javax.net.ssl.SSLSession#getPeerPort()}
+ * @return the session data or null if none is cached
+ * @throws NullPointerException if host is null
+ */
+ public byte[] getSessionData(String host, int port);
+
+ /**
+ * Stores session data for the given session.
+ *
+ * @param session to cache data for
+ * @param sessionData to cache
+ * @throws NullPointerException if session, result of
+ * {@code session.getPeerHost()} or data is null
+ */
+ public void putSessionData(SSLSession session, byte[] sessionData);
+}
\ No newline at end of file
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLContextImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLContextImpl.java
index 3f4ab96..2e4de04 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLContextImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLContextImpl.java
@@ -22,9 +22,6 @@
package org.apache.harmony.xnet.provider.jsse;
-// BEGIN android-removed
-// import org.apache.harmony.xnet.provider.jsse.SSLSocketFactoryImpl;
-// END android-removed
import org.apache.harmony.xnet.provider.jsse.SSLEngineImpl;
import org.apache.harmony.xnet.provider.jsse.SSLParameters;
// BEGIN android-removed
@@ -42,19 +39,21 @@
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
+// BEGIN android-note
+// Modified heavily during SSLSessionContext refactoring. Added support for
+// persistent session caches.
+// END android-note
+
/**
* Implementation of SSLContext service provider interface.
*/
public class SSLContextImpl extends SSLContextSpi {
- // client session context contains the set of reusable
- // client-side SSL sessions
- private SSLSessionContextImpl clientSessionContext =
- new SSLSessionContextImpl();
- // server session context contains the set of reusable
- // server-side SSL sessions
- private SSLSessionContextImpl serverSessionContext =
- new SSLSessionContextImpl();
+ /** Client session cache. */
+ private ClientSessionContext clientSessionContext;
+
+ /** Server session cache. */
+ private ServerSessionContext serverSessionContext;
protected SSLParameters sslParameters;
@@ -64,26 +63,44 @@
public void engineInit(KeyManager[] kms, TrustManager[] tms,
SecureRandom sr) throws KeyManagementException {
- sslParameters = new SSLParameters(kms, tms, sr, clientSessionContext,
- serverSessionContext);
+ engineInit(kms, tms, sr, null, null);
+ }
+
+ /**
+ * Initializes this {@code SSLContext} instance. All of the arguments are
+ * optional, and the security providers will be searched for the required
+ * implementations of the needed algorithms.
+ *
+ * @param kms the key sources or {@code null}
+ * @param tms the trust decision sources or {@code null}
+ * @param sr the randomness source or {@code null}
+ * @param clientCache persistent client session cache or {@code null}
+ * @param serverCache persistent server session cache or {@code null}
+ * @throws KeyManagementException if initializing this instance fails
+ *
+ * @since Android 1.1
+ */
+ public void engineInit(KeyManager[] kms, TrustManager[] tms,
+ SecureRandom sr, SSLClientSessionCache clientCache,
+ SSLServerSessionCache serverCache) throws KeyManagementException {
+ sslParameters = new SSLParameters(kms, tms, sr,
+ clientCache, serverCache);
+ clientSessionContext = sslParameters.getClientSessionContext();
+ serverSessionContext = sslParameters.getServerSessionContext();
}
public SSLSocketFactory engineGetSocketFactory() {
if (sslParameters == null) {
throw new IllegalStateException("SSLContext is not initiallized.");
}
- // BEGIN android-changed
return new OpenSSLSocketFactoryImpl(sslParameters);
- // END android-changed
}
public SSLServerSocketFactory engineGetServerSocketFactory() {
if (sslParameters == null) {
throw new IllegalStateException("SSLContext is not initiallized.");
}
- // BEGIN android-changed
return new OpenSSLServerSocketFactoryImpl(sslParameters);
- // END android-changed
}
public SSLEngine engineCreateSSLEngine(String host, int port) {
@@ -101,12 +118,11 @@
return new SSLEngineImpl((SSLParameters) sslParameters.clone());
}
- public SSLSessionContext engineGetServerSessionContext() {
+ public ServerSessionContext engineGetServerSessionContext() {
return serverSessionContext;
}
- public SSLSessionContext engineGetClientSessionContext() {
+ public ClientSessionContext engineGetClientSessionContext() {
return clientSessionContext;
}
-}
-
+}
\ No newline at end of file
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java
index 9a14f46..60a5535 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java
@@ -22,8 +22,6 @@
package org.apache.harmony.xnet.provider.jsse;
-import org.apache.harmony.xnet.provider.jsse.SSLSessionContextImpl;
-
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -45,7 +43,9 @@
* and controls whether new SSL sessions may be established by this
* socket or not.
*/
-public class SSLParameters {
+// BEGIN android-changed
+public class SSLParameters implements Cloneable {
+// END android-changed
// default source of authentication keys
private static X509KeyManager defaultKeyManager;
@@ -58,10 +58,12 @@
// client session context contains the set of reusable
// client-side SSL sessions
- private SSLSessionContextImpl clientSessionContext;
+// BEGIN android-changed
+ private final ClientSessionContext clientSessionContext;
// server session context contains the set of reusable
// server-side SSL sessions
- private SSLSessionContextImpl serverSessionContext;
+ private final ServerSessionContext serverSessionContext;
+// END android-changed
// source of authentication keys
private X509KeyManager keyManager;
// source of authentication trust decisions
@@ -88,16 +90,7 @@
// if the peer with this parameters allowed to cteate new SSL session
private boolean enable_session_creation = true;
- /**
- * Creates an instance of SSLParameters.
- */
- private SSLParameters() {
- // BEGIN android-removed
- // this.enabledCipherSuites = CipherSuite.defaultCipherSuites;
- // END android-removed
- }
-
- // BEGIN android-added
+// BEGIN android-changed
protected CipherSuite[] getEnabledCipherSuitesMember() {
if (enabledCipherSuites == null) this.enabledCipherSuites = CipherSuite.defaultCipherSuites;
return enabledCipherSuites;
@@ -120,23 +113,26 @@
if (ssl_ctx == 0) ssl_ctx = nativeinitsslctx();
return ssl_ctx;
}
- // END android-added
+// END android-changed
/**
* Initializes the parameters. Naturally this constructor is used
* in SSLContextImpl.engineInit method which dirrectly passes its
* parameters. In other words this constructor holds all
* the functionality provided by SSLContext.init method.
- * See {@link javax.net.ssl.SSLContext#init(KeyManager[],TrustManager[],SecureRandom)}
- * for more information
+ * See {@link javax.net.ssl.SSLContext#init(KeyManager[],TrustManager[],
+ * SecureRandom)} for more information
*/
protected SSLParameters(KeyManager[] kms, TrustManager[] tms,
- SecureRandom sr, SSLSessionContextImpl clientSessionContext,
- SSLSessionContextImpl serverSessionContext)
+// BEGIN android-changed
+ SecureRandom sr, SSLClientSessionCache clientCache,
+ SSLServerSessionCache serverCache)
throws KeyManagementException {
- this();
- this.serverSessionContext = serverSessionContext;
- this.clientSessionContext = clientSessionContext;
+ this.serverSessionContext
+ = new ServerSessionContext(this, serverCache);
+ this.clientSessionContext
+ = new ClientSessionContext(this, clientCache);
+// END android-changed
try {
// initialize key manager
boolean initialize_default = false;
@@ -228,8 +224,9 @@
protected static SSLParameters getDefault() throws KeyManagementException {
if (defaultParameters == null) {
- defaultParameters = new SSLParameters(null, null, null,
- new SSLSessionContextImpl(), new SSLSessionContextImpl());
+// BEGIN android-changed
+ defaultParameters = new SSLParameters(null, null, null, null, null);
+// END android-changed
}
return (SSLParameters) defaultParameters.clone();
}
@@ -237,14 +234,18 @@
/**
* @return server session context
*/
- protected SSLSessionContextImpl getServerSessionContext() {
+// BEGIN android-changed
+ protected ServerSessionContext getServerSessionContext() {
+// END android-changed
return serverSessionContext;
}
/**
* @return client session context
*/
- protected SSLSessionContextImpl getClientSessionContext() {
+// BEGIN android-changed
+ protected ClientSessionContext getClientSessionContext() {
+// END android-changed
return clientSessionContext;
}
@@ -335,7 +336,7 @@
/**
* Sets the set of available protocols for use in SSL connection.
- * @param suites: String[]
+ * @param protocols String[]
*/
protected void setEnabledProtocols(String[] protocols) {
if (protocols == null) {
@@ -422,23 +423,13 @@
* @return the clone.
*/
protected Object clone() {
- SSLParameters parameters = new SSLParameters();
-
- parameters.clientSessionContext = clientSessionContext;
- parameters.serverSessionContext = serverSessionContext;
- parameters.keyManager = keyManager;
- parameters.trustManager = trustManager;
- parameters.secureRandom = secureRandom;
-
- parameters.enabledCipherSuites = enabledCipherSuites;
- parameters.enabledProtocols = enabledProtocols;
-
- parameters.client_mode = client_mode;
- parameters.need_client_auth = need_client_auth;
- parameters.want_client_auth = want_client_auth;
- parameters.enable_session_creation = enable_session_creation;
-
- return parameters;
+// BEGIN android-changed
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
+// END android-changed
}
}
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLServerSessionCache.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLServerSessionCache.java
new file mode 100644
index 0000000..32a0e72
--- /dev/null
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLServerSessionCache.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2009 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 javax.net.ssl.SSLSession;
+
+/**
+ * A persistent {@link javax.net.ssl.SSLSession} cache used by
+ * {@link javax.net.ssl.SSLSessionContext} to share server-side SSL sessions
+ * across processes. For example, this cache enables one server to resume
+ * a session started by a different server based on a session ID provided
+ * by the client.
+ *
+ * <p>The {@code SSLSessionContext} implementation converts
+ * {@code SSLSession}s into raw bytes and vice versa. The exact makeup of the
+ * session data is dependent upon the caller's implementation and is opaque to
+ * the {@code SSLServerSessionCache} implementation.
+ */
+public interface SSLServerSessionCache {
+
+ /**
+ * Gets the session data for given session ID.
+ *
+ * @param id from {@link javax.net.ssl.SSLSession#getId()}
+ * @return the session data or null if none is cached
+ * @throws NullPointerException if id is null
+ */
+ public byte[] getSessionData(byte[] id);
+
+ /**
+ * Stores session data for the given session.
+ *
+ * @param session to cache data for
+ * @param sessionData to cache
+ * @throws NullPointerException if session or data is null
+ */
+ public void putSessionData(SSLSession session, byte[] sessionData);
+}
\ No newline at end of file
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionContextImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionContextImpl.java
deleted file mode 100644
index 6882aa1..0000000
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionContextImpl.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.
- */
-/*
- * Copyright (C) 2008 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.
- */
-
-/**
- * @author Boris Kuznetsov
- * @version $Revision$
- */
-package org.apache.harmony.xnet.provider.jsse;
-
-import java.nio.ByteBuffer;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Set;
-
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSessionContext;
-
-/**
- * SSLSessionContext implementation
- *
- * @see javax.net.ssl.SSLSessionContext
- */
-public class SSLSessionContextImpl implements SSLSessionContext {
-
- private int cacheSize = 20;
- private long timeout = 0;
- private final LinkedHashMap<ByteBuffer, SSLSession> sessions =
- new LinkedHashMap<ByteBuffer, SSLSession>(cacheSize, 0.75f, true) {
- @Override
- public boolean removeEldestEntry(
- Map.Entry<ByteBuffer, SSLSession> eldest) {
- return cacheSize > 0 && this.size() > cacheSize;
- }
- };
- private volatile LinkedHashMap<ByteBuffer, SSLSession> clone =
- new LinkedHashMap<ByteBuffer, SSLSession>();
-
- /**
- * @see javax.net.ssl.SSLSessionContext#getIds()
- */
- public Enumeration<byte[]> getIds() {
- return new Enumeration<byte[]>() {
- Iterator<ByteBuffer> iterator = clone.keySet().iterator();
- public boolean hasMoreElements() {
- return iterator.hasNext();
- }
- public byte[] nextElement() {
- return iterator.next().array();
- }
- };
- }
-
- /**
- * @see javax.net.ssl.SSLSessionContext#getSession(byte[] sessionId)
- */
- public SSLSession getSession(byte[] sessionId) {
- synchronized (sessions) {
- return sessions.get(ByteBuffer.wrap(sessionId));
- }
- }
-
- /**
- * @see javax.net.ssl.SSLSessionContext#getSessionCacheSize()
- */
- public int getSessionCacheSize() {
- synchronized (sessions) {
- return cacheSize;
- }
- }
-
- /**
- * @see javax.net.ssl.SSLSessionContext#getSessionTimeout()
- */
- public int getSessionTimeout() {
- synchronized (sessions) {
- return (int) (timeout/1000);
- }
- }
-
- /**
- * @see javax.net.ssl.SSLSessionContext#setSessionCacheSize(int size)
- */
- public void setSessionCacheSize(int size) throws IllegalArgumentException {
- if (size < 0) {
- throw new IllegalArgumentException("size < 0");
- }
- synchronized (sessions) {
- cacheSize = size;
- if (cacheSize > 0 && cacheSize < sessions.size()) {
- int removals = sessions.size() - cacheSize;
- Iterator<ByteBuffer> iterator = sessions.keySet().iterator();
- while (removals-- > 0) {
- iterator.next();
- iterator.remove();
- }
- clone = (LinkedHashMap<ByteBuffer, SSLSession>)
- sessions.clone();
- }
- }
- }
-
- /**
- * @see javax.net.ssl.SSLSessionContext#setSessionTimeout(int seconds)
- */
- public void setSessionTimeout(int seconds) throws IllegalArgumentException {
- if (seconds < 0) {
- throw new IllegalArgumentException("seconds < 0");
- }
- synchronized (sessions) {
- timeout = seconds*1000;
- // Check timeouts and remove expired sessions
- SSLSession ses;
- Iterator<Map.Entry<ByteBuffer, SSLSession>> iterator =
- sessions.entrySet().iterator();
- while (iterator.hasNext()) {
- SSLSession session = iterator.next().getValue();
- if (!session.isValid()) {
- // safe to remove with this special method since it doesn't
- // make the iterator throw a ConcurrentModificationException
- iterator.remove();
- }
- }
- clone = (LinkedHashMap<ByteBuffer, SSLSession>) sessions.clone();
- }
- }
-
- /**
- * Adds session to the session cache
- * @param ses
- */
- void putSession(SSLSession ses) {
- synchronized (sessions) {
- sessions.put(ByteBuffer.wrap(ses.getId()), ses);
- clone = (LinkedHashMap<ByteBuffer, SSLSession>) sessions.clone();
- }
- }
-}
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java
index df46094..4510c96 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java
@@ -40,7 +40,6 @@
import javax.net.ssl.SSLSessionContext;
import org.apache.harmony.luni.util.TwoKeyHashMap;
-import org.apache.harmony.xnet.provider.jsse.SSLSessionContextImpl;
/**
*
@@ -83,8 +82,9 @@
/**
* Context of the session
*/
- SSLSessionContextImpl context;
-
+// BEGIN android-changed
+ SSLSessionContext context;
+// END android-changed
/**
* certificates were sent to the peer
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java
index 93dc9c4..f6eef23 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java
@@ -81,7 +81,6 @@
/**
* Start session negotiation
- * @param session
*/
public void start() {
if (session == null) { // initial handshake
@@ -299,7 +298,7 @@
clientFinished = new Finished(io_stream, length);
verifyFinished(clientFinished.getData());
// BEGIN android-added
- session.context = parameters.getClientSessionContext();
+ session.context = parameters.getServerSessionContext();
// END android-added
parameters.getServerSessionContext().putSession(session);
if (!isResuming) {
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java
new file mode 100644
index 0000000..a3c2c6d
--- /dev/null
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2009 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.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.net.ssl.SSLSession;
+
+/**
+ * Caches server sessions. Indexes by session ID. Users typically look up
+ * sessions using the ID provided by an SSL client.
+ */
+public class ServerSessionContext extends AbstractSessionContext {
+
+ /*
+ * TODO: Expire timed-out sessions more pro-actively.
+ */
+
+ private final Map<ByteArray, SSLSession> sessions
+ = new LinkedHashMap<ByteArray, SSLSession>() {
+ @Override
+ protected boolean removeEldestEntry(
+ Map.Entry<ByteArray, SSLSession> eldest) {
+ return maximumSize > 0 && size() > maximumSize;
+ }
+ };
+
+ private final SSLServerSessionCache persistentCache;
+
+ public ServerSessionContext(SSLParameters parameters,
+ SSLServerSessionCache persistentCache) {
+ super(parameters, 100, 0);
+ this.persistentCache = persistentCache;
+ }
+
+ Iterator<SSLSession> sessionIterator() {
+ synchronized (sessions) {
+ SSLSession[] array = sessions.values().toArray(
+ new SSLSession[sessions.size()]);
+ return Arrays.asList(array).iterator();
+ }
+ }
+
+ void trimToSize() {
+ synchronized (sessions) {
+ int size = sessions.size();
+ if (size > maximumSize) {
+ int removals = size - maximumSize;
+ Iterator<SSLSession> i = sessions.values().iterator();
+ do {
+ i.next();
+ i.remove();
+ } while (--removals > 0);
+ }
+ }
+ }
+
+ public void setSessionTimeout(int seconds)
+ throws IllegalArgumentException {
+ if (seconds < 0) {
+ throw new IllegalArgumentException("seconds < 0");
+ }
+ timeout = seconds;
+ }
+
+ public SSLSession getSession(byte[] sessionId) {
+ ByteArray key = new ByteArray(sessionId);
+ synchronized (sessions) {
+ SSLSession session = sessions.get(key);
+ if (session != null) {
+ return session;
+ }
+ }
+
+ // Check persistent cache.
+ if (persistentCache != null) {
+ byte[] data = persistentCache.getSessionData(sessionId);
+ if (data != null) {
+ SSLSession session = toSession(data, null, -1);
+ if (session != null) {
+ synchronized (sessions) {
+ sessions.put(key, session);
+ }
+ return session;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ void putSession(SSLSession session) {
+ ByteArray key = new ByteArray(session.getId());
+ synchronized (sessions) {
+ sessions.put(key, session);
+ }
+
+ // TODO: In background thread.
+ if (persistentCache != null) {
+ byte[] data = toBytes(session);
+ if (data != null) {
+ persistentCache.putSessionData(session, data);
+ }
+ }
+ }
+}
diff --git a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp
index 0c0e455..feae690 100644
--- a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp
+++ b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp
@@ -61,7 +61,7 @@
}
/**
- * Gets the peer certificate in the chain and fills a byte array with the
+ * Gets the peer certificate in the chain and fills a byte array with the
* information therein.
*/
static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeercertificates(JNIEnv* env,
@@ -91,7 +91,62 @@
}
/**
- * Gets and returns in a byte array the ID of the actual SSL session.
+ * Serialize the session.
+ * See apache mod_ssl
+ */
+static jbyteArray org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_serialize(JNIEnv* env, jobject object)
+{
+ SSL_SESSION * ssl_session;
+ jbyteArray bytes = NULL;
+ jbyte *tmp;
+ int size;
+ unsigned char *ucp;
+
+ ssl_session = getSslSessionPointer(env, object);
+ if (ssl_session == NULL) {
+ return NULL;
+ }
+
+ // Compute the size of the DER data
+ size = i2d_SSL_SESSION(ssl_session, NULL);
+ if (size == 0) {
+ return NULL;
+ }
+
+ bytes = env->NewByteArray(size);
+ if (bytes != NULL) {
+ tmp = env->GetByteArrayElements(bytes, NULL);
+ ucp = (unsigned char *) tmp;
+ i2d_SSL_SESSION(ssl_session, &ucp);
+ env->ReleaseByteArrayElements(bytes, tmp, 0);
+ }
+
+ return bytes;
+
+}
+
+/**
+ * Deserialize the session.
+ */
+static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_deserialize(JNIEnv* env, jobject object, jbyteArray bytes, jint size)
+{
+ const unsigned char *ucp;
+ jbyte *tmp;
+ SSL_SESSION *ssl_session;
+
+ if (bytes != NULL) {
+ tmp = env->GetByteArrayElements(bytes, NULL);
+ ucp = (const unsigned char *) tmp;
+ ssl_session = d2i_SSL_SESSION(NULL, &ucp, (long) size);
+ env->ReleaseByteArrayElements(bytes, tmp, 0);
+
+ return (jint) ssl_session;
+ }
+ return 0;
+}
+
+/**
+ * Gets and returns in a byte array the ID of the actual SSL session.
*/
static jbyteArray org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getid(JNIEnv* env, jobject object)
{
@@ -112,8 +167,8 @@
}
/**
- * Gets and returns in a long integer the creation's time of the
- * actual SSL session.
+ * Gets and returns in a long integer the creation's time of the
+ * actual SSL session.
*/
static jlong org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getcreationtime(JNIEnv* env, jobject object)
{
@@ -126,7 +181,7 @@
}
/**
- * Gets and returns in a string the peer's host's name.
+ * Gets and returns in a string the peer's host's name.
*/
static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeerhost(JNIEnv* env, jobject object)
{
@@ -158,7 +213,7 @@
}
/**
- * Gets and returns in a string the peer's port name (https, ftp, etc.).
+ * Gets and returns in a string the peer's port name (https, ftp, etc.).
*/
static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeerport(JNIEnv* env, jobject object)
{
@@ -179,7 +234,7 @@
bio = SSL_get_rbio(ssl);
port = BIO_get_conn_port(bio);
- /* Notice: port name can be NULL */
+ /* Notice: port name can be NULL */
result = env->NewStringUTF(port);
SSL_free(ssl);
@@ -189,7 +244,7 @@
}
/**
- * Gets and returns in a string the version of the SSL protocol. If it
+ * Gets and returns in a string the version of the SSL protocol. If it
* returns the string "unknown" it means that no connection is established.
*/
static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getprotocol(JNIEnv* env, jobject object)
@@ -218,7 +273,7 @@
}
/**
- * Gets and returns in a string the set of ciphers the actual SSL session uses.
+ * Gets and returns in a string the set of ciphers the actual SSL session uses.
*/
static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getciphersuite(JNIEnv* env, jobject object)
{
@@ -264,7 +319,9 @@
{"nativegetprotocol", "()Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getprotocol},
{"nativegetciphersuite", "()Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getciphersuite},
{"nativegetpeercertificates", "()[[B", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeercertificates},
- {"nativefree", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_free}
+ {"nativefree", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_free},
+ {"nativeserialize", "()[B", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_serialize},
+ {"nativedeserialize", "([BI)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_deserialize}
};
/**
diff --git a/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContextTest.java b/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContextTest.java
new file mode 100644
index 0000000..af4490b
--- /dev/null
+++ b/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContextTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2009 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 junit.framework.TestCase;
+
+import javax.net.ssl.SSLSession;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.HashSet;
+
+public class ClientSessionContextTest extends TestCase {
+
+ public void testGetSessionById() {
+ ClientSessionContext context = new ClientSessionContext(null, null);
+
+ SSLSession a = new FakeSession("a");
+ SSLSession b = new FakeSession("b");
+
+ context.putSession(a);
+ context.putSession(b);
+
+ assertSame(a, context.getSession("a".getBytes()));
+ assertSame(b, context.getSession("b".getBytes()));
+
+ assertSame(a, context.getSession("a", 443));
+ assertSame(b, context.getSession("b", 443));
+
+ assertEquals(2, context.sessions.size());
+
+ Set<SSLSession> sessions = new HashSet<SSLSession>();
+ Enumeration ids = context.getIds();
+ while (ids.hasMoreElements()) {
+ sessions.add(context.getSession((byte[]) ids.nextElement()));
+ }
+
+ Set<SSLSession> expected = new HashSet<SSLSession>();
+ expected.add(a);
+ expected.add(b);
+
+ assertEquals(expected, sessions);
+ }
+
+ public void testTrimToSize() {
+ ClientSessionContext context = new ClientSessionContext(null, null);
+
+ FakeSession a = new FakeSession("a");
+ FakeSession b = new FakeSession("b");
+ FakeSession c = new FakeSession("c");
+ FakeSession d = new FakeSession("d");
+
+ context.putSession(a);
+ context.putSession(b);
+ context.putSession(c);
+ context.putSession(d);
+
+ context.setSessionCacheSize(2);
+
+ Set<SSLSession> sessions = new HashSet<SSLSession>();
+ Enumeration ids = context.getIds();
+ while (ids.hasMoreElements()) {
+ sessions.add(context.getSession((byte[]) ids.nextElement()));
+ }
+
+ Set<SSLSession> expected = new HashSet<SSLSession>();
+ expected.add(c);
+ expected.add(d);
+
+ assertEquals(expected, sessions);
+ }
+
+}
diff --git a/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/FakeSession.java b/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/FakeSession.java
new file mode 100644
index 0000000..4a793dd
--- /dev/null
+++ b/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/FakeSession.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2009 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 javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
+import java.security.cert.Certificate;
+import java.security.Principal;
+
+class FakeSession implements SSLSession {
+ final String host;
+
+ FakeSession(String host) {
+ this.host = host;
+ }
+
+ public int getApplicationBufferSize() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getCipherSuite() {
+ throw new UnsupportedOperationException();
+ }
+
+ public long getCreationTime() {
+ throw new UnsupportedOperationException();
+ }
+
+ public byte[] getId() {
+ return host.getBytes();
+ }
+
+ public long getLastAccessedTime() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Certificate[] getLocalCertificates() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Principal getLocalPrincipal() {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getPacketBufferSize() {
+ throw new UnsupportedOperationException();
+ }
+
+ public javax.security.cert.X509Certificate[] getPeerCertificateChain() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Certificate[] getPeerCertificates() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getPeerHost() {
+ return host;
+ }
+
+ public int getPeerPort() {
+ return 443;
+ }
+
+ public Principal getPeerPrincipal() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getProtocol() {
+ throw new UnsupportedOperationException();
+ }
+
+ public SSLSessionContext getSessionContext() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object getValue(String name) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String[] getValueNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void invalidate() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isValid() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void putValue(String name, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void removeValue(String name) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/FileClientSessionCacheTest.java b/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/FileClientSessionCacheTest.java
new file mode 100644
index 0000000..ee50863a
--- /dev/null
+++ b/x-net/src/test/java/org/apache/harmony/xnet/provider/jsse/FileClientSessionCacheTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009 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 junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+
+public class FileClientSessionCacheTest extends TestCase {
+
+ public void testMaxSize() throws IOException, InterruptedException {
+ String tmpDir = System.getProperty("java.io.tmpdir");
+ if (tmpDir == null) {
+ fail("Please set 'java.io.tmpdir' system property.");
+ }
+ File cacheDir = new File(tmpDir
+ + "/" + FileClientSessionCacheTest.class.getName() + "/cache");
+ final SSLClientSessionCache cache
+ = FileClientSessionCache.usingDirectory(cacheDir);
+ Thread[] threads = new Thread[10];
+ final int iterations = FileClientSessionCache.MAX_SIZE * 10;
+ for (int i = 0; i < threads.length; i++) {
+ final int id = i;
+ threads[i] = new Thread() {
+ @Override
+ public void run() {
+ for (int i = 0; i < iterations; i++) {
+ cache.putSessionData(new FakeSession(id + "." + i),
+ new byte[10]);
+ }
+ }
+ };
+ }
+ for (int i = 0; i < threads.length; i++) {
+ threads[i].start();
+ }
+ for (int i = 0; i < threads.length; i++) {
+ threads[i].join();
+ }
+ assertEquals(FileClientSessionCache.MAX_SIZE, cacheDir.list().length);
+ }
+}