blob: a551ab19af98d0500e33319fe16b84060f782711 [file] [log] [blame]
/*
* Copyright (c) 2016, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements the Joiner role.
*/
#define WPP_NAME "joiner.tmh"
#include <openthread/config.h>
#include "joiner.hpp"
#include <stdio.h>
#include <openthread/platform/radio.h>
#include <openthread/platform/random.h>
#include "common/code_utils.hpp"
#include "common/crc16.hpp"
#include "common/debug.hpp"
#include "common/encoding.hpp"
#include "common/logging.hpp"
#include "mac/mac_frame.hpp"
#include "meshcop/meshcop.hpp"
#include "thread/thread_netif.hpp"
#include "thread/thread_uri_paths.hpp"
#if OPENTHREAD_ENABLE_JOINER
using ot::Encoding::BigEndian::HostSwap16;
using ot::Encoding::BigEndian::HostSwap64;
namespace ot {
namespace MeshCoP {
Joiner::Joiner(ThreadNetif &aNetif):
ThreadNetifLocator(aNetif),
mState(kStateIdle),
mCallback(NULL),
mContext(NULL),
mCcitt(0),
mAnsi(0),
mJoinerRouterChannel(0),
mJoinerRouterPanId(0),
mJoinerUdpPort(0),
mVendorName(NULL),
mVendorModel(NULL),
mVendorSwVersion(NULL),
mVendorData(NULL),
mTimer(aNetif.GetIp6().mTimerScheduler, &Joiner::HandleTimer, this),
mJoinerEntrust(OT_URI_PATH_JOINER_ENTRUST, &Joiner::HandleJoinerEntrust, this)
{
aNetif.GetCoap().AddResource(mJoinerEntrust);
}
otError Joiner::Start(const char *aPSKd, const char *aProvisioningUrl,
const char *aVendorName, const char *aVendorModel, const char *aVendorSwVersion,
const char *aVendorData, otJoinerCallback aCallback, void *aContext)
{
ThreadNetif &netif = GetNetif();
otError error;
Mac::ExtAddress extAddress;
Crc16 ccitt(Crc16::kCcitt);
Crc16 ansi(Crc16::kAnsi);
otLogFuncEntry();
VerifyOrExit(mState == kStateIdle, error = OT_ERROR_BUSY);
// use extended address based on factory-assigned IEEE EUI-64
netif.GetMac().GetHashMacAddress(&extAddress);
netif.GetMac().SetExtAddress(extAddress);
netif.GetMle().UpdateLinkLocalAddress();
for (size_t i = 0; i < sizeof(extAddress); i++)
{
ccitt.Update(extAddress.m8[i]);
ansi.Update(extAddress.m8[i]);
}
mCcitt = ccitt.Get();
mAnsi = ansi.Get();
error = netif.GetCoapSecure().Start(OPENTHREAD_CONFIG_JOINER_UDP_PORT);
SuccessOrExit(error);
error = netif.GetCoapSecure().GetDtls().SetPsk(reinterpret_cast<const uint8_t *>(aPSKd),
static_cast<uint8_t>(strlen(aPSKd)));
SuccessOrExit(error);
error = netif.GetCoapSecure().GetDtls().mProvisioningUrl.SetProvisioningUrl(aProvisioningUrl);
SuccessOrExit(error);
mJoinerRouterPanId = Mac::kPanIdBroadcast;
SuccessOrExit(error = netif.GetMle().Discover(0, netif.GetMac().GetPanId(), true, false, HandleDiscoverResult,
this));
mVendorName = aVendorName;
mVendorModel = aVendorModel;
mVendorSwVersion = aVendorSwVersion;
mVendorData = aVendorData;
mCallback = aCallback;
mContext = aContext;
mState = kStateDiscover;
exit:
otLogFuncExitErr(error);
return error;
}
otError Joiner::Stop(void)
{
otLogFuncEntry();
Close();
otLogFuncExit();
return OT_ERROR_NONE;
}
void Joiner::Close(void)
{
ThreadNetif &netif = GetNetif();
otLogFuncEntry();
netif.GetCoapSecure().Disconnect();
netif.GetIp6Filter().RemoveUnsecurePort(netif.GetCoapSecure().GetPort());
otLogFuncExit();
}
void Joiner::Complete(otError aError)
{
mState = kStateIdle;
GetNetif().GetCoapSecure().Stop();
if (mCallback)
{
otJoinerCallback callback = mCallback;
mCallback = NULL;
callback(aError, mContext);
}
}
void Joiner::HandleDiscoverResult(otActiveScanResult *aResult, void *aContext)
{
static_cast<Joiner *>(aContext)->HandleDiscoverResult(aResult);
}
void Joiner::HandleDiscoverResult(otActiveScanResult *aResult)
{
ThreadNetif &netif = GetNetif();
Ip6::MessageInfo messageInfo;
otLogFuncEntry();
if (aResult != NULL)
{
otLogFuncEntryMsg("aResult = %llX", HostSwap64(*reinterpret_cast<uint64_t *>(&aResult->mExtAddress)));
// Joining is disabled if the Steering Data is not included
if (aResult->mSteeringData.mLength == 0)
{
otLogDebgMeshCoP(GetInstance(), "No steering data, joining disabled");
ExitNow();
}
SteeringDataTlv steeringData;
steeringData.SetLength(aResult->mSteeringData.mLength);
memcpy(steeringData.GetValue(), aResult->mSteeringData.m8, steeringData.GetLength());
if (steeringData.DoesAllowAny() ||
(steeringData.GetBit(mCcitt % steeringData.GetNumBits()) &&
steeringData.GetBit(mAnsi % steeringData.GetNumBits())))
{
mJoinerUdpPort = aResult->mJoinerUdpPort;
mJoinerRouterPanId = aResult->mPanId;
mJoinerRouterChannel = aResult->mChannel;
memcpy(&mJoinerRouter, &aResult->mExtAddress, sizeof(mJoinerRouter));
}
else
{
otLogDebgMeshCoP(GetInstance(), "Steering data does not include this device");
}
}
else if (mJoinerRouterPanId != Mac::kPanIdBroadcast)
{
otLogFuncEntryMsg("aResult = NULL");
netif.GetMac().SetPanId(mJoinerRouterPanId);
netif.GetMac().SetChannel(mJoinerRouterChannel);
netif.GetIp6Filter().AddUnsecurePort(netif.GetCoapSecure().GetPort());
messageInfo.GetPeerAddr().mFields.m16[0] = HostSwap16(0xfe80);
messageInfo.GetPeerAddr().SetIid(mJoinerRouter);
messageInfo.mPeerPort = mJoinerUdpPort;
messageInfo.mInterfaceId = OT_NETIF_INTERFACE_ID_THREAD;
netif.GetCoapSecure().Connect(messageInfo, Joiner::HandleSecureCoapClientConnect, this);
mState = kStateConnect;
}
else
{
otLogDebgMeshCoP(GetInstance(), "No joinable network found");
Complete(OT_ERROR_NOT_FOUND);
}
exit:
otLogFuncExit();
}
void Joiner::HandleSecureCoapClientConnect(bool aConnected, void *aContext)
{
static_cast<Joiner *>(aContext)->HandleSecureCoapClientConnect(aConnected);
}
void Joiner::HandleSecureCoapClientConnect(bool aConnected)
{
switch (mState)
{
case kStateConnect:
if (aConnected)
{
mState = kStateConnected;
SendJoinerFinalize();
mTimer.Start(kTimeout);
}
else
{
Complete(OT_ERROR_SECURITY);
}
break;
default:
break;
}
}
void Joiner::SendJoinerFinalize(void)
{
ThreadNetif &netif = GetNetif();
Coap::Header header;
otError error = OT_ERROR_NONE;
Message *message = NULL;
StateTlv stateTlv;
VendorNameTlv vendorNameTlv;
VendorModelTlv vendorModelTlv;
VendorSwVersionTlv vendorSwVersionTlv;
VendorStackVersionTlv vendorStackVersionTlv;
otLogFuncEntry();
header.Init(OT_COAP_TYPE_CONFIRMABLE, OT_COAP_CODE_POST);
header.AppendUriPathOptions(OT_URI_PATH_JOINER_FINALIZE);
header.SetPayloadMarker();
VerifyOrExit((message = NewMeshCoPMessage(netif.GetCoapSecure(), header)) != NULL, error = OT_ERROR_NO_BUFS);
stateTlv.Init();
stateTlv.SetState(MeshCoP::StateTlv::kAccept);
SuccessOrExit(error = message->Append(&stateTlv, sizeof(stateTlv)));
vendorNameTlv.Init();
vendorNameTlv.SetVendorName(mVendorName);
SuccessOrExit(error = message->Append(&vendorNameTlv, vendorNameTlv.GetSize()));
vendorModelTlv.Init();
vendorModelTlv.SetVendorModel(mVendorModel);
SuccessOrExit(error = message->Append(&vendorModelTlv, vendorModelTlv.GetSize()));
vendorSwVersionTlv.Init();
vendorSwVersionTlv.SetVendorSwVersion(mVendorSwVersion);
SuccessOrExit(error = message->Append(&vendorSwVersionTlv, vendorSwVersionTlv.GetSize()));
vendorStackVersionTlv.Init();
vendorStackVersionTlv.SetOui(OPENTHREAD_CONFIG_STACK_VENDOR_OUI);
vendorStackVersionTlv.SetMajor(OPENTHREAD_CONFIG_STACK_VERSION_MAJOR);
vendorStackVersionTlv.SetMinor(OPENTHREAD_CONFIG_STACK_VERSION_MINOR);
vendorStackVersionTlv.SetRevision(OPENTHREAD_CONFIG_STACK_VERSION_REV);
SuccessOrExit(error = message->Append(&vendorStackVersionTlv, vendorStackVersionTlv.GetSize()));
if (mVendorData != NULL)
{
VendorDataTlv vendorDataTlv;
vendorDataTlv.Init();
vendorDataTlv.SetVendorData(mVendorData);
SuccessOrExit(error = message->Append(&vendorDataTlv, vendorDataTlv.GetSize()));
}
if (netif.GetCoapSecure().GetDtls().mProvisioningUrl.GetLength() > 0)
{
SuccessOrExit(error = message->Append(&netif.GetCoapSecure().GetDtls().mProvisioningUrl,
netif.GetCoapSecure().GetDtls().mProvisioningUrl.GetSize()));
}
#if OPENTHREAD_ENABLE_CERT_LOG
uint8_t buf[OPENTHREAD_CONFIG_MESSAGE_BUFFER_SIZE];
VerifyOrExit(message->GetLength() <= sizeof(buf));
message->Read(header.GetLength(), message->GetLength() - header.GetLength(), buf);
otDumpCertMeshCoP(GetInstance(), "[THCI] direction=send | type=JOIN_FIN.req |", buf,
message->GetLength() - header.GetLength());
#endif
SuccessOrExit(error = netif.GetCoapSecure().SendMessage(*message, Joiner::HandleJoinerFinalizeResponse, this));
otLogInfoMeshCoP(GetInstance(), "Sent joiner finalize");
exit:
if (error != OT_ERROR_NONE && message != NULL)
{
message->Free();
}
otLogFuncExit();
}
void Joiner::HandleJoinerFinalizeResponse(void *aContext, otCoapHeader *aHeader, otMessage *aMessage,
const otMessageInfo *aMessageInfo, otError aResult)
{
static_cast<Joiner *>(aContext)->HandleJoinerFinalizeResponse(
static_cast<Coap::Header *>(aHeader), static_cast<Message *>(aMessage),
static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
}
void Joiner::HandleJoinerFinalizeResponse(Coap::Header *aHeader, Message *aMessage,
const Ip6::MessageInfo *aMessageInfo, otError aResult)
{
(void) aMessageInfo;
StateTlv state;
otLogFuncEntry();
VerifyOrExit(mState == kStateConnected &&
aResult == OT_ERROR_NONE &&
aHeader->GetType() == OT_COAP_TYPE_ACKNOWLEDGMENT &&
aHeader->GetCode() == OT_COAP_CODE_CHANGED);
SuccessOrExit(Tlv::GetTlv(*aMessage, Tlv::kState, sizeof(state), state));
VerifyOrExit(state.IsValid());
mState = kStateEntrust;
mTimer.Start(kTimeout);
otLogInfoMeshCoP(GetInstance(), "received joiner finalize response %d", static_cast<uint8_t>(state.GetState()));
#if OPENTHREAD_ENABLE_CERT_LOG
uint8_t buf[OPENTHREAD_CONFIG_MESSAGE_BUFFER_SIZE];
VerifyOrExit(aMessage->GetLength() <= sizeof(buf));
aMessage->Read(aHeader->GetLength(), aMessage->GetLength() - aHeader->GetLength(), buf);
otDumpCertMeshCoP(GetInstance(), "[THCI] direction=recv | type=JOIN_FIN.rsp |", buf,
aMessage->GetLength() - aHeader->GetLength());
#endif
exit:
Close();
otLogFuncExit();
}
void Joiner::HandleJoinerEntrust(void *aContext, otCoapHeader *aHeader, otMessage *aMessage,
const otMessageInfo *aMessageInfo)
{
static_cast<Joiner *>(aContext)->HandleJoinerEntrust(
*static_cast<Coap::Header *>(aHeader), *static_cast<Message *>(aMessage),
*static_cast<const Ip6::MessageInfo *>(aMessageInfo));
}
void Joiner::HandleJoinerEntrust(Coap::Header &aHeader, Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
ThreadNetif &netif = GetNetif();
otError error;
NetworkMasterKeyTlv masterKey;
MeshLocalPrefixTlv meshLocalPrefix;
ExtendedPanIdTlv extendedPanId;
NetworkNameTlv networkName;
ActiveTimestampTlv activeTimestamp;
NetworkKeySequenceTlv networkKeySeq;
otLogFuncEntry();
VerifyOrExit(mState == kStateEntrust &&
aHeader.GetType() == OT_COAP_TYPE_CONFIRMABLE &&
aHeader.GetCode() == OT_COAP_CODE_POST, error = OT_ERROR_DROP);
otLogInfoMeshCoP(GetInstance(), "Received joiner entrust");
otLogCertMeshCoP(GetInstance(), "[THCI] direction=recv | type=JOIN_ENT.ntf");
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kNetworkMasterKey, sizeof(masterKey), masterKey));
VerifyOrExit(masterKey.IsValid(), error = OT_ERROR_PARSE);
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kMeshLocalPrefix, sizeof(meshLocalPrefix), meshLocalPrefix));
VerifyOrExit(meshLocalPrefix.IsValid(), error = OT_ERROR_PARSE);
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kExtendedPanId, sizeof(extendedPanId), extendedPanId));
VerifyOrExit(extendedPanId.IsValid(), error = OT_ERROR_PARSE);
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kNetworkName, sizeof(networkName), networkName));
VerifyOrExit(networkName.IsValid(), error = OT_ERROR_PARSE);
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kActiveTimestamp, sizeof(activeTimestamp), activeTimestamp));
VerifyOrExit(activeTimestamp.IsValid(), error = OT_ERROR_PARSE);
SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kNetworkKeySequence, sizeof(networkKeySeq), networkKeySeq));
VerifyOrExit(networkKeySeq.IsValid(), error = OT_ERROR_PARSE);
netif.GetKeyManager().SetMasterKey(masterKey.GetNetworkMasterKey());
netif.GetKeyManager().SetCurrentKeySequence(networkKeySeq.GetNetworkKeySequence());
netif.GetMle().SetMeshLocalPrefix(meshLocalPrefix.GetMeshLocalPrefix());
netif.GetMac().SetExtendedPanId(extendedPanId.GetExtendedPanId());
netif.GetMac().SetNetworkName(networkName.GetNetworkName());
otLogInfoMeshCoP(GetInstance(), "join success!");
// Send dummy response.
SendJoinerEntrustResponse(aHeader, aMessageInfo);
// Delay extended address configuration to allow DTLS wrap up.
mTimer.Start(kConfigExtAddressDelay);
exit:
if (error != OT_ERROR_NONE)
{
otLogWarnMeshCoP(GetInstance(), "Error while processing joiner entrust: %s",
otThreadErrorToString(error));
}
otLogFuncExit();
}
void Joiner::SendJoinerEntrustResponse(const Coap::Header &aRequestHeader,
const Ip6::MessageInfo &aRequestInfo)
{
ThreadNetif &netif = GetNetif();
otError error = OT_ERROR_NONE;
Message *message;
Coap::Header responseHeader;
Ip6::MessageInfo responseInfo(aRequestInfo);
otLogFuncEntry();
responseHeader.SetDefaultResponseHeader(aRequestHeader);
VerifyOrExit((message = NewMeshCoPMessage(netif.GetCoap(), responseHeader)) != NULL, error = OT_ERROR_NO_BUFS);
message->SetSubType(Message::kSubTypeJoinerEntrust);
memset(&responseInfo.mSockAddr, 0, sizeof(responseInfo.mSockAddr));
SuccessOrExit(error = netif.GetCoap().SendMessage(*message, responseInfo));
mState = kStateJoined;
otLogInfoArp(GetInstance(), "Sent Joiner Entrust response");
otLogInfoMeshCoP(GetInstance(), "Sent joiner entrust response length = %d", message->GetLength());
otLogCertMeshCoP(GetInstance(), "[THCI] direction=send | type=JOIN_ENT.rsp");
exit:
if (error != OT_ERROR_NONE && message != NULL)
{
message->Free();
}
otLogFuncExit();
}
void Joiner::HandleTimer(Timer &aTimer)
{
GetOwner(aTimer).HandleTimer();
}
void Joiner::HandleTimer(void)
{
ThreadNetif &netif = GetNetif();
otError error = OT_ERROR_NONE;
switch (mState)
{
case kStateIdle:
case kStateDiscover:
case kStateConnect:
assert(false);
break;
case kStateConnected:
case kStateEntrust:
error = OT_ERROR_RESPONSE_TIMEOUT;
break;
case kStateJoined:
Mac::ExtAddress extAddress;
netif.GetMac().GenerateExtAddress(&extAddress);
netif.GetMac().SetExtAddress(extAddress);
netif.GetMle().UpdateLinkLocalAddress();
error = OT_ERROR_NONE;
break;
}
Complete(error);
}
Joiner &Joiner::GetOwner(const Context &aContext)
{
#if OPENTHREAD_ENABLE_MULTIPLE_INSTANCES
Joiner &joiner = *static_cast<Joiner *>(aContext.GetContext());
#else
Joiner &joiner = otGetThreadNetif().GetJoiner();
OT_UNUSED_VARIABLE(aContext);
#endif
return joiner;
}
} // namespace MeshCoP
} // namespace ot
#endif // OPENTHREAD_ENABLE_JOINER