commaai/openpilot · error · RuntimeError

Invalid BoundProfilePackage

Error message

Invalid BoundProfilePackage

What it means

_split_bpp() chunks the BoundProfilePackage for installation per SGP.22 §5.7.6 by locating the root TAG_BPP (0xBF36) element via iter_tlv with positions. If the decoded bpp bytes contain no BF36 element, the structure is not recognized as a BPP and RuntimeError('Invalid BoundProfilePackage') is raised.

Source

Thrown at openpilot/common/esim/lpa.py:530

  return b64e(response)


def _parse_tlv_header_len(data: bytes) -> int:
  tag_len = 2 if data[0] & 0x1F == 0x1F else 1
  length_byte = data[tag_len]
  return tag_len + (1 + (length_byte & 0x7F) if length_byte & 0x80 else 1)


def _split_bpp(bpp: bytes) -> list[bytes]:
  """Split a BoundProfilePackage into APDU chunks per SGP.22 §5.7.6."""
  root_value = None
  for tag, value, start, end in iter_tlv(bpp, with_positions=True):
    if tag == TAG_BPP:
      root_value = value
      val_start = start + _parse_tlv_header_len(bpp[start:end])
      break
  if root_value is None:
    raise RuntimeError("Invalid BoundProfilePackage")

  chunks: list[bytes] = []
  for tag, value, start, end in iter_tlv(root_value, with_positions=True):
    if tag == TAG_BPP_COMMAND:
      chunks.append(bpp[0 : val_start + end])
    elif tag in (0xA0, 0xA2):
      chunks.append(bpp[val_start + start : val_start + end])
    elif tag in (0xA1, 0xA3):
      hdr_len = _parse_tlv_header_len(root_value[start:end])
      chunks.append(bpp[val_start + start : val_start + start + hdr_len])
      for _, _, cs, ce in iter_tlv(value, with_positions=True):
        chunks.append(value[cs:ce])
  return chunks


def _parse_install_result(response: bytes) -> dict[str, Any] | None:
  """Parse a ProfileInstallResult from an APDU response, or None if not present."""
  root = find_tag(response, TAG_PROFILE_INSTALL_RESULT)

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Verify you pass exactly the boundProfilePackage field from the ES9+ handleNotification response to load_bpp
  2. Inspect the decoded bytes' first tags (hex dump) — a valid BPP starts with BF36; if it starts with another BF tag, the wrong field was used
  3. Re-run the download from the QR; server-transient truncation yields a new, complete BPP (note: some already-consumed QRs cannot be reused)
Defensive patterns

Strategy: validation

Validate before calling

import base64
from openpilot.common.esim.lpa import find_tag, TAG_BPP

raw = base64.b64decode(b64_bpp)
if find_tag(raw, TAG_BPP) is None:
  raise SystemExit('payload is not a BoundProfilePackage (no BF36); wrong field or corrupted BPP')

Type guard

def looks_like_bpp(b64: str) -> bool:
  try:
    return find_tag(base64.b64decode(b64), 0xBF36) is not None
  except Exception:
    return False

Prevention

When it happens

Trigger: load_bpp() is called with base64 that decodes to bytes lacking a BF36 root — wrong field passed (e.g. profileMetadata instead of boundProfilePackage), truncated BPP from a segmented server response, or a server-format mismatch (SGP.22 version differences in wrapping).

Common situations: Wiring bugs in custom download flows mixing up handleNotification response fields; carrier servers returning the BPP wrapped or encoded differently; base64 corruption (whitespace/newlines) truncating the payload.

Related errors


AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15). Data as JSON: /api/errors/de6bc36f708989d8. Report an issue: GitHub.