Skip to content

ecorm/cppwamp

Repository files navigation

CppWAMP

C++11 client library for the WAMP protocol.

PROJECT RENAMING AND RELOCATION

(2024-01-24) This project will be renamed and moved to a new repository. I am planning a major release of this library in Q1 2024, and will perform the transition to the new repository at the same time. Please see the announcement in the Discussions tab for the reason why.

Upon migration to the new repository, I will preserve this one for a reasonable grace period to give users the chance to migrate.

I apologize in advance for any inconvenience this may cause.

Features:

  • Supports the WAMP Basic Profile
  • Supports some advanced WAMP profile features
  • Roles: Caller, Callee, Subscriber, Publisher
  • Transports: TCP and Unix domain raw sockets
  • Serializations: JSON, MsgPack, and CBOR
  • Supports Boost.Asio completion tokens for callbacks, stackful coroutines, stackless couroutines, C++20 coroutines, and std::future.
  • Easy conversion between static and dynamic types
  • RPC and pub/sub event handlers can have static argument types
  • User-defined types can be registered and exchanged via RPC and pub-sub
  • Header-only, but may also be optionally compiled
  • Unit tested
  • Permissive license (Boost)

Dependencies:

Documentation

Tested Platforms

This library has been tested with:

  • GCC x86_64-linux-gnu, version 10.3
  • Clang x86_64-pc-linux-gnu, version 12.0

Supported Advanced Profile Features

  • General: agent identification, feature announcement
  • Callee: call_canceling, call_timeout, caller_identification, call_trustlevels, pattern_based_registration, progressive_call_results
  • Caller: call_canceling, call_timeout, caller_identification
  • Publisher: publisher_exclusion, publisher_identification, subscriber_blackwhite_listing
  • Subscriber: pattern_based_subscription, publication_trustlevels, publisher_identification

Roadmap

v1.0

  • Embedded router functionality
  • Remove all deprecated types and functions
  • Websocket support via Boost.Beast
  • HTTP file server support via Boost.Beast
  • Aim for API stability until v2.0.

v2.0 (maybe)

  • Migrate from jsoncons to another serialization library currently in development, which features the ability to skip an intermediary variant type and directly encode/decode the network data into C++ data types.

Questions, Discussions, and Issues

For general questions and discussions regarding CppWAMP, please use the project's GitHub discussions page.

For reporting bugs or for suggesting enhancements, please use the GitHub issue tracker.

Usage Examples Using Stackful Coroutines

For a more comprehensive overview, check out the Tutorials in the documentation.

Establishing a WAMP session

wamp::AsioContext ioctx;
wamp::Session session(ioctx);
wamp::spawn(
    ioctx,
    [&](wamp::YieldContext yield)
    {
        auto tcp = wamp::TcpHost("localhost", 8001).withFormat(wamp::json);
        session.connect(tcp, yield).value();
        auto sessionInfo = session.join(wamp::Realm("myrealm"), yield).value();
        std::cout << "Client joined. Session ID = "
                  << sessionInfo.id() << "\n";
        // etc.
        });
ioctx.run();

Registering a remote procedure

wamp::spawn(
    ioctx,
    [&](wamp::YieldContext yield)
    {
        :::
        session.enroll(wamp::Procedure("add"),
                       wamp::simpleRpc<int, int, int>(
                           [](int n, int m) -> int {return n+m;}),
                       yield).value();
        :::
    });

Calling a remote procedure

auto result = session.call(wamp::Rpc("add").withArgs(2, 2), yield).value();
std::cout << "2 + 2 is " << result[0].to<int>() << "\n";

Subscribing to a topic

void sensorSampled(float value)
{
    std::cout << "Sensor sampled, value = " << value << "\n";
}

wamp::spawn(
    ioctx,
    [&](wamp::YieldContext yield)
    {
        :::
        session.subscribe(wamp::Topic("sensorSampled"),
                          wamp::simpleEvent<float>(&sensorSampled),
                          yield).value();
        :::
    });

Publishing an event

float value = std::rand() % 10;
session.publish(wamp::Pub("sensorSampled").withArgs(value));

Usage Examples Using Asynchronous Callbacks

Establishing a WAMP session

class App : public std::enable_shared_from_this<App>
{ 
public:
    App(wamp::AnyIoExecutor exec)
        : session_(std::move(exec))
    {}

    void start(wamp::ConnectionWish where)
    {
        // `self` is used to ensure the App instance still exists
        // when the callback is invoked.
        auto self = shared_from_this();

        // Perform the connection, then chain to the next operation.
        session_.connect(
            where,
            [this, self](wamp::ErrorOr<size_t> result)
            {
                // 'result' contains the index of the connector used to
                // establish the connection, or an error
                result.value(); // Throws if result contains an error
                onConnect();
            });
    }

private:
    void onConnect()
    {
        auto self = shared_from_this();
        session_.join(
            wamp::Realm("myrealm"),
            [this, self](wamp::ErrorOr<wamp::SessionInfo> info)
            {
                onJoin(info.value());
            });
    }

    void onJoin(wamp::SessionInfo info)
    {
        std::cout << "Client joined. Session ID = "
                  << info.id() << "\n";
        // etc...
    }

    wamp::Session session_;
};

int main()
{
    wamp::AsioContext ioctx;
    App app(ioctx.get_executor());
    app.start(wamp::TcpHost("localhost", 8001).withFormat(wamp::json);
    ioctx.run();
}

Registering a remote procedure

class App : public std::shared_from_this<App>
{ 
public:
    App(wamp::AnyIoExecutor exec); // As above
    void start(wamp::ConnectionWish where); // As above

private:
    static int add(int n, int m) {return n + m;}

    void onConnect(); // As above

    void onJoin(wamp::SessionInfo)
    {
        auto self = shared_from_this();
        session_.enroll(
            wamp::Procedure("add"),
            wamp::simpleRpc<int, int, int>(&App::add),
            [this, self](wamp::ErrorOr<Registration> reg)
            {
                onRegistered(reg.value());
            });
    }

    void onRegistered(wamp::Registation reg)
    {
        // etc
    }

    wamp::Session session_;
};

Calling a remote procedure

class App : public std::shared_from_this<App>
{ 
public:
    App(wamp::AnyIoExecutor exec); // As above
    void start(wamp::ConnectionWish where); // As above

private:
    void onConnect(); // As above

    void onJoin(wamp::SessionInfo info)
    {
        auto self = shared_from_this();
        session_.call(
            wamp::Rpc("add").withArgs(2, 2),
            [this, self](wamp::ErrorOr<wamp::Result> sum)
            {
                onAdd(sum.value());
            });
    }

    void onAdd(wamp::Result result)
    {
        std::cout << "2 + 2 is " << result[0].to<int>() << "\n";
    }

    wamp::Session session_;
};

Subscribing to a topic

class App : public std::shared_from_this<App>
{ 
public:
    App(wamp::AnyIoExecutor exec); // As above
    void start(wamp::ConnectionWish where); // As above

private:
    static void sensorSampled(float value)
    {
        std::cout << "Sensor sampled, value = " << value << "\n";
    }

    void onConnect(); // As above

    void onJoin(wamp::SessionInfo info)
    {
        auto self = shared_from_this();
        session_.subscribe(
            wamp::Topic("sensorSampled"),
            wamp::simpleEvent<float>(&App::sensorSampled),
            [this, self](wamp::ErrorOr<wamp::Subscription> sub)
            {
                onSubscribed(sub.value());
            });
    }

    void onSubscribed(wamp::Subscription sub)
    {
        std::cout << "Subscribed, subscription ID = " << sub.id() << "\n";
    }

    wamp::Session session_;
};

Publishing an event

float value = std::rand() % 10;
session_.publish(wamp::Pub("sensorSampled").withArgs(value));

Header-Only Installation

CppWAMP supports header-only usage. You may simply add the include directories of CppWAMP and its dependencies into your project's include search path. On GCC/Clang, this can be done with the -isystem compiler flag. You'll also need to link to the necessary Boost libraries.

You may use CppWAMP's CMake scripts to fetch and build dependencies. The following commands will clone the CppWAMP repository, build the third-party dependencies, and install the headers and CMake package config:

git clone https://github.com/ecorm/cppwamp
cd cppwamp
cmake -DCPPWAMP_OPT_VENDORIZE -DCPPWAMP_OPT_HEADERS_ONLY -S . -B ./_build
cmake --build ./_build
cmake --install ./_build --prefix ./_stage/cppwamp

Two subdirectories will be generated as a result:

  • _build will contain intermediary build files and may be deleted.
  • _stage will contain third-party dependencies, as well as the CppWAMP headers and its CMake package config.

You may then use the following GCC/Clang compiler flags:

-isystem path/to/cppwamp/_stage/boost/include
-isystem path/to/cppwamp/_stage/cppwamp/include
-isystem path/to/cppwamp/_stage/jsoncons/include

as well as the following GCC/Clang linker flags:

-Lpath/to/cppwamp/_stage/boost/lib
-lboost_coroutine -lboost_context -lboost_thread -lboost_system

Note that only -lboost_system is necessary if you're not using the coroutine API.

You may omit the -DCPPWAMP_OPT_VENDORIZE option if you want to use the third-party libraries installed on your system. You may provide hints to their location via the following CMake configuration options:

  • -DBoost_ROOT=path/to/boost
  • -Djsoncons_ROOT=path/to/jsoncons

Compiling the library, tests, and examples

The steps are similar to the above Header-Only Installation, except that the -DCPPWAMP_OPT_HEADERS_ONLY option is omitted.

git clone https://github.com/ecorm/cppwamp
cd cppwamp
cmake -DCPPWAMP_OPT_VENDORIZE=On -S . -B ./_build
cmake --build ./_build
cmake --install ./_build --prefix ./_stage/cppwamp

The necessary compiler flags to use in your project are the same as the above Header-Only Installation, with the following extra needed linker flags:

-L path/to/cppwamp/_stage/cppwamp/lib
-lcppwamp-core

Consult the root CMakeLists.txt file for a list of CPPWAMP_OPT_<option> cache variables that control what's included in the build.

Integrating CppWAMP into a CMake-based Project

With add_subdirectory

The following example CMakeLists.txt shows how to include CppWAMP via add_subdirectory:

cmake_minimum_required (VERSION 3.12)
project(MyApp)

if(allow_cppwamp_to_download_and_build_dependencies)
    option(CPPWAMP_OPT_VENDORIZE "" ON)
else()
    option(CPPWAMP_OPT_VENDORIZE "" OFF)
    # If the following are not set, CppWAMP will use the default
    # search paths of CMake's find_package() to find its dependencies.
    set(Boost_ROOT /path/to/boost)
    set(jsoncons_ROOT /path/to/jsoncons)
endif()

add_subdirectory(cppwamp)
add_executable(myapp main.cpp)
target_link_libraries(myapp
    PRIVATE CppWAMP::coro-headers)

Any of the CppWAMP::* targets will automatically add the basic usage requirements of CppWAMP into your app's generated compiler/linker flags.

Please consult CppWAMP's root CMakeLists.txt file for a complete list of targets that you may specify for your app's target_link_libraries.

With FetchContent

You can use CMake's FetchContent to download CppWAMP and its dependencies at configuration time from within your project's CMakeLists.txt, as shown in the following example.

cmake_minimum_required (VERSION 3.12)
project(MyApp)

if(allow_cppwamp_to_download_and_build_dependencies)
    option(CPPWAMP_OPT_VENDORIZE "" ON)
else()
    option(CPPWAMP_OPT_VENDORIZE "" OFF)
    # If the following are not set, CppWAMP will use the default
    # search paths of CMake's find_package() to find its dependencies.
    set(Boost_ROOT /path/to/boost)
    set(jsoncons_ROOT /path/to/jsoncons)
endif()

include(FetchContent)
FetchContent_Declare(
    cppwamp
    GIT_REPOSITORY https://github.com/ecorm/cppwamp
)
FetchContent_MakeAvailable(cppwamp)

add_executable(myapp main.cpp)

target_link_libraries(myapp
                      PRIVATE CppWAMP::coro-headers)

With find_package

If you do not wish to embed CppWAMP as a subdirectory of your project, you may use find_package instead:

cmake_minimum_required (VERSION 3.12)
project(MyApp)

# If the following are not set, CppWAMP will use the default
# search paths of CMake's find_package() to find its dependencies.
set(Boost_ROOT /path/to/boost)
set(jsoncons_ROOT /path/to/jsoncons)

find_package(CppWAMP
             REQUIRED coro-headers
             CONFIG
             PATHS /path/to/cppwamp_installation
             NO_DEFAULT_PATH)

add_executable(myapp main.cpp)
target_link_libraries(myapp
    PRIVATE CppWAMP::coro-headers)

This method requires that CppWAMP be previously built (either as header-only, or compiled) and installed so that its CMake package config (i.e. CppWAMPConfig.cmake) is generated. This can either be done outside of your project or via your project's CMake scripts (for example by using ExternalProject_add or FetchContent).

Bundled Open-Source Software

This library bundles modified versions of any_completion_handler and any_completion_executor written by Christopher M. Kohlhoff, Copyright (c) 2003-2022, under the terms of the Boost Software License, Version 1.0 (http://www.boost.org/LICENSE_1_0.txt).

License

Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

Copyright © Butterfly Energy Systems, 2014-2022