Files
cimtechniques-service-suite/docs/subnet-mask-handling-handoff.md
2026-06-04 16:06:47 -04:00

6.7 KiB
Raw Permalink Blame History

DA-12C Subnet Mask / Subnet-Bits Handling — Service-Tool Implementation Spec

Audience: the DA-12C service-tool (Python) project. Scope: host-side only. No DA-12 firmware change is required or authorized. Status: verified against DA-12C firmware. This document is self-contained — you do not need access to the firmware to implement against it.


1. What the DA-12C stores

The DA-12C stores its subnet setting as a single byte, xpSubnetBits.

This byte is the count of host bits — the number of trailing zero bits in the subnet mask. It is NOT the CIDR prefix length. This is the legacy Lantronix XPort encoding the firmware inherited.

xpSubnetBits = 32  CIDR_prefix = number of trailing zero bits in the mask

255.255.255.0 (/24)  -> stored value 8     (8 host/zero bits)
255.255.0.0   (/16)  -> stored value 16
255.0.0.0     (/8)   -> stored value 24

⚠️ The naming trap that has caused field confusion. The field is labeled "Subnet bits," which most people read as the CIDR prefix (24 for a /24). It is the opposite end — the host-bit count. A user who wants 255.255.255.0 and enters 24 actually gets 255.0.0.0 (a /8). To get 255.255.255.0 they must enter 8. Surfacing the dotted mask alongside the stored number (this spec) is intended to remove that ambiguity.

When the DA-12C applies the setting, it converts the stored byte back to a dotted mask with:

nm = 0xFFFFFFFF & ~((1UL << xpSubnetBits) - 1);   // host-bit count -> dotted mask

This round-trips correctly for any contiguous mask, including non-octet-aligned ones (/23, /28, etc.). Nothing about the stored representation changes — this spec only adds a friendlier display and an alternate input format.


2. What to build in the service tool

2.1 Display

Show both the stored host-bit count and the equivalent dotted subnet mask, so the user sees what is stored and what it means. Example:

Subnet bits: 8   (mask 255.255.255.0)

2.2 Edit

Accept either input format and store the host-bit count unchanged:

  • A bare integer 032 → interpreted as the host-bit count (the legacy method, preserved exactly so existing users are unaffected).
  • A dotted-decimal mask (contains .) → converted to the host-bit count and stored.

Disambiguate by the presence of a .: contains a dot → parse as a mask; otherwise → parse as an integer host-bit count.

The stored value written back to the device is always the host-bit count. The legacy entry method is not deprecated.


3. Conversion rules

Mask → stored value:

stored = 32  prefix_length        (prefix_length = count of leading 1-bits)

Validate the mask is contiguous (all 1-bits followed by all 0-bits) before converting. Reject masks like 255.0.255.0.

Stored value → mask (for display; mirrors the firmware):

mask = 0xFFFFFFFF & ~((1 << stored) - 1)

4. Edge cases — handle these host-side

  • Default sentinels. Stored 0 and 255 are both treated by the firmware as "default → 255.255.255.0." When displaying a stored 0 or 255, show the mask as 255.255.255.0 and label it the default. Do not convert an entered mask of 255.255.255.0 to stored 0 — store 8 for it (the normal value). Leave an existing stored 0/255 untouched unless the user edits the field.
  • Clamp valid stored values to 131. The firmware computes 1UL << stored, which is undefined behavior when stored >= 32, and the device only guards the 0/255 sentinels on its primary (NetBurner) path. Reject anything outside 131, other than the 0/255 sentinels you may read back from a device.
  • Reject 0.0.0.0 (/0). Maps to stored 32 (undefined behavior on the device). Not valid.
  • Reject 255.255.255.255 (/32). Maps to host bits 0, which collides with the "default" sentinel and cannot be represented. Not meaningful for this device.

5. Round-trip reference table

Dotted mask CIDR Stored xpSubnetBits
255.255.255.252 /30 2
255.255.255.240 /28 4
255.255.255.128 /25 7
255.255.255.0 /24 8
255.255.254.0 /23 9
255.255.252.0 /22 10
255.255.0.0 /16 16
255.0.0.0 /8 24

6. Reference helpers (Python)

def mask_to_subnet_bits(mask: str) -> int:
    """Dotted-decimal mask -> stored host-bit count. Raises ValueError if invalid."""
    octets = [int(o) for o in mask.split(".")]
    if len(octets) != 4 or any(o < 0 or o > 255 for o in octets):
        raise ValueError(f"Invalid mask: {mask}")
    value = (octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]
    # Must be contiguous 1s followed by contiguous 0s
    inverted = (~value) & 0xFFFFFFFF          # host portion as 1s
    if inverted & (inverted + 1) != 0:
        raise ValueError(f"Non-contiguous mask: {mask}")
    host_bits = bin(inverted).count("1")      # = trailing zeros of the mask
    if not (1 <= host_bits <= 31):
        raise ValueError(f"Mask out of supported range (1-31 host bits): {mask}")
    return host_bits


def subnet_bits_to_mask(stored: int) -> str:
    """Stored host-bit count -> dotted-decimal mask (mirrors firmware)."""
    if stored in (0, 255):                    # firmware default sentinels
        return "255.255.255.0"
    if not (1 <= stored <= 31):
        raise ValueError(f"Unsupported stored subnet bits: {stored}")
    nm = 0xFFFFFFFF & ~((1 << stored) - 1)
    return f"{(nm >> 24) & 0xFF}.{(nm >> 16) & 0xFF}.{(nm >> 8) & 0xFF}.{nm & 0xFF}"


def parse_subnet_input(text: str) -> int:
    """Accept either a dotted mask or a bare host-bit count; return stored value."""
    text = text.strip()
    if "." in text:
        return mask_to_subnet_bits(text)
    n = int(text)
    if not (1 <= n <= 31):
        raise ValueError(f"Host-bit count out of range (1-31): {n}")
    return n

7. Acceptance checks

  • Entering 8 and entering 255.255.255.0 both store 8 and display 8 (mask 255.255.255.0).
  • Reading a device with stored 8 displays both forms; writing it back is unchanged.
  • Stored 0/255 display as 255.255.255.0 (default) and are left untouched unless the user edits the field.
  • Non-contiguous masks, 0.0.0.0, 255.255.255.255, and values outside 131 are rejected with a clear message.

8. Provenance (firmware references, informational)

Not needed to implement, recorded for traceability:

  • config.h:155xpSubnetBits is a single unsigned char.
  • op05.c:689 — the misleading "Subnet bits (0=class A B C)" menu label.
  • netburner.c:480494 — the active DA-12C conversion path (sentinels 0/255 → default; nm = 0xFFFFFFFF & ~((1UL << bits) - 1)).
  • rn131.c:580, wroom.c:267 — the same conversion on the legacy Wi-Fi paths.