github.com/ethersphere/bee/v2@v2.2.0/pkg/api/chunk.go (about) 1 // Copyright 2020 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package api 6 7 import ( 8 "bytes" 9 "errors" 10 "fmt" 11 "io" 12 "net/http" 13 "strconv" 14 15 "github.com/ethersphere/bee/v2/pkg/accesscontrol" 16 "github.com/ethersphere/bee/v2/pkg/cac" 17 "github.com/ethersphere/bee/v2/pkg/soc" 18 "github.com/ethersphere/bee/v2/pkg/storer" 19 20 "github.com/ethersphere/bee/v2/pkg/jsonhttp" 21 "github.com/ethersphere/bee/v2/pkg/postage" 22 "github.com/ethersphere/bee/v2/pkg/storage" 23 "github.com/ethersphere/bee/v2/pkg/swarm" 24 "github.com/gorilla/mux" 25 ) 26 27 type chunkAddressResponse struct { 28 Reference swarm.Address `json:"reference"` 29 } 30 31 func (s *Service) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { 32 logger := s.logger.WithName("post_chunk").Build() 33 34 headers := struct { 35 BatchID []byte `map:"Swarm-Postage-Batch-Id"` 36 StampSig []byte `map:"Swarm-Postage-Stamp"` 37 SwarmTag uint64 `map:"Swarm-Tag"` 38 Act bool `map:"Swarm-Act"` 39 HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"` 40 }{} 41 if response := s.mapStructure(r.Header, &headers); response != nil { 42 response("invalid header params", logger, w) 43 return 44 } 45 46 var ( 47 tag uint64 48 err error 49 ) 50 if headers.SwarmTag > 0 { 51 tag, err = s.getOrCreateSessionID(headers.SwarmTag) 52 if err != nil { 53 logger.Debug("get or create tag failed", "error", err) 54 logger.Error(nil, "get or create tag failed") 55 switch { 56 case errors.Is(err, storage.ErrNotFound): 57 jsonhttp.NotFound(w, "tag not found") 58 default: 59 jsonhttp.InternalServerError(w, "cannot get or create tag") 60 } 61 return 62 } 63 } 64 65 if len(headers.BatchID) == 0 && len(headers.StampSig) == 0 { 66 logger.Error(nil, batchIdOrStampSig) 67 jsonhttp.BadRequest(w, batchIdOrStampSig) 68 return 69 } 70 71 // Currently the localstore supports session based uploads. We don't want to 72 // create new session for single chunk uploads. So if the chunk upload is not 73 // part of a session already, then we directly push the chunk. This way we dont 74 // need to go through the UploadStore. 75 deferred := tag != 0 76 77 var putter storer.PutterSession 78 if len(headers.StampSig) != 0 { 79 stamp := postage.Stamp{} 80 if err := stamp.UnmarshalBinary(headers.StampSig); err != nil { 81 errorMsg := "Stamp deserialization failure" 82 logger.Debug(errorMsg, "error", err) 83 logger.Error(nil, errorMsg) 84 jsonhttp.BadRequest(w, errorMsg) 85 return 86 } 87 88 putter, err = s.newStampedPutter(r.Context(), putterOptions{ 89 BatchID: stamp.BatchID(), 90 TagID: tag, 91 Deferred: deferred, 92 }, &stamp) 93 } else { 94 putter, err = s.newStamperPutter(r.Context(), putterOptions{ 95 BatchID: headers.BatchID, 96 TagID: tag, 97 Deferred: deferred, 98 }) 99 } 100 if err != nil { 101 errorMsg := "get putter failed" 102 logger.Debug(errorMsg, "error", err) 103 logger.Error(nil, errorMsg) 104 switch { 105 case errors.Is(err, errBatchUnusable) || errors.Is(err, postage.ErrNotUsable): 106 jsonhttp.UnprocessableEntity(w, "batch not usable yet or does not exist") 107 case errors.Is(err, postage.ErrNotFound): 108 jsonhttp.NotFound(w, "batch with id not found") 109 case errors.Is(err, errInvalidPostageBatch): 110 jsonhttp.BadRequest(w, "invalid batch id") 111 case errors.Is(err, errUnsupportedDevNodeOperation): 112 jsonhttp.BadRequest(w, errUnsupportedDevNodeOperation) 113 default: 114 jsonhttp.BadRequest(w, errorMsg) 115 } 116 return 117 } 118 119 ow := &cleanupOnErrWriter{ 120 ResponseWriter: w, 121 onErr: putter.Cleanup, 122 logger: logger, 123 } 124 125 data, err := io.ReadAll(r.Body) 126 if err != nil { 127 if jsonhttp.HandleBodyReadError(err, ow) { 128 return 129 } 130 logger.Debug("chunk upload: read chunk data failed", "error", err) 131 logger.Error(nil, "chunk upload: read chunk data failed") 132 jsonhttp.InternalServerError(ow, "cannot read chunk data") 133 return 134 } 135 136 if len(data) < swarm.SpanSize { 137 logger.Debug("chunk upload: insufficient data length") 138 logger.Error(nil, "chunk upload: insufficient data length") 139 jsonhttp.BadRequest(ow, "insufficient data length") 140 return 141 } 142 143 chunk, err := cac.NewWithDataSpan(data) 144 if err != nil { 145 // not a valid cac chunk. Check if it's a replica soc chunk. 146 logger.Debug("chunk upload: create chunk failed", "error", err) 147 148 // FromChunk only uses the chunk data to recreate the soc chunk. So the address is irrelevant. 149 sch, err := soc.FromChunk(swarm.NewChunk(swarm.EmptyAddress, data)) 150 if err != nil { 151 logger.Debug("chunk upload: create soc chunk from data failed", "error", err) 152 logger.Error(nil, "chunk upload: create chunk error") 153 jsonhttp.InternalServerError(ow, "create chunk error") 154 return 155 } 156 chunk, err = sch.Chunk() 157 if err != nil { 158 logger.Debug("chunk upload: create chunk from soc failed", "error", err) 159 logger.Error(nil, "chunk upload: create chunk error") 160 jsonhttp.InternalServerError(ow, "create chunk error") 161 return 162 } 163 164 if !soc.Valid(chunk) { 165 logger.Debug("chunk upload: invalid soc chunk") 166 logger.Error(nil, "chunk upload: create chunk error") 167 jsonhttp.InternalServerError(ow, "create chunk error") 168 return 169 } 170 } 171 172 reference := chunk.Address() 173 if headers.Act { 174 reference, err = s.actEncryptionHandler(r.Context(), w, putter, reference, headers.HistoryAddress) 175 if err != nil { 176 logger.Debug("access control upload failed", "error", err) 177 logger.Error(nil, "access control upload failed") 178 switch { 179 case errors.Is(err, accesscontrol.ErrNotFound): 180 jsonhttp.NotFound(w, "act or history entry not found") 181 case errors.Is(err, accesscontrol.ErrInvalidPublicKey) || errors.Is(err, accesscontrol.ErrSecretKeyInfinity): 182 jsonhttp.BadRequest(w, "invalid public key") 183 case errors.Is(err, accesscontrol.ErrUnexpectedType): 184 jsonhttp.BadRequest(w, "failed to create history") 185 default: 186 jsonhttp.InternalServerError(w, errActUpload) 187 } 188 return 189 } 190 } 191 192 err = putter.Put(r.Context(), chunk) 193 if err != nil { 194 logger.Debug("chunk upload: write chunk failed", "chunk_address", chunk.Address(), "error", err) 195 logger.Error(nil, "chunk upload: write chunk failed") 196 switch { 197 case errors.Is(err, postage.ErrBucketFull): 198 jsonhttp.PaymentRequired(ow, "batch is overissued") 199 case errors.Is(err, postage.ErrInvalidBatchSignature): 200 jsonhttp.BadRequest(ow, "stamp signature is invalid") 201 default: 202 jsonhttp.InternalServerError(ow, "chunk write error") 203 } 204 return 205 } 206 207 err = putter.Done(swarm.ZeroAddress) 208 if err != nil { 209 logger.Debug("done split failed", "error", err) 210 logger.Error(nil, "done split failed") 211 jsonhttp.InternalServerError(ow, "done split failed") 212 return 213 } 214 215 if tag != 0 { 216 w.Header().Set(SwarmTagHeader, fmt.Sprint(tag)) 217 } 218 219 w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader) 220 jsonhttp.Created(w, chunkAddressResponse{Reference: reference}) 221 } 222 223 func (s *Service) chunkGetHandler(w http.ResponseWriter, r *http.Request) { 224 logger := s.logger.WithName("get_chunk_by_address").Build() 225 loggerV1 := logger.V(1).Build() 226 227 headers := struct { 228 Cache *bool `map:"Swarm-Cache"` 229 }{} 230 if response := s.mapStructure(r.Header, &headers); response != nil { 231 response("invalid header params", logger, w) 232 return 233 } 234 cache := true 235 if headers.Cache != nil { 236 cache = *headers.Cache 237 } 238 239 paths := struct { 240 Address swarm.Address `map:"address,resolve" validate:"required"` 241 }{} 242 if response := s.mapStructure(mux.Vars(r), &paths); response != nil { 243 response("invalid path params", logger, w) 244 return 245 } 246 247 address := paths.Address 248 if v := getAddressFromContext(r.Context()); !v.IsZero() { 249 address = v 250 } 251 252 chunk, err := s.storer.Download(cache).Get(r.Context(), address) 253 if err != nil { 254 if errors.Is(err, storage.ErrNotFound) { 255 loggerV1.Debug("chunk not found", "address", address) 256 jsonhttp.NotFound(w, "chunk not found") 257 return 258 259 } 260 logger.Debug("read chunk failed", "chunk_address", address, "error", err) 261 logger.Error(nil, "read chunk failed") 262 jsonhttp.InternalServerError(w, "read chunk failed") 263 return 264 } 265 w.Header().Set(ContentTypeHeader, "binary/octet-stream") 266 w.Header().Set(ContentLengthHeader, strconv.FormatInt(int64(len(chunk.Data())), 10)) 267 _, _ = io.Copy(w, bytes.NewReader(chunk.Data())) 268 }