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 }