///|
/// 解析 IPv6 头(NextHeader 偏移 6,src 8,dst 24,固定头 40 字节;帧内 +14)
fn parse_ipv6(
  data : Bytes,
  ts_sec : Int64,
  ts_usec : Int,
  local_ips : FixedArray[IpAddr],
) -> ParsedPacket? {
  let total = data.length()
  let l3 = ETH_HDR
  guard total >= l3 + 40 else { return None }
  let vtc = data[l3].to_int()
  guard vtc >> 4 == 6 else { return None } // 非 IPv6
  let payload_len = u16(data, l3 + 4) // 不含 40 字节固定头
  let ip_total = 40 + payload_len
  guard ip_total <= total - l3 else { return None }
  let proto = classify_proto(data[l3 + 6])
  let src = { v4: false, bytes: read_bytes(data, l3 + 8, 16) }
  let dst = { v4: false, bytes: read_bytes(data, l3 + 24, 16) }
  let l4 = match proto {
    Tcp => parse_tcp(data, l3 + 40, payload_len)
    Udp => parse_udp(data, l3 + 40, payload_len)
    _ => Some((0, 0, payload_len))
  }
  let (sport, dport, payload) = match l4 {
    Some(x) => x
    None => return None
  }
  Some(
    ParsedPacket::new(
      ts_sec,
      ts_usec,
      { src_ip: src, dst_ip: dst, src_port: sport, dst_port: dport, proto },
      direction(src, dst, local_ips),
      payload,
      ip_total,
    ),
  )
}