From aee44d2f7d15d6ed28d70fe9e456960f4ba9aff8 Mon Sep 17 00:00:00 2001
From: Jozef Kralik <jojo.lwin@gmail.com>
Date: Wed, 1 Nov 2023 12:48:39 +0000
Subject: [PATCH] mux: Root path handling improvement

This modification ensures that the mux treats both "/" and an empty
path ("") as requests to the root path. The reason behind this is
the absence of the Uri-path option in CoAP requests in such instances.
---
 mux/router.go      |  2 +-
 udp/server_test.go | 75 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 76 insertions(+), 1 deletion(-)

diff --git a/mux/router.go b/mux/router.go
index 2bcafe07..0596f845 100644
--- a/mux/router.go
+++ b/mux/router.go
@@ -215,7 +215,7 @@ func (r *Router) ServeCOAP(w ResponseWriter, req *Message) {
 	r.m.RLock()
 	defaultHandler := r.defaultHandler
 	r.m.RUnlock()
-	if err != nil {
+	if err != nil && !errors.Is(err, message.ErrOptionNotFound) {
 		defaultHandler.ServeCOAP(w, req)
 		return
 	}
diff --git a/udp/server_test.go b/udp/server_test.go
index 0ec3e22a..4af06146 100644
--- a/udp/server_test.go
+++ b/udp/server_test.go
@@ -13,6 +13,7 @@ import (
 	"github.com/plgd-dev/go-coap/v3/message"
 	"github.com/plgd-dev/go-coap/v3/message/codes"
 	"github.com/plgd-dev/go-coap/v3/message/pool"
+	"github.com/plgd-dev/go-coap/v3/mux"
 	coapNet "github.com/plgd-dev/go-coap/v3/net"
 	"github.com/plgd-dev/go-coap/v3/net/responsewriter"
 	"github.com/plgd-dev/go-coap/v3/options"
@@ -603,3 +604,77 @@ func TestServerReconnectNewClient(t *testing.T) {
 		require.False(t, checkMessageID[cc.GetMessageID()])
 	}
 }
+
+func TestMuxPath(t *testing.T) {
+	r := mux.NewRouter()
+	err := r.Handle("/", mux.HandlerFunc(handleA))
+	require.NoError(t, err)
+	err = r.Handle("/a", mux.HandlerFunc(handleA))
+	require.NoError(t, err)
+
+	l, err := coapNet.NewListenUDP("udp", ":5688")
+	require.NoError(t, err)
+	defer func() {
+		errC := l.Close()
+		require.NoError(t, errC)
+	}()
+	s := udp.NewServer(options.WithMux(r))
+	go func() {
+		errL := s.Serve(l)
+		require.NoError(t, errL)
+	}()
+
+	type args struct {
+		path string
+	}
+	tests := []struct {
+		name     string
+		args     args
+		wantCode codes.Code
+	}{
+		{
+			name:     "empty path",
+			args:     args{path: ""},
+			wantCode: codes.Content,
+		},
+		{
+			name:     "root path",
+			args:     args{path: "/"},
+			wantCode: codes.Content,
+		},
+		{
+			name:     "a path",
+			args:     args{path: "/a"},
+			wantCode: codes.Content,
+		},
+		{
+			name:     "not found path",
+			args:     args{path: "/b"},
+			wantCode: codes.NotFound,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			testPath(t, tt.args.path, tt.wantCode)
+		})
+	}
+}
+
+func testPath(t *testing.T, path string, expCode codes.Code) {
+	co, err := udp.Dial("localhost:5688")
+	require.NoError(t, err)
+	defer func() {
+		errC := co.Close()
+		require.NoError(t, errC)
+	}()
+	resp, err := co.Get(context.Background(), path)
+	require.NoError(t, err)
+	require.Equal(t, expCode, resp.Code())
+}
+
+func handleA(w mux.ResponseWriter, _ *mux.Message) {
+	err := w.SetResponse(codes.Content, message.TextPlain, bytes.NewReader([]byte("hello world")))
+	if err != nil {
+		log.Printf("cannot set response: %v", err)
+	}
+}