ip 1.7.7 → 1.7.8
raw patch · 24 files changed
+4302/−3633 lines, 24 filesdep ~aesondep ~attoparsecdep ~bytebuildsetup-changednew-uploaderPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: aeson, attoparsec, bytebuild, byteslice, bytesmith, bytestring, natural-arithmetic, primitive, text, text-short, vector, wide-word, word-compat
API changes (from Hackage documentation)
Files
- CHANGELOG.md +102/−0
- README.md +25/−0
- Setup.hs +0/−2
- ip.cabal +70/−59
- src/Data/ByteString/Builder/Fixed.hs +13/−13
- src/Data/Text/Builder/Common/Compat.hs +1/−0
- src/Data/Text/Builder/Common/Internal.hs +36/−24
- src/Data/Text/Builder/Fixed.hs +13/−12
- src/Data/Text/Builder/Variable.hs +30/−28
- src/Data/Word/Synthetic/Word12.hs +125/−114
- src/Net/IP.hs +135/−111
- src/Net/IPv4.hs +1447/−1366
- src/Net/IPv6.hs +1065/−957
- src/Net/Mac.hs +470/−390
- src/Net/Types.hs +13/−15
- test/Bench.hs +89/−78
- test/IPv4ByteString1.hs +40/−35
- test/IPv4DecodeText1.hs +19/−18
- test/IPv4DecodeText2.hs +19/−18
- test/IPv4Text1.hs +17/−15
- test/IPv4Text2.hs +13/−13
- test/Naive.hs +18/−16
- test/Net/IPv4Spec.hs +91/−60
- test/Test.hs +451/−289
+ CHANGELOG.md view
@@ -0,0 +1,102 @@+# Changelog+All notable changes to this project will be documented in this file.++The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)+and this project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/).++## 1.7.8 -- 2024-03-01++* Update package metadata.+* Relaxed upper bounds on dependencies.++## 1.7.7 -- 2023-08-24++* Add `isSubsetOf` for `IPv4` and `IPv6` ranges.++## 1.7.6 -- 2022-10-07++* Bump upper bound on `text` to `< 2.1`.+* Add `Hashable` instances for `IP` and `IPv6`.++## 1.7.5 -- 2022-07-28++* Add `boundedBuilderOctetsBE` and `boundedBuilderOctetsLE` to `Net.IPv4`.+* Make doctests work again. Requires `doctest-0.20` or higher.+* Bump upper bound on `attoparsec` to `< 0.15`.+* Bump upper bound on `hashable` to `< 1.5`.+* Derive `Generic` for `Net.IPv6.IPv6`.+* Bump lower bound on `wide-word` to `>= 0.1.1.2`.+* Add compatibility with GHC 9.2.3.++## 1.7.4 -- 2021-12-28++* Add `decodeUtf8Bytes` to `Net.IP`.+* Fix IPv4 octet overflow bug (#74)++## 1.7.3 -- 2021-01-22++* Export `decodeOctets` from `Net.Mac`.+* Add `encodeShort` to `Net.Mac`.++## 1.7.2 -- 2020-05-30+* Fix bug in `doctest` documentation+* Bump upper bound on `aeson`: (< 1.5) -> (< 1.6)++## 1.7.1 -- 2020-01-22+* Deprecate 'decodeBytes' in favor of 'decodeOctets'.+* Add `Bytes`-oriented encode and decode functions to `Net.Mac`:+ `boundedBuilderUtf8`, `decodeUtf8Bytes`, and `parserUtf8Bytes`.+* Add `parserRangeUtf8Bytes` and `parserRangeUtf8BytesLenient` to+ both `Net.IPv4` and `Net.IPv6` modules.++## 1.7.0 -- 2019-11-05+* Add `Data` instances for all types.+* Add `Ix` instances for all address types.+* Add missing `ToJSON`/`FromJSON` instances for `IPv6Range`.+* Remove `Num`, `Integral`, and `Real` instances from `IPv6`.+* Remove `Bits` instance for `IPv4Range`.+* Switch to derived `Bits` instance for `IPv4.+* Remove old spec test for IPv4 Bits laws, instead use+ quickcheck-classes.+* Bump exclusive upper bound on small-bytearray-builder++## 1.6.0 -- 2019-09-30+* Provide decode functions for decoding from `ShortText` and+ from `Bytes`. These two are implemented internally using+ the same function.+* Dependency on `bytesmith` effectively restricts users to+ GHC 8.6 and up. Since GHC 8.8 is about to be released,+ this is deemed an acceptable cost.+* Require cabal version 2.2 so that leading commas are accepted+ in dependencies lists.++## 1.5.1 -- 2019-07-29+* Allow building with primitive-0.7.+* Add more doctests to Net.IP.+* Add to Net.IP: `isIPv4` and `isIPv6`.+* Bump lower bound on primitive from 0.6 to 0.6.4.+* Bump upper bound on hashable from < 1.3. to < 1.4.++## 1.5.0 -- 2019-03-23+* Implement `IPv6` using `wide-word`'s `Word128`. (This is a breaking change.)++## 1.4.2.1 -- 2019-03-18+* Docfix for `Net.IPv4.toList`++## 1.4.2 -- 2019-03-14+* Fix existing `spec` test suite.+* 100% haddock coverage, along with significantly more doctest coverage.+* Add `Net.IPv4.localhost` and `Net.IPv6.localhost`, aliases for `loopback`.++## 1.4.1 -- 2018-08-19+* Add `Enum` and `Bounded` instances for `Mac`.+* Add `NFData` instances for all types.++## 1.4.0 -- 2018-07-18+* Combine `Net.IPv4` and `Net.IPv4.Range` modules.+* Add `IPv6Range`.+* Drop support for older aeson.+* Add `Enum` instance for `IPv6`.++## 1.2.1 -- 2018-05-10+* Added a `Prim` instance for `Mac`.
+ README.md view
@@ -0,0 +1,25 @@+[](https://hackage.haskell.org/package/ip)++# Instructions++Look at the [haddocks](http://hackage.haskell.org/package/ip) for this +package to learn how to use it.++# Contributing++Most contributions are welcome, especially performance improvements in encoding and decoding of Text/ByteString.+Please make sure to follow naming conventions followed in the modules.++## Doctest++Doctest used to be provided as a test suite, but `doctest-0.20` and higher+do not require this to be run. To run the doctests, make sure you have+`doctest` on your path (i.e. run `cabal install doctest`), and then run:++ cabal build+ cabal repl --build-depends=QuickCheck --with-ghc=doctest --repl-options='-fno-warn-orphans'++This runs incredibly slowly, but it works for now. Doctest is not run by CI,+so if you make a change that adds more doctests, it needs to be run by hand+by someone. (The maintainer is happy to do this if you're on a platform+where doctest is finicky.)
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
ip.cabal view
@@ -1,15 +1,16 @@-cabal-version: 2.2-name: ip-version: 1.7.7-synopsis: Library for IP and MAC addresses-homepage: https://github.com/andrewthad/haskell-ip#readme-license: BSD-3-Clause-license-file: LICENSE-author: Andrew Martin-maintainer: andrew.thaddeus@gmail.com-copyright: 2016 Andrew Martin-category: web-build-type: Simple+cabal-version: 3.0+name: ip+version: 1.7.8+synopsis: Library for IP and MAC addresses+homepage: https://github.com/byteverse/haskell-ip+bug-reports: https://github.com/byteverse/haskell-ip/issues+license: BSD-3-Clause+license-file: LICENSE+author: Andrew Martin+maintainer: amartin@layer3com.com+copyright: 2016 Andrew Martin+category: web+build-type: Simple description: The `ip` package provides types and functions for dealing with IPv4 addresses, CIDR blocks, and MAC addresses. We provide instances@@ -29,16 +30,29 @@ The following packages are intended to be used with this package: . * `yesod-ip`: Provides orphan instances needed to work with yesod and- persistent. Also, provides a `yesod-form` helper.+ persistent. Also, provides a `yesod-form` helper. +extra-doc-files:+ CHANGELOG.md+ README.md++tested-with: GHC ==9.4.8 || ==9.6.3 || ==9.8.1++common build-settings+ default-language: Haskell2010+ ghc-options: -Wall -Wunused-packages+ library- hs-source-dirs: src+ import: build-settings+ hs-source-dirs: src+ ghc-options: -O2 exposed-modules: Net.IP Net.IPv4 Net.IPv6 Net.Mac Net.Types+ other-modules: Data.ByteString.Builder.Fixed Data.Text.Builder.Common.Compat@@ -46,70 +60,71 @@ Data.Text.Builder.Fixed Data.Text.Builder.Variable Data.Word.Synthetic.Word12+ build-depends:- , aeson >= 1.0 && < 2.2- , attoparsec >= 0.13 && < 0.15- , base >= 4.9 && < 5- , byteslice >= 0.1.2 && < 0.3- , bytesmith >= 0.3.9 && < 0.4- , bytestring >= 0.10.8 && < 0.12- , deepseq >= 1.4 && < 1.5- , hashable >= 1.2 && < 1.5- , natural-arithmetic >= 0.1 && <0.3- , primitive >= 0.6.4 && < 0.10- , bytebuild >= 0.3.4 && <0.4- , text >= 1.2 && < 2.1- , text-short >= 0.1.3 && < 0.2- , vector >= 0.11 && < 0.14- , wide-word >= 0.1.1.2 && < 0.2- , word-compat >= 0.0.4 && <0.1- ghc-options: -Wall -O2- default-language: Haskell2010+ , aeson >=1.0+ , attoparsec >=0.13+ , base >=4.9 && <5+ , bytebuild >=0.3.4+ , byteslice >=0.1.2+ , bytesmith >=0.3.9+ , bytestring >=0.10.8+ , deepseq >=1.4+ , hashable >=1.2+ , natural-arithmetic >=0.1+ , primitive >=0.6.4+ , text >=1.2+ , text-short >=0.1.3+ , vector >=0.11+ , wide-word >=0.1.1.2+ , word-compat >=0.0.4 test-suite test- type: exitcode-stdio-1.0- hs-source-dirs: test- main-is: Test.hs+ import: build-settings+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Test.hs build-depends:- , HUnit- , QuickCheck , attoparsec , base , byteslice , bytestring+ , HUnit , ip- , quickcheck-classes >= 0.4.13 && < 0.7.0.0+ , QuickCheck+ , quickcheck-classes >=0.4.13 && <0.7.0.0 , tasty , tasty-hunit , tasty-quickcheck , text , text-short , wide-word+ other-modules: IPv4ByteString1 IPv4Text1 IPv4Text2 Naive- ghc-options: -Wall -O2- default-language: Haskell2010 test-suite spec- type: exitcode-stdio-1.0- hs-source-dirs: test- main-is: Spec.hs+ import: build-settings+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Spec.hs build-depends: , base- , hspec >= 2.5.5+ , hspec >=2.5.5 , ip- , wide-word- other-modules:- Net.IPv4Spec- ghc-options: -Wall -O2- default-language: Haskell2010- build-tool-depends: hspec-discover:hspec-discover >= 2.5.5 + other-modules: Net.IPv4Spec+ build-tool-depends: hspec-discover:hspec-discover >=2.5.5+ benchmark criterion- type: exitcode-stdio-1.0+ import: build-settings+ hs-source-dirs: test+ main-is: Bench.hs+ ghc-options: -O2+ type: exitcode-stdio-1.0 build-depends: , attoparsec , base@@ -118,8 +133,9 @@ , criterion , ip , primitive- , text , random+ , text+ other-modules: IPv4ByteString1 IPv4DecodeText1@@ -127,12 +143,7 @@ IPv4Text1 IPv4Text2 Naive- ghc-options: -Wall -O2- default-language: Haskell2010- hs-source-dirs: test- main-is: Bench.hs source-repository head- type: git- location: https://github.com/andrewthad/haskell-ip-+ type: git+ location: git://github.com/byteverse/haskell-ip.git
src/Data/ByteString/Builder/Fixed.hs view
@@ -4,10 +4,9 @@ {-# LANGUAGE MagicHash #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-}- {-# OPTIONS_GHC -Wall -funbox-strict-fields #-} -{-| For concatenating fixed-width strings that are only a few+{- | For concatenating fixed-width strings that are only a few characters each, this can be six times faster than the builder that ships with @bytestring@. -}@@ -27,14 +26,14 @@ #if !MIN_VERSION_base(4,11,0) import Data.Monoid #endif-import Data.Word-import Data.Word.Synthetic.Word12 (Word12) import Data.Bits+import Data.ByteString.Internal (ByteString (..))+import Data.ByteString.Short (ShortByteString) import Data.Char (ord)-import Text.Printf-import Data.ByteString.Internal (ByteString(..))+import Data.Word+import Data.Word.Synthetic.Word12 (Word12) import Foreign-import Data.ByteString.Short (ShortByteString)+import Text.Printf import qualified Data.ByteString as ByteString import qualified Data.ByteString.Char8 as BC8@@ -109,12 +108,12 @@ hexValuesWord12Upper :: ShortByteString hexValuesWord12Upper =- SBS.pack $ map (fromIntegral . ord) $ concat $ map (printf "%03X") [0 :: Int ..4095]+ SBS.pack $ map (fromIntegral . ord) $ concat $ map (printf "%03X") [0 :: Int .. 4095] {-# NOINLINE hexValuesWord12Upper #-} hexValuesWord12Lower :: ShortByteString hexValuesWord12Lower =- SBS.pack $ map (fromIntegral . ord) $ concat $ map (printf "%03x") [0 :: Int ..4095]+ SBS.pack $ map (fromIntegral . ord) $ concat $ map (printf "%03x") [0 :: Int .. 4095] {-# NOINLINE hexValuesWord12Lower #-} word8HexFixedUpper :: Builder Word8@@ -137,12 +136,12 @@ hexValuesWord8Upper :: ShortByteString hexValuesWord8Upper =- SBS.pack $ map (fromIntegral . ord) $ concat $ map (printf "%02X") [0 :: Int ..255]+ SBS.pack $ map (fromIntegral . ord) $ concat $ map (printf "%02X") [0 :: Int .. 255] {-# NOINLINE hexValuesWord8Upper #-} hexValuesWord8Lower :: ShortByteString hexValuesWord8Lower =- SBS.pack $ map (fromIntegral . ord) $ concat $ map (printf "%02x") [0 :: Int ..255]+ SBS.pack $ map (fromIntegral . ord) $ concat $ map (printf "%02x") [0 :: Int .. 255] {-# NOINLINE hexValuesWord8Lower #-} char8 :: Builder Char@@ -153,8 +152,9 @@ word8 = BuilderFunction (BC8.pack "-") $ \i marr w -> pokeByteOff marr i w {-# INLINE word8 #-} --- | Taken from @Data.ByteString.Internal@. The same warnings--- apply here.+{- | Taken from @Data.ByteString.Internal@. The same warnings+ apply here.+-} c2w :: Char -> Word8 c2w = fromIntegral . ord {-# INLINE c2w #-}
src/Data/Text/Builder/Common/Compat.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+ {- | Compatibility module allowing us to support UTF-16 & UTF-8 versions of the 'text' package. -}
src/Data/Text/Builder/Common/Internal.hs view
@@ -1,29 +1,31 @@ module Data.Text.Builder.Common.Internal where -import Data.Text (Text) import Control.Monad.ST-import Data.Monoid-import Text.Printf import Data.Char (ord) import Data.Foldable (fold)+import Data.Monoid+import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Text.Array as A import qualified Data.Text.Internal.Unsafe.Char as TC+import Text.Printf --- | This is slower that just pattern matching on the Text data constructor.--- However, it will work with GHCJS. This should only be used in places--- where we know that it will only be evaluated once.+{- | This is slower that just pattern matching on the Text data constructor.+ However, it will work with GHCJS. This should only be used in places+ where we know that it will only be evaluated once.+-} portableTextArray :: Text -> A.Array portableTextArray = fst . portableUntext {-# INLINE portableTextArray #-} --- | This length is not the character length. It is the length of Word16s--- required by a UTF16 representation.+{- | This length is not the character length. It is the length of Word16s+ required by a UTF16 representation.+-} portableTextLength :: Text -> Int portableTextLength = snd . portableUntext {-# INLINE portableTextLength #-} -portableUntext :: Text -> (A.Array,Int)+portableUntext :: Text -> (A.Array, Int) portableUntext t = let str = Text.unpack t Sum len = foldMap (Sum . charUtf16Size) str@@ -31,11 +33,12 @@ marr <- A.new len writeString marr str return marr- in (arr,len)+ in (arr, len) {-# NOINLINE portableUntext #-} writeString :: A.MArray s -> String -> ST s ()-writeString marr = go 0 where+writeString marr = go 0+ where go i s = case s of c : cs -> do n <- TC.unsafeWrite marr i c@@ -46,32 +49,41 @@ charUtf16Size c = if ord c < 0x10000 then 1 else 2 hexValuesWord12Upper :: A.Array-hexValuesWord12Upper = portableTextArray $ fold- $ map (Text.pack . printf "%03X") [0 :: Int ..4096]+hexValuesWord12Upper =+ portableTextArray $+ fold $+ map (Text.pack . printf "%03X") [0 :: Int .. 4096] {-# NOINLINE hexValuesWord12Upper #-} hexValuesWord12Lower :: A.Array-hexValuesWord12Lower = portableTextArray $ fold- $ map (Text.pack . printf "%03x") [0 :: Int ..4096]+hexValuesWord12Lower =+ portableTextArray $+ fold $+ map (Text.pack . printf "%03x") [0 :: Int .. 4096] {-# NOINLINE hexValuesWord12Lower #-} hexValuesWord8Upper :: A.Array-hexValuesWord8Upper = portableTextArray $ fold- $ map (Text.pack . printf "%02X") [0 :: Int ..255]+hexValuesWord8Upper =+ portableTextArray $+ fold $+ map (Text.pack . printf "%02X") [0 :: Int .. 255] {-# NOINLINE hexValuesWord8Upper #-} hexValuesWord8Lower :: A.Array-hexValuesWord8Lower = portableTextArray $ fold- $ map (Text.pack . printf "%02x") [0 :: Int ..255]+hexValuesWord8Lower =+ portableTextArray $+ fold $+ map (Text.pack . printf "%02x") [0 :: Int .. 255] {-# NOINLINE hexValuesWord8Lower #-} twoDecimalDigits :: A.Array-twoDecimalDigits = portableTextArray- $ foldMap (Text.pack . printf "%02d") [0 :: Int ..99]+twoDecimalDigits =+ portableTextArray $+ foldMap (Text.pack . printf "%02d") [0 :: Int .. 99] {-# NOINLINE twoDecimalDigits #-} threeDecimalDigits :: A.Array-threeDecimalDigits = portableTextArray- $ foldMap (Text.pack . printf "%03d") [0 :: Int ..255]+threeDecimalDigits =+ portableTextArray $+ foldMap (Text.pack . printf "%03d") [0 :: Int .. 255] {-# NOINLINE threeDecimalDigits #-}-
src/Data/Text/Builder/Fixed.hs view
@@ -1,12 +1,11 @@+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-}-{-# LANGUAGE RankNTypes #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE BangPatterns #-}-+{-# LANGUAGE RankNTypes #-} {-# OPTIONS_GHC -Wall -funbox-strict-fields #-} -{-| For concatenating fixed-width strings that are only a few+{- | For concatenating fixed-width strings that are only a few characters each, this can be ten times faster than the builder that ships with @text@. -}@@ -28,14 +27,14 @@ #endif import Data.Bits import Data.Char (ord)-import Data.Word-import Data.Word.Synthetic.Word12 (Word12)-import Data.Text (Text) import qualified Data.Semigroup as Semigroup+import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Text.Array as A-import qualified Data.Text.Internal as TI import qualified Data.Text.Builder.Common.Internal as I+import qualified Data.Text.Internal as TI+import Data.Word+import Data.Word.Synthetic.Word12 (Word12) data Builder a where BuilderStatic :: Text -> Builder a@@ -75,6 +74,7 @@ BuilderFunction t g -> BuilderFunction t (\ix marr b -> g ix marr (f b)) {-# INLINE contramapBuilder #-} +{- FOURMOLU_DISABLE -} run :: Builder a -> a -> Text run x = case x of BuilderStatic t -> \_ -> t@@ -92,6 +92,7 @@ A.unsafeFreeze marr in TI.text outArr 0 len {-# INLINE run #-}+{- FOURMOLU_ENABLE -} word8HexFixedUpper :: Builder Word8 word8HexFixedUpper = word8HexFixedGeneral True@@ -112,9 +113,10 @@ A.unsafeWrite marr (i + 1) (A.unsafeIndex arr ix2) {-# INLINE word8HexFixedGeneral #-} --- | Characters outside the basic multilingual plane are not handled--- correctly by this function. They will not cause a program to crash;--- instead, the character will have the upper bits masked out.+{- | Characters outside the basic multilingual plane are not handled+ correctly by this function. They will not cause a program to crash;+ instead, the character will have the upper bits masked out.+-} charBmp :: Builder Char charBmp = BuilderFunction (Text.pack "-") $ \i marr c -> A.unsafeWrite marr i (fromIntegral (ord c))@@ -138,4 +140,3 @@ word12HexFixedLower :: Builder Word12 word12HexFixedLower = word12HexFixedGeneral False {-# INLINE word12HexFixedLower #-}-
src/Data/Text/Builder/Variable.hs view
@@ -1,9 +1,9 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE RankNTypes #-} {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE RankNTypes #-} -{-| This is a builder optimized for concatenating short+{- | This is a builder optimized for concatenating short variable-length strings whose length has a known upper bound. In these cases, this can be up to ten times faster than the builder provided by the @text@ library. However,@@ -23,18 +23,18 @@ , word8 ) where -import Data.Word-import Data.Text (Text)-import Data.Text.Builder.Common.Compat (Codepoint) import Control.Monad.ST import Data.Char (ord)-import Data.Vector (Vector) import Data.Maybe (fromMaybe)-import qualified Data.Vector as Vector import qualified Data.Semigroup as Semigroup+import Data.Text (Text) import qualified Data.Text.Array as A+import Data.Text.Builder.Common.Compat (Codepoint) import qualified Data.Text.Builder.Common.Internal as I import qualified Data.Text.Internal as TI+import Data.Vector (Vector)+import qualified Data.Vector as Vector+import Data.Word data Builder a = Builder@@ -60,10 +60,10 @@ run :: Builder a -> a -> Text run (Builder maxLen f) = \a ->- let (outArr,len) = A.run2 $ do+ let (outArr, len) = A.run2 $ do marr <- A.new maxLen finalIx <- f 0 marr a- return (marr,finalIx)+ return (marr, finalIx) in TI.text outArr 0 len {-# INLINE run #-} @@ -90,25 +90,27 @@ {-# INLINE staticCharBmp #-} word8 :: Builder Word8-word8 = Builder 3 $ \pos marr w -> if- | w < 10 -> do- A.unsafeWrite marr pos (i2w w)- return (pos + 1)- | w < 100 -> do- let wInt = fromIntegral w- ix = wInt + wInt- A.unsafeWrite marr pos (A.unsafeIndex I.twoDecimalDigits ix)- A.unsafeWrite marr (pos + 1) (A.unsafeIndex I.twoDecimalDigits (ix + 1))- return (pos + 2)- | otherwise -> do- let wInt = fromIntegral w- ix = wInt + wInt + wInt- A.unsafeWrite marr pos (A.unsafeIndex I.threeDecimalDigits ix)- A.unsafeWrite marr (pos + 1) (A.unsafeIndex I.threeDecimalDigits (ix + 1))- A.unsafeWrite marr (pos + 2) (A.unsafeIndex I.threeDecimalDigits (ix + 2))- return (pos + 3)+word8 = Builder 3 $ \pos marr w ->+ if+ | w < 10 -> do+ A.unsafeWrite marr pos (i2w w)+ return (pos + 1)+ | w < 100 -> do+ let wInt = fromIntegral w+ ix = wInt + wInt+ A.unsafeWrite marr pos (A.unsafeIndex I.twoDecimalDigits ix)+ A.unsafeWrite marr (pos + 1) (A.unsafeIndex I.twoDecimalDigits (ix + 1))+ return (pos + 2)+ | otherwise -> do+ let wInt = fromIntegral w+ ix = wInt + wInt + wInt+ A.unsafeWrite marr pos (A.unsafeIndex I.threeDecimalDigits ix)+ A.unsafeWrite marr (pos + 1) (A.unsafeIndex I.threeDecimalDigits (ix + 1))+ A.unsafeWrite marr (pos + 2) (A.unsafeIndex I.threeDecimalDigits (ix + 2))+ return (pos + 3) {-# INLINE word8 #-} +{- FOURMOLU_DISABLE -} -- This has not yet been tested. _vector :: Text -- ^ Default, used when index is out of range@@ -133,8 +135,8 @@ i2w :: Integral a => a -> Codepoint i2w v = asciiZero + fromIntegral v {-# INLINE i2w #-}+{- FOURMOLU_ENABLE -} asciiZero :: Codepoint asciiZero = 48 {-# INLINE asciiZero #-}-
src/Data/Word/Synthetic/Word12.hs view
@@ -1,48 +1,47 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE MagicHash #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE NoImplicitPrelude #-}-{-# LANGUAGE PatternSynonyms #-} ---- |--- Module : Data.Word.Word12--- License : see src/Data/LICENSE--- Stability : experimental--- Portability : non-portable (GHC Extensions)- -- Provide a 12-bit unsigned integral type: 'Word12', analagous to Word8, -- Word16, etc. -- -module Data.Word.Synthetic.Word12 (- -- * Word12 type- Word12(..)- -- * Internal helpers+{- |+Module : Data.Word.Word12+License : see src/Data/LICENSE+Stability : experimental+Portability : non-portable (GHC Extensions)+-}+module Data.Word.Synthetic.Word12+ ( -- * Word12 type+ Word12 (..)++ -- * Internal helpers , narrow12Word# , clz12# , ctz12# , popCnt12# )- where -import Data.Bits-import Data.Data-import Data.Maybe+import Data.Bits+import Data.Data+import Data.Maybe -import GHC.Arr-import GHC.Base-import GHC.Enum+import GHC.Arr+import GHC.Base+import GHC.Enum #if MIN_VERSION_base(4,15,0) import GHC.Integer (integerToWord, smallInteger) import GHC.Num hiding (integerToWord) #else import GHC.Num #endif-import GHC.Read-import GHC.Real-import GHC.Show+import GHC.Read+import GHC.Real+import GHC.Show import qualified GHC.Word.Compat as Compat @@ -52,7 +51,6 @@ -- must ensure that it holds only values in its logical range. -- | 12-bit unsigned integer type--- data Word12 = W12# Word# deriving (Eq, Ord) word12Type :: DataType@@ -61,9 +59,12 @@ instance Data Word12 where toConstr x = mkIntegralConstr word12Type x gunfold _ z c = case constrRep c of- (IntConstr x) -> z (fromIntegral x)- _ -> error $ "Data.Data.gunfold: Constructor " ++ show c- ++ " is not of type Word12."+ (IntConstr x) -> z (fromIntegral x)+ _ ->+ error $+ "Data.Data.gunfold: Constructor "+ ++ show c+ ++ " is not of type Word12." dataTypeOf _ = word12Type -- | narrowings represented as primop 'and#' in GHC.@@ -71,17 +72,14 @@ narrow12Word# = and# 0xFFF## -- | count leading zeros--- clz12# :: Word# -> Word# clz12# w# = clz32# (narrow12Word# w#) `minusWord#` 20## -- | count trailing zeros--- ctz12# :: Word# -> Word# ctz12# w# = ctz# w# -- | the number of set bits--- popCnt12# :: Word# -> Word# popCnt12# w# = popCnt# (narrow12Word# w#) @@ -92,134 +90,147 @@ (W12# x#) + (W12# y#) = W12# (narrow12Word# (x# `plusWord#` y#)) (W12# x#) - (W12# y#) = W12# (narrow12Word# (x# `minusWord#` y#)) (W12# x#) * (W12# y#) = W12# (narrow12Word# (x# `timesWord#` y#))- negate (W12# x#) = W12# (narrow12Word# (int2Word# (negateInt# (word2Int# x#))))- abs x = x- signum 0 = 0- signum _ = 1- fromInteger i = W12# (narrow12Word# (integerToWord i))+ negate (W12# x#) = W12# (narrow12Word# (int2Word# (negateInt# (word2Int# x#))))+ abs x = x+ signum 0 = 0+ signum _ = 1+ fromInteger i = W12# (narrow12Word# (integerToWord i)) instance Real Word12 where toRational x = toInteger x % 1 instance Enum Word12 where succ x- | x /= maxBound = x + 1- | otherwise = succError "Word12"+ | x /= maxBound = x + 1+ | otherwise = succError "Word12" pred x- | x /= minBound = x - 1- | otherwise = predError "Word12"+ | x /= minBound = x - 1+ | otherwise = predError "Word12" toEnum i@(I# i#)- | i >= 0 && i <= fromIntegral (maxBound :: Word12)- = W12# (int2Word# i#)- | otherwise = toEnumError "Word12" i (minBound::Word12, maxBound::Word12)+ | i >= 0 && i <= fromIntegral (maxBound :: Word12) =+ W12# (int2Word# i#)+ | otherwise = toEnumError "Word12" i (minBound :: Word12, maxBound :: Word12) fromEnum (W12# x#) = I# (word2Int# x#)- enumFrom = boundedEnumFrom- enumFromThen = boundedEnumFromThen+ enumFrom = boundedEnumFrom+ enumFromThen = boundedEnumFromThen instance Integral Word12 where quot (W12# x#) y@(W12# y#)- | y /= 0 = W12# (x# `quotWord#` y#)- | otherwise = divZeroError+ | y /= 0 = W12# (x# `quotWord#` y#)+ | otherwise = divZeroError rem (W12# x#) y@(W12# y#)- | y /= 0 = W12# (x# `remWord#` y#)- | otherwise = divZeroError+ | y /= 0 = W12# (x# `remWord#` y#)+ | otherwise = divZeroError div (W12# x#) y@(W12# y#)- | y /= 0 = W12# (x# `quotWord#` y#)- | otherwise = divZeroError+ | y /= 0 = W12# (x# `quotWord#` y#)+ | otherwise = divZeroError mod (W12# x#) y@(W12# y#)- | y /= 0 = W12# (x# `remWord#` y#)- | otherwise = divZeroError+ | y /= 0 = W12# (x# `remWord#` y#)+ | otherwise = divZeroError quotRem (W12# x#) y@(W12# y#)- | y /= 0 = (W12# (x# `quotWord#` y#), W12# (x# `remWord#` y#))- | otherwise = divZeroError+ | y /= 0 = (W12# (x# `quotWord#` y#), W12# (x# `remWord#` y#))+ | otherwise = divZeroError divMod (W12# x#) y@(W12# y#)- | y /= 0 = (W12# (x# `quotWord#` y#), W12# (x# `remWord#` y#))- | otherwise = divZeroError- toInteger (W12# x#) = smallInteger (word2Int# x#)+ | y /= 0 = (W12# (x# `quotWord#` y#), W12# (x# `remWord#` y#))+ | otherwise = divZeroError+ toInteger (W12# x#) = smallInteger (word2Int# x#) instance Bounded Word12 where minBound = 0 maxBound = 0xFFFFFF instance Ix Word12 where- range (m,n) = [m..n]- unsafeIndex (m,_) i = fromIntegral (i - m)- inRange (m,n) i = m <= i && i <= n+ range (m, n) = [m .. n]+ unsafeIndex (m, _) i = fromIntegral (i - m)+ inRange (m, n) i = m <= i && i <= n instance Read Word12 where- readsPrec p s = [(fromIntegral (x::Int), r) | (x, r) <- readsPrec p s]+ readsPrec p s = [(fromIntegral (x :: Int), r) | (x, r) <- readsPrec p s] instance Bits Word12 where- {-# INLINE shift #-}- {-# INLINE bit #-}- {-# INLINE testBit #-}+ {-# INLINE shift #-}+ {-# INLINE bit #-}+ {-# INLINE testBit #-} - (W12# x#) .&. (W12# y#) = W12# (x# `and#` y#)- (W12# x#) .|. (W12# y#) = W12# (x# `or#` y#)- (W12# x#) `xor` (W12# y#) = W12# (x# `xor#` y#)- complement (W12# x#) = W12# (x# `xor#` mb#) where !(W12# mb#) = maxBound- (W12# x#) `shift` (I# i#)- | isTrue# (i# >=# 0#) = W12# (narrow12Word# (x# `shiftL#` i#))- | otherwise = W12# (x# `shiftRL#` negateInt# i#)- (W12# x#) `shiftL` (I# i#) = W12# (narrow12Word# (x# `shiftL#` i#))- (W12# x#) `unsafeShiftL` (I# i#) =- W12# (narrow12Word# (x# `uncheckedShiftL#` i#))- (W12# x#) `shiftR` (I# i#) = W12# (x# `shiftRL#` i#)- (W12# x#) `unsafeShiftR` (I# i#) = W12# (x# `uncheckedShiftRL#` i#)- (W12# x#) `rotate` i- | isTrue# (i'# ==# 0#) = W12# x#- | otherwise = W12# (narrow12Word# ((x# `uncheckedShiftL#` i'#) `or#`- (x# `uncheckedShiftRL#` (12# -# i'#))))- where- !(I# i'#) = i `mod` 12- bitSizeMaybe i = Just (finiteBitSize i)- bitSize = finiteBitSize- isSigned _ = False- popCount (W12# x#) = I# (word2Int# (popCnt12# x#))- bit = bitDefault- testBit = testBitDefault+ (W12# x#) .&. (W12# y#) = W12# (x# `and#` y#)+ (W12# x#) .|. (W12# y#) = W12# (x# `or#` y#)+ (W12# x#) `xor` (W12# y#) = W12# (x# `xor#` y#)+ complement (W12# x#) = W12# (x# `xor#` mb#) where !(W12# mb#) = maxBound+ (W12# x#) `shift` (I# i#)+ | isTrue# (i# >=# 0#) = W12# (narrow12Word# (x# `shiftL#` i#))+ | otherwise = W12# (x# `shiftRL#` negateInt# i#)+ (W12# x#) `shiftL` (I# i#) = W12# (narrow12Word# (x# `shiftL#` i#))+ (W12# x#) `unsafeShiftL` (I# i#) =+ W12# (narrow12Word# (x# `uncheckedShiftL#` i#))+ (W12# x#) `shiftR` (I# i#) = W12# (x# `shiftRL#` i#)+ (W12# x#) `unsafeShiftR` (I# i#) = W12# (x# `uncheckedShiftRL#` i#)+ (W12# x#) `rotate` i+ | isTrue# (i'# ==# 0#) = W12# x#+ | otherwise =+ W12#+ ( narrow12Word#+ ( (x# `uncheckedShiftL#` i'#)+ `or#` (x# `uncheckedShiftRL#` (12# -# i'#))+ )+ )+ where+ !(I# i'#) = i `mod` 12+ bitSizeMaybe i = Just (finiteBitSize i)+ bitSize = finiteBitSize+ isSigned _ = False+ popCount (W12# x#) = I# (word2Int# (popCnt12# x#))+ bit = bitDefault+ testBit = testBitDefault instance FiniteBits Word12 where- finiteBitSize _ = 12- countLeadingZeros (W12# x#) = I# (word2Int# (clz12# x#))- countTrailingZeros (W12# x#) = I# (word2Int# (ctz12# x#))+ finiteBitSize _ = 12+ countLeadingZeros (W12# x#) = I# (word2Int# (clz12# x#))+ countTrailingZeros (W12# x#) = I# (word2Int# (ctz12# x#)) {-# RULES-"fromIntegral/Word8->Word12" fromIntegral = \x -> case x of { Compat.W8# y -> W12# y }-"fromIntegral/Word12->Word12" fromIntegral = id :: Word12 -> Word12-"fromIntegral/Word12->Integer" fromIntegral = toInteger :: Word12 -> Integer-"fromIntegral/a->Word12" fromIntegral = \x -> case fromIntegral x of W# x# -> W12# (narrow12Word# x#)-"fromIntegral/Word12->a" fromIntegral = \(W12# x#) -> fromIntegral (W# x#)+"fromIntegral/Word8->Word12" fromIntegral = \x -> case x of Compat.W8# y -> W12# y+"fromIntegral/Word12->Word12" fromIntegral = id :: Word12 -> Word12+"fromIntegral/Word12->Integer" fromIntegral = toInteger :: Word12 -> Integer+"fromIntegral/a->Word12" fromIntegral = \x -> case fromIntegral x of W# x# -> W12# (narrow12Word# x#)+"fromIntegral/Word12->a" fromIntegral = \(W12# x#) -> fromIntegral (W# x#) #-} {-# RULES "properFraction/Float->(Word12,Float)"- properFraction = \x ->- case properFraction x of {- (n, y) -> ((fromIntegral :: Int -> Word12) n, y :: Float) }+ properFraction =+ \x ->+ case properFraction x of+ (n, y) -> ((fromIntegral :: Int -> Word12) n, y :: Float) "truncate/Float->Word12"- truncate = (fromIntegral :: Int -> Word12) . (truncate :: Float -> Int)+ truncate =+ (fromIntegral :: Int -> Word12) . (truncate :: Float -> Int) "floor/Float->Word12"- floor = (fromIntegral :: Int -> Word12) . (floor :: Float -> Int)+ floor =+ (fromIntegral :: Int -> Word12) . (floor :: Float -> Int) "ceiling/Float->Word12"- ceiling = (fromIntegral :: Int -> Word12) . (ceiling :: Float -> Int)+ ceiling =+ (fromIntegral :: Int -> Word12) . (ceiling :: Float -> Int) "round/Float->Word12"- round = (fromIntegral :: Int -> Word12) . (round :: Float -> Int)+ round =+ (fromIntegral :: Int -> Word12) . (round :: Float -> Int) #-} {-# RULES "properFraction/Double->(Word12,Double)"- properFraction = \x ->- case properFraction x of {- (n, y) -> ((fromIntegral :: Int -> Word12) n, y :: Double) }+ properFraction =+ \x ->+ case properFraction x of+ (n, y) -> ((fromIntegral :: Int -> Word12) n, y :: Double) "truncate/Double->Word12"- truncate = (fromIntegral :: Int -> Word12) . (truncate :: Double -> Int)+ truncate =+ (fromIntegral :: Int -> Word12) . (truncate :: Double -> Int) "floor/Double->Word12"- floor = (fromIntegral :: Int -> Word12) . (floor :: Double -> Int)+ floor =+ (fromIntegral :: Int -> Word12) . (floor :: Double -> Int) "ceiling/Double->Word12"- ceiling = (fromIntegral :: Int -> Word12) . (ceiling :: Double -> Int)+ ceiling =+ (fromIntegral :: Int -> Word12) . (ceiling :: Double -> Int) "round/Double->Word12"- round = (fromIntegral :: Int -> Word12) . (round :: Double -> Int)+ round =+ (fromIntegral :: Int -> Word12) . (round :: Double -> Int) #-}--
src/Net/IP.hs view
@@ -1,13 +1,12 @@ {-# LANGUAGE BangPatterns #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}- {-# OPTIONS_GHC -Wall #-} -{-| An IP data type representing either an IPv4 address or+{- | An IP data type representing either an IPv4 address or an IPv6 address. The user can think of this as though it were a sum type. However, to minimize indirections, it is actually implemented as an 'IPv6' address, with 'IPv4'@@ -30,91 +29,108 @@ >>> decode "A3F5:12:F26::1466:8B91" Just (ipv6 0xa3f5 0x0012 0x0f26 0x0000 0x0000 0x0000 0x1466 0x8b91) -}- module Net.IP ( -- * Pattern Matching case_ , isIPv4 , isIPv6+ -- * Construction , ipv4 , ipv6 , fromIPv4 , fromIPv6+ -- * Textual Conversion+ -- ** Text , encode , encodeShort , decode , decodeShort , boundedBuilderUtf8+ -- ** Bytes , decodeUtf8Bytes , parserUtf8Bytes+ -- ** Printing , print+ -- * Types- , IP(..)+ , IP (..) ) where import Control.DeepSeq (NFData)-import Data.Aeson (FromJSON(..),ToJSON(..))+import Data.Aeson (FromJSON (..), ToJSON (..)) import Data.Bits import Data.Coerce (coerce) import Data.Data (Data) import Data.Hashable (Hashable) import Data.Ix (Ix) import Data.Text (Text)-import Data.WideWord (Word128(..))-import Data.Word (Word8,Word16)+import Data.Text.Short (ShortText)+import Data.WideWord (Word128 (..))+import Data.Word (Word16, Word8) import GHC.Generics (Generic)-import Net.IPv4 (IPv4(..))-import Net.IPv6 (IPv6(..))-import Prelude hiding (print)+import Net.IPv4 (IPv4 (..))+import Net.IPv6 (IPv6 (..)) import Text.ParserCombinators.ReadPrec ((+++))-import Text.Read (Read(..))-import Data.Text.Short (ShortText)+import Text.Read (Read (..))+import Prelude hiding (print) import qualified Arithmetic.Lte as Lte import qualified Data.Aeson as Aeson import qualified Data.Bytes as Bytes import qualified Data.Bytes.Builder.Bounded as BB-import qualified Data.Text.IO as TIO import qualified Data.Bytes.Parser as Parser+import qualified Data.Text.IO as TIO import qualified Net.IPv4 as IPv4 import qualified Net.IPv6 as IPv6 --- $setup--- >>> :set -XOverloadedStrings--- >>> import qualified Arithmetic.Nat as Nat+{- $setup+>>> :set -XOverloadedStrings+>>> import qualified Arithmetic.Nat as Nat+-} --- | Run a function over an 'IP' depending on its status--- as an 'IPv4' or 'IPv6'.------ >>> case_ IPv4.encode IPv6.encode (ipv4 192 168 2 47)--- "192.168.2.47"------ >>> addr = ipv6 0x2001 0x0db8 0x0000 0x0000 0x0000 0x0000 0x0000 0x0001--- >>> case_ IPv4.encode IPv6.encode addr--- "2001:db8::1"+{- | Run a function over an 'IP' depending on its status+ as an 'IPv4' or 'IPv6'.++ >>> case_ IPv4.encode IPv6.encode (ipv4 192 168 2 47)+ "192.168.2.47"++ >>> addr = ipv6 0x2001 0x0db8 0x0000 0x0000 0x0000 0x0000 0x0000 0x0001+ >>> case_ IPv4.encode IPv6.encode addr+ "2001:db8::1"+-} case_ :: (IPv4 -> a) -> (IPv6 -> a) -> IP -> a -- Note: rather than performing the masking operations on the 'Word128', -- we unwrap the 'Word64's, as that's probably a bit more efficient, and -- we might need the lower word anyway.-case_ f g (IP addr@(IPv6 (Word128 w1 w2))) = if w1 == 0 && (0xFFFFFFFF00000000 .&. w2 == 0x0000FFFF00000000)- then f (IPv4 (fromIntegral w2))- else g addr+case_ f g (IP addr@(IPv6 (Word128 w1 w2))) =+ if w1 == 0 && (0xFFFFFFFF00000000 .&. w2 == 0x0000FFFF00000000)+ then f (IPv4 (fromIntegral w2))+ else g addr --- | Construct an 'IP' address from the four octets of--- an IPv4 address.+{- | Construct an 'IP' address from the four octets of+ an IPv4 address.+-} ipv4 :: Word8 -> Word8 -> Word8 -> Word8 -> IP ipv4 a b c d = fromIPv4 (IPv4.fromOctets a b c d) --- | Construct an 'IP' address from the eight 16-bit--- chunks of an IPv6 address.-ipv6 :: Word16 -> Word16 -> Word16 -> Word16- -> Word16 -> Word16 -> Word16 -> Word16- -> IP+{- | Construct an 'IP' address from the eight 16-bit+ chunks of an IPv6 address.+-}+ipv6 ::+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ IP ipv6 a b c d e f g h = fromIPv6 (IPv6.fromWord16s a b c d e f g h) -- | Turn an 'IPv4' into an 'IP'.@@ -125,48 +141,53 @@ fromIPv6 :: IPv6 -> IP fromIPv6 = IP --- | Encode an 'IP' as 'Text'.------ >>> encode (ipv4 10 0 0 25)--- "10.0.0.25"------ >>> encode (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1)--- "3124::dead:cafe:ff:fe00:1"+{- | Encode an 'IP' as 'Text'.++ >>> encode (ipv4 10 0 0 25)+ "10.0.0.25"++ >>> encode (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1)+ "3124::dead:cafe:ff:fe00:1"+-} encode :: IP -> Text encode = case_ IPv4.encode IPv6.encode --- | Encode an 'IP' as 'ShortText'.------ >>> encodeShort (ipv4 10 0 1 26)--- "10.0.1.26"------ >>> encodeShort (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE01 0x0000)--- "3124::dead:cafe:ff:fe01:0"+{- | Encode an 'IP' as 'ShortText'.++ >>> encodeShort (ipv4 10 0 1 26)+ "10.0.1.26"++ >>> encodeShort (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE01 0x0000)+ "3124::dead:cafe:ff:fe01:0"+-} encodeShort :: IP -> ShortText encodeShort = case_ IPv4.encodeShort IPv6.encodeShort --- | Encode an 'IP' as a bounded bytearray builder.------ >>> BB.run Nat.constant (boundedBuilderUtf8 (ipv4 192 168 2 14))--- [0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x32, 0x2e, 0x31, 0x34]+{- | Encode an 'IP' as a bounded bytearray builder.++>>> BB.run Nat.constant (boundedBuilderUtf8 (ipv4 192 168 2 14))+[0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x32, 0x2e, 0x31, 0x34]+-} boundedBuilderUtf8 :: IP -> BB.Builder 39-boundedBuilderUtf8 = case_- (\y -> BB.weaken Lte.constant (IPv4.boundedBuilderUtf8 y))- IPv6.boundedBuilderUtf8+boundedBuilderUtf8 =+ case_+ (\y -> BB.weaken Lte.constant (IPv4.boundedBuilderUtf8 y))+ IPv6.boundedBuilderUtf8 --- | Decode an 'IP' from 'Text'.------ >>> decode "10.0.0.25"--- Just (ipv4 10 0 0 25)------ >>> fmap isIPv4 (decode "10.0.0.25")--- Just True------ >>> decode "3124::dead:cafe:ff:fe00:1"--- Just (ipv6 0x3124 0x0000 0x0000 0xdead 0xcafe 0x00ff 0xfe00 0x0001)------ >>> fmap isIPv6 (decode "3124::dead:cafe:ff:fe00:1")--- Just True+{- | Decode an 'IP' from 'Text'.++ >>> decode "10.0.0.25"+ Just (ipv4 10 0 0 25)++ >>> fmap isIPv4 (decode "10.0.0.25")+ Just True++ >>> decode "3124::dead:cafe:ff:fe00:1"+ Just (ipv6 0x3124 0x0000 0x0000 0xdead 0xcafe 0x00ff 0xfe00 0x0001)++ >>> fmap isIPv6 (decode "3124::dead:cafe:ff:fe00:1")+ Just True+-} decode :: Text -> Maybe IP decode t = case IPv4.decode t of Nothing -> case IPv6.decode t of@@ -174,12 +195,13 @@ Just v6 -> Just (fromIPv6 v6) Just v4 -> Just (fromIPv4 v4) --- | Decode an 'IP' from 'ShortText'.------ >>> decodeShort "10.0.0.25"--- Just (ipv4 10 0 0 25)--- >>> decodeShort "::dead:cafe"--- Just (ipv6 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0xdead 0xcafe)+{- | Decode an 'IP' from 'ShortText'.++ >>> decodeShort "10.0.0.25"+ Just (ipv4 10 0 0 25)+ >>> decodeShort "::dead:cafe"+ Just (ipv6 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0xdead 0xcafe)+-} decodeShort :: ShortText -> Maybe IP decodeShort t | Just x <- IPv4.decodeShort t = Just (fromIPv4 x)@@ -197,49 +219,52 @@ parserUtf8Bytes :: e -> Parser.Parser e s IP parserUtf8Bytes e = fmap fromIPv4 (IPv4.parserUtf8Bytes ())- `Parser.orElse`- coerce (IPv6.parserUtf8Bytes e)+ `Parser.orElse` coerce (IPv6.parserUtf8Bytes e) --- | Is the 'IP' an IPv4 address?------ >>> isIPv4 (ipv4 10 0 0 25)--- True------ >>> isIPv4 (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1)--- False+{- | Is the 'IP' an IPv4 address?++ >>> isIPv4 (ipv4 10 0 0 25)+ True++ >>> isIPv4 (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1)+ False+-} isIPv4 :: IP -> Bool isIPv4 = case_ (const True) (const False)-{-# inline isIPv4 #-}+{-# INLINE isIPv4 #-} --- | Is the 'IP' an IPv6 address?------ >>> isIPv6 (ipv4 10 0 0 25)--- False------ >>> isIPv6 (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1)--- True+{- | Is the 'IP' an IPv6 address?++ >>> isIPv6 (ipv4 10 0 0 25)+ False++ >>> isIPv6 (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1)+ True+-} isIPv6 :: IP -> Bool isIPv6 = case_ (const False) (const True)-{-# inline isIPv6 #-}+{-# INLINE isIPv6 #-} --- | Print an 'IP' using the textual encoding. This exists mostly for--- debugging purposes.------ >>> print (ipv4 10 0 0 25)--- 10.0.0.25------ >>> print (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1)--- 3124::dead:cafe:ff:fe00:1+{- | Print an 'IP' using the textual encoding. This exists mostly for+ debugging purposes.++ >>> print (ipv4 10 0 0 25)+ 10.0.0.25++ >>> print (ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1)+ 3124::dead:cafe:ff:fe00:1+-} print :: IP -> IO () print = TIO.putStrLn . encode --- | A 32-bit 'IPv4' address or a 128-bit 'IPv6' address. Internally, this--- is just represented as an 'IPv6' address. The functions provided--- in @Net.IP@ help simulate constructing and pattern matching on values--- of this type. All functions and typeclass methods that convert--- 'IP' values to text will display it as an 'IPv4' address if possible.-newtype IP = IP { getIP :: IPv6 }- deriving stock (Eq,Ord,Generic,Ix,Data)+{- | A 32-bit 'IPv4' address or a 128-bit 'IPv6' address. Internally, this+ is just represented as an 'IPv6' address. The functions provided+ in @Net.IP@ help simulate constructing and pattern matching on values+ of this type. All functions and typeclass methods that convert+ 'IP' values to text will display it as an 'IPv4' address if possible.+-}+newtype IP = IP {getIP :: IPv6}+ deriving stock (Eq, Ord, Generic, Ix, Data) deriving newtype (Hashable) instance NFData IP@@ -257,4 +282,3 @@ parseJSON = Aeson.withText "IP" $ \t -> case decode t of Nothing -> fail "Could not parse IP address" Just addr -> return addr-
src/Net/IPv4.hs view
@@ -1,1368 +1,1449 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MagicHash #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeInType #-}-{-# LANGUAGE UnboxedTuples #-}--{-| This module provides the IPv4 data type and functions for working- with it.--}--module Net.IPv4- ( -- * Conversion Functions- ipv4- , fromOctets- , fromTupleOctets- , toOctets- -- * Special IP Addresses- , any- , loopback- , localhost- , broadcast- -- * Range Predicates- , private- , reserved- , public- -- * Textual Conversion- -- ** Text- , encode- , decode- , builder- , reader- , parser- , decodeShort- , encodeShort- -- ** UTF-8 ByteString- , encodeUtf8- , decodeUtf8- , builderUtf8- , parserUtf8- -- ** UTF-8 Bytes- , decodeUtf8Bytes- , parserUtf8Bytes- , byteArrayBuilderUtf8- , boundedBuilderUtf8- -- ** Non-textual Bytes- , boundedBuilderOctetsBE- , boundedBuilderOctetsLE- -- ** String- -- $string- , encodeString- , decodeString- -- ** Printing- , print- -- * IPv4 Ranges- -- ** Range functions- , range- , fromBounds- , normalize- , contains- , isSubsetOf- , member- , lowerInclusive- , upperInclusive- -- ** Conversion to IPv4- , toList- , toGenerator- -- ** Private Ranges- , private24- , private20- , private16- -- ** Textual Conversion- -- *** Text- , encodeRange- , decodeRange- , builderRange- , parserRange- , printRange- -- ** UTF-8 Bytes- , parserRangeUtf8Bytes- , parserRangeUtf8BytesLenient- -- * Types- , IPv4(..)- , IPv4#- , IPv4Range(..)- -- * Unboxing- -- | These functions are useful for micro-optimizing- -- when GHC does a poor job with worker-wrapper.- , box- , unbox- , parserUtf8Bytes#- -- * Interoperability- -- $interoperability- ) where--import Control.DeepSeq (NFData)-import Control.Monad-import Control.Monad.ST (ST,runST)-import Data.Aeson (FromJSON(..),ToJSON(..))-import Data.Aeson (ToJSONKey(..),FromJSONKey(..),ToJSONKeyFunction(..),FromJSONKeyFunction(..))-import Data.Bits (Bits(..))-import Data.ByteString (ByteString)-import Data.Coerce (coerce)-import Data.Data (Data)-import Data.Hashable-import Data.Ix (Ix)-import Data.Primitive.Types (Prim)-import Data.Text (Text)-import Data.Text.Builder.Common.Compat (Codepoint)-import Data.Text.Encoding (decodeUtf8')-import Data.Text.Internal (Text(..))-import Data.Text.Short (ShortText)-import Data.Vector.Generic.Mutable (MVector(..))-import Data.Word-import Foreign.Ptr (Ptr,plusPtr)-import Foreign.Storable (Storable, poke)-import GHC.Exts (Word#)-import GHC.Generics (Generic)-import Prelude hiding (any, print, print)-import Text.ParserCombinators.ReadPrec (prec,step)-import Text.Printf (printf)-import Text.Read (Read(..),Lexeme(Ident),lexP,parens)--import qualified Arithmetic.Nat as Nat-import qualified Data.Aeson as Aeson-import qualified Data.Aeson.Types as Aeson-import qualified Data.Attoparsec.ByteString.Char8 as AB-import qualified Data.Attoparsec.Text as AT-import qualified Data.Bits as Bits-import qualified Data.Bytes.Builder.Bounded as BB-import qualified Data.Bytes.Builder as UB-import qualified Data.ByteString.Builder as Builder-import qualified Data.ByteString.Char8 as BC8-import qualified Data.ByteString.Internal as I-import qualified Data.ByteString.Unsafe as ByteString-import qualified Data.ByteString.Short.Internal as BSS-import qualified Data.Bytes as Bytes-import qualified Data.Bytes.Parser as Parser-import qualified Data.Bytes.Parser.Latin as Latin-import qualified Data.Char as Char-import qualified Data.Primitive as PM-import qualified Data.Text as Text-import qualified Data.Text.Array as TArray-import qualified Data.Text.IO as TIO-import qualified Data.Text.Lazy as LText-import qualified Data.Text.Lazy.Builder as TBuilder-import qualified Data.Text.Lazy.Builder.Int as TBI-import qualified Data.Text.Read as TextRead-import qualified Data.Text.Short as TS-import qualified Data.Text.Short.Unsafe as TS-import qualified Data.Vector.Generic as GVector-import qualified Data.Vector.Generic.Mutable as MGVector-import qualified Data.Vector.Primitive as PVector-import qualified Data.Vector.Unboxed as UVector-import qualified Data.Vector.Unboxed.Mutable as MUVector-import qualified GHC.Word.Compat as Compat--#if MIN_VERSION_aeson(2,0,0)-import qualified Data.Aeson.Key as AesonKey-#endif---- $setup------ These are here to get doctest's property checking to work------ >>> :set -XOverloadedStrings--- >>> import Test.QuickCheck (Arbitrary(..))--- >>> import Net.IPv4 (getIPv4)--- >>> import qualified Prelude as P--- >>> import qualified Data.Text.IO as T--- >>> import qualified Data.Bytes.Text.Ascii as Ascii--- >>> import qualified Data.Attoparsec.Text as AT--- >>> import qualified Data.ByteString.Builder as Builder--- >>> import qualified Data.Bytes.Builder as UB--- >>> import qualified Data.Attoparsec.ByteString.Char8 as AB--- >>> instance Arbitrary IPv4 where { arbitrary = fmap IPv4 arbitrary }--- >>> instance Arbitrary IPv4.IPv4Range where { arbitrary = IPv4.IPv4Range <$> arbitrary <*> arbitrary }--- >>> import qualified Data.Bytes.Chunks as Chunks----- | Create an 'IPv4' address from four octets. The first argument--- is the most significant octet. The last argument is the least--- significant. Since IP addresses are commonly written using dot-decimal--- notation, this is the recommended way to create an IP address.--- Additionally, it is used for the 'Show' and 'Read' instances--- of 'IPv4' to help keep things readable in GHCi.------ >>> let addr = IPv4.ipv4 192 168 1 1--- >>> addr--- ipv4 192 168 1 1--- >>> getIPv4 addr--- 3232235777----ipv4 :: Word8 -> Word8 -> Word8 -> Word8 -> IPv4-ipv4 = fromOctets---- | An alias for the 'ipv4' smart constructor.-fromOctets :: Word8 -> Word8 -> Word8 -> Word8 -> IPv4-fromOctets a b c d = fromOctets'- (fromIntegral a) (fromIntegral b) (fromIntegral c) (fromIntegral d)---- | An uncurried variant of 'fromOctets'.-fromTupleOctets :: (Word8,Word8,Word8,Word8) -> IPv4-fromTupleOctets (a,b,c,d) = fromOctets a b c d---- | Convert an 'IPv4' address into a quadruple of octets. The first--- element in the quadruple is the most significant octet. The last--- element is the least significant octet.-toOctets :: IPv4 -> (Word8,Word8,Word8,Word8)-toOctets (IPv4 w) =- ( fromIntegral (shiftR w 24)- , fromIntegral (shiftR w 16)- , fromIntegral (shiftR w 8)- , fromIntegral w- )---- | The IP address representing any host.------ >>> IPv4.any--- ipv4 0 0 0 0-any :: IPv4-any = IPv4 0---- | The local loopback IP address.------ >>> IPv4.loopback--- ipv4 127 0 0 1-loopback :: IPv4-loopback = fromOctets 127 0 0 1---- | A useful and common alias for 'loopback'.------ >>> IPv4.localhost--- ipv4 127 0 0 1-localhost :: IPv4-localhost = loopback---- | The broadcast IP address.------ >>> IPv4.broadcast--- ipv4 255 255 255 255-broadcast :: IPv4-broadcast = fromOctets 255 255 255 255---- | Checks to see if the 'IPv4' address belongs to a private--- network. The three private networks that are checked are--- @10.0.0.0/8@, @172.16.0.0/12@, and @192.168.0.0/16@.-private :: IPv4 -> Bool-private (IPv4 w) =- mask8 .&. w == p24- || mask12 .&. w == p20- || mask16 .&. w == p16--------------------------------------------- Note [The implementation of reserved]-------------------------------------------- The @reserved@ function has been optimized to perform well in the--- microbenchmark @CIDR Inclusion/reserved@. We perform an inital case--- on the upper three bits (8 possible values), which GHC will compile--- to a jump table. This helps because the reserved ranges of IPv4--- addresses are somewhat clustered. Notice that everything in--- 32.0.0.0/3, 64.0.0.0/3, and 128.0.0.0/3 is publicly routable, and--- everything in 224.0.0.0/3 is reserved. This means that for exactly--- half of the IPv4 addresses that exist, this single jump is sufficient--- for determining whether or not they are reserved. For the others,--- there is a little more work to do, particularly in the 192.0.0.0/3--- range. On the laptop that ran the microbenchmark, this function--- decided the reservedness of 100 random IPv4 addresses in 200ns.---- | Checks to see if the 'IPv4' address belongs to a reserved--- network. This includes the three private networks that 'private'--- checks along with several other ranges that are not used--- on the public Internet. The implementation of this function--- is optimized.-reserved :: IPv4 -> Bool-reserved !(IPv4 w) = case unsafeShiftR w 29 of- 0 ->- let a = getIPv4 $ fromOctets' 0 0 0 0- y = getIPv4 $ fromOctets' 10 0 0 0- in mask8 .&. w == a- || mask8 .&. w == y- 1 -> False- 2 -> False- 3 ->- let b = getIPv4 $ fromOctets' 100 64 0 0- c = getIPv4 $ fromOctets' 127 0 0 0- in mask8 .&. w == c- || mask10 .&. w == b- 4 -> False- 5 ->- let d = getIPv4 $ fromOctets' 169 254 0 0- x = getIPv4 $ fromOctets' 172 16 0 0- in mask12 .&. w == x- || mask16 .&. w == d- 6 ->- let e = getIPv4 $ fromOctets' 192 0 0 0- f = getIPv4 $ fromOctets' 192 0 2 0- g = getIPv4 $ fromOctets' 192 88 99 0- h = getIPv4 $ fromOctets' 198 18 0 0- i = getIPv4 $ fromOctets' 198 51 100 0- j = getIPv4 $ fromOctets' 203 0 113 0- z = getIPv4 $ fromOctets' 192 168 0 0- in mask15 .&. w == h- || mask16 .&. w == z- || mask24 .&. w == e- || mask24 .&. w == f- || mask24 .&. w == g- || mask24 .&. w == i- || mask24 .&. w == j- _ -> True--mask8,mask12,mask16,mask10,mask24,mask15 :: Word32-mask8 = 0xFF000000-mask10 = 0xFFC00000-mask12 = 0xFFF00000-mask15 = 0xFFFE0000-mask16 = 0xFFFF0000-mask24 = 0xFFFFFF00---- | Checks to see if the 'IPv4' address is publicly routable.------ prop> IPv4.public x == not (IPv4.reserved x)-public :: IPv4 -> Bool-public = not . reserved---- | Encode an 'IPv4' address to 'Text' using dot-decimal notation:------ >>> T.putStrLn (IPv4.encode (IPv4.ipv4 192 168 2 47))--- 192.168.2.47-encode :: IPv4 -> Text-encode = toDotDecimalText---- | Decode an 'IPv4' address.------ >>> IPv4.decode "192.168.2.47"--- Just (ipv4 192 168 2 47)------ >>> IPv4.decode "10.100.256.256"--- Nothing-decode :: Text -> Maybe IPv4-decode = decodeIPv4TextMaybe---- | Encode an 'IPv4' address to a text 'TBuilder.Builder'.------ >>> IPv4.builder (IPv4.ipv4 192 168 2 47)--- "192.168.2.47"-builder :: IPv4 -> TBuilder.Builder-builder = toDotDecimalBuilder---- | Parse an 'IPv4' address using a 'TextRead.Reader'.------ >>> IPv4.reader "192.168.2.47"--- Right (ipv4 192 168 2 47,"")------ >>> IPv4.reader "192.168.2.470"--- Left "All octets in an IPv4 address must be between 0 and 255"-reader :: TextRead.Reader IPv4-reader = decodeIPv4TextReader---- | Parse an 'IPv4' address using a 'AT.Parser'.------ >>> AT.parseOnly IPv4.parser "192.168.2.47"--- Right (ipv4 192 168 2 47)------ >>> AT.parseOnly IPv4.parser "192.168.2.470"--- Left "Failed reading: All octets in an IPv4 address must be between 0 and 255"-parser :: AT.Parser IPv4-parser = dotDecimalParser---- | Encode an 'IPv4' address to a UTF-8 encoded 'ByteString'.------ >>> IPv4.encodeUtf8 (IPv4.ipv4 192 168 2 47)--- "192.168.2.47"-encodeUtf8 :: IPv4 -> ByteString-encodeUtf8 = toBSPreAllocated--toBSPreAllocated :: IPv4 -> ByteString-toBSPreAllocated (IPv4 !w) = I.unsafeCreateUptoN 15 (\ptr1 ->- do len1 <- writeWord ptr1 w1- let ptr2 = ptr1 `plusPtr` len1- poke ptr2 dot- len2 <- writeWord (ptr2 `plusPtr` 1) w2- let ptr3 = ptr2 `plusPtr` len2 `plusPtr` 1- poke ptr3 dot- len3 <- writeWord (ptr3 `plusPtr` 1) w3- let ptr4 = ptr3 `plusPtr` len3 `plusPtr` 1- poke ptr4 dot- len4 <- writeWord (ptr4 `plusPtr` 1) w4- return (3 + len1 + len2 + len3 + len4))- where w1 = fromIntegral $ shiftR w 24- w2 = fromIntegral $ shiftR w 16- w3 = fromIntegral $ shiftR w 8- w4 = fromIntegral w- dot = 46 :: Word8- writeWord :: Ptr Word8 -> Word8 -> IO Int- writeWord !ptr !word- | word >= 100 = do- let int = fromIntegral word- indx = int + int + int- get3 = fromIntegral . ByteString.unsafeIndex threeDigits- poke ptr (get3 indx)- poke (ptr `plusPtr` 1) (get3 (indx + 1))- poke (ptr `plusPtr` 2) (get3 (indx + 2))- return 3- | word >= 10 = do- let int = fromIntegral word- indx = int + int- get2 = fromIntegral . ByteString.unsafeIndex twoDigits- poke ptr (get2 indx)- poke (ptr `plusPtr` 1) (get2 (indx + 1))- return 2- | otherwise = do- poke ptr (word + 48)- return 1---- | Decode a UTF8-encoded 'ByteString' into an 'IPv4'.------ >>> IPv4.decodeUtf8 "192.168.2.47"--- Just (ipv4 192 168 2 47)------ Currently not terribly efficient since the implementation--- re-encodes the argument as UTF-16 text before decoding that--- IPv4 address from that. PRs to fix this are welcome.-decodeUtf8 :: ByteString -> Maybe IPv4-decodeUtf8 = decode <=< rightToMaybe . decodeUtf8'--- This (decodeUtf8) should be rewritten to not go through text--- as an intermediary.---- | Decode 'ShortText' as an 'IPv4' address.------ >>> IPv4.decodeShort "192.168.3.48"--- Just (ipv4 192 168 3 48)-decodeShort :: ShortText -> Maybe IPv4-decodeShort t = decodeUtf8Bytes (Bytes.fromByteArray b)- where b = shortByteStringToByteArray (TS.toShortByteString t)---- | Encode an 'IPv4' address as 'ShortText'.------ >>> IPv4.encodeShort (IPv4.ipv4 192 168 5 99)--- "192.168.5.99"-encodeShort :: IPv4 -> ShortText-encodeShort !w = id- $ TS.fromShortByteStringUnsafe- $ byteArrayToShortByteString- $ BB.run Nat.constant- $ boundedBuilderUtf8- $ w--shortByteStringToByteArray :: BSS.ShortByteString -> PM.ByteArray-shortByteStringToByteArray (BSS.SBS x) = PM.ByteArray x--byteArrayToShortByteString :: PM.ByteArray -> BSS.ShortByteString-byteArrayToShortByteString (PM.ByteArray x) = BSS.SBS x---- | Decode UTF-8-encoded 'Bytes' into an 'IPv4' address.------ >>> IPv4.decodeUtf8Bytes (Ascii.fromString "127.0.0.1")--- Just (ipv4 127 0 0 1)-decodeUtf8Bytes :: Bytes.Bytes -> Maybe IPv4-decodeUtf8Bytes !b = case Parser.parseBytes (parserUtf8Bytes ()) b of- Parser.Success (Parser.Slice _ len addr) -> case len of- 0 -> Just addr- _ -> Nothing- Parser.Failure _ -> Nothing---- | Parse UTF-8-encoded 'Bytes' as an 'IPv4' address.------ >>> Parser.parseBytes (IPv4.parserUtf8Bytes ()) (Ascii.fromString "10.0.1.254")--- Success (Slice {offset = 10, length = 0, value = ipv4 10 0 1 254})-parserUtf8Bytes :: e -> Parser.Parser e s IPv4-{-# inline parserUtf8Bytes #-}-parserUtf8Bytes e = coerce (Parser.boxWord32 (parserUtf8Bytes# e))---- | Variant of 'parserUtf8Bytes' with unboxed result type.-parserUtf8Bytes# :: e -> Parser.Parser e s IPv4#-{-# noinline parserUtf8Bytes# #-}-parserUtf8Bytes# e = Parser.unboxWord32 $ do- !a <- Latin.decWord8 e- Latin.char e '.'- !b <- Latin.decWord8 e- Latin.char e '.'- !c <- Latin.decWord8 e- Latin.char e '.'- !d <- Latin.decWord8 e- pure (getIPv4 (fromOctets a b c d))---- | Parse UTF-8-encoded 'Bytes' into an 'IPv4Range'.--- This requires the mask to be present.------ >>> maybe (putStrLn "nope") IPv4.printRange $ Parser.parseBytesMaybe (IPv4.parserRangeUtf8Bytes ()) (Ascii.fromString "192.168.0.0/16")--- 192.168.0.0/16--- >>> maybe (putStrLn "nope") IPv4.printRange $ Parser.parseBytesMaybe (IPv4.parserRangeUtf8Bytes ()) (Ascii.fromString "10.10.10.1")--- nope------ See 'parserRangeUtf8BytesLenient' for a variant that treats--- a missing mask as a @/32@ mask.-parserRangeUtf8Bytes :: e -> Parser.Parser e s IPv4Range-parserRangeUtf8Bytes e = do- base <- parserUtf8Bytes e- Latin.char e '/'- theMask <- Latin.decWord8 e- if theMask > 32- then Parser.fail e- else pure $! normalize (IPv4Range base theMask)---- | Variant of 'parserRangeUtf8Bytes' that allows the mask--- to be omitted. An omitted mask is treated as a @/32@ mask.------ >>> maybe (putStrLn "nope") IPv4.printRange $ Parser.parseBytesMaybe (IPv4.parserRangeUtf8BytesLenient ()) (Ascii.fromString "192.168.0.0/16")--- 192.168.0.0/16--- >>> maybe (putStrLn "nope") IPv4.printRange $ Parser.parseBytesMaybe (IPv4.parserRangeUtf8BytesLenient ()) (Ascii.fromString "10.10.10.1")--- 10.10.10.1/32-parserRangeUtf8BytesLenient :: e -> Parser.Parser e s IPv4Range-parserRangeUtf8BytesLenient e = do- base <- parserUtf8Bytes e- Latin.trySatisfy (=='/') >>= \case- True -> do- theMask <- Latin.decWord8 e- if theMask > 32- then Parser.fail e- else pure $! normalize (IPv4Range base theMask)- False -> pure $! IPv4Range base 32---- | Encode an 'IPv4' as a bytestring 'Builder.Builder'------ >>> Builder.toLazyByteString (IPv4.builderUtf8 (IPv4.fromOctets 192 168 2 12))--- "192.168.2.12"-builderUtf8 :: IPv4 -> Builder.Builder-builderUtf8 = Builder.byteString . encodeUtf8---- | Encode an 'IPv4' address as a unbounded byte array builder.------ >>> Chunks.concat (UB.run 1 (IPv4.byteArrayBuilderUtf8 (IPv4.fromOctets 192 168 2 13)))--- [0x31,0x39,0x32,0x2e,0x31,0x36,0x38,0x2e,0x32,0x2e,0x31,0x33]------ Note that period is encoded by UTF-8 as @0x2e@.-byteArrayBuilderUtf8 :: IPv4 -> UB.Builder-byteArrayBuilderUtf8 = UB.fromBounded Nat.constant . boundedBuilderUtf8---- | Encode an 'IPv4' address as a bounded byte array builder.------ >>> BB.run Nat.constant (IPv4.boundedBuilderUtf8 (IPv4.fromOctets 192 168 2 14))--- [0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x32, 0x2e, 0x31, 0x34]------ Note that period is encoded by UTF-8 as @0x2e@.-boundedBuilderUtf8 :: IPv4 -> BB.Builder 15-boundedBuilderUtf8 (IPv4 !w) =- BB.word8Dec w1- `BB.append`- BB.ascii '.'- `BB.append`- BB.word8Dec w2- `BB.append`- BB.ascii '.'- `BB.append`- BB.word8Dec w3- `BB.append`- BB.ascii '.'- `BB.append`- BB.word8Dec w4- where- w1 = fromIntegral (shiftR w 24) :: Word8- w2 = fromIntegral (shiftR w 16) :: Word8- w3 = fromIntegral (shiftR w 8) :: Word8- w4 = fromIntegral w :: Word8---- | Encode 'IPv4' address to a sequence a 4 bytes with the first--- byte representing corresponding to the most significant byte in--- the address.------ >>> BB.run Nat.constant (IPv4.boundedBuilderOctetsBE (IPv4.fromOctets 0xc0 0xa8 0x02 0x1f))--- [0xc0, 0xa8, 0x02, 0x1f]-boundedBuilderOctetsBE :: IPv4 -> BB.Builder 4-{-# inline boundedBuilderOctetsBE #-}-boundedBuilderOctetsBE (IPv4 !w) =- BB.word8 w1- `BB.append`- BB.word8 w2- `BB.append`- BB.word8 w3- `BB.append`- BB.word8 w4- where- w1 = fromIntegral (shiftR w 24) :: Word8- w2 = fromIntegral (shiftR w 16) :: Word8- w3 = fromIntegral (shiftR w 8) :: Word8- w4 = fromIntegral w :: Word8---- | Encode 'IPv4' address to a sequence a 4 bytes with the first--- byte representing corresponding to the least significant byte in--- the address.------ >>> BB.run Nat.constant (IPv4.boundedBuilderOctetsLE (IPv4.fromOctets 0xc0 0xa8 0x02 0x1f))--- [0x1f, 0x02, 0xa8, 0xc0]-boundedBuilderOctetsLE :: IPv4 -> BB.Builder 4-{-# inline boundedBuilderOctetsLE #-}-boundedBuilderOctetsLE (IPv4 !w) =- BB.word8 w4- `BB.append`- BB.word8 w3- `BB.append`- BB.word8 w2- `BB.append`- BB.word8 w1- where- w1 = fromIntegral (shiftR w 24) :: Word8- w2 = fromIntegral (shiftR w 16) :: Word8- w3 = fromIntegral (shiftR w 8) :: Word8- w4 = fromIntegral w :: Word8---- | Parse an 'IPv4' using a 'AB.Parser'.------ >>> AB.parseOnly IPv4.parserUtf8 "192.168.2.47"--- Right (ipv4 192 168 2 47)------ >>> AB.parseOnly IPv4.parserUtf8 "192.168.2.470"--- Left "Failed reading: All octets in an ipv4 address must be between 0 and 255"-parserUtf8 :: AB.Parser IPv4-parserUtf8 = fromOctets'- <$> (AB.decimal >>= limitSize)- <* AB.char '.'- <*> (AB.decimal >>= limitSize)- <* AB.char '.'- <*> (AB.decimal >>= limitSize)- <* AB.char '.'- <*> (AB.decimal >>= limitSize)- where- limitSize i =- if i > 255- then fail "All octets in an ipv4 address must be between 0 and 255"- else return i--{- $string-- These functions exist for the convenience of those who need a- 'String' representation of an 'IPv4' address. Using them- is discouraged unless the end user is working with a library- that can only use 'String' to deal with textual data (such as- @pandoc@, @hxr@, or @network@).---}---- | Encode an 'IPv4' as a 'String'.-encodeString :: IPv4 -> String-encodeString = Text.unpack . encode---- | Decode an 'IPv4' from a 'String'.-decodeString :: String -> Maybe IPv4-decodeString = decode . Text.pack----- | Unboxed variant of 'IPv4'. Before GHC 8.10, this is--- implemented as a type synonym. Portable use of this type requires--- treating it as though it were opaque. Use 'box' and 'unbox' to--- convert between this and the lifted 'IPv4'.-type IPv4# = Word#---- | Convert an unboxed IPv4 address to a boxed one.-box :: IPv4# -> IPv4-{-# inline box #-}-box w = IPv4 (Compat.W32# w)---- | Convert a boxed IPv4 address to an unboxed one.-unbox :: IPv4 -> IPv4#-{-# inline unbox #-}-unbox (IPv4 (Compat.W32# w)) = w---- | A 32-bit Internet Protocol version 4 address. To use this with the--- @network@ library, it is necessary to use @Network.Socket.htonl@ to--- convert the underlying 'Word32' from host byte order to network byte--- order.-newtype IPv4 = IPv4 { getIPv4 :: Word32 }- deriving (Bits.Bits,Bounded,Data,Enum,Eq,Bits.FiniteBits,Generic,Hashable,Ix,Ord,Prim,Storable)--instance NFData IPv4--instance Show IPv4 where- showsPrec p addr = showParen (p > 10)- $ showString "ipv4 "- . showsPrec 11 a- . showChar ' '- . showsPrec 11 b- . showChar ' '- . showsPrec 11 c- . showChar ' '- . showsPrec 11 d- where- (a,b,c,d) = toOctets addr--instance Read IPv4 where- readPrec = parens $ prec 10 $ do- Ident "ipv4" <- lexP- a <- step readPrec- b <- step readPrec- c <- step readPrec- d <- step readPrec- return (fromOctets a b c d)---- | Print an 'IPv4' using the textual encoding.-print :: IPv4 -> IO ()-print = TIO.putStrLn . encode--newtype instance UVector.MVector s IPv4 = MV_IPv4 (PVector.MVector s IPv4)-newtype instance UVector.Vector IPv4 = V_IPv4 (PVector.Vector IPv4)--instance UVector.Unbox IPv4--instance MGVector.MVector UVector.MVector IPv4 where- {-# INLINE basicLength #-}- {-# INLINE basicUnsafeSlice #-}- {-# INLINE basicOverlaps #-}- {-# INLINE basicUnsafeNew #-}- {-# INLINE basicInitialize #-}- {-# INLINE basicUnsafeReplicate #-}- {-# INLINE basicUnsafeRead #-}- {-# INLINE basicUnsafeWrite #-}- {-# INLINE basicClear #-}- {-# INLINE basicSet #-}- {-# INLINE basicUnsafeCopy #-}- {-# INLINE basicUnsafeGrow #-}- basicLength (MV_IPv4 v) = MGVector.basicLength v- basicUnsafeSlice i n (MV_IPv4 v) = MV_IPv4 $ MGVector.basicUnsafeSlice i n v- basicOverlaps (MV_IPv4 v1) (MV_IPv4 v2) = MGVector.basicOverlaps v1 v2- basicUnsafeNew n = MV_IPv4 `liftM` MGVector.basicUnsafeNew n- basicInitialize (MV_IPv4 v) = MGVector.basicInitialize v- basicUnsafeReplicate n x = MV_IPv4 `liftM` MGVector.basicUnsafeReplicate n x- basicUnsafeRead (MV_IPv4 v) i = MGVector.basicUnsafeRead v i- basicUnsafeWrite (MV_IPv4 v) i x = MGVector.basicUnsafeWrite v i x- basicClear (MV_IPv4 v) = MGVector.basicClear v- basicSet (MV_IPv4 v) x = MGVector.basicSet v x- basicUnsafeCopy (MV_IPv4 v1) (MV_IPv4 v2) = MGVector.basicUnsafeCopy v1 v2- basicUnsafeMove (MV_IPv4 v1) (MV_IPv4 v2) = MGVector.basicUnsafeMove v1 v2- basicUnsafeGrow (MV_IPv4 v) n = MV_IPv4 `liftM` MGVector.basicUnsafeGrow v n--instance GVector.Vector UVector.Vector IPv4 where- {-# INLINE basicUnsafeFreeze #-}- {-# INLINE basicUnsafeThaw #-}- {-# INLINE basicLength #-}- {-# INLINE basicUnsafeSlice #-}- {-# INLINE basicUnsafeIndexM #-}- {-# INLINE elemseq #-}- basicUnsafeFreeze (MV_IPv4 v) = V_IPv4 `liftM` GVector.basicUnsafeFreeze v- basicUnsafeThaw (V_IPv4 v) = MV_IPv4 `liftM` GVector.basicUnsafeThaw v- basicLength (V_IPv4 v) = GVector.basicLength v- basicUnsafeSlice i n (V_IPv4 v) = V_IPv4 $ GVector.basicUnsafeSlice i n v- basicUnsafeIndexM (V_IPv4 v) i = GVector.basicUnsafeIndexM v i- basicUnsafeCopy (MV_IPv4 mv) (V_IPv4 v) = GVector.basicUnsafeCopy mv v- elemseq _ = seq--instance ToJSON IPv4 where- toJSON = Aeson.String . encode--instance FromJSON IPv4 where- parseJSON = Aeson.withText "IPv4" aesonParser--instance ToJSONKey IPv4 where- toJSONKey = ToJSONKeyText- (keyFromText . encode)- (\addr -> Aeson.unsafeToEncoding $ Builder.char7 '"' <> builderUtf8 addr <> Builder.char7 '"')- where-#if MIN_VERSION_aeson(2,0,0)- keyFromText = AesonKey.fromText-#else- keyFromText = id-#endif--instance FromJSONKey IPv4 where- fromJSONKey = FromJSONKeyTextParser aesonParser--aesonParser :: Text -> Aeson.Parser IPv4-aesonParser t = case decode t of- Nothing -> fail "Could not parse IPv4 address"- Just addr -> return addr----------------------------------------- Internal functions, not exported---------------------------------------decodeIPv4TextMaybe :: Text -> Maybe IPv4-decodeIPv4TextMaybe t = case decodeIPv4TextReader t of- Left _ -> Nothing- Right (w,t') -> if Text.null t'- then Just w- else Nothing--decodeIPv4TextReader :: TextRead.Reader IPv4-decodeIPv4TextReader t1' = do- (a,t2) <- readOctet t1'- t2' <- stripDecimal t2- (b,t3) <- readOctet t2'- t3' <- stripDecimal t3- (c,t4) <- readOctet t3'- t4' <- stripDecimal t4- (d,t5) <- readOctet t4'- Right (fromOctets' a b c d,t5)---- | Read an IPv4 octet (@0 <= n <= 255@)------ The input must begin with at least one decimal digit. Input is consumed--- until a non-digit is reached, the end of the input is reached, or the--- accumulated value exceeds the maximum bound (255). As with--- 'TextRead.decimal', any number of leading zeros are permitted.------ Optimizations:------ * The 'Char.isDigit' and 'Char.digitToInt' functions are avoided in order--- to avoiding checking the range more than once. This implementation calls--- 'Char.ord' (once) and uses the result for both the range check and the--- calculation.--- * The type of the accumulated value is 'Int', allowing for a single--- 'fromIntegral' call instead of one for each digit. This is possible--- because the maximum bound (255) is sufficiently less than the maximum--- bound of 'Int'. Specifically: @255 * 10 + Char.ord '9' <= maxBound@--- * This implementation does not make use of @UnboxedTuples@ because the--- @span_@ function is part of the internal API. Additional performance--- could be gained by using this internal API function.-readOctet :: TextRead.Reader Word-readOctet t = do- let (digits, rest) = Text.span Char.isDigit t- when (Text.null digits) $ Left "octet does not start with a digit"- case Text.foldr go Just digits 0 of- Just n -> Right (fromIntegral n, rest)- Nothing -> Left ipOctetSizeErrorMsg- where- go :: Char -> (Int -> Maybe Int) -> Int -> Maybe Int- go !d !f !n =- let n' = n * 10 + Char.ord d - 48- in if n' <= 255 then f n' else Nothing--stripDecimal :: Text -> Either String Text-stripDecimal t = case Text.uncons t of- Nothing -> Left "expected a dot but input ended instead"- Just (c,tnext) -> if c == '.'- then Right tnext- else Left "expected a dot but found a different character"---- | This is sort of a misnomer. It takes Word to make--- dotDecimalParser perform better. This is mostly--- for internal use. The arguments must all fit--- in a Word8.-fromOctets' :: Word -> Word -> Word -> Word -> IPv4-fromOctets' a b c d = IPv4 $ fromIntegral- ( shiftL a 24- .|. shiftL b 16- .|. shiftL c 8- .|. d- )--p24 :: Word32-p24 = getIPv4 (fromOctets' 10 0 0 0)--p20 :: Word32-p20 = getIPv4 (fromOctets' 172 16 0 0)--p16 :: Word32-p16 = getIPv4 (fromOctets' 192 168 0 0)---- | This does not do an endOfInput check because it is--- reused in the range parser implementation.-dotDecimalParser :: AT.Parser IPv4-dotDecimalParser = fromOctets'- <$> (AT.decimal >>= limitSize)- <* AT.char '.'- <*> (AT.decimal >>= limitSize)- <* AT.char '.'- <*> (AT.decimal >>= limitSize)- <* AT.char '.'- <*> (AT.decimal >>= limitSize)- where- limitSize i =- if i > 255- then fail ipOctetSizeErrorMsg- else return i--ipOctetSizeErrorMsg :: String-ipOctetSizeErrorMsg = "All octets in an IPv4 address must be between 0 and 255"--toDotDecimalText :: IPv4 -> Text-toDotDecimalText = toTextPreAllocated--toDotDecimalBuilder :: IPv4 -> TBuilder.Builder-toDotDecimalBuilder = TBuilder.fromText . toTextPreAllocated---- | I think that this function can be improved. Right now, it--- always allocates enough space for a fifteen-character text--- rendering of an IP address. I think that it should be possible--- to do more of the math upfront and allocate less space.-toTextPreAllocated :: IPv4 -> Text-toTextPreAllocated (IPv4 w) =- let w1 = 255 .&. unsafeShiftR (fromIntegral w) 24- w2 = 255 .&. unsafeShiftR (fromIntegral w) 16- w3 = 255 .&. unsafeShiftR (fromIntegral w) 8- w4 = 255 .&. fromIntegral w- in toTextPreallocatedPartTwo w1 w2 w3 w4--toTextPreallocatedPartTwo :: Word -> Word -> Word -> Word -> Text-toTextPreallocatedPartTwo !w1 !w2 !w3 !w4 =-#ifdef ghcjs_HOST_OS- let dotStr = "."- in Text.pack $ concat- [ show w1- , "."- , show w2- , "."- , show w3- , "."- , show w4- ]-#else- let dot = 46- (arr,len) = runST $ do- marr <- TArray.new 15- i1 <- putAndCount 0 w1 marr- let n1 = i1- n1' = i1 + 1- TArray.unsafeWrite marr n1 dot- i2 <- putAndCount n1' w2 marr- let n2 = i2 + n1'- n2' = n2 + 1- TArray.unsafeWrite marr n2 dot- i3 <- putAndCount n2' w3 marr- let n3 = i3 + n2'- n3' = n3 + 1- TArray.unsafeWrite marr n3 dot- i4 <- putAndCount n3' w4 marr- theArr <- TArray.unsafeFreeze marr- return (theArr,i4 + n3')- in Text arr 0 len-#endif--twoDigits :: ByteString-twoDigits = foldMap (BC8.pack . printf "%02d") $ enumFromTo (0 :: Int) 99-{-# NOINLINE twoDigits #-}--threeDigits :: ByteString-threeDigits = foldMap (BC8.pack . printf "%03d") $ enumFromTo (0 :: Int) 999-{-# NOINLINE threeDigits #-}--i2w :: Integral a => a -> Codepoint-i2w v = zero + fromIntegral v--zero :: Codepoint-zero = 48--putAndCount :: Int -> Word -> TArray.MArray s -> ST s Int-putAndCount pos w marr- | w < 10 = TArray.unsafeWrite marr pos (i2w w) >> return 1- | w < 100 = write2 pos w >> return 2- | otherwise = write3 pos w >> return 3- where- write2 off i0 = do- let i = fromIntegral i0; j = i + i- TArray.unsafeWrite marr off $ get2 j- TArray.unsafeWrite marr (off + 1) $ get2 (j + 1)- write3 off i0 = do- let i = fromIntegral i0; j = i + i + i- TArray.unsafeWrite marr off $ get3 j- TArray.unsafeWrite marr (off + 1) $ get3 (j + 1)- TArray.unsafeWrite marr (off + 2) $ get3 (j + 2)- get2 = fromIntegral . ByteString.unsafeIndex twoDigits- get3 = fromIntegral . ByteString.unsafeIndex threeDigits--rightToMaybe :: Either a b -> Maybe b-rightToMaybe = either (const Nothing) Just--{- $interoperability--The @<http://hackage.haskell.org/package/network network>@ library is commonly-used to open sockets and communicate over them. In the @Network.Socket@ module,-it provides a type synonym @HostAddress@ that, like 'IPv4', is used-to represent an IPv4 address. However, while 'IPv4' uses a big-endian representation-for ip addresses, @HostAddress@ has platform dependent endianness.-Consequently, it is necessary to convert between the two as follows:--> import Network.Socket (HostAddress,htonl,ntohl)->-> toHostAddr :: IPv4 -> HostAddress-> toHostAddr (IPv4 w) = htonl w->-> fromHostAddr :: HostAddress -> IPv4-> fromHostAddr w = IPv4 (ntohl w)--These functions are not included with this library since it would require-picking up a dependency on @network@.---}---- $setup------ These are here to get doctest's property checking to work.------ >>> import qualified Prelude as P--- >>> import qualified Data.Text.IO as T--- >>> import Net.IPv4 (fromOctets,ipv4)--- >>> import Test.QuickCheck (Arbitrary(..))--- >>> instance Arbitrary IPv4 where { arbitrary = fmap IPv4 arbitrary }--- >>> instance Arbitrary IPv4Range where { arbitrary = IPv4Range <$> arbitrary <*> arbitrary }------- | Smart constructor for 'IPv4Range'. Ensures the mask is appropriately--- sized and sets masked bits in the 'IPv4' to zero.-range :: IPv4 -> Word8 -> IPv4Range-range addr len = normalize (IPv4Range addr len)---- | Given an inclusive lower and upper ip address, create the smallest--- 'IPv4Range' that contains the two. This is helpful in situations where--- input given as a range like @192.168.16.0-192.168.19.255@ needs to be--- handled. This makes the range broader if it cannot be represented in--- CIDR notation.------ >>> IPv4.printRange $ IPv4.fromBounds (IPv4.fromOctets 192 168 16 0) (IPv4.fromOctets 192 168 19 255)--- 192.168.16.0/22--- >>> IPv4.printRange $ IPv4.fromBounds (IPv4.fromOctets 10 0 5 7) (IPv4.fromOctets 10 0 5 14)--- 10.0.5.0/28-fromBounds :: IPv4 -> IPv4 -> IPv4Range-fromBounds (IPv4 a) (IPv4 b) =- normalize (IPv4Range (IPv4 a) (maskFromBounds a b))--maskFromBounds :: Word32 -> Word32 -> Word8-maskFromBounds lo hi = fromIntegral (Bits.countLeadingZeros (Bits.xor lo hi))---- | Checks to see if an 'IPv4' address belongs in the 'IPv4Range'.------ >>> let ip = IPv4.fromOctets 10 10 1 92--- >>> IPv4.contains (IPv4.IPv4Range (IPv4.fromOctets 10 0 0 0) 8) ip--- True--- >>> IPv4.contains (IPv4.IPv4Range (IPv4.fromOctets 10 11 0 0) 16) ip--- False------ Typically, element-testing functions are written to take the element--- as the first argument and the set as the second argument. This is intentionally--- written the other way for better performance when iterating over a collection.--- For example, you might test elements in a list for membership like this:------ >>> let r = IPv4.IPv4Range (IPv4.fromOctets 10 10 10 6) 31--- >>> mapM_ (P.print . IPv4.contains r) (take 5 $ iterate succ $ IPv4.fromOctets 10 10 10 5)--- False--- True--- True--- False--- False------ The implementation of 'contains' ensures that (with GHC), the bitmask--- creation and range normalization only occur once in the above example.--- They are reused as the list is iterated.-contains :: IPv4Range -> IPv4 -> Bool-contains (IPv4Range (IPv4 wsubnet) len) =- let theMask = mask len- wsubnetNormalized = wsubnet .&. theMask- in \(IPv4 w) -> (w .&. theMask) == wsubnetNormalized---- | Checks if the first range is a subset of the second range.------ >>> IPv4.isSubsetOf (IPv4.IPv4Range (IPv4.fromOctets 192 0 2 128) 25) (IPv4.IPv4Range (IPv4.fromOctets 192 0 2 0) 24)--- True--- >>> IPv4.isSubsetOf (IPv4.IPv4Range (IPv4.fromOctets 192 0 2 0) 30) (IPv4.IPv4Range (IPv4.fromOctets 192 0 2 4) 30)--- False-isSubsetOf :: IPv4Range -> IPv4Range -> Bool-isSubsetOf a b =- lowerInclusive a >= lowerInclusive b- &&- upperInclusive a <= upperInclusive b--mask :: Word8 -> Word32-mask = complement . shiftR 0xffffffff . fromIntegral---- | This is provided to mirror the interface provided by @Data.Set@. It--- behaves just like 'contains' but with flipped arguments.------ prop> IPv4.member ip r == IPv4.contains r ip-member :: IPv4 -> IPv4Range -> Bool-member = flip contains---- | The inclusive lower bound of an 'IPv4Range'. This is conventionally--- understood to be the broadcast address of a subnet. For example:------ >>> T.putStrLn $ IPv4.encode $ IPv4.lowerInclusive $ IPv4.IPv4Range (IPv4.ipv4 10 10 1 160) 25--- 10.10.1.128------ Note that the lower bound of a normalized 'IPv4Range' is simply the--- ip address of the range:------ prop> IPv4.lowerInclusive r == IPv4.ipv4RangeBase (IPv4.normalize r)-lowerInclusive :: IPv4Range -> IPv4-lowerInclusive (IPv4Range (IPv4 w) len) =- IPv4 (w .&. mask len)---- | The inclusive upper bound of an 'IPv4Range'.------ >>> T.putStrLn $ IPv4.encode $ IPv4.upperInclusive $ IPv4.IPv4Range (IPv4.ipv4 10 10 1 160) 25--- 10.10.1.255-upperInclusive :: IPv4Range -> IPv4-upperInclusive (IPv4Range (IPv4 w) len) =- let theInvertedMask = shiftR 0xffffffff (fromIntegral len)- theMask = complement theInvertedMask- in IPv4 ((w .&. theMask) .|. theInvertedMask)---- Given the size of the mask, return the total number of ips in the subnet. This--- only works for IPv4 addresses because an IPv6 subnet can have up to 2^128--- addresses. Not exported.-countAddrs :: Word8 -> Word64-countAddrs w =- let amountToShift = if w > 32- then 0- else 32 - fromIntegral w- in shift 1 amountToShift--wordSuccessors :: Word64 -> IPv4 -> [IPv4]-wordSuccessors !w (IPv4 !a) = if w > 0- then IPv4 a : wordSuccessors (w - 1) (IPv4 (a + 1))- else []--wordSuccessorsM :: MonadPlus m => Word64 -> IPv4 -> m IPv4-wordSuccessorsM = go where- go !w (IPv4 !a) = if w > 0- then mplus (return (IPv4 a)) (go (w - 1) (IPv4 (a + 1)))- else mzero---- | Convert an 'IPv4Range' into a list of the 'IPv4' addresses that--- are in it.------ >>> let r = IPv4.IPv4Range (IPv4.fromOctets 192 168 1 8) 30--- >>> mapM_ (T.putStrLn . IPv4.encode) (IPv4.toList r)--- 192.168.1.8--- 192.168.1.9--- 192.168.1.10--- 192.168.1.11--toList :: IPv4Range -> [IPv4]-toList (IPv4Range ip len) =- let totalAddrs = countAddrs len- in wordSuccessors totalAddrs ip---- | A stream-polymorphic generator over an 'IPv4Range'.--- For more information, see <http://www.haskellforall.com/2014/11/how-to-build-library-agnostic-streaming.html How to build library-agnostic streaming sources>.-toGenerator :: MonadPlus m => IPv4Range -> m IPv4-toGenerator (IPv4Range ip len) =- let totalAddrs = countAddrs len- in wordSuccessorsM totalAddrs ip---- | The RFC1918 24-bit block. Subnet mask: @10.0.0.0/8@-private24 :: IPv4Range-private24 = IPv4Range (fromOctets 10 0 0 0) 8---- | The RFC1918 20-bit block. Subnet mask: @172.16.0.0/12@-private20 :: IPv4Range-private20 = IPv4Range (fromOctets 172 16 0 0) 12---- | The RFC1918 16-bit block. Subnet mask: @192.168.0.0/16@-private16 :: IPv4Range-private16 = IPv4Range (fromOctets 192 168 0 0) 16---- | Normalize an 'IPv4Range'. The first result of this is that the--- 'IPv4' inside the 'IPv4Range' is changed so that the insignificant--- bits are zeroed out. For example:------ >>> IPv4.printRange $ IPv4.normalize $ IPv4.IPv4Range (IPv4.fromOctets 192 168 1 19) 24--- 192.168.1.0/24--- >>> IPv4.printRange $ IPv4.normalize $ IPv4.IPv4Range (IPv4.fromOctets 192 168 1 163) 28--- 192.168.1.160/28------ The second effect of this is that the mask length is lowered to--- be 32 or smaller. Working with 'IPv4Range's that have not been--- normalized does not cause any issues for this library, although--- other applications may reject such ranges (especially those with--- a mask length above 32).------ Note that 'normalize' is idempotent, that is:------ prop> IPv4.normalize r == (IPv4.normalize . IPv4.normalize) r-normalize :: IPv4Range -> IPv4Range-normalize (IPv4Range (IPv4 w) len) =- let len' = min len 32- w' = w .&. mask len'- in IPv4Range (IPv4 w') len'---- | Encode an 'IPv4Range' as 'Text'.------ >>> IPv4.encodeRange (IPv4.IPv4Range (IPv4.ipv4 172 16 0 0) 12)--- "172.16.0.0/12"-encodeRange :: IPv4Range -> Text-encodeRange = rangeToDotDecimalText---- | Decode an 'IPv4Range' from 'Text'.------ >>> IPv4.decodeRange "172.16.0.0/12"--- Just (IPv4Range {ipv4RangeBase = ipv4 172 16 0 0, ipv4RangeLength = 12})--- >>> IPv4.decodeRange "192.168.25.254/16"--- Just (IPv4Range {ipv4RangeBase = ipv4 192 168 0 0, ipv4RangeLength = 16})-decodeRange :: Text -> Maybe IPv4Range-decodeRange = rightToMaybe . AT.parseOnly (parserRange <* AT.endOfInput)---- | Encode an 'IPv4Range' to a 'TBuilder.Builder'.------ >>> IPv4.builderRange (IPv4.IPv4Range (IPv4.ipv4 172 16 0 0) 12)--- "172.16.0.0/12"-builderRange :: IPv4Range -> TBuilder.Builder-builderRange = rangeToDotDecimalBuilder---- | Parse an 'IPv4Range' using a 'AT.Parser'.------ >>> AT.parseOnly IPv4.parserRange "192.168.25.254/16"--- Right (IPv4Range {ipv4RangeBase = ipv4 192 168 0 0, ipv4RangeLength = 16})-parserRange :: AT.Parser IPv4Range-parserRange = do- ip <- parser- _ <- AT.char '/'- theMask <- AT.decimal >>= limitSize- return (normalize (IPv4Range ip theMask))- where- limitSize i =- if i > 32- then fail "An IP range length must be between 0 and 32"- else return i---- | Print an 'IPv4Range'. Helper function that--- exists mostly for testing purposes.-printRange :: IPv4Range -> IO ()-printRange = TIO.putStrLn . encodeRange---- | The length should be between 0 and 32. These bounds are inclusive.--- This expectation is not in any way enforced by this library because--- it does not cause errors. A mask length greater than 32 will be--- treated as if it were 32.-data IPv4Range = IPv4Range- { ipv4RangeBase :: {-# UNPACK #-} !IPv4- , ipv4RangeLength :: {-# UNPACK #-} !Word8- } deriving (Eq,Ord,Show,Read,Generic,Data)--instance NFData IPv4Range-instance Hashable IPv4Range--instance ToJSON IPv4Range where- toJSON = Aeson.String . encodeRange--instance FromJSON IPv4Range where- parseJSON (Aeson.String t) = case decodeRange t of- Nothing -> fail "Could not decodeRange IPv4 range"- Just res -> return res- parseJSON _ = mzero--data instance MUVector.MVector s IPv4Range = MV_IPv4Range- !(MUVector.MVector s IPv4)- !(MUVector.MVector s Word8)-data instance UVector.Vector IPv4Range = V_IPv4Range- !(UVector.Vector IPv4)- !(UVector.Vector Word8)--instance UVector.Unbox IPv4Range-instance MGVector.MVector MUVector.MVector IPv4Range where- {-# INLINE basicLength #-}- basicLength (MV_IPv4Range as _) = MGVector.basicLength as- {-# INLINE basicUnsafeSlice #-}- basicUnsafeSlice i_ m_ (MV_IPv4Range as bs)- = MV_IPv4Range (MGVector.basicUnsafeSlice i_ m_ as)- (MGVector.basicUnsafeSlice i_ m_ bs)- {-# INLINE basicOverlaps #-}- basicOverlaps (MV_IPv4Range as1 bs1) (MV_IPv4Range as2 bs2)- = MGVector.basicOverlaps as1 as2- || MGVector.basicOverlaps bs1 bs2- {-# INLINE basicUnsafeNew #-}- basicUnsafeNew n_- = do- as <- MGVector.basicUnsafeNew n_- bs <- MGVector.basicUnsafeNew n_- return $ MV_IPv4Range as bs- {-# INLINE basicInitialize #-}- basicInitialize (MV_IPv4Range as bs)- = do- MGVector.basicInitialize as- MGVector.basicInitialize bs- {-# INLINE basicUnsafeReplicate #-}- basicUnsafeReplicate n_ (IPv4Range a b)- = do- as <- MGVector.basicUnsafeReplicate n_ a- bs <- MGVector.basicUnsafeReplicate n_ b- return (MV_IPv4Range as bs)- {-# INLINE basicUnsafeRead #-}- basicUnsafeRead (MV_IPv4Range as bs) i_- = do- a <- MGVector.basicUnsafeRead as i_- b <- MGVector.basicUnsafeRead bs i_- return (IPv4Range a b)- {-# INLINE basicUnsafeWrite #-}- basicUnsafeWrite (MV_IPv4Range as bs) i_ (IPv4Range a b)- = do- MGVector.basicUnsafeWrite as i_ a- MGVector.basicUnsafeWrite bs i_ b- {-# INLINE basicClear #-}- basicClear (MV_IPv4Range as bs)- = do- MGVector.basicClear as- MGVector.basicClear bs- {-# INLINE basicSet #-}- basicSet (MV_IPv4Range as bs) (IPv4Range a b)- = do- MGVector.basicSet as a- MGVector.basicSet bs b- {-# INLINE basicUnsafeCopy #-}- basicUnsafeCopy (MV_IPv4Range as1 bs1) (MV_IPv4Range as2 bs2)- = do- MGVector.basicUnsafeCopy as1 as2- MGVector.basicUnsafeCopy bs1 bs2- {-# INLINE basicUnsafeMove #-}- basicUnsafeMove (MV_IPv4Range as1 bs1) (MV_IPv4Range as2 bs2)- = do- MGVector.basicUnsafeMove as1 as2- MGVector.basicUnsafeMove bs1 bs2- {-# INLINE basicUnsafeGrow #-}- basicUnsafeGrow (MV_IPv4Range as bs) m_- = do- as' <- MGVector.basicUnsafeGrow as m_- bs' <- MGVector.basicUnsafeGrow bs m_- return $ MV_IPv4Range as' bs'--instance GVector.Vector UVector.Vector IPv4Range where- {-# INLINE basicUnsafeFreeze #-}- basicUnsafeFreeze (MV_IPv4Range as bs)- = do- as' <- GVector.basicUnsafeFreeze as- bs' <- GVector.basicUnsafeFreeze bs- return $ V_IPv4Range as' bs'- {-# INLINE basicUnsafeThaw #-}- basicUnsafeThaw (V_IPv4Range as bs)- = do- as' <- GVector.basicUnsafeThaw as- bs' <- GVector.basicUnsafeThaw bs- return $ MV_IPv4Range as' bs'- {-# INLINE basicLength #-}- basicLength (V_IPv4Range as _) = GVector.basicLength as- {-# INLINE basicUnsafeSlice #-}- basicUnsafeSlice i_ m_ (V_IPv4Range as bs)- = V_IPv4Range (GVector.basicUnsafeSlice i_ m_ as)- (GVector.basicUnsafeSlice i_ m_ bs)- {-# INLINE basicUnsafeIndexM #-}- basicUnsafeIndexM (V_IPv4Range as bs) i_- = do- a <- GVector.basicUnsafeIndexM as i_- b <- GVector.basicUnsafeIndexM bs i_- return (IPv4Range a b)- {-# INLINE basicUnsafeCopy #-}- basicUnsafeCopy (MV_IPv4Range as1 bs1) (V_IPv4Range as2 bs2)- = do- GVector.basicUnsafeCopy as1 as2- GVector.basicUnsafeCopy bs1 bs2- {-# INLINE elemseq #-}- elemseq _ (IPv4Range a b)- = GVector.elemseq (undefined :: UVector.Vector a) a- . GVector.elemseq (undefined :: UVector.Vector b) b---------------------- Internal Stuff--------------------rangeToDotDecimalText :: IPv4Range -> Text-rangeToDotDecimalText = LText.toStrict . TBuilder.toLazyText . rangeToDotDecimalBuilder--rangeToDotDecimalBuilder :: IPv4Range -> TBuilder.Builder-rangeToDotDecimalBuilder (IPv4Range addr len) =- builder addr- <> TBuilder.singleton '/'- <> TBI.decimal len-+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UnboxedTuples #-}++{- | This module provides the IPv4 data type and functions for working+ with it.+-}+module Net.IPv4+ ( -- * Conversion Functions+ ipv4+ , fromOctets+ , fromTupleOctets+ , toOctets++ -- * Special IP Addresses+ , any+ , loopback+ , localhost+ , broadcast++ -- * Range Predicates+ , private+ , reserved+ , public++ -- * Textual Conversion++ -- ** Text+ , encode+ , decode+ , builder+ , reader+ , parser+ , decodeShort+ , encodeShort++ -- ** UTF-8 ByteString+ , encodeUtf8+ , decodeUtf8+ , builderUtf8+ , parserUtf8++ -- ** UTF-8 Bytes+ , decodeUtf8Bytes+ , parserUtf8Bytes+ , byteArrayBuilderUtf8+ , boundedBuilderUtf8++ -- ** Non-textual Bytes+ , boundedBuilderOctetsBE+ , boundedBuilderOctetsLE++ -- ** String+ -- $string+ , encodeString+ , decodeString++ -- ** Printing+ , print++ -- * IPv4 Ranges++ -- ** Range functions+ , range+ , fromBounds+ , normalize+ , contains+ , isSubsetOf+ , member+ , lowerInclusive+ , upperInclusive++ -- ** Conversion to IPv4+ , toList+ , toGenerator++ -- ** Private Ranges+ , private24+ , private20+ , private16++ -- ** Textual Conversion++ -- *** Text+ , encodeRange+ , decodeRange+ , builderRange+ , parserRange+ , printRange++ -- ** UTF-8 Bytes+ , parserRangeUtf8Bytes+ , parserRangeUtf8BytesLenient++ -- * Types+ , IPv4 (..)+ , IPv4#+ , IPv4Range (..)++ -- * Unboxing++ -- | These functions are useful for micro-optimizing+ -- when GHC does a poor job with worker-wrapper.+ , box+ , unbox+ , parserUtf8Bytes#++ -- * Interoperability+ -- $interoperability+ ) where++import Control.DeepSeq (NFData)+import Control.Monad+import Control.Monad.ST (ST, runST)+import Data.Aeson (FromJSON (..), FromJSONKey (..), FromJSONKeyFunction (..), ToJSON (..), ToJSONKey (..), ToJSONKeyFunction (..))+import Data.Bits (Bits (..))+import Data.ByteString (ByteString)+import Data.Coerce (coerce)+import Data.Data (Data)+import Data.Hashable+import Data.Ix (Ix)+import Data.Primitive.Types (Prim)+import Data.Text (Text)+import Data.Text.Builder.Common.Compat (Codepoint)+import Data.Text.Encoding (decodeUtf8')+import Data.Text.Internal (Text (..))+import Data.Text.Short (ShortText)+import Data.Vector.Generic.Mutable (MVector (..))+import Data.Word+import Foreign.Ptr (Ptr, plusPtr)+import Foreign.Storable (Storable, poke)+import GHC.Exts (Word#)+import GHC.Generics (Generic)+import Text.ParserCombinators.ReadPrec (prec, step)+import Text.Printf (printf)+import Text.Read (Lexeme (Ident), Read (..), lexP, parens)+import Prelude hiding (any, print)++import qualified Arithmetic.Nat as Nat+import qualified Data.Aeson as Aeson+import qualified Data.Aeson.Types as Aeson+import qualified Data.Attoparsec.ByteString.Char8 as AB+import qualified Data.Attoparsec.Text as AT+import qualified Data.Bits as Bits+import qualified Data.ByteString.Builder as Builder+import qualified Data.ByteString.Char8 as BC8+import qualified Data.ByteString.Internal as I+import qualified Data.ByteString.Short.Internal as BSS+import qualified Data.ByteString.Unsafe as ByteString+import qualified Data.Bytes as Bytes+import qualified Data.Bytes.Builder as UB+import qualified Data.Bytes.Builder.Bounded as BB+import qualified Data.Bytes.Parser as Parser+import qualified Data.Bytes.Parser.Latin as Latin+import qualified Data.Char as Char+import qualified Data.Primitive as PM+import qualified Data.Text as Text+import qualified Data.Text.Array as TArray+import qualified Data.Text.IO as TIO+import qualified Data.Text.Lazy as LText+import qualified Data.Text.Lazy.Builder as TBuilder+import qualified Data.Text.Lazy.Builder.Int as TBI+import qualified Data.Text.Read as TextRead+import qualified Data.Text.Short as TS+import qualified Data.Text.Short.Unsafe as TS+import qualified Data.Vector.Generic as GVector+import qualified Data.Vector.Generic.Mutable as MGVector+import qualified Data.Vector.Primitive as PVector+import qualified Data.Vector.Unboxed as UVector+import qualified Data.Vector.Unboxed.Mutable as MUVector+import qualified GHC.Word.Compat as Compat++#if MIN_VERSION_aeson(2,0,0)+import qualified Data.Aeson.Key as AesonKey+#endif++{- $setup++These are here to get doctest's property checking to work++>>> :set -XOverloadedStrings+>>> import Test.QuickCheck (Arbitrary(..))+>>> import Net.IPv4 (getIPv4)+>>> import qualified Prelude as P+>>> import qualified Data.Text.IO as T+>>> import qualified Data.Bytes.Text.Ascii as Ascii+>>> import qualified Data.Attoparsec.Text as AT+>>> import qualified Data.ByteString.Builder as Builder+>>> import qualified Data.Bytes.Builder as UB+>>> import qualified Data.Attoparsec.ByteString.Char8 as AB+>>> instance Arbitrary IPv4 where { arbitrary = fmap IPv4 arbitrary }+>>> instance Arbitrary IPv4.IPv4Range where { arbitrary = IPv4.IPv4Range <$> arbitrary <*> arbitrary }+>>> import qualified Data.Bytes.Chunks as Chunks+-}++{- | Create an 'IPv4' address from four octets. The first argument+ is the most significant octet. The last argument is the least+ significant. Since IP addresses are commonly written using dot-decimal+ notation, this is the recommended way to create an IP address.+ Additionally, it is used for the 'Show' and 'Read' instances+ of 'IPv4' to help keep things readable in GHCi.++ >>> let addr = IPv4.ipv4 192 168 1 1+ >>> addr+ ipv4 192 168 1 1+ >>> getIPv4 addr+ 3232235777+-}+ipv4 :: Word8 -> Word8 -> Word8 -> Word8 -> IPv4+ipv4 = fromOctets++-- | An alias for the 'ipv4' smart constructor.+fromOctets :: Word8 -> Word8 -> Word8 -> Word8 -> IPv4+fromOctets a b c d =+ fromOctets'+ (fromIntegral a)+ (fromIntegral b)+ (fromIntegral c)+ (fromIntegral d)++-- | An uncurried variant of 'fromOctets'.+fromTupleOctets :: (Word8, Word8, Word8, Word8) -> IPv4+fromTupleOctets (a, b, c, d) = fromOctets a b c d++{- | Convert an 'IPv4' address into a quadruple of octets. The first+ element in the quadruple is the most significant octet. The last+ element is the least significant octet.+-}+toOctets :: IPv4 -> (Word8, Word8, Word8, Word8)+toOctets (IPv4 w) =+ ( fromIntegral (shiftR w 24)+ , fromIntegral (shiftR w 16)+ , fromIntegral (shiftR w 8)+ , fromIntegral w+ )++{- | The IP address representing any host.++ >>> IPv4.any+ ipv4 0 0 0 0+-}+any :: IPv4+any = IPv4 0++{- | The local loopback IP address.++ >>> IPv4.loopback+ ipv4 127 0 0 1+-}+loopback :: IPv4+loopback = fromOctets 127 0 0 1++{- | A useful and common alias for 'loopback'.++ >>> IPv4.localhost+ ipv4 127 0 0 1+-}+localhost :: IPv4+localhost = loopback++{- | The broadcast IP address.++ >>> IPv4.broadcast+ ipv4 255 255 255 255+-}+broadcast :: IPv4+broadcast = fromOctets 255 255 255 255++{- | Checks to see if the 'IPv4' address belongs to a private+network. The three private networks that are checked are+@10.0.0.0/8@, @172.16.0.0/12@, and @192.168.0.0/16@.+-}+private :: IPv4 -> Bool+private (IPv4 w) =+ mask8 .&. w == p24+ || mask12 .&. w == p20+ || mask16 .&. w == p16++----------------------------------------+-- Note [The implementation of reserved]+----------------------------------------+-- The @reserved@ function has been optimized to perform well in the+-- microbenchmark @CIDR Inclusion/reserved@. We perform an inital case+-- on the upper three bits (8 possible values), which GHC will compile+-- to a jump table. This helps because the reserved ranges of IPv4+-- addresses are somewhat clustered. Notice that everything in+-- 32.0.0.0/3, 64.0.0.0/3, and 128.0.0.0/3 is publicly routable, and+-- everything in 224.0.0.0/3 is reserved. This means that for exactly+-- half of the IPv4 addresses that exist, this single jump is sufficient+-- for determining whether or not they are reserved. For the others,+-- there is a little more work to do, particularly in the 192.0.0.0/3+-- range. On the laptop that ran the microbenchmark, this function+-- decided the reservedness of 100 random IPv4 addresses in 200ns.++{- | Checks to see if the 'IPv4' address belongs to a reserved+network. This includes the three private networks that 'private'+checks along with several other ranges that are not used+on the public Internet. The implementation of this function+is optimized.+-}+reserved :: IPv4 -> Bool+reserved !(IPv4 w) = case unsafeShiftR w 29 of+ 0 ->+ let a = getIPv4 $ fromOctets' 0 0 0 0+ y = getIPv4 $ fromOctets' 10 0 0 0+ in mask8 .&. w == a+ || mask8 .&. w == y+ 1 -> False+ 2 -> False+ 3 ->+ let b = getIPv4 $ fromOctets' 100 64 0 0+ c = getIPv4 $ fromOctets' 127 0 0 0+ in mask8 .&. w == c+ || mask10 .&. w == b+ 4 -> False+ 5 ->+ let d = getIPv4 $ fromOctets' 169 254 0 0+ x = getIPv4 $ fromOctets' 172 16 0 0+ in mask12 .&. w == x+ || mask16 .&. w == d+ 6 ->+ let e = getIPv4 $ fromOctets' 192 0 0 0+ f = getIPv4 $ fromOctets' 192 0 2 0+ g = getIPv4 $ fromOctets' 192 88 99 0+ h = getIPv4 $ fromOctets' 198 18 0 0+ i = getIPv4 $ fromOctets' 198 51 100 0+ j = getIPv4 $ fromOctets' 203 0 113 0+ z = getIPv4 $ fromOctets' 192 168 0 0+ in mask15 .&. w == h+ || mask16 .&. w == z+ || mask24 .&. w == e+ || mask24 .&. w == f+ || mask24 .&. w == g+ || mask24 .&. w == i+ || mask24 .&. w == j+ _ -> True++mask8, mask12, mask16, mask10, mask24, mask15 :: Word32+mask8 = 0xFF000000+mask10 = 0xFFC00000+mask12 = 0xFFF00000+mask15 = 0xFFFE0000+mask16 = 0xFFFF0000+mask24 = 0xFFFFFF00++{- | Checks to see if the 'IPv4' address is publicly routable.++prop> IPv4.public x == not (IPv4.reserved x)+-}+public :: IPv4 -> Bool+public = not . reserved++{- | Encode an 'IPv4' address to 'Text' using dot-decimal notation:++ >>> T.putStrLn (IPv4.encode (IPv4.ipv4 192 168 2 47))+ 192.168.2.47+-}+encode :: IPv4 -> Text+encode = toDotDecimalText++{- | Decode an 'IPv4' address.++ >>> IPv4.decode "192.168.2.47"+ Just (ipv4 192 168 2 47)++ >>> IPv4.decode "10.100.256.256"+ Nothing+-}+decode :: Text -> Maybe IPv4+decode = decodeIPv4TextMaybe++{- | Encode an 'IPv4' address to a text 'TBuilder.Builder'.++ >>> IPv4.builder (IPv4.ipv4 192 168 2 47)+ "192.168.2.47"+-}+builder :: IPv4 -> TBuilder.Builder+builder = toDotDecimalBuilder++{- | Parse an 'IPv4' address using a 'TextRead.Reader'.++ >>> IPv4.reader "192.168.2.47"+ Right (ipv4 192 168 2 47,"")++ >>> IPv4.reader "192.168.2.470"+ Left "All octets in an IPv4 address must be between 0 and 255"+-}+reader :: TextRead.Reader IPv4+reader = decodeIPv4TextReader++{- | Parse an 'IPv4' address using a 'AT.Parser'.++ >>> AT.parseOnly IPv4.parser "192.168.2.47"+ Right (ipv4 192 168 2 47)++ >>> AT.parseOnly IPv4.parser "192.168.2.470"+ Left "Failed reading: All octets in an IPv4 address must be between 0 and 255"+-}+parser :: AT.Parser IPv4+parser = dotDecimalParser++{- | Encode an 'IPv4' address to a UTF-8 encoded 'ByteString'.++ >>> IPv4.encodeUtf8 (IPv4.ipv4 192 168 2 47)+ "192.168.2.47"+-}+encodeUtf8 :: IPv4 -> ByteString+encodeUtf8 = toBSPreAllocated++toBSPreAllocated :: IPv4 -> ByteString+toBSPreAllocated (IPv4 !w) =+ I.unsafeCreateUptoN+ 15+ ( \ptr1 ->+ do+ len1 <- writeWord ptr1 w1+ let ptr2 = ptr1 `plusPtr` len1+ poke ptr2 dot+ len2 <- writeWord (ptr2 `plusPtr` 1) w2+ let ptr3 = ptr2 `plusPtr` len2 `plusPtr` 1+ poke ptr3 dot+ len3 <- writeWord (ptr3 `plusPtr` 1) w3+ let ptr4 = ptr3 `plusPtr` len3 `plusPtr` 1+ poke ptr4 dot+ len4 <- writeWord (ptr4 `plusPtr` 1) w4+ return (3 + len1 + len2 + len3 + len4)+ )+ where+ w1 = fromIntegral $ shiftR w 24+ w2 = fromIntegral $ shiftR w 16+ w3 = fromIntegral $ shiftR w 8+ w4 = fromIntegral w+ dot = 46 :: Word8+ writeWord :: Ptr Word8 -> Word8 -> IO Int+ writeWord !ptr !word+ | word >= 100 = do+ let int = fromIntegral word+ indx = int + int + int+ get3 = fromIntegral . ByteString.unsafeIndex threeDigits+ poke ptr (get3 indx)+ poke (ptr `plusPtr` 1) (get3 (indx + 1))+ poke (ptr `plusPtr` 2) (get3 (indx + 2))+ return 3+ | word >= 10 = do+ let int = fromIntegral word+ indx = int + int+ get2 = fromIntegral . ByteString.unsafeIndex twoDigits+ poke ptr (get2 indx)+ poke (ptr `plusPtr` 1) (get2 (indx + 1))+ return 2+ | otherwise = do+ poke ptr (word + 48)+ return 1++{- | Decode a UTF8-encoded 'ByteString' into an 'IPv4'.++ >>> IPv4.decodeUtf8 "192.168.2.47"+ Just (ipv4 192 168 2 47)++ Currently not terribly efficient since the implementation+ re-encodes the argument as UTF-16 text before decoding that+ IPv4 address from that. PRs to fix this are welcome.+-}+decodeUtf8 :: ByteString -> Maybe IPv4+decodeUtf8 = decode <=< rightToMaybe . decodeUtf8'++-- This (decodeUtf8) should be rewritten to not go through text+-- as an intermediary.++{- | Decode 'ShortText' as an 'IPv4' address.++ >>> IPv4.decodeShort "192.168.3.48"+ Just (ipv4 192 168 3 48)+-}+decodeShort :: ShortText -> Maybe IPv4+decodeShort t = decodeUtf8Bytes (Bytes.fromByteArray b)+ where+ b = shortByteStringToByteArray (TS.toShortByteString t)++{- | Encode an 'IPv4' address as 'ShortText'.++ >>> IPv4.encodeShort (IPv4.ipv4 192 168 5 99)+ "192.168.5.99"+-}+encodeShort :: IPv4 -> ShortText+encodeShort !w =+ id $+ TS.fromShortByteStringUnsafe $+ byteArrayToShortByteString $+ BB.run Nat.constant $+ boundedBuilderUtf8 $+ w++shortByteStringToByteArray :: BSS.ShortByteString -> PM.ByteArray+shortByteStringToByteArray (BSS.SBS x) = PM.ByteArray x++byteArrayToShortByteString :: PM.ByteArray -> BSS.ShortByteString+byteArrayToShortByteString (PM.ByteArray x) = BSS.SBS x++{- | Decode UTF-8-encoded 'Bytes' into an 'IPv4' address.++ >>> IPv4.decodeUtf8Bytes (Ascii.fromString "127.0.0.1")+ Just (ipv4 127 0 0 1)+-}+decodeUtf8Bytes :: Bytes.Bytes -> Maybe IPv4+decodeUtf8Bytes !b = case Parser.parseBytes (parserUtf8Bytes ()) b of+ Parser.Success (Parser.Slice _ len addr) -> case len of+ 0 -> Just addr+ _ -> Nothing+ Parser.Failure _ -> Nothing++{- | Parse UTF-8-encoded 'Bytes' as an 'IPv4' address.++ >>> Parser.parseBytes (IPv4.parserUtf8Bytes ()) (Ascii.fromString "10.0.1.254")+ Success (Slice {offset = 10, length = 0, value = ipv4 10 0 1 254})+-}+parserUtf8Bytes :: e -> Parser.Parser e s IPv4+{-# INLINE parserUtf8Bytes #-}+parserUtf8Bytes e = coerce (Parser.boxWord32 (parserUtf8Bytes# e))++-- | Variant of 'parserUtf8Bytes' with unboxed result type.+parserUtf8Bytes# :: e -> Parser.Parser e s IPv4#+{-# NOINLINE parserUtf8Bytes# #-}+parserUtf8Bytes# e = Parser.unboxWord32 $ do+ !a <- Latin.decWord8 e+ Latin.char e '.'+ !b <- Latin.decWord8 e+ Latin.char e '.'+ !c <- Latin.decWord8 e+ Latin.char e '.'+ !d <- Latin.decWord8 e+ pure (getIPv4 (fromOctets a b c d))++{- | Parse UTF-8-encoded 'Bytes' into an 'IPv4Range'.+This requires the mask to be present.++>>> maybe (putStrLn "nope") IPv4.printRange $ Parser.parseBytesMaybe (IPv4.parserRangeUtf8Bytes ()) (Ascii.fromString "192.168.0.0/16")+192.168.0.0/16+>>> maybe (putStrLn "nope") IPv4.printRange $ Parser.parseBytesMaybe (IPv4.parserRangeUtf8Bytes ()) (Ascii.fromString "10.10.10.1")+nope++See 'parserRangeUtf8BytesLenient' for a variant that treats+a missing mask as a @/32@ mask.+-}+parserRangeUtf8Bytes :: e -> Parser.Parser e s IPv4Range+parserRangeUtf8Bytes e = do+ base <- parserUtf8Bytes e+ Latin.char e '/'+ theMask <- Latin.decWord8 e+ if theMask > 32+ then Parser.fail e+ else pure $! normalize (IPv4Range base theMask)++{- | Variant of 'parserRangeUtf8Bytes' that allows the mask+to be omitted. An omitted mask is treated as a @/32@ mask.++>>> maybe (putStrLn "nope") IPv4.printRange $ Parser.parseBytesMaybe (IPv4.parserRangeUtf8BytesLenient ()) (Ascii.fromString "192.168.0.0/16")+192.168.0.0/16+>>> maybe (putStrLn "nope") IPv4.printRange $ Parser.parseBytesMaybe (IPv4.parserRangeUtf8BytesLenient ()) (Ascii.fromString "10.10.10.1")+10.10.10.1/32+-}+parserRangeUtf8BytesLenient :: e -> Parser.Parser e s IPv4Range+parserRangeUtf8BytesLenient e = do+ base <- parserUtf8Bytes e+ Latin.trySatisfy (== '/') >>= \case+ True -> do+ theMask <- Latin.decWord8 e+ if theMask > 32+ then Parser.fail e+ else pure $! normalize (IPv4Range base theMask)+ False -> pure $! IPv4Range base 32++{- | Encode an 'IPv4' as a bytestring 'Builder.Builder'++>>> Builder.toLazyByteString (IPv4.builderUtf8 (IPv4.fromOctets 192 168 2 12))+"192.168.2.12"+-}+builderUtf8 :: IPv4 -> Builder.Builder+builderUtf8 = Builder.byteString . encodeUtf8++{- | Encode an 'IPv4' address as a unbounded byte array builder.++>>> Chunks.concat (UB.run 1 (IPv4.byteArrayBuilderUtf8 (IPv4.fromOctets 192 168 2 13)))+[0x31,0x39,0x32,0x2e,0x31,0x36,0x38,0x2e,0x32,0x2e,0x31,0x33]++Note that period is encoded by UTF-8 as @0x2e@.+-}+byteArrayBuilderUtf8 :: IPv4 -> UB.Builder+byteArrayBuilderUtf8 = UB.fromBounded Nat.constant . boundedBuilderUtf8++{- | Encode an 'IPv4' address as a bounded byte array builder.++>>> BB.run Nat.constant (IPv4.boundedBuilderUtf8 (IPv4.fromOctets 192 168 2 14))+[0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x32, 0x2e, 0x31, 0x34]++Note that period is encoded by UTF-8 as @0x2e@.+-}+boundedBuilderUtf8 :: IPv4 -> BB.Builder 15+boundedBuilderUtf8 (IPv4 !w) =+ BB.word8Dec w1+ `BB.append` BB.ascii '.'+ `BB.append` BB.word8Dec w2+ `BB.append` BB.ascii '.'+ `BB.append` BB.word8Dec w3+ `BB.append` BB.ascii '.'+ `BB.append` BB.word8Dec w4+ where+ w1 = fromIntegral (shiftR w 24) :: Word8+ w2 = fromIntegral (shiftR w 16) :: Word8+ w3 = fromIntegral (shiftR w 8) :: Word8+ w4 = fromIntegral w :: Word8++{- | Encode 'IPv4' address to a sequence a 4 bytes with the first+byte representing corresponding to the most significant byte in+the address.++>>> BB.run Nat.constant (IPv4.boundedBuilderOctetsBE (IPv4.fromOctets 0xc0 0xa8 0x02 0x1f))+[0xc0, 0xa8, 0x02, 0x1f]+-}+boundedBuilderOctetsBE :: IPv4 -> BB.Builder 4+{-# INLINE boundedBuilderOctetsBE #-}+boundedBuilderOctetsBE (IPv4 !w) =+ BB.word8 w1+ `BB.append` BB.word8 w2+ `BB.append` BB.word8 w3+ `BB.append` BB.word8 w4+ where+ w1 = fromIntegral (shiftR w 24) :: Word8+ w2 = fromIntegral (shiftR w 16) :: Word8+ w3 = fromIntegral (shiftR w 8) :: Word8+ w4 = fromIntegral w :: Word8++{- | Encode 'IPv4' address to a sequence a 4 bytes with the first+byte representing corresponding to the least significant byte in+the address.++>>> BB.run Nat.constant (IPv4.boundedBuilderOctetsLE (IPv4.fromOctets 0xc0 0xa8 0x02 0x1f))+[0x1f, 0x02, 0xa8, 0xc0]+-}+boundedBuilderOctetsLE :: IPv4 -> BB.Builder 4+{-# INLINE boundedBuilderOctetsLE #-}+boundedBuilderOctetsLE (IPv4 !w) =+ BB.word8 w4+ `BB.append` BB.word8 w3+ `BB.append` BB.word8 w2+ `BB.append` BB.word8 w1+ where+ w1 = fromIntegral (shiftR w 24) :: Word8+ w2 = fromIntegral (shiftR w 16) :: Word8+ w3 = fromIntegral (shiftR w 8) :: Word8+ w4 = fromIntegral w :: Word8++{- | Parse an 'IPv4' using a 'AB.Parser'.++ >>> AB.parseOnly IPv4.parserUtf8 "192.168.2.47"+ Right (ipv4 192 168 2 47)++ >>> AB.parseOnly IPv4.parserUtf8 "192.168.2.470"+ Left "Failed reading: All octets in an ipv4 address must be between 0 and 255"+-}+parserUtf8 :: AB.Parser IPv4+parserUtf8 =+ fromOctets'+ <$> (AB.decimal >>= limitSize)+ <* AB.char '.'+ <*> (AB.decimal >>= limitSize)+ <* AB.char '.'+ <*> (AB.decimal >>= limitSize)+ <* AB.char '.'+ <*> (AB.decimal >>= limitSize)+ where+ limitSize i =+ if i > 255+ then fail "All octets in an ipv4 address must be between 0 and 255"+ else return i++{- $string++ These functions exist for the convenience of those who need a+ 'String' representation of an 'IPv4' address. Using them+ is discouraged unless the end user is working with a library+ that can only use 'String' to deal with textual data (such as+ @pandoc@, @hxr@, or @network@).+-}++-- | Encode an 'IPv4' as a 'String'.+encodeString :: IPv4 -> String+encodeString = Text.unpack . encode++-- | Decode an 'IPv4' from a 'String'.+decodeString :: String -> Maybe IPv4+decodeString = decode . Text.pack++{- | Unboxed variant of 'IPv4'. Before GHC 8.10, this is+implemented as a type synonym. Portable use of this type requires+treating it as though it were opaque. Use 'box' and 'unbox' to+convert between this and the lifted 'IPv4'.+-}+type IPv4# = Word#++-- | Convert an unboxed IPv4 address to a boxed one.+box :: IPv4# -> IPv4+{-# INLINE box #-}+box w = IPv4 (Compat.W32# w)++-- | Convert a boxed IPv4 address to an unboxed one.+unbox :: IPv4 -> IPv4#+{-# INLINE unbox #-}+unbox (IPv4 (Compat.W32# w)) = w++{- | A 32-bit Internet Protocol version 4 address. To use this with the+ @network@ library, it is necessary to use @Network.Socket.htonl@ to+ convert the underlying 'Word32' from host byte order to network byte+ order.+-}+newtype IPv4 = IPv4 {getIPv4 :: Word32}+ deriving (Bits.Bits, Bounded, Data, Enum, Eq, Bits.FiniteBits, Generic, Hashable, Ix, Ord, Prim, Storable)++instance NFData IPv4++instance Show IPv4 where+ showsPrec p addr =+ showParen (p > 10) $+ showString "ipv4 "+ . showsPrec 11 a+ . showChar ' '+ . showsPrec 11 b+ . showChar ' '+ . showsPrec 11 c+ . showChar ' '+ . showsPrec 11 d+ where+ (a, b, c, d) = toOctets addr++instance Read IPv4 where+ readPrec = parens $ prec 10 $ do+ Ident "ipv4" <- lexP+ a <- step readPrec+ b <- step readPrec+ c <- step readPrec+ d <- step readPrec+ return (fromOctets a b c d)++-- | Print an 'IPv4' using the textual encoding.+print :: IPv4 -> IO ()+print = TIO.putStrLn . encode++newtype instance UVector.MVector s IPv4 = MV_IPv4 (PVector.MVector s IPv4)+newtype instance UVector.Vector IPv4 = V_IPv4 (PVector.Vector IPv4)++instance UVector.Unbox IPv4++instance MGVector.MVector UVector.MVector IPv4 where+ {-# INLINE basicLength #-}+ {-# INLINE basicUnsafeSlice #-}+ {-# INLINE basicOverlaps #-}+ {-# INLINE basicUnsafeNew #-}+ {-# INLINE basicInitialize #-}+ {-# INLINE basicUnsafeReplicate #-}+ {-# INLINE basicUnsafeRead #-}+ {-# INLINE basicUnsafeWrite #-}+ {-# INLINE basicClear #-}+ {-# INLINE basicSet #-}+ {-# INLINE basicUnsafeCopy #-}+ {-# INLINE basicUnsafeGrow #-}+ basicLength (MV_IPv4 v) = MGVector.basicLength v+ basicUnsafeSlice i n (MV_IPv4 v) = MV_IPv4 $ MGVector.basicUnsafeSlice i n v+ basicOverlaps (MV_IPv4 v1) (MV_IPv4 v2) = MGVector.basicOverlaps v1 v2+ basicUnsafeNew n = MV_IPv4 `liftM` MGVector.basicUnsafeNew n+ basicInitialize (MV_IPv4 v) = MGVector.basicInitialize v+ basicUnsafeReplicate n x = MV_IPv4 `liftM` MGVector.basicUnsafeReplicate n x+ basicUnsafeRead (MV_IPv4 v) i = MGVector.basicUnsafeRead v i+ basicUnsafeWrite (MV_IPv4 v) i x = MGVector.basicUnsafeWrite v i x+ basicClear (MV_IPv4 v) = MGVector.basicClear v+ basicSet (MV_IPv4 v) x = MGVector.basicSet v x+ basicUnsafeCopy (MV_IPv4 v1) (MV_IPv4 v2) = MGVector.basicUnsafeCopy v1 v2+ basicUnsafeMove (MV_IPv4 v1) (MV_IPv4 v2) = MGVector.basicUnsafeMove v1 v2+ basicUnsafeGrow (MV_IPv4 v) n = MV_IPv4 `liftM` MGVector.basicUnsafeGrow v n++instance GVector.Vector UVector.Vector IPv4 where+ {-# INLINE basicUnsafeFreeze #-}+ {-# INLINE basicUnsafeThaw #-}+ {-# INLINE basicLength #-}+ {-# INLINE basicUnsafeSlice #-}+ {-# INLINE basicUnsafeIndexM #-}+ {-# INLINE elemseq #-}+ basicUnsafeFreeze (MV_IPv4 v) = V_IPv4 `liftM` GVector.basicUnsafeFreeze v+ basicUnsafeThaw (V_IPv4 v) = MV_IPv4 `liftM` GVector.basicUnsafeThaw v+ basicLength (V_IPv4 v) = GVector.basicLength v+ basicUnsafeSlice i n (V_IPv4 v) = V_IPv4 $ GVector.basicUnsafeSlice i n v+ basicUnsafeIndexM (V_IPv4 v) i = GVector.basicUnsafeIndexM v i+ basicUnsafeCopy (MV_IPv4 mv) (V_IPv4 v) = GVector.basicUnsafeCopy mv v+ elemseq _ = seq++instance ToJSON IPv4 where+ toJSON = Aeson.String . encode++instance FromJSON IPv4 where+ parseJSON = Aeson.withText "IPv4" aesonParser++instance ToJSONKey IPv4 where+ toJSONKey =+ ToJSONKeyText+ (keyFromText . encode)+ (\addr -> Aeson.unsafeToEncoding $ Builder.char7 '"' <> builderUtf8 addr <> Builder.char7 '"')+ where+#if MIN_VERSION_aeson(2,0,0)+ keyFromText = AesonKey.fromText+#else+ keyFromText = id+#endif++instance FromJSONKey IPv4 where+ fromJSONKey = FromJSONKeyTextParser aesonParser++aesonParser :: Text -> Aeson.Parser IPv4+aesonParser t = case decode t of+ Nothing -> fail "Could not parse IPv4 address"+ Just addr -> return addr++------------------------------------+-- Internal functions, not exported+------------------------------------++decodeIPv4TextMaybe :: Text -> Maybe IPv4+decodeIPv4TextMaybe t = case decodeIPv4TextReader t of+ Left _ -> Nothing+ Right (w, t') ->+ if Text.null t'+ then Just w+ else Nothing++decodeIPv4TextReader :: TextRead.Reader IPv4+decodeIPv4TextReader t1' = do+ (a, t2) <- readOctet t1'+ t2' <- stripDecimal t2+ (b, t3) <- readOctet t2'+ t3' <- stripDecimal t3+ (c, t4) <- readOctet t3'+ t4' <- stripDecimal t4+ (d, t5) <- readOctet t4'+ Right (fromOctets' a b c d, t5)++{- | Read an IPv4 octet (@0 <= n <= 255@)++The input must begin with at least one decimal digit. Input is consumed+until a non-digit is reached, the end of the input is reached, or the+accumulated value exceeds the maximum bound (255). As with+'TextRead.decimal', any number of leading zeros are permitted.++Optimizations:++* The 'Char.isDigit' and 'Char.digitToInt' functions are avoided in order+ to avoiding checking the range more than once. This implementation calls+ 'Char.ord' (once) and uses the result for both the range check and the+ calculation.+* The type of the accumulated value is 'Int', allowing for a single+ 'fromIntegral' call instead of one for each digit. This is possible+ because the maximum bound (255) is sufficiently less than the maximum+ bound of 'Int'. Specifically: @255 * 10 + Char.ord '9' <= maxBound@+* This implementation does not make use of @UnboxedTuples@ because the+ @span_@ function is part of the internal API. Additional performance+ could be gained by using this internal API function.+-}+readOctet :: TextRead.Reader Word+readOctet t = do+ let (digits, rest) = Text.span Char.isDigit t+ when (Text.null digits) $ Left "octet does not start with a digit"+ case Text.foldr go Just digits 0 of+ Just n -> Right (fromIntegral n, rest)+ Nothing -> Left ipOctetSizeErrorMsg+ where+ go :: Char -> (Int -> Maybe Int) -> Int -> Maybe Int+ go !d !f !n =+ let n' = n * 10 + Char.ord d - 48+ in if n' <= 255 then f n' else Nothing++stripDecimal :: Text -> Either String Text+stripDecimal t = case Text.uncons t of+ Nothing -> Left "expected a dot but input ended instead"+ Just (c, tnext) ->+ if c == '.'+ then Right tnext+ else Left "expected a dot but found a different character"++{- | This is sort of a misnomer. It takes Word to make+ dotDecimalParser perform better. This is mostly+ for internal use. The arguments must all fit+ in a Word8.+-}+fromOctets' :: Word -> Word -> Word -> Word -> IPv4+fromOctets' a b c d =+ IPv4 $+ fromIntegral+ ( shiftL a 24+ .|. shiftL b 16+ .|. shiftL c 8+ .|. d+ )++p24 :: Word32+p24 = getIPv4 (fromOctets' 10 0 0 0)++p20 :: Word32+p20 = getIPv4 (fromOctets' 172 16 0 0)++p16 :: Word32+p16 = getIPv4 (fromOctets' 192 168 0 0)++{- | This does not do an endOfInput check because it is+reused in the range parser implementation.+-}+dotDecimalParser :: AT.Parser IPv4+dotDecimalParser =+ fromOctets'+ <$> (AT.decimal >>= limitSize)+ <* AT.char '.'+ <*> (AT.decimal >>= limitSize)+ <* AT.char '.'+ <*> (AT.decimal >>= limitSize)+ <* AT.char '.'+ <*> (AT.decimal >>= limitSize)+ where+ limitSize i =+ if i > 255+ then fail ipOctetSizeErrorMsg+ else return i++ipOctetSizeErrorMsg :: String+ipOctetSizeErrorMsg = "All octets in an IPv4 address must be between 0 and 255"++toDotDecimalText :: IPv4 -> Text+toDotDecimalText = toTextPreAllocated++toDotDecimalBuilder :: IPv4 -> TBuilder.Builder+toDotDecimalBuilder = TBuilder.fromText . toTextPreAllocated++{- | I think that this function can be improved. Right now, it+ always allocates enough space for a fifteen-character text+ rendering of an IP address. I think that it should be possible+ to do more of the math upfront and allocate less space.+-}+toTextPreAllocated :: IPv4 -> Text+toTextPreAllocated (IPv4 w) =+ let w1 = 255 .&. unsafeShiftR (fromIntegral w) 24+ w2 = 255 .&. unsafeShiftR (fromIntegral w) 16+ w3 = 255 .&. unsafeShiftR (fromIntegral w) 8+ w4 = 255 .&. fromIntegral w+ in toTextPreallocatedPartTwo w1 w2 w3 w4++{- FOURMOLU_DISABLE -}+toTextPreallocatedPartTwo :: Word -> Word -> Word -> Word -> Text+toTextPreallocatedPartTwo !w1 !w2 !w3 !w4 =+#ifdef ghcjs_HOST_OS+ let dotStr = "."+ in Text.pack $ concat+ [ show w1+ , "."+ , show w2+ , "."+ , show w3+ , "."+ , show w4+ ]+#else+ let dot = 46+ (arr,len) = runST $ do+ marr <- TArray.new 15+ i1 <- putAndCount 0 w1 marr+ let n1 = i1+ n1' = i1 + 1+ TArray.unsafeWrite marr n1 dot+ i2 <- putAndCount n1' w2 marr+ let n2 = i2 + n1'+ n2' = n2 + 1+ TArray.unsafeWrite marr n2 dot+ i3 <- putAndCount n2' w3 marr+ let n3 = i3 + n2'+ n3' = n3 + 1+ TArray.unsafeWrite marr n3 dot+ i4 <- putAndCount n3' w4 marr+ theArr <- TArray.unsafeFreeze marr+ return (theArr,i4 + n3')+ in Text arr 0 len+#endif+{- FOURMOLU_ENABLE -}++twoDigits :: ByteString+twoDigits = foldMap (BC8.pack . printf "%02d") $ enumFromTo (0 :: Int) 99+{-# NOINLINE twoDigits #-}++threeDigits :: ByteString+threeDigits = foldMap (BC8.pack . printf "%03d") $ enumFromTo (0 :: Int) 999+{-# NOINLINE threeDigits #-}++i2w :: (Integral a) => a -> Codepoint+i2w v = zero + fromIntegral v++zero :: Codepoint+zero = 48++putAndCount :: Int -> Word -> TArray.MArray s -> ST s Int+putAndCount pos w marr+ | w < 10 = TArray.unsafeWrite marr pos (i2w w) >> return 1+ | w < 100 = write2 pos w >> return 2+ | otherwise = write3 pos w >> return 3+ where+ write2 off i0 = do+ let i = fromIntegral i0; j = i + i+ TArray.unsafeWrite marr off $ get2 j+ TArray.unsafeWrite marr (off + 1) $ get2 (j + 1)+ write3 off i0 = do+ let i = fromIntegral i0; j = i + i + i+ TArray.unsafeWrite marr off $ get3 j+ TArray.unsafeWrite marr (off + 1) $ get3 (j + 1)+ TArray.unsafeWrite marr (off + 2) $ get3 (j + 2)+ get2 = fromIntegral . ByteString.unsafeIndex twoDigits+ get3 = fromIntegral . ByteString.unsafeIndex threeDigits++rightToMaybe :: Either a b -> Maybe b+rightToMaybe = either (const Nothing) Just++{- $interoperability++The @<http://hackage.haskell.org/package/network network>@ library is commonly+used to open sockets and communicate over them. In the @Network.Socket@ module,+it provides a type synonym @HostAddress@ that, like 'IPv4', is used+to represent an IPv4 address. However, while 'IPv4' uses a big-endian representation+for ip addresses, @HostAddress@ has platform dependent endianness.+Consequently, it is necessary to convert between the two as follows:++> import Network.Socket (HostAddress,htonl,ntohl)+>+> toHostAddr :: IPv4 -> HostAddress+> toHostAddr (IPv4 w) = htonl w+>+> fromHostAddr :: HostAddress -> IPv4+> fromHostAddr w = IPv4 (ntohl w)++These functions are not included with this library since it would require+picking up a dependency on @network@.+-}++{- $setup++These are here to get doctest's property checking to work.++>>> import qualified Prelude as P+>>> import qualified Data.Text.IO as T+>>> import Net.IPv4 (fromOctets,ipv4)+>>> import Test.QuickCheck (Arbitrary(..))+>>> instance Arbitrary IPv4 where { arbitrary = fmap IPv4 arbitrary }+>>> instance Arbitrary IPv4Range where { arbitrary = IPv4Range <$> arbitrary <*> arbitrary }+-}++{- | Smart constructor for 'IPv4Range'. Ensures the mask is appropriately+ sized and sets masked bits in the 'IPv4' to zero.+-}+range :: IPv4 -> Word8 -> IPv4Range+range addr len = normalize (IPv4Range addr len)++{- | Given an inclusive lower and upper ip address, create the smallest+'IPv4Range' that contains the two. This is helpful in situations where+input given as a range like @192.168.16.0-192.168.19.255@ needs to be+handled. This makes the range broader if it cannot be represented in+CIDR notation.++>>> IPv4.printRange $ IPv4.fromBounds (IPv4.fromOctets 192 168 16 0) (IPv4.fromOctets 192 168 19 255)+192.168.16.0/22+>>> IPv4.printRange $ IPv4.fromBounds (IPv4.fromOctets 10 0 5 7) (IPv4.fromOctets 10 0 5 14)+10.0.5.0/28+-}+fromBounds :: IPv4 -> IPv4 -> IPv4Range+fromBounds (IPv4 a) (IPv4 b) =+ normalize (IPv4Range (IPv4 a) (maskFromBounds a b))++maskFromBounds :: Word32 -> Word32 -> Word8+maskFromBounds lo hi = fromIntegral (Bits.countLeadingZeros (Bits.xor lo hi))++{- | Checks to see if an 'IPv4' address belongs in the 'IPv4Range'.++>>> let ip = IPv4.fromOctets 10 10 1 92+>>> IPv4.contains (IPv4.IPv4Range (IPv4.fromOctets 10 0 0 0) 8) ip+True+>>> IPv4.contains (IPv4.IPv4Range (IPv4.fromOctets 10 11 0 0) 16) ip+False++Typically, element-testing functions are written to take the element+as the first argument and the set as the second argument. This is intentionally+written the other way for better performance when iterating over a collection.+For example, you might test elements in a list for membership like this:++>>> let r = IPv4.IPv4Range (IPv4.fromOctets 10 10 10 6) 31+>>> mapM_ (P.print . IPv4.contains r) (take 5 $ iterate succ $ IPv4.fromOctets 10 10 10 5)+False+True+True+False+False++The implementation of 'contains' ensures that (with GHC), the bitmask+creation and range normalization only occur once in the above example.+They are reused as the list is iterated.+-}+contains :: IPv4Range -> IPv4 -> Bool+contains (IPv4Range (IPv4 wsubnet) len) =+ let theMask = mask len+ wsubnetNormalized = wsubnet .&. theMask+ in \(IPv4 w) -> (w .&. theMask) == wsubnetNormalized++{- | Checks if the first range is a subset of the second range.++>>> IPv4.isSubsetOf (IPv4.IPv4Range (IPv4.fromOctets 192 0 2 128) 25) (IPv4.IPv4Range (IPv4.fromOctets 192 0 2 0) 24)+True+>>> IPv4.isSubsetOf (IPv4.IPv4Range (IPv4.fromOctets 192 0 2 0) 30) (IPv4.IPv4Range (IPv4.fromOctets 192 0 2 4) 30)+False+-}+isSubsetOf :: IPv4Range -> IPv4Range -> Bool+isSubsetOf a b =+ lowerInclusive a >= lowerInclusive b+ && upperInclusive a <= upperInclusive b++mask :: Word8 -> Word32+mask = complement . shiftR 0xffffffff . fromIntegral++{- | This is provided to mirror the interface provided by @Data.Set@. It+behaves just like 'contains' but with flipped arguments.++prop> IPv4.member ip r == IPv4.contains r ip+-}+member :: IPv4 -> IPv4Range -> Bool+member = flip contains++{- | The inclusive lower bound of an 'IPv4Range'. This is conventionally+ understood to be the broadcast address of a subnet. For example:++>>> T.putStrLn $ IPv4.encode $ IPv4.lowerInclusive $ IPv4.IPv4Range (IPv4.ipv4 10 10 1 160) 25+10.10.1.128++Note that the lower bound of a normalized 'IPv4Range' is simply the+ip address of the range:++prop> IPv4.lowerInclusive r == IPv4.ipv4RangeBase (IPv4.normalize r)+-}+lowerInclusive :: IPv4Range -> IPv4+lowerInclusive (IPv4Range (IPv4 w) len) =+ IPv4 (w .&. mask len)++{- | The inclusive upper bound of an 'IPv4Range'.++ >>> T.putStrLn $ IPv4.encode $ IPv4.upperInclusive $ IPv4.IPv4Range (IPv4.ipv4 10 10 1 160) 25+ 10.10.1.255+-}+upperInclusive :: IPv4Range -> IPv4+upperInclusive (IPv4Range (IPv4 w) len) =+ let theInvertedMask = shiftR 0xffffffff (fromIntegral len)+ theMask = complement theInvertedMask+ in IPv4 ((w .&. theMask) .|. theInvertedMask)++-- Given the size of the mask, return the total number of ips in the subnet. This+-- only works for IPv4 addresses because an IPv6 subnet can have up to 2^128+-- addresses. Not exported.+countAddrs :: Word8 -> Word64+countAddrs w =+ let amountToShift =+ if w > 32+ then 0+ else 32 - fromIntegral w+ in shift 1 amountToShift++wordSuccessors :: Word64 -> IPv4 -> [IPv4]+wordSuccessors !w (IPv4 !a) =+ if w > 0+ then IPv4 a : wordSuccessors (w - 1) (IPv4 (a + 1))+ else []++wordSuccessorsM :: (MonadPlus m) => Word64 -> IPv4 -> m IPv4+wordSuccessorsM = go+ where+ go !w (IPv4 !a) =+ if w > 0+ then mplus (return (IPv4 a)) (go (w - 1) (IPv4 (a + 1)))+ else mzero++{- | Convert an 'IPv4Range' into a list of the 'IPv4' addresses that+ are in it.++>>> let r = IPv4.IPv4Range (IPv4.fromOctets 192 168 1 8) 30+>>> mapM_ (T.putStrLn . IPv4.encode) (IPv4.toList r)+192.168.1.8+192.168.1.9+192.168.1.10+192.168.1.11+-}+toList :: IPv4Range -> [IPv4]+toList (IPv4Range ip len) =+ let totalAddrs = countAddrs len+ in wordSuccessors totalAddrs ip++{- | A stream-polymorphic generator over an 'IPv4Range'.+ For more information, see <http://www.haskellforall.com/2014/11/how-to-build-library-agnostic-streaming.html How to build library-agnostic streaming sources>.+-}+toGenerator :: (MonadPlus m) => IPv4Range -> m IPv4+toGenerator (IPv4Range ip len) =+ let totalAddrs = countAddrs len+ in wordSuccessorsM totalAddrs ip++-- | The RFC1918 24-bit block. Subnet mask: @10.0.0.0/8@+private24 :: IPv4Range+private24 = IPv4Range (fromOctets 10 0 0 0) 8++-- | The RFC1918 20-bit block. Subnet mask: @172.16.0.0/12@+private20 :: IPv4Range+private20 = IPv4Range (fromOctets 172 16 0 0) 12++-- | The RFC1918 16-bit block. Subnet mask: @192.168.0.0/16@+private16 :: IPv4Range+private16 = IPv4Range (fromOctets 192 168 0 0) 16++{- | Normalize an 'IPv4Range'. The first result of this is that the+'IPv4' inside the 'IPv4Range' is changed so that the insignificant+bits are zeroed out. For example:++>>> IPv4.printRange $ IPv4.normalize $ IPv4.IPv4Range (IPv4.fromOctets 192 168 1 19) 24+192.168.1.0/24+>>> IPv4.printRange $ IPv4.normalize $ IPv4.IPv4Range (IPv4.fromOctets 192 168 1 163) 28+192.168.1.160/28++The second effect of this is that the mask length is lowered to+be 32 or smaller. Working with 'IPv4Range's that have not been+normalized does not cause any issues for this library, although+other applications may reject such ranges (especially those with+a mask length above 32).++Note that 'normalize' is idempotent, that is:++prop> IPv4.normalize r == (IPv4.normalize . IPv4.normalize) r+-}+normalize :: IPv4Range -> IPv4Range+normalize (IPv4Range (IPv4 w) len) =+ let len' = min len 32+ w' = w .&. mask len'+ in IPv4Range (IPv4 w') len'++{- | Encode an 'IPv4Range' as 'Text'.++ >>> IPv4.encodeRange (IPv4.IPv4Range (IPv4.ipv4 172 16 0 0) 12)+ "172.16.0.0/12"+-}+encodeRange :: IPv4Range -> Text+encodeRange = rangeToDotDecimalText++{- | Decode an 'IPv4Range' from 'Text'.++ >>> IPv4.decodeRange "172.16.0.0/12"+ Just (IPv4Range {ipv4RangeBase = ipv4 172 16 0 0, ipv4RangeLength = 12})+ >>> IPv4.decodeRange "192.168.25.254/16"+ Just (IPv4Range {ipv4RangeBase = ipv4 192 168 0 0, ipv4RangeLength = 16})+-}+decodeRange :: Text -> Maybe IPv4Range+decodeRange = rightToMaybe . AT.parseOnly (parserRange <* AT.endOfInput)++{- | Encode an 'IPv4Range' to a 'TBuilder.Builder'.++ >>> IPv4.builderRange (IPv4.IPv4Range (IPv4.ipv4 172 16 0 0) 12)+ "172.16.0.0/12"+-}+builderRange :: IPv4Range -> TBuilder.Builder+builderRange = rangeToDotDecimalBuilder++{- | Parse an 'IPv4Range' using a 'AT.Parser'.++ >>> AT.parseOnly IPv4.parserRange "192.168.25.254/16"+ Right (IPv4Range {ipv4RangeBase = ipv4 192 168 0 0, ipv4RangeLength = 16})+-}+parserRange :: AT.Parser IPv4Range+parserRange = do+ ip <- parser+ _ <- AT.char '/'+ theMask <- AT.decimal >>= limitSize+ return (normalize (IPv4Range ip theMask))+ where+ limitSize i =+ if i > 32+ then fail "An IP range length must be between 0 and 32"+ else return i++{- | Print an 'IPv4Range'. Helper function that+ exists mostly for testing purposes.+-}+printRange :: IPv4Range -> IO ()+printRange = TIO.putStrLn . encodeRange++{- | The length should be between 0 and 32. These bounds are inclusive.+ This expectation is not in any way enforced by this library because+ it does not cause errors. A mask length greater than 32 will be+ treated as if it were 32.+-}+data IPv4Range = IPv4Range+ { ipv4RangeBase :: {-# UNPACK #-} !IPv4+ , ipv4RangeLength :: {-# UNPACK #-} !Word8+ }+ deriving (Eq, Ord, Show, Read, Generic, Data)++instance NFData IPv4Range+instance Hashable IPv4Range++instance ToJSON IPv4Range where+ toJSON = Aeson.String . encodeRange++instance FromJSON IPv4Range where+ parseJSON (Aeson.String t) = case decodeRange t of+ Nothing -> fail "Could not decodeRange IPv4 range"+ Just res -> return res+ parseJSON _ = mzero++data instance MUVector.MVector s IPv4Range+ = MV_IPv4Range+ !(MUVector.MVector s IPv4)+ !(MUVector.MVector s Word8)+data instance UVector.Vector IPv4Range+ = V_IPv4Range+ !(UVector.Vector IPv4)+ !(UVector.Vector Word8)++instance UVector.Unbox IPv4Range+instance MGVector.MVector MUVector.MVector IPv4Range where+ {-# INLINE basicLength #-}+ basicLength (MV_IPv4Range as _) = MGVector.basicLength as+ {-# INLINE basicUnsafeSlice #-}+ basicUnsafeSlice i_ m_ (MV_IPv4Range as bs) =+ MV_IPv4Range+ (MGVector.basicUnsafeSlice i_ m_ as)+ (MGVector.basicUnsafeSlice i_ m_ bs)+ {-# INLINE basicOverlaps #-}+ basicOverlaps (MV_IPv4Range as1 bs1) (MV_IPv4Range as2 bs2) =+ MGVector.basicOverlaps as1 as2+ || MGVector.basicOverlaps bs1 bs2+ {-# INLINE basicUnsafeNew #-}+ basicUnsafeNew n_ =+ do+ as <- MGVector.basicUnsafeNew n_+ bs <- MGVector.basicUnsafeNew n_+ return $ MV_IPv4Range as bs+ {-# INLINE basicInitialize #-}+ basicInitialize (MV_IPv4Range as bs) =+ do+ MGVector.basicInitialize as+ MGVector.basicInitialize bs+ {-# INLINE basicUnsafeReplicate #-}+ basicUnsafeReplicate n_ (IPv4Range a b) =+ do+ as <- MGVector.basicUnsafeReplicate n_ a+ bs <- MGVector.basicUnsafeReplicate n_ b+ return (MV_IPv4Range as bs)+ {-# INLINE basicUnsafeRead #-}+ basicUnsafeRead (MV_IPv4Range as bs) i_ =+ do+ a <- MGVector.basicUnsafeRead as i_+ b <- MGVector.basicUnsafeRead bs i_+ return (IPv4Range a b)+ {-# INLINE basicUnsafeWrite #-}+ basicUnsafeWrite (MV_IPv4Range as bs) i_ (IPv4Range a b) =+ do+ MGVector.basicUnsafeWrite as i_ a+ MGVector.basicUnsafeWrite bs i_ b+ {-# INLINE basicClear #-}+ basicClear (MV_IPv4Range as bs) =+ do+ MGVector.basicClear as+ MGVector.basicClear bs+ {-# INLINE basicSet #-}+ basicSet (MV_IPv4Range as bs) (IPv4Range a b) =+ do+ MGVector.basicSet as a+ MGVector.basicSet bs b+ {-# INLINE basicUnsafeCopy #-}+ basicUnsafeCopy (MV_IPv4Range as1 bs1) (MV_IPv4Range as2 bs2) =+ do+ MGVector.basicUnsafeCopy as1 as2+ MGVector.basicUnsafeCopy bs1 bs2+ {-# INLINE basicUnsafeMove #-}+ basicUnsafeMove (MV_IPv4Range as1 bs1) (MV_IPv4Range as2 bs2) =+ do+ MGVector.basicUnsafeMove as1 as2+ MGVector.basicUnsafeMove bs1 bs2+ {-# INLINE basicUnsafeGrow #-}+ basicUnsafeGrow (MV_IPv4Range as bs) m_ =+ do+ as' <- MGVector.basicUnsafeGrow as m_+ bs' <- MGVector.basicUnsafeGrow bs m_+ return $ MV_IPv4Range as' bs'++instance GVector.Vector UVector.Vector IPv4Range where+ {-# INLINE basicUnsafeFreeze #-}+ basicUnsafeFreeze (MV_IPv4Range as bs) =+ do+ as' <- GVector.basicUnsafeFreeze as+ bs' <- GVector.basicUnsafeFreeze bs+ return $ V_IPv4Range as' bs'+ {-# INLINE basicUnsafeThaw #-}+ basicUnsafeThaw (V_IPv4Range as bs) =+ do+ as' <- GVector.basicUnsafeThaw as+ bs' <- GVector.basicUnsafeThaw bs+ return $ MV_IPv4Range as' bs'+ {-# INLINE basicLength #-}+ basicLength (V_IPv4Range as _) = GVector.basicLength as+ {-# INLINE basicUnsafeSlice #-}+ basicUnsafeSlice i_ m_ (V_IPv4Range as bs) =+ V_IPv4Range+ (GVector.basicUnsafeSlice i_ m_ as)+ (GVector.basicUnsafeSlice i_ m_ bs)+ {-# INLINE basicUnsafeIndexM #-}+ basicUnsafeIndexM (V_IPv4Range as bs) i_ =+ do+ a <- GVector.basicUnsafeIndexM as i_+ b <- GVector.basicUnsafeIndexM bs i_+ return (IPv4Range a b)+ {-# INLINE basicUnsafeCopy #-}+ basicUnsafeCopy (MV_IPv4Range as1 bs1) (V_IPv4Range as2 bs2) =+ do+ GVector.basicUnsafeCopy as1 as2+ GVector.basicUnsafeCopy bs1 bs2+ {-# INLINE elemseq #-}+ elemseq _ (IPv4Range a b) =+ GVector.elemseq (undefined :: UVector.Vector a) a+ . GVector.elemseq (undefined :: UVector.Vector b) b++-----------------+-- Internal Stuff+-----------------++rangeToDotDecimalText :: IPv4Range -> Text+rangeToDotDecimalText = LText.toStrict . TBuilder.toLazyText . rangeToDotDecimalBuilder++rangeToDotDecimalBuilder :: IPv4Range -> TBuilder.Builder+rangeToDotDecimalBuilder (IPv4Range addr len) =+ builder addr+ <> TBuilder.singleton '/'+ <> TBI.decimal len
src/Net/IPv6.hs view
@@ -8,963 +8,1071 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeInType #-}-{-# LANGUAGE UnboxedTuples #-}--{-| This module provides the IPv6 data type and functions for working- with it.--}-module Net.IPv6- ( -- * Convert- ipv6- , fromOctets- , fromWord16s- , fromWord32s- , fromTupleWord16s- , fromTupleWord32s- , toWord16s- , toWord32s- -- * Special IP Addresses- , any- , loopback- , localhost- -- * Textual Conversion- -- ** Text- , encode- , encodeShort- , decode- , decodeShort- , parser- -- * UTF-8 Bytes- , parserUtf8Bytes- , decodeUtf8Bytes- , boundedBuilderUtf8- -- ** Printing- , print- -- * IPv6 Ranges- -- ** Range functions- , range- , fromBounds- , normalize- , contains- , isSubsetOf- , member- , lowerInclusive- , upperInclusive- -- ** Textual Conversion- -- *** Text- , encodeRange- , decodeRange- , parserRange- , printRange- -- ** UTF-8 Bytes- , parserRangeUtf8Bytes- , parserRangeUtf8BytesLenient- -- * Types- , IPv6(..)- , IPv6Range(..)- ) where--import Prelude hiding (any, print)--import Net.IPv4 (IPv4(..))--import Control.Applicative-import Control.DeepSeq (NFData)-import Control.Monad (mzero)-import Control.Monad.ST (ST)-import Data.Bits-import Data.Char (chr)-import Data.Data (Data)-import Data.Ix (Ix)-import Data.Hashable (Hashable,hashWithSalt)-import Data.List (intercalate, group)-import Data.Primitive (MutablePrimArray)-import Data.Primitive.Types (Prim)-import Data.Text (Text)-import Data.Text.Short (ShortText)-import Data.WideWord.Word128 (Word128(..), zeroWord128)-import Data.Word-import Foreign.Storable (Storable)-import GHC.Exts (Int#,Word#,Int(I#))-import GHC.Generics (Generic)-import Numeric (showHex)-import Text.ParserCombinators.ReadPrec (prec,step)-import Text.Read (Read(..),Lexeme(Ident),lexP,parens)--import qualified Arithmetic.Lte as Lte-import qualified Arithmetic.Nat as Nat-import qualified Data.Aeson as Aeson-import qualified Data.Attoparsec.Text as AT-import qualified Data.Attoparsec.Text as Atto-import qualified Data.Bytes.Builder.Bounded as BB-import qualified Data.Bytes as Bytes-import qualified Data.Bytes.Parser as Parser-import qualified Data.Bytes.Parser.Latin as Latin-import qualified Data.ByteString.Short.Internal as BSS-import qualified Data.Primitive as PM-import qualified Data.Text as Text-import qualified Data.Text.IO as TIO-import qualified Data.Text.Short.Unsafe as TS-import qualified Data.Text.Short as TS-import qualified GHC.Word.Compat as Compat-import qualified Net.IPv4 as IPv4---- $setup------ These are here to get doctest work.------ >>> import qualified Prelude as P--- >>> import qualified Data.Text.IO as T--- >>> import qualified Data.Text as Text--- >>> import qualified Data.Attoparsec.Text as Atto--- >>> import qualified Data.Bytes.Text.Ascii as Ascii--- >>> import Test.QuickCheck (Arbitrary(..))--- >>> instance Arbitrary Word128 where { arbitrary = Word128 <$> arbitrary <*> arbitrary }--- >>> instance Arbitrary IPv6 where { arbitrary = IPv6 <$> arbitrary }--- >>> instance Arbitrary IPv6.IPv6Range where { arbitrary = IPv6.IPv6Range <$> arbitrary <*> arbitrary }------- | A 128-bit Internet Protocol version 6 address.-newtype IPv6 = IPv6 { getIPv6 :: Word128 }- deriving (Bounded,Enum,Eq,Ord,Storable,Bits,FiniteBits,NFData,Prim,Ix,Data,Generic)--instance Hashable IPv6 where- hashWithSalt s (IPv6 (Word128 a b)) = hashWithSalt (hashWithSalt s a) b--instance Show IPv6 where- showsPrec p addr = showParen (p > 10)- $ showString "ipv6 "- . showHexWord16 a- . showChar ' '- . showHexWord16 b- . showChar ' '- . showHexWord16 c- . showChar ' '- . showHexWord16 d- . showChar ' '- . showHexWord16 e- . showChar ' '- . showHexWord16 f- . showChar ' '- . showHexWord16 g- . showChar ' '- . showHexWord16 h- where- (a,b,c,d,e,f,g,h) = toWord16s addr---- | Print an 'IPv6' using the textual encoding.-print :: IPv6 -> IO ()-print = TIO.putStrLn . encode---- | Decode 'ShortText' as an 'IPv6' address.------ >>> decodeShort "ffff::2:b"--- Just (ipv6 0xffff 0x0000 0x0000 0x0000 0x0000 0x0000 0x0002 0x000b)-decodeShort :: ShortText -> Maybe IPv6-decodeShort t = decodeUtf8Bytes (Bytes.fromByteArray b)- where b = shortByteStringToByteArray (TS.toShortByteString t)--shortByteStringToByteArray :: BSS.ShortByteString -> PM.ByteArray-shortByteStringToByteArray (BSS.SBS x) = PM.ByteArray x--showHexWord16 :: Word16 -> ShowS-showHexWord16 w =- showString "0x"- . showChar (nibbleToHex (unsafeShiftR (fromIntegral w) 12))- . showChar (nibbleToHex ((unsafeShiftR (fromIntegral w) 8) .&. 0xF))- . showChar (nibbleToHex ((unsafeShiftR (fromIntegral w) 4) .&. 0xF))- . showChar (nibbleToHex ((fromIntegral w) .&. 0xF))---- invariant: argument must be less than 16-nibbleToHex :: Word -> Char-nibbleToHex w- | w < 10 = chr (fromIntegral (w + 48))- | otherwise = chr (fromIntegral (w + 87))--instance Read IPv6 where- readPrec = parens $ prec 10 $ do- Ident "ipv6" <- lexP- a <- step readPrec- b <- step readPrec- c <- step readPrec- d <- step readPrec- e <- step readPrec- f <- step readPrec- g <- step readPrec- h <- step readPrec- return (fromWord16s a b c d e f g h)--instance Aeson.ToJSON IPv6 where- toJSON = Aeson.String . encode--instance Aeson.FromJSON IPv6 where- parseJSON = Aeson.withText "IPv6" $ \t -> case decode t of- Nothing -> fail "invalid IPv6 address"- Just i -> return i--rightToMaybe :: Either a b -> Maybe b-rightToMaybe = either (const Nothing) Just---- | This could be useful for the rare occasion--- in which one could construct an 'IPv6' from--- octets.------ Note that while @Net.IPv4.'Net.IPv4.fromOctets' = Net.IPv4.'Net.IPv4.ipv4'@,--- @Net.IPv6.fromOctets /= Net.IPv6.ipv6@. While this should be obvious--- from their types, it is worth mentioning since the similarity in naming--- might be confusing.-fromOctets ::- Word8 -> Word8 -> Word8 -> Word8- -> Word8 -> Word8 -> Word8 -> Word8- -> Word8 -> Word8 -> Word8 -> Word8- -> Word8 -> Word8 -> Word8 -> Word8- -> IPv6-fromOctets a b c d e f g h i j k l m n o p =- IPv6 $ fromOctetsWord128- (fromIntegral a) (fromIntegral b) (fromIntegral c) (fromIntegral d)- (fromIntegral e) (fromIntegral f) (fromIntegral g) (fromIntegral h)- (fromIntegral i) (fromIntegral j) (fromIntegral k) (fromIntegral l)- (fromIntegral m) (fromIntegral n) (fromIntegral o) (fromIntegral p)--fromOctetsWord128 ::- Word128 -> Word128 -> Word128 -> Word128- -> Word128 -> Word128 -> Word128 -> Word128- -> Word128 -> Word128 -> Word128 -> Word128- -> Word128 -> Word128 -> Word128 -> Word128- -> Word128-fromOctetsWord128 a b c d e f g h i j k l m n o p = fromIntegral- ( shiftL a 120- .|. shiftL b 112- .|. shiftL c 104- .|. shiftL d 96- .|. shiftL e 88- .|. shiftL f 80- .|. shiftL g 72- .|. shiftL h 64- .|. shiftL i 56- .|. shiftL j 48- .|. shiftL k 40- .|. shiftL l 32- .|. shiftL m 24- .|. shiftL n 16- .|. shiftL o 8- .|. p- )---- | Create an 'IPv6' address from the eight 16-bit fragments that make--- it up. This closely resembles the standard IPv6 notation, so--- is used for the 'Show' instance. Note that this lacks the formatting--- feature for suppress zeroes in an 'IPv6' address, but it should be--- readable enough for hacking in GHCi.------ >>> let addr = ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1--- >>> addr--- ipv6 0x3124 0x0000 0x0000 0xdead 0xcafe 0x00ff 0xfe00 0x0001--- >>> T.putStrLn (encode addr)--- 3124::dead:cafe:ff:fe00:1-ipv6 ::- Word16 -> Word16 -> Word16 -> Word16- -> Word16 -> Word16 -> Word16 -> Word16- -> IPv6-ipv6 = fromWord16s---- | An alias for the 'ipv6' smart constructor.-fromWord16s ::- Word16 -> Word16 -> Word16 -> Word16- -> Word16 -> Word16 -> Word16 -> Word16- -> IPv6-fromWord16s a b c d e f g h =- IPv6 $ fromWord16sWord128- (fromIntegral a) (fromIntegral b) (fromIntegral c) (fromIntegral d)- (fromIntegral e) (fromIntegral f) (fromIntegral g) (fromIntegral h)--fromWord16sWord128 ::- Word128 -> Word128 -> Word128 -> Word128- -> Word128 -> Word128 -> Word128 -> Word128- -> Word128-fromWord16sWord128 a b c d e f g h = fromIntegral- ( shiftL a 112- .|. shiftL b 96- .|. shiftL c 80- .|. shiftL d 64- .|. shiftL e 48- .|. shiftL f 32- .|. shiftL g 16- .|. h- )---- | Convert an 'IPv6' to eight 16-bit words.-toWord16s :: IPv6 -> (Word16,Word16,Word16,Word16,Word16,Word16,Word16,Word16)-toWord16s (IPv6 (Word128 a b)) =- -- Note: implementing this as 2 Word64 shifts with 'unsafeShiftR'- -- is up to 40% faster than using 128-bit shifts on a Word128 value.- ( fromIntegral (unsafeShiftR a 48)- , fromIntegral (unsafeShiftR a 32)- , fromIntegral (unsafeShiftR a 16)- , fromIntegral a- , fromIntegral (unsafeShiftR b 48)- , fromIntegral (unsafeShiftR b 32)- , fromIntegral (unsafeShiftR b 16)- , fromIntegral b- )---- | Uncurried variant of 'fromWord16s'.-fromTupleWord16s :: (Word16,Word16,Word16,Word16,Word16,Word16,Word16,Word16) -> IPv6-fromTupleWord16s (a,b,c,d,e,f,g,h) = fromWord16s a b c d e f g h---- | Build an 'IPv6' from four 32-bit words. The leftmost argument--- is the high word and the rightword is the low word.-fromWord32s :: Word32 -> Word32 -> Word32 -> Word32 -> IPv6-fromWord32s a b c d =- IPv6 $ fromWord32sWord128- (fromIntegral a) (fromIntegral b) (fromIntegral c) (fromIntegral d)--fromWord32sWord128 ::- Word128 -> Word128 -> Word128 -> Word128- -> Word128-fromWord32sWord128 a b c d = fromIntegral- ( shiftL a 96- .|. shiftL b 64- .|. shiftL c 32- .|. d- )---- | Uncurried variant of 'fromWord32s'.-fromTupleWord32s :: (Word32,Word32,Word32,Word32) -> IPv6-fromTupleWord32s (a,b,c,d) = fromWord32s a b c d---- | Convert an 'IPv6' to four 32-bit words.-toWord32s :: IPv6 -> (Word32,Word32,Word32,Word32)-toWord32s (IPv6 (Word128 a b)) =- -- Note: implementing this as 2 Word64 shifts with 'unsafeShiftR'- -- is about 10% faster than using 128-bit shifts on a Word128 value.- ( fromIntegral (unsafeShiftR a 32)- , fromIntegral a- , fromIntegral (unsafeShiftR b 32)- , fromIntegral b- )---- | The local loopback IP address.------ >>> IPv6.loopback--- ipv6 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0001-loopback :: IPv6-loopback = IPv6 (Word128 0 1)---- | A useful alias for 'loopback'.------ >>> IPv6.localhost--- ipv6 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0001-localhost :: IPv6-localhost = loopback---- | The IP address representing any host.------ >>> IPv6.any--- ipv6 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000-any :: IPv6-any = IPv6 zeroWord128---- | Encodes the 'IPv6' address using zero-compression on the leftmost longest--- string of zeroes in the address.--- Per <https://tools.ietf.org/html/rfc5952#section-5 RFC 5952 Section 5>,--- this uses mixed notation when encoding an IPv4-mapped IPv6 address:------ >>> T.putStrLn $ IPv6.encode $ IPv6.fromWord16s 0xDEAD 0xBEEF 0x0 0x0 0x0 0x0 0x0 0x1234--- dead:beef::1234--- >>> T.putStrLn $ IPv6.encode $ IPv6.fromWord16s 0x0 0x0 0x0 0x0 0x0 0xFFFF 0x6437 0xA5B4--- ::ffff:100.55.165.180--- >>> T.putStrLn $ IPv6.encode $ IPv6.fromWord16s 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0--- ::------ Per <https://tools.ietf.org/html/rfc5952#section-4.2.2 Section 4.2.2> of the--- same RFC, this does not use @::@ to shorten a single 16-bit 0 field. Only--- runs of multiple 0 fields are considered.-encode :: IPv6 -> Text-encode !ip =- -- TODO: This implementation, while correct, is not particularly efficient.- -- It uses string all over the place.- if isIPv4Mapped ip- -- This representation is RECOMMENDED by https://tools.ietf.org/html/rfc5952#section-5- then- Text.pack "::ffff:"- `mappend`- IPv4.encode (IPv4.IPv4 (fromIntegral w7 `unsafeShiftL` 16 .|. fromIntegral w8))- else toText [w1, w2, w3, w4, w5, w6, w7, w8]- where- (w1, w2, w3, w4, w5, w6, w7, w8) = toWord16s ip- toText ws = Text.pack $ intercalate ":"- $ expand 0 (if longestZ > 1 then longestZ else 0) grouped- where- expand !_ 8 !_ = ["::"]- expand !_ !_ [] = []- expand !i !longest ((x, len):wsNext)- -- zero-compressed group:- | x == 0 && len == longest =- -- first and last need an extra colon since there's nothing- -- to concat against- (if i == 0 || (i+len) == 8 then ":" else "")- : expand (i+len) 0 wsNext- -- normal group:- | otherwise = replicate len (showHex x "") ++ expand (i+len) longest wsNext- longestZ = maximum . (0:) . map snd . filter ((==0) . fst) $ grouped- grouped = map (\x -> (head x, length x)) (group ws)--isIPv4Mapped :: IPv6 -> Bool-isIPv4Mapped (IPv6 (Word128 w1 w2)) =- w1 == 0 && (0xFFFFFFFF00000000 .&. w2 == 0x0000FFFF00000000)---- | Decode UTF-8-encoded 'Bytes' into an 'IPv6' address.------ >>> decodeUtf8Bytes (Ascii.fromString "::cab:1")--- Just (ipv6 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0cab 0x0001)-decodeUtf8Bytes :: Bytes.Bytes -> Maybe IPv6-decodeUtf8Bytes !b = case Parser.parseBytes (parserUtf8Bytes ()) b of- Parser.Success (Parser.Slice _ len addr) -> case len of- 0 -> Just addr- _ -> Nothing- Parser.Failure _ -> Nothing---- | Encodes the 'IPv6' address using zero-compression on the--- leftmost longest string of zeroes in the address.------ >>> BB.run Nat.constant $ IPv6.boundedBuilderUtf8 $ IPv6.fromWord16s 0xDEAD 0xBEEF 0x0 0x0 0x0 0x0 0x0 0x1234--- [0x64, 0x65, 0x61, 0x64, 0x3a, 0x62, 0x65, 0x65, 0x66, 0x3a, 0x3a, 0x31, 0x32, 0x33, 0x34]-boundedBuilderUtf8 :: IPv6 -> BB.Builder 39-boundedBuilderUtf8 !ip@(IPv6 (Word128 hi lo))- | hi == 0 && lo == 0 = BB.weaken Lte.constant- (BB.ascii ':' `BB.append` BB.ascii ':')- | isIPv4Mapped ip = BB.weaken Lte.constant $- BB.ascii ':'- `BB.append`- BB.ascii ':'- `BB.append`- BB.ascii 'f'- `BB.append`- BB.ascii 'f'- `BB.append`- BB.ascii 'f'- `BB.append`- BB.ascii 'f'- `BB.append`- BB.ascii ':'- `BB.append`- IPv4.boundedBuilderUtf8 (IPv4.IPv4 (fromIntegral lo))- | otherwise =- let (w0,w1,w2,w3,w4,w5,w6,w7) = toWord16s ip- IntTriple startLongest longest _ = longestRun w0 w1 w2 w3 w4 w5 w6 w7- start = startLongest- end = start + longest- -- start is inclusive. end is exclusive- in firstPiece w0 start- `BB.append`- piece 1 w1 start end- `BB.append`- piece 2 w2 start end- `BB.append`- piece 3 w3 start end- `BB.append`- piece 4 w4 start end- `BB.append`- piece 5 w5 start end- `BB.append`- piece 6 w6 start end- `BB.append`- lastPiece w7 end--firstPiece :: Word16 -> Int -> BB.Builder 4-firstPiece !w !start = case start of- 0 -> BB.weaken Lte.constant (BB.ascii ':')- _ -> BB.word16LowerHex w---- Note about the implementation of piece:--- It is important to manually perform worker-wrapper so that--- we can stop piece from inlining. If we do not do this, GHC--- inlines piece, leading to enormous blowup in the generated--- Core. The implementation of boundedBuilderUtf8 becomes--- thousands of lines of Core. Even in the microbenchmark that--- comes with this library, it can be observed that preventing--- this inlining improves performance of encodeShort by 50%.-piece :: Int -> Word16 -> Int -> Int -> BB.Builder 5-{-# inline piece #-}-piece (I# ix) (Compat.W16# w) (I# start) (I# end) =- piece# ix w start end--piece# :: Int# -> Word# -> Int# -> Int# -> BB.Builder 5-{-# noinline piece# #-}-piece# !ix# !w# !start# !end# = case compare ix start of- LT -> BB.ascii ':' `BB.append` BB.word16LowerHex w- EQ -> BB.weaken Lte.constant (BB.ascii ':')- GT -> if ix < end- then BB.weaken Lte.constant BB.empty- else BB.ascii ':' `BB.append` BB.word16LowerHex w- where- ix = I# ix#- start = I# start#- end = I# end#- w = Compat.W16# w#--lastPiece :: Word16 -> Int -> BB.Builder 5-lastPiece !w !end = case end of- 8 -> BB.weaken Lte.constant (BB.ascii ':')- _ -> BB.ascii ':' `BB.append` BB.word16LowerHex w--data IntTriple = IntTriple !Int !Int !Int---- Choose the longest run. Prefer the leftmost run in the--- event of a tie.-stepZeroRunLength :: Int -> Word16 -> IntTriple -> IntTriple-stepZeroRunLength !ix !w (IntTriple startLongest longest current) = case w of- 0 -> let !x = current + 1 in- if x > longest- then IntTriple (ix - current) x x- else IntTriple startLongest longest x- _ -> IntTriple startLongest longest 0---- We start out by setting the longest run to size 1. This--- means that we will only detect runs of length two or greater.-longestRun ::- Word16- -> Word16- -> Word16- -> Word16- -> Word16- -> Word16- -> Word16- -> Word16- -> IntTriple-longestRun !w0 !w1 !w2 !w3 !w4 !w5 !w6 !w7 = id- $ stepZeroRunLength 7 w7- $ stepZeroRunLength 6 w6- $ stepZeroRunLength 5 w5- $ stepZeroRunLength 4 w4- $ stepZeroRunLength 3 w3- $ stepZeroRunLength 2 w2- $ stepZeroRunLength 1 w1- $ stepZeroRunLength 0 w0- $ IntTriple (-1) 1 0---- | Encodes the 'IPv6' address as 'ShortText' using zero-compression on--- the leftmost longest string of zeroes in the address.--- Per <https://tools.ietf.org/html/rfc5952#section-5 RFC 5952 Section 5>,--- this uses mixed notation when encoding an IPv4-mapped IPv6 address.--- --- >>> IPv6.encodeShort $ IPv6.fromWord16s 0xDEAD 0xBEEF 0x0 0x0 0x0 0x0ABC 0x0 0x1234--- "dead:beef::abc:0:1234"-encodeShort :: IPv6 -> ShortText-encodeShort w = id- $ TS.fromShortByteStringUnsafe- $ byteArrayToShortByteString- $ BB.run Nat.constant- $ boundedBuilderUtf8- $ w--byteArrayToShortByteString :: PM.ByteArray -> BSS.ShortByteString-byteArrayToShortByteString (PM.ByteArray x) = BSS.SBS x---- | Decode an 'IPv6' address. This accepts both standard IPv6--- notation (with zero compression) and mixed notation for--- IPv4-mapped IPv6 addresses. For a decoding function that--- additionally accepts dot-decimal-encoded IPv4 addresses,--- see @Net.IP.decode@.-decode :: Text -> Maybe IPv6-decode t = rightToMaybe (AT.parseOnly (parser <* AT.endOfInput) t)---- | Parse UTF-8-encoded 'Bytes' as an 'IPv6' address. This accepts--- both uppercase and lowercase characters in the hexadecimal components.------ >>> let str = "dead:beef:3240:a426:ba68:1cd0:4263:109b -> alive"--- >>> Parser.parseBytes (parserUtf8Bytes ()) (Ascii.fromString str)--- Success (Slice {offset = 39, length = 9, value = ipv6 0xdead 0xbeef 0x3240 0xa426 0xba68 0x1cd0 0x4263 0x109b})------ This does not currently support parsing embedded IPv4 address--- (e.g. @ff00:8000:abc::224.1.2.3@).-parserUtf8Bytes :: e -> Parser.Parser e s IPv6-parserUtf8Bytes e = do- marr <- Parser.effect (PM.newPrimArray 8)- -- We cannot immidiately call preZeroes since it wants a- -- leading colon present.- Latin.trySatisfy (== ':') >>= \case- True -> do- Latin.char e ':'- postZeroesBegin e marr 0 0- False -> do- w <- pieceParser e- Parser.effect (PM.writePrimArray marr 0 w)- preZeroes e marr 1---- This is called when we are positioned before a colon.--- We may encounter another colon immidiately after--- the one that we consume here. This indicates zero--- compression. Or we may encounter another hex-encoded--- number.-preZeroes ::- e- -> MutablePrimArray s Word16 -- length must be 8- -> Int- -> Parser.Parser e s IPv6-preZeroes e !marr !ix = case ix of- 8 -> Parser.effect (combinePieces marr)- _ -> do- Latin.char e ':'- Latin.trySatisfy (== ':') >>= \case- True -> postZeroesBegin e marr ix ix- False -> do- w <- pieceParser e- Parser.effect (PM.writePrimArray marr ix w)- preZeroes e marr (ix + 1)---- The same as postZeroes except that there is no--- leading that gets consumed. This is called right--- after a double colon is consumed.--- Precondition: the index is less than 8. This parser--- is only called by preZeroes, which ensures that--- this holds.-postZeroesBegin ::- e- -> MutablePrimArray s Word16 -- length must be 8- -> Int -- current index in array- -> Int -- index where compression happened- -> Parser.Parser e s IPv6-postZeroesBegin e !marr !ix !compress = do- optionalPieceParser e >>= \case- Nothing -> do -- the end has come- Parser.effect (conclude marr ix compress)- Just w -> do- Parser.effect (PM.writePrimArray marr ix w)- postZeroes e marr (ix + 1) compress---- Should be run right before a colon.-postZeroes :: - e- -> MutablePrimArray s Word16 -- length must be 8- -> Int -- current index in array- -> Int -- index where compression happened- -> Parser.Parser e s IPv6-postZeroes e !marr !ix !compress = case ix of- 8 -> Parser.fail e- _ -> do- Latin.trySatisfy (== ':') >>= \case- False -> -- The end has come- Parser.effect (conclude marr ix compress)- True -> do- w <- pieceParser e- Parser.effect (PM.writePrimArray marr ix w)- postZeroes e marr (ix + 1) compress--conclude :: MutablePrimArray s Word16 -> Int -> Int -> ST s IPv6-conclude !marr !ix !compress = do- -- This will overlap, but GHC's copy primop is fine with that.- let postCompressionLen = ix - compress- PM.copyMutablePrimArray marr (8 - postCompressionLen) marr compress postCompressionLen- let compressedArea = 8 - ix- PM.setPrimArray marr compress compressedArea (0 :: Word16)- combinePieces marr---- Example memmove that may need to happen:--- A B C H ==> A B C 0 0 0 0 H--- *--- ix = 4, compress = 3, postCompressionLen = 1, compressedArea = 4--- copyPrimArray marr 7 marr 3 1--- setPrimArray marr 3 4 (0 :: Word16)--combinePieces ::- MutablePrimArray s Word16- -> ST s IPv6-combinePieces !marr = fromWord16s- <$> PM.readPrimArray marr 0- <*> PM.readPrimArray marr 1- <*> PM.readPrimArray marr 2- <*> PM.readPrimArray marr 3- <*> PM.readPrimArray marr 4- <*> PM.readPrimArray marr 5- <*> PM.readPrimArray marr 6- <*> PM.readPrimArray marr 7--optionalPieceParser :: e -> Parser.Parser e s (Maybe Word16)-optionalPieceParser e = Latin.tryHexNibble >>= \case- Nothing -> pure Nothing- Just w0 -> do- r <- pieceParserStep e w0- pure (Just r)---- This should probably be moved into bytesmith and renamed.-pieceParser :: e -> Parser.Parser e s Word16-pieceParser e = Latin.hexNibble e >>= pieceParserStep e---- Parses the remainder of a lowercase hexadecimal number.--- Leaves trailing colons alone. This fails if there are--- more than four hex digits unless there are leading zeroes.--- I cannot find a spec that is clear about what to do--- if someone puts 00000 in a piece of an encoded IPv6--- address, so I veer on the side of leniency.-pieceParserStep ::- e- -> Word- -> Parser.Parser e s Word16-pieceParserStep e !acc = if acc > 0xFFFF- then Parser.fail e- else Latin.tryHexNibble >>= \case- Nothing -> pure (fromIntegral acc)- Just w -> pieceParserStep e (16 * acc + w)---- | Parse UTF-8-encoded 'Bytes' into an 'IPv4Range'.--- This requires the mask to be present.------ >>> maybe (putStrLn "nope") IPv6.printRange $ Parser.parseBytesMaybe (IPv6.parserRangeUtf8Bytes ()) (Ascii.fromString "1b02:f001:5:200b::/80")--- 1b02:f001:5:200b::/80--- >>> maybe (putStrLn "nope") IPv6.printRange $ Parser.parseBytesMaybe (IPv6.parserRangeUtf8Bytes ()) (Ascii.fromString "abcd::")--- nope------ See 'parserRangeUtf8BytesLenient' for a variant that treats--- a missing mask as a @/32@ mask.-parserRangeUtf8Bytes :: e -> Parser.Parser e s IPv6Range-parserRangeUtf8Bytes e = do- base <- parserUtf8Bytes e- Latin.char e '/'- theMask <- Latin.decWord8 e- if theMask > 128- then Parser.fail e- else pure $! normalize (IPv6Range base theMask)---- | Variant of 'parserRangeUtf8Bytes' that allows the mask--- to be omitted. An omitted mask is treated as a @/128@ mask.------ >>> maybe (putStrLn "nope") IPv6.printRange $ Parser.parseBytesMaybe (IPv6.parserRangeUtf8BytesLenient ()) (Ascii.fromString "1b02:f001:5:200b::/80")--- 1b02:f001:5:200b::/80--- >>> maybe (putStrLn "nope") IPv6.printRange $ Parser.parseBytesMaybe (IPv6.parserRangeUtf8BytesLenient ()) (Ascii.fromString "abcd::")--- abcd::/128-parserRangeUtf8BytesLenient :: e -> Parser.Parser e s IPv6Range-parserRangeUtf8BytesLenient e = do- base <- parserUtf8Bytes e- Latin.trySatisfy (=='/') >>= \case- True -> do- theMask <- Latin.decWord8 e- if theMask > 128- then Parser.fail e- else pure $! normalize (IPv6Range base theMask)- False -> pure $! IPv6Range base 128---- | Parse an 'IPv6' using 'Atto.Parser'.------ >>> Atto.parseOnly IPv6.parser (Text.pack "dead:beef:3240:a426:ba68:1cd0:4263:109b")--- Right (ipv6 0xdead 0xbeef 0x3240 0xa426 0xba68 0x1cd0 0x4263 0x109b)-parser :: Atto.Parser IPv6-parser = makeIP <$> ip- where- makeIP [w1, w2, w3, w4, w5, w6, w7, w8] = fromWord16s w1 w2 w3 w4 w5 w6 w7 w8- makeIP _ = error "Net.IPv6.parser: Implementation error. Please open a bug report."-- ip = (Atto.char ':' *> Atto.char ':' *> doubleColon 0) <|> part 0-- part :: Int -> Atto.Parser [Word16]- part n =- case n of- -- max 8 parts in an IPv6 address- 7 -> pure <$> Atto.hexadecimal- -- after 6 parts it could end in IPv4 dotted notation- 6 -> ipv4 <|> hexPart- _ -> hexPart- where- hexPart = (:)- <$> Atto.hexadecimal- <*> (Atto.char ':' *>- (- (Atto.char ':' *> doubleColon (n+1))- <|>- part (n+1)- )- )-- doubleColon :: Int -> Atto.Parser [Word16]- doubleColon count = do- rest <- afterDoubleColon <|> pure []- let fillerLength = (8 - count - length rest)- if fillerLength <= 0- then fail "too many parts in IPv6 address"- else pure (replicate fillerLength 0 ++ rest)-- -- after double colon, IPv4 dotted notation could appear anywhere- afterDoubleColon :: Atto.Parser [Word16]- afterDoubleColon =- ipv4 <|>- (:) <$> Atto.hexadecimal <*> ((Atto.char ':' *> afterDoubleColon) <|> pure [])-- ipv4 :: Atto.Parser [Word16]- ipv4 = ipv4ToWord16s <$> IPv4.parser-- ipv4ToWord16s :: IPv4 -> [Word16]- ipv4ToWord16s (IPv4 word) = [fromIntegral (word `unsafeShiftR` 16), fromIntegral (word .&. 0xFFFF)]---- | An 'IPv6Range'. It is made up of the first 'IPv6' in the range--- and its length.-data IPv6Range = IPv6Range- { ipv6RangeBase :: {-# UNPACK #-} !IPv6- , ipv6RangeLength :: {-# UNPACK #-} !Word8- } deriving (Eq,Ord,Show,Read,Generic,Data)--instance NFData IPv6Range--instance Aeson.ToJSON IPv6Range where- toJSON = Aeson.String . encodeRange--instance Aeson.FromJSON IPv6Range where- parseJSON (Aeson.String t) = case decodeRange t of- Nothing -> fail "Could not decodeRange IPv6 range"- Just res -> return res- parseJSON _ = mzero--mask128 :: IPv6-mask128 = maxBound--mask :: Word8 -> IPv6-mask = complement . shiftR mask128 . fromIntegral---- | Normalize an 'IPv6Range'. The first result of this is that the--- 'IPv6' inside the 'IPv6Range' is changed so that the insignificant--- bits are zeroed out. For example:------ >>> addr1 = IPv6.ipv6 0x0192 0x0168 0x0001 0x0019 0x0000 0x0000 0x0000 0x0000--- >>> addr2 = IPv6.ipv6 0x0192 0x0168 0x0001 0x0163 0x0000 0x0000 0x0000 0x0000--- >>> IPv6.printRange $ IPv6.normalize $ IPv6.IPv6Range addr1 24--- 192:100::/24--- >>> IPv6.printRange $ IPv6.normalize $ IPv6.IPv6Range addr2 28--- 192:160::/28------ The second effect of this is that the mask length is lowered to be 128--- or smaller. Working with 'IPv6Range's that have not been normalized does--- not cause any issues for this library, although other applications may--- reject such ranges (especially those with a mask length above 128).------ Note that 'normalize is idempotent, that is:------ prop> IPv6.normalize r == (IPv6.normalize . IPv6.normalize) r-normalize :: IPv6Range -> IPv6Range-normalize (IPv6Range ip len) =- let len' = min len 128- ip' = ip .&. mask len'- in IPv6Range ip' len'---- | Encode an 'IPv6Range' as 'Text'.------ >>> addr = IPv6.ipv6 0xDEAD 0xBEEF 0x3240 0xA426 0xBA68 0x1CD0 0x4263 0x109B--- >>> T.putStrLn $ IPv6.encodeRange $ IPv6.IPv6Range addr 28--- dead:beef:3240:a426:ba68:1cd0:4263:109b/28-encodeRange :: IPv6Range -> Text-encodeRange x = encode (ipv6RangeBase x) <> Text.pack "/" <> (Text.pack $ (show . fromEnum) $ ipv6RangeLength x)---- | Decode an 'IPv6Range' from 'Text'.------ >>> addr = IPv6.ipv6 0xDEAD 0xBEEF 0x3240 0xA426 0xBA68 0x1CD0 0x4263 0x109B--- >>> fmap IPv6.encodeRange $ IPv6.decodeRange (Text.pack "dead:beef:3240:a426:ba68:1cd0:4263:109b/28")--- Just "dead:bee0::/28"-decodeRange :: Text -> Maybe IPv6Range-decodeRange = rightToMaybe . AT.parseOnly (parserRange <* AT.endOfInput)---- | Parse an 'IPv6Range' using a 'AT.Parser'.-parserRange :: AT.Parser IPv6Range-parserRange = do- ip <- parser- _ <- AT.char '/'- theMask <- AT.decimal >>= limitSize- return (normalize (IPv6Range ip theMask))- where- limitSize i =- if i > 128- then fail "An IP range length must be between 0 and 128"- else return i---- | Checks to see if an 'IPv6' address belongs in the 'IPv6Range'.------ >>> let ip = IPv6.ipv6 0x2001 0x0db8 0x0db8 0x1094 0x2051 0x0000 0x0000 0x0001--- >>> let iprange mask = IPv6.IPv6Range (IPv6.ipv6 0x2001 0x0db8 0x0000 0x0000 0x0000 0x0000 0x0000 0x0001) mask--- >>> IPv6.contains (iprange 8) ip--- True--- >>> IPv6.contains (iprange 48) ip--- False------ Typically, element-testing functions are written to take the element--- as the first argument and the set as the second argument. This is intentionally--- written the other way for better performance when iterating over a collection.--- For example, you might test elements in a list for membership like this:------ >>> let r = IPv6.IPv6Range (IPv6.ipv6 0x2001 0x0db8 0x0000 0x0000 0x0000 0x0000 0x0000 0x0001) 64--- >>> fmap (IPv6.contains r) (take 5 $ iterate succ $ IPv6.ipv6 0x2001 0x0db8 0x0000 0x0000 0xffff 0xffff 0xffff 0xfffe)--- [True,True,False,False,False]------ The implementation of 'contains' ensures that (with GHC), the bitmask--- creation and range normalization only occur once in the above example.--- They are reused as the list is iterated.-contains :: IPv6Range -> IPv6 -> Bool-contains (IPv6Range subnet len) =- let theMask = mask len- subnetNormalized = subnet .&. theMask- in \ip -> (ip .&. theMask) == subnetNormalized---- | Checks if the first range is a subset of the second range.-isSubsetOf :: IPv6Range -> IPv6Range -> Bool-isSubsetOf a b =- lowerInclusive a >= lowerInclusive b- &&- upperInclusive a <= upperInclusive b---- | This is provided to mirror the interface provided by @Data.Set@. It--- behaves just like 'contains' but with flipped arguments.------ prop> IPv6.member ip r == IPv6.contains r ip-member :: IPv6 -> IPv6Range -> Bool-member = flip contains---- | The inclusive lower bound of an 'IPv6Range'. This is conventionally--- understood to be the broadcast address of a subnet. For example:------ >>> T.putStrLn $ IPv6.encode $ IPv6.lowerInclusive $ IPv6.IPv6Range (IPv6.ipv6 0x2001 0x0db8 0x0000 0x0000 0x0000 0x0000 0x0000 0x0001) 25--- 2001:d80::------ Note that the lower bound of a normalized 'IPv6Range' is simply the--- ip address of the range:------ prop> IPv6.lowerInclusive r == IPv6.ipv6RangeBase (IPv6.normalize r)-lowerInclusive :: IPv6Range -> IPv6-lowerInclusive = ipv6RangeBase . normalize---- | The inclusive upper bound of an 'IPv6Range'.------ >>> let addr = IPv6.ipv6 0xDEAD 0xBEEF 0x3240 0xA426 0xBA68 0x1CD0 0x4263 0x109B--- >>> T.putStrLn $ IPv6.encode $ IPv6.upperInclusive $ IPv6.IPv6Range addr 25--- dead:beff:ffff:ffff:ffff:ffff:ffff:ffff----upperInclusive :: IPv6Range -> IPv6-upperInclusive (IPv6Range ip len) =- let len' = min 128 len- theInvertedMask :: IPv6- theInvertedMask = shiftR mask128 (fromIntegral len')- in ip .|. theInvertedMask---- | Print an 'IPv6Range' using the textual encoding.-printRange :: IPv6Range -> IO ()-printRange = TIO.putStrLn . encodeRange---- | Smart constructor for 'IPv6Range'. Ensures the mask is appropriately--- sized and sets masked bits in the 'IPv6' to zero.------ >>> let addr = IPv6.ipv6 0xDEAD 0xBEEF 0x3240 0xA426 0xBA68 0x1CD0 0x4263 0x109B--- >>> IPv6.printRange $ IPv6.range addr 25--- dead:be80::/25-range :: IPv6 -> Word8 -> IPv6Range-range addr len = normalize (IPv6Range addr len)---- | Given an inclusive lower and upper ip address, create the smallest 'IPv6Range'--- that contains the two. This is helpful in situations where input is given as a--- range, like @ @.------ This makes the range broader if it cannot be represented in <https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR> notation.------ >>> addrLower = IPv6.ipv6 0xDEAD 0xBE80 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000--- >>> addrUpper = IPv6.ipv6 0xDEAD 0xBEFF 0xFFFF 0xFFFF 0xFFFF 0xFFFF 0xFFFF 0xFFFF--- >>> IPv6.printRange $ IPv6.fromBounds addrLower addrUpper--- dead:be80::/25+{-# LANGUAGE UnboxedTuples #-}++{- | This module provides the IPv6 data type and functions for working+ with it.+-}+module Net.IPv6+ ( -- * Convert+ ipv6+ , fromOctets+ , fromWord16s+ , fromWord32s+ , fromTupleWord16s+ , fromTupleWord32s+ , toWord16s+ , toWord32s++ -- * Special IP Addresses+ , any+ , loopback+ , localhost++ -- * Textual Conversion++ -- ** Text+ , encode+ , encodeShort+ , decode+ , decodeShort+ , parser++ -- * UTF-8 Bytes+ , parserUtf8Bytes+ , decodeUtf8Bytes+ , boundedBuilderUtf8++ -- ** Printing+ , print++ -- * IPv6 Ranges++ -- ** Range functions+ , range+ , fromBounds+ , normalize+ , contains+ , isSubsetOf+ , member+ , lowerInclusive+ , upperInclusive++ -- ** Textual Conversion++ -- *** Text+ , encodeRange+ , decodeRange+ , parserRange+ , printRange++ -- ** UTF-8 Bytes+ , parserRangeUtf8Bytes+ , parserRangeUtf8BytesLenient++ -- * Types+ , IPv6 (..)+ , IPv6Range (..)+ ) where++import Prelude hiding (any, print)++import Net.IPv4 (IPv4 (..))++import Control.Applicative+import Control.DeepSeq (NFData)+import Control.Monad (mzero)+import Control.Monad.ST (ST)+import Data.Bits+import Data.Char (chr)+import Data.Data (Data)+import Data.Hashable (Hashable, hashWithSalt)+import Data.Ix (Ix)+import Data.List (group, intercalate)+import Data.Primitive (MutablePrimArray)+import Data.Primitive.Types (Prim)+import Data.Text (Text)+import Data.Text.Short (ShortText)+import Data.WideWord.Word128 (Word128 (..), zeroWord128)+import Data.Word+import Foreign.Storable (Storable)+import GHC.Exts (Int (I#), Int#, Word#)+import GHC.Generics (Generic)+import Numeric (showHex)+import Text.ParserCombinators.ReadPrec (prec, step)+import Text.Read (Lexeme (Ident), Read (..), lexP, parens)++import qualified Arithmetic.Lte as Lte+import qualified Arithmetic.Nat as Nat+import qualified Data.Aeson as Aeson+import qualified Data.Attoparsec.Text as AT+import qualified Data.Attoparsec.Text as Atto+import qualified Data.ByteString.Short.Internal as BSS+import qualified Data.Bytes as Bytes+import qualified Data.Bytes.Builder.Bounded as BB+import qualified Data.Bytes.Parser as Parser+import qualified Data.Bytes.Parser.Latin as Latin+import qualified Data.Primitive as PM+import qualified Data.Text as Text+import qualified Data.Text.IO as TIO+import qualified Data.Text.Short as TS+import qualified Data.Text.Short.Unsafe as TS+import qualified GHC.Word.Compat as Compat+import qualified Net.IPv4 as IPv4++{- $setup++These are here to get doctest work.++>>> import qualified Prelude as P+>>> import qualified Data.Text.IO as T+>>> import qualified Data.Text as Text+>>> import qualified Data.Attoparsec.Text as Atto+>>> import qualified Data.Bytes.Text.Ascii as Ascii+>>> import Test.QuickCheck (Arbitrary(..))+>>> instance Arbitrary Word128 where { arbitrary = Word128 <$> arbitrary <*> arbitrary }+>>> instance Arbitrary IPv6 where { arbitrary = IPv6 <$> arbitrary }+>>> instance Arbitrary IPv6.IPv6Range where { arbitrary = IPv6.IPv6Range <$> arbitrary <*> arbitrary }+-}++-- | A 128-bit Internet Protocol version 6 address.+newtype IPv6 = IPv6 {getIPv6 :: Word128}+ deriving (Bounded, Enum, Eq, Ord, Storable, Bits, FiniteBits, NFData, Prim, Ix, Data, Generic)++instance Hashable IPv6 where+ hashWithSalt s (IPv6 (Word128 a b)) = hashWithSalt (hashWithSalt s a) b++instance Show IPv6 where+ showsPrec p addr =+ showParen (p > 10) $+ showString "ipv6 "+ . showHexWord16 a+ . showChar ' '+ . showHexWord16 b+ . showChar ' '+ . showHexWord16 c+ . showChar ' '+ . showHexWord16 d+ . showChar ' '+ . showHexWord16 e+ . showChar ' '+ . showHexWord16 f+ . showChar ' '+ . showHexWord16 g+ . showChar ' '+ . showHexWord16 h+ where+ (a, b, c, d, e, f, g, h) = toWord16s addr++-- | Print an 'IPv6' using the textual encoding.+print :: IPv6 -> IO ()+print = TIO.putStrLn . encode++{- | Decode 'ShortText' as an 'IPv6' address.++ >>> decodeShort "ffff::2:b"+ Just (ipv6 0xffff 0x0000 0x0000 0x0000 0x0000 0x0000 0x0002 0x000b)+-}+decodeShort :: ShortText -> Maybe IPv6+decodeShort t = decodeUtf8Bytes (Bytes.fromByteArray b)+ where+ b = shortByteStringToByteArray (TS.toShortByteString t)++shortByteStringToByteArray :: BSS.ShortByteString -> PM.ByteArray+shortByteStringToByteArray (BSS.SBS x) = PM.ByteArray x++showHexWord16 :: Word16 -> ShowS+showHexWord16 w =+ showString "0x"+ . showChar (nibbleToHex (unsafeShiftR (fromIntegral w) 12))+ . showChar (nibbleToHex ((unsafeShiftR (fromIntegral w) 8) .&. 0xF))+ . showChar (nibbleToHex ((unsafeShiftR (fromIntegral w) 4) .&. 0xF))+ . showChar (nibbleToHex ((fromIntegral w) .&. 0xF))++-- invariant: argument must be less than 16+nibbleToHex :: Word -> Char+nibbleToHex w+ | w < 10 = chr (fromIntegral (w + 48))+ | otherwise = chr (fromIntegral (w + 87))++instance Read IPv6 where+ readPrec = parens $ prec 10 $ do+ Ident "ipv6" <- lexP+ a <- step readPrec+ b <- step readPrec+ c <- step readPrec+ d <- step readPrec+ e <- step readPrec+ f <- step readPrec+ g <- step readPrec+ h <- step readPrec+ return (fromWord16s a b c d e f g h)++instance Aeson.ToJSON IPv6 where+ toJSON = Aeson.String . encode++instance Aeson.FromJSON IPv6 where+ parseJSON = Aeson.withText "IPv6" $ \t -> case decode t of+ Nothing -> fail "invalid IPv6 address"+ Just i -> return i++rightToMaybe :: Either a b -> Maybe b+rightToMaybe = either (const Nothing) Just++{- | This could be useful for the rare occasion+ in which one could construct an 'IPv6' from+ octets.++ Note that while @Net.IPv4.'Net.IPv4.fromOctets' = Net.IPv4.'Net.IPv4.ipv4'@,+ @Net.IPv6.fromOctets /= Net.IPv6.ipv6@. While this should be obvious+ from their types, it is worth mentioning since the similarity in naming+ might be confusing.+-}+fromOctets ::+ Word8 ->+ Word8 ->+ Word8 ->+ Word8 ->+ Word8 ->+ Word8 ->+ Word8 ->+ Word8 ->+ Word8 ->+ Word8 ->+ Word8 ->+ Word8 ->+ Word8 ->+ Word8 ->+ Word8 ->+ Word8 ->+ IPv6+fromOctets a b c d e f g h i j k l m n o p =+ IPv6 $+ fromOctetsWord128+ (fromIntegral a)+ (fromIntegral b)+ (fromIntegral c)+ (fromIntegral d)+ (fromIntegral e)+ (fromIntegral f)+ (fromIntegral g)+ (fromIntegral h)+ (fromIntegral i)+ (fromIntegral j)+ (fromIntegral k)+ (fromIntegral l)+ (fromIntegral m)+ (fromIntegral n)+ (fromIntegral o)+ (fromIntegral p)++fromOctetsWord128 ::+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128+fromOctetsWord128 a b c d e f g h i j k l m n o p =+ fromIntegral+ ( shiftL a 120+ .|. shiftL b 112+ .|. shiftL c 104+ .|. shiftL d 96+ .|. shiftL e 88+ .|. shiftL f 80+ .|. shiftL g 72+ .|. shiftL h 64+ .|. shiftL i 56+ .|. shiftL j 48+ .|. shiftL k 40+ .|. shiftL l 32+ .|. shiftL m 24+ .|. shiftL n 16+ .|. shiftL o 8+ .|. p+ )++{- | Create an 'IPv6' address from the eight 16-bit fragments that make+ it up. This closely resembles the standard IPv6 notation, so+ is used for the 'Show' instance. Note that this lacks the formatting+ feature for suppress zeroes in an 'IPv6' address, but it should be+ readable enough for hacking in GHCi.++ >>> let addr = ipv6 0x3124 0x0 0x0 0xDEAD 0xCAFE 0xFF 0xFE00 0x1+ >>> addr+ ipv6 0x3124 0x0000 0x0000 0xdead 0xcafe 0x00ff 0xfe00 0x0001+ >>> T.putStrLn (encode addr)+ 3124::dead:cafe:ff:fe00:1+-}+ipv6 ::+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ IPv6+ipv6 = fromWord16s++-- | An alias for the 'ipv6' smart constructor.+fromWord16s ::+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ IPv6+fromWord16s a b c d e f g h =+ IPv6 $+ fromWord16sWord128+ (fromIntegral a)+ (fromIntegral b)+ (fromIntegral c)+ (fromIntegral d)+ (fromIntegral e)+ (fromIntegral f)+ (fromIntegral g)+ (fromIntegral h)++fromWord16sWord128 ::+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128+fromWord16sWord128 a b c d e f g h =+ fromIntegral+ ( shiftL a 112+ .|. shiftL b 96+ .|. shiftL c 80+ .|. shiftL d 64+ .|. shiftL e 48+ .|. shiftL f 32+ .|. shiftL g 16+ .|. h+ )++-- | Convert an 'IPv6' to eight 16-bit words.+toWord16s :: IPv6 -> (Word16, Word16, Word16, Word16, Word16, Word16, Word16, Word16)+toWord16s (IPv6 (Word128 a b)) =+ -- Note: implementing this as 2 Word64 shifts with 'unsafeShiftR'+ -- is up to 40% faster than using 128-bit shifts on a Word128 value.+ ( fromIntegral (unsafeShiftR a 48)+ , fromIntegral (unsafeShiftR a 32)+ , fromIntegral (unsafeShiftR a 16)+ , fromIntegral a+ , fromIntegral (unsafeShiftR b 48)+ , fromIntegral (unsafeShiftR b 32)+ , fromIntegral (unsafeShiftR b 16)+ , fromIntegral b+ )++-- | Uncurried variant of 'fromWord16s'.+fromTupleWord16s :: (Word16, Word16, Word16, Word16, Word16, Word16, Word16, Word16) -> IPv6+fromTupleWord16s (a, b, c, d, e, f, g, h) = fromWord16s a b c d e f g h++{- | Build an 'IPv6' from four 32-bit words. The leftmost argument+ is the high word and the rightword is the low word.+-}+fromWord32s :: Word32 -> Word32 -> Word32 -> Word32 -> IPv6+fromWord32s a b c d =+ IPv6 $+ fromWord32sWord128+ (fromIntegral a)+ (fromIntegral b)+ (fromIntegral c)+ (fromIntegral d)++fromWord32sWord128 ::+ Word128 ->+ Word128 ->+ Word128 ->+ Word128 ->+ Word128+fromWord32sWord128 a b c d =+ fromIntegral+ ( shiftL a 96+ .|. shiftL b 64+ .|. shiftL c 32+ .|. d+ )++-- | Uncurried variant of 'fromWord32s'.+fromTupleWord32s :: (Word32, Word32, Word32, Word32) -> IPv6+fromTupleWord32s (a, b, c, d) = fromWord32s a b c d++-- | Convert an 'IPv6' to four 32-bit words.+toWord32s :: IPv6 -> (Word32, Word32, Word32, Word32)+toWord32s (IPv6 (Word128 a b)) =+ -- Note: implementing this as 2 Word64 shifts with 'unsafeShiftR'+ -- is about 10% faster than using 128-bit shifts on a Word128 value.+ ( fromIntegral (unsafeShiftR a 32)+ , fromIntegral a+ , fromIntegral (unsafeShiftR b 32)+ , fromIntegral b+ )++{- | The local loopback IP address.++ >>> IPv6.loopback+ ipv6 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0001+-}+loopback :: IPv6+loopback = IPv6 (Word128 0 1)++{- | A useful alias for 'loopback'.++ >>> IPv6.localhost+ ipv6 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0001+-}+localhost :: IPv6+localhost = loopback++{- | The IP address representing any host.++ >>> IPv6.any+ ipv6 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000+-}+any :: IPv6+any = IPv6 zeroWord128++{- | Encodes the 'IPv6' address using zero-compression on the leftmost longest+string of zeroes in the address.+Per <https://tools.ietf.org/html/rfc5952#section-5 RFC 5952 Section 5>,+this uses mixed notation when encoding an IPv4-mapped IPv6 address:++>>> T.putStrLn $ IPv6.encode $ IPv6.fromWord16s 0xDEAD 0xBEEF 0x0 0x0 0x0 0x0 0x0 0x1234+dead:beef::1234+>>> T.putStrLn $ IPv6.encode $ IPv6.fromWord16s 0x0 0x0 0x0 0x0 0x0 0xFFFF 0x6437 0xA5B4+::ffff:100.55.165.180+>>> T.putStrLn $ IPv6.encode $ IPv6.fromWord16s 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0+::++Per <https://tools.ietf.org/html/rfc5952#section-4.2.2 Section 4.2.2> of the+same RFC, this does not use @::@ to shorten a single 16-bit 0 field. Only+runs of multiple 0 fields are considered.+-}+encode :: IPv6 -> Text+encode !ip =+ -- TODO: This implementation, while correct, is not particularly efficient.+ -- It uses string all over the place.+ if isIPv4Mapped ip+ then -- This representation is RECOMMENDED by https://tools.ietf.org/html/rfc5952#section-5++ Text.pack "::ffff:"+ `mappend` IPv4.encode (IPv4.IPv4 (fromIntegral w7 `unsafeShiftL` 16 .|. fromIntegral w8))+ else toText [w1, w2, w3, w4, w5, w6, w7, w8]+ where+ (w1, w2, w3, w4, w5, w6, w7, w8) = toWord16s ip+ toText ws =+ Text.pack $+ intercalate ":" $+ expand 0 (if longestZ > 1 then longestZ else 0) grouped+ where+ expand !_ 8 !_ = ["::"]+ expand !_ !_ [] = []+ expand !i !longest ((x, len) : wsNext)+ -- zero-compressed group:+ | x == 0 && len == longest =+ -- first and last need an extra colon since there's nothing+ -- to concat against+ (if i == 0 || (i + len) == 8 then ":" else "")+ : expand (i + len) 0 wsNext+ -- normal group:+ | otherwise = replicate len (showHex x "") ++ expand (i + len) longest wsNext+ longestZ = maximum . (0 :) . map snd . filter ((== 0) . fst) $ grouped+ grouped = map (\x -> (head x, length x)) (group ws)++isIPv4Mapped :: IPv6 -> Bool+isIPv4Mapped (IPv6 (Word128 w1 w2)) =+ w1 == 0 && (0xFFFFFFFF00000000 .&. w2 == 0x0000FFFF00000000)++{- | Decode UTF-8-encoded 'Bytes' into an 'IPv6' address.++ >>> decodeUtf8Bytes (Ascii.fromString "::cab:1")+ Just (ipv6 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0cab 0x0001)+-}+decodeUtf8Bytes :: Bytes.Bytes -> Maybe IPv6+decodeUtf8Bytes !b = case Parser.parseBytes (parserUtf8Bytes ()) b of+ Parser.Success (Parser.Slice _ len addr) -> case len of+ 0 -> Just addr+ _ -> Nothing+ Parser.Failure _ -> Nothing++{- | Encodes the 'IPv6' address using zero-compression on the+leftmost longest string of zeroes in the address.++>>> BB.run Nat.constant $ IPv6.boundedBuilderUtf8 $ IPv6.fromWord16s 0xDEAD 0xBEEF 0x0 0x0 0x0 0x0 0x0 0x1234+[0x64, 0x65, 0x61, 0x64, 0x3a, 0x62, 0x65, 0x65, 0x66, 0x3a, 0x3a, 0x31, 0x32, 0x33, 0x34]+-}+boundedBuilderUtf8 :: IPv6 -> BB.Builder 39+boundedBuilderUtf8 !ip@(IPv6 (Word128 hi lo))+ | hi == 0 && lo == 0 =+ BB.weaken+ Lte.constant+ (BB.ascii ':' `BB.append` BB.ascii ':')+ | isIPv4Mapped ip =+ BB.weaken Lte.constant $+ BB.ascii ':'+ `BB.append` BB.ascii ':'+ `BB.append` BB.ascii 'f'+ `BB.append` BB.ascii 'f'+ `BB.append` BB.ascii 'f'+ `BB.append` BB.ascii 'f'+ `BB.append` BB.ascii ':'+ `BB.append` IPv4.boundedBuilderUtf8 (IPv4.IPv4 (fromIntegral lo))+ | otherwise =+ let (w0, w1, w2, w3, w4, w5, w6, w7) = toWord16s ip+ IntTriple startLongest longest _ = longestRun w0 w1 w2 w3 w4 w5 w6 w7+ start = startLongest+ end = start + longest+ in -- start is inclusive. end is exclusive+ firstPiece w0 start+ `BB.append` piece 1 w1 start end+ `BB.append` piece 2 w2 start end+ `BB.append` piece 3 w3 start end+ `BB.append` piece 4 w4 start end+ `BB.append` piece 5 w5 start end+ `BB.append` piece 6 w6 start end+ `BB.append` lastPiece w7 end++firstPiece :: Word16 -> Int -> BB.Builder 4+firstPiece !w !start = case start of+ 0 -> BB.weaken Lte.constant (BB.ascii ':')+ _ -> BB.word16LowerHex w++-- Note about the implementation of piece:+-- It is important to manually perform worker-wrapper so that+-- we can stop piece from inlining. If we do not do this, GHC+-- inlines piece, leading to enormous blowup in the generated+-- Core. The implementation of boundedBuilderUtf8 becomes+-- thousands of lines of Core. Even in the microbenchmark that+-- comes with this library, it can be observed that preventing+-- this inlining improves performance of encodeShort by 50%.+piece :: Int -> Word16 -> Int -> Int -> BB.Builder 5+{-# INLINE piece #-}+piece (I# ix) (Compat.W16# w) (I# start) (I# end) =+ piece# ix w start end++piece# :: Int# -> Word# -> Int# -> Int# -> BB.Builder 5+{-# NOINLINE piece# #-}+piece# !ix# !w# !start# !end# = case compare ix start of+ LT -> BB.ascii ':' `BB.append` BB.word16LowerHex w+ EQ -> BB.weaken Lte.constant (BB.ascii ':')+ GT ->+ if ix < end+ then BB.weaken Lte.constant BB.empty+ else BB.ascii ':' `BB.append` BB.word16LowerHex w+ where+ ix = I# ix#+ start = I# start#+ end = I# end#+ w = Compat.W16# w#++lastPiece :: Word16 -> Int -> BB.Builder 5+lastPiece !w !end = case end of+ 8 -> BB.weaken Lte.constant (BB.ascii ':')+ _ -> BB.ascii ':' `BB.append` BB.word16LowerHex w++data IntTriple = IntTriple !Int !Int !Int++-- Choose the longest run. Prefer the leftmost run in the+-- event of a tie.+stepZeroRunLength :: Int -> Word16 -> IntTriple -> IntTriple+stepZeroRunLength !ix !w (IntTriple startLongest longest current) = case w of+ 0 ->+ let !x = current + 1+ in if x > longest+ then IntTriple (ix - current) x x+ else IntTriple startLongest longest x+ _ -> IntTriple startLongest longest 0++-- We start out by setting the longest run to size 1. This+-- means that we will only detect runs of length two or greater.+longestRun ::+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ Word16 ->+ IntTriple+longestRun !w0 !w1 !w2 !w3 !w4 !w5 !w6 !w7 =+ id $+ stepZeroRunLength 7 w7 $+ stepZeroRunLength 6 w6 $+ stepZeroRunLength 5 w5 $+ stepZeroRunLength 4 w4 $+ stepZeroRunLength 3 w3 $+ stepZeroRunLength 2 w2 $+ stepZeroRunLength 1 w1 $+ stepZeroRunLength 0 w0 $+ IntTriple (-1) 1 0++{- | Encodes the 'IPv6' address as 'ShortText' using zero-compression on+the leftmost longest string of zeroes in the address.+Per <https://tools.ietf.org/html/rfc5952#section-5 RFC 5952 Section 5>,+this uses mixed notation when encoding an IPv4-mapped IPv6 address.++>>> IPv6.encodeShort $ IPv6.fromWord16s 0xDEAD 0xBEEF 0x0 0x0 0x0 0x0ABC 0x0 0x1234+"dead:beef::abc:0:1234"+-}+encodeShort :: IPv6 -> ShortText+encodeShort w =+ id $+ TS.fromShortByteStringUnsafe $+ byteArrayToShortByteString $+ BB.run Nat.constant $+ boundedBuilderUtf8 $+ w++byteArrayToShortByteString :: PM.ByteArray -> BSS.ShortByteString+byteArrayToShortByteString (PM.ByteArray x) = BSS.SBS x++{- | Decode an 'IPv6' address. This accepts both standard IPv6+notation (with zero compression) and mixed notation for+IPv4-mapped IPv6 addresses. For a decoding function that+additionally accepts dot-decimal-encoded IPv4 addresses,+see @Net.IP.decode@.+-}+decode :: Text -> Maybe IPv6+decode t = rightToMaybe (AT.parseOnly (parser <* AT.endOfInput) t)++{- | Parse UTF-8-encoded 'Bytes' as an 'IPv6' address. This accepts+both uppercase and lowercase characters in the hexadecimal components.++>>> let str = "dead:beef:3240:a426:ba68:1cd0:4263:109b -> alive"+>>> Parser.parseBytes (parserUtf8Bytes ()) (Ascii.fromString str)+Success (Slice {offset = 39, length = 9, value = ipv6 0xdead 0xbeef 0x3240 0xa426 0xba68 0x1cd0 0x4263 0x109b})++This does not currently support parsing embedded IPv4 address+(e.g. @ff00:8000:abc::224.1.2.3@).+-}+parserUtf8Bytes :: e -> Parser.Parser e s IPv6+parserUtf8Bytes e = do+ marr <- Parser.effect (PM.newPrimArray 8)+ -- We cannot immidiately call preZeroes since it wants a+ -- leading colon present.+ Latin.trySatisfy (== ':') >>= \case+ True -> do+ Latin.char e ':'+ postZeroesBegin e marr 0 0+ False -> do+ w <- pieceParser e+ Parser.effect (PM.writePrimArray marr 0 w)+ preZeroes e marr 1++-- This is called when we are positioned before a colon.+-- We may encounter another colon immidiately after+-- the one that we consume here. This indicates zero+-- compression. Or we may encounter another hex-encoded+-- number.+preZeroes ::+ e ->+ MutablePrimArray s Word16 -> -- length must be 8+ Int ->+ Parser.Parser e s IPv6+preZeroes e !marr !ix = case ix of+ 8 -> Parser.effect (combinePieces marr)+ _ -> do+ Latin.char e ':'+ Latin.trySatisfy (== ':') >>= \case+ True -> postZeroesBegin e marr ix ix+ False -> do+ w <- pieceParser e+ Parser.effect (PM.writePrimArray marr ix w)+ preZeroes e marr (ix + 1)++-- The same as postZeroes except that there is no+-- leading that gets consumed. This is called right+-- after a double colon is consumed.+-- Precondition: the index is less than 8. This parser+-- is only called by preZeroes, which ensures that+-- this holds.+postZeroesBegin ::+ e ->+ MutablePrimArray s Word16 -> -- length must be 8+ Int -> -- current index in array+ Int -> -- index where compression happened+ Parser.Parser e s IPv6+postZeroesBegin e !marr !ix !compress = do+ optionalPieceParser e >>= \case+ Nothing -> do+ -- the end has come+ Parser.effect (conclude marr ix compress)+ Just w -> do+ Parser.effect (PM.writePrimArray marr ix w)+ postZeroes e marr (ix + 1) compress++-- Should be run right before a colon.+postZeroes ::+ e ->+ MutablePrimArray s Word16 -> -- length must be 8+ Int -> -- current index in array+ Int -> -- index where compression happened+ Parser.Parser e s IPv6+postZeroes e !marr !ix !compress = case ix of+ 8 -> Parser.fail e+ _ -> do+ Latin.trySatisfy (== ':') >>= \case+ False ->+ -- The end has come+ Parser.effect (conclude marr ix compress)+ True -> do+ w <- pieceParser e+ Parser.effect (PM.writePrimArray marr ix w)+ postZeroes e marr (ix + 1) compress++conclude :: MutablePrimArray s Word16 -> Int -> Int -> ST s IPv6+conclude !marr !ix !compress = do+ -- This will overlap, but GHC's copy primop is fine with that.+ let postCompressionLen = ix - compress+ PM.copyMutablePrimArray marr (8 - postCompressionLen) marr compress postCompressionLen+ let compressedArea = 8 - ix+ PM.setPrimArray marr compress compressedArea (0 :: Word16)+ combinePieces marr++-- Example memmove that may need to happen:+-- A B C H ==> A B C 0 0 0 0 H+-- *+-- ix = 4, compress = 3, postCompressionLen = 1, compressedArea = 4+-- copyPrimArray marr 7 marr 3 1+-- setPrimArray marr 3 4 (0 :: Word16)++combinePieces ::+ MutablePrimArray s Word16 ->+ ST s IPv6+combinePieces !marr =+ fromWord16s+ <$> PM.readPrimArray marr 0+ <*> PM.readPrimArray marr 1+ <*> PM.readPrimArray marr 2+ <*> PM.readPrimArray marr 3+ <*> PM.readPrimArray marr 4+ <*> PM.readPrimArray marr 5+ <*> PM.readPrimArray marr 6+ <*> PM.readPrimArray marr 7++optionalPieceParser :: e -> Parser.Parser e s (Maybe Word16)+optionalPieceParser e =+ Latin.tryHexNibble >>= \case+ Nothing -> pure Nothing+ Just w0 -> do+ r <- pieceParserStep e w0+ pure (Just r)++-- This should probably be moved into bytesmith and renamed.+pieceParser :: e -> Parser.Parser e s Word16+pieceParser e = Latin.hexNibble e >>= pieceParserStep e++-- Parses the remainder of a lowercase hexadecimal number.+-- Leaves trailing colons alone. This fails if there are+-- more than four hex digits unless there are leading zeroes.+-- I cannot find a spec that is clear about what to do+-- if someone puts 00000 in a piece of an encoded IPv6+-- address, so I veer on the side of leniency.+pieceParserStep ::+ e ->+ Word ->+ Parser.Parser e s Word16+pieceParserStep e !acc =+ if acc > 0xFFFF+ then Parser.fail e+ else+ Latin.tryHexNibble >>= \case+ Nothing -> pure (fromIntegral acc)+ Just w -> pieceParserStep e (16 * acc + w)++{- | Parse UTF-8-encoded 'Bytes' into an 'IPv4Range'.+This requires the mask to be present.++>>> maybe (putStrLn "nope") IPv6.printRange $ Parser.parseBytesMaybe (IPv6.parserRangeUtf8Bytes ()) (Ascii.fromString "1b02:f001:5:200b::/80")+1b02:f001:5:200b::/80+>>> maybe (putStrLn "nope") IPv6.printRange $ Parser.parseBytesMaybe (IPv6.parserRangeUtf8Bytes ()) (Ascii.fromString "abcd::")+nope++See 'parserRangeUtf8BytesLenient' for a variant that treats+a missing mask as a @/32@ mask.+-}+parserRangeUtf8Bytes :: e -> Parser.Parser e s IPv6Range+parserRangeUtf8Bytes e = do+ base <- parserUtf8Bytes e+ Latin.char e '/'+ theMask <- Latin.decWord8 e+ if theMask > 128+ then Parser.fail e+ else pure $! normalize (IPv6Range base theMask)++{- | Variant of 'parserRangeUtf8Bytes' that allows the mask+to be omitted. An omitted mask is treated as a @/128@ mask.++>>> maybe (putStrLn "nope") IPv6.printRange $ Parser.parseBytesMaybe (IPv6.parserRangeUtf8BytesLenient ()) (Ascii.fromString "1b02:f001:5:200b::/80")+1b02:f001:5:200b::/80+>>> maybe (putStrLn "nope") IPv6.printRange $ Parser.parseBytesMaybe (IPv6.parserRangeUtf8BytesLenient ()) (Ascii.fromString "abcd::")+abcd::/128+-}+parserRangeUtf8BytesLenient :: e -> Parser.Parser e s IPv6Range+parserRangeUtf8BytesLenient e = do+ base <- parserUtf8Bytes e+ Latin.trySatisfy (== '/') >>= \case+ True -> do+ theMask <- Latin.decWord8 e+ if theMask > 128+ then Parser.fail e+ else pure $! normalize (IPv6Range base theMask)+ False -> pure $! IPv6Range base 128++{- | Parse an 'IPv6' using 'Atto.Parser'.++ >>> Atto.parseOnly IPv6.parser (Text.pack "dead:beef:3240:a426:ba68:1cd0:4263:109b")+ Right (ipv6 0xdead 0xbeef 0x3240 0xa426 0xba68 0x1cd0 0x4263 0x109b)+-}+parser :: Atto.Parser IPv6+parser = makeIP <$> ip+ where+ makeIP [w1, w2, w3, w4, w5, w6, w7, w8] = fromWord16s w1 w2 w3 w4 w5 w6 w7 w8+ makeIP _ = error "Net.IPv6.parser: Implementation error. Please open a bug report."++ ip = (Atto.char ':' *> Atto.char ':' *> doubleColon 0) <|> part 0++ part :: Int -> Atto.Parser [Word16]+ part n =+ case n of+ -- max 8 parts in an IPv6 address+ 7 -> pure <$> Atto.hexadecimal+ -- after 6 parts it could end in IPv4 dotted notation+ 6 -> ipv4 <|> hexPart+ _ -> hexPart+ where+ hexPart =+ (:)+ <$> Atto.hexadecimal+ <*> ( Atto.char ':'+ *> ( (Atto.char ':' *> doubleColon (n + 1))+ <|> part (n + 1)+ )+ )++ doubleColon :: Int -> Atto.Parser [Word16]+ doubleColon count = do+ rest <- afterDoubleColon <|> pure []+ let fillerLength = (8 - count - length rest)+ if fillerLength <= 0+ then fail "too many parts in IPv6 address"+ else pure (replicate fillerLength 0 ++ rest)++ -- after double colon, IPv4 dotted notation could appear anywhere+ afterDoubleColon :: Atto.Parser [Word16]+ afterDoubleColon =+ ipv4+ <|> (:) <$> Atto.hexadecimal <*> ((Atto.char ':' *> afterDoubleColon) <|> pure [])++ ipv4 :: Atto.Parser [Word16]+ ipv4 = ipv4ToWord16s <$> IPv4.parser++ ipv4ToWord16s :: IPv4 -> [Word16]+ ipv4ToWord16s (IPv4 word) = [fromIntegral (word `unsafeShiftR` 16), fromIntegral (word .&. 0xFFFF)]++{- | An 'IPv6Range'. It is made up of the first 'IPv6' in the range+ and its length.+-}+data IPv6Range = IPv6Range+ { ipv6RangeBase :: {-# UNPACK #-} !IPv6+ , ipv6RangeLength :: {-# UNPACK #-} !Word8+ }+ deriving (Eq, Ord, Show, Read, Generic, Data)++instance NFData IPv6Range++instance Aeson.ToJSON IPv6Range where+ toJSON = Aeson.String . encodeRange++instance Aeson.FromJSON IPv6Range where+ parseJSON (Aeson.String t) = case decodeRange t of+ Nothing -> fail "Could not decodeRange IPv6 range"+ Just res -> return res+ parseJSON _ = mzero++mask128 :: IPv6+mask128 = maxBound++mask :: Word8 -> IPv6+mask = complement . shiftR mask128 . fromIntegral++{- | Normalize an 'IPv6Range'. The first result of this is that the+ 'IPv6' inside the 'IPv6Range' is changed so that the insignificant+ bits are zeroed out. For example:++ >>> addr1 = IPv6.ipv6 0x0192 0x0168 0x0001 0x0019 0x0000 0x0000 0x0000 0x0000+ >>> addr2 = IPv6.ipv6 0x0192 0x0168 0x0001 0x0163 0x0000 0x0000 0x0000 0x0000+ >>> IPv6.printRange $ IPv6.normalize $ IPv6.IPv6Range addr1 24+ 192:100::/24+ >>> IPv6.printRange $ IPv6.normalize $ IPv6.IPv6Range addr2 28+ 192:160::/28++ The second effect of this is that the mask length is lowered to be 128+ or smaller. Working with 'IPv6Range's that have not been normalized does+ not cause any issues for this library, although other applications may+ reject such ranges (especially those with a mask length above 128).++ Note that 'normalize is idempotent, that is:++ prop> IPv6.normalize r == (IPv6.normalize . IPv6.normalize) r+-}+normalize :: IPv6Range -> IPv6Range+normalize (IPv6Range ip len) =+ let len' = min len 128+ ip' = ip .&. mask len'+ in IPv6Range ip' len'++{- | Encode an 'IPv6Range' as 'Text'.++ >>> addr = IPv6.ipv6 0xDEAD 0xBEEF 0x3240 0xA426 0xBA68 0x1CD0 0x4263 0x109B+ >>> T.putStrLn $ IPv6.encodeRange $ IPv6.IPv6Range addr 28+ dead:beef:3240:a426:ba68:1cd0:4263:109b/28+-}+encodeRange :: IPv6Range -> Text+encodeRange x = encode (ipv6RangeBase x) <> Text.pack "/" <> (Text.pack $ (show . fromEnum) $ ipv6RangeLength x)++{- | Decode an 'IPv6Range' from 'Text'.++ >>> addr = IPv6.ipv6 0xDEAD 0xBEEF 0x3240 0xA426 0xBA68 0x1CD0 0x4263 0x109B+ >>> fmap IPv6.encodeRange $ IPv6.decodeRange (Text.pack "dead:beef:3240:a426:ba68:1cd0:4263:109b/28")+ Just "dead:bee0::/28"+-}+decodeRange :: Text -> Maybe IPv6Range+decodeRange = rightToMaybe . AT.parseOnly (parserRange <* AT.endOfInput)++-- | Parse an 'IPv6Range' using a 'AT.Parser'.+parserRange :: AT.Parser IPv6Range+parserRange = do+ ip <- parser+ _ <- AT.char '/'+ theMask <- AT.decimal >>= limitSize+ return (normalize (IPv6Range ip theMask))+ where+ limitSize i =+ if i > 128+ then fail "An IP range length must be between 0 and 128"+ else return i++{- | Checks to see if an 'IPv6' address belongs in the 'IPv6Range'.++>>> let ip = IPv6.ipv6 0x2001 0x0db8 0x0db8 0x1094 0x2051 0x0000 0x0000 0x0001+>>> let iprange mask = IPv6.IPv6Range (IPv6.ipv6 0x2001 0x0db8 0x0000 0x0000 0x0000 0x0000 0x0000 0x0001) mask+>>> IPv6.contains (iprange 8) ip+True+>>> IPv6.contains (iprange 48) ip+False++Typically, element-testing functions are written to take the element+as the first argument and the set as the second argument. This is intentionally+written the other way for better performance when iterating over a collection.+For example, you might test elements in a list for membership like this:++>>> let r = IPv6.IPv6Range (IPv6.ipv6 0x2001 0x0db8 0x0000 0x0000 0x0000 0x0000 0x0000 0x0001) 64+>>> fmap (IPv6.contains r) (take 5 $ iterate succ $ IPv6.ipv6 0x2001 0x0db8 0x0000 0x0000 0xffff 0xffff 0xffff 0xfffe)+[True,True,False,False,False]++The implementation of 'contains' ensures that (with GHC), the bitmask+creation and range normalization only occur once in the above example.+They are reused as the list is iterated.+-}+contains :: IPv6Range -> IPv6 -> Bool+contains (IPv6Range subnet len) =+ let theMask = mask len+ subnetNormalized = subnet .&. theMask+ in \ip -> (ip .&. theMask) == subnetNormalized++-- | Checks if the first range is a subset of the second range.+isSubsetOf :: IPv6Range -> IPv6Range -> Bool+isSubsetOf a b =+ lowerInclusive a >= lowerInclusive b+ && upperInclusive a <= upperInclusive b++{- | This is provided to mirror the interface provided by @Data.Set@. It+behaves just like 'contains' but with flipped arguments.++prop> IPv6.member ip r == IPv6.contains r ip+-}+member :: IPv6 -> IPv6Range -> Bool+member = flip contains++{- | The inclusive lower bound of an 'IPv6Range'. This is conventionally+ understood to be the broadcast address of a subnet. For example:++>>> T.putStrLn $ IPv6.encode $ IPv6.lowerInclusive $ IPv6.IPv6Range (IPv6.ipv6 0x2001 0x0db8 0x0000 0x0000 0x0000 0x0000 0x0000 0x0001) 25+2001:d80::++Note that the lower bound of a normalized 'IPv6Range' is simply the+ip address of the range:++prop> IPv6.lowerInclusive r == IPv6.ipv6RangeBase (IPv6.normalize r)+-}+lowerInclusive :: IPv6Range -> IPv6+lowerInclusive = ipv6RangeBase . normalize++{- | The inclusive upper bound of an 'IPv6Range'.++ >>> let addr = IPv6.ipv6 0xDEAD 0xBEEF 0x3240 0xA426 0xBA68 0x1CD0 0x4263 0x109B+ >>> T.putStrLn $ IPv6.encode $ IPv6.upperInclusive $ IPv6.IPv6Range addr 25+ dead:beff:ffff:ffff:ffff:ffff:ffff:ffff+-}+upperInclusive :: IPv6Range -> IPv6+upperInclusive (IPv6Range ip len) =+ let len' = min 128 len+ theInvertedMask :: IPv6+ theInvertedMask = shiftR mask128 (fromIntegral len')+ in ip .|. theInvertedMask++-- | Print an 'IPv6Range' using the textual encoding.+printRange :: IPv6Range -> IO ()+printRange = TIO.putStrLn . encodeRange++{- | Smart constructor for 'IPv6Range'. Ensures the mask is appropriately+ sized and sets masked bits in the 'IPv6' to zero.++ >>> let addr = IPv6.ipv6 0xDEAD 0xBEEF 0x3240 0xA426 0xBA68 0x1CD0 0x4263 0x109B+ >>> IPv6.printRange $ IPv6.range addr 25+ dead:be80::/25+-}+range :: IPv6 -> Word8 -> IPv6Range+range addr len = normalize (IPv6Range addr len)++{- | Given an inclusive lower and upper ip address, create the smallest 'IPv6Range'+ that contains the two. This is helpful in situations where input is given as a+ range, like @ @.++ This makes the range broader if it cannot be represented in <https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR> notation.++ >>> addrLower = IPv6.ipv6 0xDEAD 0xBE80 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000+ >>> addrUpper = IPv6.ipv6 0xDEAD 0xBEFF 0xFFFF 0xFFFF 0xFFFF 0xFFFF 0xFFFF 0xFFFF+ >>> IPv6.printRange $ IPv6.fromBounds addrLower addrUpper+ dead:be80::/25+-} fromBounds :: IPv6 -> IPv6 -> IPv6Range fromBounds lo hi = normalize (IPv6Range lo (maskFromBounds lo hi))
src/Net/Mac.hs view
@@ -13,7 +13,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UnboxedTuples #-} -{-| This module provides the Mac data type and functions for working+{- | This module provides the Mac data type and functions for working with it. -} module Net.Mac@@ -21,7 +21,9 @@ mac , fromOctets , toOctets+ -- * Textual Conversion+ -- ** Text , encode , encodeWith@@ -30,8 +32,10 @@ , builder , parser , parserWith+ -- ** ShortText , encodeShort+ -- ** UTF-8 ByteString , encodeUtf8 , encodeWithUtf8@@ -40,38 +44,41 @@ , builderUtf8 , parserUtf8 , parserWithUtf8+ -- ** ByteString , decodeBytes , decodeOctets+ -- ** UTF-8 Bytes , boundedBuilderUtf8 , decodeUtf8Bytes , parserUtf8Bytes+ -- ** Printing , print+ -- * Default Codec , defCodec+ -- * Types- , Mac(..)- , MacCodec(..)- , MacGrouping(..)+ , Mac (..)+ , MacCodec (..)+ , MacGrouping (..) ) where import Prelude hiding (print) import Control.DeepSeq (NFData)-import Data.Aeson (FromJSON(..),ToJSON(..))-import Data.Aeson (ToJSONKey(..),FromJSONKey(..))-import Data.Aeson (ToJSONKeyFunction(..),FromJSONKeyFunction(..))-import Data.Bits ((.|.),unsafeShiftL,unsafeShiftR,(.&.))+import Data.Aeson (FromJSON (..), FromJSONKey (..), FromJSONKeyFunction (..), ToJSON (..), ToJSONKey (..), ToJSONKeyFunction (..))+import Data.Bits (unsafeShiftL, unsafeShiftR, (.&.), (.|.)) import Data.ByteString (ByteString)-import Data.ByteString.Short.Internal (ShortByteString(SBS))-import Data.Char (ord,chr)+import Data.ByteString.Short.Internal (ShortByteString (SBS))+import Data.Char (chr, ord) import Data.Data (Data) import Data.Hashable (Hashable) import Data.Ix (Ix)-import Data.Primitive.ByteArray (ByteArray(ByteArray))-import Data.Primitive.Types (Prim(..))+import Data.Primitive.ByteArray (ByteArray (ByteArray))+import Data.Primitive.Types (Prim (..)) import Data.Text (Text) import Data.Text.Short (ShortText) import Data.Word@@ -79,8 +86,8 @@ import GHC.Enum (predError, succError) import GHC.Exts import GHC.Generics (Generic)-import Text.ParserCombinators.ReadPrec (prec,step)-import Text.Read (Read(..),Lexeme(Ident),lexP,parens)+import Text.ParserCombinators.ReadPrec (prec, step)+import Text.Read (Lexeme (Ident), Read (..), lexP, parens) import qualified Arithmetic.Nat as Nat import qualified Data.Aeson as Aeson@@ -88,19 +95,19 @@ import qualified Data.Attoparsec.ByteString as AB import qualified Data.Attoparsec.ByteString as ABW import qualified Data.Attoparsec.Text as AT-import qualified Data.Bytes.Builder.Bounded as BBB-import qualified Data.Bytes as Bytes-import qualified Data.Bytes.Parser as Parser-import qualified Data.Bytes.Parser.Latin as Latin import qualified Data.ByteString as B import qualified Data.ByteString.Builder as BB import qualified Data.ByteString.Builder.Fixed as BFB import qualified Data.ByteString.Unsafe as BU+import qualified Data.Bytes as Bytes+import qualified Data.Bytes.Builder.Bounded as BBB+import qualified Data.Bytes.Parser as Parser+import qualified Data.Bytes.Parser.Latin as Latin+import qualified Data.Text as Text () import qualified Data.Text.Builder.Fixed as TFB import qualified Data.Text.IO as TIO import qualified Data.Text.Lazy.Builder as TBuilder import qualified Data.Text.Short.Unsafe as TS-import qualified Data.Text as Text () import qualified GHC.Prim.Compat as Compat import qualified GHC.Word.Compat as Compat @@ -108,51 +115,60 @@ import qualified Data.Aeson.Key as AK #endif --- | A 48-bit MAC address. Do not use the data constructor for this--- type. It is not considered part of the stable API, and it--- allows you to construct invalid MAC addresses.+{- | A 48-bit MAC address. Do not use the data constructor for this+ type. It is not considered part of the stable API, and it+ allows you to construct invalid MAC addresses.+-} newtype Mac = Mac Word64- deriving stock (Eq,Ord,Generic,Ix,Data)+ deriving stock (Eq, Ord, Generic, Ix, Data) deriving newtype (Hashable) instance NFData Mac +{- $setup --- $setup------ These are here to get doctest's property checking to work------ >>> :set -XOverloadedStrings--- >>> import Test.QuickCheck (Arbitrary(..),CoArbitrary(..),coarbitraryEnum)--- >>> import qualified Data.Text as Text (pack)--- >>> import qualified Data.Text.IO as T--- >>> import qualified Data.ByteString.Char8 as BC--- >>> import qualified Data.ByteString as B--- >>> import qualified Data.Bytes.Text.Ascii as Ascii--- >>> import qualified Net.Mac as Mac--- >>> import qualified Arithmetic.Nat as Nat--- >>> import qualified Data.Attoparsec.Text as AT--- >>> import qualified Data.Bytes.Builder.Bounded as BBB--- >>> import Net.Mac (Mac(Mac))--- >>> instance Arbitrary Mac where { arbitrary = fmap (Mac . (0xFFFFFFFFFFFF .&.)) arbitrary }--- >>> instance CoArbitrary Mac where { coarbitrary = coarbitraryEnum }+These are here to get doctest's property checking to work --- | Construct a 'Mac' address from a 'Word64'. Only the lower--- 48 bits are used.+>>> :set -XOverloadedStrings+>>> import Test.QuickCheck (Arbitrary(..),CoArbitrary(..),coarbitraryEnum)+>>> import qualified Data.Text as Text (pack)+>>> import qualified Data.Text.IO as T+>>> import qualified Data.ByteString.Char8 as BC+>>> import qualified Data.ByteString as B+>>> import qualified Data.Bytes.Text.Ascii as Ascii+>>> import qualified Net.Mac as Mac+>>> import qualified Arithmetic.Nat as Nat+>>> import qualified Data.Attoparsec.Text as AT+>>> import qualified Data.Bytes.Builder.Bounded as BBB+>>> import Net.Mac (Mac(Mac))+>>> instance Arbitrary Mac where { arbitrary = fmap (Mac . (0xFFFFFFFFFFFF .&.)) arbitrary }+>>> instance CoArbitrary Mac where { coarbitrary = coarbitraryEnum }+-}++{- | Construct a 'Mac' address from a 'Word64'. Only the lower+ 48 bits are used.+-} mac :: Word64 -> Mac mac w = Mac (w .&. 0xFFFFFFFFFFFF) -- | Create a 'Mac' address from six octets. fromOctets :: Word8 -> Word8 -> Word8 -> Word8 -> Word8 -> Word8 -> Mac-fromOctets a b c d e f = Mac $ unsafeWord48FromOctets- (fromIntegral a) (fromIntegral b) (fromIntegral c)- (fromIntegral d) (fromIntegral e) (fromIntegral f)+fromOctets a b c d e f =+ Mac $+ unsafeWord48FromOctets+ (fromIntegral a)+ (fromIntegral b)+ (fromIntegral c)+ (fromIntegral d)+ (fromIntegral e)+ (fromIntegral f) --- | Convert a 'Mac' address to the six octets that make it up.--- This function and 'fromOctets' are inverses:------ prop> m == (let (a,b,c,d,e,f) = Mac.toOctets m in Mac.fromOctets a b c d e f)-toOctets :: Mac -> (Word8,Word8,Word8,Word8,Word8,Word8)+{- | Convert a 'Mac' address to the six octets that make it up.+ This function and 'fromOctets' are inverses:++ prop> m == (let (a,b,c,d,e,f) = Mac.toOctets m in Mac.fromOctets a b c d e f)+-}+toOctets :: Mac -> (Word8, Word8, Word8, Word8, Word8, Word8) toOctets (Mac w) = ( fromIntegral $ unsafeShiftR w 40 , fromIntegral $ unsafeShiftR w 32@@ -162,35 +178,40 @@ , fromIntegral w ) --- | This function is deprecated. It will be renamed in a future release--- since the name is misleading.+{- | This function is deprecated. It will be renamed in a future release+ since the name is misleading.+-} decodeBytes :: ByteString -> Maybe Mac {-# DEPRECATED decodeBytes "Prefer decodeOctets" #-} decodeBytes = decodeOctets --- | Decode a 'Mac' address from a 'ByteString'. Each byte is interpreted--- as an octet of the 'Mac' address. Consequently, 'ByteString's--- of length 6 successfully decode, and all other 'ByteString's fail--- to decode.------ >>> Mac.decodeOctets (B.pack [0x6B,0x47,0x18,0x90,0x55,0xC3])--- Just (mac 0x6b47189055c3)--- >>> Mac.decodeOctets (B.replicate 6 0x3A)--- Just (mac 0x3a3a3a3a3a3a)--- >>> Mac.decodeOctets (B.replicate 7 0x3A)--- Nothing------ Note that the octets are interpreted in a big-endian fashion.+{- | Decode a 'Mac' address from a 'ByteString'. Each byte is interpreted+ as an octet of the 'Mac' address. Consequently, 'ByteString's+ of length 6 successfully decode, and all other 'ByteString's fail+ to decode.++ >>> Mac.decodeOctets (B.pack [0x6B,0x47,0x18,0x90,0x55,0xC3])+ Just (mac 0x6b47189055c3)+ >>> Mac.decodeOctets (B.replicate 6 0x3A)+ Just (mac 0x3a3a3a3a3a3a)+ >>> Mac.decodeOctets (B.replicate 7 0x3A)+ Nothing++ Note that the octets are interpreted in a big-endian fashion.+-} decodeOctets :: ByteString -> Maybe Mac-decodeOctets bs = if B.length bs == 6- then Just $ fromOctets- (BU.unsafeIndex bs 0)- (BU.unsafeIndex bs 1)- (BU.unsafeIndex bs 2)- (BU.unsafeIndex bs 3)- (BU.unsafeIndex bs 4)- (BU.unsafeIndex bs 5)- else Nothing+decodeOctets bs =+ if B.length bs == 6+ then+ Just $+ fromOctets+ (BU.unsafeIndex bs 0)+ (BU.unsafeIndex bs 1)+ (BU.unsafeIndex bs 2)+ (BU.unsafeIndex bs 3)+ (BU.unsafeIndex bs 4)+ (BU.unsafeIndex bs 5)+ else Nothing rightToMaybe :: Either a b -> Maybe b rightToMaybe = either (const Nothing) Just@@ -198,21 +219,23 @@ c2w :: Char -> Word8 c2w = fromIntegral . ord --- | Encode a 'Mac' address using the default 'MacCodec' 'defCodec'.------ >>> T.putStrLn (Mac.encode (Mac 0xA47F247AB423))--- a4:7f:24:7a:b4:23+{- | Encode a 'Mac' address using the default 'MacCodec' 'defCodec'.++ >>> T.putStrLn (Mac.encode (Mac 0xA47F247AB423))+ a4:7f:24:7a:b4:23+-} encode :: Mac -> Text encode = encodeWith defCodec --- | Encode a 'Mac' address using the given 'MacCodec'.------ >>> m = Mac 0xA47F247AB423--- >>> T.putStrLn $ Mac.encodeWith Mac.defCodec m--- a4:7f:24:7a:b4:23------ >>> T.putStrLn $ Mac.encodeWith (Mac.MacCodec (Mac.MacGroupingTriples '-') True) m--- A47-F24-7AB-423+{- | Encode a 'Mac' address using the given 'MacCodec'.++ >>> m = Mac 0xA47F247AB423+ >>> T.putStrLn $ Mac.encodeWith Mac.defCodec m+ a4:7f:24:7a:b4:23++ >>> T.putStrLn $ Mac.encodeWith (Mac.MacCodec (Mac.MacGroupingTriples '-') True) m+ A47-F24-7AB-423+-} encodeWith :: MacCodec -> Mac -> Text encodeWith (MacCodec g u) m = case g of MacGroupingNoSeparator -> case u of@@ -221,7 +244,7 @@ MacGroupingPairs c -> case u of True -> TFB.run (fixedBuilderPairs TFB.word8HexFixedUpper) (Pair c m) False -> TFB.run (fixedBuilderPairs TFB.word8HexFixedLower) (Pair c m)- -- withCasedBuilder u $ \bw8 -> TFB.run (fixedBuilderPairs bw8) (Pair c m)+ -- withCasedBuilder u $ \bw8 -> TFB.run (fixedBuilderPairs bw8) (Pair c m) MacGroupingTriples c -> case u of True -> TFB.run (fixedBuilderTriples TFB.word12HexFixedUpper) (Pair c m) False -> TFB.run (fixedBuilderTriples TFB.word12HexFixedLower) (Pair c m)@@ -229,23 +252,25 @@ True -> TFB.run (fixedBuilderQuadruples TFB.word8HexFixedUpper) (Pair c m) False -> TFB.run (fixedBuilderQuadruples TFB.word8HexFixedLower) (Pair c m) --- | Decode a 'Mac' address using the default 'MacCodec' 'defCodec'.------ >>> Mac.decode (Text.pack "a4:7f:24:7a:b4:23")--- Just (mac 0xa47f247ab423)------ >>> Mac.decode (Text.pack "a47-f24-7ab-423")--- Nothing+{- | Decode a 'Mac' address using the default 'MacCodec' 'defCodec'.++ >>> Mac.decode (Text.pack "a4:7f:24:7a:b4:23")+ Just (mac 0xa47f247ab423)++ >>> Mac.decode (Text.pack "a47-f24-7ab-423")+ Nothing+-} decode :: Text -> Maybe Mac decode = decodeWith defCodec --- | Decode a 'Mac' address from 'Text' using the given 'MacCodec'.------ >>> Mac.decodeWith Mac.defCodec (Text.pack "a4:7f:24:7a:b4:23")--- Just (mac 0xa47f247ab423)------ >>> Mac.decodeWith (Mac.MacCodec Mac.MacGroupingNoSeparator False) (Text.pack "a47f247ab423")--- Just (mac 0xa47f247ab423)+{- | Decode a 'Mac' address from 'Text' using the given 'MacCodec'.++>>> Mac.decodeWith Mac.defCodec (Text.pack "a4:7f:24:7a:b4:23")+Just (mac 0xa47f247ab423)++>>> Mac.decodeWith (Mac.MacCodec Mac.MacGroupingNoSeparator False) (Text.pack "a47f247ab423")+Just (mac 0xa47f247ab423)+-} decodeWith :: MacCodec -> Text -> Maybe Mac decodeWith codec t = rightToMaybe (AT.parseOnly (parserWith codec <* AT.endOfInput) t) @@ -253,25 +278,27 @@ builder :: Mac -> TBuilder.Builder builder = TBuilder.fromText . encode --- | Parse a 'Mac' address using a 'AT.Parser'.------ >>> AT.parseOnly Mac.parser (Text.pack "a4:7f:24:7a:b4:23")--- Right (mac 0xa47f247ab423)------ >>> AT.parseOnly Mac.parser (Text.pack "a47-f24-7ab-423")--- Left "':': Failed reading: satisfy"+{- | Parse a 'Mac' address using a 'AT.Parser'.++ >>> AT.parseOnly Mac.parser (Text.pack "a4:7f:24:7a:b4:23")+ Right (mac 0xa47f247ab423)++ >>> AT.parseOnly Mac.parser (Text.pack "a47-f24-7ab-423")+ Left "':': Failed reading: satisfy"+-} parser :: AT.Parser Mac parser = parserWith defCodec --- | Parser a 'Mac' address using the given 'MacCodec'.------ >>> p1 = Mac.parserWith Mac.defCodec--- >>> AT.parseOnly p1 (Text.pack "a4:7f:24:7a:b4:23")--- Right (mac 0xa47f247ab423)------ >>> p2 = Mac.parserWith (Mac.MacCodec Mac.MacGroupingNoSeparator False)--- >>> AT.parseOnly p2 (Text.pack "a47f247ab423")--- Right (mac 0xa47f247ab423)+{- | Parser a 'Mac' address using the given 'MacCodec'.++ >>> p1 = Mac.parserWith Mac.defCodec+ >>> AT.parseOnly p1 (Text.pack "a4:7f:24:7a:b4:23")+ Right (mac 0xa47f247ab423)++ >>> p2 = Mac.parserWith (Mac.MacCodec Mac.MacGroupingNoSeparator False)+ >>> AT.parseOnly p2 (Text.pack "a47f247ab423")+ Right (mac 0xa47f247ab423)+-} parserWith :: MacCodec -> AT.Parser Mac parserWith (MacCodec g _) = case g of MacGroupingQuadruples c -> parserQuadruples c@@ -279,27 +306,40 @@ MacGroupingPairs c -> parserPairs c MacGroupingNoSeparator -> parserNoSeparator --- | The default 'MacCodec': all characters are lowercase hex, separated by colons into pairs.------ >>> T.putStrLn $ Mac.encodeWith Mac.defCodec (Mac 0xa47f247ab423)--- a4:7f:24:7a:b4:23+{- | The default 'MacCodec': all characters are lowercase hex, separated by colons into pairs.++ >>> T.putStrLn $ Mac.encodeWith Mac.defCodec (Mac 0xa47f247ab423)+ a4:7f:24:7a:b4:23+-} defCodec :: MacCodec defCodec = MacCodec (MacGroupingPairs ':') False parserQuadruples :: Char -> AT.Parser Mac-parserQuadruples s = fromOctets- <$> parseTwoHex <*> parseTwoHex <* AT.char s- <*> parseTwoHex <*> parseTwoHex <* AT.char s- <*> parseTwoHex <*> parseTwoHex+parserQuadruples s =+ fromOctets+ <$> parseTwoHex+ <*> parseTwoHex+ <* AT.char s+ <*> parseTwoHex+ <*> parseTwoHex+ <* AT.char s+ <*> parseTwoHex+ <*> parseTwoHex parserPairs :: Char -> AT.Parser Mac-parserPairs s = fromOctets- <$> parseTwoHex <* AT.char s- <*> parseTwoHex <* AT.char s- <*> parseTwoHex <* AT.char s- <*> parseTwoHex <* AT.char s- <*> parseTwoHex <* AT.char s- <*> parseTwoHex+parserPairs s =+ fromOctets+ <$> parseTwoHex+ <* AT.char s+ <*> parseTwoHex+ <* AT.char s+ <*> parseTwoHex+ <* AT.char s+ <*> parseTwoHex+ <* AT.char s+ <*> parseTwoHex+ <* AT.char s+ <*> parseTwoHex parserTriples :: Char -> AT.Parser Mac parserTriples s = do@@ -318,22 +358,24 @@ a10 <- parseOneHex a11 <- parseOneHex a12 <- parseOneHex- return $ fromOctets- (unsafeShiftL a1 4 + a2)- (unsafeShiftL a3 4 + a4)- (unsafeShiftL a5 4 + a6)- (unsafeShiftL a7 4 + a8)- (unsafeShiftL a9 4 + a10)- (unsafeShiftL a11 4 + a12)+ return $+ fromOctets+ (unsafeShiftL a1 4 + a2)+ (unsafeShiftL a3 4 + a4)+ (unsafeShiftL a5 4 + a6)+ (unsafeShiftL a7 4 + a8)+ (unsafeShiftL a9 4 + a10)+ (unsafeShiftL a11 4 + a12) parserNoSeparator :: AT.Parser Mac-parserNoSeparator = fromOctets- <$> parseTwoHex- <*> parseTwoHex- <*> parseTwoHex- <*> parseTwoHex- <*> parseTwoHex- <*> parseTwoHex+parserNoSeparator =+ fromOctets+ <$> parseTwoHex+ <*> parseTwoHex+ <*> parseTwoHex+ <*> parseTwoHex+ <*> parseTwoHex+ <*> parseTwoHex parseTwoHex :: AT.Parser Word8 parseTwoHex = do@@ -347,7 +389,8 @@ | w >= 65 && w <= 70 = return (w - 55) | w >= 97 && w <= 102 = return (w - 87) | otherwise = a- where w = c2w c+ where+ w = c2w c parseOneHex :: AT.Parser Word8 parseOneHex = AT.anyChar >>= parseCharHex@@ -362,50 +405,50 @@ fixedBuilderTriples :: TFB.Builder Word12 -> TFB.Builder Pair fixedBuilderTriples tripBuilder =- TFB.contramapBuilder (word12At 36 . pairMac) tripBuilder- <> TFB.contramapBuilder pairSep TFB.charBmp- <> TFB.contramapBuilder (word12At 24 . pairMac) tripBuilder- <> TFB.contramapBuilder pairSep TFB.charBmp- <> TFB.contramapBuilder (word12At 12 . pairMac) tripBuilder- <> TFB.contramapBuilder pairSep TFB.charBmp- <> TFB.contramapBuilder (word12At 0 . pairMac) tripBuilder+ TFB.contramapBuilder (word12At 36 . pairMac) tripBuilder+ <> TFB.contramapBuilder pairSep TFB.charBmp+ <> TFB.contramapBuilder (word12At 24 . pairMac) tripBuilder+ <> TFB.contramapBuilder pairSep TFB.charBmp+ <> TFB.contramapBuilder (word12At 12 . pairMac) tripBuilder+ <> TFB.contramapBuilder pairSep TFB.charBmp+ <> TFB.contramapBuilder (word12At 0 . pairMac) tripBuilder {-# INLINE fixedBuilderTriples #-} fixedBuilderNoSeparator :: TFB.Builder Word8 -> TFB.Builder Mac fixedBuilderNoSeparator hexBuilder =- TFB.contramapBuilder (word8At 40) hexBuilder- <> TFB.contramapBuilder (word8At 32) hexBuilder- <> TFB.contramapBuilder (word8At 24) hexBuilder- <> TFB.contramapBuilder (word8At 16) hexBuilder- <> TFB.contramapBuilder (word8At 8) hexBuilder- <> TFB.contramapBuilder (word8At 0) hexBuilder+ TFB.contramapBuilder (word8At 40) hexBuilder+ <> TFB.contramapBuilder (word8At 32) hexBuilder+ <> TFB.contramapBuilder (word8At 24) hexBuilder+ <> TFB.contramapBuilder (word8At 16) hexBuilder+ <> TFB.contramapBuilder (word8At 8) hexBuilder+ <> TFB.contramapBuilder (word8At 0) hexBuilder {-# INLINE fixedBuilderNoSeparator #-} fixedBuilderQuadruples :: TFB.Builder Word8 -> TFB.Builder Pair fixedBuilderQuadruples pairBuilder =- TFB.contramapBuilder (word8At 40 . pairMac) pairBuilder- <> TFB.contramapBuilder (word8At 32 . pairMac) pairBuilder- <> TFB.contramapBuilder pairSep TFB.charBmp- <> TFB.contramapBuilder (word8At 24 . pairMac) pairBuilder- <> TFB.contramapBuilder (word8At 16 . pairMac) pairBuilder- <> TFB.contramapBuilder pairSep TFB.charBmp- <> TFB.contramapBuilder (word8At 8 . pairMac) pairBuilder- <> TFB.contramapBuilder (word8At 0 . pairMac) pairBuilder+ TFB.contramapBuilder (word8At 40 . pairMac) pairBuilder+ <> TFB.contramapBuilder (word8At 32 . pairMac) pairBuilder+ <> TFB.contramapBuilder pairSep TFB.charBmp+ <> TFB.contramapBuilder (word8At 24 . pairMac) pairBuilder+ <> TFB.contramapBuilder (word8At 16 . pairMac) pairBuilder+ <> TFB.contramapBuilder pairSep TFB.charBmp+ <> TFB.contramapBuilder (word8At 8 . pairMac) pairBuilder+ <> TFB.contramapBuilder (word8At 0 . pairMac) pairBuilder {-# INLINE fixedBuilderQuadruples #-} fixedBuilderPairs :: TFB.Builder Word8 -> TFB.Builder Pair fixedBuilderPairs pairBuilder =- TFB.contramapBuilder (word8At 40 . pairMac) pairBuilder- <> TFB.contramapBuilder pairSep TFB.charBmp- <> TFB.contramapBuilder (word8At 32 . pairMac) pairBuilder- <> TFB.contramapBuilder pairSep TFB.charBmp- <> TFB.contramapBuilder (word8At 24 . pairMac) pairBuilder- <> TFB.contramapBuilder pairSep TFB.charBmp- <> TFB.contramapBuilder (word8At 16 . pairMac) pairBuilder- <> TFB.contramapBuilder pairSep TFB.charBmp- <> TFB.contramapBuilder (word8At 8 . pairMac) pairBuilder- <> TFB.contramapBuilder pairSep TFB.charBmp- <> TFB.contramapBuilder (word8At 0 . pairMac) pairBuilder+ TFB.contramapBuilder (word8At 40 . pairMac) pairBuilder+ <> TFB.contramapBuilder pairSep TFB.charBmp+ <> TFB.contramapBuilder (word8At 32 . pairMac) pairBuilder+ <> TFB.contramapBuilder pairSep TFB.charBmp+ <> TFB.contramapBuilder (word8At 24 . pairMac) pairBuilder+ <> TFB.contramapBuilder pairSep TFB.charBmp+ <> TFB.contramapBuilder (word8At 16 . pairMac) pairBuilder+ <> TFB.contramapBuilder pairSep TFB.charBmp+ <> TFB.contramapBuilder (word8At 8 . pairMac) pairBuilder+ <> TFB.contramapBuilder pairSep TFB.charBmp+ <> TFB.contramapBuilder (word8At 0 . pairMac) pairBuilder {-# INLINE fixedBuilderPairs #-} word8At :: Int -> Mac -> Word8@@ -416,92 +459,89 @@ word12At i (Mac w) = fromIntegral (unsafeShiftR w i) {-# INLINE word12At #-} --- | Encode a 'Mac' address using the default 'MacCodec' 'defCodec'.------ >>> BC.putStrLn (Mac.encodeUtf8 (Mac.mac 0x64255A0F2C47))--- 64:25:5a:0f:2c:47+{- | Encode a 'Mac' address using the default 'MacCodec' 'defCodec'.++ >>> BC.putStrLn (Mac.encodeUtf8 (Mac.mac 0x64255A0F2C47))+ 64:25:5a:0f:2c:47+-} encodeUtf8 :: Mac -> ByteString encodeUtf8 = encodeWithUtf8 defCodec --- | Lenient decoding of MAC address that accepts lowercase, uppercase,--- and any kind of separator.------ >>> Mac.decodeUtf8 "A2:DE:AD:BE:EF:67"--- Just (mac 0xa2deadbeef67)--- >>> Mac.decodeUtf8 "13-a2-fe-a4-17-96"--- Just (mac 0x13a2fea41796)--- >>> Mac.decodeUtf8 "0A42.47BA.67C2"--- Just (mac 0x0a4247ba67c2)+{- | Lenient decoding of MAC address that accepts lowercase, uppercase,+ and any kind of separator.++ >>> Mac.decodeUtf8 "A2:DE:AD:BE:EF:67"+ Just (mac 0xa2deadbeef67)+ >>> Mac.decodeUtf8 "13-a2-fe-a4-17-96"+ Just (mac 0x13a2fea41796)+ >>> Mac.decodeUtf8 "0A42.47BA.67C2"+ Just (mac 0x0a4247ba67c2)+-} decodeUtf8 :: ByteString -> Maybe Mac decodeUtf8 = decodeLenientUtf8 --- | Decode a 'ByteString' as a 'Mac' address using the given 'MacCodec'.------ >>> Mac.decodeWithUtf8 Mac.defCodec (BC.pack "64:25:5a:0f:2c:47")--- Just (mac 0x64255a0f2c47)------ >>> Mac.decodeWithUtf8 (Mac.MacCodec Mac.MacGroupingNoSeparator False) (BC.pack "64255a0f2c47")--- Just (mac 0x64255a0f2c47)+{- | Decode a 'ByteString' as a 'Mac' address using the given 'MacCodec'.++ >>> Mac.decodeWithUtf8 Mac.defCodec (BC.pack "64:25:5a:0f:2c:47")+ Just (mac 0x64255a0f2c47)++ >>> Mac.decodeWithUtf8 (Mac.MacCodec Mac.MacGroupingNoSeparator False) (BC.pack "64255a0f2c47")+ Just (mac 0x64255a0f2c47)+-} decodeWithUtf8 :: MacCodec -> ByteString -> Maybe Mac decodeWithUtf8 codec bs = rightToMaybe (AB.parseOnly (parserWithUtf8 codec <* AB.endOfInput) bs) decodeLenientUtf8 :: ByteString -> Maybe Mac decodeLenientUtf8 bs = rightToMaybe (AB.parseOnly (parserLenientUtf8 <* AB.endOfInput) bs) --- | Encode a 'Mac' address as colon-separated hexadecimal octets,--- preferring lowercase for alphabetical characters.+{- | Encode a 'Mac' address as colon-separated hexadecimal octets,+ preferring lowercase for alphabetical characters.+-} encodeShort :: Mac -> ShortText encodeShort !m = case BBB.run Nat.constant (boundedBuilderUtf8 m) of ByteArray x -> TS.fromShortByteStringUnsafe (SBS x) --- | Encode a 'Mac' address as colon-separated hexadecimal octets,--- preferring lowercase for alphabetical characters.------ >>> BBB.run Nat.constant $ Mac.boundedBuilderUtf8 $ Mac.mac 0xDEADBEEF1609--- [0x64, 0x65, 0x3a, 0x61, 0x64, 0x3a, 0x62, 0x65, 0x3a, 0x65, 0x66, 0x3a, 0x31, 0x36, 0x3a, 0x30, 0x39]+{- | Encode a 'Mac' address as colon-separated hexadecimal octets,+ preferring lowercase for alphabetical characters.++ >>> BBB.run Nat.constant $ Mac.boundedBuilderUtf8 $ Mac.mac 0xDEADBEEF1609+ [0x64, 0x65, 0x3a, 0x61, 0x64, 0x3a, 0x62, 0x65, 0x3a, 0x65, 0x66, 0x3a, 0x31, 0x36, 0x3a, 0x30, 0x39]+-} boundedBuilderUtf8 :: Mac -> BBB.Builder 17 boundedBuilderUtf8 !w = BBB.word8PaddedLowerHex w0- `BBB.append`- BBB.ascii ':'- `BBB.append`- BBB.word8PaddedLowerHex w1- `BBB.append`- BBB.ascii ':'- `BBB.append`- BBB.word8PaddedLowerHex w2- `BBB.append`- BBB.ascii ':'- `BBB.append`- BBB.word8PaddedLowerHex w3- `BBB.append`- BBB.ascii ':'- `BBB.append`- BBB.word8PaddedLowerHex w4- `BBB.append`- BBB.ascii ':'- `BBB.append`- BBB.word8PaddedLowerHex w5- where- (w0,w1,w2,w3,w4,w5) = toOctets w+ `BBB.append` BBB.ascii ':'+ `BBB.append` BBB.word8PaddedLowerHex w1+ `BBB.append` BBB.ascii ':'+ `BBB.append` BBB.word8PaddedLowerHex w2+ `BBB.append` BBB.ascii ':'+ `BBB.append` BBB.word8PaddedLowerHex w3+ `BBB.append` BBB.ascii ':'+ `BBB.append` BBB.word8PaddedLowerHex w4+ `BBB.append` BBB.ascii ':'+ `BBB.append` BBB.word8PaddedLowerHex w5+ where+ (w0, w1, w2, w3, w4, w5) = toOctets w --- | Lenient decoding of MAC address. This--- is case insensitive and allows either @:@ or @-@ as the separator.--- It also allows leading zeroes to be missing.------ >>> Mac.decodeUtf8Bytes (Ascii.fromString "A2:DE:AD:BE:EF:67")--- Just (mac 0xa2deadbeef67)--- >>> Mac.decodeUtf8Bytes (Ascii.fromString "13-a2-FE-A4-17-96")--- Just (mac 0x13a2fea41796)+{- | Lenient decoding of MAC address. This+ is case insensitive and allows either @:@ or @-@ as the separator.+ It also allows leading zeroes to be missing.++ >>> Mac.decodeUtf8Bytes (Ascii.fromString "A2:DE:AD:BE:EF:67")+ Just (mac 0xa2deadbeef67)+ >>> Mac.decodeUtf8Bytes (Ascii.fromString "13-a2-FE-A4-17-96")+ Just (mac 0x13a2fea41796)+-} decodeUtf8Bytes :: Bytes.Bytes -> Maybe Mac decodeUtf8Bytes = Parser.parseBytesMaybe (parserUtf8Bytes ()) --- | Leniently parse UTF-8-encoded 'Bytes' as a 'Mac' address. This--- is case insensitive and allows either @:@ or @-@ as the separator.--- It also allows leading zeroes to be missing.------ >>> Parser.parseBytes (Mac.parserUtf8Bytes ()) (Ascii.fromString "de:ad:BE:EF:1:23")--- Success (Slice {offset = 16, length = 0, value = mac 0xdeadbeef0123})+{- | Leniently parse UTF-8-encoded 'Bytes' as a 'Mac' address. This+ is case insensitive and allows either @:@ or @-@ as the separator.+ It also allows leading zeroes to be missing.++ >>> Parser.parseBytes (Mac.parserUtf8Bytes ()) (Ascii.fromString "de:ad:BE:EF:1:23")+ Success (Slice {offset = 16, length = 0, value = mac 0xdeadbeef0123})+-} parserUtf8Bytes :: e -> Parser.Parser e s Mac parserUtf8Bytes e = do w1 <- Latin.hexWord8 e@@ -522,14 +562,16 @@ pure (fromOctets w1 w2 w3 w4 w5 w6) _ -> Parser.fail e --- | Make a bytestring builder from a 'Mac' address--- using a colon as the separator.+{- | Make a bytestring builder from a 'Mac' address+ using a colon as the separator.+-} builderUtf8 :: Mac -> BB.Builder builderUtf8 = BB.byteString . encodeUtf8 --- | Lenient parser for a 'Mac' address using any character--- as the separator and accepting any digit grouping--- (i.e. @FA:43:B2:C0:0F:99@ or @A065.647B.87FA@).+{- | Lenient parser for a 'Mac' address using any character+ as the separator and accepting any digit grouping+ (i.e. @FA:43:B2:C0:0F:99@ or @A065.647B.87FA@).+-} parserUtf8 :: AB.Parser Mac parserUtf8 = parserLenientUtf8 @@ -555,32 +597,39 @@ a10 <- parseOneHexLenientUtf8 a11 <- parseOneHexLenientUtf8 a12 <- parseOneHexLenientUtf8- return $ fromOctets- (unsafeShiftL a1 4 + a2)- (unsafeShiftL a3 4 + a4)- (unsafeShiftL a5 4 + a6)- (unsafeShiftL a7 4 + a8)- (unsafeShiftL a9 4 + a10)- (unsafeShiftL a11 4 + a12)-+ return $+ fromOctets+ (unsafeShiftL a1 4 + a2)+ (unsafeShiftL a3 4 + a4)+ (unsafeShiftL a5 4 + a6)+ (unsafeShiftL a7 4 + a8)+ (unsafeShiftL a9 4 + a10)+ (unsafeShiftL a11 4 + a12) parserNoSeparatorUtf8 :: AB.Parser Mac-parserNoSeparatorUtf8 = fromOctets- <$> parseTwoHexUtf8- <*> parseTwoHexUtf8- <*> parseTwoHexUtf8- <*> parseTwoHexUtf8- <*> parseTwoHexUtf8- <*> parseTwoHexUtf8+parserNoSeparatorUtf8 =+ fromOctets+ <$> parseTwoHexUtf8+ <*> parseTwoHexUtf8+ <*> parseTwoHexUtf8+ <*> parseTwoHexUtf8+ <*> parseTwoHexUtf8+ <*> parseTwoHexUtf8 parserPairsUtf8 :: Word8 -> AB.Parser Mac-parserPairsUtf8 s = fromOctets- <$> parseTwoHexUtf8 <* ABW.word8 s- <*> parseTwoHexUtf8 <* ABW.word8 s- <*> parseTwoHexUtf8 <* ABW.word8 s- <*> parseTwoHexUtf8 <* ABW.word8 s- <*> parseTwoHexUtf8 <* ABW.word8 s- <*> parseTwoHexUtf8+parserPairsUtf8 s =+ fromOctets+ <$> parseTwoHexUtf8+ <* ABW.word8 s+ <*> parseTwoHexUtf8+ <* ABW.word8 s+ <*> parseTwoHexUtf8+ <* ABW.word8 s+ <*> parseTwoHexUtf8+ <* ABW.word8 s+ <*> parseTwoHexUtf8+ <* ABW.word8 s+ <*> parseTwoHexUtf8 parserTriplesUtf8 :: Word8 -> AB.Parser Mac parserTriplesUtf8 s = do@@ -599,25 +648,33 @@ a10 <- parseOneHexUtf8 a11 <- parseOneHexUtf8 a12 <- parseOneHexUtf8- return $ fromOctets- (unsafeShiftL a1 4 + a2)- (unsafeShiftL a3 4 + a4)- (unsafeShiftL a5 4 + a6)- (unsafeShiftL a7 4 + a8)- (unsafeShiftL a9 4 + a10)- (unsafeShiftL a11 4 + a12)+ return $+ fromOctets+ (unsafeShiftL a1 4 + a2)+ (unsafeShiftL a3 4 + a4)+ (unsafeShiftL a5 4 + a6)+ (unsafeShiftL a7 4 + a8)+ (unsafeShiftL a9 4 + a10)+ (unsafeShiftL a11 4 + a12) parserQuadruplesUtf8 :: Word8 -> AB.Parser Mac-parserQuadruplesUtf8 s = fromOctets- <$> parseTwoHexUtf8 <*> parseTwoHexUtf8 <* ABW.word8 s- <*> parseTwoHexUtf8 <*> parseTwoHexUtf8 <* ABW.word8 s- <*> parseTwoHexUtf8 <*> parseTwoHexUtf8+parserQuadruplesUtf8 s =+ fromOctets+ <$> parseTwoHexUtf8+ <*> parseTwoHexUtf8+ <* ABW.word8 s+ <*> parseTwoHexUtf8+ <*> parseTwoHexUtf8+ <* ABW.word8 s+ <*> parseTwoHexUtf8+ <*> parseTwoHexUtf8 parseOneHexUtf8 :: AB.Parser Word8 parseOneHexUtf8 = ABW.anyWord8 >>= parseWord8Hex --- | Parse a single hexidecimal character. This will skip--- at most one character to do this.+{- | Parse a single hexidecimal character. This will skip+ at most one character to do this.+-} parseOneHexLenientUtf8 :: AB.Parser Word8 parseOneHexLenientUtf8 = do a <- ABW.anyWord8@@ -631,9 +688,10 @@ b <- ABW.anyWord8 >>= parseWord8Hex return (unsafeShiftL a 4 + b) --- | Kind of a confusing type signature. The Word8 that stands--- alone represented an ascii-encoded value. The others actually--- describes the numbers that would be decoded from this value.+{- | Kind of a confusing type signature. The Word8 that stands+ alone represented an ascii-encoded value. The others actually+ describes the numbers that would be decoded from this value.+-} tryParseWord8Hex :: AB.Parser Word8 -> Word8 -> AB.Parser Word8 tryParseWord8Hex a w | w >= 48 && w <= 57 = return (w - 48)@@ -644,14 +702,15 @@ parseWord8Hex :: Word8 -> AB.Parser Word8 parseWord8Hex = tryParseWord8Hex (fail "invalid hexadecimal character") --- | Encode a 'Mac' address as a 'ByteString' using the given 'MacCodec'.------ >>> m = Mac 0xA47F247AB423--- >>> BC.putStrLn $ Mac.encodeWithUtf8 Mac.defCodec m--- a4:7f:24:7a:b4:23------ >>> BC.putStrLn $ Mac.encodeWithUtf8 (Mac.MacCodec (Mac.MacGroupingTriples '-') True) m--- A47-F24-7AB-423+{- | Encode a 'Mac' address as a 'ByteString' using the given 'MacCodec'.++ >>> m = Mac 0xA47F247AB423+ >>> BC.putStrLn $ Mac.encodeWithUtf8 Mac.defCodec m+ a4:7f:24:7a:b4:23++ >>> BC.putStrLn $ Mac.encodeWithUtf8 (Mac.MacCodec (Mac.MacGroupingTriples '-') True) m+ A47-F24-7AB-423+-} encodeWithUtf8 :: MacCodec -> Mac -> ByteString encodeWithUtf8 (MacCodec g u) m = case g of MacGroupingNoSeparator -> case u of@@ -660,7 +719,7 @@ MacGroupingPairs c -> case u of True -> BFB.run (fixedBuilderPairsUtf8 BFB.word8HexFixedUpper) (PairUtf8 (c2w c) m) False -> BFB.run (fixedBuilderPairsUtf8 BFB.word8HexFixedLower) (PairUtf8 (c2w c) m)- -- withCasedBuilder u $ \bw8 -> BFB.run (fixedBuilderPairs bw8) (Pair c m)+ -- withCasedBuilder u $ \bw8 -> BFB.run (fixedBuilderPairs bw8) (Pair c m) MacGroupingTriples c -> case u of True -> BFB.run (fixedBuilderTriplesUtf8 BFB.word12HexFixedUpper) (PairUtf8 (c2w c) m) False -> BFB.run (fixedBuilderTriplesUtf8 BFB.word12HexFixedLower) (PairUtf8 (c2w c) m)@@ -675,50 +734,50 @@ fixedBuilderTriplesUtf8 :: BFB.Builder Word12 -> BFB.Builder PairUtf8 fixedBuilderTriplesUtf8 tripBuilder =- BFB.contramapBuilder (word12AtUtf8 36 . pairMacUtf8) tripBuilder- <> BFB.contramapBuilder pairSepUtf8 BFB.word8- <> BFB.contramapBuilder (word12AtUtf8 24 . pairMacUtf8) tripBuilder- <> BFB.contramapBuilder pairSepUtf8 BFB.word8- <> BFB.contramapBuilder (word12AtUtf8 12 . pairMacUtf8) tripBuilder- <> BFB.contramapBuilder pairSepUtf8 BFB.word8- <> BFB.contramapBuilder (word12AtUtf8 0 . pairMacUtf8) tripBuilder+ BFB.contramapBuilder (word12AtUtf8 36 . pairMacUtf8) tripBuilder+ <> BFB.contramapBuilder pairSepUtf8 BFB.word8+ <> BFB.contramapBuilder (word12AtUtf8 24 . pairMacUtf8) tripBuilder+ <> BFB.contramapBuilder pairSepUtf8 BFB.word8+ <> BFB.contramapBuilder (word12AtUtf8 12 . pairMacUtf8) tripBuilder+ <> BFB.contramapBuilder pairSepUtf8 BFB.word8+ <> BFB.contramapBuilder (word12AtUtf8 0 . pairMacUtf8) tripBuilder {-# INLINE fixedBuilderTriplesUtf8 #-} fixedBuilderQuadruplesUtf8 :: BFB.Builder Word8 -> BFB.Builder PairUtf8 fixedBuilderQuadruplesUtf8 pairBuilder =- BFB.contramapBuilder (word8AtUtf8 40 . pairMacUtf8) pairBuilder- <> BFB.contramapBuilder (word8AtUtf8 32 . pairMacUtf8) pairBuilder- <> BFB.contramapBuilder pairSepUtf8 BFB.word8- <> BFB.contramapBuilder (word8AtUtf8 24 . pairMacUtf8) pairBuilder- <> BFB.contramapBuilder (word8AtUtf8 16 . pairMacUtf8) pairBuilder- <> BFB.contramapBuilder pairSepUtf8 BFB.word8- <> BFB.contramapBuilder (word8AtUtf8 8 . pairMacUtf8) pairBuilder- <> BFB.contramapBuilder (word8AtUtf8 0 . pairMacUtf8) pairBuilder+ BFB.contramapBuilder (word8AtUtf8 40 . pairMacUtf8) pairBuilder+ <> BFB.contramapBuilder (word8AtUtf8 32 . pairMacUtf8) pairBuilder+ <> BFB.contramapBuilder pairSepUtf8 BFB.word8+ <> BFB.contramapBuilder (word8AtUtf8 24 . pairMacUtf8) pairBuilder+ <> BFB.contramapBuilder (word8AtUtf8 16 . pairMacUtf8) pairBuilder+ <> BFB.contramapBuilder pairSepUtf8 BFB.word8+ <> BFB.contramapBuilder (word8AtUtf8 8 . pairMacUtf8) pairBuilder+ <> BFB.contramapBuilder (word8AtUtf8 0 . pairMacUtf8) pairBuilder {-# INLINE fixedBuilderQuadruplesUtf8 #-} fixedBuilderPairsUtf8 :: BFB.Builder Word8 -> BFB.Builder PairUtf8 fixedBuilderPairsUtf8 pairBuilder =- BFB.contramapBuilder (word8AtUtf8 40 . pairMacUtf8) pairBuilder- <> BFB.contramapBuilder pairSepUtf8 BFB.word8- <> BFB.contramapBuilder (word8AtUtf8 32 . pairMacUtf8) pairBuilder- <> BFB.contramapBuilder pairSepUtf8 BFB.word8- <> BFB.contramapBuilder (word8AtUtf8 24 . pairMacUtf8) pairBuilder- <> BFB.contramapBuilder pairSepUtf8 BFB.word8- <> BFB.contramapBuilder (word8AtUtf8 16 . pairMacUtf8) pairBuilder- <> BFB.contramapBuilder pairSepUtf8 BFB.word8- <> BFB.contramapBuilder (word8AtUtf8 8 . pairMacUtf8) pairBuilder- <> BFB.contramapBuilder pairSepUtf8 BFB.word8- <> BFB.contramapBuilder (word8AtUtf8 0 . pairMacUtf8) pairBuilder+ BFB.contramapBuilder (word8AtUtf8 40 . pairMacUtf8) pairBuilder+ <> BFB.contramapBuilder pairSepUtf8 BFB.word8+ <> BFB.contramapBuilder (word8AtUtf8 32 . pairMacUtf8) pairBuilder+ <> BFB.contramapBuilder pairSepUtf8 BFB.word8+ <> BFB.contramapBuilder (word8AtUtf8 24 . pairMacUtf8) pairBuilder+ <> BFB.contramapBuilder pairSepUtf8 BFB.word8+ <> BFB.contramapBuilder (word8AtUtf8 16 . pairMacUtf8) pairBuilder+ <> BFB.contramapBuilder pairSepUtf8 BFB.word8+ <> BFB.contramapBuilder (word8AtUtf8 8 . pairMacUtf8) pairBuilder+ <> BFB.contramapBuilder pairSepUtf8 BFB.word8+ <> BFB.contramapBuilder (word8AtUtf8 0 . pairMacUtf8) pairBuilder {-# INLINE fixedBuilderPairsUtf8 #-} fixedBuilderNoSeparatorUtf8 :: BFB.Builder Word8 -> BFB.Builder Mac fixedBuilderNoSeparatorUtf8 hexBuilder =- BFB.contramapBuilder (word8AtUtf8 40) hexBuilder- <> BFB.contramapBuilder (word8AtUtf8 32) hexBuilder- <> BFB.contramapBuilder (word8AtUtf8 24) hexBuilder- <> BFB.contramapBuilder (word8AtUtf8 16) hexBuilder- <> BFB.contramapBuilder (word8AtUtf8 8) hexBuilder- <> BFB.contramapBuilder (word8AtUtf8 0) hexBuilder+ BFB.contramapBuilder (word8AtUtf8 40) hexBuilder+ <> BFB.contramapBuilder (word8AtUtf8 32) hexBuilder+ <> BFB.contramapBuilder (word8AtUtf8 24) hexBuilder+ <> BFB.contramapBuilder (word8AtUtf8 16) hexBuilder+ <> BFB.contramapBuilder (word8AtUtf8 8) hexBuilder+ <> BFB.contramapBuilder (word8AtUtf8 0) hexBuilder {-# INLINE fixedBuilderNoSeparatorUtf8 #-} word8AtUtf8 :: Int -> Mac -> Word8@@ -729,76 +788,88 @@ word12AtUtf8 i (Mac w) = fromIntegral (unsafeShiftR w i) {-# INLINE word12AtUtf8 #-} --- | This only preserves the lower 6 bytes of the 8-byte word that backs a mac address.--- It runs slower than it would if it used a full 8-byte word, but it consumes less--- space. When storing millions of mac addresses, this is a good trade to make. When--- storing a small number of mac address, it might be preferable to make a primitive--- array of 'Word64' instead and use the mac address data constructor to coerce between--- the two.+{- | This only preserves the lower 6 bytes of the 8-byte word that backs a mac address.+It runs slower than it would if it used a full 8-byte word, but it consumes less+space. When storing millions of mac addresses, this is a good trade to make. When+storing a small number of mac address, it might be preferable to make a primitive+array of 'Word64' instead and use the mac address data constructor to coerce between+the two.+-} instance Prim Mac where sizeOf# _ = 6# alignment# _ = 2#- indexByteArray# arr i0 = macFromWord16#- (Compat.indexWord16Array# arr i)- (Compat.indexWord16Array# arr (i +# 1#))- (Compat.indexWord16Array# arr (i +# 2#))- where !i = 3# *# i0+ indexByteArray# arr i0 =+ macFromWord16#+ (Compat.indexWord16Array# arr i)+ (Compat.indexWord16Array# arr (i +# 1#))+ (Compat.indexWord16Array# arr (i +# 2#))+ where+ !i = 3# *# i0 readByteArray# arr i0 s0 = case Compat.readWord16Array# arr i s0 of (# s1, a #) -> case Compat.readWord16Array# arr (i +# 1#) s1 of (# s2, b #) -> case Compat.readWord16Array# arr (i +# 2#) s2 of (# s3, c #) -> (# s3, macFromWord16# a b c #)- where !i = 3# *# i0+ where+ !i = 3# *# i0 writeByteArray# arr i0 m s0 = case Compat.writeWord16Array# arr i (macToWord16A# m) s0 of s1 -> case Compat.writeWord16Array# arr (i +# 1#) (macToWord16B# m) s1 of s2 -> Compat.writeWord16Array# arr (i +# 2#) (macToWord16C# m) s2- where !i = 3# *# i0- indexOffAddr# arr i0 = macFromWord16#- (Compat.indexWord16OffAddr# arr i)- (Compat.indexWord16OffAddr# arr (i +# 1#))- (Compat.indexWord16OffAddr# arr (i +# 2#))- where !i = 3# *# i0+ where+ !i = 3# *# i0+ indexOffAddr# arr i0 =+ macFromWord16#+ (Compat.indexWord16OffAddr# arr i)+ (Compat.indexWord16OffAddr# arr (i +# 1#))+ (Compat.indexWord16OffAddr# arr (i +# 2#))+ where+ !i = 3# *# i0 readOffAddr# arr i0 s0 = case Compat.readWord16OffAddr# arr i s0 of (# s1, a #) -> case Compat.readWord16OffAddr# arr (i +# 1#) s1 of (# s2, b #) -> case Compat.readWord16OffAddr# arr (i +# 2#) s2 of (# s3, c #) -> (# s3, macFromWord16# a b c #)- where !i = 3# *# i0+ where+ !i = 3# *# i0 writeOffAddr# arr i0 m s0 = case Compat.writeWord16OffAddr# arr i (macToWord16A# m) s0 of s1 -> case Compat.writeWord16OffAddr# arr (i +# 1#) (macToWord16B# m) s1 of s2 -> Compat.writeWord16OffAddr# arr (i +# 2#) (macToWord16C# m) s2- where !i = 3# *# i0+ where+ !i = 3# *# i0 setByteArray# arr# i# len# ident = go 0#- where- go ix# s0 = if isTrue# (ix# <# len#)+ where+ go ix# s0 =+ if isTrue# (ix# <# len#) then case writeByteArray# arr# (i# +# ix#) ident s0 of s1 -> go (ix# +# 1#) s1 else s0 setOffAddr# addr# i# len# ident = go 0#- where- go ix# s0 = if isTrue# (ix# <# len#)+ where+ go ix# s0 =+ if isTrue# (ix# <# len#) then case writeOffAddr# addr# (i# +# ix#) ident s0 of s1 -> go (ix# +# 1#) s1 else s0 macToWord16A# :: Mac -> Word#-{-# inline macToWord16A# #-}+{-# INLINE macToWord16A# #-} macToWord16A# (Mac w) = case word64ToWord16 (unsafeShiftR w 32) of Compat.W16# x -> x macToWord16B# :: Mac -> Word#-{-# inline macToWord16B# #-}+{-# INLINE macToWord16B# #-} macToWord16B# (Mac w) = case word64ToWord16 (unsafeShiftR w 16) of Compat.W16# x -> x macToWord16C# :: Mac -> Word#-{-# inline macToWord16C# #-}+{-# INLINE macToWord16C# #-} macToWord16C# (Mac w) = case word64ToWord16 w of Compat.W16# x -> x macFromWord16# :: Word# -> Word# -> Word# -> Mac-macFromWord16# a b c = Mac- $ (unsafeShiftL (word16ToWord64 (Compat.W16# a)) 32)- .|. (unsafeShiftL (word16ToWord64 (Compat.W16# b)) 16)- .|. (word16ToWord64 (Compat.W16# c))+macFromWord16# a b c =+ Mac $+ (unsafeShiftL (word16ToWord64 (Compat.W16# a)) 32)+ .|. (unsafeShiftL (word16ToWord64 (Compat.W16# b)) 16)+ .|. (word16ToWord64 (Compat.W16# c)) word16ToWord64 :: Word16 -> Word64 word16ToWord64 = fromIntegral@@ -812,9 +883,10 @@ -- It also uses the smart constructor instead -- of the actual constructor instance Show Mac where- showsPrec p (Mac addr) = showParen (p > 10)- $ showString "mac "- . showHexWord48 addr+ showsPrec p (Mac addr) =+ showParen (p > 10) $+ showString "mac "+ . showHexWord48 addr instance Read Mac where readPrec = parens $ prec 10 $ do@@ -842,43 +914,52 @@ showHexWord48 :: Word64 -> ShowS showHexWord48 w = showString "0x" . go 11- where+ where go :: Int -> ShowS- go !ix = if ix >= 0- then showChar (nibbleToHex ((unsafeShiftR (fromIntegral w) (ix * 4)) .&. 0xF)) . go (ix - 1)- else id+ go !ix =+ if ix >= 0+ then showChar (nibbleToHex ((unsafeShiftR (fromIntegral w) (ix * 4)) .&. 0xF)) . go (ix - 1)+ else id nibbleToHex :: Word -> Char nibbleToHex w | w < 10 = chr (fromIntegral (w + 48)) | otherwise = chr (fromIntegral (w + 87)) --- | A 'MacCodec' allows users to control the encoding/decoding--- of their 'Mac' addresses.+{- | A 'MacCodec' allows users to control the encoding/decoding+ of their 'Mac' addresses.+-} data MacCodec = MacCodec { macCodecGrouping :: !MacGrouping , macCodecUpperCase :: !Bool- } deriving (Eq,Ord,Show,Read,Generic,Data)+ }+ deriving (Eq, Ord, Show, Read, Generic, Data) --- | The format expected by the mac address parser. The 'Word8' taken--- by some of these constructors is the ascii value of the character--- to be used as the separator. This is typically a colon, a hyphen, or--- a space character. All decoding functions are case insensitive.+{- | The format expected by the mac address parser. The 'Word8' taken+ by some of these constructors is the ascii value of the character+ to be used as the separator. This is typically a colon, a hyphen, or+ a space character. All decoding functions are case insensitive.+-} data MacGrouping- = MacGroupingPairs !Char -- ^ Two-character groups, @FA:2B:40:09:8C:11@- | MacGroupingTriples !Char -- ^ Three-character groups, @24B-F0A-025-829@- | MacGroupingQuadruples !Char -- ^ Four-character groups, @A220.0745.CAC7@- | MacGroupingNoSeparator -- ^ No separator, @24AF4B5B0780@- deriving (Eq,Ord,Show,Read,Generic,Data)+ = -- | Two-character groups, @FA:2B:40:09:8C:11@+ MacGroupingPairs !Char+ | -- | Three-character groups, @24B-F0A-025-829@+ MacGroupingTriples !Char+ | -- | Four-character groups, @A220.0745.CAC7@+ MacGroupingQuadruples !Char+ | -- | No separator, @24AF4B5B0780@+ MacGroupingNoSeparator+ deriving (Eq, Ord, Show, Read, Generic, Data) instance ToJSON Mac where toJSON = Aeson.String . encode instance ToJSONKey Mac where- toJSONKey = ToJSONKeyText- (keyFromText . encode)- (\m -> Aeson.unsafeToEncoding $ BB.char7 '"' <> builderUtf8 m <> BB.char7 '"')- where+ toJSONKey =+ ToJSONKeyText+ (keyFromText . encode)+ (\m -> Aeson.unsafeToEncoding $ BB.char7 '"' <> builderUtf8 m <> BB.char7 '"')+ where #if MIN_VERSION_aeson(2,0,0) keyFromText = AK.fromText #else@@ -898,7 +979,7 @@ case v of Aeson.String t -> case AT.parseOnly p t of- Left err -> fail err+ Left err -> fail err Right res -> return res _ -> fail "expected a String" @@ -906,11 +987,10 @@ -- than 256. unsafeWord48FromOctets :: Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> Word64 -> Word64 unsafeWord48FromOctets a b c d e f =- fromIntegral- $ unsafeShiftL a 40- .|. unsafeShiftL b 32- .|. unsafeShiftL c 24- .|. unsafeShiftL d 16- .|. unsafeShiftL e 8- .|. f-+ fromIntegral $+ unsafeShiftL a 40+ .|. unsafeShiftL b 32+ .|. unsafeShiftL c 24+ .|. unsafeShiftL d 16+ .|. unsafeShiftL e 8+ .|. f
src/Net/Types.hs view
@@ -1,18 +1,16 @@-{-| This module re-exports all of the thematic types that this library defines.--}+-- | This module re-exports all of the thematic types that this library defines. module Net.Types- ( IPv4(..)- , IPv6(..)- , IP(..)- , IPv4Range(..)- , IPv6Range(..)- , Mac(..)- , MacCodec(..)- , MacGrouping(..)+ ( IPv4 (..)+ , IPv6 (..)+ , IP (..)+ , IPv4Range (..)+ , IPv6Range (..)+ , Mac (..)+ , MacCodec (..)+ , MacGrouping (..) ) where -import Net.IPv6 (IPv6(..),IPv6Range(..))-import Net.IPv4 (IPv4(..), IPv4Range(..))-import Net.IP (IP(..))-import Net.Mac (Mac(..),MacCodec(..),MacGrouping(..))-+import Net.IP (IP (..))+import Net.IPv4 (IPv4 (..), IPv4Range (..))+import Net.IPv6 (IPv6 (..), IPv6Range (..))+import Net.Mac (Mac (..), MacCodec (..), MacGrouping (..))
test/Bench.hs view
@@ -1,40 +1,39 @@ module Main (main) where import Criterion.Main-import Net.Types (IPv4(..),MacGrouping(..),MacCodec(..))-import Data.Maybe (fromJust)-import Data.Primitive (PrimArray,foldlPrimArray') import Data.Bool (bool)-import System.Random (mkStdGen,randoms)-import qualified Data.Bytes as Bytes+import Data.Maybe (fromJust)+import Data.Primitive (PrimArray, foldlPrimArray') import qualified Data.Text as Text-import qualified Net.Mac as Mac import qualified Net.IPv4 as IPv4 import qualified Net.IPv6 as IPv6+import qualified Net.Mac as Mac+import Net.Types (IPv4 (..), MacCodec (..), MacGrouping (..))+import System.Random (mkStdGen, randoms) -import qualified Naive-import qualified IPv4Text1-import qualified IPv4Text2+import qualified Data.Bytes.Text.Ascii as Ascii+import qualified GHC.Exts as Exts import qualified IPv4ByteString1 import qualified IPv4DecodeText1 import qualified IPv4DecodeText2-import qualified GHC.Exts as Exts--- import qualified IPv4TextVariableBuilder+import qualified IPv4Text1+import qualified IPv4Text2+import qualified Naive main :: IO () main = do let ipAddr = IPv4 1000000009 ipText = Text.pack "192.168.5.99"- ipBytes = Bytes.fromAsciiString "192.168.5.99"+ ipBytes = Ascii.fromString "192.168.5.99" mac = Mac.fromOctets 0xFA 0xBB 0x43 0xA1 0x22 0x09 ip6Text = Text.pack "::" ip6 = fromJust $ IPv6.decode ip6Text ip6StrBigger = "1:2:3:4:5:6:7:8" ip6TextBigger = Text.pack ip6StrBigger- ip6BytesBigger = Bytes.fromAsciiString "1:2:3:4:5:6:7:8"+ ip6BytesBigger = Ascii.fromString "1:2:3:4:5:6:7:8" ip6Bigger = fromJust $ IPv6.decode ip6TextBigger ip6ComplicatedStr = "2001:db8:ba1:0:aaaa:542c:bb:cc00"- ip6ComplicatedBytes = Bytes.fromAsciiString ip6ComplicatedStr+ ip6ComplicatedBytes = Ascii.fromString ip6ComplicatedStr ip6Complicated = fromJust $ IPv6.decode (Text.pack ip6ComplicatedStr) ip6TextSkip = Text.pack "1:2::7:8" ip6Skip = fromJust $ IPv6.decode ip6TextSkip@@ -42,74 +41,86 @@ ip6Hex = fromJust $ IPv6.decode ip6TextHex hundredAddrs = Exts.fromList (map IPv4 (take 100 (randoms (mkStdGen 42)))) :: PrimArray IPv4 defaultMain- [ bgroup "Mac to Text"- [ bench "Current Implementation, pairs" $ whnf Mac.encode mac- , bench "Current Implementation, no separator"- $ whnf (Mac.encodeWith (MacCodec MacGroupingNoSeparator True)) mac- , bench "Current Implementation, quads"- $ whnf (Mac.encodeWith (MacCodec (MacGroupingQuadruples '-') True)) mac- , bench "Current Implementation, triples"- $ whnf (Mac.encodeWith (MacCodec (MacGroupingQuadruples '.') False)) mac- ]- , bgroup "Mac to ByteString"- [ bench "Current Implementation, pairs" $ whnf Mac.encodeUtf8 mac- , bench "Current Implementation, no separator"- $ whnf (Mac.encodeWithUtf8 (MacCodec MacGroupingNoSeparator True)) mac- ]- , bgroup "IPv4 to ShortText"- [ bench "Implementation" $ whnf IPv4.encodeShort ipAddr- ]- , bgroup "IPv4 to Text"- [ bench "Naive" $ whnf Naive.encodeText ipAddr- , bench "Text Builder" $ whnf IPv4Text2.encode ipAddr- , bench "Preallocated" $ whnf IPv4Text1.encode ipAddr- -- , bench "Variable Builder" $ whnf IPv4TextVariableBuilder.encode ipAddr- ]- , bgroup "IPv4 from Text"- [ bench "Naive" $ whnf Naive.decodeText ipText- , bench "Attoparsec" $ whnf IPv4DecodeText2.decodeText ipText- , bench "Text Reader" $ whnf IPv4DecodeText1.decodeText ipText- ]- , bgroup "IPv4 from Bytes"- [ bench "Current" $ whnf IPv4.decodeUtf8Bytes ipBytes- ]- , bgroup "IPv4 to ByteString"- [ bench "Naive" $ whnf Naive.encodeByteString ipAddr- , bench "Preallocated: No Lookup Tables" $ whnf IPv4ByteString1.encode ipAddr- , bench "Preallocated" $ whnf IPv4.encodeUtf8 ipAddr- ]- , bgroup "IPv6 from Text"- [ bench "::" $ whnf IPv6.decode ip6Text- , bench "1:2:3:4:5:6:7:8" $ whnf IPv6.decode ip6TextBigger- , bench "1:2::7:8" $ whnf IPv6.decode ip6TextSkip- , bench "a:b::c:d" $ whnf IPv6.decode ip6TextHex- ]- , bgroup "IPv6 bytesmith"- [ bench "1:2:3:4:5:6:7:8" $ whnf IPv6.decodeUtf8Bytes ip6BytesBigger- , bench "2001:db8:ba1:0:aaaa:542c:bb:cc00" $ whnf IPv6.decodeUtf8Bytes ip6ComplicatedBytes- ]- , bgroup "IPv6 to Text"- [ bench "::" $ whnf IPv6.encode ip6- , bench "1:2:3:4:5:6:7:8" $ whnf IPv6.encode ip6Bigger- , bench "1:2::7:8" $ whnf IPv6.encode ip6Skip- , bench "a:b::c:d" $ whnf IPv6.encode ip6Hex- ]- , bgroup "IPv6 to ShortText"- [ bench "1:2:3:4:5:6:7:8" $ whnf IPv6.encodeShort ip6Bigger- , bench "1:2::7:8" $ whnf IPv6.encodeShort ip6Skip- , bench "a:b::c:d" $ whnf IPv6.encodeShort ip6Hex- , bench "2001:db8:ba1:0:aaaa:542c:bb:cc00" $ whnf IPv6.encodeShort ip6Complicated- ]- , bgroup "CIDR Inclusion"- [ bench "reserved" $ whnf manyReserved hundredAddrs- , bench "private" $ whnf manyPrivate hundredAddrs- ]+ [ bgroup+ "Mac to Text"+ [ bench "Current Implementation, pairs" $ whnf Mac.encode mac+ , bench "Current Implementation, no separator" $+ whnf (Mac.encodeWith (MacCodec MacGroupingNoSeparator True)) mac+ , bench "Current Implementation, quads" $+ whnf (Mac.encodeWith (MacCodec (MacGroupingQuadruples '-') True)) mac+ , bench "Current Implementation, triples" $+ whnf (Mac.encodeWith (MacCodec (MacGroupingQuadruples '.') False)) mac+ ]+ , bgroup+ "Mac to ByteString"+ [ bench "Current Implementation, pairs" $ whnf Mac.encodeUtf8 mac+ , bench "Current Implementation, no separator" $+ whnf (Mac.encodeWithUtf8 (MacCodec MacGroupingNoSeparator True)) mac+ ]+ , bgroup+ "IPv4 to ShortText"+ [ bench "Implementation" $ whnf IPv4.encodeShort ipAddr+ ]+ , bgroup+ "IPv4 to Text"+ [ bench "Naive" $ whnf Naive.encodeText ipAddr+ , bench "Text Builder" $ whnf IPv4Text2.encode ipAddr+ , bench "Preallocated" $ whnf IPv4Text1.encode ipAddr+ -- , bench "Variable Builder" $ whnf IPv4TextVariableBuilder.encode ipAddr+ ]+ , bgroup+ "IPv4 from Text"+ [ bench "Naive" $ whnf Naive.decodeText ipText+ , bench "Attoparsec" $ whnf IPv4DecodeText2.decodeText ipText+ , bench "Text Reader" $ whnf IPv4DecodeText1.decodeText ipText+ ]+ , bgroup+ "IPv4 from Bytes"+ [ bench "Current" $ whnf IPv4.decodeUtf8Bytes ipBytes+ ]+ , bgroup+ "IPv4 to ByteString"+ [ bench "Naive" $ whnf Naive.encodeByteString ipAddr+ , bench "Preallocated: No Lookup Tables" $ whnf IPv4ByteString1.encode ipAddr+ , bench "Preallocated" $ whnf IPv4.encodeUtf8 ipAddr+ ]+ , bgroup+ "IPv6 from Text"+ [ bench "::" $ whnf IPv6.decode ip6Text+ , bench "1:2:3:4:5:6:7:8" $ whnf IPv6.decode ip6TextBigger+ , bench "1:2::7:8" $ whnf IPv6.decode ip6TextSkip+ , bench "a:b::c:d" $ whnf IPv6.decode ip6TextHex+ ]+ , bgroup+ "IPv6 bytesmith"+ [ bench "1:2:3:4:5:6:7:8" $ whnf IPv6.decodeUtf8Bytes ip6BytesBigger+ , bench "2001:db8:ba1:0:aaaa:542c:bb:cc00" $ whnf IPv6.decodeUtf8Bytes ip6ComplicatedBytes+ ]+ , bgroup+ "IPv6 to Text"+ [ bench "::" $ whnf IPv6.encode ip6+ , bench "1:2:3:4:5:6:7:8" $ whnf IPv6.encode ip6Bigger+ , bench "1:2::7:8" $ whnf IPv6.encode ip6Skip+ , bench "a:b::c:d" $ whnf IPv6.encode ip6Hex+ ]+ , bgroup+ "IPv6 to ShortText"+ [ bench "1:2:3:4:5:6:7:8" $ whnf IPv6.encodeShort ip6Bigger+ , bench "1:2::7:8" $ whnf IPv6.encodeShort ip6Skip+ , bench "a:b::c:d" $ whnf IPv6.encodeShort ip6Hex+ , bench "2001:db8:ba1:0:aaaa:542c:bb:cc00" $ whnf IPv6.encodeShort ip6Complicated+ ]+ , bgroup+ "CIDR Inclusion"+ [ bench "reserved" $ whnf manyReserved hundredAddrs+ , bench "private" $ whnf manyPrivate hundredAddrs+ ] ] manyReserved :: PrimArray IPv4 -> Int-{-# noinline manyReserved #-}+{-# NOINLINE manyReserved #-} manyReserved x = foldlPrimArray' (\acc addr -> bool 0 1 (IPv4.reserved addr) + acc) 0 x manyPrivate :: PrimArray IPv4 -> Int-{-# noinline manyPrivate #-}+{-# NOINLINE manyPrivate #-} manyPrivate x = foldlPrimArray' (\acc addr -> bool 0 1 (IPv4.private addr) + acc) 0 x
test/IPv4ByteString1.hs view
@@ -1,43 +1,48 @@ module IPv4ByteString1 where -import Net.Types (IPv4(..))+import Net.Types (IPv4 (..)) -import Data.ByteString.Internal as I import Data.Bits+import Data.ByteString.Internal as I+import Data.Word import Foreign.Ptr import Foreign.Storable-import Data.Word encode :: IPv4 -> ByteString-encode (IPv4 w) = I.unsafeCreateUptoN 15 (\ptr1 ->- do (len1,ptr2) <- writeWord ptr1 w1- poke ptr2 dot- (len2,ptr3) <- writeWord (ptr2 `plusPtr` 1) w2- poke ptr3 dot- (len3,ptr4) <- writeWord (ptr3 `plusPtr` 1) w3- poke ptr4 dot- (len4,_) <- writeWord (ptr4 `plusPtr` 1) w4- return (3 + len1 + len2 + len3 + len4))- where w1 = fromIntegral $ shiftR w 24- w2 = fromIntegral $ shiftR w 16- w3 = fromIntegral $ shiftR w 8- w4 = fromIntegral w- dot = 46- writeWord :: Ptr Word8 -> Word8 -> IO (Int,Ptr Word8)- writeWord ptr word- | word >= 100 = do- let (word1,char3) = word `quotRem` 10- (char1,char2) = word1 `quotRem` 10- poke ptr (char1 + 48)- poke (ptr `plusPtr` 1) (char2 + 48)- poke (ptr `plusPtr` 2) (char3 + 48)- return (3,ptr `plusPtr` 3)- | word >= 10 = do- let (char1,char2) = word `quotRem` 10- poke ptr (char1 + 48)- poke (ptr `plusPtr` 1) (char2 + 48)- return (2,ptr `plusPtr` 2)- | otherwise = do- poke ptr (word + 48)- return (1,ptr `plusPtr` 1)-+encode (IPv4 w) =+ I.unsafeCreateUptoN+ 15+ ( \ptr1 ->+ do+ (len1, ptr2) <- writeWord ptr1 w1+ poke ptr2 dot+ (len2, ptr3) <- writeWord (ptr2 `plusPtr` 1) w2+ poke ptr3 dot+ (len3, ptr4) <- writeWord (ptr3 `plusPtr` 1) w3+ poke ptr4 dot+ (len4, _) <- writeWord (ptr4 `plusPtr` 1) w4+ return (3 + len1 + len2 + len3 + len4)+ )+ where+ w1 = fromIntegral $ shiftR w 24+ w2 = fromIntegral $ shiftR w 16+ w3 = fromIntegral $ shiftR w 8+ w4 = fromIntegral w+ dot = 46+ writeWord :: Ptr Word8 -> Word8 -> IO (Int, Ptr Word8)+ writeWord ptr word+ | word >= 100 = do+ let (word1, char3) = word `quotRem` 10+ (char1, char2) = word1 `quotRem` 10+ poke ptr (char1 + 48)+ poke (ptr `plusPtr` 1) (char2 + 48)+ poke (ptr `plusPtr` 2) (char3 + 48)+ return (3, ptr `plusPtr` 3)+ | word >= 10 = do+ let (char1, char2) = word `quotRem` 10+ poke ptr (char1 + 48)+ poke (ptr `plusPtr` 1) (char2 + 48)+ return (2, ptr `plusPtr` 2)+ | otherwise = do+ poke ptr (word + 48)+ return (1, ptr `plusPtr` 1)
test/IPv4DecodeText1.hs view
@@ -1,30 +1,31 @@ module IPv4DecodeText1 where -import Net.Types-import Data.Word-import Data.Text.Internal (Text(..)) import Control.Monad-import Data.Bits ((.|.),shiftL)-import qualified Data.Text as Text-import qualified Data.Text.Read as TextRead+import Data.Bits (shiftL, (.|.))+import qualified Data.Text as Text+import Data.Text.Internal (Text (..))+import qualified Data.Text.Read as TextRead+import Data.Word+import Net.Types stripDecimal :: Text -> Either String Text stripDecimal t = case Text.uncons t of Nothing -> Left "expected a dot but input ended instead"- Just (c,tnext) -> if c == '.'- then Right tnext- else Left "expected a dot but found a different character"+ Just (c, tnext) ->+ if c == '.'+ then Right tnext+ else Left "expected a dot but found a different character" {-# INLINE stripDecimal #-} decodeIPv4TextEither :: Text -> Either String Word32 decodeIPv4TextEither t1' = do- (a,t2) <- TextRead.decimal t1'+ (a, t2) <- TextRead.decimal t1' t2' <- stripDecimal t2- (b,t3) <- TextRead.decimal t2'+ (b, t3) <- TextRead.decimal t2' t3' <- stripDecimal t3- (c,t4) <- TextRead.decimal t3'+ (c, t4) <- TextRead.decimal t3' t4' <- stripDecimal t4- (d,t5) <- TextRead.decimal t4'+ (d, t5) <- TextRead.decimal t4' when (not (Text.null t5)) (Left "expected end of text but it continued instead") if a > 255 || b > 255 || c > 255 || d > 255 then Left ipOctetSizeErrorMsg@@ -40,8 +41,8 @@ fromOctets' :: Word32 -> Word32 -> Word32 -> Word32 -> Word32 fromOctets' a b c d =- ( shiftL a 24- .|. shiftL b 16- .|. shiftL c 8- .|. d- )+ ( shiftL a 24+ .|. shiftL b 16+ .|. shiftL c 8+ .|. d+ )
test/IPv4DecodeText2.hs view
@@ -1,21 +1,22 @@ module IPv4DecodeText2 where -import Net.Types+import qualified Data.Attoparsec.Text as AT+import Data.Bits (shiftL, (.|.))+import Data.Text.Internal (Text (..)) import Data.Word-import Data.Bits (shiftL,(.|.))-import Data.Text.Internal (Text(..))-import qualified Data.Attoparsec.Text as AT+import Net.Types dotDecimalParser :: AT.Parser Word32-dotDecimalParser = fromOctets'- <$> (AT.decimal >>= limitSize)- <* AT.char '.'- <*> (AT.decimal >>= limitSize)- <* AT.char '.'- <*> (AT.decimal >>= limitSize)- <* AT.char '.'- <*> (AT.decimal >>= limitSize)- where+dotDecimalParser =+ fromOctets'+ <$> (AT.decimal >>= limitSize)+ <* AT.char '.'+ <*> (AT.decimal >>= limitSize)+ <* AT.char '.'+ <*> (AT.decimal >>= limitSize)+ <* AT.char '.'+ <*> (AT.decimal >>= limitSize)+ where limitSize i = if i > 255 then fail ipOctetSizeErrorMsg@@ -31,8 +32,8 @@ fromOctets' :: Word32 -> Word32 -> Word32 -> Word32 -> Word32 fromOctets' a b c d =- ( shiftL a 24- .|. shiftL b 16- .|. shiftL c 8- .|. d- )+ ( shiftL a 24+ .|. shiftL b 16+ .|. shiftL c 8+ .|. d+ )
test/IPv4Text1.hs view
@@ -1,17 +1,18 @@ {-# LANGUAGE CPP #-}+ module IPv4Text1 where -import Net.Types (IPv4(..))-import Data.Text (Text)-import Data.Text.Internal (Text(..))-import Data.Word-import Data.ByteString (ByteString) import Control.Monad.ST-import Data.Bits (shiftR,(.&.))-import qualified Data.ByteString.Char8 as BC8-import qualified Data.ByteString as ByteString+import Data.Bits (shiftR, (.&.))+import Data.ByteString (ByteString)+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as BC8 import qualified Data.ByteString.Unsafe as ByteString-import qualified Data.Text.Array as TArray+import Data.Text (Text)+import qualified Data.Text.Array as TArray+import Data.Text.Internal (Text (..))+import Data.Word+import Net.Types (IPv4 (..)) ------------------------ -- This implementation operates directly on@@ -27,7 +28,7 @@ w2 = fromIntegral $ 255 .&. shiftR w 16 w3 = fromIntegral $ 255 .&. shiftR w 8 w4 = fromIntegral $ 255 .&. w- (arr,len) = runST $ do+ (arr, len) = runST $ do marr <- TArray.new 15 i1 <- putAndCount 0 w1 marr let n1 = i1@@ -43,15 +44,15 @@ TArray.unsafeWrite marr n3 dot i4 <- putAndCount n3' w4 marr theArr <- TArray.unsafeFreeze marr- return (theArr,i4 + n3')- in Text arr 0 len+ return (theArr, i4 + n3')+ in Text arr 0 len putAndCount :: Int -> Word8 -> TArray.MArray s -> ST s Int putAndCount pos w marr | w < 10 = TArray.unsafeWrite marr pos (i2w w) >> return 1 | w < 100 = write2 pos w >> return 2 | otherwise = write3 pos w >> return 3- where+ where write2 off i0 = do let i = fromIntegral i0; j = i + i TArray.unsafeWrite marr off $ get2 j@@ -72,7 +73,7 @@ type Codepoint = Word16 #endif -zero,dot :: Codepoint+zero, dot :: Codepoint zero = 48 {-# INLINE zero #-} dot = 46@@ -83,6 +84,7 @@ {-# INLINE i2w #-} -- Note: these double backslashes are need here because CPP is enabled.+{- FOURMOLU_DISABLE -} twoDigits :: ByteString twoDigits = BC8.pack "0001020304050607080910111213141516171819\\@@ -106,4 +108,4 @@ \217218219220221222223224225226227228229\\ \230231232233234235236237238239240241242\\ \243244245246247248249250251252253254255"-+{- FOURMOLU_ENABLE -}
test/IPv4Text2.hs view
@@ -1,11 +1,11 @@ module IPv4Text2 where -import Net.Types (IPv4(..))+import Data.Bits (shiftR, (.&.)) import Data.Text (Text)-import Data.Bits ((.&.),shiftR)-import Data.Text.Lazy.Builder.Int (decimal)-import qualified Data.Text.Lazy as LText+import qualified Data.Text.Lazy as LText import qualified Data.Text.Lazy.Builder as TBuilder+import Data.Text.Lazy.Builder.Int (decimal)+import Net.Types (IPv4 (..)) ----------------------------------------- -- Text Builder implementation. This ends@@ -17,13 +17,13 @@ toDotDecimalBuilder :: IPv4 -> TBuilder.Builder toDotDecimalBuilder (IPv4 w) =- decimal (255 .&. shiftR w 24 )- <> dot- <> decimal (255 .&. shiftR w 16 )- <> dot- <> decimal (255 .&. shiftR w 8 )- <> dot- <> decimal (255 .&. w)- where dot = TBuilder.singleton '.'+ decimal (255 .&. shiftR w 24)+ <> dot+ <> decimal (255 .&. shiftR w 16)+ <> dot+ <> decimal (255 .&. shiftR w 8)+ <> dot+ <> decimal (255 .&. w)+ where+ dot = TBuilder.singleton '.' {-# INLINE toDotDecimalBuilder #-}-
test/Naive.hs view
@@ -1,31 +1,33 @@ module Naive where -import Net.Types (IPv4(..))+import Data.ByteString (ByteString) import Data.Text (Text)-import qualified Net.IPv4 as IPv4 import qualified Data.Text as Text-import Text.Read (readMaybe)-import Data.ByteString (ByteString) import Data.Text.Encoding (encodeUtf8)+import qualified Net.IPv4 as IPv4+import Net.Types (IPv4 (..))+import Text.Read (readMaybe) encodeByteString :: IPv4 -> ByteString encodeByteString = encodeUtf8 . encodeText encodeText :: IPv4 -> Text-encodeText i = Text.pack $ concat- [ show a- , "."- , show b- , "."- , show c- , "."- , show d- ]- where (a,b,c,d) = IPv4.toOctets i+encodeText i =+ Text.pack $+ concat+ [ show a+ , "."+ , show b+ , "."+ , show c+ , "."+ , show d+ ]+ where+ (a, b, c, d) = IPv4.toOctets i decodeText :: Text -> Maybe IPv4 decodeText t = case mapM (readMaybe . Text.unpack) (Text.splitOn (Text.pack ".") t) of- Just [a,b,c,d] -> Just (IPv4.fromOctets a b c d)+ Just [a, b, c, d] -> Just (IPv4.fromOctets a b c d) _ -> Nothing-
test/Net/IPv4Spec.hs view
@@ -1,68 +1,99 @@ {-# OPTIONS_GHC -Wno-deprecations #-}+ module Net.IPv4Spec (spec) where-import Prelude hiding (any)+ import Data.Bits import Net.IPv4 import Test.Hspec+import Prelude hiding (any) spec :: Spec spec = do- describe "Bits" $ do- it ".&." $ do- any .&. any `shouldBe` any- any .&. loopback `shouldBe` any- loopback .&. broadcast `shouldBe` loopback- broadcast .&. broadcast `shouldBe` broadcast- it ".|." $ do- any .|. any `shouldBe` any- any .|. loopback `shouldBe` loopback- loopback .|. broadcast `shouldBe` broadcast- broadcast .|. broadcast `shouldBe` broadcast- it "xor" $ do- any `xor` any `shouldBe` any- any `xor` loopback `shouldBe` loopback- loopback `xor` broadcast `shouldBe` complement loopback- broadcast `xor` broadcast `shouldBe` any- it "complement" $ do- complement any `shouldBe` broadcast- complement loopback `shouldBe` ipv4 128 255 255 254- complement broadcast `shouldBe` any- it "shift" $ do- shift any 0 `shouldBe` any- shift broadcast 0 `shouldBe` broadcast- shift broadcast 8 `shouldBe` ipv4 255 255 255 0- shift broadcast (-8) `shouldBe` ipv4 0 255 255 255- shift broadcast 32 `shouldBe` any- shift broadcast 40 `shouldBe` any- it "rotate" $ do- rotate loopback 0 `shouldBe` loopback- rotate loopback 0 `shouldBe` loopback- rotate loopback 8 `shouldBe` ipv4 0 0 1 127- rotate loopback (-8) `shouldBe` ipv4 1 127 0 0- rotate loopback 32 `shouldBe` loopback- it "bitSize" $ do- bitSize any `shouldBe` 32- it "bitSizeMaybe" $ do- bitSizeMaybe any `shouldBe` Just 32- it "isSigned" $ do- isSigned any `shouldBe` False- isSigned broadcast `shouldBe` False- it "testBit" $ do- testBit loopback <$> [0..31] `shouldBe`- [ True, False, False, False, False, False, False, False- , False, False, False, False, False, False, False, False- , False, False, False, False, False, False, False, False- , True, True, True, True, True, True, True, False ]- it "bit" $ do- bit 0 `shouldBe` ipv4 0 0 0 1- bit 1 `shouldBe` ipv4 0 0 0 2- bit 31 `shouldBe` ipv4 128 0 0 0- it "popCount" $ do- popCount any `shouldBe` 0- popCount loopback `shouldBe` 8- popCount broadcast `shouldBe` 32- describe "FiniteBits" $ do- it "finiteBitSize" $ do- finiteBitSize any `shouldBe` 32- finiteBitSize loopback `shouldBe` 32- finiteBitSize broadcast `shouldBe` 32+ describe "Bits" $ do+ it ".&." $ do+ any .&. any `shouldBe` any+ any .&. loopback `shouldBe` any+ loopback .&. broadcast `shouldBe` loopback+ broadcast .&. broadcast `shouldBe` broadcast+ it ".|." $ do+ any .|. any `shouldBe` any+ any .|. loopback `shouldBe` loopback+ loopback .|. broadcast `shouldBe` broadcast+ broadcast .|. broadcast `shouldBe` broadcast+ it "xor" $ do+ any `xor` any `shouldBe` any+ any `xor` loopback `shouldBe` loopback+ loopback `xor` broadcast `shouldBe` complement loopback+ broadcast `xor` broadcast `shouldBe` any+ it "complement" $ do+ complement any `shouldBe` broadcast+ complement loopback `shouldBe` ipv4 128 255 255 254+ complement broadcast `shouldBe` any+ it "shift" $ do+ shift any 0 `shouldBe` any+ shift broadcast 0 `shouldBe` broadcast+ shift broadcast 8 `shouldBe` ipv4 255 255 255 0+ shift broadcast (-8) `shouldBe` ipv4 0 255 255 255+ shift broadcast 32 `shouldBe` any+ shift broadcast 40 `shouldBe` any+ it "rotate" $ do+ rotate loopback 0 `shouldBe` loopback+ rotate loopback 0 `shouldBe` loopback+ rotate loopback 8 `shouldBe` ipv4 0 0 1 127+ rotate loopback (-8) `shouldBe` ipv4 1 127 0 0+ rotate loopback 32 `shouldBe` loopback+ it "bitSize" $ do+ bitSize any `shouldBe` 32+ it "bitSizeMaybe" $ do+ bitSizeMaybe any `shouldBe` Just 32+ it "isSigned" $ do+ isSigned any `shouldBe` False+ isSigned broadcast `shouldBe` False+ it "testBit" $ do+ testBit loopback <$> [0 .. 31]+ `shouldBe` [ True+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , False+ , True+ , True+ , True+ , True+ , True+ , True+ , True+ , False+ ]+ it "bit" $ do+ bit 0 `shouldBe` ipv4 0 0 0 1+ bit 1 `shouldBe` ipv4 0 0 0 2+ bit 31 `shouldBe` ipv4 128 0 0 0+ it "popCount" $ do+ popCount any `shouldBe` 0+ popCount loopback `shouldBe` 8+ popCount broadcast `shouldBe` 32+ describe "FiniteBits" $ do+ it "finiteBitSize" $ do+ finiteBitSize any `shouldBe` 32+ finiteBitSize loopback `shouldBe` 32+ finiteBitSize broadcast `shouldBe` 32
test/Test.hs view
@@ -1,242 +1,311 @@-{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-+{-# LANGUAGE StandaloneDeriving #-} {-# OPTIONS_GHC -Wall -fno-warn-orphans #-} module Main (main) where -import Naive+#if MIN_VERSION_base(4,18,0)+#else import Control.Applicative (liftA2)+#endif++import Data.Bifunctor import Data.Bytes (Bytes)-import Data.Proxy (Proxy(..))-import Test.Tasty (defaultMain, testGroup, TestTree)-import Test.Tasty.QuickCheck (testProperty)-import Test.QuickCheck (Arbitrary(..),oneof,Gen,elements,choose,(===))-import Test.HUnit (Assertion,(@?=),(@=?))+import Data.Proxy (Proxy (..))+import Naive import Numeric (showHex)-import Test.QuickCheck.Property (failed,succeeded,Result(..))-import Data.Bifunctor-import Test.QuickCheck.Classes (Laws(..),jsonLaws,showReadLaws,primLaws,boundedEnumLaws,bitsLaws)+import Test.HUnit (Assertion, (@=?), (@?=))+import Test.QuickCheck (Arbitrary (..), Gen, choose, elements, oneof, (===))+import Test.QuickCheck.Classes (Laws (..), bitsLaws, boundedEnumLaws, jsonLaws, primLaws, showReadLaws)+import Test.QuickCheck.Property (Result (..), failed, succeeded)+import Test.Tasty (TestTree, defaultMain, testGroup) import qualified Test.Tasty.HUnit as PH+import Test.Tasty.QuickCheck (testProperty) -import Net.Types (IP,IPv4(..),IPv4Range(..),Mac(..),IPv6(..),MacGrouping(..),MacCodec(..),IPv6Range(..))-import Data.WideWord (Word128(..))+import qualified Data.ByteString.Char8 as BC8 import qualified Data.Bytes.Text.Ascii as Ascii import qualified Data.Text as Text import qualified Data.Text.Short as TS-import qualified Data.ByteString.Char8 as BC8+import Data.WideWord (Word128 (..))+import qualified Net.IP as IP import qualified Net.IPv4 as IPv4 import qualified Net.IPv6 as IPv6 import qualified Net.Mac as Mac-import qualified Net.IP as IP+import Net.Types (IP, IPv4 (..), IPv4Range (..), IPv6 (..), IPv6Range (..), Mac (..), MacCodec (..), MacGrouping (..)) -import qualified Data.Attoparsec.Text as AT import qualified Data.Attoparsec.ByteString as AB+import qualified Data.Attoparsec.Text as AT +import qualified IPv4ByteString1 import qualified IPv4Text1 import qualified IPv4Text2-import qualified IPv4ByteString1+ -- import qualified IPv4TextVariableBuilder main :: IO () main = defaultMain tests tests :: TestTree-tests = testGroup "tests"- [ testGroup "Encoding and Decoding"- [ testGroup "Currently used IPv4 encode/decode" $- [ testProperty "Isomorphism"- $ propEncodeDecodeIso IPv4.encode IPv4.decode- , PH.testCase "Decode an IP" testIPv4Decode- ] ++ testDecodeFailures- , testGroup "Currently used IPv4 encodeShort/decodeShort" $- [ testProperty "Isomorphism"- $ propEncodeDecodeIso IPv4.encodeShort IPv4.decodeShort- ] ++ testDecodeFailures- , testGroup "Currently used IPv4 UTF-8 Bytes decode"- [ testProperty "Isomorphism"- $ propEncodeDecodeIso (byteStringToBytes . IPv4.encodeUtf8) IPv4.decodeUtf8Bytes- , PH.testCase "Encode a MAC Address" testMacEncode- ]- , testGroup "Currently used MAC Text encode/decode"- [ testProperty "Isomorphism"- $ propEncodeDecodeIsoSettings Mac.encodeWith Mac.decodeWith- , PH.testCase "Encode a MAC Address" testMacEncode- ]- , testGroup "Currently used MAC ByteString encode/decode"- [ testProperty "Isomorphism"- $ propEncodeDecodeIsoSettings Mac.encodeWithUtf8 Mac.decodeWithUtf8- , PH.testCase "Lenient Decoding" testLenientMacByteStringParser- ]- , testGroup "Naive IPv4 encode/decode"- [ testProperty "Isomorphism"- $ propEncodeDecodeIso Naive.encodeText Naive.decodeText- ]- , testGroup "Text Builder IPv4 Text encode/decode"- [ testProperty "Identical to Naive"- $ propMatching IPv4Text2.encode Naive.encodeText- ]- -- , testGroup "Variable Text Builder IPv4 Text encode/decode"- -- [ testProperty "Identical to Naive"- -- $ propMatching IPv4TextVariableBuilder.encode Naive.encodeText- -- ]- , testGroup "Raw byte array IPv4 Text encode/decode"- [ testProperty "Identical to Naive"- $ propMatching IPv4Text1.encode Naive.encodeText- ]- , testGroup "Raw byte array (without lookup table) IPv4 ByteString encode/decode"- [ testProperty "Identical to Naive"- $ propMatching IPv4ByteString1.encode Naive.encodeByteString- ]- , testGroup "Raw byte array (with lookup table) IPv4 ByteString encode/decode"- [ testProperty "Identical to Naive"- $ propMatching IPv4.encodeUtf8 Naive.encodeByteString- ]- , testGroup "IPv4 encode/decode"- [ PH.testCase "Parser Test Cases" testIPv4Parser- ]- , testGroup "IPv6 encode/decode"- [ PH.testCase "Parser Test Cases" $ testIPv6Parser $ \str ->- either (\_ -> Nothing) (Just . HexIPv6)- (AT.parseOnly- (IPv6.parser <* AT.endOfInput)- (Text.pack str)- )- , PH.testCase "Bytes Parser Test Cases" $ testIPv6Parser $ \str ->- fmap HexIPv6 (IPv6.decodeUtf8Bytes (Ascii.fromString str))- , PH.testCase "Encode test cases" (testIPv6Encode IPv6.encode)- , PH.testCase "Encode ShortText" (testIPv6Encode (TS.toText . IPv6.encodeShort))- , PH.testCase "Parser Failure Test Cases"- (testIPv6ParserFailure expectIPv6ParserFailure)- , PH.testCase "Bytes Parser Failure Test Cases"- (testIPv6ParserFailure expectIPv6BytesParserFailure)- ]- ]- , testGroup "IPv4 Range Operations"- [ testProperty "Idempotence of normalizing IPv4 range"- $ propIdempotence IPv4.normalize- , testProperty "Normalize does not affect membership" propNormalizeMember- , testProperty "Membership agrees with bounds" propMemberUpperLower- , testProperty "Range contains self" propRangeSelf- , testGroup "reserved"- [ PH.testCase "A" $ IPv4.reserved (IPv4.ipv4 0 1 2 3) @=? True- , PH.testCase "B" $ IPv4.reserved (IPv4.ipv4 1 0 0 0) @=? False- , PH.testCase "C" $ IPv4.reserved (IPv4.ipv4 100 64 0 3) @=? True- , PH.testCase "D" $ IPv4.reserved (IPv4.ipv4 127 255 255 255) @=? True- , PH.testCase "E" $ IPv4.reserved (IPv4.ipv4 110 0 0 255) @=? False- , PH.testCase "F" $ IPv4.reserved (IPv4.ipv4 192 0 2 255) @=? True- , PH.testCase "G" $ IPv4.reserved (IPv4.ipv4 203 0 113 0) @=? True- , PH.testCase "H" $ IPv4.reserved (IPv4.ipv4 225 0 0 0) @=? True- , PH.testCase "I" $ IPv4.reserved (IPv4.ipv4 226 0 0 0) @=? True- , PH.testCase "J" $ IPv4.reserved (IPv4.ipv4 255 255 255 254) @=? True- , PH.testCase "K" $ IPv4.reserved (IPv4.ipv4 255 255 255 255) @=? True- , PH.testCase "L" $ IPv4.reserved (IPv4.ipv4 224 0 0 0) @=? True- , PH.testCase "M" $ IPv4.reserved (IPv4.ipv4 239 255 255 255) @=? True- , PH.testCase "N" $ IPv4.reserved (IPv4.ipv4 223 255 255 255) @=? False- , PH.testCase "O" $ IPv4.reserved (IPv4.ipv4 203 0 114 0) @=? False- , PH.testCase "P" $ IPv4.reserved (IPv4.ipv4 203 0 112 255) @=? False- , PH.testCase "Q" $ IPv4.reserved (IPv4.ipv4 203 0 113 255) @=? True- , PH.testCase "R" $ IPv4.reserved (IPv4.ipv4 192 88 100 0) @=? False- , PH.testCase "S" $ IPv4.reserved (IPv4.ipv4 192 88 99 0) @=? True- , PH.testCase "T" $ IPv4.reserved (IPv4.ipv4 192 0 1 0) @=? False- ]- , testGroup "private"- [ PH.testCase "A" $ IPv4.private (IPv4.ipv4 198 73 8 38) @=? False- , PH.testCase "B" $ IPv4.private (IPv4.ipv4 192 168 100 5) @=? True- , PH.testCase "C" $ IPv4.private (IPv4.ipv4 10 0 0 0) @=? True- , PH.testCase "D" $ IPv4.private (IPv4.ipv4 10 255 255 255) @=? True- ]- ]- , testGroup "IPv6 Range Operations"- [ testProperty "Idempotence of normalizing IPv6 range"- $ propIdempotence IPv6.normalize- , testProperty "Normalize does not affect membership" $ \i r ->- IPv6.member i r == IPv6.member i (IPv6.normalize r)- , testProperty "Membership agrees with bounds" $ \i r ->- (i >= IPv6.lowerInclusive r && i <= IPv6.upperInclusive r) == IPv6.member i r- , testProperty "Range contains self" $ \r ->- IPv6.member (ipv6RangeBase r) r == True- , testProperty "Idempotence of upperInclusive-lowerInclusive and fromBounds" $ \r ->- IPv6.fromBounds (IPv6.lowerInclusive r) (IPv6.upperInclusive r) === r- , testGroup "Cases"- [ PH.testCase "A" $ False @=? IPv6.contains- (IPv6.range (IPv6.ipv6 0 0 0 1 0 0 0 0) 64)- (IPv6.ipv6 0 0 0 0 0 0 0 0)- , PH.testCase "B" $ True @=? IPv6.contains- (IPv6.range (IPv6.ipv6 0 0 0 0 0 0 0 0) 126)- (IPv6.ipv6 0 0 0 0 0 0 0 1)- , PH.testCase "C" $ False @=? IPv6.contains- (IPv6.range (IPv6.ipv6 0 0 0 0 0 0 0 0) 125)- (IPv6.ipv6 0 0 0 0 0 0 0 0xFFFF)- ]- ]- , testGroup "Instances"- [ testGroup "IPv4"- [ lawsToTest (jsonLaws (Proxy :: Proxy IPv4))- , lawsToTest (showReadLaws (Proxy :: Proxy IPv4))- , lawsToTest (bitsLaws (Proxy :: Proxy IPv4))- ]- , testGroup "IPv4Range"- [ lawsToTest (jsonLaws (Proxy :: Proxy IPv4Range))- , lawsToTest (showReadLaws (Proxy :: Proxy IPv4Range))- ]- , testGroup "IPv6"- [ lawsToTest (jsonLaws (Proxy :: Proxy IPv6))- , lawsToTest (showReadLaws (Proxy :: Proxy IPv6))- , lawsToTest (primLaws (Proxy :: Proxy IPv6))- , lawsToTest (boundedEnumLaws (Proxy :: Proxy IPv6))- , lawsToTest (bitsLaws (Proxy :: Proxy IPv6))- ]- , testGroup "IPv6Range"- [ lawsToTest (jsonLaws (Proxy :: Proxy IPv6Range))- , lawsToTest (showReadLaws (Proxy :: Proxy IPv6Range))- ]- , testGroup "IP"- [ lawsToTest (jsonLaws (Proxy :: Proxy IP))- , lawsToTest (showReadLaws (Proxy :: Proxy IP))- ]- , testGroup "Mac"- [ lawsToTest (jsonLaws (Proxy :: Proxy Mac))- , lawsToTest (showReadLaws (Proxy :: Proxy Mac))- , lawsToTest (primLaws (Proxy :: Proxy Mac))- ]+tests =+ testGroup+ "tests"+ [ testGroup+ "Encoding and Decoding"+ [ testGroup "Currently used IPv4 encode/decode" $+ [ testProperty "Isomorphism" $+ propEncodeDecodeIso IPv4.encode IPv4.decode+ , PH.testCase "Decode an IP" testIPv4Decode+ ]+ ++ testDecodeFailures+ , testGroup "Currently used IPv4 encodeShort/decodeShort" $+ [ testProperty "Isomorphism" $+ propEncodeDecodeIso IPv4.encodeShort IPv4.decodeShort+ ]+ ++ testDecodeFailures+ , testGroup+ "Currently used IPv4 UTF-8 Bytes decode"+ [ testProperty "Isomorphism" $+ propEncodeDecodeIso (byteStringToBytes . IPv4.encodeUtf8) IPv4.decodeUtf8Bytes+ , PH.testCase "Encode a MAC Address" testMacEncode+ ]+ , testGroup+ "Currently used MAC Text encode/decode"+ [ testProperty "Isomorphism" $+ propEncodeDecodeIsoSettings Mac.encodeWith Mac.decodeWith+ , PH.testCase "Encode a MAC Address" testMacEncode+ ]+ , testGroup+ "Currently used MAC ByteString encode/decode"+ [ testProperty "Isomorphism" $+ propEncodeDecodeIsoSettings Mac.encodeWithUtf8 Mac.decodeWithUtf8+ , PH.testCase "Lenient Decoding" testLenientMacByteStringParser+ ]+ , testGroup+ "Naive IPv4 encode/decode"+ [ testProperty "Isomorphism" $+ propEncodeDecodeIso Naive.encodeText Naive.decodeText+ ]+ , testGroup+ "Text Builder IPv4 Text encode/decode"+ [ testProperty "Identical to Naive" $+ propMatching IPv4Text2.encode Naive.encodeText+ ]+ , -- , testGroup "Variable Text Builder IPv4 Text encode/decode"+ -- [ testProperty "Identical to Naive"+ -- $ propMatching IPv4TextVariableBuilder.encode Naive.encodeText+ -- ]+ testGroup+ "Raw byte array IPv4 Text encode/decode"+ [ testProperty "Identical to Naive" $+ propMatching IPv4Text1.encode Naive.encodeText+ ]+ , testGroup+ "Raw byte array (without lookup table) IPv4 ByteString encode/decode"+ [ testProperty "Identical to Naive" $+ propMatching IPv4ByteString1.encode Naive.encodeByteString+ ]+ , testGroup+ "Raw byte array (with lookup table) IPv4 ByteString encode/decode"+ [ testProperty "Identical to Naive" $+ propMatching IPv4.encodeUtf8 Naive.encodeByteString+ ]+ , testGroup+ "IPv4 encode/decode"+ [ PH.testCase "Parser Test Cases" testIPv4Parser+ ]+ , testGroup+ "IPv6 encode/decode"+ [ PH.testCase "Parser Test Cases" $ testIPv6Parser $ \str ->+ either+ (\_ -> Nothing)+ (Just . HexIPv6)+ ( AT.parseOnly+ (IPv6.parser <* AT.endOfInput)+ (Text.pack str)+ )+ , PH.testCase "Bytes Parser Test Cases" $ testIPv6Parser $ \str ->+ fmap HexIPv6 (IPv6.decodeUtf8Bytes (Ascii.fromString str))+ , PH.testCase "Encode test cases" (testIPv6Encode IPv6.encode)+ , PH.testCase "Encode ShortText" (testIPv6Encode (TS.toText . IPv6.encodeShort))+ , PH.testCase+ "Parser Failure Test Cases"+ (testIPv6ParserFailure expectIPv6ParserFailure)+ , PH.testCase+ "Bytes Parser Failure Test Cases"+ (testIPv6ParserFailure expectIPv6BytesParserFailure)+ ]+ ]+ , testGroup+ "IPv4 Range Operations"+ [ testProperty "Idempotence of normalizing IPv4 range" $+ propIdempotence IPv4.normalize+ , testProperty "Normalize does not affect membership" propNormalizeMember+ , testProperty "Membership agrees with bounds" propMemberUpperLower+ , testProperty "Range contains self" propRangeSelf+ , testGroup+ "reserved"+ [ PH.testCase "A" $ IPv4.reserved (IPv4.ipv4 0 1 2 3) @=? True+ , PH.testCase "B" $ IPv4.reserved (IPv4.ipv4 1 0 0 0) @=? False+ , PH.testCase "C" $ IPv4.reserved (IPv4.ipv4 100 64 0 3) @=? True+ , PH.testCase "D" $ IPv4.reserved (IPv4.ipv4 127 255 255 255) @=? True+ , PH.testCase "E" $ IPv4.reserved (IPv4.ipv4 110 0 0 255) @=? False+ , PH.testCase "F" $ IPv4.reserved (IPv4.ipv4 192 0 2 255) @=? True+ , PH.testCase "G" $ IPv4.reserved (IPv4.ipv4 203 0 113 0) @=? True+ , PH.testCase "H" $ IPv4.reserved (IPv4.ipv4 225 0 0 0) @=? True+ , PH.testCase "I" $ IPv4.reserved (IPv4.ipv4 226 0 0 0) @=? True+ , PH.testCase "J" $ IPv4.reserved (IPv4.ipv4 255 255 255 254) @=? True+ , PH.testCase "K" $ IPv4.reserved (IPv4.ipv4 255 255 255 255) @=? True+ , PH.testCase "L" $ IPv4.reserved (IPv4.ipv4 224 0 0 0) @=? True+ , PH.testCase "M" $ IPv4.reserved (IPv4.ipv4 239 255 255 255) @=? True+ , PH.testCase "N" $ IPv4.reserved (IPv4.ipv4 223 255 255 255) @=? False+ , PH.testCase "O" $ IPv4.reserved (IPv4.ipv4 203 0 114 0) @=? False+ , PH.testCase "P" $ IPv4.reserved (IPv4.ipv4 203 0 112 255) @=? False+ , PH.testCase "Q" $ IPv4.reserved (IPv4.ipv4 203 0 113 255) @=? True+ , PH.testCase "R" $ IPv4.reserved (IPv4.ipv4 192 88 100 0) @=? False+ , PH.testCase "S" $ IPv4.reserved (IPv4.ipv4 192 88 99 0) @=? True+ , PH.testCase "T" $ IPv4.reserved (IPv4.ipv4 192 0 1 0) @=? False+ ]+ , testGroup+ "private"+ [ PH.testCase "A" $ IPv4.private (IPv4.ipv4 198 73 8 38) @=? False+ , PH.testCase "B" $ IPv4.private (IPv4.ipv4 192 168 100 5) @=? True+ , PH.testCase "C" $ IPv4.private (IPv4.ipv4 10 0 0 0) @=? True+ , PH.testCase "D" $ IPv4.private (IPv4.ipv4 10 255 255 255) @=? True+ ]+ ]+ , testGroup+ "IPv6 Range Operations"+ [ testProperty "Idempotence of normalizing IPv6 range" $+ propIdempotence IPv6.normalize+ , testProperty "Normalize does not affect membership" $ \i r ->+ IPv6.member i r == IPv6.member i (IPv6.normalize r)+ , testProperty "Membership agrees with bounds" $ \i r ->+ (i >= IPv6.lowerInclusive r && i <= IPv6.upperInclusive r) == IPv6.member i r+ , testProperty "Range contains self" $ \r ->+ IPv6.member (ipv6RangeBase r) r == True+ , testProperty "Idempotence of upperInclusive-lowerInclusive and fromBounds" $ \r ->+ IPv6.fromBounds (IPv6.lowerInclusive r) (IPv6.upperInclusive r) === r+ , testGroup+ "Cases"+ [ PH.testCase "A" $+ False+ @=? IPv6.contains+ (IPv6.range (IPv6.ipv6 0 0 0 1 0 0 0 0) 64)+ (IPv6.ipv6 0 0 0 0 0 0 0 0)+ , PH.testCase "B" $+ True+ @=? IPv6.contains+ (IPv6.range (IPv6.ipv6 0 0 0 0 0 0 0 0) 126)+ (IPv6.ipv6 0 0 0 0 0 0 0 1)+ , PH.testCase "C" $+ False+ @=? IPv6.contains+ (IPv6.range (IPv6.ipv6 0 0 0 0 0 0 0 0) 125)+ (IPv6.ipv6 0 0 0 0 0 0 0 0xFFFF)+ ]+ ]+ , testGroup+ "Instances"+ [ testGroup+ "IPv4"+ [ lawsToTest (jsonLaws (Proxy :: Proxy IPv4))+ , lawsToTest (showReadLaws (Proxy :: Proxy IPv4))+ , lawsToTest (bitsLaws (Proxy :: Proxy IPv4))+ ]+ , testGroup+ "IPv4Range"+ [ lawsToTest (jsonLaws (Proxy :: Proxy IPv4Range))+ , lawsToTest (showReadLaws (Proxy :: Proxy IPv4Range))+ ]+ , testGroup+ "IPv6"+ [ lawsToTest (jsonLaws (Proxy :: Proxy IPv6))+ , lawsToTest (showReadLaws (Proxy :: Proxy IPv6))+ , lawsToTest (primLaws (Proxy :: Proxy IPv6))+ , lawsToTest (boundedEnumLaws (Proxy :: Proxy IPv6))+ , lawsToTest (bitsLaws (Proxy :: Proxy IPv6))+ ]+ , testGroup+ "IPv6Range"+ [ lawsToTest (jsonLaws (Proxy :: Proxy IPv6Range))+ , lawsToTest (showReadLaws (Proxy :: Proxy IPv6Range))+ ]+ , testGroup+ "IP"+ [ lawsToTest (jsonLaws (Proxy :: Proxy IP))+ , lawsToTest (showReadLaws (Proxy :: Proxy IP))+ ]+ , testGroup+ "Mac"+ [ lawsToTest (jsonLaws (Proxy :: Proxy Mac))+ , lawsToTest (showReadLaws (Proxy :: Proxy Mac))+ , lawsToTest (primLaws (Proxy :: Proxy Mac))+ ]+ ] ]- ] lawsToTest :: Laws -> TestTree lawsToTest (Laws name pairs) = testGroup name (map (uncurry testProperty) pairs) -propEncodeDecodeIso :: (Eq a, Show a, Show b)- => (a -> b) -> (b -> Maybe a) -> a -> Result+propEncodeDecodeIso ::+ (Eq a, Show a, Show b) =>+ (a -> b) ->+ (b -> Maybe a) ->+ a ->+ Result propEncodeDecodeIso f g a = let fa = f a gfa = g fa in if gfa == Just a then succeeded- else failure $ concat- [ "x: ", show a, "\n"- , "f(x): ", show fa, "\n"- , "g(f(x)): ", show gfa, "\n"- ]+ else+ failure $+ concat+ [ "x: "+ , show a+ , "\n"+ , "f(x): "+ , show fa+ , "\n"+ , "g(f(x)): "+ , show gfa+ , "\n"+ ] -propEncodeDecodeIsoSettings :: (Eq a,Show a,Show b,Show e)- => (e -> a -> b) -> (e -> b -> Maybe a) -> e -> a -> Result+propEncodeDecodeIsoSettings ::+ (Eq a, Show a, Show b, Show e) =>+ (e -> a -> b) ->+ (e -> b -> Maybe a) ->+ e ->+ a ->+ Result propEncodeDecodeIsoSettings f g e a = let fa = f e a gfa = g e fa in if gfa == Just a then succeeded- else failure $ concat- [ "env: ", show e, "\n"- , "x: ", show a, "\n"- , "f(x): ", show fa, "\n"- , "g(f(x)): ", show gfa, "\n"- ]+ else+ failure $+ concat+ [ "env: "+ , show e+ , "\n"+ , "x: "+ , show a+ , "\n"+ , "f(x): "+ , show fa+ , "\n"+ , "g(f(x)): "+ , show gfa+ , "\n"+ ] -propMatching :: Eq b => (a -> b) -> (a -> b) -> a -> Bool+propMatching :: (Eq b) => (a -> b) -> (a -> b) -> a -> Bool propMatching f g a = f a == g a -propIdempotence :: Eq a => (a -> a) -> a -> Bool+propIdempotence :: (Eq a) => (a -> a) -> a -> Bool propIdempotence f a = f a == f (f a) propNormalizeMember :: IPv4 -> IPv4Range -> Bool@@ -250,58 +319,134 @@ propRangeSelf r = IPv4.member (ipv4RangeBase r) r == True testIPv4Decode :: Assertion-testIPv4Decode = IPv4.decode (Text.pack "124.222.255.0")- @?= Just (IPv4.fromOctets 124 222 255 0)+testIPv4Decode =+ IPv4.decode (Text.pack "124.222.255.0")+ @?= Just (IPv4.fromOctets 124 222 255 0) testLenientMacByteStringParser :: Assertion testLenientMacByteStringParser = do- go 0xAB 0x12 0x0F 0x1C 0x88 0x79- "AB:12:0F:1C:88:79"- go 0xAB 0x12 0x0F 0x0C 0xAA 0x76- "AB1-20F-0CA-A76"- where+ go+ 0xAB+ 0x12+ 0x0F+ 0x1C+ 0x88+ 0x79+ "AB:12:0F:1C:88:79"+ go+ 0xAB+ 0x12+ 0x0F+ 0x0C+ 0xAA+ 0x76+ "AB1-20F-0CA-A76"+ where go a b c d e f str = Just (HexMac (Mac.fromOctets a b c d e f))- @=? fmap HexMac (Mac.decodeUtf8 (BC8.pack str))+ @=? fmap HexMac (Mac.decodeUtf8 (BC8.pack str)) testIPv4Parser :: Assertion testIPv4Parser = do go 202 10 19 54 "202.10.19.54" go 10 202 96 25 "10.202.96.25"- where+ where go a b c d str = Right (IPv4.fromOctets a b c d)- @=? (AB.parseOnly- (IPv4.parserUtf8 <* AT.endOfInput)- (BC8.pack str)- )+ @=? ( AB.parseOnly+ (IPv4.parserUtf8 <* AT.endOfInput)+ (BC8.pack str)+ ) testIPv6Parser :: (String -> Maybe HexIPv6) -> Assertion testIPv6Parser decode = do -- Basic test- go 0xABCD 0x1234 0xABCD 0x1234 0xDCBA 0x4321 0xFFFF 0xE0E0- "ABCD:1234:ABCD:1234:DCBA:4321:FFFF:E0E0"+ go+ 0xABCD+ 0x1234+ 0xABCD+ 0x1234+ 0xDCBA+ 0x4321+ 0xFFFF+ 0xE0E0+ "ABCD:1234:ABCD:1234:DCBA:4321:FFFF:E0E0" -- Tests that leading zeros can be omitted- go 0x1234 0x5678 0x9ABC 0xDEF0 0x0123 0x4567 0x89AB 0xCDEF- "1234:5678:9ABC:DEF0:123:4567:89AB:CDEF"+ go+ 0x1234+ 0x5678+ 0x9ABC+ 0xDEF0+ 0x0123+ 0x4567+ 0x89AB+ 0xCDEF+ "1234:5678:9ABC:DEF0:123:4567:89AB:CDEF" -- Test that the IPv6 "any" abbreviation works- go 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000- "::"- go 0x1623 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000- "1623::"- go 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0xABCD 0x1234- "::ABCD:1234"- go 0xAAAA 0x0000 0x0000 0x0000 0x0000 0x0000 0xABCD 0x1234- "AAAA::ABCD:1234"- go 0xAAAA 0x0000 0x0000 0x0000 0xBBBB 0x0000 0xABCD 0x1234- "AAAA::BBBB:0000:ABCD:1234"- go 0xAAAA 0x0000 0x0000 0x0000 0xBBBB 0x0000 0xABCD 0x1234- "AAAA:0000:0000:0000:BBBB::ABCD:1234"- where+ go+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ "::"+ go+ 0x1623+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ "1623::"+ go+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ 0xABCD+ 0x1234+ "::ABCD:1234"+ go+ 0xAAAA+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ 0x0000+ 0xABCD+ 0x1234+ "AAAA::ABCD:1234"+ go+ 0xAAAA+ 0x0000+ 0x0000+ 0x0000+ 0xBBBB+ 0x0000+ 0xABCD+ 0x1234+ "AAAA::BBBB:0000:ABCD:1234"+ go+ 0xAAAA+ 0x0000+ 0x0000+ 0x0000+ 0xBBBB+ 0x0000+ 0xABCD+ 0x1234+ "AAAA:0000:0000:0000:BBBB::ABCD:1234"+ where go a b c d e f g h str = Just (HexIPv6 (IPv6.fromWord16s a b c d e f g h))- @=?- decode str+ @=? decode str testIPv6ParserFailure :: (String -> Assertion) -> Assertion testIPv6ParserFailure go = do@@ -350,22 +495,21 @@ expectIPv6ParserFailure :: String -> Assertion expectIPv6ParserFailure str = Left ()- @=?- bimap (\_ -> ()) HexIPv6- (AT.parseOnly- (IPv6.parser <* AT.endOfInput)- (Text.pack str)- )+ @=? bimap+ (\_ -> ())+ HexIPv6+ ( AT.parseOnly+ (IPv6.parser <* AT.endOfInput)+ (Text.pack str)+ ) expectIPv6BytesParserFailure :: String -> Assertion expectIPv6BytesParserFailure s = Nothing- @=?- IPv6.decodeUtf8Bytes (Ascii.fromString s)+ @=? IPv6.decodeUtf8Bytes (Ascii.fromString s) testIPv6Encode :: (IPv6 -> Text.Text) -> Assertion testIPv6Encode enc = do- -- degenerate cases: "::" `roundTripsTo` "::" "1234::" `roundTripsTo` "1234::"@@ -402,10 +546,9 @@ "::ffff:00ff:ff00" `roundTripsTo` "::ffff:0.255.255.0" "::ffff:203.0.113.17" `roundTripsTo` "::ffff:203.0.113.17" "1234:5678::10.0.1.2" `roundTripsTo` "1234:5678::a00:102"- where- roundTripsTo s sExpected =- case AT.parseOnly (IPv6.parser <* AT.endOfInput) (Text.pack s) of+ roundTripsTo s sExpected =+ case AT.parseOnly (IPv6.parser <* AT.endOfInput) (Text.pack s) of Right result -> enc result @?= Text.pack sExpected Left failMsg -> fail ("failed to parse '" ++ s ++ "': " ++ failMsg) @@ -427,98 +570,117 @@ PH.testCase ("Should fail to decode [" ++ str ++ "]") $ IPv4.decode (Text.pack str) @?= Nothing testMacEncode :: Assertion-testMacEncode = Mac.encode (Mac.fromOctets 0xFF 0x00 0xAB 0x12 0x99 0x0F)- @?= Text.pack "ff:00:ab:12:99:0f"+testMacEncode =+ Mac.encode (Mac.fromOctets 0xFF 0x00 0xAB 0x12 0x99 0x0F)+ @?= Text.pack "ff:00:ab:12:99:0f" failure :: String -> Result-failure msg = failed- { reason = msg- , theException = Nothing- }+failure msg =+ failed+ { reason = msg+ , theException = Nothing+ } newtype HexMac = HexMac Mac deriving (Eq) instance Show HexMac where showsPrec _ (HexMac v) =- let (a,b,c,d,e,f) = Mac.toOctets v- in showHex a . showChar ':'- . showHex b . showChar ':'- . showHex c . showChar ':'- . showHex d . showChar ':'- . showHex e . showChar ':'- . showHex f-+ let (a, b, c, d, e, f) = Mac.toOctets v+ in showHex a+ . showChar ':'+ . showHex b+ . showChar ':'+ . showHex c+ . showChar ':'+ . showHex d+ . showChar ':'+ . showHex e+ . showChar ':'+ . showHex f newtype HexIPv6 = HexIPv6 IPv6 deriving (Eq) instance Show HexIPv6 where showsPrec _ (HexIPv6 v) =- let (a,b,c,d,e,f,g,h) = IPv6.toWord16s v- in showHex a . showChar ':'- . showHex b . showChar ':'- . showHex c . showChar ':'- . showHex d . showChar ':'- . showHex e . showChar ':'- . showHex f . showChar ':'- . showHex g . showChar ':'- . showHex h-+ let (a, b, c, d, e, f, g, h) = IPv6.toWord16s v+ in showHex a+ . showChar ':'+ . showHex b+ . showChar ':'+ . showHex c+ . showChar ':'+ . showHex d+ . showChar ':'+ . showHex e+ . showChar ':'+ . showHex f+ . showChar ':'+ . showHex g+ . showChar ':'+ . showHex h deriving instance Arbitrary IPv4 instance Arbitrary Word128 where arbitrary = Word128 <$> arbitrary <*> arbitrary- shrink (Word128 a b) = filter (/= Word128 a b)- [ Word128 0 0- , Word128 (div a 2) b- , Word128 a (div b 2)- ]+ shrink (Word128 a b) =+ filter+ (/= Word128 a b)+ [ Word128 0 0+ , Word128 (div a 2) b+ , Word128 a (div b 2)+ ] deriving instance Arbitrary IPv6 -- Half of the test cases generated are IPv6 mapped -- IPv4 addresses. instance Arbitrary IP where- arbitrary = oneof- [ IP.fromIPv4 <$> arbitrary- , IP.fromIPv6 <$> arbitrary- ]+ arbitrary =+ oneof+ [ IP.fromIPv4 <$> arbitrary+ , IP.fromIPv6 <$> arbitrary+ ] instance Arbitrary Mac where- arbitrary = Mac.fromOctets- <$> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary+ arbitrary =+ Mac.fromOctets+ <$> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary -- This instance can generate masks that exceed the recommended -- length of 32. instance Arbitrary IPv4Range where- arbitrary = IPv4.range <$> arbitrary <*> choose (0,32)+ arbitrary = IPv4.range <$> arbitrary <*> choose (0, 32) instance Arbitrary IPv6Range where- arbitrary = IPv6.range <$> arbitrary <*> choose (0,128)- shrink (IPv6Range addr mask) = liftA2 IPv6.range- (shrink addr)- (filter (/= mask) [0,div mask 2,if mask > 0 then mask - 1 else 0])+ arbitrary = IPv6.range <$> arbitrary <*> choose (0, 128)+ shrink (IPv6Range addr mask) =+ liftA2+ IPv6.range+ (shrink addr)+ (filter (/= mask) [0, div mask 2, if mask > 0 then mask - 1 else 0]) instance Arbitrary MacCodec where arbitrary = MacCodec <$> arbitrary <*> arbitrary instance Arbitrary MacGrouping where- arbitrary = oneof- [ MacGroupingPairs <$> arbitraryMacSeparator- , MacGroupingTriples <$> arbitraryMacSeparator- , MacGroupingQuadruples <$> arbitraryMacSeparator- , pure MacGroupingNoSeparator- ]+ arbitrary =+ oneof+ [ MacGroupingPairs <$> arbitraryMacSeparator+ , MacGroupingTriples <$> arbitraryMacSeparator+ , MacGroupingQuadruples <$> arbitraryMacSeparator+ , pure MacGroupingNoSeparator+ ] arbitraryMacSeparator :: Gen Char-arbitraryMacSeparator = elements [':','-','.','_']+arbitraryMacSeparator = elements [':', '-', '.', '_'] byteStringToBytes :: BC8.ByteString -> Bytes byteStringToBytes = Ascii.fromString . BC8.unpack