module Network.Protocol.NetSNMP (
ASNValue(..), SnmpResult(..), SnmpVersion(..), RawOID, OIDpart,
Hostname, Community,
snmp_version_1, snmp_version_2c, snmp_version_3,
initialize,
snmpGet, snmpNext, snmpWalk, snmpBulkWalk,
showASNValue,
)
where
import Control.Monad
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.ByteString.Char8 ()
import qualified Data.ByteString.UTF8 as Utf8
import Data.List
import Foreign hiding (void)
import Foreign.C.String
import Foreign.C.Types
max_string_len = 1023
data Session = Session
{ getVersion :: SnmpVersion
, getSessp :: Ptr SnmpSession
, getSptr :: Ptr SnmpSession
}
data SnmpSession
instance Storable SnmpSession where
sizeOf _ = (416)
alignment _ = 16
data SnmpPDU
instance Storable SnmpPDU where
sizeOf _ = (272)
alignment _ = 16
type OIDpart = Word64
type RawOID = [OIDpart]
showOid :: RawOID -> String
showOid = concatMap (('.':) . show)
oidToByteString :: RawOID -> ByteString
oidToByteString = Utf8.fromString . showOid
data CVarList
data ASNValue
= OctetString ByteString [Word8]
| OID ByteString ByteString [Word32]
| Integer32 Int32
| Integer64 Int64
| Counter32 Word32
| Counter64 Word64
| Unsigned32 Word32
| Unsigned64 Word64
| Gauge32 Word32
| IpAddress ByteString [Word8]
| Opaque [Word8]
| TimeTicks ByteString Word32
| Boolean Bool
| IEEEFloat Float
| IEEEDouble Double
| Null
| Unsupported Int ByteString
deriving (Eq, Show)
data SnmpResult = SnmpResult {
oid :: RawOID,
value :: ASNValue
} deriving (Eq, Show)
newtype SnmpVersion = SnmpVersion {
unSnmpVersion :: CLong
} deriving (Eq, Show)
type Hostname = ByteString
type Community = ByteString
snmp_version_1 :: SnmpVersion
snmp_version_1 = SnmpVersion 0
snmp_version_2c :: SnmpVersion
snmp_version_2c = SnmpVersion 1
snmp_version_3 :: SnmpVersion
snmp_version_3 = SnmpVersion 3
asn_boolean :: CUChar
asn_boolean = 1
asn_integer :: CUChar
asn_integer = 2
asn_bit_str :: CUChar
asn_bit_str = 3
asn_octet_str :: CUChar
asn_octet_str = 4
asn_null :: CUChar
asn_null = 5
asn_object_id :: CUChar
asn_object_id = 6
asn_sequence :: CUChar
asn_sequence = 16
asn_set :: CUChar
asn_set = 17
asn_universal :: CUChar
asn_universal = 0
asn_application :: CUChar
asn_application = 64
asn_context :: CUChar
asn_context = 128
asn_private :: CUChar
asn_private = 192
asn_primitive :: CUChar
asn_primitive = 0
asn_constructor :: CUChar
asn_constructor = 32
asn_long_len :: CUChar
asn_long_len = 128
asn_extension_id :: CUChar
asn_extension_id = 31
asn_bit8 :: CUChar
asn_bit8 = 128
asn_ipaddress :: CUChar
asn_ipaddress = 64
asn_counter :: CUChar
asn_counter = 65
asn_gauge :: CUChar
asn_gauge = 66
asn_unsigned :: CUChar
asn_unsigned = 66
asn_timeticks :: CUChar
asn_timeticks = 67
asn_opaque :: CUChar
asn_opaque = 68
asn_nsap :: CUChar
asn_nsap = 69
asn_counter64 :: CUChar
asn_counter64 = 70
asn_uinteger :: CUChar
asn_uinteger = 71
asn_float :: CUChar
asn_float = 72
asn_double :: CUChar
asn_double = 73
asn_integer64 :: CUChar
asn_integer64 = 74
asn_unsigned64 :: CUChar
asn_unsigned64 = 75
newtype SnmpPDUType = SnmpPDUType { unSnmpPDUType :: CInt }
snmp_msg_get :: SnmpPDUType
snmp_msg_get = SnmpPDUType 160
snmp_msg_getnext :: SnmpPDUType
snmp_msg_getnext = SnmpPDUType 161
snmp_msg_response :: SnmpPDUType
snmp_msg_response = SnmpPDUType 162
snmp_msg_set :: SnmpPDUType
snmp_msg_set = SnmpPDUType 163
snmp_msg_trap :: SnmpPDUType
snmp_msg_trap = SnmpPDUType 164
snmp_msg_getbulk :: SnmpPDUType
snmp_msg_getbulk = SnmpPDUType 165
snmp_msg_inform :: SnmpPDUType
snmp_msg_inform = SnmpPDUType 166
snmp_msg_trap2 :: SnmpPDUType
snmp_msg_trap2 = SnmpPDUType 167
snmp_msg_report :: SnmpPDUType
snmp_msg_report = SnmpPDUType 168
snmp_stat_success :: CInt
snmp_stat_success = 0
snmp_stat_error :: CInt
snmp_stat_error = 1
snmp_stat_timeout :: CInt
snmp_stat_timeout = 2
snmp_err_noerror :: CInt
snmp_err_noerror = 0
max_oid_len = 128 :: CInt
initialize :: IO ()
initialize = do
withCString "Haskell bindings" c_init_snmp
void $ withCString "127.0.0.1" $ \localhost ->
withCString "public" $ \public ->
alloca $ \session -> runTrouble $
readyCommunitySession snmp_version_2c localhost public session >>= closeSession
readyCommunitySession
:: SnmpVersion
-> CString
-> CString
-> Ptr SnmpSession
-> Trouble Session
readyCommunitySession version hostname community session = do
community_len <- t_strlen community
t_snmp_sess_init session
pokeSessRetries session 3
pokeSessTimeout session 10000000
pokeSessPeername session hostname
pokeSessVersion session (unSnmpVersion version)
pokeSessCommunity session community
pokeSessCommLen session community_len
sessp <- t_snmp_sess_open session
sptr <- t_snmp_sess_session sessp
return $ Session version sessp sptr
closeSession :: Session -> Trouble ()
closeSession session = hoistT (c_snmp_sess_close (getSessp session))
snmpGet
:: SnmpVersion
-> Hostname
-> Community
-> RawOID
-> IO (Either String SnmpResult)
snmpGet version hostname community oid' =
B.useAsCString hostname $ \cshost ->
B.useAsCString community $ \cscomm ->
alloca $ \session ->
runTrouble $ bracketT
(readyCommunitySession version cshost cscomm session)
closeSession
(flip (mkSnmpGet snmp_msg_get) oid')
snmpNext
:: SnmpVersion
-> Hostname
-> Community
-> RawOID
-> IO (Either String SnmpResult)
snmpNext version hostname community oid' =
B.useAsCString hostname $ \cshost ->
B.useAsCString community $ \cscomm ->
alloca $ \session ->
runTrouble $ bracketT
(readyCommunitySession version cshost cscomm session)
closeSession
(flip (mkSnmpGet snmp_msg_getnext) oid')
snmpWalk
:: SnmpVersion
-> Hostname
-> Community
-> RawOID
-> IO (Either String [SnmpResult])
snmpWalk version hostname community walkoid =
B.useAsCString hostname $ \cshost ->
B.useAsCString community $ \cscomm ->
alloca $ \session ->
runTrouble $ bracketT
(readyCommunitySession version cshost cscomm session)
closeSession
(go walkoid . mkSnmpGet snmp_msg_getnext)
where
go :: RawOID -> (RawOID -> Trouble SnmpResult) -> Trouble [SnmpResult]
go oid' next = do
v@(SnmpResult nextoid _) <- next oid'
case () of
_ | nextoid == oid' -> return []
| walkoid `isPrefixOf` nextoid -> do
vs <- go nextoid next
return (v:vs)
| otherwise -> return []
snmpBulkWalk
:: Hostname
-> Community
-> RawOID
-> IO (Either String [SnmpResult])
snmpBulkWalk hostname community walkoid =
B.useAsCString hostname $ \cshost ->
B.useAsCString community $ \cscomm ->
alloca $ \session ->
runTrouble $ bracketT
(readyCommunitySession snmp_version_2c cshost cscomm session)
closeSession
(bulkWalk walkoid walkoid)
where
bulkWalk :: RawOID -> RawOID -> Session -> Trouble [SnmpResult]
bulkWalk rootoid startoid session = do
vals <- filter (\r -> oid r `isSubIdOf` rootoid) <$> mkSnmpBulkGet 0 30 startoid session
case vals of
[] -> return []
rs -> (vals ++) <$> bulkWalk rootoid (oid (last rs)) session
isSubIdOf :: RawOID -> RawOID -> Bool
isSubIdOf oa ob = ob `isPrefixOf` oa
mkSnmpBulkGet :: CLong -> CLong -> RawOID -> Session -> Trouble [SnmpResult]
mkSnmpBulkGet non_repeaters max_repetitions oid' session =
allocaArrayT (fromIntegral max_oid_len) $ \oids -> do
let version = getVersion session
pdu_req <- buildPDU snmp_msg_getbulk oid' oids version
pokePDUNonRepeaters pdu_req non_repeaters
pokePDUMaxRepetitions pdu_req max_repetitions
dispatchSnmpReq pdu_req session
mkSnmpGet :: SnmpPDUType -> Session -> RawOID -> Trouble SnmpResult
mkSnmpGet pdutype session oid' = do
res <- allocaArrayT (fromIntegral max_oid_len) $ \oids -> do
let version = getVersion session
pdu_req <- buildPDU pdutype oid' oids version
dispatchSnmpReq pdu_req session
if null res then throwT ("Could not get the snmp value at " ++ showOid oid')
else return $ head res
dispatchSnmpReq :: Ptr SnmpPDU -> Session -> Trouble [SnmpResult]
dispatchSnmpReq pdu_req session =
allocaT $ \response_ptr -> do
let sessp = getSessp session
let sptr = getSptr session
pokeT response_ptr nullPtr
handleT
(\s -> do
pdu_resp <- peekT response_ptr
unless (pdu_resp == nullPtr) $ t_snmp_free_pdu pdu_resp
throwT s)
(do
t_snmp_sess_synch_response sessp sptr pdu_req response_ptr
pdu_resp <- peekT response_ptr
errstat <- peekPDUErrstat pdu_resp
when (errstat /= snmp_err_noerror) (throwT ("response PDU error: "++show errstat))
rawvars <- peekPDUVariables pdu_resp
vars <- extractVars rawvars
unless (pdu_resp == nullPtr) $ t_snmp_free_pdu pdu_resp
return vars)
vlist2oid :: Ptr CVarList -> Trouble RawOID
vlist2oid rv = do
oidptr <- peekVariableName rv
len <- peekVariableLen rv
peekArrayT (fromIntegral len) oidptr
extractVars :: Ptr CVarList -> Trouble [SnmpResult]
extractVars rv
| rv == nullPtr = return []
| otherwise = do
v <- extractVar rv
nextrv <- peekVariableNext rv
vs <- extractVars nextrv
return (v : vs)
extractVar :: Ptr CVarList -> Trouble SnmpResult
extractVar rv = do
oid' <- vlist2oid rv
t <- peekVariableType rv
v <- case () of
_ | t == asn_octet_str -> extractOctetStr rv
_ | t == asn_ipaddress -> extractIpAddress rv
_ | t == asn_counter -> extractIntegralType rv Counter32
_ | t == asn_gauge -> extractIntegralType rv Gauge32
_ | t == asn_timeticks -> extractTimeTicks rv
_ | t == asn_opaque -> extractOpaque rv
_ | t == asn_integer -> extractIntegralType rv Integer32
_ | t == asn_unsigned -> extractIntegralType rv Unsigned32
_ | t == asn_counter64 -> extractIntegral64Type rv Counter64
_ | t == asn_integer64 -> extractIntegral64Type rv Integer64
_ | t == asn_unsigned64 -> extractIntegral64Type rv Unsigned64
_ | t == asn_object_id -> extractOID rv
_ | t == asn_null -> return Null
_ -> do
descr <- rawvar2cstring rv
return $ Unsupported (fromIntegral t) descr
return (SnmpResult oid' v)
extractOctetStr rv = do
ptr <- peekVariableValString rv
len <- peekVariableValLen rv
s <- peekCStringLenT (ptr , fromIntegral len)
octets <- peekArrayT (fromIntegral len) (castPtr ptr)
return (OctetString s octets)
extractOpaque rv = do
ptr <- peekVariableValBits rv
len <- peekVariableValLen rv
arr <- peekArrayT (fromIntegral len) ptr
return (Opaque (map fromIntegral arr))
extractIntegralType rv constructor = do
intptr <- peekVariableValInt rv
n <- fromIntegral <$> peekT intptr
return (constructor n)
extractIntegral64Type rv constructor = do
ptr <- peekVariableValInt rv
(high:low:[]) <- peekArrayT 2 (castPtr ptr) :: Trouble [Word64]
return (constructor (fromIntegral ((high * (2 ^ (32 :: Word64)) + low) :: Word64)))
extractIpAddress rv = do
ptr <- peekVariableValInt rv
octets <- peekArrayT 4 (castPtr ptr) :: Trouble [Word8]
let str = B.intercalate "." (map (B.pack . (:[])) octets)
return (IpAddress str octets)
extractOID :: Ptr CVarList -> Trouble ASNValue
extractOID rv = do
oidptr <- peekVariableValObjid rv :: Trouble (Ptr OIDpart)
len <- peekVariableValLen rv
let oidlen = (fromIntegral len) `div` (8)
oid' <- peekArrayT oidlen oidptr :: Trouble RawOID
let str = oidToByteString oid'
descr <- rawvar2cstring rv
return (OID descr str (map fromIntegral oid'))
extractTimeTicks rv = do
intptr <- peekVariableValInt rv
ticks <- fromIntegral <$> peekT intptr
descr <- rawvar2cstring rv
return (TimeTicks descr ticks)
showASNValue :: ASNValue -> String
showASNValue v = case v of
OctetString s _ -> show s
IpAddress s _ -> show s
Counter32 c -> show c
Gauge32 c -> show c
OID _ os _ -> show os
Opaque cs -> show cs
Integer32 c -> show c
Unsigned32 c -> show c
Counter64 c -> show c
Integer64 c -> show c
Unsigned64 c -> show c
TimeTicks s _ -> show s
Boolean c -> show c
IEEEDouble c -> show c
IEEEFloat c -> show c
Null -> "ASN_NULL"
Unsupported t s -> "Unknown type " ++ show t ++ ": " ++ show s
buildPDU
:: SnmpPDUType
-> RawOID
-> Ptr OIDpart
-> SnmpVersion
-> Trouble (Ptr SnmpPDU)
buildPDU pdutype oid' oids version =
withCStringT (showOid oid') $ \oid_cstr ->
allocaT $ \oidlen_ptr -> do
pdu_req <- t_snmp_pdu_create pdutype
pokePDUVersion pdu_req (unSnmpVersion version)
pokePDUCommand pdu_req (unSnmpPDUType pdutype)
pokeT oidlen_ptr (fromIntegral max_oid_len)
_ <- t_read_objid oid_cstr oids oidlen_ptr
oidlen <- peekT oidlen_ptr
t_snmp_add_null_var pdu_req oids oidlen
return pdu_req
rawvar2cstring :: Ptr CVarList -> Trouble ByteString
rawvar2cstring rv =
allocaArray0T max_string_len $ \buf -> do
_ <- t_snprint_by_type buf (fromIntegral max_string_len) rv
nullPtr nullPtr nullPtr
peekCStringT buf
allocaT :: (Storable a) => (Ptr a -> Trouble b) -> Trouble b
allocaT f = Trouble $ alloca $ \p -> runTrouble (f p)
allocaArrayT :: (Storable a) => Int -> (Ptr a -> Trouble b) -> Trouble b
allocaArrayT n f = Trouble $ allocaArray n $ \p -> runTrouble (f p)
allocaArray0T :: (Storable a) => Int -> (Ptr a -> Trouble b) -> Trouble b
allocaArray0T n f = Trouble $ allocaArray0 n $ \p -> runTrouble (f p)
withCStringT :: String -> (CString -> Trouble b) -> Trouble b
withCStringT s f = Trouble $ withCString s $ \p -> runTrouble (f p)
peekCStringT = hoistT1 B.packCString
peekCStringLenT = hoistT1 B.packCStringLen
peekT :: (Storable a) => Ptr a -> Trouble a
peekT = hoistT1 peek
pokeT :: (Storable a) => Ptr a -> a -> Trouble ()
pokeT = hoistT2 poke
peekArrayT :: (Storable a) => Int -> Ptr a -> Trouble [a]
peekArrayT = hoistT2 peekArray
peekPDUErrstat :: Ptr SnmpPDU -> Trouble CInt
peekPDUErrstat p = hoistT $ (\hsc_ptr -> peekByteOff hsc_ptr 48) p
peekPDUVariables :: Ptr SnmpPDU -> Trouble (Ptr CVarList)
peekPDUVariables p = hoistT $ (\hsc_ptr -> peekByteOff hsc_ptr 128) p
peekVariableName :: Ptr CVarList -> Trouble (Ptr OIDpart)
peekVariableName rv = hoistT $ (\hsc_ptr -> peekByteOff hsc_ptr 8) rv
peekVariableLen :: Ptr CVarList -> Trouble CSize
peekVariableLen rv = hoistT $ (\hsc_ptr -> peekByteOff hsc_ptr 16) rv
peekVariableNext :: Ptr CVarList -> Trouble (Ptr CVarList)
peekVariableNext rv = hoistT $ (\hsc_ptr -> peekByteOff hsc_ptr 0) rv
peekVariableType :: Ptr CVarList -> Trouble CUChar
peekVariableType rv = hoistT $ (\hsc_ptr -> peekByteOff hsc_ptr 24) rv
peekVariableValBits :: Ptr CVarList -> Trouble (Ptr CUChar)
peekVariableValBits rv = hoistT $ (\hsc_ptr -> peekByteOff hsc_ptr 32) rv
peekVariableValInt :: Ptr CVarList -> Trouble (Ptr CLong)
peekVariableValInt rv = hoistT $ (\hsc_ptr -> peekByteOff hsc_ptr 32) rv
peekVariableValString :: Ptr CVarList -> Trouble CString
peekVariableValString rv = hoistT $ (\hsc_ptr -> peekByteOff hsc_ptr 32) rv
peekVariableValObjid :: Ptr CVarList -> Trouble (Ptr OIDpart)
peekVariableValObjid rv = hoistT $ (\hsc_ptr -> peekByteOff hsc_ptr 32) rv
peekVariableValLen :: Ptr CVarList -> Trouble CSize
peekVariableValLen rv = hoistT $ (\hsc_ptr -> peekByteOff hsc_ptr 40) rv
pokeSessRetries :: Ptr SnmpSession -> CInt -> Trouble ()
pokeSessRetries s h = hoistT $ (\hsc_ptr -> pokeByteOff hsc_ptr 8) s h
pokeSessTimeout :: Ptr SnmpSession -> CLong -> Trouble ()
pokeSessTimeout s h = hoistT $ (\hsc_ptr -> pokeByteOff hsc_ptr 16) s h
pokeSessPeername :: Ptr SnmpSession -> CString -> Trouble ()
pokeSessPeername s h = hoistT $ (\hsc_ptr -> pokeByteOff hsc_ptr 48) s h
pokeSessVersion :: Ptr SnmpSession -> CLong -> Trouble ()
pokeSessVersion s v = hoistT $ (\hsc_ptr -> pokeByteOff hsc_ptr 0) s v
pokeSessCommunity :: Ptr SnmpSession -> CString -> Trouble ()
pokeSessCommunity s c = hoistT $ (\hsc_ptr -> pokeByteOff hsc_ptr 120) s c
pokeSessCommLen :: Ptr SnmpSession -> CSize -> Trouble ()
pokeSessCommLen s l = hoistT $ (\hsc_ptr -> pokeByteOff hsc_ptr 128) s l
pokePDUVersion :: Ptr SnmpPDU -> CLong -> Trouble ()
pokePDUVersion p v = hoistT $ (\hsc_ptr -> pokeByteOff hsc_ptr 0) p v
pokePDUCommand :: Ptr SnmpPDU -> CInt -> Trouble ()
pokePDUCommand p t = hoistT $ (\hsc_ptr -> pokeByteOff hsc_ptr 8) p t
pokePDUNonRepeaters :: Ptr SnmpPDU -> CLong -> Trouble ()
pokePDUNonRepeaters p n = hoistT $ (\hsc_ptr -> pokeByteOff hsc_ptr 48) p n
pokePDUMaxRepetitions :: Ptr SnmpPDU -> CLong -> Trouble ()
pokePDUMaxRepetitions p r = hoistT $ (\hsc_ptr -> pokeByteOff hsc_ptr 56) p r
foreign import ccall unsafe "net-snmp/net-snmp-includes.h init_snmp"
c_init_snmp :: CString -> IO ()
foreign import ccall unsafe "net-snmp/net-snmp-includes.h snmp_sess_init"
c_snmp_sess_init :: Ptr SnmpSession -> IO ()
t_snmp_sess_init = hoistT1 c_snmp_sess_init
foreign import ccall unsafe "net-snmp/net-snmp-includes.h snmp_sess_open"
c_snmp_sess_open :: Ptr SnmpSession -> IO (Ptr SnmpSession)
t_snmp_sess_open = hoistTE1
(predToMaybe (== nullPtr) "snmp_sess_open failed") c_snmp_sess_open
foreign import ccall unsafe "net-snmp/net-snmp-includes.h snmp_sess_session"
c_snmp_sess_session :: Ptr SnmpSession -> IO (Ptr SnmpSession)
t_snmp_sess_session = hoistTE1
(predToMaybe (== nullPtr) "snmp_sess_session failed") c_snmp_sess_session
foreign import ccall unsafe "net-snmp/net-snmp-includes.h snmp_pdu_create"
c_snmp_pdu_create :: SnmpPDUType -> IO (Ptr SnmpPDU)
t_snmp_pdu_create = hoistTE1
(predToMaybe (== nullPtr) "snmp_pdu_create failed")
c_snmp_pdu_create
foreign import ccall unsafe "net-snmp/net-snmp-includes.h read_objid"
c_read_objid :: CString -> Ptr OIDpart -> Ptr CSize -> IO CInt
t_read_objid = hoistTE3
(predToMaybe (not . (>0)) "read_objid failed")
c_read_objid
foreign import ccall unsafe "net-snmp/net-snmp-includes.h snmp_add_null_var"
c_snmp_add_null_var :: Ptr SnmpPDU -> Ptr OIDpart -> CSize -> IO ()
t_snmp_add_null_var = hoistT3 c_snmp_add_null_var
foreign import ccall safe
"net-snmp/net-snmp-includes.h snmp_sess_synch_response"
c_snmp_sess_synch_response :: Ptr SnmpSession -> Ptr SnmpPDU
-> Ptr (Ptr SnmpPDU) -> IO CInt
t_snmp_sess_synch_response :: Ptr SnmpSession -> Ptr SnmpSession -> Ptr SnmpPDU -> Ptr (Ptr SnmpPDU) -> Trouble ()
t_snmp_sess_synch_response sessp sptr pdu_req response_ptr = Trouble $ do
success <- c_snmp_sess_synch_response sessp pdu_req response_ptr
if success == snmp_stat_success
then return (Right ())
else Left <$> snmpError sptr
foreign import ccall unsafe "net-snmp/net-snmp-includes.h snmp_free_pdu"
c_snmp_free_pdu :: Ptr SnmpPDU -> IO ()
t_snmp_free_pdu = hoistT1 c_snmp_free_pdu
foreign import ccall unsafe "net-snmp/net-snmp-includes.h snmp_sess_close"
c_snmp_sess_close :: Ptr SnmpSession -> IO ()
foreign import ccall unsafe "net-snmp/net-snmp-includes.h snmp_error"
c_snmp_error :: Ptr SnmpSession -> Ptr CInt -> Ptr CInt
-> Ptr CString -> IO ()
snmpError :: Ptr SnmpSession -> IO String
snmpError p =
alloca $ \libp ->
alloca $ \sysp ->
alloca $ \errp -> do
c_snmp_error p libp sysp errp
liberr <- peek libp
syserr <- peek sysp
cserr <- peek errp
err <- peekCString cserr
free cserr
return $ "snmpError: lib:" ++ show liberr ++ " ; sys:" ++ show syserr
++ " ; " ++ err
foreign import ccall unsafe "net-snmp/net-snmp-includes.h snprint_by_type"
c_snprint_by_type :: CString -> CSize -> Ptr CVarList ->
Ptr () -> Ptr () -> Ptr () -> IO CInt
t_snprint_by_type = hoistT6 c_snprint_by_type
foreign import ccall unsafe "string.h strlen"
c_strlen :: Ptr CChar -> IO CSize
t_strlen = hoistT1 c_strlen
newtype Trouble a = Trouble { runTrouble :: IO (Either String a) }
instance Applicative Trouble where
pure a = Trouble $ pure (Right a)
f <*> v = Trouble $ do
r <- runTrouble f
v' <- runTrouble v
case r of (Left s) -> return (Left s)
(Right f') -> return (fmap f' v')
instance Functor Trouble where
fmap f m = Trouble $ do
r <- runTrouble m
case r of (Left s) -> return (Left s)
(Right v) -> return (Right (f v))
instance Monad Trouble where
return a = Trouble $ return (Right a)
m >>= f = Trouble $ do
r <- runTrouble m
case r of (Left s) -> return (Left s)
(Right v) -> runTrouble (f v)
throwT :: String -> Trouble a
throwT s = Trouble $ return (Left s)
catchT :: Trouble a -> (String -> Trouble a) -> Trouble a
catchT m h = Trouble $ do
r <- runTrouble m
case r of (Left s) -> runTrouble (h s)
(Right _) -> return r
handleT :: (String -> Trouble a) -> Trouble a -> Trouble a
handleT = flip catchT
bracketT :: Trouble a -> (a -> Trouble b) -> (a -> Trouble c) -> Trouble c
bracketT before after thing = do
a <- before
result <- handleT (\s -> after a >> throwT s) (thing a)
_ <- after a
return result
hoistT :: IO t -> Trouble t
hoistT f = Trouble $ Right <$> f
hoistT1 :: (a -> IO t) -> a -> Trouble t
hoistT1 f a = hoistT (f a)
hoistT2 f a = hoistT1 (f a)
hoistT3 f a = hoistT2 (f a)
hoistT4 f a = hoistT3 (f a)
hoistT5 f a = hoistT4 (f a)
hoistT6 f a = hoistT5 (f a)
hoistTE0 :: (t -> Maybe String) -> IO t -> Trouble t
hoistTE0 e f = Trouble $ do
t <- f
return $ maybe (Right t) Left (e t)
hoistTE1 e f a = hoistTE0 e (f a)
hoistTE2 e f a = hoistTE1 e (f a)
hoistTE3 e f a = hoistTE2 e (f a)
predToMaybe :: (a -> Bool) -> b -> a -> Maybe b
predToMaybe p b a = if p a then Just b else Nothing