yp-client Example Applications

Files Required to Build Applications

The api-*.hpp files are needed for an application to use the libraries.

Note

  • If building the code from source the PTHREADS=1 make flag is required.

  • If using a binary distribution the appropriate "yumapro-pthreads" package for your platform is required.

Example Code Preparation

There are several example programs in the /usr/share/yumapro/src/yp-client folder. They are provided to show how the supplied library is used to connect and exchange information with servers, and how notifications from the server are handled.

The C++ Examples:

Directory Name

Function

action

YANG 1.1 Action example

callhome-server

NETCONF Call Home Server example

libtest

Test code for Users, Devices, Sessions

sget-system

Connect to a server using SSH and retrieve the system information

sget-system-tls

Connect to a server using TLS and retrieve the system information

toaster

An app following the toaster example in the YumaPro Quickstart Guide

The C wrapper examples:

Directory Name

Function

c-toaster

A c-wrapper app following the toaster example in the YumaPro Quickstart Guide, using SSH

c-toaster-tls

A c-wrapper app following the toaster example in the YumaPro Quickstart Guide, using TLS

The examples have three parameters that are needed to connect to a server:

Parameter

Usage

Default Value

cServer

Server name

“localhost”

cUsername

User name

“user”

cPassword

User password

None – the user is prompted for the password

Note

Before running the code the .cpp or .c file needs to be updated with the correct connection parameters.

If using the netconfd-pro server then make sure it is running.

Example Code: sget-system

The sget-system.cpp example code demonstrates the following:

  • setup code to deal with any exceptions

  • start a NETCONF session to a server for a user – prompt for a password

  • some YangAPI object settings are made to make things more readable

  • send the request "sget /netconf-state/statistics" to the server to request a node of the server’s YANG datastore

  • receive the response from the server into a buffer that can be processed

  • close the session to the server

sget-system.cpp

/*
 * Copyright (c) 2016 - 2019, YumaWorks, Inc., All Rights Reserved.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include "api-yangapi.hpp"
#include "api-session.hpp"
#include <iostream>
#include <string>


// For the prompt-for-password code.
#include <unistd.h>
#include <termios.h>

// All of the YumaWorks C++ code is in namespace yuma, to avoid naming clashes
// and cluttering the default namespace.
using namespace yuma;

// Used to tell this code to prompt for a password. Don't change this.
const xstring cPromptForPassword { "*" };

// The server to use. Note that the yuma::xstring class requires that its
// initializers be in UTF-8 format. There's no need to explicitly mark the
// strings here as UTF-8 since they're entirely ASCII, we've done so solely for
// explanatory purposes.
const xstring cServer { u8"localhost" };

// The username to use.
const xstring cUsername { u8"user" };

// The password to use. The cPrompForPassword here is a signal to the code below
// that it should prompt the user for his password. You can replace this with an
// actual password if desired, or nullptr to use no password. (You can also
// achieve the no-password effect by omitting the password() call to the
// SessionFactory object entirely.)
const xstring cPassword { cPromptForPassword };

// Some convenience declarations.
using std::cout;
using std::endl;
using std::cin;
using std::string;

static Session session;

// This class will receive notifications for the session, through its operator()
// function. It also holds them and manages access to them from multiple
// threads, necessary because they're usually added from a background thread.
//
// To the outside world, this class will act like std::stack<xstring>.
class NotificationStack {
    public:
    NotificationStack(): mData(std::make_shared<data_t>()) { }

    // Functions to emulate std::stack<xstring>.
    bool empty() const { lock_t lock(mData->mutex); return mData->notes.empty(); }
    size_t size() const { lock_t lock(mData->mutex); return mData->notes.size(); }
    const xstring& top() const { lock_t lock(mData->mutex); return mData->notes.front(); }
    void push(const xstring &n) { lock_t lock(mData->mutex); mData->notes.push_front(n); }
    void pop() { lock_t lock(mData->mutex); mData->notes.pop_front(); }

    // This function blocks until a notification is received or the provided
    // wait time runs out.
    bool waitForNotification(const std::chrono::duration<clock_t> &maxWaitTime) {
        // Start waiting. The condition variable eliminates the need for
        // wasteful polling.
        lock_t lock(mData->mutex);
        return mData->waiter.wait_until(lock, clock_t::now() + maxWaitTime,
            [=]() { return !mData->notes.empty(); });
    }

    // This function turns objects created from this class into "functors" --
    // objects that act like a function, but can hold a non-global state as
    // well. We're exploiting that capability to create an object that we can
    // use as a notification function.
    bool operator()(const xstring &newNotification) {
        cout << "Received notification:" << endl << newNotification << endl
            << endl;

        // Must lock the mutex before all accesses to mData->notes, because this
        // function will usually be called from a background thread.
        {
            lock_t lock(mData->mutex);
            mData->notes.push_back(newNotification);
        }
        mData->waiter.notify_one();

        // We could return false here, if we were only looking for a particular
        // notification and had found it. That would remove this notification
        // function from the Session's list and prevent it from receiving any
        // later notifications. Since we want to continue receiving them, we'll
        // return true instead.
        return true;
    }

    private:
    typedef std::mutex mutex_t;
    typedef std::unique_lock<mutex_t> lock_t;
    typedef std::chrono::steady_clock clock_t;

    struct data_t {
        mutable mutex_t mutex;
        std::deque<xstring> notes;
        std::condition_variable waiter;
    };

    // Since we're using this class as a functor, it has to be copyable, and
    // neither std::mutex nor std::condition_variable is. This works around that
    // problem.
    std::shared_ptr<data_t> mData;
};

// Ask user for input, returns True if user inputs "No", False otherwise
//
static bool userInputNo() {
	string input;
	const char cN ='N', cn = 'n';

	getline (cin, input);
	// Anything starting with "N" or "n" will exit, else continue
	if ((input[0] == cN) || (input[0] == cn)){
		return true;
	}
	else {
		return false;
	}
}

// A function to request a password from the user, showing only asterisks on the
// screen.
xstring fetchPassword(const xstring &prompt, bool showAsterisk = true) {
    const char cReturn = 10, cBackspace = 127;

    std::cout << prompt << ' ';

    xstring r;
    while (true) {
        struct termios t_old;
        tcgetattr(STDIN_FILENO, &t_old);
        struct termios t_new = t_old;
        t_new.c_lflag &= ~(ICANON | ECHO);
        tcsetattr(STDIN_FILENO, TCSANOW, &t_new);
        xmlChar ch = getchar();
        tcsetattr(STDIN_FILENO, TCSANOW, &t_old);

        if (ch == cReturn) {
            break;
        } else if (ch == cBackspace) {
            if (r.length() != 0) {
                if (showAsterisk)
                    std::cout << "\b \b";
                r = r.substr(0, r.length() - 1);
            }
        } else {
            r += ch;
            if (showAsterisk)
                cout << '*';
        }
    }

    std::cout << std::endl;
    return r;
}


// A convenience function for sending a command and reporting its results.
static bool sendCommand(const xstring &command) {
    try {
        cout << " Sending: " << command << endl;

        Response response = session.command(command);
        if (response.error) {
            cout << "Received error:" << endl << response.result << endl << endl;
            return false;
        }

        if (response.result.empty())
            cout << "Received: OK" << endl << endl;
        else
            cout << "Received:" << endl << response.result << endl << endl;
        return true;
    } catch (StatusFailure &e) {
        cout << "StatusFailure exception: " << e.what() << endl << endl;
        return false;
    } catch (std::exception &e) {
        cout << "other exception: " << e.what() << endl << endl;
        return false;
    }
}

int main() {
    // The first step is to initialize the API by creating a yuma::YangAPI
    // object. No parameters are needed for this, but the object must persist
    // as long as you want to use the library.
    YangAPI api;

    // You can change many settings using the YangAPI object. As an example,
    // we'll set the indent value to four spaces instead of the default two.
    api.indent(4);

    // for XML structured response output add
    api.displayMode(displaymode::xml);

    // We enclose most of the API code in a try/catch structure, to catch any
    // exceptions detected in it. The code will generally use the
    // yuma::StatusFailure exception, but a few other std::exception-based
    // classes might also appear on occasion.
    int r = EXIT_FAILURE;
    try {
        // We'll start by creating a notification stack to receive notifications
        // for the session. We'll tie it into the session through a
        // SessionFactory call. We could do it with the Session object's
        // addNotificationFunction member function instead, but we don't need
        // the Token to remove it later, which is the only advantage of doing
        // it that way.
        NotificationStack notifications;

        // If the user has requested that we prompt for a password, we'll do so
        // here.
        xstring password = (cPassword == cPromptForPassword ?
            fetchPassword("Enter your password for server " + cServer + ":",
                true) :
            cPassword);

        // Now we'll create a User. The User class holds information about the
        // user account on a server -- its username and credentials. There's no
        // requirement for this, we could specify all of these options when
        // creating the Session instead, but a User can be saved to a file for
        // later reloading and reuse.
        User user = UserFactory("me", cUsername).password(password).create();

        // We'll create a Device too. The Device class holds information about
        // the server we're trying to contact: its type, address information,
        // and protocols. Note that we've omitted the port parameter (it would
        // normally be specified after the server's address in the DeviceFactory
        // constructor), which tells the API to use the default port for the
        // session type.
        //
        // As with the User object, there's no requirement to create this. We
        // could specify all this information when creating a Session. The only
        // advantage to using a Device is that it can be saved to a file for
        // later reloading and reuse.
        Device server = NetconfDeviceFactory("myDevice", cServer).create();

        // Now we can create a session. We use one of the SessionFactory classes
        // for this, ending with a call to SessionFactory::create().
        cout << "Attempting to establish connection to server " << cServer <<
            "..." << endl;
        session = DeviceSessionFactory(user, server)
            .notificationFunction(notifications)
            .create();
        cout << "Session established!" << endl << endl;

        do {

		// Collect the response from the server to the "sget  /netconf-state/statistics" request
		Response response = session.command("sget /netconf-state/statistics");
        if (response.error) {
            cout << "Received error:" << endl << response.result << endl << endl;
            break;
		}
		cout << "error:" << response.error << endl << "response:" << endl << response.result << endl;

            // If we get this far, we've succeeded.
            r = EXIT_SUCCESS;
        } while (0);

        // Finally, close the session.
        if (!sendCommand("close-session")) {
            cout << "Unexpected failure, aborting." << endl;
            return EXIT_FAILURE;
        }

        // When the Session object goes out of scope, the session is terminated
        // and automatically cleaned up.
    } catch (StatusFailure &e) {
        cout << "StatusFailure exception: " << e.what() << endl;
    } catch (std::exception &e) {
        cout << "other exception: " << e.what() << endl;
    }

    // When the YangAPI object goes out of scope, the library is deinitialized.
    return r;
}

When sget-system.cpp is built the following is displayed (example):

user@YW-U18W sget-system$ make

g++ -g -std=c++11 -I/usr/include/yumapro/ycli -I/usr/include/yumapro/mgr
-I/usr/include/yumapro/ncx -I/usr/include/yumapro/platform
-I/usr/include/libxml2/libxml -I/usr/include/libxml2 -pthread -o
sget-system sget-system.cpp -lyumapro_ycli -lyumapro_mgr -lyumapro_ncx
-lyumapro_subsys-pro -lssl -lcrypto -lssh2 -lcrypt

When sget-system.cpp is executed the following is displayed (example):

user@YW-U18W sget-system$ ./sget-system
Enter your password for server localhost: ***********
Attempting to establish connection to server localhost...
Session established!

error:0
response:
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
    <data>
        <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
            <statistics>
                <netconf-start-time>2019-12-13T23:26:08Z</netconf-start-time>
                <in-bad-hellos>0</in-bad-hellos>
                <in-sessions>4</in-sessions>
                <dropped-sessions>0</dropped-sessions>
                <in-rpcs>9</in-rpcs>
                <in-bad-rpcs>0</in-bad-rpcs>
                <out-rpc-errors>0</out-rpc-errors>
                <out-notifications>0</out-notifications>
            </statistics>
        </netconf-state>
    </data>
</rpc-reply>
 Sending: close-session
Received: OK

user@YW-U18W sget-system$

In this example, the session is setup with a username and password:

session = DeviceSessionFactory(user, server)
             .create();

The user is defined by:

User user = UserFactory("me", cUsername).password(password).create();

The device is defined by:

Device server = NetconfDeviceFactory("myDevice", cServer).create();

Example Code: sget-system-tls

To use TLS instead of SSH, use the sget-system-tls example. The User creation is different:

User user = UserFactory("me", cUsername)
         .sslClientCertificate("$HOME/certs/client.crt")
         .sslClientCertificateKeyFile("$HOME/certs/client.key")
         .sslTrustStore("/etc/ssl/certs/")
         .fallbackOkay(false).create();

To configure TLS certificates for YumaPro SDK see the “Configure TLS” section of the YumaPro SDK Installation Guide.

Specify the port and protocol the communication with the device will use with:

Device server = NetconfDeviceFactory("myDevice",
                                      cServer,
                                      6513,
                                      netconf-tls").create();

When building sget-system-tls the output will look something like:

user@YW-U18W sget-system-tls$ make

g++ -g -std=c++11 -I/usr/include/yumapro/ycli -I/usr/include/yumapro/mgr
-I/usr/include/yumapro/ncx -I/usr/include/yumapro/platform
-I/usr/include/libxml2/libxml -I/usr/include/libxml2 -pthread -o
sget-system-tls sget-system-tls.cpp -lyumapro_ycli -lyumapro_mgr
-lyumapro_ncx -lyumapro_subsys-pro -lssl -lcrypto -lssh2 -lcrypt

When invoking sget-system-tls the output will look something like:

user@YW-U18W sget-system-tls$ ./sget-system-tls
Attempting to establish connection to server localhost...
Session established!

 Sending: $$display-mode = cli
Received: OK

error:0
response:
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
    <data>
        <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
            <statistics>
                <netconf-start-time>2019-12-14T00:17:57Z</netconf-start-time>
                <in-bad-hellos>0</in-bad-hellos>
                <in-sessions>1</in-sessions>
                <dropped-sessions>0</dropped-sessions>
                <in-rpcs>1</in-rpcs>
                <in-bad-rpcs>0</in-bad-rpcs>
                <out-rpc-errors>0</out-rpc-errors>
                <out-notifications>0</out-notifications>
            </statistics>
        </netconf-state>
    </data>
</rpc-reply>
 Sending: close-session
Received: OK

user@YW-U18W sget-system-tls$

Example Code: toaster

The toaster.cpp example code demonstrates the following, see also the example of libtoaster in the section “Getting Started with toaster.yang” in the YumaPro Quickstart Guide.

  • setup code to deal with any exceptions

  • start a NETCONF session to a server for a user – ask them for their password

  • display the session number (if you are running the server in debug mode you’ll see the session numbers match)

  • setup to receive notifications for this session

  • setup a subscription with the server for this session

  • messages received from the server will display the message number that will match the message numbers displayed in the debug output from the server)

  • set some session globals and local variables to show how that is done

  • load the toaster.yang module to the server

  • mgrload the toaster.yang module

  • lock the datastore

  • create the /toaster node (if it already exists, due to running the program several times, ignore the error)

  • unlock the datastore

  • display the /toaster node by sending the request “sget /toaster”

  • display the /toaster node by sending the request “xget /toaster”

  • send the command to make-toast with specific parameters

  • receive the toastStatus=done notification (after about 12seconds)

  • resend the command to make-toast with specific parameters

  • don’t wait for the toastStatus=done notification, send the cancel-toast command

  • receive the toastStatus=cancelled notification

  • close the session to the server

** toaster.cpp**

/*
 * Copyright (c) 2016 - 2018, YumaWorks, Inc., All Rights Reserved.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include "api-yangapi.hpp"
#include "api-session.hpp"
#include <iostream>

// For the prompt-for-password code.
#include <unistd.h>
#include <termios.h>

// All of the YumaWorks C++ code is in namespace yuma, to avoid naming clashes
// and cluttering the default namespace.
using namespace yuma;

// Used to tell this code to prompt for a password. Don't change this.
const xstring cPromptForPassword { "*" };

// The server to use. Note that the yuma::xstring class requires that its
// initializers be in UTF-8 format. There's no need to explicitly mark the
// strings here as UTF-8 since they're entirely ASCII, we've done so solely for
// explanatory purposes.
const xstring cServer { u8"localhost" };

// The username to use.
const xstring cUsername { u8"user" };

// The password to use. The cPrompForPassword here is a signal to the code below
// that it should prompt the user for his password. You can replace this with an
// actual password if desired, or nullptr to use no password. (You can also
// achieve the no-password effect by omitting the password() call to the
// SessionFactory object entirely.)
const xstring cPassword { cPromptForPassword };

// Some convenience declarations.
using std::cout;
using std::endl;

static Session session;

// This class will receive notifications for the session, through its operator()
// function. It also holds them and manages access to them from multiple
// threads, necessary because they're usually added from a background thread.
//
// To the outside world, this class will act like std::stack<xstring>.
class NotificationStack {
    public:
    NotificationStack(): mData(std::make_shared<data_t>()) { }

    // Functions to emulate std::stack<xstring>.
    bool empty() const { lock_t lock(mData->mutex); return mData->notes.empty(); }
    size_t size() const { lock_t lock(mData->mutex); return mData->notes.size(); }
    const xstring& top() const { lock_t lock(mData->mutex); return mData->notes.front(); }
    void push(const xstring &n) { lock_t lock(mData->mutex); mData->notes.push_front(n); }
    void pop() { lock_t lock(mData->mutex); mData->notes.pop_front(); }

    // This function blocks until a notification is received or the provided
    // wait time runs out.
    bool waitForNotification(const std::chrono::duration<clock_t> &maxWaitTime) {
        // Start waiting. The condition variable eliminates the need for
        // wasteful polling.
        lock_t lock(mData->mutex);
        return mData->waiter.wait_until(lock, clock_t::now() + maxWaitTime,
            [=]() { return !mData->notes.empty(); });
    }

    // This function turns objects created from this class into "functors" --
    // objects that act like a function, but can hold a non-global state as
    // well. We're exploiting that capability to create an object that we can
    // use as a notification function.
    bool operator()(const xstring &newNotification) {
        cout << "Received notification:" << endl << newNotification << endl
            << endl;

        // Must lock the mutex before all accesses to mData->notes, because this
        // function will usually be called from a background thread.
        {
            lock_t lock(mData->mutex);
            mData->notes.push_back(newNotification);
        }
        mData->waiter.notify_one();

        // We could return false here, if we were only looking for a particular
        // notification and had found it. That would remove this notification
        // function from the Session's list and prevent it from receiving any
        // later notifications. Since we want to continue receiving them, we'll
        // return true instead.
        return true;
    }

    private:
    typedef std::mutex mutex_t;
    typedef std::unique_lock<mutex_t> lock_t;

    // The C++11 standard defines std::chrono::steady_clock, but before it was
    // finalized it defined std::chrono::monotonic_clock instead. This code must
    // compile with pre-standard g++ versions too.
    #if __GNUC_PREREQ(4,7)
        typedef std::chrono::steady_clock clock_t;
    #else
        typedef std::chrono::monotonic_clock clock_t;
    #endif

    struct data_t {
        mutable mutex_t mutex;
        std::deque<xstring> notes;
        std::condition_variable waiter;
    };

    // Since we're using this class as a functor, it has to be copyable, and
    // neither std::mutex nor std::condition_variable is. This works around that
    // problem.
    std::shared_ptr<data_t> mData;
};

// A function to request a password from the user, showing only asterisks on the
// screen.
xstring fetchPassword(const xstring &prompt, bool showAsterisk = true) {
    const char cReturn = 10, cBackspace = 127;

    std::cout << prompt << ' ';

    xstring r;
    while (true) {
        struct termios t_old;
        tcgetattr(STDIN_FILENO, &t_old);
        struct termios t_new = t_old;
        t_new.c_lflag &= ~(ICANON | ECHO);
        tcsetattr(STDIN_FILENO, TCSANOW, &t_new);
        xmlChar ch = getchar();
        tcsetattr(STDIN_FILENO, TCSANOW, &t_old);

        if (ch == cReturn) {
            break;
        } else if (ch == cBackspace) {
            if (r.length() != 0) {
                if (showAsterisk)
                    std::cout << "\b \b";
                r = r.substr(0, r.length() - 1);
            }
        } else {
            r += ch;
            if (showAsterisk)
                cout << '*';
        }
    }

    std::cout << std::endl;
    return r;
}

// A convenience function for sending a command and reporting its results.
static bool sendCommand(const xstring &command) {
    try {
        cout << "Sending command: " << command << endl;

        Response response = session.command(command);
        xstring id = (response.internalRequestId &&
            !response.internalRequestId.empty() ?
                " (" + response.internalRequestId + ")" : "");

        if (response.error) {
            cout << "Received error" << id << ":" << endl << response.result
                << endl << endl;
            return false;
        }

        if (response.result.empty())
            cout << "Received OK" << id << endl << endl;
        else
            cout << "Received response" << id << ":" << endl << response.result
                << endl << endl;
        return true;
    } catch (StatusFailure &e) {
        cout << "StatusFailure exception: " << e.what() << endl << endl;
        return false;
    } catch (std::exception &e) {
        cout << "other exception: " << e.what() << endl << endl;
        return false;
    }
}

int main() {
    // The first step is to initialize the API by creating a yuma::YangAPI
    // object. No parameters are needed for this, but the object must persist
    // as long as you want to use the library.
    YangAPI api;

    // You can change many settings using the YangAPI object. As an example,
    // we'll set the indent value to four spaces instead of the default two.
    api.indent(4);

    // We enclose most of the API code in a try/catch structure, to catch any
    // exceptions detected in it. The code will generally use the
    // yuma::StatusFailure exception, but a few other std::exception-based
    // classes might also appear on occasion.
    int r = EXIT_FAILURE;
    try {
        // We'll start by creating a notification stack to receive notifications
        // for the session. We'll tie it into the session through a
        // SessionFactory call. We could do it with the Session object's
        // addNotificationFunction member function instead, but we don't need
        // the Token to remove it later, which is the only advantage of doing
        // it that way.
        NotificationStack notifications;

        // If the user has requested that we prompt for a password, we'll do so
        // here.
        xstring password = (cPassword == cPromptForPassword ?
            fetchPassword("Enter your password for server " + cServer + ":",
                true) :
            cPassword);

        // Now we'll create a User. The User class holds information about the
        // user account on a server -- its username and credentials. There's no
        // requirement for this, we could specify all of these options when
        // creating the Session instead, but a User can be saved to a file for
        // later reloading and reuse.
        User user = UserFactory("me", cUsername).password(password).create();

        // We'll create a Device too. The Device class holds information about
        // the server we're trying to contact: its type, address information,
        // and protocols. Note that we've omitted the port parameter (it would
        // normally be specified after the server's address in the DeviceFactory
        // constructor), which tells the API to use the default port for the
        // session type.
        //
        // As with the User object, there's no requirement to create this. We
        // could specify all this information when creating a Session. The only
        // advantage to using a Device is that it can be saved to a file for
        // later reloading and reuse.
        Device server = NetconfDeviceFactory("myDevice", cServer).create();

        // Now we can create a session. We use one of the SessionFactory classes
        // for this, ending with a call to SessionFactory::create().
        //
        // The notificationFunction option tells it where to send notifications
        // (messages that are not direct replies to commands). The timeout
        // option tells it how long to wait before deciding that something has
        // gone wrong with the connection; it defaults to sixty seconds, but
        // telling it to time out after fifteen will let it fail more quickly,
        // if we know that the connection to the device we're trying to contact
        // doesn't have an inordinate amount of latency.
        cout << "Attempting to establish connection to server " << cServer <<
            "..." << endl;
        session = DeviceSessionFactory(user, server)
            .notificationFunction(notifications)
            .timeout(std::chrono::seconds(15))
            .create();
        cout << "Session " << session.serverSessionId() << " established!"
            << endl << endl;

        // Setup a subscription with the server
        if (!sendCommand("create-subscription")) {
            cout << "Unexpected failure, aborting." << endl;
            return EXIT_FAILURE;
        }

        do {
            // You can create or read global and local variables through the
            // Session class as well. These variables can contain anything
            // except the null character.
            session.global("param1", "parameter value including \nnewlines, 'single-' and \"double-quotes\", and \tt\ta\tb\ts");
            cout << "param1 is currently set to '" << session.global("param1")
                << "'" << endl << endl;

            // If this one fails (maybe the 'toaster' module isn't installed?),
            // we'll skip the rest of the tests and just close the session. If
            // it succeeds, it should receive (and print) the toaster module's
            // revision, though we're not checking for that.
            if (!sendCommand("ysys:load toaster")) {
                cout << "Unexpected failure, skipping toaster tests." << endl;
                break;
            }


            // This one should succeed.
            if (!sendCommand("get-locks")) {
                cout << "Unexpected failure, aborting." << endl;
                break;
            }

            // This one will fail if the /toaster has already been created.
            // For our purposes, that's not an error. If it succeeds, save the
            // changes.
            if (sendCommand("create /toaster")) {
                if (!sendCommand("save")) {
                    cout << "Unexpected failure, aborting." << endl;
                    break;
                }
            }

            // This one should succeed.
            if (!sendCommand("release-locks")) {
                cout << "Unexpected failure, aborting." << endl;
                break;
            }

            // These two commands should return (identical) data. We don't check
            // the data, just that there's no error.
            if (!sendCommand("sget /toaster") || !sendCommand("xget /toaster")) {
                cout << "Unexpected failure, aborting." << endl;
                break;
            }

            // Let's make some toast! We'll check for any recent notifications
            // first, to ensure that there aren't any that might throw us off.
            while (!notifications.empty()) {
                notifications.pop();
            }

	    if (sendCommand("make-toast toasterDoneness=1 toasterToastType=toast:wonder-bread")) {
                // This will take about twelve seconds, if all goes well, so
                // we'll use a twenty-second timeout.
                cout << "Toast should be done in about 12 seconds..." << endl;
                if (!notifications.waitForNotification(std::chrono::seconds(20))) {
                    cout << "Did not receive toastDone notification within provided time limit, aborting."
                        << endl;
                    break;
                } else {
                    // Remove the toastDone notification from the stack, so it
                    // doesn't interfere with the later call.
                    notifications.pop();
                }
            } else {
                cout << "Unexpected failure, aborting." << endl;
                break;
            }

            // We'll repeat the make-toast command, but this time we'll cancel
            // it immediately.
            if (!notifications.empty()) {
                cout << "Unexpected notification in stack, aborting." << endl;
                break;
            } else if (sendCommand("make-toast toasterDoneness=1 toasterToastType=toast:frozen-waffle")) {
                if (sendCommand("cancel-toast")) {
                    // This notification should be sent almost immediately.
                    // We'll only use a five-second timeout, and no message.
                    if (!notifications.waitForNotification(std::chrono::seconds(5))) {
                        cout << "Did not receive toastDone notification within provided time limit, aborting."
                            << endl;
                        break;
                    } else {
                        // Remove the toastDone notification from the stack.
                        notifications.pop();
                    }
                } else {
                    cout << "Unexpected failure, aborting." << endl;
                    break;
                }
            } else {
                cout << "Unexpected failure, aborting." << endl;
                break;
            }

            // If we get this far, we've succeeded.
            r = EXIT_SUCCESS;
        } while (0);

        // Finally, close the session.
        if (!sendCommand("close-session")) {
            cout << "Unexpected failure, aborting." << endl;
            return EXIT_FAILURE;
        }

        // When the Session object goes out of scope, the session is terminated
        // and automatically cleaned up.
    } catch (StatusFailure &e) {
        cout << "StatusFailure exception: " << e.what() << endl;
    } catch (std::exception &e) {
        cout << "other exception: " << e.what() << endl;
    }

    // When the YangAPI object goes out of scope, the library is deinitialized.
    return r;
}

When toaster is built the following is displayed (example):

user@YW-U18W toaster$ make

g++ -g -std=c++11 -I/usr/include/yumapro/ycli -I/usr/include/yumapro/mgr
-I/usr/include/yumapro/ncx -I/usr/include/yumapro/platform
-I/usr/include/libxml2/libxml -I/usr/include/libxml2 -pthread -o toaster
toaster.cpp -lyumapro_ycli -lyumapro_mgr -lyumapro_ncx
-lyumapro_subsys-pro -lssl -lcrypto -lssh2 -lcrypt

user@YW-U18W toaster$

When toaster is executed the following is displayed (example):

user@YW-U18W toaster$ ./toaster
Enter your password for server localhost: ***********
Attempting to establish connection to server localhost...
Session 4 established!

Sending command: create-subscription
Received OK (2)

param1 is currently set to 'parameter value including
newlines, 'single-' and "double-quotes", and 	t	a	b	s'

Sending command: ysys:load toaster
Received notification:
notification {
    eventTime 2019-12-14T00:28:53Z
    yang-library-change {
        module-set-id 3755
    }
}

Received notification:
notification {
    eventTime 2019-12-14T00:28:53Z
    netconf-capability-change {
        changed-by {
            username john
            session-id 4
            source-host 127.0.0.1
        }
        added-capability http://netconfcentral.org/ns/toaster?module=toaster&revision=2009-11-20
    }
}

Received response (3):
rpc-reply {
    mod-revision 2009-11-20
}

Sending command: get-locks
Received OK (4)

Sending command: create /toaster
Received OK (6)

Sending command: save
Received notification:
notification {
    eventTime 2019-12-14T00:28:54Z
    netconf-config-change {
        changed-by {
            username john
            session-id 4
            source-host 127.0.0.1
        }
        datastore running
        edit {
            target /toast:toaster
            operation create
        }
    }
}

Received OK (7)

Sending command: release-locks
Received OK (8)

Sending command: sget /toaster
Received response (10):
rpc-reply {
    data {
        toaster {
            toasterManufacturer 'Acme, Inc.'
            toasterModelNumber 'Super Toastamatic 2000'
            toasterStatus up
        }
    }
}

Sending command: xget /toaster
Received response (11):
rpc-reply {
    data {
        toaster {
            toasterManufacturer 'Acme, Inc.'
            toasterModelNumber 'Super Toastamatic 2000'
            toasterStatus up
        }
    }
}

Sending command: make-toast toasterDoneness=1 toasterToastType=toast:wonder-bread
Received OK (12)

Toast should be done in about 12 seconds...
Received notification:
notification {
    eventTime 2019-12-14T00:29:07Z
    toastDone {
        toastStatus done
    }
}

Sending command: make-toast toasterDoneness=1 toasterToastType=toast:frozen-waffle
Received OK (13)

Sending command: cancel-toast
Received notification:
notification {
    eventTime 2019-12-14T00:29:08Z
    toastDone {
        toastStatus cancelled
    }
}

Received OK (14)

Sending command: close-session
Received OK (15)

user@YW-U18W toaster$