Skip to content

Arithmetic: address stepping#123

Merged
zanbaldwin merged 1 commit into
6.xfrom
z/arithmetic
Jun 16, 2026
Merged

Arithmetic: address stepping#123
zanbaldwin merged 1 commit into
6.xfrom
z/arithmetic

Conversation

@zanbaldwin

Copy link
Copy Markdown
Member

Add next(), previous(), and offset(int) to Contracts\ArithmeticInterface.

Add `next()`, `previous()`, and `offset(int)` to `Contracts\ArithmeticInterface`.
@zanbaldwin zanbaldwin self-assigned this Jun 16, 2026
@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds next(), previous(), and offset(int $offset) to Contracts\ArithmeticInterface, implementing them in AbstractIP (covering IPv4 and IPv6) and overriding offset() in Multi to step within the embedded IPv4 address space rather than the enclosing IPv6 space.

  • AbstractIP::offset() delegates directly to the pre-existing Binary::addIntegerOffset() which throws OverflowException when the result falls outside the address space; next() and previous() are thin wrappers.
  • Multi::offset() extracts the short 4-byte binary for embedded addresses, offsets it through an IPv4 instance (triggering IPv4-space overflow detection), then re-packs via the new packIntoNonCanonical() helper that preserves all bits outside the embedded IPv4 region for strategies that implement CanonicalEmbeddingInterface.
  • Tests cover basic stepping, boundary overflow, the next()/previous() delegation contract, and the non-canonical bit-preservation guarantee with a Derived (6to4) strategy.

Confidence Score: 4/5

Safe to merge; the arithmetic logic, overflow detection, and Multi embedding-strategy branching are all correct and well-tested.

The core Binary::addIntegerOffset carry algorithm was already battle-tested; the new code wires it up cleanly and the Multi override correctly isolates stepping within the IPv4 address space. A single documentation nuance — the non-canonical bit-preservation guarantee is stated unconditionally but silently degrades for strategies that don't implement CanonicalEmbeddingInterface — is worth a follow-up but does not affect runtime correctness.

docs/04-helpers.md — the preservation guarantee needs a caveat for CanonicalEmbeddingInterface-less strategies.

Important Files Changed

Filename Overview
src/AbstractIP.php Adds next(), previous(), and offset() delegating to Binary::addIntegerOffset(); straightforward and consistent with existing getNetworkIp/getBroadcastIp patterns.
src/Version/Multi.php Adds packIntoNonCanonical() helper and overrides offset() to step within the embedded IPv4 space; correctly preserves non-canonical IPv6 bits for CanonicalEmbeddingInterface strategies with documented graceful degradation for older strategies.
src/Contracts/ArithmeticInterface.php Adds next(), previous(), and offset(int) with @return static and @throws OverflowException docblocks, consistent with the existing interface style.
tests/Version/MultiTest.php Comprehensive test coverage including data-provider offset/overflow tests, next()/previous() delegation assertion, and a dedicated non-canonical bit-preservation test using Strategy\Derived.
tests/DataProvider/Multi.php Offset and overflow fixtures correctly distinguish embedded (IPv4-space boundary) from non-embedded (IPv6-space boundary) overflow cases.
docs/04-helpers.md Documents non-canonical bit preservation unconditionally; this only holds for CanonicalEmbeddingInterface strategies — a minor caveat that could mislead users of legacy custom strategies.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["caller: ip->offset(n) / next() / previous()"] --> B{Instance type?}

    B -->|IPv4 or IPv6\nAbstractIP| C["Binary::addIntegerOffset(binary, n)"]
    C -->|carry != 0| D["throw OverflowException"]
    C -->|carry == 0| E["new static(newBinary)"]

    B -->|Multi| F{isEmbedded?}
    F -->|No — pure IPv6| G["parent::offset(n)\n→ Binary::addIntegerOffset on 16-byte binary"]
    G -->|carry != 0| D
    G -->|ok| H["new static(newBinary, clone strategy)"]

    F -->|Yes — IPv4 embedded| I["getShortBinary()\n→ extract 4-byte IPv4"]
    I --> J["new IPv4(short)->offset(n)"]
    J -->|OverflowException\nat IPv4 boundary| D
    J -->|ok: newV4| K{strategy implements\nCanonicalEmbeddingInterface?}
    K -->|Yes| L["strategy->packIntoNonCanonical(ipv6, newV4)\npreserves non-IPv4 bits"]
    K -->|No — graceful degradation| M["strategy->pack(newV4)\nreturns canonical form"]
    L --> N["new static(packed16, clone strategy)"]
    M --> N
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["caller: ip->offset(n) / next() / previous()"] --> B{Instance type?}

    B -->|IPv4 or IPv6\nAbstractIP| C["Binary::addIntegerOffset(binary, n)"]
    C -->|carry != 0| D["throw OverflowException"]
    C -->|carry == 0| E["new static(newBinary)"]

    B -->|Multi| F{isEmbedded?}
    F -->|No — pure IPv6| G["parent::offset(n)\n→ Binary::addIntegerOffset on 16-byte binary"]
    G -->|carry != 0| D
    G -->|ok| H["new static(newBinary, clone strategy)"]

    F -->|Yes — IPv4 embedded| I["getShortBinary()\n→ extract 4-byte IPv4"]
    I --> J["new IPv4(short)->offset(n)"]
    J -->|OverflowException\nat IPv4 boundary| D
    J -->|ok: newV4| K{strategy implements\nCanonicalEmbeddingInterface?}
    K -->|Yes| L["strategy->packIntoNonCanonical(ipv6, newV4)\npreserves non-IPv4 bits"]
    K -->|No — graceful degradation| M["strategy->pack(newV4)\nreturns canonical form"]
    L --> N["new static(packed16, clone strategy)"]
    M --> N
Loading

Reviews (1): Last reviewed commit: "feature(arithmetic): ✨ step IP addresses..." | Re-trigger Greptile

Comment thread docs/04-helpers.md
Comment on lines +176 to +179
### Address Arithmetic

Arithmetic is performed within the address space of the instance. For an
IPv4-embedded `Multi` address the embedded IPv4 address is stepped and re-packed

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Non-canonical bit preservation caveat missing from docs

The statement "preserving any bits the strategy does not own" is only accurate for embedding strategies that implement CanonicalEmbeddingInterface. For a legacy or user-defined strategy that implements only EmbeddingStrategyInterface, packIntoNonCanonical falls back to calling the deprecated pack($ipv4), which creates a fully canonical address and silently discards any non-canonical bits (SLA ID, interface ID, etc.) — the same graceful-degradation behaviour already present in getNetworkIp and getBroadcastIp.

Adding a short qualifier (e.g. "…re-packed according to its embedding strategy (preserving any bits the strategy does not own, provided the strategy implements CanonicalEmbeddingInterface)…") would prevent users with custom strategies from being surprised.

@zanbaldwin zanbaldwin merged commit 18eea77 into 6.x Jun 16, 2026
24 checks passed
@zanbaldwin zanbaldwin deleted the z/arithmetic branch June 16, 2026 23:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant