Weave Example Application Tutorial

Introduction

The Weave Echo example application shows you how to implement a Weave application program using one of the supported Weave profiles, namely the very simple Weave Echo profile. This profile allows you to send a Weave message to a peer and expect a Weave response (similar to the ICMP Echo Request/Echo Response messages). For the purposes of this tutorial, we will focus on implementing Weave Echo over an underlying TCP connection. However, Weave supports other transport protocols, for example, regular UDP and a reliable version of UDP, called the Weave Reliable Messaging Protocol (WRMP).

Weave Profiles are, essentially, implementations of specific protocols over the Weave transport. An example of such a protocol is Weave Data Management which is a data synchronization protocol using a publish/subscribe mechanism between peers. Furthermore, when two Weave nodes are exchanging messages of a particular Weave Profile, they do so over a construct called a Weave Exchange which is a description of a Weave-based conversation over a Weave profile. A Weave Exchange is characterised by the ExchangeContext object, and every Weave node must create an ExchangeContext object before initiating a Weave conversation. After constructing a Weave ExchangeContext, Weave messages are sent and received using the WeaveMessageLayer class which sends the Weave message over a chosen transport (TCP, UDP, or WRMP).

Setup

To run the Weave Echo example application, each Weave node (requester and responder) must have a unique IPv6 address which it can use to exchange messages with its peer. For simplicity, these addresses are hardcoded within the example code as fd00:0:1:1::1 for the WeaveEchoRequester and fd00:0:1:1::2 for the WeaveEchoResponder, respectively. These addresses could be assigned to the local machine’s loopback interface using the following Linux commands :

$ sudo ip addr add fd00:0:1:1::1 dev lo $ sudo ip addr add fd00:0:1:1::2 dev lo

Code Walk Through

As part of this example, we have a WeaveEchoRequester program that acts as the client and sends echo requests to a WeaveEchoResponder program that receives EchoRequests and sends back EchoResponse messages. Additionally, we have collected a set of common functions, for example, for initialization and shutdown of the Weave stack in weave-app-common.cpp.

Weave EchoRequester

The Weave EchoRequester is implemented in the file weave-echo-requester.cpp. We dissect the program here to understand the general flow.

The program includes the required header files:

  • weave-app-common.h : Defines the common functions for initializing/shutting down the Weave stack and perform network I/O, etc.
  • Weave/Profiles/echo/WeaveEcho.h : Header file for the Weave Echo Profile that would be used by the application.
  • Weave/Support/ErrorStr.h : Utility support file for printing Weave errors in string format.

The main function logic flow is organized as follows:-

  1. Initialize the Weave stack.

    // Initialize the Weave stack as the client.
    InitializeWeave(false);
    
    • As part of initializing Weave, the several sublayers of the Weave stack are initialized.
    • The layers of primary interest to developers (from bottom upwards) are:
      1. SystemLayer: For handling PacketBuffers, Timers, etc.
      2. InetLayer: Abstracts transport layers, e.g., TCP, UDP, etc. over Linux based and LwIP network platforms.
      3. WeaveMessageLayer: Handles mapping of Weave messaging onto IP-based transports. It provides APIs for establishing listening sockets, initiating and accepting TCP connections, and sending and receiving encoded Weave messages.
      4. WeaveExchangeManager: The WeaveExchangeManager manages ExchangeContexts for all Profile communication
    • The WeaveEchoClient initializes the Weave stack as a TCP client that would connect to a Weave server.
  2. Initialize the EchoClient in the Weave Echo profile.

    // Initialize the EchoClient application.
    err = gEchoClient.Init(&ExchangeMgr);
    
  3. Set the application callback for the Echo responses.

    gEchoClient.OnEchoResponseReceived = HandleEchoResponseReceived;
    
  4. Formulate the address, Weave node identifier, and destination port for the peer.

    // Set the destination fields before initiating the Weave connection.
    
    IPAddress::FromString("fd00:0:1:1::2", gDestv6Addr);
    
    // Derive the destination node ID from the IPv6 address.
    gDestNodeId = IPv6InterfaceIdToWeaveNodeId(gDestv6Addr.InterfaceId());
    
    // Set the destination port to be the Weave port.
    gDestPort = WEAVE_PORT;
    
  5. Attempt to establish a TCP connection (managed by the Weave connection object) to the peer. Attempt a number of times before giving up and exiting the program.

     // Wait until the Weave connection is established.
     while (!gClientConEstablished)
    {
        // Start the Weave connection to the weave echo responder.
        StartClientConnection();
    
        DriveIO();
    
        if (gConnectAttempts > MAX_CONNECT_ATTEMPTS)
        {
            exit(EXIT_FAILURE);
        }
    }
    
    • When creating the TCP connection in StartClientConnection(), we create a new WeaveConnection object.
      gCon = MessageLayer.NewConnection();
      
    • Set the callbacks for handling completion of a connection attempt and the closure of an established connection.
      gCon->OnConnectionComplete = HandleConnectionComplete;
      gCon->OnConnectionClosed = HandleConnectionClosed;
      
    • Next, attempt to connect to the peer using the WeaveConnection object.
      err = gCon->Connect(gDestNodeId, gDestv6Addr, gDestPort);
      
  6. Once established, send the configured number of Echo requests.

    // Connection has been established. Now send the EchoRequests.
    for (int i = 0; i < MAX_ECHO_COUNT; i++)
    {
        // Send an EchoRequest message.
        SendEchoRequest();
    
        // Wait for response until the Echo interval.
        while (!EchoIntervalExpired())
        {
            DriveIO();
    
        }
    
        // Check if expected response was received.
        if (gWaitingForEchoResp)
        {
            printf("No response received\n");
    
            gWaitingForEchoResp = false;
        }
    }
    
    • In SendEchoRequest(), as part of formulating the EchoRequest, we create a new PacketBuffer and write some application data into it.
      FormulateEchoRequestBuffer(payloadBuf);
      
    • Inside the SendEchoRequest() API function , the WeaveEchoClient profile creates an ExchangeContext.
      ExchangeCtx = ExchangeMgr->NewContext(nodeId, nodeAddr, WEAVE_PORT,
                                            sendIntfId, this);
      
    • Sets the response handler on the created ExchangeContext.
      ExchangeCtx->OnMessageReceived = HandleResponse;
      
    • Sends the message over that ExchangeContext specifying the appropriate profile and message type.
      err = ExchangeCtx->SendMessage(kWeaveProfile_Echo,
                                     kEchoMessageType_EchoRequest, payload);
      
  7. Close the connection.

    // Done with all EchoRequests and EchoResponses.
    CloseClientConnection();
    
  8. Shutdown the EchoClient in the Weave Echo Profile.

    gEchoClient.Shutdown();
    
  9. Shutdown the Weave stack.

Weave EchoResponder

The Weave EchoResponder is implemented in the file weave-echo-responder.cpp. Weave EchoResponder's logic flow is similar to the Weave EchoRequester.

  1. We initialize the Weave stack as the TCP server.

    // Initialize the Weave stack.
    
    InitializeWeave(true);
    
  2. Then, we initialize the Weave EchoServer profile that listens for incoming TCP connections.

    // Initialize the EchoServer application.
    
    err = gEchoServer.Init(&ExchangeMgr);
    
  3. We wait in a loop for EchoRequest messages from the client. The requests are handled by the EchoServer profile in the following function.

    void WeaveEchoServer::HandleEchoRequest(ExchangeContext *ec,
                                            const IPPacketInfo *pktInfo,
                                            const WeaveMessageInfo *msgInfo,
                                            uint32_t profileId, uint8_t msgType,
                                            PacketBuffer *payload)
    {
       ...
    
    }
    

    The response is sent inside this function by calling the SendMessage(..) API on the received ExchangeContext specifying the appropriate profile and message type.

    // Send an Echo Response back to the sender.
    ec->SendMessage(kWeaveProfile_Echo, kEchoMessageType_EchoResponse, payload);