├── .github ├── FUNDING.yml └── workflows │ ├── test.yml │ └── main.yml ├── net ├── error.go ├── udp.go ├── observation │ ├── observation.go │ └── observation_test.go ├── isTemporary.go ├── blockwise │ └── error.go ├── monitor │ └── inactivity │ │ ├── keepalive.go │ │ └── inactivitymonitor.go ├── conn_test.go ├── options.go ├── tlslistener.go ├── tcplistener.go ├── connUDP_test.go ├── dtlslistener.go └── conn.go ├── .vscode └── settings.json ├── udp ├── client │ ├── inactivitymonitor.go │ ├── mux.go │ ├── container.go │ ├── responsewriter.go │ ├── mutexmap_test.go │ ├── mutexmap.go │ ├── client.go │ ├── clientobserve.go │ └── clientobserve_test.go ├── message │ ├── error.go │ ├── getmid.go │ ├── type.go │ ├── pool │ │ ├── message_test.go │ │ └── message.go │ └── message.go ├── optionmux.go ├── nocopy.go ├── example_test.go ├── discover.go ├── session.go ├── client.go └── server_test.go ├── message ├── noresponse │ ├── error.go │ ├── noresponse_test.go │ └── noresponse.go ├── getToken_test.go ├── option_test.go ├── getToken.go ├── error.go ├── getETag.go ├── message.go ├── encodeDecodeUint32.go ├── codes │ ├── codes_test.go │ ├── code_string.go │ └── codes.go ├── encodeDecodeUint32_test.go ├── getETag_test.go └── status │ └── status.go ├── dtls ├── optionmux.go ├── nocopy.go ├── example_test.go ├── session.go └── client.go ├── .gitignore ├── renovate.json ├── Dockerfile ├── shared └── logger.go ├── tcp ├── nocopy.go ├── example_test.go ├── message │ ├── pool │ │ ├── message_test.go │ │ └── message.go │ └── message_test.go ├── optionmux.go ├── container.go ├── responsewriter.go ├── client.go ├── clientobserve.go ├── clientobserve_test.go ├── server_test.go └── server.go ├── go.mod ├── examples ├── dtls │ ├── pki │ │ ├── cert_gen_test.go │ │ ├── cert_generation.md │ │ ├── client │ │ │ └── main.go │ │ ├── cert_util.go │ │ ├── server │ │ │ └── main.go │ │ └── cert_gen.go │ └── psk │ │ ├── client │ │ └── main.go │ │ └── server │ │ └── main.go ├── simple │ ├── client │ │ └── main.go │ └── server │ │ └── main.go ├── observe │ ├── client │ │ └── main.go │ └── server │ │ └── main.go └── mcast │ ├── client │ └── main.go │ └── server │ └── main.go ├── mux ├── example_logging_middleware_test.go ├── message.go ├── middleware.go ├── client.go └── router.go ├── server.go └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: go-coap 2 | -------------------------------------------------------------------------------- /net/error.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import "errors" 4 | 5 | var ErrListenerIsClosed = errors.New("listen socket was closed") 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.testEnvVars": { 3 | // "GOFLAGS":"-mod=vendor", 4 | }, 5 | "go.testTimeout": "40s" 6 | } -------------------------------------------------------------------------------- /udp/client/inactivitymonitor.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | // nilNotifier implements a Notify method that does nothing 4 | type nilNotifier struct { 5 | } 6 | 7 | func (n *nilNotifier) Notify() { 8 | } 9 | -------------------------------------------------------------------------------- /udp/message/error.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrMessageTruncated = errors.New("message is truncated") 7 | ErrMessageInvalidVersion = errors.New("message has invalid version") 8 | ) 9 | -------------------------------------------------------------------------------- /message/noresponse/error.go: -------------------------------------------------------------------------------- 1 | package noresponse 2 | 3 | import "errors" 4 | 5 | // ErrMessageNotInterested message is not of interest to the client 6 | var ErrMessageNotInterested = errors.New("message not to be sent due to disinterest") 7 | -------------------------------------------------------------------------------- /message/getToken_test.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestGetToken(t *testing.T) { 10 | token, err := GetToken() 11 | require.NoError(t, err) 12 | require.NotEmpty(t, token) 13 | require.NotEmpty(t, token.String()) 14 | 15 | } 16 | -------------------------------------------------------------------------------- /udp/optionmux.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "github.com/matrix-org/go-coap/v2/mux" 5 | "github.com/matrix-org/go-coap/v2/udp/client" 6 | ) 7 | 8 | // WithMux set's multiplexer for handle requests. 9 | func WithMux(m mux.Handler) HandlerFuncOpt { 10 | return WithHandlerFunc(client.HandlerFuncToMux(m)) 11 | } 12 | -------------------------------------------------------------------------------- /dtls/optionmux.go: -------------------------------------------------------------------------------- 1 | package dtls 2 | 3 | import ( 4 | "github.com/matrix-org/go-coap/v2/mux" 5 | "github.com/matrix-org/go-coap/v2/udp/client" 6 | ) 7 | 8 | // WithMux set's multiplexer for handle requests. 9 | func WithMux(m mux.Handler) HandlerFuncOpt { 10 | return WithHandlerFunc(client.HandlerFuncToMux(m)) 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | debug 8 | server 9 | !server/ 10 | client 11 | !client/ 12 | vendor/ 13 | 14 | # Test binary, build with `go test -c` 15 | *.test 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "postUpdateOptions": [ 6 | "gomodTidy" 7 | ], 8 | "commitBody": "Generated by renovateBot", 9 | "packageRules": [ 10 | { 11 | "packagePatterns": [".+"], 12 | "schedule": ["on the first day of the month"] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /message/option_test.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMediaType_String(t *testing.T) { 8 | for i := 0; i < 12000; i++ { 9 | func(string) { 10 | }(MediaType(i).String()) 11 | } 12 | } 13 | 14 | func TestOptionID_String(t *testing.T) { 15 | for i := 0; i < 12000; i++ { 16 | func(string) { 17 | }(OptionID(i).String()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 AS build 2 | RUN apt-get update \ 3 | && apt-get install -y gcc make git curl file 4 | RUN git clone https://github.com/udhos/update-golang.git \ 5 | && cd update-golang \ 6 | && ./update-golang.sh \ 7 | && ln -s /usr/local/go/bin/go /usr/bin/go 8 | WORKDIR $GOPATH/src/github.com/matrix-org/go-coap 9 | COPY go.mod go.sum ./ 10 | RUN go mod download 11 | COPY . . -------------------------------------------------------------------------------- /shared/logger.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | // Logger is an interface which if satisfied will add debug logging to this library 4 | type Logger interface { 5 | Printf(format string, v ...interface{}) 6 | } 7 | 8 | // NOPLogger doesn't log - it is the default set on structs in this library 9 | type NOPLogger struct{} 10 | 11 | func (l *NOPLogger) Printf(format string, v ...interface{}) { 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /udp/message/getmid.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | "sync/atomic" 7 | ) 8 | 9 | var msgID uint32 10 | 11 | func init() { 12 | b := make([]byte, 4) 13 | rand.Read(b) 14 | msgID = binary.BigEndian.Uint32(b) 15 | } 16 | 17 | // GetMID generates a message id for UDP-coap 18 | func GetMID() uint16 { 19 | return uint16(atomic.AddUint32(&msgID, 1)) 20 | } 21 | -------------------------------------------------------------------------------- /tcp/nocopy.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | // Embed this type into a struct, which mustn't be copied, 4 | // so `go vet` gives a warning if this struct is copied. 5 | // 6 | // See https://github.com/golang/go/issues/8005#issuecomment-190753527 for details. 7 | // and also: https://stackoverflow.com/questions/52494458/nocopy-minimal-example 8 | 9 | type noCopy struct{} 10 | 11 | func (*noCopy) Lock() {} 12 | func (*noCopy) Unlock() {} 13 | -------------------------------------------------------------------------------- /udp/nocopy.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | // Embed this type into a struct, which mustn't be copied, 4 | // so `go vet` gives a warning if this struct is copied. 5 | // 6 | // See https://github.com/golang/go/issues/8005#issuecomment-190753527 for details. 7 | // and also: https://stackoverflow.com/questions/52494458/nocopy-minimal-example 8 | 9 | type noCopy struct{} 10 | 11 | func (*noCopy) Lock() {} 12 | func (*noCopy) Unlock() {} 13 | -------------------------------------------------------------------------------- /dtls/nocopy.go: -------------------------------------------------------------------------------- 1 | package dtls 2 | 3 | // Embed this type into a struct, which mustn't be copied, 4 | // so `go vet` gives a warning if this struct is copied. 5 | // 6 | // See https://github.com/golang/go/issues/8005#issuecomment-190753527 for details. 7 | // and also: https://stackoverflow.com/questions/52494458/nocopy-minimal-example 8 | 9 | type noCopy struct{} 10 | 11 | func (*noCopy) Lock() {} 12 | func (*noCopy) Unlock() {} 13 | -------------------------------------------------------------------------------- /net/udp.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | // WriteToUDP acts just like net.UDPConn.WriteTo(), but uses a *SessionUDP instead of a net.Addr. 8 | func WriteToUDP(conn *net.UDPConn, raddr *net.UDPAddr, b []byte) (int, error) { 9 | if conn.RemoteAddr() == nil { 10 | // Connection remote address must be nil otherwise 11 | // "WriteTo with pre-connected connection" will be thrown 12 | return conn.WriteToUDP(b, raddr) 13 | } 14 | return conn.Write(b) 15 | 16 | } 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/matrix-org/go-coap/v2 2 | 3 | require ( 4 | github.com/dsnet/golib/memfile v0.0.0-20200723050859-c110804dfa93 5 | github.com/flynn/noise v1.0.0 6 | github.com/patrickmn/go-cache v2.1.0+incompatible 7 | github.com/pion/dtls/v2 v2.0.10-0.20210502094952-3dc563b9aede 8 | github.com/plgd-dev/kit v0.0.0-20200819113605-d5fcf3e94f63 9 | github.com/stretchr/testify v1.7.0 10 | go.uber.org/atomic v1.6.0 11 | golang.org/x/net v0.0.0-20210502030024-e5908800b52b 12 | ) 13 | 14 | go 1.13 15 | -------------------------------------------------------------------------------- /message/getToken.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/hex" 6 | ) 7 | 8 | type Token []byte 9 | 10 | func (t Token) String() string { 11 | return hex.EncodeToString(t) 12 | } 13 | 14 | // GetToken generates a random token by a given length 15 | func GetToken() (Token, error) { 16 | b := make(Token, 8) 17 | _, err := rand.Read(b) 18 | // Note that err == nil only if we read len(b) bytes. 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | return b, nil 24 | } 25 | -------------------------------------------------------------------------------- /examples/dtls/pki/cert_gen_test.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestGenerateCA(t *testing.T) { 10 | ca, cert, key, caPriv, err := GenerateCA() 11 | require.NoError(t, err) 12 | require.Contains(t, string(cert), "-----BEGIN CERTIFICATE-----") 13 | require.Contains(t, string(key), "-----BEGIN EC PRIVATE KEY-----") 14 | 15 | cert, key, err = GenerateCertificate(ca, caPriv, "cert@test.com") 16 | require.NoError(t, err) 17 | require.Contains(t, string(cert), "-----BEGIN CERTIFICATE-----") 18 | require.Contains(t, string(key), "-----BEGIN EC PRIVATE KEY-----") 19 | } 20 | -------------------------------------------------------------------------------- /examples/simple/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | "github.com/matrix-org/go-coap/v2/udp" 10 | ) 11 | 12 | func main() { 13 | co, err := udp.Dial("localhost:5688") 14 | if err != nil { 15 | log.Fatalf("Error dialing: %v", err) 16 | } 17 | path := "/a" 18 | if len(os.Args) > 1 { 19 | path = os.Args[1] 20 | } 21 | 22 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 23 | defer cancel() 24 | resp, err := co.Get(ctx, path) 25 | if err != nil { 26 | log.Fatalf("Error sending request: %v", err) 27 | } 28 | log.Printf("Response payload: %v", resp.String()) 29 | } 30 | -------------------------------------------------------------------------------- /mux/example_logging_middleware_test.go: -------------------------------------------------------------------------------- 1 | package mux_test 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/matrix-org/go-coap/v2/mux" 7 | ) 8 | 9 | // Middleware function, which will be called for each request 10 | func loggingMiddleware(next mux.Handler) mux.Handler { 11 | return mux.HandlerFunc(func(w mux.ResponseWriter, r *mux.Message) { 12 | log.Printf("ClientAddress %v, %v\n", w.Client().RemoteAddr(), r.String()) 13 | next.ServeCOAP(w, r) 14 | }) 15 | } 16 | 17 | func Example_authenticationMiddleware() { 18 | r := mux.NewRouter() 19 | r.HandleFunc("/", func(w mux.ResponseWriter, r *mux.Message) { 20 | // Do something here 21 | }) 22 | r.Use(loggingMiddleware) 23 | } 24 | -------------------------------------------------------------------------------- /mux/message.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import "github.com/matrix-org/go-coap/v2/message" 4 | 5 | // Message contains message with sequence number. 6 | type Message struct { 7 | *message.Message 8 | // SequenceNumber identifies the order of the message from a TCP connection. For UDP it is just for debugging. 9 | SequenceNumber uint64 10 | // IsConfirmable indicates that a UDP message is confirmable. For TCP the value has no semantic. 11 | // When a handler blocks a confirmable message, the client might decide to issue a re-transmission. 12 | // Long running handlers can be handled in a go routine and send the response via w.Client(). 13 | // The ACK is sent as soon as the handler returns. 14 | IsConfirmable bool 15 | } 16 | -------------------------------------------------------------------------------- /net/observation/observation.go: -------------------------------------------------------------------------------- 1 | package observation 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // ObservationSequenceTimeout defines how long is sequence number is valid. https://tools.ietf.org/html/rfc7641#section-3.4 8 | const ObservationSequenceTimeout = 128 * time.Second 9 | 10 | // ValidSequenceNumber implements conditions in https://tools.ietf.org/html/rfc7641#section-3.4 11 | func ValidSequenceNumber(old, new uint32, lastEventOccurs time.Time, now time.Time) bool { 12 | if old < new && (new-old) < (1<<23) { 13 | return true 14 | } 15 | if old > new && (old-new) > (1<<23) { 16 | return true 17 | } 18 | if now.Sub(lastEventOccurs) > ObservationSequenceTimeout { 19 | return true 20 | } 21 | return false 22 | } 23 | -------------------------------------------------------------------------------- /examples/observe/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "time" 7 | 8 | "github.com/matrix-org/go-coap/v2/udp" 9 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 10 | ) 11 | 12 | func main() { 13 | sync := make(chan bool) 14 | co, err := udp.Dial("localhost:5688") 15 | if err != nil { 16 | log.Fatalf("Error dialing: %v", err) 17 | } 18 | num := 0 19 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 20 | defer cancel() 21 | obs, err := co.Observe(ctx, "/some/path", func(req *pool.Message) { 22 | log.Printf("Got %+v\n", req) 23 | num++ 24 | if num >= 10 { 25 | sync <- true 26 | } 27 | }) 28 | if err != nil { 29 | log.Fatalf("Unexpected error '%v'", err) 30 | } 31 | <-sync 32 | ctx, cancel = context.WithTimeout(context.Background(), time.Second) 33 | defer cancel() 34 | obs.Cancel(ctx) 35 | } 36 | -------------------------------------------------------------------------------- /message/error.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrTooSmall = errors.New("too small bytes buffer") 7 | ErrInvalidOptionHeaderExt = errors.New("invalid option header ext") 8 | ErrInvalidTokenLen = errors.New("invalid token length") 9 | ErrInvalidValueLength = errors.New("invalid value length") 10 | ErrShortRead = errors.New("invalid shor read") 11 | ErrOptionTruncated = errors.New("option truncated") 12 | ErrOptionUnexpectedExtendMarker = errors.New("option unexpected extend marker") 13 | ErrOptionsTooSmall = errors.New("too small options buffer") 14 | ErrInvalidEncoding = errors.New("invalid encoding") 15 | ErrOptionNotFound = errors.New("option not found") 16 | ErrOptionDuplicate = errors.New("duplicated option") 17 | ) 18 | -------------------------------------------------------------------------------- /tcp/example_test.go: -------------------------------------------------------------------------------- 1 | package tcp_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "time" 9 | 10 | "github.com/matrix-org/go-coap/v2/net" 11 | "github.com/matrix-org/go-coap/v2/tcp" 12 | ) 13 | 14 | func ExampleGet() { 15 | conn, err := tcp.Dial("pluggedin.cloud:5683") 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | defer conn.Close() 20 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 21 | defer cancel() 22 | res, err := conn.Get(ctx, "/oic/res") 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | data, err := ioutil.ReadAll(res.Body()) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | fmt.Printf("%v", data) 31 | } 32 | 33 | func ExampleServe() { 34 | l, err := net.NewTCPListener("tcp", "0.0.0.0:5683") 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | defer l.Close() 39 | s := tcp.NewServer() 40 | defer s.Stop() 41 | log.Fatal(s.Serve(l)) 42 | } 43 | -------------------------------------------------------------------------------- /message/getETag.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "encoding/binary" 5 | "hash/crc64" 6 | "io" 7 | ) 8 | 9 | // GetETag calculate ETag from payload via CRC64 10 | func GetETag(r io.ReadSeeker) ([]byte, error) { 11 | if r == nil { 12 | return make([]byte, 8), nil 13 | } 14 | c64 := crc64.New(crc64.MakeTable(crc64.ISO)) 15 | orig, err := r.Seek(0, io.SeekCurrent) 16 | if err != nil { 17 | return nil, err 18 | } 19 | _, err = r.Seek(0, io.SeekStart) 20 | if err != nil { 21 | return nil, err 22 | } 23 | buf := make([]byte, 4096) 24 | for { 25 | bufR := buf 26 | n, err := r.Read(bufR) 27 | if err == io.EOF { 28 | break 29 | } 30 | if err != nil { 31 | return nil, err 32 | } 33 | bufR = bufR[:n] 34 | c64.Write(bufR) 35 | } 36 | _, err = r.Seek(orig, io.SeekStart) 37 | if err != nil { 38 | return nil, err 39 | } 40 | b := make([]byte, 8) 41 | binary.LittleEndian.PutUint64(b, c64.Sum64()) 42 | return b, nil 43 | } 44 | -------------------------------------------------------------------------------- /examples/mcast/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "time" 7 | 8 | "github.com/matrix-org/go-coap/v2/net" 9 | "github.com/matrix-org/go-coap/v2/udp" 10 | "github.com/matrix-org/go-coap/v2/udp/client" 11 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 12 | ) 13 | 14 | func main() { 15 | l, err := net.NewListenUDP("udp4", "") 16 | if err != nil { 17 | log.Println(err) 18 | return 19 | } 20 | defer l.Close() 21 | s := udp.NewServer() 22 | defer s.Stop() 23 | go func() { 24 | err := s.Serve(l) 25 | if err != nil { 26 | log.Println(err) 27 | } 28 | }() 29 | 30 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 31 | defer cancel() 32 | err = s.Discover(ctx, "224.0.1.187:5683", "/oic/res", func(cc *client.ClientConn, resp *pool.Message) { 33 | log.Printf("discovered %v: %+v", cc.RemoteAddr(), resp.Message) 34 | }) 35 | if err != nil { 36 | log.Println(err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /net/isTemporary.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | // https://github.com/golang/go/blob/958e212db799e609b2a8df51cdd85c9341e7a404/src/internal/poll/fd.go#L43 10 | const ioTimeout = "i/o timeout" 11 | 12 | func isTemporary(err error, deadline time.Time) bool { 13 | netErr, ok := err.(net.Error) 14 | if ok { 15 | if netErr.Timeout() { 16 | // when connection is closed during TLS handshake, it returns i/o timeout 17 | // so we need to validate if timeout real occurs by set deadline otherwise infinite loop occurs. 18 | return deadline.Before(time.Now()) 19 | } 20 | if netErr.Temporary() { 21 | return true 22 | } 23 | } 24 | 25 | if strings.Contains(err.Error(), ioTimeout) { 26 | // when connection is closed during TLS handshake, it returns i/o timeout 27 | // so we need to validate if timeout real occurs by set deadline otherwise infinite loop occurs. 28 | return deadline.Before(time.Now()) 29 | } 30 | return false 31 | } 32 | -------------------------------------------------------------------------------- /udp/message/type.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // Type represents the message type. 8 | // It's only part of CoAP UDP messages. 9 | // Reliable transports like TCP do not have a type. 10 | type Type uint8 11 | 12 | const ( 13 | // Confirmable messages require acknowledgements. 14 | Confirmable Type = 0 15 | // NonConfirmable messages do not require acknowledgements. 16 | NonConfirmable Type = 1 17 | // Acknowledgement is a message indicating a response to confirmable message. 18 | Acknowledgement Type = 2 19 | // Reset indicates a permanent negative acknowledgement. 20 | Reset Type = 3 21 | ) 22 | 23 | var typeToString = map[Type]string{ 24 | Confirmable: "Confirmable", 25 | NonConfirmable: "NonConfirmable", 26 | Acknowledgement: "Acknowledgement", 27 | Reset: "reset", 28 | } 29 | 30 | func (t Type) String() string { 31 | val, ok := typeToString[t] 32 | if ok { 33 | return val 34 | } 35 | return "Type(" + strconv.FormatInt(int64(t), 10) + ")" 36 | } 37 | -------------------------------------------------------------------------------- /examples/dtls/psk/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | piondtls "github.com/pion/dtls/v2" 11 | "github.com/matrix-org/go-coap/v2/dtls" 12 | ) 13 | 14 | func main() { 15 | co, err := dtls.Dial("localhost:5688", &piondtls.Config{ 16 | PSK: func(hint []byte) ([]byte, error) { 17 | fmt.Printf("Server's hint: %s \n", hint) 18 | return []byte{0xAB, 0xC1, 0x23}, nil 19 | }, 20 | PSKIdentityHint: []byte("Pion DTLS Client"), 21 | CipherSuites: []piondtls.CipherSuiteID{piondtls.TLS_PSK_WITH_AES_128_CCM_8}, 22 | }) 23 | if err != nil { 24 | log.Fatalf("Error dialing: %v", err) 25 | } 26 | path := "/a" 27 | if len(os.Args) > 1 { 28 | path = os.Args[1] 29 | } 30 | 31 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 32 | defer cancel() 33 | resp, err := co.Get(ctx, path) 34 | if err != nil { 35 | log.Fatalf("Error sending request: %v", err) 36 | } 37 | log.Printf("Response payload: %+v", resp) 38 | } 39 | -------------------------------------------------------------------------------- /tcp/message/pool/message_test.go: -------------------------------------------------------------------------------- 1 | package pool_test 2 | 3 | import ( 4 | "context" 5 | "io/ioutil" 6 | "sync" 7 | "testing" 8 | 9 | "github.com/matrix-org/go-coap/v2/message/codes" 10 | "github.com/matrix-org/go-coap/v2/tcp/message/pool" 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func Test_ConvertTo(t *testing.T) { 16 | msg := pool.AcquireMessage(context.Background()) 17 | _, err := msg.Unmarshal([]byte{35, byte(codes.GET), 0x1, 0x2, 0x3, 0xff, 0x1}) 18 | require.NoError(t, err) 19 | a, _ := pool.ConvertTo(msg) 20 | require.NoError(t, err) 21 | pool.ReleaseMessage(msg) 22 | var wg sync.WaitGroup 23 | wg.Add(1) 24 | go func() { 25 | defer wg.Done() 26 | buf, err := ioutil.ReadAll(a.Body) 27 | assert.NoError(t, err) 28 | assert.Equal(t, []byte{1}, buf) 29 | }() 30 | msg = pool.AcquireMessage(context.Background()) 31 | _, err = msg.Unmarshal([]byte{35, byte(codes.GET), 0x1, 0x2, 0x3, 0xff, 0x2}) 32 | require.NoError(t, err) 33 | wg.Wait() 34 | } 35 | -------------------------------------------------------------------------------- /udp/message/pool/message_test.go: -------------------------------------------------------------------------------- 1 | package pool_test 2 | 3 | import ( 4 | "context" 5 | "io/ioutil" 6 | "sync" 7 | "testing" 8 | 9 | "github.com/matrix-org/go-coap/v2/message/codes" 10 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func Test_ConvertTo(t *testing.T) { 16 | msg := pool.AcquireMessage(context.Background()) 17 | _, err := msg.Unmarshal([]byte{67, byte(codes.GET), 0, 0, 0x1, 0x2, 0x3, 0xff, 0x1}) 18 | require.NoError(t, err) 19 | a, _ := pool.ConvertTo(msg) 20 | require.NoError(t, err) 21 | pool.ReleaseMessage(msg) 22 | var wg sync.WaitGroup 23 | wg.Add(1) 24 | go func() { 25 | defer wg.Done() 26 | buf, err := ioutil.ReadAll(a.Body) 27 | assert.NoError(t, err) 28 | assert.Equal(t, []byte{1}, buf) 29 | }() 30 | msg = pool.AcquireMessage(context.Background()) 31 | _, err = msg.Unmarshal([]byte{67, byte(codes.GET), 0, 0, 0x1, 0x2, 0x3, 0xff, 0x2}) 32 | require.NoError(t, err) 33 | wg.Wait() 34 | } 35 | -------------------------------------------------------------------------------- /message/message.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/matrix-org/go-coap/v2/message/codes" 9 | ) 10 | 11 | // MaxTokenSize maximum of token size that can be used in message 12 | const MaxTokenSize = 8 13 | 14 | type Message struct { 15 | // Context context of request. 16 | Context context.Context 17 | Token Token 18 | Code codes.Code 19 | Options Options 20 | // Body of message. It is nil for message without body. 21 | Body io.ReadSeeker 22 | } 23 | 24 | func (r *Message) String() string { 25 | buf := fmt.Sprintf("Code: %v, Token: %v", r.Code, r.Token) 26 | path, err := r.Options.Path() 27 | if err == nil { 28 | buf = fmt.Sprintf("%s, Path: %v", buf, path) 29 | } 30 | cf, err := r.Options.ContentFormat() 31 | if err == nil { 32 | mt := MediaType(cf) 33 | buf = fmt.Sprintf("%s, ContentFormat: %v", buf, mt) 34 | } 35 | queries, err := r.Options.Queries() 36 | if err == nil { 37 | buf = fmt.Sprintf("%s, Queries: %+v", buf, queries) 38 | } 39 | return buf 40 | } 41 | -------------------------------------------------------------------------------- /net/blockwise/error.go: -------------------------------------------------------------------------------- 1 | package blockwise 2 | 3 | import "errors" 4 | 5 | var ( 6 | // ErrBlockNumberExceedLimit block number exceeded the limit 1,048,576 7 | ErrBlockNumberExceedLimit = errors.New("block number exceeded the limit 1,048,576") 8 | 9 | // ErrBlockInvalidSize block has invalid size 10 | ErrBlockInvalidSize = errors.New("block has invalid size") 11 | 12 | // ErrInvalidOptionBlock2 message has invalid value of Block2 13 | ErrInvalidOptionBlock2 = errors.New("message has invalid value of Block2") 14 | 15 | // ErrInvalidOptionBlock1 message has invalid value of Block1 16 | ErrInvalidOptionBlock1 = errors.New("message has invalid value of Block1") 17 | 18 | // ErrInvalidReponseCode response code has invalid value 19 | ErrInvalidReponseCode = errors.New("response code has invalid value") 20 | 21 | // ErrInvalidPayloadSize invalid payload size 22 | ErrInvalidPayloadSize = errors.New("invalid payload size") 23 | 24 | // ErrInvalidSZX invalid block-wise transfer szx 25 | ErrInvalidSZX = errors.New("invalid block-wise transfer szx") 26 | ) 27 | -------------------------------------------------------------------------------- /mux/middleware.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | // MiddlewareFunc is a function which receives an Handler and returns another Handler. 4 | // Typically, the returned handler is a closure which does something with the ResponseWriter and Message passed 5 | // to it, and then calls the handler passed as parameter to the MiddlewareFunc. 6 | type MiddlewareFunc func(Handler) Handler 7 | 8 | // middleware interface is anything which implements a MiddlewareFunc named Middleware. 9 | type middleware interface { 10 | Middleware(handler Handler) Handler 11 | } 12 | 13 | // Middleware allows MiddlewareFunc to implement the middleware interface. 14 | func (mw MiddlewareFunc) Middleware(handler Handler) Handler { 15 | return mw(handler) 16 | } 17 | 18 | // Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. 19 | func (r *Router) Use(mwf ...MiddlewareFunc) { 20 | for _, fn := range mwf { 21 | r.middlewares = append(r.middlewares, fn) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tcp/optionmux.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/matrix-org/go-coap/v2/message" 7 | "github.com/matrix-org/go-coap/v2/message/codes" 8 | "github.com/matrix-org/go-coap/v2/mux" 9 | "github.com/matrix-org/go-coap/v2/tcp/message/pool" 10 | ) 11 | 12 | // WithMux set's multiplexer for handle requests. 13 | func WithMux(m mux.Handler) HandlerFuncOpt { 14 | h := func(w *ResponseWriter, r *pool.Message) { 15 | muxw := &muxResponseWriter{ 16 | w: w, 17 | } 18 | muxr, err := pool.ConvertTo(r) 19 | if err != nil { 20 | return 21 | } 22 | m.ServeCOAP(muxw, &mux.Message{ 23 | Message: muxr, 24 | SequenceNumber: r.Sequence(), 25 | }) 26 | } 27 | return WithHandlerFunc(h) 28 | } 29 | 30 | type muxResponseWriter struct { 31 | w *ResponseWriter 32 | } 33 | 34 | func (w *muxResponseWriter) SetResponse(code codes.Code, contentFormat message.MediaType, d io.ReadSeeker, opts ...message.Option) error { 35 | return w.w.SetResponse(code, contentFormat, d, opts...) 36 | } 37 | 38 | func (w *muxResponseWriter) Client() mux.Client { 39 | return w.w.ClientConn().Client() 40 | } 41 | -------------------------------------------------------------------------------- /message/encodeDecodeUint32.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | func EncodeUint32(buf []byte, value uint32) (int, error) { 8 | switch { 9 | case value == 0: 10 | return 0, nil 11 | case value <= max1ByteNumber: 12 | if len(buf) < 1 { 13 | return 1, ErrTooSmall 14 | } 15 | buf[0] = byte(value) 16 | return 1, nil 17 | case value <= max2ByteNumber: 18 | if len(buf) < 2 { 19 | return 2, ErrTooSmall 20 | } 21 | binary.BigEndian.PutUint16(buf, uint16(value)) 22 | return 2, nil 23 | case value <= max3ByteNumber: 24 | if len(buf) < 3 { 25 | return 3, ErrTooSmall 26 | } 27 | rv := make([]byte, 4) 28 | binary.BigEndian.PutUint32(rv[:], value) 29 | copy(buf, rv[1:]) 30 | return 3, nil 31 | default: 32 | if len(buf) < 4 { 33 | return 4, ErrTooSmall 34 | } 35 | binary.BigEndian.PutUint32(buf, value) 36 | return 4, nil 37 | } 38 | } 39 | 40 | func DecodeUint32(buf []byte) (uint32, int, error) { 41 | if len(buf) > 4 { 42 | buf = buf[:4] 43 | } 44 | tmp := []byte{0, 0, 0, 0} 45 | copy(tmp[4-len(buf):], buf) 46 | value := binary.BigEndian.Uint32(tmp) 47 | return value, len(buf), nil 48 | } 49 | -------------------------------------------------------------------------------- /message/noresponse/noresponse_test.go: -------------------------------------------------------------------------------- 1 | package noresponse 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/matrix-org/go-coap/v2/message/codes" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestNoResponse2XXCodes(t *testing.T) { 11 | codes := decodeNoResponseOption(2) 12 | exp := resp2XXCodes 13 | require.Equal(t, exp, codes) 14 | } 15 | 16 | func TestNoResponse4XXCodes(t *testing.T) { 17 | codes := decodeNoResponseOption(8) 18 | exp := resp4XXCodes 19 | require.Equal(t, exp, codes) 20 | } 21 | 22 | func TestNoResponse5XXCodes(t *testing.T) { 23 | codes := decodeNoResponseOption(16) 24 | exp := resp5XXCodes 25 | require.Equal(t, exp, codes) 26 | } 27 | 28 | func TestNoResponseCombinationXXCodes(t *testing.T) { 29 | codes := decodeNoResponseOption(18) 30 | exp := append(resp2XXCodes, resp5XXCodes...) 31 | require.Equal(t, exp, codes) 32 | } 33 | 34 | func TestNoResponseAllCodes(t *testing.T) { 35 | allCodes := decodeNoResponseOption(0) 36 | exp := []codes.Code(nil) 37 | require.Equal(t, exp, allCodes) 38 | } 39 | 40 | func TestNoResponseBehaviour(t *testing.T) { 41 | err := IsNoResponseCode(codes.Content, 2) 42 | require.Error(t, err) 43 | } 44 | -------------------------------------------------------------------------------- /udp/client/mux.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/matrix-org/go-coap/v2/message" 7 | "github.com/matrix-org/go-coap/v2/message/codes" 8 | "github.com/matrix-org/go-coap/v2/mux" 9 | udpMessage "github.com/matrix-org/go-coap/v2/udp/message" 10 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 11 | ) 12 | 13 | func HandlerFuncToMux(m mux.Handler) HandlerFunc { 14 | h := func(w *ResponseWriter, r *pool.Message) { 15 | muxw := &muxResponseWriter{ 16 | w: w, 17 | } 18 | muxr, err := pool.ConvertTo(r) 19 | if err != nil { 20 | return 21 | } 22 | m.ServeCOAP(muxw, &mux.Message{ 23 | Message: muxr, 24 | SequenceNumber: r.Sequence(), 25 | IsConfirmable: r.Type() == udpMessage.Confirmable, 26 | }) 27 | } 28 | return h 29 | } 30 | 31 | type muxResponseWriter struct { 32 | w *ResponseWriter 33 | } 34 | 35 | func (w *muxResponseWriter) SetResponse(code codes.Code, contentFormat message.MediaType, d io.ReadSeeker, opts ...message.Option) error { 36 | return w.w.SetResponse(code, contentFormat, d, opts...) 37 | } 38 | 39 | func (w *muxResponseWriter) Client() mux.Client { 40 | return w.w.ClientConn().Client() 41 | } 42 | -------------------------------------------------------------------------------- /mux/client.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | 8 | "github.com/matrix-org/go-coap/v2/message" 9 | ) 10 | 11 | type Observation = interface { 12 | Cancel(ctx context.Context) error 13 | } 14 | 15 | type Client interface { 16 | Ping(ctx context.Context) error 17 | Get(ctx context.Context, path string, opts ...message.Option) (*message.Message, error) 18 | Delete(ctx context.Context, path string, opts ...message.Option) (*message.Message, error) 19 | Post(ctx context.Context, path string, contentFormat message.MediaType, payload io.ReadSeeker, opts ...message.Option) (*message.Message, error) 20 | Put(ctx context.Context, path string, contentFormat message.MediaType, payload io.ReadSeeker, opts ...message.Option) (*message.Message, error) 21 | Observe(ctx context.Context, path string, observeFunc func(notification *message.Message), opts ...message.Option) (Observation, error) 22 | ClientConn() interface{} 23 | 24 | RemoteAddr() net.Addr 25 | Context() context.Context 26 | SetContextValue(key interface{}, val interface{}) 27 | WriteMessage(req *message.Message) error 28 | Do(req *message.Message) (*message.Message, error) 29 | Close() error 30 | Sequence() uint64 31 | } 32 | -------------------------------------------------------------------------------- /message/codes/codes_test.go: -------------------------------------------------------------------------------- 1 | package codes 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestJSONUnmarshal(t *testing.T) { 11 | var got []Code 12 | want := []Code{GET, NotFound, InternalServerError, Abort} 13 | in := `["GET", "NotFound", "InternalServerError", "Abort"]` 14 | err := json.Unmarshal([]byte(in), &got) 15 | require.NoError(t, err) 16 | require.Equal(t, want, got) 17 | } 18 | 19 | func TestUnmarshalJSON_NilReceiver(t *testing.T) { 20 | var got *Code 21 | in := GET.String() 22 | err := got.UnmarshalJSON([]byte(in)) 23 | require.Error(t, err) 24 | } 25 | 26 | func TestUnmarshalJSON_UnknownInput(t *testing.T) { 27 | var got Code 28 | for _, in := range [][]byte{[]byte(""), []byte("xxx"), []byte("Code(17)"), nil} { 29 | err := got.UnmarshalJSON([]byte(in)) 30 | require.Error(t, err) 31 | } 32 | } 33 | 34 | func TestUnmarshalJSON_MarshalUnmarshal(t *testing.T) { 35 | for i := 0; i < _maxCode; i++ { 36 | var cUnMarshaled Code 37 | c := Code(i) 38 | 39 | cJSON, err := json.Marshal(c) 40 | require.NoError(t, err) 41 | 42 | err = json.Unmarshal(cJSON, &cUnMarshaled) 43 | require.NoError(t, err) 44 | 45 | require.Equal(t, c, cUnMarshaled) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /message/encodeDecodeUint32_test.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestEncodeUint32(t *testing.T) { 10 | type args struct { 11 | value uint32 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want int 17 | wantErr bool 18 | }{ 19 | { 20 | name: "0", 21 | args: args{0}, 22 | }, 23 | { 24 | name: "256", 25 | args: args{256}, 26 | want: 2, 27 | }, 28 | { 29 | name: "16384", 30 | args: args{16384}, 31 | want: 2, 32 | }, 33 | { 34 | name: "5000000", 35 | args: args{5000000}, 36 | want: 3, 37 | }, 38 | { 39 | name: "20000000", 40 | args: args{20000000}, 41 | want: 4, 42 | }, 43 | } 44 | for _, tt := range tests { 45 | t.Run(tt.name, func(t *testing.T) { 46 | buf := make([]byte, 4) 47 | got, err := EncodeUint32(buf, tt.args.value) 48 | if tt.wantErr { 49 | require.Error(t, err) 50 | return 51 | } 52 | require.NoError(t, err) 53 | require.Equal(t, tt.want, got) 54 | buf = buf[:got] 55 | val, n, err := DecodeUint32(buf) 56 | require.NoError(t, err) 57 | require.Equal(t, len(buf), n) 58 | require.Equal(t, tt.args.value, val) 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /examples/dtls/pki/cert_generation.md: -------------------------------------------------------------------------------- 1 | # Server And Client PKI generation 2 | The certificates used in the pki examples are generated using golang crypto library. 3 | You can also generate them using openssl, as seen below. 4 | 5 | ## Generate self signed CA 6 | ```sh 7 | CERT_SUBJ="/C=BR/ST=Parana/L=Curitiba/O=Dis/CN=example.com" 8 | openssl ecparam -name secp224r1 -genkey -noout -out root_ca_key.pem 9 | openssl ec -in root_ca_key.pem -pubout -out root_ca_pubkey.pem 10 | openssl req -new -key root_ca_key.pem -x509 -nodes -days 365 -out root_ca_cert.pem -subj $CERT_SUBJ 11 | ``` 12 | 13 | ## Generate server 14 | ```sh 15 | openssl ecparam -name secp224r1 -genkey -noout -out server_key.pem 16 | openssl req -new -sha256 -key server_key.pem -subj $CERT_SUBJ -out server.csr 17 | openssl x509 -req -in server.csr -CA root_ca_cert.pem -CAkey root_ca_key.pem -CAcreateserial -out server_cert.pem -days 500 -sha256 18 | ``` 19 | 20 | ## Generate client 21 | ```sh 22 | CERT_SUBJ="/C=BR/ST=Parana/L=Curitiba/O=Dis/CN=example.com/emailAddress=client1@example.com" 23 | openssl ecparam -name secp224r1 -genkey -noout -out client_key.pem 24 | openssl req -new -sha256 -key client_key.pem -subj $CERT_SUBJ -out client.csr 25 | openssl x509 -req -in client.csr -CA root_ca_cert.pem -CAkey root_ca_key.pem -CAcreateserial -out client_cert.pem -days 500 -sha256 26 | ``` 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Test 4 | 5 | # Controls when the action will run. 6 | on: 7 | pull_request: 8 | 9 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 10 | jobs: 11 | # This workflow contains a single job called "build" 12 | test: 13 | # The type of runner that the job will run on 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest, macOS-latest] 18 | 19 | # Steps represent a sequence of tasks that will be executed as part of the job 20 | steps: 21 | - name: Set up Go 1.15 22 | uses: actions/setup-go@v2 23 | with: 24 | go-version: ^1.15 25 | 26 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 27 | - name: Checkout 28 | uses: actions/checkout@v2 29 | 30 | # Runs a single command using the runners shell 31 | - name: Run a build 32 | run: go build ./... 33 | 34 | # Runs a single command using the runners shell, -p1 for `race: limit on 8128 simultaneously alive goroutines is exceeded, dying` at macos 35 | - name: Run a test 36 | run: go test -race -p 1 ./... -covermode=atomic -coverprofile=./coverage.txt 37 | -------------------------------------------------------------------------------- /message/getETag_test.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestGetETag(t *testing.T) { 11 | got, err := GetETag(bytes.NewReader([]byte("vfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGhvfbslVuq3Ql5XUGh"))) 12 | require.NoError(t, err) 13 | require.Equal(t, []byte{0x54, 0x4e, 0x28, 0x79, 0x1c, 0x23, 0x17, 0x24}, got) 14 | } 15 | -------------------------------------------------------------------------------- /net/monitor/inactivity/keepalive.go: -------------------------------------------------------------------------------- 1 | package inactivity 2 | 3 | import ( 4 | "sync/atomic" 5 | ) 6 | 7 | type KeepAlive struct { 8 | pongToken uint64 9 | sendToken uint64 10 | numFails uint32 11 | 12 | maxRetries uint32 13 | onInactive OnInactiveFunc 14 | 15 | sendPing func(cc ClientConn, receivePong func()) (func(), error) 16 | cancelPing func() 17 | } 18 | 19 | func NewKeepAlive(maxRetries uint32, onInactive OnInactiveFunc, sendPing func(cc ClientConn, receivePong func()) (func(), error)) *KeepAlive { 20 | return &KeepAlive{ 21 | maxRetries: maxRetries, 22 | sendPing: sendPing, 23 | onInactive: onInactive, 24 | } 25 | } 26 | 27 | func (m *KeepAlive) OnInactive(cc ClientConn) { 28 | v := m.incrementFails() 29 | if m.cancelPing != nil { 30 | m.cancelPing() 31 | m.cancelPing = nil 32 | } 33 | if v > m.maxRetries { 34 | m.onInactive(cc) 35 | return 36 | } 37 | pongToken := atomic.AddUint64(&m.pongToken, 1) 38 | cancel, err := m.sendPing(cc, func() { 39 | if atomic.LoadUint64(&m.pongToken) == pongToken { 40 | m.resetFails() 41 | } 42 | }) 43 | if err != nil { 44 | return 45 | } 46 | m.cancelPing = cancel 47 | } 48 | 49 | func (m *KeepAlive) incrementFails() uint32 { 50 | return atomic.AddUint32(&m.numFails, 1) 51 | } 52 | 53 | func (m *KeepAlive) resetFails() { 54 | atomic.StoreUint32(&m.numFails, 0) 55 | } 56 | -------------------------------------------------------------------------------- /tcp/container.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/matrix-org/go-coap/v2/message" 8 | ) 9 | 10 | // HandlerContainer for regirstration handlers by key 11 | type HandlerContainer struct { 12 | datas map[interface{}]HandlerFunc 13 | mutex sync.Mutex 14 | } 15 | 16 | // NewHandlerContainer factory 17 | func NewHandlerContainer() *HandlerContainer { 18 | return &HandlerContainer{ 19 | datas: make(map[interface{}]HandlerFunc), 20 | } 21 | } 22 | 23 | // Insert handler for key. 24 | func (s *HandlerContainer) Insert(key interface{}, handler HandlerFunc) error { 25 | if v, ok := key.(message.Token); ok { 26 | key = v.String() 27 | } 28 | s.mutex.Lock() 29 | defer s.mutex.Unlock() 30 | if s.datas[key] != nil { 31 | return fmt.Errorf("key already exist") 32 | } 33 | s.datas[key] = handler 34 | return nil 35 | } 36 | 37 | // Get returns handler for key 38 | func (s *HandlerContainer) Get(key interface{}) (HandlerFunc, error) { 39 | if v, ok := key.(message.Token); ok { 40 | key = v.String() 41 | } 42 | s.mutex.Lock() 43 | defer s.mutex.Unlock() 44 | v := s.datas[key] 45 | if v == nil { 46 | return nil, fmt.Errorf("key not exist") 47 | } 48 | return v, nil 49 | } 50 | 51 | // Pop pops handler for key 52 | func (s *HandlerContainer) Pop(key interface{}) (HandlerFunc, error) { 53 | if v, ok := key.(message.Token); ok { 54 | key = v.String() 55 | } 56 | s.mutex.Lock() 57 | defer s.mutex.Unlock() 58 | v := s.datas[key] 59 | if v == nil { 60 | return nil, fmt.Errorf("key not exist") 61 | } 62 | delete(s.datas, key) 63 | return v, nil 64 | } 65 | -------------------------------------------------------------------------------- /net/monitor/inactivity/inactivitymonitor.go: -------------------------------------------------------------------------------- 1 | package inactivity 2 | 3 | import ( 4 | "context" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | type Monitor = interface { 10 | CheckInactivity(cc ClientConn) 11 | Notify() 12 | } 13 | 14 | type OnInactiveFunc = func(cc ClientConn) 15 | 16 | type ClientConn = interface { 17 | Context() context.Context 18 | Close() error 19 | } 20 | 21 | type inactivityMonitor struct { 22 | duration time.Duration 23 | onInactive OnInactiveFunc 24 | // lastActivity stores time.Time 25 | lastActivity atomic.Value 26 | } 27 | 28 | func (m *inactivityMonitor) Notify() { 29 | m.lastActivity.Store(time.Now()) 30 | } 31 | 32 | func (m *inactivityMonitor) LastActivity() time.Time { 33 | if t, ok := m.lastActivity.Load().(time.Time); ok { 34 | return t 35 | } 36 | return time.Time{} 37 | } 38 | 39 | func CloseClientConn(cc ClientConn) { 40 | cc.Close() 41 | } 42 | 43 | func NewInactivityMonitor(duration time.Duration, onInactive OnInactiveFunc) Monitor { 44 | m := &inactivityMonitor{ 45 | duration: duration, 46 | onInactive: onInactive, 47 | } 48 | m.Notify() 49 | return m 50 | } 51 | 52 | func (m *inactivityMonitor) CheckInactivity(cc ClientConn) { 53 | if m.onInactive == nil || m.duration == time.Duration(0) { 54 | return 55 | } 56 | if time.Until(m.LastActivity().Add(m.duration)) <= 0 { 57 | m.onInactive(cc) 58 | } 59 | } 60 | 61 | type nilMonitor struct { 62 | } 63 | 64 | func (m *nilMonitor) CheckInactivity(cc ClientConn) { 65 | } 66 | 67 | func (m *nilMonitor) Notify() { 68 | } 69 | 70 | func NewNilMonitor() Monitor { 71 | return &nilMonitor{} 72 | } 73 | -------------------------------------------------------------------------------- /udp/client/container.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/matrix-org/go-coap/v2/message" 8 | ) 9 | 10 | // HandlerContainer for regirstration handlers by key 11 | type HandlerContainer struct { 12 | datas map[interface{}]HandlerFunc 13 | mutex sync.Mutex 14 | } 15 | 16 | // NewHandlerContainer factory 17 | func NewHandlerContainer() *HandlerContainer { 18 | return &HandlerContainer{ 19 | datas: make(map[interface{}]HandlerFunc), 20 | } 21 | } 22 | 23 | // Insert handler for key. 24 | func (s *HandlerContainer) Insert(key interface{}, handler HandlerFunc) error { 25 | if v, ok := key.(message.Token); ok { 26 | key = v.String() 27 | } 28 | s.mutex.Lock() 29 | defer s.mutex.Unlock() 30 | if s.datas[key] != nil { 31 | return fmt.Errorf("key already exist") 32 | } 33 | s.datas[key] = handler 34 | return nil 35 | } 36 | 37 | // Get returns handler for key 38 | func (s *HandlerContainer) Get(key interface{}) (HandlerFunc, error) { 39 | if v, ok := key.(message.Token); ok { 40 | key = v.String() 41 | } 42 | s.mutex.Lock() 43 | defer s.mutex.Unlock() 44 | v := s.datas[key] 45 | if v == nil { 46 | return nil, fmt.Errorf("key not exist") 47 | } 48 | return v, nil 49 | } 50 | 51 | // Pop pops handler for key 52 | func (s *HandlerContainer) Pop(key interface{}) (HandlerFunc, error) { 53 | if v, ok := key.(message.Token); ok { 54 | key = v.String() 55 | } 56 | s.mutex.Lock() 57 | defer s.mutex.Unlock() 58 | v := s.datas[key] 59 | if v == nil { 60 | return nil, fmt.Errorf("key not exist") 61 | } 62 | delete(s.datas, key) 63 | return v, nil 64 | } 65 | -------------------------------------------------------------------------------- /dtls/example_test.go: -------------------------------------------------------------------------------- 1 | package dtls_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "time" 9 | 10 | piondtls "github.com/pion/dtls/v2" 11 | "github.com/matrix-org/go-coap/v2/dtls" 12 | "github.com/matrix-org/go-coap/v2/net" 13 | ) 14 | 15 | func ExampleGet() { 16 | dtlsCfg := &piondtls.Config{ 17 | PSK: func(hint []byte) ([]byte, error) { 18 | fmt.Printf("Hint: %s \n", hint) 19 | return []byte{0xAB, 0xC1, 0x23}, nil 20 | }, 21 | PSKIdentityHint: []byte("Pion DTLS Server"), 22 | CipherSuites: []piondtls.CipherSuiteID{piondtls.TLS_PSK_WITH_AES_128_CCM_8}, 23 | } 24 | conn, err := dtls.Dial("pluggedin.cloud:5684", dtlsCfg) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | defer conn.Close() 29 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 30 | defer cancel() 31 | res, err := conn.Get(ctx, "/oic/res") 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | data, err := ioutil.ReadAll(res.Body()) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | fmt.Printf("%v", data) 40 | } 41 | 42 | func ExampleServe() { 43 | dtlsCfg := &piondtls.Config{ 44 | PSK: func(hint []byte) ([]byte, error) { 45 | fmt.Printf("Hint: %s \n", hint) 46 | return []byte{0xAB, 0xC1, 0x23}, nil 47 | }, 48 | PSKIdentityHint: []byte("Pion DTLS Server"), 49 | CipherSuites: []piondtls.CipherSuiteID{piondtls.TLS_PSK_WITH_AES_128_CCM_8}, 50 | } 51 | l, err := net.NewDTLSListener("udp", "0.0.0.0:5683", dtlsCfg) 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | defer l.Close() 56 | s := dtls.NewServer() 57 | defer s.Stop() 58 | log.Fatal(s.Serve(l)) 59 | } 60 | -------------------------------------------------------------------------------- /examples/mcast/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | gonet "net" 7 | 8 | "github.com/matrix-org/go-coap/v2/message" 9 | "github.com/matrix-org/go-coap/v2/message/codes" 10 | "github.com/matrix-org/go-coap/v2/mux" 11 | "github.com/matrix-org/go-coap/v2/net" 12 | "github.com/matrix-org/go-coap/v2/udp" 13 | ) 14 | 15 | func handleMcast(w mux.ResponseWriter, r *mux.Message) { 16 | path, err := r.Options.Path() 17 | if err != nil { 18 | log.Printf("cannot get path: %v", err) 19 | return 20 | } 21 | 22 | log.Printf("Got mcast message: path=%q: from %v", path, w.Client().RemoteAddr()) 23 | w.SetResponse(codes.Content, message.TextPlain, bytes.NewReader([]byte("mcast response"))) 24 | } 25 | 26 | func main() { 27 | m := mux.NewRouter() 28 | m.Handle("/oic/res", mux.HandlerFunc(handleMcast)) 29 | multicastAddr := "224.0.1.187:5683" 30 | 31 | l, err := net.NewListenUDP("udp4", multicastAddr) 32 | if err != nil { 33 | log.Println(err) 34 | return 35 | } 36 | 37 | ifaces, err := gonet.Interfaces() 38 | if err != nil { 39 | log.Println(err) 40 | return 41 | } 42 | 43 | a, err := gonet.ResolveUDPAddr("udp", multicastAddr) 44 | if err != nil { 45 | log.Println(err) 46 | return 47 | } 48 | 49 | for _, iface := range ifaces { 50 | err := l.JoinGroup(&iface, a) 51 | if err != nil { 52 | log.Printf("cannot JoinGroup(%v, %v): %v", iface, a, err) 53 | } 54 | } 55 | err = l.SetMulticastLoopback(true) 56 | if err != nil { 57 | log.Println(err) 58 | return 59 | } 60 | 61 | defer l.Close() 62 | s := udp.NewServer(udp.WithMux(m)) 63 | defer s.Stop() 64 | log.Fatal(s.Serve(l)) 65 | } 66 | -------------------------------------------------------------------------------- /tcp/responsewriter.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/matrix-org/go-coap/v2/message" 7 | "github.com/matrix-org/go-coap/v2/message/codes" 8 | "github.com/matrix-org/go-coap/v2/message/noresponse" 9 | "github.com/matrix-org/go-coap/v2/tcp/message/pool" 10 | ) 11 | 12 | // A ResponseWriter interface is used by an CAOP handler to construct an COAP response. 13 | type ResponseWriter struct { 14 | noResponseValue *uint32 15 | response *pool.Message 16 | cc *ClientConn 17 | } 18 | 19 | func NewResponseWriter(response *pool.Message, cc *ClientConn, requestOptions message.Options) *ResponseWriter { 20 | var noResponseValue *uint32 21 | v, err := requestOptions.GetUint32(message.NoResponse) 22 | if err == nil { 23 | noResponseValue = &v 24 | } 25 | 26 | return &ResponseWriter{ 27 | response: response, 28 | cc: cc, 29 | noResponseValue: noResponseValue, 30 | } 31 | } 32 | 33 | func (r *ResponseWriter) SetResponse(code codes.Code, contentFormat message.MediaType, d io.ReadSeeker, opts ...message.Option) error { 34 | if r.noResponseValue != nil { 35 | err := noresponse.IsNoResponseCode(code, *r.noResponseValue) 36 | if err != nil { 37 | return err 38 | } 39 | } 40 | 41 | r.response.SetCode(code) 42 | r.response.ResetOptionsTo(opts) 43 | if d != nil { 44 | r.response.SetContentFormat(contentFormat) 45 | r.response.SetBody(d) 46 | if !r.response.HasOption(message.ETag) { 47 | etag, err := message.GetETag(d) 48 | if err != nil { 49 | return err 50 | } 51 | r.response.SetOptionBytes(message.ETag, etag) 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | func (r *ResponseWriter) ClientConn() *ClientConn { 58 | return r.cc 59 | } 60 | -------------------------------------------------------------------------------- /examples/dtls/pki/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | piondtls "github.com/pion/dtls/v2" 11 | "github.com/matrix-org/go-coap/v2/dtls" 12 | "github.com/matrix-org/go-coap/v2/examples/dtls/pki" 13 | ) 14 | 15 | func main() { 16 | config, err := createClientConfig(context.Background()) 17 | if err != nil { 18 | log.Fatalln(err) 19 | return 20 | } 21 | co, err := dtls.Dial("localhost:5688", config) 22 | if err != nil { 23 | log.Fatalf("Error dialing: %v", err) 24 | } 25 | path := "/a" 26 | if len(os.Args) > 1 { 27 | path = os.Args[1] 28 | } 29 | 30 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 31 | defer cancel() 32 | resp, err := co.Get(ctx, path) 33 | if err != nil { 34 | log.Fatalf("Error sending request: %v", err) 35 | } 36 | log.Printf("Response payload: %+v", resp) 37 | } 38 | 39 | func createClientConfig(ctx context.Context) (*piondtls.Config, error) { 40 | // root cert 41 | ca, rootBytes, _, caPriv, err := pki.GenerateCA() 42 | if err != nil { 43 | return nil, err 44 | } 45 | // client cert 46 | certBytes, keyBytes, err := pki.GenerateCertificate(ca, caPriv, "client@test.com") 47 | if err != nil { 48 | return nil, err 49 | } 50 | certificate, err := pki.LoadKeyAndCertificate(keyBytes, certBytes) 51 | if err != nil { 52 | return nil, err 53 | } 54 | // cert pool 55 | certPool, err := pki.LoadCertPool(rootBytes) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | return &piondtls.Config{ 61 | Certificates: []tls.Certificate{*certificate}, 62 | ExtendedMasterSecret: piondtls.RequireExtendedMasterSecret, 63 | RootCAs: certPool, 64 | InsecureSkipVerify: true, 65 | }, nil 66 | } 67 | -------------------------------------------------------------------------------- /message/noresponse/noresponse.go: -------------------------------------------------------------------------------- 1 | package noresponse 2 | 3 | import ( 4 | "github.com/matrix-org/go-coap/v2/message/codes" 5 | ) 6 | 7 | var ( 8 | resp2XXCodes = []codes.Code{codes.Created, codes.Deleted, codes.Valid, codes.Changed, codes.Content} 9 | resp4XXCodes = []codes.Code{codes.BadRequest, codes.Unauthorized, codes.BadOption, codes.Forbidden, codes.NotFound, codes.MethodNotAllowed, codes.NotAcceptable, codes.PreconditionFailed, codes.RequestEntityTooLarge, codes.UnsupportedMediaType} 10 | resp5XXCodes = []codes.Code{codes.InternalServerError, codes.NotImplemented, codes.BadGateway, codes.ServiceUnavailable, codes.GatewayTimeout, codes.ProxyingNotSupported} 11 | noResponseValueMap = map[uint32][]codes.Code{ 12 | 2: resp2XXCodes, 13 | 8: resp4XXCodes, 14 | 16: resp5XXCodes, 15 | } 16 | ) 17 | 18 | func isSet(n uint32, pos uint32) bool { 19 | val := n & (1 << pos) 20 | return (val > 0) 21 | } 22 | 23 | func decodeNoResponseOption(v uint32) []codes.Code { 24 | var codes []codes.Code 25 | if v == 0 { 26 | // No suppresed code 27 | return codes 28 | } 29 | 30 | var i uint32 31 | // Max bit value:4; ref:table_2_rfc7967 32 | for i = 0; i <= 4; i++ { 33 | if isSet(v, i) { 34 | index := uint32(1 << i) 35 | codes = append(codes, noResponseValueMap[index]...) 36 | } 37 | } 38 | return codes 39 | } 40 | 41 | // IsNoResponseCode validates response code againts NoResponse option from request. 42 | // https://www.rfc-editor.org/rfc/rfc7967.txt 43 | func IsNoResponseCode(code codes.Code, noRespValue uint32) error { 44 | suppressedCodes := decodeNoResponseOption(noRespValue) 45 | 46 | for _, suppressedCode := range suppressedCodes { 47 | if suppressedCode == code { 48 | return ErrMessageNotInterested 49 | } 50 | } 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /message/codes/code_string.go: -------------------------------------------------------------------------------- 1 | package codes 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | var codeToString = map[Code]string{ 9 | Empty: "Empty", 10 | GET: "GET", 11 | POST: "POST", 12 | PUT: "PUT", 13 | DELETE: "DELETE", 14 | Created: "Created", 15 | Deleted: "Deleted", 16 | Valid: "Valid", 17 | Changed: "Changed", 18 | Content: "Content", 19 | BadRequest: "BadRequest", 20 | Unauthorized: "Unauthorized", 21 | BadOption: "BadOption", 22 | Forbidden: "Forbidden", 23 | NotFound: "NotFound", 24 | MethodNotAllowed: "MethodNotAllowed", 25 | NotAcceptable: "NotAcceptable", 26 | PreconditionFailed: "PreconditionFailed", 27 | RequestEntityTooLarge: "RequestEntityTooLarge", 28 | UnsupportedMediaType: "UnsupportedMediaType", 29 | InternalServerError: "InternalServerError", 30 | NotImplemented: "NotImplemented", 31 | BadGateway: "BadGateway", 32 | ServiceUnavailable: "ServiceUnavailable", 33 | GatewayTimeout: "GatewayTimeout", 34 | ProxyingNotSupported: "ProxyingNotSupported", 35 | CSM: "Capabilities and Settings Messages", 36 | Ping: "Ping", 37 | Pong: "Pong", 38 | Release: "Release", 39 | Abort: "Abort", 40 | } 41 | 42 | func (c Code) String() string { 43 | val, ok := codeToString[c] 44 | if ok { 45 | return val 46 | } 47 | return "Code(" + strconv.FormatInt(int64(c), 10) + ")" 48 | } 49 | 50 | func ToCode(v string) (Code, error) { 51 | for key, val := range codeToString { 52 | if v == val { 53 | return key, nil 54 | } 55 | } 56 | return 0, fmt.Errorf("not found") 57 | } 58 | -------------------------------------------------------------------------------- /udp/example_test.go: -------------------------------------------------------------------------------- 1 | package udp_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "sync" 9 | "time" 10 | 11 | "github.com/matrix-org/go-coap/v2/net" 12 | "github.com/matrix-org/go-coap/v2/udp" 13 | "github.com/matrix-org/go-coap/v2/udp/client" 14 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 15 | ) 16 | 17 | func ExampleGet() { 18 | conn, err := udp.Dial("pluggedin.cloud:5683") 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | defer conn.Close() 23 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 24 | defer cancel() 25 | res, err := conn.Get(ctx, "/oic/res") 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | data, err := ioutil.ReadAll(res.Body()) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | fmt.Printf("%v", data) 34 | } 35 | 36 | func ExampleServe() { 37 | l, err := net.NewListenUDP("udp", "0.0.0.0:5683") 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | defer l.Close() 42 | s := udp.NewServer() 43 | defer s.Stop() 44 | log.Fatal(s.Serve(l)) 45 | } 46 | 47 | func ExampleDiscovery() { 48 | l, err := net.NewListenUDP("udp", "") 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | defer l.Close() 53 | var wg sync.WaitGroup 54 | defer wg.Wait() 55 | 56 | s := udp.NewServer() 57 | defer s.Stop() 58 | wg.Add(1) 59 | go func() { 60 | defer wg.Done() 61 | err := s.Serve(l) 62 | if err != nil { 63 | log.Println(err) 64 | } 65 | }() 66 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 67 | defer cancel() 68 | err = s.Discover(ctx, "224.0.1.187:5683", "/oic/res", func(cc *client.ClientConn, res *pool.Message) { 69 | data, err := ioutil.ReadAll(res.Body()) 70 | if err != nil { 71 | log.Fatal(err) 72 | } 73 | fmt.Printf("%v", data) 74 | }) 75 | if err != nil { 76 | log.Fatal(err) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /net/conn_test.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestConn_WriteWithContext(t *testing.T) { 13 | ctxCanceled, ctxCancel := context.WithCancel(context.Background()) 14 | ctxCancel() 15 | helloWorld := make([]byte, 1024*1024*256) 16 | 17 | type args struct { 18 | ctx context.Context 19 | data []byte 20 | } 21 | tests := []struct { 22 | name string 23 | args args 24 | wantErr bool 25 | }{ 26 | { 27 | name: "valid", 28 | args: args{ 29 | ctx: context.Background(), 30 | data: helloWorld, 31 | }, 32 | }, 33 | { 34 | name: "cancelled", 35 | args: args{ 36 | ctx: ctxCanceled, 37 | data: helloWorld, 38 | }, 39 | wantErr: true, 40 | }, 41 | } 42 | 43 | listener, err := NewTCPListener("tcp", "127.0.0.1:", WithHeartBeat(time.Millisecond*100)) 44 | assert.NoError(t, err) 45 | defer listener.Close() 46 | ctx, cancel := context.WithCancel(context.Background()) 47 | defer cancel() 48 | 49 | go func() { 50 | for { 51 | conn, err := listener.AcceptWithContext(ctx) 52 | if err != nil { 53 | return 54 | } 55 | c := NewConn(conn, WithHeartBeat(time.Millisecond*10)) 56 | b := make([]byte, len(helloWorld)) 57 | _ = c.ReadFullWithContext(ctx, b) 58 | c.Close() 59 | } 60 | }() 61 | 62 | for _, tt := range tests { 63 | t.Run(tt.name, func(t *testing.T) { 64 | tcpConn, err := net.Dial("tcp", listener.Addr().String()) 65 | assert.NoError(t, err) 66 | c := NewConn(tcpConn, WithHeartBeat(time.Millisecond*100)) 67 | defer c.Close() 68 | 69 | c.LocalAddr() 70 | c.RemoteAddr() 71 | 72 | err = c.WriteWithContext(tt.args.ctx, tt.args.data) 73 | if tt.wantErr { 74 | assert.Error(t, err) 75 | } else { 76 | assert.NoError(t, err) 77 | } 78 | }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /examples/simple/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | 7 | coap "github.com/matrix-org/go-coap/v2" 8 | "github.com/matrix-org/go-coap/v2/message" 9 | "github.com/matrix-org/go-coap/v2/message/codes" 10 | "github.com/matrix-org/go-coap/v2/mux" 11 | ) 12 | 13 | func loggingMiddleware(next mux.Handler) mux.Handler { 14 | return mux.HandlerFunc(func(w mux.ResponseWriter, r *mux.Message) { 15 | log.Printf("ClientAddress %v, %v\n", w.Client().RemoteAddr(), r.String()) 16 | next.ServeCOAP(w, r) 17 | }) 18 | } 19 | 20 | func handleA(w mux.ResponseWriter, r *mux.Message) { 21 | err := w.SetResponse(codes.Content, message.TextPlain, bytes.NewReader([]byte("hello world"))) 22 | if err != nil { 23 | log.Printf("cannot set response: %v", err) 24 | } 25 | } 26 | 27 | func handleB(w mux.ResponseWriter, r *mux.Message) { 28 | customResp := message.Message{ 29 | Code: codes.Content, 30 | Token: r.Token, 31 | Context: r.Context, 32 | Options: make(message.Options, 0, 16), 33 | Body: bytes.NewReader([]byte("B hello world")), 34 | } 35 | optsBuf := make([]byte, 32) 36 | opts, used, err := customResp.Options.SetContentFormat(optsBuf, message.TextPlain) 37 | if err == message.ErrTooSmall { 38 | optsBuf = append(optsBuf, make([]byte, used)...) 39 | opts, used, err = customResp.Options.SetContentFormat(optsBuf, message.TextPlain) 40 | } 41 | if err != nil { 42 | log.Printf("cannot set options to response: %v", err) 43 | return 44 | } 45 | optsBuf = optsBuf[:used] 46 | customResp.Options = opts 47 | 48 | err = w.Client().WriteMessage(&customResp) 49 | if err != nil { 50 | log.Printf("cannot set response: %v", err) 51 | } 52 | } 53 | 54 | func main() { 55 | r := mux.NewRouter() 56 | r.Use(loggingMiddleware) 57 | r.Handle("/a", mux.HandlerFunc(handleA)) 58 | r.Handle("/b", mux.HandlerFunc(handleB)) 59 | 60 | log.Fatal(coap.ListenAndServe("udp", ":5688", r)) 61 | } 62 | -------------------------------------------------------------------------------- /udp/client/responsewriter.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/matrix-org/go-coap/v2/message" 7 | "github.com/matrix-org/go-coap/v2/message/codes" 8 | "github.com/matrix-org/go-coap/v2/message/noresponse" 9 | udpMessage "github.com/matrix-org/go-coap/v2/udp/message" 10 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 11 | ) 12 | 13 | // A ResponseWriter interface is used by an COAP handler to construct an COAP response. 14 | type ResponseWriter struct { 15 | noResponseValue *uint32 16 | response *pool.Message 17 | cc *ClientConn 18 | } 19 | 20 | func NewResponseWriter(response *pool.Message, cc *ClientConn, requestOptions message.Options) *ResponseWriter { 21 | var noResponseValue *uint32 22 | v, err := requestOptions.GetUint32(message.NoResponse) 23 | if err == nil { 24 | noResponseValue = &v 25 | } 26 | 27 | return &ResponseWriter{ 28 | response: response, 29 | cc: cc, 30 | noResponseValue: noResponseValue, 31 | } 32 | } 33 | 34 | func (r *ResponseWriter) SetResponse(code codes.Code, contentFormat message.MediaType, d io.ReadSeeker, opts ...message.Option) error { 35 | if r.noResponseValue != nil { 36 | err := noresponse.IsNoResponseCode(code, *r.noResponseValue) 37 | if err != nil { 38 | return err 39 | } 40 | } 41 | 42 | r.response.SetCode(code) 43 | r.response.ResetOptionsTo(opts) 44 | if d != nil { 45 | r.response.SetContentFormat(contentFormat) 46 | r.response.SetBody(d) 47 | if !r.response.HasOption(message.ETag) { 48 | etag, err := message.GetETag(d) 49 | if err != nil { 50 | return err 51 | } 52 | r.response.SetOptionBytes(message.ETag, etag) 53 | } 54 | } 55 | return nil 56 | } 57 | 58 | func (r *ResponseWriter) ClientConn() *ClientConn { 59 | return r.cc 60 | } 61 | 62 | func (r *ResponseWriter) SendReset() { 63 | r.response.Reset() 64 | r.response.SetCode(codes.Empty) 65 | r.response.SetType(udpMessage.Reset) 66 | } 67 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | // Package coap provides a CoAP client and server. 2 | package coap 3 | 4 | import ( 5 | "crypto/tls" 6 | "fmt" 7 | 8 | piondtls "github.com/pion/dtls/v2" 9 | "github.com/matrix-org/go-coap/v2/dtls" 10 | "github.com/matrix-org/go-coap/v2/mux" 11 | "github.com/matrix-org/go-coap/v2/net" 12 | "github.com/matrix-org/go-coap/v2/tcp" 13 | "github.com/matrix-org/go-coap/v2/udp" 14 | ) 15 | 16 | // ListenAndServe Starts a server on address and network specified Invoke handler 17 | // for incoming queries. 18 | func ListenAndServe(network string, addr string, handler mux.Handler) error { 19 | switch network { 20 | case "udp", "udp4", "udp6", "": 21 | l, err := net.NewListenUDP(network, addr) 22 | if err != nil { 23 | return err 24 | } 25 | defer l.Close() 26 | s := udp.NewServer(udp.WithMux(handler)) 27 | return s.Serve(l) 28 | case "tcp", "tcp4", "tcp6": 29 | l, err := net.NewTCPListener(network, addr) 30 | if err != nil { 31 | return err 32 | } 33 | defer l.Close() 34 | s := tcp.NewServer(tcp.WithMux(handler)) 35 | return s.Serve(l) 36 | default: 37 | return fmt.Errorf("invalid network (%v)", network) 38 | } 39 | } 40 | 41 | // ListenAndServeTCPTLS Starts a server on address and network over TLS specified Invoke handler 42 | // for incoming queries. 43 | func ListenAndServeTCPTLS(network, addr string, config *tls.Config, handler mux.Handler) error { 44 | l, err := net.NewTLSListener(network, addr, config) 45 | if err != nil { 46 | return err 47 | } 48 | defer l.Close() 49 | s := tcp.NewServer(tcp.WithMux(handler)) 50 | return s.Serve(l) 51 | } 52 | 53 | // ListenAndServeDTLS Starts a server on address and network over DTLS specified Invoke handler 54 | // for incoming queries. 55 | func ListenAndServeDTLS(network string, addr string, config *piondtls.Config, handler mux.Handler) error { 56 | l, err := net.NewDTLSListener(network, addr, config) 57 | if err != nil { 58 | return err 59 | } 60 | defer l.Close() 61 | s := dtls.NewServer(dtls.WithMux(handler)) 62 | return s.Serve(l) 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the master branch 5 | on: 6 | push: 7 | branches: [ master ] 8 | 9 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 10 | jobs: 11 | test: 12 | # The type of runner that the job will run on 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, windows-latest, macOS-latest] 17 | 18 | # Steps represent a sequence of tasks that will be executed as part of the job 19 | steps: 20 | - name: Set up Go 1.15 21 | uses: actions/setup-go@v2 22 | with: 23 | go-version: ^1.15 24 | 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - name: Checkout 27 | uses: actions/checkout@v2 28 | 29 | # Runs a single command using the runners shell 30 | - name: Run a build 31 | run: go build ./... 32 | 33 | # Runs a single command using the runners shell 34 | - name: Run a test 35 | run: go test -p 1 -race ./... -covermode=atomic -coverprofile=./coverage.txt 36 | 37 | # This workflow contains a single job called "build" 38 | ci: 39 | # The type of runner that the job will run on 40 | runs-on: ubuntu-20.04 41 | 42 | # Steps represent a sequence of tasks that will be executed as part of the job 43 | steps: 44 | - name: Set up Go 1.15 45 | uses: actions/setup-go@v2 46 | with: 47 | go-version: ^1.15 48 | 49 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 50 | - name: Checkout 51 | uses: actions/checkout@v2 52 | 53 | # Runs a single command using the runners shell 54 | - name: Run a test 55 | run: go test -race ./... -covermode=atomic -coverprofile=./coverage.txt 56 | 57 | - name: Publish the coverage 58 | run: bash <(curl -s https://codecov.io/bash) 59 | -------------------------------------------------------------------------------- /udp/client/mutexmap_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "math/rand" 5 | "strconv" 6 | "strings" 7 | "sync" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestMutexMap(t *testing.T) { 13 | 14 | r := rand.New(rand.NewSource(42)) 15 | 16 | m := NewMutexMap() 17 | _ = m 18 | 19 | keyCount := 20 20 | iCount := 10000 21 | out := make(chan string, iCount*2) 22 | 23 | // run a bunch of concurrent requests for various keys, 24 | // the idea is to have a lot of lock contention 25 | var wg sync.WaitGroup 26 | wg.Add(iCount) 27 | for i := 0; i < iCount; i++ { 28 | go func(rn int) { 29 | defer wg.Done() 30 | key := strconv.Itoa(rn) 31 | 32 | // you can prove the test works by commenting the locking out and seeing it fail 33 | l := m.Lock(key) 34 | defer l.Unlock() 35 | 36 | out <- key + " A" 37 | time.Sleep(time.Microsecond) // make 'em wait a mo' 38 | out <- key + " B" 39 | }(r.Intn(keyCount)) 40 | } 41 | wg.Wait() 42 | close(out) 43 | 44 | // verify the map is empty now 45 | if l := len(m.ma); l != 0 { 46 | t.Errorf("unexpected map length at test end: %v", l) 47 | } 48 | 49 | // confirm that the output always produced the correct sequence 50 | outLists := make([][]string, keyCount) 51 | for s := range out { 52 | sParts := strings.Fields(s) 53 | kn, err := strconv.Atoi(sParts[0]) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | outLists[kn] = append(outLists[kn], sParts[1]) 58 | } 59 | for kn := 0; kn < keyCount; kn++ { 60 | l := outLists[kn] // list of output for this particular key 61 | for i := 0; i < len(l); i += 2 { 62 | if l[i] != "A" || l[i+1] != "B" { 63 | t.Errorf("For key=%v and i=%v got unexpected values %v and %v", kn, i, l[i], l[i+1]) 64 | break 65 | } 66 | } 67 | } 68 | if t.Failed() { 69 | t.Logf("Failed, outLists: %#v", outLists) 70 | } 71 | 72 | } 73 | 74 | func BenchmarkM(b *testing.B) { 75 | m := NewMutexMap() 76 | b.ResetTimer() 77 | for i := 0; i < b.N; i++ { 78 | // run uncontended lock/unlock - should be quite fast 79 | m.Lock(i).Unlock() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /udp/client/mutexmap.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // MutexMap wraps a map of mutexes. Each key locks separately. 9 | type MutexMap struct { 10 | ml sync.Mutex // lock for entry map 11 | ma map[interface{}]*mutexMapEntry // entry map 12 | } 13 | 14 | type mutexMapEntry struct { 15 | m *MutexMap // point back to MutexMap, so we can synchronize removing this mutexMapEntry when cnt==0 16 | el sync.Mutex // entry-specific lock 17 | cnt int // reference count 18 | key interface{} // key in ma 19 | } 20 | 21 | // Unlocker provides an Unlock method to release the lock. 22 | type Unlocker interface { 23 | Unlock() 24 | } 25 | 26 | // NewMutexMap returns an initalized MutexMap. 27 | func NewMutexMap() *MutexMap { 28 | return &MutexMap{ma: make(map[interface{}]*mutexMapEntry)} 29 | } 30 | 31 | // Lock acquires a lock corresponding to this key. 32 | // This method will never return nil and Unlock() must be called 33 | // to release the lock when done. 34 | func (m *MutexMap) Lock(key interface{}) Unlocker { 35 | 36 | // read or create entry for this key atomically 37 | m.ml.Lock() 38 | e, ok := m.ma[key] 39 | if !ok { 40 | e = &mutexMapEntry{m: m, key: key} 41 | m.ma[key] = e 42 | } 43 | e.cnt++ // ref count 44 | m.ml.Unlock() 45 | 46 | // acquire lock, will block here until e.cnt==1 47 | e.el.Lock() 48 | 49 | return e 50 | } 51 | 52 | // Unlock releases the lock for this entry. 53 | func (entry *mutexMapEntry) Unlock() { 54 | 55 | m := entry.m 56 | 57 | // decrement and if needed remove entry atomically 58 | m.ml.Lock() 59 | e, ok := m.ma[entry.key] 60 | if !ok { // entry must exist 61 | m.ml.Unlock() 62 | panic(fmt.Errorf("unlock requested for key=%v but no entry found", entry.key)) 63 | } 64 | e.cnt-- // ref count 65 | if e.cnt < 1 { // if it hits zero then we own it and remove from map 66 | delete(m.ma, entry.key) 67 | } 68 | m.ml.Unlock() 69 | 70 | // now that map stuff is handled, we unlock and let 71 | // anything else waiting on this key through 72 | e.el.Unlock() 73 | 74 | } 75 | -------------------------------------------------------------------------------- /examples/dtls/psk/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | 8 | piondtls "github.com/pion/dtls/v2" 9 | coap "github.com/matrix-org/go-coap/v2" 10 | "github.com/matrix-org/go-coap/v2/message" 11 | "github.com/matrix-org/go-coap/v2/message/codes" 12 | "github.com/matrix-org/go-coap/v2/mux" 13 | ) 14 | 15 | func handleA(w mux.ResponseWriter, r *mux.Message) { 16 | log.Printf("got message in handleA: %+v from %v\n", r, w.Client().RemoteAddr()) 17 | err := w.SetResponse(codes.GET, message.TextPlain, bytes.NewReader([]byte("A hello world"))) 18 | if err != nil { 19 | log.Printf("cannot set response: %v", err) 20 | } 21 | } 22 | 23 | func handleB(w mux.ResponseWriter, r *mux.Message) { 24 | log.Printf("got message in handleB: %+v from %v\n", r, w.Client().RemoteAddr()) 25 | customResp := message.Message{ 26 | Code: codes.Content, 27 | Token: r.Token, 28 | Context: r.Context, 29 | Options: make(message.Options, 0, 16), 30 | Body: bytes.NewReader([]byte("B hello world")), 31 | } 32 | optsBuf := make([]byte, 32) 33 | opts, used, err := customResp.Options.SetContentFormat(optsBuf, message.TextPlain) 34 | if err == message.ErrTooSmall { 35 | optsBuf = append(optsBuf, make([]byte, used)...) 36 | opts, used, err = customResp.Options.SetContentFormat(optsBuf, message.TextPlain) 37 | } 38 | if err != nil { 39 | log.Printf("cannot set options to response: %v", err) 40 | return 41 | } 42 | optsBuf = optsBuf[:used] 43 | customResp.Options = opts 44 | 45 | err = w.Client().WriteMessage(&customResp) 46 | if err != nil { 47 | log.Printf("cannot set response: %v", err) 48 | } 49 | } 50 | 51 | func main() { 52 | m := mux.NewRouter() 53 | m.Handle("/a", mux.HandlerFunc(handleA)) 54 | m.Handle("/b", mux.HandlerFunc(handleB)) 55 | 56 | log.Fatal(coap.ListenAndServeDTLS("udp", ":5688", &piondtls.Config{ 57 | PSK: func(hint []byte) ([]byte, error) { 58 | fmt.Printf("Client's hint: %s \n", hint) 59 | return []byte{0xAB, 0xC1, 0x23}, nil 60 | }, 61 | PSKIdentityHint: []byte("Pion DTLS Client"), 62 | CipherSuites: []piondtls.CipherSuiteID{piondtls.TLS_PSK_WITH_AES_128_CCM_8}, 63 | }, m)) 64 | } 65 | -------------------------------------------------------------------------------- /examples/observe/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | coap "github.com/matrix-org/go-coap/v2" 10 | "github.com/matrix-org/go-coap/v2/message" 11 | "github.com/matrix-org/go-coap/v2/message/codes" 12 | "github.com/matrix-org/go-coap/v2/mux" 13 | ) 14 | 15 | func getPath(opts message.Options) string { 16 | path, err := opts.Path() 17 | if err != nil { 18 | log.Printf("cannot get path: %v", err) 19 | return "" 20 | } 21 | return path 22 | } 23 | 24 | func sendResponse(cc mux.Client, token []byte, subded time.Time, obs int64) error { 25 | m := message.Message{ 26 | Code: codes.Content, 27 | Token: token, 28 | Context: cc.Context(), 29 | Body: bytes.NewReader([]byte(fmt.Sprintf("Been running for %v", time.Since(subded)))), 30 | } 31 | var opts message.Options 32 | var buf []byte 33 | opts, n, err := opts.SetContentFormat(buf, message.TextPlain) 34 | if err == message.ErrTooSmall { 35 | buf = append(buf, make([]byte, n)...) 36 | opts, n, err = opts.SetContentFormat(buf, message.TextPlain) 37 | } 38 | if err != nil { 39 | return fmt.Errorf("cannot set content format to response: %w", err) 40 | } 41 | if obs >= 0 { 42 | opts, n, err = opts.SetObserve(buf, uint32(obs)) 43 | if err == message.ErrTooSmall { 44 | buf = append(buf, make([]byte, n)...) 45 | opts, n, err = opts.SetObserve(buf, uint32(obs)) 46 | } 47 | if err != nil { 48 | return fmt.Errorf("cannot set options to response: %w", err) 49 | } 50 | } 51 | m.Options = opts 52 | return cc.WriteMessage(&m) 53 | } 54 | 55 | func periodicTransmitter(cc mux.Client, token []byte) { 56 | subded := time.Now() 57 | obs := int64(2) 58 | for { 59 | err := sendResponse(cc, token, subded, obs) 60 | if err != nil { 61 | log.Printf("Error on transmitter, stopping: %v", err) 62 | return 63 | } 64 | time.Sleep(time.Second) 65 | } 66 | } 67 | 68 | func main() { 69 | log.Fatal(coap.ListenAndServe("udp", ":5688", 70 | mux.HandlerFunc(func(w mux.ResponseWriter, r *mux.Message) { 71 | log.Printf("Got message path=%v: %+v from %v", getPath(r.Options), r, w.Client().RemoteAddr()) 72 | obs, err := r.Options.Observe() 73 | switch { 74 | case r.Code == codes.GET && err == nil && obs == 0: 75 | go periodicTransmitter(w.Client(), r.Token) 76 | case r.Code == codes.GET: 77 | subded := time.Now() 78 | err := sendResponse(w.Client(), r.Token, subded, -1) 79 | if err != nil { 80 | log.Printf("Error on transmitter: %v", err) 81 | } 82 | } 83 | }))) 84 | } 85 | -------------------------------------------------------------------------------- /udp/discover.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | 8 | "github.com/matrix-org/go-coap/v2/udp/client" 9 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 10 | ) 11 | 12 | var defaultMulticastOptions = multicastOptions{ 13 | hopLimit: 2, 14 | } 15 | 16 | type multicastOptions struct { 17 | hopLimit int 18 | } 19 | 20 | // A MulticastOption sets options such as hop limit, etc. 21 | type MulticastOption interface { 22 | apply(*multicastOptions) 23 | } 24 | 25 | // Discover sends GET to multicast address and wait for responses until context timeouts or server shutdown. 26 | func (s *Server) Discover(ctx context.Context, multicastAddr, path string, receiverFunc func(cc *client.ClientConn, resp *pool.Message), opts ...MulticastOption) error { 27 | req, err := client.NewGetRequest(ctx, path) 28 | if err != nil { 29 | return fmt.Errorf("cannot create discover request: %w", err) 30 | } 31 | req.SetMessageID(s.getMID()) 32 | defer pool.ReleaseMessage(req) 33 | return s.DiscoveryRequest(req, multicastAddr, receiverFunc, opts...) 34 | } 35 | 36 | // DiscoveryRequest sends request to multicast addressand wait for responses until request timeouts or server shutdown. 37 | func (s *Server) DiscoveryRequest(req *pool.Message, multicastAddr string, receiverFunc func(cc *client.ClientConn, resp *pool.Message), opts ...MulticastOption) error { 38 | token := req.Token() 39 | if len(token) == 0 { 40 | return fmt.Errorf("invalid token") 41 | } 42 | cfg := defaultMulticastOptions 43 | for _, o := range opts { 44 | o.apply(&cfg) 45 | } 46 | c := s.conn() 47 | if c == nil { 48 | return fmt.Errorf("server doesn't serve connection") 49 | } 50 | addr, err := net.ResolveUDPAddr(c.Network(), multicastAddr) 51 | if err != nil { 52 | return fmt.Errorf("cannot resolve address: %w", err) 53 | } 54 | if !addr.IP.IsMulticast() { 55 | return fmt.Errorf("invalid multicast address") 56 | } 57 | data, err := req.Marshal() 58 | if err != nil { 59 | return fmt.Errorf("cannot marshal req: %w", err) 60 | } 61 | s.multicastRequests.Store(token.String(), req) 62 | defer s.multicastRequests.Delete(token.String()) 63 | err = s.multicastHandler.Insert(token, func(w *client.ResponseWriter, r *pool.Message) { 64 | receiverFunc(w.ClientConn(), r) 65 | }) 66 | if err != nil { 67 | return err 68 | } 69 | defer s.multicastHandler.Pop(token) 70 | 71 | err = c.WriteMulticast(req.Context(), addr, cfg.hopLimit, data) 72 | if err != nil { 73 | return err 74 | } 75 | select { 76 | case <-req.Context().Done(): 77 | return nil 78 | case <-s.ctx.Done(): 79 | return fmt.Errorf("server was closed: %w", s.ctx.Err()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /examples/dtls/pki/cert_util.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "crypto" 5 | "crypto/ecdsa" 6 | "crypto/rsa" 7 | "crypto/tls" 8 | "crypto/x509" 9 | "encoding/pem" 10 | "errors" 11 | "strings" 12 | ) 13 | 14 | // LoadCertificate loads cert from bytes 15 | func LoadCertificate(certBytes []byte) (*tls.Certificate, error) { 16 | var certificate tls.Certificate 17 | 18 | for { 19 | block, rest := pem.Decode(certBytes) 20 | if block == nil { 21 | break 22 | } 23 | 24 | if block.Type != "CERTIFICATE" { 25 | return nil, errors.New("block is not a certificate, unable to load certificates") 26 | } 27 | 28 | certificate.Certificate = append(certificate.Certificate, block.Bytes) 29 | certBytes = rest 30 | } 31 | 32 | if len(certificate.Certificate) == 0 { 33 | return nil, errors.New("no certificate found, unable to load certificates") 34 | } 35 | 36 | return &certificate, nil 37 | } 38 | 39 | // LoadKey loads key from bytes 40 | func LoadKey(keyBytes []byte) (crypto.PrivateKey, error) { 41 | block, _ := pem.Decode(keyBytes) 42 | if block == nil || !strings.HasSuffix(block.Type, "PRIVATE KEY") { 43 | return nil, errors.New("block is not a private key, unable to load key") 44 | } 45 | 46 | if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { 47 | return key, nil 48 | } 49 | 50 | if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil { 51 | switch key := key.(type) { 52 | case *rsa.PrivateKey, *ecdsa.PrivateKey: 53 | return key, nil 54 | default: 55 | return nil, errors.New("unknown key time in PKCS#8 wrapping, unable to load key") 56 | } 57 | } 58 | 59 | if key, err := x509.ParseECPrivateKey(block.Bytes); err == nil { 60 | return key, nil 61 | } 62 | 63 | return nil, errors.New("no private key found, unable to load key") 64 | } 65 | 66 | // LoadKeyAndCertificate loads client certificate 67 | func LoadKeyAndCertificate(keyBytes []byte, certBytes []byte) (*tls.Certificate, error) { 68 | certificate, err := LoadCertificate(certBytes) 69 | if err != nil { 70 | return nil, err 71 | } 72 | key, err := LoadKey(keyBytes) 73 | if err != nil { 74 | return nil, err 75 | } 76 | certificate.PrivateKey = key 77 | return certificate, nil 78 | } 79 | 80 | // LoadCertPool loads cert pool from ca certificate 81 | func LoadCertPool(caBytes []byte) (*x509.CertPool, error) { 82 | rootCertificate, err := LoadCertificate(caBytes) 83 | if err != nil { 84 | return nil, err 85 | } 86 | certPool := x509.NewCertPool() 87 | for _, certBytes := range rootCertificate.Certificate { 88 | cert, err := x509.ParseCertificate(certBytes) 89 | if err != nil { 90 | certPool = nil 91 | return nil, err 92 | } 93 | certPool.AddCert(cert) 94 | } 95 | 96 | return certPool, nil 97 | } 98 | -------------------------------------------------------------------------------- /net/observation/observation_test.go: -------------------------------------------------------------------------------- 1 | package observation 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestValidSequenceNumber(t *testing.T) { 11 | type args struct { 12 | old uint32 13 | new uint32 14 | lastEventOccurs time.Time 15 | now time.Time 16 | } 17 | tests := []struct { 18 | name string 19 | args args 20 | want bool 21 | }{ 22 | { 23 | name: "(1 << 25)-1, 0, now-1s, now", 24 | args: args{ 25 | old: (1 << 25)-1, 26 | new: 0, 27 | lastEventOccurs: time.Now().Add(-time.Second), 28 | now: time.Now(), 29 | }, 30 | want: true, 31 | }, 32 | { 33 | name: "0, 1, 0, now", 34 | args: args{ 35 | new: 1, 36 | now: time.Now(), 37 | }, 38 | want: true, 39 | }, 40 | { 41 | name: "1582, 1583, now-1s, now", 42 | args: args{ 43 | old: 1582, 44 | new: 1583, 45 | lastEventOccurs: time.Now().Add(-time.Second), 46 | now: time.Now(), 47 | }, 48 | want: true, 49 | }, 50 | { 51 | name: "1582, 1, now-129s, now", 52 | args: args{ 53 | old: 1582, 54 | new: 1, 55 | lastEventOccurs: time.Now().Add(-time.Second * 129), 56 | now: time.Now(), 57 | }, 58 | want: true, 59 | }, 60 | { 61 | name: "1582, 1, now-125s, now", 62 | args: args{ 63 | old: 1582, 64 | new: 1, 65 | lastEventOccurs: time.Now().Add(-time.Second * 125), 66 | now: time.Now(), 67 | }, 68 | want: false, 69 | }, 70 | { 71 | name: "1 << 23, 0, now-1s, now", 72 | args: args{ 73 | old: 1 << 23, 74 | new: 0, 75 | lastEventOccurs: time.Now().Add(-time.Second), 76 | now: time.Now(), 77 | }, 78 | want: false, 79 | }, 80 | { 81 | name: "0, 1 << 23+1, now-1s, now", 82 | args: args{ 83 | old: 0, 84 | new: 1 << 23+1, 85 | lastEventOccurs: time.Now().Add(-time.Second), 86 | now: time.Now(), 87 | }, 88 | want: false, 89 | }, 90 | { 91 | name: "1582, 1582, now-1s, now", 92 | args: args{ 93 | old: 1582, 94 | new: 1582, 95 | lastEventOccurs: time.Now().Add(-time.Second), 96 | now: time.Now(), 97 | }, 98 | want: false, 99 | }, 100 | } 101 | for _, tt := range tests { 102 | t.Run(tt.name, func(t *testing.T) { 103 | got := ValidSequenceNumber(tt.args.old, tt.args.new, tt.args.lastEventOccurs, tt.args.now) 104 | assert.Equal(t, tt.want, got) 105 | }) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /net/options.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import "time" 4 | 5 | // A UDPOption sets options such as heartBeat, errors parameters, etc. 6 | type UDPOption interface { 7 | applyUDP(*udpConnOptions) 8 | } 9 | 10 | type HeartBeatOpt struct { 11 | heartBeat time.Duration 12 | } 13 | 14 | func (h HeartBeatOpt) applyUDP(o *udpConnOptions) { 15 | o.heartBeat = h.heartBeat 16 | } 17 | 18 | func (h HeartBeatOpt) applyConn(o *connOptions) { 19 | o.heartBeat = h.heartBeat 20 | } 21 | 22 | func (h HeartBeatOpt) applyTCPListener(o *tcpListenerOptions) { 23 | o.heartBeat = h.heartBeat 24 | } 25 | 26 | func (h HeartBeatOpt) applyTLSListener(o *tlsListenerOptions) { 27 | o.heartBeat = h.heartBeat 28 | } 29 | 30 | func (h HeartBeatOpt) applyDTLSListener(o *dtlsListenerOptions) { 31 | o.heartBeat = h.heartBeat 32 | } 33 | 34 | func WithHeartBeat(v time.Duration) HeartBeatOpt { 35 | return HeartBeatOpt{ 36 | heartBeat: v, 37 | } 38 | } 39 | 40 | type ErrorsOpt struct { 41 | errors func(err error) 42 | } 43 | 44 | func (h ErrorsOpt) applyUDP(o *udpConnOptions) { 45 | o.errors = h.errors 46 | } 47 | 48 | func WithErrors(v func(err error)) ErrorsOpt { 49 | return ErrorsOpt{ 50 | errors: v, 51 | } 52 | } 53 | 54 | type OnTimeoutOpt struct { 55 | onTimeout func() error 56 | } 57 | 58 | func WithOnTimeout(onTimeout func() error) OnTimeoutOpt { 59 | return OnTimeoutOpt{ 60 | onTimeout: onTimeout, 61 | } 62 | } 63 | 64 | func (h OnTimeoutOpt) applyConn(o *connOptions) { 65 | o.onReadTimeout = h.onTimeout 66 | o.onWriteTimeout = h.onTimeout 67 | } 68 | 69 | func (h OnTimeoutOpt) applyUDP(o *udpConnOptions) { 70 | o.onReadTimeout = h.onTimeout 71 | o.onWriteTimeout = h.onTimeout 72 | } 73 | 74 | func (h OnTimeoutOpt) applyTCPListener(o *tcpListenerOptions) { 75 | o.onTimeout = h.onTimeout 76 | } 77 | 78 | func (h OnTimeoutOpt) applyTLSListener(o *tlsListenerOptions) { 79 | o.onTimeout = h.onTimeout 80 | } 81 | 82 | func (h OnTimeoutOpt) applyDTLSListener(o *dtlsListenerOptions) { 83 | o.onTimeout = h.onTimeout 84 | } 85 | 86 | type OnReadTimeoutOpt struct { 87 | onReadTimeout func() error 88 | } 89 | 90 | func WithOnReadTimeout(onReadTimeout func() error) OnReadTimeoutOpt { 91 | return OnReadTimeoutOpt{ 92 | onReadTimeout: onReadTimeout, 93 | } 94 | } 95 | 96 | func (h OnReadTimeoutOpt) applyConn(o *connOptions) { 97 | o.onReadTimeout = h.onReadTimeout 98 | } 99 | 100 | func (h OnReadTimeoutOpt) applyUDP(o *udpConnOptions) { 101 | o.onReadTimeout = h.onReadTimeout 102 | } 103 | 104 | type OnWriteTimeoutOpt struct { 105 | onWriteTimeout func() error 106 | } 107 | 108 | func WithOnWriteTimeout(onWriteTimeout func() error) OnWriteTimeoutOpt { 109 | return OnWriteTimeoutOpt{ 110 | onWriteTimeout: onWriteTimeout, 111 | } 112 | } 113 | 114 | func (h OnWriteTimeoutOpt) applyConn(o *connOptions) { 115 | o.onWriteTimeout = h.onWriteTimeout 116 | } 117 | 118 | func (h OnWriteTimeoutOpt) applyUDP(o *udpConnOptions) { 119 | o.onWriteTimeout = h.onWriteTimeout 120 | } 121 | -------------------------------------------------------------------------------- /net/tlslistener.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "fmt" 7 | "net" 8 | "sync/atomic" 9 | "time" 10 | ) 11 | 12 | // TLSListener is a TLS listener that provides accept with context. 13 | type TLSListener struct { 14 | tcp *net.TCPListener 15 | listener net.Listener 16 | heartBeat time.Duration 17 | closed uint32 18 | onTimeout func() error 19 | } 20 | 21 | var defaultTLSListenerOptions = tlsListenerOptions{ 22 | heartBeat: time.Millisecond * 200, 23 | } 24 | 25 | type tlsListenerOptions struct { 26 | heartBeat time.Duration 27 | onTimeout func() error 28 | } 29 | 30 | // A TLSListenerOption sets options such as heartBeat parameters, etc. 31 | type TLSListenerOption interface { 32 | applyTLSListener(*tlsListenerOptions) 33 | } 34 | 35 | // NewTLSListener creates tcp listener. 36 | // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only). 37 | func NewTLSListener(network string, addr string, tlsCfg *tls.Config, opts ...TLSListenerOption) (*TLSListener, error) { 38 | cfg := defaultTLSListenerOptions 39 | for _, o := range opts { 40 | o.applyTLSListener(&cfg) 41 | } 42 | tcp, err := newNetTCPListen(network, addr) 43 | if err != nil { 44 | return nil, fmt.Errorf("cannot create new tls listener: %w", err) 45 | } 46 | tls := tls.NewListener(tcp, tlsCfg) 47 | return &TLSListener{ 48 | tcp: tcp, 49 | listener: tls, 50 | heartBeat: cfg.heartBeat, 51 | }, nil 52 | } 53 | 54 | // AcceptWithContext waits with context for a generic Conn. 55 | func (l *TLSListener) AcceptWithContext(ctx context.Context) (net.Conn, error) { 56 | for { 57 | select { 58 | case <-ctx.Done(): 59 | return nil, ctx.Err() 60 | default: 61 | } 62 | if atomic.LoadUint32(&l.closed) == 1 { 63 | return nil, ErrListenerIsClosed 64 | } 65 | deadline := time.Now().Add(l.heartBeat) 66 | err := l.SetDeadline(deadline) 67 | if err != nil { 68 | return nil, fmt.Errorf("cannot set deadline to accept connection: %w", err) 69 | } 70 | rw, err := l.listener.Accept() 71 | if err != nil { 72 | if isTemporary(err, deadline) { 73 | if l.onTimeout != nil { 74 | err := l.onTimeout() 75 | if err != nil { 76 | return nil, fmt.Errorf("cannot accept connection : on timeout returns error: %w", err) 77 | } 78 | } 79 | continue 80 | } 81 | return nil, fmt.Errorf("cannot accept connection: %w", err) 82 | } 83 | return rw, nil 84 | } 85 | } 86 | 87 | // SetDeadline sets deadline for accept operation. 88 | func (l *TLSListener) SetDeadline(t time.Time) error { 89 | return l.tcp.SetDeadline(t) 90 | } 91 | 92 | // Accept waits for a generic Conn. 93 | func (l *TLSListener) Accept() (net.Conn, error) { 94 | return l.AcceptWithContext(context.Background()) 95 | } 96 | 97 | // Close closes the connection. 98 | func (l *TLSListener) Close() error { 99 | if !atomic.CompareAndSwapUint32(&l.closed, 0, 1) { 100 | return nil 101 | } 102 | return l.listener.Close() 103 | } 104 | 105 | // Addr represents a network end point address. 106 | func (l *TLSListener) Addr() net.Addr { 107 | return l.listener.Addr() 108 | } 109 | -------------------------------------------------------------------------------- /udp/session.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "sync" 8 | "sync/atomic" 9 | 10 | coapNet "github.com/matrix-org/go-coap/v2/net" 11 | "github.com/matrix-org/go-coap/v2/udp/client" 12 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 13 | ) 14 | 15 | type EventFunc = func() 16 | 17 | type Session struct { 18 | connection *coapNet.UDPConn 19 | raddr *net.UDPAddr 20 | maxMessageSize int 21 | closeSocket bool 22 | 23 | mutex sync.Mutex 24 | onClose []EventFunc 25 | 26 | cancel context.CancelFunc 27 | ctx atomic.Value 28 | } 29 | 30 | func NewSession( 31 | ctx context.Context, 32 | connection *coapNet.UDPConn, 33 | raddr *net.UDPAddr, 34 | maxMessageSize int, 35 | closeSocket bool, 36 | ) *Session { 37 | ctx, cancel := context.WithCancel(ctx) 38 | s := &Session{ 39 | cancel: cancel, 40 | connection: connection, 41 | raddr: raddr, 42 | maxMessageSize: maxMessageSize, 43 | closeSocket: closeSocket, 44 | } 45 | s.ctx.Store(&ctx) 46 | return s 47 | } 48 | 49 | // SetContextValue stores the value associated with key to context of connection. 50 | func (s *Session) SetContextValue(key interface{}, val interface{}) { 51 | s.mutex.Lock() 52 | defer s.mutex.Unlock() 53 | ctx := context.WithValue(s.Context(), key, val) 54 | s.ctx.Store(&ctx) 55 | } 56 | 57 | func (s *Session) Done() <-chan struct{} { 58 | return s.Context().Done() 59 | } 60 | 61 | func (s *Session) AddOnClose(f EventFunc) { 62 | s.mutex.Lock() 63 | defer s.mutex.Unlock() 64 | s.onClose = append(s.onClose, f) 65 | } 66 | 67 | func (s *Session) popOnClose() []EventFunc { 68 | s.mutex.Lock() 69 | defer s.mutex.Unlock() 70 | tmp := s.onClose 71 | s.onClose = nil 72 | return tmp 73 | } 74 | 75 | func (s *Session) close() error { 76 | for _, f := range s.popOnClose() { 77 | f() 78 | } 79 | if s.closeSocket { 80 | return s.connection.Close() 81 | } 82 | return nil 83 | } 84 | 85 | func (s *Session) Close() error { 86 | s.cancel() 87 | return nil 88 | } 89 | 90 | func (s *Session) Context() context.Context { 91 | return *s.ctx.Load().(*context.Context) 92 | } 93 | 94 | func (s *Session) WriteMessage(req *pool.Message) error { 95 | data, err := req.Marshal() 96 | if err != nil { 97 | return fmt.Errorf("cannot marshal: %w", err) 98 | } 99 | return s.connection.WriteWithContext(req.Context(), s.raddr, data) 100 | } 101 | 102 | func (s *Session) Run(cc *client.ClientConn) (err error) { 103 | defer func() { 104 | err1 := s.Close() 105 | if err == nil { 106 | err = err1 107 | } 108 | err1 = s.close() 109 | if err == nil { 110 | err = err1 111 | } 112 | }() 113 | m := make([]byte, s.maxMessageSize) 114 | for { 115 | buf := m 116 | n, _, err := s.connection.ReadWithContext(s.Context(), buf) 117 | if err != nil { 118 | return err 119 | } 120 | buf = buf[:n] 121 | err = cc.Process(buf) 122 | if err != nil { 123 | return err 124 | } 125 | } 126 | } 127 | 128 | func (s *Session) MaxMessageSize() int { 129 | return s.maxMessageSize 130 | } 131 | 132 | func (s *Session) RemoteAddr() net.Addr { 133 | return s.raddr 134 | } 135 | -------------------------------------------------------------------------------- /examples/dtls/pki/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "fmt" 9 | "log" 10 | "math/big" 11 | "time" 12 | 13 | piondtls "github.com/pion/dtls/v2" 14 | "github.com/matrix-org/go-coap/v2/dtls" 15 | "github.com/matrix-org/go-coap/v2/examples/dtls/pki" 16 | "github.com/matrix-org/go-coap/v2/message" 17 | "github.com/matrix-org/go-coap/v2/message/codes" 18 | "github.com/matrix-org/go-coap/v2/mux" 19 | "github.com/matrix-org/go-coap/v2/net" 20 | "github.com/matrix-org/go-coap/v2/udp/client" 21 | ) 22 | 23 | func onNewClientConn(cc *client.ClientConn, dtlsConn *piondtls.Conn) { 24 | clientCert, err := x509.ParseCertificate(dtlsConn.ConnectionState().PeerCertificates[0]) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | cc.SetContextValue("client-cert", clientCert) 29 | cc.AddOnClose(func() { 30 | log.Println("closed connection") 31 | }) 32 | } 33 | 34 | func toHexInt(n *big.Int) string { 35 | return fmt.Sprintf("%x", n) // or %X or upper case 36 | } 37 | 38 | func handleA(w mux.ResponseWriter, r *mux.Message) { 39 | clientCert := r.Context.Value("client-cert").(*x509.Certificate) 40 | log.Println("Serial number:", toHexInt(clientCert.SerialNumber)) 41 | log.Println("Subject:", clientCert.Subject) 42 | log.Println("Email:", clientCert.EmailAddresses) 43 | 44 | log.Printf("got message in handleA: %+v from %v\n", r, w.Client().RemoteAddr()) 45 | err := w.SetResponse(codes.GET, message.TextPlain, bytes.NewReader([]byte("A hello world"))) 46 | if err != nil { 47 | log.Printf("cannot set response: %v", err) 48 | } 49 | } 50 | 51 | func main() { 52 | m := mux.NewRouter() 53 | m.Handle("/a", mux.HandlerFunc(handleA)) 54 | 55 | config, err := createServerConfig(context.Background()) 56 | if err != nil { 57 | log.Fatalln(err) 58 | return 59 | } 60 | 61 | log.Fatal(listenAndServeDTLS("udp", ":5688", config, m)) 62 | } 63 | 64 | func listenAndServeDTLS(network string, addr string, config *piondtls.Config, handler mux.Handler) error { 65 | l, err := net.NewDTLSListener(network, addr, config) 66 | if err != nil { 67 | return err 68 | } 69 | defer l.Close() 70 | s := dtls.NewServer(dtls.WithMux(handler), dtls.WithOnNewClientConn(onNewClientConn)) 71 | return s.Serve(l) 72 | } 73 | 74 | func createServerConfig(ctx context.Context) (*piondtls.Config, error) { 75 | // root cert 76 | ca, rootBytes, _, caPriv, err := pki.GenerateCA() 77 | if err != nil { 78 | return nil, err 79 | } 80 | // server cert 81 | certBytes, keyBytes, err := pki.GenerateCertificate(ca, caPriv, "server@test.com") 82 | if err != nil { 83 | return nil, err 84 | } 85 | certificate, err := pki.LoadKeyAndCertificate(keyBytes, certBytes) 86 | if err != nil { 87 | return nil, err 88 | } 89 | // cert pool 90 | certPool, err := pki.LoadCertPool(rootBytes) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | return &piondtls.Config{ 96 | Certificates: []tls.Certificate{*certificate}, 97 | ExtendedMasterSecret: piondtls.RequireExtendedMasterSecret, 98 | ClientCAs: certPool, 99 | ClientAuth: piondtls.RequireAndVerifyClientCert, 100 | ConnectContextMaker: func() (context.Context, func()) { 101 | return context.WithTimeout(ctx, 30*time.Second) 102 | }, 103 | }, nil 104 | } 105 | -------------------------------------------------------------------------------- /dtls/session.go: -------------------------------------------------------------------------------- 1 | package dtls 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "sync" 8 | "sync/atomic" 9 | 10 | coapNet "github.com/matrix-org/go-coap/v2/net" 11 | "github.com/matrix-org/go-coap/v2/udp/client" 12 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 13 | ) 14 | 15 | type EventFunc = func() 16 | 17 | type Session struct { 18 | connection *coapNet.Conn 19 | maxMessageSize int 20 | closeSocket bool 21 | 22 | mutex sync.Mutex 23 | onClose []EventFunc 24 | 25 | cancel context.CancelFunc 26 | ctx atomic.Value 27 | } 28 | 29 | func NewSession( 30 | ctx context.Context, 31 | connection *coapNet.Conn, 32 | maxMessageSize int, 33 | closeSocket bool, 34 | ) *Session { 35 | ctx, cancel := context.WithCancel(ctx) 36 | s := &Session{ 37 | cancel: cancel, 38 | connection: connection, 39 | maxMessageSize: maxMessageSize, 40 | closeSocket: closeSocket, 41 | } 42 | s.ctx.Store(&ctx) 43 | return s 44 | } 45 | 46 | func (s *Session) Done() <-chan struct{} { 47 | return s.Context().Done() 48 | } 49 | 50 | func (s *Session) AddOnClose(f EventFunc) { 51 | s.mutex.Lock() 52 | defer s.mutex.Unlock() 53 | s.onClose = append(s.onClose, f) 54 | } 55 | 56 | func (s *Session) popOnClose() []EventFunc { 57 | s.mutex.Lock() 58 | defer s.mutex.Unlock() 59 | tmp := s.onClose 60 | s.onClose = nil 61 | return tmp 62 | } 63 | 64 | func (s *Session) close() error { 65 | for _, f := range s.popOnClose() { 66 | f() 67 | } 68 | if s.closeSocket { 69 | return s.connection.Close() 70 | } 71 | return nil 72 | } 73 | 74 | func (s *Session) Close() error { 75 | s.cancel() 76 | return nil 77 | } 78 | 79 | func (s *Session) Context() context.Context { 80 | return *s.ctx.Load().(*context.Context) 81 | } 82 | 83 | // SetContextValue stores the value associated with key to context of connection. 84 | func (s *Session) SetContextValue(key interface{}, val interface{}) { 85 | s.mutex.Lock() 86 | defer s.mutex.Unlock() 87 | ctx := context.WithValue(s.Context(), key, val) 88 | s.ctx.Store(&ctx) 89 | } 90 | 91 | func (s *Session) WriteMessage(req *pool.Message) error { 92 | data, err := req.Marshal() 93 | if err != nil { 94 | return fmt.Errorf("cannot marshal: %w", err) 95 | } 96 | err = s.connection.WriteWithContext(req.Context(), data) 97 | if err != nil { 98 | return fmt.Errorf("cannot write to connection: %w", err) 99 | } 100 | return err 101 | } 102 | 103 | func (s *Session) MaxMessageSize() int { 104 | return s.maxMessageSize 105 | } 106 | 107 | func (s *Session) RemoteAddr() net.Addr { 108 | return s.connection.RemoteAddr() 109 | } 110 | 111 | // Run reads and process requests from a connection, until the connection is not closed. 112 | func (s *Session) Run(cc *client.ClientConn) (err error) { 113 | defer func() { 114 | err1 := s.Close() 115 | if err == nil { 116 | err = err1 117 | } 118 | err1 = s.close() 119 | if err == nil { 120 | err = err1 121 | } 122 | }() 123 | m := make([]byte, s.maxMessageSize) 124 | for { 125 | readBuf := m 126 | readLen, err := s.connection.ReadWithContext(s.Context(), readBuf) 127 | if err != nil { 128 | return fmt.Errorf("cannot read from connection: %w", err) 129 | } 130 | readBuf = readBuf[:readLen] 131 | err = cc.Process(readBuf) 132 | if err != nil { 133 | return err 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /net/tcplistener.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "sync/atomic" 8 | "time" 9 | ) 10 | 11 | // TCPListener is a TCP network listener that provides accept with context. 12 | type TCPListener struct { 13 | listener *net.TCPListener 14 | heartBeat time.Duration 15 | closed uint32 16 | onTimeout func() error 17 | } 18 | 19 | func newNetTCPListen(network string, addr string) (*net.TCPListener, error) { 20 | a, err := net.ResolveTCPAddr(network, addr) 21 | if err != nil { 22 | return nil, fmt.Errorf("cannot create new net tcp listener: %w", err) 23 | } 24 | 25 | tcp, err := net.ListenTCP(network, a) 26 | if err != nil { 27 | return nil, fmt.Errorf("cannot create new net tcp listener: %w", err) 28 | } 29 | return tcp, nil 30 | } 31 | 32 | var defaultTCPListenerOptions = tcpListenerOptions{ 33 | heartBeat: time.Millisecond * 200, 34 | } 35 | 36 | type tcpListenerOptions struct { 37 | heartBeat time.Duration 38 | onTimeout func() error 39 | } 40 | 41 | // A TCPListenerOption sets options such as heartBeat parameters, etc. 42 | type TCPListenerOption interface { 43 | applyTCPListener(*tcpListenerOptions) 44 | } 45 | 46 | // NewTCPListener creates tcp listener. 47 | // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only). 48 | func NewTCPListener(network string, addr string, opts ...TCPListenerOption) (*TCPListener, error) { 49 | cfg := defaultTCPListenerOptions 50 | for _, o := range opts { 51 | o.applyTCPListener(&cfg) 52 | } 53 | tcp, err := newNetTCPListen(network, addr) 54 | if err != nil { 55 | return nil, fmt.Errorf("cannot create new tcp listener: %w", err) 56 | } 57 | return &TCPListener{listener: tcp, heartBeat: cfg.heartBeat, onTimeout: cfg.onTimeout}, nil 58 | } 59 | 60 | // AcceptWithContext waits with context for a generic Conn. 61 | func (l *TCPListener) AcceptWithContext(ctx context.Context) (net.Conn, error) { 62 | for { 63 | select { 64 | case <-ctx.Done(): 65 | return nil, ctx.Err() 66 | default: 67 | } 68 | if atomic.LoadUint32(&l.closed) == 1 { 69 | return nil, ErrListenerIsClosed 70 | } 71 | deadline := time.Now().Add(l.heartBeat) 72 | err := l.SetDeadline(deadline) 73 | if err != nil { 74 | return nil, fmt.Errorf("cannot set deadline to accept connection: %w", err) 75 | } 76 | rw, err := l.listener.Accept() 77 | if err != nil { 78 | // check context in regular intervals and then resume listening 79 | if isTemporary(err, deadline) { 80 | if l.onTimeout != nil { 81 | err := l.onTimeout() 82 | if err != nil { 83 | return nil, fmt.Errorf("cannot accept connection : on timeout returns error: %w", err) 84 | } 85 | } 86 | continue 87 | } 88 | return nil, fmt.Errorf("cannot accept connection: %w", err) 89 | } 90 | return rw, nil 91 | } 92 | } 93 | 94 | // SetDeadline sets deadline for accept operation. 95 | func (l *TCPListener) SetDeadline(t time.Time) error { 96 | return l.listener.SetDeadline(t) 97 | } 98 | 99 | // Accept waits for a generic Conn. 100 | func (l *TCPListener) Accept() (net.Conn, error) { 101 | return l.AcceptWithContext(context.Background()) 102 | } 103 | 104 | // Close closes the connection. 105 | func (l *TCPListener) Close() error { 106 | if !atomic.CompareAndSwapUint32(&l.closed, 0, 1) { 107 | return nil 108 | } 109 | return l.listener.Close() 110 | } 111 | 112 | // Addr represents a network end point address. 113 | func (l *TCPListener) Addr() net.Addr { 114 | return l.listener.Addr() 115 | } 116 | -------------------------------------------------------------------------------- /udp/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | 8 | "github.com/matrix-org/go-coap/v2/message" 9 | "github.com/matrix-org/go-coap/v2/mux" 10 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 11 | ) 12 | 13 | type Client struct { 14 | cc *ClientConn 15 | } 16 | 17 | func NewClient(cc *ClientConn) *Client { 18 | return &Client{ 19 | cc: cc, 20 | } 21 | } 22 | 23 | func (c *Client) Ping(ctx context.Context) error { 24 | return c.cc.Ping(ctx) 25 | } 26 | 27 | func (c *Client) Delete(ctx context.Context, path string, opts ...message.Option) (*message.Message, error) { 28 | resp, err := c.cc.Delete(ctx, path, opts...) 29 | if err != nil { 30 | return nil, err 31 | } 32 | defer pool.ReleaseMessage(resp) 33 | return pool.ConvertTo(resp) 34 | } 35 | 36 | func (c *Client) Put(ctx context.Context, path string, contentFormat message.MediaType, payload io.ReadSeeker, opts ...message.Option) (*message.Message, error) { 37 | resp, err := c.cc.Put(ctx, path, contentFormat, payload, opts...) 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer pool.ReleaseMessage(resp) 42 | return pool.ConvertTo(resp) 43 | } 44 | 45 | func (c *Client) Post(ctx context.Context, path string, contentFormat message.MediaType, payload io.ReadSeeker, opts ...message.Option) (*message.Message, error) { 46 | resp, err := c.cc.Post(ctx, path, contentFormat, payload, opts...) 47 | if err != nil { 48 | return nil, err 49 | } 50 | defer pool.ReleaseMessage(resp) 51 | return pool.ConvertTo(resp) 52 | } 53 | 54 | func (c *Client) Get(ctx context.Context, path string, opts ...message.Option) (*message.Message, error) { 55 | resp, err := c.cc.Get(ctx, path, opts...) 56 | if err != nil { 57 | return nil, err 58 | } 59 | defer pool.ReleaseMessage(resp) 60 | return pool.ConvertTo(resp) 61 | } 62 | 63 | func (c *Client) Close() error { 64 | return c.cc.Close() 65 | } 66 | 67 | func (c *Client) RemoteAddr() net.Addr { 68 | return c.cc.RemoteAddr() 69 | } 70 | 71 | func (c *Client) Context() context.Context { 72 | return c.cc.Context() 73 | } 74 | 75 | func (c *Client) SetContextValue(key interface{}, val interface{}) { 76 | c.cc.Session().SetContextValue(key, val) 77 | } 78 | 79 | func (c *Client) WriteMessage(req *message.Message) error { 80 | r, err := pool.ConvertFrom(req) 81 | if err != nil { 82 | return err 83 | } 84 | defer pool.ReleaseMessage(r) 85 | return c.cc.WriteMessage(r) 86 | } 87 | 88 | func (c *Client) Do(req *message.Message) (*message.Message, error) { 89 | r, err := pool.ConvertFrom(req) 90 | if err != nil { 91 | return nil, err 92 | } 93 | defer pool.ReleaseMessage(r) 94 | resp, err := c.cc.Do(r) 95 | if err != nil { 96 | return nil, err 97 | } 98 | defer pool.ReleaseMessage(resp) 99 | return pool.ConvertTo(resp) 100 | } 101 | 102 | func createClientConnObserveHandler(observeFunc func(notification *message.Message)) func(n *pool.Message) { 103 | return func(n *pool.Message) { 104 | muxn, err := pool.ConvertTo(n) 105 | if err != nil { 106 | return 107 | } 108 | observeFunc(muxn) 109 | } 110 | } 111 | 112 | func (c *Client) Observe(ctx context.Context, path string, observeFunc func(notification *message.Message), opts ...message.Option) (mux.Observation, error) { 113 | return c.cc.Observe(ctx, path, createClientConnObserveHandler(observeFunc), opts...) 114 | } 115 | 116 | // Sequence acquires sequence number. 117 | func (c *Client) Sequence() uint64 { 118 | return c.cc.Sequence() 119 | } 120 | 121 | // ClientConn get's underlaying client connection. 122 | func (c *Client) ClientConn() interface{} { 123 | return c.cc 124 | } 125 | -------------------------------------------------------------------------------- /message/status/status.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/matrix-org/go-coap/v2/message" 8 | "github.com/matrix-org/go-coap/v2/message/codes" 9 | ) 10 | 11 | const ( 12 | OK codes.Code = 10000 13 | Timeout codes.Code = 10001 14 | Canceled codes.Code = 10002 15 | Unknown codes.Code = 10003 16 | ) 17 | 18 | // Status holds error of coap 19 | type Status struct { 20 | err error 21 | msg *message.Message 22 | code codes.Code 23 | } 24 | 25 | func CodeToString(c codes.Code) string { 26 | switch c { 27 | case OK: 28 | return "OK" 29 | case Timeout: 30 | return "Timeout" 31 | case Canceled: 32 | return "Canceled" 33 | case Unknown: 34 | return "Unknown" 35 | } 36 | return c.String() 37 | } 38 | 39 | func (se Status) Error() string { 40 | return fmt.Sprintf("coap error: code = %s desc = %v", CodeToString(se.msg.Code), se.err) 41 | } 42 | 43 | // Code returns the status code contained in se. 44 | func (se Status) Code() codes.Code { 45 | if se.msg != nil { 46 | return se.msg.Code 47 | } 48 | return se.code 49 | } 50 | 51 | // Message returns a coap message. 52 | func (se Status) Message() *message.Message { 53 | return se.msg 54 | } 55 | 56 | // COAPError just for check interface 57 | func (se Status) COAPError() Status { 58 | return se 59 | } 60 | 61 | // Error returns an error representing c and msg. If c is OK, returns nil. 62 | func Error(msg *message.Message, err error) Status { 63 | return Status{ 64 | msg: msg, 65 | err: err, 66 | } 67 | } 68 | 69 | // Errorf returns Error(c, fmt.Sprintf(format, a...)). 70 | func Errorf(msg *message.Message, format string, a ...interface{}) Status { 71 | return Error(msg, fmt.Errorf(format, a...)) 72 | } 73 | 74 | // FromError returns a Status representing err if it was produced from this 75 | // package or has a method `COAPError() *Status`. Otherwise, ok is false and a 76 | // Status is returned with codes.Unknown and the original error message. 77 | func FromError(err error) (s Status, ok bool) { 78 | if err == nil { 79 | return Status{ 80 | code: OK, 81 | }, true 82 | } 83 | if se, ok := err.(interface { 84 | COAPStatus() Status 85 | }); ok { 86 | return se.COAPStatus(), true 87 | } 88 | return Status{ 89 | code: Unknown, 90 | err: err, 91 | }, false 92 | } 93 | 94 | // Convert is a convenience function which removes the need to handle the 95 | // boolean return value from FromError. 96 | func Convert(err error) Status { 97 | s, _ := FromError(err) 98 | return s 99 | } 100 | 101 | // Code returns the Code of the error if it is a Status error, codes.OK if err 102 | // is nil, or codes.Unknown otherwise. 103 | func Code(err error) codes.Code { 104 | // Don't use FromError to avoid allocation of OK status. 105 | if err == nil { 106 | return OK 107 | } 108 | if se, ok := err.(interface { 109 | COAPError() Status 110 | }); ok { 111 | return se.COAPError().Code() 112 | } 113 | return Unknown 114 | } 115 | 116 | // FromContextError converts a context error into a Status. It returns a 117 | // Status with codes.OK if err is nil, or a Status with codes.Unknown if err is 118 | // non-nil and not a context error. 119 | func FromContextError(err error) Status { 120 | switch err { 121 | case nil: 122 | return Status{ 123 | code: OK, 124 | } 125 | case context.DeadlineExceeded: 126 | return Status{ 127 | code: Timeout, 128 | err: err, 129 | } 130 | case context.Canceled: 131 | return Status{ 132 | code: Canceled, 133 | err: err, 134 | } 135 | default: 136 | return Status{ 137 | code: Unknown, 138 | err: err, 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/dtls/pki/cert_gen.go: -------------------------------------------------------------------------------- 1 | package pki 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ecdsa" 6 | "crypto/elliptic" 7 | "crypto/rand" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/pem" 11 | "io" 12 | "math/big" 13 | "net" 14 | "time" 15 | ) 16 | 17 | var ( 18 | algo = elliptic.P256() 19 | notBefore = time.Now() 20 | notAfter = notBefore.Add(time.Hour) 21 | subject = pkix.Name{ 22 | Country: []string{"BR"}, 23 | Province: []string{"Parana"}, 24 | Locality: []string{"Curitiba"}, 25 | Organization: []string{"Test"}, 26 | CommonName: "test.com", 27 | } 28 | ) 29 | 30 | func sequentialBytes(n int) io.Reader { 31 | sequence := make([]byte, n) 32 | for i := 0; i < n; i++ { 33 | sequence[i] = byte(i) 34 | } 35 | return bytes.NewReader(sequence) 36 | } 37 | 38 | // GenerateCA creates a deterministic certificate authority (for test purposes only) 39 | func GenerateCA() (ca *x509.Certificate, cert, key []byte, priv *ecdsa.PrivateKey, err error) { 40 | priv, err = ecdsa.GenerateKey(algo, sequentialBytes(64)) 41 | if err != nil { 42 | return 43 | } 44 | 45 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 46 | serialNumber, err := rand.Int(sequentialBytes(128), serialNumberLimit) 47 | 48 | ca = &x509.Certificate{ 49 | NotBefore: notBefore, 50 | NotAfter: notAfter, 51 | SerialNumber: serialNumber, 52 | 53 | Subject: subject, 54 | EmailAddresses: []string{"ca@test.com"}, 55 | IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, 56 | 57 | IsCA: true, 58 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 59 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 60 | BasicConstraintsValid: true, 61 | } 62 | 63 | derBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &priv.PublicKey, priv) 64 | if err != nil { 65 | return 66 | } 67 | cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) 68 | 69 | privBytes, err := x509.MarshalPKCS8PrivateKey(priv) 70 | if err != nil { 71 | return 72 | } 73 | key = pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes}) 74 | 75 | return 76 | } 77 | 78 | // GenerateCertificate creates a certificate 79 | func GenerateCertificate(ca *x509.Certificate, caPriv *ecdsa.PrivateKey, email string) (cert, key []byte, err error) { 80 | priv, err := ecdsa.GenerateKey(algo, rand.Reader) 81 | if err != nil { 82 | return 83 | } 84 | 85 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 86 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 87 | if err != nil { 88 | return 89 | } 90 | 91 | template := x509.Certificate{ 92 | NotBefore: notBefore, 93 | NotAfter: notAfter, 94 | SerialNumber: serialNumber, 95 | 96 | Subject: subject, 97 | EmailAddresses: []string{email}, 98 | IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, 99 | 100 | SubjectKeyId: []byte{1, 2, 3, 4, 6}, 101 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 102 | KeyUsage: x509.KeyUsageDigitalSignature, 103 | } 104 | 105 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, ca, &priv.PublicKey, caPriv) 106 | if err != nil { 107 | return 108 | } 109 | 110 | cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) 111 | 112 | privBytes, err := x509.MarshalPKCS8PrivateKey(priv) 113 | if err != nil { 114 | return 115 | } 116 | key = pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes}) 117 | 118 | return 119 | } 120 | -------------------------------------------------------------------------------- /tcp/client.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | 8 | "github.com/matrix-org/go-coap/v2/message" 9 | "github.com/matrix-org/go-coap/v2/mux" 10 | "github.com/matrix-org/go-coap/v2/tcp/message/pool" 11 | ) 12 | 13 | type ClientTCP struct { 14 | cc *ClientConn 15 | } 16 | 17 | func NewClientTCP(cc *ClientConn) *ClientTCP { 18 | return &ClientTCP{ 19 | cc: cc, 20 | } 21 | } 22 | 23 | func (c *ClientTCP) Ping(ctx context.Context) error { 24 | return c.cc.Ping(ctx) 25 | } 26 | 27 | func (c *ClientTCP) Delete(ctx context.Context, path string, opts ...message.Option) (*message.Message, error) { 28 | resp, err := c.cc.Delete(ctx, path, opts...) 29 | if err != nil { 30 | return nil, err 31 | } 32 | defer pool.ReleaseMessage(resp) 33 | return pool.ConvertTo(resp) 34 | } 35 | 36 | func (c *ClientTCP) Put(ctx context.Context, path string, contentFormat message.MediaType, payload io.ReadSeeker, opts ...message.Option) (*message.Message, error) { 37 | resp, err := c.cc.Put(ctx, path, contentFormat, payload, opts...) 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer pool.ReleaseMessage(resp) 42 | return pool.ConvertTo(resp) 43 | } 44 | 45 | func (c *ClientTCP) Post(ctx context.Context, path string, contentFormat message.MediaType, payload io.ReadSeeker, opts ...message.Option) (*message.Message, error) { 46 | resp, err := c.cc.Post(ctx, path, contentFormat, payload, opts...) 47 | if err != nil { 48 | return nil, err 49 | } 50 | defer pool.ReleaseMessage(resp) 51 | return pool.ConvertTo(resp) 52 | } 53 | 54 | func (c *ClientTCP) Get(ctx context.Context, path string, opts ...message.Option) (*message.Message, error) { 55 | resp, err := c.cc.Get(ctx, path, opts...) 56 | if err != nil { 57 | return nil, err 58 | } 59 | defer pool.ReleaseMessage(resp) 60 | return pool.ConvertTo(resp) 61 | } 62 | 63 | func (c *ClientTCP) Close() error { 64 | return c.cc.Close() 65 | } 66 | 67 | func (c *ClientTCP) RemoteAddr() net.Addr { 68 | return c.cc.RemoteAddr() 69 | } 70 | 71 | func (c *ClientTCP) Context() context.Context { 72 | return c.cc.Context() 73 | } 74 | 75 | func (c *ClientTCP) SetContextValue(key interface{}, val interface{}) { 76 | c.cc.Session().SetContextValue(key, val) 77 | } 78 | 79 | func (c *ClientTCP) WriteMessage(req *message.Message) error { 80 | r, err := pool.ConvertFrom(req) 81 | if err != nil { 82 | return err 83 | } 84 | defer pool.ReleaseMessage(r) 85 | return c.cc.WriteMessage(r) 86 | } 87 | 88 | func (c *ClientTCP) Do(req *message.Message) (*message.Message, error) { 89 | r, err := pool.ConvertFrom(req) 90 | if err != nil { 91 | return nil, err 92 | } 93 | defer pool.ReleaseMessage(r) 94 | resp, err := c.cc.Do(r) 95 | if err != nil { 96 | return nil, err 97 | } 98 | defer pool.ReleaseMessage(resp) 99 | return pool.ConvertTo(resp) 100 | } 101 | 102 | func createClientConnObserveHandler(observeFunc func(notification *message.Message)) func(n *pool.Message) { 103 | return func(n *pool.Message) { 104 | muxn, err := pool.ConvertTo(n) 105 | if err != nil { 106 | return 107 | } 108 | observeFunc(muxn) 109 | } 110 | } 111 | 112 | func (c *ClientTCP) Observe(ctx context.Context, path string, observeFunc func(notification *message.Message), opts ...message.Option) (mux.Observation, error) { 113 | return c.cc.Observe(ctx, path, createClientConnObserveHandler(observeFunc), opts...) 114 | } 115 | 116 | // Sequence acquires sequence number. 117 | func (c *ClientTCP) Sequence() uint64 { 118 | return c.cc.Sequence() 119 | } 120 | 121 | // ClientConn get's underlaying client connection. 122 | func (c *ClientTCP) ClientConn() interface{} { 123 | return c.cc 124 | } 125 | -------------------------------------------------------------------------------- /message/codes/codes.go: -------------------------------------------------------------------------------- 1 | package codes 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | // A Code is an unsigned 16-bit coap code as defined in the coap spec. 9 | type Code uint16 10 | 11 | // Request Codes 12 | const ( 13 | GET Code = 1 14 | POST Code = 2 15 | PUT Code = 3 16 | DELETE Code = 4 17 | ) 18 | 19 | // Response Codes 20 | const ( 21 | Empty Code = 0 22 | Created Code = 65 23 | Deleted Code = 66 24 | Valid Code = 67 25 | Changed Code = 68 26 | Content Code = 69 27 | Continue Code = 95 28 | BadRequest Code = 128 29 | Unauthorized Code = 129 30 | BadOption Code = 130 31 | Forbidden Code = 131 32 | NotFound Code = 132 33 | MethodNotAllowed Code = 133 34 | NotAcceptable Code = 134 35 | RequestEntityIncomplete Code = 136 36 | PreconditionFailed Code = 140 37 | RequestEntityTooLarge Code = 141 38 | UnsupportedMediaType Code = 143 39 | InternalServerError Code = 160 40 | NotImplemented Code = 161 41 | BadGateway Code = 162 42 | ServiceUnavailable Code = 163 43 | GatewayTimeout Code = 164 44 | ProxyingNotSupported Code = 165 45 | ) 46 | 47 | //Signaling Codes for TCP 48 | const ( 49 | CSM Code = 225 50 | Ping Code = 226 51 | Pong Code = 227 52 | Release Code = 228 53 | Abort Code = 229 54 | ) 55 | 56 | const _maxCode = 255 57 | 58 | var strToCode = map[string]Code{ 59 | `"GET"`: GET, 60 | `"POST"`: POST, 61 | `"PUT"`: PUT, 62 | `"DELETE"`: DELETE, 63 | `"Created"`: Created, 64 | `"Deleted"`: Deleted, 65 | `"Valid"`: Valid, 66 | `"Changed"`: Changed, 67 | `"Content"`: Content, 68 | `"BadRequest"`: BadRequest, 69 | `"Unauthorized"`: Unauthorized, 70 | `"BadOption"`: BadOption, 71 | `"Forbidden"`: Forbidden, 72 | `"NotFound"`: NotFound, 73 | `"MethodNotAllowed"`: MethodNotAllowed, 74 | `"NotAcceptable"`: NotAcceptable, 75 | `"PreconditionFailed"`: PreconditionFailed, 76 | `"RequestEntityTooLarge"`: RequestEntityTooLarge, 77 | `"UnsupportedMediaType"`: UnsupportedMediaType, 78 | `"InternalServerError"`: InternalServerError, 79 | `"NotImplemented"`: NotImplemented, 80 | `"BadGateway"`: BadGateway, 81 | `"ServiceUnavailable"`: ServiceUnavailable, 82 | `"GatewayTimeout"`: GatewayTimeout, 83 | `"ProxyingNotSupported"`: ProxyingNotSupported, 84 | `"Capabilities and Settings Messages"`: CSM, 85 | `"Ping"`: Ping, 86 | `"Pong"`: Pong, 87 | `"Release"`: Release, 88 | `"Abort"`: Abort, 89 | } 90 | 91 | // UnmarshalJSON unmarshals b into the Code. 92 | func (c *Code) UnmarshalJSON(b []byte) error { 93 | // From json.Unmarshaler: By convention, to approximate the behavior of 94 | // Unmarshal itself, Unmarshalers implement UnmarshalJSON([]byte("null")) as 95 | // a no-op. 96 | if string(b) == "null" { 97 | return nil 98 | } 99 | if c == nil { 100 | return fmt.Errorf("nil receiver passed to UnmarshalJSON") 101 | } 102 | 103 | if ci, err := strconv.ParseUint(string(b), 10, 32); err == nil { 104 | if ci >= _maxCode { 105 | return fmt.Errorf("invalid code: %q", ci) 106 | } 107 | 108 | *c = Code(ci) 109 | return nil 110 | } 111 | 112 | if jc, ok := strToCode[string(b)]; ok { 113 | *c = jc 114 | return nil 115 | } 116 | return fmt.Errorf("invalid code: %q", string(b)) 117 | } 118 | -------------------------------------------------------------------------------- /tcp/message/message_test.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "testing" 5 | 6 | coap "github.com/matrix-org/go-coap/v2/message" 7 | "github.com/matrix-org/go-coap/v2/message/codes" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func testMarshalMessage(t *testing.T, msg Message, buf []byte, expectedOut []byte) { 12 | length, err := msg.MarshalTo(buf) 13 | require.NoError(t, err) 14 | buf = buf[:length] 15 | require.Equal(t, expectedOut, buf) 16 | } 17 | 18 | func testUnmarshalMessage(t *testing.T, msg Message, buf []byte, expectedOut Message) { 19 | _, err := msg.Unmarshal(buf) 20 | require.NoError(t, err) 21 | require.Equal(t, expectedOut, msg) 22 | } 23 | 24 | func TestMarshalMessage(t *testing.T) { 25 | buf := make([]byte, 1024) 26 | testMarshalMessage(t, Message{}, buf, []byte{0, 0}) 27 | testMarshalMessage(t, Message{Code: codes.GET}, buf, []byte{0, byte(codes.GET)}) 28 | testMarshalMessage(t, Message{Code: codes.GET, Payload: []byte{0x1}}, buf, []byte{32, byte(codes.GET), 0xff, 0x1}) 29 | testMarshalMessage(t, Message{Code: codes.GET, Payload: []byte{0x1}, Token: []byte{0x1, 0x2, 0x3}}, buf, []byte{35, byte(codes.GET), 0x1, 0x2, 0x3, 0xff, 0x1}) 30 | bufOptions := make([]byte, 1024) 31 | bufOptionsUsed := bufOptions 32 | options := make(coap.Options, 0, 32) 33 | enc := 0 34 | options, enc, err := options.SetPath(bufOptionsUsed, "/a/b/c/d/e") 35 | if err != nil { 36 | t.Fatalf("Cannot set uri") 37 | } 38 | bufOptionsUsed = bufOptionsUsed[enc:] 39 | options, enc, err = options.SetContentFormat(bufOptionsUsed, coap.TextPlain) 40 | if err != nil { 41 | t.Fatalf("Cannot set content format") 42 | } 43 | bufOptionsUsed = bufOptionsUsed[enc:] 44 | 45 | testMarshalMessage(t, Message{ 46 | Code: codes.GET, 47 | Payload: []byte{0x1}, 48 | Token: []byte{0x1, 0x2, 0x3}, 49 | Options: options, 50 | }, buf, []byte{211, 0, 1, 1, 2, 3, 177, 97, 1, 98, 1, 99, 1, 100, 1, 101, 16, 255, 1}) 51 | } 52 | 53 | func TestUnmarshalMessage(t *testing.T) { 54 | testUnmarshalMessage(t, Message{}, []byte{0, 0}, Message{}) 55 | testUnmarshalMessage(t, Message{}, []byte{0, byte(codes.GET)}, Message{Code: codes.GET}) 56 | testUnmarshalMessage(t, Message{}, []byte{32, byte(codes.GET), 0xff, 0x1}, Message{Code: codes.GET, Payload: []byte{0x1}}) 57 | testUnmarshalMessage(t, Message{}, []byte{35, byte(codes.GET), 0x1, 0x2, 0x3, 0xff, 0x1}, Message{Code: codes.GET, Payload: []byte{0x1}, Token: []byte{0x1, 0x2, 0x3}}) 58 | testUnmarshalMessage(t, Message{Options: make(coap.Options, 0, 32)}, []byte{211, 0, 1, 1, 2, 3, 177, 97, 1, 98, 1, 99, 1, 100, 1, 101, 16, 255, 1}, Message{ 59 | Code: codes.GET, 60 | Payload: []byte{0x1}, 61 | Token: []byte{0x1, 0x2, 0x3}, 62 | Options: []coap.Option{{11, []byte{97}}, {11, []byte{98}}, {11, []byte{99}}, {11, []byte{100}}, {11, []byte{101}}, {12, []byte{}}}, 63 | }) 64 | } 65 | 66 | func BenchmarkMarshalMessage(b *testing.B) { 67 | options := make(coap.Options, 0, 32) 68 | bufOptions := make([]byte, 1024) 69 | bufOptionsUsed := bufOptions 70 | 71 | enc := 0 72 | 73 | options, enc, _ = options.SetPath(bufOptionsUsed, "/a/b/c/d/e") 74 | bufOptionsUsed = bufOptionsUsed[enc:] 75 | 76 | options, enc, _ = options.SetContentFormat(bufOptionsUsed, coap.TextPlain) 77 | bufOptionsUsed = bufOptionsUsed[enc:] 78 | msg := Message{ 79 | Code: codes.GET, 80 | Payload: []byte{0x1}, 81 | Token: []byte{0x1, 0x2, 0x3}, 82 | Options: options, 83 | } 84 | buffer := make([]byte, 1024) 85 | 86 | b.ResetTimer() 87 | for i := uint32(0); i < uint32(b.N); i++ { 88 | 89 | _, err := msg.MarshalTo(buffer) 90 | if err != nil { 91 | b.Fatalf("cannot marshal") 92 | } 93 | } 94 | } 95 | 96 | func BenchmarkUnmarshalMessage(b *testing.B) { 97 | buffer := []byte{211, 0, 1, 1, 2, 3, 177, 97, 1, 98, 1, 99, 1, 100, 1, 101, 16, 255, 1} 98 | options := make(coap.Options, 0, 32) 99 | msg := Message{ 100 | Options: options, 101 | } 102 | 103 | b.ResetTimer() 104 | for i := uint32(0); i < uint32(b.N); i++ { 105 | msg.Options = options 106 | _, err := msg.Unmarshal(buffer) 107 | if err != nil { 108 | b.Fatalf("cannot unmarshal: %v", err) 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /udp/message/message.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/matrix-org/go-coap/v2/message" 7 | "github.com/matrix-org/go-coap/v2/message/codes" 8 | ) 9 | 10 | const ( 11 | MESSAGE_LEN13_BASE = 13 12 | MESSAGE_LEN14_BASE = 269 13 | MESSAGE_LEN15_BASE = 65805 14 | MESSAGE_MAX_LEN = 0x7fff0000 // Large number that works in 32-bit builds. 15 | ) 16 | 17 | // TcpMessage is a CoAP MessageBase that can encode itself for Message 18 | // transport. 19 | type Message struct { 20 | Code codes.Code 21 | 22 | Token message.Token 23 | Payload []byte 24 | 25 | MessageID uint16 26 | Type Type 27 | 28 | Options message.Options //Options must be sorted by ID 29 | } 30 | 31 | func (m Message) Size() (int, error) { 32 | if len(m.Token) > message.MaxTokenSize { 33 | return -1, message.ErrInvalidTokenLen 34 | } 35 | size := 4 + len(m.Token) 36 | payloadLen := len(m.Payload) 37 | optionsLen, err := m.Options.Marshal(nil) 38 | if err != message.ErrTooSmall { 39 | return -1, err 40 | } 41 | if payloadLen > 0 { 42 | //for separator 0xff 43 | payloadLen++ 44 | } 45 | size += payloadLen + optionsLen 46 | return size, nil 47 | } 48 | 49 | func (m Message) Marshal() ([]byte, error) { 50 | b := make([]byte, 1024) 51 | l, err := m.MarshalTo(b) 52 | if err == message.ErrTooSmall { 53 | b = append(b[:0], make([]byte, l)...) 54 | l, err = m.MarshalTo(b) 55 | } 56 | return b[:l], err 57 | } 58 | 59 | func (m Message) MarshalTo(buf []byte) (int, error) { 60 | /* 61 | 0 1 2 3 62 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 63 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 64 | |Ver| T | TKL | Code | Message ID | 65 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 66 | | Token (if any, TKL bytes) ... 67 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 68 | | Options (if any) ... 69 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 70 | |1 1 1 1 1 1 1 1| Payload (if any) ... 71 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 72 | */ 73 | size, err := m.Size() 74 | if err != nil { 75 | return -1, err 76 | } 77 | if len(buf) < size { 78 | return size, message.ErrTooSmall 79 | } 80 | 81 | tmpbuf := []byte{0, 0} 82 | binary.BigEndian.PutUint16(tmpbuf, m.MessageID) 83 | 84 | buf[0] = (1 << 6) | byte(m.Type)<<4 | byte(0xf&len(m.Token)) 85 | buf[1] = byte(m.Code) 86 | buf[2] = tmpbuf[0] 87 | buf[3] = tmpbuf[1] 88 | buf = buf[4:] 89 | 90 | if len(m.Token) > message.MaxTokenSize { 91 | return -1, message.ErrInvalidTokenLen 92 | } 93 | copy(buf, m.Token) 94 | buf = buf[len(m.Token):] 95 | 96 | optionsLen, err := m.Options.Marshal(buf) 97 | switch err { 98 | case nil: 99 | case message.ErrTooSmall: 100 | return size, err 101 | default: 102 | return -1, err 103 | } 104 | buf = buf[optionsLen:] 105 | 106 | if len(m.Payload) > 0 { 107 | buf[0] = 0xff 108 | buf = buf[1:] 109 | } 110 | copy(buf, m.Payload) 111 | return size, nil 112 | } 113 | 114 | func (m *Message) Unmarshal(data []byte) (int, error) { 115 | size := len(data) 116 | if size < 4 { 117 | return -1, ErrMessageTruncated 118 | } 119 | 120 | if data[0]>>6 != 1 { 121 | return -1, ErrMessageInvalidVersion 122 | } 123 | 124 | typ := Type((data[0] >> 4) & 0x3) 125 | tokenLen := int(data[0] & 0xf) 126 | if tokenLen > 8 { 127 | return -1, message.ErrInvalidTokenLen 128 | } 129 | 130 | code := codes.Code(data[1]) 131 | messageID := binary.BigEndian.Uint16(data[2:4]) 132 | data = data[4:] 133 | if len(data) < tokenLen { 134 | return -1, ErrMessageTruncated 135 | } 136 | token := data[:tokenLen] 137 | if len(token) == 0 { 138 | token = nil 139 | } 140 | data = data[tokenLen:] 141 | 142 | optionDefs := message.CoapOptionDefs 143 | proc, err := m.Options.Unmarshal(data, optionDefs) 144 | if err != nil { 145 | return -1, err 146 | } 147 | data = data[proc:] 148 | if len(data) == 0 { 149 | data = nil 150 | } 151 | 152 | m.Payload = data 153 | m.Code = code 154 | m.Token = token 155 | m.Type = typ 156 | m.MessageID = messageID 157 | 158 | return size, nil 159 | } 160 | -------------------------------------------------------------------------------- /tcp/message/pool/message.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "io" 8 | "sync" 9 | "sync/atomic" 10 | 11 | "github.com/matrix-org/go-coap/v2/message" 12 | "github.com/matrix-org/go-coap/v2/message/pool" 13 | tcp "github.com/matrix-org/go-coap/v2/tcp/message" 14 | ) 15 | 16 | const maxMessagePool = 10240 17 | const maxMessageBufferSize = 2048 18 | 19 | var ( 20 | currentMessagesInPool int32 21 | messagePool sync.Pool 22 | ) 23 | 24 | type Message struct { 25 | *pool.Message 26 | 27 | //local vars 28 | rawData []byte 29 | rawMarshalData []byte 30 | 31 | ctx context.Context 32 | isModified bool 33 | } 34 | 35 | // Reset clear message for next reuse 36 | func (r *Message) Reset() { 37 | r.Message.Reset() 38 | if cap(r.rawData) > maxMessageBufferSize { 39 | r.rawData = make([]byte, 256) 40 | } 41 | if cap(r.rawMarshalData) > maxMessageBufferSize { 42 | r.rawMarshalData = make([]byte, 256) 43 | } 44 | r.isModified = false 45 | } 46 | 47 | func (r *Message) Context() context.Context { 48 | return r.ctx 49 | } 50 | 51 | func (r *Message) IsModified() bool { 52 | return r.isModified || r.Message.IsModified() 53 | } 54 | 55 | func (r *Message) Unmarshal(data []byte) (int, error) { 56 | if len(r.rawData) < len(data) { 57 | r.rawData = append(r.rawData, make([]byte, len(data)-len(r.rawData))...) 58 | } 59 | copy(r.rawData, data) 60 | r.rawData = r.rawData[:len(data)] 61 | m := &tcp.Message{ 62 | Options: make(message.Options, 0, 16), 63 | } 64 | 65 | n, err := m.Unmarshal(r.rawData) 66 | if err != nil { 67 | return n, err 68 | } 69 | r.Message.SetCode(m.Code) 70 | r.Message.SetToken(m.Token) 71 | r.Message.ResetOptionsTo(m.Options) 72 | if len(m.Payload) > 0 { 73 | r.Message.SetBody(bytes.NewReader(m.Payload)) 74 | } 75 | return n, err 76 | } 77 | 78 | func (r *Message) Marshal() ([]byte, error) { 79 | m := tcp.Message{ 80 | Code: r.Code(), 81 | Token: r.Message.Token(), 82 | Options: r.Message.Options(), 83 | } 84 | payload, err := r.ReadBody() 85 | if err != nil { 86 | return nil, err 87 | } 88 | m.Payload = payload 89 | size, err := m.Size() 90 | if err != nil { 91 | return nil, err 92 | } 93 | if len(r.rawMarshalData) < size { 94 | r.rawMarshalData = append(r.rawMarshalData, make([]byte, size-len(r.rawMarshalData))...) 95 | } 96 | n, err := m.MarshalTo(r.rawMarshalData) 97 | if err != nil { 98 | return nil, err 99 | } 100 | r.rawMarshalData = r.rawMarshalData[:n] 101 | return r.rawMarshalData, nil 102 | } 103 | 104 | // AcquireMessage returns an empty Message instance from Message pool. 105 | // 106 | // The returned Message instance may be passed to ReleaseMessage when it is 107 | // no longer needed. This allows Message recycling, reduces GC pressure 108 | // and usually improves performance. 109 | func AcquireMessage(ctx context.Context) *Message { 110 | v := messagePool.Get() 111 | if v == nil { 112 | return &Message{ 113 | Message: pool.NewMessage(), 114 | rawData: make([]byte, 256), 115 | rawMarshalData: make([]byte, 256), 116 | ctx: ctx, 117 | } 118 | } 119 | atomic.AddInt32(¤tMessagesInPool, -1) 120 | r := v.(*Message) 121 | r.ctx = ctx 122 | return r 123 | } 124 | 125 | // ReleaseMessage returns req acquired via AcquireMessage to Message pool. 126 | // 127 | // It is forbidden accessing req and/or its' members after returning 128 | // it to Message pool. 129 | func ReleaseMessage(req *Message) { 130 | v := atomic.LoadInt32(¤tMessagesInPool) 131 | if v >= maxMessagePool { 132 | return 133 | } 134 | atomic.AddInt32(¤tMessagesInPool, 1) 135 | req.Reset() 136 | messagePool.Put(req) 137 | } 138 | 139 | // ConvertFrom converts common message to pool message. 140 | func ConvertFrom(m *message.Message) (*Message, error) { 141 | if m.Context == nil { 142 | return nil, fmt.Errorf("invalid context") 143 | } 144 | r := AcquireMessage(m.Context) 145 | r.SetCode(m.Code) 146 | r.ResetOptionsTo(m.Options) 147 | r.SetBody(m.Body) 148 | r.SetToken(m.Token) 149 | return r, nil 150 | } 151 | 152 | // ConvertTo converts pool message to common message. 153 | func ConvertTo(m *Message) (*message.Message, error) { 154 | opts, err := m.Options().Clone() 155 | if err != nil { 156 | return nil, err 157 | } 158 | var body io.ReadSeeker 159 | if m.Body() != nil { 160 | payload, err := m.ReadBody() 161 | if err != nil { 162 | return nil, err 163 | } 164 | body = bytes.NewReader(payload) 165 | } 166 | return &message.Message{ 167 | Context: m.Context(), 168 | Code: m.Code(), 169 | Token: m.Token(), 170 | Body: body, 171 | Options: opts, 172 | }, nil 173 | } 174 | -------------------------------------------------------------------------------- /udp/client/clientobserve.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/matrix-org/go-coap/v2/message" 11 | "github.com/matrix-org/go-coap/v2/message/codes" 12 | "github.com/matrix-org/go-coap/v2/net/observation" 13 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 14 | ) 15 | 16 | func NewObservationHandler(obsertionTokenHandler *HandlerContainer, next HandlerFunc) HandlerFunc { 17 | return func(w *ResponseWriter, r *pool.Message) { 18 | v, err := obsertionTokenHandler.Get(r.Token()) 19 | if err == nil { 20 | v(w, r) 21 | return 22 | } 23 | obs, err := r.Observe() 24 | if err == nil && obs > 1 { 25 | w.SendReset() 26 | return 27 | } 28 | next(w, r) 29 | } 30 | } 31 | 32 | //Observation represents subscription to resource on the server 33 | type Observation struct { 34 | token message.Token 35 | path string 36 | cc *ClientConn 37 | observeFunc func(req *pool.Message) 38 | respCodeChan chan codes.Code 39 | 40 | obsSequence uint32 41 | etag []byte 42 | lastEvent time.Time 43 | mutex sync.Mutex 44 | 45 | waitForReponse uint32 46 | } 47 | 48 | func newObservation(token message.Token, path string, cc *ClientConn, observeFunc func(req *pool.Message), respCodeChan chan codes.Code) *Observation { 49 | return &Observation{ 50 | token: token, 51 | path: path, 52 | obsSequence: 0, 53 | cc: cc, 54 | waitForReponse: 1, 55 | respCodeChan: respCodeChan, 56 | observeFunc: observeFunc, 57 | } 58 | } 59 | 60 | func (o *Observation) cleanUp() { 61 | o.cc.observationTokenHandler.Pop(o.token) 62 | registeredRequest, ok := o.cc.observationRequests.PullOut(o.token.String()) 63 | if ok { 64 | pool.ReleaseMessage(registeredRequest.(*pool.Message)) 65 | } 66 | } 67 | 68 | func (o *Observation) handler(w *ResponseWriter, r *pool.Message) { 69 | code := r.Code() 70 | if atomic.CompareAndSwapUint32(&o.waitForReponse, 1, 0) { 71 | select { 72 | case o.respCodeChan <- code: 73 | default: 74 | } 75 | o.respCodeChan = nil 76 | } 77 | if o.wantBeNotified(r) { 78 | o.observeFunc(r) 79 | } 80 | } 81 | 82 | // Cancel remove observation from server. For recreate observation use Observe. 83 | func (o *Observation) Cancel(ctx context.Context) error { 84 | o.cleanUp() 85 | req, err := NewGetRequest(ctx, o.path) 86 | if err != nil { 87 | return fmt.Errorf("cannot cancel observation request: %w", err) 88 | } 89 | defer pool.ReleaseMessage(req) 90 | req.SetObserve(1) 91 | req.SetToken(o.token) 92 | resp, err := o.cc.Do(req) 93 | if err != nil { 94 | return err 95 | } 96 | defer pool.ReleaseMessage(resp) 97 | if resp.Code() != codes.Content { 98 | return fmt.Errorf("unexpected return code(%v)", resp.Code()) 99 | } 100 | return err 101 | } 102 | 103 | func (o *Observation) wantBeNotified(r *pool.Message) bool { 104 | obsSequence, err := r.Observe() 105 | if err != nil { 106 | return true 107 | } 108 | now := time.Now() 109 | 110 | o.mutex.Lock() 111 | defer o.mutex.Unlock() 112 | 113 | if observation.ValidSequenceNumber(o.obsSequence, obsSequence, o.lastEvent, now) { 114 | o.obsSequence = obsSequence 115 | o.lastEvent = now 116 | return true 117 | } 118 | 119 | return false 120 | } 121 | 122 | // Observe subscribes for every change of resource on path. 123 | func (cc *ClientConn) Observe(ctx context.Context, path string, observeFunc func(req *pool.Message), opts ...message.Option) (*Observation, error) { 124 | req, err := NewGetRequest(ctx, path, opts...) 125 | if err != nil { 126 | return nil, fmt.Errorf("cannot create observe request: %w", err) 127 | } 128 | token := req.Token() 129 | req.SetObserve(0) 130 | respCodeChan := make(chan codes.Code, 1) 131 | o := newObservation(token, path, cc, observeFunc, respCodeChan) 132 | 133 | cc.observationRequests.Store(token.String(), req) 134 | err = o.cc.observationTokenHandler.Insert(token.String(), o.handler) 135 | defer func(err *error) { 136 | if *err != nil { 137 | o.cleanUp() 138 | } 139 | }(&err) 140 | if err != nil { 141 | return nil, err 142 | } 143 | 144 | err = cc.WriteMessage(req) 145 | if err != nil { 146 | return nil, err 147 | } 148 | select { 149 | case <-req.Context().Done(): 150 | err = req.Context().Err() 151 | return nil, err 152 | case <-cc.Context().Done(): 153 | err = fmt.Errorf("connection was closed: %w", cc.Context().Err()) 154 | return nil, err 155 | case respCode := <-respCodeChan: 156 | if respCode != codes.Content { 157 | err = fmt.Errorf("unexpected return code(%v)", respCode) 158 | return nil, err 159 | } 160 | return o, nil 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /tcp/clientobserve.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/matrix-org/go-coap/v2/message" 11 | "github.com/matrix-org/go-coap/v2/message/codes" 12 | "github.com/matrix-org/go-coap/v2/net/observation" 13 | "github.com/matrix-org/go-coap/v2/tcp/message/pool" 14 | ) 15 | 16 | func NewObservationHandler(obsertionTokenHandler *HandlerContainer, next HandlerFunc) HandlerFunc { 17 | return func(w *ResponseWriter, r *pool.Message) { 18 | v, err := obsertionTokenHandler.Get(r.Token()) 19 | if err != nil { 20 | next(w, r) 21 | return 22 | } 23 | v(w, r) 24 | } 25 | } 26 | 27 | //Observation represents subscription to resource on the server 28 | type Observation struct { 29 | token message.Token 30 | path string 31 | cc *ClientConn 32 | observeFunc func(req *pool.Message) 33 | respCodeChan chan codes.Code 34 | 35 | obsSequence uint32 36 | etag []byte 37 | lastEvent time.Time 38 | mutex sync.Mutex 39 | 40 | waitForReponse uint32 41 | } 42 | 43 | func newObservation(token message.Token, path string, cc *ClientConn, observeFunc func(req *pool.Message), respCodeChan chan codes.Code) *Observation { 44 | return &Observation{ 45 | token: token, 46 | path: path, 47 | obsSequence: 0, 48 | cc: cc, 49 | waitForReponse: 1, 50 | respCodeChan: respCodeChan, 51 | observeFunc: observeFunc, 52 | } 53 | } 54 | 55 | func (o *Observation) handler(w *ResponseWriter, r *pool.Message) { 56 | code := r.Code() 57 | if atomic.CompareAndSwapUint32(&o.waitForReponse, 1, 0) { 58 | select { 59 | case o.respCodeChan <- code: 60 | default: 61 | } 62 | o.respCodeChan = nil 63 | } 64 | if o.wantBeNotified(r) { 65 | o.observeFunc(r) 66 | } 67 | } 68 | 69 | func (o *Observation) cleanUp() { 70 | o.cc.observationTokenHandler.Pop(o.token) 71 | o.cc.observationRequests.PullOut(o.token.String()) 72 | } 73 | 74 | // Cancel remove observation from server. For recreate observation use Observe. 75 | func (o *Observation) Cancel(ctx context.Context) error { 76 | o.cleanUp() 77 | req, err := NewGetRequest(ctx, o.path) 78 | if err != nil { 79 | return fmt.Errorf("cannot cancel observation request: %w", err) 80 | } 81 | defer pool.ReleaseMessage(req) 82 | req.SetObserve(1) 83 | req.SetToken(o.token) 84 | resp, err := o.cc.Do(req) 85 | if err != nil { 86 | return err 87 | } 88 | defer pool.ReleaseMessage(resp) 89 | if resp.Code() != codes.Content { 90 | return fmt.Errorf("unexpected return code(%v)", resp.Code()) 91 | } 92 | return nil 93 | } 94 | 95 | func (o *Observation) wantBeNotified(r *pool.Message) bool { 96 | obsSequence, err := r.Observe() 97 | if err != nil { 98 | return true 99 | } 100 | now := time.Now() 101 | 102 | o.mutex.Lock() 103 | defer o.mutex.Unlock() 104 | if observation.ValidSequenceNumber(o.obsSequence, obsSequence, o.lastEvent, now) { 105 | o.obsSequence = obsSequence 106 | o.lastEvent = now 107 | return true 108 | } 109 | 110 | return false 111 | } 112 | 113 | // Observe subscribes for every change of resource on path. 114 | func (cc *ClientConn) Observe(ctx context.Context, path string, observeFunc func(req *pool.Message), opts ...message.Option) (*Observation, error) { 115 | req, err := NewGetRequest(ctx, path, opts...) 116 | if err != nil { 117 | return nil, fmt.Errorf("cannot create observe request: %w", err) 118 | } 119 | defer pool.ReleaseMessage(req) 120 | token := req.Token() 121 | req.SetObserve(0) 122 | 123 | respCodeChan := make(chan codes.Code, 1) 124 | o := newObservation(token, path, cc, observeFunc, respCodeChan) 125 | 126 | options, err := req.Options().Clone() 127 | if err != nil { 128 | return nil, fmt.Errorf("cannot clone options: %w", err) 129 | } 130 | 131 | obs := message.Message{ 132 | Context: req.Context(), 133 | Token: req.Token(), 134 | Code: req.Code(), 135 | Options: options, 136 | } 137 | cc.observationRequests.Store(token.String(), obs) 138 | err = o.cc.observationTokenHandler.Insert(token.String(), o.handler) 139 | defer func(err *error) { 140 | if *err != nil { 141 | o.cleanUp() 142 | } 143 | }(&err) 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | err = cc.WriteMessage(req) 149 | if err != nil { 150 | return nil, err 151 | } 152 | select { 153 | case <-req.Context().Done(): 154 | err = req.Context().Err() 155 | return nil, err 156 | case <-cc.Context().Done(): 157 | err = fmt.Errorf("connection was closed: %w", cc.Context().Err()) 158 | return nil, err 159 | case respCode := <-respCodeChan: 160 | if respCode != codes.Content { 161 | err = fmt.Errorf("unexpected return code(%v)", respCode) 162 | return nil, err 163 | } 164 | return o, nil 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go-CoAP 2 | 3 | This is the matrix.org fork of go-coap, specialised for [MSC3079](https://github.com/matrix-org/matrix-doc/pull/3079). 4 | There are several reasons to fork [the original implementation](https://github.com/plgd-dev/go-coap): 5 | - FIN packet handling is easier to do at the go-coap level, and is matrix.org specific. 6 | See [these comments](https://github.com/matrix-org/matrix-doc/blob/kegan/low-bandwidth/proposals/3079-low-bandwidth-csapi.md#potential-issues) for context. 7 | - We were hitting many known issues with the original implementation around retry handling, congestion control (NSTART handling), 8 | accessing MIDs on UDP messages, etc. 9 | - We want to add WebSockets support. 10 | 11 | This repo was originally forked from an even earlier implementation for 12 | [FOSDEM 2019](https://matrix.org/blog/2019/03/12/breaking-the-100-bps-barrier-with-matrix-meshsim-coap-proxy), but this new 13 | work is based on [v2.4.0](https://github.com/plgd-dev/go-coap/releases/tag/v2.4.0). 14 | 15 | The go-coap provides servers and clients for DTLS, TCP-TLS, UDP, TCP in golang. 16 | 17 | ## Unique features in fork 18 | - `WithLogger` to expose internals. 19 | 20 | ## Features 21 | * CoAP over UDP [RFC 7252][coap]. 22 | * CoAP over TCP/TLS [RFC 8232][coap-tcp] 23 | * Observe resources in CoAP [RFC 7641][coap-observe] 24 | * Block-wise transfers in CoAP [RFC 7959][coap-block-wise-transfers] 25 | * request multiplexer 26 | * multicast 27 | * CoAP NoResponse option in CoAP [RFC 7967][coap-noresponse] 28 | * CoAP over DTLS [pion/dtls][pion-dtls] 29 | 30 | [coap]: http://tools.ietf.org/html/rfc7252 31 | [coap-tcp]: https://tools.ietf.org/html/rfc8323 32 | [coap-block-wise-transfers]: https://tools.ietf.org/html/rfc7959 33 | [coap-observe]: https://tools.ietf.org/html/rfc7641 34 | [coap-noresponse]: https://tools.ietf.org/html/rfc7967 35 | [pion-dtls]: https://github.com/pion/dtls 36 | 37 | ## Samples 38 | 39 | ### Simple 40 | 41 | #### Server UDP/TCP 42 | ```go 43 | // Server 44 | 45 | // Middleware function, which will be called for each request. 46 | func loggingMiddleware(next mux.Handler) mux.Handler { 47 | return mux.HandlerFunc(func(w mux.ResponseWriter, r *mux.Message) { 48 | log.Printf("ClientAddress %v, %v\n", w.Client().RemoteAddr(), r.String()) 49 | next.ServeCOAP(w, r) 50 | }) 51 | } 52 | 53 | // See /examples/simple/server/main.go 54 | func handleA(w mux.ResponseWriter, req *mux.Message) { 55 | err := w.SetResponse(codes.GET, message.TextPlain, bytes.NewReader([]byte("hello world"))) 56 | if err != nil { 57 | log.Printf("cannot set response: %v", err) 58 | } 59 | } 60 | 61 | func main() { 62 | r := mux.NewRouter() 63 | r.Use(loggingMiddleware) 64 | r.Handle("/a", mux.HandlerFunc(handleA)) 65 | r.Handle("/b", mux.HandlerFunc(handleB)) 66 | 67 | log.Fatal(coap.ListenAndServe("udp", ":5688", r)) 68 | 69 | 70 | // for tcp 71 | // log.Fatal(coap.ListenAndServe("tcp", ":5688", r)) 72 | 73 | // for tcp-tls 74 | // log.Fatal(coap.ListenAndServeTLS("tcp", ":5688", &tls.Config{...}, r)) 75 | 76 | // for udp-dtls 77 | // log.Fatal(coap.ListenAndServeDTLS("udp", ":5688", &dtls.Config{...}, r)) 78 | } 79 | ``` 80 | #### Client 81 | ```go 82 | // Client 83 | // See /examples/simpler/client/main.go 84 | func main() { 85 | co, err := udp.Dial("localhost:5688") 86 | 87 | // for tcp 88 | // co, err := tcp.Dial("localhost:5688") 89 | 90 | // for tcp-tls 91 | // co, err := tcp.Dial("localhost:5688", tcp.WithTLS(&tls.Config{...})) 92 | 93 | // for dtls 94 | // co, err := dtls.Dial("localhost:5688", &dtls.Config{...})) 95 | 96 | if err != nil { 97 | log.Fatalf("Error dialing: %v", err) 98 | } 99 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 100 | defer cancel() 101 | resp, err := co.Get(ctx, "/a") 102 | if err != nil { 103 | log.Fatalf("Cannot get response: %v", err) 104 | return 105 | } 106 | log.Printf("Response: %+v", resp) 107 | } 108 | ``` 109 | 110 | ### Observe / Notify 111 | 112 | [Server](examples/observe/server/main.go) example. 113 | 114 | [Client](examples/observe/client/main.go) example. 115 | 116 | ### Multicast 117 | 118 | [Server](examples/mcast/server/main.go) example. 119 | 120 | [Client](examples/mcast/client/main.go) example. 121 | 122 | ## Contributing 123 | 124 | In order to run the tests that the CI will run locally, the following two commands can be used to build the Docker image and run the tests. When making changes, these are the tests that the CI will run, so please make sure that the tests work locally before committing. 125 | 126 | ```shell 127 | $ docker build . --network=host -t go-coap:build --target build 128 | $ docker run --mount type=bind,source="$(pwd)",target=/shared,readonly --network=host go-coap:build go test './...' 129 | ``` -------------------------------------------------------------------------------- /net/connUDP_test.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "strconv" 7 | "sync" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestUDPConn_WriteWithContext(t *testing.T) { 16 | peerAddr := "127.0.0.1:2154" 17 | b, err := net.ResolveUDPAddr("udp", peerAddr) 18 | require.NoError(t, err) 19 | 20 | ctxCanceled, ctxCancel := context.WithCancel(context.Background()) 21 | ctxCancel() 22 | 23 | type args struct { 24 | ctx context.Context 25 | udpCtx *net.UDPAddr 26 | buffer []byte 27 | } 28 | tests := []struct { 29 | name string 30 | args args 31 | wantErr bool 32 | }{ 33 | { 34 | name: "valid", 35 | args: args{ 36 | ctx: context.Background(), 37 | udpCtx: b, 38 | buffer: []byte("hello world"), 39 | }, 40 | }, 41 | { 42 | name: "cancelled", 43 | args: args{ 44 | ctx: ctxCanceled, 45 | buffer: []byte("hello world"), 46 | }, 47 | wantErr: true, 48 | }, 49 | } 50 | 51 | a, err := net.ResolveUDPAddr("udp", "127.0.0.1:") 52 | require.NoError(t, err) 53 | l1, err := net.ListenUDP("udp", a) 54 | require.NoError(t, err) 55 | c1 := NewUDPConn("udp", l1, WithHeartBeat(time.Millisecond*100), WithErrors(func(err error) { t.Log(err) })) 56 | defer c1.Close() 57 | ctx, cancel := context.WithCancel(context.Background()) 58 | defer cancel() 59 | 60 | l2, err := net.ListenUDP("udp", b) 61 | require.NoError(t, err) 62 | c2 := NewUDPConn("udp", l2, WithHeartBeat(time.Millisecond*100), WithErrors(func(err error) { t.Log(err) })) 63 | defer c2.Close() 64 | 65 | go func() { 66 | b := make([]byte, 1024) 67 | _, _, err := c2.ReadWithContext(ctx, b) 68 | if err != nil { 69 | return 70 | } 71 | }() 72 | 73 | for _, tt := range tests { 74 | t.Run(tt.name, func(t *testing.T) { 75 | err = c1.WriteWithContext(tt.args.ctx, tt.args.udpCtx, tt.args.buffer) 76 | 77 | c1.LocalAddr() 78 | c1.RemoteAddr() 79 | 80 | if tt.wantErr { 81 | assert.Error(t, err) 82 | } else { 83 | assert.NoError(t, err) 84 | } 85 | }) 86 | } 87 | } 88 | 89 | func TestUDPConn_writeMulticastWithContext(t *testing.T) { 90 | peerAddr := "224.0.1.187:5683" 91 | b, err := net.ResolveUDPAddr("udp4", peerAddr) 92 | require.NoError(t, err) 93 | 94 | ctxCanceled, ctxCancel := context.WithCancel(context.Background()) 95 | ctxCancel() 96 | payload := []byte("hello world") 97 | 98 | type args struct { 99 | ctx context.Context 100 | udpCtx *net.UDPAddr 101 | buffer []byte 102 | } 103 | tests := []struct { 104 | name string 105 | args args 106 | wantErr bool 107 | }{ 108 | { 109 | name: "valid", 110 | args: args{ 111 | ctx: context.Background(), 112 | udpCtx: b, 113 | buffer: payload, 114 | }, 115 | }, 116 | { 117 | name: "cancelled", 118 | args: args{ 119 | ctx: ctxCanceled, 120 | udpCtx: b, 121 | buffer: payload, 122 | }, 123 | wantErr: true, 124 | }, 125 | } 126 | 127 | listenAddr := ":" + strconv.Itoa(b.Port) 128 | c, err := net.ResolveUDPAddr("udp4", listenAddr) 129 | require.NoError(t, err) 130 | l2, err := net.ListenUDP("udp4", c) 131 | require.NoError(t, err) 132 | c2 := NewUDPConn("udp", l2, WithHeartBeat(time.Millisecond*100), WithErrors(func(err error) { t.Log(err) })) 133 | defer c2.Close() 134 | ifaces, err := net.Interfaces() 135 | require.NoError(t, err) 136 | for _, iface := range ifaces { 137 | ifa := iface 138 | err = c2.JoinGroup(&ifa, b) 139 | if err != nil { 140 | t.Logf("fmt cannot join group %v: %v", ifa.Name, err) 141 | } 142 | } 143 | 144 | err = c2.SetMulticastLoopback(true) 145 | require.NoError(t, err) 146 | 147 | a, err := net.ResolveUDPAddr("udp4", "") 148 | require.NoError(t, err) 149 | l1, err := net.ListenUDP("udp4", a) 150 | require.NoError(t, err) 151 | c1 := NewUDPConn("udp", l1, WithHeartBeat(time.Millisecond*100), WithErrors(func(err error) { t.Log(err) })) 152 | defer c1.Close() 153 | require.NoError(t, err) 154 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) 155 | defer cancel() 156 | 157 | var wg sync.WaitGroup 158 | wg.Add(1) 159 | go func() { 160 | b := make([]byte, 1024) 161 | n, _, err := c2.ReadWithContext(ctx, b) 162 | assert.NoError(t, err) 163 | if n > 0 { 164 | b = b[:n] 165 | assert.Equal(t, payload, b) 166 | } 167 | wg.Done() 168 | }() 169 | defer wg.Wait() 170 | 171 | for _, tt := range tests { 172 | t.Run(tt.name, func(t *testing.T) { 173 | err = c1.WriteMulticast(tt.args.ctx, tt.args.udpCtx, 2, tt.args.buffer) 174 | 175 | c1.LocalAddr() 176 | c1.RemoteAddr() 177 | 178 | if tt.wantErr { 179 | assert.Error(t, err) 180 | } else { 181 | assert.NoError(t, err) 182 | } 183 | }) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /tcp/clientobserve_test.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "sync" 8 | "testing" 9 | "time" 10 | 11 | "github.com/matrix-org/go-coap/v2/message" 12 | "github.com/matrix-org/go-coap/v2/message/codes" 13 | coapNet "github.com/matrix-org/go-coap/v2/net" 14 | "github.com/matrix-org/go-coap/v2/tcp/message/pool" 15 | "github.com/stretchr/testify/require" 16 | ) 17 | 18 | func TestClientConn_Observe(t *testing.T) { 19 | type args struct { 20 | path string 21 | payload []byte 22 | numEvents int 23 | } 24 | tests := []struct { 25 | name string 26 | args args 27 | wantErr bool 28 | }{ 29 | /* 30 | { 31 | name: "10bytes", 32 | args: args{ 33 | path: "/tmp", 34 | numEvents: 1, 35 | payload: make([]byte, 10), 36 | }, 37 | }, 38 | */ 39 | { 40 | name: "5000bytes", 41 | args: args{ 42 | path: "/tmp", 43 | numEvents: 20, 44 | payload: make([]byte, 5000), 45 | }, 46 | }, 47 | } 48 | for _, tt := range tests { 49 | t.Run(tt.name, func(t *testing.T) { 50 | l, err := coapNet.NewTCPListener("tcp", "") 51 | require.NoError(t, err) 52 | defer l.Close() 53 | var wg sync.WaitGroup 54 | defer wg.Wait() 55 | 56 | s := NewServer(WithHandlerFunc(func(w *ResponseWriter, r *pool.Message) { 57 | switch r.Code() { 58 | case codes.PUT, codes.POST, codes.DELETE: 59 | w.SetResponse(codes.NotFound, message.TextPlain, nil) 60 | case codes.GET: 61 | default: 62 | return 63 | } 64 | obs, err := r.Observe() 65 | if err != nil { 66 | err := w.SetResponse(codes.Content, message.TextPlain, bytes.NewReader(tt.args.payload)) 67 | require.NoError(t, err) 68 | return 69 | } 70 | require.NoError(t, err) 71 | require.NotNil(t, w.ClientConn()) 72 | token := r.Token() 73 | switch obs { 74 | case 0: 75 | cc := w.ClientConn() 76 | for i := 0; i < tt.args.numEvents; i++ { 77 | tmpPay := make([]byte, len(tt.args.payload)) 78 | copy(tmpPay, tt.args.payload) 79 | if len(tmpPay) > 0 { 80 | if i == tt.args.numEvents-1 { 81 | tmpPay[0] = 0 82 | } else { 83 | tmpPay[0] = byte(i) + 1 84 | } 85 | } 86 | p := bytes.NewReader(tt.args.payload) 87 | etag, err := message.GetETag(p) 88 | require.NoError(t, err) 89 | req := pool.AcquireMessage(cc.Context()) 90 | defer pool.ReleaseMessage(req) 91 | req.SetCode(codes.Content) 92 | req.SetContentFormat(message.TextPlain) 93 | req.SetObserve(uint32(i) + 2) 94 | req.SetBody(p) 95 | req.SetETag(etag) 96 | req.SetToken(token) 97 | err = cc.WriteMessage(req) 98 | require.NoError(t, err) 99 | } 100 | case 1: 101 | err := w.SetResponse(codes.Content, message.TextPlain, bytes.NewReader([]byte("close"))) 102 | require.NoError(t, err) 103 | default: 104 | err := w.SetResponse(codes.BadRequest, message.TextPlain, nil) 105 | require.NoError(t, err) 106 | } 107 | })) 108 | defer s.Stop() 109 | 110 | wg.Add(1) 111 | go func() { 112 | defer wg.Done() 113 | err := s.Serve(l) 114 | require.NoError(t, err) 115 | }() 116 | 117 | cc, err := Dial(l.Addr().String()) 118 | require.NoError(t, err) 119 | defer cc.Close() 120 | 121 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*3600) 122 | defer cancel() 123 | obs := &observer{ 124 | t: t, 125 | done: make(chan bool, 1), 126 | numEvents: tt.args.numEvents, 127 | } 128 | got, err := cc.Observe(ctx, tt.args.path, obs.observe) 129 | if tt.wantErr { 130 | require.Error(t, err) 131 | return 132 | } 133 | require.NoError(t, err) 134 | <-obs.done 135 | err = got.Cancel(ctx) 136 | }) 137 | } 138 | } 139 | 140 | /* 141 | func TestClientConn_ObserveIotivityLite(t *testing.T) { 142 | cc, err := Dial("10.112.112.10:60956") 143 | require.NoError(t, err) 144 | defer cc.Close() 145 | 146 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) 147 | defer cancel() 148 | obs, err := cc.Observe(ctx, "/light/2", func(cc *ClientConn, req *pool.Message) { 149 | fmt.Printf("observe %+v\n", req) 150 | }) 151 | require.NoError(t, err) 152 | fmt.Printf("Running\n") 153 | time.Sleep(time.Second * 3600) 154 | 155 | defer obs.Cancel(context.Background()) 156 | } 157 | */ 158 | 159 | type observer struct { 160 | t require.TestingT 161 | msgs []*pool.Message 162 | sync.Mutex 163 | done chan bool 164 | numEvents int 165 | } 166 | 167 | func (o *observer) observe(req *pool.Message) { 168 | req.Hijack() 169 | o.Lock() 170 | defer o.Unlock() 171 | o.msgs = append(o.msgs, req) 172 | obs, err := req.Observe() 173 | require.NoError(o.t, err) 174 | fmt.Printf("OBS %v\n", obs) 175 | if obs > uint32(o.numEvents) { 176 | select { 177 | case o.done <- true: 178 | default: 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /mux/router.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "sync" 7 | 8 | "github.com/matrix-org/go-coap/v2/message" 9 | "github.com/matrix-org/go-coap/v2/message/codes" 10 | ) 11 | 12 | type ResponseWriter = interface { 13 | SetResponse(code codes.Code, contentFormat message.MediaType, d io.ReadSeeker, opts ...message.Option) error 14 | Client() Client 15 | } 16 | 17 | type Handler interface { 18 | ServeCOAP(w ResponseWriter, r *Message) 19 | } 20 | 21 | // The HandlerFunc type is an adapter to allow the use of 22 | // ordinary functions as COAP handlers. If f is a function 23 | // with the appropriate signature, HandlerFunc(f) is a 24 | // Handler object that calls f. 25 | type HandlerFunc func(w ResponseWriter, r *Message) 26 | 27 | // ServeCOAP calls f(w, r). 28 | func (f HandlerFunc) ServeCOAP(w ResponseWriter, r *Message) { 29 | f(w, r) 30 | } 31 | 32 | // Router is an COAP request multiplexer. It matches the 33 | // path name of each incoming request against a list of 34 | // registered patterns add calls the handler for the pattern 35 | // with same name. 36 | // Router is also safe for concurrent access from multiple goroutines. 37 | type Router struct { 38 | z map[string]muxEntry 39 | m *sync.RWMutex 40 | defaultHandler Handler 41 | middlewares []MiddlewareFunc 42 | } 43 | 44 | type muxEntry struct { 45 | h Handler 46 | pattern string 47 | } 48 | 49 | // NewRouter allocates and returns a new Router. 50 | func NewRouter() *Router { 51 | return &Router{ 52 | z: make(map[string]muxEntry), 53 | m: new(sync.RWMutex), 54 | middlewares: make([]MiddlewareFunc, 0, 2), 55 | defaultHandler: HandlerFunc(func(w ResponseWriter, r *Message) { 56 | w.SetResponse(codes.NotFound, message.TextPlain, nil) 57 | }), 58 | } 59 | } 60 | 61 | // Does path match pattern? 62 | func pathMatch(pattern, path string) bool { 63 | switch pattern { 64 | case "", "/": 65 | switch path { 66 | case "", "/": 67 | return true 68 | } 69 | return false 70 | default: 71 | n := len(pattern) 72 | if pattern[n-1] != '/' { 73 | return pattern == path 74 | } 75 | return len(path) >= n && path[0:n] == pattern 76 | } 77 | } 78 | 79 | // Find a handler on a handler map given a path string 80 | // Most-specific (longest) pattern wins 81 | func (r *Router) match(path string) (h Handler, pattern string) { 82 | r.m.RLock() 83 | defer r.m.RUnlock() 84 | var n = 0 85 | for k, v := range r.z { 86 | if !pathMatch(k, path) { 87 | continue 88 | } 89 | if h == nil || len(k) > n { 90 | n = len(k) 91 | h = v.h 92 | pattern = v.pattern 93 | } 94 | } 95 | return 96 | } 97 | 98 | // Handle adds a handler to the Router for pattern. 99 | func (r *Router) Handle(pattern string, handler Handler) error { 100 | switch pattern { 101 | case "", "/": 102 | pattern = "/" 103 | default: 104 | if pattern[0] == '/' { 105 | pattern = pattern[1:] 106 | } 107 | } 108 | 109 | if handler == nil { 110 | return errors.New("nil handler") 111 | } 112 | 113 | r.m.Lock() 114 | r.z[pattern] = muxEntry{h: handler, pattern: pattern} 115 | r.m.Unlock() 116 | return nil 117 | } 118 | 119 | // DefaultHandle set default handler to the Router 120 | func (r *Router) DefaultHandle(handler Handler) { 121 | r.m.Lock() 122 | r.defaultHandler = handler 123 | r.m.Unlock() 124 | } 125 | 126 | // HandleFunc adds a handler function to the Router for pattern. 127 | func (r *Router) HandleFunc(pattern string, handler func(w ResponseWriter, r *Message)) { 128 | r.Handle(pattern, HandlerFunc(handler)) 129 | } 130 | 131 | // DefaultHandleFunc set a default handler function to the Router. 132 | func (r *Router) DefaultHandleFunc(handler func(w ResponseWriter, r *Message)) { 133 | r.DefaultHandle(HandlerFunc(handler)) 134 | } 135 | 136 | // HandleRemove deregistrars the handler specific for pattern from the Router. 137 | func (r *Router) HandleRemove(pattern string) error { 138 | switch pattern { 139 | case "", "/": 140 | pattern = "/" 141 | } 142 | r.m.Lock() 143 | defer r.m.Unlock() 144 | if _, ok := r.z[pattern]; ok { 145 | delete(r.z, pattern) 146 | return nil 147 | } 148 | return errors.New("pattern is not registered in") 149 | } 150 | 151 | // ServeCOAP dispatches the request to the handler whose 152 | // pattern most closely matches the request message. If DefaultServeMux 153 | // is used the correct thing for DS queries is done: a possible parent 154 | // is sought. 155 | // If no handler is found a standard NotFound message is returned 156 | func (r *Router) ServeCOAP(w ResponseWriter, req *Message) { 157 | path, err := req.Options.Path() 158 | if err != nil { 159 | r.defaultHandler.ServeCOAP(w, req) 160 | return 161 | } 162 | h, _ := r.match(path) 163 | if h == nil { 164 | h = r.defaultHandler 165 | } 166 | if h == nil { 167 | return 168 | } 169 | for i := len(r.middlewares) - 1; i >= 0; i-- { 170 | h = r.middlewares[i].Middleware(h) 171 | } 172 | h.ServeCOAP(w, req) 173 | } 174 | -------------------------------------------------------------------------------- /udp/client/clientobserve_test.go: -------------------------------------------------------------------------------- 1 | package client_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "sync" 7 | "testing" 8 | "time" 9 | 10 | "github.com/matrix-org/go-coap/v2/message" 11 | "github.com/matrix-org/go-coap/v2/message/codes" 12 | coapNet "github.com/matrix-org/go-coap/v2/net" 13 | "github.com/matrix-org/go-coap/v2/udp" 14 | "github.com/matrix-org/go-coap/v2/udp/client" 15 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 16 | "github.com/stretchr/testify/require" 17 | ) 18 | 19 | func TestClientConn_Observe(t *testing.T) { 20 | type args struct { 21 | path string 22 | payload []byte 23 | numEvents int 24 | } 25 | tests := []struct { 26 | name string 27 | args args 28 | wantErr bool 29 | }{ 30 | { 31 | name: "10bytes", 32 | args: args{ 33 | path: "/tmp", 34 | numEvents: 1, 35 | payload: make([]byte, 10), 36 | }, 37 | }, 38 | { 39 | name: "5000bytes", 40 | args: args{ 41 | path: "/tmp", 42 | numEvents: 20, 43 | payload: make([]byte, 5000), 44 | }, 45 | }, 46 | } 47 | for _, tt := range tests { 48 | t.Run(tt.name, func(t *testing.T) { 49 | l, err := coapNet.NewListenUDP("udp", "") 50 | require.NoError(t, err) 51 | defer l.Close() 52 | var wg sync.WaitGroup 53 | defer wg.Wait() 54 | 55 | s := udp.NewServer(udp.WithHandlerFunc(func(w *client.ResponseWriter, r *pool.Message) { 56 | switch r.Code() { 57 | case codes.PUT, codes.POST, codes.DELETE: 58 | w.SetResponse(codes.NotFound, message.TextPlain, nil) 59 | case codes.GET: 60 | default: 61 | return 62 | } 63 | obs, err := r.Observe() 64 | if err != nil { 65 | err := w.SetResponse(codes.Content, message.TextPlain, bytes.NewReader(tt.args.payload)) 66 | require.NoError(t, err) 67 | return 68 | } 69 | require.NoError(t, err) 70 | require.NotNil(t, w.ClientConn()) 71 | token := r.Token() 72 | switch obs { 73 | case 0: 74 | cc := w.ClientConn() 75 | for i := 0; i < tt.args.numEvents; i++ { 76 | tmpPay := make([]byte, len(tt.args.payload)) 77 | copy(tmpPay, tt.args.payload) 78 | if len(tmpPay) > 0 { 79 | if i == tt.args.numEvents-1 { 80 | tmpPay[0] = 0 81 | } else { 82 | tmpPay[0] = byte(i) + 1 83 | } 84 | } 85 | p := bytes.NewReader(tt.args.payload) 86 | etag, err := message.GetETag(p) 87 | require.NoError(t, err) 88 | req := pool.AcquireMessage(cc.Context()) 89 | defer pool.ReleaseMessage(req) 90 | req.SetCode(codes.Content) 91 | req.SetContentFormat(message.TextPlain) 92 | req.SetObserve(uint32(i) + 2) 93 | req.SetBody(p) 94 | req.SetETag(etag) 95 | req.SetToken(token) 96 | err = cc.WriteMessage(req) 97 | require.NoError(t, err) 98 | } 99 | case 1: 100 | err := w.SetResponse(codes.Content, message.TextPlain, bytes.NewReader([]byte("close"))) 101 | require.NoError(t, err) 102 | default: 103 | err := w.SetResponse(codes.BadRequest, message.TextPlain, nil) 104 | require.NoError(t, err) 105 | } 106 | })) 107 | defer s.Stop() 108 | 109 | wg.Add(1) 110 | go func() { 111 | defer wg.Done() 112 | err := s.Serve(l) 113 | require.NoError(t, err) 114 | }() 115 | 116 | cc, err := udp.Dial(l.LocalAddr().String()) 117 | require.NoError(t, err) 118 | defer cc.Close() 119 | 120 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 121 | defer cancel() 122 | obs := &observer{ 123 | t: t, 124 | done: make(chan bool, 1), 125 | numEvents: tt.args.numEvents, 126 | } 127 | got, err := cc.Observe(ctx, tt.args.path, obs.observe) 128 | if tt.wantErr { 129 | require.Error(t, err) 130 | return 131 | } 132 | require.NoError(t, err) 133 | <-obs.done 134 | err = got.Cancel(ctx) 135 | }) 136 | } 137 | } 138 | 139 | /* 140 | func TestClientConn_ObserveIotivityLite(t *testing.T) { 141 | cc, err := Dial("10.112.112.10:60956") 142 | require.NoError(t, err) 143 | defer cc.Close() 144 | 145 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) 146 | defer cancel() 147 | obs, err := cc.Observe(ctx, "/light/2", func(cc *ClientConn, req *pool.Message) { 148 | fmt.Printf("observe %+v\n", req) 149 | }) 150 | require.NoError(t, err) 151 | fmt.Printf("Running\n") 152 | time.Sleep(time.Second * 3600) 153 | 154 | defer obs.Cancel(context.Background()) 155 | } 156 | */ 157 | 158 | type observer struct { 159 | t require.TestingT 160 | msgs []*pool.Message 161 | sync.Mutex 162 | done chan bool 163 | numEvents int 164 | } 165 | 166 | func (o *observer) observe(req *pool.Message) { 167 | req.Hijack() 168 | o.Lock() 169 | defer o.Unlock() 170 | o.msgs = append(o.msgs, req) 171 | obs, err := req.Observe() 172 | require.NoError(o.t, err) 173 | if obs > uint32(o.numEvents) { 174 | select { 175 | case o.done <- true: 176 | default: 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /net/dtlslistener.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "sync" 8 | "sync/atomic" 9 | "time" 10 | 11 | dtls "github.com/pion/dtls/v2" 12 | ) 13 | 14 | type connData struct { 15 | conn net.Conn 16 | err error 17 | } 18 | 19 | // DTLSListener is a DTLS listener that provides accept with context. 20 | type DTLSListener struct { 21 | listener net.Listener 22 | heartBeat time.Duration 23 | wg sync.WaitGroup 24 | doneCh chan struct{} 25 | connCh chan connData 26 | onTimeout func() error 27 | 28 | cancel context.CancelFunc 29 | mutex sync.Mutex 30 | 31 | closed uint32 32 | deadline atomic.Value 33 | } 34 | 35 | func (l *DTLSListener) acceptLoop() { 36 | defer l.wg.Done() 37 | for { 38 | conn, err := l.listener.Accept() 39 | if err != nil { 40 | select { 41 | case <-l.doneCh: 42 | return 43 | case l.connCh <- connData{conn: conn, err: err}: 44 | } 45 | } else { 46 | select { 47 | case l.connCh <- connData{conn: conn, err: err}: 48 | case <-l.doneCh: 49 | return 50 | } 51 | } 52 | } 53 | } 54 | 55 | var defaultDTLSListenerOptions = dtlsListenerOptions{ 56 | heartBeat: time.Millisecond * 200, 57 | } 58 | 59 | type dtlsListenerOptions struct { 60 | heartBeat time.Duration 61 | onTimeout func() error 62 | } 63 | 64 | // A DTLSListenerOption sets options such as heartBeat parameters, etc. 65 | type DTLSListenerOption interface { 66 | applyDTLSListener(*dtlsListenerOptions) 67 | } 68 | 69 | // NewDTLSListener creates dtls listener. 70 | // Known networks are "udp", "udp4" (IPv4-only), "udp6" (IPv6-only). 71 | func NewDTLSListener(network string, addr string, dtlsCfg *dtls.Config, opts ...DTLSListenerOption) (*DTLSListener, error) { 72 | cfg := defaultDTLSListenerOptions 73 | for _, o := range opts { 74 | o.applyDTLSListener(&cfg) 75 | } 76 | 77 | a, err := net.ResolveUDPAddr(network, addr) 78 | if err != nil { 79 | return nil, fmt.Errorf("cannot resolve address: %w", err) 80 | } 81 | l := DTLSListener{ 82 | heartBeat: cfg.heartBeat, 83 | connCh: make(chan connData), 84 | doneCh: make(chan struct{}), 85 | } 86 | 87 | connectContextMaker := dtlsCfg.ConnectContextMaker 88 | if connectContextMaker == nil { 89 | connectContextMaker = func() (context.Context, func()) { 90 | return context.WithTimeout(context.Background(), 30*time.Second) 91 | } 92 | } 93 | dtlsCfg.ConnectContextMaker = func() (context.Context, func()) { 94 | ctx, cancel := connectContextMaker() 95 | l.mutex.Lock() 96 | defer l.mutex.Unlock() 97 | if l.closed > 0 { 98 | cancel() 99 | } 100 | l.cancel = cancel 101 | return ctx, cancel 102 | } 103 | 104 | listener, err := dtls.Listen(network, a, dtlsCfg) 105 | if err != nil { 106 | return nil, fmt.Errorf("cannot create new dtls listener: %w", err) 107 | } 108 | l.listener = listener 109 | l.wg.Add(1) 110 | 111 | go l.acceptLoop() 112 | 113 | return &l, nil 114 | } 115 | 116 | // AcceptWithContext waits with context for a generic Conn. 117 | func (l *DTLSListener) AcceptWithContext(ctx context.Context) (net.Conn, error) { 118 | for { 119 | select { 120 | case <-ctx.Done(): 121 | return nil, ctx.Err() 122 | default: 123 | } 124 | if atomic.LoadUint32(&l.closed) == 1 { 125 | return nil, ErrListenerIsClosed 126 | } 127 | deadline := time.Now().Add(l.heartBeat) 128 | err := l.SetDeadline(deadline) 129 | if err != nil { 130 | return nil, fmt.Errorf("cannot set deadline to accept connection: %w", err) 131 | } 132 | rw, err := l.Accept() 133 | if err != nil { 134 | // check context in regular intervals and then resume listening 135 | if isTemporary(err, deadline) { 136 | if l.onTimeout != nil { 137 | err := l.onTimeout() 138 | if err != nil { 139 | return nil, fmt.Errorf("cannot accept connection : on timeout returns error: %w", err) 140 | } 141 | } 142 | continue 143 | } 144 | return nil, fmt.Errorf("cannot accept connection: %w", err) 145 | } 146 | return rw, nil 147 | } 148 | } 149 | 150 | // SetDeadline sets deadline for accept operation. 151 | func (l *DTLSListener) SetDeadline(t time.Time) error { 152 | l.deadline.Store(t) 153 | return nil 154 | } 155 | 156 | // Accept waits for a generic Conn. 157 | func (l *DTLSListener) Accept() (net.Conn, error) { 158 | var deadline time.Time 159 | v := l.deadline.Load() 160 | if v != nil { 161 | deadline = v.(time.Time) 162 | } 163 | 164 | if deadline.IsZero() { 165 | select { 166 | case d := <-l.connCh: 167 | if d.err != nil { 168 | return nil, d.err 169 | } 170 | return d.conn, nil 171 | } 172 | } 173 | 174 | select { 175 | case d := <-l.connCh: 176 | if d.err != nil { 177 | return nil, d.err 178 | } 179 | return d.conn, nil 180 | case <-time.After(deadline.Sub(time.Now())): 181 | return nil, fmt.Errorf(ioTimeout) 182 | } 183 | } 184 | 185 | func (l *DTLSListener) close() (bool, error) { 186 | l.mutex.Lock() 187 | defer l.mutex.Unlock() 188 | if l.closed > 0 { 189 | return false, nil 190 | } 191 | close(l.doneCh) 192 | err := l.listener.Close() 193 | if l.cancel != nil { 194 | l.cancel() 195 | } 196 | return true, err 197 | } 198 | 199 | // Close closes the connection. 200 | func (l *DTLSListener) Close() error { 201 | wait, err := l.close() 202 | if wait { 203 | l.wg.Wait() 204 | } 205 | return err 206 | } 207 | 208 | // Addr represents a network end point address. 209 | func (l *DTLSListener) Addr() net.Addr { 210 | return l.listener.Addr() 211 | } 212 | -------------------------------------------------------------------------------- /udp/message/pool/message.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "io" 8 | "sync" 9 | "sync/atomic" 10 | 11 | "github.com/matrix-org/go-coap/v2/message" 12 | "github.com/matrix-org/go-coap/v2/message/codes" 13 | "github.com/matrix-org/go-coap/v2/message/pool" 14 | udp "github.com/matrix-org/go-coap/v2/udp/message" 15 | ) 16 | 17 | const maxMessagePool = 10240 18 | const maxMessageBufferSize = 2048 19 | 20 | var ( 21 | currentMessagesInPool int32 22 | messagePool sync.Pool 23 | ) 24 | 25 | type Message struct { 26 | *pool.Message 27 | messageID uint16 28 | typ udp.Type 29 | 30 | //local vars 31 | rawData []byte 32 | rawMarshalData []byte 33 | 34 | ctx context.Context 35 | isModified bool 36 | } 37 | 38 | // Reset clear message for next reuse 39 | func (r *Message) Reset() { 40 | r.Message.Reset() 41 | r.messageID = 0 42 | r.typ = udp.NonConfirmable 43 | if cap(r.rawData) > maxMessageBufferSize { 44 | r.rawData = make([]byte, 256) 45 | } 46 | if cap(r.rawMarshalData) > maxMessageBufferSize { 47 | r.rawMarshalData = make([]byte, 256) 48 | } 49 | r.isModified = false 50 | } 51 | 52 | func (r *Message) Context() context.Context { 53 | return r.ctx 54 | } 55 | 56 | func (r *Message) SetMessageID(mid uint16) { 57 | r.messageID = mid 58 | r.isModified = true 59 | } 60 | 61 | func (r *Message) MessageID() uint16 { 62 | return r.messageID 63 | } 64 | 65 | func (r *Message) SetType(typ udp.Type) { 66 | r.typ = typ 67 | r.isModified = true 68 | } 69 | 70 | func (r *Message) Type() udp.Type { 71 | return r.typ 72 | } 73 | 74 | func (r *Message) IsModified() bool { 75 | return r.isModified || r.Message.IsModified() 76 | } 77 | 78 | func (r *Message) SetModified(b bool) { 79 | r.isModified = b 80 | r.Message.SetModified(b) 81 | } 82 | 83 | func (r *Message) Unmarshal(data []byte) (int, error) { 84 | if len(r.rawData) < len(data) { 85 | r.rawData = append(r.rawData, make([]byte, len(data)-len(r.rawData))...) 86 | } 87 | copy(r.rawData, data) 88 | r.rawData = r.rawData[:len(data)] 89 | m := &udp.Message{ 90 | Options: make(message.Options, 0, 16), 91 | } 92 | 93 | n, err := m.Unmarshal(r.rawData) 94 | if err != nil { 95 | return n, err 96 | } 97 | r.Message.SetCode(m.Code) 98 | r.Message.SetToken(m.Token) 99 | r.Message.ResetOptionsTo(m.Options) 100 | r.typ = m.Type 101 | r.messageID = m.MessageID 102 | if len(m.Payload) > 0 { 103 | r.Message.SetBody(bytes.NewReader(m.Payload)) 104 | } 105 | return n, err 106 | } 107 | 108 | func (r *Message) Marshal() ([]byte, error) { 109 | m := udp.Message{ 110 | Code: r.Code(), 111 | Token: r.Message.Token(), 112 | Options: r.Message.Options(), 113 | MessageID: r.messageID, 114 | Type: r.typ, 115 | } 116 | payload, err := r.ReadBody() 117 | if err != nil { 118 | return nil, err 119 | } 120 | m.Payload = payload 121 | size, err := m.Size() 122 | if err != nil { 123 | return nil, err 124 | } 125 | if len(r.rawMarshalData) < size { 126 | r.rawMarshalData = append(r.rawMarshalData, make([]byte, size-len(r.rawMarshalData))...) 127 | } 128 | n, err := m.MarshalTo(r.rawMarshalData) 129 | if err != nil { 130 | return nil, err 131 | } 132 | r.rawMarshalData = r.rawMarshalData[:n] 133 | return r.rawMarshalData, nil 134 | } 135 | 136 | func (r *Message) IsSeparate() bool { 137 | return r.Code() == codes.Empty && r.Token() == nil && r.Type() == udp.Acknowledgement && len(r.Options()) == 0 && r.Body() == nil 138 | } 139 | 140 | func (r *Message) String() string { 141 | return fmt.Sprintf("Type: %v, MID: %v, %s", r.Type(), r.MessageID(), r.Message.String()) 142 | } 143 | 144 | // AcquireMessage returns an empty Message instance from Message pool. 145 | // 146 | // The returned Message instance may be passed to ReleaseMessage when it is 147 | // no longer needed. This allows Message recycling, reduces GC pressure 148 | // and usually improves performance. 149 | func AcquireMessage(ctx context.Context) *Message { 150 | v := messagePool.Get() 151 | if v == nil { 152 | return &Message{ 153 | Message: pool.NewMessage(), 154 | rawData: make([]byte, 256), 155 | rawMarshalData: make([]byte, 256), 156 | ctx: ctx, 157 | } 158 | } 159 | r := v.(*Message) 160 | atomic.AddInt32(¤tMessagesInPool, -1) 161 | r.ctx = ctx 162 | return r 163 | } 164 | 165 | // ReleaseMessage returns req acquired via AcquireMessage to Message pool. 166 | // 167 | // It is forbidden accessing req and/or its' members after returning 168 | // it to Message pool. 169 | func ReleaseMessage(req *Message) { 170 | v := atomic.LoadInt32(¤tMessagesInPool) 171 | if v >= maxMessagePool { 172 | return 173 | } 174 | atomic.AddInt32(¤tMessagesInPool, 1) 175 | req.Reset() 176 | messagePool.Put(req) 177 | } 178 | 179 | // ConvertFrom converts common message to pool message. 180 | func ConvertFrom(m *message.Message) (*Message, error) { 181 | if m.Context == nil { 182 | return nil, fmt.Errorf("invalid context") 183 | } 184 | r := AcquireMessage(m.Context) 185 | r.SetCode(m.Code) 186 | r.ResetOptionsTo(m.Options) 187 | r.SetBody(m.Body) 188 | r.SetToken(m.Token) 189 | return r, nil 190 | } 191 | 192 | // ConvertTo converts pool message to common message. 193 | func ConvertTo(m *Message) (*message.Message, error) { 194 | opts, err := m.Options().Clone() 195 | if err != nil { 196 | return nil, err 197 | } 198 | var body io.ReadSeeker 199 | if m.Body() != nil { 200 | payload, err := m.ReadBody() 201 | if err != nil { 202 | return nil, err 203 | } 204 | body = bytes.NewReader(payload) 205 | } 206 | return &message.Message{ 207 | Context: m.Context(), 208 | Code: m.Code(), 209 | Token: m.Token(), 210 | Body: body, 211 | Options: opts, 212 | }, nil 213 | } 214 | -------------------------------------------------------------------------------- /udp/client.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "time" 8 | 9 | "github.com/matrix-org/go-coap/v2/message" 10 | "github.com/matrix-org/go-coap/v2/net/blockwise" 11 | "github.com/matrix-org/go-coap/v2/net/monitor/inactivity" 12 | "github.com/matrix-org/go-coap/v2/shared" 13 | kitSync "github.com/plgd-dev/kit/sync" 14 | 15 | "github.com/matrix-org/go-coap/v2/message/codes" 16 | coapNet "github.com/matrix-org/go-coap/v2/net" 17 | "github.com/matrix-org/go-coap/v2/udp/client" 18 | udpMessage "github.com/matrix-org/go-coap/v2/udp/message" 19 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 20 | ) 21 | 22 | var defaultDialOptions = dialOptions{ 23 | ctx: context.Background(), 24 | maxMessageSize: 64 * 1024, 25 | heartBeat: time.Millisecond * 100, 26 | handler: func(w *client.ResponseWriter, r *pool.Message) { 27 | switch r.Code() { 28 | case codes.POST, codes.PUT, codes.GET, codes.DELETE: 29 | w.SetResponse(codes.NotFound, message.TextPlain, nil) 30 | } 31 | }, 32 | errors: func(err error) { 33 | fmt.Println(err) 34 | }, 35 | goPool: func(f func()) error { 36 | go func() { 37 | f() 38 | }() 39 | return nil 40 | }, 41 | dialer: &net.Dialer{Timeout: time.Second * 3}, 42 | net: "udp", 43 | blockwiseSZX: blockwise.SZX1024, 44 | blockwiseEnable: true, 45 | blockwiseTransferTimeout: time.Second * 3, 46 | transmissionNStart: time.Second, 47 | transmissionAcknowledgeTimeout: time.Second * 2, 48 | transmissionMaxRetransmit: 4, 49 | getMID: udpMessage.GetMID, 50 | createInactivityMonitor: func() inactivity.Monitor { 51 | return inactivity.NewNilMonitor() 52 | }, 53 | } 54 | 55 | type dialOptions struct { 56 | ctx context.Context 57 | maxMessageSize int 58 | heartBeat time.Duration 59 | handler HandlerFunc 60 | errors ErrorFunc 61 | goPool GoPoolFunc 62 | dialer *net.Dialer 63 | net string 64 | blockwiseSZX blockwise.SZX 65 | blockwiseEnable bool 66 | blockwiseTransferTimeout time.Duration 67 | transmissionNStart time.Duration 68 | transmissionAcknowledgeTimeout time.Duration 69 | transmissionMaxRetransmit int 70 | getMID GetMIDFunc 71 | closeSocket bool 72 | createInactivityMonitor func() inactivity.Monitor 73 | logger shared.Logger 74 | } 75 | 76 | // A DialOption sets options such as credentials, keepalive parameters, etc. 77 | type DialOption interface { 78 | applyDial(*dialOptions) 79 | } 80 | 81 | // Dial creates a client connection to the given target. 82 | func Dial(target string, opts ...DialOption) (*client.ClientConn, error) { 83 | cfg := defaultDialOptions 84 | for _, o := range opts { 85 | o.applyDial(&cfg) 86 | } 87 | 88 | c, err := cfg.dialer.DialContext(cfg.ctx, cfg.net, target) 89 | if err != nil { 90 | return nil, err 91 | } 92 | conn, ok := c.(*net.UDPConn) 93 | if !ok { 94 | return nil, fmt.Errorf("unsupported connection type: %T", c) 95 | } 96 | opts = append(opts, WithCloseSocket()) 97 | return Client(conn, opts...), nil 98 | } 99 | 100 | func bwAcquireMessage(ctx context.Context) blockwise.Message { 101 | return pool.AcquireMessage(ctx) 102 | } 103 | 104 | func bwReleaseMessage(m blockwise.Message) { 105 | pool.ReleaseMessage(m.(*pool.Message)) 106 | } 107 | 108 | func bwCreateHandlerFunc(observatioRequests *kitSync.Map) func(token message.Token) (blockwise.Message, bool) { 109 | return func(token message.Token) (blockwise.Message, bool) { 110 | msg, ok := observatioRequests.LoadWithFunc(token.String(), func(v interface{}) interface{} { 111 | r := v.(*pool.Message) 112 | d := pool.AcquireMessage(r.Context()) 113 | d.ResetOptionsTo(r.Options()) 114 | d.SetCode(r.Code()) 115 | d.SetToken(r.Token()) 116 | d.SetMessageID(r.MessageID()) 117 | return d 118 | }) 119 | if !ok { 120 | return nil, ok 121 | } 122 | bwMessage := msg.(blockwise.Message) 123 | return bwMessage, ok 124 | } 125 | } 126 | 127 | // Client creates client over udp connection. 128 | func Client(conn *net.UDPConn, opts ...DialOption) *client.ClientConn { 129 | cfg := defaultDialOptions 130 | for _, o := range opts { 131 | o.applyDial(&cfg) 132 | } 133 | if cfg.errors == nil { 134 | cfg.errors = func(error) {} 135 | } 136 | if cfg.createInactivityMonitor == nil { 137 | cfg.createInactivityMonitor = func() inactivity.Monitor { 138 | return inactivity.NewNilMonitor() 139 | } 140 | } 141 | 142 | errors := cfg.errors 143 | cfg.errors = func(err error) { 144 | errors(fmt.Errorf("udp: %v: %w", conn.RemoteAddr(), err)) 145 | } 146 | 147 | addr, _ := conn.RemoteAddr().(*net.UDPAddr) 148 | observatioRequests := kitSync.NewMap() 149 | var blockWise *blockwise.BlockWise 150 | if cfg.blockwiseEnable { 151 | blockWise = blockwise.NewBlockWise( 152 | bwAcquireMessage, 153 | bwReleaseMessage, 154 | cfg.blockwiseTransferTimeout, 155 | cfg.errors, 156 | false, 157 | bwCreateHandlerFunc(observatioRequests), 158 | cfg.logger, 159 | ) 160 | } 161 | 162 | observationTokenHandler := client.NewHandlerContainer() 163 | monitor := cfg.createInactivityMonitor() 164 | var cc *client.ClientConn 165 | l := coapNet.NewUDPConn(cfg.net, conn, coapNet.WithHeartBeat(cfg.heartBeat), coapNet.WithErrors(cfg.errors), coapNet.WithOnReadTimeout(func() error { 166 | monitor.CheckInactivity(cc) 167 | return nil 168 | })) 169 | session := NewSession(cfg.ctx, 170 | l, 171 | addr, 172 | cfg.maxMessageSize, 173 | cfg.closeSocket, 174 | ) 175 | cc = client.NewClientConn(session, 176 | observationTokenHandler, observatioRequests, cfg.transmissionNStart, cfg.transmissionAcknowledgeTimeout, cfg.transmissionMaxRetransmit, 177 | client.NewObservationHandler(observationTokenHandler, cfg.handler), 178 | cfg.blockwiseSZX, 179 | blockWise, 180 | cfg.goPool, 181 | cfg.errors, 182 | cfg.getMID, 183 | monitor, 184 | cfg.logger, 185 | ) 186 | 187 | go func() { 188 | err := cc.Run() 189 | if err != nil { 190 | cfg.errors(err) 191 | } 192 | }() 193 | 194 | return cc 195 | } 196 | -------------------------------------------------------------------------------- /net/conn.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "net" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | // Conn is a generic stream-oriented network connection that provides Read/Write with context. 13 | // 14 | // Multiple goroutines may invoke methods on a Conn simultaneously. 15 | type Conn struct { 16 | heartBeat time.Duration 17 | connection net.Conn 18 | onReadTimeout func() error 19 | onWriteTimeout func() error 20 | 21 | handshake func() error 22 | readBuffer *bufio.Reader 23 | lock sync.Mutex 24 | } 25 | 26 | var defaultConnOptions = connOptions{ 27 | heartBeat: time.Millisecond * 200, 28 | } 29 | 30 | type connOptions struct { 31 | heartBeat time.Duration 32 | onReadTimeout func() error 33 | onWriteTimeout func() error 34 | } 35 | 36 | // A ConnOption sets options such as heartBeat, errors parameters, etc. 37 | type ConnOption interface { 38 | applyConn(*connOptions) 39 | } 40 | 41 | // NewConn creates connection over net.Conn. 42 | func NewConn(c net.Conn, opts ...ConnOption) *Conn { 43 | cfg := defaultConnOptions 44 | for _, o := range opts { 45 | o.applyConn(&cfg) 46 | } 47 | connection := Conn{ 48 | connection: c, 49 | heartBeat: cfg.heartBeat, 50 | readBuffer: bufio.NewReaderSize(c, 2048), 51 | onReadTimeout: cfg.onReadTimeout, 52 | onWriteTimeout: cfg.onWriteTimeout, 53 | } 54 | if v, ok := c.(interface{ Handshake() error }); ok { 55 | connection.handshake = v.Handshake 56 | } 57 | 58 | return &connection 59 | } 60 | 61 | // LocalAddr returns the local network address. The Addr returned is shared by all invocations of LocalAddr, so do not modify it. 62 | func (c *Conn) LocalAddr() net.Addr { 63 | return c.connection.LocalAddr() 64 | } 65 | 66 | // Connection returns the network connection. The Conn returned is shared by all invocations of Connection, so do not modify it. 67 | func (c *Conn) Connection() net.Conn { 68 | return c.connection 69 | } 70 | 71 | // RemoteAddr returns the remote network address. The Addr returned is shared by all invocations of RemoteAddr, so do not modify it. 72 | func (c *Conn) RemoteAddr() net.Addr { 73 | return c.connection.RemoteAddr() 74 | } 75 | 76 | // Close closes the connection. 77 | func (c *Conn) Close() error { 78 | return c.connection.Close() 79 | } 80 | 81 | // WriteWithContext writes data with context. 82 | func (c *Conn) WriteWithContext(ctx context.Context, data []byte) error { 83 | written := 0 84 | c.lock.Lock() 85 | defer c.lock.Unlock() 86 | for written < len(data) { 87 | select { 88 | case <-ctx.Done(): 89 | return ctx.Err() 90 | default: 91 | } 92 | err := c.doHandshakeLocked(ctx, c.onWriteTimeout) 93 | if err != nil { 94 | return fmt.Errorf("cannot TLS handshake: %w", err) 95 | } 96 | deadline := time.Now().Add(c.heartBeat) 97 | err = c.connection.SetWriteDeadline(deadline) 98 | if err != nil { 99 | return fmt.Errorf("cannot set write deadline for connection: %w", err) 100 | } 101 | n, err := c.connection.Write(data[written:]) 102 | 103 | if err != nil { 104 | if isTemporary(err, deadline) { 105 | if n > 0 { 106 | written += n 107 | } 108 | if c.onWriteTimeout != nil { 109 | err := c.onWriteTimeout() 110 | if err != nil { 111 | return fmt.Errorf("cannot write to connection: on timeout returns error: %w", err) 112 | } 113 | } 114 | continue 115 | } 116 | return err 117 | } 118 | written += n 119 | } 120 | return nil 121 | } 122 | 123 | // ReadFullWithContext reads stream with context until whole buffer is satisfied. 124 | func (c *Conn) ReadFullWithContext(ctx context.Context, buffer []byte) error { 125 | offset := 0 126 | for offset < len(buffer) { 127 | n, err := c.ReadWithContext(ctx, buffer[offset:]) 128 | if err != nil { 129 | return fmt.Errorf("cannot read full from connection: %w", err) 130 | } 131 | offset += n 132 | } 133 | return nil 134 | } 135 | 136 | // During handshake wee need to use setDeadline because https://github.com/golang/go/issues/31224 137 | // added comment in https://github.com/golang/go/commit/c9b9cd73bb7a7828d34f4a7844f16c3fbc0674dd 138 | func (c *Conn) doHandshakeLocked(ctx context.Context, onTimeout func() error) error { 139 | if c.handshake == nil { 140 | return nil 141 | } 142 | for { 143 | select { 144 | case <-ctx.Done(): 145 | if ctx.Err() != nil { 146 | return ctx.Err() 147 | } 148 | return fmt.Errorf("handshake failed") 149 | default: 150 | } 151 | deadline := time.Now().Add(c.heartBeat) 152 | err := c.connection.SetDeadline(deadline) 153 | if err != nil { 154 | return fmt.Errorf("cannot set deadline for handshake: %w", err) 155 | } 156 | err = c.handshake() 157 | if err != nil { 158 | if isTemporary(err, deadline) { 159 | if onTimeout != nil { 160 | err := onTimeout() 161 | if err != nil { 162 | return fmt.Errorf("on timeout returns error: %w", err) 163 | } 164 | } 165 | continue 166 | } 167 | } 168 | return err 169 | } 170 | } 171 | 172 | func (c *Conn) doHandshake(ctx context.Context, onTimeout func() error) error { 173 | if c.handshake == nil { 174 | return nil 175 | } 176 | c.lock.Lock() 177 | defer c.lock.Unlock() 178 | return c.doHandshakeLocked(ctx, onTimeout) 179 | } 180 | 181 | // ReadWithContext reads stream with context. 182 | func (c *Conn) ReadWithContext(ctx context.Context, buffer []byte) (int, error) { 183 | for { 184 | select { 185 | case <-ctx.Done(): 186 | if ctx.Err() != nil { 187 | return -1, ctx.Err() 188 | } 189 | return -1, fmt.Errorf("cannot read from connection") 190 | default: 191 | } 192 | err := c.doHandshake(ctx, c.onReadTimeout) 193 | if err != nil { 194 | return -1, fmt.Errorf("cannot TLS handshake: %w", err) 195 | } 196 | deadline := time.Now().Add(c.heartBeat) 197 | err = c.connection.SetReadDeadline(deadline) 198 | if err != nil { 199 | return -1, fmt.Errorf("cannot set read deadline for connection: %w", err) 200 | } 201 | n, err := c.readBuffer.Read(buffer) 202 | if err != nil { 203 | if isTemporary(err, deadline) { 204 | if c.onReadTimeout != nil { 205 | err := c.onReadTimeout() 206 | if err != nil { 207 | return -1, fmt.Errorf("cannot read from connection: on timeout returns error: %w", err) 208 | } 209 | } 210 | continue 211 | } 212 | return -1, err 213 | } 214 | return n, err 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /dtls/client.go: -------------------------------------------------------------------------------- 1 | package dtls 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "time" 8 | 9 | "github.com/matrix-org/go-coap/v2/message" 10 | "github.com/matrix-org/go-coap/v2/net/blockwise" 11 | "github.com/matrix-org/go-coap/v2/net/monitor/inactivity" 12 | "github.com/matrix-org/go-coap/v2/shared" 13 | "github.com/pion/dtls/v2" 14 | 15 | "github.com/matrix-org/go-coap/v2/message/codes" 16 | coapNet "github.com/matrix-org/go-coap/v2/net" 17 | "github.com/matrix-org/go-coap/v2/udp/client" 18 | udpMessage "github.com/matrix-org/go-coap/v2/udp/message" 19 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 20 | kitSync "github.com/plgd-dev/kit/sync" 21 | ) 22 | 23 | var defaultDialOptions = dialOptions{ 24 | ctx: context.Background(), 25 | maxMessageSize: 64 * 1024, 26 | heartBeat: time.Millisecond * 100, 27 | handler: func(w *client.ResponseWriter, r *pool.Message) { 28 | switch r.Code() { 29 | case codes.POST, codes.PUT, codes.GET, codes.DELETE: 30 | w.SetResponse(codes.NotFound, message.TextPlain, nil) 31 | } 32 | }, 33 | errors: func(err error) { 34 | fmt.Println(err) 35 | }, 36 | goPool: func(f func()) error { 37 | go func() { 38 | f() 39 | }() 40 | return nil 41 | }, 42 | dialer: &net.Dialer{Timeout: time.Second * 3}, 43 | net: "udp", 44 | blockwiseSZX: blockwise.SZX1024, 45 | blockwiseEnable: true, 46 | blockwiseTransferTimeout: time.Second * 5, 47 | transmissionNStart: time.Second, 48 | transmissionAcknowledgeTimeout: time.Second * 2, 49 | transmissionMaxRetransmit: 4, 50 | getMID: udpMessage.GetMID, 51 | createInactivityMonitor: func() inactivity.Monitor { 52 | return inactivity.NewNilMonitor() 53 | }, 54 | } 55 | 56 | type dialOptions struct { 57 | ctx context.Context 58 | maxMessageSize int 59 | heartBeat time.Duration 60 | handler HandlerFunc 61 | errors ErrorFunc 62 | goPool GoPoolFunc 63 | dialer *net.Dialer 64 | net string 65 | blockwiseSZX blockwise.SZX 66 | blockwiseEnable bool 67 | blockwiseTransferTimeout time.Duration 68 | transmissionNStart time.Duration 69 | transmissionAcknowledgeTimeout time.Duration 70 | transmissionMaxRetransmit int 71 | getMID GetMIDFunc 72 | closeSocket bool 73 | createInactivityMonitor func() inactivity.Monitor 74 | logger shared.Logger 75 | } 76 | 77 | // A DialOption sets options such as credentials, keepalive parameters, etc. 78 | type DialOption interface { 79 | applyDial(*dialOptions) 80 | } 81 | 82 | // Dial creates a client connection to the given target. 83 | func Dial(target string, dtlsCfg *dtls.Config, opts ...DialOption) (*client.ClientConn, error) { 84 | cfg := defaultDialOptions 85 | for _, o := range opts { 86 | o.applyDial(&cfg) 87 | } 88 | 89 | c, err := cfg.dialer.DialContext(cfg.ctx, cfg.net, target) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | conn, err := dtls.Client(c, dtlsCfg) 95 | if err != nil { 96 | return nil, err 97 | } 98 | opts = append(opts, WithCloseSocket()) 99 | return Client(conn, opts...), nil 100 | } 101 | 102 | func bwAcquireMessage(ctx context.Context) blockwise.Message { 103 | msg := pool.AcquireMessage(ctx) 104 | msg.SetType(udpMessage.Confirmable) 105 | return msg 106 | } 107 | 108 | func bwReleaseMessage(m blockwise.Message) { 109 | pool.ReleaseMessage(m.(*pool.Message)) 110 | } 111 | 112 | func bwCreateHandlerFunc(observatioRequests *kitSync.Map) func(token message.Token) (blockwise.Message, bool) { 113 | return func(token message.Token) (blockwise.Message, bool) { 114 | msg, ok := observatioRequests.LoadWithFunc(token.String(), func(v interface{}) interface{} { 115 | r := v.(*pool.Message) 116 | d := pool.AcquireMessage(r.Context()) 117 | d.ResetOptionsTo(r.Options()) 118 | d.SetCode(r.Code()) 119 | d.SetToken(r.Token()) 120 | d.SetMessageID(r.MessageID()) 121 | return d 122 | }) 123 | if !ok { 124 | return nil, ok 125 | } 126 | bwMessage := msg.(blockwise.Message) 127 | return bwMessage, ok 128 | } 129 | } 130 | 131 | // Client creates client over dtls connection. 132 | func Client(conn *dtls.Conn, opts ...DialOption) *client.ClientConn { 133 | cfg := defaultDialOptions 134 | for _, o := range opts { 135 | o.applyDial(&cfg) 136 | } 137 | if cfg.errors == nil { 138 | cfg.errors = func(error) {} 139 | } 140 | if cfg.createInactivityMonitor == nil { 141 | cfg.createInactivityMonitor = func() inactivity.Monitor { 142 | return inactivity.NewNilMonitor() 143 | } 144 | } 145 | errors := cfg.errors 146 | cfg.errors = func(err error) { 147 | errors(fmt.Errorf("dtls: %v: %w", conn.RemoteAddr(), err)) 148 | } 149 | 150 | observatioRequests := kitSync.NewMap() 151 | var blockWise *blockwise.BlockWise 152 | if cfg.blockwiseEnable { 153 | blockWise = blockwise.NewBlockWise( 154 | bwAcquireMessage, 155 | bwReleaseMessage, 156 | cfg.blockwiseTransferTimeout, 157 | cfg.errors, 158 | false, 159 | bwCreateHandlerFunc(observatioRequests), 160 | cfg.logger, 161 | ) 162 | } 163 | 164 | observationTokenHandler := client.NewHandlerContainer() 165 | monitor := cfg.createInactivityMonitor() 166 | var cc *client.ClientConn 167 | l := coapNet.NewConn(conn, coapNet.WithHeartBeat(cfg.heartBeat), coapNet.WithOnReadTimeout(func() error { 168 | monitor.CheckInactivity(cc) 169 | return nil 170 | })) 171 | session := NewSession(cfg.ctx, 172 | l, 173 | cfg.maxMessageSize, 174 | cfg.closeSocket, 175 | ) 176 | cc = client.NewClientConn(session, 177 | observationTokenHandler, observatioRequests, cfg.transmissionNStart, cfg.transmissionAcknowledgeTimeout, cfg.transmissionMaxRetransmit, 178 | client.NewObservationHandler(observationTokenHandler, cfg.handler), 179 | cfg.blockwiseSZX, 180 | blockWise, 181 | cfg.goPool, 182 | cfg.errors, 183 | cfg.getMID, 184 | // The client does not support activity monitoring yet 185 | monitor, 186 | cfg.logger, 187 | ) 188 | 189 | go func() { 190 | err := cc.Run() 191 | if err != nil { 192 | cfg.errors(err) 193 | } 194 | }() 195 | 196 | return cc 197 | } 198 | -------------------------------------------------------------------------------- /tcp/server_test.go: -------------------------------------------------------------------------------- 1 | package tcp_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "math/big" 9 | "sync" 10 | "testing" 11 | "time" 12 | 13 | "github.com/matrix-org/go-coap/v2/examples/dtls/pki" 14 | "github.com/matrix-org/go-coap/v2/message" 15 | "github.com/matrix-org/go-coap/v2/message/codes" 16 | coapNet "github.com/matrix-org/go-coap/v2/net" 17 | "github.com/matrix-org/go-coap/v2/net/monitor/inactivity" 18 | "github.com/matrix-org/go-coap/v2/tcp" 19 | "github.com/matrix-org/go-coap/v2/tcp/message/pool" 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestServer_CleanUpConns(t *testing.T) { 24 | ld, err := coapNet.NewTCPListener("tcp4", "") 25 | require.NoError(t, err) 26 | defer ld.Close() 27 | 28 | var checkCloseWg sync.WaitGroup 29 | defer checkCloseWg.Wait() 30 | sd := tcp.NewServer(tcp.WithOnNewClientConn(func(cc *tcp.ClientConn, tlsconn *tls.Conn) { 31 | require.Nil(t, tlsconn) // tcp without tls 32 | 33 | checkCloseWg.Add(1) 34 | cc.AddOnClose(func() { 35 | checkCloseWg.Done() 36 | }) 37 | })) 38 | defer sd.Stop() 39 | 40 | var wg sync.WaitGroup 41 | wg.Add(1) 42 | go func() { 43 | defer wg.Done() 44 | err := sd.Serve(ld) 45 | require.NoError(t, err) 46 | }() 47 | 48 | cc, err := tcp.Dial(ld.Addr().String()) 49 | require.NoError(t, err) 50 | checkCloseWg.Add(1) 51 | cc.AddOnClose(func() { 52 | checkCloseWg.Done() 53 | }) 54 | defer cc.Close() 55 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 56 | defer cancel() 57 | err = cc.Ping(ctx) 58 | require.NoError(t, err) 59 | } 60 | 61 | func createTLSConfig(ctx context.Context) (serverConfig *tls.Config, clientConfig *tls.Config, clientSerial *big.Int, err error) { 62 | // root cert 63 | ca, rootBytes, _, caPriv, err := pki.GenerateCA() 64 | if err != nil { 65 | return 66 | } 67 | // server cert 68 | certBytes, keyBytes, err := pki.GenerateCertificate(ca, caPriv, "server@test.com") 69 | if err != nil { 70 | return 71 | } 72 | certificate, err := pki.LoadKeyAndCertificate(keyBytes, certBytes) 73 | if err != nil { 74 | return 75 | } 76 | // cert pool 77 | certPool, err := pki.LoadCertPool(rootBytes) 78 | if err != nil { 79 | return 80 | } 81 | 82 | serverConfig = &tls.Config{ 83 | Certificates: []tls.Certificate{*certificate}, 84 | ClientAuth: tls.RequireAndVerifyClientCert, 85 | ClientCAs: certPool, 86 | } 87 | 88 | // client cert 89 | certBytes, keyBytes, err = pki.GenerateCertificate(ca, caPriv, "client@test.com") 90 | if err != nil { 91 | return 92 | } 93 | certificate, err = pki.LoadKeyAndCertificate(keyBytes, certBytes) 94 | if err != nil { 95 | return 96 | } 97 | clientInfo, err := x509.ParseCertificate(certificate.Certificate[0]) 98 | if err != nil { 99 | return 100 | } 101 | clientSerial = clientInfo.SerialNumber 102 | 103 | clientConfig = &tls.Config{ 104 | Certificates: []tls.Certificate{*certificate}, 105 | RootCAs: certPool, 106 | InsecureSkipVerify: true, 107 | } 108 | 109 | return 110 | } 111 | 112 | func TestServer_SetContextValueWithPKI(t *testing.T) { 113 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 114 | defer cancel() 115 | serverCgf, clientCgf, clientSerial, err := createTLSConfig(ctx) 116 | require.NoError(t, err) 117 | 118 | ld, err := coapNet.NewTLSListener("tcp4", "", serverCgf) 119 | require.NoError(t, err) 120 | defer ld.Close() 121 | 122 | onNewConn := func(cc *tcp.ClientConn, tlscon *tls.Conn) { 123 | require.NotNil(t, tlscon) 124 | // set connection context certificate 125 | clientCert := tlscon.ConnectionState().PeerCertificates[0] 126 | cc.SetContextValue("client-cert", clientCert) 127 | } 128 | handle := func(w *tcp.ResponseWriter, r *pool.Message) { 129 | // get certificate from connection context 130 | clientCert := r.Context().Value("client-cert").(*x509.Certificate) 131 | require.Equal(t, clientCert.SerialNumber, clientSerial) 132 | require.NotNil(t, clientCert) 133 | w.SetResponse(codes.Content, message.TextPlain, bytes.NewReader([]byte("done"))) 134 | } 135 | 136 | sd := tcp.NewServer(tcp.WithHandlerFunc(handle), tcp.WithOnNewClientConn(onNewConn)) 137 | defer sd.Stop() 138 | go func() { 139 | err := sd.Serve(ld) 140 | require.NoError(t, err) 141 | }() 142 | 143 | cc, err := tcp.Dial(ld.Addr().String(), tcp.WithTLS(clientCgf)) 144 | require.NoError(t, err) 145 | defer cc.Close() 146 | 147 | _, err = cc.Get(ctx, "/") 148 | require.NoError(t, err) 149 | } 150 | 151 | func TestServer_InactiveMonitor(t *testing.T) { 152 | inactivityDetected := false 153 | 154 | ld, err := coapNet.NewTCPListener("tcp", "") 155 | require.NoError(t, err) 156 | defer ld.Close() 157 | 158 | var checkCloseWg sync.WaitGroup 159 | defer checkCloseWg.Wait() 160 | sd := tcp.NewServer( 161 | tcp.WithOnNewClientConn(func(cc *tcp.ClientConn, tlscon *tls.Conn) { 162 | checkCloseWg.Add(1) 163 | cc.AddOnClose(func() { 164 | checkCloseWg.Done() 165 | }) 166 | }), 167 | tcp.WithInactivityMonitor(100*time.Millisecond, func(cc inactivity.ClientConn) { 168 | require.False(t, inactivityDetected) 169 | inactivityDetected = true 170 | cc.Close() 171 | }), 172 | ) 173 | 174 | var serverWg sync.WaitGroup 175 | defer func() { 176 | sd.Stop() 177 | serverWg.Wait() 178 | }() 179 | serverWg.Add(1) 180 | go func() { 181 | defer serverWg.Done() 182 | err := sd.Serve(ld) 183 | require.NoError(t, err) 184 | }() 185 | 186 | cc, err := tcp.Dial( 187 | ld.Addr().String(), 188 | ) 189 | require.NoError(t, err) 190 | checkCloseWg.Add(1) 191 | cc.AddOnClose(func() { 192 | checkCloseWg.Done() 193 | }) 194 | 195 | // send ping to create serverside connection 196 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 197 | defer cancel() 198 | err = cc.Ping(ctx) 199 | require.NoError(t, err) 200 | 201 | err = cc.Ping(ctx) 202 | require.NoError(t, err) 203 | 204 | time.Sleep(time.Second * 2) 205 | 206 | cc.Close() 207 | 208 | checkCloseWg.Wait() 209 | require.True(t, inactivityDetected) 210 | } 211 | 212 | func TestServer_KeepAliveMonitor(t *testing.T) { 213 | inactivityDetected := false 214 | 215 | ld, err := coapNet.NewTCPListener("tcp", "") 216 | require.NoError(t, err) 217 | defer ld.Close() 218 | 219 | var checkCloseWg sync.WaitGroup 220 | defer checkCloseWg.Wait() 221 | sd := tcp.NewServer( 222 | tcp.WithOnNewClientConn(func(cc *tcp.ClientConn, tlscon *tls.Conn) { 223 | checkCloseWg.Add(1) 224 | cc.AddOnClose(func() { 225 | checkCloseWg.Done() 226 | }) 227 | }), 228 | tcp.WithKeepAlive(3, 100*time.Millisecond, func(cc inactivity.ClientConn) { 229 | require.False(t, inactivityDetected) 230 | inactivityDetected = true 231 | cc.Close() 232 | }), 233 | ) 234 | 235 | var serverWg sync.WaitGroup 236 | defer func() { 237 | sd.Stop() 238 | serverWg.Wait() 239 | }() 240 | serverWg.Add(1) 241 | go func() { 242 | defer serverWg.Done() 243 | err := sd.Serve(ld) 244 | require.NoError(t, err) 245 | }() 246 | 247 | cc, err := tcp.Dial( 248 | ld.Addr().String(), 249 | tcp.WithInactivityMonitor(time.Millisecond*10, func(cc inactivity.ClientConn) { 250 | time.Sleep(time.Millisecond * 500) 251 | }), 252 | ) 253 | require.NoError(t, err) 254 | checkCloseWg.Add(1) 255 | cc.AddOnClose(func() { 256 | checkCloseWg.Done() 257 | }) 258 | 259 | // send ping to create serverside connection 260 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 261 | defer cancel() 262 | cc.Ping(ctx) 263 | 264 | checkCloseWg.Wait() 265 | require.True(t, inactivityDetected) 266 | } 267 | -------------------------------------------------------------------------------- /udp/server_test.go: -------------------------------------------------------------------------------- 1 | package udp_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "net" 7 | "sync" 8 | "testing" 9 | "time" 10 | 11 | "github.com/matrix-org/go-coap/v2/message" 12 | "github.com/matrix-org/go-coap/v2/message/codes" 13 | coapNet "github.com/matrix-org/go-coap/v2/net" 14 | "github.com/matrix-org/go-coap/v2/net/monitor/inactivity" 15 | "github.com/matrix-org/go-coap/v2/udp" 16 | "github.com/matrix-org/go-coap/v2/udp/client" 17 | "github.com/matrix-org/go-coap/v2/udp/message/pool" 18 | "github.com/stretchr/testify/assert" 19 | "github.com/stretchr/testify/require" 20 | ) 21 | 22 | type mcastreceiver struct { 23 | msgs []*pool.Message 24 | sync.Mutex 25 | } 26 | 27 | func (m *mcastreceiver) process(cc *client.ClientConn, resp *pool.Message) { 28 | m.Lock() 29 | defer m.Unlock() 30 | resp.Hijack() 31 | m.msgs = append(m.msgs, resp) 32 | } 33 | 34 | func (m *mcastreceiver) pop() []*pool.Message { 35 | m.Lock() 36 | defer m.Unlock() 37 | r := m.msgs 38 | m.msgs = nil 39 | return r 40 | } 41 | 42 | /* 43 | func TestServer_DiscoverIotivity(t *testing.T) { 44 | timeout := time.Millisecond * 500 45 | multicastAddr := "224.0.1.187:5683" 46 | path := "/oic/res" 47 | 48 | var wg sync.WaitGroup 49 | defer wg.Wait() 50 | 51 | ld, err := coapNet.NewListenUDP("udp4", "") 52 | require.NoError(t, err) 53 | defer ld.Close() 54 | 55 | sd := NewServer() 56 | defer sd.Stop() 57 | 58 | wg.Add(1) 59 | go func() { 60 | defer wg.Done() 61 | err := sd.Serve(ld) 62 | require.NoError(t, err) 63 | }() 64 | 65 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 66 | defer cancel() 67 | recv := &mcastreceiver{} 68 | err = sd.Discover(ctx, multicastAddr, path, recv.process) 69 | require.NoError(t, err) 70 | got := recv.pop() 71 | assert.Greater(t, len(got), 1) 72 | assert.Equal(t, codes.Content, got[0].Code()) 73 | buf, err := ioutil.ReadAll(got[0].Body()) 74 | require.NoError(t, err) 75 | } 76 | */ 77 | 78 | func TestServer_Discover(t *testing.T) { 79 | timeout := time.Millisecond * 500 80 | multicastAddr := "224.0.1.187:5684" 81 | path := "/oic/res" 82 | 83 | l, err := coapNet.NewListenUDP("udp4", multicastAddr) 84 | require.NoError(t, err) 85 | defer l.Close() 86 | 87 | ifaces, err := net.Interfaces() 88 | require.NoError(t, err) 89 | 90 | a, err := net.ResolveUDPAddr("udp4", multicastAddr) 91 | require.NoError(t, err) 92 | 93 | for _, iface := range ifaces { 94 | err := l.JoinGroup(&iface, a) 95 | if err != nil { 96 | t.Logf("cannot JoinGroup(%v, %v): %v", iface, a, err) 97 | } 98 | } 99 | err = l.SetMulticastLoopback(true) 100 | require.NoError(t, err) 101 | 102 | var wg sync.WaitGroup 103 | defer wg.Wait() 104 | 105 | s := udp.NewServer(udp.WithHandlerFunc(func(w *client.ResponseWriter, r *pool.Message) { 106 | w.SetResponse(codes.BadRequest, message.TextPlain, bytes.NewReader(make([]byte, 5330))) 107 | require.NotNil(t, w.ClientConn()) 108 | })) 109 | defer s.Stop() 110 | 111 | wg.Add(1) 112 | go func() { 113 | defer wg.Done() 114 | err := s.Serve(l) 115 | require.NoError(t, err) 116 | }() 117 | 118 | ld, err := coapNet.NewListenUDP("udp4", "") 119 | require.NoError(t, err) 120 | defer ld.Close() 121 | 122 | sd := udp.NewServer() 123 | defer sd.Stop() 124 | 125 | wg.Add(1) 126 | go func() { 127 | defer wg.Done() 128 | err := sd.Serve(ld) 129 | require.NoError(t, err) 130 | }() 131 | 132 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 133 | defer cancel() 134 | recv := &mcastreceiver{} 135 | err = sd.Discover(ctx, multicastAddr, path, recv.process) 136 | require.NoError(t, err) 137 | got := recv.pop() 138 | assert.Greater(t, len(got), 1) 139 | assert.Equal(t, codes.BadRequest, got[0].Code()) 140 | } 141 | 142 | func TestServer_CleanUpConns(t *testing.T) { 143 | ld, err := coapNet.NewListenUDP("udp4", "") 144 | require.NoError(t, err) 145 | defer ld.Close() 146 | 147 | var checkCloseWg sync.WaitGroup 148 | defer checkCloseWg.Wait() 149 | sd := udp.NewServer(udp.WithOnNewClientConn(func(cc *client.ClientConn) { 150 | checkCloseWg.Add(1) 151 | cc.AddOnClose(func() { 152 | checkCloseWg.Done() 153 | }) 154 | })) 155 | defer sd.Stop() 156 | 157 | var wg sync.WaitGroup 158 | wg.Add(1) 159 | go func() { 160 | defer wg.Done() 161 | err := sd.Serve(ld) 162 | require.NoError(t, err) 163 | }() 164 | 165 | cc, err := udp.Dial(ld.LocalAddr().String()) 166 | require.NoError(t, err) 167 | checkCloseWg.Add(1) 168 | cc.AddOnClose(func() { 169 | checkCloseWg.Done() 170 | }) 171 | defer cc.Close() 172 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 173 | defer cancel() 174 | err = cc.Ping(ctx) 175 | require.NoError(t, err) 176 | } 177 | 178 | func TestServer_InactiveMonitor(t *testing.T) { 179 | inactivityDetected := false 180 | 181 | ld, err := coapNet.NewListenUDP("udp4", "") 182 | require.NoError(t, err) 183 | defer ld.Close() 184 | 185 | var checkCloseWg sync.WaitGroup 186 | defer checkCloseWg.Wait() 187 | sd := udp.NewServer( 188 | udp.WithOnNewClientConn(func(cc *client.ClientConn) { 189 | checkCloseWg.Add(1) 190 | cc.AddOnClose(func() { 191 | checkCloseWg.Done() 192 | }) 193 | }), 194 | udp.WithInactivityMonitor(100*time.Millisecond, func(cc inactivity.ClientConn) { 195 | require.False(t, inactivityDetected) 196 | inactivityDetected = true 197 | cc.Close() 198 | }), 199 | ) 200 | 201 | var serverWg sync.WaitGroup 202 | defer func() { 203 | sd.Stop() 204 | serverWg.Wait() 205 | }() 206 | serverWg.Add(1) 207 | go func() { 208 | defer serverWg.Done() 209 | err := sd.Serve(ld) 210 | require.NoError(t, err) 211 | }() 212 | 213 | cc, err := udp.Dial( 214 | ld.LocalAddr().String(), 215 | ) 216 | require.NoError(t, err) 217 | checkCloseWg.Add(1) 218 | cc.AddOnClose(func() { 219 | checkCloseWg.Done() 220 | }) 221 | 222 | // send ping to create serverside connection 223 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 224 | defer cancel() 225 | err = cc.Ping(ctx) 226 | require.NoError(t, err) 227 | 228 | err = cc.Ping(ctx) 229 | require.NoError(t, err) 230 | 231 | // wait for fire inactivity 232 | time.Sleep(time.Second * 2) 233 | 234 | cc.Close() 235 | 236 | checkCloseWg.Wait() 237 | require.True(t, inactivityDetected) 238 | } 239 | 240 | func TestServer_KeepAliveMonitor(t *testing.T) { 241 | inactivityDetected := false 242 | 243 | ld, err := coapNet.NewListenUDP("udp4", "") 244 | require.NoError(t, err) 245 | defer ld.Close() 246 | 247 | var checkCloseWg sync.WaitGroup 248 | defer checkCloseWg.Wait() 249 | sd := udp.NewServer( 250 | udp.WithOnNewClientConn(func(cc *client.ClientConn) { 251 | checkCloseWg.Add(1) 252 | cc.AddOnClose(func() { 253 | checkCloseWg.Done() 254 | }) 255 | }), 256 | udp.WithKeepAlive(3, 100*time.Millisecond, func(cc inactivity.ClientConn) { 257 | require.False(t, inactivityDetected) 258 | inactivityDetected = true 259 | cc.Close() 260 | }), 261 | ) 262 | 263 | var serverWg sync.WaitGroup 264 | defer func() { 265 | sd.Stop() 266 | serverWg.Wait() 267 | }() 268 | serverWg.Add(1) 269 | go func() { 270 | defer serverWg.Done() 271 | err := sd.Serve(ld) 272 | require.NoError(t, err) 273 | }() 274 | 275 | cc, err := udp.Dial( 276 | ld.LocalAddr().String(), 277 | udp.WithInactivityMonitor(time.Millisecond*10, func(cc inactivity.ClientConn) { 278 | time.Sleep(time.Millisecond * 500) 279 | cc.Close() 280 | }), 281 | ) 282 | require.NoError(t, err) 283 | checkCloseWg.Add(1) 284 | cc.AddOnClose(func() { 285 | checkCloseWg.Done() 286 | }) 287 | 288 | // send ping to create serverside connection 289 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 290 | defer cancel() 291 | cc.Ping(ctx) 292 | 293 | checkCloseWg.Wait() 294 | require.True(t, inactivityDetected) 295 | } 296 | -------------------------------------------------------------------------------- /tcp/server.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "fmt" 7 | "net" 8 | "sync" 9 | "time" 10 | 11 | "github.com/matrix-org/go-coap/v2/message" 12 | "github.com/matrix-org/go-coap/v2/net/blockwise" 13 | "github.com/matrix-org/go-coap/v2/net/monitor/inactivity" 14 | "github.com/matrix-org/go-coap/v2/tcp/message/pool" 15 | kitSync "github.com/plgd-dev/kit/sync" 16 | 17 | "github.com/matrix-org/go-coap/v2/message/codes" 18 | 19 | coapNet "github.com/matrix-org/go-coap/v2/net" 20 | ) 21 | 22 | // A ServerOption sets options such as credentials, codec and keepalive parameters, etc. 23 | type ServerOption interface { 24 | apply(*serverOptions) 25 | } 26 | 27 | // The HandlerFunc type is an adapter to allow the use of 28 | // ordinary functions as COAP handlers. 29 | type HandlerFunc = func(*ResponseWriter, *pool.Message) 30 | 31 | type ErrorFunc = func(error) 32 | 33 | type GoPoolFunc = func(func()) error 34 | 35 | type BlockwiseFactoryFunc = func(getSendedRequest func(token message.Token) (blockwise.Message, bool)) *blockwise.BlockWise 36 | 37 | // OnNewClientConnFunc is the callback for new connections. 38 | // 39 | // Note: Calling `tlscon.Close()` is forbidden, and `tlscon` should be treated as a 40 | // "read-only" parameter, mainly used to get the peer certificate from the underlining connection 41 | type OnNewClientConnFunc = func(cc *ClientConn, tlscon *tls.Conn) 42 | 43 | var defaultServerOptions = serverOptions{ 44 | ctx: context.Background(), 45 | maxMessageSize: 64 * 1024, 46 | handler: func(w *ResponseWriter, r *pool.Message) { 47 | w.SetResponse(codes.NotFound, message.TextPlain, nil) 48 | }, 49 | errors: func(err error) { 50 | fmt.Println(err) 51 | }, 52 | goPool: func(f func()) error { 53 | go func() { 54 | f() 55 | }() 56 | return nil 57 | }, 58 | blockwiseEnable: true, 59 | blockwiseSZX: blockwise.SZX1024, 60 | blockwiseTransferTimeout: time.Second * 3, 61 | onNewClientConn: func(cc *ClientConn, tlscon *tls.Conn) {}, 62 | heartBeat: time.Millisecond * 100, 63 | createInactivityMonitor: func() inactivity.Monitor { 64 | return inactivity.NewNilMonitor() 65 | }, 66 | } 67 | 68 | type serverOptions struct { 69 | ctx context.Context 70 | maxMessageSize int 71 | handler HandlerFunc 72 | errors ErrorFunc 73 | goPool GoPoolFunc 74 | createInactivityMonitor func() inactivity.Monitor 75 | blockwiseSZX blockwise.SZX 76 | blockwiseEnable bool 77 | blockwiseTransferTimeout time.Duration 78 | onNewClientConn OnNewClientConnFunc 79 | heartBeat time.Duration 80 | disablePeerTCPSignalMessageCSMs bool 81 | disableTCPSignalMessageCSM bool 82 | } 83 | 84 | // Listener defined used by coap 85 | type Listener interface { 86 | Close() error 87 | AcceptWithContext(ctx context.Context) (net.Conn, error) 88 | } 89 | 90 | type Server struct { 91 | maxMessageSize int 92 | handler HandlerFunc 93 | errors ErrorFunc 94 | goPool GoPoolFunc 95 | createInactivityMonitor func() inactivity.Monitor 96 | blockwiseSZX blockwise.SZX 97 | blockwiseEnable bool 98 | blockwiseTransferTimeout time.Duration 99 | onNewClientConn OnNewClientConnFunc 100 | heartBeat time.Duration 101 | disablePeerTCPSignalMessageCSMs bool 102 | disableTCPSignalMessageCSM bool 103 | 104 | ctx context.Context 105 | cancel context.CancelFunc 106 | 107 | listen Listener 108 | listenMutex sync.Mutex 109 | } 110 | 111 | func NewServer(opt ...ServerOption) *Server { 112 | opts := defaultServerOptions 113 | for _, o := range opt { 114 | o.apply(&opts) 115 | } 116 | 117 | ctx, cancel := context.WithCancel(opts.ctx) 118 | 119 | if opts.createInactivityMonitor == nil { 120 | opts.createInactivityMonitor = func() inactivity.Monitor { 121 | return inactivity.NewNilMonitor() 122 | } 123 | } 124 | 125 | return &Server{ 126 | ctx: ctx, 127 | cancel: cancel, 128 | handler: opts.handler, 129 | maxMessageSize: opts.maxMessageSize, 130 | errors: func(err error) { 131 | opts.errors(fmt.Errorf("tcp: %w", err)) 132 | }, 133 | goPool: opts.goPool, 134 | blockwiseSZX: opts.blockwiseSZX, 135 | blockwiseEnable: opts.blockwiseEnable, 136 | blockwiseTransferTimeout: opts.blockwiseTransferTimeout, 137 | heartBeat: opts.heartBeat, 138 | disablePeerTCPSignalMessageCSMs: opts.disablePeerTCPSignalMessageCSMs, 139 | disableTCPSignalMessageCSM: opts.disableTCPSignalMessageCSM, 140 | onNewClientConn: opts.onNewClientConn, 141 | createInactivityMonitor: opts.createInactivityMonitor, 142 | } 143 | } 144 | 145 | func (s *Server) checkAndSetListener(l Listener) error { 146 | s.listenMutex.Lock() 147 | defer s.listenMutex.Unlock() 148 | if s.listen != nil { 149 | return fmt.Errorf("server already serve listener") 150 | } 151 | s.listen = l 152 | return nil 153 | } 154 | 155 | func (s *Server) checkAcceptError(err error) (bool, error) { 156 | if err == nil { 157 | return true, nil 158 | } 159 | switch err { 160 | case coapNet.ErrListenerIsClosed: 161 | s.Stop() 162 | return false, nil 163 | case context.DeadlineExceeded, context.Canceled: 164 | select { 165 | case <-s.ctx.Done(): 166 | default: 167 | s.errors(fmt.Errorf("cannot accept connection: %w", err)) 168 | return true, nil 169 | } 170 | return false, nil 171 | default: 172 | return true, nil 173 | } 174 | } 175 | 176 | func (s *Server) Serve(l Listener) error { 177 | if s.blockwiseSZX > blockwise.SZXBERT { 178 | return fmt.Errorf("invalid blockwiseSZX") 179 | } 180 | 181 | err := s.checkAndSetListener(l) 182 | if err != nil { 183 | return err 184 | } 185 | 186 | defer func() { 187 | s.listenMutex.Lock() 188 | defer s.listenMutex.Unlock() 189 | s.listen = nil 190 | }() 191 | var wg sync.WaitGroup 192 | defer wg.Wait() 193 | for { 194 | rw, err := l.AcceptWithContext(s.ctx) 195 | ok, err := s.checkAcceptError(err) 196 | if err != nil { 197 | return err 198 | } 199 | if !ok { 200 | return nil 201 | } 202 | if rw != nil { 203 | wg.Add(1) 204 | go func() { 205 | defer wg.Done() 206 | var cc *ClientConn 207 | monitor := s.createInactivityMonitor() 208 | opts := []coapNet.ConnOption{ 209 | coapNet.WithHeartBeat(s.heartBeat), 210 | coapNet.WithOnReadTimeout(func() error { 211 | monitor.CheckInactivity(cc) 212 | return nil 213 | }), 214 | } 215 | cc = s.createClientConn(coapNet.NewConn(rw, opts...), monitor) 216 | if s.onNewClientConn != nil { 217 | if tlscon, ok := rw.(*tls.Conn); ok { 218 | s.onNewClientConn(cc, tlscon) 219 | } else { 220 | s.onNewClientConn(cc, nil) 221 | } 222 | } 223 | err := cc.Run() 224 | if err != nil { 225 | s.errors(fmt.Errorf("%v: %w", cc.RemoteAddr(), err)) 226 | } 227 | }() 228 | } 229 | } 230 | } 231 | 232 | // Stop stops server without wait of ends Serve function. 233 | func (s *Server) Stop() { 234 | s.cancel() 235 | } 236 | 237 | func (s *Server) createClientConn(connection *coapNet.Conn, monitor inactivity.Monitor) *ClientConn { 238 | var blockWise *blockwise.BlockWise 239 | if s.blockwiseEnable { 240 | blockWise = blockwise.NewBlockWise( 241 | bwAcquireMessage, 242 | bwReleaseMessage, 243 | s.blockwiseTransferTimeout, 244 | s.errors, 245 | false, 246 | func(token message.Token) (blockwise.Message, bool) { 247 | return nil, false 248 | }, 249 | nil, 250 | ) 251 | } 252 | obsHandler := NewHandlerContainer() 253 | cc := NewClientConn( 254 | NewSession( 255 | s.ctx, 256 | connection, 257 | NewObservationHandler(obsHandler, s.handler), 258 | s.maxMessageSize, 259 | s.goPool, 260 | s.errors, 261 | s.blockwiseSZX, 262 | blockWise, 263 | s.disablePeerTCPSignalMessageCSMs, 264 | s.disableTCPSignalMessageCSM, 265 | true, 266 | monitor), 267 | obsHandler, kitSync.NewMap(), 268 | ) 269 | 270 | return cc 271 | } 272 | --------------------------------------------------------------------------------