diff --git a/include/commissioning/commissioner.h b/include/commissioning/commissioner.h index f78d24760..db2b547c3 100644 --- a/include/commissioning/commissioner.h +++ b/include/commissioning/commissioner.h @@ -50,23 +50,60 @@ extern "C" { * This function enables the Thread Commissioner role. * * @param[in] aInstance A pointer to an OpenThread instance. - * @param[in] aPSKd A pointer to the PSKd. - * @param[in] aProvisioningUrl A pointer to the Provisioning URL (may be NULL). * - * @retval kThreadError_None Successfully started the Commissioner role. - * @retval kThreadError_InvalidArgs @p aPSKd or @p aProvisioningUrl is invalid. + * @retval kThreadError_None Successfully started the Commissioner role. * */ -ThreadError otCommissionerStart(otInstance *aInstance, const char *aPSKd, const char *aProvisioningUrl); +ThreadError otCommissionerStart(otInstance *aInstance); /** * This function disables the Thread Commissioner role. * - * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval kThreadError_None Successfully started the Commissioner role. * */ ThreadError otCommissionerStop(otInstance *aInstance); +/** + * This function adds a Joiner entry. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aExtAddress A pointer to the Joiner's extended address or NULL for any Joiner. + * @param[in] aPSKd A pointer to the PSKd. + * + * @retval kThreadError_None Successfully added the Joiner. + * @retval kThreadError_NoBufs No buffers available to add the Joiner. + * @retval kThreadError_InvalidArgs @p aExtAddress or @p aPSKd is invalid. + * + */ +ThreadError otCommissionerAddJoiner(otInstance *aInstance, const otExtAddress *aExtAddress, const char *aPSKd); + +/** + * This function removes a Joiner entry. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aExtAddress A pointer to the Joiner's extended address or NULL for any Joiner. + * + * @retval kThreadError_None Successfully added the Joiner. + * @retval kThreadError_NotFound The Joiner specified by @p aExtAddress was not found. + * @retval kThreadError_InvalidArgs @p aExtAddress is invalid. + * + */ +ThreadError otCommissionerRemoveJoiner(otInstance *aIntsance, const otExtAddress *aExtAddress); + +/** + * This function sets the Provisioning URL. + * + * @param[in] aProvisioningUrl A pointer to the Provisioning URL (may be NULL). + * + * @retval kThreadError_None Successfully added the Joiner. + * @retval kThreadError_InvalidArgs @p aProvisioningUrl is invalid. + * + */ +ThreadError otCommissionerSetProvisioningUrl(otInstance *aInstance, const char *aProvisioningUrl); + /** * This function pointer is called when the Commissioner receives an Energy Report. * diff --git a/src/cli/README.md b/src/cli/README.md index 6f0e30e9f..686bdd38e 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -213,17 +213,16 @@ Set the Thread Child Timeout value. Done ``` -### commissioner start \ \ +### commissioner start \ Start the Commissioner role. -* pskd: Pre-Shared Key for the Joiner. * provisioningUrl: Provisioning URL for the Joiner (optional). This command will cause the device to send LEAD_PET and LEAD_KA messages. ```bash -> commissioner start PSK +> commissioner start Done ``` @@ -238,6 +237,38 @@ This command will cause the device to send LEAD_KA[Reject] messages. Done ``` +### commissioner joiner add \ \ + +Add a Joiner entry. + +* hashmacaddr: The Extended Address of the Joiner or '*' to match any Joiner. +* pskd: Pre-Shared Key for the Joiner. + +```bash +> commissioner joiner add d45e64fa83f81cf7 PSK +Done +``` + +### commissioner joiner remove \ + +Remove a Joiner entry. + +* hashmacaddr: The Extended Address of the Joiner or '*' to match any Joiner. + +```bash +> commissioner joiner remove d45e64fa83f81cf7 +Done +``` + +### commissioner provisioningurl \ + +Set the Provisioning URL. + +```bash +> commissioner provisioningurl http://github.com/openthread/openthread +Done +``` + ### commissioner energy \ \ \ \ \ Send a MGMT_ED_SCAN message. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 6a1ac85ee..43236ab49 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -1992,14 +1992,42 @@ void Interpreter::ProcessCommissioner(int argc, char *argv[]) if (strcmp(argv[0], "start") == 0) { - const char *provisioningUrl; - VerifyOrExit(argc > 1, error = kThreadError_Parse); - provisioningUrl = argc > 2 ? argv[2] : NULL; - otCommissionerStart(mInstance, argv[1], provisioningUrl); + SuccessOrExit(error = otCommissionerStart(mInstance)); } else if (strcmp(argv[0], "stop") == 0) { - otCommissionerStop(mInstance); + SuccessOrExit(error = otCommissionerStop(mInstance)); + } + else if (strcmp(argv[0], "joiner") == 0) + { + otExtAddress addr; + const otExtAddress *addrPtr; + + VerifyOrExit(argc > 2, error = kThreadError_Parse); + + if (strcmp(argv[2], "*") == 0) + { + addrPtr = NULL; + } + else + { + VerifyOrExit(Hex2Bin(argv[2], addr.m8, sizeof(addr)) == sizeof(addr), error = kThreadError_Parse); + addrPtr = &addr; + } + + if (strcmp(argv[1], "add") == 0) + { + VerifyOrExit(argc > 3, error = kThreadError_Parse); + SuccessOrExit(error = otCommissionerAddJoiner(mInstance, addrPtr, argv[3])); + } + else if (strcmp(argv[1], "remove") == 0) + { + SuccessOrExit(error = otCommissionerRemoveJoiner(mInstance, addrPtr)); + } + } + else if (strcmp(argv[0], "provisioningurl") == 0) + { + SuccessOrExit(error = otCommissionerSetProvisioningUrl(mInstance, (argc > 1) ? argv[1] : NULL)); } else if (strcmp(argv[0], "energy") == 0) { diff --git a/src/core/meshcop/commissioner.cpp b/src/core/meshcop/commissioner.cpp index 05b04859d..7f71c3f26 100644 --- a/src/core/meshcop/commissioner.cpp +++ b/src/core/meshcop/commissioner.cpp @@ -38,6 +38,7 @@ #endif #include +#include #include #include @@ -63,18 +64,16 @@ Commissioner::Commissioner(ThreadNetif &aThreadNetif): mNetif(aThreadNetif), mIsSendMgmtCommRequest(false) { + memset(mJoiners, 0, sizeof(mJoiners)); aThreadNetif.GetCoapServer().AddResource(mRelayReceive); } -ThreadError Commissioner::Start(const char *aPSKd, const char *aProvisioningUrl) +ThreadError Commissioner::Start(void) { ThreadError error = kThreadError_None; VerifyOrExit(mState == kStateDisabled, error = kThreadError_InvalidState); - SuccessOrExit(error = mNetif.GetDtls().SetPsk(reinterpret_cast(aPSKd), - static_cast(strlen(aPSKd)))); - SuccessOrExit(error = mNetif.GetDtls().mProvisioningUrl.SetProvisioningUrl(aProvisioningUrl)); SuccessOrExit(error = mSocket.Open(HandleUdpReceive, this)); mState = kStatePetition; SendPetition(); @@ -91,6 +90,75 @@ ThreadError Commissioner::Stop(void) return kThreadError_None; } +ThreadError Commissioner::AddJoiner(const Mac::ExtAddress *aExtAddress, const char *aPSKd) +{ + ThreadError error = kThreadError_NoBufs; + + VerifyOrExit(strlen(aPSKd) <= Dtls::kPskMaxLength, error = kThreadError_InvalidArgs); + RemoveJoiner(aExtAddress); + + for (size_t i = 0; i < sizeof(mJoiners) / sizeof(mJoiners[0]); i++) + { + if (mJoiners[i].mValid) + { + continue; + } + + if (aExtAddress != NULL) + { + mJoiners[i].mExtAddress = *aExtAddress; + mJoiners[i].mAny = false; + } + else + { + mJoiners[i].mAny = true; + } + + strncpy(mJoiners[i].mPsk, aPSKd, sizeof(mJoiners[i].mPsk)); + mJoiners[i].mValid = true; + ExitNow(error = kThreadError_None); + } + +exit: + return error; +} + +ThreadError Commissioner::RemoveJoiner(const Mac::ExtAddress *aExtAddress) +{ + ThreadError error = kThreadError_NotFound; + + for (size_t i = 0; i < sizeof(mJoiners) / sizeof(mJoiners[0]); i++) + { + if (!mJoiners[i].mValid) + { + continue; + } + + if (aExtAddress != NULL) + { + if (memcmp(&mJoiners[i].mExtAddress, &aExtAddress, sizeof(mJoiners[i].mExtAddress))) + { + continue; + } + } + else if (!mJoiners[i].mAny) + { + continue; + } + + mJoiners[i].mValid = false; + ExitNow(error = kThreadError_None); + } + +exit: + return error; +} + +ThreadError Commissioner::SetProvisioningUrl(const char *aProvisioningUrl) +{ + return mNetif.GetDtls().mProvisioningUrl.SetProvisioningUrl(aProvisioningUrl); +} + uint16_t Commissioner::GetSessionId(void) const { return mSessionId; @@ -383,10 +451,36 @@ void Commissioner::HandleRelayReceive(Coap::Header &aHeader, Message &aMessage, SuccessOrExit(error = Tlv::GetValueOffset(aMessage, Tlv::kJoinerDtlsEncapsulation, offset, length)); memcpy(mJoinerIid, joinerIid.GetIid(), sizeof(mJoinerIid)); - mNetif.GetDtls().SetClientId(mJoinerIid, sizeof(mJoinerIid)); mJoinerPort = joinerPort.GetUdpPort(); mJoinerRloc = joinerRloc.GetJoinerRouterLocator(); + if (!mNetif.GetDtls().IsStarted()) + { + mJoinerIid[0] ^= 0x2; + + for (size_t i = 0; i < sizeof(mJoiners) / sizeof(mJoiners[0]); i++) + { + if (!mJoiners[i].mValid) + { + continue; + } + + if (mJoiners[i].mAny || !memcmp(&mJoiners[i].mExtAddress, mJoinerIid, sizeof(mJoiners[i].mExtAddress))) + { + SuccessOrExit(error = mNetif.GetDtls().SetPsk(reinterpret_cast(mJoiners[i].mPsk), + static_cast(strlen(mJoiners[i].mPsk)))); + SuccessOrExit(error = mNetif.GetDtls().Start(false, HandleDtlsReceive, HandleDtlsSend, this)); + otLogInfoMeshCoP("found joiner, starting new session\r\n"); + break; + } + } + + mJoinerIid[0] ^= 0x2; + } + + VerifyOrExit(mNetif.GetDtls().IsStarted() && memcmp(mJoinerIid, joinerIid.GetIid(), sizeof(mJoinerIid)) == 0,); + + SuccessOrExit(error = mNetif.GetDtls().SetClientId(mJoinerIid, sizeof(mJoinerIid))); mNetif.GetDtls().Receive(aMessage, offset, length); exit: @@ -441,8 +535,6 @@ void Commissioner::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &a mState = kStateActive; mTimer.Start(5000); - mNetif.GetDtls().Start(false, HandleDtlsReceive, HandleDtlsSend, this); - otLogInfoMeshCoP("received petition response\r\n"); break; diff --git a/src/core/meshcop/commissioner.hpp b/src/core/meshcop/commissioner.hpp index fb9d04b64..d527d9496 100644 --- a/src/core/meshcop/commissioner.hpp +++ b/src/core/meshcop/commissioner.hpp @@ -34,8 +34,12 @@ #ifndef COMMISSIONER_HPP_ #define COMMISSIONER_HPP_ +#include + #include #include +#include +#include #include #include #include @@ -61,13 +65,10 @@ public: /** * This method starts the Commissioner service. * - * @param[in] aPSKd A pointer to the PSKd. - * @param[in] aProvisioningUrl A pointer to the Provisioning URL (may be NULL). - * * @retval kThreadError_None Successfully started the Commissioner service. * */ - ThreadError Start(const char *aPSKd, const char *aProvisioningUrl); + ThreadError Start(void); /** * This method stops the Commissioner service. @@ -77,6 +78,40 @@ public: */ ThreadError Stop(void); + /** + * This method adds a Joiner entry. + * + * @param[in] aExtAddress A pointer to the Joiner's extended address or NULL for any Joiner. + * @param[in] aPSKd A pointer to the PSKd + * + * @retval kThreadError_None Successfully added the Joiner. + * @retval kThreadError_NoBufs No buffers available to add the Joiner. + * + */ + ThreadError AddJoiner(const Mac::ExtAddress *aExtAddress, const char *aPSKd); + + /** + * This method removes a Joiner entry. + * + * @param[in] aExtAddress A pointer to the Joiner's extended address or NULL for any Joiner. + * + * @retval kThreadError_None Successfully added the Joiner. + * @retval kThreadError_NotFound The Joiner specified by @p aExtAddress was not found. + * + */ + ThreadError RemoveJoiner(const Mac::ExtAddress *aExtAddress); + + /** + * This function sets the Provisioning URL. + * + * @param[in] aProvisioningUrl A pointer to the Provisioning URL (may be NULL). + * + * @retval kThreadError_None Successfully added the Joiner. + * @retval kThreadError_InvalidArgs @p aProvisioningUrl is invalid. + * + */ + ThreadError SetProvisioningUrl(const char *aProvisioningUrl); + /** * This method returns the Commissioner Session ID. * @@ -88,7 +123,6 @@ public: EnergyScanClient mEnergyScan; PanIdQueryClient mPanIdQuery; - /** * This method sends MGMT_COMMISSIONER_GET. * @@ -147,9 +181,17 @@ private: kStatePetition = 1, kStateActive = 2, }; - uint8_t mState; + struct Joiner + { + Mac::ExtAddress mExtAddress; + char mPsk[Dtls::kPskMaxLength + 1]; + bool mValid : 1; + bool mAny : 1; + }; + Joiner mJoiners[OPENTHREAD_CONFIG_MAX_JOINER_ENTRIES]; + uint8_t mJoinerIid[8]; uint16_t mJoinerPort; uint16_t mJoinerRloc; diff --git a/src/core/meshcop/dtls.cpp b/src/core/meshcop/dtls.cpp index 5cca91c46..76ef0d8e1 100644 --- a/src/core/meshcop/dtls.cpp +++ b/src/core/meshcop/dtls.cpp @@ -121,14 +121,24 @@ exit: ThreadError Dtls::Stop(void) { - mStarted = false; mbedtls_ssl_close_notify(&mSsl); + Close(); + return kThreadError_None; +} + +void Dtls::Close(void) +{ + mStarted = false; mbedtls_ssl_free(&mSsl); mbedtls_ssl_config_free(&mConf); mbedtls_ctr_drbg_free(&mCtrDrbg); mbedtls_entropy_free(&mEntropy); mbedtls_ssl_cookie_free(&mCookieCtx); - return kThreadError_None; +} + +bool Dtls::IsStarted(void) +{ + return mStarted; } ThreadError Dtls::SetPsk(const uint8_t *aPsk, uint8_t aPskLength) @@ -321,6 +331,7 @@ void Dtls::HandleTimer(void) void Dtls::Process(void) { uint8_t buf[MBEDTLS_SSL_MAX_CONTENT_LEN]; + bool shouldClose = false; int rval; while (mStarted) @@ -348,10 +359,14 @@ void Dtls::Process(void) { case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: mbedtls_ssl_close_notify(&mSsl); + ExitNow(shouldClose = true); break; case MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED: + break; + case MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE: + ExitNow(shouldClose = true); break; case MBEDTLS_ERR_SSL_INVALID_MAC: @@ -359,6 +374,7 @@ void Dtls::Process(void) { mbedtls_ssl_send_alert_message(&mSsl, MBEDTLS_SSL_ALERT_LEVEL_FATAL, MBEDTLS_SSL_ALERT_MSG_BAD_RECORD_MAC); + ExitNow(shouldClose = true); } break; @@ -368,6 +384,7 @@ void Dtls::Process(void) { mbedtls_ssl_send_alert_message(&mSsl, MBEDTLS_SSL_ALERT_LEVEL_FATAL, MBEDTLS_SSL_ALERT_MSG_HANDSHAKE_FAILURE); + ExitNow(shouldClose = true); } break; @@ -378,6 +395,13 @@ void Dtls::Process(void) break; } } + +exit: + + if (shouldClose) + { + Close(); + } } ThreadError Dtls::MapError(int rval) diff --git a/src/core/meshcop/dtls.hpp b/src/core/meshcop/dtls.hpp index e34b0eca6..67497122f 100644 --- a/src/core/meshcop/dtls.hpp +++ b/src/core/meshcop/dtls.hpp @@ -56,6 +56,11 @@ namespace MeshCoP { class Dtls { public: + enum + { + kPskMaxLength = 32, + }; + /** * This constructor initializes the DTLS object. * @@ -105,6 +110,14 @@ public: */ ThreadError Stop(void); + /** + * This method indicates whether or not the DTLS service is active. + * + * @returns TRUE if the DTLS service is active, FALSE otherwise. + * + */ + bool IsStarted(void); + /** * This method sets the PSK. * @@ -166,11 +179,6 @@ public: ProvisioningUrlTlv mProvisioningUrl; private: - enum - { - kPskMaxLength = 32, - }; - static ThreadError MapError(int rval); static void HandleMbedtlsDebug(void *ctx, int level, const char *file, int line, const char *str); @@ -196,6 +204,7 @@ private: static void HandleTimer(void *aContext); void HandleTimer(void); + void Close(void); void Process(void); uint8_t mPsk[kPskMaxLength]; diff --git a/src/core/openthread-core-default-config.h b/src/core/openthread-core-default-config.h index 68ac16153..89b615755 100644 --- a/src/core/openthread-core-default-config.h +++ b/src/core/openthread-core-default-config.h @@ -175,6 +175,16 @@ #define OPENTHREAD_CONFIG_MAX_ENERGY_RESULTS 64 #endif // OPENTHREAD_CONFIG_MAX_ENERGY_RESULTS +/** + * @def OPENTHREAD_CONFIG_MAX_JOINER_ENTRIES + * + * The maximum number of Joiner entries maintained by the Commissioner. + * + */ +#ifndef OPENTHREAD_CONFIG_MAX_JOINER_ENTRIES +#define OPENTHREAD_CONFIG_MAX_JOINER_ENTRIES 2 +#endif // OPENTHREAD_CONFIG_MAX_JOINER_ENTRIES + /** * @def OPENTHREAD_CONFIG_LOG_LEVEL * diff --git a/src/core/openthread.cpp b/src/core/openthread.cpp index daadb1b2d..1b67cc81b 100644 --- a/src/core/openthread.cpp +++ b/src/core/openthread.cpp @@ -1418,9 +1418,9 @@ ThreadError otSendPendingSet(otInstance *, const otOperationalDataset *aDataset, #if OPENTHREAD_ENABLE_COMMISSIONER #include -ThreadError otCommissionerStart(otInstance *, const char *aPSKd, const char *aProvisioningUrl) +ThreadError otCommissionerStart(otInstance *) { - return sThreadNetif->GetCommissioner().Start(aPSKd, aProvisioningUrl); + return sThreadNetif->GetCommissioner().Start(); } ThreadError otCommissionerStop(otInstance *) @@ -1428,6 +1428,21 @@ ThreadError otCommissionerStop(otInstance *) return sThreadNetif->GetCommissioner().Stop(); } +ThreadError otCommissionerAddJoiner(otInstance *, const otExtAddress *aExtAddress, const char *aPSKd) +{ + return sThreadNetif->GetCommissioner().AddJoiner(static_cast(aExtAddress), aPSKd); +} + +ThreadError otCommissionerRemoveJoiner(otInstance *, const otExtAddress *aExtAddress) +{ + return sThreadNetif->GetCommissioner().RemoveJoiner(static_cast(aExtAddress)); +} + +ThreadError otCommissionerSetProvisioningUrl(otInstance *, const char *aProvisioningUrl) +{ + return sThreadNetif->GetCommissioner().SetProvisioningUrl(aProvisioningUrl); +} + ThreadError otCommissionerEnergyScan(otInstance *, uint32_t aChannelMask, uint8_t aCount, uint16_t aPeriod, uint16_t aScanDuration, const otIp6Address *aAddress, otCommissionerEnergyReportCallback aCallback, void *aContext) diff --git a/tests/scripts/thread-cert/Cert_8_1_01_Commissioning.py b/tests/scripts/thread-cert/Cert_8_1_01_Commissioning.py index 0924b87e0..c3dda15e8 100755 --- a/tests/scripts/thread-cert/Cert_8_1_01_Commissioning.py +++ b/tests/scripts/thread-cert/Cert_8_1_01_Commissioning.py @@ -59,7 +59,8 @@ class Cert_8_1_01_Commissioning(unittest.TestCase): self.nodes[COMMISSIONER].thread_start() time.sleep(5) self.assertEqual(self.nodes[COMMISSIONER].get_state(), 'leader') - self.nodes[COMMISSIONER].commissioner_start('openthread') + self.nodes[COMMISSIONER].commissioner_start() + self.nodes[COMMISSIONER].commissioner_add_joiner('*', 'openthread') self.nodes[JOINER].interface_up() self.nodes[JOINER].joiner_start('openthread') diff --git a/tests/scripts/thread-cert/Cert_8_1_02_Commissioning.py b/tests/scripts/thread-cert/Cert_8_1_02_Commissioning.py index 2b0895310..b54da808d 100755 --- a/tests/scripts/thread-cert/Cert_8_1_02_Commissioning.py +++ b/tests/scripts/thread-cert/Cert_8_1_02_Commissioning.py @@ -59,7 +59,8 @@ class Cert_8_1_02_Commissioning(unittest.TestCase): self.nodes[COMMISSIONER].thread_start() time.sleep(5) self.assertEqual(self.nodes[COMMISSIONER].get_state(), 'leader') - self.nodes[COMMISSIONER].commissioner_start('openthread') + self.nodes[COMMISSIONER].commissioner_start() + self.nodes[COMMISSIONER].commissioner_add_joiner(self.nodes[JOINER].get_hashmacaddr(), 'openthread') self.nodes[JOINER].interface_up() self.nodes[JOINER].joiner_start('daerhtnepo') diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index a5422399c..8492e2c1f 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -144,8 +144,13 @@ class Node: self.send_command('thread stop') self.pexpect.expect('Done') - def commissioner_start(self, pskd='', provisioning_url=''): - cmd = 'commissioner start ' + pskd + ' ' + provisioning_url + def commissioner_start(self): + cmd = 'commissioner start' + self.send_command(cmd) + self.pexpect.expect('Done') + + def commissioner_add_joiner(self, addr, psk): + cmd = 'commissioner joiner add ' + addr + ' ' + psk self.send_command(cmd) self.pexpect.expect('Done') @@ -202,6 +207,14 @@ class Node: self.pexpect.expect('Done') return addr64 + def get_hashmacaddr(self): + self.send_command('hashmacaddr') + i = self.pexpect.expect('([0-9a-fA-F]{16})') + if i == 0: + addr = self.pexpect.match.groups()[0].decode("utf-8") + self.pexpect.expect('Done') + return addr + def set_channel(self, channel): cmd = 'channel %d' % channel self.send_command(cmd)