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  }