diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..19bac1f --- /dev/null +++ b/.credo.exs @@ -0,0 +1,145 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any exec using `mix credo -C `. If no exec name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: ["lib/", "src/", "web/", "apps/"], + excluded: [~r"/_build/", ~r"/deps/"] + }, + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: false, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: [ + {Credo.Check.Consistency.ExceptionNames}, + {Credo.Check.Consistency.LineEndings}, + {Credo.Check.Consistency.ParameterPatternMatching}, + {Credo.Check.Consistency.SpaceAroundOperators}, + {Credo.Check.Consistency.SpaceInParentheses}, + {Credo.Check.Consistency.TabsOrSpaces}, + + # For some checks, like AliasUsage, you can only customize the priority + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, priority: :low}, + + # For others you can set parameters + + # If you don't want the `setup` and `test` macro calls in ExUnit tests + # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just + # set the `excluded_macros` parameter to `[:schema, :setup, :test]`. + # + {Credo.Check.Design.DuplicatedCode, excluded_macros: []}, + + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, exit_status: 2}, + {Credo.Check.Design.TagFIXME}, + + {Credo.Check.Readability.FunctionNames}, + {Credo.Check.Readability.LargeNumbers}, + {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 120}, + {Credo.Check.Readability.ModuleAttributeNames}, + {Credo.Check.Readability.ModuleDoc}, + {Credo.Check.Readability.ModuleNames}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs}, + {Credo.Check.Readability.ParenthesesInCondition}, + {Credo.Check.Readability.PredicateFunctionNames}, + {Credo.Check.Readability.PreferImplicitTry}, + {Credo.Check.Readability.RedundantBlankLines}, + {Credo.Check.Readability.StringSigils}, + {Credo.Check.Readability.TrailingBlankLine}, + {Credo.Check.Readability.TrailingWhiteSpace}, + {Credo.Check.Readability.VariableNames}, + {Credo.Check.Readability.Semicolons}, + {Credo.Check.Readability.SpaceAfterCommas}, + + {Credo.Check.Refactor.DoubleBooleanNegation}, + {Credo.Check.Refactor.CondStatements}, + {Credo.Check.Refactor.CyclomaticComplexity}, + {Credo.Check.Refactor.FunctionArity}, + {Credo.Check.Refactor.LongQuoteBlocks}, + {Credo.Check.Refactor.MatchInCondition}, + {Credo.Check.Refactor.NegatedConditionsInUnless}, + {Credo.Check.Refactor.NegatedConditionsWithElse}, + {Credo.Check.Refactor.Nesting}, + {Credo.Check.Refactor.PipeChainStart}, + {Credo.Check.Refactor.UnlessWithElse}, + + {Credo.Check.Warning.BoolOperationOnSameValues}, + {Credo.Check.Warning.IExPry}, + {Credo.Check.Warning.IoInspect}, + {Credo.Check.Warning.LazyLogging}, + {Credo.Check.Warning.OperationOnSameValues}, + {Credo.Check.Warning.OperationWithConstantResult}, + {Credo.Check.Warning.UnusedEnumOperation}, + {Credo.Check.Warning.UnusedFileOperation}, + {Credo.Check.Warning.UnusedKeywordOperation}, + {Credo.Check.Warning.UnusedListOperation}, + {Credo.Check.Warning.UnusedPathOperation}, + {Credo.Check.Warning.UnusedRegexOperation}, + {Credo.Check.Warning.UnusedStringOperation}, + {Credo.Check.Warning.UnusedTupleOperation}, + {Credo.Check.Warning.RaiseInsideRescue}, + + # Controversial and experimental checks (opt-in, just remove `, false`) + # + {Credo.Check.Refactor.ABCSize, false}, + {Credo.Check.Refactor.AppendSingleItem, false}, + {Credo.Check.Refactor.VariableRebinding, false}, + {Credo.Check.Warning.MapGetUnsafePass, false}, + {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, + + # Deprecated checks (these will be deleted after a grace period) + # + {Credo.Check.Readability.Specs, false}, + {Credo.Check.Warning.NameRedeclarationByAssignment, false}, + {Credo.Check.Warning.NameRedeclarationByCase, false}, + {Credo.Check.Warning.NameRedeclarationByDef, false}, + {Credo.Check.Warning.NameRedeclarationByFn, false}, + + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + ] +} diff --git a/lib/enumerable/ip/prefix.ex b/lib/enumerable/ip/prefix.ex index 8100491..d71439c 100644 --- a/lib/enumerable/ip/prefix.ex +++ b/lib/enumerable/ip/prefix.ex @@ -35,7 +35,7 @@ defimpl Enumerable, for: IP.Prefix do true """ @spec member?(Prefix.t, Address.t) :: {:ok, boolean} | {:error, module} - def member?(prefix, %Address{} = address), do: {:ok, Prefix.contains?(prefix, address)} + def member?(prefix, %Address{} = address), do: {:ok, Prefix.contains_address?(prefix, address)} @doc """ Allows the reduction of `prefix` into a colection of addresses. diff --git a/lib/ip/prefix.ex b/lib/ip/prefix.ex index 87ba325..a3bd403 100644 --- a/lib/ip/prefix.ex +++ b/lib/ip/prefix.ex @@ -228,7 +228,7 @@ defmodule IP.Prefix do """ @spec first(t) :: Address.t def first(%Prefix{address: %Address{address: address, version: version}, mask: mask}) do - Address.from_integer!(address &&& mask, version) + Address.from_integer!(lowest_address(address, mask), version) end @doc """ @@ -245,12 +245,11 @@ defmodule IP.Prefix do #IP.Address<2001:db8::ffff:ffff:ffff:ffff DOCUMENTATION> """ def last(%Prefix{address: %Address{address: address, version: 4}, mask: mask}) do - Address.from_integer!((address &&& mask) + (~~~mask &&& @ipv4_mask), 4) + Address.from_integer!(highest_address(address, mask, 4), 4) end def last(%Prefix{address: %Address{address: address, version: 6}, mask: mask}) do - address = (address &&& mask) + (~~~mask &&& @ipv6_mask) - Address.from_integer!(address, 6) + Address.from_integer!(highest_address(address, mask, 6), 6) end @doc """ @@ -260,38 +259,84 @@ defmodule IP.Prefix do ## Examples iex> IP.Prefix.from_string!("192.0.2.0/24") - ...> |> IP.Prefix.contains?(IP.Address.from_string!("192.0.2.127")) + ...> |> IP.Prefix.contains_address?(IP.Address.from_string!("192.0.2.127")) true iex> IP.Prefix.from_string!("192.0.2.0/24") - ...> |> IP.Prefix.contains?(IP.Address.from_string!("198.51.100.1")) + ...> |> IP.Prefix.contains_address?(IP.Address.from_string!("198.51.100.1")) false iex> IP.Prefix.from_string!("2001:db8::/64") - ...> |> IP.Prefix.contains?(IP.Address.from_string!("2001:db8::1")) + ...> |> IP.Prefix.contains_address?(IP.Address.from_string!("2001:db8::1")) true iex> IP.Prefix.from_string!("2001:db8::/64") - ...> |> IP.Prefix.contains?(IP.Address.from_string!("2001:db8:1::1")) + ...> |> IP.Prefix.contains_address?(IP.Address.from_string!("2001:db8:1::1")) false """ - def contains?(%Prefix{address: %Address{address: addr0, version: 4}, mask: mask} = _prefix, - %Address{address: addr1, version: 4} = _address) - when (addr0 &&& mask) <= addr1 - and ((addr0 &&& mask) + (~~~(mask) &&& @ipv4_mask)) >= addr1 + def contains_address?(%Prefix{address: %Address{address: addr0, version: 4}, mask: mask} = _prefix, + %Address{address: addr1, version: 4} = _address) + when lowest_address(addr0, mask) <= addr1 + and highest_address(addr0, mask, 4) >= addr1 do true end - def contains?(%Prefix{address: %Address{address: addr0, version: 6}, mask: mask} = _prefix, - %Address{address: addr1, version: 6} = _address) - when (addr0 &&& mask) <= addr1 - and ((addr0 &&& mask) + (~~~(mask) &&& @ipv6_mask)) >= addr1 + def contains_address?(%Prefix{address: %Address{address: addr0, version: 6}, mask: mask} = _prefix, + %Address{address: addr1, version: 6} = _address) + when lowest_address(addr0, mask) <= addr1 + and highest_address(addr0, mask, 6) >= addr1 do true end - def contains?(_prefix, _address), do: false + def contains_address?(_prefix, _address), do: false + + @doc """ + Returns `true` or `false` depending on whether the supplied `inside` is + completely contained by `outside`. + + ## Examples + + iex> outside = IP.Prefix.from_string!("192.0.2.0/24") + ...> inside = IP.Prefix.from_string!("192.0.2.128/25") + ...> IP.Prefix.contains_prefix?(outside, inside) + true + + iex> outside = IP.Prefix.from_string!("192.0.2.128/25") + ...> inside = IP.Prefix.from_string!("192.0.2.0/24") + ...> IP.Prefix.contains_prefix?(outside, inside) + false + + iex> outside = IP.Prefix.from_string!("2001:db8::/64") + ...> inside = IP.Prefix.from_string!("2001:db8::/128") + ...> IP.Prefix.contains_prefix?(outside, inside) + true + + iex> outside = IP.Prefix.from_string!("2001:db8::/128") + ...> inside = IP.Prefix.from_string!("2001:db8::/64") + ...> IP.Prefix.contains_prefix?(outside, inside) + false + + """ + @spec contains_prefix?(t, t) :: true | false + def contains_prefix?(%Prefix{address: %Address{address: oaddr, version: 4}, mask: omask} = _outside, + %Prefix{address: %Address{address: iaddr, version: 4}, mask: imask} = _inside) + when lowest_address(oaddr, omask) <= lowest_address(iaddr, imask) + and highest_address(oaddr, omask, 4) >= highest_address(iaddr, imask, 4) + do + true + end + + def contains_prefix?(%Prefix{address: %Address{address: oaddr, version: 6}, mask: omask} = _outside, + %Prefix{address: %Address{address: iaddr, version: 6}, mask: imask} = _inside) + when lowest_address(oaddr, omask) <= lowest_address(iaddr, imask) + and highest_address(oaddr, omask, 6) >= highest_address(iaddr, imask, 6) + do + true + end + + def contains_prefix?(_outside, _inside), do: false @doc """ Generate an EUI-64 host address within the specifed IPv6 `prefix`. diff --git a/lib/ip/prefix/helpers.ex b/lib/ip/prefix/helpers.ex index ffef3e4..25ee441 100644 --- a/lib/ip/prefix/helpers.ex +++ b/lib/ip/prefix/helpers.ex @@ -26,4 +26,24 @@ defmodule IP.Prefix.Helpers do acc = fun.(n, acc) n_times_reduce(n - 1, acc, fun) end + + defmacro lowest_address(addr, mask) do + quote do + unquote(addr) &&& unquote(mask) + end + end + + defmacro highest_address(addr, mask, 4) do + quote do + (unquote(addr) &&& unquote(mask)) + + (~~~unquote(mask) &&& 0xffffffff) + end + end + + defmacro highest_address(addr, mask, 6) do + quote do + (unquote(addr) &&& unquote(mask)) + + (~~~unquote(mask) &&& 0xffffffffffffffffffffffffffffffff) + end + end end