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  }