diff --git a/src/controller/java/AndroidDeviceControllerWrapper.cpp b/src/controller/java/AndroidDeviceControllerWrapper.cpp index df8c48bbf2a805..2c585c70fb0c6d 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.cpp +++ b/src/controller/java/AndroidDeviceControllerWrapper.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -34,6 +35,7 @@ #include #include #include +#include using namespace chip; using namespace chip::Controller; @@ -47,6 +49,12 @@ AndroidDeviceControllerWrapper::~AndroidDeviceControllerWrapper() JniReferences::GetInstance().GetEnvForCurrentThread()->DeleteGlobalRef(mJavaObjectRef); } mController->Shutdown(); + + if (mKeypairBridge != nullptr) + { + chip::Platform::Delete(mKeypairBridge); + mKeypairBridge = nullptr; + } } void AndroidDeviceControllerWrapper::SetJavaObjectRef(JavaVM * vm, jobject obj) @@ -65,7 +73,8 @@ AndroidDeviceControllerWrapper * AndroidDeviceControllerWrapper::AllocateNew( JavaVM * vm, jobject deviceControllerObj, chip::NodeId nodeId, const chip::CATValues & cats, chip::System::Layer * systemLayer, chip::Inet::EndPointManager * tcpEndPointManager, chip::Inet::EndPointManager * udpEndPointManager, AndroidOperationalCredentialsIssuerPtr opCredsIssuerPtr, - uint16_t listenPort, CHIP_ERROR * errInfoOnFailure) + jobject keypairDelegate, jbyteArray rootCertificate, jbyteArray intermediateCertificate, jbyteArray nodeOperationalCertificate, + jbyteArray ipkEpochKey, uint16_t listenPort, CHIP_ERROR * errInfoOnFailure) { if (errInfoOnFailure == nullptr) { @@ -93,6 +102,14 @@ AndroidDeviceControllerWrapper * AndroidDeviceControllerWrapper::AllocateNew( *errInfoOnFailure = CHIP_NO_ERROR; + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + if (env == nullptr) + { + ChipLogError(Controller, "Failed to retrieve JNIEnv."); + *errInfoOnFailure = CHIP_ERROR_INCORRECT_STATE; + return nullptr; + } + std::unique_ptr controller(new DeviceCommissioner()); if (!controller) @@ -166,25 +183,53 @@ AndroidDeviceControllerWrapper * AndroidDeviceControllerWrapper::AllocateNew( } MutableByteSpan rcacSpan(rcac.Get(), kMaxCHIPDERCertLength); - Crypto::P256Keypair ephemeralKey; - *errInfoOnFailure = ephemeralKey.Initialize(); - if (*errInfoOnFailure != CHIP_NO_ERROR) + if (rootCertificate != nullptr && intermediateCertificate != nullptr && nodeOperationalCertificate != nullptr && + keypairDelegate != nullptr) { - return nullptr; + CHIPP256KeypairBridge * nativeKeypairBridge = wrapper->GetP256KeypairBridge(); + nativeKeypairBridge->SetDelegate(keypairDelegate); + *errInfoOnFailure = nativeKeypairBridge->Initialize(); + if (*errInfoOnFailure != CHIP_NO_ERROR) + { + return nullptr; + } + + setupParams.operationalKeypair = nativeKeypairBridge; + setupParams.hasExternallyOwnedOperationalKeypair = true; + + JniByteArray jniRcac(env, rootCertificate); + JniByteArray jniIcac(env, intermediateCertificate); + JniByteArray jniNoc(env, nodeOperationalCertificate); + + setupParams.controllerRCAC = jniRcac.byteSpan(); + setupParams.controllerICAC = jniIcac.byteSpan(); + setupParams.controllerNOC = jniNoc.byteSpan(); } - - *errInfoOnFailure = opCredsIssuer->GenerateNOCChainAfterValidation(nodeId, /* fabricId = */ 1, cats, ephemeralKey.Pubkey(), - rcacSpan, icacSpan, nocSpan); - if (*errInfoOnFailure != CHIP_NO_ERROR) + else { - return nullptr; + Crypto::P256Keypair ephemeralKey; + *errInfoOnFailure = ephemeralKey.Initialize(); + if (*errInfoOnFailure != CHIP_NO_ERROR) + { + return nullptr; + } + setupParams.operationalKeypair = &ephemeralKey; + setupParams.hasExternallyOwnedOperationalKeypair = false; + + *errInfoOnFailure = opCredsIssuer->GenerateNOCChainAfterValidation(nodeId, + /* fabricId = */ 1, cats, ephemeralKey.Pubkey(), + rcacSpan, icacSpan, nocSpan); + + if (*errInfoOnFailure != CHIP_NO_ERROR) + { + return nullptr; + } + + setupParams.controllerRCAC = rcacSpan; + setupParams.controllerICAC = icacSpan; + setupParams.controllerNOC = nocSpan; } - setupParams.operationalKeypair = &ephemeralKey; - setupParams.controllerRCAC = rcacSpan; - setupParams.controllerICAC = icacSpan; - setupParams.controllerNOC = nocSpan; - *errInfoOnFailure = DeviceControllerFactory::GetInstance().Init(initParams); if (*errInfoOnFailure != CHIP_NO_ERROR) { @@ -216,10 +261,19 @@ AndroidDeviceControllerWrapper * AndroidDeviceControllerWrapper::AllocateNew( static_cast(fabricInfo->GetFabricIndex())); ChipLogByteSpan(Support, compressedFabricIdSpan); - chip::ByteSpan defaultIpk = chip::GroupTesting::DefaultIpkValue::GetDefaultIpk(); + chip::ByteSpan ipkSpan; + if (ipkEpochKey != nullptr) + { + JniByteArray jniIpk(env, ipkEpochKey); + ipkSpan = jniIpk.byteSpan(); + } + else + { + ipkSpan = chip::GroupTesting::DefaultIpkValue::GetDefaultIpk(); + } - *errInfoOnFailure = chip::Credentials::SetSingleIpkEpochKey(&wrapper->mGroupDataProvider, fabricInfo->GetFabricIndex(), - defaultIpk, compressedFabricIdSpan); + *errInfoOnFailure = chip::Credentials::SetSingleIpkEpochKey(&wrapper->mGroupDataProvider, fabricInfo->GetFabricIndex(), ipkSpan, + compressedFabricIdSpan); if (*errInfoOnFailure != CHIP_NO_ERROR) { return nullptr; diff --git a/src/controller/java/AndroidDeviceControllerWrapper.h b/src/controller/java/AndroidDeviceControllerWrapper.h index d66d015800f17a..7437701cbcf8aa 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.h +++ b/src/controller/java/AndroidDeviceControllerWrapper.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "AndroidOperationalCredentialsIssuer.h" @@ -46,6 +47,20 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel jobject JavaObjectRef() { return mJavaObjectRef; } jlong ToJNIHandle(); + /** + * Returns a CHIPP256KeypairBridge which can be used to delegate signing operations + * to a KeypairDelegate in the Java layer. Note that this will always return a pointer + * to the same instance, once initialized. + */ + CHIPP256KeypairBridge * GetP256KeypairBridge() + { + if (mKeypairBridge == nullptr) + { + mKeypairBridge = chip::Platform::New(); + } + return mKeypairBridge; + } + void CallJavaMethod(const char * methodName, jint argument); CHIP_ERROR InitializeOperationalCredentialsIssuer(); @@ -72,12 +87,41 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel using AndroidOperationalCredentialsIssuerPtr = std::unique_ptr; + /** + * Initializes a new CHIPDeviceController using the given parameters, and returns a pointer to the + * AndroidDeviceControllerWrapper that holds the underlying controller. + * + * If the keypairDelegate is provided, then the rootCertificate, nodeOperationalCertificate, and + * ipkEpochKey must also be specified. If no operational credentials are specified here, then an + * ephemeral signing configuration will be generated for you. + * + * If there are any errors during the initialization of this controller, then a nullptr will be + * returned. + * + * @param[in] vm the JavaVM + * @param[in] deviceControllerObj a reference to the Java ChipDeviceController + * @param[in] nodeId the local node ID to use for this controller instance + * @param[in] cats the set of CASE authenticated tags + * @param[in] systemLayer a pointer to the System::Layer instance + * @param[in] tcpEndpointManager a pointer to a Inet::EndPointManager for TCP connections + * @param[in] udpEndpointManager a pointer to a Inet::EndPointManager for UDP connections + * @param[in] opCredsIssuer a pointer to an issuer for Android operational credentials + * @param[in] keypairDelegate a pointer to a Java KeypairDelegate implementation. + * @param[in] rootCertificate an X.509 DER-encoded trusted root certificate for this node + * @param[in] intermediateCertificate an X.509 DER-encoded intermediate certificate for this node + * @param[in] nodeOperationalCertificate an X.509 DER-encoded operational certificate for this node + * @param[in] ipkEpochKey the IPK epoch key to use for this node + * @param[in] listenPort the UDP port to listen on + * @param[out] errInfoOnFailure a pointer to a CHIP_ERROR that will be populated if this method returns nullptr + */ static AndroidDeviceControllerWrapper * AllocateNew(JavaVM * vm, jobject deviceControllerObj, chip::NodeId nodeId, const chip::CATValues & cats, chip::System::Layer * systemLayer, chip::Inet::EndPointManager * tcpEndPointManager, chip::Inet::EndPointManager * udpEndPointManager, - AndroidOperationalCredentialsIssuerPtr opCredsIssuer, uint16_t listenPort, - CHIP_ERROR * errInfoOnFailure); + AndroidOperationalCredentialsIssuerPtr opCredsIssuer, + jobject keypairDelegate, jbyteArray rootCertificate, + jbyteArray intermediateCertificate, jbyteArray nodeOperationalCertificate, + jbyteArray ipkEpochKey, uint16_t listenPort, CHIP_ERROR * errInfoOnFailure); private: using ChipDeviceControllerPtr = std::unique_ptr; @@ -87,8 +131,9 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel // TODO: This may need to be injected as a GroupDataProvider* chip::Credentials::GroupDataProviderImpl mGroupDataProvider; - JavaVM * mJavaVM = nullptr; - jobject mJavaObjectRef = nullptr; + JavaVM * mJavaVM = nullptr; + jobject mJavaObjectRef = nullptr; + CHIPP256KeypairBridge * mKeypairBridge = nullptr; // These fields allow us to release the string/byte array memory later. jstring ssidStr = nullptr; diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index 0c0ddd4e80413d..212d47a4c37bb6 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -71,7 +71,7 @@ using namespace chip::Credentials; static void * IOThreadMain(void * arg); static CHIP_ERROR N2J_PaseVerifierParams(JNIEnv * env, jlong setupPincode, jbyteArray pakeVerifier, jobject & outParams); -static CHIP_ERROR N2J_NetworkLocation(JNIEnv * env, jstring ipAddress, jint port, jobject & outLocation); +static CHIP_ERROR N2J_NetworkLocation(JNIEnv * env, jstring ipAddress, jint port, jint interfaceIndex, jobject & outLocation); static CHIP_ERROR GetChipPathIdValue(jobject chipPathId, uint32_t wildcardValue, uint32_t & outValue); static CHIP_ERROR ParseAttributePathList(jobject attributePathList, std::vector & outAttributePathParamsList); @@ -87,6 +87,8 @@ pthread_t sIOThread = PTHREAD_NULL; jclass sChipDeviceControllerExceptionCls = NULL; +const char * PARAMS_CLASS = "()Lchip/devicecontroller/ControllerParams;"; + } // namespace // NOTE: Remote device ID is in sync with the echo server device id @@ -164,17 +166,44 @@ JNI_METHOD(jlong, newDeviceController)(JNIEnv * env, jobject self, jobject contr // Retrieve initialization params. jmethodID getUdpListenPort; - err = chip::JniReferences::GetInstance().FindMethod(env, controllerParams, "getUdpListenPort", - "()Lchip/devicecontroller/ControllerParams;", &getUdpListenPort); + err = chip::JniReferences::GetInstance().FindMethod( + env, controllerParams, "getUdpListenPort", PARAMS_CLASS, &getUdpListenPort); SuccessOrExit(err); + + jmethodID getKeypairDelegate; + err = chip::JniReferences::GetInstance().FindMethod( + env, controllerParams, "getKeypairDelegate", PARAMS_CLASS, &getKeypairDelegate); + + jmethodID getRootCertificate; + err = chip::JniReferences::GetInstance().FindMethod( + env, controllerParams, "getRootCertificate", PARAMS_CLASS, &getRootCertificate); + + jmethodID getIntermediateCertificate; + err = chip::JniReferences::GetInstance().FindMethod( + env, controllerParams, "getIntermediateCertificate", PARAMS_CLASS, &getIntermediateCertificate); + + jmethodID getOperationalCertificate; + err = chip::JniReferences::GetInstance().FindMethod( + env, controllerParams, "getOperationalCertificate", PARAMS_CLASS, &getOperationalCertificate); + + jmethodID getIpk; + err = chip::JniReferences::GetInstance().FindMethod( + env, controllerParams, "getIpk", PARAMS_CLASS, &getIpk); + { uint16_t listenPort = env->CallIntMethod(controllerParams, getUdpListenPort); + jobject keypairDelegate = env->CallObjectMethod(controllerParams, getKeypairDelegate); + jbyteArray rootCertificate = (jbyteArray)env->CallObjectMethod(controllerParams, getRootCertificate); + jbyteArray intermediateCertificate = (jbyteArray)env->CallObjectMethod(controllerParams, getIntermediateCertificate); + jbyteArray operationalCertificate = (jbyteArray)env->CallObjectMethod(controllerParams, getOperationalCertificate); + jbyteArray ipk = (jbyteArray)env->CallObjectMethod(controllerParams, getIpk); std::unique_ptr opCredsIssuer( new chip::Controller::AndroidOperationalCredentialsIssuer()); wrapper = AndroidDeviceControllerWrapper::AllocateNew( sJVM, self, kLocalDeviceId, chip::kUndefinedCATs, &DeviceLayer::SystemLayer(), DeviceLayer::TCPEndPointManager(), - DeviceLayer::UDPEndPointManager(), std::move(opCredsIssuer), listenPort, &err); + DeviceLayer::UDPEndPointManager(), std::move(opCredsIssuer), keypairDelegate, rootCertificate, intermediateCertificate, + operationalCertificate, ipk, listenPort, &err); SuccessOrExit(err); } @@ -278,16 +307,13 @@ JNI_METHOD(void, pairDeviceWithAddress) ChipLogProgress(Controller, "pairDeviceWithAddress() called"); - Inet::IPAddress addr; JniUtfString addrJniString(env, address); - VerifyOrReturn(Inet::IPAddress::FromString(addrJniString.c_str(), addr), - ChipLogError(Controller, "Failed to parse IP address."), - JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, CHIP_ERROR_INVALID_ARGUMENT)); - RendezvousParameters rendezvousParams = RendezvousParameters() - .SetDiscriminator(discriminator) - .SetSetupPINCode(pinCode) - .SetPeerAddress(Transport::PeerAddress::UDP(addr, port)); + RendezvousParameters rendezvousParams = + RendezvousParameters() + .SetDiscriminator(discriminator) + .SetSetupPINCode(pinCode) + .SetPeerAddress(Transport::PeerAddress::UDP(const_cast(addrJniString.c_str()), port)); CommissioningParameters commissioningParams = CommissioningParameters(); if (csrNonce != nullptr) { @@ -533,26 +559,21 @@ JNI_METHOD(jobject, getNetworkLocation)(JNIEnv * env, jobject self, jlong handle chip::DeviceLayer::StackLock lock; AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); - chip::Inet::IPAddress addr; - uint16_t port; + Transport::PeerAddress addr; jobject networkLocation; char addrStr[50]; - CHIP_ERROR err = - wrapper->Controller()->GetPeerAddressAndPort(PeerId() - .SetCompressedFabricId(wrapper->Controller()->GetCompressedFabricId()) - .SetNodeId(static_cast(deviceId)), - addr, port); - + CHIP_ERROR err = wrapper->Controller()->GetPeerAddress(static_cast(deviceId), addr); if (err != CHIP_NO_ERROR) { ChipLogError(Controller, "Failed to get device address."); JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err); } - addr.ToString(addrStr); + addr.GetIPAddress().ToString(addrStr); - err = N2J_NetworkLocation(env, env->NewStringUTF(addrStr), static_cast(port), networkLocation); + err = N2J_NetworkLocation(env, env->NewStringUTF(addrStr), static_cast(addr.GetPort()), + static_cast(addr.GetInterface().GetPlatformInterface()), networkLocation); if (err != CHIP_NO_ERROR) { ChipLogError(Controller, "Failed to create NetworkLocation"); @@ -1069,7 +1090,7 @@ CHIP_ERROR N2J_PaseVerifierParams(JNIEnv * env, jlong setupPincode, jbyteArray p return err; } -CHIP_ERROR N2J_NetworkLocation(JNIEnv * env, jstring ipAddress, jint port, jobject & outLocation) +CHIP_ERROR N2J_NetworkLocation(JNIEnv * env, jstring ipAddress, jint port, jint interfaceIndex, jobject & outLocation) { CHIP_ERROR err = CHIP_NO_ERROR; jmethodID constructor; @@ -1080,10 +1101,10 @@ CHIP_ERROR N2J_NetworkLocation(JNIEnv * env, jstring ipAddress, jint port, jobje SuccessOrExit(err); env->ExceptionClear(); - constructor = env->GetMethodID(locationClass, "", "(Ljava/lang/String;I)V"); + constructor = env->GetMethodID(locationClass, "", "(Ljava/lang/String;II)V"); VerifyOrExit(constructor != nullptr, err = CHIP_JNI_ERROR_METHOD_NOT_FOUND); - outLocation = (jobject) env->NewObject(locationClass, constructor, ipAddress, port); + outLocation = (jobject) env->NewObject(locationClass, constructor, ipAddress, port, interfaceIndex); VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); exit: diff --git a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java index 1980739c87117f..56d12f4ed3f020 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java +++ b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java @@ -39,10 +39,16 @@ public static void loadJni() { return; } + /** + * Returns a new {@link ChipDeviceController} with default parameters. + */ public ChipDeviceController() { - this(new ControllerParams()); + this(ControllerParams.newBuilder().build()); } + /** + * Returns a new {@link ChipDeviceController} with the specified parameters. + */ public ChipDeviceController(ControllerParams params) { deviceControllerPtr = newDeviceController(params); } diff --git a/src/controller/java/src/chip/devicecontroller/ControllerParams.java b/src/controller/java/src/chip/devicecontroller/ControllerParams.java index 300c71174e2a86..159e610e1da51c 100644 --- a/src/controller/java/src/chip/devicecontroller/ControllerParams.java +++ b/src/controller/java/src/chip/devicecontroller/ControllerParams.java @@ -1,26 +1,137 @@ package chip.devicecontroller; -/** Parameters representing initialization arguments for {@link ChipDeviceController}. */ +import androidx.annotation.Nullable; + +/** + * Parameters representing initialization arguments for {@link ChipDeviceController}. + */ public final class ControllerParams { + private final int udpListenPort; + @Nullable + private final KeypairDelegate keypairDelegate; + @Nullable + private final byte[] rootCertificate; + @Nullable + private final byte[] intermediateCertificate; + @Nullable + private final byte[] operationalCertificate; + @Nullable + private final byte[] ipk; private static final int LEGACY_GLOBAL_CHIP_PORT = 5540; - /** Creates a {@link ControllerParams} with default values. */ - public ControllerParams() { - this(LEGACY_GLOBAL_CHIP_PORT + 1 /* To match old behavior of CHIP_PORT + 1 */); - } - - /** @param udpListenPort the UDP listening port, or 0 to pick any available port. */ - public ControllerParams(int udpListenPort) { - if (udpListenPort < 0) { - throw new IllegalArgumentException("udpListenPort must be >= 0"); - } - this.udpListenPort = udpListenPort; + /** + * @param udpListenPort the UDP listening port, or 0 to pick any available port. + */ + private ControllerParams(Builder builder) { + this.udpListenPort = builder.udpListenPort; + this.keypairDelegate = builder.keypairDelegate; + this.rootCertificate = builder.rootCertificate; + this.intermediateCertificate = builder.intermediateCertificate; + this.operationalCertificate = builder.operationalCertificate; + this.ipk = builder.ipk; } - /** Gets the UDP listening port; 0 indicates "any available port" */ + /** + * Gets the UDP listening port; 0 indicates "any available port" + */ public int getUdpListenPort() { return udpListenPort; } + + public KeypairDelegate getKeypairDelegate() { + return keypairDelegate; + } + + public byte[] getRootCertificate() { + return rootCertificate; + } + + public byte[] getIntermediateCertificate() { + return intermediateCertificate; + } + + public byte[] getOperationalCertificate() { + return operationalCertificate; + } + + public byte[] getIpk() { + return ipk; + } + + /** + * Returns parameters with ephemerally generated operational credentials + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Returns parameters which uses the provided {@code operationalKeyConfig} as its operating + * credentials. + */ + public static Builder newBuilder(OperationalKeyConfig operationalKeyConfig) { + return newBuilder() + .setKeypairDelegate(operationalKeyConfig.getKeypairDelegate()) + .setRootCertificate(operationalKeyConfig.getTrustedRootCertificate()) + .setIntermediateCertificate(operationalKeyConfig.getIntermediateCertificate()) + .setOperationalCertificate(operationalKeyConfig.getNodeOperationalCertificate()) + .setIpk(operationalKeyConfig.getIpkEpochKey()); + } + + /** Builder for {@link ControllerParams}. */ + public static class Builder { + private int udpListenPort = LEGACY_GLOBAL_CHIP_PORT + 1; + @Nullable + private KeypairDelegate keypairDelegate = null; + @Nullable + private byte[] rootCertificate = null; + @Nullable + private byte[] intermediateCertificate = null; + @Nullable + private byte[] operationalCertificate = null; + @Nullable + private byte[] ipk = null; + + private Builder() { + } + + public Builder setUdpListenPort(int udpListenPort) { + if (udpListenPort < 0) { + throw new IllegalArgumentException("udpListenPort must be >= 0"); + } + this.udpListenPort = udpListenPort; + return this; + } + + public Builder setKeypairDelegate(KeypairDelegate keypairDelegate) { + this.keypairDelegate = keypairDelegate; + return this; + } + + public Builder setRootCertificate(byte[] rootCertificate) { + this.rootCertificate = rootCertificate; + return this; + } + + public Builder setIntermediateCertificate(byte[] intermediateCertificate) { + this.intermediateCertificate = intermediateCertificate; + return this; + } + + public Builder setOperationalCertificate(byte[] operationalCertificate) { + this.operationalCertificate = operationalCertificate; + return this; + } + + public Builder setIpk(byte[] ipk) { + this.ipk = ipk; + return this; + } + + public ControllerParams build() { + return new ControllerParams(this); + } + } } diff --git a/src/controller/java/src/chip/devicecontroller/NetworkLocation.java b/src/controller/java/src/chip/devicecontroller/NetworkLocation.java index 2f1380f98d8e96..a09aec2df50724 100644 --- a/src/controller/java/src/chip/devicecontroller/NetworkLocation.java +++ b/src/controller/java/src/chip/devicecontroller/NetworkLocation.java @@ -6,10 +6,12 @@ public final class NetworkLocation { private final String ipAddress; private final int port; + private final int interfaceIndex; - public NetworkLocation(String ipAddress, int port) { + public NetworkLocation(String ipAddress, int port, int interfaceIndex) { this.ipAddress = ipAddress; this.port = port; + this.interfaceIndex = interfaceIndex; } /** Returns the IP address (e.g. fe80::3e61:5ff:fe0c:89f8). */ @@ -21,8 +23,18 @@ public int getPort() { return port; } + /** Returns the index of the network interface to which this address belongs, or zero. */ + public int getInterfaceIndex() { + return interfaceIndex; + } + @Override public String toString() { - return String.format(Locale.ROOT, "%s[%d]", ipAddress, port); + return String.format( + Locale.ROOT, + "%s%s[%d]", + ipAddress, + (interfaceIndex == 0 ? "" : "%" + interfaceIndex), + port); } }