Introducing DBus-ASIO
CC Attribution 2.0 Belgium:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor.
Goodwin, Steven
Modern software needs inter-process communication. Often through DBus. But the state of the existing libraries are more perfunctory, than active. Consequently, the limits of these libraries become apparent when you go beyond a single thread, are limited to standard libraries, or use anything other than a classical event loop. In this talk we cover the journey, from the realisation of the problem, the guiding development tenets, to the implementation of a library that covers everything from the low-level protocol and high-level C++ interface. We start with a breakdown on the whys and wherefores of the choices made, be it C++11 or C++14, to fork or not to fork existing code, or whether boost::asio should be used. We continue with a discussion on the implementation details, such as the pitfalls in serial protocols, zero-length strings, non-aligned data, and the problems in C++ over C. We end up with production-quality code, the politics of naming, and a question: will this be the final DBus library we need to write? Background: D-Bus is an inter-process mechanism that allows communication between multiple programs running on the same machine. Asio is a cross-platform C++ library for network and low-level I/O programming. This talk covers how and why the former was implemented using the latter.
 So, as that part of the slide says, my name is Steve, and as that part of the slide says, I'm a geek. As you can probably tell from my voice, my accent is English. I am a European, despite what the government is saying. So, that joke doesn't work in the UK for some reason. So, if we're all in the right talk, this is about debuzz-azio, which means I'll start off by mentioning what debuzz is, what azio is, and at which point you should be able to work out what debuzz-azio is all about. I'll talk about the existing libraries that solve the same problem, and then why we actually built the whole thing again. So, debuzz is an inter-process tool. You have a debuzz-damant that sits here that runs when the system starts. You have an application that talks to the debuzz-library. It serializes a function call to the library. That debuzz-damant then sends it off to another application, and then the result is returned via debuzz-damant to your other application. This example is the first one I always play with. It allows me to control the screensaver programmatically. Not big, not clever, but it is kind of useful. I know someone's got a chip in their arm that when they walk away from their machine, it automatically locks the screensaver with this. So, that's debuzz. Just communication. Nothing more. Azio is a library that does asynchronous communication, generally within sort of networks and things like that. So, it's kind of useful for I.O. programming. It's also part of Boost, but we'll come to that later. Therefore, debuzz.azio is a library that allows you to serialize a function call that communicates to the debuzz-damant before passing on to another application. And as you can probably guess, it already exists. There are lots of libraries that do this, but as always, they're never quite what you need. It doesn't quite match the use case. So, the question is, well, what is our use case? So, our use case are these things. That's a bright sign player. It's a embedded Linux box, and it's used for media, playing video files, HD, 4K files, that sort of thing. Well, this is what we build, and it's embedded Linux, and it's got lots of processes, and therefore has to work multi-threaded. A lot of the libraries seem to have problems with threads. Not quite sure why. We've got a million of these things in the field. They're running 24 hours a day, seven days a week. Therefore, if you only have a race condition in a multi-threaded application, go one once every 10,000 hours. Normally on a desktop, that's fine. When you've got this many things, it's going to happen fairly regularly, except when you're actually trying to test to debug it. It's also embedded. We've got 32 and 64-bit platforms. We want as few dependencies as possible. Even though it is an embedded platform, and it's quite a big platform, we still don't want to bring in a whole load of libraries just to facilitate the communications. It's why one of the things like QtBus was not available. We were trying to remove dependencies rather than put them back in. As I said, it's an embedded Linux box, so we've got a whole load of open source software, not that it's important for this, but it is important for our license compatibility. The license has to be good as well. You've got the standard three options. You either take an existing project and work on that. You've got to talk to people in the project team. You've got to learn the code base. You fork it because you don't want to talk to the developers. Or you just say, no, going from scratch, I'm going to build this myself. It turned out that this was the option that we had to take. Why we built it? We started off because we needed it. There was too many bugs coming at random points because of race conditions because of threading. We said, right, well, that's our use case. That's all we're going to build. Nothing fancy, nothing clever. We're just doing the bare minimum we need. Which language should we use? I put go at the top because go is the one I really wanted to use properly in anger. It turns out no one else in the company does go. While for some people it's like this is a great career move, this makes sure no one ever can ever replace me because I'm the only go person in the company. Unfortunately, when you take that approach, you're the only go person in the company which means you never get to do anything else or anything interesting. You're stuck maintaining your own little projects. So it's like, not that. It's going to have to be C++ and we'll take version 14. We're not quite bleeding edge enough to 17. So 14 is a happy medium. And not there's anything wrong with C, by the way. I did start my career as a C programmer and there is nothing wrong with this code. This is recently decent code. It locks everything. It unlocks everything. It checks for null pointers coming back from memory allocation. It unlocked. But I don't want to do all of that when the language will do all that for me, via exceptions and so forth. So it was almost a no-brainer at that point. It's C++ 14. We could have gone 11 and we might have picked up some legacy users who are on 11 code bases. Unfortunately that would add to their tech debt as well as ours maintaining an older version of the spec. So straight forward to version 14. Which async library did we use? You already know that. It's in the title. It's in the video. Chose them because it is actually fairly decent and it is by Boost. Boost has quite a good history of coming up with nice specifications that are taken by the C++ standard and then folded into the next version of the standard. So adopting things with Boost seems to be a good win for future compatibilities. Another design decision we had to make. It's not about the bright side. Even though this is a company project, it's open source and we don't want to have any of the bright side stuff in it. These books have been made for the last 12, 15 years or something like that. Which means we've got a very good logging system which I don't want to use. We've got all this kind of streaming stuff. I don't want to use it. Sockets code. I don't want to use any of that. The simple reason, if I'm saying we can't be using QtBus or this Dbus library because it brings extra dependencies, it would be rather hypocritical if I then started in throwing a whole load of dependencies from our existing code base. So didn't do that. But it turns out that writing little bits of logging and streaming code isn't actually that complex or takes any real time. So two words on the API compatibility. We didn't do any. Who cares? It's a new thing. If you want to use new clever C++ things, you'll use the new APIs. So you'll have when you make a call to Dbus and it goes through to another application, the result comes back, you want it coming back in a callback but not the callback that was called and trendy 20 years ago in C. You want lambdas because they're new and cool. So you're going to have to use the new API anyway so we might as well make a clean break and not waste time back implementing old APIs. So the methods we built it is simple. You lock me in a room for three weeks and I churn out code. I know this quotes about turns coffee into code. I turn beer into code. It works much better. So the first step was pre-production. It's the same as you do in the film industry. You start in the film industry, you draw a whole load of boards and you have a little picture for every scene you're going to have. Here I read through the spec. It is quite long. It isn't too dull but I read through it a couple of times just so that in my head I could see things like, well, if I build it this way, I've suddenly realized that page 50 of the spec says this thing happens. So therefore when I do this, I don't do that. It's one of the few times I don't follow the agile approach of just build the very first bit. So I know there'll be something later on to trip me up. I did a lot of stuff with D-Bust Send. So obviously D-Bust exists as a protocol and has existed for protocol for about 15 years. So there were various tools like D-Bust Send and Defeat that I was able to use to look at how the methods were serialized to the D-Bust Damon and how the results were serialized back from the other application. Also I used SoCat for the first time. I don't know if anyone has played with that at the time. It is what the name says. It's a socket concatenation thing. And you can just look at all the bytes flowing through from the D-Bust Damon to your application and back again. This was an absolute godsend because by doing this I could look at all the data going forward. It's like, well, spec says it's this. I might have interpreting that spec correctly. I look at the bytes coming through SoCat and yes, I am, which is a very good time saver. So first things first, I built an MVP that did nothing but say hello to the Damon. This isn't me trying to be clever. The very first thing you have to send to the D-Bust Damon as an application that wishes to use D-Bust is the word hello. If you don't say hello, it will throw you off. It will close the socket and say bye-bye, be more polite next time. So I wrote a thing that said hello to the Damon. I sent the messages that I'd already grabbed from SoCat and I just sent them as big binary blobs and it worked. I thought, great. I then started building up that binary blob programmatically saying I know what this calls. This is calling a lock command to the screensaver. I'm going to then program this in. I'd written that so I could write a line of code that would lock the screensaver in my world. That was really great. I then deleted, oh yeah, I did tests. If the boss asks, I did tests. What I then did is I deleted all of this and I started again. Why? Well, you just do. You learn various things about getting stuff up and you think there's a few bits of crofty rubbish in here. I don't want that going out. So I'll start again knowing what I know now. I can do a better version next time. So I think the code is a lot better. It also means when the boss saw my very first implementation, it was actually my second, which means they now think I'm slightly better than I really am. Well I started building it for real. I started with threads. I knew we had to do threads. Threads was one of the driving factors to this. So I made sure that the threads were in there at the very first day of coding. It helps because it means they're going to get tested for the longest. And I know that's where problems are going to come in. So I started everything with threads. It's the same as if you do security. You don't add security as a feature at the end of the product unless you're doing IoT stuff, I guess. But it's meant to be there at the ground level. Everything is based on security. Everything here was based on the threads. I use callbacks with landers because I like landers. And it's easier to send data than it is to receive. That might sound kind of obvious if you've spoken a foreign language. You'll realize that it's much easier to repeat something the teacher has told you than actually understand what someone is saying. With the reading of data, it's really, really difficult because we're dealing with serial data here. And serial data, as we know, is boop, boop, boop. Currently, if you're pushing some serial data out and you make a mistake, you're not going to realize until it actually crashes. There's no real security or protection for you reading too many or too few bytes of serial data. So you get the first message in. You then get the second message, which you've badly parsed. You then get the third message, which says, I've got a string, which is 4 billion characters long. And then your program stops here, not here, where the error occurred. So there's a lot of things you have to do there. And the simple data types point is because D-Bus allows you to send integers and strings and arrays. Because it's serial, you can't just jump over a block you don't understand. So you have to get a lot of things in place before you can even run the simplest of tests when it comes to reading the data in. The problems, as we say, the serial protocol, they put padding on the spec, which is great. We like padding. It means it's nice and fast when it gets into CPU land. And there is always the question of, if I've got a string that is 0 bytes, does this mean I now, because it's already padded to 4 bytes, if the string is 0, it's already aligned. So does that mean I add 0 bytes of padding or 4 bytes of padding? And I found that out by the hard way by trial and error. It also means there's various other side issues where you're trying to do padding and you're like, well, I've got 3 bytes. I've got to get the next one in as padding. Variable data types. Yeah, the fun thing with C++ being a strongly typed language is when you've got a protocol such as this which supports any data types, you can't just say, well, I'll cast it to a void pointer and then I'll just do something clever later on. So I discovered the thing in Boost called Boost Any, which essentially allows you to do data types as a union. So the same variable can be a string or an integer. Or array, for that matter. And going back to what I said earlier, things in Boost tend to find their way into the standard. Halfway between writing the first line of this talk and the last line of this talk, turns out Boost Any is already in the standard. It's in the 17 version. So again, it was a good choice to make these sort of things future compatible. And here is an example of the code running. It's pretty simple. You open up a socket to the debug statement, a file run. You do your basic authentication, which is the only one that's ever really supported. You make the hello, I'm here method call, and then you just hook onto the network. Marshalling is what they call serialization. So when you have a method call that you serialize to send to the debug statement, in the debug world, that's called marshalling and un-marshalling. Schedule-wise, it took me four weeks. I said three weeks earlier that was three weeks of work, one week of meetings, emails, status reports, all that sort of, you know, going for lunch, all the auxiliary things you have to do to support yourself as a developer. Only around six weeks of reviewing casually here and there. Excuse me. This is the practical reason. Much nicer than water. As you can see from the last joke, far too long working out the names. I wanted to call it depot because then it would be debust depot. But judging from your reaction, it was probably right not to call it that. So we went with debust asio as sort of a mindshader say, we have asio here. So conclusions. Sometimes you do actually have to rewrite things. It's not very open-sourced. We don't need or should be rewriting, reinventing the wheel every time. But every now and again, you get to the point of, well, we just have to. It is going to be quicker and more effective in the long run. We had a lot of good solid use cases because we got the players. We know what we're going to be sending as far as debust messages go. We chose the proper language. Step by step debugging comes first. So whenever I write code, I never run it. I just step through it in the debugger to make sure it's doing what I think it's actually meant to be doing. It takes a little longer to get to the first version, but when you get to that version, you think, I'm pretty sure this is right. And writing protocols is easier than reading them. So that's it. I've got 12 questions, seconds for questions, and I'm just going to update my scorecard. There we go. 19 false stems. Makes me feel old. But thank you. So I've got a question, a four seconds, three second question. You've got two seconds to answer a question. Any? I'm afraid it's a bit too late now, but if you want to tell people a way to find you, they can ask you. Yeah, I'll be outside. So if anyone's got any questions, call me then. Thank you. Thank you. Thank you.
 So, as that part of the slide says, my name is Steve, and as that part of the slide says,  I'm a geek.  As you can probably tell from my voice, my accent is English.  I am a European, despite what the government is saying.  So, that joke doesn't work in the UK for some reason.  So, if we're all in the right talk, this is about debuzz-azio, which means I'll start  off by mentioning what debuzz is, what azio is, and at which point you should be able  to work out what debuzz-azio is all about.  I'll talk about the existing libraries that solve the same problem, and then why we actually  built the whole thing again.  So, debuzz is an inter-process tool.  You have a debuzz-damant that sits here that runs when the system starts.  You have an application that talks to the debuzz-library.  It serializes a function call to the library.  That debuzz-damant then sends it off to another application, and then the result is returned  via debuzz-damant to your other application.  This example is the first one I always play with.  It allows me to control the screensaver programmatically.  Not big, not clever, but it is kind of useful.  I know someone's got a chip in their arm that when they walk away from their machine, it  automatically locks the screensaver with this.  So, that's debuzz.  Just communication.  Nothing more.  Azio is a library that does asynchronous communication, generally within sort of networks and things  like that.  So, it's kind of useful for I.O. programming.  It's also part of Boost, but we'll come to that later.  Therefore, debuzz.azio is a library that allows you to serialize a function call that communicates  to the debuzz-damant before passing on to another application.  And as you can probably guess, it already exists.  There are lots of libraries that do this, but as always, they're never quite what you need.  It doesn't quite match the use case.  So, the question is, well, what is our use case?  So, our use case are these things.  That's a bright sign player.  It's a embedded Linux box, and it's used for media, playing video files, HD, 4K files,  that sort of thing.  Well, this is what we build, and it's embedded Linux, and it's got lots of processes, and  therefore has to work multi-threaded.  A lot of the libraries seem to have problems with threads.  Not quite sure why.  We've got a million of these things in the field.  They're running 24 hours a day, seven days a week.  Therefore, if you only have a race condition in a multi-threaded application, go one once  every 10,000 hours.  Normally on a desktop, that's fine.  When you've got this many things, it's going to happen fairly regularly, except when you're  actually trying to test to debug it.  It's also embedded.  We've got 32 and 64-bit platforms.  We want as few dependencies as possible.  Even though it is an embedded platform, and it's quite a big platform, we still don't  want to bring in a whole load of libraries just to facilitate the communications.  It's why one of the things like QtBus was not available.  We were trying to remove dependencies rather than put them back in.  As I said, it's an embedded Linux box, so we've got a whole load of open source software,  not that it's important for this, but it is important for our license compatibility.  The license has to be good as well.  You've got the standard three options.  You either take an existing project and work on that.  You've got to talk to people in the project team.  You've got to learn the code base.  You fork it because you don't want to talk to the developers.  Or you just say, no, going from scratch, I'm going to build this myself.  It turned out that this was the option that we had to take.  Why we built it?  We started off because we needed it.  There was too many bugs coming at random points because of race conditions because of threading.  We said, right, well, that's our use case.  That's all we're going to build.  Nothing fancy, nothing clever.  We're just doing the bare minimum we need.  Which language should we use?  I put go at the top because go is the one I really wanted to use properly in anger.  It turns out no one else in the company does go.  While for some people it's like this is a great career move, this makes sure no one  ever can ever replace me because I'm the only go person in the company.  Unfortunately, when you take that approach, you're the only go person in the company which  means you never get to do anything else or anything interesting.  You're stuck maintaining your own little projects.  So it's like, not that.  It's going to have to be C++ and we'll take version 14.  We're not quite bleeding edge enough to 17.  So 14 is a happy medium.  And not there's anything wrong with C, by the way.  I did start my career as a C programmer and there is nothing wrong with this code.  This is recently decent code.  It locks everything.  It unlocks everything.  It checks for null pointers coming back from memory allocation.  It unlocked.  But I don't want to do all of that when the language will do all that for me, via exceptions  and so forth.  So it was almost a no-brainer at that point.  It's C++ 14.  We could have gone 11 and we might have picked up some legacy users who are on 11 code bases.  Unfortunately that would add to their tech debt as well as ours maintaining an older  version of the spec.  So straight forward to version 14.  Which async library did we use?  You already know that.  It's in the title.  It's in the video.  Chose them because it is actually fairly decent and it is by Boost.  Boost has quite a good history of coming up with nice specifications that are taken by  the C++ standard and then folded into the next version of the standard.  So adopting things with Boost seems to be a good win for future compatibilities.  Another design decision we had to make.  It's not about the bright side.  Even though this is a company project, it's open source and we don't want to have any  of the bright side stuff in it.  These books have been made for the last 12, 15 years or something like that.  Which means we've got a very good logging system which I don't want to use.  We've got all this kind of streaming stuff.  I don't want to use it.  Sockets code.  I don't want to use any of that.  The simple reason, if I'm saying we can't be using QtBus or this Dbus library because  it brings extra dependencies, it would be rather hypocritical if I then started in throwing  a whole load of dependencies from our existing code base.  So didn't do that.  But it turns out that writing little bits of logging and streaming code isn't actually  that complex or takes any real time.  So two words on the API compatibility.  We didn't do any.  Who cares?  It's a new thing.  If you want to use new clever C++ things, you'll use the new APIs.  So you'll have when you make a call to Dbus and it goes through to another application,  the result comes back, you want it coming back in a callback but not the callback that  was called and trendy 20 years ago in C. You want lambdas because they're new and cool.  So you're going to have to use the new API anyway so we might as well make a clean break  and not waste time back implementing old APIs.  So the methods we built it is simple.  You lock me in a room for three weeks and I churn out code.  I know this quotes about turns coffee into code.  I turn beer into code.  It works much better.  So the first step was pre-production.  It's the same as you do in the film industry.  You start in the film industry, you draw a whole load of boards and you have a little  picture for every scene you're going to have.  Here I read through the spec.  It is quite long.  It isn't too dull but I read through it a couple of times just so that in my head I  could see things like, well, if I build it this way, I've suddenly realized that page  50 of the spec says this thing happens.  So therefore when I do this, I don't do that.  It's one of the few times I don't follow the agile approach of just build the very first  bit.  So I know there'll be something later on to trip me up.  I did a lot of stuff with D-Bust Send.  So obviously D-Bust exists as a protocol and has existed for protocol for about 15 years.  So there were various tools like D-Bust Send and Defeat that I was able to use to look at  how the methods were serialized to the D-Bust Damon and how the results were serialized  back from the other application.  Also I used SoCat for the first time.  I don't know if anyone has played with that at the time.  It is what the name says.  It's a socket concatenation thing.  And you can just look at all the bytes flowing through from the D-Bust Damon to your application  and back again.  This was an absolute godsend because by doing this I could look at all the data going forward.  It's like, well, spec says it's this.  I might have interpreting that spec correctly.  I look at the bytes coming through SoCat and yes, I am, which is a very good time saver.  So first things first, I built an MVP that did nothing but say hello to the Damon.  This isn't me trying to be clever.  The very first thing you have to send to the D-Bust Damon as an application that wishes  to use D-Bust is the word hello.  If you don't say hello, it will throw you off.  It will close the socket and say bye-bye, be more polite next time.  So I wrote a thing that said hello to the Damon.  I sent the messages that I'd already grabbed from SoCat and I just sent them as big binary  blobs and it worked.  I thought, great.  I then started building up that binary blob programmatically saying I know what this calls.  This is calling a lock command to the screensaver.  I'm going to then program this in.  I'd written that so I could write a line of code that would lock the screensaver in my  world.  That was really great.  I then deleted, oh yeah, I did tests.  If the boss asks, I did tests.  What I then did is I deleted all of this and I started again.  Why?  Well, you just do.  You learn various things about getting stuff up and you think there's a few bits of crofty  rubbish in here.  I don't want that going out.  So I'll start again knowing what I know now.  I can do a better version next time.  So I think the code is a lot better.  It also means when the boss saw my very first implementation, it was actually my second,  which means they now think I'm slightly better than I really am.  Well I started building it for real.  I started with threads.  I knew we had to do threads.  Threads was one of the driving factors to this.  So I made sure that the threads were in there at the very first day of coding.  It helps because it means they're going to get tested for the longest.  And I know that's where problems are going to come in.  So I started everything with threads.  It's the same as if you do security.  You don't add security as a feature at the end of the product unless you're doing IoT  stuff, I guess.  But it's meant to be there at the ground level.  Everything is based on security.  Everything here was based on the threads.  I use callbacks with landers because I like landers.  And it's easier to send data than it is to receive.  That might sound kind of obvious if you've spoken a foreign language.  You'll realize that it's much easier to repeat something the teacher has told you than actually  understand what someone is saying.  With the reading of data, it's really, really difficult because we're dealing with serial  data here.  And serial data, as we know, is boop, boop, boop.  Currently, if you're pushing some serial data out and you make a mistake, you're not going  to realize until it actually crashes.  There's no real security or protection for you reading too many or too few bytes of serial  data.  So you get the first message in.  You then get the second message, which you've badly parsed.  You then get the third message, which says, I've got a string, which is 4 billion characters  long.  And then your program stops here, not here, where the error occurred.  So there's a lot of things you have to do there.  And the simple data types point is because D-Bus allows you to send integers and strings  and arrays.  Because it's serial, you can't just jump over a block you don't understand.  So you have to get a lot of things in place before you can even run the simplest of tests  when it comes to reading the data in.  The problems, as we say, the serial protocol, they put padding on the spec, which is great.  We like padding.  It means it's nice and fast when it gets into CPU land.  And there is always the question of, if I've got a string that is 0 bytes, does this mean  I now, because it's already padded to 4 bytes, if the string is 0, it's already aligned.  So does that mean I add 0 bytes of padding or 4 bytes of padding?  And I found that out by the hard way by trial and error.  It also means there's various other side issues where you're trying to do padding and you're  like, well, I've got 3 bytes.  I've got to get the next one in as padding.  Variable data types.  Yeah, the fun thing with C++ being a strongly typed language is when you've got a protocol  such as this which supports any data types, you can't just say, well, I'll cast it to  a void pointer and then I'll just do something clever later on.  So I discovered the thing in Boost called Boost Any, which essentially allows you to  do data types as a union.  So the same variable can be a string or an integer.  Or array, for that matter.  And going back to what I said earlier, things in Boost tend to find their way into the standard.  Halfway between writing the first line of this talk and the last line of this talk,  turns out Boost Any is already in the standard.  It's in the 17 version.  So again, it was a good choice to make these sort of things future compatible.  And here is an example of the code running.  It's pretty simple.  You open up a socket to the debug statement, a file run.  You do your basic authentication, which is the only one that's ever really supported.  You make the hello, I'm here method call, and then you just hook onto the network.  Marshalling is what they call serialization.  So when you have a method call that you serialize to send to the debug statement, in the debug  world, that's called marshalling and un-marshalling.  Schedule-wise, it took me four weeks.  I said three weeks earlier that was three weeks of work, one week of meetings, emails,  status reports, all that sort of, you know, going for lunch, all the auxiliary things  you have to do to support yourself as a developer.  Only around six weeks of reviewing casually here and there.  Excuse me.  This is the practical reason.  Much nicer than water.  As you can see from the last joke, far too long working out the names.  I wanted to call it depot because then it would be debust depot.  But judging from your reaction, it was probably right not to call it that.  So we went with debust asio as sort of a mindshader say, we have asio here.  So conclusions.  Sometimes you do actually have to rewrite things.  It's not very open-sourced.  We don't need or should be rewriting, reinventing the wheel every time.  But every now and again, you get to the point of, well, we just have to.  It is going to be quicker and more effective in the long run.  We had a lot of good solid use cases because we got the players.  We know what we're going to be sending as far as debust messages go.  We chose the proper language.  Step by step debugging comes first.  So whenever I write code, I never run it.  I just step through it in the debugger to make sure it's doing what I think it's actually  meant to be doing.  It takes a little longer to get to the first version, but when you get to that version,  you think, I'm pretty sure this is right.  And writing protocols is easier than reading them.  So that's it.  I've got 12 questions, seconds for questions, and I'm just going to update my scorecard.  There we go.  19 false stems.  Makes me feel old.  But thank you.  So I've got a question, a four seconds, three second question.  You've got two seconds to answer a question.  Any?  I'm afraid it's a bit too late now, but if you want to tell people a way to find you,  they can ask you.  Yeah, I'll be outside.  So if anyone's got any questions, call me then.  Thank you.  Thank you.  Thank you.
Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_0_179.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_180_635.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_636_683.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_684_731.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_732_923.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_924_1847.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_1848_2147.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_2148_2495.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_2496_2795.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_2796_4691.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_4692_5183.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_5184_6635.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_6636_7175.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_7176_7319.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_7320_7811.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_7812_8303.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_8304_9347.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_9348_10499.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_10500_12599.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_12600_19439.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_19440_20135.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_20136_21227.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_21228_22403.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_22404_23171.png Introducing_DBus-ASIO_How_and_why_we_built_a_new_D-Bus_library_from_the_ground_up_LVxPs0MONl0_23172_23285.png