```ts
export function prefixFromMask(mask: string): number {
  if (typeof mask !== "string") {
    throw new Error("mask must be a string");
  }

  // Split the mask into four octet fields separated by dots
  const parts = mask.split(".");
  if (parts.length !== 4) {
    throw new Error("mask must have exactly four dot-separated fields");
  }

  let leadingOnesCount = 0;

  for (const part of parts) {
    // Validate each octet field
    if (!/^\d+$/.test(part)) {
      throw new Error(`octet "${part}" contains non-numeric characters`);
    }
    const octet = parseInt(part, 10);
    if (octet < 0 || octet > 255) {
      throw new Error(`invalid octet value ${octet} in mask`);
    }

    // Count leading ones
    let currentOctet = octet;
    while (currentOctet !== 0 && (currentOctet & 1) === 1) {
      leadingOnesCount += 1;
      currentOctet >>= 1;
    }

    // If the last non-zero bit is not followed by a zero, it's invalid
    if (currentOctet !== 0 && (currentOctet & 1) === 0) {
      throw new Error(`invalid mask: one bits are broken`);
    }
  }

  return leadingOnesCount;
}

export function maskFromPrefix(prefix: number): string {
  if (!Number.isInteger(prefix) || prefix < 0 || prefix > 32) {
    throw new Error("prefix must be an integer from 0 to 32");
  }
  const octets: number[] = [];
  for (let slot = 0; slot < 4; slot += 1) {
    const ones = Math.min(8, Math.max(0, prefix - slot * 8));
    octets.push(256 - 2 ** (8 - ones));
  }
  return octets.join(".");
}
```