{-# LANGUAGE OverloadedStrings #-} -- | This module was taken, with modifications, from the -- package. module Data.Mac ( Mac () -- ** String conversions , readMac , showMac -- ** Octet conversions , fromOctets , toOctets -- ** Other conversions , toWord64 ) where import Control.Monad (unless) import Data.Bits import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Lazy as L import qualified Data.Text.Lazy.Builder as L import qualified Data.Text.Lazy.Builder.Int as L import Data.Text.Read import Data.Word import Import -- |A Mac is a 6-byte unique identifier used in layer-two network addressing. -- Its `Storable` instance occupies 6 bytes of memory when `poke`d with the -- first byte occupying the lowest address, and the last byte occupying the -- highest address. -- -- A `Show` instance is omitted to avoid confusion. Use `showMac` and -- `readMac` to convert between `String`s. newtype Mac = Mac { unMac :: Word64 } deriving (Bounded, Eq, Ord) instance Storable Mac where sizeOf _ = 6 alignment _ = 1 peek p = fromOctets <$> (peek $ castPtr p) <*> peekByteOff p 1 <*> peekByteOff p 2 <*> peekByteOff p 3 <*> peekByteOff p 4 <*> peekByteOff p 5 poke p mac = do poke (castPtr p) a pokeByteOff p 1 b pokeByteOff p 2 c pokeByteOff p 3 d pokeByteOff p 4 e pokeByteOff p 5 f where (a, b, c, d, e, f) = toOctets mac toWord64 :: Mac -> Word64 toWord64 = unMac -- |Represent a `Mac` as a `String`. The supplied separator will be placed -- between each octet in the final output. -- -- >>> showMac "" $ fromOctets 0xa 0xb 0xc 0xd 0xe 0xf -- "0a0b0c0d0e0f" -- -- >>> showMac ":" $ fromOctets 0x11 0x22 0x33 0x44 0x55 0x66 -- "11:22:33:44:55:66" showMac :: Text -> Mac -> Text showMac sep mac = T.intercalate sep . map octet $ [a, b, c, d, e, f] where octet x = pad (2 :: Int) . L.toStrict . L.toLazyText . L.hexadecimal $ x (a, b, c, d, e, f) = toOctets mac pad n str = T.replicate (max 0 (n - T.length str)) "0" <> str -- |Parse a `Text` value as a `Mac`. The string should not use any -- separators between octets. -- -- >>> let Right mac = readMac "000102030405" -- >>> toOctets mac -- (0, 1, 2, 3, 4, 5) readMac :: Text -> Either String Mac readMac s = fmapL (\e -> "Error parsing MAC address: " ++ e) $ do (a, s2) <- octet s (b, s3) <- octet s2 (c, s4) <- octet s3 (d, s5) <- octet s4 (e, s6) <- octet s5 (f, s7) <- octet s6 unless (s7 == "") $ Left "exactly 6 octets were expected." fromOctets <$> digit a <*> digit b <*> digit c <*> digit d <*> digit e <*> digit f where octet :: Text -> Either String (Int, Text) octet s0 = do (a, s2) <- hexadecimal a0 unless (s2 == "") $ Left "invalid characters" return (a, rest) where (a0, rest) = T.splitAt 2 s0 digit :: Int -> Either String Word8 digit x | x < 0 || x > 255 = Left "digit out of range." | otherwise = Right $ fromIntegral x -- |A Mac address is 48-bits wide. This function will construct a `Mac` from -- 6 octets. fromOctets :: Word8 -> Word8 -> Word8 -> Word8 -> Word8 -> Word8 -> Mac fromOctets a b c d e f = Mac $ (fromIntegral a `shiftL` 40) .|. (fromIntegral b `shiftL` 32) .|. (fromIntegral c `shiftL` 24) .|. (fromIntegral d `shiftL` 16) .|. (fromIntegral e `shiftL` 8) .|. (fromIntegral f) -- |Extract each of the 6 octets from a `Mac`. toOctets :: Mac -> (Word8, Word8, Word8, Word8, Word8, Word8) toOctets (Mac word) = ( byte 5 word, byte 4 word, byte 3 word , byte 2 word, byte 1 word, byte 0 word) where byte i w = fromIntegral (w `shiftR` (i * 8))