github.com/ewagmig/fabric@v2.1.1+incompatible/core/ledger/kvledger/txmgmt/statedb/statecouchdb/couchdoc_conv.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package statecouchdb 8 9 import ( 10 "bytes" 11 "encoding/json" 12 "strings" 13 "unicode/utf8" 14 15 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb" 16 "github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version" 17 "github.com/hyperledger/fabric/core/ledger/util/couchdb" 18 "github.com/pkg/errors" 19 ) 20 21 const ( 22 binaryWrapper = "valueBytes" 23 idField = "_id" 24 revField = "_rev" 25 versionField = "~version" 26 deletedField = "_deleted" 27 ) 28 29 type keyValue struct { 30 key string 31 revision string 32 *statedb.VersionedValue 33 } 34 35 type jsonValue map[string]interface{} 36 37 func tryCastingToJSON(b []byte) (isJSON bool, val jsonValue) { 38 var jsonVal map[string]interface{} 39 err := json.Unmarshal(b, &jsonVal) 40 return err == nil, jsonValue(jsonVal) 41 } 42 43 func castToJSON(b []byte) (jsonValue, error) { 44 var jsonVal map[string]interface{} 45 err := json.Unmarshal(b, &jsonVal) 46 err = errors.Wrap(err, "error unmarshalling json data") 47 return jsonVal, err 48 } 49 50 func (v jsonValue) checkReservedFieldsNotPresent() error { 51 for fieldName := range v { 52 if fieldName == versionField || strings.HasPrefix(fieldName, "_") { 53 return errors.Errorf("field [%s] is not valid for the CouchDB state database", fieldName) 54 } 55 } 56 return nil 57 } 58 59 func (v jsonValue) removeRevField() { 60 delete(v, revField) 61 } 62 63 func (v jsonValue) toBytes() ([]byte, error) { 64 jsonBytes, err := json.Marshal(v) 65 err = errors.Wrap(err, "error marshalling json data") 66 return jsonBytes, err 67 } 68 69 func couchDocToKeyValue(doc *couchdb.CouchDoc) (*keyValue, error) { 70 // initialize the return value 71 var returnValue []byte 72 var err error 73 // create a generic map unmarshal the json 74 jsonResult := make(map[string]interface{}) 75 decoder := json.NewDecoder(bytes.NewBuffer(doc.JSONValue)) 76 decoder.UseNumber() 77 if err = decoder.Decode(&jsonResult); err != nil { 78 return nil, err 79 } 80 // verify the version field exists 81 if _, fieldFound := jsonResult[versionField]; !fieldFound { 82 return nil, errors.Errorf("version field %s was not found", versionField) 83 } 84 key := jsonResult[idField].(string) 85 // create the return version from the version field in the JSON 86 87 returnVersion, returnMetadata, err := decodeVersionAndMetadata(jsonResult[versionField].(string)) 88 if err != nil { 89 return nil, err 90 } 91 var revision string 92 if jsonResult[revField] != nil { 93 revision = jsonResult[revField].(string) 94 } 95 96 // remove the _id, _rev and version fields 97 delete(jsonResult, idField) 98 delete(jsonResult, revField) 99 delete(jsonResult, versionField) 100 101 // handle binary or json data 102 if doc.Attachments != nil { // binary attachment 103 // get binary data from attachment 104 for _, attachment := range doc.Attachments { 105 if attachment.Name == binaryWrapper { 106 returnValue = attachment.AttachmentBytes 107 } 108 } 109 } else { 110 // marshal the returned JSON data. 111 if returnValue, err = json.Marshal(jsonResult); err != nil { 112 return nil, err 113 } 114 } 115 return &keyValue{ 116 key, revision, 117 &statedb.VersionedValue{ 118 Value: returnValue, 119 Metadata: returnMetadata, 120 Version: returnVersion}, 121 }, nil 122 } 123 124 func keyValToCouchDoc(kv *keyValue) (*couchdb.CouchDoc, error) { 125 type kvType int32 126 const ( 127 kvTypeDelete = iota 128 kvTypeJSON 129 kvTypeAttachment 130 ) 131 key, value, metadata, version := kv.key, kv.Value, kv.Metadata, kv.Version 132 jsonMap := make(jsonValue) 133 134 var kvtype kvType 135 switch { 136 case value == nil: 137 kvtype = kvTypeDelete 138 // check for the case where the jsonMap is nil, this will indicate 139 // a special case for the Unmarshal that results in a valid JSON returning nil 140 case json.Unmarshal(value, &jsonMap) == nil && jsonMap != nil: 141 kvtype = kvTypeJSON 142 if err := jsonMap.checkReservedFieldsNotPresent(); err != nil { 143 return nil, err 144 } 145 default: 146 // create an empty map, if the map is nil 147 if jsonMap == nil { 148 jsonMap = make(jsonValue) 149 } 150 kvtype = kvTypeAttachment 151 } 152 153 verAndMetadata, err := encodeVersionAndMetadata(version, metadata) 154 if err != nil { 155 return nil, err 156 } 157 // add the (version + metadata), id, revision, and delete marker (if needed) 158 jsonMap[versionField] = verAndMetadata 159 jsonMap[idField] = key 160 if kv.revision != "" { 161 jsonMap[revField] = kv.revision 162 } 163 if kvtype == kvTypeDelete { 164 jsonMap[deletedField] = true 165 } 166 jsonBytes, err := jsonMap.toBytes() 167 if err != nil { 168 return nil, err 169 } 170 couchDoc := &couchdb.CouchDoc{JSONValue: jsonBytes} 171 if kvtype == kvTypeAttachment { 172 attachment := &couchdb.AttachmentInfo{} 173 attachment.AttachmentBytes = value 174 attachment.ContentType = "application/octet-stream" 175 attachment.Name = binaryWrapper 176 attachments := append([]*couchdb.AttachmentInfo{}, attachment) 177 couchDoc.Attachments = attachments 178 } 179 return couchDoc, nil 180 } 181 182 // couchSavepointData data for couchdb 183 type couchSavepointData struct { 184 BlockNum uint64 `json:"BlockNum"` 185 TxNum uint64 `json:"TxNum"` 186 } 187 188 func encodeSavepoint(height *version.Height) (*couchdb.CouchDoc, error) { 189 var err error 190 var savepointDoc couchSavepointData 191 // construct savepoint document 192 savepointDoc.BlockNum = height.BlockNum 193 savepointDoc.TxNum = height.TxNum 194 savepointDocJSON, err := json.Marshal(savepointDoc) 195 if err != nil { 196 err = errors.Wrap(err, "failed to marshal savepoint data") 197 logger.Errorf("%+v", err) 198 return nil, err 199 } 200 return &couchdb.CouchDoc{JSONValue: savepointDocJSON, Attachments: nil}, nil 201 } 202 203 func decodeSavepoint(couchDoc *couchdb.CouchDoc) (*version.Height, error) { 204 savepointDoc := &couchSavepointData{} 205 if err := json.Unmarshal(couchDoc.JSONValue, &savepointDoc); err != nil { 206 err = errors.Wrap(err, "failed to unmarshal savepoint data") 207 logger.Errorf("%+v", err) 208 return nil, err 209 } 210 return &version.Height{BlockNum: savepointDoc.BlockNum, TxNum: savepointDoc.TxNum}, nil 211 } 212 213 type dataformatInfo struct { 214 Version string `json:"Version"` 215 } 216 217 func encodeDataformatInfo(dataFormatVersion string) (*couchdb.CouchDoc, error) { 218 var err error 219 dataformatInfo := &dataformatInfo{ 220 Version: dataFormatVersion, 221 } 222 dataformatInfoJSON, err := json.Marshal(dataformatInfo) 223 if err != nil { 224 err = errors.Wrapf(err, "failed to marshal dataformatInfo [%#v]", dataformatInfo) 225 logger.Errorf("%+v", err) 226 return nil, err 227 } 228 return &couchdb.CouchDoc{JSONValue: dataformatInfoJSON, Attachments: nil}, nil 229 } 230 231 func decodeDataformatInfo(couchDoc *couchdb.CouchDoc) (string, error) { 232 dataformatInfo := &dataformatInfo{} 233 if err := json.Unmarshal(couchDoc.JSONValue, dataformatInfo); err != nil { 234 err = errors.Wrapf(err, "failed to unmarshal json [%#v] into dataformatInfo", couchDoc.JSONValue) 235 logger.Errorf("%+v", err) 236 return "", err 237 } 238 return dataformatInfo.Version, nil 239 } 240 241 func validateValue(value []byte) error { 242 isJSON, jsonVal := tryCastingToJSON(value) 243 if !isJSON { 244 return nil 245 } 246 return jsonVal.checkReservedFieldsNotPresent() 247 } 248 249 func validateKey(key string) error { 250 if !utf8.ValidString(key) { 251 return errors.Errorf("invalid key [%x], must be a UTF-8 string", key) 252 } 253 if strings.HasPrefix(key, "_") { 254 return errors.Errorf("invalid key [%s], cannot begin with \"_\"", key) 255 } 256 if key == "" { 257 return errors.New("invalid key. Empty string is not supported as a key by couchdb") 258 } 259 return nil 260 } 261 262 // removeJSONRevision removes the "_rev" if this is a JSON 263 func removeJSONRevision(jsonValue *[]byte) error { 264 jsonVal, err := castToJSON(*jsonValue) 265 if err != nil { 266 logger.Errorf("Failed to unmarshal couchdb JSON data: %+v", err) 267 return err 268 } 269 jsonVal.removeRevField() 270 if *jsonValue, err = jsonVal.toBytes(); err != nil { 271 logger.Errorf("Failed to marshal couchdb JSON data: %+v", err) 272 } 273 return err 274 }