"""GLB and ZIP fixture construction, mutation, and case records.""" from __future__ import annotations import binascii import struct import zlib from copy import deepcopy from typing import Callable from simplecadapi.scene import ( canonical_json_bytes, canonical_zip_bytes, parse_strict_json, preflight_glb, preflight_zip_bytes, ) from .common import JsonObject, b64 LOCAL = struct.Struct(" float: result = struct.unpack(" bytes: positions = [tuple(_f32(component) for component in point) for point in positions] position_bytes = b"".join(struct.pack(" bytes: json_length = struct.unpack_from(" bytes: local_parts: list[bytes] = [] central_parts: list[bytes] = [] local_offset = 0 for name in sorted(members, key=lambda value: value.encode("ascii")): name_bytes = name.encode("ascii") payload = members[name] compressor = zlib.compressobj(level=9, wbits=-zlib.MAX_WBITS) compressed = compressor.compress(payload) + compressor.flush() crc = binascii.crc32(payload) & 0xFFFFFFFF local_header = LOCAL.pack( 0x04034B50, 20, 0x0800, 8, 0, 0x0021, crc, len(compressed), len(payload), len(name_bytes), 0, ) local_parts.extend((local_header, name_bytes, compressed)) central_parts.extend( ( CENTRAL.pack( 0x02014B50, 0x0314, 20, 0x0800, 8, 0, 0x0021, crc, len(compressed), len(payload), len(name_bytes), 0, 0, 0, 0, 0x81A40000, local_offset, ), name_bytes, ) ) local_offset += len(local_header) + len(name_bytes) + len(compressed) central = b"".join(central_parts) return b"".join(local_parts) + central + EOCD.pack( 0x06054B50, 0, 0, len(members), len(members), len(central), local_offset, 0, ) def _replace_u16(data: bytes, offset: int, value: int) -> bytes: result = bytearray(data) struct.pack_into(" bytes: result = bytearray(data) struct.pack_into(" int: return 28 + struct.unpack_from(" bytes: return _replace_u32(data, _glb_bin_start(data) + relative_offset, value) def _set_path(value: object, path: tuple[object, ...], replacement: object) -> None: current = value for part in path[:-1]: current = current[part] # type: ignore[index] current[path[-1]] = replacement # type: ignore[index] def _add_path(value: object, path: tuple[object, ...], key: str, item: object) -> None: current = value for part in path: current = current[part] # type: ignore[index] current[key] = item # type: ignore[index] def _append_path(value: object, path: tuple[object, ...], item: object) -> None: current = value for part in path: current = current[part] # type: ignore[index] current.append(item) # type: ignore[attr-defined] def _nonminimal_glb_json_padding(data: bytes) -> bytes: json_length = struct.unpack_from(" bytes: json_length = struct.unpack_from(" float: return struct.unpack(" dict[str, object]: try: info = preflight_glb(payload, expected_kind=expected_kind) error = None actual_valid = True actual_kind = info.kind except ValueError as exc: error = str(exc) actual_valid = False actual_kind = None assert actual_valid == valid, name return { "error": error, "expected_kind": expected_kind, "kind": actual_kind, "name": name, "payload_base64": b64(payload), "valid": valid, } def _zip_case( name: str, payload: bytes, valid: bool, used_deflate: bool | None, ) -> dict[str, object]: try: info = preflight_zip_bytes(payload) error = None actual_valid = True actual_deflate = info.used_deflate except ValueError as exc: error = str(exc) actual_valid = False actual_deflate = None assert actual_valid == valid, name assert actual_deflate == used_deflate, name return { "error": error, "name": name, "payload_base64": b64(payload), "used_deflate": used_deflate, "valid": valid, } def _zip_layout(data: bytes) -> tuple[list[tuple[int, int, int]], int, int]: eocd = len(data) - EOCD.size count = struct.unpack_from(" bytes: entries, _central, _eocd = _zip_layout(data) central_offset, local_offset, name_length = entries[index] assert len(name) == name_length assert struct.unpack_from(" bytes: entries, central, eocd = _zip_layout(data) result = bytearray(b"x" + data) for central_offset, local_offset, _name_length in entries: struct.pack_into(" bytes: _entries, central, eocd = _zip_layout(data) result = bytearray(data[:central] + b"x" + data[central:]) struct.pack_into(" tuple[bytes, bytes, list[dict[str, object]], list[dict[str, object]]]: canonical_zip = canonical_zip_bytes(members) deflate_zip = _deflate_zip(members) string_bounds_glb = replace_glb_json( triangle_glb, lambda document: document["accessors"][0]["min"].__setitem__(0, "0"), ) boolean_scene_glb = replace_glb_json( triangle_glb, lambda document: document.__setitem__("scene", False) ) simple_triangle = glb( "triangle", [(0, 0, 0), (1, 0, 0), (0, 1, 0)], [0, 1, 2], [(0, 0, 1)] * 3, ) float_extreme_line = glb( "line", [ ( _f32_from_bits(0xFF7FFFFF), _f32_from_bits(0x80000001), _f32_from_bits(0x007FFFFF), ), ( _f32_from_bits(0x80800000), 0.0, _f32_from_bits(0x00000001), ), ( _f32_from_bits(0x00800000), _f32_from_bits(0x007FFFFF), _f32_from_bits(0x7F7FFFFF), ), ], [0, 1, 1, 2], ) glb_payloads: list[tuple[str, bytes, str, bool]] = [ ("valid_triangle", triangle_glb, "triangle", True), ("valid_line", line_glb, "line", True), ("valid_float32_finite_extremes", float_extreme_line, "line", True), ("string_accessor_bounds", string_bounds_glb, "triangle", False), ("boolean_scene_index", boolean_scene_glb, "triangle", False), ] for name, base, path, key, value in ( ("root_extensions_used", triangle_glb, (), "extensionsUsed", []), ("asset_extras", triangle_glb, ("asset",), "extras", {}), ("node_name", triangle_glb, ("nodes", 0), "name", "node"), ("scene_name", triangle_glb, ("scenes", 0), "name", "scene"), ("mesh_name", triangle_glb, ("meshes", 0), "name", "mesh"), ( "primitive_material", triangle_glb, ("meshes", 0, "primitives", 0), "material", 0, ), ("buffer_uri", triangle_glb, ("buffers", 0), "uri", "buffer.bin"), ("buffer_view_byte_stride", triangle_glb, ("bufferViews", 0), "byteStride", 12), ("position_accessor_byte_offset", triangle_glb, ("accessors", 0), "byteOffset", 0), ("normal_accessor_normalized", triangle_glb, ("accessors", 1), "normalized", False), ("index_accessor_sparse", triangle_glb, ("accessors", 2), "sparse", {}), ): glb_payloads.append( ( name, replace_glb_json( base, lambda document, path=path, key=key, value=value: _add_path( document, path, key, value ), ), "triangle", False, ) ) for name, base, path, value, kind in ( ("empty_position_accessor", line_glb, ("accessors", 0, "count"), 0, "line"), ("empty_index_accessor", line_glb, ("accessors", 1, "count"), 0, "line"), ( "wrong_triangle_mode", triangle_glb, ("meshes", 0, "primitives", 0, "mode"), 1, "triangle", ), ( "wrong_position_component_type", triangle_glb, ("accessors", 0, "componentType"), 5125, "triangle", ), ("wrong_normal_count", triangle_glb, ("accessors", 1, "count"), 1, "triangle"), ( "wrong_index_component_type", triangle_glb, ("accessors", 2, "componentType"), 5125, "triangle", ), ("buffer_view_gap", triangle_glb, ("bufferViews", 1, "byteOffset"), 4, "triangle"), ( "buffer_view_wrong_target", triangle_glb, ("bufferViews", 0, "target"), 34963, "triangle", ), ( "buffer_view_wrong_length", triangle_glb, ("bufferViews", 0, "byteLength"), 4, "triangle", ), ): glb_payloads.append( ( name, replace_glb_json( base, lambda document, path=path, value=value: _set_path( document, path, value ), ), kind, False, ) ) glb_payloads.extend( ( ("bad_glb_magic", _replace_u32(triangle_glb, 0, 0), "triangle", False), ("bad_glb_version", _replace_u32(triangle_glb, 4, 1), "triangle", False), ( "bad_glb_total_length", _replace_u32(triangle_glb, 8, len(triangle_glb) + 4), "triangle", False, ), ("bad_json_chunk_type", _replace_u32(triangle_glb, 16, 0), "triangle", False), ( "nonminimal_json_padding", _nonminimal_glb_json_padding(triangle_glb), "triangle", False, ), ( "bad_bin_chunk_type", _replace_u32( triangle_glb, 24 + struct.unpack_from("