moduleCrypto.MAC.TOTP.Factory(Factory(..),initialize,initializeIO,initGrace,epochEq,authenticate,authenticateBS,roundTime,setTime,validUntil,shouldRefresh,refresh,refreshIO,tryRefreshEvery,startRefreshThread,getNext,getMessages,getNextIO,getMessagesIO)whereimportCrypto.Hashhiding(hmac)importCrypto.MAC.HMACimportqualifiedData.ByteStringasBSimportData.ByteString(ByteString)importData.IntimportData.WordimportData.BitsimportSystem.Posix.TimeimportSystem.Posix.Types(EpochTime)importForeign.C.Types(CTime(..))importControl.ConcurrentimportData.IORefimportData.SerializeinstanceIntegralCTimewherequot(CTimea)(CTimeb)=CTime(a`quot`b)rem(CTimea)(CTimeb)=CTime(a`rem`b)div(CTimea)(CTimeb)=CTime(a`div`b)mod(CTimea)(CTimeb)=CTime(a`mod`b)quotRem(CTimen)(CTimed)=(\(d,m)->(CTimed,CTimem))(quotRemnd)divMod(CTimen)(CTimed)=(\(d,m)->(CTimed,CTimem))(divModnd)toInteger(CTimet)=toIntegertdataFactory=Factory{secret::ByteString,secretInit::ByteString,count::Int64,validSeconds::CTime,refreshEpoch::EpochTime,hashMethod::ByteString->ByteString,blockSize::Int,prefix::ByteString->ByteString}initialize::(ByteString->ByteString)->Int->Int->ByteString->CTime->FactoryinitializehashMethodblockSizetokenBytessecretInitvalidSeconds=ifvalidSeconds<1thenerror"validSeconds must be >= 1"elseFactory{secret=BS.empty,secretInit,count=0,validSeconds,refreshEpoch=0,hashMethod,blockSize,prefix=BS.taketokenBytes}initializeIO::(ByteString->ByteString)->Int->Int->ByteString->CTime->IO(Factory)initializeIOhashMethodblockSizetokenBytessecretInitvalidSeconds=dotime<-epochTimereturn$refreshtime(initializehashMethodblockSizetokenBytessecretInitvalidSeconds)initGrace::Factory->CTime->FactoryinitGrace(Factory_secretInit_validSecondsrefreshEpochhashMethodblockSizeprefix)graceSeconds=lettime=refreshEpoch-graceSeconds*validSecondsinrefreshtime$Factory{secret=BS.empty,secretInit,count=0,validSeconds,refreshEpoch=0,hashMethod,blockSize,prefix}epochEq::Factory->CTime->Factory->BoolepochEqbaseFnf=refreshEpochf==refreshEpochbaseF-n*(validSecondsbaseF)incr::Factory->Factoryincrf=f{count=countf+1}authenticate::Serializeb=>Factory->b->ByteStringauthenticatefactory=authenticateBSfactoryencodeauthenticateBS::Factory->(b->ByteString)->b->ByteStringauthenticateBSfactoryencodeFunmessage=(prefixfactory)$hmac(hashMethodfactory)(blockSizefactory)(secretfactory)(encodeFunmessage)hashCount::Factory->ByteStringhashCountf=authenticatef(countf)roundTime::CTime->CTime->CTimeroundTimetr=(t`div`r)*rsetTime::CTime->Factory->FactorysetTimetf=letct'@(CTimet')=roundTimet(validSecondsf)timeBytes=encodet'inf{refreshEpoch=ct',secret=(hashMethodf)$BS.concat[secretInitf,timeBytes]}validUntil::Factory->EpochTimevalidUntilf=refreshEpochf+validSecondsfshouldRefresh::Factory->EpochTime->BoolshouldRefreshft=t>=validUntilfrefresh::EpochTime->Factory->Factoryrefreshtimefactory=ifshouldRefreshfactorytimethen(setTimetimefactory){count=0}elsefactoryrefreshIO::Factory->IO(Factory)refreshIOfactory=dotime<-epochTimereturn$refreshtimefactorytryRefreshEvery::Int-- ^ The delay according to Control.Concurrent.threadDelay before refresh attempts.->IORef(Factory)-- ^ The current factory.->IO()tryRefreshEverydelayfactoryRef=dothreadDelaydelaytime<-epochTimeatomicModifyIOReffactoryRef(\f->letf'=ifvalidUntilf<=timethenrefreshtimefelsefin(f',()))tryRefreshEverydelayfactoryRefstartRefreshThread::Int->Factory->IO(ThreadId,IORef(Factory))startRefreshThreaddelayfactory=dotime<-epochTimeletfactory'=refreshtimefactoryfactoryRef<-newIOReffactory't<-forkIO(tryRefreshEverydelayfactoryRef)return(t,factoryRef)getNext::Factory->(Factory,ByteString)getNextf=(incrf,hashCountf)getMessages::Int->Factory->(Factory,[ByteString])getMessages0f=(f,[])getMessagesnf=let(f',keys)=getMessages(n-1)f(f'',key)=getNextf'in(f'',key:keys)getNextIO::IORef(Factory)->IO(ByteString)getNextIOfactoryRef=atomicModifyIOReffactoryRefgetNextgetMessagesIO::IORef(Factory)->Int->IO[ByteString]getMessagesIO_n|n<1=return[]getMessagesIOfactoryRefn=atomicModifyIOReffactoryRef(getMessagesn)