diff --git a/benchmarks/gluon_bench/imap_benchmarks/append.go b/benchmarks/gluon_bench/imap_benchmarks/append.go index d74037ac..d1d7b679 100644 --- a/benchmarks/gluon_bench/imap_benchmarks/append.go +++ b/benchmarks/gluon_bench/imap_benchmarks/append.go @@ -39,7 +39,8 @@ func (a *Append) Setup(ctx context.Context, addr net.Addr) error { } func (a *Append) TearDown(ctx context.Context, addr net.Addr) error { - return a.cleanupWithAddr(addr) + //return a.cleanupWithAddr(addr) + return nil } func (a *Append) Run(ctx context.Context, addr net.Addr) error { diff --git a/internal/state/snapshot.go b/internal/state/snapshot.go index e3942285..245ebb49 100644 --- a/internal/state/snapshot.go +++ b/internal/state/snapshot.go @@ -2,8 +2,6 @@ package state import ( "context" - "fmt" - "strconv" "strings" "github.com/ProtonMail/gluon/imap" @@ -171,47 +169,7 @@ func (snap *snapshot) resolveSeqInterval(seq *proto.SequenceSet) ([]SeqInterval, return nil, err } - res := make([]SeqInterval, 0, len(seqSet)) - - for _, seqRange := range seqSet { - switch len(seqRange) { - case 1: - seq, err := snap.resolveSeq(seqRange[0]) - if err != nil { - return nil, err - } - - res = append(res, SeqInterval{ - begin: seq, - end: seq, - }) - - case 2: - begin, err := snap.resolveSeq(seqRange[0]) - if err != nil { - return nil, err - } - - end, err := snap.resolveSeq(seqRange[1]) - if err != nil { - return nil, err - } - - if begin > end { - begin, end = end, begin - } - - res = append(res, SeqInterval{ - begin: begin, - end: end, - }) - - default: - return nil, fmt.Errorf("bad sequence range") - } - } - - return res, nil + return snap.messages.resolveSeqInterval(seqSet) } func (snap *snapshot) resolveUIDInterval(seq *proto.SequenceSet) ([]UIDInterval, error) { @@ -220,104 +178,25 @@ func (snap *snapshot) resolveUIDInterval(seq *proto.SequenceSet) ([]UIDInterval, return nil, err } - res := make([]UIDInterval, 0, len(seqSet)) - - for _, uidRange := range seqSet { - switch len(uidRange) { - case 1: - uid, err := snap.resolveUID(uidRange[0]) - if err != nil { - return nil, err - } - - res = append(res, UIDInterval{ - begin: uid, - end: uid, - }) - - case 2: - begin, err := snap.resolveUID(uidRange[0]) - if err != nil { - return nil, err - } - - end, err := snap.resolveUID(uidRange[1]) - if err != nil { - return nil, err - } - - if begin > end { - begin, end = end, begin - } - - res = append(res, UIDInterval{ - begin: begin, - end: end, - }) - - default: - return nil, fmt.Errorf("bad sequence range") - } - } - - return res, nil + return snap.messages.resolveUIDInterval(seqSet) } func (snap *snapshot) getMessagesInSeqRange(seq *proto.SequenceSet) ([]snapMsgWithSeq, error) { - var res []snapMsgWithSeq - - intervals, err := snap.resolveSeqInterval(seq) + seqSet, err := toSeqSet(seq) if err != nil { return nil, err } - for _, seqRange := range intervals { - if seqRange.begin == seqRange.end { - msg, ok := snap.messages.getWithSeqID(seqRange.begin) - if !ok { - return nil, ErrNoSuchMessage - } - - res = append(res, msg) - } else { - if !snap.messages.existsWithSeqID(seqRange.begin) || !snap.messages.existsWithSeqID(seqRange.end) { - return nil, ErrNoSuchMessage - } - - res = append(res, snap.messages.seqRange(seqRange.begin, seqRange.end)...) - } - } - - return res, nil + return snap.messages.getMessagesInSeqRange(seqSet) } func (snap *snapshot) getMessagesInUIDRange(seq *proto.SequenceSet) ([]snapMsgWithSeq, error) { - var res []snapMsgWithSeq - - // If there are no messages in the mailbox, we still resolve without error. - if snap.messages.len() == 0 { - return nil, nil - } - - intervals, err := snap.resolveUIDInterval(seq) + seqSet, err := toSeqSet(seq) if err != nil { return nil, err } - for _, uidRange := range intervals { - if uidRange.begin == uidRange.end { - msg, ok := snap.messages.getWithUID(uidRange.begin) - if !ok { - continue - } - - res = append(res, msg) - } else { - res = append(res, snap.messages.uidRange(uidRange.begin, uidRange.end)...) - } - } - - return res, nil + return snap.messages.getMessagesInUIDRange(seqSet) } func (snap *snapshot) firstMessageWithFlag(flag string) (snapMsgWithSeq, bool) { @@ -421,39 +300,3 @@ func (snap *snapshot) updateMessageRemoteID(internalID imap.InternalMessageID, r return nil } - -// resolveSeq converts a textual sequence number into an integer. -// According to RFC 3501, the definition of seq-number, page 89, for message sequence numbers -// - No sequence number is valid if mailbox is empty, not even "*" -// - "*" is converted to the number of messages in the mailbox -// - when used in a range, the order of the indexes in irrelevant. -func (snap *snapshot) resolveSeq(number string) (imap.SeqID, error) { - if number == "*" { - return imap.SeqID(snap.messages.len()), nil - } - - num, err := strconv.ParseUint(number, 10, 32) - if err != nil { - return 0, fmt.Errorf("failed to parse sequence number: %w", err) - } - - return imap.SeqID(num), nil -} - -// resolveUID converts a textual message UID into an integer. -func (snap *snapshot) resolveUID(number string) (imap.UID, error) { - if snap.messages.len() == 0 { - return 0, ErrNoSuchMessage - } - - if number == "*" { - return snap.messages.last().UID, nil - } - - num, err := strconv.ParseUint(number, 10, 32) - if err != nil { - return 0, fmt.Errorf("failed to parse UID number: %w", err) - } - - return imap.UID(num), nil -} diff --git a/internal/state/snapshot_messages.go b/internal/state/snapshot_messages.go index 4c128b3e..ff5c3e61 100644 --- a/internal/state/snapshot_messages.go +++ b/internal/state/snapshot_messages.go @@ -1,10 +1,12 @@ package state import ( + "fmt" "github.com/ProtonMail/gluon/imap" "github.com/ProtonMail/gluon/internal/ids" "github.com/bradenaw/juniper/xslices" "golang.org/x/exp/slices" + "strconv" ) // snapMsg is a single message inside a snapshot. @@ -270,3 +272,200 @@ func (list *snapMsgList) existsWithSeqID(id imap.SeqID) bool { return true } + +func (list *snapMsgList) resolveSeqInterval(seqSet [][]string) ([]SeqInterval, error) { + res := make([]SeqInterval, 0, len(seqSet)) + + for _, seqRange := range seqSet { + switch len(seqRange) { + case 1: + seq, err := list.resolveSeq(seqRange[0]) + if err != nil { + return nil, err + } + + res = append(res, SeqInterval{ + begin: seq, + end: seq, + }) + + case 2: + if seqRange[0] == "*" { + seqRange[0], seqRange[1] = seqRange[1], seqRange[0] + } + + begin, err := list.resolveSeq(seqRange[0]) + if err != nil { + return nil, err + } + + end, err := list.resolveSeq(seqRange[1]) + if err != nil { + return nil, err + } + + if begin > end { + if seqRange[1] != "*" { + begin, end = end, begin + } else { + end = begin + } + } + + res = append(res, SeqInterval{ + begin: begin, + end: end, + }) + + default: + return nil, fmt.Errorf("bad sequence range") + } + } + + return res, nil +} + +func (list *snapMsgList) resolveUIDInterval(seqSet [][]string) ([]UIDInterval, error) { + res := make([]UIDInterval, 0, len(seqSet)) + + for _, uidRange := range seqSet { + switch len(uidRange) { + case 1: + uid, err := list.resolveUID(uidRange[0]) + if err != nil { + return nil, err + } + + res = append(res, UIDInterval{ + begin: uid, + end: uid, + }) + + case 2: + if uidRange[0] == "*" { + uidRange[0], uidRange[1] = uidRange[1], uidRange[0] + } + + begin, err := list.resolveUID(uidRange[0]) + if err != nil { + return nil, err + } + + end, err := list.resolveUID(uidRange[1]) + if err != nil { + return nil, err + } + + if begin > end { + if uidRange[1] != "*" { + begin, end = end, begin + } else { + end = begin + } + } + + res = append(res, UIDInterval{ + begin: begin, + end: end, + }) + + default: + return nil, fmt.Errorf("bad sequence range") + } + } + + return res, nil +} + +// resolveSeq converts a textual sequence number into an integer. +// According to RFC 3501, the definition of seq-number, page 89, for message sequence numbers +// - No sequence number is valid if mailbox is empty, not even "*" +// - "*" is converted to the number of messages in the mailbox +// - when used in a range, the order of the indexes in irrelevant. +func (list *snapMsgList) resolveSeq(number string) (imap.SeqID, error) { + if number == "*" { + return imap.SeqID(list.len()), nil + } + + num, err := strconv.ParseUint(number, 10, 32) + if err != nil { + return 0, fmt.Errorf("failed to parse sequence number: %w", err) + } + + return imap.SeqID(num), nil +} + +// resolveUID converts a textual message UID into an integer. +func (list *snapMsgList) resolveUID(number string) (imap.UID, error) { + if list.len() == 0 { + return 0, ErrNoSuchMessage + } + + if number == "*" { + return list.last().UID, nil + } + + num, err := strconv.ParseUint(number, 10, 32) + if err != nil { + return 0, fmt.Errorf("failed to parse UID number: %w", err) + } + + return imap.UID(num), nil +} + +func (list *snapMsgList) getMessagesInSeqRange(seqSet [][]string) ([]snapMsgWithSeq, error) { + var res []snapMsgWithSeq + + intervals, err := list.resolveSeqInterval(seqSet) + if err != nil { + return nil, err + } + + for _, seqRange := range intervals { + if seqRange.begin == seqRange.end { + msg, ok := list.getWithSeqID(seqRange.begin) + if !ok { + return nil, ErrNoSuchMessage + } + + res = append(res, msg) + } else { + if !list.existsWithSeqID(seqRange.begin) || !list.existsWithSeqID(seqRange.end) { + return nil, ErrNoSuchMessage + } + + res = append(res, list.seqRange(seqRange.begin, seqRange.end)...) + } + } + + return res, nil +} + +func (list *snapMsgList) getMessagesInUIDRange(seqSet [][]string) ([]snapMsgWithSeq, error) { + var res []snapMsgWithSeq + + // If there are no messages in the mailbox, we still resolve without error. + if list.len() == 0 { + return nil, nil + } + + intervals, err := list.resolveUIDInterval(seqSet) + if err != nil { + return nil, err + } + + for _, uidRange := range intervals { + if uidRange.begin == uidRange.end { + msg, ok := list.getWithUID(uidRange.begin) + if !ok { + continue + } + + res = append(res, msg) + } else { + res = append(res, list.uidRange(uidRange.begin, uidRange.end)...) + } + } + + return res, nil +} diff --git a/internal/state/snapshot_messages_test.go b/internal/state/snapshot_messages_test.go index 6be9134a..4e68bf73 100644 --- a/internal/state/snapshot_messages_test.go +++ b/internal/state/snapshot_messages_test.go @@ -135,6 +135,91 @@ func TestMessageUIDRange(t *testing.T) { } } +func TestMessageRange1HigherThanMax(t *testing.T) { + msg := newMsgList(8) + + id1 := imap.NewInternalMessageID() + id2 := imap.NewInternalMessageID() + + msg.insert(messageIDPair(id1, "1"), 1, imap.NewFlagSet(imap.FlagSeen)) + msg.insert(messageIDPair(id2, "2"), 2, imap.NewFlagSet(imap.FlagSeen)) + + seqSetInterval := [][]string{{"3", "*"}} + + { + uidInterval, err := msg.resolveUIDInterval(seqSetInterval) + require.NoError(t, err) + seqInterval, err := msg.resolveSeqInterval(seqSetInterval) + require.NoError(t, err) + + require.Equal(t, uidInterval, []UIDInterval{{begin: imap.UID(3), end: imap.UID(3)}}) + require.Equal(t, seqInterval, []SeqInterval{{begin: imap.SeqID(3), end: imap.SeqID(3)}}) + } + + { + _, err := msg.getMessagesInSeqRange(seqSetInterval) + require.Error(t, err) + } + { + messages, err := msg.getMessagesInUIDRange(seqSetInterval) + require.NoError(t, err) + require.Empty(t, messages) + } +} + +func TestSnapListGetMessages(t *testing.T) { + msg := newMsgList(8) + + id1 := imap.NewInternalMessageID() + id2 := imap.NewInternalMessageID() + + msg.insert(messageIDPair(id1, "1"), 1, imap.NewFlagSet(imap.FlagSeen)) + msg.insert(messageIDPair(id2, "2"), 2, imap.NewFlagSet(imap.FlagSeen)) + + { + seqSetInterval := [][]string{{"3", "*"}} + _, err := msg.getMessagesInSeqRange(seqSetInterval) + require.Error(t, err) + + uidInterval, err := msg.getMessagesInUIDRange(seqSetInterval) + require.NoError(t, err) + require.Empty(t, uidInterval) + } + { + seqSetInterval := [][]string{{"*", "3"}} + _, err := msg.getMessagesInSeqRange(seqSetInterval) + require.Error(t, err) + + uidInterval, err := msg.getMessagesInUIDRange(seqSetInterval) + require.NoError(t, err) + require.Empty(t, uidInterval) + } + { + seqSetInterval := [][]string{{"1", "*"}} + seqList, err := msg.getMessagesInSeqRange(seqSetInterval) + require.NoError(t, err) + require.Equal(t, 2, len(seqList)) + require.Equal(t, seqList[0].Seq, imap.SeqID(1)) + require.Equal(t, seqList[1].Seq, imap.SeqID(2)) + + uidList, err := msg.getMessagesInUIDRange(seqSetInterval) + require.NoError(t, err) + require.Equal(t, 2, len(uidList)) + require.Equal(t, uidList[0].UID, imap.UID(1)) + require.Equal(t, uidList[1].UID, imap.UID(2)) + } + { + seqSetInterval := [][]string{{"2", "3"}} + _, err := msg.getMessagesInSeqRange(seqSetInterval) + require.Error(t, err) + + uidList, err := msg.getMessagesInUIDRange(seqSetInterval) + require.NoError(t, err) + require.Equal(t, 1, len(uidList)) + require.Equal(t, uidList[0].UID, imap.UID(2)) + } +} + func messageIDPair(internalID imap.InternalMessageID, remoteID imap.MessageID) ids.MessageIDPair { return ids.MessageIDPair{InternalID: internalID, RemoteID: remoteID} }