Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Library example #165

Closed
krbvroc1 opened this issue Jan 31, 2021 · 10 comments
Closed

Library example #165

krbvroc1 opened this issue Jan 31, 2021 · 10 comments
Labels
question Further information is requested

Comments

@krbvroc1
Copy link

krbvroc1 commented Jan 31, 2021

Are there any examples or guidelines for using the library? I cannot find any documentation at all.

My goal is to create a commissioner application that has some slight modifications from what ot-commissioner provides but non-interactive. I am not really sure where to start. It does not help that I am a C programmer, not C++. Even a simple 'hello world' example that starts the commissioner, performs an enableall meshcop, adds a custom 'On Join' handler so an application can accept/reject, and runs in whatever event loop is required for the example to continuing running until killed.

Thank you!

@jwhui jwhui added the question Further information is requested label Jan 31, 2021
@wgtdkp
Copy link
Member

wgtdkp commented Feb 1, 2021

Hi, the commissioner_app.(cpp|hpp) in src/app is actually serving such example, through it provides both 1.1 & 1.2 functions and may not be easy to follow. One important fact is that, the commissioner library doesn't store data (user data like joiner list, network data like Active Dataset) and just process user commands. The CommissionerApp is an example of how a Thread Commissioner App may use the commissioner library and maintain data.

For the simple hello world example, you need to

  1. create a Commissioner instance with a CommissionerHandler instance. The commissioner handler is where we receive commissioner/joiner events (e.g. a joiner is requesting joining).
class MyCommissionerHandler : public ot::Commissioner::CommissionerHandler {
public:
    bool OnJoinerFinalize(const ByteArray &  aJoinerId,
                                  const std::string &aVendorName,
                                  const std::string &aVendorModel,
                                  const std::string &aVendorSwVersion,
                                  const ByteArray &  aVendorStackVersion,
                                  const std::string &aProvisioningUrl,
                                  const ByteArray &  aVendorData) override {
        printf("a joiner from %s is commissioned\n", aVendorName.c_str());
        return true;
    }
};

MyCommissionerHandler myHandler;
auto commissioner = ot::Commissioner::Commissioner::Create(myHandler);
  1. Initialize the commissioner instance with proper configuration:
ot::Commissioner::Config config;
config.mEnableCcm = false;
config.mPskc = {}; // Set this to your pskc, you can get it with the `pskc` command on a OT CLI node.

commissioner->Init(config);
  1. Petition to be the active commissioner:
std::string existingCommissionerId;
commissioner->Petition(existingCommissionerId, <your-BR-address>, <your-BR-port>);

// Check if we succed.
printf("the commissioner is active: %s\n", commissioner->IsActive() ? "true" : "false");
  1. Enable MeshCop for all joiners:
ot::Commissioner::CommissionerDataset dataset;
dataset.mPresentFlags |= ot::Commissioner::CommissionerDataset::kSteeringDataBit;
dataset.mSteeringData = {0xFF}; // Set the steeering data to all-ones to allow all joiners.

commissioner->SetCommissionerDataset(dataset);
  1. Loop:
while (true) {
    sleep(1);
}

We don't need to run an event loop because the commissioner takes care of it in a background thread. The client just need to keep the commissioner instance from destroyed.

Please note that most those commissioner APIs return an Error and you need to make sure that it's Error::kNone before you can process with the next API. You can find more details of using those APIs in src/app/commissioner_app.cpp by search the API name. Hope this can help.

@krbvroc1
Copy link
Author

krbvroc1 commented Feb 3, 2021

Thank you @wgtdkp for the detailed response to help me use the API. It is much appreciated.

I am trying to implement what you indicate. I am getting a lot of linker errors trying to create a standalone app that implements the outline you describe above.

I don't know if it is something I am doing incorrectly or if it is an issue with the source code / library packaging.

Most of the errors relate to the libcommissioner.a library having undefined references to
ot::commissioner::Address::Set(), ot::commissioner::Error::ToString, ot::commissioner::utils::Hex

All the undefined references seem to relate to the source code that is part of the 'common' directory. ie; address.cpp, utils.cpp, error.cpp, and time.cpp. Are these 'common' things not being placed in the library that is produced? I have very little experience with cmake or ninja either. I would think a library should not have any undefined references (other than other libraries like mbedtls, etc).

Should libcommissioner-common.a be installed to /usr/local/lib along with libcommissioner.a ?

@wgtdkp
Copy link
Member

wgtdkp commented Feb 3, 2021

Hi, yes I think there could be some problems with the static library. actually we did only have good tests against the shared libraries since it will be used for the Java binding and Android App. I will fix this issue and for a quick fix, you can build ot-commisisoner with BUILD_SHARED_LIBS=ON:

cmake -GNinja -DBUILD_SHARED_LIBS=ON ..
sudo ninja install

and link against the share library:

# assume minim_commissioner.cpp is your mininum commissioner example.
clang++ -std=c++11 -Wall -g mini_commissioner.cpp  -l:libcommissioner.so

I agree with you that an minimum commissioner example would be helpful for using the commissioner library, I will create a PR for this maybe today or tomorrow.

@krbvroc1
Copy link
Author

krbvroc1 commented Feb 3, 2021

Thank you, using the shared library worked as a quick fix. That also installed the various mbed, event, and fmtd libraries that are needed to run.

I had a misunderstanding about how OnJoinerFinalize() works and instead I am using OnJoinerRequest(). It seems OnJoinerFinalize() is too late in the Join flow to accept / reject a generic device. OnJoinerFinalize() only works if the joining device is programmed a certain way... It seems impossible to reject a generic device using OnJoinerFinalize() which I think is consistent with the Thread specification but a bit confusing.

@wgtdkp
Copy link
Member

wgtdkp commented Feb 3, 2021

@krbvroc1 please see the PR #167 that adds the minimum Thread Commissioner example.

@wgtdkp
Copy link
Member

wgtdkp commented Feb 3, 2021

I had a misunderstanding about how OnJoinerFinalize() works and instead I am using OnJoinerRequest(). It seems OnJoinerFinalize() is too late in the Join flow to accept / reject a generic device.

If you want to reject a joiner by its joinerId, you can return an empty pskd from OnJoinerRequest():

/**
* The function notifies the start of a joining request from given joiner.
*
* @param[in] aJoinerId A joiner ID.
*
* @return PSKd of the joiner. An empty PSKd indicates that the joiner is not
* enabled.
*
*/
virtual std::string OnJoinerRequest(const ByteArray &aJoinerId)
{
(void)aJoinerId;
return "";
}

Typical Commissioner application should have a joiner dictionary that maps joinerId to pskd and returns different pskd for different joiners.

@krbvroc1
Copy link
Author

krbvroc1 commented Feb 3, 2021

If you want to reject a joiner by its joinerId, you can return an empty pskd from OnJoinerRequest():

That is exactly what I discovered. I thought OnJoinerFinalize() would handle that, but even returning 'false' for OnJoinerFinalize() still allows the device to join. Looking at the Thread spec, the OnJoinerFinalize() only seems to matter if a provisioningurl is included, so I am not sure OnJoinerFinalize() is the best example for a minimal application.

@wgtdkp
Copy link
Member

wgtdkp commented Feb 3, 2021

BTW, Thread Spec define steering data to filter out joiners by their joinerId, for joiners don't match, there are no discovery responses sent from Joiner Router to the joiner. I think this is the best way of steering joiners.

@krbvroc1
Copy link
Author

krbvroc1 commented Feb 3, 2021

In my application, I want to accept or reject devices based on EUI64 lookup in a database. In order to make that feature work, the steering data must be a wildcard so that ALL potential joiners contact the Commissioner and lookup occurs. If I understand correctly, lookup logic for this use case needs to be in OnJoinerRequest().

@wgtdkp
Copy link
Member

wgtdkp commented Feb 4, 2021

In my application, I want to accept or reject devices based on EUI64 lookup in a database.

I think your requirement is well implemented by the commissioner CLI APP. What the CLI supporting is that: you can add joiners by joiner enable meshcop xxx xxx and this actually adds a joiner entry to its in-memory "joiner database". It updates the steering data with the new joiner ID (derived from EUI64) and in OnJoinerRequest() we search the joiner pskd by the pass-in joinerId:

std::string CommissionerApp::OnJoinerRequest(const ByteArray &aJoinerId)
{
std::string pskd;
auto joinerInfo = mJoiners.find({JoinerType::kMeshCoP, aJoinerId});
if (joinerInfo != mJoiners.end())
{
ExitNow(pskd = joinerInfo->second.mPSKd);
}
// Check if all joiners has been enabled.
joinerInfo = mJoiners.find({JoinerType::kMeshCoP, Commissioner::ComputeJoinerId(0)});
if (joinerInfo != mJoiners.end())
{
ExitNow(pskd = joinerInfo->second.mPSKd);
}
exit:
return pskd;
}

In order to make that feature work, the steering data must be a wildcard so that ALL potential joiners contact the Commissioner and lookup occurs.

Not really necessary,you just need to update the steering data with all your joiners. Using steering data cannot always rejects a specific joiner as the steering data is actually a bloom filter, but I think it is good to always updates the steering data with the joiner ID/EUI64 to reject unwanted joiners earlier.

Error CommissionerApp::EnableJoiner(JoinerType aType,
uint64_t aEui64,
const std::string &aPSKd,
const std::string &aProvisioningUrl)
{
Error error;
auto joinerId = Commissioner::ComputeJoinerId(aEui64);
auto commDataset = mCommDataset;
commDataset.mPresentFlags &= ~CommissionerDataset::kSessionIdBit;
commDataset.mPresentFlags &= ~CommissionerDataset::kBorderAgentLocatorBit;
auto &steeringData = GetSteeringData(commDataset, aType);
SuccessOrExit(error = ValidatePSKd(aPSKd));
VerifyOrExit(IsActive(), error = ERROR_INVALID_STATE("the commissioner is not active"));
VerifyOrExit(mJoiners.count({aType, joinerId}) == 0,
error = ERROR_ALREADY_EXISTS("joiner(type={}, EUI64={:X}) has already been enabled",
utils::to_underlying(aType), aEui64));
Commissioner::AddJoiner(steeringData, joinerId);
SuccessOrExit(error = mCommissioner->SetCommissionerDataset(commDataset));
MergeDataset(mCommDataset, commDataset);
mJoiners.emplace(JoinerKey{aType, joinerId}, JoinerInfo{aType, aEui64, aPSKd, aProvisioningUrl});
exit:
return error;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants