1 /*
2  * Copyright (c) 2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include "schema_negotiate.h"
16 
17 #include "db_common.h"
18 #include "log_print.h"
19 #include "schema_utils.h"
20 
21 namespace DistributedDB {
22 // Some principle in current version describe below. (Relative-type will be introduced in future but not involved now)
23 // 1.   PermitSync: Be false may because schemaType-unrecognized, schemaType-different, schema-unparsable,
24 //      schemaVersion-unrecognized, schema-incompatible, and so on.
25 // 2.   RequirePeerConvert: Be true normally when permitSync false, for future possible sync and convert(by remote).
26 // 3.   checkOnReceive: Be false when local is KV-DB, or when local is not KV-DB only if schema type equal as well as
27 //      define equal or remote is the upgradation of local.
MakeLocalSyncOpinion(const SchemaObject & localSchema,const std::string & remoteSchema,uint8_t remoteSchemaType)28 SyncOpinion SchemaNegotiate::MakeLocalSyncOpinion(const SchemaObject &localSchema, const std::string &remoteSchema,
29     uint8_t remoteSchemaType)
30 {
31     SchemaType localType = localSchema.GetSchemaType(); // An invalid schemaObject will return SchemaType::NONE
32     SchemaType remoteType = ReadSchemaType(remoteSchemaType);
33     // Logic below only be correct in current version, should be redesigned if new type added in the future
34     // 1. If remote-type unrecognized(Include Relative-type), Do not permit sync.
35     if (remoteType == SchemaType::UNRECOGNIZED) {
36         LOGE("[Schema][Opinion] Remote-type=%" PRIu8 " unrecognized.", remoteSchemaType);
37         return SyncOpinion{false, true, true};
38     }
39     // 2. If local-type is KV(Here remote-type is within recognized), Always permit sync.
40     if (localType == SchemaType::NONE) {
41         LOGI("[Schema][Opinion] Local-type KV.");
42         return SyncOpinion{true, false, false};
43     }
44     // 3. If remote-type is KV(Here local-type can only be JSON or FLATBUFFER), Always permit sync but need check.
45     if (remoteType == SchemaType::NONE) {
46         LOGI("[Schema][Opinion] Remote-type KV.");
47         return SyncOpinion{true, false, true};
48     }
49     // 4. If local-type differ with remote-type(Here both type can only be JSON or FLATBUFFER), Do not permit sync.
50     if (localType != remoteType) {
51         LOGE("[Schema][Opinion] Local-type=%s differ remote-type=%s.", SchemaUtils::SchemaTypeString(localType).c_str(),
52             SchemaUtils::SchemaTypeString(remoteType).c_str());
53         return SyncOpinion{false, true, true};
54     }
55     // 5. If schema parse fail, Do not permit sync.
56     SchemaObject remoteSchemaObj;
57     int errCode = remoteSchemaObj.ParseFromSchemaString(remoteSchema);
58     if (errCode != E_OK) {
59         LOGE("[Schema][Opinion] Parse remote-schema fail, errCode=%d, remote-type=%s.", errCode,
60             SchemaUtils::SchemaTypeString(remoteType).c_str());
61         return SyncOpinion{false, true, true};
62     }
63     // 6. If remote-schema is not incompatible based on local-schema(SchemaDefine Equal), Permit sync and don't check.
64     errCode = localSchema.CompareAgainstSchemaObject(remoteSchemaObj);
65     if (errCode != -E_SCHEMA_UNEQUAL_INCOMPATIBLE) {
66         return SyncOpinion{true, false, false};
67     }
68     // 7. If local-schema is not incompatible based on remote-schema(Can only be COMPATIBLE_UPGRADE), Sync and check.
69     errCode = remoteSchemaObj.CompareAgainstSchemaObject(localSchema);
70     if (errCode != -E_SCHEMA_UNEQUAL_INCOMPATIBLE) {
71         return SyncOpinion{true, false, true};
72     }
73     // 8. Local-schema incompatible with remote-schema mutually.
74     LOGE("[Schema][Opinion] Local-schema incompatible with remote-schema mutually.");
75     return SyncOpinion{false, true, true};
76 }
77 
ConcludeSyncStrategy(const SyncOpinion & localOpinion,const SyncOpinion & remoteOpinion)78 SyncStrategy SchemaNegotiate::ConcludeSyncStrategy(const SyncOpinion &localOpinion, const SyncOpinion &remoteOpinion)
79 {
80     SyncStrategy outStrategy;
81     // Any side permit sync, the final conclusion is permit sync.
82     outStrategy.permitSync = (localOpinion.permitSync || remoteOpinion.permitSync);
83     bool convertConflict = (localOpinion.requirePeerConvert && remoteOpinion.requirePeerConvert);
84     if (convertConflict) {
85         outStrategy.permitSync = false;
86     }
87     // Responsible for conversion on send now that local do not require remote to do conversion
88     outStrategy.convertOnSend = (!localOpinion.requirePeerConvert);
89     // Responsible for conversion on receive since remote will not do conversion on send and require local to convert
90     outStrategy.convertOnReceive = remoteOpinion.requirePeerConvert;
91     // Only depend on local opinion
92     outStrategy.checkOnReceive = localOpinion.checkOnReceive;
93     LOGI("[Schema][Strategy] PermitSync=%d, SendConvert=%d, ReceiveConvert=%d, ReceiveCheck=%d.",
94         outStrategy.permitSync, outStrategy.convertOnSend, outStrategy.convertOnReceive, outStrategy.checkOnReceive);
95     return outStrategy;
96 }
97 
MakeOpinionEachTable(const RelationalSchemaObject & localSchema,const RelationalSchemaObject & remoteSchema)98 RelationalSyncOpinion SchemaNegotiate::MakeOpinionEachTable(const RelationalSchemaObject &localSchema,
99     const RelationalSchemaObject &remoteSchema)
100 {
101     RelationalSyncOpinion opinion;
102     for (const auto &it : localSchema.GetTables()) {
103         if (!DBCommon::CaseInsensitiveCompare(remoteSchema.GetTable(it.first).GetTableName(), it.first)) {
104             LOGW("[RelationalSchema][opinion] Table was missing in remote schema");
105             continue;
106         }
107         // remote table is compatible(equal or upgrade) based on local table, permit sync and don't need check
108         int errCode = it.second.CompareWithTable(remoteSchema.GetTable(it.first), localSchema.GetSchemaVersion());
109         if (errCode != -E_RELATIONAL_TABLE_INCOMPATIBLE) {
110             opinion[it.first] = {true, false, false};
111             continue;
112         }
113         // local table is compatible upgrade based on remote table, permit sync and need check
114         errCode = remoteSchema.GetTable(it.first).CompareWithTable(it.second, remoteSchema.GetSchemaVersion());
115         if (errCode != -E_RELATIONAL_TABLE_INCOMPATIBLE) {
116             opinion[it.first] = {true, false, true};
117             continue;
118         }
119         // local table is incompatible with remote table mutually, don't permit sync and need check
120         LOGW("[RelationalSchema][opinion] Local table is incompatible with remote table mutually.");
121         opinion[it.first] = {false, true, true};
122     }
123     return opinion;
124 }
125 
MakeLocalSyncOpinion(const RelationalSchemaObject & localSchema,const std::string & remoteSchema,uint8_t remoteSchemaType)126 RelationalSyncOpinion SchemaNegotiate::MakeLocalSyncOpinion(const RelationalSchemaObject &localSchema,
127     const std::string &remoteSchema, uint8_t remoteSchemaType)
128 {
129     SchemaType localType = localSchema.GetSchemaType();
130     SchemaType remoteType = ReadSchemaType(remoteSchemaType);
131     if (remoteType == SchemaType::UNRECOGNIZED) {
132         LOGW("[RelationalSchema][opinion] Remote schema type %" PRIu8 " is unrecognized.", remoteSchemaType);
133         return {};
134     }
135 
136     if (remoteType != SchemaType::RELATIVE) {
137         LOGW("[RelationalSchema][opinion] Not support sync with schema type: local-type=[%s] remote-type=[%s]",
138             SchemaUtils::SchemaTypeString(localType).c_str(), SchemaUtils::SchemaTypeString(remoteType).c_str());
139         return {};
140     }
141 
142     if (!localSchema.IsSchemaValid()) {
143         LOGW("[RelationalSchema][opinion] Local schema is not valid");
144         return {};
145     }
146 
147     RelationalSchemaObject remoteSchemaObj;
148     int errCode = remoteSchemaObj.ParseFromSchemaString(remoteSchema);
149     if (errCode != E_OK) {
150         LOGW("[RelationalSchema][opinion] Parse remote schema failed %d, remote schema type %s", errCode,
151             SchemaUtils::SchemaTypeString(remoteType).c_str());
152         return {};
153     }
154 
155     if (localSchema.GetSchemaVersion() != remoteSchemaObj.GetSchemaVersion()) {
156         LOGW("[RelationalSchema][opinion] Schema version mismatch, local %s, remote %s",
157             localSchema.GetSchemaVersion().c_str(), remoteSchemaObj.GetSchemaVersion().c_str());
158         return {};
159     }
160 
161     if (localSchema.GetSchemaVersion() == SchemaConstant::SCHEMA_SUPPORT_VERSION_V2_1 &&
162         localSchema.GetTableMode() != remoteSchemaObj.GetTableMode()) {
163         LOGW("[RelationalSchema][opinion] Schema table mode mismatch, local %d, remote %d",
164             localSchema.GetTableMode(), remoteSchemaObj.GetTableMode());
165         return {};
166     }
167 
168     return MakeOpinionEachTable(localSchema, remoteSchemaObj);
169 }
170 
ConcludeSyncStrategy(const RelationalSyncOpinion & localOpinion,const RelationalSyncOpinion & remoteOpinion)171 RelationalSyncStrategy SchemaNegotiate::ConcludeSyncStrategy(const RelationalSyncOpinion &localOpinion,
172     const RelationalSyncOpinion &remoteOpinion)
173 {
174     RelationalSyncStrategy syncStrategy;
175     for (const auto &itLocal : localOpinion) {
176         if (remoteOpinion.find(itLocal.first) == remoteOpinion.end()) {
177             LOGW("[RelationalSchema][Strategy] Table opinion is not found from remote.");
178             continue;
179         }
180         SyncOpinion localTableOpinion = itLocal.second;
181         SyncOpinion remoteTableOpinion = remoteOpinion.at(itLocal.first);
182         syncStrategy[itLocal.first] = ConcludeSyncStrategy(localTableOpinion, remoteTableOpinion);
183     }
184 
185     return syncStrategy;
186 }
187 
188 namespace {
189     const std::string MAGIC = "relational_opinion";
190     const uint32_t SYNC_OPINION_VERSION = 1;
191 } // namespace
192 
193 
CalculateParcelLen(const RelationalSyncOpinion & opinions)194 uint32_t SchemaNegotiate::CalculateParcelLen(const RelationalSyncOpinion &opinions)
195 {
196     uint64_t len = Parcel::GetStringLen(MAGIC);
197     len += Parcel::GetUInt32Len();
198     len += Parcel::GetUInt32Len();
199     len = Parcel::GetEightByteAlign(len);
200     for (const auto &it : opinions) {
201         len += Parcel::GetStringLen(it.first);
202         len += Parcel::GetUInt32Len();
203         len += Parcel::GetUInt32Len();
204         len = Parcel::GetEightByteAlign(len);
205     }
206     if (len > UINT32_MAX) {
207         return 0;
208     }
209     return static_cast<uint32_t>(len);
210 }
211 
SerializeData(const RelationalSyncOpinion & opinions,Parcel & parcel)212 int SchemaNegotiate::SerializeData(const RelationalSyncOpinion &opinions, Parcel &parcel)
213 {
214     (void)parcel.WriteString(MAGIC);
215     (void)parcel.WriteUInt32(SYNC_OPINION_VERSION);
216     (void)parcel.WriteUInt32(static_cast<uint32_t>(opinions.size()));
217     parcel.EightByteAlign();
218     for (const auto &it : opinions) {
219         (void)parcel.WriteString(it.first);
220         (void)parcel.WriteUInt32(it.second.permitSync);
221         (void)parcel.WriteUInt32(it.second.requirePeerConvert);
222         parcel.EightByteAlign();
223     }
224     return parcel.IsError() ? -E_INVALID_ARGS : E_OK;
225 }
226 
DeserializeData(Parcel & parcel,RelationalSyncOpinion & opinion)227 int SchemaNegotiate::DeserializeData(Parcel &parcel, RelationalSyncOpinion &opinion)
228 {
229     if (!parcel.IsContinueRead()) {
230         return E_OK;
231     }
232     std::string magicStr;
233     (void)parcel.ReadString(magicStr);
234     if (magicStr != MAGIC) {
235         LOGE("Deserialize sync opinion failed while read MAGIC string");
236         return -E_INVALID_ARGS;
237     }
238     uint32_t version;
239     (void)parcel.ReadUInt32(version);
240     if (version != SYNC_OPINION_VERSION) {
241         LOGE("Not support sync opinion version: %u", version);
242         return -E_NOT_SUPPORT;
243     }
244     uint32_t opinionSize;
245     (void)parcel.ReadUInt32(opinionSize);
246     parcel.EightByteAlign();
247     static const uint32_t MAX_OPINION_SIZE = 1024; // max 1024 opinions
248     if (parcel.IsError() || opinionSize > MAX_OPINION_SIZE) {
249         return -E_INVALID_ARGS;
250     }
251     for (uint32_t i = 0; i < opinionSize; i++) {
252         std::string tableName;
253         SyncOpinion tableOpinion;
254         (void)parcel.ReadString(tableName);
255         uint32_t permitSync;
256         (void)parcel.ReadUInt32(permitSync);
257         tableOpinion.permitSync = static_cast<bool>(permitSync);
258         uint32_t requirePeerConvert;
259         (void)parcel.ReadUInt32(requirePeerConvert);
260         tableOpinion.requirePeerConvert = static_cast<bool>(requirePeerConvert);
261         (void)parcel.EightByteAlign();
262         opinion[tableName] = tableOpinion;
263     }
264     return parcel.IsError() ? -E_INVALID_ARGS : E_OK;
265 }
266 }