UUID Version 7 Implementation and Conundrums

During otherwise uninteresting summer, without too much noise, we got new UUID version(s). While 6 and 8 are nice numbers, version 7 got me intrigued. It’s essentially just a combination of Unix timestamp with some random bits mixed in. Exactly what a doctor might order if you want to use such UUID as a primary key in a database whose index you don’t want to fragment to hell.

Format is easy enough as its ASCII description would suggest:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           unix_ts_ms                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          unix_ts_ms           |  ver  |       rand_a          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var|                        rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

It’s essentially 48 bits of (Unix) timestamp, followed by 74 bits of randomness, with 6 remaining bits being version and variant information. While timestamp ensures that IDs generated close in time get sorted close to each other, randomness is there to ensure uniqueness for stuff that happens at the same millisecond. And, of course, I am simplifying things a bit, especially for rand_a field but you get the gist of it.

That all behind us, let me guide you through my implementation of the same.

Generating the first 48-bits is straightforward with the only really issue being endianness. Since BinaryPrimitives doesn’t really deal with 48-bit integers, a temporary array was needed.

var msBytes = new byte[8];
BinaryPrimitives.WriteInt64BigEndian(msBytes, ms);
Buffer.BlockCopy(msBytes, 2, Bytes, 0, 6);

Generating randomness has two paths. Every time new millisecond is detected, it will generate 10 bytes of randomness. The lowest 10 bits of first 2 bytes will be used to initialize random starting value (as per Monotonic Random method in 6.2). After accounting for the 4-bit version field that shares the same space, we have 2 bits remaining. Those are simply set to 0 (as per Counter Rollover Guards in the same section). I decided not to implement “bit stealing” from rand_b field nor to implement any fancy rollover handling. If we’re still in the same millisecond, 10-bit counter is just increased and it’s lower bits are written.

if (LastMillisecond != ms) {
    LastMillisecond = ms;
    RandomNumberGenerator.Fill(Bytes.AsSpan(6));
    RandomA = (ushort)(((Bytes[6] & 0x03) << 8) | Bytes[7]);
} else {
    RandomA++;
    Bytes[7] = (byte)(RandomA & 0xFF);
    RandomNumberGenerator.Fill(Bytes.AsSpan(8));
}

Despite code looking a bit smelly when it comes to multithreading, it’s actually thread safe. Why? Well, it comes to both RandomA and LastMillisecond field having a special ThreadStatic attribute.

[ThreadStatic] private static long LastMillisecond;
[ThreadStatic] private static ushort RandomA;

This actually makes each thread have a separate copy of these variables and thus no collisions will happen. Of course, since counters are determined for each thread separately, you don’t get a sequential output between threads. Not ideal, but a conscious choice to avoid a performance hit a proper locking would introduce.

The last part is to fixup bytes in order to add version bits (always a binary 0111) and variant bits (always a binary 10).

Bytes[6] = (byte)(0x70 | ((RandomA >> 8) & 0x0F));
Bytes[8] = (byte)(0x80 | (Bytes[8] & 0x3F));

Add a couple of overrides and that’s it. You can even convert it to Guid. However…

Microsoft’s implementation of UUID know to all as System.Guid is slightly broken. Or isn’t. I guess it depends from where you look at it from. If you look at it as how RFC4122 specifies components, you can see them as data types. And that’s how the original developer thought of it. Not as a binary blob but as a structure containing (little-endian on x86) numbers even though the specification clearly says all numbers are big endian.

Had it stopped at just internal storage, it would be fine. But Microsoft went as far as to convert endianness when converting 128-bit value to the string. And that is fine if you work only with Microsoft’s implementation but it causes issues when you try to deal with almost any other variant.

This also causes one peculiar problem when it comes to converting my version 7 UUID to Microsoft’s Guid. While their binary representations are the same, converting them to string format yields a different value. You can have either binary or string compatibility between those two. But never both. In my case I decided that binary compatibility is more important since you should really be using UUID in its binary form and not space-wasting hexadecimal format.

As always, the full code is available on GitHub.

[2023-01-12: Code has been adjusted a bit in order to follow the current RFC draft. Main changes are introduction of longer monotonic counter and altenate text conversion methods (Base35 and Base58).]

Disabling Horizontal Scroll on Logitech MX Master on Ubuntu 22.04

One feature that annoys the heck out of me on my MX Master is horizontal scroll. While usefulness of the horizontal scroll itself is already debatable, its positioning on MX Master is abhorrent. Side scroll wheels are extremely easy to hit by accident and they turn as soon as you even think about them. The only saving grace is that most applications don’t support them.

Under Windows, it’s easy to disable them in Logitech Options software. But Linux has no equivalent of this (and yes, I’m discounting LogiOps here, potentially unfairly so). Fortunately, Linux does offer a fine-grained control by default.

The first step is finding mouse ID under pointer devices (ignore it under keyboard).

xinput list

To verify you have the correct mouse and if you want to check which buttons horizontal scrool wheel occupies, you can use xinput test:

xinput test <id>

For MX master (and honestly pretty much any other mouse I tried), these values were 6 and 7. Thus we can simply disable them using xinput again.

xinput set-button-map <id> 1 2 3 4 5 0 0 8 9

And that has our horizontal scroll gone for good. Interestingly, while we can still see motion values, no application seems to react to those alone.

As this change will dissapear with reboot, I personally place them in .bash_profile so they get applied each time my user logs on.


PS: I also have my thumb button reconfigured.

Single Instance Application for .NET 6 or 7

A while ago I wrote C# code to handle single instance application. And that code has served me well on Windows. However, due to its dependency on the Windows API, you really couldn’t target multiplatform .NET code. It was time for an update.

My original code was using a combination of a global mutex in order to detect another instance running, followed by a named pipe communication to transfer arguments to the first-running instance. Fortunatelly, .NET 6 also contained those primitives. Even better, I could replace my named pipe API calls with multiplatform NamedPipeServerStream and NamedPipeClientStream classes.

Unlike my Windows-specific code, I had to use Global\\ prefix in order for code to work properly on Linux. While unfortunate, it actually wasn’t too bad as my mutex name already included the user name. Combine that with assembly location, hash it a bit, and you have a globally unique identifier. While the exact code was changed slightly, the logic remained the same and new code worked without much effort.

Code to transfer arguments had a few more issues. First of all, I had to swap my binary serializer for JSON. Afterward, I had to write a new pipe handling code, albeit using portable .NET implementation as a base this time. Mind you, back when I wrote it for Windows, neither has been supported. Regardless, a bit of time later, both tasks were successfuly done and the freshly updated code has been tested on Linux. Success!

But success was shortlived as the same code didn’t work on Windows. Well, technically it did work but the old instance newer saw the data that was sent. It took a bit of troubleshooting to figure a basic named pipe constructor limited communication to a single process and overload setting PipeOptions.CurrentUserOnly for both client and server was needed. Thankfuly, that didn’t present any issues on Linux so the same code was good for both.

And that was it. Now I had working .NET 6 (or 7) code for a single instance application working for both Windows and Linux (probably MacOS too), allowing not only for detection but also argument forwarding. Just what I needed. :)

You can see both this class and example of its usage in my Medo repository.

RS-485 Framework Expansion Card (FTDI Edition)

Boards for this project were sponsored by PCBWay (affiliate link).


Illustration

Playing with electronics means dealing with a lot of protocols. Be it UART, RS-232, I2C, or CAN-bus, I have made a version to fit my Framework laptop. However, one popular protocol was strangely absent - RS-485. Well, it’s time to correct that mistake.

Since I successfully used JST XH with many Framework expansion cards before, connector decision was easy. However, deciding which pinout to use was a bit more troublesome as I needed 3 pins and common wisdom was that order is GND-A-B. However, my UART cards were using 3-pin setup with GND in middle. It would be too easy to forget which card was plugged in and get the most crucial pin wrong.

So, the only remaining choice was to have A-GND-B order but I really didn’t like it and honestly I am not sure why. It’s a good pinout allowing for excellent routability (since protection diodes have this pinout already). But no other RS-485 device has it. While moving GND pin to center would alleviate error rate among my framework expansion cards, I could see even myself accidentally connecting ground to the first pin.

At the end I decided to use 4-pin socket with the last pin pulled out. This would allow for standard GND-A-B setup while making connector distinctively different from my cards that use center pin for GND. Even better, GND signal here actually matches my CAN-bus card where that last pin is populated (albeit not used).

Since I still had bunch of SP485EN chips I used for another project, it was easy to select RS-485 transceiver. And honestly, while aged, it’s not a bad chip for the purpose. It’s available from multiple sources, it has low pin count in easily hand-solderable package, and at 5 Mbps it’s way faster than UART I was planning to pair it with.

Speaking of UARTs, there is one that you will find mentioned time and time again when it comes to RS-485 - FTDI FT232RL. And that’s for a good reason. It’s a single chip solution without the need for crystal, allows for reasonably high baud rates, and it has specialized TXDEN pin allowing for automatic flow control. While its pin count might be on a high side and comes with a bit of baggage, it really fits nicely for the purpose.

While FT232 does offer reasonably full RS-485 schematics, I added a few things to spice it all up. First of all, I added a MOSFET to keep RS-485 transceiver fully off when in USB suspend. I later did find this was not really necessary as SP485EN has a suspend mode that FT232 can nicely trigger, it does offer me a bit of flexibility for further testing.

Another thing I added were biasing resistors. In my own setups I found that this increases bus resiliency a lot and it only costs you 3 resistors. What value resistors? Well, that’s where it gets tricky. There are so many ways to calculate these resistor values and you need to know a lot about the exact setup in order to make it perfect. Since I wanted to use this device in many different networks, the best I could hope for is to get close enough. In this case this meant 130/750Ω combination. When combined with 120Ω termination on the other side of the bus, it should handle many scenarios.

For protection, I decided to use good old SM712 TVS diodes to keep SP485EN safe and a few fast fuses on both data lines and on the ground for catastrophe prevention. Ideally you might want to have an isolated interface between your laptop and any bus. However, I simply didn’t have enough space on board for discrete solution and solutions with integrated DC-DC converter were either way to expensive or unobtainable.

While fuses are not ideal replacement for isolation since bad things can still happen if you deal with wildly different potentials, things are not as bleak as it’s easy to insulate your laptop. Simply unplugging it from its power supply and running it of battery will do the trick and allow you safe interfacing.

With the overall design done, I turned to PCB fabrication and PCBWay. I decided to go with them due to previous good experiences when it came to Framework expansion boards. While they are simple enough, routing them properly is a bit tricky. Only once I already made an order did PCBWay come back and proposed a sponsorship in the form of free PCBs. And that jinxed it, I guess, since for the first time routing was not ok.

Illustration

Per my understanding, most commonly fabs do board edge routing with a 1.6 mm or larger mill end. This is done for both speed and durability as smaller bits do tend to break more. Routing boards intended for type-C connector used here requires 1.0 mm or smaller bit in order to properly do the notches. If you check the bad board (green) and compare it to the good board (red), you’ll see how little difference there is.

In any case, that gave me an opportunity to deal with their helpdesk for the first time. After writing a ticket, I got a response in less than 24 hours despite them being in holiday time. After a short back-and-forth where I got to explain the exact problem, I had my updated order with extra notes attached. And yes, I selected a different color so I don’t mix them up.

Illustration

When it comes to the PCB quality, there isn’t much I can say I haven’t said before. A default HASL finish is good enough and there were no leveling issues. If you want ENIG, that is an option albeit quoted price skyrockets when selected. Soldermask is decent and can handle lead-free soldering temperatures without issues. I usually go fora green color as it has the shortest lead time, but you can select other 5 colors at no extra cost (purple and matte colors are extra).

Silk screen is of decent quality and resolution even for small font sizes. And yes, PCBWay still adds their identification number on board by default with option to remove it requiring $1.50. For these boards I don’t really care since they won’t be visible to the end user but if you want to do user-facing panels, you might want to pay extra.

With all components soldered, I connected it to a Framework laptop and the experience was as good as you can get. One great advantage of FTDI chips is that they work flawlessly. I tested it on a few small RS-485 busses at varying speeds (up to 1 Mbaud) without any communication issues. Even under scope things were looking good.

But, is there a way to get rid of FTDI? Well, we can discuss that in the next instalment of the RS-485 Framework expansion card saga. :)


As always, you can find project on GitHub

Hashing It Out

For a while now I had a selection of CRC algorithms in my library. It offered support for many CRC-8, CRC-16, and CRC-32 variants, all inheriting from a bit clunky HashAlgorithm base class. It wasn’t an ideal choice as hashes and checksums are different beasts but it did a job.

With .NET 7 out, we finally got NonCryptographicHashAlgorithm base class to inherit from and that one is much better at dealing with CRC nuances. Adjustment of algorithms was easy enough but testing has shown one issue. Output of Microsoft’s CRC-32 class and one I have created was exactly reversed. For example, if an input would resut in 0x1A2B3C4D output from my class, Microsoft’s class would return 0x4D3C2B1A. Yep, we selected a different endianess.

I originally created my CRC classes with microcontroller communication in mind. Since most of microcontrollers are from the big-endian world, my classes were designed to output bytes in big-endian fashion. On other hand, Microsoft designed their CRC-32 class for communication on x86 platform. And that one is little-endian.

After giving it a lot of thought, I decided to go the Microsoft route and output result in native endianess if using GetCurrentHash method from NonCryptographicHashAlgorithm base class. Main reason was to have the same overall behavior whether someone uses my, Microsoft’s, or any other class. That said, I my classes always had an “escape hatch” method that outputs a number (HashAsUInt32) that can be then converted to any endianess.

In any case, if you need any CRC-8, CRC-16, or CRC-32 calculations with custom polynomials, do check out Medo.IO.Hashing library.