CODAN Selcall Part 2 - Pagecall Magic

In part one I left you with a cliff hanger regarding page calls. And I particularly like this little note:

This shouldn’t be hard

Like it’s a radio from 1993. Easy right?

That was nearly 7 months ago. That being said I haven’t been working on that project full time and up until recently have only really just been chipping a way at it here and there. In the last few weeks I’ve been using the project as a bit of an escape. Since there’s so many facets to reverse engineering, different pathways and different things to discover the project has provided many simple tasks for me to work on that don’t require much capacity. I can just put some music on then label some functions, decompile code, run emulator tasks. Just poke random memory locations to see what they do.

On Sunday I was able to replicate the magic hashing function used in the page call protocol in Python, and hence why I can write Part 2!

What is page call and why is it different?

Page calls are text messages sent between radios. Essentially SMS for radios. Or I guess WhatsApp(?) messages for radios. The protocol is very similar to the selcall protocol, just with the message spliced in…. however…..

Head unit for Codan 9323 showing a pagecall “rawr nuzzles”

Codan decided that page calls should be locked down. My assumption here is that the 93xx series of radios were the first by Codan to be advanced enough to allow paging and to keep a market edge they wanted to make it harder for competitors to provide interoperability (pretty sad imho). So page calls include two extra bytes that are validated before receiving.

screenshot of code with two bytes being commented as “not sure what these are”
Freeselcall up until now has only been able to receive messages. Messages sent would not be received by radios using the magic bytes.

Surely someones worked it out?

I’m not sure if anyone actually has. I can’t imagine I’m the first but I can’t really find any resources online about these two bytes. I found one groups.io post about someone asking about it - but no responses (shame I can’t find that post anymore! I’d love to respond! I found the post!)

But doesn’t Barrett radio support sending pages? Well some more modern radios do, except they somewhat side stepped the problem by working with Codan.

Screenshot of the intellectual property exchange deed which lists out how codan and barrett are working together

What about Skippy? Well it claims it can send page calls but if you look at the details it can only send them to other Skippy users - likely because of these two bytes not being known. I’d love to know if Skippy to Freeselcall worked.

skippy compatability matrix where barret and ngt can’t receive pagecall
And to be honest, I’m not all that surprised. There’s not a lot of demand of sending messages like this. There’s limited users and the people who do need this functionality will just pay for the radio.

But these two bytes were like holding a red flag to bull for me. I needed to know.

The basics

Apart from doing prior research and asking ex-Codan employees I checked a few things. How do the bytes change when I change the selcall id by 1. What happens when I change it by 1000. What happens if I change the message. What happens if I flip the to and from address.

This data quickly builds up test cases. Lets you validate assumptions. I only ended up with a few of these tests though as it requires a lot of tedious work to do, along with waiting for transmission. But I had enough to rule out basic single XOR or just swapping parts of bytes. It was certainly cascading in some way since changing one bit change a lot. It also told me that since the bytes didn’t change with the message that the bytes weren’t used for confirmation or recovery. Purely as a security check.

Effort to not touch our radio

Even though the radio was designed in 1993, the Codan 9323 is still a 100W HF radio. And a good one. This means they still sell for $400+ on eBay. Buying a replacement one would be annoying and expensive.

I wanted to minimise fucking around and finding out on our radio. No pulling eeproms, no tapping into the memory bus.

Luckily there are dumps of the firmware online now. Bless these people. This allows us to look at just the software side of things. Likewise there’s a bunch of service manuals, PCB and circuit diagrams for many of the units allowing us to paint a picture of how the system works.

At this point I blindly fired up Ghidra and pretended I knew what I was doing. Then I spent about 3 hours reverse engineering a supposed crypto function that turned out to be 16bit long division.

Update

In between writing the first draft of this post and today, I managed to find a bug where a malformed pagecall packet caused our radio to hang and on reboot crash / act odd. I was luckily able to fix this. After trying so hard to ensure I didn’t break out radio that was an extremely close call.

Maybe we should emulate it?

At its core it’s an 8051. Surely there are a ton of of 8051 emulators. I could just dump the ROM into one and go.

Well, that’s partly true. There are a ton of 8051 emulators, however it’s not just the CPU that needs emulating, its all the supporting hardware around it. So out come the schematics, service manuals and we begin collecting datasheets for all the random ICs scattered on the board.

Then you realise that the ROM is 128k, and an 8051 can only support 64k. How does that work? The magic of bank switching. The code will use additional pins to switch in and out banks of memory. The code has to support this and know what its doing. Since this is done in software the emulator needs to support this as well, and everyone does this differently so there isn’t a standard way to emulate the bank switching.

So I ended up starting with py8051, which is a python wrapper for emu8051. I really enjoyed working in Python for this project. I didn’t need speed - just something I could quickly modify to get the job done.

The emulator was super handy in understanding what code was, what functions were doing and allowed me to continue reverse engineering in Ghidra. Eventually the emulator progressed so far in hardware emulation that I was able to get the boot screen to display on a virtual display, and with some memory poking I could even just some of the normal displays to show up.

virtual display of emulated codan 9323 - shows “no channels fitted, lsb, call, rx”

I never got the emulator(s) to a point where they would run normally though.

Emulators? With an s?

Yes. Many of you probably already gathered but our Codan 9323 radio has two parts, a remote head and a base. (Other units are sold with a front panel on the base, however this is effectively the head unit packed inside the base - other fun fact I found out is that you can have multiple head units connected!)

So this poses the question - does the magic get calculated on the base or the head unit.

  • Surely the base unit since the head would just be a dumb interface right?
  • Except you can set a privacy code on the head unit
  • Except maybe it just sends that back to the base unit
  • But maybe the base unit a dumb device
  • But you can also send a page call from the serial port?
  • Maybe the base unit just asks the head unit to encode the packet?

I continued chipping away at both. When I got stuck with one unit and I switched to the other. Was this the fastest way to work. No. Certainly not. But its what I could fit in to random spare gaps in time and days where I didn’t really want to go out to play.

Maybe I should just check?

At this point I had emulated both the bit banged I2C and the hardware based I2C… hold up… hardware based I2C?

Oh yeah this is not a standard 8051, It’s a Philips 80C552. This means there’s a bunch of extra functionality, which is more logic to handle in the emulator sigh.

Anyway, at this point I had emulated all the I2C. Not to the point that the two emulators could talk to each other, but enough for me to send packets by hand and see what happens. Reverse engineering the code gave me an idea of what the packets looked like, but not enough to get anything to work.

base unit of codan 9323 with logic analyser connected to some pins

Eventually I gave in and decided on a safe way to capture the I2C data between the head unit and the base unit. The front panel connector that wasn’t used on our 9323 was perfect for tapping into the I2C between head and base unit.

pulseview decoding the i2c packets

These packet captures done with PulseView were super useful. I could replay the messages in the emulator and see the results. It also revealed very quickly that the me magic bytes were not sent from the head unit to the base unit when sending a page.

My effort focused again on reverse engineering the base unit. But more importantly building out the emulator for the base to a point that I could the page call I2C messages.

With some bugs fixed in the emulator I finally had the emulated base station processing the page call messages. It even tried to transmit them - however I hadn’t emulated the tone generator….

It didn’t matter though because the important part was that it generated the bitstream for the transmission including the secret bytes.

hex editor view showing the pagecall encoded

At this point we have some options

  • Ship a 8051 emulator and a copy of the 9323 base station firmware in Freeselcall
  • Pre-calculate all possible value pairs (this is actually pretty possible, and not tooooo big of a lookup table)
  • Work out the algorithm

The algorithm

I took two approaches. Looking what happens to the emulator step by step, along with labelling and correcting Ghidra decompilation. Some parts I could only work out by watching the emulator. Eventually I came up with a solution that worked for 3 test cases. But failed on the next one - some tweaking and testing and all the test cases that I have recorded now work.

I then hastily set up freeselcall to use the new code and sent a test transmission from the IC7100 to the Codan 9323 and it worked! I then spent about 20 minutes reading the users manual to figure out how to actually open a page call (turns out you need the microphone, then press the recall button then call button). I found it hilarious that I knew how to generate the magic bytes but not know how to open the message on radio itself.

I generated a test file for Mark to transmit and it worked on their NGT radio!

codan ngt with a pagecall message: woof woof woof
Update It’s been been tested against a Codan Envoy!

envoy pagecall: vk5qi selcall test

Fin

Where does this leave us on this epic journey.

  • By the time you are reading this, freeselcall should be released on GitHub (and maybe pypy) with support for pagecalls - if not it will be very soon I promise
  • I specifically haven’t implemented privacy codes or 6 digit pagecalls. I have no use for these. It’s not hard work - but it is work. If someone makes a PR and tests for it I will support it.
  • I now know far too much 8051 assembly and Philips 8051 I2C information than I ever wanted to know
  • I have a copy of all the random IC datasheets now
  • The emulators won’t be released publicly nor the Ghidra annotated project
  • 6 months ago I tagged a series of functions as likely to be related to the pagecall magic - I was right, but I didn’t have a way of working out the inputs/outputs without the emulator

Side quests

Like all good projects there are side quests. I won’t document them here, but to give you a taste of where things lead to:

  • Figured out why the Codan 9323 gets stuck TXing
  • Made a script to colorise Ghidra based on logs
  • Found 3 different 8051 emulation quirks/bugs where my CPU didn’t match the emulators view of the world - these were annoying to troubleshoot. One of which was even present in the commerical Keil 8051 emulator
  • There is a 1-wire IC - DS2401 - that is packaged to look identical to a through hole transistor. It stores the serial number of the unit. I feel like the packaging is specifically to hide where the serial number is so people can’t reprogram / change them? I read ours by sniffing the 1 wire data pin on boot

CODAN Selcall Part 1

We drive long distances in remote places. Sometimes we want people to be able to check-in on us while we are out and about. While cell coverage is getting better we still often find ourselves in places with no coverage.

Our 4x4 with large HF antennas mounted driving through a dry lake

Often we leave our HF radio tuned to a known frequency (usually 7045 kHz LSB) while we are driving and have VSC or power level squelch enabled. However this has false triggers. When driving through towns there is usually a lot of RF noise and even when we aren’t there’s noise from our car, other HF radios, or even just ionsondes or CW operators.

Random noises while driving are very frustrating. In the commercial space radios are fitted with selective calling (Selcall) functionality. This allows one radio to call another radio provided you know the ID number of the other radio.

The great thing about standards is that there are so many to chose from - Selcall being no exception. While there are many different Selcall variations today we are going to talk about the Codan specific Selcall - specifically the not the CCIR / UN / WA2 / RDD / Customs variants. Nor aircraft SelCal. The proprietary Codan “standard” is what we want to look at. The reason behind this is that there are numerous Selcall users on 7045kHz already using Codan Selcall and while it’s proprietary we can still interoperate with those users.

Modulating

The actual Codan Selcall process is actually pretty well documented with VK5QI already creating an open modulator including some of the less documented features such as channel test modes.

I decided to use freedvtnc2 as a skeleton project as it already has an FSK modem (Codec2), audio and rigctl components. From there I implemented the Selcall protocol while testing and researching some of the extra modes from our Codan 9323. Modulating a Selcall signal was now trivial - but we already knew and had that. What we really wanted was an opensource decoder. Something that could detect Selcalls.

Demodulating

Demodulating is a little bit harder, but given that we know what the signal looks like and Codec2 provides a good FSK demodulator it was not outside my skill level.

Codan Selcalls start with a preamble, usually quite long to allow radios scanning channels enough time to detect the signal. To an extent we can ignore the preamble as the Codec2 modem will handle locking onto the signal for us.

After the preamble there is a phasing pattern. This is a header that is at the start of every Codan Selcall. It’s nearly as simple as looking for this pattern to determine when a Selcall has started. But you don’t want to do an exact match as in HF radio its likely that noise has caused some bits to flip. I settled for a 85% threshold, however I think it could be even lower in practice.

Once the phasing pattern is found it’s matter of decoding the bits as they come in. There’s a few fun things in here. Codan uses a 7 bit word and 3 bits of parity, so you have to handle the incoming data as a series of bits rather than your typical bytes. Parity is only at the word level. There is no checksum on the entire message. The data is effectively sent twice (offset) so if parity fails on one word you might be able to recover it from the other.

The parity is a count of the number of 0’s in the word.

For my demodulator it works like this:

  • Store a rolling buffer long enough to fit a Selcall
  • Look for phasing pattern
  • If a phasing pattern is found try to decode each field - check parity - use the redundant field if required and available

Fun fact - if you are really unlucky both copies of a field could return a successful parity check but have different data - as there is no overall checksum for the message you won’t know which one is correct. When receiving a Selcall we just check if either field is our Selcall ID.

So you decoded a Selcall - what now?

Decoding the Selcall is only part of the process. We need a way of alerting the user that there’s a call for them. It would be really really really nice if ICOM allowed user defined messages (more on this later) and activating a beeper - but in the meantime lets aim for something simple. Rigctl.

When we send a Selcall we actually need to control the rig - turn the radio to data mode, trigger ptt, return the radio to voice mode. Since we already have rigctl for that, how about we use it for alerting the use as well.

The simplest solution I thought of is using the squelch adjust. On our rig you can configure the USB audio output to remain active even when the radio is squelched on the head unit. So the flow looks like this now.

  1. Squelch the radio to a fairly high level - unlikely to get false triggers
  2. Software listens for the Selcall
  3. When it receives a Selcall for our configured ID number use rigctl to unsquelch the radio
  4. (additionally) if the Selcall requested the channel test mode and the option is enabled send back a test signal

Freeselcall

I built all of this over a couple of days and have called it freeselcall. It’s had some over the air tests - but I wouldn’t say it’s battle hardened.

I realised that most people, myself included, wouldn’t want to use a terminal to perform selcalls so I’ve also added a web interface. It uses websockets so it should be easy to integrate with other systems as well.

Freeselcall web interface
(It even has browser notifications for those so inclined)

More than just Selcall

Freeselcall allows for sending and receiving Selcalls with various priority types along with the option for channel tests. However Codan radios can do more. They can do paging (short messages).

Today freeselcall can receive pages. It also has code to send pages however Codan decided to add some security words to process. As it stands a Codan radio will not receive the page messages from freeselcall.

Terminal prompt showing Codan 9323 boot up logo and some debug output

I’ve spent a lot of time in Ghidra along with building a Codan 9323 emulator (it can display the LCD boot up message!) to help reverse engineer how these security bytes work. Hopefully in part 2 I can share how the paging messages work. Emulating a unique flavour of 8051 along with the hardware it connects to has been fun.

The future (and a terrible idea)

As I alluded to earlier - being able to display a message on our ICOM 7100 would be ideal. However there’s no rigctl or CI-V way of displaying a message on the 7100. Or so I thought. It was only when I was dialing in a repeater on the 7100 that I came across an idea that might work. I haven’t tested this, but here’s the plan.

  1. Dedicate a memory channel for freeselcall.
  2. When a call comes, if the radio is in memory mode, store what memory the radio is in.
  3. Use CI-V command to save the current radio state as a memory in the predefined freeselcall memory channel overwriting any existing configuration in that slot.
  4. Update the channel name with the message / selcall id / ect….
  5. Tell the radio to load that memory
  6. After some time has passed and the radio hasn’t PTT’d in awhile, either restore the radio to the memory it was set to or turn off memory mode

I think this will work? But I have no idea until I try

The other addition I want to add is either implementing more sensible Selcall protocols or building a more modern robust one. This can run in parallel for backwards compatibility.

I haven’t even looked into 6 digit pagecall. I haven’t seen it used on amateur bands so I’m not overly interested.

A final note….

Part 2 won’t be coming out until I’ve either been able to emulate the Codan firmware (just because thats cool in its own right) or I’ve been able to figure out the magic for the secret words. The emulator is at the point where I need to have the base and the head unit emulated at the same time and talk via the I2C bus. This shouldn’t be hard, however it’s also not something that is trivial.

Please do not send me yet another a copy of the Codan CCIR 493-4 PDF. This afaik doesn’t contain any information on the secret words and I have enough copies of it from people being helpful.