github.com/metacurrency/holochain@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/entry.go (about)

     1  // Copyright (C) 2013-2018, The MetaCurrency Project (Eric Harris-Braun, Arthur Brock, et. al.)
     2  // Use of this source code is governed by GPLv3 found in the LICENSE file
     3  //----------------------------------------------------------------------------------------
     4  
     5  // implements chain entry structures and functions
     6  
     7  package holochain
     8  
     9  import (
    10  	"encoding/binary"
    11  	"encoding/json"
    12  	. "github.com/holochain/holochain-proto/hash"
    13  	"github.com/lestrrat/go-jsval"
    14  	"io"
    15  	"strings"
    16  	"fmt"
    17  	"errors"
    18  )
    19  
    20  const (
    21  	SysEntryTypePrefix     = "%"
    22  	VirtualEntryTypePrefix = "%%"
    23  
    24  	// Entry type formats
    25  
    26  	DataFormatJSON    = "json"
    27  	DataFormatString  = "string"
    28  	DataFormatRawJS   = "js"
    29  	DataFormatRawZygo = "zygo"
    30  
    31  	// Entry sharing types
    32  
    33  	Public  = "public"
    34  	Partial = "partial"
    35  	Private = "private"
    36  )
    37  
    38  // EntryDef struct holds an entry definition
    39  type EntryDef struct {
    40  	Name       string
    41  	DataFormat string
    42  	Sharing    string
    43  	Schema     string
    44  	validator  SchemaValidator
    45  }
    46  
    47  func (def EntryDef) isSharingPublic() bool {
    48  	return def.Sharing == Public || def.DataFormat == DataFormatLinks
    49  }
    50  
    51  // Entry describes serialization and deserialziation of entry data
    52  type Entry interface {
    53  	Marshal() ([]byte, error)
    54  	Unmarshal([]byte) error
    55  	Content() interface{}
    56  	Sum(s HashSpec) (hash Hash, err error)
    57  }
    58  
    59  // SchemaValidator interface for schema validation
    60  type SchemaValidator interface {
    61  	Validate(interface{}) error
    62  }
    63  
    64  // GobEntry is a structure for implementing Gob encoding of Entry content
    65  type GobEntry struct {
    66  	C interface{}
    67  }
    68  
    69  // JSONEntry is a structure for implementing JSON encoding of Entry content
    70  type JSONEntry struct {
    71  	C interface{}
    72  }
    73  
    74  // IsSysEntry returns true if the entry type is system defined
    75  func (def *EntryDef) IsSysEntry() bool {
    76  	return strings.HasPrefix(def.Name, SysEntryTypePrefix)
    77  }
    78  
    79  // IsVirtualEntry returns true if the entry type is virtual
    80  func (def *EntryDef) IsVirtualEntry() bool {
    81  	return strings.HasPrefix(def.Name, VirtualEntryTypePrefix)
    82  }
    83  
    84  // MarshalEntry serializes an entry to a writer
    85  func MarshalEntry(writer io.Writer, e Entry) (err error) {
    86  	var b []byte
    87  	b, err = e.Marshal()
    88  	l := uint64(len(b))
    89  	err = binary.Write(writer, binary.LittleEndian, l)
    90  	if err != nil {
    91  		return
    92  	}
    93  	err = binary.Write(writer, binary.LittleEndian, b)
    94  	return
    95  }
    96  
    97  // UnmarshalEntry unserializes an entry from a reader
    98  func UnmarshalEntry(reader io.Reader) (e Entry, err error) {
    99  	var l uint64
   100  	err = binary.Read(reader, binary.LittleEndian, &l)
   101  	if err != nil {
   102  		return
   103  	}
   104  	var b = make([]byte, l)
   105  	err = binary.Read(reader, binary.LittleEndian, b)
   106  	if err != nil {
   107  		return
   108  	}
   109  
   110  	var g GobEntry
   111  	err = g.Unmarshal(b)
   112  
   113  	e = &g
   114  	return
   115  }
   116  
   117  // implementation of Entry interface with gobs
   118  
   119  func (e *GobEntry) Marshal() (b []byte, err error) {
   120  	b, err = ByteEncoder(&e.C)
   121  	return
   122  }
   123  func (e *GobEntry) Unmarshal(b []byte) (err error) {
   124  	err = ByteDecoder(b, &e.C)
   125  	return
   126  }
   127  
   128  func (e *GobEntry) Content() interface{} { return e.C }
   129  
   130  func (e *GobEntry) Sum(s HashSpec) (h Hash, err error) {
   131  	// encode the entry into bytes
   132  	m, err := e.Marshal()
   133  	if err != nil {
   134  		return
   135  	}
   136  
   137  	// calculate the entry's hash and store it in the header
   138  	h, err = Sum(s, m)
   139  	if err != nil {
   140  		return
   141  	}
   142  
   143  	return
   144  }
   145  
   146  // implementation of Entry interface with JSON
   147  
   148  func (e *JSONEntry) Marshal() (b []byte, err error) {
   149  	j, err := json.Marshal(e.C)
   150  	if err != nil {
   151  		return
   152  	}
   153  	b = []byte(j)
   154  	return
   155  }
   156  func (e *JSONEntry) Unmarshal(b []byte) (err error) {
   157  	err = json.Unmarshal(b, &e.C)
   158  	return
   159  }
   160  func (e *JSONEntry) Content() interface{} { return e.C }
   161  
   162  type JSONSchemaValidator struct {
   163  	v *jsval.JSVal
   164  }
   165  
   166  // implementation of SchemaValidator with JSONSchema
   167  
   168  func (v *JSONSchemaValidator) Validate(entry interface{}) (err error) {
   169  	err = v.v.Validate(entry)
   170  	return
   171  }
   172  
   173  // BuildJSONSchemaValidator builds a validator in an EntryDef
   174  func (d *EntryDef) BuildJSONSchemaValidator(path string) (err error) {
   175  	validator, err := BuildJSONSchemaValidatorFromFile(path)
   176  	if err != nil {
   177  		return
   178  	}
   179  	validator.v.SetName(d.Name)
   180  	d.validator = validator
   181  	return
   182  }
   183  
   184  func (d *EntryDef) BuildJSONSchemaValidatorFromString(schema string) (err error) {
   185  	validator, err := BuildJSONSchemaValidatorFromString(schema)
   186  	if err != nil {
   187  		return
   188  	}
   189  	validator.v.SetName(d.Name)
   190  	d.validator = validator
   191  	return
   192  }
   193  
   194  // sysValidateEntry does system level validation for adding an entry (put or commit)
   195  // It checks that entry is not nil, and that it conforms to the entry schema in the definition
   196  // if it's a Links entry that the contents are correctly structured
   197  // if it's a new agent entry, that identity matches the defined identity structure
   198  // if it's a key that the structure is actually a public key
   199  func sysValidateEntry(h *Holochain, def *EntryDef, entry Entry, pkg *Package) (err error) {
   200  	switch def.Name {
   201  	case DNAEntryType:
   202  		err = ErrNotValidForDNAType
   203  		return
   204  	case KeyEntryType:
   205  		b58pk, ok := entry.Content().(string)
   206  		if !ok || !isValidPubKey(b58pk) {
   207  			err = ValidationFailed(ValidationFailureBadPublicKeyFormat)
   208  			return
   209  		}
   210  	case AgentEntryType:
   211  		j, ok := entry.Content().(string)
   212  		if !ok {
   213  			err = ValidationFailedErr
   214  			return
   215  		}
   216  		ae, _ := AgentEntryFromJSON(j)
   217  
   218  		// check that the public key is unmarshalable
   219  		if !isValidPubKey(ae.PublicKey) {
   220  			err = ValidationFailed(ValidationFailureBadPublicKeyFormat)
   221  			return err
   222  		}
   223  
   224  		// if there's a revocation, confirm that has a reasonable format
   225  		if ae.Revocation != "" {
   226  			revocation := &SelfRevocation{}
   227  			err := revocation.Unmarshal(ae.Revocation)
   228  			if err != nil {
   229  				err = ValidationFailed(ValidationFailureBadRevocationFormat)
   230  				return err
   231  			}
   232  		}
   233  
   234  		// TODO check anything in the package
   235  	case HeadersEntryType:
   236  		// TODO check signatures!
   237  	case DelEntryType:
   238  		// TODO checks according to CRDT configuration?
   239  	}
   240  
   241  	if entry == nil {
   242  		err = ValidationFailed(ErrNilEntryInvalid.Error())
   243  		return
   244  	}
   245  
   246  	// see if there is a schema validator for the entry type and validate it if so
   247  	if def.validator != nil {
   248  		var input interface{}
   249  		if def.DataFormat == DataFormatJSON {
   250  			if err = json.Unmarshal([]byte(entry.Content().(string)), &input); err != nil {
   251  				return
   252  			}
   253  		} else {
   254  			input = entry
   255  		}
   256  		h.Debugf("Validating %v against schema", input)
   257  		if err = def.validator.Validate(input); err != nil {
   258  			err = ValidationFailed(err.Error())
   259  			return
   260  		}
   261  		if def == DelEntryDef {
   262  			// @TODO refactor and use in other sys types
   263  			// @see https://github.com/holochain/holochain-proto/issues/733
   264  			hashValue, ok := input.(map[string]interface{})["Hash"].(string)
   265  			if !ok {
   266  				err = ValidationFailed("expected string!")
   267  				return
   268  			}
   269  			_, err = NewHash(hashValue)
   270  			if err != nil {
   271  				err = ValidationFailed(fmt.Sprintf("Error (%s) when decoding Hash value '%s'", err.Error(), hashValue))
   272  				return
   273  			}
   274  		}
   275  		if def == MigrateEntryDef {
   276  			// @TODO refactor with above
   277  			// @see https://github.com/holochain/holochain-proto/issues/733
   278  			dnaHashValue, ok := input.(map[string]interface{})["DNAHash"].(string)
   279  			if !ok {
   280  				err = ValidationFailed("expected string!")
   281  				return
   282  			}
   283  			_, err = NewHash(dnaHashValue)
   284  			if err != nil {
   285  				err = ValidationFailed(fmt.Sprintf("Error (%s) when decoding DNAHash value '%s'", err.Error(), dnaHashValue))
   286  				return
   287  			}
   288  
   289  			keyValue, ok := input.(map[string]interface{})["Key"].(string)
   290  			if !ok {
   291  				err = ValidationFailed("expected string!")
   292  				return
   293  			}
   294  			_, err = NewHash(keyValue)
   295  			if err != nil {
   296  				err = ValidationFailed(fmt.Sprintf("Error (%s) when decoding Key value '%s'", err.Error(), keyValue))
   297  				return
   298  			}
   299  
   300  			typeValue, ok := input.(map[string]interface{})["Type"].(string)
   301  			if !ok {
   302  				err = ValidationFailed("expected string!")
   303  				return
   304  			}
   305  			if !(typeValue == MigrateEntryTypeClose || typeValue == MigrateEntryTypeOpen) {
   306  				err = ValidationFailed(fmt.Sprintf("Type value '%s' must be either '%s' or '%s'", typeValue, MigrateEntryTypeOpen, MigrateEntryTypeClose))
   307  				return
   308  			}
   309  		}
   310  	} else if def.DataFormat == DataFormatLinks {
   311  		// Perform base validation on links entries, i.e. that all items exist and are of the right types
   312  		// so first unmarshall the json, and then check that the hashes are real.
   313  		var l struct{ Links []map[string]string }
   314  		err = json.Unmarshal([]byte(entry.Content().(string)), &l)
   315  		if err != nil {
   316  			err = fmt.Errorf("invalid links entry, invalid json: %v", err)
   317  			return
   318  		}
   319  		if len(l.Links) == 0 {
   320  			err = errors.New("invalid links entry: you must specify at least one link")
   321  			return
   322  		}
   323  		for _, link := range l.Links {
   324  			h, ok := link["Base"]
   325  			if !ok {
   326  				err = errors.New("invalid links entry: missing Base")
   327  				return
   328  			}
   329  			if _, err = NewHash(h); err != nil {
   330  				err = fmt.Errorf("invalid links entry: Base %v", err)
   331  				return
   332  			}
   333  			h, ok = link["Link"]
   334  			if !ok {
   335  				err = errors.New("invalid links entry: missing Link")
   336  				return
   337  			}
   338  			if _, err = NewHash(h); err != nil {
   339  				err = fmt.Errorf("invalid links entry: Link %v", err)
   340  				return
   341  			}
   342  			_, ok = link["Tag"]
   343  			if !ok {
   344  				err = errors.New("invalid links entry: missing Tag")
   345  				return
   346  			}
   347  		}
   348  
   349  	}
   350  	return
   351  }