github.com/holochain/holochain-proto@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/chain.go (about) 1 // Copyright (C) 2013-2017, 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 in-memory chain representation with marshaling, & validation 6 7 package holochain 8 9 import ( 10 "bytes" 11 "encoding/binary" 12 "encoding/json" 13 "errors" 14 "fmt" 15 "io" 16 "os" 17 "strconv" 18 "strings" 19 "sync" 20 "time" 21 22 ic "github.com/libp2p/go-libp2p-crypto" 23 24 . "github.com/holochain/holochain-proto/hash" 25 ) 26 27 // WalkerFn a function type for call Walk 28 type WalkerFn func(key *Hash, header *Header, entry Entry) error 29 30 var ErrHashNotFound = errors.New("hash not found") 31 var ErrIncompleteChain = errors.New("operation not allowed on incomplete chain") 32 var ErrChainLockedForBundle = errors.New("chain locked for bundle") 33 var ErrBundleNotStarted = errors.New("bundle not started") 34 35 const ( 36 ChainMarshalFlagsNone = 0x00 37 ChainMarshalFlagsNoHeaders = 0x01 38 ChainMarshalFlagsNoEntries = 0x02 39 ChainMarshalFlagsOmitDNA = 0x04 40 ChainMarshalFlagsNoPrivate = 0x08 41 ChainMarshalPrivateEntryRedacted = "%%PRIVATE ENTRY REDACTED%%" 42 ) 43 44 type Bundle struct { 45 idx int 46 userParam string 47 chain *Chain 48 sharing []CommittingAction 49 } 50 51 // Chain structure for providing in-memory access to chain data, entries headers and hashes 52 type Chain struct { 53 Hashes []Hash 54 Headers []*Header 55 Entries []Entry 56 TypeTops map[string]int // pointer to index of top of a given type 57 Hmap map[Hash]int // map header hashes to index number 58 Emap map[Hash]int // map entry hashes to index number 59 60 //--- 61 62 s *os.File // if this stream is not nil, new entries will get marshaled to it 63 hashSpec HashSpec 64 lk sync.RWMutex 65 bundle *Bundle // non-nil when this chain has a bundle in progress 66 bundleOf *Chain // non-nil if this chain is a bundle of a different chain 67 } 68 69 // NewChain creates and empty chain 70 func NewChain(hashSpec HashSpec) (chain *Chain) { 71 c := Chain{ 72 Headers: make([]*Header, 0), 73 Entries: make([]Entry, 0), 74 Hashes: make([]Hash, 0), 75 TypeTops: make(map[string]int), 76 Hmap: make(map[Hash]int), 77 Emap: make(map[Hash]int), 78 hashSpec: hashSpec, 79 } 80 chain = &c 81 return 82 } 83 84 // NewChainFromFile creates a chain from a file, loading any data there, 85 // and setting it to be persisted to. If no file exists it will be created. 86 func NewChainFromFile(spec HashSpec, path string) (c *Chain, err error) { 87 defer func() { 88 if err != nil { 89 Debugf("error loading chain :%s", err.Error()) 90 } 91 }() 92 c = NewChain(spec) 93 94 var f *os.File 95 if FileExists(path) { 96 f, err = os.Open(path) 97 if err != nil { 98 return 99 } 100 var i int 101 for { 102 var header *Header 103 var e Entry 104 header, e, err = readPair(ChainMarshalFlagsNone, f) 105 if err != nil && err.Error() == "EOF" { 106 err = nil 107 break 108 } 109 if err != nil { 110 Debugf("error reading pair:%s", err.Error()) 111 return 112 } 113 c.addPair(header, e, i) 114 i++ 115 } 116 f.Close() 117 i-- 118 // if we read anything then we have to calculate the final hash and add it 119 if i >= 0 { 120 hd := c.Headers[i] 121 var hash Hash 122 123 // hash the header 124 hash, _, err = hd.Sum(spec) 125 if err != nil { 126 return 127 } 128 129 c.Hashes = append(c.Hashes, hash) 130 c.Hmap[hash] = i 131 132 // finally validate that it all hashes out correctly 133 /* err = c.Validate(h) 134 if err != nil { 135 return 136 } 137 */ 138 } 139 140 f, err = os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0600) 141 if err != nil { 142 return 143 } 144 } else { 145 f, err = os.Create(path) 146 if err != nil { 147 return 148 } 149 } 150 c.s = f 151 return 152 } 153 154 // Top returns the latest header 155 func (c *Chain) Top() (header *Header) { 156 return c.Nth(0) 157 } 158 159 // Nth returns the nth latest header 160 func (c *Chain) Nth(n int) (header *Header) { 161 c.lk.RLock() 162 defer c.lk.RUnlock() 163 l := len(c.Headers) 164 if l-n > 0 { 165 header = c.Headers[l-n-1] 166 } 167 return 168 } 169 170 // TopType returns the latest header of a given type 171 func (c *Chain) TopType(entryType string) (hash *Hash, header *Header) { 172 c.lk.RLock() 173 defer c.lk.RUnlock() 174 i, ok := c.TypeTops[entryType] 175 if ok { 176 header = c.Headers[i] 177 var hs = c.Hashes[i].Clone() 178 hash = &hs 179 } 180 return 181 } 182 183 // AddEntry creates a new header and adds it to a chain 184 func (c *Chain) AddEntry(now time.Time, entryType string, e Entry, privKey ic.PrivKey) (hash Hash, err error) { 185 c.lk.Lock() 186 defer c.lk.Unlock() 187 var l int 188 var header *Header 189 now = now.Round(0) 190 l, hash, header, err = c.prepareHeader(now, entryType, e, privKey, NullHash()) 191 if err == nil { 192 err = c.addEntry(l, hash, header, e) 193 } 194 return 195 } 196 197 // prepareHeader builds a header that could be added to the chain. 198 // Not thread safe, this must be called with the chain locked for writing so something else 199 // doesn't get inserted 200 func (c *Chain) prepareHeader(now time.Time, entryType string, e Entry, privKey ic.PrivKey, change Hash) (entryIdx int, hash Hash, header *Header, err error) { 201 202 if c.BundleStarted() != nil { 203 err = ErrChainLockedForBundle 204 return 205 } 206 // get the previous hashes 207 var ph, pth Hash 208 209 l := len(c.Hashes) 210 if l == 0 { 211 if c.bundleOf != nil { 212 l := len(c.bundleOf.Hashes) 213 if l == 0 { 214 ph = NullHash() 215 } else { 216 ph = c.bundleOf.Hashes[l-1] 217 } 218 } else { 219 ph = NullHash() 220 } 221 } else { 222 ph = c.Hashes[l-1] 223 } 224 225 i, ok := c.TypeTops[entryType] 226 if !ok { 227 if c.bundleOf != nil { 228 i, ok = c.bundleOf.TypeTops[entryType] 229 if !ok { 230 pth = NullHash() 231 } else { 232 pth = c.bundleOf.Hashes[i] 233 } 234 } else { 235 pth = NullHash() 236 } 237 } else { 238 pth = c.Hashes[i] 239 } 240 241 hash, header, err = newHeader(c.hashSpec, now, entryType, e, privKey, ph, pth, change) 242 if err != nil { 243 return 244 } 245 entryIdx = l 246 return 247 } 248 249 // addEntry, low level entry add, not thread safe, must call c.lock in the calling funciton 250 func (c *Chain) addEntry(entryIdx int, hash Hash, header *Header, e Entry) (err error) { 251 if c.BundleStarted() != nil { 252 err = ErrChainLockedForBundle 253 return 254 } 255 l := len(c.Hashes) 256 if l != entryIdx { 257 err = errors.New("entry indexes don't match can't create new entry") 258 return 259 } 260 261 if l != len(c.Entries) { 262 err = ErrIncompleteChain 263 return 264 } 265 266 var g GobEntry 267 g = *e.(*GobEntry) 268 269 c.Hashes = append(c.Hashes, hash) 270 c.Headers = append(c.Headers, header) 271 c.Entries = append(c.Entries, &g) 272 c.TypeTops[header.Type] = entryIdx 273 c.Emap[header.EntryLink] = entryIdx 274 c.Hmap[hash] = entryIdx 275 276 if c.s != nil { 277 err = writePair(c.s, header, &g) 278 } 279 280 return 281 } 282 283 // Get returns the header of a given hash 284 func (c *Chain) Get(h Hash) (header *Header, err error) { 285 c.lk.RLock() 286 defer c.lk.RUnlock() 287 i, ok := c.Hmap[h] 288 if ok { 289 header = c.Headers[i] 290 } else { 291 err = ErrHashNotFound 292 } 293 return 294 } 295 296 // GetEntry returns the entry of a given entry hash 297 func (c *Chain) GetEntry(h Hash) (entry Entry, entryType string, err error) { 298 c.lk.RLock() 299 defer c.lk.RUnlock() 300 i, ok := c.Emap[h] 301 if ok { 302 entry = c.Entries[i] 303 entryType = c.Headers[i].Type 304 } else { 305 err = ErrHashNotFound 306 } 307 return 308 } 309 310 // GetEntryHeader returns the header of a given entry hash 311 func (c *Chain) GetEntryHeader(h Hash) (header *Header, err error) { 312 c.lk.RLock() 313 defer c.lk.RUnlock() 314 i, ok := c.Emap[h] 315 if ok { 316 header = c.Headers[i] 317 } else { 318 err = ErrHashNotFound 319 } 320 return 321 } 322 323 func writePair(writer io.Writer, header *Header, entry Entry) (err error) { 324 if header != nil { 325 err = MarshalHeader(writer, header) 326 if err != nil { 327 return 328 } 329 } 330 if entry != nil { 331 err = MarshalEntry(writer, entry) 332 } 333 return 334 } 335 336 func readPair(flags int64, reader io.Reader) (header *Header, entry Entry, err error) { 337 if (flags & ChainMarshalFlagsNoHeaders) == 0 { 338 var hd Header 339 err = UnmarshalHeader(reader, &hd, 34) 340 if err != nil { 341 return 342 } 343 header = &hd 344 } 345 if (flags & ChainMarshalFlagsNoEntries) == 0 { 346 entry, err = UnmarshalEntry(reader) 347 } 348 return 349 } 350 351 func contains(s []string, e string) bool { 352 for _, a := range s { 353 if a == e { 354 return true 355 } 356 } 357 return false 358 } 359 360 func filterPass(index int, header *Header, whitelistTypes []string, blacklistTypes []string) bool { 361 pass := true 362 if len(whitelistTypes) > 0 { 363 pass = contains(whitelistTypes, header.Type) 364 } 365 366 if len(blacklistTypes) > 0 { 367 pass = pass && !contains(blacklistTypes, header.Type) 368 } 369 return pass 370 } 371 372 type ChainPair struct { 373 Header *Header 374 Entry Entry 375 } 376 377 // MarshalChain serializes a chain data to a writer 378 func (c *Chain) MarshalChain(writer io.Writer, flags int64, whitelistTypes []string, privateTypes []string) (err error) { 379 c.lk.RLock() 380 defer c.lk.RUnlock() 381 382 if len(c.Headers) != len(c.Entries) { 383 err = ErrIncompleteChain 384 return 385 } 386 387 err = binary.Write(writer, binary.LittleEndian, flags) 388 if err != nil { 389 return err 390 } 391 392 var pairsToWrite []ChainPair 393 var lastHeaderToWrite int 394 395 for i, hdr := range c.Headers { 396 var empty []string 397 var e Entry 398 399 if i == 0 || filterPass(i, hdr, whitelistTypes, empty) { 400 e = c.Entries[i] 401 402 if (i == 0) && ((flags & ChainMarshalFlagsOmitDNA) != 0) { 403 e = &GobEntry{C: ""} 404 } 405 406 if (flags & ChainMarshalFlagsNoEntries) != 0 { 407 e = nil 408 } 409 410 if !filterPass(i, hdr, whitelistTypes, privateTypes) { 411 e = &GobEntry{C: ChainMarshalPrivateEntryRedacted} 412 } 413 414 if (flags & ChainMarshalFlagsNoHeaders) != 0 { 415 hdr = nil 416 } else { 417 lastHeaderToWrite = i 418 } 419 420 pairsToWrite = append(pairsToWrite, ChainPair{Header: hdr, Entry: e}) 421 } 422 } 423 424 err = binary.Write(writer, binary.LittleEndian, int64(len(pairsToWrite))) 425 if err != nil { 426 return err 427 } 428 429 for _, pair := range pairsToWrite { 430 err = writePair(writer, pair.Header, pair.Entry) 431 if err != nil { 432 return 433 } 434 } 435 436 if (flags & ChainMarshalFlagsNoHeaders) == 0 { 437 hash := c.Hashes[lastHeaderToWrite] 438 err = hash.MarshalHash(writer) 439 } 440 return 441 } 442 443 // addPair adds header and entry pairs to the chain during unmarshaling 444 // This call assumes that Hashes array is one element behind the Headers and Entries 445 // because for each pair (except the 0th) it adds the hash of the previous entry 446 // thus it also means that you must add the last Hash after you have finished calling addPair 447 func (c *Chain) addPair(header *Header, entry Entry, i int) { 448 if header != nil { 449 if i > 0 { 450 h := header.HeaderLink 451 c.Hashes = append(c.Hashes, h) 452 c.Hmap[h] = i - 1 453 } 454 c.Headers = append(c.Headers, header) 455 c.TypeTops[header.Type] = i 456 c.Emap[header.EntryLink] = i 457 } 458 if entry != nil { 459 c.Entries = append(c.Entries, entry) 460 } 461 } 462 463 // UnmarshalChain unserializes a chain from a reader 464 func UnmarshalChain(hashSpec HashSpec, reader io.Reader) (flags int64, c *Chain, err error) { 465 defer func() { 466 if err != nil { 467 Debugf("error unmarshaling chain:%s", err.Error()) 468 } 469 }() 470 c = NewChain(hashSpec) 471 err = binary.Read(reader, binary.LittleEndian, &flags) 472 if err != nil { 473 return 474 } 475 var l, i int64 476 err = binary.Read(reader, binary.LittleEndian, &l) 477 if err != nil { 478 return 479 } 480 for i = 0; i < l; i++ { 481 var header *Header 482 var e Entry 483 header, e, err = readPair(flags, reader) 484 if err != nil { 485 return 486 } 487 c.addPair(header, e, int(i)) 488 } 489 490 if (flags & ChainMarshalFlagsNoHeaders) == 0 { 491 // decode final hash 492 var h Hash 493 h, err = UnmarshalHash(reader) 494 if err != nil { 495 return 496 } 497 c.Hashes = append(c.Hashes, h) 498 c.Hmap[h] = int(i - 1) 499 } 500 return 501 } 502 503 // Walk traverses chain from most recent to first entry calling fn on each one 504 func (c *Chain) Walk(fn WalkerFn) (err error) { 505 l := len(c.Headers) 506 for i := l - 1; i >= 0; i-- { 507 err = fn(&c.Hashes[i], c.Headers[i], c.Entries[i]) 508 if err != nil { 509 return 510 } 511 } 512 return 513 } 514 515 // Validate traverses chain confirming the hashes 516 // @TODO confirm that TypeLinks are also correct 517 // @TODO confirm signatures 518 func (c *Chain) Validate(skipEntries bool) (err error) { 519 c.lk.RLock() 520 defer c.lk.RUnlock() 521 l := len(c.Headers) 522 for i := 0; i < l; i++ { 523 hd := c.Headers[i] 524 525 var hash, nexth Hash 526 // hash the header 527 hash, _, err = hd.Sum(c.hashSpec) 528 if err != nil { 529 return 530 } 531 // we can't compare top hash to next link, because it doesn't exist yet! 532 if i < l-2 { 533 nexth = c.Headers[i+1].HeaderLink 534 } else { 535 // so get it from the Hashes (even though this could be cheated) 536 nexth = c.Hashes[i] 537 } 538 539 if !hash.Equal(nexth) { 540 err = fmt.Errorf("header hash mismatch at link %d", i) 541 return 542 } 543 544 if !skipEntries { 545 var b []byte 546 b, err = c.Entries[i].Marshal() 547 if err != nil { 548 return 549 } 550 hash, err = Sum(c.hashSpec, b) 551 if err != nil { 552 return 553 } 554 555 if !hash.Equal(hd.EntryLink) { 556 err = fmt.Errorf("entry hash mismatch at link %d", i) 557 return 558 } 559 } 560 } 561 return 562 } 563 564 // String converts a chain to a textual dump of the headers and entries 565 func (c *Chain) String() string { 566 return c.Dump(0) 567 } 568 569 // Dump converts a chain to a textual dump of the headers and entries from a starting index 570 func (c *Chain) Dump(start int) string { 571 c.lk.RLock() 572 defer c.lk.RUnlock() 573 l := len(c.Headers) 574 r := "" 575 for i := start; i < l; i++ { 576 hdr := c.Headers[i] 577 hash := c.Hashes[i] 578 r += fmt.Sprintf("%s:%s @ %v\n", hdr.Type, hash, hdr.Time) 579 r += fmt.Sprintf(" Sig: %v\n", hdr.Sig) 580 r += fmt.Sprintf(" Next Header: %v\n", hdr.HeaderLink) 581 r += fmt.Sprintf(" Next %s: %v\n", hdr.Type, hdr.TypeLink) 582 r += fmt.Sprintf(" Entry: %v\n", hdr.EntryLink) 583 e := c.Entries[i] 584 switch hdr.Type { 585 case KeyEntryType: 586 r += fmt.Sprintf(" %v\n", e.(*GobEntry).C) 587 case DNAEntryType: 588 r += fmt.Sprintf(" %s\n", e.(*GobEntry).C) 589 case AgentEntryType: 590 r += fmt.Sprintf(" %v\n", e.(*GobEntry).C) 591 case MigrateEntryType: 592 r += fmt.Sprintf(" %v\n", e.(*GobEntry).C) 593 default: 594 r += fmt.Sprintf(" %v\n", e) 595 } 596 r += "\n" 597 } 598 return r 599 } 600 601 // JSON converts a chain to a json string dump of the headers and entries 602 func (c *Chain) JSON(start int) (string, error) { 603 c.lk.RLock() 604 defer c.lk.RUnlock() 605 l := len(c.Headers) 606 firstEntry := false 607 lastEntry := false 608 609 var buffer bytes.Buffer 610 611 buffer.WriteString("{") 612 613 for i := start; i < l; i++ { 614 hdr := c.Headers[i] 615 hash := c.Hashes[i] 616 617 e := c.Entries[i] 618 lastEntry = (i == l-1) 619 620 switch hdr.Type { 621 case KeyEntryType, AgentEntryType, DNAEntryType: 622 buffer.WriteString("\"" + hdr.Type + "\":") 623 appendEntryAsJSON(&buffer, hdr, &hash, e.(*GobEntry)) 624 if !lastEntry { 625 buffer.WriteString(",") 626 } 627 default: 628 if !firstEntry { 629 buffer.WriteString("\"entries\":[") 630 firstEntry = true 631 } 632 633 appendEntryAsJSON(&buffer, hdr, &hash, e.(*GobEntry)) 634 635 if lastEntry { 636 buffer.WriteString("]") 637 } else { 638 buffer.WriteString(",") 639 } 640 } 641 } 642 643 buffer.WriteString("}") 644 return PrettyPrintJSON(buffer.Bytes()) 645 } 646 647 // Dot converts a chain to a GraphViz 'dot' format dump of the headers and entries 648 func (c *Chain) Dot(start int) (dump string, err error) { 649 c.lk.RLock() 650 defer c.lk.RUnlock() 651 l := len(c.Headers) 652 653 var buffer bytes.Buffer 654 655 buffer.WriteString("digraph chain {\n") 656 buffer.WriteString("graph [splines=line];\n") 657 buffer.WriteString(`node [shape=record fontname="Arial",fontsize="10",style="rounded, filled",penwidth=2,fontcolor="#c5c5c5",color="#8d00ff",fillcolor="#181818"];` + "\n") 658 buffer.WriteString(`edge [penwidth=2, color="#8d00ff"];` + "\n") 659 660 for i := start; i < l; i++ { 661 hdr := c.Headers[i] 662 hash := c.Hashes[i] 663 headerLabel := "" 664 contentLabel := "" 665 contentBody := "" 666 667 if i == 0 { 668 headerLabel = ": GENESIS" 669 } 670 671 // header 672 buffer.WriteString(fmt.Sprintf("header%d [label=<{HEADER %d%s|\n", i, i, headerLabel)) 673 buffer.WriteString(fmt.Sprintf("{Type|%s}|\n", hdr.Type)) 674 buffer.WriteString(fmt.Sprintf("{Hash|%s}|\n", hash)) 675 buffer.WriteString(fmt.Sprintf("{Timestamp|%v}|\n", hdr.Time)) 676 buffer.WriteString(fmt.Sprintf("{Next Header|%v}|\n", hdr.HeaderLink)) 677 buffer.WriteString(fmt.Sprintf("{Next|%s: %v}|\n", hdr.Type, hdr.TypeLink)) 678 buffer.WriteString(fmt.Sprintf("{Entry|%v}\n", hdr.EntryLink)) 679 buffer.WriteString("}>];\n") 680 681 if i == 0 { 682 contentLabel = "HOLOCHAIN DNA" 683 } else if i == 1 { 684 contentLabel = "AGENT ID" 685 } else { 686 contentLabel = fmt.Sprintf("ENTRY %d", i) 687 } 688 689 if i == 0 { 690 contentBody = "See dna.json" 691 } else { 692 e := c.Entries[i] 693 contentBody = fmt.Sprintf("%s", e.(*GobEntry).C) 694 contentBody = strings.Replace(contentBody, `{"`, `\{"`, -1) 695 contentBody = strings.Replace(contentBody, `"}`, `"\}`, -1) 696 contentBody = strings.Replace(contentBody, `:[`, `:[<br/>`, -1) 697 contentBody = strings.Replace(contentBody, `]}`, `]\}`, -1) 698 contentBody = strings.Replace(contentBody, `,`, `,<br/>`, -1) 699 } 700 701 buffer.WriteString(fmt.Sprintf("content%d [label=<{%s|%s}>];\n", i, contentLabel, contentBody)) 702 703 // arrows 704 buffer.WriteString(fmt.Sprintf("header%d->content%d;\n", i, i)) 705 if i < l-1 { 706 buffer.WriteString(fmt.Sprintf("header%d->header%d;\n", i, i+1)) 707 } 708 } 709 710 buffer.WriteString("}") 711 return buffer.String(), nil 712 } 713 714 // Length returns the number of entries in the chain 715 func (c *Chain) Length() int { 716 return len(c.Headers) 717 } 718 719 // BundleStarted returns the index of the chain item before the bundle or 0 if no bundle is active 720 func (c *Chain) BundleStarted() *Bundle { 721 return c.bundle 722 } 723 724 // StartBundle marks a bundle start point and returns an error if already started 725 func (c *Chain) StartBundle(userParam interface{}) (err error) { 726 j, err := json.Marshal(userParam) 727 if err != nil { 728 return 729 } 730 c.lk.RLock() 731 defer c.lk.RUnlock() 732 if c.BundleStarted() != nil { 733 err = errors.New("Bundle already started") 734 return 735 } 736 bundle := Bundle{ 737 idx: c.Length() - 1, 738 chain: NewChain(c.hashSpec), 739 userParam: string(j), 740 } 741 bundle.sharing = make([]CommittingAction, 0) 742 bundle.chain.bundleOf = c 743 c.bundle = &bundle 744 return 745 } 746 747 // CloseBundle closes a started bundle and if commit 748 // copies entries from the bundle onto the chain 749 func (c *Chain) CloseBundle(commit bool) (err error) { 750 c.lk.RLock() 751 defer c.lk.RUnlock() 752 if c.bundle == nil { 753 err = ErrBundleNotStarted 754 return 755 } 756 bundle := c.bundle 757 c.bundle = nil 758 if commit { 759 l := c.Length() 760 for i, header := range bundle.chain.Headers { 761 err = c.addEntry(i+l, bundle.chain.Hashes[i], header, bundle.chain.Entries[i]) 762 if err != nil { 763 return 764 } 765 } 766 } 767 return 768 } 769 770 // Close the chain's file 771 func (c *Chain) Close() { 772 c.s.Close() 773 c.s = nil 774 } 775 776 func appendEntryAsJSON(buffer *bytes.Buffer, hdr *Header, hash *Hash, g *GobEntry) { 777 buffer.WriteString("{") 778 appendEntryHeaderAsJSON(buffer, hdr, hash) 779 buffer.WriteString(",") 780 appendEntryContentAsJSON(buffer, hdr, g) 781 buffer.WriteString("}") 782 } 783 784 func appendEntryHeaderAsJSON(buffer *bytes.Buffer, hdr *Header, hash *Hash) { 785 buffer.WriteString("\"header\":{") 786 buffer.WriteString("\"type\":" + "\"" + hdr.Type + "\",") 787 buffer.WriteString(fmt.Sprintf("\"signature\":\"%v\",", hdr.Sig)) 788 buffer.WriteString(fmt.Sprintf("\"hash\":\"%v\",", hash)) 789 buffer.WriteString(fmt.Sprintf("\"time\":\"%v\",", hdr.Time)) 790 buffer.WriteString(fmt.Sprintf("\"nextHeader\":\"%v\",", hdr.HeaderLink)) 791 buffer.WriteString(fmt.Sprintf("\"next\":\"%v: %v\",", hdr.Type, hdr.TypeLink)) 792 buffer.WriteString(fmt.Sprintf("\"entry\":\"%v\"", hdr.EntryLink)) 793 buffer.WriteString("}") 794 } 795 796 func appendEntryContentAsJSON(buffer *bytes.Buffer, hdr *Header, g *GobEntry) { 797 buffer.WriteString("\"content\":") 798 buffer.WriteString(jsonEncode(g)) 799 } 800 801 func jsonEncode(g *GobEntry) (encodedValue string) { 802 var err error 803 switch g.C.(type) { 804 case []byte: 805 var decoded map[string]interface{} 806 content := fmt.Sprintf("%s", g.C) 807 buffer := bytes.NewBufferString(content) 808 err = Decode(buffer, "json", &decoded) 809 810 if err != nil { 811 // DNA content may be TOML or YAML encoded, so escape it to make it JSON safe. 812 // (an improvement could be to convert from TOML/YAML to JSON) 813 encodedValue = strconv.Quote(content) 814 } else { 815 // DNA content is already in JSON so just use it. 816 encodedValue = content 817 } 818 default: 819 var result []byte 820 result, err = json.Marshal(g.C) 821 if err != nil { 822 encodedValue = strconv.Quote(err.Error()) 823 return 824 } 825 encodedValue = string(result) 826 } 827 return 828 }