diff options
48 files changed, 2528 insertions, 476 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index fbbc2a3d9dc4..de83f3e01074 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -17,3 +17,5 @@ strings_lint_hook = ${REPO_ROOT}/frameworks/base/tools/stringslint/stringslint_s hidden_api_txt_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT} ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES} + +owners_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "OWNERS$" diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt index a78773692ef3..9ca0745c04f4 100644 --- a/config/boot-image-profile.txt +++ b/config/boot-image-profile.txt @@ -12204,11 +12204,11 @@ HPLorg/json/JSONStringer;->value(J)Lorg/json/JSONStringer; HPLorg/json/JSONStringer;->value(Z)Lorg/json/JSONStringer; HPLorg/json/JSONTokener;->syntaxError(Ljava/lang/String;)Lorg/json/JSONException; HPLorg/json/JSONTokener;->toString()Ljava/lang/String; -HPLorg/kxml2/io/KXmlSerializer;->getDepth()I -HPLorg/kxml2/io/KXmlSerializer;->getNamespace()Ljava/lang/String; -HPLorg/kxml2/io/KXmlSerializer;->getPrefix(Ljava/lang/String;ZZ)Ljava/lang/String; -HPLorg/kxml2/io/KXmlSerializer;->setPrefix(Ljava/lang/String;Ljava/lang/String;)V -HPLorg/kxml2/io/KXmlSerializer;->text(Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; +HPLcom/android/org/kxml2/io/KXmlSerializer;->getDepth()I +HPLcom/android/org/kxml2/io/KXmlSerializer;->getNamespace()Ljava/lang/String; +HPLcom/android/org/kxml2/io/KXmlSerializer;->getPrefix(Ljava/lang/String;ZZ)Ljava/lang/String; +HPLcom/android/org/kxml2/io/KXmlSerializer;->setPrefix(Ljava/lang/String;Ljava/lang/String;)V +HPLcom/android/org/kxml2/io/KXmlSerializer;->text(Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; HPLorg/w3c/dom/NamedNodeMap;->getLength()I HPLorg/w3c/dom/NamedNodeMap;->getNamedItem(Ljava/lang/String;)Lorg/w3c/dom/Node; HPLorg/w3c/dom/NamedNodeMap;->getNamedItemNS(Ljava/lang/String;Ljava/lang/String;)Lorg/w3c/dom/Node; @@ -51896,7 +51896,7 @@ HSPLorg/apache/harmony/xml/dom/NodeListImpl;->item(I)Lorg/w3c/dom/Node; HSPLorg/apache/harmony/xml/dom/TextImpl;->getNodeType()S HSPLorg/apache/harmony/xml/parsers/DocumentBuilderFactoryImpl;->newDocumentBuilder()Ljavax/xml/parsers/DocumentBuilder; HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->appendText(Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;ILjava/lang/String;)V -HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lorg/kxml2/io/KXmlParser;Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;I)V +HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lcom/android/org/kxml2/io/KXmlParser;Lorg/apache/harmony/xml/dom/DocumentImpl;Lorg/w3c/dom/Node;I)V HSPLorg/apache/harmony/xml/parsers/DocumentBuilderImpl;->parse(Lorg/xml/sax/InputSource;)Lorg/w3c/dom/Document; HSPLorg/apache/harmony/xml/parsers/SAXParserFactoryImpl;->getFeature(Ljava/lang/String;)Z HSPLorg/apache/harmony/xml/parsers/SAXParserFactoryImpl;->isValidating()Z @@ -52176,64 +52176,64 @@ HSPLorg/json/JSONTokener;->readEscapeCharacter()C HSPLorg/json/JSONTokener;->readLiteral()Ljava/lang/Object; HSPLorg/json/JSONTokener;->readObject()Lorg/json/JSONObject; HSPLorg/json/JSONTokener;->skipToEndOfLine()V -HSPLorg/kxml2/io/KXmlParser$ValueContext;-><init>(Ljava/lang/String;I)V -HSPLorg/kxml2/io/KXmlParser;-><init>()V -HSPLorg/kxml2/io/KXmlParser;->adjustNsp()Z -HSPLorg/kxml2/io/KXmlParser;->close()V -HSPLorg/kxml2/io/KXmlParser;->fillBuffer(I)Z -HSPLorg/kxml2/io/KXmlParser;->getAttributeCount()I -HSPLorg/kxml2/io/KXmlParser;->getAttributeName(I)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getAttributeNamespace(I)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getAttributePrefix(I)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getAttributeType(I)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getAttributeValue(I)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getAttributeValue(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getColumnNumber()I -HSPLorg/kxml2/io/KXmlParser;->getDepth()I -HSPLorg/kxml2/io/KXmlParser;->getEventType()I -HSPLorg/kxml2/io/KXmlParser;->getFeature(Ljava/lang/String;)Z -HSPLorg/kxml2/io/KXmlParser;->getLineNumber()I -HSPLorg/kxml2/io/KXmlParser;->getName()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getNamespace()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getNamespace(Ljava/lang/String;)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getNamespaceCount(I)I -HSPLorg/kxml2/io/KXmlParser;->getPositionDescription()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getPrefix()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getText()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->getTextCharacters([I)[C -HSPLorg/kxml2/io/KXmlParser;->next()I -HSPLorg/kxml2/io/KXmlParser;->next(Z)I -HSPLorg/kxml2/io/KXmlParser;->nextTag()I -HSPLorg/kxml2/io/KXmlParser;->nextText()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->nextToken()I -HSPLorg/kxml2/io/KXmlParser;->parseStartTag(ZZ)V -HSPLorg/kxml2/io/KXmlParser;->peekType(Z)I -HSPLorg/kxml2/io/KXmlParser;->read(C)V -HSPLorg/kxml2/io/KXmlParser;->read([C)V -HSPLorg/kxml2/io/KXmlParser;->readComment(Z)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->readEndTag()V -HSPLorg/kxml2/io/KXmlParser;->readEntity(Ljava/lang/StringBuilder;ZZLorg/kxml2/io/KXmlParser$ValueContext;)V -HSPLorg/kxml2/io/KXmlParser;->readName()Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->readUntil([CZ)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->readValue(CZZLorg/kxml2/io/KXmlParser$ValueContext;)Ljava/lang/String; -HSPLorg/kxml2/io/KXmlParser;->readXmlDeclaration()V -HSPLorg/kxml2/io/KXmlParser;->require(ILjava/lang/String;Ljava/lang/String;)V -HSPLorg/kxml2/io/KXmlParser;->setFeature(Ljava/lang/String;Z)V -HSPLorg/kxml2/io/KXmlParser;->setInput(Ljava/io/InputStream;Ljava/lang/String;)V -HSPLorg/kxml2/io/KXmlParser;->setInput(Ljava/io/Reader;)V -HSPLorg/kxml2/io/KXmlSerializer;-><init>()V -HSPLorg/kxml2/io/KXmlSerializer;->append(Ljava/lang/String;II)V -HSPLorg/kxml2/io/KXmlSerializer;->attribute(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; -HSPLorg/kxml2/io/KXmlSerializer;->check(Z)V -HSPLorg/kxml2/io/KXmlSerializer;->endDocument()V -HSPLorg/kxml2/io/KXmlSerializer;->endTag(Ljava/lang/String;Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; -HSPLorg/kxml2/io/KXmlSerializer;->flush()V -HSPLorg/kxml2/io/KXmlSerializer;->setFeature(Ljava/lang/String;Z)V -HSPLorg/kxml2/io/KXmlSerializer;->setOutput(Ljava/io/OutputStream;Ljava/lang/String;)V -HSPLorg/kxml2/io/KXmlSerializer;->setOutput(Ljava/io/Writer;)V -HSPLorg/kxml2/io/KXmlSerializer;->startDocument(Ljava/lang/String;Ljava/lang/Boolean;)V -HSPLorg/kxml2/io/KXmlSerializer;->startTag(Ljava/lang/String;Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; -HSPLorg/kxml2/io/KXmlSerializer;->writeEscaped(Ljava/lang/String;I)V +HSPLcom/android/org/kxml2/io/KXmlParser$ValueContext;-><init>(Ljava/lang/String;I)V +HSPLcom/android/org/kxml2/io/KXmlParser;-><init>()V +HSPLcom/android/org/kxml2/io/KXmlParser;->adjustNsp()Z +HSPLcom/android/org/kxml2/io/KXmlParser;->close()V +HSPLcom/android/org/kxml2/io/KXmlParser;->fillBuffer(I)Z +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeCount()I +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeName(I)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeNamespace(I)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributePrefix(I)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeType(I)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeValue(I)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getAttributeValue(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getColumnNumber()I +HSPLcom/android/org/kxml2/io/KXmlParser;->getDepth()I +HSPLcom/android/org/kxml2/io/KXmlParser;->getEventType()I +HSPLcom/android/org/kxml2/io/KXmlParser;->getFeature(Ljava/lang/String;)Z +HSPLcom/android/org/kxml2/io/KXmlParser;->getLineNumber()I +HSPLcom/android/org/kxml2/io/KXmlParser;->getName()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getNamespace()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getNamespace(Ljava/lang/String;)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getNamespaceCount(I)I +HSPLcom/android/org/kxml2/io/KXmlParser;->getPositionDescription()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getPrefix()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getText()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->getTextCharacters([I)[C +HSPLcom/android/org/kxml2/io/KXmlParser;->next()I +HSPLcom/android/org/kxml2/io/KXmlParser;->next(Z)I +HSPLcom/android/org/kxml2/io/KXmlParser;->nextTag()I +HSPLcom/android/org/kxml2/io/KXmlParser;->nextText()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->nextToken()I +HSPLcom/android/org/kxml2/io/KXmlParser;->parseStartTag(ZZ)V +HSPLcom/android/org/kxml2/io/KXmlParser;->peekType(Z)I +HSPLcom/android/org/kxml2/io/KXmlParser;->read(C)V +HSPLcom/android/org/kxml2/io/KXmlParser;->read([C)V +HSPLcom/android/org/kxml2/io/KXmlParser;->readComment(Z)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->readEndTag()V +HSPLcom/android/org/kxml2/io/KXmlParser;->readEntity(Ljava/lang/StringBuilder;ZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)V +HSPLcom/android/org/kxml2/io/KXmlParser;->readName()Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->readUntil([CZ)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->readValue(CZZLcom/android/org/kxml2/io/KXmlParser$ValueContext;)Ljava/lang/String; +HSPLcom/android/org/kxml2/io/KXmlParser;->readXmlDeclaration()V +HSPLcom/android/org/kxml2/io/KXmlParser;->require(ILjava/lang/String;Ljava/lang/String;)V +HSPLcom/android/org/kxml2/io/KXmlParser;->setFeature(Ljava/lang/String;Z)V +HSPLcom/android/org/kxml2/io/KXmlParser;->setInput(Ljava/io/InputStream;Ljava/lang/String;)V +HSPLcom/android/org/kxml2/io/KXmlParser;->setInput(Ljava/io/Reader;)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;-><init>()V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->append(Ljava/lang/String;II)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->attribute(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; +HSPLcom/android/org/kxml2/io/KXmlSerializer;->check(Z)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->endDocument()V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->endTag(Ljava/lang/String;Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; +HSPLcom/android/org/kxml2/io/KXmlSerializer;->flush()V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->setFeature(Ljava/lang/String;Z)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->setOutput(Ljava/io/OutputStream;Ljava/lang/String;)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->setOutput(Ljava/io/Writer;)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->startDocument(Ljava/lang/String;Ljava/lang/Boolean;)V +HSPLcom/android/org/kxml2/io/KXmlSerializer;->startTag(Ljava/lang/String;Ljava/lang/String;)Lorg/xmlpull/v1/XmlSerializer; +HSPLcom/android/org/kxml2/io/KXmlSerializer;->writeEscaped(Ljava/lang/String;I)V HSPLorg/w3c/dom/Attr;->getName()Ljava/lang/String; HSPLorg/w3c/dom/Attr;->getOwnerElement()Lorg/w3c/dom/Element; HSPLorg/w3c/dom/Attr;->getSchemaTypeInfo()Lorg/w3c/dom/TypeInfo; @@ -63397,9 +63397,9 @@ Lorg/json/JSONObject; Lorg/json/JSONStringer$Scope; Lorg/json/JSONStringer; Lorg/json/JSONTokener; -Lorg/kxml2/io/KXmlParser$ValueContext; -Lorg/kxml2/io/KXmlParser; -Lorg/kxml2/io/KXmlSerializer; +Lcom/android/org/kxml2/io/KXmlParser$ValueContext; +Lcom/android/org/kxml2/io/KXmlParser; +Lcom/android/org/kxml2/io/KXmlSerializer; Lorg/w3c/dom/CharacterData; Lorg/w3c/dom/DOMImplementation; Lorg/w3c/dom/Document; diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index 3a96cd3f3eca..7bf506453a05 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -455,59 +455,6 @@ Landroid/database/IContentObserver$Stub;->asInterface(Landroid/os/IBinder;)Landr Landroid/database/IContentObserver;->onChange(ZLandroid/net/Uri;I)V Landroid/hardware/biometrics/BiometricConstants;->BIOMETRIC_ERROR_VENDOR_BASE:I Landroid/hardware/biometrics/BiometricFingerprintConstants;->FINGERPRINT_ERROR_VENDOR_BASE:I -Landroid/hardware/camera2/CameraCharacteristics;->CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->CONTROL_MAX_REGIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->LED_AVAILABLE_LEDS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->LENS_INFO_SHADING_MAP_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->LOGICAL_MULTI_CAMERA_PHYSICAL_IDS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->QUIRKS_USE_PARTIAL_RESULT:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->REQUEST_AVAILABLE_CHARACTERISTICS_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->REQUEST_AVAILABLE_REQUEST_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->REQUEST_AVAILABLE_RESULT_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->REQUEST_AVAILABLE_SESSION_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->REQUEST_MAX_NUM_OUTPUT_STREAMS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_FORMATS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_JPEG_MIN_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_JPEG_SIZES:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_PROCESSED_SIZES:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CameraCharacteristics;->SCALER_AVAILABLE_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key; -Landroid/hardware/camera2/CaptureRequest;->JPEG_GPS_COORDINATES:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->JPEG_GPS_PROCESSING_METHOD:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->JPEG_GPS_TIMESTAMP:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->LED_TRANSMIT:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->REQUEST_ID:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->TONEMAP_CURVE_BLUE:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->TONEMAP_CURVE_GREEN:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureRequest;->TONEMAP_CURVE_RED:Landroid/hardware/camera2/CaptureRequest$Key; -Landroid/hardware/camera2/CaptureResult;->JPEG_GPS_COORDINATES:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->JPEG_GPS_PROCESSING_METHOD:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->JPEG_GPS_TIMESTAMP:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->LED_TRANSMIT:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->QUIRKS_PARTIAL_RESULT:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->REQUEST_FRAME_COUNT:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->REQUEST_ID:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_FACE_IDS:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_FACE_LANDMARKS:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_FACE_RECTANGLES:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_FACE_SCORES:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_LENS_SHADING_MAP:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_OIS_TIMESTAMPS:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_OIS_X_SHIFTS:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_OIS_Y_SHIFTS:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_PREDICTED_COLOR_GAINS:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->STATISTICS_PREDICTED_COLOR_TRANSFORM:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->SYNC_FRAME_NUMBER:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->TONEMAP_CURVE_BLUE:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->TONEMAP_CURVE_GREEN:Landroid/hardware/camera2/CaptureResult$Key; -Landroid/hardware/camera2/CaptureResult;->TONEMAP_CURVE_RED:Landroid/hardware/camera2/CaptureResult$Key; Landroid/hardware/display/IDisplayManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/display/IDisplayManager; Landroid/hardware/display/IDisplayManager;->getDisplayInfo(I)Landroid/view/DisplayInfo; Landroid/hardware/fingerprint/IFingerprintService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V @@ -2480,7 +2427,6 @@ Lcom/android/internal/textservice/ITextServicesManager$Stub$Proxy;-><init>(Landr Lcom/android/internal/util/HexDump;->toHexString([BZ)Ljava/lang/String; Lcom/android/internal/view/BaseIWindow;-><init>()V Lcom/android/internal/view/IInputMethod$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/view/IInputMethod; -Lcom/android/internal/view/IInputMethod;->attachToken(Landroid/os/IBinder;)V Lcom/android/internal/view/IInputMethod;->bindInput(Landroid/view/inputmethod/InputBinding;)V Lcom/android/internal/view/IInputMethod;->hideSoftInput(ILandroid/os/ResultReceiver;)V Lcom/android/internal/view/IInputMethod;->setSessionEnabled(Lcom/android/internal/view/IInputMethodSession;Z)V diff --git a/core/java/android/net/IpConfiguration.java b/core/java/android/net/IpConfiguration.java index 7543920e363f..3319f33878c1 100644 --- a/core/java/android/net/IpConfiguration.java +++ b/core/java/android/net/IpConfiguration.java @@ -35,7 +35,7 @@ public class IpConfiguration implements Parcelable { * with staticIpConfiguration */ @UnsupportedAppUsage STATIC, - /* Use dynamically configured IP settigns */ + /* Use dynamically configured IP settings */ DHCP, /* no IP details are assigned, this is used to indicate * that any existing IP settings should be retained */ diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index 599ccb287a26..34e9476b3e08 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -22,6 +22,7 @@ import android.util.Log; import android.util.Pair; import java.io.FileDescriptor; +import java.io.IOException; import java.math.BigInteger; import java.net.Inet4Address; import java.net.Inet6Address; @@ -131,6 +132,17 @@ public class NetworkUtils { public native static boolean queryUserAccess(int uid, int netId); /** + * Add an entry into the ARP cache. + */ + public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname, + FileDescriptor fd) throws IOException { + addArpEntry(ethAddr.toByteArray(), ipv4Addr.getAddress(), ifname, fd); + } + + private static native void addArpEntry(byte[] ethAddr, byte[] netAddr, String ifname, + FileDescriptor fd) throws IOException; + + /** * @see #intToInet4AddressHTL(int) * @deprecated Use either {@link #intToInet4AddressHTH(int)} * or {@link #intToInet4AddressHTL(int)} @@ -149,7 +161,7 @@ public class NetworkUtils { * @param hostAddress an int coding for an IPv4 address, where higher-order int byte is * lower-order IPv4 address byte */ - public static InetAddress intToInet4AddressHTL(int hostAddress) { + public static Inet4Address intToInet4AddressHTL(int hostAddress) { return intToInet4AddressHTH(Integer.reverseBytes(hostAddress)); } @@ -157,14 +169,14 @@ public class NetworkUtils { * Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4) * @param hostAddress an int coding for an IPv4 address */ - public static InetAddress intToInet4AddressHTH(int hostAddress) { + public static Inet4Address intToInet4AddressHTH(int hostAddress) { byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)), (byte) (0xff & (hostAddress >> 16)), (byte) (0xff & (hostAddress >> 8)), (byte) (0xff & hostAddress) }; try { - return InetAddress.getByAddress(addressBytes); + return (Inet4Address) InetAddress.getByAddress(addressBytes); } catch (UnknownHostException e) { throw new AssertionError(); } @@ -397,6 +409,28 @@ public class NetworkUtils { } /** + * Get a prefix mask as Inet4Address for a given prefix length. + * + * <p>For example 20 -> 255.255.240.0 + */ + public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength) + throws IllegalArgumentException { + return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength)); + } + + /** + * Get the broadcast address for a given prefix. + * + * <p>For example 192.168.0.1/24 -> 192.168.0.255 + */ + public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength) + throws IllegalArgumentException { + final int intBroadcastAddr = inet4AddressToIntHTH(addr) + | ~prefixLengthToV4NetmaskIntHTH(prefixLength); + return intToInet4AddressHTH(intBroadcastAddr); + } + + /** * Check if IP address type is consistent between two InetAddress. * @return true if both are the same type. False otherwise. */ diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 067e8493717b..732d3778ec6d 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -384,7 +384,10 @@ public class ZygoteProcess { argsForZygote.add("--mount-external-read"); } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) { argsForZygote.add("--mount-external-write"); + } else if (mountExternal == Zygote.MOUNT_EXTERNAL_FULL) { + argsForZygote.add("--mount-external-full"); } + argsForZygote.add("--target-sdk-version=" + targetSdkVersion); // --setgroups is a comma-separated list diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index f89a9d990b76..0c6f832ff33a 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -374,8 +374,12 @@ public abstract class FileSystemProvider extends DocumentsProvider { final File parent = getFileForDocId(parentDocumentId); final MatrixCursor result = new DirectoryCursor( resolveProjection(projection), parentDocumentId, parent); - for (File file : parent.listFiles()) { - includeFile(result, null, file); + if (parent.isDirectory()) { + for (File file : FileUtils.listFilesOrEmpty(parent)) { + includeFile(result, null, file); + } + } else { + Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory"); } return result; } diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 413f89da571e..927322e97e28 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -81,6 +81,8 @@ public final class Zygote { public static final int MOUNT_EXTERNAL_READ = IVold.REMOUNT_MODE_READ; /** Read-write external storage should be mounted. */ public static final int MOUNT_EXTERNAL_WRITE = IVold.REMOUNT_MODE_WRITE; + /** Read-write external storage should be mounted instead of package sandbox */ + public static final int MOUNT_EXTERNAL_FULL = IVold.REMOUNT_MODE_FULL; private static final ZygoteHooks VM_HOOKS = new ZygoteHooks(); diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index b9c717f03749..b60b43a8d45d 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -644,7 +644,9 @@ class ZygoteConnection { mountExternal = Zygote.MOUNT_EXTERNAL_READ; } else if (arg.equals("--mount-external-write")) { mountExternal = Zygote.MOUNT_EXTERNAL_WRITE; - } else if (arg.equals("--query-abi-list")) { + } else if (arg.equals("--mount-external-full")) { + mountExternal = Zygote.MOUNT_EXTERNAL_FULL; + } else if (arg.equals("--query-abi-list")) { abiListQuery = true; } else if (arg.equals("--get-pid")) { pidQuery = true; diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 823f1cc36225..9b138ebb760a 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -323,6 +323,55 @@ static jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jin return (jboolean) !queryUserAccess(uid, netId); } +static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst) +{ + if (env->GetArrayLength(addr) != len) { + return false; + } + env->GetByteArrayRegion(addr, 0, len, reinterpret_cast<jbyte*>(dst)); + return true; +} + +static void android_net_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray ethAddr, + jbyteArray ipv4Addr, jstring ifname, jobject javaFd) +{ + struct arpreq req = {}; + struct sockaddr_in& netAddrStruct = *reinterpret_cast<sockaddr_in*>(&req.arp_pa); + struct sockaddr& ethAddrStruct = req.arp_ha; + + ethAddrStruct.sa_family = ARPHRD_ETHER; + if (!checkLenAndCopy(env, ethAddr, ETH_ALEN, ethAddrStruct.sa_data)) { + jniThrowException(env, "java/io/IOException", "Invalid ethAddr length"); + return; + } + + netAddrStruct.sin_family = AF_INET; + if (!checkLenAndCopy(env, ipv4Addr, sizeof(in_addr), &netAddrStruct.sin_addr)) { + jniThrowException(env, "java/io/IOException", "Invalid ipv4Addr length"); + return; + } + + int ifLen = env->GetStringLength(ifname); + // IFNAMSIZ includes the terminating NULL character + if (ifLen >= IFNAMSIZ) { + jniThrowException(env, "java/io/IOException", "ifname too long"); + return; + } + env->GetStringUTFRegion(ifname, 0, ifLen, req.arp_dev); + + req.arp_flags = ATF_COM; // Completed entry (ha valid) + int fd = jniGetFDFromFileDescriptor(env, javaFd); + if (fd < 0) { + jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor"); + return; + } + // See also: man 7 arp + if (ioctl(fd, SIOCSARP, &req)) { + jniThrowExceptionFmt(env, "java/io/IOException", "ioctl error: %s", strerror(errno)); + return; + } +} + // ---------------------------------------------------------------------------- @@ -337,6 +386,7 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "bindSocketToNetwork", "(II)I", (void*) android_net_utils_bindSocketToNetwork }, { "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn }, { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess }, + { "addArpEntry", "([B[BLjava/lang/String;Ljava/io/FileDescriptor;)V", (void*) android_net_utils_addArpEntry }, { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter }, { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter }, { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter }, diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 19691e2cfbe3..364393e1c649 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -93,6 +93,7 @@ enum MountExternalKind { MOUNT_EXTERNAL_DEFAULT = 1, MOUNT_EXTERNAL_READ = 2, MOUNT_EXTERNAL_WRITE = 3, + MOUNT_EXTERNAL_FULL = 4, }; static void RuntimeAbort(JNIEnv* env, int line, const char* msg) { @@ -416,7 +417,7 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, storageSource = "/mnt/runtime/read"; } else if (mount_mode == MOUNT_EXTERNAL_WRITE) { storageSource = "/mnt/runtime/write"; - } else if (!force_mount_namespace) { + } else if (mount_mode != MOUNT_EXTERNAL_FULL && !force_mount_namespace) { // Sane default of no storage visible return true; } @@ -433,19 +434,44 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, } if (GetBoolProperty(kIsolatedStorage, false)) { - if (package_name == nullptr) { - return true; - } - - std::string pkgSandboxDir("/mnt/user"); - if (!createPkgSandbox(uid, package_name, pkgSandboxDir, error_msg)) { - return false; - } - if (TEMP_FAILURE_RETRY(mount(pkgSandboxDir.c_str(), "/storage", - nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", - pkgSandboxDir.c_str(), strerror(errno)); - return false; + if (mount_mode == MOUNT_EXTERNAL_FULL) { + storageSource = "/mnt/runtime/write"; + if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", + NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) { + *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", + storageSource.string(), + strerror(errno)); + return false; + } + + // Mount user-specific symlink helper into place + userid_t user_id = multiuser_get_user_id(uid); + const String8 userSource(String8::format("/mnt/user/%d", user_id)); + if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) { + *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string()); + return false; + } + if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", + NULL, MS_BIND, NULL)) == -1) { + *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s", + userSource.string(), + strerror(errno)); + return false; + } + } else { + if (package_name == nullptr) { + return true; + } + std::string pkgSandboxDir("/mnt/user"); + if (!createPkgSandbox(uid, package_name, pkgSandboxDir, error_msg)) { + return false; + } + if (TEMP_FAILURE_RETRY(mount(pkgSandboxDir.c_str(), "/storage", + nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) { + *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", + pkgSandboxDir.c_str(), strerror(errno)); + return false; + } } } else { if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index c6931aa97a8b..c664abf55100 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3517,4 +3517,11 @@ <!-- Whether or not battery saver should be "sticky" when manually enabled. --> <bool name="config_batterySaverStickyBehaviourDisabled">false</bool> + + <!-- Model of potentially misprovisioned devices. If none is specified in an overlay, an + empty string is passed in. --> + <string name="config_misprovisionedDeviceModel" translatable="false"></string> + + <!-- Brand value for attestation of misprovisioned device. --> + <string name="config_misprovisionedBrandValue" translatable="false"></string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7fbad164add1..9cf6ac6a61af 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3446,4 +3446,7 @@ <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" /> <java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" /> + + <java-symbol type="string" name="config_misprovisionedDeviceModel" /> + <java-symbol type="string" name="config_misprovisionedBrandValue" /> </resources> diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java index 1be8309bcf5a..3d2a2718a9c8 100644 --- a/keystore/java/android/security/keystore/AttestationUtils.java +++ b/keystore/java/android/security/keystore/AttestationUtils.java @@ -22,9 +22,9 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.content.Context; +import android.content.res.Resources; import android.os.Build; import android.security.KeyStore; -import android.security.KeyStoreException; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterCertificateChain; import android.security.keymaster.KeymasterDefs; @@ -117,6 +117,37 @@ public abstract class AttestationUtils { @NonNull public static KeymasterArguments prepareAttestationArguments(Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws DeviceIdAttestationException { + return prepareAttestationArguments(context, idTypes,attestationChallenge, Build.BRAND); + } + + /** + * Prepares Keymaster Arguments with attestation data for misprovisioned Pixel 2 device. + * See http://go/keyAttestationFailure and http://b/69471841 for more info. + * @hide should only be used by KeyChain. + */ + @NonNull public static KeymasterArguments prepareAttestationArgumentsIfMisprovisioned( + Context context, @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws + DeviceIdAttestationException { + if (!isPotentiallyMisprovisionedDevice(context)) { + return null; + } + Resources resources = context.getResources(); + String misprovisionedBrand = resources.getString( + com.android.internal.R.string.config_misprovisionedBrandValue); + return prepareAttestationArguments( + context, idTypes, attestationChallenge, misprovisionedBrand); + } + + @NonNull private static boolean isPotentiallyMisprovisionedDevice(Context context) { + Resources resources = context.getResources(); + String misprovisionedModel = resources.getString( + com.android.internal.R.string.config_misprovisionedDeviceModel); + return (Build.MODEL.equals(misprovisionedModel)); + } + + @NonNull private static KeymasterArguments prepareAttestationArguments(Context context, + @NonNull int[] idTypes, @NonNull byte[] attestationChallenge, String brand) throws + DeviceIdAttestationException { // Check method arguments, retrieve requested device IDs and prepare attestation arguments. if (attestationChallenge == null) { throw new NullPointerException("Missing attestation challenge"); @@ -169,7 +200,7 @@ public abstract class AttestationUtils { } } attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND, - Build.BRAND.getBytes(StandardCharsets.UTF_8)); + brand.getBytes(StandardCharsets.UTF_8)); attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE, Build.DEVICE.getBytes(StandardCharsets.UTF_8)); attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT, diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java index a91c45dd59af..a84222909072 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java @@ -57,9 +57,7 @@ public interface BluetoothCallback { default void onDeviceAdded(CachedBluetoothDevice cachedDevice) {} /** - * It will be called when a remote device that was - * found in the last discovery and is not found in the current discovery. - * It is listening {@link android.bluetooth.BluetoothDevice#ACTION_DISAPPEARED} + * It will be called when requiring to remove a remote device from CachedBluetoothDevice list * * @param cachedDevice the Bluetooth device. */ diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index ac08f6aed029..25e683100ee8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -81,7 +81,6 @@ public class BluetoothEventManager { addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true)); addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false)); addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler()); - addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler()); addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler()); addHandler(BluetoothDevice.ACTION_ALIAS_CHANGED, new NameChangedHandler()); @@ -299,24 +298,6 @@ public class BluetoothEventManager { } } - private class DeviceDisappearedHandler implements Handler { - public void onReceive(Context context, Intent intent, - BluetoothDevice device) { - CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); - if (cachedDevice == null) { - Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device); - return; - } - if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) { - synchronized (mCallbacks) { - for (BluetoothCallback callback : mCallbacks) { - callback.onDeviceDeleted(cachedDevice); - } - } - } - } - } - private class NameChangedHandler implements Handler { public void onReceive(Context context, Intent intent, BluetoothDevice device) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index 9bf781678962..5ffdc7b221f3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -145,6 +145,8 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit mLockPatternView = findViewById(R.id.lockPatternView); mLockPatternView.setSaveEnabled(false); mLockPatternView.setOnPatternListener(new UnlockPatternListener()); + mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( + KeyguardUpdateMonitor.getCurrentUser())); // vibrate mode will be the same for the life of this screen mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 39e315566daf..acc7b49d9c35 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -274,7 +274,9 @@ public class ImageWallpaper extends WallpaperService { // Sometimes a wallpaper is not large enough to cover the screen in one dimension. // Call updateSurfaceSize -- it will only actually do the update if the dimensions // should change - if (newRotation != mLastRotation) { + if (newRotation != mLastRotation + || mDisplayWidthAtLastSurfaceSizeUpdate != displayInfo.logicalWidth + || mDisplayHeightAtLastSurfaceSizeUpdate != displayInfo.logicalHeight) { // Update surface size (if necessary) if (!updateSurfaceSize(getSurfaceHolder(), displayInfo, true /* forDraw */)) { return; // had to reload wallpaper, will retry later diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 4c1938a62137..46019e3b48ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -417,7 +417,6 @@ public abstract class ExpandableView extends FrameLayout { public void setWillBeGone(boolean willBeGone) { mWillBeGone = willBeGone; - invalidate(); } public int getMinClipTopAmount() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 5d65b5645330..33ddfde845a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -2967,7 +2967,6 @@ public class NotificationPanelView extends PanelView implements } public void updateNotificationViews() { - if (mNotificationStackScroller == null) return; mNotificationStackScroller.updateSpeedBumpIndex(); mNotificationStackScroller.updateFooter(); updateShowEmptyShadeView(); diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index b3f6bd1d41cc..998e441f67d8 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -45,8 +45,10 @@ import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; import android.os.storage.StorageManagerInternal; import android.provider.Settings; import android.util.ArrayMap; @@ -657,33 +659,35 @@ public class AppOpsService extends IAppOpsService.Stub { } }); - StorageManagerInternal storageManagerInternal = LocalServices.getService( - StorageManagerInternal.class); - storageManagerInternal.addExternalStoragePolicy( - new StorageManagerInternal.ExternalStorageMountPolicy() { - @Override - public int getMountMode(int uid, String packageName) { - if (Process.isIsolated(uid)) { - return Zygote.MOUNT_EXTERNAL_NONE; - } - if (noteOperation(AppOpsManager.OP_READ_EXTERNAL_STORAGE, uid, - packageName) != AppOpsManager.MODE_ALLOWED) { - return Zygote.MOUNT_EXTERNAL_NONE; - } - if (noteOperation(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE, uid, - packageName) != AppOpsManager.MODE_ALLOWED) { - return Zygote.MOUNT_EXTERNAL_READ; + if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + StorageManagerInternal storageManagerInternal = LocalServices.getService( + StorageManagerInternal.class); + storageManagerInternal.addExternalStoragePolicy( + new StorageManagerInternal.ExternalStorageMountPolicy() { + @Override + public int getMountMode(int uid, String packageName) { + if (Process.isIsolated(uid)) { + return Zygote.MOUNT_EXTERNAL_NONE; + } + if (noteOperation(AppOpsManager.OP_READ_EXTERNAL_STORAGE, uid, + packageName) != AppOpsManager.MODE_ALLOWED) { + return Zygote.MOUNT_EXTERNAL_NONE; + } + if (noteOperation(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE, uid, + packageName) != AppOpsManager.MODE_ALLOWED) { + return Zygote.MOUNT_EXTERNAL_READ; + } + return Zygote.MOUNT_EXTERNAL_WRITE; } - return Zygote.MOUNT_EXTERNAL_WRITE; - } - @Override - public boolean hasExternalStorage(int uid, String packageName) { - final int mountMode = getMountMode(uid, packageName); - return mountMode == Zygote.MOUNT_EXTERNAL_READ - || mountMode == Zygote.MOUNT_EXTERNAL_WRITE; - } - }); + @Override + public boolean hasExternalStorage(int uid, String packageName) { + final int mountMode = getMountMode(uid, packageName); + return mountMode == Zygote.MOUNT_EXTERNAL_READ + || mountMode == Zygote.MOUNT_EXTERNAL_WRITE; + } + }); + } } public void packageRemoved(int uid, String packageName) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 18bed54d42c1..59c4697e4f1e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -24,6 +24,7 @@ import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.REQUEST_DELETE_PACKAGES; import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; +import static android.Manifest.permission.WRITE_MEDIA_STORAGE; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_DEFAULT; import static android.content.Intent.CATEGORY_HOME; @@ -20034,6 +20035,11 @@ public class PackageManagerService extends IPackageManager.Stub if (Process.isIsolated(uid)) { return Zygote.MOUNT_EXTERNAL_NONE; } + if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) { + return checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED + ? Zygote.MOUNT_EXTERNAL_FULL + : Zygote.MOUNT_EXTERNAL_WRITE; + } if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) { return Zygote.MOUNT_EXTERNAL_DEFAULT; } diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index 2f189a6bcdb9..df97027da64f 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -16,20 +16,18 @@ package com.android.server.wm; -import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; +import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING; +import static com.android.server.wm.ScreenRotationAnimationProto.STARTED; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER; -import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING; -import static com.android.server.wm.ScreenRotationAnimationProto.STARTED; import android.content.Context; import android.graphics.Matrix; import android.graphics.Rect; -import android.os.IBinder; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.Display; @@ -37,7 +35,6 @@ import android.view.DisplayInfo; import android.view.Surface; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; -import android.view.SurfaceSession; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; @@ -268,6 +265,12 @@ class ScreenRotationAnimation { .setSecure(isSecure) .build(); + // In case display bounds change, screenshot buffer and surface may mismatch so set a + // scaling mode. + SurfaceControl.Transaction t2 = new SurfaceControl.Transaction(); + t2.setOverrideScalingMode(mSurfaceControl, Surface.SCALING_MODE_SCALE_TO_WINDOW); + t2.apply(true /* sync */); + // Capture a screenshot into the surface we just created. final int displayId = display.getDisplayId(); final Surface surface = new Surface(); diff --git a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java b/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java index 91e6bd6469d8..11f2b6118e24 100644 --- a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java +++ b/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java @@ -24,10 +24,17 @@ import java.nio.ByteBuffer; */ class DhcpDiscoverPacket extends DhcpPacket { /** + * The IP address of the client which sent this packet. + */ + final Inet4Address mSrcIp; + + /** * Generates a DISCOVER packet with the specified parameters. */ - DhcpDiscoverPacket(int transId, short secs, byte[] clientMac, boolean broadcast) { - super(transId, secs, INADDR_ANY, INADDR_ANY, INADDR_ANY, INADDR_ANY, clientMac, broadcast); + DhcpDiscoverPacket(int transId, short secs, Inet4Address relayIp, byte[] clientMac, + boolean broadcast, Inet4Address srcIp) { + super(transId, secs, INADDR_ANY, INADDR_ANY, INADDR_ANY, relayIp, clientMac, broadcast); + mSrcIp = srcIp; } public String toString() { @@ -41,8 +48,8 @@ class DhcpDiscoverPacket extends DhcpPacket { */ public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); - fillInPacket(encap, INADDR_BROADCAST, INADDR_ANY, destUdp, - srcUdp, result, DHCP_BOOTREQUEST, mBroadcast); + fillInPacket(encap, INADDR_BROADCAST, mSrcIp, destUdp, srcUdp, result, DHCP_BOOTREQUEST, + mBroadcast); result.flip(); return result; } diff --git a/services/net/java/android/net/dhcp/DhcpLease.java b/services/net/java/android/net/dhcp/DhcpLease.java new file mode 100644 index 000000000000..d2a15b37dcc0 --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpLease.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.dhcp; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.MacAddress; +import android.os.SystemClock; +import android.text.TextUtils; + +import com.android.internal.util.HexDump; + +import java.net.Inet4Address; +import java.util.Arrays; +import java.util.Objects; + +/** + * An IPv4 address assignment done through DHCPv4. + * @hide + */ +public class DhcpLease { + public static final long EXPIRATION_NEVER = Long.MAX_VALUE; + public static final String HOSTNAME_NONE = null; + + @Nullable + private final byte[] mClientId; + @NonNull + private final MacAddress mHwAddr; + @NonNull + private final Inet4Address mNetAddr; + /** + * Expiration time for the lease, to compare with {@link SystemClock#elapsedRealtime()}. + */ + private final long mExpTime; + @Nullable + private final String mHostname; + + public DhcpLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, + @NonNull Inet4Address netAddr, long expTime, @Nullable String hostname) { + mClientId = (clientId == null ? null : Arrays.copyOf(clientId, clientId.length)); + mHwAddr = hwAddr; + mNetAddr = netAddr; + mExpTime = expTime; + mHostname = hostname; + } + + @Nullable + public byte[] getClientId() { + if (mClientId == null) { + return null; + } + return Arrays.copyOf(mClientId, mClientId.length); + } + + @NonNull + public MacAddress getHwAddr() { + return mHwAddr; + } + + @Nullable + public String getHostname() { + return mHostname; + } + + @NonNull + public Inet4Address getNetAddr() { + return mNetAddr; + } + + public long getExpTime() { + return mExpTime; + } + + /** + * Push back the expiration time of this lease. If the provided time is sooner than the original + * expiration time, the lease time will not be updated. + * + * <p>The lease hostname is updated with the provided one if set. + * @return A {@link DhcpLease} with expiration time set to max(expTime, currentExpTime) + */ + public DhcpLease renewedLease(long expTime, @Nullable String hostname) { + return new DhcpLease(mClientId, mHwAddr, mNetAddr, Math.max(expTime, mExpTime), + (hostname == null ? mHostname : hostname)); + } + + public boolean matchesClient(@Nullable byte[] clientId, @NonNull MacAddress hwAddr) { + if (mClientId != null) { + return Arrays.equals(mClientId, clientId); + } else { + return clientId == null && mHwAddr.equals(hwAddr); + } + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof DhcpLease)) { + return false; + } + final DhcpLease other = (DhcpLease)obj; + return Arrays.equals(mClientId, other.mClientId) + && mHwAddr.equals(other.mHwAddr) + && mNetAddr.equals(other.mNetAddr) + && mExpTime == other.mExpTime + && TextUtils.equals(mHostname, other.mHostname); + } + + @Override + public int hashCode() { + return Objects.hash(mClientId, mHwAddr, mNetAddr, mHostname, mExpTime); + } + + static String clientIdToString(byte[] bytes) { + if (bytes == null) { + return "null"; + } + return HexDump.toHexString(bytes); + } + + @Override + public String toString() { + return String.format("clientId: %s, hwAddr: %s, netAddr: %s, expTime: %d, hostname: %s", + clientIdToString(mClientId), mHwAddr.toString(), mNetAddr, mExpTime, mHostname); + } +} diff --git a/services/net/java/android/net/dhcp/DhcpLeaseRepository.java b/services/net/java/android/net/dhcp/DhcpLeaseRepository.java new file mode 100644 index 000000000000..7e57c9f46350 --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpLeaseRepository.java @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.dhcp; + +import static android.net.NetworkUtils.inet4AddressToIntHTH; +import static android.net.NetworkUtils.intToInet4AddressHTH; +import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH; +import static android.net.dhcp.DhcpLease.EXPIRATION_NEVER; +import static android.net.util.NetworkConstants.IPV4_ADDR_BITS; + +import static java.lang.Math.min; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.IpPrefix; +import android.net.MacAddress; +import android.net.util.SharedLog; +import android.os.SystemClock; +import android.util.ArrayMap; + +import java.net.Inet4Address; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.Function; + +/** + * A repository managing IPv4 address assignments through DHCPv4. + * + * <p>This class is not thread-safe. All public methods should be called on a common thread or + * use some synchronization mechanism. + * + * <p>Methods are optimized for a small number of allocated leases, assuming that most of the time + * only 2~10 addresses will be allocated, which is the common case. Managing a large number of + * addresses is supported but will be slower: some operations have complexity in O(num_leases). + * @hide + */ +class DhcpLeaseRepository { + public static final byte[] CLIENTID_UNSPEC = null; + public static final Inet4Address INETADDR_UNSPEC = null; + + @NonNull + private final SharedLog mLog; + @NonNull + private final Clock mClock; + + @NonNull + private IpPrefix mPrefix; + @NonNull + private Set<Inet4Address> mReservedAddrs; + private int mSubnetAddr; + private int mSubnetMask; + private int mNumAddresses; + private long mLeaseTimeMs; + + public static class Clock { + /** + * @see SystemClock#elapsedRealtime() + */ + public long elapsedRealtime() { + return SystemClock.elapsedRealtime(); + } + } + + /** + * Next timestamp when committed or declined leases should be checked for expired ones. This + * will always be lower than or equal to the time for the first lease to expire: it's OK not to + * update this when removing entries, but it must always be updated when adding/updating. + */ + private long mNextExpirationCheck = EXPIRATION_NEVER; + + static class DhcpLeaseException extends Exception { + DhcpLeaseException(String message) { + super(message); + } + } + + static class OutOfAddressesException extends DhcpLeaseException { + OutOfAddressesException(String message) { + super(message); + } + } + + static class InvalidAddressException extends DhcpLeaseException { + InvalidAddressException(String message) { + super(message); + } + } + + /** + * Leases by IP address + */ + private final ArrayMap<Inet4Address, DhcpLease> mCommittedLeases = new ArrayMap<>(); + + /** + * Map address -> expiration timestamp in ms. Addresses are guaranteed to be valid as defined + * by {@link #isValidAddress(Inet4Address)}, but are not necessarily otherwise available for + * assignment. + */ + private final LinkedHashMap<Inet4Address, Long> mDeclinedAddrs = new LinkedHashMap<>(); + + public DhcpLeaseRepository(@NonNull IpPrefix prefix, @NonNull Set<Inet4Address> reservedAddrs, + long leaseTimeMs, @NonNull SharedLog log, @NonNull Clock clock) { + updateParams(prefix, reservedAddrs, leaseTimeMs); + mLog = log; + mClock = clock; + } + + public void updateParams(@NonNull IpPrefix prefix, @NonNull Set<Inet4Address> reservedAddrs, + long leaseTimeMs) { + mPrefix = prefix; + mReservedAddrs = Collections.unmodifiableSet(new HashSet<>(reservedAddrs)); + mSubnetMask = prefixLengthToV4NetmaskIntHTH(prefix.getPrefixLength()); + mSubnetAddr = inet4AddressToIntHTH((Inet4Address) prefix.getAddress()) & mSubnetMask; + mNumAddresses = 1 << (IPV4_ADDR_BITS - prefix.getPrefixLength()); + mLeaseTimeMs = leaseTimeMs; + + cleanMap(mCommittedLeases); + cleanMap(mDeclinedAddrs); + } + + /** + * From a map keyed by {@link Inet4Address}, remove entries where the key is invalid (as + * specified by {@link #isValidAddress(Inet4Address)}), or is a reserved address. + */ + private <T> void cleanMap(Map<Inet4Address, T> map) { + final Iterator<Entry<Inet4Address, T>> it = map.entrySet().iterator(); + while (it.hasNext()) { + final Inet4Address addr = it.next().getKey(); + if (!isValidAddress(addr) || mReservedAddrs.contains(addr)) { + it.remove(); + } + } + } + + /** + * Get a DHCP offer, to reply to a DHCPDISCOVER. Follows RFC2131 #4.3.1. + * + * @param clientId Client identifier option if specified, or {@link #CLIENTID_UNSPEC} + * @param relayAddr Internet address of the relay (giaddr), can be {@link Inet4Address#ANY} + * @param reqAddr Requested address by the client (option 50), or {@link #INETADDR_UNSPEC} + * @param hostname Client-provided hostname, or {@link DhcpLease#HOSTNAME_NONE} + * @throws OutOfAddressesException The server does not have any available address + * @throws InvalidAddressException The lease was requested from an unsupported subnet + */ + @NonNull + public DhcpLease getOffer(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, + @NonNull Inet4Address relayAddr, + @Nullable Inet4Address reqAddr, @Nullable String hostname) + throws OutOfAddressesException, InvalidAddressException { + final long currentTime = mClock.elapsedRealtime(); + final long expTime = currentTime + mLeaseTimeMs; + + removeExpiredLeases(currentTime); + + // As per #4.3.1, addresses are assigned based on the relay address if present. This + // implementation only assigns addresses if the relayAddr is inside our configured subnet. + // This also applies when the client requested a specific address for consistency between + // requests, and with older behavior. + if (isIpAddrOutsidePrefix(mPrefix, relayAddr)) { + throw new InvalidAddressException("Lease requested by relay from outside of subnet"); + } + + final DhcpLease currentLease = findByClient(clientId, hwAddr); + final DhcpLease newLease; + if (currentLease != null) { + newLease = currentLease.renewedLease(expTime, hostname); + mLog.log("Offering extended lease " + newLease); + // Do not update lease time in the map: the offer is not committed yet. + } else if (reqAddr != null && isValidAddress(reqAddr) && isAvailable(reqAddr)) { + newLease = new DhcpLease(clientId, hwAddr, reqAddr, expTime, hostname); + mLog.log("Offering requested lease " + newLease); + } else { + newLease = makeNewOffer(clientId, hwAddr, expTime, hostname); + mLog.log("Offering new generated lease " + newLease); + } + return newLease; + } + + private static boolean isIpAddrOutsidePrefix(IpPrefix prefix, Inet4Address addr) { + return addr != null && !addr.equals(Inet4Address.ANY) && !prefix.contains(addr); + } + + @Nullable + private DhcpLease findByClient(@Nullable byte[] clientId, @NonNull MacAddress hwAddr) { + for (DhcpLease lease : mCommittedLeases.values()) { + if (lease.matchesClient(clientId, hwAddr)) { + return lease; + } + } + + // Note this differs from dnsmasq behavior, which would match by hwAddr if clientId was + // given but no lease keyed on clientId matched. This would prevent one interface from + // obtaining multiple leases with different clientId. + return null; + } + + /** + * Make a lease conformant to a client DHCPREQUEST or renew the client's existing lease, + * commit it to the repository and return it. + * + * <p>This method always succeeds and commits the lease if it does not throw, and has no side + * effects if it throws. + * + * @param clientId Client identifier option if specified, or {@link #CLIENTID_UNSPEC} + * @param reqAddr Requested address by the client (option 50), or {@link #INETADDR_UNSPEC} + * @param sidSet Whether the server identifier was set in the request + * @return The newly created or renewed lease + * @throws InvalidAddressException The client provided an address that conflicts with its + * current configuration, or other committed/reserved leases. + */ + @NonNull + public DhcpLease requestLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, + @NonNull Inet4Address clientAddr, @Nullable Inet4Address reqAddr, boolean sidSet, + @Nullable String hostname) throws InvalidAddressException { + final long currentTime = mClock.elapsedRealtime(); + removeExpiredLeases(currentTime); + final DhcpLease assignedLease = findByClient(clientId, hwAddr); + + final Inet4Address leaseAddr = reqAddr != null ? reqAddr : clientAddr; + if (assignedLease != null) { + if (sidSet && reqAddr != null) { + // Client in SELECTING state; remove any current lease before creating a new one. + mCommittedLeases.remove(assignedLease.getNetAddr()); + } else if (!assignedLease.getNetAddr().equals(leaseAddr)) { + // reqAddr null (RENEWING/REBINDING): client renewing its own lease for clientAddr. + // reqAddr set with sid not set (INIT-REBOOT): client verifying configuration. + // In both cases, throw if clientAddr or reqAddr does not match the known lease. + throw new InvalidAddressException("Incorrect address for client in " + + (reqAddr != null ? "INIT-REBOOT" : "RENEWING/REBINDING")); + } + } + + // In the init-reboot case, RFC2131 #4.3.2 says that the server must not reply if + // assignedLease == null, but dnsmasq will let the client use the requested address if + // available, when configured with --dhcp-authoritative. This is preferable to avoid issues + // if the server lost the lease DB: the client would not get a reply because the server + // does not know their lease. + // Similarly in RENEWING/REBINDING state, create a lease when possible if the + // client-provided lease is unknown. + final DhcpLease lease = + checkClientAndMakeLease(clientId, hwAddr, leaseAddr, hostname, currentTime); + mLog.logf("DHCPREQUEST assignedLease %s, reqAddr=%s, sidSet=%s: created/renewed lease %s", + assignedLease, reqAddr, sidSet, lease); + return lease; + } + + /** + * Check that the client can request the specified address, make or renew the lease if yes, and + * commit it. + * + * <p>This method always succeeds and returns the lease if it does not throw, and has no + * side-effect if it throws. + * + * @return The newly created or renewed, committed lease + * @throws InvalidAddressException The client provided an address that conflicts with its + * current configuration, or other committed/reserved leases. + */ + private DhcpLease checkClientAndMakeLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, + @NonNull Inet4Address addr, @Nullable String hostname, long currentTime) + throws InvalidAddressException { + final long expTime = currentTime + mLeaseTimeMs; + final DhcpLease currentLease = mCommittedLeases.getOrDefault(addr, null); + if (currentLease != null && !currentLease.matchesClient(clientId, hwAddr)) { + throw new InvalidAddressException("Address in use"); + } + + final DhcpLease lease; + if (currentLease == null) { + if (isValidAddress(addr) && !mReservedAddrs.contains(addr)) { + lease = new DhcpLease(clientId, hwAddr, addr, expTime, hostname); + } else { + throw new InvalidAddressException("Lease not found and address unavailable"); + } + } else { + lease = currentLease.renewedLease(expTime, hostname); + } + commitLease(lease); + return lease; + } + + private void commitLease(@NonNull DhcpLease lease) { + mCommittedLeases.put(lease.getNetAddr(), lease); + maybeUpdateEarliestExpiration(lease.getExpTime()); + } + + /** + * Delete a committed lease from the repository. + * + * @return true if a lease matching parameters was found. + */ + public boolean releaseLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, + @NonNull Inet4Address addr) { + final DhcpLease currentLease = mCommittedLeases.getOrDefault(addr, null); + if (currentLease == null) { + mLog.w("Could not release unknown lease for " + addr); + return false; + } + if (currentLease.matchesClient(clientId, hwAddr)) { + mCommittedLeases.remove(addr); + mLog.log("Released lease " + currentLease); + return true; + } + mLog.w(String.format("Not releasing lease %s: does not match client (cid %s, hwAddr %s)", + currentLease, DhcpLease.clientIdToString(clientId), hwAddr)); + return false; + } + + public void markLeaseDeclined(@NonNull Inet4Address addr) { + if (mDeclinedAddrs.containsKey(addr) || !isValidAddress(addr)) { + mLog.logf("Not marking %s as declined: already declined or not assignable", addr); + return; + } + final long expTime = mClock.elapsedRealtime() + mLeaseTimeMs; + mDeclinedAddrs.put(addr, expTime); + mLog.logf("Marked %s as declined expiring %d", addr, expTime); + maybeUpdateEarliestExpiration(expTime); + } + + /** + * Get the list of currently valid committed leases in the repository. + */ + @NonNull + public List<DhcpLease> getCommittedLeases() { + removeExpiredLeases(mClock.elapsedRealtime()); + return new ArrayList<>(mCommittedLeases.values()); + } + + /** + * Get the set of addresses that have been marked as declined in the repository. + */ + @NonNull + public Set<Inet4Address> getDeclinedAddresses() { + removeExpiredLeases(mClock.elapsedRealtime()); + return new HashSet<>(mDeclinedAddrs.keySet()); + } + + /** + * Given the expiration time of a new committed lease or declined address, update + * {@link #mNextExpirationCheck} so it stays lower than or equal to the time for the first lease + * to expire. + */ + private void maybeUpdateEarliestExpiration(long expTime) { + if (expTime < mNextExpirationCheck) { + mNextExpirationCheck = expTime; + } + } + + /** + * Remove expired entries from a map keyed by {@link Inet4Address}. + * + * @param tag Type of lease in the map, for logging + * @param getExpTime Functor returning the expiration time for an object in the map. + * Must not return null. + * @return The lowest expiration time among entries remaining in the map + */ + private <T> long removeExpired(long currentTime, @NonNull Map<Inet4Address, T> map, + @NonNull String tag, @NonNull Function<T, Long> getExpTime) { + final Iterator<Entry<Inet4Address, T>> it = map.entrySet().iterator(); + long firstExpiration = EXPIRATION_NEVER; + while (it.hasNext()) { + final Entry<Inet4Address, T> lease = it.next(); + final long expTime = getExpTime.apply(lease.getValue()); + if (expTime <= currentTime) { + mLog.logf("Removing expired %s lease for %s (expTime=%s, currentTime=%s)", + tag, lease.getKey(), expTime, currentTime); + it.remove(); + } else { + firstExpiration = min(firstExpiration, expTime); + } + } + return firstExpiration; + } + + /** + * Go through committed and declined leases and remove the expired ones. + */ + private void removeExpiredLeases(long currentTime) { + if (currentTime < mNextExpirationCheck) { + return; + } + + final long commExp = removeExpired( + currentTime, mCommittedLeases, "committed", DhcpLease::getExpTime); + final long declExp = removeExpired( + currentTime, mDeclinedAddrs, "declined", Function.identity()); + + mNextExpirationCheck = min(commExp, declExp); + } + + private boolean isAvailable(@NonNull Inet4Address addr) { + return !mReservedAddrs.contains(addr) && !mCommittedLeases.containsKey(addr); + } + + /** + * Get the 0-based index of an address in the subnet. + * + * <p>Given ordering of addresses 5.6.7.8 < 5.6.7.9 < 5.6.8.0, the index on a subnet is defined + * so that the first address is 0, the second 1, etc. For example on a /16, 192.168.0.0 -> 0, + * 192.168.0.1 -> 1, 192.168.1.0 -> 256 + * + */ + private int getAddrIndex(int addr) { + return addr & ~mSubnetMask; + } + + private int getAddrByIndex(int index) { + return mSubnetAddr | index; + } + + /** + * Get a valid address starting from the supplied one. + * + * <p>This only checks that the address is numerically valid for assignment, not whether it is + * already in use. The return value is always inside the configured prefix, even if the supplied + * address is not. + * + * <p>If the provided address is valid, it is returned as-is. Otherwise, the next valid + * address (with the ordering in {@link #getAddrIndex(int)}) is returned. + */ + private int getValidAddress(int addr) { + final int lastByteMask = 0xff; + int addrIndex = getAddrIndex(addr); // 0-based index of the address in the subnet + + // Some OSes do not handle addresses in .255 or .0 correctly: avoid those. + final int lastByte = getAddrByIndex(addrIndex) & lastByteMask; + if (lastByte == lastByteMask) { + // Avoid .255 address, and .0 address that follows + addrIndex = (addrIndex + 2) % mNumAddresses; + } else if (lastByte == 0) { + // Avoid .0 address + addrIndex = (addrIndex + 1) % mNumAddresses; + } + + // Do not use first or last address of range + if (addrIndex == 0 || addrIndex == mNumAddresses - 1) { + // Always valid and not end of range since prefixLength is at most 30 in serving params + addrIndex = 1; + } + return getAddrByIndex(addrIndex); + } + + /** + * Returns whether the address is in the configured subnet and part of the assignable range. + */ + private boolean isValidAddress(Inet4Address addr) { + final int intAddr = inet4AddressToIntHTH(addr); + return getValidAddress(intAddr) == intAddr; + } + + private int getNextAddress(int addr) { + final int addrIndex = getAddrIndex(addr); + final int nextAddress = getAddrByIndex((addrIndex + 1) % mNumAddresses); + return getValidAddress(nextAddress); + } + + /** + * Calculate a first candidate address for a client by hashing the hardware address. + * + * <p>This will be a valid address as checked by {@link #getValidAddress(int)}, but may be + * in use. + * + * @return An IPv4 address encoded as 32-bit int + */ + private int getFirstClientAddress(MacAddress hwAddr) { + // This follows dnsmasq behavior. Advantages are: clients will often get the same + // offers for different DISCOVER even if the lease was not yet accepted or has expired, + // and address generation will generally not need to loop through many allocated addresses + // until it finds a free one. + int hash = 0; + for (byte b : hwAddr.toByteArray()) { + hash += b + (b << 8) + (b << 16); + } + // This implementation will not always result in the same IPs as dnsmasq would give out in + // Android <= P, because it includes invalid and reserved addresses in mNumAddresses while + // the configured ranges for dnsmasq did not. + final int addrIndex = hash % mNumAddresses; + return getValidAddress(getAddrByIndex(addrIndex)); + } + + /** + * Create a lease that can be offered to respond to a client DISCOVER. + * + * <p>This method always succeeds and returns the lease if it does not throw. If no non-declined + * address is available, it will try to offer the oldest declined address if valid. + * + * @throws OutOfAddressesException The server has no address left to offer + */ + private DhcpLease makeNewOffer(@Nullable byte[] clientId, @NonNull MacAddress hwAddr, + long expTime, @Nullable String hostname) throws OutOfAddressesException { + int intAddr = getFirstClientAddress(hwAddr); + // Loop until a free address is found, or there are no more addresses. + // There is slightly less than this many usable addresses, but some extra looping is OK + for (int i = 0; i < mNumAddresses; i++) { + final Inet4Address addr = intToInet4AddressHTH(intAddr); + if (isAvailable(addr) && !mDeclinedAddrs.containsKey(addr)) { + return new DhcpLease(clientId, hwAddr, addr, expTime, hostname); + } + intAddr = getNextAddress(intAddr); + } + + // Try freeing DECLINEd addresses if out of addresses. + final Iterator<Inet4Address> it = mDeclinedAddrs.keySet().iterator(); + while (it.hasNext()) { + final Inet4Address addr = it.next(); + it.remove(); + mLog.logf("Out of addresses in address pool: dropped declined addr %s", addr); + // isValidAddress() is always verified for entries in mDeclinedAddrs. + // However declined addresses may have been requested (typically by the machine that was + // already using the address) after being declined. + if (isAvailable(addr)) { + return new DhcpLease(clientId, hwAddr, addr, expTime, hostname); + } + } + + throw new OutOfAddressesException("No address available for offer"); + } +} diff --git a/services/net/java/android/net/dhcp/DhcpNakPacket.java b/services/net/java/android/net/dhcp/DhcpNakPacket.java index 6458232b8fa0..ef9af527d063 100644 --- a/services/net/java/android/net/dhcp/DhcpNakPacket.java +++ b/services/net/java/android/net/dhcp/DhcpNakPacket.java @@ -26,11 +26,9 @@ class DhcpNakPacket extends DhcpPacket { /** * Generates a NAK packet with the specified parameters. */ - DhcpNakPacket(int transId, short secs, Inet4Address clientIp, Inet4Address yourIp, - Inet4Address nextIp, Inet4Address relayIp, - byte[] clientMac) { - super(transId, secs, INADDR_ANY, INADDR_ANY, nextIp, relayIp, - clientMac, false); + DhcpNakPacket(int transId, short secs, Inet4Address nextIp, Inet4Address relayIp, + byte[] clientMac, boolean broadcast) { + super(transId, secs, INADDR_ANY, INADDR_ANY, nextIp, relayIp, clientMac, broadcast); } public String toString() { @@ -43,11 +41,11 @@ class DhcpNakPacket extends DhcpPacket { */ public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); - Inet4Address destIp = mClientIp; - Inet4Address srcIp = mYourIp; + // Constructor does not set values for layers <= 3: use empty values + Inet4Address destIp = INADDR_ANY; + Inet4Address srcIp = INADDR_ANY; - fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result, - DHCP_BOOTREPLY, mBroadcast); + fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result, DHCP_BOOTREPLY, mBroadcast); result.flip(); return result; } diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java index d90a4a235972..888821a8ad92 100644 --- a/services/net/java/android/net/dhcp/DhcpPacket.java +++ b/services/net/java/android/net/dhcp/DhcpPacket.java @@ -1,5 +1,6 @@ package android.net.dhcp; +import android.annotation.Nullable; import android.net.DhcpResults; import android.net.LinkAddress; import android.net.NetworkUtils; @@ -204,6 +205,7 @@ public abstract class DhcpPacket { protected static final byte DHCP_MESSAGE_TYPE_DECLINE = 4; protected static final byte DHCP_MESSAGE_TYPE_ACK = 5; protected static final byte DHCP_MESSAGE_TYPE_NAK = 6; + protected static final byte DHCP_MESSAGE_TYPE_RELEASE = 7; protected static final byte DHCP_MESSAGE_TYPE_INFORM = 8; /** @@ -252,6 +254,7 @@ public abstract class DhcpPacket { * DHCP Optional Type: DHCP Client Identifier */ protected static final byte DHCP_CLIENT_IDENTIFIER = 61; + protected byte[] mClientId; /** * DHCP zero-length option code: pad @@ -281,7 +284,7 @@ public abstract class DhcpPacket { protected final Inet4Address mClientIp; protected final Inet4Address mYourIp; private final Inet4Address mNextIp; - private final Inet4Address mRelayIp; + protected final Inet4Address mRelayIp; /** * Does the client request a broadcast response? @@ -338,13 +341,28 @@ public abstract class DhcpPacket { return mClientMac; } + // TODO: refactor DhcpClient to set clientId when constructing packets and remove + // hasExplicitClientId logic /** - * Returns the client ID. This follows RFC 2132 and is based on the hardware address. + * Returns whether a client ID was set in the options for this packet. + */ + public boolean hasExplicitClientId() { + return mClientId != null; + } + + /** + * Returns the client ID. If not set explicitly, this follows RFC 2132 and creates a client ID + * based on the hardware address. */ public byte[] getClientId() { - byte[] clientId = new byte[mClientMac.length + 1]; - clientId[0] = CLIENT_ID_ETHER; - System.arraycopy(mClientMac, 0, clientId, 1, mClientMac.length); + final byte[] clientId; + if (hasExplicitClientId()) { + clientId = Arrays.copyOf(mClientId, mClientId.length); + } else { + clientId = new byte[mClientMac.length + 1]; + clientId[0] = CLIENT_ID_ETHER; + System.arraycopy(mClientMac, 0, clientId, 1, mClientMac.length); + } return clientId; } @@ -531,8 +549,10 @@ public abstract class DhcpPacket { /** * Adds an optional parameter containing an array of bytes. + * + * <p>This method is a no-op if the payload argument is null. */ - protected static void addTlv(ByteBuffer buf, byte type, byte[] payload) { + protected static void addTlv(ByteBuffer buf, byte type, @Nullable byte[] payload) { if (payload != null) { if (payload.length > MAX_OPTION_LEN) { throw new IllegalArgumentException("DHCP option too long: " @@ -546,8 +566,10 @@ public abstract class DhcpPacket { /** * Adds an optional parameter containing an IP address. + * + * <p>This method is a no-op if the address argument is null. */ - protected static void addTlv(ByteBuffer buf, byte type, Inet4Address addr) { + protected static void addTlv(ByteBuffer buf, byte type, @Nullable Inet4Address addr) { if (addr != null) { addTlv(buf, type, addr.getAddress()); } @@ -555,8 +577,10 @@ public abstract class DhcpPacket { /** * Adds an optional parameter containing a list of IP addresses. + * + * <p>This method is a no-op if the addresses argument is null or empty. */ - protected static void addTlv(ByteBuffer buf, byte type, List<Inet4Address> addrs) { + protected static void addTlv(ByteBuffer buf, byte type, @Nullable List<Inet4Address> addrs) { if (addrs == null || addrs.size() == 0) return; int optionLen = 4 * addrs.size(); @@ -574,9 +598,11 @@ public abstract class DhcpPacket { } /** - * Adds an optional parameter containing a short integer + * Adds an optional parameter containing a short integer. + * + * <p>This method is a no-op if the value argument is null. */ - protected static void addTlv(ByteBuffer buf, byte type, Short value) { + protected static void addTlv(ByteBuffer buf, byte type, @Nullable Short value) { if (value != null) { buf.put(type); buf.put((byte) 2); @@ -585,9 +611,11 @@ public abstract class DhcpPacket { } /** - * Adds an optional parameter containing a simple integer + * Adds an optional parameter containing a simple integer. + * + * <p>This method is a no-op if the value argument is null. */ - protected static void addTlv(ByteBuffer buf, byte type, Integer value) { + protected static void addTlv(ByteBuffer buf, byte type, @Nullable Integer value) { if (value != null) { buf.put(type); buf.put((byte) 4); @@ -597,12 +625,16 @@ public abstract class DhcpPacket { /** * Adds an optional parameter containing an ASCII string. + * + * <p>This method is a no-op if the string argument is null. */ - protected static void addTlv(ByteBuffer buf, byte type, String str) { - try { - addTlv(buf, type, str.getBytes("US-ASCII")); - } catch (UnsupportedEncodingException e) { - throw new IllegalArgumentException("String is not US-ASCII: " + str); + protected static void addTlv(ByteBuffer buf, byte type, @Nullable String str) { + if (str != null) { + try { + addTlv(buf, type, str.getBytes("US-ASCII")); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("String is not US-ASCII: " + str); + } } } @@ -740,6 +772,7 @@ public abstract class DhcpPacket { Inet4Address nextIp; Inet4Address relayIp; byte[] clientMac; + byte[] clientId = null; List<Inet4Address> dnsServers = new ArrayList<>(); List<Inet4Address> gateways = new ArrayList<>(); // aka router Inet4Address serverIdentifier = null; @@ -1038,8 +1071,8 @@ public abstract class DhcpPacket { throw new ParseException(DhcpErrorEvent.DHCP_NO_MSG_TYPE, "No DHCP message type option"); case DHCP_MESSAGE_TYPE_DISCOVER: - newPacket = new DhcpDiscoverPacket( - transactionId, secs, clientMac, broadcast); + newPacket = new DhcpDiscoverPacket(transactionId, secs, relayIp, clientMac, + broadcast, ipSrc); break; case DHCP_MESSAGE_TYPE_OFFER: newPacket = new DhcpOfferPacket( @@ -1047,7 +1080,7 @@ public abstract class DhcpPacket { break; case DHCP_MESSAGE_TYPE_REQUEST: newPacket = new DhcpRequestPacket( - transactionId, secs, clientIp, clientMac, broadcast); + transactionId, secs, clientIp, relayIp, clientMac, broadcast); break; case DHCP_MESSAGE_TYPE_DECLINE: newPacket = new DhcpDeclinePacket( @@ -1060,8 +1093,15 @@ public abstract class DhcpPacket { break; case DHCP_MESSAGE_TYPE_NAK: newPacket = new DhcpNakPacket( - transactionId, secs, clientIp, yourIp, nextIp, relayIp, - clientMac); + transactionId, secs, nextIp, relayIp, clientMac, broadcast); + break; + case DHCP_MESSAGE_TYPE_RELEASE: + if (serverIdentifier == null) { + throw new ParseException(DhcpErrorEvent.MISC_ERROR, + "DHCPRELEASE without server identifier"); + } + newPacket = new DhcpReleasePacket( + transactionId, serverIdentifier, clientIp, relayIp, clientMac); break; case DHCP_MESSAGE_TYPE_INFORM: newPacket = new DhcpInformPacket( @@ -1074,6 +1114,7 @@ public abstract class DhcpPacket { } newPacket.mBroadcastAddress = bcAddr; + newPacket.mClientId = clientId; newPacket.mDnsServers = dnsServers; newPacket.mDomainName = domainName; newPacket.mGateways = gateways; @@ -1173,8 +1214,8 @@ public abstract class DhcpPacket { */ public static ByteBuffer buildDiscoverPacket(int encap, int transactionId, short secs, byte[] clientMac, boolean broadcast, byte[] expectedParams) { - DhcpPacket pkt = new DhcpDiscoverPacket( - transactionId, secs, clientMac, broadcast); + DhcpPacket pkt = new DhcpDiscoverPacket(transactionId, secs, INADDR_ANY /* relayIp */, + clientMac, broadcast, INADDR_ANY /* srcIp */); pkt.mRequestedParams = expectedParams; return pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT); } @@ -1223,12 +1264,11 @@ public abstract class DhcpPacket { /** * Builds a DHCP-NAK packet from the required specified parameters. */ - public static ByteBuffer buildNakPacket(int encap, int transactionId, - Inet4Address serverIpAddr, Inet4Address clientIpAddr, byte[] mac) { - DhcpPacket pkt = new DhcpNakPacket(transactionId, (short) 0, clientIpAddr, - serverIpAddr, serverIpAddr, serverIpAddr, mac); - pkt.mMessage = "requested address not available"; - pkt.mRequestedIp = clientIpAddr; + public static ByteBuffer buildNakPacket(int encap, int transactionId, Inet4Address serverIpAddr, + byte[] mac, boolean broadcast, String message) { + DhcpPacket pkt = new DhcpNakPacket( + transactionId, (short) 0, serverIpAddr, serverIpAddr, mac, broadcast); + pkt.mMessage = message; return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER); } @@ -1240,7 +1280,7 @@ public abstract class DhcpPacket { byte[] clientMac, Inet4Address requestedIpAddress, Inet4Address serverIdentifier, byte[] requestedParams, String hostName) { DhcpPacket pkt = new DhcpRequestPacket(transactionId, secs, clientIp, - clientMac, broadcast); + INADDR_ANY /* relayIp */, clientMac, broadcast); pkt.mRequestedIp = requestedIpAddress; pkt.mServerIdentifier = serverIdentifier; pkt.mHostName = hostName; diff --git a/services/net/java/android/net/dhcp/DhcpPacketListener.java b/services/net/java/android/net/dhcp/DhcpPacketListener.java new file mode 100644 index 000000000000..498fd93fff59 --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpPacketListener.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.dhcp; + +import android.annotation.Nullable; +import android.net.util.FdEventsReader; +import android.net.util.PacketReader; +import android.os.Handler; +import android.system.Os; + +import java.io.FileDescriptor; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; + +/** + * A {@link FdEventsReader} to receive and parse {@link DhcpPacket}. + * @hide + */ +abstract class DhcpPacketListener extends FdEventsReader<DhcpPacketListener.Payload> { + static final class Payload { + final byte[] bytes = new byte[DhcpPacket.MAX_LENGTH]; + Inet4Address srcAddr; + } + + public DhcpPacketListener(Handler handler) { + super(handler, new Payload()); + } + + @Override + protected int recvBufSize(Payload buffer) { + return buffer.bytes.length; + } + + @Override + protected final void handlePacket(Payload recvbuf, int length) { + if (recvbuf.srcAddr == null) { + return; + } + + try { + final DhcpPacket packet = DhcpPacket.decodeFullPacket(recvbuf.bytes, length, + DhcpPacket.ENCAP_BOOTP); + onReceive(packet, recvbuf.srcAddr); + } catch (DhcpPacket.ParseException e) { + logParseError(recvbuf.bytes, length, e); + } + } + + @Override + protected int readPacket(FileDescriptor fd, Payload packetBuffer) throws Exception { + final InetSocketAddress addr = new InetSocketAddress(); + final int read = Os.recvfrom( + fd, packetBuffer.bytes, 0, packetBuffer.bytes.length, 0 /* flags */, addr); + + // Buffers with null srcAddr will be dropped in handlePacket() + packetBuffer.srcAddr = inet4AddrOrNull(addr); + return read; + } + + @Nullable + private static Inet4Address inet4AddrOrNull(InetSocketAddress addr) { + return addr.getAddress() instanceof Inet4Address + ? (Inet4Address) addr.getAddress() + : null; + } + + protected abstract void onReceive(DhcpPacket packet, Inet4Address srcAddr); + protected abstract void logParseError(byte[] packet, int length, DhcpPacket.ParseException e); +} diff --git a/services/net/java/android/net/dhcp/DhcpReleasePacket.java b/services/net/java/android/net/dhcp/DhcpReleasePacket.java new file mode 100644 index 000000000000..39583032c20d --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpReleasePacket.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.dhcp; + +import java.net.Inet4Address; +import java.nio.ByteBuffer; + +/** + * Implements DHCP-RELEASE + */ +class DhcpReleasePacket extends DhcpPacket { + + final Inet4Address mClientAddr; + + /** + * Generates a RELEASE packet with the specified parameters. + */ + public DhcpReleasePacket(int transId, Inet4Address serverId, Inet4Address clientAddr, + Inet4Address relayIp, byte[] clientMac) { + super(transId, (short)0, clientAddr, INADDR_ANY /* yourIp */, INADDR_ANY /* nextIp */, + relayIp, clientMac, false /* broadcast */); + mServerIdentifier = serverId; + mClientAddr = clientAddr; + } + + + @Override + public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) { + ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH); + fillInPacket(encap, mServerIdentifier /* destIp */, mClientIp /* srcIp */, destUdp, srcUdp, + result, DHCP_BOOTREPLY, mBroadcast); + result.flip(); + return result; + } + + @Override + void finishPacket(ByteBuffer buffer) { + addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_RELEASE); + addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId()); + addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier); + addCommonClientTlvs(buffer); + addTlvEnd(buffer); + } +} diff --git a/services/net/java/android/net/dhcp/DhcpRequestPacket.java b/services/net/java/android/net/dhcp/DhcpRequestPacket.java index 4f9aa01151ca..231d04576c28 100644 --- a/services/net/java/android/net/dhcp/DhcpRequestPacket.java +++ b/services/net/java/android/net/dhcp/DhcpRequestPacket.java @@ -28,9 +28,9 @@ class DhcpRequestPacket extends DhcpPacket { /** * Generates a REQUEST packet with the specified parameters. */ - DhcpRequestPacket(int transId, short secs, Inet4Address clientIp, byte[] clientMac, - boolean broadcast) { - super(transId, secs, clientIp, INADDR_ANY, INADDR_ANY, INADDR_ANY, clientMac, broadcast); + DhcpRequestPacket(int transId, short secs, Inet4Address clientIp, Inet4Address relayIp, + byte[] clientMac, boolean broadcast) { + super(transId, secs, clientIp, INADDR_ANY, INADDR_ANY, relayIp, clientMac, broadcast); } public String toString() { diff --git a/services/net/java/android/net/dhcp/DhcpServingParams.java b/services/net/java/android/net/dhcp/DhcpServingParams.java new file mode 100644 index 000000000000..ba9d116d0bd5 --- /dev/null +++ b/services/net/java/android/net/dhcp/DhcpServingParams.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.dhcp; + +import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; +import static android.net.dhcp.DhcpPacket.INFINITE_LEASE; +import static android.net.util.NetworkConstants.IPV4_MAX_MTU; +import static android.net.util.NetworkConstants.IPV4_MIN_MTU; + +import static java.lang.Integer.toUnsignedLong; + +import android.annotation.NonNull; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.NetworkUtils; + +import java.net.Inet4Address; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Parameters used by the DhcpServer to serve requests. + * + * <p>Instances are immutable. Use {@link DhcpServingParams.Builder} to instantiate. + * @hide + */ +public class DhcpServingParams { + public static final int MTU_UNSET = 0; + public static final int MIN_PREFIX_LENGTH = 16; + public static final int MAX_PREFIX_LENGTH = 30; + + /** Server inet address and prefix to serve */ + @NonNull + public final LinkAddress serverAddr; + + /** + * Default routers to be advertised to DHCP clients. May be empty. + * This set is provided by {@link DhcpServingParams.Builder} and is immutable. + */ + @NonNull + public final Set<Inet4Address> defaultRouters; + + /** + * DNS servers to be advertised to DHCP clients. May be empty. + * This set is provided by {@link DhcpServingParams.Builder} and is immutable. + */ + @NonNull + public final Set<Inet4Address> dnsServers; + + /** + * Excluded addresses that the DHCP server is not allowed to assign to clients. + * This set is provided by {@link DhcpServingParams.Builder} and is immutable. + */ + @NonNull + public final Set<Inet4Address> excludedAddrs; + + // DHCP uses uint32. Use long for clearer code, and check range when building. + public final long dhcpLeaseTimeSecs; + public final int linkMtu; + + /** + * Checked exception thrown when some parameters used to build {@link DhcpServingParams} are + * missing or invalid. + */ + public static class InvalidParameterException extends Exception { + public InvalidParameterException(String message) { + super(message); + } + } + + private DhcpServingParams(@NonNull LinkAddress serverAddr, + @NonNull Set<Inet4Address> defaultRouters, + @NonNull Set<Inet4Address> dnsServers, @NonNull Set<Inet4Address> excludedAddrs, + long dhcpLeaseTimeSecs, int linkMtu) { + this.serverAddr = serverAddr; + this.defaultRouters = defaultRouters; + this.dnsServers = dnsServers; + this.excludedAddrs = excludedAddrs; + this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs; + this.linkMtu = linkMtu; + } + + @NonNull + public Inet4Address getServerInet4Addr() { + return (Inet4Address) serverAddr.getAddress(); + } + + /** + * Get the served prefix mask as an IPv4 address. + * + * <p>For example, if the served prefix is 192.168.42.0/24, this will return 255.255.255.0. + */ + @NonNull + public Inet4Address getPrefixMaskAsAddress() { + return getPrefixMaskAsInet4Address(serverAddr.getPrefixLength()); + } + + /** + * Get the server broadcast address. + * + * <p>For example, if the server {@link LinkAddress} is 192.168.42.1/24, this will return + * 192.168.42.255. + */ + @NonNull + public Inet4Address getBroadcastAddress() { + return NetworkUtils.getBroadcastAddress(getServerInet4Addr(), serverAddr.getPrefixLength()); + } + + /** + * Utility class to create new instances of {@link DhcpServingParams} while checking validity + * of the parameters. + */ + public static class Builder { + private LinkAddress serverAddr; + private Set<Inet4Address> defaultRouters; + private Set<Inet4Address> dnsServers; + private Set<Inet4Address> excludedAddrs; + private long dhcpLeaseTimeSecs; + private int linkMtu = MTU_UNSET; + + /** + * Set the server address and served prefix for the DHCP server. + * + * <p>This parameter is required. + */ + public Builder setServerAddr(@NonNull LinkAddress serverAddr) { + this.serverAddr = serverAddr; + return this; + } + + /** + * Set the default routers to be advertised to DHCP clients. + * + * <p>Each router must be inside the served prefix. This may be an empty set, but it must + * always be set explicitly before building the {@link DhcpServingParams}. + */ + public Builder setDefaultRouters(@NonNull Set<Inet4Address> defaultRouters) { + this.defaultRouters = defaultRouters; + return this; + } + + /** + * Set the DNS servers to be advertised to DHCP clients. + * + * <p>This may be an empty set, but it must always be set explicitly before building the + * {@link DhcpServingParams}. + */ + public Builder setDnsServers(@NonNull Set<Inet4Address> dnsServers) { + this.dnsServers = dnsServers; + return this; + } + + /** + * Set excluded addresses that the DHCP server is not allowed to assign to clients. + * + * <p>This parameter is optional. DNS servers and default routers are always excluded + * and do not need to be set here. + */ + public Builder setExcludedAddrs(@NonNull Set<Inet4Address> excludedAddrs) { + this.excludedAddrs = excludedAddrs; + return this; + } + + /** + * Set the lease time for leases assigned by the DHCP server. + * + * <p>This parameter is required. + */ + public Builder setDhcpLeaseTimeSecs(long dhcpLeaseTimeSecs) { + this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs; + return this; + } + + /** + * Set the link MTU to be advertised to DHCP clients. + * + * <p>If set to {@link #MTU_UNSET}, no MTU will be advertised to clients. This parameter + * is optional and defaults to {@link #MTU_UNSET}. + */ + public Builder setLinkMtu(int linkMtu) { + this.linkMtu = linkMtu; + return this; + } + + /** + * Create a new {@link DhcpServingParams} instance based on parameters set in the builder. + * + * <p>This method has no side-effects. If it does not throw, a valid + * {@link DhcpServingParams} is returned. + * @return The constructed parameters. + * @throws InvalidParameterException At least one parameter is missing or invalid. + */ + @NonNull + public DhcpServingParams build() throws InvalidParameterException { + if (serverAddr == null) { + throw new InvalidParameterException("Missing serverAddr"); + } + if (defaultRouters == null) { + throw new InvalidParameterException("Missing defaultRouters"); + } + if (dnsServers == null) { + // Empty set is OK, but enforce explicitly setting it + throw new InvalidParameterException("Missing dnsServers"); + } + if (dhcpLeaseTimeSecs <= 0 || dhcpLeaseTimeSecs > toUnsignedLong(INFINITE_LEASE)) { + throw new InvalidParameterException("Invalid lease time: " + dhcpLeaseTimeSecs); + } + if (linkMtu != MTU_UNSET && (linkMtu < IPV4_MIN_MTU || linkMtu > IPV4_MAX_MTU)) { + throw new InvalidParameterException("Invalid link MTU: " + linkMtu); + } + if (!serverAddr.isIPv4()) { + throw new InvalidParameterException("serverAddr must be IPv4"); + } + if (serverAddr.getPrefixLength() < MIN_PREFIX_LENGTH + || serverAddr.getPrefixLength() > MAX_PREFIX_LENGTH) { + throw new InvalidParameterException("Prefix length is not in supported range"); + } + + final IpPrefix prefix = makeIpPrefix(serverAddr); + for (Inet4Address addr : defaultRouters) { + if (!prefix.contains(addr)) { + throw new InvalidParameterException(String.format( + "Default router %s is not in server prefix %s", addr, serverAddr)); + } + } + + final Set<Inet4Address> excl = new HashSet<>(); + if (excludedAddrs != null) { + excl.addAll(excludedAddrs); + } + excl.add((Inet4Address) serverAddr.getAddress()); + excl.addAll(defaultRouters); + excl.addAll(dnsServers); + + return new DhcpServingParams(serverAddr, + Collections.unmodifiableSet(new HashSet<>(defaultRouters)), + Collections.unmodifiableSet(new HashSet<>(dnsServers)), + Collections.unmodifiableSet(excl), + dhcpLeaseTimeSecs, linkMtu); + } + } + + @NonNull + static IpPrefix makeIpPrefix(@NonNull LinkAddress addr) { + return new IpPrefix(addr.getAddress(), addr.getPrefixLength()); + } +} diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java index 7f821ffd369a..b77da28401d6 100644 --- a/services/net/java/android/net/ip/IpClient.java +++ b/services/net/java/android/net/ip/IpClient.java @@ -524,7 +524,7 @@ public class IpClient extends StateMachine { return false; } // There no more than one IPv4 address - if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) { + if (ipAddresses.stream().filter(LinkAddress::isIPv4).count() > 1) { return false; } diff --git a/services/net/java/android/net/util/FdEventsReader.java b/services/net/java/android/net/util/FdEventsReader.java new file mode 100644 index 000000000000..575444f89bc3 --- /dev/null +++ b/services/net/java/android/net/util/FdEventsReader.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2016 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 android.net.util; + +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.Looper; +import android.os.MessageQueue; +import android.system.ErrnoException; +import android.system.OsConstants; + +import libcore.io.IoUtils; + +import java.io.FileDescriptor; + + +/** + * This class encapsulates the mechanics of registering a file descriptor + * with a thread's Looper and handling read events (and errors). + * + * Subclasses MUST implement createFd() and SHOULD override handlePacket(). They MAY override + * onStop() and onStart(). + * + * Subclasses can expect a call life-cycle like the following: + * + * [1] when a client calls start(), createFd() is called, followed by the onStart() hook if all + * goes well. Implementations may override onStart() for additional initialization. + * + * [2] yield, waiting for read event or error notification: + * + * [a] readPacket() && handlePacket() + * + * [b] if (no error): + * goto 2 + * else: + * goto 3 + * + * [3] when a client calls stop(), the onStop() hook is called (unless already stopped or never + * started). Implementations may override onStop() for additional cleanup. + * + * The packet receive buffer is recycled on every read call, so subclasses + * should make any copies they would like inside their handlePacket() + * implementation. + * + * All public methods MUST only be called from the same thread with which + * the Handler constructor argument is associated. + * + * @hide + */ +public abstract class FdEventsReader<BufferType> { + private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; + private static final int UNREGISTER_THIS_FD = 0; + + @NonNull + private final Handler mHandler; + @NonNull + private final MessageQueue mQueue; + @NonNull + private final BufferType mBuffer; + @Nullable + private FileDescriptor mFd; + private long mPacketsReceived; + + protected static void closeFd(FileDescriptor fd) { + IoUtils.closeQuietly(fd); + } + + protected FdEventsReader(@NonNull Handler h, @NonNull BufferType buffer) { + mHandler = h; + mQueue = mHandler.getLooper().getQueue(); + mBuffer = buffer; + } + + public final void start() { + if (onCorrectThread()) { + createAndRegisterFd(); + } else { + mHandler.post(() -> { + logError("start() called from off-thread", null); + createAndRegisterFd(); + }); + } + } + + public final void stop() { + if (onCorrectThread()) { + unregisterAndDestroyFd(); + } else { + mHandler.post(() -> { + logError("stop() called from off-thread", null); + unregisterAndDestroyFd(); + }); + } + } + + @NonNull + public Handler getHandler() { return mHandler; } + + protected abstract int recvBufSize(@NonNull BufferType buffer); + + public int recvBufSize() { return recvBufSize(mBuffer); } + + /** + * Get the number of successful calls to {@link #readPacket(FileDescriptor, Object)}. + * + * <p>A call was successful if {@link #readPacket(FileDescriptor, Object)} returned a value > 0. + */ + public final long numPacketsReceived() { return mPacketsReceived; } + + /** + * Subclasses MUST create the listening socket here, including setting + * all desired socket options, interface or address/port binding, etc. + */ + @Nullable + protected abstract FileDescriptor createFd(); + + /** + * Implementations MUST return the bytes read or throw an Exception. + * + * <p>The caller may throw a {@link ErrnoException} with {@link OsConstants#EAGAIN} or + * {@link OsConstants#EINTR}, in which case {@link FdEventsReader} will ignore the buffer + * contents and respectively wait for further input or retry the read immediately. For all other + * exceptions, the {@link FdEventsReader} will be stopped with no more interactions with this + * method. + */ + protected abstract int readPacket(@NonNull FileDescriptor fd, @NonNull BufferType buffer) + throws Exception; + + /** + * Called by the main loop for every packet. Any desired copies of + * |recvbuf| should be made in here, as the underlying byte array is + * reused across all reads. + */ + protected void handlePacket(@NonNull BufferType recvbuf, int length) {} + + /** + * Called by the main loop to log errors. In some cases |e| may be null. + */ + protected void logError(@NonNull String msg, @Nullable Exception e) {} + + /** + * Called by start(), if successful, just prior to returning. + */ + protected void onStart() {} + + /** + * Called by stop() just prior to returning. + */ + protected void onStop() {} + + private void createAndRegisterFd() { + if (mFd != null) return; + + try { + mFd = createFd(); + if (mFd != null) { + // Force the socket to be non-blocking. + IoUtils.setBlocking(mFd, false); + } + } catch (Exception e) { + logError("Failed to create socket: ", e); + closeFd(mFd); + mFd = null; + } + + if (mFd == null) return; + + mQueue.addOnFileDescriptorEventListener( + mFd, + FD_EVENTS, + (fd, events) -> { + // Always call handleInput() so read/recvfrom are given + // a proper chance to encounter a meaningful errno and + // perhaps log a useful error message. + if (!isRunning() || !handleInput()) { + unregisterAndDestroyFd(); + return UNREGISTER_THIS_FD; + } + return FD_EVENTS; + }); + onStart(); + } + + private boolean isRunning() { return (mFd != null) && mFd.valid(); } + + // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error. + private boolean handleInput() { + while (isRunning()) { + final int bytesRead; + + try { + bytesRead = readPacket(mFd, mBuffer); + if (bytesRead < 1) { + if (isRunning()) logError("Socket closed, exiting", null); + break; + } + mPacketsReceived++; + } catch (ErrnoException e) { + if (e.errno == OsConstants.EAGAIN) { + // We've read everything there is to read this time around. + return true; + } else if (e.errno == OsConstants.EINTR) { + continue; + } else { + if (isRunning()) logError("readPacket error: ", e); + break; + } + } catch (Exception e) { + if (isRunning()) logError("readPacket error: ", e); + break; + } + + try { + handlePacket(mBuffer, bytesRead); + } catch (Exception e) { + logError("handlePacket error: ", e); + break; + } + } + + return false; + } + + private void unregisterAndDestroyFd() { + if (mFd == null) return; + + mQueue.removeOnFileDescriptorEventListener(mFd); + closeFd(mFd); + mFd = null; + onStop(); + } + + private boolean onCorrectThread() { + return (mHandler.getLooper() == Looper.myLooper()); + } +} diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java index de04fd0ced04..3defe56939f5 100644 --- a/services/net/java/android/net/util/NetworkConstants.java +++ b/services/net/java/android/net/util/NetworkConstants.java @@ -77,10 +77,12 @@ public final class NetworkConstants { /** * IPv4 constants. * - * See als: + * See also: * - https://tools.ietf.org/html/rfc791 */ public static final int IPV4_HEADER_MIN_LEN = 20; + public static final int IPV4_MIN_MTU = 68; + public static final int IPV4_MAX_MTU = 65_535; public static final int IPV4_IHL_MASK = 0xf; public static final int IPV4_FLAGS_OFFSET = 6; public static final int IPV4_FRAGMENT_MASK = 0x1fff; diff --git a/services/net/java/android/net/util/PacketReader.java b/services/net/java/android/net/util/PacketReader.java index 10da2a551e21..4aec6b6753a6 100644 --- a/services/net/java/android/net/util/PacketReader.java +++ b/services/net/java/android/net/util/PacketReader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,236 +16,46 @@ package android.net.util; -import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; -import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; +import static java.lang.Math.max; -import android.annotation.Nullable; import android.os.Handler; -import android.os.Looper; -import android.os.MessageQueue; -import android.os.MessageQueue.OnFileDescriptorEventListener; -import android.system.ErrnoException; import android.system.Os; -import android.system.OsConstants; - -import libcore.io.IoUtils; import java.io.FileDescriptor; -import java.io.IOException; - /** - * This class encapsulates the mechanics of registering a file descriptor - * with a thread's Looper and handling read events (and errors). - * - * Subclasses MUST implement createFd() and SHOULD override handlePacket(). - - * Subclasses can expect a call life-cycle like the following: - * - * [1] start() calls createFd() and (if all goes well) onStart() - * - * [2] yield, waiting for read event or error notification: - * - * [a] readPacket() && handlePacket() - * - * [b] if (no error): - * goto 2 - * else: - * goto 3 - * - * [3] stop() calls onStop() if not previously stopped - * - * The packet receive buffer is recycled on every read call, so subclasses - * should make any copies they would like inside their handlePacket() - * implementation. - * - * All public methods MUST only be called from the same thread with which - * the Handler constructor argument is associated. + * Specialization of {@link FdEventsReader} that reads packets into a byte array. * * TODO: rename this class to something more correctly descriptive (something * like [or less horrible than] FdReadEventsHandler?). * * @hide */ -public abstract class PacketReader { - private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; - private static final int UNREGISTER_THIS_FD = 0; +public abstract class PacketReader extends FdEventsReader<byte[]> { public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024; - private final Handler mHandler; - private final MessageQueue mQueue; - private final byte[] mPacket; - private FileDescriptor mFd; - private long mPacketsReceived; - - protected static void closeFd(FileDescriptor fd) { - IoUtils.closeQuietly(fd); - } - protected PacketReader(Handler h) { this(h, DEFAULT_RECV_BUF_SIZE); } - protected PacketReader(Handler h, int recvbufsize) { - mHandler = h; - mQueue = mHandler.getLooper().getQueue(); - mPacket = new byte[Math.max(recvbufsize, DEFAULT_RECV_BUF_SIZE)]; - } - - public final void start() { - if (onCorrectThread()) { - createAndRegisterFd(); - } else { - mHandler.post(() -> { - logError("start() called from off-thread", null); - createAndRegisterFd(); - }); - } + protected PacketReader(Handler h, int recvBufSize) { + super(h, new byte[max(recvBufSize, DEFAULT_RECV_BUF_SIZE)]); } - public final void stop() { - if (onCorrectThread()) { - unregisterAndDestroyFd(); - } else { - mHandler.post(() -> { - logError("stop() called from off-thread", null); - unregisterAndDestroyFd(); - }); - } + @Override + protected final int recvBufSize(byte[] buffer) { + return buffer.length; } - public Handler getHandler() { return mHandler; } - - public final int recvBufSize() { return mPacket.length; } - - public final long numPacketsReceived() { return mPacketsReceived; } - - /** - * Subclasses MUST create the listening socket here, including setting - * all desired socket options, interface or address/port binding, etc. - */ - protected abstract FileDescriptor createFd(); - /** * Subclasses MAY override this to change the default read() implementation * in favour of, say, recvfrom(). * * Implementations MUST return the bytes read or throw an Exception. */ + @Override protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception { return Os.read(fd, packetBuffer, 0, packetBuffer.length); } - - /** - * Called by the main loop for every packet. Any desired copies of - * |recvbuf| should be made in here, as the underlying byte array is - * reused across all reads. - */ - protected void handlePacket(byte[] recvbuf, int length) {} - - /** - * Called by the main loop to log errors. In some cases |e| may be null. - */ - protected void logError(String msg, Exception e) {} - - /** - * Called by start(), if successful, just prior to returning. - */ - protected void onStart() {} - - /** - * Called by stop() just prior to returning. - */ - protected void onStop() {} - - private void createAndRegisterFd() { - if (mFd != null) return; - - try { - mFd = createFd(); - if (mFd != null) { - // Force the socket to be non-blocking. - IoUtils.setBlocking(mFd, false); - } - } catch (Exception e) { - logError("Failed to create socket: ", e); - closeFd(mFd); - mFd = null; - return; - } - - if (mFd == null) return; - - mQueue.addOnFileDescriptorEventListener( - mFd, - FD_EVENTS, - new OnFileDescriptorEventListener() { - @Override - public int onFileDescriptorEvents(FileDescriptor fd, int events) { - // Always call handleInput() so read/recvfrom are given - // a proper chance to encounter a meaningful errno and - // perhaps log a useful error message. - if (!isRunning() || !handleInput()) { - unregisterAndDestroyFd(); - return UNREGISTER_THIS_FD; - } - return FD_EVENTS; - } - }); - onStart(); - } - - private boolean isRunning() { return (mFd != null) && mFd.valid(); } - - // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error. - private boolean handleInput() { - while (isRunning()) { - final int bytesRead; - - try { - bytesRead = readPacket(mFd, mPacket); - if (bytesRead < 1) { - if (isRunning()) logError("Socket closed, exiting", null); - break; - } - mPacketsReceived++; - } catch (ErrnoException e) { - if (e.errno == OsConstants.EAGAIN) { - // We've read everything there is to read this time around. - return true; - } else if (e.errno == OsConstants.EINTR) { - continue; - } else { - if (isRunning()) logError("readPacket error: ", e); - break; - } - } catch (Exception e) { - if (isRunning()) logError("readPacket error: ", e); - break; - } - - try { - handlePacket(mPacket, bytesRead); - } catch (Exception e) { - logError("handlePacket error: ", e); - break; - } - } - - return false; - } - - private void unregisterAndDestroyFd() { - if (mFd == null) return; - - mQueue.removeOnFileDescriptorEventListener(mFd); - closeFd(mFd); - mFd = null; - onStop(); - } - - private boolean onCorrectThread() { - return (mHandler.getLooper() == Looper.myLooper()); - } } diff --git a/services/net/java/android/net/util/SharedLog.java b/services/net/java/android/net/util/SharedLog.java index bbd3d13efbd6..f7bf393f367b 100644 --- a/services/net/java/android/net/util/SharedLog.java +++ b/services/net/java/android/net/util/SharedLog.java @@ -16,6 +16,7 @@ package android.net.util; +import android.annotation.NonNull; import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; @@ -90,6 +91,13 @@ public class SharedLog { Log.e(mTag, record(Category.ERROR, msg)); } + /** + * Log an error due to an exception, with the exception stacktrace. + */ + public void e(@NonNull String msg, @NonNull Throwable e) { + Log.e(mTag, record(Category.ERROR, msg + ": " + e.getMessage()), e); + } + public void i(String msg) { Log.i(mTag, record(Category.NONE, msg)); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index 9d617a96e60f..48c89025e865 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -18,13 +18,13 @@ package com.android.server.hdmi; import static com.google.common.truth.Truth.assertThat; import android.annotation.Nullable; +import android.app.Instrumentation; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,13 +44,16 @@ public class ArcTerminationActionFromAvrTest { private boolean mShouldDispatchReportArcTerminated; private boolean mArcEnabled; private boolean mSetArcStatusCalled; + private Instrumentation mInstrumentation; @Before public void setUp() { mDeviceInfoForTests = new HdmiDeviceInfo(1000, 1); + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + HdmiControlService hdmiControlService = - new HdmiControlService(null) { + new HdmiControlService(mInstrumentation.getTargetContext()) { @Override void sendCecCommand( HdmiCecMessage command, @Nullable SendMessageCallback callback) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java index 81145103f689..3736df547b05 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java @@ -22,9 +22,8 @@ import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - import com.android.server.hdmi.HdmiCecLocalDeviceAudioSystem.TvSystemAudioModeSupportedCallback; import org.junit.Before; @@ -50,7 +49,7 @@ public class DetectTvSystemAudioModeSupportActionTest { public void SetUp() { mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234); HdmiControlService hdmiControlService = - new HdmiControlService(null) { + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override void sendCecCommand( diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java index cc005ed266b3..da840be9bca7 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java @@ -18,7 +18,6 @@ package com.android.server.hdmi; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV; - import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; @@ -26,18 +25,15 @@ import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3; import static com.android.server.hdmi.Constants.ADDR_SPECIFIC_USE; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; - import static junit.framework.Assert.assertEquals; import android.content.Context; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -84,7 +80,7 @@ public class HdmiCecControllerTest { public void SetUp() { mMyLooper = mTestLooper.getLooper(); mMyLooper = mTestLooper.getLooper(); - mHdmiControlService = new MyHdmiControlService(null); + mHdmiControlService = new MyHdmiControlService(InstrumentationRegistry.getTargetContext()); mNativeWrapper = new FakeNativeWrapper(); mHdmiCecController = HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index 6dbbbfe8e9e4..0e15ba6d0f46 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -30,14 +30,13 @@ import android.media.AudioManager; import android.os.Looper; import android.os.SystemProperties; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; import org.junit.Before; -import org.junit.Test; import org.junit.Ignore; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -48,7 +47,6 @@ import java.util.ArrayList; /** Tests for {@link HdmiCecLocalDeviceAudioSystem} class. */ public class HdmiCecLocalDeviceAudioSystemTest { - private static final String TAG = "HdmiCecLocalDeviceAudioSystemTest"; private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem; @@ -61,9 +59,9 @@ public class HdmiCecLocalDeviceAudioSystemTest { private boolean mMusicMute; @Before - public void SetUp() { + public void setUp() { mHdmiControlService = - new HdmiControlService(null) { + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override AudioManager getAudioManager() { return new AudioManager() { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index daf35dd382fa..910af78b2d7e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -30,9 +30,9 @@ import static junit.framework.Assert.assertTrue; import android.hardware.hdmi.HdmiControlManager; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - +import java.util.Arrays; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -95,7 +95,7 @@ public class HdmiCecLocalDeviceTest { @Before public void SetUp() { mHdmiControlService = - new HdmiControlService(null) { + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override boolean isControlEnabled() { return isControlEnabled; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 71af71e711ea..18c9a653dc7d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -25,9 +25,8 @@ import static junit.framework.Assert.assertTrue; import android.os.Looper; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -107,7 +106,7 @@ public class HdmiControlServiceTest { @Before public void SetUp() { mHdmiControlService = - new HdmiControlService(null) { + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override boolean isStandbyMessageReceived() { return mStandbyMessageReceived; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java index 6ff1c0f2cef1..d914b9a090a2 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -26,9 +26,8 @@ import android.hardware.tv.cec.V1_0.SendMessageResult; import android.media.AudioManager; import android.os.Looper; import android.os.test.TestLooper; - +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -55,7 +54,7 @@ public class SystemAudioInitiationActionFromAvrTest { public void SetUp() { mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234); HdmiControlService hdmiControlService = - new HdmiControlService(null) { + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { @Override void sendCecCommand( diff --git a/tests/net/java/android/net/NetworkUtilsTest.java b/tests/net/java/android/net/NetworkUtilsTest.java index 2b172dac4865..3452819835f5 100644 --- a/tests/net/java/android/net/NetworkUtilsTest.java +++ b/tests/net/java/android/net/NetworkUtilsTest.java @@ -24,6 +24,8 @@ import static android.net.NetworkUtils.intToInet4AddressHTL; import static android.net.NetworkUtils.netmaskToPrefixLength; import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH; import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTL; +import static android.net.NetworkUtils.getBroadcastAddress; +import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; import static junit.framework.Assert.assertEquals; @@ -125,7 +127,6 @@ public class NetworkUtilsTest { assertInvalidNetworkMask(IPv4Address("255.255.0.255")); } - @Test public void testPrefixLengthToV4NetmaskIntHTL() { assertEquals(0, prefixLengthToV4NetmaskIntHTL(0)); @@ -266,4 +267,44 @@ public class NetworkUtilsTest { assertEquals(BigInteger.valueOf(7l - 4 + 4 + 16 + 65536), NetworkUtils.routedIPv6AddressCount(set)); } + + @Test + public void testGetPrefixMaskAsAddress() { + assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress()); + assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress()); + assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress()); + assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress()); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetPrefixMaskAsAddress_PrefixTooLarge() { + getPrefixMaskAsInet4Address(33); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetPrefixMaskAsAddress_NegativePrefix() { + getPrefixMaskAsInet4Address(-1); + } + + @Test + public void testGetBroadcastAddress() { + assertEquals("192.168.15.255", + getBroadcastAddress(IPv4Address("192.168.0.123"), 20).getHostAddress()); + assertEquals("192.255.255.255", + getBroadcastAddress(IPv4Address("192.168.0.123"), 8).getHostAddress()); + assertEquals("192.168.0.123", + getBroadcastAddress(IPv4Address("192.168.0.123"), 32).getHostAddress()); + assertEquals("255.255.255.255", + getBroadcastAddress(IPv4Address("192.168.0.123"), 0).getHostAddress()); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetBroadcastAddress_PrefixTooLarge() { + getBroadcastAddress(IPv4Address("192.168.0.123"), 33); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetBroadcastAddress_NegativePrefix() { + getBroadcastAddress(IPv4Address("192.168.0.123"), -1); + } } diff --git a/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java b/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java new file mode 100644 index 000000000000..edadd6ead667 --- /dev/null +++ b/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.dhcp; + +import static android.net.dhcp.DhcpLease.HOSTNAME_NONE; +import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC; +import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.when; + +import static java.lang.String.format; +import static java.net.InetAddress.parseNumericAddress; + +import android.annotation.NonNull; +import android.net.IpPrefix; +import android.net.MacAddress; +import android.net.dhcp.DhcpLeaseRepository.Clock; +import android.net.util.SharedLog; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.net.Inet4Address; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DhcpLeaseRepositoryTest { + private static final Inet4Address INET4_ANY = (Inet4Address) Inet4Address.ANY; + private static final Inet4Address TEST_DEF_ROUTER = parseAddr4("192.168.42.247"); + private static final Inet4Address TEST_SERVER_ADDR = parseAddr4("192.168.42.241"); + private static final Inet4Address TEST_RESERVED_ADDR = parseAddr4("192.168.42.243"); + private static final MacAddress TEST_MAC_1 = MacAddress.fromBytes( + new byte[] { 5, 4, 3, 2, 1, 0 }); + private static final MacAddress TEST_MAC_2 = MacAddress.fromBytes( + new byte[] { 0, 1, 2, 3, 4, 5 }); + private static final MacAddress TEST_MAC_3 = MacAddress.fromBytes( + new byte[] { 0, 1, 2, 3, 4, 6 }); + private static final Inet4Address TEST_INETADDR_1 = parseAddr4("192.168.42.248"); + private static final Inet4Address TEST_INETADDR_2 = parseAddr4("192.168.42.249"); + private static final String TEST_HOSTNAME_1 = "hostname1"; + private static final String TEST_HOSTNAME_2 = "hostname2"; + private static final IpPrefix TEST_IP_PREFIX = new IpPrefix(TEST_SERVER_ADDR, 22); + private static final long TEST_TIME = 100L; + private static final int TEST_LEASE_TIME_MS = 3_600_000; + private static final Set<Inet4Address> TEST_EXCL_SET = + Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + TEST_SERVER_ADDR, TEST_DEF_ROUTER, TEST_RESERVED_ADDR))); + + @NonNull + private SharedLog mLog; + @NonNull @Mock + private Clock mClock; + @NonNull + private DhcpLeaseRepository mRepo; + + private static Inet4Address parseAddr4(String inet4Addr) { + return (Inet4Address) parseNumericAddress(inet4Addr); + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mLog = new SharedLog("DhcpLeaseRepositoryTest"); + when(mClock.elapsedRealtime()).thenReturn(TEST_TIME); + mRepo = new DhcpLeaseRepository( + TEST_IP_PREFIX, TEST_EXCL_SET, TEST_LEASE_TIME_MS, mLog, mClock); + } + + /** + * Request a number of addresses through offer/request. Useful to test address exhaustion. + * @param nAddr Number of addresses to request. + */ + private void requestAddresses(byte nAddr) throws Exception { + final HashSet<Inet4Address> addrs = new HashSet<>(); + byte[] hwAddrBytes = new byte[] { 8, 4, 3, 2, 1, 0 }; + for (byte i = 0; i < nAddr; i++) { + hwAddrBytes[5] = i; + MacAddress newMac = MacAddress.fromBytes(hwAddrBytes); + final String hostname = "host_" + i; + final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, newMac, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname); + + assertNotNull(lease); + assertEquals(newMac, lease.getHwAddr()); + assertEquals(hostname, lease.getHostname()); + assertTrue(format("Duplicate address allocated: %s in %s", lease.getNetAddr(), addrs), + addrs.add(lease.getNetAddr())); + + mRepo.requestLease(null, newMac, null, lease.getNetAddr(), true, hostname); + } + } + + @Test + public void testAddressExhaustion() throws Exception { + // Use a /28 to quickly run out of addresses + mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), TEST_EXCL_SET, TEST_LEASE_TIME_MS); + + // /28 should have 16 addresses, 14 w/o the first/last, 11 w/o excluded addresses + requestAddresses((byte)11); + + try { + mRepo.getOffer(null, TEST_MAC_2, + null /* relayAddr */, null /* reqAddr */, HOSTNAME_NONE); + fail("Should be out of addresses"); + } catch (DhcpLeaseRepository.OutOfAddressesException e) { + // Expected + } + } + + @Test + public void testUpdateParams_LeaseCleanup() throws Exception { + // Inside /28: + final Inet4Address reqAddrIn28 = parseAddr4("192.168.42.242"); + final Inet4Address declinedAddrIn28 = parseAddr4("192.168.42.245"); + + // Inside /28, but not available there (first address of the range) + final Inet4Address declinedFirstAddrIn28 = parseAddr4("192.168.42.240"); + + final DhcpLease reqAddrIn28Lease = mRepo.requestLease( + CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, reqAddrIn28, false, HOSTNAME_NONE); + mRepo.markLeaseDeclined(declinedAddrIn28); + mRepo.markLeaseDeclined(declinedFirstAddrIn28); + + // Inside /22, but outside /28: + final Inet4Address reqAddrIn22 = parseAddr4("192.168.42.3"); + final Inet4Address declinedAddrIn22 = parseAddr4("192.168.42.4"); + + final DhcpLease reqAddrIn22Lease = mRepo.requestLease( + CLIENTID_UNSPEC, TEST_MAC_3, INET4_ANY, reqAddrIn22, false, HOSTNAME_NONE); + mRepo.markLeaseDeclined(declinedAddrIn22); + + // Address that will be reserved in the updateParams call below + final Inet4Address reservedAddr = parseAddr4("192.168.42.244"); + final DhcpLease reservedAddrLease = mRepo.requestLease( + CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, reservedAddr, false, HOSTNAME_NONE); + + // Update from /22 to /28 and add another reserved address + Set<Inet4Address> newReserved = new HashSet<>(TEST_EXCL_SET); + newReserved.add(reservedAddr); + mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), newReserved, TEST_LEASE_TIME_MS); + + assertHasLease(reqAddrIn28Lease); + assertDeclined(declinedAddrIn28); + + assertNotDeclined(declinedFirstAddrIn28); + + assertNoLease(reqAddrIn22Lease); + assertNotDeclined(declinedAddrIn22); + + assertNoLease(reservedAddrLease); + } + + @Test + public void testGetOffer_StableAddress() throws Exception { + for (final MacAddress macAddr : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) { + final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + + // Same lease is offered twice + final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + assertEquals(lease, newLease); + } + } + + @Test + public void testUpdateParams_UsesNewPrefix() throws Exception { + final IpPrefix newPrefix = new IpPrefix(parseAddr4("192.168.123.0"), 24); + mRepo.updateParams(newPrefix, TEST_EXCL_SET, TEST_LEASE_TIME_MS); + + DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, + INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE); + assertTrue(newPrefix.contains(lease.getNetAddr())); + } + + @Test + public void testGetOffer_ExistingLease() throws Exception { + mRepo.requestLease( + CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, TEST_HOSTNAME_1); + + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, + INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE); + assertEquals(TEST_INETADDR_1, offer.getNetAddr()); + assertEquals(TEST_HOSTNAME_1, offer.getHostname()); + } + + @Test + public void testGetOffer_ClientIdHasExistingLease() throws Exception { + final byte[] clientId = new byte[] { 1, 2 }; + mRepo.requestLease(clientId, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, + TEST_HOSTNAME_1); + + // Different MAC, but same clientId + DhcpLease offer = mRepo.getOffer(clientId, TEST_MAC_2, + INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE); + assertEquals(TEST_INETADDR_1, offer.getNetAddr()); + assertEquals(TEST_HOSTNAME_1, offer.getHostname()); + } + + @Test + public void testGetOffer_DifferentClientId() throws Exception { + final byte[] clientId1 = new byte[] { 1, 2 }; + final byte[] clientId2 = new byte[] { 3, 4 }; + mRepo.requestLease(clientId1, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, + TEST_HOSTNAME_1); + + // Same MAC, different client ID + DhcpLease offer = mRepo.getOffer(clientId2, TEST_MAC_1, + INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE); + // Obtains a different address + assertNotEquals(TEST_INETADDR_1, offer.getNetAddr()); + assertEquals(HOSTNAME_NONE, offer.getHostname()); + assertEquals(TEST_MAC_1, offer.getHwAddr()); + } + + @Test + public void testGetOffer_RequestedAddress() throws Exception { + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, TEST_HOSTNAME_1); + assertEquals(TEST_INETADDR_1, offer.getNetAddr()); + assertEquals(TEST_HOSTNAME_1, offer.getHostname()); + } + + @Test + public void testGetOffer_RequestedAddressInUse() throws Exception { + mRepo.requestLease( + CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, HOSTNAME_NONE); + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, + TEST_INETADDR_1, HOSTNAME_NONE); + assertNotEquals(TEST_INETADDR_1, offer.getNetAddr()); + } + + @Test + public void testGetOffer_RequestedAddressReserved() throws Exception { + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_RESERVED_ADDR, HOSTNAME_NONE); + assertNotEquals(TEST_RESERVED_ADDR, offer.getNetAddr()); + } + + @Test + public void testGetOffer_RequestedAddressInvalid() throws Exception { + final Inet4Address invalidAddr = parseAddr4("192.168.42.0"); + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + invalidAddr, HOSTNAME_NONE); + assertNotEquals(invalidAddr, offer.getNetAddr()); + } + + @Test + public void testGetOffer_RequestedAddressOutsideSubnet() throws Exception { + final Inet4Address invalidAddr = parseAddr4("192.168.254.2"); + DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + invalidAddr, HOSTNAME_NONE); + assertNotEquals(invalidAddr, offer.getNetAddr()); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testGetOffer_RelayInInvalidSubnet() throws Exception { + mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, + parseAddr4("192.168.254.2") /* relayAddr */, INETADDR_UNSPEC, HOSTNAME_NONE); + } + + @Test + public void testRequestLease_SelectingTwice() throws Exception { + DhcpLease lease1 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, TEST_HOSTNAME_1); + + // Second request from same client for a different address + DhcpLease lease2 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_2, true /* sidSet */, TEST_HOSTNAME_2); + + assertEquals(TEST_INETADDR_1, lease1.getNetAddr()); + assertEquals(TEST_HOSTNAME_1, lease1.getHostname()); + + assertEquals(TEST_INETADDR_2, lease2.getNetAddr()); + assertEquals(TEST_HOSTNAME_2, lease2.getHostname()); + + // First address freed when client requested a different one: another client can request it + DhcpLease lease3 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + assertEquals(TEST_INETADDR_1, lease3.getNetAddr()); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_SelectingInvalid() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + parseAddr4("192.168.254.5"), true /* sidSet */, HOSTNAME_NONE); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_SelectingInUse() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_SelectingReserved() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_RESERVED_ADDR, true /* sidSet */, HOSTNAME_NONE); + } + + @Test + public void testRequestLease_InitReboot() throws Exception { + // Request address once + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + + final long newTime = TEST_TIME + 100; + when(mClock.elapsedRealtime()).thenReturn(newTime); + + // init-reboot (sidSet == false): verify configuration + DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, false, HOSTNAME_NONE); + assertEquals(TEST_INETADDR_1, lease.getNetAddr()); + assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime()); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_InitRebootWrongAddr() throws Exception { + // Request address once + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + // init-reboot with different requested address + mRepo.requestLease( + CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, TEST_INETADDR_2, false, HOSTNAME_NONE); + } + + @Test + public void testRequestLease_InitRebootUnknownAddr() throws Exception { + // init-reboot with unknown requested address + DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_2, false, HOSTNAME_NONE); + // RFC2131 says we should not reply to accommodate other servers, but since we are + // authoritative we allow creating the lease to avoid issues with lost lease DB (same as + // dnsmasq behavior) + assertEquals(TEST_INETADDR_2, lease.getNetAddr()); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_InitRebootWrongSubnet() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + parseAddr4("192.168.254.2"), false /* sidSet */, HOSTNAME_NONE); + } + + @Test + public void testRequestLease_Renewing() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, + INET4_ANY /* clientAddr */, TEST_INETADDR_1 /* reqAddr */, true, HOSTNAME_NONE); + + final long newTime = TEST_TIME + 100; + when(mClock.elapsedRealtime()).thenReturn(newTime); + + // Renewing: clientAddr filled in, no reqAddr + DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, + TEST_INETADDR_1 /* clientAddr */, INETADDR_UNSPEC /* reqAddr */, false, + HOSTNAME_NONE); + + assertEquals(TEST_INETADDR_1, lease.getNetAddr()); + assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime()); + } + + @Test + public void testRequestLease_RenewingUnknownAddr() throws Exception { + final long newTime = TEST_TIME + 100; + when(mClock.elapsedRealtime()).thenReturn(newTime); + DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, + TEST_INETADDR_1 /* clientAddr */, INETADDR_UNSPEC /* reqAddr */, false, + HOSTNAME_NONE); + // Allows renewing an unknown address if available + assertEquals(TEST_INETADDR_1, lease.getNetAddr()); + assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime()); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_RenewingAddrInUse() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, + INET4_ANY /* clientAddr */, TEST_INETADDR_1 /* reqAddr */, true, HOSTNAME_NONE); + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, + TEST_INETADDR_1 /* clientAddr */, INETADDR_UNSPEC /* reqAddr */, false, + HOSTNAME_NONE); + } + + @Test(expected = DhcpLeaseRepository.InvalidAddressException.class) + public void testRequestLease_RenewingInvalidAddr() throws Exception { + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, parseAddr4("192.168.254.2") /* clientAddr */, + INETADDR_UNSPEC /* reqAddr */, false, HOSTNAME_NONE); + } + + @Test + public void testReleaseLease() throws Exception { + DhcpLease lease1 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + + assertHasLease(lease1); + assertTrue(mRepo.releaseLease(CLIENTID_UNSPEC, TEST_MAC_1, TEST_INETADDR_1)); + assertNoLease(lease1); + + DhcpLease lease2 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, + TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE); + + assertEquals(TEST_INETADDR_1, lease2.getNetAddr()); + } + + @Test + public void testReleaseLease_UnknownLease() { + assertFalse(mRepo.releaseLease(CLIENTID_UNSPEC, TEST_MAC_1, TEST_INETADDR_1)); + } + + @Test + public void testReleaseLease_StableOffer() throws Exception { + for (MacAddress mac : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) { + final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, mac, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + mRepo.requestLease( + CLIENTID_UNSPEC, mac, INET4_ANY, lease.getNetAddr(), true, + HOSTNAME_NONE); + mRepo.releaseLease(CLIENTID_UNSPEC, mac, lease.getNetAddr()); + + // Same lease is offered after it was released + final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, mac, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + assertEquals(lease.getNetAddr(), newLease.getNetAddr()); + } + } + + @Test + public void testMarkLeaseDeclined() throws Exception { + final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + + mRepo.markLeaseDeclined(lease.getNetAddr()); + + // Same lease is not offered again + final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + assertNotEquals(lease.getNetAddr(), newLease.getNetAddr()); + } + + @Test + public void testMarkLeaseDeclined_UsedIfOutOfAddresses() throws Exception { + // Use a /28 to quickly run out of addresses + mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), TEST_EXCL_SET, TEST_LEASE_TIME_MS); + + mRepo.markLeaseDeclined(TEST_INETADDR_1); + mRepo.markLeaseDeclined(TEST_INETADDR_2); + + // /28 should have 16 addresses, 14 w/o the first/last, 11 w/o excluded addresses + requestAddresses((byte)9); + + // Last 2 addresses: addresses marked declined should be used + final DhcpLease firstLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1); + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, firstLease.getNetAddr(), true, + HOSTNAME_NONE); + + final DhcpLease secondLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, + INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2); + mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, secondLease.getNetAddr(), true, + HOSTNAME_NONE); + + // Now out of addresses + try { + mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, INETADDR_UNSPEC /* relayAddr */, + INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE); + fail("Repository should be out of addresses and throw"); + } catch (DhcpLeaseRepository.OutOfAddressesException e) { /* expected */ } + + assertEquals(TEST_INETADDR_1, firstLease.getNetAddr()); + assertEquals(TEST_HOSTNAME_1, firstLease.getHostname()); + assertEquals(TEST_INETADDR_2, secondLease.getNetAddr()); + assertEquals(TEST_HOSTNAME_2, secondLease.getHostname()); + } + + private void assertNoLease(DhcpLease lease) { + assertFalse("Leases contain " + lease, mRepo.getCommittedLeases().contains(lease)); + } + + private void assertHasLease(DhcpLease lease) { + assertTrue("Leases do not contain " + lease, mRepo.getCommittedLeases().contains(lease)); + } + + private void assertNotDeclined(Inet4Address addr) { + assertFalse("Address is declined: " + addr, mRepo.getDeclinedAddresses().contains(addr)); + } + + private void assertDeclined(Inet4Address addr) { + assertTrue("Address is not declined: " + addr, mRepo.getDeclinedAddresses().contains(addr)); + } +} diff --git a/tests/net/java/android/net/dhcp/DhcpServingParamsTest.java b/tests/net/java/android/net/dhcp/DhcpServingParamsTest.java new file mode 100644 index 000000000000..dfa09a9f205c --- /dev/null +++ b/tests/net/java/android/net/dhcp/DhcpServingParamsTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.dhcp; + +import static android.net.dhcp.DhcpServingParams.MTU_UNSET; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static java.net.InetAddress.parseNumericAddress; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.LinkAddress; +import android.net.dhcp.DhcpServingParams.InvalidParameterException; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DhcpServingParamsTest { + @NonNull + private DhcpServingParams.Builder mBuilder; + + private static final Set<Inet4Address> TEST_DEFAULT_ROUTERS = new HashSet<>( + Arrays.asList(parseAddr("192.168.0.123"), parseAddr("192.168.0.124"))); + private static final long TEST_LEASE_TIME_SECS = 3600L; + private static final Set<Inet4Address> TEST_DNS_SERVERS = new HashSet<>( + Arrays.asList(parseAddr("192.168.0.126"), parseAddr("192.168.0.127"))); + private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2"); + private static final LinkAddress TEST_LINKADDR = new LinkAddress(TEST_SERVER_ADDR, 20); + private static final int TEST_MTU = 1500; + private static final Set<Inet4Address> TEST_EXCLUDED_ADDRS = new HashSet<>( + Arrays.asList(parseAddr("192.168.0.200"), parseAddr("192.168.0.201"))); + + @Before + public void setUp() { + mBuilder = new DhcpServingParams.Builder() + .setDefaultRouters(TEST_DEFAULT_ROUTERS) + .setDhcpLeaseTimeSecs(TEST_LEASE_TIME_SECS) + .setDnsServers(TEST_DNS_SERVERS) + .setServerAddr(TEST_LINKADDR) + .setLinkMtu(TEST_MTU) + .setExcludedAddrs(TEST_EXCLUDED_ADDRS); + } + + @Test + public void testBuild_Immutable() throws InvalidParameterException { + final Set<Inet4Address> routers = new HashSet<>(TEST_DEFAULT_ROUTERS); + final Set<Inet4Address> dnsServers = new HashSet<>(TEST_DNS_SERVERS); + final Set<Inet4Address> excludedAddrs = new HashSet<>(TEST_EXCLUDED_ADDRS); + + final DhcpServingParams params = mBuilder + .setDefaultRouters(routers) + .setDnsServers(dnsServers) + .setExcludedAddrs(excludedAddrs) + .build(); + + // Modifications to source objects should not affect builder or final parameters + final Inet4Address addedAddr = parseAddr("192.168.0.223"); + routers.add(addedAddr); + dnsServers.add(addedAddr); + excludedAddrs.add(addedAddr); + + assertEquals(TEST_DEFAULT_ROUTERS, params.defaultRouters); + assertEquals(TEST_LEASE_TIME_SECS, params.dhcpLeaseTimeSecs); + assertEquals(TEST_DNS_SERVERS, params.dnsServers); + assertEquals(TEST_LINKADDR, params.serverAddr); + assertEquals(TEST_MTU, params.linkMtu); + + assertContains(params.excludedAddrs, TEST_EXCLUDED_ADDRS); + assertContains(params.excludedAddrs, TEST_DEFAULT_ROUTERS); + assertContains(params.excludedAddrs, TEST_DNS_SERVERS); + assertContains(params.excludedAddrs, TEST_SERVER_ADDR); + + assertFalse("excludedAddrs should not contain " + addedAddr, + params.excludedAddrs.contains(addedAddr)); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_NegativeLeaseTime() throws InvalidParameterException { + mBuilder.setDhcpLeaseTimeSecs(-1).build(); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_LeaseTimeTooLarge() throws InvalidParameterException { + // Set lease time larger than max value for uint32 + mBuilder.setDhcpLeaseTimeSecs(1L << 32).build(); + } + + @Test + public void testBuild_InfiniteLeaseTime() throws InvalidParameterException { + final long infiniteLeaseTime = 0xffffffffL; + final DhcpServingParams params = mBuilder + .setDhcpLeaseTimeSecs(infiniteLeaseTime).build(); + assertEquals(infiniteLeaseTime, params.dhcpLeaseTimeSecs); + assertTrue(params.dhcpLeaseTimeSecs > 0L); + } + + @Test + public void testBuild_UnsetMtu() throws InvalidParameterException { + final DhcpServingParams params = mBuilder.setLinkMtu(MTU_UNSET).build(); + assertEquals(MTU_UNSET, params.linkMtu); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_MtuTooSmall() throws InvalidParameterException { + mBuilder.setLinkMtu(20).build(); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_MtuTooLarge() throws InvalidParameterException { + mBuilder.setLinkMtu(65_536).build(); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_IPv6Addr() throws InvalidParameterException { + mBuilder.setServerAddr(new LinkAddress(parseNumericAddress("fe80::1111"), 120)).build(); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_PrefixTooLarge() throws InvalidParameterException { + mBuilder.setServerAddr(new LinkAddress(TEST_SERVER_ADDR, 15)).build(); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_PrefixTooSmall() throws InvalidParameterException { + mBuilder.setDefaultRouters(Collections.singleton(parseAddr("192.168.0.254"))) + .setServerAddr(new LinkAddress(TEST_SERVER_ADDR, 31)) + .build(); + } + + @Test(expected = InvalidParameterException.class) + public void testBuild_RouterNotInPrefix() throws InvalidParameterException { + mBuilder.setDefaultRouters(Collections.singleton(parseAddr("192.168.254.254"))).build(); + } + + private static <T> void assertContains(@NonNull Set<T> set, @NonNull Set<T> subset) { + for (final T elem : subset) { + assertContains(set, elem); + } + } + + private static <T> void assertContains(@NonNull Set<T> set, @Nullable T elem) { + assertTrue("Set does not contain " + elem, set.contains(elem)); + } + + @NonNull + private static Inet4Address parseAddr(@NonNull String inet4Addr) { + return (Inet4Address) parseNumericAddress(inet4Addr); + } +} diff --git a/tools/aosp/aosp_sha.sh b/tools/aosp/aosp_sha.sh new file mode 100755 index 000000000000..29bf74c7a8b9 --- /dev/null +++ b/tools/aosp/aosp_sha.sh @@ -0,0 +1,18 @@ +#!/bin/bash +LOCAL_DIR="$( dirname ${BASH_SOURCE} )" + +if git branch -vv | grep "^*" | grep "\[aosp/master" > /dev/null; then + # Change appears to be in AOSP + exit 0 +else + # Change appears to be non-AOSP; search for files + git show --name-only --pretty=format: $1 | grep $2 | while read file; do + echo + echo -e "\033[0;31mThe source of truth for '$file' is in AOSP.\033[0m" + echo + echo "If your change contains no confidential details, please upload and merge" + echo "this change at https://android-review.googlesource.com/." + echo + exit 77 + done +fi |