diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-02 22:54:18 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-02 22:54:18 -0800 |
commit | d11a65b017fde8c4c997f41ee59e87e90d68b3f5 (patch) | |
tree | 6aed8b4923ca428942cbaa7e848d50237a3d31e0 | |
parent | ac4f307115a738b0206030fe0d127f7c7f035b03 (diff) | |
download | libcore-d11a65b017fde8c4c997f41ee59e87e90d68b3f5.zip libcore-d11a65b017fde8c4c997f41ee59e87e90d68b3f5.tar.gz libcore-d11a65b017fde8c4c997f41ee59e87e90d68b3f5.tar.bz2 |
auto import from //depot/cupcake/@137055
30 files changed, 2146 insertions, 512 deletions
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 @@ static UBool icuError(JNIEnv *env, UErrorCode errorcode) 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 @@ static jint openDecimalFormatImpl(JNIEnv *env, jclass clazz, jstring locale, 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 jint openDecimalFormatImpl(JNIEnv *env, jclass clazz, jstring locale, 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 @@ static void setSymbol(JNIEnv *env, jclass clazz, jint addr, jint symbol, 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 @@ static jstring getSymbol(JNIEnv *env, jclass clazz, jint addr, jint symbol) { // 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 @@ static jstring getSymbol(JNIEnv *env, jclass clazz, jint addr, jint symbol) { 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 @@ static jstring getSymbol(JNIEnv *env, jclass clazz, jint addr, jint symbol) { 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 @@ static jint getAttribute(JNIEnv *env, jclass clazz, jint addr, jint symbol) { 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 @@ static jstring getTextAttribute(JNIEnv *env, jclass clazz, jint addr, // 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 @@ static jstring getTextAttribute(JNIEnv *env, jclass clazz, jint addr, 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 @@ static jstring getTextAttribute(JNIEnv *env, jclass clazz, jint addr, 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 @@ static void applyPatternImpl(JNIEnv *env, jclass clazz, jint addr, 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 @@ static jstring toPatternImpl(JNIEnv *env, jclass clazz, jint addr, // 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 @@ static jstring toPatternImpl(JNIEnv *env, jclass clazz, jint addr, 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 @@ static jstring toPatternImpl(JNIEnv *env, jclass clazz, jint addr, 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 @@ static jstring formatLong(JNIEnv *env, jclass clazz, jint addr, jlong value, 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 @@ static jstring formatLong(JNIEnv *env, jclass clazz, jint addr, jlong value, 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 @@ static jstring formatDouble(JNIEnv *env, jclass clazz, jint addr, jdouble value, 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 @@ static jstring formatDouble(JNIEnv *env, jclass clazz, jint addr, jdouble value, 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 @@ static jstring formatDigitList(JNIEnv *env, jclass clazz, jint addr, jstring val 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 @@ static jstring formatDigitList(JNIEnv *env, jclass clazz, jint addr, jstring val 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 @@ static jstring formatDigitList(JNIEnv *env, jclass clazz, jint addr, jstring val 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 @@ static jstring formatDigitList(JNIEnv *env, jclass clazz, jint addr, jstring val } free(attrBuffer->buffer); free(attrBuffer); - return NULL; + return NULL; } int attrLength = (strlen(attrBuffer->buffer) + 1 ); @@ -596,7 +610,7 @@ static jstring formatDigitList(JNIEnv *env, jclass clazz, jint addr, jstring val // 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 @@ static jstring formatDigitList(JNIEnv *env, jclass clazz, jint addr, jstring val 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 @@ static jobject parse(JNIEnv *env, jclass clazz, jint addr, jstring text, 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 @@ static jobject parse(JNIEnv *env, jclass clazz, jint addr, jstring text, 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 @@ static jobject parse(JNIEnv *env, jclass clazz, jint addr, jstring text, 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 @@ static jobject parse(JNIEnv *env, jclass clazz, jint addr, jstring text, 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 @@ static jobject parse(JNIEnv *env, jclass clazz, jint addr, jstring text, 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 jint cloneImpl(JNIEnv *env, jclass clazz, jint addr) { 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 @@ static JNINativeMethod gMethods[] = { {"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 @@ public class BufferedInputStream extends FilterInputStream { 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 @@ public class BufferedOutputStream extends FilterOutputStream { 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 @@ public class BufferedReader extends Reader { 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 @@ public class BufferedWriter extends Writer { 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 @@ public class ArrayList<E> extends AbstractList<E> implements List<E>, Cloneable, * @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 @@ public class StringTest extends TestCase { } 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 @@ mkdir $tmp 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 @@ public class X509NameTokenizer { 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 @@ public class X509NameTokenizer 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. + */ + 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 boolean verify(String hostname, SSLSession session) { - return false; + 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 @@ import java.security.Security; // 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 @@ public abstract class SSLSocketFactory extends SocketFactory { * @since Android 1.0 */ public static SocketFactory getDefault() { - 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; + 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; } - return null; - } - }); - } + }); + } - if (defaultSocketFactory == null) { - // Try to find in providers - SSLContext context = DefaultSSLContext.getContext(); - if (context != null) { - defaultSocketFactory = context.getSocketFactory(); + 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 (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 @@ public class ClientHandshakeImpl extends HandshakeProtocol { 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 @@ public class OpenSSLServerSocketImpl extends javax.net.ssl.SSLServerSocket { @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.Principal; 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 @@ public class OpenSSLSessionImpl implements SSLSession { 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 @@ public class OpenSSLSessionImpl implements SSLSession { 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 @@ public class OpenSSLSessionImpl implements SSLSession { if (sm != null) { sm.checkPermission(new SSLPermission("getSSLSessionContext")); } - return sslParameters.getClientSessionContext(); + return sessionContext; } /** @@ -347,7 +402,7 @@ public class OpenSSLSessionImpl implements SSLSession { * @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 @@ public class OpenSSLSessionImpl implements SSLSession { * 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 @@ public class OpenSSLSessionImpl implements SSLSession { * -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 @@ public class OpenSSLSessionImpl implements SSLSession { * 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.IOException; 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.CertificateException; 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { /** * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { /** * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { /** * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { /** * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { /** * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { /** * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { * * @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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { 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; + + LoggerHolder.logger.fine("Reused cached session for " + + getInetAddress().getHostName() + "."); } 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()); + 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(); + X509Certificate[] peerCertificates = (X509Certificate[]) + sslSession.getPeerCertificates(); - if (peerCertificates == null || peerCertificates.length == 0) { + 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { /** * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { /** * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { /** * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { * 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 @@ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { } /** - * 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.SSLSessionContext; 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 class SSLContextImpl extends SSLContextSpi { 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 @@ public class SSLContextImpl extends SSLContextSpi { 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 @@ import javax.net.ssl.X509TrustManager; * 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 @@ public class SSLParameters { // 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 @@ public class SSLParameters { // 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 @@ public class SSLParameters { 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 @@ public class SSLParameters { 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 @@ public class SSLParameters { /** * @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 @@ public class SSLParameters { /** * 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 @@ public class SSLParameters { * @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.SSLSessionBindingListener; import javax.net.ssl.SSLSessionContext; import org.apache.harmony.luni.util.TwoKeyHashMap; -import org.apache.harmony.xnet.provider.jsse.SSLSessionContextImpl; /** * @@ -83,8 +82,9 @@ public class SSLSessionImpl implements SSLSession { /** * 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 @@ public class ServerHandshakeImpl extends HandshakeProtocol { /** * Start session negotiation - * @param session */ public void start() { if (session == null) { // initial handshake @@ -299,7 +298,7 @@ public class ServerHandshakeImpl extends HandshakeProtocol { 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 @@ static void throwIOExceptionStr(JNIEnv* env, const char* message) } /** - * 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 @@ static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_get } /** - * 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 @@ static jbyteArray org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getid } /** - * 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 @@ static jlong org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getcreatio } /** - * 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 @@ static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeerh } /** - * 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 @@ static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeerp 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 @@ static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeerp } /** - * 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 @@ static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getproto } /** - * 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 @@ static JNINativeMethod sMethods[] = {"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..ee50863 --- /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); + } +} |