module Database.Blacktip ( module Database.Blacktip.Types , generateUniqueId , generateUniqueId' , getInterfaceByName , getUnixMillis , integerToRecord , toBase62 ) where import qualified Control.Concurrent as CC import qualified Control.Concurrent.MVar as MV import qualified Data.ByteString.Char8 as B import qualified Data.Locator as DL import qualified Data.Time.Clock.POSIX as PSX import qualified Filesystem as FS import qualified Filesystem.Path.CurrentOS as FPC import qualified Network.Info as NI import qualified Safe import Control.Exception (mask, try) import Control.Monad (forever, when) import Data.Bits import Data.Bits.Bitwise (fromListBE) import Data.List.Split (chunksOf) import Database.Blacktip.Types import System.IO.Unsafe (unsafePerformIO) -- There are only supposed to be one of these -- babies running per node (MAC address)! getInterfaceByName :: InterfaceName -> IO (Maybe NI.NetworkInterface) getInterfaceByName n = fmap (Safe.headMay . filter ((n ==) . NI.name)) NI.getNetworkInterfaces toBase62 :: Integer -> String toBase62 = DL.toBase62 getMac :: Interface -> IO (Maybe NI.MAC) getMac iface = case iface of NIInterface interf -> return $ Just (NI.mac interf) IName name -> (fmap . fmap) NI.mac (getInterfaceByName name) getUnixMillis :: IO Milliseconds getUnixMillis = fmap (round . (*1000)) PSX.getPOSIXTime -- We don't want multiple of these running around via inlining {-# NOINLINE serverState #-} serverState :: MV.MVar ServerState -- unsafePerformIO so it doesn't create an -- emptyMVar when I bind against serverState serverState = unsafePerformIO $ MV.newEmptyMVar readTimestamp :: FPC.FilePath -> IO Int readTimestamp path = do result <- try $ FS.readFile path :: IO (Either IOError B.ByteString) case result of (Right "") -> return 0 _ -> return $ either (const 0) (read . B.unpack) result writeTimestamp :: MV.MVar ServerState -> FPC.FilePath -> IO CC.ThreadId writeTimestamp s path = do CC.forkIO go where go = forever $ do ss <- MV.readMVar s mask $ \_ -> do FS.writeFile path (B.pack (show (ssTime ss))) -- sleep for 1 second CC.threadDelay 1000000 arrowOfTimeError :: (Show a, Show b) => a -> b -> c arrowOfTimeError ts stateTime = error ("ERROR ARROW OF TIME BACKWARDS - Had timestamp: " ++ show ts ++ " and state time: " ++ show stateTime) bumpItYo :: Milliseconds -> ServerState -> ServerState bumpItYo ts s | ts == stateTime = s { ssSequence = (+1) stateSeq } | ts > stateTime = s { ssTime = ts, ssSequence = 0 } | otherwise = arrowOfTimeError ts stateTime where stateTime = ssTime s stateSeq = ssSequence s generateUniqueId' :: Config -> IO (Either NoInterfaceError (UniqueId, IdentityRecord)) generateUniqueId' config = do millis <- getUnixMillis empty <- MV.isEmptyMVar serverState _ <- when empty (initWriter (timestampPath config)) -- newState <- MV.modifyMVar ss (bumpItYo ms) mState <- MV.takeMVar serverState let newState = bumpItYo millis mState _ <- MV.putMVar serverState newState let sSeq = ssSequence $ newState mMac <- (getMac . interface) config case mMac of Nothing -> return $ Left NoInterfaceError Just mac -> return $ Right ((binnify millis mac sSeq), IdentityRecord millis mac sSeq) generateUniqueId :: Config -> IO (Either NoInterfaceError UniqueId) generateUniqueId config = do uid <- generateUniqueId' config return $ fmap fst uid initWriter :: FPC.FilePath -> IO () initWriter path = do ms <- getUnixMillis stateTime <- readTimestamp path _ <- when (ms <= stateTime) (arrowOfTimeError ms stateTime) _ <- MV.putMVar serverState (ServerState ms 0) _ <- writeTimestamp serverState path return () binnify :: Milliseconds -> NI.MAC -> Sequence -> Integer binnify ms (NI.MAC a b c d e f) sq = withSq where withTimestamp = shift (toInteger ms) 64 withMacA = shift (toInteger a) 56 .|. withTimestamp withMacB = shift (toInteger b) 48 .|. withMacA withMacC = shift (toInteger c) 40 .|. withMacB withMacD = shift (toInteger d) 32 .|. withMacC withMacE = shift (toInteger e) 24 .|. withMacD withMacF = shift (toInteger f) 16 .|. withMacE withSq = withMacF .|. toInteger sq -- works with Integer since I am providing range, big-endian bitRange :: Bits a => a -> Int -> Int -> [Bool] bitRange n lo hi = reverse $ map (testBit n) [lo..hi] integerToRecord :: Integer -> IdentityRecord integerToRecord n = IdentityRecord milliseconds mac recSequence -- extract 128 bits. where extractedBits = bitRange n 0 127 milliBits = take 64 extractedBits macBits = chunksOf 8 $ take 48 $ drop 64 extractedBits sequenceBits = take 16 $ drop 112 extractedBits milliseconds = fromListBE milliBits :: Milliseconds a = fromListBE (macBits !! 0) b = fromListBE (macBits !! 1) c = fromListBE (macBits !! 2) d = fromListBE (macBits !! 3) e = fromListBE (macBits !! 4) f = fromListBE (macBits !! 5) mac = (NI.MAC a b c d e f) recSequence = fromListBE sequenceBits :: Sequence