From 6ef779f0b3e3abe01e31c5c7ab3cd2fbfbf1981d Mon Sep 17 00:00:00 2001
From: "sergei.bakhtiarov" <sbakhtiarov@gmail.com>
Date: Thu, 19 Dec 2024 13:03:18 +0100
Subject: [PATCH] feat: send and receive in-call reactions [#WPB-14254]

---
 .../logic/data/call/InCallReactionMessage.kt  |  26 ++++
 .../wire/kalium/logic/data/message/Message.kt |   5 +
 .../logic/data/message/MessageContent.kt      |   5 +
 .../data/message/MessageContentLogging.kt     |   1 +
 .../data/call/InCallReactionsRepository.kt    |  53 ++++++++
 .../data/message/PersistMessageUseCase.kt     |   4 +-
 .../logic/data/message/ProtoContentMapper.kt  |  34 ++++-
 .../kalium/logic/feature/UserSessionScope.kt  |  10 +-
 .../kalium/logic/feature/call/CallsScope.kt   |  11 +-
 .../usecase/ObserveInCallReactionsUseCase.kt  |  38 ++++++
 .../SendInCallReactionUseCase.kt              |  70 ++++++++++
 .../logic/feature/message/MessageScope.kt     |  10 ++
 .../message/PersistMigratedMessagesUseCase.kt |   1 +
 .../message/ApplicationMessageHandler.kt      |  14 +-
 .../call/InCallReactionsRepositoryTest.kt     | 122 ++++++++++++++++++
 .../data/message/ProtoContentMapperTest.kt    |  20 ++-
 .../SendInCallReactionUseCaseTest.kt          | 119 +++++++++++++++++
 .../message/ApplicationMessageHandlerTest.kt  |  40 ++++++
 .../src/main/proto/messages.proto             |  27 +++-
 19 files changed, 588 insertions(+), 22 deletions(-)
 create mode 100644 data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/InCallReactionMessage.kt
 create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/data/call/InCallReactionsRepository.kt
 create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/usecase/ObserveInCallReactionsUseCase.kt
 create mode 100644 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/incallreaction/SendInCallReactionUseCase.kt
 create mode 100644 logic/src/commonTest/kotlin/com/wire/kalium/logic/data/call/InCallReactionsRepositoryTest.kt
 create mode 100644 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/incallreaction/SendInCallReactionUseCaseTest.kt

diff --git a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/InCallReactionMessage.kt b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/InCallReactionMessage.kt
new file mode 100644
index 00000000000..ca4e4ea63d2
--- /dev/null
+++ b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/call/InCallReactionMessage.kt
@@ -0,0 +1,26 @@
+/*
+ * Wire
+ * Copyright (C) 2024 Wire Swiss GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/.
+ */
+package com.wire.kalium.logic.data.call
+
+import com.wire.kalium.logic.data.id.QualifiedID
+
+data class InCallReactionMessage(
+    val emojis: Set<String>,
+    val senderUserId: QualifiedID,
+    val senderUserName: String?
+)
diff --git a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt
index 5ea75254462..5f9ae5f1577 100644
--- a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt
+++ b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/Message.kt
@@ -248,6 +248,11 @@ sealed interface Message {
                     typeKey to "dataTransfer",
                     "content" to content.toLogMap(),
                 )
+
+                is MessageContent.InCallEmoji -> mutableMapOf(
+                    typeKey to "inCallEmoji",
+                    "content" to content.emojis
+                )
             }
 
             val standardProperties = mapOf(
diff --git a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt
index b386f096073..e2a6eaf711a 100644
--- a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt
+++ b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContent.kt
@@ -395,6 +395,10 @@ sealed interface MessageContent {
             data object Disabled : ForConversation()
         }
     }
+
+    data class InCallEmoji(
+        val emojis: Map<String, Int>
+    ) : Signaling
 }
 
 /**
@@ -455,6 +459,7 @@ fun MessageContent?.getType() = when (this) {
     is MessageContent.LegalHold.ForMembers.Disabled -> "LegalHold.ForMembers.Disabled"
     is MessageContent.LegalHold.ForMembers.Enabled -> "LegalHold.ForMembers.Enabled"
     is MessageContent.DataTransfer -> "DataTransfer"
+    is MessageContent.InCallEmoji -> "InCallEmoji"
     null -> "null"
 }
 
diff --git a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContentLogging.kt b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContentLogging.kt
index 7cbfe0fbd57..d576fa6ddc5 100644
--- a/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContentLogging.kt
+++ b/data/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageContentLogging.kt
@@ -41,4 +41,5 @@ inline fun MessageContent.FromProto.typeDescription(): String = when (this) {
     is MessageContent.Receipt -> "Receipt"
     is MessageContent.TextEdited -> "TextEdited"
     is MessageContent.DataTransfer -> "DataTransfer"
+    is MessageContent.InCallEmoji -> "InCallEmoji"
 }
diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/call/InCallReactionsRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/call/InCallReactionsRepository.kt
new file mode 100644
index 00000000000..7f02e5e73b7
--- /dev/null
+++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/call/InCallReactionsRepository.kt
@@ -0,0 +1,53 @@
+/*
+ * Wire
+ * Copyright (C) 2024 Wire Swiss GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/.
+ */
+package com.wire.kalium.logic.data.call
+
+import com.wire.kalium.logic.data.user.UserId
+import com.wire.kalium.logic.data.user.UserRepository
+import com.wire.kalium.logic.functional.onSuccess
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+internal interface InCallReactionsRepository {
+    suspend fun addInCallReaction(emojis: Set<String>, senderUserId: UserId)
+    fun observeInCallReactions(): Flow<InCallReactionMessage>
+}
+
+internal class InCallReactionsDataSource(
+    private val userRepository: UserRepository,
+) : InCallReactionsRepository {
+
+    private val inCallReactionsFlow: MutableSharedFlow<InCallReactionMessage> =
+        MutableSharedFlow(extraBufferCapacity = BUFFER_SIZE, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+
+    override suspend fun addInCallReaction(emojis: Set<String>, senderUserId: UserId) {
+        userRepository.userById(senderUserId).onSuccess { user ->
+            inCallReactionsFlow.emit(
+                InCallReactionMessage(emojis, senderUserId, user.name)
+            )
+        }
+    }
+
+    override fun observeInCallReactions(): Flow<InCallReactionMessage> = inCallReactionsFlow.asSharedFlow()
+
+    private companion object {
+        const val BUFFER_SIZE = 32 // drop after this threshold
+    }
+}
diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/PersistMessageUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/PersistMessageUseCase.kt
index 0d0288de34f..69828375295 100644
--- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/PersistMessageUseCase.kt
+++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/PersistMessageUseCase.kt
@@ -126,6 +126,7 @@ internal class PersistMessageUseCaseImpl(
             is MessageContent.MemberChange.RemovedFromTeam -> false
             is MessageContent.TeamMemberRemoved -> false
             is MessageContent.DataTransfer -> false
+            is MessageContent.InCallEmoji -> false
         }
 
     @Suppress("ComplexMethod")
@@ -180,6 +181,7 @@ internal class PersistMessageUseCaseImpl(
             is MessageContent.LegalHold,
             is MessageContent.MemberChange.RemovedFromTeam,
             is MessageContent.TeamMemberRemoved,
-            is MessageContent.DataTransfer -> false
+            is MessageContent.DataTransfer,
+            is MessageContent.InCallEmoji -> false
         }
 }
diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapper.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapper.kt
index 31013c6e633..49f5dc765a2 100644
--- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapper.kt
+++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapper.kt
@@ -45,6 +45,8 @@ import com.wire.kalium.protobuf.messages.DataTransfer
 import com.wire.kalium.protobuf.messages.Ephemeral
 import com.wire.kalium.protobuf.messages.External
 import com.wire.kalium.protobuf.messages.GenericMessage
+import com.wire.kalium.protobuf.messages.GenericMessage.UnknownStrategy
+import com.wire.kalium.protobuf.messages.InCallEmoji
 import com.wire.kalium.protobuf.messages.Knock
 import com.wire.kalium.protobuf.messages.LastRead
 import com.wire.kalium.protobuf.messages.LegalHoldStatus
@@ -57,7 +59,6 @@ import com.wire.kalium.protobuf.messages.Quote
 import com.wire.kalium.protobuf.messages.Reaction
 import com.wire.kalium.protobuf.messages.Text
 import com.wire.kalium.protobuf.messages.TrackingIdentifier
-import com.wire.kalium.protobuf.messages.UnknownStrategy
 import kotlinx.datetime.Instant
 import pbandk.ByteArr
 
@@ -146,6 +147,7 @@ class ProtoContentMapperImpl(
             is MessageContent.Location -> packLocation(readableContent, expectsReadConfirmation, legalHoldStatus)
 
             is MessageContent.DataTransfer -> packDataTransfer(readableContent)
+            is MessageContent.InCallEmoji -> packInCallEmoji(readableContent)
         }
     }
 
@@ -266,7 +268,8 @@ class ProtoContentMapperImpl(
             is MessageContent.ButtonAction,
             is MessageContent.ButtonActionConfirmation,
             is MessageContent.TextEdited,
-            is MessageContent.DataTransfer -> throw IllegalArgumentException(
+            is MessageContent.DataTransfer,
+            is MessageContent.InCallEmoji -> throw IllegalArgumentException(
                 "Unexpected message content type: ${readableContent.getType()}"
             )
         }
@@ -377,6 +380,8 @@ class ProtoContentMapperImpl(
                 MessageContent.Ignored
             }
 
+            is GenericMessage.Content.InCallEmoji -> unpackInCallEmoji(protoContent)
+
             null -> {
                 kaliumLogger.w(
                     "Null content when parsing protobuf. Message UUID = ${genericMessage.messageId.obfuscateId()}" +
@@ -390,6 +395,8 @@ class ProtoContentMapperImpl(
                     null -> MessageContent.Ignored
                 }
             }
+
+            is GenericMessage.Content.InCallHandRaise -> MessageContent.Ignored
         }
         return readableContent
     }
@@ -752,6 +759,29 @@ class ProtoContentMapperImpl(
         )
     }
 
+    private fun unpackInCallEmoji(protoContent: GenericMessage.Content.InCallEmoji): MessageContent.InCallEmoji {
+        return MessageContent.InCallEmoji(
+            // Map of emoji to senderId
+            emojis = protoContent.value.emojis
+                .mapNotNull {
+                    val key = it.key ?: return@mapNotNull null
+                    val value = it.value ?: return@mapNotNull null
+                    key to value
+                }
+                .associateBy({ it.first }, { it.second })
+        )
+    }
+
+    private fun packInCallEmoji(content: MessageContent.InCallEmoji): GenericMessage.Content.InCallEmoji {
+        return GenericMessage.Content.InCallEmoji(
+            inCallEmoji = InCallEmoji(
+                emojis = content.emojis.map { entry ->
+                   InCallEmoji.EmojisEntry(key = entry.key, value = entry.value)
+                }
+            )
+        )
+    }
+
     private fun extractConversationId(
         qualifiedConversationID: QualifiedConversationId?,
         unqualifiedConversationID: String
diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt
index 438fbc5eda3..89f6f418f9e 100644
--- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt
+++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt
@@ -40,6 +40,8 @@ import com.wire.kalium.logic.data.asset.KaliumFileSystem
 import com.wire.kalium.logic.data.asset.KaliumFileSystemImpl
 import com.wire.kalium.logic.data.call.CallDataSource
 import com.wire.kalium.logic.data.call.CallRepository
+import com.wire.kalium.logic.data.call.InCallReactionsDataSource
+import com.wire.kalium.logic.data.call.InCallReactionsRepository
 import com.wire.kalium.logic.data.call.VideoStateChecker
 import com.wire.kalium.logic.data.call.VideoStateCheckerImpl
 import com.wire.kalium.logic.data.call.mapper.CallMapper
@@ -1387,7 +1389,8 @@ class UserSessionScope internal constructor(
             receiptMessageHandler,
             buttonActionConfirmationHandler,
             dataTransferEventHandler,
-            userId
+            inCallReactionsRepository,
+            userId,
         )
 
     private val staleEpochVerifier: StaleEpochVerifier
@@ -2066,7 +2069,8 @@ class UserSessionScope internal constructor(
             userConfigRepository = userConfigRepository,
             getCallConversationType = getCallConversationType,
             conversationClientsInCallUpdater = conversationClientsInCallUpdater,
-            kaliumConfigs = kaliumConfigs
+            kaliumConfigs = kaliumConfigs,
+            inCallReactionsRepository = inCallReactionsRepository,
         )
 
     val connection: ConnectionScope
@@ -2165,6 +2169,8 @@ class UserSessionScope internal constructor(
         )
     }
 
+    private val inCallReactionsRepository: InCallReactionsRepository = InCallReactionsDataSource(userRepository)
+
     /**
      * This will start subscribers of observable work per user session, as long as the user is logged in.
      * When the user logs out, this work will be canceled.
diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/CallsScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/CallsScope.kt
index a67589af772..9066eccd245 100644
--- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/CallsScope.kt
+++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/CallsScope.kt
@@ -22,6 +22,7 @@ import com.wire.kalium.logic.configuration.UserConfigRepository
 import com.wire.kalium.logic.data.call.CallRepository
 import com.wire.kalium.logic.data.call.CallingParticipantsOrder
 import com.wire.kalium.logic.data.call.CallingParticipantsOrderImpl
+import com.wire.kalium.logic.data.call.InCallReactionsRepository
 import com.wire.kalium.logic.data.call.ParticipantsFilterImpl
 import com.wire.kalium.logic.data.call.ParticipantsOrderByNameImpl
 import com.wire.kalium.logic.data.conversation.ConversationRepository
@@ -43,8 +44,6 @@ import com.wire.kalium.logic.feature.call.usecase.GetAllCallsWithSortedParticipa
 import com.wire.kalium.logic.feature.call.usecase.GetCallConversationTypeProvider
 import com.wire.kalium.logic.feature.call.usecase.GetIncomingCallsUseCase
 import com.wire.kalium.logic.feature.call.usecase.GetIncomingCallsUseCaseImpl
-import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnabledUseCase
-import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnabledUseCaseImpl
 import com.wire.kalium.logic.feature.call.usecase.IsCallRunningUseCase
 import com.wire.kalium.logic.feature.call.usecase.IsEligibleToStartCallUseCase
 import com.wire.kalium.logic.feature.call.usecase.IsEligibleToStartCallUseCaseImpl
@@ -53,12 +52,16 @@ import com.wire.kalium.logic.feature.call.usecase.IsLastCallClosedUseCaseImpl
 import com.wire.kalium.logic.feature.call.usecase.MuteCallUseCase
 import com.wire.kalium.logic.feature.call.usecase.MuteCallUseCaseImpl
 import com.wire.kalium.logic.feature.call.usecase.ObserveAskCallFeedbackUseCase
+import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnabledUseCase
+import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnabledUseCaseImpl
 import com.wire.kalium.logic.feature.call.usecase.ObserveEndCallDueToConversationDegradationUseCase
 import com.wire.kalium.logic.feature.call.usecase.ObserveEndCallDueToConversationDegradationUseCaseImpl
 import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallWithSortedParticipantsUseCase
 import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallWithSortedParticipantsUseCaseImpl
 import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase
 import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCaseImpl
+import com.wire.kalium.logic.feature.call.usecase.ObserveInCallReactionsUseCase
+import com.wire.kalium.logic.feature.call.usecase.ObserveInCallReactionsUseCaseImpl
 import com.wire.kalium.logic.feature.call.usecase.ObserveOngoingCallsUseCase
 import com.wire.kalium.logic.feature.call.usecase.ObserveOngoingCallsUseCaseImpl
 import com.wire.kalium.logic.feature.call.usecase.ObserveOutgoingCallUseCase
@@ -103,6 +106,7 @@ class CallsScope internal constructor(
     private val conversationClientsInCallUpdater: ConversationClientsInCallUpdater,
     private val getCallConversationType: GetCallConversationTypeProvider,
     private val kaliumConfigs: KaliumConfigs,
+    private val inCallReactionsRepository: InCallReactionsRepository,
     internal val dispatcher: KaliumDispatcher = KaliumDispatcherImpl
 ) {
 
@@ -239,4 +243,7 @@ class CallsScope internal constructor(
         get() = ObserveRecentlyEndedCallMetadataUseCaseImpl(
             callRepository = callRepository
         )
+
+    val observeInCallReactions: ObserveInCallReactionsUseCase
+        get() = ObserveInCallReactionsUseCaseImpl(inCallReactionsRepository)
 }
diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/usecase/ObserveInCallReactionsUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/usecase/ObserveInCallReactionsUseCase.kt
new file mode 100644
index 00000000000..93e737acd9c
--- /dev/null
+++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/usecase/ObserveInCallReactionsUseCase.kt
@@ -0,0 +1,38 @@
+/*
+ * Wire
+ * Copyright (C) 2024 Wire Swiss GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/.
+ */
+package com.wire.kalium.logic.feature.call.usecase
+
+import com.wire.kalium.logic.data.call.InCallReactionMessage
+import com.wire.kalium.logic.data.call.InCallReactionsRepository
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Observe incoming in-call reactions
+ */
+interface ObserveInCallReactionsUseCase {
+    operator fun invoke(): Flow<InCallReactionMessage>
+}
+
+internal class ObserveInCallReactionsUseCaseImpl(
+    private val inCallReactionsRepository: InCallReactionsRepository,
+) : ObserveInCallReactionsUseCase {
+
+    override fun invoke(): Flow<InCallReactionMessage> {
+        return inCallReactionsRepository.observeInCallReactions()
+    }
+}
diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/incallreaction/SendInCallReactionUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/incallreaction/SendInCallReactionUseCase.kt
new file mode 100644
index 00000000000..8b57a9735ef
--- /dev/null
+++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/incallreaction/SendInCallReactionUseCase.kt
@@ -0,0 +1,70 @@
+/*
+ * Wire
+ * Copyright (C) 2024 Wire Swiss GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/.
+ */
+package com.wire.kalium.logic.feature.incallreaction
+
+import com.benasher44.uuid.uuid4
+import com.wire.kalium.logic.CoreFailure
+import com.wire.kalium.logic.data.id.ConversationId
+import com.wire.kalium.logic.data.id.CurrentClientIdProvider
+import com.wire.kalium.logic.data.id.QualifiedID
+import com.wire.kalium.logic.data.message.Message
+import com.wire.kalium.logic.data.message.MessageContent
+import com.wire.kalium.logic.feature.message.MessageSender
+import com.wire.kalium.logic.functional.Either
+import com.wire.kalium.logic.functional.flatMap
+import com.wire.kalium.util.KaliumDispatcher
+import com.wire.kalium.util.KaliumDispatcherImpl
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+import kotlinx.datetime.Clock
+
+/**
+ * Sends in-call reaction to the call with the conversationId
+ */
+class SendInCallReactionUseCase(
+    private val selfUserId: QualifiedID,
+    private val provideClientId: CurrentClientIdProvider,
+    private val messageSender: MessageSender,
+    private val dispatchers: KaliumDispatcher = KaliumDispatcherImpl,
+    private val scope: CoroutineScope
+) {
+
+    suspend operator fun invoke(conversationId: ConversationId, reaction: String): Either<CoreFailure, Unit> =
+        scope.async(dispatchers.io) {
+
+            val generatedMessageUuid = uuid4().toString()
+
+            provideClientId().flatMap { clientId ->
+                val message = Message.Signaling(
+                    id = generatedMessageUuid,
+                    content = MessageContent.InCallEmoji(
+                        emojis = mapOf(reaction to 1)
+                    ),
+                    conversationId = conversationId,
+                    date = Clock.System.now(),
+                    senderUserId = selfUserId,
+                    senderClientId = clientId,
+                    status = Message.Status.Pending,
+                    isSelfMessage = true,
+                    expirationData = null,
+                )
+
+                messageSender.sendMessage(message)
+            }
+        }.await()
+}
diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageScope.kt
index bb49c7fda27..b2041cbef35 100644
--- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageScope.kt
+++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MessageScope.kt
@@ -62,6 +62,7 @@ import com.wire.kalium.logic.feature.asset.UpdateAssetMessageTransferStatusUseCa
 import com.wire.kalium.logic.feature.asset.UpdateAssetMessageTransferStatusUseCaseImpl
 import com.wire.kalium.logic.feature.asset.ValidateAssetFileTypeUseCase
 import com.wire.kalium.logic.feature.asset.ValidateAssetFileTypeUseCaseImpl
+import com.wire.kalium.logic.feature.incallreaction.SendInCallReactionUseCase
 import com.wire.kalium.logic.feature.message.composite.SendButtonActionConfirmationMessageUseCase
 import com.wire.kalium.logic.feature.message.composite.SendButtonActionMessageUseCase
 import com.wire.kalium.logic.feature.message.composite.SendButtonMessageUseCase
@@ -453,4 +454,13 @@ class MessageScope internal constructor(
 
     val removeMessageDraftUseCase: RemoveMessageDraftUseCase
         get() = RemoveMessageDraftUseCaseImpl(messageDraftRepository)
+
+    val sendInCallReactionUseCase: SendInCallReactionUseCase
+        get() = SendInCallReactionUseCase(
+            selfUserId = selfUserId,
+            provideClientId = currentClientIdProvider,
+            messageSender = messageSender,
+            dispatchers = dispatcher,
+            scope = scope,
+        )
 }
diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/PersistMigratedMessagesUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/PersistMigratedMessagesUseCase.kt
index ce4b80f2ee5..fd5b3509a21 100644
--- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/PersistMigratedMessagesUseCase.kt
+++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/PersistMigratedMessagesUseCase.kt
@@ -212,5 +212,6 @@ internal class PersistMigratedMessagesUseCaseImpl(
         is MessageContent.ButtonActionConfirmation -> MessageEntity.Visibility.HIDDEN
         is MessageContent.Location -> MessageEntity.Visibility.VISIBLE
         is MessageContent.DataTransfer -> MessageEntity.Visibility.HIDDEN
+        is MessageContent.InCallEmoji -> MessageEntity.Visibility.HIDDEN
     }
 }
diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandler.kt
index 08f6fc47213..580e3763227 100644
--- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandler.kt
+++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandler.kt
@@ -19,6 +19,7 @@
 package com.wire.kalium.logic.sync.receiver.conversation.message
 
 import com.wire.kalium.logger.KaliumLogger.Companion.ApplicationFlow
+import com.wire.kalium.logic.data.call.InCallReactionsRepository
 import com.wire.kalium.logic.data.conversation.ClientId
 import com.wire.kalium.logic.data.id.ConversationId
 import com.wire.kalium.logic.data.message.AssetContent
@@ -90,7 +91,8 @@ internal class ApplicationMessageHandlerImpl(
     private val receiptMessageHandler: ReceiptMessageHandler,
     private val buttonActionConfirmationHandler: ButtonActionConfirmationHandler,
     private val dataTransferEventHandler: DataTransferEventHandler,
-    private val selfUserId: UserId
+    private val inCallReactionsRepository: InCallReactionsRepository,
+    private val selfUserId: UserId,
 ) : ApplicationMessageHandler {
 
     private val logger by lazy { kaliumLogger.withFeatureId(ApplicationFlow.EVENT_RECEIVER) }
@@ -106,18 +108,12 @@ internal class ApplicationMessageHandlerImpl(
         when (val protoContent = content.messageContent) {
             is MessageContent.Regular -> {
                 val visibility = when (protoContent) {
-                    is MessageContent.DeleteMessage -> Message.Visibility.HIDDEN
-                    is MessageContent.TextEdited -> Message.Visibility.HIDDEN
-                    is MessageContent.DeleteForMe -> Message.Visibility.HIDDEN
                     is MessageContent.Unknown -> if (protoContent.hidden) Message.Visibility.HIDDEN else Message.Visibility.VISIBLE
                     is MessageContent.Text -> Message.Visibility.VISIBLE
-                    is MessageContent.Calling -> Message.Visibility.VISIBLE
                     is MessageContent.Asset -> Message.Visibility.VISIBLE
                     is MessageContent.Knock -> Message.Visibility.VISIBLE
                     is MessageContent.RestrictedAsset -> Message.Visibility.VISIBLE
                     is MessageContent.FailedDecryption -> Message.Visibility.VISIBLE
-                    is MessageContent.LastRead -> Message.Visibility.HIDDEN
-                    is MessageContent.Cleared -> Message.Visibility.HIDDEN
                     is MessageContent.Composite -> Message.Visibility.VISIBLE
                     is MessageContent.Location -> Message.Visibility.VISIBLE
                 }
@@ -222,6 +218,10 @@ internal class ApplicationMessageHandlerImpl(
             )
 
             is MessageContent.DataTransfer -> dataTransferEventHandler.handle(signaling, content)
+            is MessageContent.InCallEmoji -> inCallReactionsRepository.addInCallReaction(
+                emojis = content.emojis.keys,
+                senderUserId = signaling.senderUserId
+            )
         }
     }
 
diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/call/InCallReactionsRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/call/InCallReactionsRepositoryTest.kt
new file mode 100644
index 00000000000..7c5afc98736
--- /dev/null
+++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/call/InCallReactionsRepositoryTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Wire
+ * Copyright (C) 2024 Wire Swiss GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/.
+ */
+package com.wire.kalium.logic.data.call
+
+import app.cash.turbine.test
+import com.wire.kalium.logic.CoreFailure
+import com.wire.kalium.logic.data.id.QualifiedID
+import com.wire.kalium.logic.data.user.ConnectionState
+import com.wire.kalium.logic.data.user.OtherUser
+import com.wire.kalium.logic.data.user.UserAvailabilityStatus
+import com.wire.kalium.logic.data.user.UserRepository
+import com.wire.kalium.logic.data.user.type.UserType
+import com.wire.kalium.logic.functional.Either
+import com.wire.kalium.logic.test_util.TestKaliumDispatcher
+import io.mockative.Mock
+import io.mockative.any
+import io.mockative.coEvery
+import io.mockative.mock
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class InCallReactionsRepositoryTest {
+
+    val sendingUserId: QualifiedID = QualifiedID("userId", "domain")
+
+    @Test
+    fun whenNewReactionIsAdded_thenRepositoryEmitsNewReactionMessage() = runBlocking {
+
+        // given
+        val (_, repository) = Arrangement()
+            .withUserAvailable()
+            .arrange()
+
+        repository.observeInCallReactions().test {
+
+            // when
+            repository.addInCallReaction(setOf("1"), sendingUserId)
+
+            // then
+            assertEquals(InCallReactionMessage(setOf("1"), sendingUserId, "TestUserName"), awaitItem())
+        }
+    }
+
+    @Test
+    fun whenUserIsNotFound_thenReactionRepositoryDoesNotEmitNewReactionMessage() = runTest(TestKaliumDispatcher.default) {
+
+        // given
+        val (_, repository) = Arrangement()
+            .withUserNotAvailable()
+            .arrange()
+
+        repository.observeInCallReactions().test {
+
+            // when
+            repository.addInCallReaction(setOf("1"), sendingUserId)
+
+            // then
+            expectNoEvents()
+        }
+    }
+
+    private class Arrangement {
+
+        val user: OtherUser = OtherUser(
+            id = QualifiedID("userId", "domain"),
+            name = "TestUserName",
+            handle = null,
+            email = null,
+            phone = null,
+            accentId = 0,
+            teamId = null,
+            connectionStatus = ConnectionState.NOT_CONNECTED,
+            previewPicture = null,
+            completePicture = null,
+            availabilityStatus = UserAvailabilityStatus.AVAILABLE,
+            expiresAt = null,
+            supportedProtocols = null,
+            userType = UserType.INTERNAL,
+            botService = null,
+            deleted = false,
+            defederated = false,
+            isProteusVerified = false,
+        )
+
+        @Mock
+        val userRepository: UserRepository = mock(UserRepository::class)
+
+        suspend fun withUserAvailable() = apply {
+            coEvery {
+                userRepository.userById(any())
+            }.returns(Either.Right(user))
+        }
+
+        suspend fun withUserNotAvailable() = apply {
+            coEvery {
+                userRepository.userById(any())
+            }.returns(Either.Left(CoreFailure.Unknown(IllegalStateException("Test user not found"))))
+        }
+
+        fun arrange() = this to InCallReactionsDataSource(
+            userRepository = userRepository
+        )
+    }
+
+}
diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapperTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapperTest.kt
index 9bdd5147807..16aa6bf4b26 100644
--- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapperTest.kt
+++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/message/ProtoContentMapperTest.kt
@@ -29,9 +29,9 @@ import com.wire.kalium.protobuf.encodeToByteArray
 import com.wire.kalium.protobuf.messages.Asset
 import com.wire.kalium.protobuf.messages.Confirmation
 import com.wire.kalium.protobuf.messages.GenericMessage
+import com.wire.kalium.protobuf.messages.GenericMessage.UnknownStrategy
 import com.wire.kalium.protobuf.messages.MessageEdit
 import com.wire.kalium.protobuf.messages.Text
-import com.wire.kalium.protobuf.messages.UnknownStrategy
 import io.ktor.utils.io.core.toByteArray
 import kotlin.test.BeforeTest
 import kotlin.test.Test
@@ -516,6 +516,24 @@ class ProtoContentMapperTest {
         assertEquals(decoded, protoContent)
     }
 
+    @Test
+    fun givenInCallEmojiContent_whenMappingToProtoDataAndBack_thenTheContentsShouldMatchTheOriginal() {
+        val messageContent = MessageContent.InCallEmoji(
+            emojis = mapOf("emoji" to 999)
+        )
+        val protoContent = ProtoContent.Readable(
+            TEST_MESSAGE_UUID,
+            messageContent,
+            false,
+            legalHoldStatus = Conversation.LegalHoldStatus.UNKNOWN
+        )
+
+        val encoded = protoContentMapper.encodeToProtobuf(protoContent)
+        val decoded = protoContentMapper.decodeFromProtobuf(encoded)
+
+        assertEquals(decoded, protoContent)
+    }
+
     private companion object {
         const val TEST_MESSAGE_UUID = "testUuid"
         val TEST_CONVERSATION_ID = TestConversation.ID
diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/incallreaction/SendInCallReactionUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/incallreaction/SendInCallReactionUseCaseTest.kt
new file mode 100644
index 00000000000..92c6fa085f9
--- /dev/null
+++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/incallreaction/SendInCallReactionUseCaseTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Wire
+ * Copyright (C) 2024 Wire Swiss GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/.
+ */
+package com.wire.kalium.logic.feature.incallreaction
+
+import com.wire.kalium.logic.NetworkFailure
+import com.wire.kalium.logic.data.conversation.ClientId
+import com.wire.kalium.logic.data.id.ConversationId
+import com.wire.kalium.logic.data.id.CurrentClientIdProvider
+import com.wire.kalium.logic.feature.message.MessageSender
+import com.wire.kalium.logic.framework.TestClient
+import com.wire.kalium.logic.framework.TestUser
+import com.wire.kalium.logic.functional.Either
+import com.wire.kalium.logic.test_util.testKaliumDispatcher
+import com.wire.kalium.logic.util.shouldFail
+import com.wire.kalium.logic.util.shouldSucceed
+import io.mockative.Mock
+import io.mockative.any
+import io.mockative.coEvery
+import io.mockative.coVerify
+import io.mockative.mock
+import io.mockative.once
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.test.runTest
+import kotlin.test.Test
+
+class SendInCallReactionUseCaseTest {
+
+    @Mock
+    val messageSender = mock(MessageSender::class)
+
+    @Test
+    fun `given established connection when sending should return success`() = runTest {
+
+        // Given
+        val (arrangement, sendReactionUseCase) = Arrangement(this)
+            .withCurrentClientProviderSuccess()
+            .withSendMessageSuccess()
+            .arrange()
+
+        // When
+        val result = sendReactionUseCase(ConversationId("id", "domain"), "reaction")
+
+        // Then
+        result.shouldSucceed()
+
+        coVerify {
+            arrangement.messageSender.sendMessage(any(), any())
+        }.wasInvoked(once)
+    }
+
+    @Test
+    fun `given no connection when sending should fail`() = runTest {
+
+        // Given
+        val (arrangement, sendReactionUseCase) = Arrangement(this)
+            .withCurrentClientProviderSuccess()
+            .withSendMessageFailure()
+            .arrange()
+
+        // When
+        val result = sendReactionUseCase(ConversationId("id", "domain"), "reaction")
+
+        // Then
+        result.shouldFail()
+
+        coVerify {
+            arrangement.messageSender.sendMessage(any(), any())
+        }.wasInvoked(once)
+    }
+
+    private class Arrangement(private val coroutineScope: CoroutineScope) {
+        @Mock
+        val messageSender = mock(MessageSender::class)
+
+        @Mock
+        val currentClientIdProvider = mock(CurrentClientIdProvider::class)
+
+        suspend fun withSendMessageSuccess() = apply {
+            coEvery {
+                messageSender.sendMessage(any(), any())
+            }.returns(Either.Right(Unit))
+        }
+
+        suspend fun withSendMessageFailure() = apply {
+            coEvery {
+                messageSender.sendMessage(any(), any())
+            }.returns(Either.Left(NetworkFailure.NoNetworkConnection(null)))
+        }
+
+        suspend fun withCurrentClientProviderSuccess(clientId: ClientId = TestClient.CLIENT_ID) = apply {
+            coEvery {
+                currentClientIdProvider.invoke()
+            }.returns(Either.Right(clientId))
+        }
+
+        fun arrange() = this to SendInCallReactionUseCase(
+            selfUserId = TestUser.SELF.id,
+            provideClientId = currentClientIdProvider,
+            messageSender = messageSender,
+            dispatchers = coroutineScope.testKaliumDispatcher,
+            scope = coroutineScope,
+        )
+    }
+}
diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandlerTest.kt
index 6aaa4706e59..5ac65ce589d 100644
--- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandlerTest.kt
+++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandlerTest.kt
@@ -22,6 +22,7 @@ import com.wire.kalium.logic.CoreFailure
 import com.wire.kalium.logic.StorageFailure
 import com.wire.kalium.logic.configuration.FileSharingStatus
 import com.wire.kalium.logic.configuration.UserConfigRepository
+import com.wire.kalium.logic.data.call.InCallReactionsRepository
 import com.wire.kalium.logic.data.conversation.Conversation
 import com.wire.kalium.logic.data.message.AssetContent
 import com.wire.kalium.logic.data.message.MessageContent
@@ -172,6 +173,41 @@ class ApplicationMessageHandlerTest {
         }.wasInvoked(exactly = once)
     }
 
+    @Test
+    fun givenInCallReactionReceived_whenHandling_thenCorrectHandlerIsInvoked() = runTest {
+        // given
+        val messageId = "messageId"
+        val inCallReactionContent = MessageContent.InCallEmoji(
+            emojis = mapOf("1" to 1)
+        )
+        val protoContent = ProtoContent.Readable(
+            messageId,
+            inCallReactionContent,
+            false,
+            Conversation.LegalHoldStatus.DISABLED
+        )
+
+        val (arrangement, messageHandler) = Arrangement()
+            .arrange()
+
+        val encodedEncryptedContent = Base64.encodeToBase64("Hello".encodeToByteArray())
+        val messageEvent = TestEvent.newMessageEvent(encodedEncryptedContent.decodeToString())
+
+        // when
+        messageHandler.handleContent(
+            messageEvent.conversationId,
+            messageEvent.messageInstant,
+            messageEvent.senderUserId,
+            messageEvent.senderClientId,
+            protoContent
+        )
+
+        // then
+        coVerify {
+            arrangement.inCallReactionsRepository.addInCallReaction(setOf("1"), messageEvent.senderUserId)
+        }.wasInvoked(exactly = once)
+    }
+
     private class Arrangement {
         @Mock
         val persistMessage = mock(PersistMessageUseCase::class)
@@ -215,6 +251,9 @@ class ApplicationMessageHandlerTest {
         @Mock
         val buttonActionConfirmationHandler = mock(ButtonActionConfirmationHandler::class)
 
+        @Mock
+        val inCallReactionsRepository = mock(InCallReactionsRepository::class)
+
         @Mock
         val dataTransferEventHandler = mock(DataTransferEventHandler::class)
 
@@ -234,6 +273,7 @@ class ApplicationMessageHandlerTest {
             receiptMessageHandler,
             buttonActionConfirmationHandler,
             dataTransferEventHandler,
+            inCallReactionsRepository,
             TestUser.SELF.id
         )
 
diff --git a/protobuf-codegen/src/main/proto/messages.proto b/protobuf-codegen/src/main/proto/messages.proto
index 9a08423e27f..ea5b30656e9 100644
--- a/protobuf-codegen/src/main/proto/messages.proto
+++ b/protobuf-codegen/src/main/proto/messages.proto
@@ -45,8 +45,19 @@ message GenericMessage {
         ButtonAction buttonAction = 21;
         ButtonActionConfirmation buttonActionConfirmation = 22;
         DataTransfer dataTransfer = 23; // client-side synchronization across devices of the same user
+        InCallEmoji inCallEmoji = 24;
+        // UnknownStrategy unknownStrategy = 25; -- Defined outside the oneof
+        // Next field should be 26 ↓
+        InCallHandRaise inCallHandRaise = 26;
+    }
+    optional UnknownStrategy unknownStrategy = 25 [default = IGNORE];
+
+    // See internal RFC: "2024-07-18 RFC Improve future-proofing for new OTR message types"
+    enum UnknownStrategy {
+        IGNORE = 0;                 // Ignore the message completely. Trash. Bye
+        DISCARD_AND_WARN = 1;       // Warn the user, but discard the message, as it won't be helpful in the future.
+        WARN_USER_ALLOW_RETRY = 2;  // Warn the user. Client has freedom to store it and retry in the future.
     }
-    optional UnknownStrategy unknownStrategy = 24 [default = IGNORE];
 }
 
 message QualifiedUserId {
@@ -332,6 +343,14 @@ message Reaction {
     optional LegalHoldStatus legal_hold_status = 3 [default = UNKNOWN]; // whether this message was sent to legal hold
 }
 
+message InCallEmoji {
+    map<string, int32> emojis = 1;
+}
+
+message InCallHandRaise {
+    required bool is_hand_up = 1; // true if the hand is raised, false if lowered
+}
+
 message Calling {
     required string content = 1;
     optional QualifiedConversationId qualified_conversation_id = 2;
@@ -362,9 +381,3 @@ enum LegalHoldStatus {
     DISABLED = 1;
     ENABLED = 2;
 }
-
-enum UnknownStrategy {
-    IGNORE = 0;                 // Ignore the message completely.
-    DISCARD_AND_WARN = 1;       // Warn the user, but discard the message, as it may not be helpful in the future
-    WARN_USER_ALLOW_RETRY = 2;  // Warn the user. Client has freedom to store it and retry in the future.
-}