module Foundation.UUID
( UUID(..)
, newUUID
, nil
, fromBinary
, uuidParser
) where
import Control.Monad (unless)
import Data.Maybe (fromMaybe)
import Basement.Compat.Base
import Foundation.Collection (Element, Sequential, foldl')
import Foundation.Class.Storable
import Foundation.Hashing.Hashable
import Foundation.Bits
import Foundation.Parser
import Foundation.Numerical
import Foundation.Primitive
import Basement.Base16
import Basement.IntegralConv
import Basement.Types.OffsetSize
import qualified Basement.UArray as UA
import Foundation.Random (MonadRandom, getRandomBytes)
data UUID = UUID !Word64 !Word64
deriving (Eq,Ord,Typeable)
instance Show UUID where
show = toLString
instance NormalForm UUID where
toNormalForm !_ = ()
instance Hashable UUID where
hashMix (UUID a b) = hashMix a . hashMix b
instance Storable UUID where
peek p = UUID <$> (fromBE <$> peekOff ptr 0)
<*> (fromBE <$> peekOff ptr 1)
where ptr = castPtr p :: Ptr (BE Word64)
poke p (UUID a b) = do
pokeOff ptr 0 (toBE a)
pokeOff ptr 1 (toBE b)
where ptr = castPtr p :: Ptr (BE Word64)
instance StorableFixed UUID where
size _ = 16
alignment _ = 8
withComponent :: UUID -> (Word32 -> Word16 -> Word16 -> Word16 -> Word64 -> a) -> a
withComponent (UUID a b) f = f x1 x2 x3 x4 x5
where
!x1 = integralDownsize (a .>>. 32)
!x2 = integralDownsize ((a .>>. 16) .&. 0xffff)
!x3 = integralDownsize (a .&. 0xffff)
!x4 = integralDownsize (b .>>. 48)
!x5 = (b .&. 0x0000ffffffffffff)
toLString :: UUID -> [Char]
toLString uuid = withComponent uuid $ \x1 x2 x3 x4 x5 ->
hexWord_4 x1 $ addDash $ hexWord_2 x2 $ addDash $ hexWord_2 x3 $ addDash $ hexWord_2 x4 $ addDash $ hexWord64_6 x5 []
where
addDash = (:) '-'
hexWord_2 w l = case hexWord16 w of
(c1,c2,c3,c4) -> c1:c2:c3:c4:l
hexWord_4 w l = case hexWord32 w of
(c1,c2,c3,c4,c5,c6,c7,c8) -> c1:c2:c3:c4:c5:c6:c7:c8:l
hexWord64_6 w l = case word64ToWord32s w of
Word32x2 wHigh wLow -> hexWord_2 (integralDownsize wHigh) $ hexWord_4 wLow l
nil :: UUID
nil = UUID 0 0
newUUID :: MonadRandom randomly => randomly UUID
newUUID = fromMaybe (error "Foundation.UUID.newUUID: the impossible happned")
. fromBinary
<$> getRandomBytes 16
fromBinary :: UA.UArray Word8 -> Maybe UUID
fromBinary ba
| UA.length ba /= 16 = Nothing
| otherwise = Just $ UUID w0 w1
where
w0 = (b15 .<<. 56) .|. (b14 .<<. 48) .|. (b13 .<<. 40) .|. (b12 .<<. 32) .|.
(b11 .<<. 24) .|. (b10 .<<. 16) .|. (b9 .<<. 8) .|. b8
w1 = (b7 .<<. 56) .|. (b6 .<<. 48) .|. (b5 .<<. 40) .|. (b4 .<<. 32) .|.
(b3 .<<. 24) .|. (b2 .<<. 16) .|. (b1 .<<. 8) .|. b0
b0 = integralUpsize (UA.unsafeIndex ba 0)
b1 = integralUpsize (UA.unsafeIndex ba 1)
b2 = integralUpsize (UA.unsafeIndex ba 2)
b3 = integralUpsize (UA.unsafeIndex ba 3)
b4 = integralUpsize (UA.unsafeIndex ba 4)
b5 = integralUpsize (UA.unsafeIndex ba 5)
b6 = integralUpsize (UA.unsafeIndex ba 6)
b7 = integralUpsize (UA.unsafeIndex ba 7)
b8 = integralUpsize (UA.unsafeIndex ba 8)
b9 = integralUpsize (UA.unsafeIndex ba 9)
b10 = integralUpsize (UA.unsafeIndex ba 10)
b11 = integralUpsize (UA.unsafeIndex ba 11)
b12 = integralUpsize (UA.unsafeIndex ba 12)
b13 = integralUpsize (UA.unsafeIndex ba 13)
b14 = integralUpsize (UA.unsafeIndex ba 14)
b15 = integralUpsize (UA.unsafeIndex ba 15)
uuidParser :: ( ParserSource input, Element input ~ Char
, Sequential (Chunk input), Element input ~ Element (Chunk input)
)
=> Parser input UUID
uuidParser = do
hex1 <- parseHex (CountOf 8) <* element '-'
hex2 <- parseHex (CountOf 4) <* element '-'
hex3 <- parseHex (CountOf 4) <* element '-'
hex4 <- parseHex (CountOf 4) <* element '-'
hex5 <- parseHex (CountOf 12)
return $ UUID (hex1 .<<. 32 .|. hex2 .<<. 16 .|. hex3)
(hex4 .<<. 48 .|. hex5)
parseHex :: ( ParserSource input, Element input ~ Char
, Sequential (Chunk input), Element input ~ Element (Chunk input)
)
=> CountOf Char -> Parser input Word64
parseHex count = do
r <- toList <$> take count
unless (and $ isValidHexa <$> r) $
reportError $ Satisfy $ Just $ "expecting hexadecimal character only: "
<> fromList (show r)
return $ listToHex 0 r
where
listToHex = foldl' (\acc' x -> acc' * 16 + fromHex x)
isValidHexa :: Char -> Bool
isValidHexa c = ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
fromHex '0' = 0
fromHex '1' = 1
fromHex '2' = 2
fromHex '3' = 3
fromHex '4' = 4
fromHex '5' = 5
fromHex '6' = 6
fromHex '7' = 7
fromHex '8' = 8
fromHex '9' = 9
fromHex 'a' = 10
fromHex 'b' = 11
fromHex 'c' = 12
fromHex 'd' = 13
fromHex 'e' = 14
fromHex 'f' = 15
fromHex 'A' = 10
fromHex 'B' = 11
fromHex 'C' = 12
fromHex 'D' = 13
fromHex 'E' = 14
fromHex 'F' = 15
fromHex _ = error "Foundation.UUID.parseUUID: the impossible happened"