 All right, for again, good morning everyone. My name is Leandro, and I'll be talking today about implementing network protocols in the Zephyr project. So before I begin a little bit about me, I've been doing open source work since 1999. I do have a few personal projects you might have heard of, such as Hard Info and L1, which is a web server. And I've been a full-time engineer since 2010. Since then I've been working on many different projects, including Enlightment, WebKit, Android, and a few others. I've recently joined the Zephyr project just last year, and I did some work on the TCP stack. And this talk was going to be given by a co-worker, which unfortunately couldn't be here today. So yeah, so also a little bit about Zephyr. As you might know, it's a microcontroller operating system with a very small footprint. It works with only 8K. It's an open source under Apache 2 license and hosted by Linux Foundation, and supports multiple architectures. We have some demos and a few booths upstairs, so if you'd like to check it out. So what I'm going to present today is basically this. We're going to talk a little bit about the new AP stack on Zephyr, and talk a little bit about the currently implemented network protocols, so application protocols, and some of the patterns that are used in these protocols. Please bear in mind that this is not an advanced talk. It's more like a beginner-to-intermediate talk, so I will not dive deep into details. But of course, you can ask more complicated questions at the end, and I'll be happy to take them. So as you might know, Zephyr has a new IP stack. So until recently, it used a micro IP from Contiki, but it had some problems, such as not supporting simultaneous IPv6 and IPv4, and supporting only one network interface. There's two things alone, we're a deal breaker, and one of the reasons that made us actually write a new one from the scratch. Moreover, it was ported to Zephyr in a way that didn't fit well with the system. So some parts of Contiki had to be ported over, and some of the changes that has been made it unstable and sort of unfixable. So we decided to rewrite it from the scratch. And now we support move point interfaces, simultaneous IPv6 and IPv4. If it fits very well with the rest of the system, including the buffer management, which is very important, and it's available since about 1.6 and 1.7, which is going to be released tomorrow, we'll enable TCP as well. So about multiple interfaces. So as with many other things in Zephyr, you can configure that at compile time. So use kconfig and select everything you need. And you can use, of course, Ethernet Bluetooth, ZigBase, LIP for testing. And soon we'll have also Wi-Fi. And of course, you can mix and match all of the interface types. And of course, you can have simultaneous IPv6 and IPv4. On this table, what you can see that basically, if you enable IPv6, there is not much need to not enable IPv4. There's just one kilobyte or about that of code that's in the text section. So it's still very tiny. But even then, we were talking about near microcontrollers that has like a megabyte of flash or lots of flash memories. So it's not much of a head to enable both at the same time. Another characteristic of Zephyr is that it is heapless. So you don't have any malloc or free or anything that is similar inside it. But we need to, at runtime, allocate in the allocated memory. So we have some memory pools. And as for network stack, we have separate pools for transmitting, receiving, and data fragments. And of course, in our TAS, we have timeouts and things from the pool. So you can wait in that way, or you can just wait for a few milliseconds. So in order to facilitate the memory allocation, we have fragment chains. So instead of allocating a single linear buffer that can fit everything in a single contiguous space, we allocate fragments of 128 bytes. And they form a sort of a link at the list or a tree that can build it actually however you want. And of course, can add and remove things and whatnot. So this makes it actually easy to implement protocols where you have data fragments and then headers. So the user only provides you the data they want to send, but then you have to add headers. So just prepand a fragment with the headers and then maybe compact. So it really makes the implementation a whole lot easier. Compacting is pretty much like the fragmentation on disks. There are three fragments. You're using like 50% of each one of them. So just copy 50% of the second fragment to the first fragment, throw away the second fragment, get the next fragment. So you can reduce the amount of fragments that are in flight. For some cases, you can copy the install linear buffer in cases where you have like, I will talk about this, of course, but there are protocols where you actually have, it's easier to parse things and have a linear buffer that is pretty linear and not really have to have traversal link of this every single time and then you read a byte. So the clearly implemented network protocols. So implement a bunch of protocols. Some of them are related to IoT, which is the focus of the effort. Some are sort of collateral and some were implemented mostly as a proof of concept. Because it's always a good thing whenever implementing a new API to implement a proof of concept to see if you're bringing the API right or not. So an important thing on the internet is the DNS. The implementation we have has been implemented from scratch. It supports only A and 4A queries. Basically, if you give the domain a name, it can get the IP address back. A is for IPv4 and 4A is for IPv6. It can have concurrent queries and, of course, the number of queries is configurable. We use memory pools for the 512 by chunks that we use. So we linearize the buffers, the fragment buffers into a large buffer to be able to parse it. UDP, yeah, so for this version of the RFC, they recommend up to 512. Well, right. Well, it's not implemented. Yeah, and it is available as a part of the platform. This is one of the patterns that I'll be talking about later. Another thing is the HTTP. It is important. Many APIs are available through HTTP endpoints. We have a both client and server. You use the parser for nginx and supports HTTP1 and 1.1. But it is only available as a sample application because there are a few reasons I'll be talking about later. There is NATS as well. Also available as a sample application. NATS is a publisher and subscriber protocol. It's a very simple TCP-based protocol. You just subscribe to data. And whenever it's updated, you are notified. It's a line-based protocol that was also implemented from the scratch. There is MQ2T, also implemented from the scratch, and available as part of the platform. And it's also one of the asynchronous APIs we have. And there is CoApp. This has been implemented based on lessons, based on the Soleta project. I'm not sure if everyone here knows it. I believe last year we had some talks about it. So you can do results of the version and notifications. You support all of these methods. And it's also available as part of the platform. And we also have IRC, which is not really useful as an IoT protocol. But it's very easy to implement. And it was the first test case we had for long-living TCP connections. It is available, of course, as a sample application. And there is a bot that can run on boards. You can turn LEDs on and off from IRC commands. It's pretty cool. Check it out. So yeah, so some of the patterns that these protocols have regarding their implementation. So asynchronous APIs. So they often use it with request-less protocols, where information can be received at any time. So clients are not blocked while reading from the network. So some background work is being done for you. And whenever you need to receive something, it will be notified via callback functions. So we have some of those. We have the linearization of buffers. So we have, like, strikes that are larger than a fragment size of 120 bytes. We currently, the only thing we have right now that does this is the ask queries. So we don't use that because it's not optimal, right? Have to have memory pools for large buffers. They have to do unnecessary memory copies. And this might even go away in the future. But it is unnecessary. It's a necessary reveal at the moment. So we're using versus re-implementing. As you've seen, we've re-implemented a few of the protocols. So there are many libraries out there that implement these actually very well. But they are actually made to run on top of Linux. So they might use dynamic memory allocation. And although you can have that on Zephyr, we try to avoid these things as much as possible. They also assume a POSIX operating system. And although our network APIs sort of mimic some of the behaviors of POSIX, the APIs are different. And we don't offer yet a facade of API so that it can just plug in your code and it is going to work. So you have to have a small sham that does the conversion of APIs. And of course, it might have been written in C++, using like standard type of library and things like that, which are not really suitable for systems that have a few single digits kilobytes of RAM. And of course, but there's always caveats. I mean, existing implementation may be very mature and may work well and support a bunch of stuff, and including stuff you actually need to do your product with. And re-implementing is tricky. And because it's not going to be as mature as limitations that have been many years in the market, you're not leveraging all the possible years of development. On the other hand, it'll have lots of control and can have a tighter integration with the rest of the system. And luckily, most of the protocols we have implemented are actually very simple protocols. So we didn't waste too much time writing those things. Mostly because we actually don't need all the features. I mean, we're just writing an RTOS for HTTP, for instance. You just sometimes need to, I don't know, call an API endpoint. I'll give you your location or do something like that. Or for ASC, you just want to turn an LED on and off. So we really don't need all the features in a protocol to have it available on an RTOS. And there is the sample applications versus the platform APIs. So sample applications, we see them mostly as a task bad for APIs. So there are no API stability guarantees. And it's mostly as a demonstration purpose. And of course, they're not part of the IoT suite of protocol. So for instance, IRC is not one of those things. MQTT is. And on the other hand, we have the APIs that are available as part of the platform. So they have stable, more complete APIs that are suitable for production use. And mostly protocols that are actually part of the IoT suite, again, quotes, I didn't think there's such a thing. So yeah. So another thing is we sometimes have to have some compromises. So the NAS protocol requires the JSON parser. So whenever you connect to it, it sends you a JSON message. So most JSON parsers will create objects in memory, give you a tree of the JSON object in memory. And that's not only only is low. And you don't really have much control of the time memory it's going to take to allocate. So we wrote our own, which is 0 copy because directly to struct it is type safe, as in, again, quotes, because it's only going to decode a value if the type is correct, is expected for that particular value. And it is no temporary structs or anything. The kids directly to struct that you need. Of course, all the necessary things are implemented. So right now, no nested objects or arrays, although I have an implementation for that. And then I just have to test it more thoroughly. It is UTF-8 aware, but not strict. So if you feed it invalid UTF-8, it's not going to bet an eye. But the thing is this is mostly used for machine-to-machine communications where you're going to send tokens if they're only ASCII. So for your majority of use cases for the JSON parser, this is not going to be a problem. It doesn't support floating-pointing numbers. So if you have, say, well, it doesn't support that. And JSON with JavaScript, all numbers are floating points. So if you need that, you're not going to have with this. And of course, because of some limitation inside, you can only have up to 32 fields, which is actually, I believe, a very good compromise. So I'll show you an example when you understand this limitation in a minute. So speaking of implementations, here are some code samples. Cherry picked two only, as we don't have much time. And this is not really an advanced talk, but I figured I should show some of these things anyway. So can anyone see that? It's very light. Well, so this is how we define your co-app endpoints and how we define the things that are in gray. I'll show you a battery and the next slide, so you don't need to squint. So basically, first of all, these things here, or LED get, LED post, and putt, are going to be called whenever these methods, or you get a post or a putt, are called on that particular endpoint. So the path is defined as an array of strings. So all the path matching is then inside the co-app library. So you don't have to worry about that as well. So pretty simple. Makes a lot of things a lot easier to work with. So JSON parsing. So this is just an overview of the code. I'll go through it, so it's easier to understand. So basically, my struct here has an integer and a string. And there is a field macro just to help creating what I call the scriptors. They basically just tell in the my struct, there is a field number, which is a number, and there is a field named string that is a string. Just for you to tell the parser that whenever you see that particular field in that object, it must be that value if it is not the parsing files. And then you allocate space for your struct, and then tell the parser, hey, do your job. And it's going to do, let's parse it and do all the thing and decode it directly to that struct. So no memory copies for strings is just but it's terminating null and sets the pointer. It's pretty simple, pretty nice. And of course, the limitation for the 32-tube field, this is just this because the return value is a bit mask. So the first field in that case number, it's going to be the least significant bit. The second field is going to be the string because it's going to be the second least significant bit. So the two are set, both two fields were decoded correctly. If they're not set, they're not decoded. They were not found in the object. So you can use that field or not based on return value. So that's why you have only 32 values because it's a 32-bit integer return value. So yeah, wrapping up. So we saw some of the differences between the old and new IP stack. We've also seen some of the protocols currently implemented, some of the patterns used by the implementations and we also saw some code samples. So I'd like to make some questions. So what will you do differently? What protocols are you going to help us and implement on Zephyr? So with that, I open for questions or answers. So the question was if I plan to implement real-time protocols such as Modbus and other similar. I haven't seen any development on that. I haven't seen any talk about implementing these protocols yet. We don't yet have Wi-Fi, oh sorry, they have to repeat the question. So the question was do we already support Wi-Fi on Zephyr and the answer is not yet. It's planned of course, but we don't yet support Wi-Fi. I don't really know, unfortunately. So the question is, if I understood correctly, what are the protocols that are gonna be implemented to support Wi-Fi, is that, if I'm going to use, well, I didn't have the answer for that. I haven't worked on drivers or, implementing the connectivity part of this stack. So I really don't have the answer for that. But you can send me an email, I can follow up with these questions later, I can figure out. Okay, so about hardware support. So we support a few boards, and some of these boards already have Wi-Fi connectivity. Some of them do not have. But of course, they're added on our modules that might be supported in the future. It depends on people actually writing the drivers for them. That really depends on a bunch of things. So, question there? Yes? Did I consider Jasmine instead of just using a JSON parser? Actually, this parser has been written in like a day. So, I mean, it was really quickly to write it, and so I didn't even bother checking out all their parsers because I would have something that was really, really small. I mean, it's like 500, 400 lines of code, so it fits well within all the limitations we have at Zephyr. Question there? Well, so the question is how do we work with the project to implement our own protocol? Is that correct? Okay, so on the Zephyr web page, we have described very clearly how our process works. So it's very easy, just the wiki, pages with documentation for that. But in a nutshell, it's actually very simple. All the development is made in the open, have open-issue trackers and code repositories, all the reviews are made in the open. We have our C-channels, so mailing lists, can even propose things in mailing lists, for instance. Yeah, yeah, exactly, so it's pretty simple. We have another question here? Yeah, sorry, I haven't given up on it. It's okay. Yeah, so we're not working, what about it? So yeah, so we have support, actually, so we support multiple interfaces, and so Ethernet on board is supported, Bluetooth, low energy, and it's leap for testing. We use this leap mostly with KEMU, for instance, which is very handy. And I can mix and match, do these things. Have another question? Back and back? Okay, so these measurements here, it's not there, okay, so you're talking about small footprint, and wondering how big the code is, actually, for these kind of things. So I didn't have, this is not my computer here, so I can really just show you a measurement. But these are similar applications. This is an Echo server. This one has been built with both UDP and TCP support. So for x86, 32 bit. So you can see that this is the text section of all the code, so all is effort, so thread scheduling, drivers, everything fits in 12 kilobytes of code of both IPv6 and IPv4 enabled, x86, yes. The RAM, I don't have the figures here, and I don't really remember that, but the thing is, it's very configurable, so you can say, okay, my application's gonna make it only one connection at a time, so you can just say, okay, only one context will be used, so you can really have things very dumb, but it runs with single digits kilobytes, so nine kilobytes, sometimes even less, sometimes a little bit more, but with that working, I'd say you'd need a few double digits kilobytes around, all right? There's another question here. Okay, so the question was about test suites, you know how much time it takes to do many, many different tasks, so there is a test suite, and we actually run that every single time someone sends a patch, but I'm not sure if you have benchmarks such as, I don't remember. I don't think we have that. I can check it out, of course, always shoot my email, I can check it out and follow up, question there? Okay, so the question was is code is not being used, eliminated or in cooperation, compilation phase? Well, yes, however, there are some cases where, for instance, we have like structs with information, and in the application, I might not need that, so you can just say in the configuration you don't need these features, so these structs will be smaller because certain fields are not to be included in the code. So it can't really do these kinds of things automatically, but so that's why there are configuration for these. And of course, if functions are not used during compilation phase, actually linking phase, they're just thrown away, so the code ends up being smaller anyway. Any other question on the back? Okay, so about 6.0 versus 6.0 PAN, we do have 6.0 PAN, it is supported, but this code doesn't use it, so it's not included here. Have another question here? Right. Oh yeah. I could, but it's not my computer, so I really don't have the source code here. So yes, about, have to repeat a question, sorry. So about the buffer linearization API, is it part of core? Yes, it is part of the core. It's part of the net and buff API. There's what is called net and buff, L copy or something like that. Okay, right. Yes, okay, that's true. So APIs that we release are gonna stay there until they are deprecated. So these API, as far as I know, is not gonna be deprecated anytime soon, but it may go away if nobody uses that. And if we are gonna deprecate any API, this is gonna be notified. We have a cadence of deprecating API, so we release, okay, so this is gonna be deprecated, next release is not gonna be there and whatnot. Okay, well, that's fair. That's fair, yes, that is fair. Okay, I'll see what I can do about that. Can I promise anything? All right, another question there? Yes. Okay, so about how to look forward to API compatibility and things like that. Right, exactly, yeah. So, well, I will answer that with an example. So for Zephyr 1.6, we did a major restructuring of our kernel. So we had a few different modes of operation, had like nano-kernel, micro-kernels, and a few other things. So we unified, I have now what we call unified kernel, but this meant that some of the APIs that were available for users had to go away, but we couldn't just do that, the new version, hey, everything you did is gonna be thrown in the garbage. You can't really do that with our users. So what we do have is a compatibility layer, which is very, very thin. It's almost invisible, almost no-cast, that converts all the APIs to the new APIs. So we think about compatibility. This is important, so, but we do also provide the application, a deprecation strategy. So in one version it's deprecated, in the next it might go away. So people are actually notified when these things are gonna happen, so they can prepare themselves. And when we provide these compatibility layers, we try to do that as least amount of, yeah, but sometimes it doesn't even use a bit more. Sometimes it's just a macro that adapts from one function call to another function call. Sometimes there's this static inline function that's gonna be inlined by compiler anyway, it's not gonna make any difference in the end. Yeah, so. Yes. Yeah, I mean, so, you know, a lot of years you get out one at a time and one at a time, but, you know, on a type kind of a future, like these are things that we're interested in, sort of LTS thing, it's looking at a two-battle release, I don't know when and where, but that would be sometimes in the future. We're excited, that's why, we're excited about the project. Yeah. That's why we like to be able to, and that's something that we know when we get back to our information, we really will. Mm-hmm. We have to fight on it, but these are part of the main, that's why we say, we're listening. Thanks. Question in the back? Yes. So in the case you have a high level as a server application and you're able to bring in a new core, I could be sick. Yes. It's possible to retain compatibility at that case level, but what about, is there a notion of subs setting the IPv6 that's for smaller platforms versus having a bigger stack with more complete features that I guess I can say that I'm not going to respect to code, that's like saying that's one number, but there's such a big gradient of platforms and subsetting in some way to do like, look down on the asset housing, I'm just curious where, where is that for, I mean that's a, it's a bigger ability to allow code like a bigger code that's a production, you can be able to pick a two, but not all platforms would have strong IPv, like that six to four, or it's just like increasing your IPv6 platform so that differently or we're not interested in how, how you guys. Okay, so the question was about how fine-grained our configuration is. So have like a big feature, like PV6 can have like smaller versions of it, to just pick things you're really going to need. Well, they're really, in a general sense that really depends on the feature, some of them you set using the K-config tool, some of these are not as fine-grained. So yeah, it really depends on the feature. Right now, at least for IPv6, we just not really fine-grained. I would have to check on that, but I did believe we have like things like header compression is optional and a few other things are optional, but I'm not really sure about that. I'd have to check it out. Unfortunately, they have my code here, so I can really show on the screen, but I can follow up. It's easy, just shoot me an email, the, there's the address. Any question there? Okay, so what about BSD socket compatibility API? Yes, that's something we really want. So, although our APIs are different, for instance, all networking, send and receive calls are actually asynchronous. You can send, like, tell timeouts and a few other things. But in generally, most of the semantics are very, very similar to what we have in the BSD sockets API. So creating a shim that converts from our API to BSD sockets is not much work. So, for 1.7, unfortunately, no, because... What's the future? The future, yes. And I believe, actually, in your future, that's something we want to do. Is there any other question? In the back? So the question was more about how SEC vendors will keep up to date with the Zephyr development. So, well, I really didn't have an answer for that, but from what I've been seeing, all the development is made on the mainline branch. I mean, things are sometimes in its own branch, but then it's merged back to the master branch. So, when you pull the code, all the support code is there. Of course, that doesn't mean a SEC vendor A or B can't have their own tree, things like that. And there was also talk yesterday by Andy Gross on the device tree support. So, it's similar to what we have in Linux, but it's gonna be with another question. Okay, sorry, I believe we're done, but if you have any other questions that I wanna ask here, just send them email. I'd be happy to have a reply. Thanks.