Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes for cidr equality, validity and consistency #535

Merged
merged 5 commits into from
Nov 4, 2023

Conversation

martijnhoekstra
Copy link
Contributor

@martijnhoekstra martijnhoekstra commented Oct 15, 2023

the root of the problem: 172.168.0.1/30 isn't a valid cidr range, because 172.168.0.1 isn't a valid prefix for a /30, but is accepted by both Cidr.fromString and Cidr.apply, which now cause cidr#toString to output invalid cidr ranges.

This MR changes CIDR equality and printing:

equality:

  • before: ip"172.168.0.1" / 30 != ip"172.168.0.0" / 30, even though it matches the same addressess
  • after: ip"172.168.0.1" / 30 == ip"172.168.0.0" / 30, even though the base address is different

printing:

  • before: (ip"172.168.0.1" / 30).toString == "172.168.0.1/30" even though that isn't a valid CIDR range
  • after: (ip"172.168.0.1" / 30).toString == "172.168.0.0/30" even though that doesn't contain the base address

misc:

  • (adress0 / 0).address == adress0 still holds
  • cidr1 == cidr2 no longer implies cidr1.address == cidr2.address
  • cidr1 == cidr2 no longer implies cidr1.productIterator.toList == cidr2.productIterator.toList

This is not the only way to fix the given problems. The other obvious option is at construction (Cidr.apply) time, set address to prefix, and deprecate address for prefix. This is a bigger breaking change: it will break users that assume (addr / 0).address == addr. It also kills the (somewhat?) useful ability to have this datatype carry at the same time an arbitrary IP address and some cidr range the address is part of. passing around some machines ip / 24 (or something) does come up a fair bit in practice, and comes "for free" in the current implementation.

A third option is to provide separate between strict and lenient constructors

@martijnhoekstra
Copy link
Contributor Author

see also: #534

@mpilquist
Copy link
Member

What if we left equality as it was but then added a method def normalized: Cidr[[A] = Cidr(prefix, prefixBits)? Or we could add a type like Cidr.Strict[A]?

@martijnhoekstra
Copy link
Contributor Author

One of the concrete problems I'm running in to, is that Cidr#toString returns something that's not a CIDR range. So maybe that's the most reasonable place for a fix, either just toString itself (but if you do that, you lose Cidr.fromString(cider.toString).get == cider), or by making something like a Cidr#toSpec: String = s"$prefix/$prefixBits" (but then you don't have the spec format in interpolations)

a problem with cidr#normalize: Cidr[A] is that you'd never know before you if the cidr you have is already normalized or still needs to be, leading to potentially calling .normalize over and over. In that sense, I like the idea of having Cidr.Strict[A] with the semantics in this PR, but then that raises the question about what returns a Strict vs a normal Cidr: maybe most importently, which would you want the ip / bits return?

would you like some drafts of various options?

@mpilquist
Copy link
Member

Okay, why don't we pursue the Cidr.Strict option and see what it looks like?

I'm concerned about overriding toString as it will likely confuse folks if only the prefix is printed. And I'm concerned that overriding equals/hashCode will lead to surprising bugs -- e.g., consider a hash map Map[Cidr[IpAddress], X] when the keys are representing both an ip and a netmask.

@martijnhoekstra
Copy link
Contributor Author

main...martijnhoekstra:strict-cidr is a potential minimal Strict version. It could have its own fromString too.

I'd need to kick the tires on whether this would work for me in practice.

@martijnhoekstra martijnhoekstra force-pushed the print-cidr-prefix branch 2 times, most recently from 98d87fa to 9c1f114 Compare October 18, 2023 06:59
@mpilquist
Copy link
Member

Looks good to me!

* This means the address will never have any bits set outside the prefix. For example, a range
* such as 192.168.0.1/31 is not allowed.
*/
final class Strict[+A <: IpAddress] private (override val prefix: A, override val prefixBits: Int)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: can't these be passed along as ctor args?

Suggested change
final class Strict[+A <: IpAddress] private (override val prefix: A, override val prefixBits: Int)
final class Strict[+A <: IpAddress] private (prefix: A, prefixBits: Int)

Copy link
Contributor Author

@martijnhoekstra martijnhoekstra Oct 20, 2023

Choose a reason for hiding this comment

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

for the prefix bits, yes, that's just plain better.

For prefix, if it's an override val, it overrides def prefix: A = address.transform(_.masked(Ipv4Address.mask(prefixBits)), _.masked(Ipv6Address.mask(prefixBits))) with the val, which adds a field but potentially saves a bit of useless work. I guess I'd rather take the extra field? What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@armanbilge what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

@martijnhoekstra sorry, I missed this.

Instead of adding an extra redundant field, can't we just do override def prefix = address?

Copy link
Contributor

Choose a reason for hiding this comment

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

@mpilquist mpilquist merged commit e25d489 into Comcast:main Nov 4, 2023
11 checks passed
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.

3 participants