aboutsummaryrefslogtreecommitdiffstats
path: root/swarm/storage/mru/request.go
blob: dd71f855d684be737766c2495d41b637681a8330 (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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
// 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 (
    "bytes"
    "encoding/json"

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

// updateRequestJSON represents a JSON-serialized UpdateRequest
type updateRequestJSON struct {
    Name      string `json:"name,omitempty"`
    Frequency uint64 `json:"frequency,omitempty"`
    StartTime uint64 `json:"startTime,omitempty"`
    Owner     string `json:"ownerAddr,omitempty"`
    RootAddr  string `json:"rootAddr,omitempty"`
    MetaHash  string `json:"metaHash,omitempty"`
    Version   uint32 `json:"version,omitempty"`
    Period    uint32 `json:"period,omitempty"`
    Data      string `json:"data,omitempty"`
    Multihash bool   `json:"multiHash"`
    Signature string `json:"signature,omitempty"`
}

// Request represents an update and/or resource create message
type Request struct {
    SignedResourceUpdate
    metadata ResourceMetadata
    isNew    bool
}

var zeroAddr = common.Address{}

// NewCreateUpdateRequest returns a ready to sign request to create and initialize a resource with data
func NewCreateUpdateRequest(metadata *ResourceMetadata) (*Request, error) {

    request, err := NewCreateRequest(metadata)
    if err != nil {
        return nil, err
    }

    // get the current time
    now := TimestampProvider.Now().Time

    request.version = 1
    request.period, err = getNextPeriod(metadata.StartTime.Time, now, metadata.Frequency)
    if err != nil {
        return nil, err
    }
    return request, nil
}

// NewCreateRequest returns a request to create a new resource
func NewCreateRequest(metadata *ResourceMetadata) (request *Request, err error) {
    if metadata.StartTime.Time == 0 { // get the current time
        metadata.StartTime = TimestampProvider.Now()
    }

    if metadata.Owner == zeroAddr {
        return nil, NewError(ErrInvalidValue, "OwnerAddr is not set")
    }

    request = &Request{
        metadata: *metadata,
    }
    request.rootAddr, request.metaHash, _, err = request.metadata.serializeAndHash()
    request.isNew = true
    return request, nil
}

// Frequency returns the resource's expected update frequency
func (r *Request) Frequency() uint64 {
    return r.metadata.Frequency
}

// Name returns the resource human-readable name
func (r *Request) Name() string {
    return r.metadata.Name
}

// Multihash returns true if the resource data should be interpreted as a multihash
func (r *Request) Multihash() bool {
    return r.multihash
}

// Period returns in which period the resource will be published
func (r *Request) Period() uint32 {
    return r.period
}

// Version returns the resource version to publish
func (r *Request) Version() uint32 {
    return r.version
}

// RootAddr returns the metadata chunk address
func (r *Request) RootAddr() storage.Address {
    return r.rootAddr
}

// StartTime returns the time that the resource was/will be created at
func (r *Request) StartTime() Timestamp {
    return r.metadata.StartTime
}

// Owner returns the resource owner's address
func (r *Request) Owner() common.Address {
    return r.metadata.Owner
}

// Sign executes the signature to validate the resource and sets the owner address field
func (r *Request) Sign(signer Signer) error {
    if r.metadata.Owner != zeroAddr && r.metadata.Owner != signer.Address() {
        return NewError(ErrInvalidSignature, "Signer does not match current owner of the resource")
    }

    if err := r.SignedResourceUpdate.Sign(signer); err != nil {
        return err
    }
    r.metadata.Owner = signer.Address()
    return nil
}

// SetData stores the payload data the resource will be updated with
func (r *Request) SetData(data []byte, multihash bool) {
    r.data = data
    r.multihash = multihash
    r.signature = nil
    if !r.isNew {
        r.metadata.Frequency = 0 // mark as update
    }
}

func (r *Request) IsNew() bool {
    return r.metadata.Frequency > 0 && (r.period <= 1 || r.version <= 1)
}

func (r *Request) IsUpdate() bool {
    return r.signature != nil
}

// fromJSON takes an update request JSON and populates an UpdateRequest
func (r *Request) fromJSON(j *updateRequestJSON) error {

    r.version = j.Version
    r.period = j.Period
    r.multihash = j.Multihash
    r.metadata.Name = j.Name
    r.metadata.Frequency = j.Frequency
    r.metadata.StartTime.Time = j.StartTime

    if err := decodeHexArray(r.metadata.Owner[:], j.Owner, "ownerAddr"); err != nil {
        return err
    }

    var err error
    if j.Data != "" {
        r.data, err = hexutil.Decode(j.Data)
        if err != nil {
            return NewError(ErrInvalidValue, "Cannot decode data")
        }
    }

    var declaredRootAddr storage.Address
    var declaredMetaHash []byte

    declaredRootAddr, err = decodeHexSlice(j.RootAddr, storage.KeyLength, "rootAddr")
    if err != nil {
        return err
    }
    declaredMetaHash, err = decodeHexSlice(j.MetaHash, 32, "metaHash")
    if err != nil {
        return err
    }

    if r.IsNew() {
        // for new resource creation, rootAddr and metaHash are optional because
        // we can derive them from the content itself.
        // however, if the user sent them, we check them for consistency.

        r.rootAddr, r.metaHash, _, err = r.metadata.serializeAndHash()
        if err != nil {
            return err
        }
        if j.RootAddr != "" && !bytes.Equal(declaredRootAddr, r.rootAddr) {
            return NewError(ErrInvalidValue, "rootAddr does not match resource metadata")
        }
        if j.MetaHash != "" && !bytes.Equal(declaredMetaHash, r.metaHash) {
            return NewError(ErrInvalidValue, "metaHash does not match resource metadata")
        }

    } else {
        //Update message
        r.rootAddr = declaredRootAddr
        r.metaHash = declaredMetaHash
    }

    if j.Signature != "" {
        sigBytes, err := hexutil.Decode(j.Signature)
        if err != nil || len(sigBytes) != signatureLength {
            return NewError(ErrInvalidSignature, "Cannot decode signature")
        }
        r.signature = new(Signature)
        r.updateAddr = r.UpdateAddr()
        copy(r.signature[:], sigBytes)
    }
    return nil
}

func decodeHexArray(dst []byte, src, name string) error {
    bytes, err := decodeHexSlice(src, len(dst), name)
    if err != nil {
        return err
    }
    if bytes != nil {
        copy(dst, bytes)
    }
    return nil
}

func decodeHexSlice(src string, expectedLength int, name string) (bytes []byte, err error) {
    if src != "" {
        bytes, err = hexutil.Decode(src)
        if err != nil || len(bytes) != expectedLength {
            return nil, NewErrorf(ErrInvalidValue, "Cannot decode %s", name)
        }
    }
    return bytes, nil
}

// UnmarshalJSON takes a JSON structure stored in a byte array and populates the Request object
// Implements json.Unmarshaler interface
func (r *Request) UnmarshalJSON(rawData []byte) error {
    var requestJSON updateRequestJSON
    if err := json.Unmarshal(rawData, &requestJSON); err != nil {
        return err
    }
    return r.fromJSON(&requestJSON)
}

// MarshalJSON takes an update request and encodes it as a JSON structure into a byte array
// Implements json.Marshaler interface
func (r *Request) MarshalJSON() (rawData []byte, err error) {
    var signatureString, dataHashString, rootAddrString, metaHashString string
    if r.signature != nil {
        signatureString = hexutil.Encode(r.signature[:])
    }
    if r.data != nil {
        dataHashString = hexutil.Encode(r.data)
    }
    if r.rootAddr != nil {
        rootAddrString = hexutil.Encode(r.rootAddr)
    }
    if r.metaHash != nil {
        metaHashString = hexutil.Encode(r.metaHash)
    }
    var ownerAddrString string
    if r.metadata.Frequency == 0 {
        ownerAddrString = ""
    } else {
        ownerAddrString = hexutil.Encode(r.metadata.Owner[:])
    }

    requestJSON := &updateRequestJSON{
        Name:      r.metadata.Name,
        Frequency: r.metadata.Frequency,
        StartTime: r.metadata.StartTime.Time,
        Version:   r.version,
        Period:    r.period,
        Owner:     ownerAddrString,
        Data:      dataHashString,
        Multihash: r.multihash,
        Signature: signatureString,
        RootAddr:  rootAddrString,
        MetaHash:  metaHashString,
    }

    return json.Marshal(requestJSON)
}