ai
I Spent a Decade Running the M6. Then I Met It Again.
"Google Drive sent me one of those notifications a couple of weeks ago."
Google Drive sent me one of those notifications a couple of weeks ago. Storage full. Time to clean up. So I started poking through the corners of my drive looking for things to delete, and that is how I ended up staring at a 4 MB file in a folder named "M6 Software" called VPFW3.vdf. I sat there for a minute just looking at the filename. I had not thought about that thing in more than ten years, and seeing it on the screen brought back a very specific kind of professional nostalgia.
For those not familiar
For those not familiar, VPFW3.vdf was a firmware image from a voice platform called the M6. The M6 was a softswitch built by a small Texas company named VocalData in the early 2000s, and it powered Primus TalkBroadband when the service launched in Canada on July 2, 2004. I took over the voice platform team at Primus in August 2006 and ended up living with the system for the next seven years. By the time I left Primus in 2013, I had spent the better part of a decade working on the M6 platform.
It was, for its time, a beautiful platform. A highly avaiaible call agent and a Java backend, all running on Solaris, talking multiple protocols (SCCP, SIP, MGCP, with Minet rumoured to be in a build I never saw), all funnelled through the call agent into an internal protocol for processing. Business features, residential features, conferencing, plus a full Java webstart desktop GUI back when the web could not have done it. Mostly API-configurable. Scaled to about fifty thousand users, which was respectable.
It also had one genuinely interesting commercial wrinkle. VocalData was one of the very few companies that had managed to license SCCP, the Cisco Skinny protocol used by Cisco IP phones. How they pulled that off, given Cisco had bought the rights from Selsius in 1998, I never fully understood. But it meant the M6 was one of the only non-Cisco PBXs in the world that could drive Cisco phones natively, which made it an attractive sale to customers who wanted Cisco endpoints without paying Cisco prices for the PBX. If anyone knows more about the history of how this all came to be I'd love to hear it.
Somewhere around 2006-7, GenBand bought VocalData (I can't remember the exact date). And in August 2008, Broadsoft acquired the M6 line from GenBand. Broadsoft did what acquirers usually do with a competing product, which is to put it on the road to end-of-life. By the time I left Primus, support was scarce and getting scarcer. For a couple of years after one of my standard consulting gigs was going around to M6 customers helping them keep the lights on.
The thing that beat me in 2014
The biggest headache of supporting a dying platform was, by a wide margin, the physical hardware. The M6's session border controllers and DTMF proxies were not virtual appliances or software you could move around like most modern platforms. The last generation were specific physical boxes, Intel pizza-box servers built around a Pentium 4 and the E7520 chipset, with specific Intel E1000 network cards. They booted from a small SD card connected over IDE. The OS on the SD card was Wind River's VxWorks, a real-time operating system that has approximately zero in common with Linux from a debugging standpoint.
When you have a small fleet of these boxes in a data centre and a vendor that has effectively walked away from the product, the question that starts keeping you up at night is what you do when one of them dies. The boards were already getting hard to source in 2013. The chipset was old then. It is ancient now.
So at various times in 2013 and 2014 I spent a series of long nights trying to virtualize the SBC. The theory was simple. Lift the SD card image, run it under VMware or QEMU, treat the physical box as a backup. The practice was awful. VMware would purple-screen on me. QEMU would not emulate the network card cleanly enough for VxWorks to bring it up (I even attempted to write patches to fix this, longer story). I tried a half-dozen variations. None of them worked. Eventually I gave up, accepted that some problems just have to wait for better tools, and moved on. I did not think about it again for ten years.
The crazy idea
Sitting in front of the file again, I had a thought that I am pretty sure ten-years-ago me would have laughed at. Forget virtualizing the original binary. What if I just figured out how the original did its magic, and built a modern implementation?
The whole thing was 4 megabytes. How complicated could it really be?
Famous last words.
Building a 2026 lab for a 2004 platform
Before any of this could go anywhere I needed something to test against. A real, running M6, in a configuration that a homemade SBC could attempt to register with. That turned out to be a small project of its own.
The M6 call agent ran on Solaris, and by the end of its commercial life that meant Solaris 11 x86. Finding a Solaris 11 x86 install image in 2026 is not a thing Oracle is making easy. The only working source I could turn up was the Internet Archive, which I felt slightly bad about and very grateful for. Pull the image, install it under VMware, watch Solaris 11 come up on a 2026 host. Done.
The next problem was the database. The call agent depends on TimesTen, Oracle's in-memory database, and I needed TimesTen 7 specifically. The later versions, the ones that are still officially available, added permissions enforcement that breaks the M6 in ways that are not easily worked around. I happened to have kept a copy of TimesTen 7 from the consulting days, mostly out of professional packrat instinct. If I had not, this project would have died right here.
The whole lab build was its own quiet object lesson. The same problem that motivated this project in the first place, hardware and software you can no longer source, was waiting for me one layer down the moment I tried to stand up a test environment. Anyone planning to do this kind of work on legacy gear should treat sourcing the dependencies as the first project, not a footnote.
Getting a clean executable out
Now that I had a working M6 lab environment for the first time in a decade, the first job was unwrapping the .vdf file. VDF is Wind River's packaging format, basically a thin shell around an executable with some chunking on top. The official tool for extracting them, a VxWorks utility called vdfext, has been out of public circulation for at least a decade. So I handed the file over to Claude and asked it to work out the format from scratch.
It took about an hour and one wrong turn. Claude's first pass produced an output that looked like a valid executable but turned out to be subtly corrupted. The way we caught it was almost comical. Claude pulled the embedded HTML files out of the binary and noticed short garbage byte sequences spliced through the <a href> tags. That was the clue that pointed at a chunked encoding the first pass had missed, and from there Claude worked out the real block structure and produced a clean extractor.
What fell out of that extractor was a statically linked 32-bit executable. About 2 MB of actual code. Built for a Pentium 4 with the same E7520 chipset I remembered, by a GCC compiler vintage around 2004. And here was the lucky part, the thing that made everything afterward possible: the binary had not been stripped. The symbol table was intact, all 7,282 of them. Every function had its original name. Every C++ class name was sitting there in the read-only data.
Reading the binary before disassembling it
This is the step everyone wants to skip and absolutely should not, and to be honest, I would probably have skipped it if I had been doing this alone. It was Claude that insisted on it. I handed over the clean executable and asked where to start, and the answer that came back was a careful pass over the readable text in the binary, with a structured set of categories to extract and dump into files that a second instance of Claude Code could read.
An hour of strings and pattern matching told us more about the architecture than a week of staring at instructions would have. Out came the C++ class hierarchy (213 classes, names like CSIPComponent, CFirewallSession, CSdcpVfw, all self-documenting), the complete CLI command vocabulary, the configuration file layout, the names of every message type in the proprietary control protocol that tied the SBC to the call agent (called CA-PH, for Call Agent to Packet Handler), and as a bonus, an entire embedded copy of an open-source toy web server that had been accidentally compiled into the binary alongside the real management UI and shipped to customers for twenty years without anyone noticing.
By the end of that session there was a clear architectural map of the SBC. Not a single instruction had been disassembled.
Where the 2026 part comes in
This is the part that ten-years-ago me would not have believed.
Claude walked me, step by step, through loading the clean executable into Ghidra. For those not familiar, Ghidra is a free reverse engineering tool from the NSA that did not exist when I was banging my head against this problem in 2014. Its decompiler turns machine code back into something that reads like C. The intact symbol table meant we were not staring at functions named FUN_00308abc; the output was something close to original source from the first day. Following the same step-by-step guidance, I bulk-decompiled every function in the binary into its own .c file, organized in a folder tree by function name. About twenty minutes of CPU time later, there was a navigable pseudo-source codebase to grep through like any other repository.
That folder of pseudo-C, plus the strings, plus the class list, plus the architecture notes, became the working corpus. Reading seven thousand functions by hand is not a thing a single person does in any reasonable timeframe. Reading seven thousand functions with an AI assistant that already has the architectural map, the class list, the string dump, and a clear set of conventions about what counts as evidence, is. We worked one subsystem at a time. First session: enumerate every CA-PH message type and find the dispatcher. Second session: take the version handshake and keepalive and document the byte-by-byte wire format. Third session: walk a complete call setup through the protocol end to end.
I held the line on a few things. Every claim in the working notes had to cite a specific decompiled function as evidence. "I think this field is the transaction ID" got rejected; "this field is the transaction ID because handler X stores it and builder Y copies it back" got accepted. Anything Claude did not know had to be tagged [UNKNOWN] and anything Claude inferred had to be tagged [INFERRED]. Each session was scoped to one subsystem and one deliverable. The discipline was as important as the tool.
The Go SBC, written entirely by Claude
Once the protocol was documented well enough to implement against, Claude wrote a somewhat working M6 session border controller in Go. I want to be very clear about this part. I did not write a line of code. Not one. Claude wrote it all. My job was to run the resulting binary against the lab, capture what the M6 call agent logged when something went sideways, and feed those logs back in for the next pass.
We worked incrementally. Get a TCP connection up to the M6 call agent. Run the binary. Read what the M6 logged about the new socket. Implement the version handshake. The M6 either accepted the version or rejected it, and either way we had a fresh signal to work with. Implement keepalive. Watch the session stay up across minutes, then hours. Implement registration. The M6 added the Go process to its active SBC list. Implement Skinny & RTP proxying. The M6 started routing calls through it, and they completed.

What I actually contributed at that stage was not code. It was running the lab, reading the M6's complaints, picking out the bits that mattered, and supplying twenty years of context about what the call agent was probably trying to do at each step. The judgment was mine. The institutional memory was mine. The code was not.
That last part is, to me, the most interesting thing about this entire project. There is something quietly strange about watching a working Go binary that I did not write, running on a modern Linux box, having a successful conversation with a piece of telecom equipment whose original counterpart was a VxWorks image compiled in 2004. The protocol does not know what year it is. It just wants its handshake.
The bottom line
The bottom line is this. The problem that beat me in 2014 was not really about the M6. It was about what a single person with a strong opinion and a stack of half-broken tooling could realistically accomplish on a long evening after the kids went to bed. The wall between "obsolete proprietary system I used to support" and "I can talk to it from modern code" was just a bit too high in 2014. Ghidra alone would have moved that wall lower. Ghidra plus an AI assistant that read the seven thousand functions, documented the protocol, and wrote the implementation, while I supplied the lab, the call agent logs, and twenty years of context about how the thing was supposed to behave, knocked it down entirely.
I think a lot about the fact that the original VPFW does what it does, very reliably, in 4 megabytes. That is not nostalgia. That is a genuine reminder of what disciplined engineering looked like in an era when memory and CPU were too expensive to waste. Some of it is the RTOS. Some of it is the people who wrote it. Some of it is just the constraint. Modern software could stand to remember.
But the better lesson, the one I really want to leave with, is that legacy does not have to mean inaccessible anymore. There is a lot of in-service telecom gear out there that is one hardware failure away from being a paperweight, and the tools to read it, understand it, and replace it have finally caught up. If you depend on something like that, the time to do this work is now, not when the last spare board dies.
Ten years ago this was impossible. This week it was a fun project after the kids went to bed. And I did not write a single line of code.
Topics: