packages feed

erebos 0.1.8.1 → 0.1.9

raw patch · 17 files changed

+429/−224 lines, 17 filesdep +cryptonitePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: cryptonite

API changes (from Hackage documentation)

+ Erebos.Discovery: discoverySearch :: (MonadIO m, MonadError String m) => Server -> Ref -> m ()
+ Erebos.Message: [msgReceived] :: DirectMessageThread -> [Stored DirectMessage]
+ Erebos.Network: ($dmdefaultServiceAttributes) :: (Service s, ServiceAttributes s ~ Proxy s) => proxy s -> ServiceAttributes s
+ Erebos.Network: ($dmemptyServiceGlobalState) :: (Service s, ServiceGlobalState s ~ ()) => proxy s -> ServiceGlobalState s
+ Erebos.Network: ($dmemptyServiceState) :: (Service s, ServiceState s ~ ()) => proxy s -> ServiceState s
+ Erebos.Network: modifyServiceGlobalState :: forall s a m proxy. (Service s, MonadIO m, MonadError String m) => Server -> proxy s -> (ServiceGlobalState s -> (ServiceGlobalState s, a)) -> m a
+ Erebos.Network: serviceStopServer :: Service s => proxy s -> Server -> ServiceGlobalState s -> [(Peer, ServiceState s)] -> IO ()
+ Erebos.Service: ($dmdefaultServiceAttributes) :: (Service s, ServiceAttributes s ~ Proxy s) => proxy s -> ServiceAttributes s
+ Erebos.Service: ($dmemptyServiceGlobalState) :: (Service s, ServiceGlobalState s ~ ()) => proxy s -> ServiceGlobalState s
+ Erebos.Service: ($dmemptyServiceState) :: (Service s, ServiceState s ~ ()) => proxy s -> ServiceState s
+ Erebos.Service: GlobalStorageWatcher :: (Stored LocalState -> a) -> (Server -> a -> ExceptT String IO ()) -> SomeStorageWatcher s
+ Erebos.Service: serviceStopServer :: Service s => proxy s -> Server -> ServiceGlobalState s -> [(Peer, ServiceState s)] -> IO ()
+ Erebos.Storage: ($dmmstore) :: (MonadStorage m, MonadIO m, Storable a) => a -> m (Stored a)
+ Erebos.Storage: refFromDigest :: forall (c :: Type -> Type). Storage' c -> RefDigest -> IO (Maybe (Ref' c))
- Erebos.Discovery: DiscoveryConnection :: Ref -> Ref -> Maybe Text -> Maybe (Stored Object) -> DiscoveryConnection
+ Erebos.Discovery: DiscoveryConnection :: Ref -> Ref -> Maybe Text -> Maybe IceRemoteInfo -> DiscoveryConnection
- Erebos.Discovery: [dconnIceInfo] :: DiscoveryConnection -> Maybe (Stored Object)
+ Erebos.Discovery: [dconnIceInfo] :: DiscoveryConnection -> Maybe IceRemoteInfo
- Erebos.Identity: data Identity m
+ Erebos.Identity: data Identity (m :: Type -> Type)
- Erebos.Identity: displayIdentity :: (Foldable m, Applicative m) => Identity m -> Text
+ Erebos.Identity: displayIdentity :: forall (m :: Type -> Type). (Foldable m, Applicative m) => Identity m -> Text
- Erebos.Identity: finalOwner :: (Foldable m, Applicative m) => Identity m -> ComposedIdentity
+ Erebos.Identity: finalOwner :: forall (m :: Type -> Type). (Foldable m, Applicative m) => Identity m -> ComposedIdentity
- Erebos.Identity: idKeyIdentity :: Identity m -> Stored PublicKey
+ Erebos.Identity: idKeyIdentity :: forall (m :: Type -> Type). Identity m -> Stored PublicKey
- Erebos.Identity: idKeyMessage :: Identity m -> Stored PublicKey
+ Erebos.Identity: idKeyMessage :: forall (m :: Type -> Type). Identity m -> Stored PublicKey
- Erebos.Identity: idName :: Identity m -> Maybe Text
+ Erebos.Identity: idName :: forall (m :: Type -> Type). Identity m -> Maybe Text
- Erebos.Identity: idOwner :: Identity m -> Maybe ComposedIdentity
+ Erebos.Identity: idOwner :: forall (m :: Type -> Type). Identity m -> Maybe ComposedIdentity
- Erebos.Identity: idUpdates :: Identity m -> [Stored (Signed ExtendedIdentityData)]
+ Erebos.Identity: idUpdates :: forall (m :: Type -> Type). Identity m -> [Stored (Signed ExtendedIdentityData)]
- Erebos.Identity: mergeIdentity :: (MonadStorage m, MonadError String m, MonadIO m) => Identity f -> m UnifiedIdentity
+ Erebos.Identity: mergeIdentity :: forall m (f :: Type -> Type). (MonadStorage m, MonadError String m, MonadIO m) => Identity f -> m UnifiedIdentity
- Erebos.Identity: sameIdentity :: (Foldable m, Foldable m') => Identity m -> Identity m' -> Bool
+ Erebos.Identity: sameIdentity :: forall (m :: Type -> Type) (m' :: Type -> Type). (Foldable m, Foldable m') => Identity m -> Identity m' -> Bool
- Erebos.Identity: toComposedIdentity :: Identity m -> ComposedIdentity
+ Erebos.Identity: toComposedIdentity :: forall (m :: Type -> Type). Identity m -> ComposedIdentity
- Erebos.Identity: toUnifiedIdentity :: Identity m -> Maybe UnifiedIdentity
+ Erebos.Identity: toUnifiedIdentity :: forall (m :: Type -> Type). Identity m -> Maybe UnifiedIdentity
- Erebos.Identity: unfoldOwners :: Foldable m => Identity m -> [ComposedIdentity]
+ Erebos.Identity: unfoldOwners :: forall (m :: Type -> Type). Foldable m => Identity m -> [ComposedIdentity]
- Erebos.Identity: updateIdentity :: [Stored (Signed ExtendedIdentityData)] -> Identity m -> ComposedIdentity
+ Erebos.Identity: updateIdentity :: forall (m :: Type -> Type). [Stored (Signed ExtendedIdentityData)] -> Identity m -> ComposedIdentity
- Erebos.Identity: updateOwners :: [Stored (Signed ExtendedIdentityData)] -> Identity m -> Identity m
+ Erebos.Identity: updateOwners :: forall (m :: Type -> Type). [Stored (Signed ExtendedIdentityData)] -> Identity m -> Identity m
- Erebos.Message: DirectMessageThread :: ComposedIdentity -> [Stored DirectMessage] -> [Stored DirectMessage] -> [Stored DirectMessage] -> DirectMessageThread
+ Erebos.Message: DirectMessageThread :: ComposedIdentity -> [Stored DirectMessage] -> [Stored DirectMessage] -> [Stored DirectMessage] -> [Stored DirectMessage] -> DirectMessageThread
- Erebos.Message: sendDirectMessage :: (Foldable f, Applicative f, MonadHead LocalState m, MonadError String m) => Identity f -> Text -> m (Stored DirectMessage)
+ Erebos.Message: sendDirectMessage :: forall (f :: Type -> Type) m. (Foldable f, Applicative f, MonadHead LocalState m, MonadError String m) => Identity f -> Text -> m (Stored DirectMessage)
- Erebos.Network: class (Typeable s, Storable s, Typeable (ServiceAttributes s), Typeable (ServiceState s), Typeable (ServiceGlobalState s)) => Service s where {
+ Erebos.Network: class (Typeable s, Storable s, Typeable ServiceAttributes s, Typeable ServiceState s, Typeable ServiceGlobalState s) => Service s where {
- Erebos.Network: defaultServiceAttributes :: (Service s, ServiceAttributes s ~ Proxy s) => proxy s -> ServiceAttributes s
+ Erebos.Network: defaultServiceAttributes :: Service s => proxy s -> ServiceAttributes s
- Erebos.Network: emptyServiceGlobalState :: (Service s, ServiceGlobalState s ~ ()) => proxy s -> ServiceGlobalState s
+ Erebos.Network: emptyServiceGlobalState :: Service s => proxy s -> ServiceGlobalState s
- Erebos.Network: emptyServiceState :: (Service s, ServiceState s ~ ()) => proxy s -> ServiceState s
+ Erebos.Network: emptyServiceState :: Service s => proxy s -> ServiceState s
- Erebos.Network: runPeerService :: forall s m. (Service s, MonadIO m) => Peer -> ServiceHandler s () -> m ()
+ Erebos.Network: runPeerService :: (Service s, MonadIO m) => Peer -> ServiceHandler s () -> m ()
- Erebos.Network: sendToPeerWith :: forall s m. (Service s, MonadIO m, MonadError String m) => Peer -> (ServiceState s -> ExceptT String IO (Maybe s, ServiceState s)) -> m ()
+ Erebos.Network: sendToPeerWith :: (Service s, MonadIO m, MonadError String m) => Peer -> (ServiceState s -> ExceptT String IO (Maybe s, ServiceState s)) -> m ()
- Erebos.Pairing: pairingAccept :: forall a m proxy. (PairingResult a, MonadIO m, MonadError String m) => proxy a -> Peer -> m ()
+ Erebos.Pairing: pairingAccept :: (PairingResult a, MonadIO m, MonadError String m) => proxy a -> Peer -> m ()
- Erebos.Pairing: pairingReject :: forall a m proxy. (PairingResult a, MonadIO m, MonadError String m) => proxy a -> Peer -> m ()
+ Erebos.Pairing: pairingReject :: (PairingResult a, MonadIO m, MonadError String m) => proxy a -> Peer -> m ()
- Erebos.Pairing: pairingRequest :: forall a m proxy. (PairingResult a, MonadIO m, MonadError String m) => proxy a -> Peer -> m ()
+ Erebos.Pairing: pairingRequest :: (PairingResult a, MonadIO m, MonadError String m) => proxy a -> Peer -> m ()
- Erebos.Service: class (Typeable s, Storable s, Typeable (ServiceAttributes s), Typeable (ServiceState s), Typeable (ServiceGlobalState s)) => Service s where {
+ Erebos.Service: class (Typeable s, Storable s, Typeable ServiceAttributes s, Typeable ServiceState s, Typeable ServiceGlobalState s) => Service s where {
- Erebos.Service: defaultServiceAttributes :: (Service s, ServiceAttributes s ~ Proxy s) => proxy s -> ServiceAttributes s
+ Erebos.Service: defaultServiceAttributes :: Service s => proxy s -> ServiceAttributes s
- Erebos.Service: emptyServiceGlobalState :: (Service s, ServiceGlobalState s ~ ()) => proxy s -> ServiceGlobalState s
+ Erebos.Service: emptyServiceGlobalState :: Service s => proxy s -> ServiceGlobalState s
- Erebos.Service: emptyServiceState :: (Service s, ServiceState s ~ ()) => proxy s -> ServiceState s
+ Erebos.Service: emptyServiceState :: Service s => proxy s -> ServiceState s
- Erebos.Service: someService :: forall s proxy. Service s => proxy s -> SomeService
+ Erebos.Service: someService :: Service s => proxy s -> SomeService
- Erebos.Service: someServiceAttr :: forall s. Service s => ServiceAttributes s -> SomeService
+ Erebos.Service: someServiceAttr :: Service s => ServiceAttributes s -> SomeService
- Erebos.Set: fromSetBy :: forall a. Mergeable a => (a -> a -> Ordering) -> Set a -> [a]
+ Erebos.Set: fromSetBy :: Mergeable a => (a -> a -> Ordering) -> Set a -> [a]
- Erebos.State: class (MonadIO m, MonadStorage m) => MonadHead a m
+ Erebos.State: class (MonadIO m, MonadStorage m) => MonadHead a (m :: Type -> Type)
- Erebos.State: interactiveIdentityUpdate :: (Foldable f, MonadStorage m, MonadIO m, MonadError String m) => Identity f -> m UnifiedIdentity
+ Erebos.State: interactiveIdentityUpdate :: forall (f :: Type -> Type) m. (Foldable f, MonadStorage m, MonadIO m, MonadError String m) => Identity f -> m UnifiedIdentity
- Erebos.State: lookupSharedValue :: forall a. SharedType a => [Stored SharedState] -> a
+ Erebos.State: lookupSharedValue :: SharedType a => [Stored SharedState] -> a
- Erebos.State: makeSharedStateUpdate :: forall a m. MonadIO m => SharedType a => Storage -> a -> [Stored SharedState] -> m (Stored SharedState)
+ Erebos.State: makeSharedStateUpdate :: forall a m. (MonadIO m, SharedType a) => Storage -> a -> [Stored SharedState] -> m (Stored SharedState)
- Erebos.State: updateSharedState_ :: forall a m. (SharedType a, MonadHead LocalState m) => (a -> m a) -> Stored LocalState -> m (Stored LocalState)
+ Erebos.State: updateSharedState_ :: (SharedType a, MonadHead LocalState m) => (a -> m a) -> Stored LocalState -> m (Stored LocalState)
- Erebos.Storage: Blob :: ByteString -> Object' c
+ Erebos.Storage: Blob :: ByteString -> Object' (c :: Type -> Type)
- Erebos.Storage: Rec :: [(ByteString, RecItem' c)] -> Object' c
+ Erebos.Storage: Rec :: [(ByteString, RecItem' c)] -> Object' (c :: Type -> Type)
- Erebos.Storage: RecBinary :: ByteString -> RecItem' c
+ Erebos.Storage: RecBinary :: ByteString -> RecItem' (c :: Type -> Type)
- Erebos.Storage: RecDate :: ZonedTime -> RecItem' c
+ Erebos.Storage: RecDate :: ZonedTime -> RecItem' (c :: Type -> Type)
- Erebos.Storage: RecEmpty :: RecItem' c
+ Erebos.Storage: RecEmpty :: RecItem' (c :: Type -> Type)
- Erebos.Storage: RecInt :: Integer -> RecItem' c
+ Erebos.Storage: RecInt :: Integer -> RecItem' (c :: Type -> Type)
- Erebos.Storage: RecNum :: Rational -> RecItem' c
+ Erebos.Storage: RecNum :: Rational -> RecItem' (c :: Type -> Type)
- Erebos.Storage: RecRef :: Ref' c -> RecItem' c
+ Erebos.Storage: RecRef :: Ref' c -> RecItem' (c :: Type -> Type)
- Erebos.Storage: RecText :: Text -> RecItem' c
+ Erebos.Storage: RecText :: Text -> RecItem' (c :: Type -> Type)
- Erebos.Storage: RecUUID :: UUID -> RecItem' c
+ Erebos.Storage: RecUUID :: UUID -> RecItem' (c :: Type -> Type)
- Erebos.Storage: RecUnknown :: ByteString -> ByteString -> RecItem' c
+ Erebos.Storage: RecUnknown :: ByteString -> ByteString -> RecItem' (c :: Type -> Type)
- Erebos.Storage: UnknownObject :: ByteString -> ByteString -> Object' c
+ Erebos.Storage: UnknownObject :: ByteString -> ByteString -> Object' (c :: Type -> Type)
- Erebos.Storage: ZeroObject :: Object' c
+ Erebos.Storage: ZeroObject :: Object' (c :: Type -> Type)
- Erebos.Storage: class Monad m => MonadStorage m
+ Erebos.Storage: class Monad m => MonadStorage (m :: Type -> Type)
- Erebos.Storage: class (Traversable compl, Monad compl) => StorageCompleteness compl
+ Erebos.Storage: class (Traversable compl, Monad compl) => StorageCompleteness (compl :: Type -> Type)
- Erebos.Storage: copyRef :: forall c c' m. (StorageCompleteness c, StorageCompleteness c', MonadIO m) => Storage' c' -> Ref' c -> m (LoadResult c (Ref' c'))
+ Erebos.Storage: copyRef :: forall (c :: Type -> Type) (c' :: Type -> Type) m. (StorageCompleteness c, StorageCompleteness c', MonadIO m) => Storage' c' -> Ref' c -> m (LoadResult c (Ref' c'))
- Erebos.Storage: copyStored :: forall c c' m a. (StorageCompleteness c, StorageCompleteness c', MonadIO m) => Storage' c' -> Stored' c a -> m (LoadResult c (Stored' c' a))
+ Erebos.Storage: copyStored :: forall (c :: Type -> Type) (c' :: Type -> Type) m a. (StorageCompleteness c, StorageCompleteness c', MonadIO m) => Storage' c' -> Stored' c a -> m (LoadResult c (Stored' c' a))
- Erebos.Storage: data Object' c
+ Erebos.Storage: data Object' (c :: Type -> Type)
- Erebos.Storage: data RecItem' c
+ Erebos.Storage: data RecItem' (c :: Type -> Type)
- Erebos.Storage: evalStore :: StorageCompleteness c => Storage' c -> Store -> IO (Ref' c)
+ Erebos.Storage: evalStore :: forall (c :: Type -> Type). StorageCompleteness c => Storage' c -> Store -> IO (Ref' c)
- Erebos.Storage: evalStoreObject :: StorageCompleteness c => Storage' c -> Store -> IO (Object' c)
+ Erebos.Storage: evalStoreObject :: forall (c :: Type -> Type). StorageCompleteness c => Storage' c -> Store -> IO (Object' c)
- Erebos.Storage: ioLoadObject :: forall c. StorageCompleteness c => Ref' c -> IO (c (Object' c))
+ Erebos.Storage: ioLoadObject :: StorageCompleteness c => Ref' c -> IO (c (Object' c))
- Erebos.Storage: lazyLoadBytes :: forall c. StorageCompleteness c => Ref' c -> LoadResult c ByteString
+ Erebos.Storage: lazyLoadBytes :: forall (c :: Type -> Type). StorageCompleteness c => Ref' c -> LoadResult c ByteString
- Erebos.Storage: loadHead :: forall a m. (HeadType a, MonadIO m) => Storage -> HeadID -> m (Maybe (Head a))
+ Erebos.Storage: loadHead :: (HeadType a, MonadIO m) => Storage -> HeadID -> m (Maybe (Head a))
- Erebos.Storage: loadHeadRaw :: forall m. MonadIO m => Storage -> HeadTypeID -> HeadID -> m (Maybe Ref)
+ Erebos.Storage: loadHeadRaw :: MonadIO m => Storage -> HeadTypeID -> HeadID -> m (Maybe Ref)
- Erebos.Storage: loadHeads :: forall a m. MonadIO m => HeadType a => Storage -> m [Head a]
+ Erebos.Storage: loadHeads :: forall a m. (MonadIO m, HeadType a) => Storage -> m [Head a]
- Erebos.Storage: mstore :: (MonadStorage m, MonadIO m) => Storable a => a -> m (Stored a)
+ Erebos.Storage: mstore :: (MonadStorage m, Storable a) => a -> m (Stored a)
- Erebos.Storage: refDigest :: Ref' c -> RefDigest
+ Erebos.Storage: refDigest :: forall (c :: Type -> Type). Ref' c -> RefDigest
- Erebos.Storage: replaceHead :: forall a m. (HeadType a, MonadIO m) => Head a -> Stored a -> m (Either (Maybe (Head a)) (Head a))
+ Erebos.Storage: replaceHead :: (HeadType a, MonadIO m) => Head a -> Stored a -> m (Either (Maybe (Head a)) (Head a))
- Erebos.Storage: replaceHeadRaw :: forall m. MonadIO m => Storage -> HeadTypeID -> HeadID -> Ref -> Ref -> m (Either (Maybe Ref) Ref)
+ Erebos.Storage: replaceHeadRaw :: MonadIO m => Storage -> HeadTypeID -> HeadID -> Ref -> Ref -> m (Either (Maybe Ref) Ref)
- Erebos.Storage: serializeObject :: Object' c -> ByteString
+ Erebos.Storage: serializeObject :: forall (c :: Type -> Type). Object' c -> ByteString
- Erebos.Storage: showRef :: Ref' c -> ByteString
+ Erebos.Storage: showRef :: forall (c :: Type -> Type). Ref' c -> ByteString
- Erebos.Storage: store :: (Storable a, StorageCompleteness c) => Storage' c -> a -> IO (Ref' c)
+ Erebos.Storage: store :: forall (c :: Type -> Type). (Storable a, StorageCompleteness c) => Storage' c -> a -> IO (Ref' c)
- Erebos.Storage: storeBinary :: ByteArrayAccess a => String -> a -> StoreRec c
+ Erebos.Storage: storeBinary :: forall a (c :: Type -> Type). ByteArrayAccess a => String -> a -> StoreRec c
- Erebos.Storage: storeDate :: StorableDate a => String -> a -> StoreRec c
+ Erebos.Storage: storeDate :: forall a (c :: Type -> Type). StorableDate a => String -> a -> StoreRec c
- Erebos.Storage: storeEmpty :: String -> StoreRec c
+ Erebos.Storage: storeEmpty :: forall (c :: Type -> Type). String -> StoreRec c
- Erebos.Storage: storeHead :: forall a m. MonadIO m => HeadType a => Storage -> a -> m (Head a)
+ Erebos.Storage: storeHead :: forall a m. (MonadIO m, HeadType a) => Storage -> a -> m (Head a)
- Erebos.Storage: storeHeadRaw :: forall m. MonadIO m => Storage -> HeadTypeID -> Ref -> m HeadID
+ Erebos.Storage: storeHeadRaw :: MonadIO m => Storage -> HeadTypeID -> Ref -> m HeadID
- Erebos.Storage: storeInt :: Integral a => String -> a -> StoreRec c
+ Erebos.Storage: storeInt :: forall a (c :: Type -> Type). Integral a => String -> a -> StoreRec c
- Erebos.Storage: storeMbBinary :: ByteArrayAccess a => String -> Maybe a -> StoreRec c
+ Erebos.Storage: storeMbBinary :: forall a (c :: Type -> Type). ByteArrayAccess a => String -> Maybe a -> StoreRec c
- Erebos.Storage: storeMbDate :: StorableDate a => String -> Maybe a -> StoreRec c
+ Erebos.Storage: storeMbDate :: forall a (c :: Type -> Type). StorableDate a => String -> Maybe a -> StoreRec c
- Erebos.Storage: storeMbEmpty :: String -> Maybe () -> StoreRec c
+ Erebos.Storage: storeMbEmpty :: forall (c :: Type -> Type). String -> Maybe () -> StoreRec c
- Erebos.Storage: storeMbInt :: Integral a => String -> Maybe a -> StoreRec c
+ Erebos.Storage: storeMbInt :: forall a (c :: Type -> Type). Integral a => String -> Maybe a -> StoreRec c
- Erebos.Storage: storeMbNum :: (Real a, Fractional a) => String -> Maybe a -> StoreRec c
+ Erebos.Storage: storeMbNum :: forall a (c :: Type -> Type). (Real a, Fractional a) => String -> Maybe a -> StoreRec c
- Erebos.Storage: storeMbRawRef :: StorageCompleteness c => String -> Maybe Ref -> StoreRec c
+ Erebos.Storage: storeMbRawRef :: forall (c :: Type -> Type). StorageCompleteness c => String -> Maybe Ref -> StoreRec c
- Erebos.Storage: storeMbRef :: Storable a => StorageCompleteness c => String -> Maybe a -> StoreRec c
+ Erebos.Storage: storeMbRef :: forall a (c :: Type -> Type). (Storable a, StorageCompleteness c) => String -> Maybe a -> StoreRec c
- Erebos.Storage: storeMbText :: StorableText a => String -> Maybe a -> StoreRec c
+ Erebos.Storage: storeMbText :: forall a (c :: Type -> Type). StorableText a => String -> Maybe a -> StoreRec c
- Erebos.Storage: storeMbUUID :: StorableUUID a => String -> Maybe a -> StoreRec c
+ Erebos.Storage: storeMbUUID :: forall a (c :: Type -> Type). StorableUUID a => String -> Maybe a -> StoreRec c
- Erebos.Storage: storeNum :: (Real a, Fractional a) => String -> a -> StoreRec c
+ Erebos.Storage: storeNum :: forall a (c :: Type -> Type). (Real a, Fractional a) => String -> a -> StoreRec c
- Erebos.Storage: storeRawRef :: StorageCompleteness c => String -> Ref -> StoreRec c
+ Erebos.Storage: storeRawRef :: forall (c :: Type -> Type). StorageCompleteness c => String -> Ref -> StoreRec c
- Erebos.Storage: storeRec :: (forall c. StorageCompleteness c => StoreRec c) -> Store
+ Erebos.Storage: storeRec :: (forall (c :: Type -> Type). StorageCompleteness c => StoreRec c) -> Store
- Erebos.Storage: storeRecItems :: StorageCompleteness c => [(ByteString, RecItem)] -> StoreRec c
+ Erebos.Storage: storeRecItems :: forall (c :: Type -> Type). StorageCompleteness c => [(ByteString, RecItem)] -> StoreRec c
- Erebos.Storage: storeRef :: Storable a => StorageCompleteness c => String -> a -> StoreRec c
+ Erebos.Storage: storeRef :: forall a (c :: Type -> Type). (Storable a, StorageCompleteness c) => String -> a -> StoreRec c
- Erebos.Storage: storeText :: StorableText a => String -> a -> StoreRec c
+ Erebos.Storage: storeText :: forall a (c :: Type -> Type). StorableText a => String -> a -> StoreRec c
- Erebos.Storage: storeUUID :: StorableUUID a => String -> a -> StoreRec c
+ Erebos.Storage: storeUUID :: forall a (c :: Type -> Type). StorableUUID a => String -> a -> StoreRec c
- Erebos.Storage: storeZRef :: (ZeroStorable a, StorageCompleteness c) => String -> a -> StoreRec c
+ Erebos.Storage: storeZRef :: forall a (c :: Type -> Type). (ZeroStorable a, StorageCompleteness c) => String -> a -> StoreRec c
- Erebos.Storage: type StoreRec c = StoreRecM c ()
+ Erebos.Storage: type StoreRec (c :: Type -> Type) = StoreRecM c ()
- Erebos.Storage: type StoredHistory a = Stored (History a)
+ Erebos.Storage: type StoredHistory a = Stored History a
- Erebos.Storage: watchHead :: forall a. HeadType a => Head a -> (Head a -> IO ()) -> IO WatchedHead
+ Erebos.Storage: watchHead :: HeadType a => Head a -> (Head a -> IO ()) -> IO WatchedHead
- Erebos.Storage: watchHeadRaw :: forall b. Eq b => Storage -> HeadTypeID -> HeadID -> (Ref -> b) -> (b -> IO ()) -> IO WatchedHead
+ Erebos.Storage: watchHeadRaw :: Eq b => Storage -> HeadTypeID -> HeadID -> (Ref -> b) -> (b -> IO ()) -> IO WatchedHead
- Erebos.Storage: watchHeadWith :: forall a b. (HeadType a, Eq b) => Head a -> (Head a -> b) -> (b -> IO ()) -> IO WatchedHead
+ Erebos.Storage: watchHeadWith :: (HeadType a, Eq b) => Head a -> (Head a -> b) -> (b -> IO ()) -> IO WatchedHead
- Erebos.Storage: wrappedStore :: MonadIO m => Storable a => Storage -> a -> m (Stored a)
+ Erebos.Storage: wrappedStore :: (MonadIO m, Storable a) => Storage -> a -> m (Stored a)
- Erebos.Storage.Internal: Head :: HeadID -> Stored' c a -> Head' c a
+ Erebos.Storage.Internal: Head :: HeadID -> Stored' c a -> Head' (c :: Type -> Type) a
- Erebos.Storage.Internal: Ref :: Storage' c -> RefDigest -> Ref' c
+ Erebos.Storage.Internal: Ref :: Storage' c -> RefDigest -> Ref' (c :: Type -> Type)
- Erebos.Storage.Internal: Storage :: StorageBacking c -> Maybe (Storage' Identity) -> MVar (BasicHashTable RefDigest Generation) -> MVar (BasicHashTable RefDigest [RefDigest]) -> Storage' c
+ Erebos.Storage.Internal: Storage :: StorageBacking c -> Maybe (Storage' Identity) -> MVar (BasicHashTable RefDigest Generation) -> MVar (BasicHashTable RefDigest [RefDigest]) -> Storage' (c :: Type -> Type)
- Erebos.Storage.Internal: StorageDir :: FilePath -> MVar (Maybe WatchManager, [HeadTypeID], WatchList c) -> StorageBacking c
+ Erebos.Storage.Internal: StorageDir :: FilePath -> MVar (Maybe WatchManager, [HeadTypeID], WatchList c) -> StorageBacking (c :: Type -> Type)
- Erebos.Storage.Internal: StorageMemory :: MVar [((HeadTypeID, HeadID), Ref' c)] -> MVar (Map RefDigest ByteString) -> MVar (Map RefDigest ScrubbedBytes) -> MVar (WatchList c) -> StorageBacking c
+ Erebos.Storage.Internal: StorageMemory :: MVar [((HeadTypeID, HeadID), Ref' c)] -> MVar (Map RefDigest ByteString) -> MVar (Map RefDigest ScrubbedBytes) -> MVar (WatchList c) -> StorageBacking (c :: Type -> Type)
- Erebos.Storage.Internal: Stored :: Ref' c -> a -> Stored' c a
+ Erebos.Storage.Internal: Stored :: Ref' c -> a -> Stored' (c :: Type -> Type) a
- Erebos.Storage.Internal: WatchList :: WatchID -> [WatchListItem c] -> WatchList c
+ Erebos.Storage.Internal: WatchList :: WatchID -> [WatchListItem c] -> WatchList (c :: Type -> Type)
- Erebos.Storage.Internal: WatchListItem :: WatchID -> (HeadTypeID, HeadID) -> (Ref' c -> IO ()) -> WatchListItem c
+ Erebos.Storage.Internal: WatchListItem :: WatchID -> (HeadTypeID, HeadID) -> (Ref' c -> IO ()) -> WatchListItem (c :: Type -> Type)
- Erebos.Storage.Internal: [dirPath] :: StorageBacking c -> FilePath
+ Erebos.Storage.Internal: [dirPath] :: StorageBacking (c :: Type -> Type) -> FilePath
- Erebos.Storage.Internal: [dirWatchers] :: StorageBacking c -> MVar (Maybe WatchManager, [HeadTypeID], WatchList c)
+ Erebos.Storage.Internal: [dirWatchers] :: StorageBacking (c :: Type -> Type) -> MVar (Maybe WatchManager, [HeadTypeID], WatchList c)
- Erebos.Storage.Internal: [memHeads] :: StorageBacking c -> MVar [((HeadTypeID, HeadID), Ref' c)]
+ Erebos.Storage.Internal: [memHeads] :: StorageBacking (c :: Type -> Type) -> MVar [((HeadTypeID, HeadID), Ref' c)]
- Erebos.Storage.Internal: [memKeys] :: StorageBacking c -> MVar (Map RefDigest ScrubbedBytes)
+ Erebos.Storage.Internal: [memKeys] :: StorageBacking (c :: Type -> Type) -> MVar (Map RefDigest ScrubbedBytes)
- Erebos.Storage.Internal: [memObjs] :: StorageBacking c -> MVar (Map RefDigest ByteString)
+ Erebos.Storage.Internal: [memObjs] :: StorageBacking (c :: Type -> Type) -> MVar (Map RefDigest ByteString)
- Erebos.Storage.Internal: [memWatchers] :: StorageBacking c -> MVar (WatchList c)
+ Erebos.Storage.Internal: [memWatchers] :: StorageBacking (c :: Type -> Type) -> MVar (WatchList c)
- Erebos.Storage.Internal: [stBacking] :: Storage' c -> StorageBacking c
+ Erebos.Storage.Internal: [stBacking] :: Storage' (c :: Type -> Type) -> StorageBacking c
- Erebos.Storage.Internal: [stParent] :: Storage' c -> Maybe (Storage' Identity)
+ Erebos.Storage.Internal: [stParent] :: Storage' (c :: Type -> Type) -> Maybe (Storage' Identity)
- Erebos.Storage.Internal: [stRefGeneration] :: Storage' c -> MVar (BasicHashTable RefDigest Generation)
+ Erebos.Storage.Internal: [stRefGeneration] :: Storage' (c :: Type -> Type) -> MVar (BasicHashTable RefDigest Generation)
- Erebos.Storage.Internal: [stRefRoots] :: Storage' c -> MVar (BasicHashTable RefDigest [RefDigest])
+ Erebos.Storage.Internal: [stRefRoots] :: Storage' (c :: Type -> Type) -> MVar (BasicHashTable RefDigest [RefDigest])
- Erebos.Storage.Internal: [wlFun] :: WatchListItem c -> Ref' c -> IO ()
+ Erebos.Storage.Internal: [wlFun] :: WatchListItem (c :: Type -> Type) -> Ref' c -> IO ()
- Erebos.Storage.Internal: [wlHead] :: WatchListItem c -> (HeadTypeID, HeadID)
+ Erebos.Storage.Internal: [wlHead] :: WatchListItem (c :: Type -> Type) -> (HeadTypeID, HeadID)
- Erebos.Storage.Internal: [wlID] :: WatchListItem c -> WatchID
+ Erebos.Storage.Internal: [wlID] :: WatchListItem (c :: Type -> Type) -> WatchID
- Erebos.Storage.Internal: [wlList] :: WatchList c -> [WatchListItem c]
+ Erebos.Storage.Internal: [wlList] :: WatchList (c :: Type -> Type) -> [WatchListItem c]
- Erebos.Storage.Internal: [wlNext] :: WatchList c -> WatchID
+ Erebos.Storage.Internal: [wlNext] :: WatchList (c :: Type -> Type) -> WatchID
- Erebos.Storage.Internal: class (Traversable compl, Monad compl) => StorageCompleteness compl where {
+ Erebos.Storage.Internal: class (Traversable compl, Monad compl) => StorageCompleteness (compl :: Type -> Type) where {
- Erebos.Storage.Internal: data Head' c a
+ Erebos.Storage.Internal: data Head' (c :: Type -> Type) a
- Erebos.Storage.Internal: data Ref' c
+ Erebos.Storage.Internal: data Ref' (c :: Type -> Type)
- Erebos.Storage.Internal: data Storage' c
+ Erebos.Storage.Internal: data Storage' (c :: Type -> Type)
- Erebos.Storage.Internal: data StorageBacking c
+ Erebos.Storage.Internal: data StorageBacking (c :: Type -> Type)
- Erebos.Storage.Internal: data Stored' c a
+ Erebos.Storage.Internal: data Stored' (c :: Type -> Type) a
- Erebos.Storage.Internal: data WatchList c
+ Erebos.Storage.Internal: data WatchList (c :: Type -> Type)
- Erebos.Storage.Internal: data WatchListItem c
+ Erebos.Storage.Internal: data WatchListItem (c :: Type -> Type)
- Erebos.Storage.Internal: ioLoadBytesFromStorage :: Storage' c -> RefDigest -> IO (Maybe ByteString)
+ Erebos.Storage.Internal: ioLoadBytesFromStorage :: forall (c :: Type -> Type). Storage' c -> RefDigest -> IO (Maybe ByteString)
- Erebos.Storage.Internal: refDigest :: Ref' c -> RefDigest
+ Erebos.Storage.Internal: refDigest :: forall (c :: Type -> Type). Ref' c -> RefDigest
- Erebos.Storage.Internal: refStorage :: Ref' c -> Storage' c
+ Erebos.Storage.Internal: refStorage :: forall (c :: Type -> Type). Ref' c -> Storage' c
- Erebos.Storage.Internal: showParentStorage :: Storage' c -> String
+ Erebos.Storage.Internal: showParentStorage :: forall (c :: Type -> Type). Storage' c -> String
- Erebos.Storage.Internal: showRef :: Ref' c -> ByteString
+ Erebos.Storage.Internal: showRef :: forall (c :: Type -> Type). Ref' c -> ByteString
- Erebos.Storage.Internal: storedStorage :: Stored' c a -> Storage' c
+ Erebos.Storage.Internal: storedStorage :: forall (c :: Type -> Type) a. Stored' c a -> Storage' c
- Erebos.Storage.Internal: type LoadResult compl a :: Type;
+ Erebos.Storage.Internal: type LoadResult (compl :: Type -> Type) a;
- Erebos.Storage.Internal: unsafeStoreRawBytes :: Storage' c -> ByteString -> IO (Ref' c)
+ Erebos.Storage.Internal: unsafeStoreRawBytes :: forall (c :: Type -> Type). Storage' c -> ByteString -> IO (Ref' c)
- Erebos.Storage.Merge: class Storable (Component a) => Mergeable a where {
+ Erebos.Storage.Merge: class Storable Component a => Mergeable a where {
- Erebos.Storage.Merge: findProperty :: forall a b. Storable a => (a -> Maybe b) -> [Stored a] -> [b]
+ Erebos.Storage.Merge: findProperty :: Storable a => (a -> Maybe b) -> [Stored a] -> [b]
- Erebos.Storage.Merge: findPropertyFirst :: forall a b. Storable a => (a -> Maybe b) -> [Stored a] -> Maybe b
+ Erebos.Storage.Merge: findPropertyFirst :: Storable a => (a -> Maybe b) -> [Stored a] -> Maybe b
- Erebos.Storage.Merge: type Component a :: Type;
+ Erebos.Storage.Merge: type Component a;

Files

CHANGELOG.md view
@@ -1,5 +1,11 @@ # Revision history for erebos +## 0.1.9 -- 2025-07-08++* Option to show details or delete a conversation by giving index parameter without first selecting it+* Improved handling of ICE connections+* Automatic discovery of peers for pending direct messages+ ## 0.1.8.1 -- 2025-03-29  * Fix build from sdist (add missing include)
README.md view
@@ -102,11 +102,13 @@ `<message>`   : Send `<message>` to selected conversation. -`/history`  -: Show message history of the selected conversation.+`/history [<number>]`  +: Show message history of the selected conversation, or the one identified by+  `<number>` if given. -`/details`  -: Show information about the selected conversations, contact or peer.+`/details [<number>]`  +: Show information about the selected conversations, contact or peer; or the+  one identified by `<number>` if given.  ### Chatrooms @@ -137,12 +139,13 @@ : Leave the chatroom. User will no longer be listed as a member and erebos tool   will no longer collect message of this chatroom. -`/delete`-: Delete the chatroom; this action is only synchronized with devices belonging-to the current user and does not affect the chatroom state for others. Due to-the storage design, the chatroom data will not be purged from the local state-history, but the chatroom will no longer be listed as available and no futher-updates for this chatroom will be collected or shared with other peers.+`/delete [<number>]`  +: Delete the chatroom (currently selected one, or the one identified by+  `<number>`); this action is only synchronized with devices belonging to the+  current user and does not affect the chatroom state for others. Due to the+  storage design, the chatroom data will not be purged from the local state+  history, but the chatroom will no longer be listed as available and no futher+  updates for this chatroom will be collected or shared with other peers.  ### Add contacts 
erebos.cabal view
@@ -1,7 +1,7 @@ Cabal-Version:       3.0  Name:                erebos-Version:             0.1.8.1+Version:             0.1.9 Synopsis:            Decentralized messaging and synchronization Description:     Library and simple CLI interface implementing the Erebos identity@@ -39,6 +39,10 @@     default: False     manual: True +Flag cryptonite+    description:    Use deprecated 'cryptonite' package+    default: False+ source-repository head     type:       git     location:   https://code.erebosprotocol.net/erebos@@ -147,7 +151,6 @@         bytestring >=0.10 && <0.13,         clock >=0.8 && < 0.9,         containers ^>= { 0.6, 0.7, 0.8 },-        crypton ^>= { 0.34, 1.0 },         deepseq >= 1.4 && <1.6,         directory >= 1.3 && <1.4,         filepath >=1.4 && <1.6,@@ -164,6 +167,13 @@         uuid >=1.3 && <1.4,         zlib >=0.6 && <0.8 +    if !flag(cryptonite)+        build-depends:+            crypton ^>= { 0.34, 1.0 },+    else+        build-depends:+            cryptonite >=0.25 && <0.31,+     if os(windows)         hs-source-dirs: src/windows         build-depends:@@ -191,7 +201,6 @@      build-depends:         bytestring,-        crypton,         directory,         erebos,         haskeline >=0.7 && <0.9,@@ -203,3 +212,10 @@         time,         transformers >= 0.5 && <0.7,         uuid,++    if !flag(cryptonite)+        build-depends:+            crypton,+    else+        build-depends:+            cryptonite,
main/Main.hs view
@@ -479,7 +479,10 @@     _ -> throwError "no chatroom selected"  getSelectedConversation :: CommandM Conversation-getSelectedConversation = gets csContext >>= \case+getSelectedConversation = gets csContext >>= getConversationFromContext++getConversationFromContext :: CommandContext -> CommandM Conversation+getConversationFromContext = \case     SelectedPeer peer -> peerIdentity peer >>= \case         PeerIdentityFull pid -> directMessageConversation $ finalOwner pid         _ -> throwError "incomplete peer identity"@@ -493,6 +496,13 @@     SelectedConversation conv -> reloadConversation conv     _ -> throwError "no contact, peer or conversation selected" +getSelectedOrManualContext :: CommandM CommandContext+getSelectedOrManualContext = do+    asks ciLine >>= \case+        "" -> gets csContext+        str | all isDigit str -> getContextByIndex (read str)+        _ -> throwError "invalid index"+ commands :: [(String, Command)] commands =     [ ("history", cmdHistory)@@ -609,19 +619,22 @@     forM_ (chatroomMembers room) $ \x -> do         liftIO $ putStrLn $ maybe "<unnamed>" T.unpack $ idName x +getContextByIndex :: Int -> CommandM CommandContext+getContextByIndex n = do+    join (asks ciContextOptions) >>= \ctxs -> if+        | n > 0, (ctx : _) <- drop (n - 1) ctxs -> return ctx+        | otherwise -> throwError "invalid index"  cmdSelectContext :: Command cmdSelectContext = do     n <- read <$> asks ciLine-    join (asks ciContextOptions) >>= \ctxs -> if-        | n > 0, (ctx : _) <- drop (n - 1) ctxs -> do-            modify $ \s -> s { csContext = ctx }-            case ctx of-                SelectedChatroom rstate -> do-                    when (not (roomStateSubscribe rstate)) $ do-                        chatroomSetSubscribe (head $ roomStateData rstate) True-                _ -> return ()-        | otherwise -> throwError "invalid index"+    ctx <- getContextByIndex n+    modify $ \s -> s { csContext = ctx }+    case ctx of+        SelectedChatroom rstate -> do+            when (not (roomStateSubscribe rstate)) $ do+                chatroomSetSubscribe (head $ roomStateData rstate) True+        _ -> return ()  cmdSend :: Command cmdSend = void $ do@@ -635,12 +648,12 @@  cmdDelete :: Command cmdDelete = void $ do-    deleteConversation =<< getSelectedConversation+    deleteConversation =<< getConversationFromContext =<< getSelectedOrManualContext     modify $ \s -> s { csContext = NoContext }  cmdHistory :: Command cmdHistory = void $ do-    conv <- getSelectedConversation+    conv <- getConversationFromContext =<< getSelectedOrManualContext     case conversationHistory conv of         thread@(_:_) -> do             tzone <- liftIO $ getCurrentTimeZone@@ -804,7 +817,7 @@  cmdDetails :: Command cmdDetails = do-    gets csContext >>= \case+    getSelectedOrManualContext >>= \case         SelectedPeer peer -> do             liftIO $ putStr $ unlines                 [ "Network peer:"@@ -880,14 +893,14 @@  cmdDiscovery :: Command cmdDiscovery = void $ do-    Just peer <- gets csIcePeer+    server <- asks ciServer     st <- getStorage     sref <- asks ciLine     eprint <- asks ciPrint     liftIO $ readRef st (BC.pack sref) >>= \case         Nothing -> error "ref does not exist"         Just ref -> do-            res <- runExceptT $ sendToPeer peer $ DiscoverySearch ref+            res <- runExceptT $ discoverySearch server ref             case res of                  Right _ -> return ()                  Left err -> eprint err
main/Test.hs view
@@ -284,6 +284,7 @@     , ("contact-set-name", cmdContactSetName)     , ("dm-send-peer", cmdDmSendPeer)     , ("dm-send-contact", cmdDmSendContact)+    , ("dm-send-identity", cmdDmSendIdentity)     , ("dm-list-peer", cmdDmListPeer)     , ("dm-list-contact", cmdDmListContact)     , ("chatroom-create", cmdChatroomCreate)@@ -736,6 +737,14 @@     Just to <- contactIdentity <$> getContact cid     void $ sendDirectMessage to msg +cmdDmSendIdentity :: Command+cmdDmSendIdentity = do+    st <- asks tiStorage+    [ tid, msg ] <- asks tiParams+    Just ref <- liftIO $ readRef st $ encodeUtf8 tid+    Just to <- return $ validateExtendedIdentity $ wrappedLoad ref+    void $ sendDirectMessage to msg+ dmList :: Foldable f => Identity f -> Command dmList peer = do     threads <- toThreadList . lookupSharedValue . lsShared . headObject <$> getHead@@ -880,8 +889,5 @@     st <- asks tiStorage     [ tref ] <- asks tiParams     Just ref <- liftIO $ readRef st $ encodeUtf8 tref-     Just RunningServer {..} <- gets tsServer-    peers <- liftIO $ getCurrentPeerList rsServer-    forM_ peers $ \peer -> do-        sendToPeer peer $ DiscoverySearch ref+    discoverySearch rsServer ref
src/Erebos/Conversation.hs view
@@ -71,7 +71,7 @@ directMessageConversation peer = do     (find (sameIdentity peer . msgPeer) . toThreadList . lookupSharedValue . lsShared . fromStored <$> getLocalHead) >>= \case         Just thread -> return $ DirectMessageConversation thread-        Nothing -> return $ DirectMessageConversation $ DirectMessageThread peer [] [] []+        Nothing -> return $ DirectMessageConversation $ DirectMessageThread peer [] [] [] []  chatroomConversation :: MonadHead LocalState m => ChatroomState -> m (Maybe Conversation) chatroomConversation rstate = chatroomConversationByStateData (head $ roomStateData rstate)
src/Erebos/Discovery.hs view
@@ -3,7 +3,9 @@ module Erebos.Discovery (     DiscoveryService(..),     DiscoveryAttributes(..),-    DiscoveryConnection(..)+    DiscoveryConnection(..),++    discoverySearch, ) where  import Control.Concurrent@@ -12,9 +14,13 @@ import Control.Monad.Reader  import Data.IP qualified as IP+import Data.List import Data.Map.Strict (Map) import Data.Map.Strict qualified as M import Data.Maybe+import Data.Proxy+import Data.Set (Set)+import Data.Set qualified as S import Data.Text (Text) import Data.Text qualified as T import Data.Word@@ -30,6 +36,13 @@ import Erebos.Storage  +#ifndef ENABLE_ICE_SUPPORT+type IceConfig = ()+type IceSession = ()+type IceRemoteInfo = Stored Object+#endif++ data DiscoveryService     = DiscoverySelf [ Text ] (Maybe Int)     | DiscoveryAcknowledged [ Text ] (Maybe Text) (Maybe Word16) (Maybe Text) (Maybe Word16)@@ -57,11 +70,7 @@     { dconnSource :: Ref     , dconnTarget :: Ref     , dconnAddress :: Maybe Text-#ifdef ENABLE_ICE_SUPPORT     , dconnIceInfo :: Maybe IceRemoteInfo-#else-    , dconnIceInfo :: Maybe (Stored Object)-#endif     }  emptyConnection :: Ref -> Ref -> DiscoveryConnection@@ -90,12 +99,13 @@             DiscoveryConnectionRequest conn -> storeConnection "request" conn             DiscoveryConnectionResponse conn -> storeConnection "response" conn -        where storeConnection ctype conn = do-                  storeText "connection" $ ctype-                  storeRawRef "source" $ dconnSource conn-                  storeRawRef "target" $ dconnTarget conn-                  storeMbText "address" $ dconnAddress conn-                  storeMbRef "ice-info" $ dconnIceInfo conn+      where+        storeConnection ctype DiscoveryConnection {..} = do+            storeText "connection" $ ctype+            storeRawRef "source" dconnSource+            storeRawRef "target" dconnTarget+            storeMbText "address" dconnAddress+            storeMbRef "ice-info" dconnIceInfo      load' = loadRec $ msum             [ do@@ -120,37 +130,60 @@             , loadConnection "request" DiscoveryConnectionRequest             , loadConnection "response" DiscoveryConnectionResponse             ]-        where loadConnection ctype ctor = do-                  ctype' <- loadText "connection"-                  guard $ ctype == ctype'-                  return . ctor =<< DiscoveryConnection-                      <$> loadRawRef "source"-                      <*> loadRawRef "target"-                      <*> loadMbText "address"-                      <*> loadMbRef "ice-info"+      where+        loadConnection ctype ctor = do+            ctype' <- loadText "connection"+            guard $ ctype == ctype'+            dconnSource <- loadRawRef "source"+            dconnTarget <- loadRawRef "target"+            dconnAddress <- loadMbText "address"+            dconnIceInfo <- loadMbRef "ice-info"+            return $ ctor DiscoveryConnection {..}  data DiscoveryPeer = DiscoveryPeer     { dpPriority :: Int     , dpPeer :: Maybe Peer     , dpAddress :: [ Text ]-#ifdef ENABLE_ICE_SUPPORT     , dpIceSession :: Maybe IceSession-#endif     } +emptyPeer :: DiscoveryPeer+emptyPeer = DiscoveryPeer+    { dpPriority = 0+    , dpPeer = Nothing+    , dpAddress = []+    , dpIceSession = Nothing+    }++data DiscoveryPeerState = DiscoveryPeerState+    { dpsStunServer :: Maybe ( Text, Word16 )+    , dpsTurnServer :: Maybe ( Text, Word16 )+    , dpsIceConfig :: Maybe IceConfig+    }++data DiscoveryGlobalState = DiscoveryGlobalState+    { dgsPeers :: Map RefDigest DiscoveryPeer+    , dgsSearchingFor :: Set RefDigest+    }+ instance Service DiscoveryService where     serviceID _ = mkServiceID "dd59c89c-69cc-4703-b75b-4ddcd4b3c23c"      type ServiceAttributes DiscoveryService = DiscoveryAttributes     defaultServiceAttributes _ = defaultDiscoveryAttributes -#ifdef ENABLE_ICE_SUPPORT-    type ServiceState DiscoveryService = Maybe IceConfig-    emptyServiceState _ = Nothing-#endif+    type ServiceState DiscoveryService = DiscoveryPeerState+    emptyServiceState _ = DiscoveryPeerState+        { dpsStunServer = Nothing+        , dpsTurnServer = Nothing+        , dpsIceConfig = Nothing+        } -    type ServiceGlobalState DiscoveryService = Map RefDigest DiscoveryPeer-    emptyServiceGlobalState _ = M.empty+    type ServiceGlobalState DiscoveryService = DiscoveryGlobalState+    emptyServiceGlobalState _ = DiscoveryGlobalState+        { dgsPeers = M.empty+        , dgsSearchingFor = S.empty+        }      serviceHandler msg = case fromStored msg of         DiscoverySelf addrs priority -> do@@ -171,15 +204,14 @@                  | otherwise -> return Nothing -            forM_ (idDataF =<< unfoldOwners pid) $ \s ->-                svcModifyGlobal $ M.insertWith insertHelper (refDigest $ storedRef s) DiscoveryPeer-                    { dpPriority = fromMaybe 0 priority-                    , dpPeer = Just peer-                    , dpAddress = addrs-#ifdef ENABLE_ICE_SUPPORT-                    , dpIceSession = Nothing-#endif-                    }+            forM_ (idDataF =<< unfoldOwners pid) $ \sdata -> do+                let dp = DiscoveryPeer+                        { dpPriority = fromMaybe 0 priority+                        , dpPeer = Just peer+                        , dpAddress = addrs+                        , dpIceSession = Nothing+                        }+                svcModifyGlobal $ \s -> s { dgsPeers = M.insertWith insertHelper (refDigest $ storedRef sdata) dp $ dgsPeers s }             attrs <- asks svcAttributes             replyPacket $ DiscoveryAcknowledged matchedAddrs                 (discoveryStunServer attrs)@@ -188,7 +220,6 @@                 (discoveryTurnPort attrs)          DiscoveryAcknowledged _ stunServer stunPort turnServer turnPort -> do-#ifdef ENABLE_ICE_SUPPORT             paddr <- asks (peerAddress . svcPeer) >>= return . \case                 (DatagramAddress saddr) -> case IP.fromSockAddr saddr of                     Just (IP.IPv6 ipv6, _)@@ -204,100 +235,91 @@                 toIceServer (Just server) Nothing = Just ( server, 0 )                 toIceServer (Just server) (Just port) = Just ( server, port ) -            cfg <- liftIO $ iceCreateConfig-                (toIceServer stunServer stunPort)-                (toIceServer turnServer turnPort)-            svcSet cfg-#endif-            return ()+            svcModify $ \s -> s+                { dpsStunServer = toIceServer stunServer stunPort+                , dpsTurnServer = toIceServer turnServer turnPort+                }          DiscoverySearch ref -> do-            dpeer <- M.lookup (refDigest ref) <$> svcGetGlobal+            dpeer <- M.lookup (refDigest ref) . dgsPeers <$> svcGetGlobal             replyPacket $ DiscoveryResult ref $ maybe [] dpAddress dpeer -        DiscoveryResult ref [] -> do-            svcPrint $ "Discovery: " ++ show (refDigest ref) ++ " not found"+        DiscoveryResult _ [] -> do+            -- not found+            return ()          DiscoveryResult ref addrs -> do+            let dgst = refDigest ref             -- TODO: check if we really requested that             server <- asks svcServer             self <- svcSelf-            mbIceConfig <- svcGet             discoveryPeer <- asks svcPeer             let runAsService = runPeerService @DiscoveryService discoveryPeer -            liftIO $ void $ forkIO $ forM_ addrs $ \addr -> if+            forM_ addrs $ \addr -> if                 | addr == T.pack "ICE"-#ifdef ENABLE_ICE_SUPPORT-                , Just config <- mbIceConfig                 -> do-                    ice <- iceCreateSession config PjIceSessRoleControlling $ \ice -> do-                        rinfo <- iceRemoteInfo ice-                        res <- runExceptT $ sendToPeer discoveryPeer $-                            DiscoveryConnectionRequest (emptyConnection (storedRef $ idData self) ref) { dconnIceInfo = Just rinfo }-                        case res of-                            Right _ -> return ()-                            Left err -> putStrLn $ "Discovery: failed to send connection request: " ++ err+#ifdef ENABLE_ICE_SUPPORT+                    getIceConfig >>= \case+                        Just config -> void $ liftIO $ forkIO $ do+                            ice <- iceCreateSession config PjIceSessRoleControlling $ \ice -> do+                                rinfo <- iceRemoteInfo ice -                    runAsService $ do-                        svcModifyGlobal $ M.insert (refDigest ref) DiscoveryPeer-                            { dpPriority = 0-                            , dpPeer = Nothing-                            , dpAddress = []-                            , dpIceSession = Just ice-                            }-#else-                -> do-                    return ()+                                res <- runExceptT $ sendToPeer discoveryPeer $+                                    DiscoveryConnectionRequest (emptyConnection (storedRef $ idData self) ref) { dconnIceInfo = Just rinfo }+                                case res of+                                    Right _ -> return ()+                                    Left err -> putStrLn $ "Discovery: failed to send connection request: " ++ err++                            runAsService $ do+                                let upd dp = dp { dpIceSession = Just ice }+                                svcModifyGlobal $ \s -> s { dgsPeers = M.alter (Just . upd . fromMaybe emptyPeer) dgst $ dgsPeers s }++                        Nothing -> do+                            return () #endif+                    return ()                  | [ ipaddr, port ] <- words (T.unpack addr) -> do-                    saddr <- head <$>-                        getAddrInfo (Just $ defaultHints { addrSocketType = Datagram }) (Just ipaddr) (Just port)-                    peer <- serverPeer server (addrAddress saddr)-                    runAsService $ do-                        svcModifyGlobal $ M.insert (refDigest ref) DiscoveryPeer-                            { dpPriority = 0-                            , dpPeer = Just peer-                            , dpAddress = []-#ifdef ENABLE_ICE_SUPPORT-                            , dpIceSession = Nothing-#endif-                        }+                    void $ liftIO $ forkIO $ do+                        saddr <- head <$>+                            getAddrInfo (Just $ defaultHints { addrSocketType = Datagram }) (Just ipaddr) (Just port)+                        peer <- serverPeer server (addrAddress saddr)+                        runAsService $ do+                            let upd dp = dp { dpPeer = Just peer }+                            svcModifyGlobal $ \s -> s { dgsPeers = M.alter (Just . upd . fromMaybe emptyPeer) dgst $ dgsPeers s }                  | otherwise -> do-                    runAsService $ do-                        svcPrint $ "Discovery: invalid address in result: " ++ T.unpack addr+                    svcPrint $ "Discovery: invalid address in result: " ++ T.unpack addr          DiscoveryConnectionRequest conn -> do             self <- svcSelf             let rconn = emptyConnection (dconnSource conn) (dconnTarget conn)-            if refDigest (dconnTarget conn) `elem` (map (refDigest . storedRef) $ idDataF =<< unfoldOwners self)-               then do+            if refDigest (dconnTarget conn) `elem` identityDigests self+              then if #ifdef ENABLE_ICE_SUPPORT-                    -- request for us, create ICE sesssion+                -- request for us, create ICE sesssion+                | Just prinfo <- dconnIceInfo conn -> do                     server <- asks svcServer                     peer <- asks svcPeer-                    svcGet >>= \case+                    getIceConfig >>= \case                         Just config -> do                             liftIO $ void $ iceCreateSession config PjIceSessRoleControlled $ \ice -> do                                 rinfo <- iceRemoteInfo ice                                 res <- runExceptT $ sendToPeer peer $ DiscoveryConnectionResponse rconn { dconnIceInfo = Just rinfo }                                 case res of-                                    Right _ -> do-                                        case dconnIceInfo conn of-                                            Just prinfo -> iceConnect ice prinfo $ void $ serverPeerIce server ice-                                            Nothing -> putStrLn $ "Discovery: connection request without ICE remote info"+                                    Right _ -> iceConnect ice prinfo $ void $ serverPeerIce server ice                                     Left err -> putStrLn $ "Discovery: failed to send connection response: " ++ err                         Nothing -> do-                            svcPrint $ "Discovery: ICE request from peer without ICE configuration"-#else-                    return ()+                            return () #endif -               else do+                | otherwise -> do+                    svcPrint $ "Discovery: unsupported connection request"++              else do                     -- request to some of our peers, relay-                    mbdp <- M.lookup (refDigest $ dconnTarget conn) <$> svcGetGlobal+                    mbdp <- M.lookup (refDigest $ dconnTarget conn) . dgsPeers <$> svcGetGlobal                     case mbdp of                         Nothing -> replyPacket $ DiscoveryConnectionResponse rconn                         Just dp@@ -307,29 +329,28 @@          DiscoveryConnectionResponse conn -> do             self <- svcSelf-            dpeers <- svcGetGlobal-            if refDigest (dconnSource conn) `elem` (map (refDigest . storedRef) $ idDataF =<< unfoldOwners self)+            dpeers <- dgsPeers <$> svcGetGlobal+            if refDigest (dconnSource conn) `elem` identityDigests self                then do                     -- response to our request, try to connect to the peer-#ifdef ENABLE_ICE_SUPPORT                     server <- asks svcServer                     if  | Just addr <- dconnAddress conn                         , [ipaddr, port] <- words (T.unpack addr) -> do                             saddr <- liftIO $ head <$>                                 getAddrInfo (Just $ defaultHints { addrSocketType = Datagram }) (Just ipaddr) (Just port)                             peer <- liftIO $ serverPeer server (addrAddress saddr)-                            svcModifyGlobal $ M.insert (refDigest $ dconnTarget conn) $-                                DiscoveryPeer 0 (Just peer) [] Nothing+                            let upd dp = dp { dpPeer = Just peer }+                            svcModifyGlobal $ \s -> s+                                { dgsPeers = M.alter (Just . upd . fromMaybe emptyPeer) (refDigest $ dconnTarget conn) $ dgsPeers s } +#ifdef ENABLE_ICE_SUPPORT                         | Just dp <- M.lookup (refDigest $ dconnTarget conn) dpeers                         , Just ice <- dpIceSession dp                         , Just rinfo <- dconnIceInfo conn -> do                             liftIO $ iceConnect ice rinfo $ void $ serverPeerIce server ice+#endif                          | otherwise -> svcPrint $ "Discovery: connection request failed"-#else-                    return ()-#endif                else do                     -- response to relayed request                     case M.lookup (refDigest $ dconnSource conn) dpeers of@@ -340,6 +361,7 @@     serviceNewPeer = do         server <- asks svcServer         peer <- asks svcPeer+        st <- getStorage          let addrToText saddr = do                 ( addr, port ) <- IP.fromSockAddr saddr@@ -351,5 +373,60 @@ #endif             ] +        pid <- asks svcPeerIdentity+        gs <- svcGetGlobal+        let searchingFor = foldl' (flip S.delete) (dgsSearchingFor gs) (identityDigests pid)+        svcModifyGlobal $ \s -> s { dgsSearchingFor = searchingFor }+         when (not $ null addrs) $ do             sendToPeer peer $ DiscoverySelf addrs Nothing+        forM_ searchingFor $ \dgst -> do+            liftIO (refFromDigest st dgst) >>= \case+                Just ref -> sendToPeer peer $ DiscoverySearch ref+                Nothing -> return ()++#ifdef ENABLE_ICE_SUPPORT+    serviceStopServer _ _ _ pstates = do+        forM_ pstates $ \( _, DiscoveryPeerState {..} ) -> do+            mapM_ iceStopThread dpsIceConfig+#endif+++identityDigests :: Foldable f => Identity f -> [ RefDigest ]+identityDigests pid = map (refDigest . storedRef) $ idDataF =<< unfoldOwners pid+++getIceConfig :: ServiceHandler DiscoveryService (Maybe IceConfig)+getIceConfig = do+    dpsIceConfig <$> svcGet >>= \case+        Just cfg -> return $ Just cfg+        Nothing -> do+#ifdef ENABLE_ICE_SUPPORT+            stun <- dpsStunServer <$> svcGet+            turn <- dpsTurnServer <$> svcGet+            liftIO (iceCreateConfig stun turn) >>= \case+                Just cfg -> do+                    svcModify $ \s -> s { dpsIceConfig = Just cfg }+                    return $ Just cfg+                Nothing -> do+                    svcPrint $ "Discovery: failed to create ICE config"+                    return Nothing+#else+            return Nothing+#endif+++discoverySearch :: (MonadIO m, MonadError String m) => Server -> Ref -> m ()+discoverySearch server ref = do+    peers <- liftIO $ getCurrentPeerList server+    match <- forM peers $ \peer -> do+        peerIdentity peer >>= \case+            PeerIdentityFull pid -> do+                return $ refDigest ref `elem` identityDigests pid+            _ -> return False+    when (not $ or match) $ do+        modifyServiceGlobalState server (Proxy @DiscoveryService) $ \s -> (, ()) s+            { dgsSearchingFor = S.insert (refDigest ref) $ dgsSearchingFor s+            }+        forM_ peers $ \peer -> do+            sendToPeer peer $ DiscoverySearch ref
src/Erebos/Flow.hs view
@@ -11,54 +11,53 @@ import Control.Concurrent.STM  -data Flow r w = Flow (TMVar [r]) (TMVar [w])-              | forall r' w'. MappedFlow (r' -> r) (w -> w') (Flow r' w')+data Flow r w+    = Flow (TBQueue r) (TBQueue w)+    | forall r' w'. MappedFlow (r' -> r) (w -> w') (Flow r' w')  type SymFlow a = Flow a a  newFlow :: STM (Flow a b, Flow b a) newFlow = do-    x <- newEmptyTMVar-    y <- newEmptyTMVar+    x <- newTBQueue 16+    y <- newTBQueue 16     return (Flow x y, Flow y x)  newFlowIO :: IO (Flow a b, Flow b a) newFlowIO = atomically newFlow  readFlow :: Flow r w -> STM r-readFlow (Flow rvar _) = takeTMVar rvar >>= \case-    (x:[]) -> return x-    (x:xs) -> putTMVar rvar xs >> return x-    [] -> error "Flow: empty list"+readFlow (Flow rvar _) = readTBQueue rvar readFlow (MappedFlow f _ up) = f <$> readFlow up  tryReadFlow :: Flow r w -> STM (Maybe r)-tryReadFlow (Flow rvar _) = tryTakeTMVar rvar >>= \case-    Just (x:[]) -> return (Just x)-    Just (x:xs) -> putTMVar rvar xs >> return (Just x)-    Just [] -> error "Flow: empty list"-    Nothing -> return Nothing+tryReadFlow (Flow rvar _) = tryReadTBQueue rvar tryReadFlow (MappedFlow f _ up) = fmap f <$> tryReadFlow up  canReadFlow :: Flow r w -> STM Bool-canReadFlow (Flow rvar _) = not <$> isEmptyTMVar rvar+canReadFlow (Flow rvar _) = not <$> isEmptyTBQueue rvar canReadFlow (MappedFlow _ _ up) = canReadFlow up  writeFlow :: Flow r w -> w -> STM ()-writeFlow (Flow _ wvar) = putTMVar wvar . (:[])+writeFlow (Flow _ wvar) = writeTBQueue wvar writeFlow (MappedFlow _ f up) = writeFlow up . f  writeFlowBulk :: Flow r w -> [w] -> STM () writeFlowBulk _ [] = return ()-writeFlowBulk (Flow _ wvar) xs = putTMVar wvar xs+writeFlowBulk (Flow _ wvar) xs = mapM_ (writeTBQueue wvar) xs writeFlowBulk (MappedFlow _ f up) xs = writeFlowBulk up $ map f xs  tryWriteFlow :: Flow r w -> w -> STM Bool-tryWriteFlow (Flow _ wvar) = tryPutTMVar wvar . (:[])-tryWriteFlow (MappedFlow _ f up) = tryWriteFlow up . f+tryWriteFlow (Flow _ wvar) x = do+    isFullTBQueue wvar >>= \case+        True -> return False+        False -> do+            writeTBQueue wvar x+            return True+tryWriteFlow (MappedFlow _ f up) x = tryWriteFlow up $ f x  canWriteFlow :: Flow r w -> STM Bool-canWriteFlow (Flow _ wvar) = isEmptyTMVar wvar+canWriteFlow (Flow _ wvar) = not <$> isFullTBQueue wvar canWriteFlow (MappedFlow _ _ up) = canWriteFlow up  readFlowIO :: Flow r w -> IO r
src/Erebos/ICE.chs view
@@ -8,6 +8,7 @@     IceRemoteInfo,      iceCreateConfig,+    iceStopThread,     iceCreateSession,     iceDestroy,     iceRemoteInfo,@@ -137,6 +138,12 @@         if cfg == nullPtr           then return Nothing           else Just . IceConfig <$> newForeignPtr ice_cfg_free cfg++foreign import ccall unsafe "pjproject.h ice_cfg_stop_thread"+    ice_cfg_stop_thread :: Ptr PjIceStransCfg -> IO ()++iceStopThread :: IceConfig -> IO ()+iceStopThread (IceConfig fcfg) = withForeignPtr fcfg ice_cfg_stop_thread  {#pointer *pj_ice_strans as ^ #} 
src/Erebos/ICE/pjproject.c view
@@ -1,6 +1,7 @@ #include "pjproject.h" #include "Erebos/ICE_stub.h" +#include <stdatomic.h> #include <stdio.h> #include <stdlib.h> #include <stdbool.h>@@ -15,6 +16,13 @@ 	pj_sockaddr def_addr; } ice; +struct erebos_ice_cfg+{+	pj_ice_strans_cfg cfg;+	pj_thread_t * thread;+	atomic_bool exit;+};+ struct user_data { 	pj_ice_sess_role role;@@ -30,17 +38,17 @@ 	fprintf(stderr, "ICE: %s: %s\n", msg, err); } -static int ice_worker_thread(void * vcfg)+static int ice_worker_thread( void * vcfg ) {-	pj_ice_strans_cfg * cfg = (pj_ice_strans_cfg *) vcfg;+	struct erebos_ice_cfg * ecfg = (struct erebos_ice_cfg *)( vcfg ); -	while (true) {+	while( ! ecfg->exit ){ 		pj_time_val max_timeout = { 0, 0 }; 		pj_time_val timeout = { 0, 0 };  		max_timeout.msec = 500; -		pj_timer_heap_poll(cfg->stun_cfg.timer_heap, &timeout);+		pj_timer_heap_poll( ecfg->cfg.stun_cfg.timer_heap, &timeout );  		pj_assert(timeout.sec >= 0 && timeout.msec >= 0); 		if (timeout.msec >= 1000)@@ -49,7 +57,7 @@ 		if (PJ_TIME_VAL_GT(timeout, max_timeout)) 			timeout = max_timeout; -		int c = pj_ioqueue_poll(cfg->stun_cfg.ioqueue, &timeout);+		int c = pj_ioqueue_poll( ecfg->cfg.stun_cfg.ioqueue, &timeout ); 		if (c < 0) 			pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout)); 	}@@ -131,80 +139,91 @@ 	pthread_mutex_unlock(&mutex); } -pj_ice_strans_cfg * ice_cfg_create( const char * stun_server, uint16_t stun_port,+struct erebos_ice_cfg * ice_cfg_create( const char * stun_server, uint16_t stun_port, 		const char * turn_server, uint16_t turn_port ) { 	ice_init(); -	pj_ice_strans_cfg * cfg = malloc( sizeof(pj_ice_strans_cfg) );-	pj_ice_strans_cfg_default( cfg );+	struct erebos_ice_cfg * ecfg = malloc( sizeof(struct erebos_ice_cfg) );+	pj_ice_strans_cfg_default( &ecfg->cfg );+	ecfg->exit = false;+	ecfg->thread = NULL; -	cfg->stun_cfg.pf = &ice.cp.factory;+	ecfg->cfg.stun_cfg.pf = &ice.cp.factory; 	if( pj_timer_heap_create( ice.pool, 100,-				&cfg->stun_cfg.timer_heap ) != PJ_SUCCESS ){+				&ecfg->cfg.stun_cfg.timer_heap ) != PJ_SUCCESS ){ 		fprintf( stderr, "pj_timer_heap_create failed\n" ); 		goto fail; 	} -	if( pj_ioqueue_create( ice.pool, 16, &cfg->stun_cfg.ioqueue ) != PJ_SUCCESS ){+	if( pj_ioqueue_create( ice.pool, 16, &ecfg->cfg.stun_cfg.ioqueue ) != PJ_SUCCESS ){ 		fprintf( stderr, "pj_ioqueue_create failed\n" ); 		goto fail; 	} -	pj_thread_t * thread; 	if( pj_thread_create( ice.pool, NULL, &ice_worker_thread,-				cfg, 0, 0, &thread ) != PJ_SUCCESS ){+				ecfg, 0, 0, &ecfg->thread ) != PJ_SUCCESS ){ 		fprintf( stderr, "pj_thread_create failed\n" ); 		goto fail; 	} -	cfg->af = pj_AF_INET();-	cfg->opt.aggressive = PJ_TRUE;+	ecfg->cfg.af = pj_AF_INET();+	ecfg->cfg.opt.aggressive = PJ_TRUE;  	if( stun_server ){-		cfg->stun.server.ptr = malloc( strlen( stun_server ));-		pj_strcpy2( &cfg->stun.server, stun_server );+		ecfg->cfg.stun.server.ptr = malloc( strlen( stun_server ));+		pj_strcpy2( &ecfg->cfg.stun.server, stun_server ); 		if( stun_port )-			cfg->stun.port = stun_port;+			ecfg->cfg.stun.port = stun_port; 	}  	if( turn_server ){-		cfg->turn.server.ptr = malloc( strlen( turn_server ));-		pj_strcpy2( &cfg->turn.server, turn_server );+		ecfg->cfg.turn.server.ptr = malloc( strlen( turn_server ));+		pj_strcpy2( &ecfg->cfg.turn.server, turn_server ); 		if( turn_port )-			cfg->turn.port = turn_port;-		cfg->turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;-		cfg->turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;-		cfg->turn.conn_type = PJ_TURN_TP_UDP;+			ecfg->cfg.turn.port = turn_port;+		ecfg->cfg.turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;+		ecfg->cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;+		ecfg->cfg.turn.conn_type = PJ_TURN_TP_UDP; 	} -	return cfg;+	return ecfg; fail:-	ice_cfg_free( cfg );+	ice_cfg_free( ecfg ); 	return NULL; } -void ice_cfg_free( pj_ice_strans_cfg * cfg )+void ice_cfg_free( struct erebos_ice_cfg * ecfg ) {-	if( ! cfg )+	if( ! ecfg ) 		return; -	if( cfg->turn.server.ptr )-		free( cfg->turn.server.ptr );+	ecfg->exit = true;+	pj_thread_join( ecfg->thread ); -	if( cfg->stun.server.ptr )-		free( cfg->stun.server.ptr );+	if( ecfg->cfg.turn.server.ptr )+		free( ecfg->cfg.turn.server.ptr ); -	if( cfg->stun_cfg.ioqueue )-		pj_ioqueue_destroy( cfg->stun_cfg.ioqueue );+	if( ecfg->cfg.stun.server.ptr )+		free( ecfg->cfg.stun.server.ptr ); -	if( cfg->stun_cfg.timer_heap )-		pj_timer_heap_destroy( cfg->stun_cfg.timer_heap );+	if( ecfg->cfg.stun_cfg.ioqueue )+		pj_ioqueue_destroy( ecfg->cfg.stun_cfg.ioqueue ); -	free( cfg );+	if( ecfg->cfg.stun_cfg.timer_heap )+		pj_timer_heap_destroy( ecfg->cfg.stun_cfg.timer_heap );++	free( ecfg ); } -pj_ice_strans * ice_create( const pj_ice_strans_cfg * cfg, pj_ice_sess_role role,+void ice_cfg_stop_thread( struct erebos_ice_cfg * ecfg )+{+	if( ! ecfg )+		return;+	ecfg->exit = true;+}++pj_ice_strans * ice_create( const struct erebos_ice_cfg * ecfg, pj_ice_sess_role role, 		HsStablePtr sptr, HsStablePtr cb ) { 	ice_init();@@ -221,7 +240,7 @@ 		.on_ice_complete = cb_on_ice_complete, 	}; -	pj_status_t status = pj_ice_strans_create( NULL, cfg, 1,+	pj_status_t status = pj_ice_strans_create( NULL, &ecfg->cfg, 1, 			udata, &icecb, &res );  	if (status != PJ_SUCCESS)
src/Erebos/ICE/pjproject.h view
@@ -3,11 +3,12 @@ #include <pjnath.h> #include <HsFFI.h> -pj_ice_strans_cfg * ice_cfg_create( const char * stun_server, uint16_t stun_port,+struct erebos_ice_cfg * ice_cfg_create( const char * stun_server, uint16_t stun_port, 		const char * turn_server, uint16_t turn_port );-void ice_cfg_free( pj_ice_strans_cfg * cfg );+void ice_cfg_free( struct erebos_ice_cfg * cfg );+void ice_cfg_stop_thread( struct erebos_ice_cfg * cfg ); -pj_ice_strans * ice_create( const pj_ice_strans_cfg *, pj_ice_sess_role role,+pj_ice_strans * ice_create( const struct erebos_ice_cfg *, pj_ice_sess_role role, 		HsStablePtr sptr, HsStablePtr cb ); void ice_destroy(pj_ice_strans * strans); 
src/Erebos/Message.hs view
@@ -29,6 +29,7 @@ import Data.Time.Format import Data.Time.LocalTime +import Erebos.Discovery import Erebos.Identity import Erebos.Network import Erebos.Service@@ -103,8 +104,10 @@      serviceNewPeer = syncDirectMessageToPeer . lookupSharedValue . lsShared . fromStored =<< svcGetLocal -    serviceStorageWatchers _ = (:[]) $-        SomeStorageWatcher (lookupSharedValue . lsShared . fromStored) syncDirectMessageToPeer+    serviceStorageWatchers _ =+        [ SomeStorageWatcher (lookupSharedValue . lsShared . fromStored) syncDirectMessageToPeer+        , GlobalStorageWatcher (lookupSharedValue . lsShared . fromStored) findMissingPeers+        ]   data MessageState = MessageState@@ -210,12 +213,19 @@               else do                 return unchanged +findMissingPeers :: Server -> DirectMessageThreads -> ExceptT String IO ()+findMissingPeers server threads = do+    forM_ (toThreadList threads) $ \thread -> do+        when (msgHead thread /= msgReceived thread) $ do+            mapM_ (discoverySearch server) $ map storedRef $ idDataF $ msgPeer thread + data DirectMessageThread = DirectMessageThread     { msgPeer :: ComposedIdentity-    , msgHead :: [Stored DirectMessage]-    , msgSent :: [Stored DirectMessage]-    , msgSeen :: [Stored DirectMessage]+    , msgHead :: [ Stored DirectMessage ]+    , msgSent :: [ Stored DirectMessage ]+    , msgSeen :: [ Stored DirectMessage ]+    , msgReceived :: [ Stored DirectMessage ]     }  threadToList :: DirectMessageThread -> [DirectMessage]@@ -249,6 +259,7 @@          , msgHead = filterAncestors $ ready ++ received          , msgSent = filterAncestors $ sent ++ received          , msgSeen = filterAncestors $ ready ++ seen+         , msgReceived = filterAncestors $ received          }  
src/Erebos/Network.hs view
@@ -24,6 +24,7 @@     sendToPeerStored, sendManyToPeerStored,     sendToPeerWith,     runPeerService,+    modifyServiceGlobalState,      discoveryPort, ) where@@ -300,13 +301,18 @@                     announceUpdate idt              forM_ serverServices $ \(SomeService service _) -> do-                forM_ (serviceStorageWatchers service) $ \(SomeStorageWatcher sel act) -> do-                    watchHeadWith serverOrigHead (sel . headStoredObject) $ \x -> do-                        withMVar serverPeers $ mapM_ $ \peer -> atomically $ do-                            readTVar (peerIdentityVar peer) >>= \case-                                PeerIdentityFull _ -> writeTQueue serverIOActions $ do-                                    runPeerService peer $ act x-                                _ -> return ()+                forM_ (serviceStorageWatchers service) $ \case+                    SomeStorageWatcher sel act -> do+                        watchHeadWith serverOrigHead (sel . headStoredObject) $ \x -> do+                            withMVar serverPeers $ mapM_ $ \peer -> atomically $ do+                                readTVar (peerIdentityVar peer) >>= \case+                                    PeerIdentityFull _ -> writeTQueue serverIOActions $ do+                                        runPeerService peer $ act x+                                    _ -> return ()+                    GlobalStorageWatcher sel act -> do+                        watchHeadWith serverOrigHead (sel . headStoredObject) $ \x -> do+                            atomically $ writeTQueue serverIOActions $ do+                                act server x              forkServerThread server $ forever $ do                 (msg, saddr) <- S.recvFrom sock 4096@@ -391,9 +397,22 @@     return server  stopServer :: Server -> IO ()-stopServer Server {..} = do-    mapM_ killThread =<< takeMVar serverThreads+stopServer server@Server {..} = do+    withMVar serverPeers $ \peers -> do+        ( global, peerStates ) <- atomically $ (,)+            <$> takeTMVar serverServiceStates+            <*> (forM (M.elems peers) $ \p@Peer {..} -> ( p, ) <$> takeTMVar peerServiceState) +        forM_ global $ \(SomeServiceGlobalState (proxy :: Proxy s) gs) -> do+            ps <- forM peerStates $ \( peer, states ) ->+                return $ ( peer, ) $ case M.lookup (serviceID proxy) states of+                    Just (SomeServiceState (_ :: Proxy ps) pstate)+                        | Just (Refl :: s :~: ps) <- eqT+                        -> pstate+                    _ -> emptyServiceState proxy+            serviceStopServer proxy server gs ps+        mapM_ killThread =<< takeMVar serverThreads+ dataResponseWorker :: Server -> IO () dataResponseWorker server = forever $ do     (peer, npref) <- atomically (readTQueue $ serverDataResponse server)@@ -899,7 +918,7 @@          Left err -> throwError err  -lookupService :: forall s. Service s => Proxy s -> [SomeService] -> Maybe (SomeService, ServiceAttributes s)+lookupService :: forall s proxy. Service s => proxy s -> [SomeService] -> Maybe (SomeService, ServiceAttributes s) lookupService proxy (service@(SomeService (_ :: Proxy t) attr) : rest)     | Just (Refl :: s :~: t) <- eqT = Just (service, attr)     | otherwise = lookupService proxy rest@@ -953,6 +972,27 @@          _ -> atomically $ do             logd $ "unhandled service '" ++ show (toUUID svc) ++ "'"++modifyServiceGlobalState+    :: forall s a m proxy. (Service s, MonadIO m, MonadError String m)+    => Server -> proxy s+    -> (ServiceGlobalState s -> ( ServiceGlobalState s, a ))+    -> m a+modifyServiceGlobalState server proxy f = do+    let svc = serviceID proxy+    case lookupService proxy (serverServices server) of+        Just ( service, _ ) -> do+            liftIO $ atomically $ do+                global <- takeTMVar (serverServiceStates server)+                ( global', res ) <- case fromMaybe (someServiceEmptyGlobalState service) $ M.lookup svc global of+                    SomeServiceGlobalState (_ :: Proxy gs) gs -> do+                        (Refl :: s :~: gs) <- return $ fromMaybe (error "service ID mismatch in global map") eqT+                        let ( gs', res ) = f gs+                        return ( M.insert svc (SomeServiceGlobalState (Proxy @s) gs') global, res )+                putTMVar (serverServiceStates server) global'+                return res+        Nothing -> do+            throwError $ "unhandled service '" ++ show (toUUID svc) ++ "'"   foreign import ccall unsafe "Network/ifaddrs.h join_multicast" cJoinMulticast :: CInt -> Ptr CSize -> IO (Ptr Word32)
src/Erebos/Network/Protocol.hs view
@@ -36,6 +36,7 @@ import Control.Concurrent import Control.Concurrent.Async import Control.Concurrent.STM+import Control.Exception import Control.Monad import Control.Monad.Except import Control.Monad.Trans@@ -510,8 +511,10 @@              race_ (waitTill next) waitForUpdate -    race_ signalTimeouts $ forever $ join $ atomically $-        passUpIncoming gs <|> processIncoming gs <|> processOutgoing gs+    race_ signalTimeouts $ forever $ do+        io <- atomically $ do+            passUpIncoming gs <|> processIncoming gs <|> processOutgoing gs+        catch io $ \(e :: SomeException) -> atomically $ gLog $ "exception during network protocol handling: " <> show e   getConnection :: GlobalState addr -> addr -> STM (Connection addr)
src/Erebos/Service.hs view
@@ -71,7 +71,10 @@     serviceStorageWatchers :: proxy s -> [SomeStorageWatcher s]     serviceStorageWatchers _ = [] +    serviceStopServer :: proxy s -> Server -> ServiceGlobalState s -> [ ( Peer, ServiceState s ) ] -> IO ()+    serviceStopServer _ _ _ _ = return () + data SomeService = forall s. Service s => SomeService (Proxy s) (ServiceAttributes s)  someService :: forall s proxy. Service s => proxy s -> SomeService@@ -100,7 +103,9 @@ someServiceEmptyGlobalState (SomeService p _) = SomeServiceGlobalState p (emptyServiceGlobalState p)  -data SomeStorageWatcher s = forall a. Eq a => SomeStorageWatcher (Stored LocalState -> a) (a -> ServiceHandler s ())+data SomeStorageWatcher s+    = forall a. Eq a => SomeStorageWatcher (Stored LocalState -> a) (a -> ServiceHandler s ())+    | forall a. Eq a => GlobalStorageWatcher (Stored LocalState -> a) (Server -> a -> ExceptT String IO ())   newtype ServiceID = ServiceID UUID
src/Erebos/State.hs view
@@ -184,9 +184,8 @@     Nothing -> throwError "no existing shared identity"  interactiveIdentityUpdate :: (Foldable f, MonadStorage m, MonadIO m, MonadError String m) => Identity f -> m UnifiedIdentity-interactiveIdentityUpdate identity = do-    let public = idKeyIdentity identity-+interactiveIdentityUpdate fidentity = do+    identity <- mergeIdentity fidentity     name <- liftIO $ do         T.putStr $ T.concat $ concat             [ [ T.pack "Name" ]@@ -198,11 +197,11 @@         hFlush stdout         T.getLine -    if  | T.null name -> mergeIdentity identity+    if  | T.null name -> return identity         | otherwise -> do-            secret <- loadKey public-            maybe (throwError "created invalid identity") return . validateIdentity =<<-                mstore =<< sign secret =<< mstore (emptyIdentityData public)-                { iddPrev = toList $ idDataF identity-                , iddName = Just name+            secret <- loadKey $ idKeyIdentity identity+            maybe (throwError "created invalid identity") return . validateExtendedIdentity =<<+                mstore =<< sign secret =<< mstore . ExtendedIdentityData =<< return (emptyIdentityExtension $ idData identity)+                { idePrev = toList $ idExtDataF identity+                , ideName = Just name                 }
src/Erebos/Storage.hs view
@@ -4,7 +4,7 @@     deriveEphemeralStorage, derivePartialStorage,      Ref, PartialRef, RefDigest,-    refDigest,+    refDigest, refFromDigest,     readRef, showRef, showRefDigest,     refDigestFromByteString, hashToRefDigest,     copyRef, partialRef, partialRefFromDigest,