aboutsummaryrefslogtreecommitdiffstats
path: root/swarm/storage/mru/metadata.go
blob: 50911489596afa8f61891bb28f13bea96b1faebd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package mru

import (
    "encoding/binary"
    "hash"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/swarm/storage"
)

// ResourceMetadata encapsulates the immutable information about a mutable resource :)
// once serialized into a chunk, the resource can be retrieved by knowing its content-addressed rootAddr
type ResourceMetadata struct {
    StartTime Timestamp      // time at which the resource starts to be valid
    Frequency uint64         // expected update frequency for the resource
    Name      string         // name of the resource, for the reference of the user or to disambiguate resources with same starttime, frequency, owneraddr
    Owner     common.Address // public address of the resource owner
}

const frequencyLength = 8 // sizeof(uint64)
const nameLengthLength = 1

// Resource metadata chunk layout:
// 4 prefix bytes (chunkPrefixLength). The first two set to zero. The second two indicate the length
// Timestamp: timestampLength bytes
// frequency: frequencyLength bytes
// name length: nameLengthLength bytes
// name (variable length, can be empty, up to 255 bytes)
// ownerAddr: common.AddressLength
const minimumMetadataLength = chunkPrefixLength + timestampLength + frequencyLength + nameLengthLength + common.AddressLength

// binaryGet populates the resource metadata from a byte array
func (r *ResourceMetadata) binaryGet(serializedData []byte) error {
    if len(serializedData) < minimumMetadataLength {
        return NewErrorf(ErrInvalidValue, "Metadata chunk to deserialize is too short. Expected at least %d. Got %d.", minimumMetadataLength, len(serializedData))
    }

    // first two bytes must be set to zero to indicate metadata chunks, so enforce this.
    if serializedData[0] != 0 || serializedData[1] != 0 {
        return NewError(ErrCorruptData, "Invalid metadata chunk")
    }

    cursor := 2
    metadataLength := int(binary.LittleEndian.Uint16(serializedData[cursor : cursor+2])) // metadataLength does not include the 4 prefix bytes
    if metadataLength+chunkPrefixLength != len(serializedData) {
        return NewErrorf(ErrCorruptData, "Incorrect declared metadata length. Expected %d, got %d.", metadataLength+chunkPrefixLength, len(serializedData))
    }

    cursor += 2

    if err := r.StartTime.binaryGet(serializedData[cursor : cursor+timestampLength]); err != nil {
        return err
    }
    cursor += timestampLength

    r.Frequency = binary.LittleEndian.Uint64(serializedData[cursor : cursor+frequencyLength])
    cursor += frequencyLength

    nameLength := int(serializedData[cursor])
    if nameLength+minimumMetadataLength > len(serializedData) {
        return NewErrorf(ErrInvalidValue, "Metadata chunk to deserialize is too short when decoding resource name. Expected at least %d. Got %d.", nameLength+minimumMetadataLength, len(serializedData))
    }
    cursor++
    r.Name = string(serializedData[cursor : cursor+nameLength])
    cursor += nameLength

    copy(r.Owner[:], serializedData[cursor:])
    cursor += common.AddressLength
    if cursor != len(serializedData) {
        return NewErrorf(ErrInvalidValue, "Metadata chunk has leftover data after deserialization. %d left to read", len(serializedData)-cursor)
    }
    return nil
}

// binaryPut encodes the metadata into a byte array
func (r *ResourceMetadata) binaryPut(serializedData []byte) error {
    metadataChunkLength := r.binaryLength()
    if len(serializedData) != metadataChunkLength {
        return NewErrorf(ErrInvalidValue, "Need a slice of exactly %d bytes to serialize this metadata, but got a slice of size %d.", metadataChunkLength, len(serializedData))
    }

    // root chunk has first two bytes both set to 0, which distinguishes from update bytes
    // therefore, skip the first two bytes of a zero-initialized array.
    cursor := 2
    binary.LittleEndian.PutUint16(serializedData[cursor:cursor+2], uint16(metadataChunkLength-chunkPrefixLength)) // metadataLength does not include the 4 prefix bytes
    cursor += 2

    r.StartTime.binaryPut(serializedData[cursor : cursor+timestampLength])
    cursor += timestampLength

    binary.LittleEndian.PutUint64(serializedData[cursor:cursor+frequencyLength], r.Frequency)
    cursor += frequencyLength

    // Encode the name string as a 1 byte length followed by the encoded string.
    // Longer strings will be truncated.
    nameLength := len(r.Name)
    if nameLength > 255 {
        nameLength = 255
    }
    serializedData[cursor] = uint8(nameLength)
    cursor++
    copy(serializedData[cursor:cursor+nameLength], []byte(r.Name[:nameLength]))
    cursor += nameLength

    copy(serializedData[cursor:cursor+common.AddressLength], r.Owner[:])
    cursor += common.AddressLength

    return nil
}

func (r *ResourceMetadata) binaryLength() int {
    return minimumMetadataLength + len(r.Name)
}

// serializeAndHash returns the root chunk addr and metadata hash that help identify and ascertain ownership of this resource
// returns the serialized metadata as a byproduct of having to hash it.
func (r *ResourceMetadata) serializeAndHash() (rootAddr, metaHash []byte, chunkData []byte, err error) {

    chunkData = make([]byte, r.binaryLength())
    if err := r.binaryPut(chunkData); err != nil {
        return nil, nil, nil, err
    }
    rootAddr, metaHash = metadataHash(chunkData)
    return rootAddr, metaHash, chunkData, nil

}

// creates a metadata chunk out of a resourceMetadata structure
func (metadata *ResourceMetadata) newChunk() (chunk storage.Chunk, metaHash []byte, err error) {
    // the metadata chunk contains a timestamp of when the resource starts to be valid
    // and also how frequently it is expected to be updated
    // from this we know at what time we should look for updates, and how often
    // it also contains the name of the resource, so we know what resource we are working with

    // the key (rootAddr) of the metadata chunk is content-addressed
    // if it wasn't we couldn't replace it later
    // resolving this relationship is left up to external agents (for example ENS)
    rootAddr, metaHash, chunkData, err := metadata.serializeAndHash()
    if err != nil {
        return nil, nil, err
    }

    // make the chunk and send it to swarm
    chunk = storage.NewChunk(rootAddr, chunkData)

    return chunk, metaHash, nil
}

// metadataHash returns the metadata chunk root address and metadata hash
// that help identify and ascertain ownership of this resource
// We compute it as rootAddr = H(ownerAddr, H(metadata))
// Where H() is SHA3
// metadata are all the metadata fields, except ownerAddr
// ownerAddr is the public address of the resource owner
// Update chunks must carry a rootAddr reference and metaHash in order to be verified
// This way, a node that receives an update can check the signature, recover the public address
// and check the ownership by computing H(ownerAddr, metaHash) and comparing it to the rootAddr
// the resource is claiming to update without having to lookup the metadata chunk.
// see verifyResourceOwnerhsip in signedupdate.go
func metadataHash(chunkData []byte) (rootAddr, metaHash []byte) {
    hasher := hashPool.Get().(hash.Hash)
    defer hashPool.Put(hasher)
    hasher.Reset()
    hasher.Write(chunkData[:len(chunkData)-common.AddressLength])
    metaHash = hasher.Sum(nil)
    hasher.Reset()
    hasher.Write(metaHash)
    hasher.Write(chunkData[len(chunkData)-common.AddressLength:])
    rootAddr = hasher.Sum(nil)
    return
}