Digital Mobile Radio (DMR) is an excellent digital mode for ham radio use, especially for HTs. It is routable via IP, so repeaters can be linked via the Internet, and inexpensive commercial radios are readily available.

Turning DMR IDs into IPs

Because it’s digital communications, DMR is IP-based, so each radio on the same network needs a globally unique ID that can fit in a 24-bit integer (0–16,777,215). In hex, this is 0xFFFFFF. Why only 24 bits? Well, DMR is based on IPv4, and the first 8 bits are used for routing. So 24 bits + 8 bits = 32 bits, which is the length of an IPv4 address.

NOTE: I may use byte and octet interchangeably, and I’m referring to 8 bits.

For example, let’s take a radio with the ID 3108128 (0x2F6D20).

radio ID hex
3108128 0x2F6D20

For reference, these are the individual hex octets of the radio ID translated into integers:

hex integer
0x2F6D20 3108128 (my DMR radio ID)
0x2F____ 47
0x__6D__ 109
0x____20 32

In MotoTRBO parlance, the radio protocol routing octet is called the Common Air Interface (CAI), and it’s the first octet of the radio’s IPv4 address. The default CAI is 12, which means that the first byte of the IP address of the DMR radio would be 0x0C. So if we prepend the CAI 0x0C to the Radio ID 0x2F6D20, we get 0x0C2F6D20. That number in decimal is 204434720, but that doesn’t look anything like a regular IPv4 address. If you break up the 4-byte IPv4 address 0x0C2F6D20 into individual octets and then convert those into integers, you get the real IPv4 address:

hex integer
0x0C2F6D20 204424720 (WTF?)
0x0C______ 12
0x__2F____ 47
0x____6D__ 109
0x______20 32

And there we have it. 12.47.109.32 is the IP address of the DMR radio with the ID 3108128 and the default CAI 12.

CAI + 1 is for devices attached via USB, and CAI + 2 is for devices attached via bluetooth:

  • 13.47.109.32 is the IP address of the computer attached to the USB programming cable.
  • 14.47.109.32 is the IP address of the computer attached to the bluetooth connection.

The Problem

The organizations that allocate global DMR radio IDs for ham use came up with a numbering scheme based on integer numbers and Mobile Country Code (MCC) regions, not 24-bit IP addresses. This is perfectly reasonable if you’re not a network engineer or have any clue how DMR IDs actually work, but it is very limiting in terms of distributing the IDs across the entire available 24-bit address space.

For example, my radio ID is 3108128. I’m in region 3108 (United States, Colorado), radio number 128. That’s easy for me as a human to identify, but what happens when there are more than 999 radios in a region? Some regions have already run out.

These IDs also require centralized coordination, something that certain hams categorically resist. Amateur radio is traditionally rules-based and loosely coordinated, but with the current DMR radio numbering scheme, you have to have a single global authority to ensure that each DMR radio ID is unique. Politics and cliquishness does occasionally rear up in ham radio: should that stop someone from joining a DMR network?

If you don’t have a unique address, another radio that is connected the same network may receive traffic intended for you, or vice-versa. It’s an IP address conflict.

A Potential Solution

I suppose each licensing body (e.g., the FCC) could issue several unique 24-bit numbers to each radio licensee, but every licensing body on Earth would have to coordinate or be given a block of numbers to use (like how IANA issues IP addresses).

One proposed solution I had (back in 2011) was to hash the callsign of the user in order to generate random-looking-but-probably-unique numbers. Hashing is a cryptographic function that reproducibly turns one thing into something else, and it’s a one-way trip. If I were to use the MD5 algorithm to hash K0PRW, I would get a8b5dbe2fe44e741c0b3f0d9d53ba632.

Suppose I stripped all the non-numeric digits from that output: I would get 85244741030953632. Then let’s take the last 8 digits to get 30953632. Hmm. That’s bigger than the largest 24-bit number (16777215), so let’s lop off the first digit to reduce it to 7 digits in length and we get 0953632. That’ll work. Now I have a 24-bit number, 953632, that I could potentially use for a DMR radio ID.

But wait a second – what if I have more than one radio? Well, how many is enough? 10? 100? Suppose we want to give each ham the opportunity to assign 10 radios to his callsign. Let’s hash K0PRW0 instead (my first radio).

  1. hash K0PRW0: a7c5744f5e868950af95cb7b0abe0127
  2. remove non-numbers: 75744586895095700127
  3. get last 8: 95700127
  4. Whoops, too big. Get the last 7 instead: 5700127

Q&A

All right, how does one go the other way, i.e., how do I determine a callsign from a hash?

You can’t.

Hashes are designed to be one-way cryptographic functions, meaning they encode data but can’t decode data. Most websites and services that require you to create a password aren’t really storing your password, they’re storing hashes of your password. That way if someone steals the database, they don’t get people’s passwords, just their hashes.

Well couldn’t we design a mathematical function to turn a callsign into a 24-bit integer and then back into a callsign?

Have at it. I’m no math whiz by any means, but in order to do this, you’d need to programmatically map every possible callsign to a unique number. The longest US callsigns can be in the 2x3 format, with the prefix starting with A, K, N, or W:

[AKNW][A-Z][0-9][AAA-ZZZ] So that’s 4 * 26 * 10 * (26^3), or 18,279,040. Let’s say this is the number of potential callsigns. This alone is greater than the maximum size of a 24-bit integer (16,777,215), and we haven’t even accounted for shorter callsigns or other countries.

Realistically, though, that’s not a likely scenario.

Now we probably won’t have more than 16,777,215 hams using DMR at the same time. (If we do, then heaven help us!) So we can reexamine the hashing function, which also has an interesting property that it is designed to reduce collisions. A collision would be if two different inputs to the hashing function produced the same output. For example, if I hashed KC0BVU1 and K0PRW1 and both resulted in the same ID. I didn’t come up with a real example, because to do that, I’d have to hash every callsign possible and compare all of the outputs.

Hashing it out

Since we’re thinking in bets, let’s calculate what the odds are of any one of those 18.2 million potential callsigns would generate a collision, or the same DMR ID. Let’s assume that each one of these 18.2 million hams will have 10 DMR radios. That’s 182 million radios (in this scenario, radio manufacturers have taken over the world). I’ve written some Python code to calculate the odds.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def generate_callsigns() -> list:
    all_callsigns = []
    for p1 in ["A", "K", "N", "W"]:
        for p2 in string.ascii_uppercase:
            for r in range(10):
                for s1 in string.ascii_uppercase:
                    for s2 in string.ascii_uppercase:
                        for s3 in string.ascii_uppercase:
                            callsign = "{0}{1}{2}{3}{4}{5}".format(p1, p2, r, s1, s2, s3)
                            all_callsigns.append(callsign)
    return all_callsigns

This gives me a list like ["AA0AAA" ... "WZ9ZZZ"]. In the real world there are special and reserved callsigns and prefixes and the like, but this is a worst-case scenario, so just go with it for now.

Now that we have a list of callsigns, let’s turn them into a list of radios.

1
2
3
4
5
ssids = range(10)
radios = []
for call in generate_callsigns():
    for i in ssids:
        radios.append("{0}{1}".format(call, i))

Now I have a list like ["AA0AA0" ... "WZ9ZZZ9"]. All that’s left to do is to hash all of these into DMR radio IDs and do some simple math to get the stats:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def hash_callsign(callsign) -> int:
    # hash the callsign + radio ID using MD5
    hd = hashlib.md5(callsign.encode()).hexdigest()
    
    # remove the non-numeric characters (only a-f are used because it's hex output)
    numeric = re.sub('[a-f]', '', hd)
    
    # get the last 8 numbers
    last8 = numeric[-8:]
    
    # if it's less than 16777215 (hex 0xFFFFFF), use it
    if int(last8) < 0xFFFFFF:
        h = last8
    # otherwise lop off the first number and use what remains instead
    else:
        last7 = numeric[-7:]
        h = last7

    # turn the string into an integer
    radio_id = int(h, 16)
    
    #return the radio ID integer
    return radio_id

hashes = {}
for radio in radios:
    hash = hash_callsign(radio)
    if hash in hashes:
        hashes[hash] += 1
    else:
        hashes[hash] = 1
        
print("{0} radios, {1} unique IDs".format(len(radios), len(hashes)))
print("{0:.2f}% unique".format((len(hashes) / len(radios)) * 100.0))
print("{0:.2f}% of 24-bit address space utilized".format((len(hashes) / 0xFFFFFF) * 100.0))

And you get an output something like this:

1
2
3
182790400 radios, 15687525 unique IDs
8.58% unique
93.50% of 24-bit address space utilized

A better hash function

That was some ugly hacking to an old hashing algorithm in order to make a bigger output fit into a smaller number. But what if there was a hashing algorithm that would let you define the output size, and in doing so gave you a better distribution with fewer collisions? Turns out there is a way. The Keccak algorithm used in SHA-3 allows you to define an arbitrary output size (all the way down to 1 bit). So by changing the hash_callsign() function to this…

1
2
3
4
5
6
7
8
9
def hash_callsign(callsign) -> int:
    # hash the callsign + radio ID using MD5
    h = shake128(data=callsign.encode(), digest_size=24).hexdigest()
    
    # turn the string into an integer
    radio_id = int(h, 16)
    
    #return the radio ID integer
    return radio_id

…we get this output:

1
2
3
182790400 radios, 16776891 unique IDs
9.18% unique
100.00% of 24-bit address space utilized

More realistic scenarios

In reality not every ham will be on DMR and not every DMR ham will have 10 radios. Let’s look at a more realistic situation. In fact, let’s get the actual list of current DMR amateur operators and assume they each have 2 radios with unique IDs. For that I had to write another function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def retrieve_current_callsigns() -> set:
    url = "https://www.radioid.net/static/users.json"

    current_callsigns = []

    with urlopen(url) as url:
        data = json.loads(url.read().decode('ISO-8859-1'))
        users = data['users']
        for u in users:
            current_callsigns.append(u['callsign'])

    return set(current_callsigns)

Now when I run this data through the program I get:

1
2
3
100722 radios, 100379 unique IDs
99.66% unique
0.60% of 24-bit address space utilized

Not bad. There are 343 radios out of 100,722 (0.34%) that may collide if they are online at the same time. Also less than 1% of the potential radio address/ID space is used, so there’s room for growth.

Just for fun, what if every current DMR ham actually had 10 radios?

1
2
3
503610 radios, 496045 unique IDs
98.50% unique
2.96% of 24-bit address space utilized

The system is scaling up quite nicely. There are now 7,565 collisions out of 503,610 radios (1.50%). The number of radios has increased x5 but the collision rate has only increased x4.4 (1.50.34).

Tradeoffs

So now we have to decide if this programmatic way of generating callsigns is worth it.

Advantages

  • You can determine a friend’s DMR radio ID by using his callsign + SSID, even if he doesn’t have a DMR radio yet.
  • No central authority needed, no politics, no power struggles
  • Faster to get online with DMR: no need to wait for someone else to assign an ID
  • DMR radio ID generation software could easily be placed on a public website or on a computer with no network access; online or offline availability: you pick
  • Does not rely on the dwindling availability of IDs in certain MCC ITU regions

Disadvantages

  • Not absolutely guaranteed to have a globally unique ID, only a high probability. Individuals would have to coordinate if collisions occur.
  • May result in collisions in the event that both users are online and on the same network at the same time
  • Unable to mentally localize received radio IDs based only on the number
  • Requires access to a website or software program to generate your radio ID
  • User database generation is more difficult (but not impossible)
  • Current software systems may be predicated around having a globally unique radio ID

Another Potential Solution

Use a centralized number registry, but rethink it to be centered around how DMR numbering actually works and, therefore, be more flexible. Divide the world up into 3 octets worth of organizational units and sub-units. Each of these octets can contain 256 numbers (0–255).

For example, the country could be the 2nd octet, the region could be the 3rd octet, and the individual radio in that region could be the 4th octet. This method more logically routes traffic according to address, much how a network admin might set up subnets and routing in a large class-A (or /8) network, which is essentially what DMR is.

So if my radio IP is 12.31.81.128, that might mean that I’m in country 31 (USA), region 81 (Colorado region 1, whatever that is, probably divided geographically by population), radio unit #128. We’d figure out the DMR ID like so:

integer hex
31 0x1f____
81 0x__51__
128 0x____80
2052480 (new-style DMR radio ID) 0x1f5180

Another cool and logical solution. In this manner I could potentially use subnet masks and routing tables to do interesting things with DMR and a computer. It wouldn’t be completely intuitive at first, but nearby radios would still have substantially similar radio ID numbers when viewed in integer format.

Or perhaps we could group radios not by geographic area, but by function. Or by registrar, group, or club (DMR-MARC, Brandmeister, etc.), giving certain organizations certain subnets.

This alternative has many of the same drawbacks as the current method, including the need for central coordination and assigning countries, states, provinces, areas, and what-have-you to numbers. But in the end it may be better to think of DMR radio IDs in IP networking terms, rather than the current space-constrained methodology.