Example #1
0
    async def _conduit_listen(self, ctx: ConduitListenCtx) -> Optional[bytes]:
        _, conduit = self._get_invitation_and_conduit(
            ctx.organization_id, ctx.token, expected_greeter=ctx.greeter
        )
        if ctx.is_greeter:
            curr_our_payload = conduit.greeter_payload
            curr_peer_payload = conduit.claimer_payload
        else:
            curr_our_payload = conduit.claimer_payload
            curr_peer_payload = conduit.greeter_payload

        if ctx.peer_payload is None:
            # We are waiting for the peer to provite it payload

            # Only peer payload should be allowed to change
            if conduit.state != ctx.state or curr_our_payload != ctx.payload:
                raise InvitationInvalidStateError()

            if curr_peer_payload is not None:
                # Our peer has provided it payload (hence it knows
                # about our payload too), we can update the conduit
                # to the next state
                conduit.state = NEXT_CONDUIT_STATE[ctx.state]
                conduit.greeter_payload = None
                conduit.claimer_payload = None
                await self._send_event(
                    BackendEvent.INVITE_CONDUIT_UPDATED,
                    organization_id=ctx.organization_id,
                    token=ctx.token,
                )
                return curr_peer_payload

        else:
            # We were waiting for the peer to take into account the
            # payload we provided. This would be done once the conduit
            # has switched to it next state.

            if conduit.state == NEXT_CONDUIT_STATE[ctx.state] and curr_our_payload is None:
                return ctx.peer_payload
            elif (
                conduit.state != ctx.state
                or curr_our_payload != ctx.payload
                or curr_peer_payload != ctx.peer_payload
            ):
                # Something unexpected has changed in our back...
                raise InvitationInvalidStateError()

        # Peer hasn't answered yet, we should wait and retry later...
        return None
Example #2
0
    async def _conduit_talk(
        self,
        organization_id: OrganizationID,
        greeter: Optional[UserID],
        token: InvitationToken,
        state: ConduitState,
        payload: bytes,
    ) -> ConduitListenCtx:
        is_greeter = greeter is not None
        _, conduit = self._get_invitation_and_conduit(organization_id,
                                                      token,
                                                      expected_greeter=greeter)
        if is_greeter:
            curr_our_payload = conduit.greeter_payload
            curr_peer_payload = conduit.claimer_payload
        else:
            curr_our_payload = conduit.claimer_payload
            curr_peer_payload = conduit.greeter_payload

        if conduit.state != state or curr_our_payload is not None:
            # We are out of sync with the conduit:
            # - the conduit state has changed in our back (maybe reseted by the peer)
            # - we want to reset the conduit
            # - we have already provided a payload for the current conduit state (most
            #   likely because a retry of a command that failed due to connection outage)
            if state == ConduitState.STATE_1_WAIT_PEERS:
                # We wait to reset the conduit
                conduit.state = state
                conduit.claimer_payload = None
                conduit.greeter_payload = None
                curr_peer_payload = None
            else:
                raise InvitationInvalidStateError()

        # Now update the conduit with our payload and send a signal if
        # the peer is already waiting for us.
        if is_greeter:
            conduit.greeter_payload = payload
        else:
            conduit.claimer_payload = payload

        # Note that in case of conduit reset, this signal will lure the peer into
        # thinking we have answered so he will wakeup and take into account the reset
        await self._send_event(BackendEvent.INVITE_CONDUIT_UPDATED,
                               organization_id=organization_id,
                               token=token)

        return ConduitListenCtx(
            organization_id=organization_id,
            greeter=greeter,
            token=token,
            state=state,
            payload=payload,
            peer_payload=curr_peer_payload,
        )
Example #3
0
async def _conduit_listen(conn, ctx: ConduitListenCtx) -> Optional[bytes]:
    async with conn.transaction():
        if ctx.is_greeter:
            row = await conn.fetchrow(
                *_q_conduit_greeter_info(
                    organization_id=ctx.organization_id.str,
                    greeter_user_id=ctx.greeter.str,  # type: ignore[union-attr]
                    token=ctx.token.uuid,
                )
            )
        else:
            row = await conn.fetchrow(
                *_q_conduit_claimer_info(
                    organization_id=ctx.organization_id.str, token=ctx.token.uuid
                )
            )

        if not row:
            raise InvitationNotFoundError()

        row_id = row["_id"]
        curr_conduit_state = ConduitState(row["conduit_state"])

        if row["deleted_on"]:
            raise InvitationAlreadyDeletedError()

        if ctx.is_greeter:
            curr_our_payload = row["conduit_greeter_payload"]
            curr_peer_payload = row["conduit_claimer_payload"]
        else:
            curr_our_payload = row["conduit_claimer_payload"]
            curr_peer_payload = row["conduit_greeter_payload"]

        if ctx.peer_payload is None:
            # We are waiting for the peer to provite it payload

            # Only peer payload should be allowed to change
            if curr_conduit_state != ctx.state or curr_our_payload != ctx.payload:
                raise InvitationInvalidStateError()

            if curr_peer_payload is not None:
                # Our peer has provided it payload (hence it knows
                # about our payload too), we can update the conduit
                # to the next state
                await conn.execute(
                    *_q_conduit_update(
                        row_id=row_id,
                        conduit_state=NEXT_CONDUIT_STATE[ctx.state].value,
                        conduit_greeter_payload=None,
                        conduit_claimer_payload=None,
                    )
                )
                await send_signal(
                    conn,
                    BackendEvent.INVITE_CONDUIT_UPDATED,
                    organization_id=ctx.organization_id,
                    token=ctx.token,
                )
                return curr_peer_payload

        else:
            # We were waiting for the peer to take into account the
            # payload we provided. This would be done once the conduit
            # has switched to it next state.

            if curr_conduit_state == NEXT_CONDUIT_STATE[ctx.state] and curr_our_payload is None:
                return ctx.peer_payload
            elif (
                curr_conduit_state != ctx.state
                or curr_our_payload != ctx.payload
                or curr_peer_payload != ctx.peer_payload
            ):
                # Something unexpected has changed in our back...
                raise InvitationInvalidStateError()

    # Peer hasn't answered yet, we should wait and retry later...
    return None
Example #4
0
async def _conduit_talk(
    conn,
    organization_id: OrganizationID,
    greeter: Optional[UserID],
    token: InvitationToken,
    state: ConduitState,
    payload: bytes,
) -> ConduitListenCtx:
    is_greeter = greeter is not None
    async with conn.transaction():
        # On top of retrieving the invitation row, this query lock the row
        # in the database for the duration of the transaction.
        # Hence concurrent request will be on hold until the end of the transaction.

        if is_greeter:
            row = await conn.fetchrow(
                *_q_conduit_greeter_info(
                    organization_id=organization_id.str,
                    greeter_user_id=greeter.str,  # type: ignore[union-attr]
                    token=token.uuid,
                )
            )
        else:
            row = await conn.fetchrow(
                *_q_conduit_claimer_info(organization_id=organization_id.str, token=token.uuid)
            )

        if not row:
            raise InvitationNotFoundError(token)

        row_id = row["_id"]
        curr_conduit_state = ConduitState(row["conduit_state"])
        curr_greeter_payload = row["conduit_greeter_payload"]
        curr_claimer_payload = row["conduit_claimer_payload"]

        if is_greeter:
            curr_our_payload = curr_greeter_payload
            curr_peer_payload = curr_claimer_payload
        else:
            curr_our_payload = curr_claimer_payload
            curr_peer_payload = curr_greeter_payload

        if row["deleted_on"]:
            raise InvitationAlreadyDeletedError(token)

        if curr_conduit_state != state or curr_our_payload is not None:
            # We are out of sync with the conduit:
            # - the conduit state has changed in our back (maybe reseted by the peer)
            # - we want to reset the conduit
            # - we have already provided a payload for the current conduit state (most
            #   likely because a retry of a command that failed due to connection outage)
            if state == ConduitState.STATE_1_WAIT_PEERS:
                # We wait to reset the conduit
                curr_conduit_state = state
                curr_claimer_payload = None
                curr_greeter_payload = None
                curr_our_payload = None
                curr_peer_payload = None
            else:
                raise InvitationInvalidStateError()

        # Now update the conduit with our payload and send a signal if
        # the peer is already waiting for us.
        if is_greeter:
            curr_greeter_payload = payload
        else:
            curr_claimer_payload = payload
        await conn.execute(
            *_q_conduit_update(
                row_id=row_id,
                conduit_state=curr_conduit_state.value,
                conduit_greeter_payload=curr_greeter_payload,
                conduit_claimer_payload=curr_claimer_payload,
            )
        )
        # Note that in case of conduit reset, this signal will lure the peer into
        # thinking we have answered so he will wakeup and take into account the reset
        await send_signal(
            conn, BackendEvent.INVITE_CONDUIT_UPDATED, organization_id=organization_id, token=token
        )

    return ConduitListenCtx(
        organization_id=organization_id,
        greeter=greeter,
        token=token,
        state=state,
        payload=payload,
        peer_payload=curr_peer_payload,
    )