/// <summary>
        ///
        /// </summary>
        /// <param name="metadata"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        /// <exception cref="ProtocolInvalidMessageException"></exception>
        private static SignalServiceTypingMessage CreateTypingMessage(SignalServiceMetadata metadata, TypingMessage content)
        {
            SignalServiceTypingMessage.Action action;

            if (content.Action == push.TypingMessage.Types.Action.Started)
            {
                action = SignalServiceTypingMessage.Action.STARTED;
            }
            else if (content.Action == push.TypingMessage.Types.Action.Stopped)
            {
                action = SignalServiceTypingMessage.Action.STOPPED;
            }
            else
            {
                action = SignalServiceTypingMessage.Action.UNKNOWN;
            }

            if (content.HasTimestamp && (long)content.Timestamp != metadata.Timestamp)
            {
                throw new ProtocolInvalidMessageException(new InvalidMessageException($"Timestamps don't match: {content.Timestamp} vs {metadata.Timestamp}"),
                                                          metadata.Sender.GetIdentifier(),
                                                          metadata.SenderDevice);
            }

            return(new SignalServiceTypingMessage(action, (long)content.Timestamp,
                                                  content.HasGroupId ? content.GroupId.ToByteArray() : null));
        }
        private static SignalServiceDataMessage CreateSignalServiceMessage(SignalServiceMetadata metadata, DataMessage content)
        {
            SignalServiceGroup?            groupInfo   = CreateGroupV1Info(content);
            List <SignalServiceAttachment> attachments = new List <SignalServiceAttachment>();
            bool endSession       = ((content.Flags & (uint)DataMessage.Types.Flags.EndSession) != 0);
            bool expirationUpdate = ((content.Flags & (uint)DataMessage.Types.Flags.ExpirationTimerUpdate) != 0);
            bool profileKeyUpdate = ((content.Flags & (uint)DataMessage.Types.Flags.ProfileKeyUpdate) != 0);

            SignalServiceDataMessage.SignalServiceQuote?quote = CreateQuote(content);
            List <SharedContact>?sharedContacts = CreateSharedContacts(content);
            List <SignalServiceDataMessage.SignalServicePreview>?previews = CreatePreviews(content);

            SignalServiceDataMessage.SignalServiceSticker?sticker = CreateSticker(content);

            if (content.RequiredProtocolVersion > (int)DataMessage.Types.ProtocolVersion.Current)
            {
                throw new UnsupportedDataMessageException((int)DataMessage.Types.ProtocolVersion.Current,
                                                          (int)content.RequiredProtocolVersion,
                                                          metadata.Sender.GetIdentifier(),
                                                          metadata.SenderDevice,
                                                          groupInfo);
            }

            foreach (AttachmentPointer pointer in content.Attachments)
            {
                attachments.Add(CreateAttachmentPointer(pointer));
            }

            if (content.HasTimestamp && (long)content.Timestamp != metadata.Timestamp)
            {
                throw new ProtocolInvalidMessageException(new InvalidMessageException("Timestamps don't match: " + content.Timestamp + " vs " + metadata.Timestamp),
                                                          metadata.Sender.GetIdentifier(),
                                                          metadata.SenderDevice);
            }

            return(new SignalServiceDataMessage(metadata.Timestamp,
                                                groupInfo,
                                                attachments,
                                                content.Body,
                                                endSession,
                                                (int)content.ExpireTimer,
                                                expirationUpdate,
                                                content.HasProfileKey ? content.ProfileKey.ToByteArray() : null,
                                                profileKeyUpdate,
                                                quote,
                                                sharedContacts,
                                                previews,
                                                sticker,
                                                content.IsViewOnce));
        }
        private static SignalServiceReceiptMessage CreateReceiptMessage(SignalServiceMetadata metadata, ReceiptMessage content)
        {
            SignalServiceReceiptMessage.Type type;

            if (content.Type == ReceiptMessage.Types.Type.Delivery)
            {
                type = SignalServiceReceiptMessage.Type.DELIVERY;
            }
            else if (content.Type == ReceiptMessage.Types.Type.Read)
            {
                type = SignalServiceReceiptMessage.Type.READ;
            }
            else
            {
                type = SignalServiceReceiptMessage.Type.UNKNOWN;
            }

            return(new SignalServiceReceiptMessage()
            {
                ReceiptType = type,
                Timestamps = content.Timestamp.ToList(),
                When = metadata.Timestamp
            });
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="metadata"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        /// <exception cref="ProtocolInvalidMessageException"></exception>
        /// <exception cref="ProtocolInvalidKeyException"></exception>
        private static SignalServiceSyncMessage CreateSynchronizeMessage(SignalServiceMetadata metadata, SyncMessage content)
        {
            if (content.Sent != null)
            {
                var unidentifiedStatuses             = new Dictionary <SignalServiceAddress, bool>();
                SyncMessage.Types.Sent   sentContent = content.Sent;
                SignalServiceDataMessage dataMessage = CreateSignalServiceMessage(metadata, sentContent.Message);
                SignalServiceAddress?    address     = SignalServiceAddress.IsValidAddress(sentContent.DestinationUuid, sentContent.Destination) ?
                                                       new SignalServiceAddress(UuidUtil.ParseOrNull(sentContent.DestinationUuid), sentContent.Destination) :
                                                       null;

                if (address == null && dataMessage.Group == null)
                {
                    throw new ProtocolInvalidMessageException(new InvalidMessageException("SyncMessage missing both destination and group ID!"), null, 0);
                }

                foreach (var status in sentContent.UnidentifiedStatus)
                {
                    if (SignalServiceAddress.IsValidAddress(status.DestinationUuid, status.Destination))
                    {
                        SignalServiceAddress recipient = new SignalServiceAddress(UuidUtil.ParseOrNull(status.DestinationUuid), status.Destination);
                        unidentifiedStatuses.Add(recipient, status.Unidentified);
                    }
                    else
                    {
                        logger.LogWarning("Encountered an invalid UnidentifiedDeliveryStatus in a SentTranscript! Ignoring.");
                    }
                }

                return(SignalServiceSyncMessage.ForSentTranscript(new SentTranscriptMessage(address !,
                                                                                            (long)sentContent.Timestamp,
                                                                                            CreateSignalServiceMessage(metadata, sentContent.Message),
                                                                                            (long)sentContent.ExpirationStartTimestamp,
                                                                                            unidentifiedStatuses,
                                                                                            sentContent.IsRecipientUpdate)));
            }

            if (content.Request != null)
            {
                return(SignalServiceSyncMessage.ForRequest(new RequestMessage(content.Request)));
            }

            if (content.Read.Count > 0)
            {
                List <ReadMessage> readMessages = new List <ReadMessage>();

                foreach (SyncMessage.Types.Read read in content.Read)
                {
                    if (SignalServiceAddress.IsValidAddress(read.SenderUuid, read.Sender))
                    {
                        SignalServiceAddress address = new SignalServiceAddress(UuidUtil.ParseOrNull(read.SenderUuid), read.Sender);
                        readMessages.Add(new ReadMessage(address, (long)read.Timestamp));
                    }
                    else
                    {
                        logger.LogWarning("Encountered an invalid ReadMessage! Ignoring.");
                    }
                }

                return(SignalServiceSyncMessage.ForRead(readMessages));
            }

            if (content.ViewOnceOpen != null)
            {
                if (SignalServiceAddress.IsValidAddress(content.ViewOnceOpen.SenderUuid, content.ViewOnceOpen.Sender))
                {
                    SignalServiceAddress address   = new SignalServiceAddress(UuidUtil.ParseOrNull(content.ViewOnceOpen.SenderUuid), content.ViewOnceOpen.Sender);
                    ViewOnceOpenMessage  timerRead = new ViewOnceOpenMessage(address, (long)content.ViewOnceOpen.Timestamp);
                    return(SignalServiceSyncMessage.ForViewOnceOpen(timerRead));
                }
                else
                {
                    throw new ProtocolInvalidMessageException(new InvalidMessageException("ViewOnceOpen message has no sender!"), null, 0);
                }
            }

            if (content.Contacts != null)
            {
                AttachmentPointer pointer = content.Contacts.Blob;
                return(SignalServiceSyncMessage.ForContacts(new ContactsMessage(CreateAttachmentPointer(pointer), content.Contacts.Complete)));
            }

            if (content.Groups != null)
            {
                AttachmentPointer pointer = content.Groups.Blob;
                return(SignalServiceSyncMessage.ForGroups(CreateAttachmentPointer(pointer)));
            }

            if (content.Verified != null)
            {
                if (SignalServiceAddress.IsValidAddress(content.Verified.DestinationUuid, content.Verified.Destination))
                {
                    try
                    {
                        Verified             verified    = content.Verified;
                        SignalServiceAddress destination = new SignalServiceAddress(UuidUtil.ParseOrNull(verified.DestinationUuid), verified.Destination);
                        IdentityKey          identityKey = new IdentityKey(verified.IdentityKey.ToByteArray(), 0);

                        VerifiedMessage.VerifiedState verifiedState;

                        if (verified.State == Verified.Types.State.Default)
                        {
                            verifiedState = VerifiedMessage.VerifiedState.Default;
                        }
                        else if (verified.State == Verified.Types.State.Verified)
                        {
                            verifiedState = VerifiedMessage.VerifiedState.Verified;
                        }
                        else if (verified.State == Verified.Types.State.Unverified)
                        {
                            verifiedState = VerifiedMessage.VerifiedState.Unverified;
                        }
                        else
                        {
                            throw new ProtocolInvalidMessageException(new InvalidMessageException($"Unknown state: {(int)verified.State}"),
                                                                      metadata.Sender.GetIdentifier(), metadata.SenderDevice);
                        }

                        return(SignalServiceSyncMessage.ForVerified(new VerifiedMessage(destination, identityKey, verifiedState, Util.CurrentTimeMillis())));
                    }
                    catch (InvalidKeyException ex)
                    {
                        throw new ProtocolInvalidKeyException(ex, metadata.Sender.GetIdentifier(), metadata.SenderDevice);
                    }
                }
                else
                {
                    throw new ProtocolInvalidMessageException(new InvalidMessageException("Verified message has no sender!"), null, 0);
                }
            }

            if (content.StickerPackOperation.Count > 0)
            {
                List <StickerPackOperationMessage> operations = new List <StickerPackOperationMessage>();

                foreach (var operation in content.StickerPackOperation)
                {
                    byte[]? packId  = operation.HasPackId ? operation.PackId.ToByteArray() : null;
                    byte[]? packKey = operation.HasPackKey ? operation.PackKey.ToByteArray() : null;
                    StickerPackOperationMessage.OperationType?type = null;

                    if (operation.HasType)
                    {
                        switch (operation.Type)
                        {
                        case SyncMessage.Types.StickerPackOperation.Types.Type.Install: type = StickerPackOperationMessage.OperationType.Install; break;

                        case SyncMessage.Types.StickerPackOperation.Types.Type.Remove: type = StickerPackOperationMessage.OperationType.Remove; break;
                        }
                    }
                    operations.Add(new StickerPackOperationMessage(packId, packKey, type));
                }

                return(SignalServiceSyncMessage.ForStickerPackOperations(operations));
            }

            if (content.Blocked != null)
            {
                List <string> numbers = content.Blocked.Numbers.ToList();
                List <string> uuids   = content.Blocked.Uuids.ToList();
                List <SignalServiceAddress> addresses = new List <SignalServiceAddress>(numbers.Count + uuids.Count);
                List <byte[]> groupIds = new List <byte[]>(content.Blocked.GroupIds.Count);

                foreach (string e164 in numbers)
                {
                    SignalServiceAddress?address = SignalServiceAddress.FromRaw(null, e164);
                    if (address != null)
                    {
                        addresses.Add(address);
                    }
                }

                foreach (string uuid in uuids)
                {
                    SignalServiceAddress?address = SignalServiceAddress.FromRaw(uuid, null);
                    if (address != null)
                    {
                        addresses.Add(address);
                    }
                }

                foreach (ByteString groupId in content.Blocked.GroupIds)
                {
                    groupIds.Add(groupId.ToByteArray());
                }

                return(SignalServiceSyncMessage.ForBlocked(new BlockedListMessage(addresses, groupIds)));
            }

            if (content.Configuration != null)
            {
                bool?readReceipts = content.Configuration.HasReadReceipts ? content.Configuration.ReadReceipts : (bool?)null;
                bool?unidentifiedDeliveryIndicators = content.Configuration.HasUnidentifiedDeliveryIndicators ? content.Configuration.UnidentifiedDeliveryIndicators : (bool?)null;
                bool?typingIndicators = content.Configuration.HasTypingIndicators ? content.Configuration.TypingIndicators : (bool?)null;
                bool?linkPreviews     = content.Configuration.HasLinkPreviews ? content.Configuration.LinkPreviews : (bool?)null;

                return(SignalServiceSyncMessage.ForConfiguration(new ConfigurationMessage(readReceipts, unidentifiedDeliveryIndicators, typingIndicators, linkPreviews)));
            }

            return(SignalServiceSyncMessage.Empty());
        }
        /// <summary>
        /// Takes internal protobuf serialization format and processes it into a <see cref="SignalServiceContent"/>.
        /// </summary>
        /// <param name="serviceContentProto"></param>
        /// <returns></returns>
        /// <exception cref="ProtocolInvalidMessageException"></exception>
        /// <exception cref="ProtocolInvalidKeyException"></exception>
        public static SignalServiceContent?CreateFromProto(SignalServiceContentProto serviceContentProto)
        {
            SignalServiceMetadata metadata     = SignalServiceMetadataProtobufSerializer.FromProtobuf(serviceContentProto.Metadata);
            SignalServiceAddress  localAddress = SignalServiceAddressProtobufSerializer.FromProtobuf(serviceContentProto.LocalAddress);

            if (serviceContentProto.DataCase == SignalServiceContentProto.DataOneofCase.LegacyDataMessage)
            {
                DataMessage message = serviceContentProto.LegacyDataMessage;

                return(new SignalServiceContent(CreateSignalServiceMessage(metadata, message),
                                                metadata.Sender,
                                                metadata.SenderDevice,
                                                metadata.Timestamp,
                                                metadata.NeedsReceipt,
                                                serviceContentProto));
            }
            else if (serviceContentProto.DataCase == SignalServiceContentProto.DataOneofCase.Content)
            {
                Content message = serviceContentProto.Content;

                if (message.DataMessage != null)
                {
                    return(new SignalServiceContent(CreateSignalServiceMessage(metadata, message.DataMessage),
                                                    metadata.Sender,
                                                    metadata.SenderDevice,
                                                    metadata.Timestamp,
                                                    metadata.NeedsReceipt,
                                                    serviceContentProto));
                }
                else if (message.SyncMessage != null && localAddress.Matches(metadata.Sender))
                {
                    return(new SignalServiceContent(CreateSynchronizeMessage(metadata, message.SyncMessage),
                                                    metadata.Sender,
                                                    metadata.SenderDevice,
                                                    metadata.Timestamp,
                                                    metadata.NeedsReceipt,
                                                    serviceContentProto));
                }
                else if (message.CallingMessage != null)
                {
                    return(new SignalServiceContent(CreateCallingMessage(message.CallingMessage),
                                                    metadata.Sender,
                                                    metadata.SenderDevice,
                                                    metadata.Timestamp,
                                                    metadata.NeedsReceipt,
                                                    serviceContentProto));
                }
                else if (message.ReceiptMessage != null)
                {
                    return(new SignalServiceContent(CreateReceiptMessage(metadata, message.ReceiptMessage),
                                                    metadata.Sender,
                                                    metadata.SenderDevice,
                                                    metadata.Timestamp,
                                                    metadata.NeedsReceipt,
                                                    serviceContentProto));
                }
                else if (message.TypingMessage != null)
                {
                    return(new SignalServiceContent(CreateTypingMessage(metadata, message.TypingMessage),
                                                    metadata.Sender,
                                                    metadata.SenderDevice,
                                                    metadata.Timestamp,
                                                    false,
                                                    serviceContentProto));
                }
            }

            return(null);
        }