```typescript
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(".");
}

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

  const parts = mask.split(".").filter(part => part.length > 0 && /^\d+$/.test(part));
  if (parts.length !== 4) {
    throw new Error("mask must have exactly four dot-separated fields");
  }

  let prefix = 0;
  for (let i = 0; i < parts.length; i++) {
    const octet = parseInt(parts[i], 10);
    if (octet < 0 || octet > 255) {
      throw new Error("each field in the mask must be a decimal digit from 0 to 255");
    }
    prefix += octet << (8 * (3 - i));
  }

  let ones = 0;
  for (let i = 0; i < 4; i++) {
    const octet = (prefix >> (8 * i)) & 255;
    if ((octet & 1) === 0) {
      break;
    }
    ones++;
  }

  // Check for broken ones
  let hasBrokenOnes = false;
  for (let i = 3; i >= 0 && !hasBrokenOnes; i--) {
    const octet = (prefix >> (8 * i)) & 255;
    if ((octet & 1) === 0) {
      hasBrokenOnes = true;
    } else {
      ones++;
    }
  }

  if (hasBrokenOnes) {
    throw new Error("mask's one bits are broken by a zero");
  }

  return ones;
}
```