network-address (empty) → 0.1.0
raw patch · 6 files changed
+372/−0 lines, 6 filesdep +Cabaldep +QuickCheckdep +basesetup-changed
Dependencies added: Cabal, QuickCheck, base, criterion, test-framework, test-framework-quickcheck2
Files
- Benchmark.hs +35/−0
- Data/Network/Address.hs +33/−0
- Data/Network/Address/IP.hs +229/−0
- LICENSE +20/−0
- Setup.hs +2/−0
- network-address.cabal +53/−0
+ Benchmark.hs view
@@ -0,0 +1,35 @@+module Main where+import Criterion.Main+import Data.Network.Address++ipv4str :: Integral a => a -> String+ipv4str n = "192.168." ++ show (n `div` 255)+ ++ "."+ ++ show (n `mod` 255)+++ipv4 :: IPv4+ipv4 = toAddress 3232235777++ipv6 :: IPv6+ipv6 = toAddress 42540766452641154071740215577757643572++main = defaultMain+ [ bgroup "IPv4"+ [ bgroup "Parse"+ [ bench "read" $ whnf (readAddress :: String -> IPv4) "192.168.1.1"+ ]+ , bgroup "PrettyPrint"+ [ bench "show" $ whnf showAddress ipv4+ ]+ ]+ , bgroup "IPv6"+ [ bgroup "Parse"+ [ bench "read" $ whnf (readAddress :: String -> IPv6) "2001:0db8:85a3:0:0:8a2e:0370:7334"+ ]+ , bgroup "PrettyPrint"+ [ bench "show" $ whnf showAddress ipv6+ ]+ ]+ ]+
+ Data/Network/Address.hs view
@@ -0,0 +1,33 @@+{-+Copyright (c) 2011 Sebastian Nowicki <sebnow@gmail.com>++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.+-}++{- |Module : Data.Network.Address+ License : MIT+ Stability : experimental+ Portability: portable+-}+module Data.Network.Address (+ module Data.Network.Address.IP+) where++import Data.Network.Address.IP+
+ Data/Network/Address/IP.hs view
@@ -0,0 +1,229 @@+{-+Copyright (c) 2011 Sebastian Nowicki <sebnow@gmail.com>++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.+-}++{- |Module : Data.Network.Address.IP+ License : MIT+ Stability : experimental+ Portability: unportable+-}++{-# LANGUAGE FlexibleInstances, FunctionalDependencies, MultiParamTypeClasses #-}+module Data.Network.Address.IP (+ Address(..),+ Subnet(..),+ IPv4(..),+ IPv6(..),+ IPSubnet(..),+ Mask,+ toMask,+ fromMask,+) where++import Control.Monad (when)+import Data.Bits+import Data.Char (digitToInt, isDigit)+import Data.List (foldl', intercalate)+import Data.Word+import Numeric (showHex, readHex)+import Text.Printf (printf)++-- |The byte representation of an IP network mask.+type Mask = Integer++-- |The abstract data structure to represent an IPv4 address.+data IPv4 = IPv4 !Word32+ deriving (Eq, Ord, Bounded, Show, Read)++-- |The abstract data structure to represent an IPv6 address.+data IPv6 = IPv6 !Word64 !Word64+ deriving (Eq, Ord, Show, Read)++-- |The abstract data structure to represent an IPv4 subnetwork.+data (Address a) => IPSubnet a = IPSubnet a Mask deriving (Eq, Ord, Show, Read)++class (Eq a) => Address a where+ -- |Convert the byte representation to an IP 'Address'.+ toAddress :: Integer -> a+ -- |Return the byte representation of an IP 'Address'.+ fromAddress :: a -> Integer+ -- |Parse a textual representation of an IP 'Address'.+ readAddress :: String -> a+ -- |Return a conanical textual representation of an IP 'Address'.+ showAddress :: a -> String+ -- |Apply a mask to an 'Address'.+ maskAddress :: (Bits b, Integral b) => a -> b -> a+ maskAddress ip mask = toAddress . (\x -> x `div` (2^n) * 2^n) . fromAddress $ ip+ where n = fromMask mask++-- |The 'Subnet' class is used to perform operations on and manipulate+-- IP subnetworks.+class (Address a) => Subnet s a | s -> a where+ -- |Parse a textual representation of a 'Subnet'.+ readSubnet :: String -> s+ -- |Return a conanical textual representation of a 'Subnet'.+ showSubnet :: s -> String+ -- |Return the first 'IP' in the 'Subnet'.+ base :: s -> a+ -- |Return an binary representation of the subnet mask.+ netmask :: s -> Mask+ -- |Determine if an 'IP' is within the range of a 'Subnet'.+ member :: a -> s -> Bool+ member ip s = fromAddress ip .&. netmask s == fromAddress (base s)++--+-- IPv4+--++instance Address IPv4 where+ fromAddress (IPv4 x) = toInteger x+ toAddress = IPv4 . fromInteger+ readAddress = readIPv4+ showAddress = showIPv4++-- |Return a conanical textual representation of an IPv4 IP address,+-- e.g. @127.0.0.1@+showIPv4 :: IPv4 -> String+showIPv4 (IPv4 ip) = printf "%d.%d.%d.%d" a b c d+ where ( _, a) = shift8 r1+ (r1, b) = shift8 r2+ (r2, c) = shift8 r3+ (r3, d) = shift8 ip+ shift8 = (`divMod` 256)++-- |Parse a textual representation of an 'IPv4' IP address, returning the+-- address and the remainded of the 'String'.+readIPv4' :: String -> (IPv4, String)+readIPv4' s = (IPv4 $ sum . zipWith shift ds $ [24, 16, 8, 0], s')+ where (a, _:xs1) = span isDigit s+ (b, _:xs2) = span isDigit xs1+ (c, _:xs3) = span isDigit xs2+ (d, s') = span isDigit xs3+ ds = map (fromIntegral . digitsToInt) [a, b, c, d] :: [Word32]++-- |Parse a textual representation of an 'IPv4' IP address,+-- e.g. @127.0.0.1@+readIPv4 :: String -> IPv4+readIPv4 = fst . readIPv4'++--+-- IPv6+--++instance Address IPv6 where+ fromAddress = fromIPv6+ toAddress = toIPv6+ readAddress = readIPv6+ showAddress = showIPv6++-- |Parse a textual representation of an 'IPv6' IP address,+-- e.g. @1080:0:0:0:8:800:200C:417A@+readIPv6 :: String -> IPv6+-- TODO: Support collapsed 0's and alternative syntax, as per RFC3513.+readIPv6 s = case ipv6octets s of+ Just os -> toIPv6 . octetsToInteger . map (fst . head . readHex) $ os+ otherwise -> error "Data.IP: no parse"++octetsToInteger :: [Word16] -> Integer+octetsToInteger = foldl' (\x y -> (x `shift` 16) + fromIntegral y) 0++-- |Return a conanical textual representation of an 'IPv6' IP address,+-- e.g. @fedc:ba98:7654:3210:fedc:ba98:7654:3210@+showIPv6 :: IPv6 -> String+-- TODO: Collapse the longest group of 0's, as per RFC5952.+showIPv6 = intercalate ":" . map (`showHex` "") . reverse . octets . fromIPv6+ where octets x = case x `divMod` (2 ^ 16) of+ (0 , o) -> [o]+ (x', o) -> o : octets x'++-- |Split an IPv6 into octets (i.e. by ':').+ipv6octets :: String -> Maybe [String]+ipv6octets s = case span (/= ':') s of+ ("", ':':os) -> fmap ((:) "0") (ipv6octets os)+ ("", "") -> Just ["0"]+ (o, ':':os) -> fmap ((:) o) (ipv6octets os)+ (o, "") -> Just [o]+ otherwise -> Nothing++-- |Convert the byte representation to an 'IPv6' address.+toIPv6 :: Integer -> IPv6+toIPv6 x = IPv6 (fromIntegral a) (fromIntegral b)+ where ( _, a) = shift64 r1+ (r1, b) = shift64 x+ shift64 x = divMod (fromIntegral x) (2 ^ 64)++-- |Return the byte representation of an 'IPv6' IP address.+fromIPv6 :: IPv6 -> Integer+fromIPv6 (IPv6 a b) = (a' `shift` 64) + b'+ where a' = fromIntegral a+ b' = fromIntegral b++--+-- Subnet+--++instance Subnet (IPSubnet IPv4) IPv4 where+ showSubnet = showIPSubnet+ readSubnet = readIPSubnet+ base (IPSubnet b _) = b+ netmask (IPSubnet _ m) = m++instance Subnet (IPSubnet IPv6) IPv6 where+ showSubnet = showIPSubnet+ readSubnet = readIPSubnet+ base (IPSubnet b _) = b+ netmask (IPSubnet _ m) = m++-- |Create an 'IPv4Subnet' data structure.+ipSubnet :: (Address a) => a -> Mask -> IPSubnet a+ipSubnet ip m = IPSubnet (maskAddress ip m) m++-- |Return a conanical textual representation of an IP 'Address' and+-- 'Subnet'.+showIPSubnet :: (Address a) => IPSubnet a -> String+showIPSubnet (IPSubnet ip mask) = showAddress ip ++ "/" ++ show (fromMask mask :: Integer)++-- |Parse a textual representation of an IP address and subnet.+readIPSubnet :: (Address a) => String -> IPSubnet a+readIPSubnet s = ipSubnet ip mask+ where (x, '/':y) = span (/= '/') s+ mask = toMask . digitsToInt $ y+ ip = readAddress x++-- |Convert a decimal mask to binary (e.g. 8 -> 1111.0000.0000.0000).+toMask :: (Integral a, Bits a, Integral b, Bits b) => a -> b+toMask size | size > 0 = complement (2^size - 1)+ | otherwise = complement 0++-- |Convert a binary mask to decimal (e.g. 1111.0000.0000.0000 -> 8).+fromMask :: (Integral a, Bits a, Integral b) => a -> b+fromMask m = timesDivisible (fromIntegral . complement $ m) 2++-- |Determine how many times a number is divisible by another number.+timesDivisible :: (Integral a) => a -> a -> a+timesDivisible a x = timesDivisible' a x 0+ where timesDivisible' a x c | a > 0 = timesDivisible' (a `div` x) x (c + 1)+ | otherwise = c++-- |Parse an integer.+digitsToInt :: String -> Int+digitsToInt = foldl' ((+) . (10 *)) 0 . map digitToInt+
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2011 Sebastian Nowicki <sebnow@gmail.com>++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ network-address.cabal view
@@ -0,0 +1,53 @@+Name: network-address+Version: 0.1.0+Synopsis: IP data structures and textual representation+Description: A library for parsing, representing, computing and+ transforming network addresses such a IPv4, IPv6+ and subnetworks.+Homepage: http://github.com/sebnow/haskell-network-address+License: MIT+License-file: LICENSE+Author: Sebastian Nowicki+Maintainer: sebnow@gmail.com+Category: Data, Network+Build-type: Simple+Cabal-version: >=1.9.2++Source-Repository head+ type: git+ location: hit://github.com/sebnow/haskell-network-address.git++Flag benchmark+ Description: Build benchmarking + Default: False++Library+ Exposed-modules:+ Data.Network.Address+ Data.Network.Address.IP++ Build-depends:+ base == 4.*,+ Cabal >= 1.9.2 && < 1.11++test-suite test-package+ Type: exitcode-stdio-1.0+ Main-is: Test.hs+ Build-depends:+ base == 4.*,+ Cabal >= 1.9.2 && < 1.11,+ QuickCheck == 2.4.*,+ test-framework == 0.4.*,+ test-framework-quickcheck2 == 0.2.*++Executable benchmark+ Main-is: Benchmark.hs+ if flag(benchmark)+ Buildable: True+ else+ Buildable: False+ Build-depends:+ base == 4.*,+ Cabal >= 1.9.2 && < 1.11,+ criterion == 0.5.0.*+