Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion internal/inspect/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
)

// maxPayloadSize is the maximum allowed inspect request body size.
// This matches the Cartesi Machine's CMIO RX buffer (2 MiB, defined as log2 size 21
// in the machine emulator's pma-defines.h). Payloads larger than this are rejected
// by the machine anyway, so there is no reason to read them into memory.
const maxPayloadSize = 1 << 21 // 2 MiB

var (
ErrInvalidMachines = errors.New("machines must not be nil")
ErrNoApp = errors.New("no application")
Expand Down Expand Up @@ -119,12 +125,24 @@ func (inspect *Inspector) ServeHTTP(w http.ResponseWriter, r *http.Request) {

dapp = r.PathValue("dapp")
if r.Method == "POST" {
payload, err = io.ReadAll(r.Body)
// Limit the request body to the machine's CMIO RX buffer size.
// Payloads larger than this are rejected by the machine, so reading
// them into memory would only waste resources. We read maxPayloadSize+1
// bytes so we can distinguish "exactly at limit" from "over limit".
limitedReader := io.LimitReader(r.Body, maxPayloadSize+1)
payload, err = io.ReadAll(limitedReader)
if err != nil {
inspect.Logger.Info("Bad request", "err", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if int64(len(payload)) > maxPayloadSize {
inspect.Logger.Info("Payload too large",
"size", len(payload),
"limit", maxPayloadSize)
http.Error(w, "Payload too large", http.StatusRequestEntityTooLarge)
return
}
} else {
inspect.Logger.Info("HTTP method not supported", "application", dapp)
http.Error(w, "HTTP method not supported", http.StatusNotFound)
Expand Down
63 changes: 62 additions & 1 deletion internal/inspect/inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,68 @@ func (s *InspectSuite) TestPostMachineNotReady() {
s.Equal(http.StatusServiceUnavailable, respByAddr.StatusCode)
}

// FIXME: add more tests
func (s *InspectSuite) TestPostMaxPayloadSize() {
inspect, app, _ := s.setup()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
s.startServer(ctx, inspect)

// A payload exactly at the max size should be accepted.
payload := make([]byte, maxPayloadSize)
_, err := crand.Read(payload)
s.Require().NoError(err)

resp, err := http.Post(
fmt.Sprintf("http://%s/inspect/%s", s.ServiceAddr, app.IApplicationAddress.Hex()),
"application/octet-stream",
bytes.NewReader(payload))
s.Require().NoError(err)
defer resp.Body.Close()
s.Equal(http.StatusOK, resp.StatusCode)

var r InspectResponse
err = json.NewDecoder(resp.Body).Decode(&r)
s.Require().NoError(err)
s.Equal("Accepted", r.Status)
s.Require().Len(r.Reports, 1)
}

func (s *InspectSuite) TestPostPayloadTooLarge() {
inspect, app, _ := s.setup()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
s.startServer(ctx, inspect)

// A payload one byte over the max size should be rejected.
payload := make([]byte, maxPayloadSize+1)
_, err := crand.Read(payload)
s.Require().NoError(err)

resp, err := http.Post(
fmt.Sprintf("http://%s/inspect/%s", s.ServiceAddr, app.IApplicationAddress.Hex()),
"application/octet-stream",
bytes.NewReader(payload))
s.Require().NoError(err)
defer resp.Body.Close()
s.Equal(http.StatusRequestEntityTooLarge, resp.StatusCode)
}

func (s *InspectSuite) startServer(ctx context.Context, inspect *Inspector) {
router := http.NewServeMux()
router.Handle("/inspect/{dapp}", inspect)
httpService := services.HttpService{Name: "http", Address: s.ServiceAddr, Handler: router}

ready := make(chan struct{}, 1)
go func() {
_ = httpService.Start(ctx, ready, service.NewLogger(slog.LevelDebug, true))
}()

select {
case <-ready:
case <-time.After(TestTimeout):
s.FailNow("timed out waiting for HttpService to be ready")
}
}

func (s *InspectSuite) setup() (*Inspector, *Application, common.Hash) {
m := newMockMachine(1)
Expand Down
Loading