"All problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections."
David Wheeler
A great quote and a good intro to this blog post! We want to give you an insight into the Internal Networking Interface (INI) of Mongoose Embedded Web Server.
The background
Originally, Mongoose used a BSD-socket API and lot of socket functions and a non-blocking mode. That meant, it was portable only to platforms which supported this API. It worked somewhat for those platforms supporting sockets. But, we found that most platforms do support in their own way. Hardware SDK authors “forget” the select function. Also they like to combine select and accept functions. This made our original BSD-socket API solution difficult to use. We had two options to fix it:
- Do not port to platforms that don’t simply support a full sockets API
- Implement any missing functions for each platform
Heroic moves
Option one isn’t really the heroic way of doing things, right? ? We love complexity.
The first microchip Mongoose was ported to was Arduino (Mega). And, it was implemented using second way. We developed a BSD-sockets layer on top of the Arduino Socket Library. It worked fine with some hiccups.
The next platform was ESP8266. Here, we had to decide what we really wanted to achieve. We started to work with ESP8266 a long time ago. There wasn’t a RTOS SDK with LWIP (and BSD sockets). Also, we wanted to port Mongoose to so many more embedded platforms and were beginning to understand, that most of them would have problems with BSD sockets.
It was a time to split Mongoose Embedded Web Server into protocol level and network level. Yes, David Wheeler, time to add another level of indirection.
Planning our Internal Networking Interface
Our Internal Networking Interface (INI) was supposed to
- be connections (not socket!) oriented — Mongoose is connection oriented, so, the usage of a connections oriented interface should be more simple.
- support both TCP and UDP (the rest of supported protocols are based on these two).
- be async, callback based.
- be simple!
You’ve guessed it, the most problematic point was: be simple. Our conundrum:
On the one hand, it is easy to design a hard to use interface, but then who is going to use it?
On the other hand, Mongoose has so many features, including network related features, it was important to find a balance between functionality and simplicity.
As result, the Mongoose Internal Networking Interface has ~20 functions.
Overview of the INI
There are several groups of functions in the INI API:
- Connections management
- Data send and receive
- Client functions
- Server functions
- Small set of utility functions.
Most functions (except the connections management functions) have two versions: one for TCP and one for UDP.
Last but not least: there is a set of callbacks. Our INI has a callback based interface. In short, that means, that functions, like connect return intermediately and invoke a callback. Then, a connection is established (or an error occurs).
The layers interactions scheme is quite traditional: application level protocols use a transport protocols API, while transport protocols use the INI implementations to communicate with the platform (or device) specific API.
At this moment, we have 3 implementations of the INI:
- BSD Sockets
- LWIP
- SimpleLink (Texas Instruments CC3100/C3200)
A specific implementation of the INI is selected at compile time by defining the CS_PLATFORM macros (this macros can be specified manually, using the compiler option, or a set of #ifdef #else in the Mongoose code will try to do it for you).
How it works
The specific INI implementation must implement a set of functions to create connections, send data, receive it etc. The core calls these functions to create the connections, send data etc. If the INI wants to deliver any kind of information to the core, it invokes a callback implemented mostly in Mongoose Core rather than in the INI, i.e. the INI implementor should not provide the implementation of such functions (in most cases they name are ended with _cb). However, the INI should implement one callback as well (mg_if_recved, see below).
Here is small diagram of the execution sequence:
Functions
Connections management functions:
Most of the activities related to connections management are performed by Mongoose itself, so the INI has functions to fulfill platform specific actions. For example, the “Socket INI” creates sockets in its implementation of mg_if_create_conn.
- mg_if_create_conn
- mg_if_destroy_conn
- mg_close_conn
Client and server functions:
- mg_if_connect_tcp
- mg_if_connect_udp
Once a connection is established, the mg_if_connect_cb function should be invoked.
- mg_if_listen_tcp
- mg_if_listen_udp
- mg_if_accept_new_conn
Then the INI accepts the new connection and it should invoke mg_if_accept_tcp_cb
Send and receive functions:
mg_if_tcp_send, mg_if_udp_send and corresponding callback mg_if_sent_cb
On receive, the INI should invoke the callbacks mg_if_recv_tcp_cb and mg_if_recv_udp_cb. If the core acknowledges consumption, it calls mg_if_recved.
Mission accomplished?
Did we achieve what we set out to do? Here a reminder of what we wanted:
Our Internal Networking Interfaces (INI) was supposed to be
- be connections (not socket!) oriented — Mongoose is connection oriented, so, the usage of a connections oriented interface should be more simple.
- support both TCP and UDP (the rest of supported protocols are based on these two).
- be async, callback based.
- be simple!
We think we got it. There is now a way to port Mongoose to different platforms. It may not always be super simple, but it is possible and we don’t need to create shortcuts and things like that to make it happen.
A look into the future, we are planning to port Mongoose to even more embedded platforms. So we’ll end up with 5–6 different INI implementations. Stay tuned for it!
To contact: send us a message or ask on the developer forum.