Riot’s magical push notifications in iOS

A couple of months ago I started playing with Matrix, a federated communication/chat system. Rather than make an account on a public homeserver I installed Synapse on my own server. I installed the Riot app on my iPhone and connected directly to my server. (Synapse is the server and Riot is the client produced by the Matrix team.)

Everything worked well. A bit too well: I was getting push notifications in iOS when I received new messages. Push notifications are very handy but it was confusing. I knew that new messages were arriving at my self-hosted homeserver but there is no way that my server would be allowed to fire notifications into Apple’s push notification service.

It’s a big problem for any kind of federated network such as Matrix, GNU social or XMPP. The entire point is that you can have an account on any server while still participating in the network. If you want to support an iOS client it can’t get realtime notifications directly from the chosen server. Push notifications can only be sent from servers run by the app developer.

You can solve this by playing pass-the-parcel: your homeserver generates an event and sends it to the app developer’s server, who passes it on to Apple, who commands your iPhone to vibrate. This solves the problem nicely apart from some privacy issues which I’ll get to shortly.

But this creates a new problem. Imagine you want to write your own Matrix app for iOS, a free and open source one that you write in your free time. Your users will insist on having push notifications because it’s the 21st century. This requires you to run a push notification server. Even if you’re willing to do this it costs real money on an ongoing basis and it may be the only significant cost you have. You can’t just make it an in-app purchase because the App Store Review Guidelines explicitly forbid taking payments to enable Push Notifications.

If you want to recoup your costs you either have to charge for your app, which will reduce the take-up of the network you presumably want to support, or take a punt and hope nice users give you donations. (This doesn’t often work.)

What we really need is some benevolent company who will release a free iOS app and subsidise the cost of notifications for everybody while focusing on making money some other way. It sounds crazy but that seems to be exactly what Riot is doing.

The above diagram is straight from the Matrix specification. They’ve built mitigation for the push notification problem right into the design.

I have Riot installed on my iPhone and iPad. When I log on to the Riot web interface it shows that the installed apps have registered themselves as “Notification Targets” on my server.

Looking in the pushers table in my server database I can see that they have registered an HTTP pusher with the following URL:

This means when I need to receive a notification my homeserver will make an HTTP POST to that URL with a JSON message describing the content of the notification. The Matrix/Riot servers will take over from there and forward it to Apple.

So that Apple knows where to send it the message contains a pushkey. This comes from the iOS device itself. The app registers for push notifications and obtains an APNS Device Token. MatrixKit then takes this token and uploads it to the homeserver when it creates the HTTP pusher.

So that explains how my push notifications magically work. It’s pretty awesome. The only downside is a small loss of privacy. One of the nice things about federated networks is that you can take total control of your data security. Communications are decentralised, accessible only on the peers that need them. Supporting iOS notifications chips away at that ideal situation, exposing at the very least some metadata to various third parties. I point the blame for that squarely at Apple’s iron grip.

The good news is that it’s easy to limit the amount of information transmitted in the notifications. While the public channels include text snippets when I’m mentioned, if I’m in an encrypted chat it simply says Message from <username>. Apple and Riot may know that I’m talking to a particular person at a particular time (more specifically, that they are talking to me) but not what was said. This is roughly the same privacy you have when you use Signal for iOS. Solution: turn on encryption. According to Matrix’s blog post this will be the default for private rooms in the future.

It’s an interesting situation. On one hand I’m happy to see Riot break the impasse of federated notifications. On the other hand it only solves the problem for Matrix, and relying on the benevolence of a company to keep a technical solution operating doesn’t fill me with confidence. It’s not ideal but at the end of the day it’s considerably better than what you get using Facebook messenger or Slack so at this point I’m happy with the trade-off!

Posted in internet | 3 Comments

Making long transmissions behind Echolink’s back

Good news today! After various trials and errors, REAST’s new Raspberry Pi-based system transmitted both the WIA and VK7 regional news, driven by a web-based scheduler.

The Raspberry Pi is a combination Echolink/IRLP node so it already has an attached radio. The existing interface has PTT control, a sound card connected to the radio and the ability to check if the channel is occupied, an important feature. Channel occupation is not something you get with a basic DIY interface so it’s nice to take advantage of it.

The Echolink software itself is a large modular collection of binaries and shell scripts that invoke each other to get their job done. This makes it straightforward to tap in and automate transmissions by running shell commands.

So far so good. Any automated system just needs to run the right commands at the right time. The only trouble is that the files we need to transmit for our broadcast are about half an hour long. (They contain station identifications at several points so it’s legal but it’s a single continuous transmission.)

As I discovered, Echolink has extensive watchdog features to make sure the radio doesn’t activate PTT for more than 5 minutes. This appears to be a hard limit that is not controllable from the configuration. For the purposes of Echolink and IRLP that’s very reasonable but it’s a major inconvenience for our broadcasts!

The watchdog is somewhere inside the compiled binaries while the actual shutdown of the radio occurs in shell scripts. At first I tried modifying the shell scripts but this quickly turned into whack-a-mole as there are various ways it can fail. After a second interrupted broadcast I decided this wasn’t good enough—Echolink should be completely prevented from knowing that we’re transmitting.

I’m not really familiar with Echolink so I had a look around with strace. I found that the readinput command is tracking PTT, COS and DTMF codes by reading 9 of the Raspberry Pi’s GPIO pins. The most important ones are these:

What I really want to do is intercept the request every time Echolink tries to read one of these values. If we’re in the middle of a transmission from the automatic system we should report “yes the channel is occupied” and “no we’re not transmitting”. To Echolink it will appear that some other station is hogging the repeater. It won’t try to transmit anything and it will just sit tight until it finishes.

How will we know if an automatic transmission is underway? I modified my code so that while it’s transmitting it will create a file called web_tx inside the local directory. When it finishes transmitting it will delete it again. Therefore whenever Echolink tries to read the GPIO we simply need to check whether a web_tx file exists at that path. If it exists, provide the fake response. If it doesn’t exist, let Echolink read the real value.

The actual mechanism for intercepting the reads requires a bunch of code. I wrote my replacement open(), read(), and close() functions in C and compiled them into a shared object. By setting the LD_PRELOAD environment variable to point to my shared object all of Echolink’s file operations end up filtered by my custom code. In a fairly roundabout way it does what I described in the last two paragraphs.

The only remaining trick is to make sure the right environment variables are set for Echolink software, but not for the web server that’s playing the long files. All of the scripts already load their environment from the file located at custom/environment so I just had to add my own settings to that. LIBC_SO and WEB_TX_OVERRIDE are parameters to the interception code.

So that the automation server knows what’s really going on I need to clear out the variable when I run it:

There’s also a persistent service called tbd. I honestly don’t know what it does so I decided to give it the same variables by changing /etc/init.d/tbd to the following:

And with that everything is working nicely. The only catch is that I have defeated Echolink’s watchdog. I’ll need to make sure that we have some form of hardware-based timeout at an appropriate length, perhaps 45 minutes, just in case the Pi crashes during transmission.

With the basics working I can now spend some time improving the code and securing it so it can go on the public internet.

Posted in programming, radio | Leave a comment

Playing an MP3 with compression on Linux/OSX

Some software I’m working on for a Raspberry Pi demands that I play MP3s at particular times. The audio is fed into a radio where it is then transmitted as narrowband FM, a very lo-fi channel. The recordings are spoken word and they will be heard most clearly if the output volume stays near the maximum allowed all the way through playback. In other words, if some parts of the file are quieter than others I’d prefer to bring them all up to the optimal level. That’s the purpose of an audio compressor.

I was pleased to find out that I can do this on the fly pretty easily using mpg123 and sox (brew install mpg123 and sox comes with OSX). Here’s a full command I’m using:

This is what all the bits mean:

-r 44100
Output sample rate. Obviously needs to be the same in the second part of the command.
-s
Send audio samples to stdout instead of audio device
-m
Mono mix (the FM transmitter has only one channel)
--buffer 2048
Use a buffer of 2048 bytes instead of sox’s default of 8192. In my testing the Pi was too slow for the default buffer size and needs smaller ones to avoid underruns.
-t raw -e signed-integer -b 16
Tell play that it will be getting headerless signed PCM with 16-bit samples
-c 1
Single channel, because it’s mono
-
Read audio data from standard input
compand
Apply a compressor effect (full docs for the following parameters, which can be more complex)
0.1,0.3
Attack and delay, in seconds. Controls response time to increasing and decreasing volume.
-60,-60,-30,-15,-20,-12,-4,-8,-2,-7
A transfer function I found online that works nicely. It’s a series of pairs—for a given input level in dB, what should the corresponding output level be? So it’s flat at -60 dB, signals around the -30 to -20 dB region will get a nice big boost, and really strong signals around -4 to -2 dB will be brought back a little.
-6
Reduce the overall gain by 6 dB to reduce clipping. In my testing it still clips occasionally but I’m intentionally pushing the limits so that’s okay.

This can all be replicated nicely in python using the subprocess module:

 

Posted in programming, radio | Leave a comment

Slack vs IRC isn’t going anywhere

If, like me, you indulge in a bit of schadenfreude then the Slack vs IRC debate is the trainwreck that keeps on giving. It’s now a perennial feature of LCA hype and recently it’s been occupying public timelines on GNU social too.

The trouble of course is that everybody’s right. By now most people understand pretty clearly the strengths and limitations of each system. A popular theory appears to be that if you talk about these facts enough you will eventually sway people to your preferred choice. Somehow that never happens. This failure injects fresh urgency because if you don’t reach a consensus there might be two chat rooms instead of one, a true calamity.

Really it’s about feels. And that’s not a bad thing. The reality that technical arguments are often about values is a common impediment to community understanding. I’ve written about it before with regard to free software. The schadenfreude comes in because so often FOSS communities pride themselves on putting technical capabilities foremost in their decision-making. It doesn’t always work.

It is beyond question that IRC is the more FOSS-friendly solution. It’s a 100% open protocol with free implementations available for all parts. While you can turn on the IRC feature of Slack it’s also an important part of free software culture that you don’t depend on or propagate closed source software by using or promoting it.

Equally it is beyond question that Slack is friendlier to non-technical users. It may be straightforward to log in to IRC but that’s not enough these days. Modern chat users demand consistent state across multiple devices and complete scrollback without having to maintain a TCP connection. Serious IRC users, myself included, use some fairly ugly hacks to get around it, whether that’s a screen session, a bouncer or IRCCloud. Some people like push notifications too but that’s a can of worms I’ll leave closed for now.

That’s the crux of it: shall we prioritise FOSS values for the success of free software, or ease of use so as not to alienate non-technical people? It’s not a technical question at all and it’s difficult to answer.

Free software being the uncompromising beast it is, the most straightforward solution would be different free software that solves the same problems that Slack does. If you’re prepared to admit that IRC might nudge people away from the four freedoms then it’s worthwhile to look at possible alternatives like Matrix. Some LCA delegates are already trying out the Freenode integration with some success. Personally I find it’s not smooth enough just yet but it has a lot of potential.

Similarly, those pushing the Slack agenda would do well to be aware of the free software ethos. Reading Richard Stallman’s essays is a thought-provoking experience whether or not you agree with him. Free software and even open source have a strong basis in his ideas and it’s important to understand why people think this stuff is important. But if someone tells me they find IRC slightly shitty I concede their point.

Where does that leave us? Nowhere! Let the games continue!

Posted in free software | Leave a comment