SHA256
Довести DM v1 до соответствия коду и документации
This commit is contained in:
@@ -11,6 +11,17 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class SignedMessagesV2DAO {
|
||||
public enum ApplyStatus {
|
||||
APPLIED,
|
||||
DUPLICATE_OR_OLDER,
|
||||
BLOCKED_BY_MESSAGE_TOMBSTONE,
|
||||
BLOCKED_BY_CONVERSATION_TOMBSTONE;
|
||||
|
||||
public boolean applied() {
|
||||
return this == APPLIED;
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile SignedMessagesV2DAO instance;
|
||||
private final SqliteDbController db = SqliteDbController.getInstance();
|
||||
|
||||
@@ -25,8 +36,11 @@ public final class SignedMessagesV2DAO {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public boolean insertIfAbsent(SignedMessageV2Entry e) throws Exception {
|
||||
public ApplyStatus insertIfAbsent(SignedMessageV2Entry e) throws Exception {
|
||||
try (Connection c = db.getConnection()) {
|
||||
if (isBlockedByConversationDelete(c, e.getFromLogin(), e.getToLogin(), e.getTimeMs())) {
|
||||
return ApplyStatus.BLOCKED_BY_CONVERSATION_TOMBSTONE;
|
||||
}
|
||||
String sql = """
|
||||
INSERT OR IGNORE INTO signed_messages_v2 (
|
||||
message_key, base_key, target_login, from_login, to_login,
|
||||
@@ -37,7 +51,7 @@ public final class SignedMessagesV2DAO {
|
||||
""";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
bindSignedMessage(ps, e);
|
||||
return ps.executeUpdate() > 0;
|
||||
return ps.executeUpdate() > 0 ? ApplyStatus.APPLIED : ApplyStatus.DUPLICATE_OR_OLDER;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,25 +81,25 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean upsertContentPair(SignedMessageV2Entry incoming, SignedMessageV2Entry outgoing) throws Exception {
|
||||
public ApplyStatus upsertContentPair(SignedMessageV2Entry incoming, SignedMessageV2Entry outgoing) throws Exception {
|
||||
try (Connection c = db.getConnection()) {
|
||||
boolean prevAutoCommit = c.getAutoCommit();
|
||||
c.setAutoCommit(false);
|
||||
try {
|
||||
if (isBlockedByConversationDelete(c, incoming.getFromLogin(), incoming.getToLogin(), incoming.getTimeMs())) {
|
||||
c.rollback();
|
||||
return false;
|
||||
return ApplyStatus.BLOCKED_BY_CONVERSATION_TOMBSTONE;
|
||||
}
|
||||
if (hasMessageDeleteTombstone(c, incoming.getBaseKey())) {
|
||||
c.rollback();
|
||||
return false;
|
||||
return ApplyStatus.BLOCKED_BY_MESSAGE_TOMBSTONE;
|
||||
}
|
||||
|
||||
Long currentRevision = getCurrentContentRevision(c, incoming.getBaseKey());
|
||||
long nextRevision = incoming.getRevisionTimeMs();
|
||||
if (currentRevision != null && nextRevision <= currentRevision) {
|
||||
RevisionMarker currentMarker = getCurrentContentMarker(c, incoming.getBaseKey());
|
||||
RevisionMarker nextMarker = RevisionMarker.of(incoming);
|
||||
if (currentMarker != null && compareMarkers(nextMarker, currentMarker) <= 0) {
|
||||
c.rollback();
|
||||
return false;
|
||||
return ApplyStatus.DUPLICATE_OR_OLDER;
|
||||
}
|
||||
|
||||
upsertMessage(c, incoming);
|
||||
@@ -94,7 +108,7 @@ public final class SignedMessagesV2DAO {
|
||||
resetDeliveryRows(c, outgoing.getMessageKey());
|
||||
|
||||
c.commit();
|
||||
return true;
|
||||
return ApplyStatus.APPLIED;
|
||||
} catch (Exception ex) {
|
||||
try { c.rollback(); } catch (Exception ignored) {}
|
||||
throw ex;
|
||||
@@ -104,31 +118,31 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean upsertIncomingCopy(SignedMessageV2Entry incoming) throws Exception {
|
||||
public ApplyStatus upsertIncomingCopy(SignedMessageV2Entry incoming) throws Exception {
|
||||
try (Connection c = db.getConnection()) {
|
||||
boolean prevAutoCommit = c.getAutoCommit();
|
||||
c.setAutoCommit(false);
|
||||
try {
|
||||
if (isBlockedByConversationDelete(c, incoming.getFromLogin(), incoming.getToLogin(), incoming.getTimeMs())) {
|
||||
c.rollback();
|
||||
return false;
|
||||
return ApplyStatus.BLOCKED_BY_CONVERSATION_TOMBSTONE;
|
||||
}
|
||||
if (hasMessageDeleteTombstone(c, incoming.getBaseKey())) {
|
||||
c.rollback();
|
||||
return false;
|
||||
return ApplyStatus.BLOCKED_BY_MESSAGE_TOMBSTONE;
|
||||
}
|
||||
|
||||
Long currentRevision = getRevisionTimeMs(c, incoming.getMessageKey());
|
||||
long nextRevision = incoming.getRevisionTimeMs();
|
||||
if (currentRevision != null && nextRevision <= currentRevision) {
|
||||
RevisionMarker currentMarker = getRevisionMarkerByMessageKey(c, incoming.getMessageKey());
|
||||
RevisionMarker nextMarker = RevisionMarker.of(incoming);
|
||||
if (currentMarker != null && compareMarkers(nextMarker, currentMarker) <= 0) {
|
||||
c.rollback();
|
||||
return false;
|
||||
return ApplyStatus.DUPLICATE_OR_OLDER;
|
||||
}
|
||||
|
||||
upsertMessage(c, incoming);
|
||||
resetDeliveryRows(c, incoming.getMessageKey());
|
||||
c.commit();
|
||||
return true;
|
||||
return ApplyStatus.APPLIED;
|
||||
} catch (Exception ex) {
|
||||
try { c.rollback(); } catch (Exception ignored) {}
|
||||
throw ex;
|
||||
@@ -138,18 +152,18 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean applyDeleteMessage(SignedMessageV2Entry tombstone) throws Exception {
|
||||
public ApplyStatus applyDeleteMessage(SignedMessageV2Entry tombstone) throws Exception {
|
||||
try (Connection c = db.getConnection()) {
|
||||
boolean prevAutoCommit = c.getAutoCommit();
|
||||
c.setAutoCommit(false);
|
||||
try {
|
||||
if (isBlockedByConversationDelete(c, tombstone.getFromLogin(), tombstone.getToLogin(), tombstone.getTimeMs())) {
|
||||
c.rollback();
|
||||
return false;
|
||||
return ApplyStatus.BLOCKED_BY_CONVERSATION_TOMBSTONE;
|
||||
}
|
||||
if (hasMessageDeleteTombstone(c, tombstone.getBaseKey())) {
|
||||
c.rollback();
|
||||
return false;
|
||||
return ApplyStatus.DUPLICATE_OR_OLDER;
|
||||
}
|
||||
|
||||
deleteMessageContentAndReceipts(c, tombstone.getBaseKey());
|
||||
@@ -157,7 +171,7 @@ public final class SignedMessagesV2DAO {
|
||||
resetDeliveryRows(c, tombstone.getMessageKey());
|
||||
|
||||
c.commit();
|
||||
return true;
|
||||
return ApplyStatus.APPLIED;
|
||||
} catch (Exception ex) {
|
||||
try { c.rollback(); } catch (Exception ignored) {}
|
||||
throw ex;
|
||||
@@ -167,7 +181,7 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean applyDeleteConversation(SignedMessageV2Entry tombstone) throws Exception {
|
||||
public ApplyStatus applyDeleteConversation(SignedMessageV2Entry tombstone) throws Exception {
|
||||
try (Connection c = db.getConnection()) {
|
||||
boolean prevAutoCommit = c.getAutoCommit();
|
||||
c.setAutoCommit(false);
|
||||
@@ -175,7 +189,7 @@ public final class SignedMessagesV2DAO {
|
||||
Long currentBoundary = getLatestConversationDeleteBoundary(c, tombstone.getFromLogin(), tombstone.getToLogin());
|
||||
if (currentBoundary != null && tombstone.getTimeMs() <= currentBoundary) {
|
||||
c.rollback();
|
||||
return false;
|
||||
return ApplyStatus.DUPLICATE_OR_OLDER;
|
||||
}
|
||||
|
||||
deleteConversationHistoryBefore(c, tombstone.getFromLogin(), tombstone.getToLogin(), tombstone.getTimeMs());
|
||||
@@ -183,7 +197,7 @@ public final class SignedMessagesV2DAO {
|
||||
resetDeliveryRows(c, tombstone.getMessageKey());
|
||||
|
||||
c.commit();
|
||||
return true;
|
||||
return ApplyStatus.APPLIED;
|
||||
} catch (Exception ex) {
|
||||
try { c.rollback(); } catch (Exception ignored) {}
|
||||
throw ex;
|
||||
@@ -214,6 +228,12 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
public SignedMessageV2Entry getLatestConversationDelete(String fromLogin, String toLogin) throws Exception {
|
||||
try (Connection c = db.getConnection()) {
|
||||
return getLatestConversationDelete(c, fromLogin, toLogin);
|
||||
}
|
||||
}
|
||||
|
||||
public void ensureDeliveryRow(String messageKey, String sessionId, long nowMs) throws Exception {
|
||||
try (Connection c = db.getConnection()) {
|
||||
String sql = """
|
||||
@@ -338,30 +358,36 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
private Long getRevisionTimeMs(Connection c, String messageKey) throws SQLException {
|
||||
String sql = "SELECT revision_time_ms FROM signed_messages_v2 WHERE message_key = ? LIMIT 1";
|
||||
private RevisionMarker getRevisionMarkerByMessageKey(Connection c, String messageKey) throws SQLException {
|
||||
String sql = """
|
||||
SELECT revision_time_ms, reencrypted_at_ms
|
||||
FROM signed_messages_v2
|
||||
WHERE message_key = ?
|
||||
LIMIT 1
|
||||
""";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, messageKey);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) return null;
|
||||
return rs.getLong(1);
|
||||
return new RevisionMarker(rs.getLong(1), rs.getLong(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Long getCurrentContentRevision(Connection c, String baseKey) throws SQLException {
|
||||
private RevisionMarker getCurrentContentMarker(Connection c, String baseKey) throws SQLException {
|
||||
String sql = """
|
||||
SELECT MAX(revision_time_ms)
|
||||
SELECT revision_time_ms, reencrypted_at_ms
|
||||
FROM signed_messages_v2
|
||||
WHERE base_key = ?
|
||||
AND message_type IN (1, 2)
|
||||
ORDER BY revision_time_ms DESC, reencrypted_at_ms DESC
|
||||
LIMIT 1
|
||||
""";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, baseKey);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) return null;
|
||||
long value = rs.getLong(1);
|
||||
return rs.wasNull() ? null : value;
|
||||
return new RevisionMarker(rs.getLong(1), rs.getLong(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -410,6 +436,34 @@ public final class SignedMessagesV2DAO {
|
||||
}
|
||||
}
|
||||
|
||||
private SignedMessageV2Entry getLatestConversationDelete(Connection c, String fromLogin, String toLogin) throws Exception {
|
||||
String sql = """
|
||||
SELECT
|
||||
message_key, base_key, target_login, from_login, to_login,
|
||||
time_ms, nonce, message_type, revision_time_ms, reencrypted_at_ms,
|
||||
raw_block, created_at_ms, source_api, origin_session_id,
|
||||
receipt_ref_base_key, receipt_ref_type
|
||||
FROM signed_messages_v2
|
||||
WHERE message_type IN (7, 8)
|
||||
AND (
|
||||
(from_login = ? COLLATE NOCASE AND to_login = ? COLLATE NOCASE)
|
||||
OR (from_login = ? COLLATE NOCASE AND to_login = ? COLLATE NOCASE)
|
||||
)
|
||||
ORDER BY time_ms DESC
|
||||
LIMIT 1
|
||||
""";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, fromLogin);
|
||||
ps.setString(2, toLogin);
|
||||
ps.setString(3, toLogin);
|
||||
ps.setString(4, fromLogin);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) return null;
|
||||
return mapRow(rs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteMessageContentAndReceipts(Connection c, String baseKey) throws SQLException {
|
||||
deleteDeliveryRowsByMessageSelection(c, """
|
||||
SELECT message_key
|
||||
@@ -532,6 +586,12 @@ public final class SignedMessagesV2DAO {
|
||||
return msg.contains("constraint") || msg.contains("unique") || msg.contains("primary key");
|
||||
}
|
||||
|
||||
private int compareMarkers(RevisionMarker left, RevisionMarker right) {
|
||||
int revisionCompare = Long.compare(left.revisionTimeMs, right.revisionTimeMs);
|
||||
if (revisionCompare != 0) return revisionCompare;
|
||||
return Long.compare(left.reencryptedAtMs, right.reencryptedAtMs);
|
||||
}
|
||||
|
||||
private SignedMessageV2Entry mapRow(ResultSet rs) throws Exception {
|
||||
SignedMessageV2Entry e = new SignedMessageV2Entry();
|
||||
e.setMessageKey(rs.getString("message_key"));
|
||||
@@ -553,4 +613,10 @@ public final class SignedMessagesV2DAO {
|
||||
e.setReceiptRefType(rs.wasNull() ? null : maybeRefType);
|
||||
return e;
|
||||
}
|
||||
|
||||
private record RevisionMarker(long revisionTimeMs, long reencryptedAtMs) {
|
||||
private static RevisionMarker of(SignedMessageV2Entry entry) {
|
||||
return new RevisionMarker(entry.getRevisionTimeMs(), entry.getReencryptedAtMs());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user