Repository: construct/construct

Issue:
Strange stream error when using Pointer()
Hi,

i am currently developing a python utility using the construct library to parse the following structure:

1. field: length of substructure
2. field: data type of substructure
3. field: offset of the substructure name inside the heap
4. field: offset of the substructure  value inside the heap

Remaining bytes: heap

    Struct(
        "offset" / Tell,
        "qualifier" / Prefixed(
            Int32ul,
            Struct(
                "data_type" / BMOF_WMI_TYPE,
                "name_offset" / Int32ul,
                "value_offset" / Int32ul,
                "heap" / Union(
                    None,
                    "name" / BmofHeapReference(
                        lambda context: context._.name_offset,
                        CString("utf_16_le")
                    ),
                    "value" / BmofHeapReference(
                        lambda context: context._.value_offset,
                        BMOF_WMI_DATA(
                            lambda context: context.data_type
                        )
                    )
                )
            ),
            includelength=True
        )
    )

I am now trying to use the Pointer() subcon to parse the name and value substructures inside the heap:

    class BmofHeapReference(IfThenElse):
        # pylint: disable=abstract-method
        """Optional pointer reference to a heap subconstruct"""
        def __init__(self, offset: Callable[[Container], int], subcon: Construct) -> None:
            super().__init__(
                lambda context: offset(context) != 0xFFFFFFFF,  # subcon does exist
                Pointer(
                    offset,
                    subcon
                ),
                Pass
            )

However when i try to parse this data (part of a larger substructure):

    1C 00 00 00 0B 00 00 00 00 00 00 00 08 00 00 00 57 00 4D 00 49 00 00 00 FF FF 00 00

i get the following backtrace:

Traceback (most recent call last):
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 206, in stream_seek
    return stream.seek(offset, whence)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 257, in seek
    super().seek(offset - self.parent_stream_offset)
ValueError: negative seek value -52

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/bin/tarkin", line 8, in <module>
    sys.exit(main_cli())
             ^^^^^^^^^^
  File "/home/wolf/Dokumente/Git/tarkin/src/tarkin/main.py", line 44, in main_cli
    return main(ARGUMENT_PARSER.parse_args())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/Dokumente/Git/tarkin/src/tarkin/main.py", line 35, in main
    bmof: Bmof = BMOF.parse_file(args.path)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 425, in parse_file
    return self.parse_stream(f, **contextkw)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 416, in parse_stream
    return self._parsereport(stream, context, "(parsing)")
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 819, in _parse
    obj = self.subcon._parsereport(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2236, in _parse
    subobj = sc._parsereport(stream, context, path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2770, in _parse
    return self.subcon._parsereport(stream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 4992, in _parse
    return self.subcon._parsereport(substream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 872, in _parse
    return self.subcon.parse(data, **context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 404, in parse
    return self.parse_stream(io.BytesIO(data), **contextkw)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 416, in parse_stream
    return self._parsereport(stream, context, "(parsing)")
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2236, in _parse
    subobj = sc._parsereport(stream, context, path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2770, in _parse
    return self.subcon._parsereport(stream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 819, in _parse
    obj = self.subcon._parsereport(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2236, in _parse
    subobj = sc._parsereport(stream, context, path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2770, in _parse
    return self.subcon._parsereport(stream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 4992, in _parse
    return self.subcon._parsereport(substream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2236, in _parse
    subobj = sc._parsereport(stream, context, path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2770, in _parse
    return self.subcon._parsereport(stream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 3233, in _parse
    parseret = sc._parsereport(stream, context, path)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2770, in _parse
    return self.subcon._parsereport(stream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2530, in _parse
    e = self.subcon._parsereport(stream, context, path)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 819, in _parse
    obj = self.subcon._parsereport(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 4870, in _parse
    return self.subcon._parsereport(substream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2236, in _parse
    subobj = sc._parsereport(stream, context, path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2770, in _parse
    return self.subcon._parsereport(stream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 4992, in _parse
    return self.subcon._parsereport(substream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2236, in _parse
    subobj = sc._parsereport(stream, context, path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2770, in _parse
    return self.subcon._parsereport(stream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 4992, in _parse
    return self.subcon._parsereport(substream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 4870, in _parse
    return self.subcon._parsereport(substream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 3233, in _parse
    parseret = sc._parsereport(stream, context, path)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2770, in _parse
    return self.subcon._parsereport(stream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2530, in _parse
    e = self.subcon._parsereport(stream, context, path)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 819, in _parse
    obj = self.subcon._parsereport(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2236, in _parse
    subobj = sc._parsereport(stream, context, path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2770, in _parse
    return self.subcon._parsereport(stream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 4870, in _parse
    return self.subcon._parsereport(substream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2236, in _parse
    subobj = sc._parsereport(stream, context, path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2770, in _parse
    return self.subcon._parsereport(stream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 3708, in _parse
    subobj = sc._parsereport(stream, context, path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 2770, in _parse
    return self.subcon._parsereport(stream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 3969, in _parse
    return sc._parsereport(stream, context, path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 428, in _parsereport
    obj = self._parse(stream, context, path)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 4411, in _parse
    stream_seek(stream, offset, 2 if offset < 0 else 0, path)
  File "/home/wolf/.local/share/hatch/env/virtual/tarkin/nSILboVs/tarkin/lib/python3.11/site-packages/construct/core.py", line 208, in stream_seek
    raise StreamError("stream.seek() failed, offset %s, whence %s" % (offset, whence), path=path)
construct.core.StreamError: Error in path (parsing) -> root -> data -> objects -> items -> data -> qualifiers -> items -> qualifier -> heap -> name
stream.seek() failed, offset 0, whence 0

It seems to me that for some reason the stream tries to seek backwards and fails.

Any ideas on how to fix this?

Discussion:
@Wer-Wolf: It seems the issue can be solved by using `RestreamData()`:

    Struct(
        "offset" / Tell,
        "qualifier" / Prefixed(
            Int32ul,
            Struct(
                "data_type" / BMOF_WMI_TYPE,
                "name_offset" / Int32ul,
                "value_offset" / Int32ul,
                "heap" / RestreamData(
                    GreedyBytes,
                    Struct(
                        "name" / BmofHeapReference(
                            lambda context: context._.name_offset,
                            CString("utf_16_le")
                        ),
                        "value" / BmofHeapReference(
                            lambda context: context._.value_offset,
                            BMOF_WMI_DATA(
                                lambda context: context._.data_type
                            )
                        )
                    )
                )
            ),
            includelength=True
        )
    )

Can idea on how to solve this properly ?@Wer-Wolf: I think i found the culprit (inside `BytesIOWithOffsets()`):

    def seek(self, offset: int, whence: int = io.SEEK_SET) -> int:
        if whence != io.SEEK_SET:
            super().seek(offset, whence)
        else:
            super().seek(offset - self.parent_stream_offset)
        return self.tell()

I think we need to **add** `parent_stream_offset` onto `offset`, not the other way.@Wer-Wolf: This would also explain why the issue disappears when using `RestreamData()`. In this case the offset would be zero.@Wer-Wolf: It seems that the error was me thinking that the offset passed to `Pointer()` is relative to the pointer itself, instead the offset is relative to the start of the whole stream.

This seems to be the correct solution:

    Struct(
        "offset" / Tell,
        "qualifier" / Prefixed(
            Int32ul,
            Struct(
                "data_type" / BMOF_WMI_TYPE,
                "name_offset" / Int32ul,
                "value_offset" / Int32ul,
                "heap" / Struct(
                    "offset" / Tell,
                    "name" / BmofHeapReference(
                        lambda context: context._.name_offset + context.offset,
                        CString("utf_16_le")
                    ),
                    "value" / BmofHeapReference(
                        lambda context: context._.value_offset + context.offset,
                        BMOF_WMI_DATA(
                            lambda context: context._.data_type
                        )
                    )
                )
            ),
            includelength=True
        )
    )@Wer-Wolf: Maybe it would make sense to introduce a keyword argument for `Pointer()` to signal that the offset is relative to the current position of the pointer itself.@franzhaas: I did run the change through the test suite, and it failed at tests/test_core.py::test_prefixed 

So there appears to be some checked expectations that the sign is the way it used to be...

(This post got overtaken by developments at Wer-Wolf)@Wer-Wolf: I know, forget my suggestion regarding `parent_stream_offset`.

It was my fault for misunderstanding the behavior of `Pointer()`.@Wer-Wolf: Still what do you think about the additional keyword argument for `Pointer()`?@franzhaas: Well, that is a good idea.

If you would like to make a pull request, I am happy to review and check the test coverage and support in every way, but I do not have merge rights to this repository. So I cant promises anything...



Solution patch:
diff --git a/construct/core.py b/construct/core.py
index 8f5919f0..3d0e7539 100644
--- a/construct/core.py
+++ b/construct/core.py
@@ -4387,11 +4387,12 @@ class Pointer(Subconstruct):
 
     Parsing and building seeks the stream to new location, processes subcon, and seeks back to original location. Size is defined as 0 but that does not mean no bytes are written into the stream.
 
-    Offset can be positive, indicating a position from stream beginning forward, or negative, indicating a position from EOF backwards.
+    Offset can be positive, indicating a position from stream beginning forward, or negative, indicating a position from EOF backwards. Alternatively the offset can be interpreted as relative to the current stream position.
 
     :param offset: integer or context lambda, positive or negative
     :param subcon: Construct instance
     :param stream: None to use original stream (default), or context lambda to provide a different stream
+    :param relativeOffset: True to interpret the offset as relative to the current stream position
 
     :raises StreamError: requested reading negative amount, could not read enough bytes, requested writing different amount than actual data, or could not write all bytes
     :raises StreamError: stream is not seekable and tellable
@@ -4407,25 +4408,31 @@ class Pointer(Subconstruct):
         b'\x00\x00\x00\x00\x00\x00\x00\x00Z'
     """
 
-    def __init__(self, offset, subcon, stream=None):
+    def __init__(self, offset, subcon, stream=None, relativeOffset=False):
         super().__init__(subcon)
         self.offset = offset
         self.stream = stream
+        self.relativeOffset = relativeOffset
 
-    def _parse(self, stream, context, path):
+    def _pointer_seek(self, stream, context, path):
         offset = evaluate(self.offset, context)
         stream = evaluate(self.stream, context) or stream
         fallback = stream_tell(stream, path)
-        stream_seek(stream, offset, 2 if offset < 0 else 0, path)
+        if self.relativeOffset:
+            stream_seek(stream, offset, 1, path)
+        else:
+            stream_seek(stream, offset, 2 if offset < 0 else 0, path)
+
+        return fallback
+
+    def _parse(self, stream, context, path):
+        fallback = self._pointer_seek(stream, context, path)
         obj = self.subcon._parsereport(stream, context, path)
         stream_seek(stream, fallback, 0, path)
         return obj
 
     def _build(self, obj, stream, context, path):
-        offset = evaluate(self.offset, context)
-        stream = evaluate(self.stream, context) or stream
-        fallback = stream_tell(stream, path)
-        stream_seek(stream, offset, 2 if offset < 0 else 0, path)
+        fallback = self._pointer_seek(stream, context, path)
         buildret = self.subcon._build(obj, stream, context, path)
         stream_seek(stream, fallback, 0, path)
         return buildret
@@ -4434,26 +4441,40 @@ def _sizeof(self, context, path):
         return 0
 
     def _emitparse(self, code):
+        if self.relativeOffset:
+            func_name = "parse_relative_pointer"
+            seek_args = "1"
+        else:
+            func_name = "parse_pointer"
+            seek_args = "2 if offset < 0 else 0"
+
         code.append(f"""
-            def parse_pointer(io, offset, func):
+            def {func_name}(io, offset, func):
                 fallback = io.tell()
-                io.seek(offset, 2 if offset < 0 else 0)
+                io.seek(offset, {seek_args})
                 obj = func()
                 io.seek(fallback)
                 return obj
         """)
-        return f"parse_pointer(io, {self.offset}, lambda: {self.subcon._compileparse(code)})"
+        return f"{func_name}(io, {self.offset}, lambda: {self.subcon._compileparse(code)})"
 
     def _emitbuild(self, code):
+        if self.relativeOffset:
+            func_name = "build_relative_pointer"
+            seek_args = "1"
+        else:
+            func_name = "build_pointer"
+            seek_args = "2 if offset < 0 else 0"
+
         code.append(f"""
-            def build_pointer(obj, io, offset, func):
+            def {func_name}(obj, io, offset, func):
                 fallback = io.tell()
-                io.seek(offset, 2 if offset < 0 else 0)
+                io.seek(offset, {seek_args})
                 ret = func()
                 io.seek(fallback)
                 return ret
         """)
-        return f"build_pointer(obj, io, {self.offset}, lambda: {self.subcon._compilebuild(code)})"
+        return f"{func_name}(obj, io, {self.offset}, lambda: {self.subcon._compilebuild(code)})"
 
     def _emitprimitivetype(self, ksy, bitwise):
         offset = self.offset.__getfield__() if callable(self.offset) else self.offset


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index eb7068fb..e7ef7de7 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -969,6 +969,27 @@ def test_pointer():
     )
     assert d.parse(bytes(20)) == Container(inner=Container(), x=0)
 
+    d = Struct (
+        'dummy' / Byte,
+        'pointer' / Pointer(1, Byte, relativeOffset=True),
+    )
+
+    common(d, b"\xde\x00\xad", Container(dummy=0xde, pointer=0xad), 1)
+
+    d = Struct (
+        'dummy' / Byte,
+        'pointer' / Pointer(0, Byte, relativeOffset=True),
+    )
+
+    common(d, b"\xde\xad", Container(dummy=0xde, pointer=0xad), 1)
+
+    d = Struct (
+        'dummy' / Byte,
+        'pointer' / Pointer(-1, Byte, relativeOffset=True),
+    )
+
+    common(d, b"\xde", Container(dummy=0xde, pointer=0xde), 1)
+
 def test_peek():
     d = Peek(Int8ub)
     assert d.parse(b"\x01") == 1