go-coap icon indicating copy to clipboard operation
go-coap copied to clipboard

Example for block transfer with UDP CoAP

Open EfraimManurung opened this issue 1 year ago • 4 comments

Hi, 

Is there documentation or examples related to CoAP for block transfer for both client and server?

I searched throughout the examples and files and still did not find it. I would like to send a big chunk of payload around 1024 bytes from client to server. The simple example is the opposite, which is the client only GET from the server that serves some payload.

I also saw many issues asked related to block transfer examples. It would be helpful to tackle this problem together for the examples code.

EfraimManurung avatar Dec 09 '24 07:12 EfraimManurung

Hi,

Blockwise transfers are enabled by default for both DTLS and UDP. To use blockwise, simply send a POST or PUT request. Here's an example:

Client

package main

import (
	"bytes"
	"context"
	"log"
	"os"
	"time"

	"github.com/plgd-dev/go-coap/v3/message"
	"github.com/plgd-dev/go-coap/v3/udp"
)

func main() {
	co, err := udp.Dial("localhost:5688")
	if err != nil {
		log.Fatalf("Error dialing: %v", err)
	}

	path := "/a"
	if len(os.Args) > 1 {
		path = os.Args[1]
	}

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	resp, err := co.Post(ctx, path, message.TextPlain, bytes.NewReader(make([]byte, 1024*1024)))
	if err != nil {
		log.Fatalf("Error sending request: %v", err)
	}

	log.Printf("Response payload: %v", resp.String())
}

Server

package main

import (
	"bytes"
	"io"
	"log"

	coap "github.com/plgd-dev/go-coap/v3"
	"github.com/plgd-dev/go-coap/v3/message"
	"github.com/plgd-dev/go-coap/v3/message/codes"
	"github.com/plgd-dev/go-coap/v3/mux"
)

func loggingMiddleware(next mux.Handler) mux.Handler {
	return mux.HandlerFunc(func(w mux.ResponseWriter, r *mux.Message) {
		log.Printf("ClientAddress %v, %v\n", w.Conn().RemoteAddr(), r.String())
		next.ServeCOAP(w, r)
	})
}

func handleRequest(w mux.ResponseWriter, r *mux.Message) {
	customResp := w.Conn().AcquireMessage(r.Context())
	defer w.Conn().ReleaseMessage(customResp)

	if r.Body() != nil {
		data, err := io.ReadAll(r.Body())
		if err != nil {
			log.Printf("Cannot read body: %v", err)
			return
		}
		log.Printf("Received length: %v", len(data))
	}

	customResp.SetCode(codes.Changed)
	customResp.SetToken(r.Token())
	customResp.SetContentFormat(message.TextPlain)
	customResp.SetBody(bytes.NewReader([]byte("Response from server")))
	err := w.Conn().WriteMessage(customResp)
	if err != nil {
		log.Printf("Cannot set response: %v", err)
	}
}

func main() {
	router := mux.NewRouter()
	router.Use(loggingMiddleware)
	router.Handle("/a", mux.HandlerFunc(handleRequest))

	log.Fatal(coap.ListenAndServe("udp", ":5688", router))
}

Additional Configuration

To customize blockwise behavior, use the option provided in WithBlockwise. Configure the server via coap.ListenAndServeWithOptions and the client via Dial similar as is in the example. Also there is a limit for max message size (64KB by default) so If you want to send 1MB you need to set also the option WithMaxMessageSize to 1024*1024.

jkralik avatar Dec 09 '24 20:12 jkralik

Hi Jozef, thank you for your response, I will try it.

I also figured it out today from the simple examples,

it would be nice if we put our code to the examples, to help others as references.

Client

package main

import (
	"bytes"
	"context"
	"log"
	"os"
	"time"

	"github.com/plgd-dev/go-coap/v3/message"
	"github.com/plgd-dev/go-coap/v3/net/blockwise"
	"github.com/plgd-dev/go-coap/v3/options"
	"github.com/plgd-dev/go-coap/v3/udp"
)

func main() {
	// Configure blockwise options: enable=true, block size=SZX1024, timeout=5s
	blockwiseOpt := options.WithBlockwise(true, blockwise.SZX1024, 5*time.Second)

	client, err := udp.Dial("127.0.0.1:5683", blockwiseOpt)
	if err != nil {
		log.Fatalf("Error dialing: %v", err)
	}

	path := "/big-endpoint"
	if len(os.Args) > 1 {
		path = os.Args[1]
	}

	// create a 1024-byte payload
	payload := make([]byte, 1024)
	for i := 0; i < 1024; i++ {
		payload[i] = byte(i % 256) // Fill with example data
	}
	
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	resp, err := client.Post(ctx, path, message.TextPlain, bytes.NewReader(payload))
	if err != nil {
		log.Fatalf("Error sending request: %v", err)
	}
	log.Printf("Response payload: %v", resp.String())
}

Server

package main

import (
	"bytes"
	"log"

	coap "github.com/plgd-dev/go-coap/v3"
	"github.com/plgd-dev/go-coap/v3/message"
	"github.com/plgd-dev/go-coap/v3/message/codes"
	"github.com/plgd-dev/go-coap/v3/mux"
)

func loggingMiddleware(next mux.Handler) mux.Handler {
	return mux.HandlerFunc(func(w mux.ResponseWriter, r *mux.Message) {
		log.Printf("ClientAddress %v, %v\n", w.Conn().RemoteAddr(), r.String())
		next.ServeCOAP(w, r)
	})
}

func handleA(w mux.ResponseWriter, r *mux.Message) {
	err := w.SetResponse(codes.Content, message.TextPlain, bytes.NewReader([]byte("success")))
	if err != nil {
		log.Printf("cannot set response: %v", err)
	}
}

func handleB(w mux.ResponseWriter, r *mux.Message) {
	customResp := w.Conn().AcquireMessage(r.Context())
	defer w.Conn().ReleaseMessage(customResp)
	customResp.SetCode(codes.Content)
	customResp.SetToken(r.Token())
	customResp.SetContentFormat(message.TextPlain)
	customResp.SetBody(bytes.NewReader([]byte("B hello world")))
	err := w.Conn().WriteMessage(customResp)
	if err != nil {
		log.Printf("cannot set response: %v", err)
	}
}

func main() {
	r := mux.NewRouter()
	r.Use(loggingMiddleware)
	r.Handle("/big-endpoint", mux.HandlerFunc(handleA))
	r.Handle("/b", mux.HandlerFunc(handleB))

	log.Fatal(coap.ListenAndServe("udp", "127.0.0.1:5683", r))
}

EfraimManurung avatar Dec 09 '24 22:12 EfraimManurung

how to set the blocksize to 512 in v3? it seems udp.WithBlockwise is removed?

zshwei avatar Nov 26 '25 15:11 zshwei

my bad, got it

how to set the blocksize to 512 in v3? it seems udp.WithBlockwise is removed?

zshwei avatar Nov 27 '25 06:11 zshwei