IoT Toolkit User Guide & Reference Manual
HTTP Client and JSON Parser.
Introduction
This section presents an overview of the IoT Toolkit, its structure,
and its capabilities.
The SEGGER IoT Toolkit is a software library that enables you to interact
with REST APIs but can equally interact with a standard HTTP web server.
It combines an HTTP client with highly-efficient JSON and SAX parsers
to deliver a compelling solution for embedded devices.
The SEGGER IoT Toolkit is hardware independent and integrates with other
components from SEGGER such as emSSL and embOS/IP.
Design goals
The IoT Toolkit is designed with the following goals in mind:
- Highly modular such that unused features are never linked into the application.
- Be completely runtime configurable, adding each modular feature as needed.
- Present a simple user-level API that is easy to use without extensive setup.
- Easy to maintain both by SEGGER and anybody with access to the sources.
- Conform to all necessary standards and current best practices.
- Be efficient both in terms of resource usage and execution speed.
- Target embedded processors with limited resources as well as workstations.
- Use the the SEGGER Cryptographic Toolkit, the foundation of all SEGGER security products.
We believe all design goals are achieved by the IoT Toolkit.
Features
The IoT Toolkit is written in ANSI C and can be used on virtually any CPU. Here is a
list of the library features:
- ISO/ANSI C source code.
- High performance.
- Small footprint.
- Runs “out-of-the-box”.
- Highly compact implementation runs effortlessly on single-chip MCUs.
- Easy-to-understand and simple-to-use API.
- Simple configuration.
- Secure communication over an open channel.
- Modular architecture links only what you need.
- Royalty-free.
Package content
The IoT Toolkit is provided in source code and contains
everything required. The following table shows the content of the package:
Files | Description |
Config | Configuration header files. |
CRYPTO | Shared cryptographic toolkit source code. |
Doc | IoT Toolkit documentation. |
Sample/Config | Example IoT Toolkit user configuration. |
SEGGER | SEGGER software component source code. |
IOT | HTTP and JSON implementation source code. |
Application | IoT sample applications. |
Windows/IOT | Host-based IoT sample applications. |
Sample applications
The IoT Toolkit ships with a number of sample applications that demonstrate how
to integrate IoT capability into your application. Each sample application
demonstrates a specific capability of the IoT Toolkit and is a small incremental
step over previous examples.
The sample applications are:
A note on the samples
Each sample that we present in this manual is written in a style
that makes it easy to describe and that fits comfortably within the
margins of printed paper. Therefore, it may well be that you would
rewrite the sample to have a slightly different structure that fits
better, but please keep in mind that these examples are written with
clarity as the prime objective, and to that end we sacrifice some
brevity and efficiency.
Where to find the sample code
All samples are included in the Application directory of the
IoT Toolkit distribution.
HTTP client
Overview
Although the HTTP protocol is mostly associated with retrieving
web pages from the Internet, the SEGGER HTTP client is mainly
intended to interacting with REST APIs — although it can
also retrieve web content just as easily.
REST APIs usually exchange data in a structured format such
as XML, JSON, or CSV. Recently, JSON has displaced XML as the
preferred REST data format and JSON is the primary format,
or indeed only format, supported by many services.
This section presents simple examples that can be used as a base
for using the HTTP client to work with REST APIs from Twitter,
Amazon, and Xively. The HTTP client also underpins the SEGGER Dropbox Client
which interacts with the Dropbox REST APIs.
A simple GET request
This section will develop the code necessary to issue a request to
retrieve the index page of a website. Even though this is a simple
request, it requires setting up the request, executing it over the
network, and processing the response.
For a complete listing of this application, see IOT_HTTP_GetRequest complete listing.
Application entry
The main application task is responsible for setting up the environment
ready for issuing the request. This is simply boilerplate code that has
no configuration:
void MainTask(void) {
IOT_HTTP_CONTEXT HTTP;
CONNECTION_CONTEXT Connection;
char aBuf[128];
char aPayload[128];
unsigned StatusCode;
int Status;
//
SEGGER_SYS_Init();
SEGGER_SYS_IP_Init();
Initialize system component
The call to SEGGER_SYS_Init() sets up the SEGGER system abstraction layer
to initialize services to the application. This abstraction layer is
implemented for Windows and Linux but is not intended for production code,
it is only for demonstration.
Initialize IP component
The call to SEGGER_SYS_IP_Init() initializes the underlying IP stack
to the point that sockets can be opened. For Windows this means starting
up Winsock, and for embOS/IP it initializes and sets the IP task running.
Set up the HTTP request
Once the system components are initialized, it’s time to set up the
HTTP request.
IOT_HTTP_Init (&HTTP, &aBuf[0], sizeof(aBuf));
IOT_HTTP_SetIO (&HTTP, &_PlainAPI, &Connection);
IOT_HTTP_SetVersion(&HTTP, &IOT_HTTP_VERSION_HTTP_1v1);
IOT_HTTP_AddMethod (&HTTP, "GET");
IOT_HTTP_AddHost (&HTTP, "www.segger.com");
IOT_HTTP_SetPort (&HTTP, 80);
IOT_HTTP_AddPath (&HTTP, "/");
Initialize the client context
The call to IOT_HTTP_Init() initializes the HTTP context
as an empty container for the request. For correct operation,
a buffer must be provided that the HTTP client uses for temporary
data during the request, for instance when processing request
headers and status lines. The exact size of this buffer will be
discussed later, and for now we size the buffer to 128 bytes which
is enough to process incoming response header lines up to 127
characters in length.
Note
If header lines exceed the capacity of the buffer, HTTP
processing will gracefully fail with an error.
Initialize I/O interface
The call to IOT_HTTP_SetIO() sets up the connection and I/O API
required for HTTP connections. The interface is described in the
section I/O over the network, and we defer implementation
details until later.
Set the HTTP request version
The specific HTTP request version may well be defined by the
REST API that you use, but in this case we ask for an HTTP/1.1
connection to the server. There are differences in requests
and responses between HTTP/1.0 and HTTP/1.1 in connection
with headers allowed and payload formats. For the basic
connection we are establishing here, the differences between
HTTP/1.0 and HTTP/1.1 requests are completely handled by the
library.
It is mandatory to set an HTTP version for the request so that
the HTTP version is sent to the server and so that the HTTP
client can properly deal with responses.
Set the HTTP method
As this is a GET request, the method is set as “GET” in
the request. Other methods are possible, the standard ones
being CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, and TRACE
for HTTP and, importantly, REST APIs.
Set the host and port
The host is set to “www.segger.com” using IOT_HTTP_AddHost()
as this happens to be the host that we wish to connect to. The host
name is important to many protocols and is passed through the HTTP
client to the connection method in the I/O API which we describe later.
As this is plain HTTP request, the connection is made to the host
using port 80, set by IOT_HTTP_SetPort(). This port number is
passed to the connection method and is otherwise unused within
the HTTP client.
Set the path
We ask to retrieve the root item of the website by setting the
path to “/” using IOT_HTTP_AddPath().
The HTTP client is flexible when developing the path in that it
allows clients to build up the paths incrementally rather than
having the client specify the path in a single call. For instance,
the path “/product/iot/toolbox.htm” could be built up by
adding the path fragments “/product/iot/”, “toolbox”,
and “.htm” in order as three separate calls to IOT_HTTP_AddPath().
The path is built by (virtually) appending each fragment onto
existing fragments.
Connect and issue the request
With the HTTP request initialized and populated, the request
is ready to be issued.
Status = IOT_HTTP_Connect(&HTTP);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot negotiate a connection to %s:%d!\n",
IOT_HTTP_GetHost(&HTTP),
IOT_HTTP_GetPort(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
//
Status = IOT_HTTP_Exec(&HTTP);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot execute %s request!\n",
IOT_HTTP_GetMethod(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
Connect to the host
The call to IOT_HTTP_Connect() attempts to establish a connection
to the host using the host name and port set in the HTTP request.
If the connection cannot be made, the sample terminates with
an error. Note that the request’s parameters can be queried and
in this case IOT_HTTP_GetHost() and IOT_HTTP_GetPort() return
the host name and port set on the request, as you would expect.
Issue the request
The request can be sent immediately after the connection is
successfully established. IOT_HTTP_Exec() will issue the request,
with any appropriate headers, to the host over the established
connection. If a processing error occurs, the sample terminates
with an error: the particular error code can come from the networking
later (e.g. the connection is closed prematurely) or internally
from the HTTP request (although this simple request will never
raise an internal error).
Process the response
Once the request is successfully issued, the web server replies
with a standardized response. The response is comprised of
the status line, optional headers, and an optional payload.
In this section we deal with the status line and the headers
only.
Status = IOT_HTTP_ProcessStatusLine(&HTTP, &StatusCode);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot process status line!\n");
SEGGER_SYS_OS_Halt(100);
}
SEGGER_SYS_IO_Printf("Returned status code: %u\n\n", StatusCode);
//
Status = IOT_HTTP_ProcessHeaders(&HTTP, NULL);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot process headers!\n");
SEGGER_SYS_OS_Halt(100);
}
Process the status line
The call to IOT_HTTP_ProcessStatusLine() retrieves the initial
status line of the HTTP response. This must immediately follow
the HTTP request being issued as the status line is sent by
the host as the first part of the response.
IOT_HTTP_ProcessStatusLine() returns a status which indicates
errors at the network level or errors in the format of the
status line. If there is an error processing the status line,
for instance the connection is prematurely closed or the status
line is not in the standard format, the returned value is negative
and the sample terminates with an error.
If IOT_HTTP_ProcessStatusLine() succeeds, the status code extracted
from the status line is written to StatusCode in this instance.
Note
It is essential to understand that the value returned as the result
of the function call is entirely independent of the value extracted from
the status line: the function result indicates successful extraction
of the status code, nothing more, and does not indicate whether the
HTTP request “executed successfully”. For instance, a HTTP 404
“not found” response does not indicate failure of the HTTP request
execution.
The list of standard HTTP response codes is defined in section 6 of
RFC 7231: Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content.
Process the response headers
Once the status code is successfully extracted, handling of the response
continues by processing response headers sent by the server. It is
necessary to process the headers as these generally describe things that
are required to process the following payload, if any.
The call to IOT_HTTP_ProcessHeaders() receives and processes each header
in turn, parsing some headers internally. The second parameter to the
function is a callback to a user-defined function to process individual
headers: in this case we pass in a null pointer which indicates that there
is no special per-header processing required.
The function result, as before, indicates problems propagated from the
network connection or issues internal to the HTTP component. If, for
instance, a response header has invalid syntax or is too long, this would
raise an error. In this sample, any error processing the response headers
terminates the application.
Receive payload and disconnect
Following the status line and headers comes the payload which, in this
case, is HTML.
IOT_HTTP_GetBegin(&HTTP);
do {
Status = IOT_HTTP_GetPayload(&HTTP, &aPayload[0], sizeof(aPayload));
SEGGER_SYS_IO_Printf("%.*s", Status, aPayload);
} while (Status > 0);
IOT_HTTP_GetEnd(&HTTP);
//
IOT_HTTP_Disconnect(&HTTP);
Prepare to receive payload
The call to IOT_HTTP_GetBegin() starts the process of receiving
the payload. The response headers describe the transfer encoding of
the payload (identity or chunked delivery) according to the request
and HTTP version and IOT_HTT_GetBegin() makes the preparations
necessary.
Receive the payload
The IoT toolkit takes care of dechunking any chunked data such
that the client receives the transmitted payload cleanly and doesn’t
need to be concerned with transfer encodings. This is important
for the chunked transfer encoding: the call to IOT_HTTP_GetPayload()
deals with chunked and identity transfer encodings so the user
doesn’t need to.
IOT_HTTP_GetPayload() receives as much data as possible into the
user’s provided buffer, but may not receive all of it: the user
must be prepared to receive fewer bytes than were requested, which
is typical of many communication mechanisms. This is not an error,
it is just the way that network and chunked communication works.
All this sample does is print the received payload, so we expect
to see HTML output when the sample runs.
Payload delivery is complete when IOT_HTTP_GetPayload() returns
zero. If an error occurs during payload delivery (e.g. network
or transfer formatting errors) then IOT_HTTP_GetPayload() returns
a negative status code.
Finish payload reception
Once the payload is received correctly or with an error, a call
to IOT_HTTP_GetEnd() synchronizes the HTTP client state.
Disconnect connection
As we have no need to keep the connection to the server open
after receiving the payload, a call to IOT_HTTP_Disconnect()
tears down the connection to the server.
I/O over the network
The preceding sections have shown how the HTTP request is
set up and executed, and how the server’s response is processed.
The mechanics of network connection, I/O, and disconnection have
been deferred until now.
The HTTP client requires a set of methods that control how a network
connection is set up and torn down, and also a set of methods
that implement I/O over that connection after it is established.
The API is embodied in the IOT_IO_API type:
typedef struct {
int (*pfConnect) (void *pConn, const char *sHost, unsigned Port);
int (*pfDisconnect)(void *pConn);
int (*pfSend) (void *pConn, const void *pData, unsigned DataLen);
int (*pfRecv) (void *pConn, void *pData, unsigned DataLen);
} IOT_IO_API;
In this example the methods are implemented by functions local to the
application:
static const IOT_IO_API _PlainAPI = {
_Connect,
_Disconnect,
_Send,
_Recv
};
Here is the initialization of HTTP I/O setup again:
IOT_HTTP_SetIO(&HTTP, &_PlainAPI, &Connection);
Notice that the third argument passed into IOT_HTTP_SetIO() is
the address of a connection context structure: this address is
passed to all IOT_IO_API methods as their first parameter, pConn,
and holds a single member, the socket ID:
typedef struct {
unsigned Socket;
} CONNECTION_CONTEXT;
Connection
Connection requires that a plain socket is opened to the
server.
static int _Connect(void *pVoid, const char *sHost, unsigned Port) {
CONNECTION_CONTEXT * pConn;
int Status;
//
pConn = pVoid;
//
Status = SEGGER_SYS_IP_Open(sHost, Port);
if (Status >= 0) {
pConn->Socket = Status;
}
return Status;
}
Receive connection parameters
The implementation of the socket setup requires that a plain TCP
socket is opened to the given host and port specified by
sHost (a domain name that must be resolved) and Port
which is in host byte order.
Recover connection context
The first parameter, pVoid, is actually a pointer to the connection
context (of type CONNECTION_CONTEXT) which holds the socket ID.
This connection context was set up using IOT_HTTP_SetIO().
Open socket connection
The incoming parameters are used to set up a plain TCP socket
to the provided host and port. Errors from the socket layer are
propagated to the caller. If a socket to the server is opened
without error, the socket ID is stored to the context passed
into the method.
Send data
Sending data to an established connection is handled by recovering
the socket ID from the context and sending the data to the socket.
static int _Send(void *pVoid, const void *pData, unsigned DataLen) {
CONNECTION_CONTEXT *pConn;
//
pConn = pVoid;
return SEGGER_SYS_IP_Send(pConn->Socket, pData, DataLen, 0);
}
Receive data
Receiving data from an established connection is handled by recovering
the socket ID from the context and receiving the data from the socket.
static int _Recv(void *pVoid, void *pData, unsigned DataLen) {
CONNECTION_CONTEXT *pConn;
//
pConn = pVoid;
return SEGGER_SYS_IP_Recv(pConn->Socket, pData, DataLen, 0);
}
Disconnection
Disconnecting an established connection is handled by recovering
the socket ID from the context and closing the socket.
Disconnection always succeeds and returns a success code.
static int _Disconnect(void *pVoid) {
CONNECTION_CONTEXT *pConn;
//
pConn = pVoid;
SEGGER_SYS_IP_Close(pConn->Socket);
//
return 0;
}
Running the sample
With the sample now complete, running it produces output similar to
the following:
C:> IOT_HTTP_GetRequest.exe
Returned status code: 301
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="https://www.segger.com/">here</a>.</p>
<hr>
<address>Apache/2.4.20 Server at www.segger.com Port 80</address>
</body></html>
C:> _
This has correctly executed the HTTP request and received a response.
What we see is not an HTTP 200 “OK” status code but a 301 “moved permanently”
redirection. The next sample shows how to capture this redirection.
IOT_HTTP_GetRequest complete listing
/*********************************************************************
* SEGGER Microcontroller GmbH *
* The Embedded Experts *
**********************************************************************
* *
* (c) 2003 - 2019 SEGGER Microcontroller GmbH *
* *
* www.segger.com Support: support@segger.com *
* *
**********************************************************************
-------------------------- END-OF-HEADER -----------------------------
File : IOT_HTTP_GetRequest.c
Purpose : Issue a GET request over a plain socket.
Additional information:
Preparations:
Set up the HTTP GET request in the MainTask.
(Default is to get the index page of www.segger.com)
For more information see the IoTToolkit manual under the chapter
"2.2 A simple GET request".
Expected behavior:
Sends a custom HTTP request to a custom host and prints out the
status code and the payload on the console.
Sample output:
Returned status code: 301
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="https://www.segger.com/">here</a>.</p>
<hr>
<address>Apache/2.4.18 (Ubuntu) Server at www.segger.com Port 80</address>
</body></html>
STOP.
*/
/*********************************************************************
*
* #include Section
*
**********************************************************************
*/
#include "IOT.h"
#include "SEGGER_SYS.h"
/*********************************************************************
*
* Local types
*
**********************************************************************
*/
typedef struct {
unsigned Socket;
} CONNECTION_CONTEXT;
/*********************************************************************
*
* Prototypes
*
**********************************************************************
*/
static int _Connect (void *pVoid, const char *sHost, unsigned Port);
static int _Disconnect(void *pVoid);
static int _Send (void *pVoid, const void *pData, unsigned DataLen);
static int _Recv (void *pVoid, void *pData, unsigned DataLen);
/*********************************************************************
*
* Static const data
*
**********************************************************************
*/
static const IOT_IO_API _PlainAPI = {
_Connect,
_Disconnect,
_Send,
_Recv
};
/*********************************************************************
*
* Static code
*
**********************************************************************
*/
/*********************************************************************
*
* _Connect()
*
* Function description
* Connect to host.
*
* Parameters
* pVoid - Pointer to connection context.
* sHost - Name of server we wish to connect to.
* Port - Port number in host byte order.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _Connect(void *pVoid, const char *sHost, unsigned Port) {
CONNECTION_CONTEXT * pConn;
int Status;
//
pConn = pVoid;
//
Status = SEGGER_SYS_IP_Open(sHost, Port);
if (Status >= 0) {
pConn->Socket = Status;
}
return Status;
}
/*********************************************************************
*
* _Disconnect()
*
* Function description
* Disconnect from host.
*
* Parameters
* pVoid - Pointer to connection context.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _Disconnect(void *pVoid) {
CONNECTION_CONTEXT *pConn;
//
pConn = pVoid;
SEGGER_SYS_IP_Close(pConn->Socket);
//
return 0;
}
/*********************************************************************
*
* _Send()
*
* Function description
* Send data to host.
*
* Parameters
* pVoid - Pointer to connection context.
* pData - Pointer to octet string to send.
* DataLen - Octet length of the octet string to send.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _Send(void *pVoid, const void *pData, unsigned DataLen) {
CONNECTION_CONTEXT *pConn;
//
pConn = pVoid;
return SEGGER_SYS_IP_Send(pConn->Socket, pData, DataLen, 0);
}
/*********************************************************************
*
* _Recv()
*
* Function description
* Receive data from host.
*
* Parameters
* pVoid - Pointer to connection context.
* pData - Pointer to object that receives the data.
* DataLen - Octet length of receiving object.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _Recv(void *pVoid, void *pData, unsigned DataLen) {
CONNECTION_CONTEXT *pConn;
//
pConn = pVoid;
return SEGGER_SYS_IP_Recv(pConn->Socket, pData, DataLen, 0);
}
/*********************************************************************
*
* Public code
*
**********************************************************************
*/
/*********************************************************************
*
* MainTask()
*
* Function description
* Application entry point.
*/
void MainTask(void);
void MainTask(void) {
IOT_HTTP_CONTEXT HTTP;
IOT_HTTP_PARA aPara[20];
CONNECTION_CONTEXT Connection;
char aBuf[128];
char aPayload[128];
unsigned StatusCode;
int Status;
//
SEGGER_SYS_Init();
SEGGER_SYS_IP_Init();
//
IOT_HTTP_Init (&HTTP, &aBuf[0], sizeof(aBuf), aPara, SEGGER_COUNTOF(aPara));
IOT_HTTP_SetIO (&HTTP, &_PlainAPI, &Connection);
IOT_HTTP_SetVersion(&HTTP, &IOT_HTTP_VERSION_HTTP_1v1);
IOT_HTTP_AddMethod (&HTTP, "GET");
IOT_HTTP_AddHost (&HTTP, "www.segger.com");
IOT_HTTP_SetPort (&HTTP, 80);
IOT_HTTP_AddPath (&HTTP, "/");
//
Status = IOT_HTTP_Connect(&HTTP);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot negotiate a connection to %s:%d!\n",
IOT_HTTP_GetHost(&HTTP),
IOT_HTTP_GetPort(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
//
Status = IOT_HTTP_Exec(&HTTP);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot execute GET request!\n",
IOT_HTTP_GetMethod(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
//
Status = IOT_HTTP_ProcessStatusLine(&HTTP, &StatusCode);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot process status line!\n");
SEGGER_SYS_OS_Halt(100);
}
SEGGER_SYS_IO_Printf("Returned status code: %u\n\n", StatusCode);
//
Status = IOT_HTTP_ProcessHeaders(&HTTP, NULL);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot process headers!\n");
SEGGER_SYS_OS_Halt(100);
}
//
IOT_HTTP_GetBegin(&HTTP);
do {
Status = IOT_HTTP_GetPayload(&HTTP, &aPayload[0], sizeof(aPayload));
SEGGER_SYS_IO_Printf("%.*s", Status, aPayload);
} while (Status > 0);
IOT_HTTP_GetEnd(&HTTP);
//
IOT_HTTP_Disconnect(&HTTP);
//
SEGGER_SYS_IP_Exit();
SEGGER_SYS_Exit();
SEGGER_SYS_OS_Halt(Status);
}
/*************************** End of file ****************************/
Capturing a redirection
This section extends the previous example to intercept a redirect
request.
For a complete listing of this application, see IOT_HTTP_RedirectRequest complete listing.
Recapping the previous example’s HTML output:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="https://www.segger.com/">here</a>.</p>
<hr>
<address>Apache/2.4.20 Server at www.segger.com Port 80</address>
</body></html>
In the HTML body there is an indication of where the redirect
should end up, https://www.segger.com/. From RFC 7231 section 7.1.2, Location,
the definitive redirection URL is contained in the “Location”
header of the response. To capture that particular header requires
a callback:
static void _HeaderCallback( IOT_HTTP_CONTEXT * pContext,
const char * sKey,
const char * sValue) {
SEGGER_SYS_IO_Printf("%s: %s\n", sKey, sValue);
}
This callback prints the headers encountered in the response. Rather than
specifying a null function when calling IOT_HTTP_ProcessHeaders(), plug
in the callback:
Status = IOT_HTTP_ProcessHeaders(&HTTP, _HeaderCallback);
Now when the application runs the headers are printed:
Date: Thu, 09 Mar 2017 00:20:08 GMT
Server: Apache/2.4.20
Location: https://www.segger.com/
Content-Length: 302
Connection: close
Content-Type: text/html; charset=iso-8859-1
The only header we are interested in is Location, so we must preserve
its content. The key and value string parameters are transitory and are
only valid during execution of the callback, so it is not possible to
save the value of the pointer for later, it is essential that the string
is copied to an object with a longer lifetime, for instance a file-scope
variable:
static void _HeaderCallback( IOT_HTTP_CONTEXT * pContext,
const char * sKey,
const char * sValue) {
if (IOT_STRCASECMP(sKey, "Location") == 0) {
if (strlen(sValue)+1 < sizeof(_aRedirectURL)) {
strcpy(_aRedirectURL, sValue);
}
}
}
Parsing the redirect URL
Now that the callback captures the redirect URL, the main
application must identify the redirection and act upon it. But
there is an issue, we have a URL which isn’t directly usable
by the library.
Every HTTP URL conforms to the syntax of a generic URI without
user name and password, and this is:
scheme:[//host[:port]][/]path[?query][#fragment]
A URL in this form must be parsed into something that is useful,
and that is an HTTP request context in this case. The following
code parses a restricted subset of a full HTTP URL but still manages
to be incredibly useful.
static void _ParseURL(IOT_HTTP_CONTEXT *pHTTP, char *sText) {
char *pPos;
char *sHost;
char *sPath;
//
if (IOT_STRNCMP(sText, "https:", 6) == 0) {
IOT_HTTP_AddScheme(pHTTP, "https");
IOT_HTTP_SetPort (pHTTP, 443);
sText += 6;
} else if (IOT_STRNCMP(sText, "http:", 5) == 0) {
IOT_HTTP_AddScheme(pHTTP, "http");
IOT_HTTP_SetPort (pHTTP, 80);
sText += 5;
} else {
IOT_HTTP_AddScheme(pHTTP, "http");
IOT_HTTP_SetPort (pHTTP, 80);
}
//
if (IOT_STRNCMP(sText, "//", 2) == 0) {
sText += 2;
}
sHost = sText;
//
pPos = IOT_STRCHR(sHost, '/');
if (pPos) {
*pPos = '\0';
sPath = pPos + 1;
} else {
sPath = "";
}
//
pPos = IOT_STRCHR(sHost, ':');
if (pPos) {
*pPos = '\0';
IOT_HTTP_SetPort(pHTTP, (unsigned)strtoul(pPos+1, NULL, 0));
}
//
IOT_HTTP_AddHost(pHTTP, sHost);
IOT_HTTP_AddPath(pHTTP, "/");
IOT_HTTP_AddPath(pHTTP, sPath);
}
Determine the scheme
The scheme always appears first and this parser only recognizes
the schemes http and https. If neither of these
schemes occur at the front of the URL, it’s reasonable to
default the scheme to http.
Once the scheme is known, or defaulted, it’s added to the
HTTP request along with the scheme’s default port.
Be forgiving
Although the syntax of the URL would require the double-slash
between the scheme and the host parts, some browsers will
accept the scheme followed immediately by the host, for example
“https:www.segger.com”. It doesn’t seem unreasonable,
in this case, to make the “//” optional.
Remember where the host part starts
Once the scheme and optional // is consumed, the host
part must start. The function remembers where the host
part starts so it can be isolated during the remainder of
the parse.
One thing that is worth mentioning now is that the function
receives a char * pointer rather than a const char *
pointer because it will modify the incoming string to isolate
parts of it into individual zero-terminated fragments which
are passed into the HTTP request context as needed.
Isolate the host
Once the start of the host is identified, the code finds
its extent. The host part extends up to the first slash or,
if a slash is not found, to the end of the string.
If a slash is found, the host part is isolated by writing
a zero character into the incoming string which separates the
host from the path. But writing this zero knocks out the
leading slash of the path, so we must be careful to remember
to add it back when constructing the path in the HTTP request.
Parse the port
The previous step did not entirely isolate the host because
everything between the double slash and the first slash (or end
of string) was grabbed, including an optional port specification.
If we find a colin in the host part, it happens to be a port
specification that overrides the default port that was set by
the scheme.
Add the host and path
Now that the host and path have been found and isolated, they
can be added to the HTTP request. The path is added as two fragments,
a leading slash, because we overwrote it when isolating the host,
and the remainder.
The path could be further parsed to isolate the query and fragment
components and add them to the URL request, but this type of parsing
happens to be specific to the scheme and cannot, in general, be
written in a generic way to cover all schemes. Therefore, any query
or fragment becomes part of the path component in the HTTP request
and is “passed through” when the request is executed.
With all this done, the URL parser is ready for testing.
Testing the URL parser
Before putting the code to use it’s always interesting to try
it with a small test harness.
static void _test(const char *sData) {
IOT_HTTP_CONTEXT HTTP;
IOT_HTTP_PARA aPara[20];
char aCopy[128];
char aURL[128];
//
strcpy(aCopy, sData);
IOT_HTTP_Init(&HTTP, NULL, 0, &aPara[0], SEGGER_COUNTOF(aPara));
_ParseURL(&HTTP, aCopy);
IOT_HTTP_QueryURL(&HTTP, &aURL[0], sizeof(aURL));
SEGGER_SYS_IO_Printf("%-44s - %s\n", sData, aURL);
}
The test harness presents valid URLs which the parser should
successfully deal with:
_test("http://www.segger.com");
_test("https://www.segger.com");
_test("https://www.segger.com:4433");
_test("http://www.segger.com/index.htm");
_test("https://www.segger.com:4433/index.htm");
_test("http://www.segger.com:4433/product/emssl.htm");
_test("www.segger.com");
_test("www.segger.com:4433");
_test("www.segger.com/index.htm");
_test("www.segger.com:4433/index.htm");
_test("www.segger.com:4433/product/emssl.htm");
_test("https:www.segger.com");
Running the parser in the test harness gives us confidence
that the code is doing what’s expected:
http://www.segger.com - http://www.segger.com:80/
https://www.segger.com - https://www.segger.com:443/
https://www.segger.com:4433 - https://www.segger.com:4433/
http://www.segger.com/index.htm - http://www.segger.com:80/index.htm
https://www.segger.com:4433/index.htm - https://www.segger.com:4433/index.htm
http://www.segger.com:4433/product/emssl.htm - http://www.segger.com:4433/product/emssl.htm
www.segger.com - http://www.segger.com:80/
www.segger.com:4433 - http://www.segger.com:4433/
www.segger.com/index.htm - http://www.segger.com:80/index.htm
www.segger.com:4433/index.htm - http://www.segger.com:4433/index.htm
www.segger.com:4433/product/emssl.htm - http://www.segger.com:4433/product/emssl.htm
https:www.segger.com - https://www.segger.com:443/
Processing the redirect
Now that we know the redirect and have code that will parse the URL,
processing the redirect is no more than a loop to follow the redirect,
or a redirected redirect:
for (;;) {
Status = IOT_HTTP_Connect(&HTTP);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot negotiate a connection to %s:%d!\n",
IOT_HTTP_GetHost(&HTTP),
IOT_HTTP_GetPort(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
//
...
//
if (StatusCode == 301 || StatusCode == 302 ||
StatusCode == 303 || StatusCode == 307 ) {
//
SEGGER_SYS_IO_Printf("Redirect to %s\n\n", _aRedirectURL);
//
IOT_HTTP_Disconnect(&HTTP);
IOT_HTTP_Reset(&HTTP);
IOT_HTTP_AddMethod(&HTTP, "GET");
_ParseURL(&HTTP, _aRedirectURL);
//
if (IOT_STRCMP(IOT_HTTP_GetScheme(&HTTP), "http") != 0) {
SEGGER_SYS_IO_Printf("Cannot handle scheme %s!\n",
IOT_HTTP_GetScheme(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
} else {
break;
}
}
Detect redirect
Only specific redirects are processed; refer to
RFC 7231 section 6.4 for
details of these.
Disconnect
We abruptly disconnect the session when we find a redirect status code
and do not progress to read the payload as it’s irrelevant in this instance.
Reset the HTTP request
Resetting the HTTP request clears down the HTTP request but keeps the
network I/O API and context and the working buffer (which is also reset).
This prepares the HTTP request context to receive a new request.
Resetting the HTTP context also clears any method that has been set, so
the method is added before parsing the redirect URL.
Parse the redirect URL
The redirect URL is parsed into the request which makes it ready for
execution.
Execute the redirect URL
As this sample only supports the HTTP scheme, it’s not possible to redirect
to an https URL. In fact, anything other than the http scheme is
rejected.
Once the redirect is parsed and accepted, the code loops to connect and
retrieve the content from the redirected URL.
Running the sample
With the sample now complete, running it produces output similar to
the following:
C:> IOT_HTTP_RedirectRequest.exe
Returned status code: 301
Redirect to https://www.segger.com/
Cannot handle scheme https!
C:> _
As the redirect is to an https scheme we cannot progress further
at this point. The next sample shows how to add support for the https
scheme using secure TLS connections.
IOT_HTTP_RedirectRequest complete listing
/*********************************************************************
* SEGGER Microcontroller GmbH *
* The Embedded Experts *
**********************************************************************
* *
* (c) 2003 - 2019 SEGGER Microcontroller GmbH *
* *
* www.segger.com Support: support@segger.com *
* *
**********************************************************************
-------------------------- END-OF-HEADER -----------------------------
File : IOT_HTTP_RedirectRequest.c
Purpose : Handle HTTP redirect requests.
Additional information:
Preparations:
Set up the HTTP request in the MainTask.
(Default is to get the index page of www.segger.com)
For more information see the IoTToolkit manual under the chapter
"2.2 A simple GET request".
Expected behavior:
Sends a custom HTTP request to a custom host and prints out the
status code and the payload on the console.
If the status code 301, 302, 303 or 307 occurs, the redirect will
be followed.
As this example does not include security, https cannot be handled.
Sample output:
Returned status code: 301
Redirect to https://www.segger.com/
Cannot handle scheme https!
STOP.
*/
/*********************************************************************
*
* #include Section
*
**********************************************************************
*/
#include "IOT.h"
#include "SEGGER_SYS.h"
/*********************************************************************
*
* Local types
*
**********************************************************************
*/
typedef struct {
unsigned Socket;
} CONNECTION_CONTEXT;
/*********************************************************************
*
* Prototypes
*
**********************************************************************
*/
static int _Connect (void *pVoid, const char *sHost, unsigned Port);
static int _Disconnect(void *pVoid);
static int _Send (void *pVoid, const void *pData, unsigned DataLen);
static int _Recv (void *pVoid, void *pData, unsigned DataLen);
/*********************************************************************
*
* Static const data
*
**********************************************************************
*/
static const IOT_IO_API _PlainAPI = {
_Connect,
_Disconnect,
_Send,
_Recv
};
/*********************************************************************
*
* Static data
*
**********************************************************************
*/
static char _aRedirectURL[128];
/*********************************************************************
*
* Static code
*
**********************************************************************
*/
/*********************************************************************
*
* _Connect()
*
* Function description
* Connect to host.
*
* Parameters
* pVoid - Pointer to connection context.
* sHost - Name of server we wish to connect to.
* Port - Port number in host byte order.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _Connect(void *pVoid, const char *sHost, unsigned Port) {
CONNECTION_CONTEXT * pConn;
int Status;
//
pConn = pVoid;
//
Status = SEGGER_SYS_IP_Open(sHost, Port);
if (Status >= 0) {
pConn->Socket = Status;
}
return Status;
}
/*********************************************************************
*
* _Disconnect()
*
* Function description
* Disconnect from host.
*
* Parameters
* pVoid - Pointer to connection context.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _Disconnect(void *pVoid) {
CONNECTION_CONTEXT * pConn;
//
pConn = pVoid;
SEGGER_SYS_IP_Close(pConn->Socket);
//
return 0;
}
/*********************************************************************
*
* _Send()
*
* Function description
* Send data to host.
*
* Parameters
* pVoid - Pointer to connection context.
* pData - Pointer to octet string to send.
* DataLen - Octet length of the octet string to send.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _Send(void *pVoid, const void *pData, unsigned DataLen) {
CONNECTION_CONTEXT * pConn;
//
pConn = pVoid;
return SEGGER_SYS_IP_Send(pConn->Socket, pData, DataLen, 0);
}
/*********************************************************************
*
* _Recv()
*
* Function description
* Receive data from host.
*
* Parameters
* pVoid - Pointer to connection context.
* pData - Pointer to object that receives the data.
* DataLen - Octet length of receiving object.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _Recv(void *pVoid, void *pData, unsigned DataLen) {
CONNECTION_CONTEXT * pConn;
//
pConn = pVoid;
return SEGGER_SYS_IP_Recv(pConn->Socket, pData, DataLen, 0);
}
/*********************************************************************
*
* _HeaderCallback()
*
* Function description
* Process response header.
*
* Parameters
* pContext - Pointer to HTTP request context.
* sKey - Pointer to key string.
* sValue - Pointer to value string.
*/
static void _HeaderCallback( IOT_HTTP_CONTEXT * pContext,
const char * sKey,
const char * sValue) {
SEGGER_USE_PARA(pContext);
if (IOT_STRCASECMP(sKey, "Location") == 0) {
if (strlen(sValue)+1 < sizeof(_aRedirectURL)) {
strcpy(_aRedirectURL, sValue);
}
}
}
/*********************************************************************
*
* _ParseURL()
*
* Function description
* Parse a URL for the HTTP(S) scheme.
*
* Parameters
* pContext - Pointer to HTTP request context.
* sText - Pointer to zero-terminated URL.
*/
static void _ParseURL(IOT_HTTP_CONTEXT *pContext, char *sText) {
char *pPos;
char *sHost;
char *sPath;
//
if (IOT_STRNCMP(sText, "https:", 6) == 0) {
IOT_HTTP_AddScheme(pContext, "https");
IOT_HTTP_SetPort (pContext, 443);
sText += 6;
} else if (IOT_STRNCMP(sText, "http:", 5) == 0) {
IOT_HTTP_AddScheme(pContext, "http");
IOT_HTTP_SetPort (pContext, 80);
sText += 5;
} else {
IOT_HTTP_AddScheme(pContext, "http");
IOT_HTTP_SetPort (pContext, 80);
}
//
if (IOT_STRNCMP(sText, "//", 2) == 0) {
sText += 2;
}
sHost = sText;
//
pPos = IOT_STRCHR(sHost, '/');
if (pPos) {
*pPos = '\0';
sPath = pPos + 1;
} else {
sPath = "";
}
//
pPos = IOT_STRCHR(sHost, ':');
if (pPos) {
*pPos = '\0';
IOT_HTTP_SetPort(pContext, (unsigned)strtoul(pPos+1, NULL, 0));
}
//
IOT_HTTP_AddHost(pContext, sHost);
IOT_HTTP_AddPath(pContext, "/");
IOT_HTTP_AddPath(pContext, sPath);
}
/*********************************************************************
*
* Public code
*
**********************************************************************
*/
/*********************************************************************
*
* MainTask()
*
* Function description
* Application entry point.
*/
void MainTask(void);
void MainTask(void) {
IOT_HTTP_CONTEXT HTTP;
IOT_HTTP_PARA aPara[20];
CONNECTION_CONTEXT Connection;
char aBuf[128];
char aPayload[128];
unsigned StatusCode;
int Status;
//
SEGGER_SYS_Init();
SEGGER_SYS_IP_Init();
//
IOT_HTTP_Init (&HTTP, &aBuf[0], sizeof(aBuf), aPara, SEGGER_COUNTOF(aPara));
IOT_HTTP_SetIO (&HTTP, &_PlainAPI, &Connection);
IOT_HTTP_SetVersion(&HTTP, &IOT_HTTP_VERSION_HTTP_1v1);
IOT_HTTP_AddMethod (&HTTP, "GET");
IOT_HTTP_AddHost (&HTTP, "www.segger.com");
IOT_HTTP_SetPort (&HTTP, 80);
IOT_HTTP_AddPath (&HTTP, "/");
//
for (;;) {
Status = IOT_HTTP_Connect(&HTTP);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot negotiate a connection to %s:%d!\n",
IOT_HTTP_GetHost(&HTTP),
IOT_HTTP_GetPort(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
//
Status = IOT_HTTP_Exec(&HTTP);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot execute GET request!\n",
IOT_HTTP_GetMethod(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
//
Status = IOT_HTTP_ProcessStatusLine(&HTTP, &StatusCode);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot process status line!\n");
SEGGER_SYS_OS_Halt(100);
}
SEGGER_SYS_IO_Printf("Returned status code: %u\n\n", StatusCode);
//
Status = IOT_HTTP_ProcessHeaders(&HTTP, _HeaderCallback);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot process headers!\n");
SEGGER_SYS_OS_Halt(100);
}
//
if (StatusCode == 301 || StatusCode == 302 ||
StatusCode == 303 || StatusCode == 307 ) {
//
SEGGER_SYS_IO_Printf("Redirect to %s\n\n", _aRedirectURL);
//
IOT_HTTP_Disconnect(&HTTP);
IOT_HTTP_Reset(&HTTP);
IOT_HTTP_AddMethod(&HTTP, "GET");
_ParseURL(&HTTP, _aRedirectURL);
//
if (IOT_STRCMP(IOT_HTTP_GetScheme(&HTTP), "http") != 0) {
SEGGER_SYS_IO_Printf("Cannot handle scheme %s!\n",
IOT_HTTP_GetScheme(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
} else {
break;
}
}
//
IOT_HTTP_GetBegin(&HTTP);
do {
Status = IOT_HTTP_GetPayload(&HTTP, &aPayload[0], sizeof(aPayload));
SEGGER_SYS_IO_Printf("%.*s", Status, aPayload);
} while (Status > 0);
IOT_HTTP_GetEnd(&HTTP);
//
IOT_HTTP_Disconnect(&HTTP);
//
SEGGER_SYS_IP_Exit();
SEGGER_SYS_Exit();
SEGGER_SYS_OS_Halt(Status);
}
/*************************** End of file ****************************/
Secure communication with SSL
This section extends the previous example to support secure connections
for the https scheme.
For a complete listing of this application, see IOT_HTTP_SecureGet complete listing.
Extending for SSL is easy!
Adding SSL (or to use it’s proper name, TLS) is indeed straightforward.
This sample will use SEGGER’s emSSL because it’s designed to be small,
efficient, and have a clean, elegant API that simply works. However,
porting this example to other SSL stacks should not be challenging, it
really depends upon the complexity of the particular SSL stack, nothing
more.
Previous examples have only ever needed an integer to hold the socket
ID of the connection, but SSL sockets are more complex and hold more
state so the connection context must be extended for that:
typedef struct {
unsigned Socket;
SSL_SESSION Session;
} CONNECTION_CONTEXT;
The connection context uses the Socket member to hold the underlying
socket ID associated with the connection and a new Session member to
hold the SSL session context for secure sockets. The type SSL_SESSION
is defined by the emSSL product and contains everything required to set
up, manage, and tear down a secure connection over TLS.
Recognize the HTTPS scheme
The code that deals with the scheme is extended to recognize https:
if (IOT_STRCMP(IOT_HTTP_GetScheme(&HTTP), "http") == 0) {
IOT_HTTP_SetIO(&HTTP, &_PlainIO, &Connection);
} else if (IOT_STRCMP(IOT_HTTP_GetScheme(&HTTP), "https") == 0) {
IOT_HTTP_SetIO(&HTTP, &_SecureIO, &Connection);
} else {
SEGGER_SYS_IO_Printf("Cannot handle scheme %s!\n",
IOT_HTTP_GetScheme(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
There are now two distinct APIs for network I/O: the existing _PlainIO
for I/O over plain sockets and the new _SecureIO for I/O over secure
sockets. This requires addition of a new set of I/O methods along
with their dispatcher.
The secure I/O implementation
This is very much like the plain socket I/O implementation. First, the
secure implementation of the I/O API, embodied by the IOT_IO_API type,
is defined:
static const IOT_IO_API _SecureIO = {
_SecureConnect,
_SecureDisconnect,
_SecureSend,
_SecureRecv
};
Now the implementation. This follows the plain socket I/O API and the
overall structure should be very familiar. The intention here is
to show only how the connection is managed by emSSL, not how to
configure the emSSL product for correct operation — for that,
please refer to the emSSL manual which contains details of how to
select what’s necessary.
Connection
Connection requires that a plain socket is opened to the
server and that socket be upgraded to secure using an SSL
handshake.
static int _SecureConnect(void *pVoid, const char *sHost, unsigned Port) {
CONNECTION_CONTEXT * pConn;
int Status;
//
pConn = pVoid;
Status = SEGGER_SYS_IP_Open(sHost, Port);
if (Status >= 0) {
pConn->Socket = Status;
SSL_SESSION_Prepare(&pConn->Session, pConn->Socket, &_IP_Transport);
Status = SSL_SESSION_Connect(&pConn->Session, sHost);
if (Status < 0) {
SEGGER_SYS_IP_Close(pConn->Socket);
}
}
return Status;
}
Receive connection parameters
As with the plain socket implementation, the secure socket receives
the same set of parameters: a host and port specified by sHost
and Port.
Recover connection context
The first parameter, pVoid, is still a pointer to the connection
context (of type CONNECTION_CONTEXT) but this extended context
holds both the socket ID and SSL session.
Open plain socket connection
The incoming parameters are used to set up a plain TCP socket
to the provided host and port. Errors from the socket layer are
propagated to the caller. If a socket to the server is opened
without error, the socket is ready to be upgraded to secure.
Prepare the SSL session
After storing the ID in the context, the SSL session is initialized
by calling SSL_SESSION_Prepare() with the session context, the
plain socket to communicate over, and an API for doing so. The
SSL transport API is described in the emSSL manual, but a synopsis
is that it sends and receives data using the regular BSD socket API.
Upgrade to secure
With the session prepared, a call to SSL_SESSION_Connect()
attempts an SSL handshake to negotiate a secure connection.
If this fails (there can be many reasons for this), the socket
is closed.
The success or failure of the connection is propagated to the
caller for it to deal with.
Send data
Sending data to an established SSL connection is no more complex than
sending over a plain socket:
static int _SecureSend(void *pVoid, const void *pData, unsigned DataLen) {
CONNECTION_CONTEXT * pConn;
//
pConn = pVoid;
return SSL_SESSION_Send(&pConn->Session, pData, DataLen);
}
Receive data
Receiving data from an established SSL connection is no more complex than
receiving over a plain socket:
static int _SecureRecv(void *pVoid, void *pData, unsigned DataLen) {
CONNECTION_CONTEXT * pConn;
//
pConn = pVoid;
return SSL_SESSION_Receive(&pConn->Session, pData, DataLen);
}
Disconnection
Disconnecting an established SSL connection is simple: the SSL session
is disconnected and then the socket is closed.
static int _SecureDisconnect(void *pVoid) {
CONNECTION_CONTEXT * pConn;
//
pConn = pVoid;
SSL_SESSION_Disconnect(&pConn->Session);
SEGGER_SYS_IP_Close(pConn->Socket);
//
return 0;
}
Odds and ends
The final piece of housekeeping to make secure connections work
is to ensure that the SSL component is initialized and finalized
using SSL_Init() and SSL_Exit() as part of the main application:
void MainTask(void) {
//
SEGGER_SYS_Init();
SEGGER_SYS_IP_Init();
SSL_Init();
//
...
//
SSL_Exit();
SEGGER_SYS_IP_Exit();
SEGGER_SYS_Exit();
SEGGER_SYS_OS_Halt(Status);
}
Running the sample
With the sample now complete, running it produces output similar to
the following:
C:> IOT_HTTP_SecureGet.exe
Returned status code: 301
Redirect to https://www.segger.com/
Returned status code: 200
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org
/TR/html4/loose.dtd"><html lang="en-US"><head><meta http-equiv="Content-Type" con
tent="text/html;"><!--<meta name="language" content="english">--><!--<meta http-e
...
<!--<script type="text/javascript" src="./js/footer.js" language="javascript"></s
cript>--><script type="text/javascript" src="./js/prettify.js"></script><script t
ype="text/javascript">prettyPrint();</script></body></html>
C:> _
This concludes the sample application which demonstrates how easy it
is to enable the HTTP client to use SSL-secured connections.
IOT_HTTP_SecureGet complete listing
/*********************************************************************
* (c) SEGGER Microcontroller GmbH *
* The Embedded Experts *
* www.segger.com *
**********************************************************************
-------------------------- END-OF-HEADER -----------------------------
File : IOT_HTTP_SecureGet.c
Purpose : REST API access using plain sockets and TLS.
*/
/*********************************************************************
*
* #include Section
*
**********************************************************************
*/
#include "IOT.h"
#include "SSL.h"
#include "SEGGER_SYS.h"
/*********************************************************************
*
* Prototypes
*
**********************************************************************
*/
static int _PlainConnect (void *pVoid, const char *sHost, unsigned Port);
static int _PlainDisconnect (void *pVoid);
static int _PlainSend (void *pVoid, const void *pData, unsigned DataLen);
static int _PlainRecv (void *pVoid, void *pData, unsigned DataLen);
//
static int _SecureConnect (void *pVoid, const char *sHost, unsigned Port);
static int _SecureDisconnect(void *pVoid);
static int _SecureSend (void *pVoid, const void *pData, unsigned DataLen);
static int _SecureRecv (void *pVoid, void *pData, unsigned DataLen);
/*********************************************************************
*
* Local types
*
**********************************************************************
*/
typedef struct {
unsigned Socket;
SSL_SESSION Session;
} CONNECTION_CONTEXT;
/*********************************************************************
*
* Static const data
*
**********************************************************************
*/
static const IOT_IO_API _PlainIO = {
_PlainConnect,
_PlainDisconnect,
_PlainSend,
_PlainRecv
};
static const IOT_IO_API _SecureIO = {
_SecureConnect,
_SecureDisconnect,
_SecureSend,
_SecureRecv
};
static const SSL_TRANSPORT_API _IP_Transport = {
SEGGER_SYS_IP_Send,
SEGGER_SYS_IP_Recv,
NULL
};
/*********************************************************************
*
* Static data
*
**********************************************************************
*/
static char _aRedirectURL[128];
/*********************************************************************
*
* Static code
*
**********************************************************************
*/
/*********************************************************************
*
* _PlainConnect()
*
* Function description
* Connect to host using secure sockets.
*
* Parameters
* pVoid - Pointer to connection context.
* sHost - Name of server we wish to connect to.
* Port - Port number in host byte order.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _PlainConnect(void *pVoid, const char *sHost, unsigned Port) {
int * pSocket;
int Status;
//
Status = SEGGER_SYS_IP_Open(sHost, Port);
if (Status >= 0) {
pSocket = pVoid;
*pSocket = Status;
}
return Status;
}
/*********************************************************************
*
* _PlainDisconnect()
*
* Function description
* Disconnect from host.
*
* Parameters
* pVoid - Pointer to connection context.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _PlainDisconnect(void *pVoid) {
CONNECTION_CONTEXT * pConn;
//
pConn = pVoid;
SEGGER_SYS_IP_Close(pConn->Socket);
//
return 0;
}
/*********************************************************************
*
* _PlainSend()
*
* Function description
* Send data to host.
*
* Parameters
* pVoid - Pointer to connection context.
* pData - Pointer to octet string to send.
* DataLen - Octet length of the octet string to send.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _PlainSend(void *pVoid, const void *pData, unsigned DataLen) {
CONNECTION_CONTEXT * pConn;
//
pConn = pVoid;
return SEGGER_SYS_IP_Send(pConn->Socket, pData, DataLen, 0);
}
/*********************************************************************
*
* _PlainRecv()
*
* Function description
* Receive data from host.
*
* Parameters
* pVoid - Pointer to connection context.
* pData - Pointer to object that receives the data.
* DataLen - Octet length of receiving object.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _PlainRecv(void *pVoid, void *pData, unsigned DataLen) {
CONNECTION_CONTEXT * pConn;
//
pConn = pVoid;
return SEGGER_SYS_IP_Recv(pConn->Socket, pData, DataLen, 0);
}
/*********************************************************************
*
* _SecureConnect()
*
* Function description
* Connect to host, secure.
*
* Parameters
* pVoid - Pointer to connection context.
* sHost - Name of server we wish to connect to.
* Port - Port number in host byte order.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _SecureConnect(void *pVoid, const char *sHost, unsigned Port) {
CONNECTION_CONTEXT * pConn;
int Status;
//
pConn = pVoid;
Status = SEGGER_SYS_IP_Open(sHost, Port);
if (Status >= 0) {
pConn->Socket = Status;
SSL_SESSION_Prepare(&pConn->Session, pConn->Socket, &_IP_Transport);
Status = SSL_SESSION_Connect(&pConn->Session, sHost);
if (Status < 0) {
SEGGER_SYS_IP_Close(pConn->Socket);
}
}
return Status;
}
/*********************************************************************
*
* _SecureDisconnect()
*
* Function description
* Disconnect from host, secure.
*
* Parameters
* pVoid - Pointer to connection context.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _SecureDisconnect(void *pVoid) {
CONNECTION_CONTEXT * pConn;
//
pConn = pVoid;
SSL_SESSION_Disconnect(&pConn->Session);
SEGGER_SYS_IP_Close(pConn->Socket);
//
return 0;
}
/*********************************************************************
*
* _SecureSend()
*
* Function description
* Send data to host, secure.
*
* Parameters
* pVoid - Pointer to connection context.
* pData - Pointer to octet string to send over SSL.
* DataLen - Octet length of the octet string to send.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _SecureSend(void *pVoid, const void *pData, unsigned DataLen) {
CONNECTION_CONTEXT * pConn;
//
pConn = pVoid;
return SSL_SESSION_Send(&pConn->Session, pData, DataLen);
}
/*********************************************************************
*
* _SecureRecv()
*
* Function description
* Receive data from host, secure.
*
* Parameters
* pVoid - Pointer to connection context.
* pData - Pointer to object that receives the data over SSL.
* DataLen - Octet length of receiving object.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _SecureRecv(void *pVoid, void *pData, unsigned DataLen) {
CONNECTION_CONTEXT * pConn;
//
pConn = pVoid;
return SSL_SESSION_Receive(&pConn->Session, pData, DataLen);
}
/*********************************************************************
*
* _HeaderCallback()
*
* Function description
* Process response header.
*
* Parameters
* pContext - Pointer to HTTP request context.
* sKey - Pointer to key string.
* sValue - Pointer to value string.
*/
static void _HeaderCallback( IOT_HTTP_CONTEXT * pContext,
const char * sKey,
const char * sValue) {
SEGGER_USE_PARA(pContext);
if (IOT_STRCASECMP(sKey, "Location") == 0) {
if (strlen(sValue)+1 < sizeof(_aRedirectURL)) {
strcpy(_aRedirectURL, sValue);
}
}
}
/*********************************************************************
*
* _ParseURL()
*
* Function description
* Parse a URL for the HTTP(S) scheme.
*
* Parameters
* pContext - Pointer to HTTP request context.
* sText - Pointer to zero-terminated URL.
*/
static void _ParseURL(IOT_HTTP_CONTEXT *pContext, char *sText) {
char *pPos;
char *sHost;
char *sPath;
//
if (IOT_STRNCMP(sText, "https:", 6) == 0) {
IOT_HTTP_AddScheme(pContext, "https");
IOT_HTTP_SetPort (pContext, 443);
sText += 6;
} else if (IOT_STRNCMP(sText, "http:", 5) == 0) {
IOT_HTTP_AddScheme(pContext, "http");
IOT_HTTP_SetPort (pContext, 80);
sText += 5;
} else {
IOT_HTTP_AddScheme(pContext, "http");
IOT_HTTP_SetPort (pContext, 80);
}
//
if (IOT_STRNCMP(sText, "//", 2) == 0) {
sText += 2;
}
sHost = sText;
//
pPos = IOT_STRCHR(sHost, '/');
if (pPos) {
*pPos = '\0';
sPath = pPos + 1;
} else {
sPath = "";
}
//
pPos = IOT_STRCHR(sHost, ':');
if (pPos) {
*pPos = '\0';
IOT_HTTP_SetPort(pContext, (unsigned)strtoul(pPos+1, NULL, 0));
}
//
IOT_HTTP_AddHost(pContext, sHost);
IOT_HTTP_AddPath(pContext, "/");
IOT_HTTP_AddPath(pContext, sPath);
}
/*********************************************************************
*
* Public code
*
**********************************************************************
*/
/*********************************************************************
*
* MainTask()
*
* Function description
* Application entry point.
*/
void MainTask(void);
void MainTask(void) {
IOT_HTTP_CONTEXT HTTP;
IOT_HTTP_PARA aPara[20];
CONNECTION_CONTEXT Connection;
char aBuf[128];
char aPayload[128];
unsigned StatusCode;
int Status;
//
SEGGER_SYS_Init();
SEGGER_SYS_IP_Init();
SSL_Init();
//
IOT_HTTP_Init (&HTTP, &aBuf[0], sizeof(aBuf), aPara, SEGGER_COUNTOF(aPara));
IOT_HTTP_SetIO (&HTTP, &_PlainIO, &Connection);
IOT_HTTP_SetVersion(&HTTP, &IOT_HTTP_VERSION_HTTP_1v1);
IOT_HTTP_AddMethod (&HTTP, "GET");
IOT_HTTP_AddHost (&HTTP, "www.segger.com");
IOT_HTTP_SetPort (&HTTP, 80);
IOT_HTTP_AddPath (&HTTP, "/");
//
for (;;) {
Status = IOT_HTTP_Connect(&HTTP);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot negotiate a connection to %s:%d!\n",
IOT_HTTP_GetHost(&HTTP),
IOT_HTTP_GetPort(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
//
Status = IOT_HTTP_Exec(&HTTP);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot execute GET request!\n",
IOT_HTTP_GetMethod(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
//
Status = IOT_HTTP_ProcessStatusLine(&HTTP, &StatusCode);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot process status line!\n");
SEGGER_SYS_OS_Halt(100);
}
SEGGER_SYS_IO_Printf("Returned status code: %u\n\n", StatusCode);
//
Status = IOT_HTTP_ProcessHeaders(&HTTP, _HeaderCallback);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot process headers!\n");
SEGGER_SYS_OS_Halt(100);
}
//
if (StatusCode == 301 || StatusCode == 302 ||
StatusCode == 303 || StatusCode == 307 ) {
//
SEGGER_SYS_IO_Printf("Redirect to %s\n\n", _aRedirectURL);
//
IOT_HTTP_Disconnect(&HTTP);
IOT_HTTP_Reset(&HTTP);
IOT_HTTP_AddMethod(&HTTP, "GET");
_ParseURL(&HTTP, _aRedirectURL);
//
if (IOT_STRCMP(IOT_HTTP_GetScheme(&HTTP), "http") == 0) {
IOT_HTTP_SetIO(&HTTP, &_PlainIO, &Connection);
} else if (IOT_STRCMP(IOT_HTTP_GetScheme(&HTTP), "https") == 0) {
IOT_HTTP_SetIO(&HTTP, &_SecureIO, &Connection);
} else {
SEGGER_SYS_IO_Printf("Cannot handle scheme %s!\n",
IOT_HTTP_GetScheme(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
} else {
break;
}
}
//
IOT_HTTP_GetBegin(&HTTP);
do {
Status = IOT_HTTP_GetPayload(&HTTP, &aPayload[0], sizeof(aPayload));
SEGGER_SYS_IO_Printf("%.*s", Status, aPayload);
} while (Status > 0);
IOT_HTTP_GetEnd(&HTTP);
//
IOT_HTTP_Disconnect(&HTTP);
//
SSL_Exit();
SEGGER_SYS_IP_Exit();
SEGGER_SYS_Exit();
SEGGER_SYS_OS_PauseBeforeHalt();
SEGGER_SYS_OS_Halt(0);
}
/*************************** End of file ****************************/
Digest authentication sample
This example shows how to execute a request to a protected resource.
For a complete listing of this application, see IOT_HTTP_AuthGetRequest complete listing.
When a resource is protected and requires authorization to access it,
the HTTP response code is set to 401 and a WWW-Authenticate header
provides authorization parameters for a retry.
For instance, trying to issue a GET to http://apidemo.segger.com/digesttest/conf/index.htm
results in the following response:
HTTP/1.1 401 Unauthorized
Date: Thu, 28 Feb 2019 15:57:22 GMT
Server: Apache/2.4.25 (Debian)
WWW-Authenticate: Digest realm="secret",
nonce="t4eJWPaCBQA=1065f8b7258a189f0f3c57896880252227324b6f",
algorithm=MD5,
domain="/var/www/com.segger.apidemo/digesttest/conf",
qop="auth"
Content-Length: 465
Content-Type: text/html; charset=iso-8859-1
In this case, digest access authentication is required as indicated by Digest
appearing in the WWW-Authenticate header.
The additional parameters are required to provide resistance tp
replay attacks and also provide some message integrity.
In order to execute the GET correctly, you must set the authorization
on a HTTP request. The HTTP client is able to correctly parse the
digest access authentication parameters and issue a correct response.
First, the parameters in the response header must be initialized
and parsed. This is simply done:
IOT_HTTP_AUTH_DIGEST_InitParas(&AuthParas);
Status = IOT_HTTP_AUTH_DIGEST_ParseParas(aAuthHeader+7, &AuthParas);
The value returned by IOT_HTTP_AUTH_DIGEST_ParseParas() indicates
whether the header could be parsed correctly. If it cannot be parsed,
then it is impossible to continue.
Setting the authorization scheme
Once the header is parsed, the correct scheme must be set up
according to the digest algorithm and the quality of protection.
The HTTP client supports MD5, SHA-256, and SHA-512/256 (two variants)
for the digest algorithm and “auth” as the quality of protection:
static int _ParseQop(IOT_HTTP_CONTEXT * pHTTP,
IOT_HTTP_AUTH_DIGEST_PARAS * pAuthParas) {
char * pQopAtom;
int CanRetry;
//
CanRetry = 0;
for (pQopAtom = strtok(pAuthParas->aQop, ", ");
pQopAtom != NULL;
pQopAtom = strtok(pAuthParas->aQop, ", ")) {
if (strcmp(pQopAtom, "auth") == 0) {
if (strcmp(pAuthParas->aAlgorithm, "MD5") == 0) {
CanRetry = 1;
strcpy(pAuthParas->aQop, "auth");
IOT_HTTP_SetAuth(pHTTP, IOT_HTTP_AUTH_DIGEST_MD5_WrHeader, pAuthParas);
break;
} else if (strcmp(pAuthParas->aAlgorithm, "SHA-256") == 0) {
CanRetry = 1;
strcpy(pAuthParas->aQop, "auth");
IOT_HTTP_SetAuth(pHTTP, IOT_HTTP_AUTH_DIGEST_SHA256_WrHeader, pAuthParas);
break;
} else if (strcmp(pAuthParas->aAlgorithm, "SHA-512-256") == 0) {
CanRetry = 1;
strcpy(pAuthParas->aQop, "auth");
IOT_HTTP_SetAuth(pHTTP, IOT_HTTP_AUTH_DIGEST_SHA512_256_WrHeader, pAuthParas);
break;
}
}
}
//
return CanRetry ? 0 : -1;
}
You can restrict the digest algorithms you require by tailoring the above code
to your application.
Setting user name, password, and client nonce
Once the authorization scheme is set up, you can set the user name and
password:
strcpy(AuthParas.aUserName, "anybody");
strcpy(AuthParas.aPassword, "segger");
In addition you must set a client number-used-once token (nonce) that
is used for message integrity and replay attack resistance. In this
example, we get some random data and convert it to ASCII digits:
static void _ChooseNewNonce(IOT_HTTP_AUTH_DIGEST_PARAS *pAuthParas) {
unsigned i;
//
memset(pAuthParas->aCnonce, 0, sizeof(pAuthParas->aCnonce));
CRYPTO_RNG_Get(pAuthParas->aCnonce, sizeof(pAuthParas->aCnonce)-1);
for (i = 0; i < sizeof(pAuthParas->aCnonce) - 1; ++i) {
pAuthParas->aCnonce[i] &= 0x07;
pAuthParas->aCnonce[i] |= 0x30;
}
}
Executing the authorized request
Once all this is set, you execute the GET request with authorization
using IOT_HTTP_ExecWithAuth(). With correct authorization computed,
the protected resource is returned with a 200 response:
GET /digesttest/conf/index.htm HTTP/1.1
Host: apidemo.segger.com
Authorization: Digest
algorithm=MD5,
username="anybody",
realm="secret",
uri="/digesttest/conf/index.htm",
nonce="4cVWmPaCBQA=98c9e49455d216052259bbdd48a091b299314cb5",
nc=00000001,
cnonce="5230745571201104766376517713011",
qop=auth,
response="5833f094697c2d6591d8d3580aab69a0"
HTTP/1.1 200 OK
Date: Thu, 28 Feb 2019 16:15:13 GMT
Server: Apache/2.4.25 (Debian)
Authentication-Info:
rspauth="8cf7dccf9e16e06057d47e4b663241c8",
cnonce="5230745571201104766376517713011",
nc=00000001,
qop=auth
Last-Modified: Thu, 21 Feb 2019 12:29:52 GMT
ETag: "52-58266a19185ba"
Accept-Ranges: bytes
Content-Length: 82
Vary: Accept-Encoding
Content-Type: text/html
<!DOCTYPE html>
<html>
<body>
<h1>Digest Conf (protected)</h1>
</body>
</html>
Please refer to the application example for a practical demonstration
of digest authentication with a protected resource hosted by SEGGER.
IOT_HTTP_AuthGetRequest complete listing
/*********************************************************************
* SEGGER Microcontroller GmbH *
* The Embedded Experts *
**********************************************************************
* *
* (c) 2003 - 2019 SEGGER Microcontroller GmbH *
* *
* www.segger.com Support: support@segger.com *
* *
**********************************************************************
-------------------------- END-OF-HEADER -----------------------------
File : IOT_HTTP_AuthGetRequest.c
Purpose : Issue a GET request, to include authentication,
over a plain socket.
Additional information:
Preparations:
Set up a server with a protected URI that requires Digest
Authentication (RFC 7616). A standard GET request is issued and,
if authentication is required, the application follows up by
answering the challenge with an authenticated GET request that
encodes the response using the registered user credentials.
*/
/*********************************************************************
*
* #include Section
*
**********************************************************************
*/
#include "IOT.h"
#include "CRYPTO.h"
#include "SEGGER.h"
#include "SEGGER_SYS.h"
/*********************************************************************
*
* Local types
*
**********************************************************************
*/
typedef struct {
unsigned Socket;
} CONNECTION_CONTEXT;
/*********************************************************************
*
* Prototypes
*
**********************************************************************
*/
static int _Connect (void *pVoid, const char *sHost, unsigned Port);
static int _Disconnect(void *pVoid);
static int _Send (void *pVoid, const void *pData, unsigned DataLen);
static int _Recv (void *pVoid, void *pData, unsigned DataLen);
/*********************************************************************
*
* Static const data
*
**********************************************************************
*/
static const IOT_IO_API _PlainAPI = {
_Connect,
_Disconnect,
_Send,
_Recv
};
/*********************************************************************
*
* Static code
*
**********************************************************************
*/
/*********************************************************************
*
* _Connect()
*
* Function description
* Connect to host.
*
* Parameters
* pVoid - Pointer to connection context.
* sHost - Name of server we wish to connect to.
* Port - Port number in host byte order.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _Connect(void *pVoid, const char *sHost, unsigned Port) {
CONNECTION_CONTEXT * pConn;
int Status;
//
pConn = pVoid;
//
Status = SEGGER_SYS_IP_Open(sHost, Port);
if (Status >= 0) {
pConn->Socket = Status;
}
return Status;
}
/*********************************************************************
*
* _Disconnect()
*
* Function description
* Disconnect from host.
*
* Parameters
* pVoid - Pointer to connection context.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _Disconnect(void *pVoid) {
CONNECTION_CONTEXT *pConn;
//
pConn = pVoid;
SEGGER_SYS_IP_Close(pConn->Socket);
//
return 0;
}
/*********************************************************************
*
* _Send()
*
* Function description
* Send data to host.
*
* Parameters
* pVoid - Pointer to connection context.
* pData - Pointer to octet string to send.
* DataLen - Octet length of the octet string to send.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _Send(void *pVoid, const void *pData, unsigned DataLen) {
CONNECTION_CONTEXT *pConn;
//
pConn = pVoid;
printf("%.*s", DataLen, (const char *)pData);
return SEGGER_SYS_IP_Send(pConn->Socket, pData, DataLen, 0);
}
/*********************************************************************
*
* _Recv()
*
* Function description
* Receive data from host.
*
* Parameters
* pVoid - Pointer to connection context.
* pData - Pointer to object that receives the data.
* DataLen - Octet length of receiving object.
*
* Return value
* >= 0 - Success.
* < 0 - Processing error.
*/
static int _Recv(void *pVoid, void *pData, unsigned DataLen) {
CONNECTION_CONTEXT * pConn;
int Status;
//
pConn = pVoid;
Status = SEGGER_SYS_IP_Recv(pConn->Socket, pData, DataLen, 0);
if (Status > 0) {
printf("%.*s", Status, (const char *)pData);
}
return Status;
}
/*********************************************************************
*
* _RememberAuthenticate()
*
* Function description
* Note any received authentication header.
*
* Parameters
* pContext - Pointer to HTTP context.
* sKey - Pointer to header key.
* sValue - Pointer to header data.
* pUserCtx - Pointer to user-provided context.
*/
static void _RememberAuthenticate( IOT_HTTP_CONTEXT * pContext,
const char * sKey,
const char * sValue,
void * pUserCtx) {
if (SEGGER_strcasecmp(sKey, "WWW-Authenticate") == 0) {
strcpy(SEGGER_PTR2PTR(char, pUserCtx), sValue);
}
}
/*********************************************************************
*
* _ParseQop()
*
* Function description
* Parse and set up quality of protection.
*
* Parameters
* pHTTP - Pointer to HTTP context.
* pAuthParas - Pointer to authentication parameters.
*
* Return value
* >= 0 - Quality of protection supported and set up.
* < 0 - Quality of protection not supported.
*/
static int _ParseQop(IOT_HTTP_CONTEXT * pHTTP,
IOT_HTTP_AUTH_DIGEST_PARAS * pAuthParas) {
char * pQopAtom;
int CanRetry;
//
CanRetry = 0;
for (pQopAtom = strtok(pAuthParas->aQop, ", ");
pQopAtom != NULL;
pQopAtom = strtok(pAuthParas->aQop, ", ")) {
if (strcmp(pQopAtom, "auth") == 0) {
if (strcmp(pAuthParas->aAlgorithm, "MD5") == 0) {
CanRetry = 1;
strcpy(pAuthParas->aQop, "auth");
IOT_HTTP_SetAuth(pHTTP, IOT_HTTP_AUTH_DIGEST_MD5_WrHeader, pAuthParas);
break;
} else if (strcmp(pAuthParas->aAlgorithm, "SHA-256") == 0) {
CanRetry = 1;
strcpy(pAuthParas->aQop, "auth");
IOT_HTTP_SetAuth(pHTTP, IOT_HTTP_AUTH_DIGEST_SHA256_WrHeader, pAuthParas);
break;
} else if (strcmp(pAuthParas->aAlgorithm, "SHA-512-256") == 0) {
CanRetry = 1;
strcpy(pAuthParas->aQop, "auth");
IOT_HTTP_SetAuth(pHTTP, IOT_HTTP_AUTH_DIGEST_SHA512_256_WrHeader, pAuthParas);
break;
}
}
}
//
return CanRetry ? 0 : -1;
}
/*********************************************************************
*
* _ChooseNewNonce()
*
* Function description
* Create a new client nonce.
*
* Parameters
* pAuthParas - Pointer to authentication parameters.
*/
static void _ChooseNewNonce(IOT_HTTP_AUTH_DIGEST_PARAS *pAuthParas) {
unsigned i;
//
memset(pAuthParas->aCnonce, 0, sizeof(pAuthParas->aCnonce));
CRYPTO_RNG_Get(pAuthParas->aCnonce, sizeof(pAuthParas->aCnonce)-1);
for (i = 0; i < sizeof(pAuthParas->aCnonce) - 1; ++i) {
pAuthParas->aCnonce[i] &= 0x07;
pAuthParas->aCnonce[i] |= 0x30;
}
}
/*********************************************************************
*
* _Exec()
*
* Function description
* Execute HTTP request.
*
* Parameters
* pHTTP - Pointer to HTTP context.
* pAuthHeader - Pointer to object that receives any autorization
* header.
*
* Return value
* >= 0 - HTTP response code.
* < 0 - Error executing request.
*/
static int _Exec(IOT_HTTP_CONTEXT *pHTTP, char *pAuthHeader) {
unsigned StatusCode;
int Status;
char aPayload[128];
//
Status = IOT_HTTP_ExecWithAuth(pHTTP);
if (Status < 0) {
return Status;
}
//
Status = IOT_HTTP_ProcessStatusLine(pHTTP, &StatusCode);
if (Status < 0) {
return Status;
}
//
pAuthHeader[0] = 0;
Status = IOT_HTTP_ProcessHeadersEx(pHTTP, _RememberAuthenticate, pAuthHeader);
if (Status < 0) {
return Status;
}
//
IOT_HTTP_GetBegin(pHTTP);
do {
Status = IOT_HTTP_GetPayload(pHTTP, &aPayload[0], sizeof(aPayload));
} while (Status > 0);
IOT_HTTP_GetEnd(pHTTP);
//
return Status >= 0 ? StatusCode : Status;
}
/*********************************************************************
*
* Public code
*
**********************************************************************
*/
/*********************************************************************
*
* MainTask()
*
* Function description
* Application entry point.
*/
void MainTask(void);
void MainTask(void) {
IOT_HTTP_AUTH_DIGEST_PARAS AuthParas;
IOT_HTTP_CONTEXT HTTP;
IOT_HTTP_PARA aPara[20];
CONNECTION_CONTEXT Connection;
char aAuthHeader[256];
char aBuf[256];
char aURL[100];
int Status;
//
CRYPTO_Init();
SEGGER_SYS_Init();
SEGGER_SYS_IP_Init();
//
strcpy(aURL, "http://apidemo.segger.com/digesttest/conf/index.htm");
IOT_HTTP_Init (&HTTP, &aBuf[0], sizeof(aBuf), aPara, SEGGER_COUNTOF(aPara));
IOT_HTTP_SetIO (&HTTP, &_PlainAPI, &Connection);
IOT_HTTP_SetVersion(&HTTP, &IOT_HTTP_VERSION_HTTP_1v1);
IOT_HTTP_AddMethod (&HTTP, "GET");
IOT_HTTP_ParseURL (&HTTP, aURL);
//
Status = IOT_HTTP_Connect(&HTTP);
if (Status < 0) {
SEGGER_SYS_IO_Printf("Cannot negotiate a connection to %s:%d!\n",
IOT_HTTP_GetHost(&HTTP),
IOT_HTTP_GetPort(&HTTP));
SEGGER_SYS_OS_Halt(100);
}
//
printf("\n\n*** INITIAL GET WITHOUT AUTHORIZATION ***\n\n");
//
memset(aAuthHeader, 0, sizeof(aAuthHeader));
Status = _Exec(&HTTP, aAuthHeader);
//
// Authentication required?
//
if (Status == 401) {
Status = -401; // Say "Authentication not possible"
if (SEGGER_strncasecmp(aAuthHeader, "Digest ", 7) == 0 && aAuthHeader[0] != 0) {
IOT_HTTP_AUTH_DIGEST_InitParas(&AuthParas);
Status = IOT_HTTP_AUTH_DIGEST_ParseParas(aAuthHeader+7, &AuthParas);
if (Status >= 0) {
Status = _ParseQop(&HTTP, &AuthParas);
}
}
}
//
// Have we accepted the digest authentication scheme with supported parameters?
//
if (Status >= 0) {
//
// At this point, display realm and request user name, password.
// This user name and password is hard coded for the SEGGER server.
// We get this wrong.
//
printf("\n\n*** SECOND GET WITH INCORRECT AUTHORIZATION ***\n\n");
//
memset(aAuthHeader, 0, sizeof(aAuthHeader));
_ChooseNewNonce(&AuthParas);
strcpy(AuthParas.aUserName, "some-bad-dude");
strcpy(AuthParas.aPassword, "open sesame");
//
Status = IOT_HTTP_AUTH_DIGEST_ParseParas(aAuthHeader+7, &AuthParas);
if (Status >= 0) {
Status = _ParseQop(&HTTP, &AuthParas);
}
Status = _Exec(&HTTP, aAuthHeader);
//
if (Status == 401 &&
SEGGER_strncasecmp(aAuthHeader, "Digest ", 7) == 0 &&
aAuthHeader[0] != 0) {
//
// OK, we expected this. Try with correct parameters.
//
printf("\n\n*** THIRD GET WITH CORRECT AUTHORIZATION ***\n\n");
//
memset(aAuthHeader, 0, sizeof(aAuthHeader));
_ChooseNewNonce(&AuthParas);
strcpy(AuthParas.aUserName, "anybody");
strcpy(AuthParas.aPassword, "segger");
//
Status = IOT_HTTP_AUTH_DIGEST_ParseParas(aAuthHeader+7, &AuthParas);
if (Status >= 0) {
Status = _ParseQop(&HTTP, &AuthParas);
}
Status = _Exec(&HTTP, aAuthHeader);
//
if (Status == 200) {
printf("\nAuthenticated and received!\n");
} else {
printf("\nAuthentication failed!\n");
}
}
}
//
SEGGER_SYS_IP_Exit();
SEGGER_SYS_Exit();
SEGGER_SYS_OS_Halt(Status);
}
/*************************** End of file ****************************/
JSON parser
Overview
The JSON parser is a simple and compact parser for JSON values.
This library provides a way to parse a JSON Data Interchange Format
value in a space-efficient manner: rather than parsing a complete JSON
value before processing, this library uses a set of events to deliver
the structure and enclosed values within the JSON value efficiently in
terms of memory space and processing overhead.
The main benefits are:
- No tree structure is created, so no dynamic memory is
required to hold JSON content. This is particularly
effective when only some of the delivered JSON content
is of interest: all other content can simply be ignored
(and therefore discarded) during the parse.
- No recursive program flow, so stack space is bounded.
- Incremental parsing is ideally suited to reading data
from the network for delivery to the JSON parser.
There are some limitations with this strategy:
- The JSON parser is a pure parser which detects syntax
errors in its input. However, semantic errors, such as
duplicate keys in the same object, are left for the client
to diagnose.
- The parser fires events as it incrementally parses
a JSON value and, therefore, there is no protection that
the parser offers against firing events for objects with
long-range syntax errors.
Standards reference
The JSON format is formalized in the following RFC:
IETF RFC 8259 — The JavaScript Object Notation (JSON) Data Interchange Format
Tracing events during parsing
The following is a valid JSON object:
{
"name": "My TV",
"resolutions": [
{ "width": 1280, "height": 720 },
{ "width": 1920, "height": 1080 },
{ "width": 3840, "height": 2160 }
]
}
This and subsequent examples use the above object to illustrate
the operation of the JSON parser.
Setting up the trace
It’s illuminating to see which events are fired during parsing and
this is achieved using methods that dump state during a parse. What
we do first is construct a table of methods that handle the events
issued by the parser:
static const IOT_JSON_EVENTS _EventAPI = {
_BeginObject,
_EndObject,
_BeginArray,
_EndArray,
_Key,
_String,
_Number,
_Literal
};
These events are handled by the following methods:
static void _BeginObject(IOT_JSON_CONTEXT *pContext);
static void _EndObject (IOT_JSON_CONTEXT *pContext);
static void _BeginArray (IOT_JSON_CONTEXT *pContext);
static void _EndArray (IOT_JSON_CONTEXT *pContext);
static void _Key (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _String (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _Number (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _Literal (IOT_JSON_CONTEXT *pContext, const char *sText);
Each method displays the method that fired and any associated data.
So, for instance, the _Key method is:
static void _Key(IOT_JSON_CONTEXT *pContext, const char *sText) {
IOT_USE_PARA(pContext);
printf("Key = \"%s\"\n", sText);
}
This method ignores the incoming JSON context and shows the value
of the key.
Using the parser
The JSON object is transcribed into a C object:
static const char _sJSONValue[] = {
"{ "
" \"name\": \"My TV\", "
" \"resolutions\": [ "
" { \"width\": 1280, \"height\": 720 }, "
" { \"width\": 1920, \"height\": 1080 }, "
" { \"width\": 3840, \"height\": 2160 } "
" ] "
"} "
};
This is parsed by a few calls to the JSON parser:
void MainTask(void) {
IOT_JSON_CONTEXT JSON;
char acBuf[32];
//
IOT_JSON_Init(&JSON, &_EventAPI, &acBuf[0], sizeof(acBuf));
if (IOT_JSON_Parse(&JSON, _sJSONValue, sizeof(_sJSONValue)-1) == 0) {
printf("\nParse OK\n");
} else {
printf("\nParse FAIL\n");
}
}
Declare a context
The JSON parser uses a single context of type IOT_JSON_CONTEXT that
maintains the parser state.
Declare a working buffer
During parsing, the JSON parser builds up tokens that are passed
to the event handlers. The token buffer must be long enough to handle
a constructed token such as a key name, a literal value, a string, and
so on. The parser diagnoses inputs where the tokens are too long to be
constructed in the buffer and reports this as an error.
Initialize the parser
The call to IOT_JSON_Init() initializes the parsing context and
provides the set of methods which will fire during a subsequent parse.
The token buffer is also provided to the parser and this buffer must
remain in scope until the parse is complete.
Parse the value
The call to IOT_JSON_Parse() provides the JSON value to parse. In
this example, the complete value is presented to the parser and we
expect events to fire during parsing and a result that indicates the
parse completed without error. If we receive anything other than
a zero result from IOT_JSON_Parse() then the parse has failed.
Run the example
Running this outputs the trace of events that were fired by the JSON
parser and shows correct operation, as expected:
C:> IOT_JSON_PlainTrace.exe
Begin object
Key = "name"
String = "My TV"
Key = "resolutions"
Begin array
Begin object
Key = "width"
Number = "1280"
Key = "height"
Number = "720"
End object
Begin object
Key = "width"
Number = "1920"
Key = "height"
Number = "1080"
End object
Begin object
Key = "width"
Number = "3840"
Key = "height"
Number = "2160"
End object
End array
End object
Parse OK
C:> _
IOT_JSON_PlainTrace complete listing
/*********************************************************************
* SEGGER Microcontroller GmbH *
* The Embedded Experts *
**********************************************************************
* *
* (c) 2003 - 2019 SEGGER Microcontroller GmbH *
* *
* www.segger.com Support: support@segger.com *
* *
**********************************************************************
-------------------------- END-OF-HEADER -----------------------------
File : IOT_JSON_PlainTrace.c
Purpose : Simple parse of a JSON object.
Additional information:
Preparations:
None.
Expected behavior:
Parses a sample JSON object and prints it out on the console
without any formatting.
For more information see the IOTToolkit manual under the
chapter "JSON parser".
Sample output:
Begin object
Key = "name"
String = "My TV"
Key = "resolutions"
Begin array
Begin object
Key = "width"
Number = "1280"
Key = "height"
Number = "720"
End object
Begin object
Key = "width"
Number = "1920"
Key = "height"
Number = "1080"
End object
Begin object
Key = "width"
Number = "3840"
Key = "height"
Number = "2160"
End object
End array
End object
Parse OK
*/
/*********************************************************************
*
* #include Section
*
**********************************************************************
*/
#include "IOT.h"
/*********************************************************************
*
* Prototypes
*
**********************************************************************
*/
static void _BeginObject(IOT_JSON_CONTEXT *pContext);
static void _EndObject (IOT_JSON_CONTEXT *pContext);
static void _BeginArray (IOT_JSON_CONTEXT *pContext);
static void _EndArray (IOT_JSON_CONTEXT *pContext);
static void _Key (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _String (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _Number (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _Literal (IOT_JSON_CONTEXT *pContext, const char *sText);
/*********************************************************************
*
* Static const data
*
**********************************************************************
*/
static const IOT_JSON_EVENTS _EventAPI = {
_BeginObject,
_EndObject,
_BeginArray,
_EndArray,
_Key,
_String,
_Number,
_Literal
};
static const char _sJSONValue[] = {
"{ "
" \"name\": \"My TV\", "
" \"resolutions\": [ "
" { \"width\": 1280, \"height\": 720 }, "
" { \"width\": 1920, \"height\": 1080 }, "
" { \"width\": 3840, \"height\": 2160 } "
" ] "
"} "
};
/*********************************************************************
*
* Static code
*
**********************************************************************
*/
/*********************************************************************
*
* _BeginObject()
*
* Function description
* Handle Begin Object event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _BeginObject(IOT_JSON_CONTEXT *pContext) {
IOT_USE_PARA(pContext);
printf("Begin object\n");
}
/*********************************************************************
*
* _EndObject()
*
* Function description
* Handle End Object event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _EndObject(IOT_JSON_CONTEXT *pContext) {
IOT_USE_PARA(pContext);
printf("End object\n");
}
/*********************************************************************
*
* _BeginArray()
*
* Function description
* Handle Begin Array event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _BeginArray(IOT_JSON_CONTEXT *pContext) {
IOT_USE_PARA(pContext);
printf("Begin array\n");
}
/*********************************************************************
*
* _EndArray()
*
* Function description
* Handle End Array event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _EndArray(IOT_JSON_CONTEXT *pContext) {
IOT_USE_PARA(pContext);
printf("End array\n");
}
/*********************************************************************
*
* _Key()
*
* Function description
* Handle Key event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated key ID.
*/
static void _Key(IOT_JSON_CONTEXT *pContext, const char *sText) {
IOT_USE_PARA(pContext);
printf("Key = \"%s\"\n", sText);
}
/*********************************************************************
*
* _String()
*
* Function description
* Handle Key event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated string value.
*/
static void _String(IOT_JSON_CONTEXT *pContext, const char *sText) {
IOT_USE_PARA(pContext);
printf("String = \"%s\"\n", sText);
}
/*********************************************************************
*
* _Number()
*
* Function description
* Handle Number event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated numeric value.
*/
static void _Number(IOT_JSON_CONTEXT *pContext, const char *sText) {
IOT_USE_PARA(pContext);
printf("Number = \"%s\"\n", sText);
}
/*********************************************************************
*
* _Literal()
*
* Function description
* Handle Literal event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated literal name.
*/
static void _Literal(IOT_JSON_CONTEXT *pContext, const char *sText) {
IOT_USE_PARA(pContext);
printf("Literal = \"%s\"\n", sText);
}
/*********************************************************************
*
* Public code
*
**********************************************************************
*/
/*********************************************************************
*
* MainTask()
*
* Function description
* Application entry point.
*/
void MainTask(void);
void MainTask(void) {
IOT_JSON_CONTEXT JSON;
char acBuf[32];
//
IOT_JSON_Init(&JSON, &_EventAPI, &acBuf[0], sizeof(acBuf));
if (IOT_JSON_Parse(&JSON, _sJSONValue, sizeof(_sJSONValue)-1) == 0) {
printf("\nParse OK\n");
} else {
printf("\nParse FAIL\n");
}
}
/*************************** End of file ****************************/
Adding a user context
The previous example shows how the events are fired, but this is
“context free” in that no user state is affected when the events
fire. In this example we attach a user state to the parse which
records how deeply nested each object, array, and value is and uses
that to produce a nicely indented trace of the events that reflects
the JSON object structure.
This example extends the previous example to add this state.
The user context
The only thing that is maintained across events is the nesting
level of objects, arrays, and keys:
typedef struct {
unsigned Indent;
} USER_CONTEXT;
The user context is attached to the parser with IOT_JSON_SetUserContext():
void MainTask(void) {
IOT_JSON_CONTEXT JSON;
USER_CONTEXT User;
char acBuf[32];
//
User.Indent = 0;
IOT_JSON_Init(&JSON, &_EventAPI, &acBuf[0], sizeof(acBuf));
IOT_JSON_SetUserContext(&JSON, &User);
if (IOT_JSON_Parse(&JSON, _sJSONValue, sizeof(_sJSONValue)-1) == 0) {
printf("\nParse OK\n");
} else {
printf("\nParse FAIL\n");
}
}
Declare a user context
This is the user context that the parser will pass through to
the event handlers.
Initialize user context
The example starts with no indentation for trace messages.
Attach user context
The user context is attached to the JSON parser context with
IOT_JSON_SetUserContext(). The user context can be retrieved
from the JSON parser context using IOT_JSON_GetUserContext().
Using the user context
Now that the user context is attached to the JSON parser context,
the methods that are invoked during parsing can retrieve that
user context and maintain their own model of the parsing process.
In this example, each method indents its messages to indicate
the structure of the value parsed.
Rather than present all methods, a single method is used as an
example:
static void _BeginObject(IOT_JSON_CONTEXT *pContext) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
printf("%-*sBegin object\n", pUserContext->Indent, "");
pUserContext->Indent += 2;
}
Run the example
Running this outputs the trace of events that were fired by the JSON
parser and shows correct operation, as expected:
C:> IOT_JSON_PrettyTrace.exe
Begin object
Key = "name"
String = "My TV"
Key = "resolutions"
Begin array
Begin object
Key = "width"
Number = "1280"
Key = "height"
Number = "720"
End object
Begin object
Key = "width"
Number = "1920"
Key = "height"
Number = "1080"
End object
Begin object
Key = "width"
Number = "3840"
Key = "height"
Number = "2160"
End object
End array
End object
Parse OK
IOT_JSON_PrettyTrace complete listing
/*********************************************************************
* SEGGER Microcontroller GmbH *
* The Embedded Experts *
**********************************************************************
* *
* (c) 2003 - 2019 SEGGER Microcontroller GmbH *
* *
* www.segger.com Support: support@segger.com *
* *
**********************************************************************
-------------------------- END-OF-HEADER -----------------------------
File : IOT_JSON_PrettyTrace.c
Purpose : Simple parse of a JSON object.
Additional information:
Preparations:
None.
Expected behavior:
Parses a sample JSON object and prints it out on the console in a
tree-like format.
For more information see the IOTToolkit manual under the
chapter "JSON parser".
Sample output:
Begin object
Key = "name"
String = "My TV"
Key = "resolutions"
Begin array
Begin object
Key = "width"
Number = "1280"
Key = "height"
Number = "720"
End object
Begin object
Key = "width"
Number = "1920"
Key = "height"
Number = "1080"
End object
Begin object
Key = "width"
Number = "3840"
Key = "height"
Number = "2160"
End object
End array
End object
Parse OK
*/
/*********************************************************************
*
* #include Section
*
**********************************************************************
*/
#include "IOT.h"
/*********************************************************************
*
* Local types
*
**********************************************************************
*/
typedef struct {
unsigned Indent;
} USER_CONTEXT;
/*********************************************************************
*
* Prototypes
*
**********************************************************************
*/
static void _BeginObject(IOT_JSON_CONTEXT *pContext);
static void _EndObject (IOT_JSON_CONTEXT *pContext);
static void _BeginArray (IOT_JSON_CONTEXT *pContext);
static void _EndArray (IOT_JSON_CONTEXT *pContext);
static void _Key (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _String (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _Number (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _Literal (IOT_JSON_CONTEXT *pContext, const char *sText);
/*********************************************************************
*
* Static const data
*
**********************************************************************
*/
static const IOT_JSON_EVENTS _EventAPI = {
_BeginObject,
_EndObject,
_BeginArray,
_EndArray,
_Key,
_String,
_Number,
_Literal
};
static const char _sJSONValue[] = {
"{ "
" \"name\": \"My TV\", "
" \"resolutions\": [ "
" { \"width\": 1280, \"height\": 720 }, "
" { \"width\": 1920, \"height\": 1080 }, "
" { \"width\": 3840, \"height\": 2160 } "
" ] "
"} "
};
/*********************************************************************
*
* Static code
*
**********************************************************************
*/
/*********************************************************************
*
* _BeginObject()
*
* Function description
* Handle Begin Object event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _BeginObject(IOT_JSON_CONTEXT *pContext) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
printf("%-*sBegin object\n", pUserContext->Indent, "");
pUserContext->Indent += 2;
}
/*********************************************************************
*
* _EndObject()
*
* Function description
* Handle End Object event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _EndObject(IOT_JSON_CONTEXT *pContext) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
pUserContext->Indent -= 2;
printf("%-*sEnd object\n", pUserContext->Indent, "");
}
/*********************************************************************
*
* _BeginArray()
*
* Function description
* Handle Begin Array event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _BeginArray(IOT_JSON_CONTEXT *pContext) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
printf("%-*sBegin array\n", pUserContext->Indent, "");
pUserContext->Indent += 2;
}
/*********************************************************************
*
* _EndArray()
*
* Function description
* Handle End Array event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _EndArray(IOT_JSON_CONTEXT *pContext) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
pUserContext->Indent -= 2;
printf("%-*sEnd array\n", pUserContext->Indent, "");
}
/*********************************************************************
*
* _Key()
*
* Function description
* Handle Key event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated key ID.
*/
static void _Key(IOT_JSON_CONTEXT *pContext, const char *sText) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
printf("%-*sKey = \"%s\"\n", pUserContext->Indent, "", sText);
}
/*********************************************************************
*
* _String()
*
* Function description
* Handle Key event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated string value.
*/
static void _String(IOT_JSON_CONTEXT *pContext, const char *sText) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
printf("%-*sString = \"%s\"\n", pUserContext->Indent, "", sText);
}
/*********************************************************************
*
* _Number()
*
* Function description
* Handle Number event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated numeric value.
*/
static void _Number(IOT_JSON_CONTEXT *pContext, const char *sText) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
printf("%-*sNumber = \"%s\"\n", pUserContext->Indent, "", sText);
}
/*********************************************************************
*
* _Literal()
*
* Function description
* Handle Literal event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated literal name.
*/
static void _Literal(IOT_JSON_CONTEXT *pContext, const char *sText) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
printf("%-*sLiteral = \"%s\"\n", pUserContext->Indent, "", sText);
}
/*********************************************************************
*
* Public code
*
**********************************************************************
*/
/*********************************************************************
*
* MainTask()
*
* Function description
* Application entry point.
*/
void MainTask(void);
void MainTask(void) {
IOT_JSON_CONTEXT JSON;
USER_CONTEXT User;
char acBuf[32];
//
User.Indent = 0;
IOT_JSON_Init(&JSON, &_EventAPI, &acBuf[0], sizeof(acBuf));
IOT_JSON_SetUserContext(&JSON, &User);
if (IOT_JSON_Parse(&JSON, _sJSONValue, sizeof(_sJSONValue)-1) == 0) {
printf("\nParse OK\n");
} else {
printf("\nParse FAIL\n");
}
}
/*************************** End of file ****************************/
Incremental parsing
The previous examples have shown how to parse a single JSON object,
in its entirety, in a single call to IOT_JSON_Parse(). This example
demonstrates how to parse JSON values incrementally and how it is
perfectly suited to parsing data originating from a socket or from
the SEGGER HTTP client.
Setting up the parse
Many JSON values are delivered in small chunks over an HTTP connection
as part of a REST API’s entity body. The following code presents a JSON
value, one character at a time, to the JSON parser. However, varying
lengths chunks of data can be passed to the parser as they become available
to your application:
void MainTask(void) {
IOT_JSON_CONTEXT JSON;
char acBuf[32];
unsigned i;
int Status;
//
IOT_JSON_Init(&JSON, &_EventAPI, &acBuf[0], sizeof(acBuf));
//
i = 0;
do {
Status = IOT_JSON_Parse(&JSON, &_sJSONValue[i++], 1);
} while (Status > 0);
//
if (Status == 0) {
printf("\nParse OK\n");
} else {
printf("\nParse FAIL\n");
}
}
Prepare to parse
The cursor i iterates over the JSON value one character at a time.
Parse a little bit
The JSON parser is given one character of data and the cursor is
advanced. The return value is saved so that it can be examined
later.
Continue parsing?
A non-zero positive value returned by IOT_JSON_Parse() indicates that
parsing of the JSON value is not complete. In this case, the loop
continues and presents the next character.
Decode completion status
The completion status here is identical to the all-in-one parse of
previous examples.
Run the example
Running this outputs the trace of events that were fired by the JSON
parser and shows correct operation, as expected:
C:> IOT_JSON_IncrementalParse.exe
Begin object
Key = "name"
String = "My TV"
Key = "resolutions"
Begin array
Begin object
Key = "width"
Number = "1280"
Key = "height"
Number = "720"
End object
Begin object
Key = "width"
Number = "1920"
Key = "height"
Number = "1080"
End object
Begin object
Key = "width"
Number = "3840"
Key = "height"
Number = "2160"
End object
End array
End object
Parse OK
C:> _
IOT_JSON_IncrementalParse complete listing
/*********************************************************************
* SEGGER Microcontroller GmbH *
* The Embedded Experts *
**********************************************************************
* *
* (c) 2003 - 2019 SEGGER Microcontroller GmbH *
* *
* www.segger.com Support: support@segger.com *
* *
**********************************************************************
-------------------------- END-OF-HEADER -----------------------------
File : IOT_JSON_IncrementalParse.c
Purpose : Incremental parse of a JSON object.
Additional information:
Preparations:
None.
Expected behavior:
Incrementally parses a sample JSON object and prints it out on the
console.
For more information see the IOTToolkit manual under the
chapter "JSON parser".
Sample output:
Begin object
Key = "name"
String = "My TV"
Key = "resolutions"
Begin array
Begin object
Key = "width"
Number = "1280"
Key = "height"
Number = "720"
End object
Begin object
Key = "width"
Number = "1920"
Key = "height"
Number = "1080"
End object
Begin object
Key = "width"
Number = "3840"
Key = "height"
Number = "2160"
End object
End array
End object
Parse OK
*/
/*********************************************************************
*
* #include Section
*
**********************************************************************
*/
#include "IOT.h"
/*********************************************************************
*
* Prototypes
*
**********************************************************************
*/
static void _BeginObject(IOT_JSON_CONTEXT *pContext);
static void _EndObject (IOT_JSON_CONTEXT *pContext);
static void _BeginArray (IOT_JSON_CONTEXT *pContext);
static void _EndArray (IOT_JSON_CONTEXT *pContext);
static void _Key (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _String (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _Number (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _Literal (IOT_JSON_CONTEXT *pContext, const char *sText);
/*********************************************************************
*
* Static const data
*
**********************************************************************
*/
static const IOT_JSON_EVENTS _EventAPI = {
_BeginObject,
_EndObject,
_BeginArray,
_EndArray,
_Key,
_String,
_Number,
_Literal
};
static const char _sJSONValue[] = {
"{ "
" \"name\": \"My TV\", "
" \"resolutions\": [ "
" { \"width\": 1280, \"height\": 720 }, "
" { \"width\": 1920, \"height\": 1080 }, "
" { \"width\": 3840, \"height\": 2160 } "
" ] "
"} "
};
/*********************************************************************
*
* Static code
*
**********************************************************************
*/
/*********************************************************************
*
* _BeginObject()
*
* Function description
* Handle Begin Object event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _BeginObject(IOT_JSON_CONTEXT *pContext) {
IOT_USE_PARA(pContext);
printf("Begin object\n");
}
/*********************************************************************
*
* _EndObject()
*
* Function description
* Handle End Object event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _EndObject(IOT_JSON_CONTEXT *pContext) {
IOT_USE_PARA(pContext);
printf("End object\n");
}
/*********************************************************************
*
* _BeginArray()
*
* Function description
* Handle Begin Array event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _BeginArray(IOT_JSON_CONTEXT *pContext) {
IOT_USE_PARA(pContext);
printf("Begin array\n");
}
/*********************************************************************
*
* _EndArray()
*
* Function description
* Handle End Array event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _EndArray(IOT_JSON_CONTEXT *pContext) {
IOT_USE_PARA(pContext);
printf("End array\n");
}
/*********************************************************************
*
* _Key()
*
* Function description
* Handle Key event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated key ID.
*/
static void _Key(IOT_JSON_CONTEXT *pContext, const char *sText) {
IOT_USE_PARA(pContext);
printf("Key = \"%s\"\n", sText);
}
/*********************************************************************
*
* _String()
*
* Function description
* Handle Key event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated string value.
*/
static void _String(IOT_JSON_CONTEXT *pContext, const char *sText) {
IOT_USE_PARA(pContext);
printf("String = \"%s\"\n", sText);
}
/*********************************************************************
*
* _Number()
*
* Function description
* Handle Number event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated numeric value.
*/
static void _Number(IOT_JSON_CONTEXT *pContext, const char *sText) {
IOT_USE_PARA(pContext);
printf("Number = \"%s\"\n", sText);
}
/*********************************************************************
*
* _Literal()
*
* Function description
* Handle Literal event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated literal name.
*/
static void _Literal(IOT_JSON_CONTEXT *pContext, const char *sText) {
IOT_USE_PARA(pContext);
printf("Literal = \"%s\"\n", sText);
}
/*********************************************************************
*
* Public code
*
**********************************************************************
*/
/*********************************************************************
*
* MainTask()
*
* Function description
* Application entry point.
*/
void MainTask(void);
void MainTask(void) {
IOT_JSON_CONTEXT JSON;
char acBuf[32];
unsigned i;
int Status;
//
IOT_JSON_Init(&JSON, &_EventAPI, &acBuf[0], sizeof(acBuf));
//
i = 0;
do {
Status = IOT_JSON_Parse(&JSON, &_sJSONValue[i++], 1);
} while (Status > 0);
//
if (Status == 0) {
printf("\nParse OK\n");
} else {
printf("\nParse FAIL\n");
}
}
/*************************** End of file ****************************/
Building a value tree
The previous examples have shown how to parse a single JSON object
and act on each event that is fired. The following example uses the
sequence of events to build a tree representation of the parsed value
which can then be queried and printed. This example can form the basis
of a more elaborate framework that transforms the trees read and, after
transformation, creates a standard external interchange representation.
JSON nodes
The internal representation of the JSON value is by way of a tree
of nodes. Each node has a type that corresponds to a JSON value:
typedef enum {
JSON_TYPE_OBJECT,
JSON_TYPE_ARRAY,
JSON_TYPE_PAIR,
JSON_TYPE_STRING,
JSON_TYPE_NUMBER,
JSON_TYPE_TRUE,
JSON_TYPE_FALSE,
JSON_TYPE_NULL,
} JSON_TYPE;
The node is made as simple as possible:
typedef struct JSON_NODE JSON_NODE;
struct JSON_NODE {
JSON_TYPE Type; // Type of this node
JSON_NODE * pNext; // Next node in list (for arrays and object keys)
JSON_NODE * pParent; // Pointer to node's parent
JSON_NODE * pValue; // Pointer to value (JSON_TYPE_{KEY,ARRAY,OBJECT})
char * sStr; // Only valid for JSON_TYPE_{PAIR,STRING}
double Number; // Only valid for JSON_TYPE_NUMBER
};
The members applicable to each node are noted in the comments.
The user context
The context that the node builder uses is:
typedef struct {
JSON_NODE *pContainer;
JSON_NODE *pRoot;
} USER_CONTEXT;
The pRoot member points to the root node during construction and
the pContainer member points to the most-recently-opened container
(an array, object, or pair node) that accumulates new values.
Constructing a new node
New nodes are created by _MakeNode() which contains all the
machinery to construct nodes and the housekeeping to maintain
the relationships between nodes:
static JSON_NODE *_MakeNode(JSON_TYPE Type, USER_CONTEXT *pContext) {
JSON_NODE *pNode;
//
pNode = malloc(sizeof(JSON_NODE));
memset(pNode, 0, sizeof(JSON_NODE));
pNode->Type = Type;
pNode->pParent = pContext->pContainer;
//
if (pContext->pRoot == NULL) {
pContext->pRoot = pNode;
} else {
_AppendNode(&pContext->pContainer->pValue, pNode);
}
//
if (Type == JSON_TYPE_ARRAY ||
Type == JSON_TYPE_OBJECT ||
Type == JSON_TYPE_PAIR) {
pContext->pContainer = pNode;
} else if (pContext->pContainer->Type == JSON_TYPE_PAIR) {
pContext->pContainer = pContext->pContainer->pParent;
}
//
return pNode;
}
The following points are noteworthy:
Create the node
The node is allocated from the heap, set to zero, and members
initialized. Using a simple mark-allocate-release style of
allocator would be an excellent alternative in an embedded
system.
Note the parent
Each created node is initialized with a parental link. This
link is used to climb back up the tree when an array or object
is closed by one of the “end” methods.
Housekeeping for roots and lists
The initial allocation of a node is always noted as this is
the root node and the root node is never part of a list.
All other nodes are appended to the end of the container
that is accumulating them.
Opening a new container
Newly constructed containers become the container for new
nodes read by the parser, up to the point that the container
is closed.
Auto-closing a pair
The JSON_TYPE_PAIR container can only contain a single element
that is the value that the key maps to. Once a value is added
to the key-value pair, the key-value pair is closed and accumulation
returns to the parent container.
Handling events
With the construction code written, handling events is little more
than creating a node of the appropriate type when invoked:
static void _BeginObject(IOT_JSON_CONTEXT *pContext) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
_MakeNode(JSON_TYPE_OBJECT, pUserContext);
}
And closing down containers when required:
static void _EndObject(IOT_JSON_CONTEXT *pContext) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
pUserContext->pContainer = pUserContext->pContainer->pParent;
}
Because the JSON parser will not fire events in an incorrect order
(i.e. all paired events are invoked in the correct order), is it
not necessary for the event handlers to check the validity of
event sequences. That is to say, even in the presence of syntax
errors, the sequence of events is well defined such that “begin
x” is only ever paired with a corresponding “end x” event.
This makes it straightdorward to write event handling code.
It’s much simpler to convert the internal form to a human-readable
external form. This function prints a JSON-style interchange representation
but does not, for instance, produce any escapes in strings that would
be necessary in practice:
static void _PrintNode(JSON_NODE *pNode, unsigned Indent) {
for (; pNode; pNode = pNode->pNext) {
switch (pNode->Type) {
case JSON_TYPE_OBJECT:
printf("\n%*s{", Indent, "");
_PrintNode(pNode->pValue, Indent+2);
printf("\n%*s}", Indent, "");
break;
case JSON_TYPE_ARRAY:
printf("\n%*s[", Indent, "");
_PrintNode(pNode->pValue, Indent+2);
printf("\n%*s]", Indent, "");
break;
case JSON_TYPE_PAIR:
printf("\n%*s\"%s\": ", Indent, "", pNode->sStr);
_PrintNode(pNode->pValue, Indent+2);
break;
case JSON_TYPE_STRING:
printf("\"%s\"", pNode->sStr);
break;
case JSON_TYPE_NUMBER:
printf("%g", pNode->Number);
break;
case JSON_TYPE_TRUE:
printf("true");
break;
case JSON_TYPE_FALSE:
printf("false");
break;
case JSON_TYPE_NULL:
printf("null");
break;
}
if (pNode->pNext) {
printf(", ");
}
}
}
Running the sample
With the sample now complete, running it produces the following output:
C:> IOT_JSON_MakeTree.exe
Parse OK, tree:
{
"name": "My TV",
"resolutions":
[
{
"width": 1280,
"height": 720
},
{
"width": 1920,
"height": 1080
},
{
"width": 3840,
"height": 2160
}
]
}
C:> _
IOT_JSON_MakeTree complete listing
/*********************************************************************
* SEGGER Microcontroller GmbH *
* The Embedded Experts *
**********************************************************************
* *
* (c) 2003 - 2019 SEGGER Microcontroller GmbH *
* *
* www.segger.com Support: support@segger.com *
* *
**********************************************************************
-------------------------- END-OF-HEADER -----------------------------
File : IOT_JSON_MakeTree.c
Purpose : Parse a JSON object and create an tree structure
that represents the object.
Additional information:
Preparations:
None.
Expected behavior:
Parses a sample JSON object and prints it out on the console in a
tree format.
For more information see the IOTToolkit manual under the
chapter "JSON parser".
Sample output:
Parse OK, tree:
{
"name": "My TV",
"resolutions":
[
{
"width": 1280,
"height": 720
},
{
"width": 1920,
"height": 1080
},
{
"width": 3840,
"height": 2160
}
]
}
*/
/*********************************************************************
*
* #include Section
*
**********************************************************************
*/
#include "IOT.h"
#include <stdlib.h>
/*********************************************************************
*
* Local types
*
**********************************************************************
*/
typedef enum {
JSON_TYPE_OBJECT,
JSON_TYPE_ARRAY,
JSON_TYPE_PAIR,
JSON_TYPE_STRING,
JSON_TYPE_NUMBER,
JSON_TYPE_TRUE,
JSON_TYPE_FALSE,
JSON_TYPE_NULL,
} JSON_TYPE;
typedef struct JSON_NODE JSON_NODE;
struct JSON_NODE {
JSON_TYPE Type; // Type of this node
JSON_NODE * pNext; // Next node in list (linking arrays and object keys)
JSON_NODE * pParent; // Pointer to node's parent
JSON_NODE * pValue; // Pointer to value (JSON_TYPE_KEY, JSON_TYPE_ARRAY, JSON_TYPE_OBJECT)
char * sStr; // Only valid for JSON_TYPE_PAIR and JSON_TYPE_STRING
double Number; // Only valid for JSON_TYPE_NUMBER
};
typedef struct {
JSON_NODE *pContainer;
JSON_NODE *pRoot;
} USER_CONTEXT;
/*********************************************************************
*
* Prototypes
*
**********************************************************************
*/
static void _BeginObject(IOT_JSON_CONTEXT *pContext);
static void _EndObject (IOT_JSON_CONTEXT *pContext);
static void _BeginArray (IOT_JSON_CONTEXT *pContext);
static void _EndArray (IOT_JSON_CONTEXT *pContext);
static void _Key (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _String (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _Number (IOT_JSON_CONTEXT *pContext, const char *sText);
static void _Literal (IOT_JSON_CONTEXT *pContext, const char *sText);
/*********************************************************************
*
* Static const data
*
**********************************************************************
*/
static const IOT_JSON_EVENTS _EventAPI = {
_BeginObject,
_EndObject,
_BeginArray,
_EndArray,
_Key,
_String,
_Number,
_Literal
};
static const char _sJSONValue[] = {
"{ "
" \"name\": \"My TV\", "
" \"resolutions\": [ "
" { \"width\": 1280, \"height\": 720 }, "
" { \"width\": 1920, \"height\": 1080 }, "
" { \"width\": 3840, \"height\": 2160 } "
" ] "
"} "
};
/*********************************************************************
*
* Static code
*
**********************************************************************
*/
/*********************************************************************
*
* _AppendNode()
*
* Function description
* Append node onto list.
*
* Parameters
* ppRoot - Pointer to list root.
* pNode - Pointer to node to append.
*/
static void _AppendNode(JSON_NODE **ppRoot, JSON_NODE *pNode) {
if (*ppRoot == NULL) {
*ppRoot = pNode;
} else {
while ((*ppRoot)->pNext != NULL) {
ppRoot = &(*ppRoot)->pNext;
}
(*ppRoot)->pNext = pNode;
}
}
/*********************************************************************
*
* _MakeNode()
*
* Function description
* Create a new node, manage lists.
*
* Parameters
* Type - Type of node to construct.
* pContext - Pointer to use context.
*
* Return value
* Pointer to newly constructed node.
*/
static JSON_NODE *_MakeNode(JSON_TYPE Type, USER_CONTEXT *pContext) {
JSON_NODE *pNode;
//
pNode = malloc(sizeof(JSON_NODE));
memset(pNode, 0, sizeof(JSON_NODE));
pNode->Type = Type;
pNode->pParent = pContext->pContainer;
//
if (pContext->pRoot == NULL) {
pContext->pRoot = pNode;
} else {
_AppendNode(&pContext->pContainer->pValue, pNode);
}
//
if (Type == JSON_TYPE_ARRAY || Type == JSON_TYPE_OBJECT || Type == JSON_TYPE_PAIR) {
pContext->pContainer = pNode;
} else if (pContext->pContainer->Type == JSON_TYPE_PAIR) {
pContext->pContainer = pContext->pContainer->pParent;
}
//
return pNode;
}
/*********************************************************************
*
* _CloseNode()
*
* Function description
* Close a pair node or a container node.
*
* Parameters
* pUserContext - Pointer to user context.
*/
static void _CloseNode(USER_CONTEXT *pUserContext) {
pUserContext->pContainer = pUserContext->pContainer->pParent;
if (pUserContext->pContainer && pUserContext->pContainer->Type == JSON_TYPE_PAIR) {
pUserContext->pContainer = pUserContext->pContainer->pParent;
}
}
/*********************************************************************
*
* _BeginObject()
*
* Function description
* Handle Begin Object event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _BeginObject(IOT_JSON_CONTEXT *pContext) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
_MakeNode(JSON_TYPE_OBJECT, pUserContext);
}
/*********************************************************************
*
* _EndObject()
*
* Function description
* Handle End Object event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _EndObject(IOT_JSON_CONTEXT *pContext) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
_CloseNode(pUserContext);
}
/*********************************************************************
*
* _BeginArray()
*
* Function description
* Handle Begin Array event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _BeginArray(IOT_JSON_CONTEXT *pContext) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
_MakeNode(JSON_TYPE_ARRAY, pUserContext);
}
/*********************************************************************
*
* _EndArray()
*
* Function description
* Handle End Array event.
*
* Parameters
* pContext - Pointer to JSON context.
*/
static void _EndArray(IOT_JSON_CONTEXT *pContext) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
_CloseNode(pUserContext);
}
/*********************************************************************
*
* _Key()
*
* Function description
* Handle Key event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated key ID.
*/
static void _Key(IOT_JSON_CONTEXT *pContext, const char *sText) {
USER_CONTEXT *pUserContext;
JSON_NODE *pNode;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
pNode = _MakeNode(JSON_TYPE_PAIR, pUserContext);
pNode->sStr = malloc(strlen(sText)+1);
strcpy(pNode->sStr, sText);
}
/*********************************************************************
*
* _String()
*
* Function description
* Handle Key event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated string value.
*/
static void _String(IOT_JSON_CONTEXT *pContext, const char *sText) {
USER_CONTEXT *pUserContext;
JSON_NODE *pNode;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
pNode = _MakeNode(JSON_TYPE_STRING, pUserContext);
pNode->sStr = malloc(strlen(sText)+1);
strcpy(pNode->sStr, sText);
}
/*********************************************************************
*
* _Number()
*
* Function description
* Handle Number event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated numeric value.
*/
static void _Number(IOT_JSON_CONTEXT *pContext, const char *sText) {
USER_CONTEXT *pUserContext;
JSON_NODE *pNode;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
pNode = _MakeNode(JSON_TYPE_NUMBER, pUserContext);
sscanf(sText, "%lf", &pNode->Number);
}
/*********************************************************************
*
* _Literal()
*
* Function description
* Handle Literal event.
*
* Parameters
* pContext - Pointer to JSON context.
* sText - Zero-terminated literal name.
*/
static void _Literal(IOT_JSON_CONTEXT *pContext, const char *sText) {
USER_CONTEXT *pUserContext;
//
pUserContext = IOT_JSON_GetUserContext(pContext);
if (strcmp(sText, "true") == 0) {
_MakeNode(JSON_TYPE_TRUE, pUserContext);
} else if (strcmp(sText, "true") == 0) {
_MakeNode(JSON_TYPE_FALSE, pUserContext);
} else {
_MakeNode(JSON_TYPE_NULL, pUserContext);
}
}
/*********************************************************************
*
* _PrintNode()
*
* Function description
* Print node.
*
* Parameters
* pNode - Pointer to node to print.
* Indent - Indentation level for node.
*/
static void _PrintNode(JSON_NODE *pNode, unsigned Indent) {
for (; pNode; pNode = pNode->pNext) {
switch (pNode->Type) {
case JSON_TYPE_OBJECT:
printf("\n%*s{", Indent, "");
_PrintNode(pNode->pValue, Indent+2);
printf("\n%*s}", Indent, "");
break;
case JSON_TYPE_ARRAY:
printf("\n%*s[", Indent, "");
_PrintNode(pNode->pValue, Indent+2);
printf("\n%*s]", Indent, "");
break;
case JSON_TYPE_PAIR:
printf("\n%*s\"%s\": ", Indent, "", pNode->sStr);
_PrintNode(pNode->pValue, Indent+2);
break;
case JSON_TYPE_STRING:
printf("\"%s\"", pNode->sStr);
break;
case JSON_TYPE_NUMBER:
printf("%g", pNode->Number);
break;
case JSON_TYPE_TRUE:
printf("true");
break;
case JSON_TYPE_FALSE:
printf("false");
break;
case JSON_TYPE_NULL:
printf("null");
break;
}
if (pNode->pNext) {
printf(", ");
}
}
}
/*********************************************************************
*
* Public code
*
**********************************************************************
*/
/*********************************************************************
*
* MainTask()
*
* Function description
* Application entry point.
*/
void MainTask(void);
void MainTask(void) {
IOT_JSON_CONTEXT JSON;
USER_CONTEXT User;
char acBuf[32];
//
User.pContainer = NULL;
User.pRoot = NULL;
IOT_JSON_Init(&JSON, &_EventAPI, &acBuf[0], sizeof(acBuf));
IOT_JSON_SetUserContext(&JSON, &User);
if (IOT_JSON_Parse(&JSON, _sJSONValue, sizeof(_sJSONValue)-1) == 0) {
printf("\nParse OK, tree:\n");
_PrintNode(User.pRoot, 0);
printf("\n");
} else {
printf("\nParse FAIL\n");
}
}
/*************************** End of file ****************************/
API reference
HTTP client
This section describes the API of the HTTP client.
Preprocessor symbols
HTTP client errors
Description
Errors that the HTTP client can generate.
Definition
#define IOT_HTTP_ERROR_FIELD_TOO_LONG (-700)
#define IOT_HTTP_ERROR_SOCKET_TERMINATED (-701)
#define IOT_HTTP_ERROR_BAD_SYNTAX (-702)
#define IOT_HTTP_ERROR_BUFFER_OVERFLOW (-703)
#define IOT_HTTP_ERROR_BUFFER_UNDERFLOW (-704)
Symbols
Definition | Description |
IOT_HTTP_ERROR_FIELD_TOO_LONG | Field is too long when parsing HTTP responses |
IOT_HTTP_ERROR_SOCKET_TERMINATED | Socket closed when reading response |
IOT_HTTP_ERROR_BAD_SYNTAX | Value doesn’t conform to valid syntax |
IOT_HTTP_ERROR_BUFFER_OVERFLOW | Internal buffer overflowed |
IOT_HTTP_ERROR_BUFFER_UNDERFLOW | Internal buffer underflowed |
I/O types
IOT_IO_CONNECT_FUNC
Description
Connect transport.
Type definition
typedef int (IOT_IO_CONNECT_FUNC)( void * pSession,
const char * sHost,
unsigned Port);
Parameters
Parameter | Description |
pSession | Pointer to user-provided session context. |
sHost | Zero-terminates string describing host to connect to. |
Port | Port to connect to. |
Return value
≥ 0 | Success, connected. |
< 0 | Failure. |
IOT_IO_DISCONNECT_FUNC
Description
Disconnect transport.
Type definition
typedef int (IOT_IO_DISCONNECT_FUNC)(void * pSession);
Parameters
Parameter | Description |
pSession | Pointer to user-provided session context. |
Return value
≥ 0 | Success, disconnected. |
< 0 | Failure. |
IOT_IO_SEND_FUNC
Description
Send data to transport.
Type definition
typedef int (IOT_IO_SEND_FUNC)( void * pSession,
const void * pData,
unsigned DataLen);
Parameters
Parameter | Description |
pSession | Pointer to user-provided session context. |
pData | Pointer to octet string to write to transport. |
DataLen | Octet length of the octet string. |
Return value
≥ 0 | Success, written. |
< 0 | Failure. |
IOT_IO_RECV_FUNC
Description
Receive data from transport.
Type definition
typedef int (IOT_IO_RECV_FUNC)(void * pSession,
void * pData,
unsigned DataLen);
Parameters
Parameter | Description |
pSession | Pointer to user-provided session context. |
pData | Pointer to octet string that receives data from transport. |
DataLen | Octet length of the octet string. |
Return value
> 0 | Success, number of bytes read from transport. |
= 0 | Underlying transport closed gracefully. |
< 0 | Failure. |
IOT_IO_API
Description
I/O API for HTTP client.
Type definition
typedef struct {
IOT_IO_CONNECT_FUNC * pfConnect;
IOT_IO_DISCONNECT_FUNC * pfDisconnect;
IOT_IO_SEND_FUNC * pfSend;
IOT_IO_RECV_FUNC * pfRecv;
} IOT_IO_API;
Structure members
Member | Description |
pfConnect | Connect transport method. |
pfDisconnect | Disconnect transport method. |
pfSend | Send data to transport method. |
pfRecv | Receive data from transport method. |
IOT_HTTP_PARA
Description
Parameters for an HTTP request.
Type definition
typedef struct {
const char * sKey;
const char * sValue;
IOT_HTTP_PART_TYPE Type;
} IOT_HTTP_PARA;
Structure members
Member | Description |
sKey | Internal: Pointer to key name or NULL |
sValue | Internal: Pointer to part value or NULL |
Type | Internal: Part type |
Additional information
All fields in this structure are private.
IOT_HTTP_PART_TYPE
Description
Individual parts of an HTTP (or other) request.
Type definition
typedef enum {
IOT_HTTP_PART_TYPE_SCHEME,
IOT_HTTP_PART_TYPE_USER,
IOT_HTTP_PART_TYPE_PASSWORD,
IOT_HTTP_PART_TYPE_HOST,
IOT_HTTP_PART_TYPE_PATH,
IOT_HTTP_PART_TYPE_QUERY,
IOT_HTTP_PART_TYPE_FRAGMENT,
IOT_HTTP_PART_TYPE_METHOD,
IOT_HTTP_PART_TYPE_HEADER,
IOT_HTTP_PART_TYPE_SIGNED_HEADER,
IOT_HTTP_PART_TYPE_CONTENT
} IOT_HTTP_PART_TYPE;
Enumeration constants
Constant | Description |
IOT_HTTP_PART_TYPE_SCHEME | Scheme type (http, https…) |
IOT_HTTP_PART_TYPE_USER | User part (e.g. for ftp scheme) |
IOT_HTTP_PART_TYPE_PASSWORD | Password part (e.g. for ftp scheme) |
IOT_HTTP_PART_TYPE_HOST | Host part |
IOT_HTTP_PART_TYPE_PATH | Path fragment |
IOT_HTTP_PART_TYPE_QUERY | Query parameter (introduced by “?”) |
IOT_HTTP_PART_TYPE_FRAGMENT | Fragment part of URI (introduced by “#”) |
IOT_HTTP_PART_TYPE_METHOD | Method part of request (GET, POST…) |
IOT_HTTP_PART_TYPE_HEADER | Request key-value header |
IOT_HTTP_PART_TYPE_SIGNED_HEADER | Request header takes part in signature |
IOT_HTTP_PART_TYPE_CONTENT | Entity body content |
IOT_HTTP_AUTH_DIGEST_PARAS
Description
Parameters required for digest authentication.
Type definition
typedef struct {
U32 NonceCount;
char aUserName[];
char aPassword[];
char aAlgorithm[];
char aRealm[];
char aDomain[];
char aNonce[];
char aCnonce[];
char aOpaque[];
char aQop[];
char aStale[];
char aCharset[];
char aUserhash[];
U8 aHashUserName[];
} IOT_HTTP_AUTH_DIGEST_PARAS;
Structure members
Member | Description |
NonceCount | Outgoing request nonce count (set by client) |
aUserName | Plaintext user name (set by client) |
aPassword | Plaintext password (set by client) |
aAlgorithm | Digest algorithm (set by server) |
aRealm | Authentication realm (set by server) |
aDomain | Domain (set by server) |
aNonce | Server nonce (set by server) |
aCnonce | Client nonce (set by client) |
aOpaque | Opaque value (set by server) |
aQop | Quality of protection (set by server and client) |
aStale | Flag indicating stale nonce (set by server) |
aCharset | Character set in use (set by server) |
aUserhash | Hashed user names supported (set by server and client) |
aHashUserName | Internal: Hashed user name (computed by IoT Toolkit) |
Callbacks
Description
HTTP response header callback.
Type definition
typedef void IOT_HTTP_HEADER_CALLBACK_FUNC( IOT_HTTP_CONTEXT * pContext,
const char * sKey,
const char * sValue);
Parameters
Parameter | Description |
pContext | Pointer to HTTP context. |
sKey | Zero-terminated header key. |
sValue | Zero-terminated header value. |
Additional information
A header callback is invoked through IOT_HTTP_ProcessHeaders(),
with each header in the HTTP response invoking a header callback.
Description
HTTP response header callback, extended.
Type definition
typedef void (IOT_HTTP_HEADER_EX_CALLBACK_FUNC)( IOT_HTTP_CONTEXT * pContext,
const char * sKey,
const char * sValue,
void * pUserCtx);
Parameters
Parameter | Description |
pHttpCtx | Pointer to HTTP context. |
sKey | Zero-terminated header key. |
sValue | Zero-terminated header value. |
pUserCtx | Pointer to user-provided context. |
Additional information
A header callback is invoked through IOT_HTTP_ProcessHeadersEx(),
with each header in the HTTP response invoking a header callback
and passing through a user-provided context.
IOT_HTTP_ENUMERATE_CALLBACK_FUNC
Description
HTTP request header enumeration callback.
Type definition
typedef void (IOT_HTTP_ENUMERATE_CALLBACK_FUNC)( IOT_HTTP_CONTEXT * pContext,
const char * sKey,
const char * sValue,
unsigned Index);
Parameters
Parameter | Description |
pContext | Pointer to HTTP context. |
sKey | Zero-terminated header key. |
sValue | Zero-terminated header value. |
Index | Zero-based index of header parameter. |
Additional information
A header enumeration callback is invoked through IOT_HTTP_EnumerateParas(),
with each parameter in the HTTP request invoking a header callback.
IOT_HTTP_AUTH_FUNC
Description
Write authorization header.
Type definition
typedef void (IOT_HTTP_AUTH_FUNC)(IOT_HTTP_CONTEXT * pContext);
Parameters
Parameter | Description |
pContext | Pointer to HTTP context. |
Additional information
This function is registered by IOT_HTTP_SetAuth() and is
invoked when the request is executed in order to construct
and send any authorization header computed over the HTTP
parameters.
The table below lists the functions that return HTTP Client information.
IOT_HTTP_GetErrorText()
Description
Get description of HTTP error.
Prototype
char *IOT_HTTP_GetErrorText(int Status);
Parameters
Parameter | Description |
Status | Return code from HTTP API function. |
Return value
Zero-terminated error description.
IOT_HTTP_GetVersionText()
Description
Get HTTP Client version as printable string.
Prototype
char *IOT_HTTP_GetVersionText(void);
Return value
Zero-terminated version string.
IOT_HTTP_GetCopyrightText()
Description
Get HTTP Client copyright as printable string.
Prototype
char *IOT_HTTP_GetCopyrightText(void);
Return value
Zero-terminated copyright string.
Request functions
IOT_HTTP_AddContent()
Description
Add content request.
Prototype
void IOT_HTTP_AddContent( IOT_HTTP_CONTEXT * pSelf,
const char * sContent);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sContent | Pointer to zero-terminated content string. |
IOT_HTTP_AddFragment()
Description
Add fragment to request.
Prototype
void IOT_HTTP_AddFragment( IOT_HTTP_CONTEXT * pSelf,
const char * sText);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sText | Pointer to fragment text. |
Description
Add header to request.
Prototype
void IOT_HTTP_AddHeader( IOT_HTTP_CONTEXT * pSelf,
const char * sKey,
const char * sValue);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sKey | Pointer to string that is the key. |
sValue | Pointer to string that is the value. |
Additional information
If the value is NULL, the header is not added.
IOT_HTTP_AddMethod()
Description
Add method to request.
Prototype
void IOT_HTTP_AddMethod( IOT_HTTP_CONTEXT * pSelf,
const char * sPath);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sPath | Pointer to method to add, e.g. “GET”. |
Additional information
Only one method should be added to the HTTP context
per request.
Methods defined for HTTP according to RFC 2626 are
CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT,
and TRACE.
IOT_HTTP_AddHost()
Description
Add host name to request.
Prototype
void IOT_HTTP_AddHost( IOT_HTTP_CONTEXT * pSelf,
const char * sHost);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sHost | Pointer to host name. |
IOT_HTTP_AddQuery()
Description
Add query to request.
Prototype
void IOT_HTTP_AddQuery( IOT_HTTP_CONTEXT * pSelf,
const char * sKey,
const char * sValue);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sKey | Pointer to string that is the key. |
sValue | Pointer to string that is the value. |
IOT_HTTP_AddUser()
Description
Add user name to request.
Prototype
void IOT_HTTP_AddUser( IOT_HTTP_CONTEXT * pSelf,
const char * sUser);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sUser | Pointer to user name. |
Additional information
The user name is not part of an HTTP request but is provided
as a convenience for other schemes (such as FTP or Telnet) where
user names and passwords are valid.
IOT_HTTP_AddPassword()
Description
Add password to request.
Prototype
void IOT_HTTP_AddPassword( IOT_HTTP_CONTEXT * pSelf,
const char * sPass);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sPass | Pointer to password. |
Additional information
The password is not part of an HTTP request but is provided
as a convenience for other schemes (such as FTP or Telnet) where
user names and passwords are valid.
IOT_HTTP_AddPath()
Description
Add path fragment to request.
Prototype
void IOT_HTTP_AddPath( IOT_HTTP_CONTEXT * pSelf,
const char * sPath);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sPath | Pointer to path fragment to add. |
IOT_HTTP_AddScheme()
Description
Add scheme to request.
Prototype
void IOT_HTTP_AddScheme( IOT_HTTP_CONTEXT * pSelf,
const char * sScheme);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sScheme | Pointer to scheme name. |
Description
Add signed header to request.
Prototype
void IOT_HTTP_AddSignedHeader( IOT_HTTP_CONTEXT * pSelf,
const char * sKey,
const char * sValue);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sKey | Pointer to string that is the key. |
sValue | Pointer to string that is the value. |
IOT_HTTP_Connect()
Description
Make HTTP connection.
Prototype
int IOT_HTTP_Connect(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Return value
≥ 0 | Success. |
< 0 | Processing error. |
IOT_HTTP_Disconnect()
Description
Close HTTP connection.
Prototype
void IOT_HTTP_Disconnect(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
IOT_HTTP_EnumerateParas()
Description
Enumerate request components.
Prototype
void IOT_HTTP_EnumerateParas(IOT_HTTP_CONTEXT * pSelf,
IOT_HTTP_PART_TYPE Type,
IOT_HTTP_ENUMERATE_CALLBACK_FUNC * pfCallback);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Type | Component type to match. |
pfCallback | Pointer to function for each matched component. |
IOT_HTTP_Exec()
Description
Execute request.
Prototype
int IOT_HTTP_Exec(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Return value
≥ 0 | Success. |
< 0 | Processing error. |
IOT_HTTP_ExecWithAuth()
Description
Execute request with authorization.
Prototype
int IOT_HTTP_ExecWithAuth(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Return value
≥ 0 | Success. |
< 0 | Processing error. |
IOT_HTTP_ExecWithBearer()
Description
Execute request with bearer token.
Prototype
int IOT_HTTP_ExecWithBearer( IOT_HTTP_CONTEXT * pSelf,
const char * sToken);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sToken | Pointer to bearer token. |
Return value
≥ 0 | Success. |
< 0 | Processing error. |
Description
Get header, by key, for request.
Prototype
char *IOT_HTTP_GetHeader( IOT_HTTP_CONTEXT * pSelf,
const char * sKey);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sKey | Pointer to string that is the key. |
Return value
= NULL | Header key not found. |
≠ NULL | Pointer to value associated with key. |
Additional information
The letter case of the key is not significant when matching,
so the keys “Content-Length” and “content-length” are
considered identical.
IOT_HTTP_GetMethod()
Description
Get method for request.
Prototype
char *IOT_HTTP_GetMethod(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Return value
= NULL | No method set. |
≠ NULL | Pointer to method. |
IOT_HTTP_GetBegin()
Description
Start receiving response payload.
Prototype
void IOT_HTTP_GetBegin(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
IOT_HTTP_GetEnd()
Description
Finish receiving response payload.
Prototype
void IOT_HTTP_GetEnd(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
IOT_HTTP_GetHost()
Description
Get host name for request.
Prototype
char *IOT_HTTP_GetHost(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Return value
= NULL | No host name set. |
≠ NULL | Pointer to host name. |
IOT_HTTP_GetPath()
Description
Get path.
Prototype
void IOT_HTTP_GetPath(IOT_HTTP_CONTEXT * pSelf,
char * sText,
unsigned TextLen);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sText | Pointer to object that receives the path. |
TextLen | Size of the object that receives the path. |
Additional information
Concatenates all added path fragments to form the entire path.
IOT_HTTP_GetContentLen()
Description
Get declared content length.
Prototype
unsigned IOT_HTTP_GetContentLen(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Return value
= 0 | No Content-Length header or content length is zero. |
≠ 0 | Content length declared by the Content-Length header. |
Additional information
This must be called after processing the headers as this is
where the content length header is parsed. If the
IOT_HTTP_GetPayload()
Description
Receive response payload.
Prototype
int IOT_HTTP_GetPayload(IOT_HTTP_CONTEXT * pSelf,
void * pData,
unsigned DataLen);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
pData | Pointer to object that receives the data. |
DataLen | Nonzero octet length of the object that receives the data. |
Return value
> 0 | Number of octets received. |
= 0 | Payload delivery complete. |
< 0 | Processing error, e.g. socket unexpectedly closed. |
Additional information
The payload can be read piecemeal until no more remains,
irrespective of the transfer encoding (chunked or identity).
For HTTP/1.1 connections using a chunked transfer encoding, this
function manages the chunking of the data at the transport
level and only delivers de-chunked data.
For HTTP/1.0 or identity transfer encodings, this function will
return data read directly from the transport.
IOT_HTTP_GetPort()
Description
Get port for request.
Prototype
unsigned IOT_HTTP_GetPort(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Return value
Port number in host byte order.
IOT_HTTP_GetUser()
Description
Get user name for request.
Prototype
char *IOT_HTTP_GetUser(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Return value
= NULL | No user name set. |
≠ NULL | Pointer to user name. |
IOT_HTTP_GetPassword()
Description
Get password for request.
Prototype
char *IOT_HTTP_GetPassword(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Return value
= NULL | No password set. |
≠ NULL | Pointer to password. |
IOT_HTTP_GetQuery()
Description
Get query, by key, for request.
Prototype
char *IOT_HTTP_GetQuery( IOT_HTTP_CONTEXT * pSelf,
const char * sKey);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sKey | Pointer to string that is the key. |
Return value
= NULL | Query key not found. |
≠ NULL | Pointer to value associated with key. |
Additional information
The letter case of the key is not significant when matching,
so the keys “Param” and “param” are considered identical.
IOT_HTTP_GetScheme()
Description
Get scheme name for request.
Prototype
char *IOT_HTTP_GetScheme(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Return value
= NULL | No scheme name set. |
≠ NULL | Pointer to scheme name. |
IOT_HTTP_Init()
Description
Initialize request.
Prototype
void IOT_HTTP_Init(IOT_HTTP_CONTEXT * pSelf,
void * pBuf,
unsigned BufLen,
IOT_HTTP_PARA * pPara,
unsigned ParaLen);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
pBuf | Pointer to working buffer. |
BufLen | Size of working buffer. |
pPara | Pointer to working HTTP parameter array. |
ParaLen | Number of elements in working HTTP parameter array. |
IOT_HTTP_ParseURL()
Description
Parse a URL for the HTTP(S) scheme.
Prototype
void IOT_HTTP_ParseURL(IOT_HTTP_CONTEXT * pContext,
char * sText);
Parameters
Parameter | Description |
pContext | Pointer to HTTP request context. |
sText | Pointer to zero-terminated URL. |
IOT_HTTP_ProcessStatusLine()
Description
Process HTTP response status line.
Prototype
int IOT_HTTP_ProcessStatusLine(IOT_HTTP_CONTEXT * pSelf,
unsigned * pStatusCode);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
pStatusCode | Pointer to object that receives the status code. |
Return value
≥ 0 | Success. |
< 0 | Processing error. |
Description
Process HTTP headers.
Prototype
int IOT_HTTP_ProcessHeaders(IOT_HTTP_CONTEXT * pSelf,
IOT_HTTP_HEADER_CALLBACK_FUNC * pfCallback);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
pfCallback | Pointer to header callback function. |
Return value
≥ 0 | Success. |
< 0 | Processing error. |
Description
Process HTTP headers, extended.
Prototype
int IOT_HTTP_ProcessHeadersEx(IOT_HTTP_CONTEXT * pSelf,
IOT_HTTP_HEADER_EX_CALLBACK_FUNC * pfCallback,
void * pUserContext);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
pfCallback | Pointer to header callback function. |
pUserContext | Pointer to user context passed through to callback. |
Return value
≥ 0 | Success. |
< 0 | Processing error. |
IOT_HTTP_PutBegin()
Description
Start sending request payload.
Prototype
void IOT_HTTP_PutBegin(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
IOT_HTTP_PutEnd()
Description
Finish sending request payload.
Prototype
void IOT_HTTP_PutEnd(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
IOT_HTTP_PutPayload()
Description
Send request payload.
Prototype
int IOT_HTTP_PutPayload( IOT_HTTP_CONTEXT * pSelf,
const void * pData,
unsigned DataLen);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
pData | Pointer to object to send. |
DataLen | Nonzero octet length of object to send. |
Return value
> 0 | Number of octets received. |
= 0 | Socket closed gracefully. |
< 0 | Processing error, e.g. socket unexpectedly closed. |
IOT_HTTP_QueryURL()
Description
Query encoded URL.
Prototype
int IOT_HTTP_QueryURL(IOT_HTTP_CONTEXT * pSelf,
char * pURL,
unsigned URLLen);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
pURL | Pointer to object that receives the URL. |
URLLen | Capacity of the object that receives the URL. |
Return value
≥ 0 | Success. |
< 0 | Processing error (buffer too small). |
IOT_HTTP_Recv()
Description
Receive raw data from connection.
Prototype
int IOT_HTTP_Recv(IOT_HTTP_CONTEXT * pSelf,
void * pData,
unsigned DataLen);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
pData | Pointer to object that receives the data. |
DataLen | Nonzero octet length of the object that receives the data. |
Return value
> 0 | Number of octets received. |
= 0 | Socket closed gracefully. |
< 0 | Processing error, e.g. socket unexpectedly closed. |
IOT_HTTP_Reset()
Description
Reset request and clear parameters.
Prototype
void IOT_HTTP_Reset(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
IOT_HTTP_Send()
Description
Send raw data to connection.
Prototype
int IOT_HTTP_Send( IOT_HTTP_CONTEXT * pSelf,
const void * pData,
unsigned DataLen);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
pData | Pointer to object that contains the data. |
DataLen | Nonzero octet length of the object that contains the data. |
Return value
> 0 | Number of octets sent. |
= 0 | Socket closed gracefully. |
< 0 | Processing error, e.g. socket unexpectedly closed. |
IOT_HTTP_SendHex()
Description
Send hex-encoded octet string to connection.
Prototype
int IOT_HTTP_SendHex( IOT_HTTP_CONTEXT * pSelf,
const U8 * pData,
unsigned DataLen);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
pData | Pointer to octet string to send. |
DataLen | Octet length of the octet string. |
Return value
> 0 | Success. |
= 0 | Socket closed gracefully. |
< 0 | Processing error, e.g. socket unexpectedly closed. |
IOT_HTTP_SendStr()
Description
Send zero-terminated string to connection.
Prototype
int IOT_HTTP_SendStr( IOT_HTTP_CONTEXT * pSelf,
const char * sText);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
sText | String to send. |
Return value
> 0 | Number of octets sent. |
= 0 | Socket closed gracefully. |
< 0 | Processing error, e.g. socket unexpectedly closed. |
IOT_HTTP_SetAuth()
Description
Set authorization method.
Prototype
void IOT_HTTP_SetAuth(IOT_HTTP_CONTEXT * pSelf,
IOT_HTTP_AUTH_FUNC * pfWrAuth,
void * pContext);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
pfWrAuth | Pointer to function that writes the authorization header. |
pContext | Pointer to authorization context passed to pfWrAuth. |
IOT_HTTP_SetIO()
Description
Set transport I/O.
Prototype
void IOT_HTTP_SetIO( IOT_HTTP_CONTEXT * pSelf,
const IOT_IO_API * pAPI,
void * pContext);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
pAPI | Pointer to I/O API. |
pContext | Pointer to context passed to I/O API. |
IOT_HTTP_SetPort()
Description
Set port for request.
Prototype
void IOT_HTTP_SetPort(IOT_HTTP_CONTEXT * pSelf,
unsigned Port);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Port | TCP port in host byte order. |
IOT_HTTP_SetVersion()
Description
Set HTTP version for request.
Prototype
void IOT_HTTP_SetVersion( IOT_HTTP_CONTEXT * pSelf,
const IOT_HTTP_VERSION_API * pAPI);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
pAPI | Pointer to HTTP API corresponding to the HTTP version the request uses. |
Authentication functions (Add-On)
IOT_HTTP_AUTH_DIGEST_InitParas()
Description
Initialize digest authentication parameters.
Prototype
void IOT_HTTP_AUTH_DIGEST_InitParas(IOT_HTTP_AUTH_DIGEST_PARAS * pParas);
Parameters
Parameter | Description |
pParas | Pointer to digest authenication parameters. |
Additional information
All strings are emptied and the nonce count is set to 1.
IOT_HTTP_AUTH_DIGEST_ParseParas()
Description
Parse digest authentication parameters.
Prototype
int IOT_HTTP_AUTH_DIGEST_ParseParas(const char * sText,
IOT_HTTP_AUTH_DIGEST_PARAS * pParas);
Parameters
Parameter | Description |
sText | Pointer to digest authentication header. |
pParas | Pointer to object that receives the parsed parameters. |
Return value
≥ 0 | Success. |
< 0 | Processing error. |
Additional information
The text to be parsed is presented in the WWW-Authenticate
header. The scheme is identified as “Digest” the parameters
appear immediately after this text.
Description
Write digest authorization header, MD5 algorithm.
Prototype
void IOT_HTTP_AUTH_DIGEST_MD5_WrHeader(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Description
Write digest authorization header, SHA-256 algorithm.
Prototype
void IOT_HTTP_AUTH_DIGEST_SHA256_WrHeader(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Description
Write digest authorization header, SHA-512/256 algorithm.
Prototype
void IOT_HTTP_AUTH_DIGEST_SHA512_256_WrHeader(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Description
Write digest authorization header, RFC 7616 SHA-512/256 algorithm.
Prototype
void IOT_HTTP_AUTH_DIGEST_RFC7616_SHA512_256_WrHeader(IOT_HTTP_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to HTTP context. |
Additional information
RFC 7616 examples use a SHA-512/256 algorithm that truncates
the SHA-512 output to 256 bits. This function implements that
algorithm and enables passing test vectors that are given
in RFC 7616.
For a FIPS-compliant SHA-512/256 where the SHA-512 output is
truncates to 256 bits with the correct FIPS-compliant IV,
use IOT_HTTP_AUTH_DIGEST_SHA512_256_WrHeader().
JSON parser
This section describes the API of the JSON parser.
Preprocessor symbols
JSON parser errors
Description
Errors that the JSON parser can generate.
Definition
#define IOT_JSON_ERROR_OUT_OF_MEMORY (-800)
#define IOT_JSON_ERROR_BUFFER_TOO_SMALL (-801)
#define IOT_JSON_ERROR_ILLEGAL_CODE_POINT (-802)
#define IOT_JSON_ERROR_ILLEGAL_ESCAPE (-803)
#define IOT_JSON_ERROR_BAD_STRUCTURE (-804)
#define IOT_JSON_ERROR_EXPECTED_VALUE (-805)
#define IOT_JSON_ERROR_EXPECTED_QUOTATION (-806)
#define IOT_JSON_ERROR_EXPECTED_COLON (-807)
#define IOT_JSON_ERROR_EXPECTED_RBRACK (-808)
#define IOT_JSON_ERROR_EXPECTED_RBRACE (-809)
#define IOT_JSON_ERROR_INCOMPLETE (-810)
#define IOT_JSON_ERROR_UNKNOWN_ID (-811)
Symbols
Definition | Description |
IOT_JSON_ERROR_OUT_OF_MEMORY | JSON tree parser ran out of memory |
IOT_JSON_ERROR_BUFFER_TOO_SMALL | Lexical buffer is to small |
IOT_JSON_ERROR_ILLEGAL_CODE_POINT | Code point outside basic multilingual plane |
IOT_JSON_ERROR_ILLEGAL_ESCAPE | Illegal escape sequence |
IOT_JSON_ERROR_BAD_STRUCTURE | Value is structured incorrectly |
IOT_JSON_ERROR_EXPECTED_VALUE | Syntax error: expected a value |
IOT_JSON_ERROR_EXPECTED_QUOTATION | Syntax error: expected a quotation mark |
IOT_JSON_ERROR_EXPECTED_COLON | Syntax error: expected a colon |
IOT_JSON_ERROR_EXPECTED_RBRACK | Syntax error: expected a comma or close bracket |
IOT_JSON_ERROR_EXPECTED_RBRACE | Syntax error: expected a comma or close brace |
IOT_JSON_ERROR_INCOMPLETE | End of input but expected more data |
IOT_JSON_ERROR_UNKNOWN_ID | Literal ID is not “true”, “false”, or “null” |
Data types
IOT_JSON_VOID_EVENT_FUNC
Description
JSON event, no parameter.
Type definition
typedef void (IOT_JSON_VOID_EVENT_FUNC)(IOT_JSON_CONTEXT * pContext);
Parameters
Parameter | Description |
pContext | Pointer to user-supplied context. |
Additional information
Function prototype for an event without a parameter.
IOT_JSON_ARG_EVENT_FUNC
Description
JSON event, string parameter.
Type definition
typedef void (IOT_JSON_ARG_EVENT_FUNC)( IOT_JSON_CONTEXT * pContext,
const char * sText);
Parameters
Parameter | Description |
pContext | Pointer to user-supplied context. |
sText | Pointer to zero-terminated string argument. |
Additional information
Function prototype for an event with a zero-terminated string
parameter.
IOT_JSON_EVENTS
Description
JSON event handlers.
Type definition
typedef struct {
IOT_JSON_VOID_EVENT_FUNC * pfBeginObject;
IOT_JSON_VOID_EVENT_FUNC * pfEndObject;
IOT_JSON_VOID_EVENT_FUNC * pfBeginArray;
IOT_JSON_VOID_EVENT_FUNC * pfEndArray;
IOT_JSON_ARG_EVENT_FUNC * pfKey;
IOT_JSON_ARG_EVENT_FUNC * pfString;
IOT_JSON_ARG_EVENT_FUNC * pfNumber;
IOT_JSON_ARG_EVENT_FUNC * pfLiteral;
} IOT_JSON_EVENTS;
Structure members
IOT_JSON_TYPE
Description
JSON value type.
Type definition
typedef enum {
IOT_JSON_TYPE_OBJECT,
IOT_JSON_TYPE_ARRAY,
IOT_JSON_TYPE_PAIR,
IOT_JSON_TYPE_STRING,
IOT_JSON_TYPE_NUMBER,
IOT_JSON_TYPE_TRUE,
IOT_JSON_TYPE_FALSE,
IOT_JSON_TYPE_NULL
} IOT_JSON_TYPE;
Enumeration constants
Constant | Description |
IOT_JSON_TYPE_OBJECT | Node is an object |
IOT_JSON_TYPE_ARRAY | Node is an array |
IOT_JSON_TYPE_PAIR | Node is a mapping pair |
IOT_JSON_TYPE_STRING | Node is a string |
IOT_JSON_TYPE_NUMBER | Node is a number |
IOT_JSON_TYPE_TRUE | Node is ’true’ |
IOT_JSON_TYPE_FALSE | Node is ’false’ |
IOT_JSON_TYPE_NULL | Node is ’null’ |
Information functions
The table below lists the functions that return JSON Parser information.
IOT_JSON_GetErrorText()
Description
Get description of JSON error.
Prototype
char *IOT_JSON_GetErrorText(int Status);
Parameters
Parameter | Description |
Status | Return code from JSON API function. |
Return value
Zero-terminated error description.
IOT_JSON_GetVersionText()
Description
Get JSON Parser version as printable string.
Prototype
char *IOT_JSON_GetVersionText(void);
Return value
Zero-terminated version string.
IOT_JSON_GetCopyrightText()
Description
Get JSON Parser copyright as printable string.
Prototype
char *IOT_JSON_GetCopyrightText(void);
Return value
Zero-terminated copyright string.
Event-based functions
IOT_JSON_Init()
Description
Initialize the JSON parser.
Prototype
void IOT_JSON_Init( IOT_JSON_CONTEXT * pSelf,
const IOT_JSON_EVENTS * pEvents,
char * pBuf,
unsigned BufLen);
Parameters
Parameter | Description |
pSelf | Pointer to JSON context. |
pEvents | Pointer to handlers for JSON parser events. |
pBuf | Pointer to working buffer for token construction. |
BufLen | Capacity of token construction buffer. |
IOT_JSON_Parse()
Description
Incrementally parse a JSON stream.
Prototype
int IOT_JSON_Parse( IOT_JSON_CONTEXT * pSelf,
const char * pBuf,
unsigned BufLen);
Parameters
Parameter | Description |
pSelf | Pointer to JSON context. |
pBuf | Source text to parse. |
BufLen | Length of source text to parse. |
Return value
= 0 | Parse complete without error. |
< 0 | Parse complete with error (syntax error or token buffer overflow). |
> 0 | Incomplete parse, call IOT_JSON_Parse() again with additional text. |
IOT_JSON_SetUserContext()
Description
Set user context.
Prototype
void IOT_JSON_SetUserContext(IOT_JSON_CONTEXT * pSelf,
void * pUserContext);
Parameters
Parameter | Description |
pSelf | Pointer to JSON context. |
pUserContext | Pointer to user context. |
IOT_JSON_GetUserContext()
Description
Get user context.
Prototype
void *IOT_JSON_GetUserContext(const IOT_JSON_CONTEXT * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to JSON context. |
Return value
Pointer to user context, NULL if not set.
Tree-based functions
IOT_JSON_ParseTree()
Description
Parse JSON value to tree.
Prototype
int IOT_JSON_ParseTree( IOT_JSON_NODE ** ppTree,
const char * pBuf,
unsigned BufLen,
SEGGER_MEM_CONTEXT * pMem);
Parameters
Parameter | Description |
ppTree | Pointer to object that receives the parsed value. |
pBuf | Pointer to octet string to parse. |
BufLen | Octet length of the octet string to parse. |
pMem | Pointer to memory allocator context for tree construction. |
Return value
≥ 0 | Parsed without error. |
< 0 | Error status indication. |
IOT_JSON_FreeTree()
Description
Free memory allocated to entire tree.
Prototype
void IOT_JSON_FreeTree(IOT_JSON_NODE * pSelf,
SEGGER_MEM_CONTEXT * pMem);
Parameters
Parameter | Description |
pSelf | Pointer to JSON tree (can be NULL). |
pMem | Pointer to memory allocator context for tree construction. |
IOT_JSON_IsNull()
Description
Is JSON value null?
Prototype
int IOT_JSON_IsNull(IOT_JSON_NODE * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value. |
Return value
≠ 0 | Object is the null JSON value or pSelf is the NULL pointer. |
= 0 | Object is not null. |
IOT_JSON_IsNumber()
Description
Is JSON value a number?
Prototype
int IOT_JSON_IsNumber(IOT_JSON_NODE * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value. |
Return value
≠ 0 | Object is a number. |
= 0 | Object is not a number. |
IOT_JSON_IsTrue()
Description
Is JSON value true?
Prototype
int IOT_JSON_IsTrue(IOT_JSON_NODE * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value. |
Return value
≠ 0 | Object is true. |
= 0 | Object is not true. |
IOT_JSON_IsFalse()
Description
Is JSON value false?
Prototype
int IOT_JSON_IsFalse(IOT_JSON_NODE * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value. |
Return value
≠ 0 | Object is flase. |
= 0 | Object is not false. |
IOT_JSON_IsString()
Description
Is JSON value a string?
Prototype
int IOT_JSON_IsString(IOT_JSON_NODE * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value. |
Return value
≠ 0 | Object is a string. |
= 0 | Object is not a string. |
IOT_JSON_IsObject()
Description
Is JSON value an object?
Prototype
int IOT_JSON_IsObject(IOT_JSON_NODE * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value. |
Return value
≠ 0 | Object is an object. |
= 0 | Object is not an object. |
IOT_JSON_IsArray()
Description
Is JSON value an array?
Prototype
int IOT_JSON_IsArray(IOT_JSON_NODE * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value. |
Return value
≠ 0 | Object is an array. |
= 0 | Object is not an array. |
IOT_JSON_GetType()
Description
Get type of JSON value.
Prototype
IOT_JSON_TYPE IOT_JSON_GetType(IOT_JSON_NODE * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value. |
Return value
≠ 0 | Object is the null JSON value or pSelf is the NULL pointer. |
= 0 | Object is not null. |
IOT_JSON_GetInteger()
Description
Get integer value of node.
Prototype
int IOT_JSON_GetInteger(IOT_JSON_NODE * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value (can be NULL). |
Return value
For number nodes, returns the number converted (by truncation)
to an integer. For non-number nodes, returns zero.
IOT_JSON_GetString()
Description
Get zero-terminated UTF-8 octet string.
Prototype
char *IOT_JSON_GetString(IOT_JSON_NODE * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value. |
Return value
The empty string if the JSON value is not a string,
or the UTF-8 octet string is the JSON value is a string.
IOT_JSON_IndexNode()
Description
Index JSON array.
Prototype
IOT_JSON_NODE *IOT_JSON_IndexNode(IOT_JSON_NODE * pSelf,
unsigned Index);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value. |
Index | Zero-based index. |
Return value
= NULL | The JSON value is not an array or is NULL, or the index Index lies outiside of the JSON array. |
≠ NULL | The JSON value at the index position. |
Additional information
As a convenience, if you index a non-array value, IOT_JSON_IndexNode()
returns a NULL pointer. All APIs that take a JSON value will
accept a NULL pointer and have documented behavior for this case.
IOT_JSON_SelectNode()
Description
Select field of JSON object.
Prototype
IOT_JSON_NODE *IOT_JSON_SelectNode( IOT_JSON_NODE * pSelf,
const char * sKey);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value to select from (can be NULL). |
sKey | Pointer to zero-terminated key name. |
Return value
= NULL | JSON value pSelf is not a JSON object or is NULL, or key sKey is not present in the JSON object pSelf. |
≠ NULL | Pointer to selected key value. |
IOT_JSON_CopyString()
Description
Copy string data from value.
Prototype
void IOT_JSON_CopyString(IOT_JSON_NODE * pSelf,
char * sKey,
unsigned KeyLen);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value to copy form (can be NULL). |
sKey | Pointer to object that receives zero-terminated string. |
KeyLen | Octet length of the object that receives zero-terminated string. |
Additional information
The string is always zero terminated even if the source string is
longer than the object that receives the copy. Copying from a non-string
value results in an empty string.
IOT_JSON_UpperBound()
Description
Get upper index bound of JSON array.
Prototype
unsigned IOT_JSON_UpperBound(IOT_JSON_NODE * pSelf);
Parameters
Parameter | Description |
pSelf | Pointer to JSON value (can be NULL). |
Return value
For array nodes, returns the number of elements in the array.
For non-array nodes, returns zero.