openthread / ot-commissioner

OpenThread Commissioner, a Thread commissioner for joining new Thread devices and managing Thread networks.
https://openthread.io/
BSD 3-Clause "New" or "Revised" License
49 stars 36 forks source link

Library example #165

Closed krbvroc1 closed 3 years ago

krbvroc1 commented 3 years ago

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!

wgtdkp commented 3 years ago

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);


2. Initialize the commissioner instance with proper configuration:
```c++
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");


4. Enable MeshCop for all joiners:

```c++
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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

@krbvroc1 please see the PR https://github.com/openthread/ot-commissioner/pull/167 that adds the minimum Thread Commissioner example.

wgtdkp commented 3 years ago

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():

https://github.com/openthread/ot-commissioner/blob/ce6b1180459d2533f730b3effee626efee16b9a3/include/commissioner/commissioner.hpp#L146-L159

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

krbvroc1 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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: https://github.com/openthread/ot-commissioner/blob/ce6b1180459d2533f730b3effee626efee16b9a3/src/app/commissioner_app.cpp#L1184-L1203

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. https://github.com/openthread/ot-commissioner/blob/ce6b1180459d2533f730b3effee626efee16b9a3/src/app/commissioner_app.cpp#L231-L259