├── .github
└── workflows
│ └── go.yml
├── .gitignore
├── .golangci.yaml
├── LICENSE
├── Makefile
├── README.md
├── bootstrap
├── bootstrap.go
├── handler.go
└── option.go
├── core
├── core.go
├── device.go
├── inactivitymonitor.go
├── manager.go
└── observe.go
├── encoding
├── corelink.go
├── corelink_test.go
├── encoding.go
├── opaque.go
├── opaque_test.go
├── textplain.go
├── textplain_test.go
├── tlv.go
└── tlv_test.go
├── examples
├── bootstrap
│ └── main.go
├── content-format
│ └── main.go
├── dtls
│ └── main.go
├── observe
│ └── main.go
├── readwrite
│ └── main.go
└── simple
│ └── main.go
├── go.mod
├── go.sum
├── node
├── definition
│ ├── 0.xml
│ ├── 1.xml
│ ├── 10.xml
│ ├── 10241.xml
│ ├── 10242.xml
│ ├── 10243.xml
│ ├── 10244.xml
│ ├── 10245.xml
│ ├── 10246.xml
│ ├── 10247.xml
│ ├── 10248.xml
│ ├── 10249.xml
│ ├── 10250.xml
│ ├── 10251.xml
│ ├── 10252.xml
│ ├── 10253.xml
│ ├── 10254.xml
│ ├── 10255.xml
│ ├── 10256.xml
│ ├── 10257.xml
│ ├── 10258.xml
│ ├── 10259.xml
│ ├── 10260.xml
│ ├── 10262.xml
│ ├── 10263.xml
│ ├── 10264.xml
│ ├── 10265.xml
│ ├── 10266.xml
│ ├── 10267.xml
│ ├── 10268.xml
│ ├── 10269.xml
│ ├── 10270.xml
│ ├── 10271.xml
│ ├── 10272.xml
│ ├── 10273.xml
│ ├── 10274.xml
│ ├── 10275.xml
│ ├── 10276.xml
│ ├── 10277.xml
│ ├── 10278.xml
│ ├── 10279.xml
│ ├── 10280.xml
│ ├── 10281.xml
│ ├── 10282.xml
│ ├── 10283.xml
│ ├── 10284.xml
│ ├── 10286.xml
│ ├── 10290.xml
│ ├── 10291.xml
│ ├── 10292.xml
│ ├── 10299.xml
│ ├── 10300.xml
│ ├── 10308.xml
│ ├── 10309.xml
│ ├── 10311.xml
│ ├── 10313.xml
│ ├── 10314.xml
│ ├── 10315.xml
│ ├── 10316.xml
│ ├── 10318.xml
│ ├── 10319.xml
│ ├── 10320.xml
│ ├── 10322.xml
│ ├── 10323.xml
│ ├── 10324.xml
│ ├── 10326.xml
│ ├── 10327.xml
│ ├── 10328.xml
│ ├── 10329.xml
│ ├── 10330.xml
│ ├── 10331.xml
│ ├── 10332.xml
│ ├── 10333.xml
│ ├── 10334.xml
│ ├── 10335.xml
│ ├── 10336.xml
│ ├── 10337.xml
│ ├── 10338.xml
│ ├── 10339.xml
│ ├── 10340.xml
│ ├── 10341.xml
│ ├── 10342.xml
│ ├── 10343.xml
│ ├── 10344.xml
│ ├── 10345.xml
│ ├── 10346.xml
│ ├── 10347.xml
│ ├── 10348.xml
│ ├── 10349.xml
│ ├── 10350.xml
│ ├── 10351.xml
│ ├── 10352.xml
│ ├── 10353.xml
│ ├── 10354.xml
│ ├── 10355.xml
│ ├── 10356.xml
│ ├── 10357.xml
│ ├── 10358.xml
│ ├── 10359.xml
│ ├── 10360.xml
│ ├── 10361.xml
│ ├── 10362.xml
│ ├── 10363.xml
│ ├── 10364.xml
│ ├── 10365.xml
│ ├── 10366.xml
│ ├── 10368.xml
│ ├── 10369.xml
│ ├── 10371.xml
│ ├── 11.xml
│ ├── 12.xml
│ ├── 13.xml
│ ├── 14.xml
│ ├── 15.xml
│ ├── 16.xml
│ ├── 18830.xml
│ ├── 18831.xml
│ ├── 19.xml
│ ├── 2.xml
│ ├── 20.xml
│ ├── 2048.xml
│ ├── 2049.xml
│ ├── 2050.xml
│ ├── 2051.xml
│ ├── 2052.xml
│ ├── 2053.xml
│ ├── 2054.xml
│ ├── 2055.xml
│ ├── 2056.xml
│ ├── 2057.xml
│ ├── 21.xml
│ ├── 22.xml
│ ├── 23.xml
│ ├── 24.xml
│ ├── 25.xml
│ ├── 26.xml
│ ├── 27.xml
│ ├── 3.xml
│ ├── 3200.xml
│ ├── 3201.xml
│ ├── 3202.xml
│ ├── 3203.xml
│ ├── 3300.xml
│ ├── 3301.xml
│ ├── 3302.xml
│ ├── 3303.xml
│ ├── 3304.xml
│ ├── 3305.xml
│ ├── 3306.xml
│ ├── 3308.xml
│ ├── 3310.xml
│ ├── 3311.xml
│ ├── 3312.xml
│ ├── 3313.xml
│ ├── 3314.xml
│ ├── 3315.xml
│ ├── 3316.xml
│ ├── 3317.xml
│ ├── 3318.xml
│ ├── 3319.xml
│ ├── 3320.xml
│ ├── 3321.xml
│ ├── 3322.xml
│ ├── 3323.xml
│ ├── 3324.xml
│ ├── 3325.xml
│ ├── 3326.xml
│ ├── 3327.xml
│ ├── 3328.xml
│ ├── 3329.xml
│ ├── 3330.xml
│ ├── 3331.xml
│ ├── 3332.xml
│ ├── 3333.xml
│ ├── 3334.xml
│ ├── 3335.xml
│ ├── 3336.xml
│ ├── 3337.xml
│ ├── 3338.xml
│ ├── 3339.xml
│ ├── 3340.xml
│ ├── 3341.xml
│ ├── 3342.xml
│ ├── 3343.xml
│ ├── 3344.xml
│ ├── 3345.xml
│ ├── 3346.xml
│ ├── 3347.xml
│ ├── 3348.xml
│ ├── 3349.xml
│ ├── 3350.xml
│ ├── 3351.xml
│ ├── 3352.xml
│ ├── 3353.xml
│ ├── 3354.xml
│ ├── 3355.xml
│ ├── 3356.xml
│ ├── 3357.xml
│ ├── 3358.xml
│ ├── 3359.xml
│ ├── 3360.xml
│ ├── 3361.xml
│ ├── 3362.xml
│ ├── 3363.xml
│ ├── 3364.xml
│ ├── 3365.xml
│ ├── 3366.xml
│ ├── 3367.xml
│ ├── 3368.xml
│ ├── 3369.xml
│ ├── 3370.xml
│ ├── 3371.xml
│ ├── 3372.xml
│ ├── 3373.xml
│ ├── 3374.xml
│ ├── 3375.xml
│ ├── 3376.xml
│ ├── 3377.xml
│ ├── 3378.xml
│ ├── 3379.xml
│ ├── 3380.xml
│ ├── 3381.xml
│ ├── 3382.xml
│ ├── 3383.xml
│ ├── 3384.xml
│ ├── 3385.xml
│ ├── 3386.xml
│ ├── 3387.xml
│ ├── 3388.xml
│ ├── 3389.xml
│ ├── 3390.xml
│ ├── 3391.xml
│ ├── 3392.xml
│ ├── 3393.xml
│ ├── 3394.xml
│ ├── 3395.xml
│ ├── 3396.xml
│ ├── 3397.xml
│ ├── 3398.xml
│ ├── 3399.xml
│ ├── 3400.xml
│ ├── 3401.xml
│ ├── 3402.xml
│ ├── 3403.xml
│ ├── 3404.xml
│ ├── 3405.xml
│ ├── 3406.xml
│ ├── 3407.xml
│ ├── 3408.xml
│ ├── 3410.xml
│ ├── 3411.xml
│ ├── 3412.xml
│ ├── 3413.xml
│ ├── 3414.xml
│ ├── 3415.xml
│ ├── 3416.xml
│ ├── 3417.xml
│ ├── 3418.xml
│ ├── 3419.xml
│ ├── 3420.xml
│ ├── 3421.xml
│ ├── 3423.xml
│ ├── 3424.xml
│ ├── 3425.xml
│ ├── 3426.xml
│ ├── 3427.xml
│ ├── 3428.xml
│ ├── 3429.xml
│ ├── 3430.xml
│ ├── 3431.xml
│ ├── 3432.xml
│ ├── 3433.xml
│ ├── 3434.xml
│ ├── 3435.xml
│ ├── 3436.xml
│ ├── 3437.xml
│ ├── 3438.xml
│ ├── 3439.xml
│ ├── 4.xml
│ ├── 5.xml
│ ├── 500.xml
│ ├── 501.xml
│ ├── 502.xml
│ ├── 503.xml
│ ├── 6.xml
│ ├── 7.xml
│ ├── 8.xml
│ └── 9.xml
├── node.go
├── node_test.go
├── object.go
├── object_test.go
├── path.go
├── path_test.go
├── registry.go
├── registry_test.go
└── resource.go
├── registration
├── config.go
└── handler.go
└── server
├── config.go
├── dtls.go
├── server.go
└── udp.go
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a golang project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
3 |
4 | name: Go
5 |
6 | on:
7 | push:
8 | branches: [ "main" ]
9 | pull_request:
10 | branches: [ "main" ]
11 |
12 | jobs:
13 |
14 | build:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v3
18 |
19 | - name: Set up Go
20 | uses: actions/setup-go@v3
21 | with:
22 | go-version: 1.18
23 |
24 | - name: Build
25 | run: go build -v ./...
26 |
27 | - name: Test
28 | run: go test -v ./...
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | vendor
3 |
--------------------------------------------------------------------------------
/.golangci.yaml:
--------------------------------------------------------------------------------
1 | linters:
2 | disable-all: true
3 | enable:
4 | - unused
5 | - ineffassign
6 | - staticcheck
7 |
8 | linters-settings:
9 | staticcheck:
10 | go: "1.18"
11 | checks: [
12 | "all",
13 | "-ST1*"
14 | ]
15 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: run_simple run_mqtt
2 |
3 | run_simple:
4 | go run examples/simple/*.go
5 |
6 | run_mqtt:
7 | go run examples/lwm2mqtt/*.go
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # lwm2m
2 |
3 |  
4 |
5 | A simple and lightweight ( but not full feature ) lwm2m server aim to run on edge router.
6 |
7 | ## Features
8 |
9 | - [ ] Bootstrap Interface
10 | - [x] Client Registration interface.
11 | * [x] Register
12 | * [x] Update
13 | * [x] Deregister
14 | - [ ] Device Management and Service Enablement interface.
15 | * [x] Read Operation, Read Resource, Read Object
16 | * [x] Write Operation, Write Resource, Write Object Instance
17 | * [x] Execute Operation
18 | * [x] Discover Operation
19 | * [x] Create Operation
20 | * [x] Delete Operation
21 | * [ ] Write-Attributes Operation
22 | * [ ] Read-Composite Operation
23 | * [ ] Write-Composite Operation
24 | - [x] Information Reporting interface.
25 | * [x] Observe Operation, Observe Resource, Observe Object
26 | * [x] Cancel Observation Operation
27 | * [ ] Observe-Composite Operation
28 | * [ ] Cancel Observation-Composite Operation
29 | * [ ] Send Operation
30 | - [ ] Data formats
31 | * [ ] Plain Text
32 | * [ ] Opaque
33 | * [ ] CBOR
34 | * [x] TLV
35 | * [ ] SenML JSON
36 | * [ ] SenML CBOR
37 | * [ ] LwM2M JSON
38 | - [ ] Security
39 | * [ ] DTLS with Certificates
40 | * [x] DTLS with PSK, only support DTLS 1.2
41 | - [x] Transport
42 | * [x] UDP transport support.
43 | * [ ] TCP transport support.(Some features may not work properly)
44 | - [ ] Tested with clients
45 | * [x] Leshan client: coap, coaps + psk
46 | * [x] Anjay client running on ESP32: coap
47 | * [x] Anjay client running on Linux: coap, coaps + psk
48 | * [x] Zephyr LWM2M client running on nrf52840 with w5500 ethernet: coap, coaps + psk
49 | * [ ] Zephyr LWM2M client running on nrf52840 with Openthread
50 |
51 | ## Installation
52 |
53 | You need a working Go environment.
54 |
55 | ```
56 | go get github.com/yplam/lwm2m
57 | ```
58 |
59 | ## Getting Started
60 |
61 | ```go
62 | package main
63 |
64 | import (
65 | "github.com/yplam/lwm2m/core"
66 | "github.com/yplam/lwm2m/registration"
67 | "github.com/yplam/lwm2m/server"
68 | "log"
69 | )
70 |
71 | func main() {
72 | r := server.DefaultRouter()
73 | deviceManager := core.DefaultManager()
74 | registration.EnableHandler(r, deviceManager)
75 | err := server.ListenAndServe(r,
76 | server.EnableUDPListener("udp", ":5683"),
77 | )
78 | if err != nil {
79 | log.Printf("serve lwm2m with err: %v", err)
80 | }
81 | }
82 |
83 | ```
84 |
85 | ## Test Commands
86 |
87 | leshan client
88 |
89 | ```shell
90 | # coap
91 | java -jar leshan-client-demo.jar --server-url=localhost:5683
92 |
93 | # coaps
94 | java -jar leshan-client-demo.jar --server-url=localhost:5684 -i 123 --psk-key=000102030405060708090a0b0c0d0e0f
95 | ```
96 | anjay client
97 | ```shell
98 | # coap
99 | ./output/bin/demo --endpoint-name $(hostname) --server-uri coap://127.0.0.1:5683
100 |
101 | # coap + tcp
102 | ./output/bin/demo --endpoint-name $(hostname) --server-uri coap+tcp://127.0.0.1:5685
103 |
104 | # coaps
105 | ./output/bin/demo --endpoint-name $(hostname) --server-uri coaps://127.0.0.1:5684 --security-mode psk --identity 666f6f --key 000102030405060708090a0b0c0d0e0f --ciphersuites 49320 --tls-version TLSv1.2
106 | ```
107 |
108 | ## License
109 |
110 | Apache License Version 2.0. See the [LICENSE](LICENSE) file for details.
--------------------------------------------------------------------------------
/bootstrap/bootstrap.go:
--------------------------------------------------------------------------------
1 | package bootstrap
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/plgd-dev/go-coap/v3/message"
7 | "github.com/plgd-dev/go-coap/v3/message/codes"
8 | "github.com/plgd-dev/go-coap/v3/mux"
9 | "github.com/yplam/lwm2m/core"
10 | "github.com/yplam/lwm2m/encoding"
11 | "github.com/yplam/lwm2m/node"
12 | "io"
13 | )
14 |
15 | type Client struct {
16 | Endpoint string
17 | conn mux.Conn
18 | selectedCt message.MediaType
19 | }
20 |
21 | func (c *Client) ContentType() message.MediaType {
22 | return c.selectedCt
23 | }
24 |
25 | func (c *Client) SetContentType(ct message.MediaType) {
26 | c.selectedCt = ct
27 | }
28 |
29 | func (c *Client) finish(ctx context.Context) (err error) {
30 | res, err := c.conn.Post(ctx, "/bs", c.selectedCt, nil)
31 | if err != nil {
32 | return err
33 | }
34 | if res.Code() != codes.Changed {
35 | return fmt.Errorf("unexpected response code: %v", res.Code())
36 | }
37 | return nil
38 | }
39 |
40 | func (c *Client) Conn() mux.Conn {
41 | return c.conn
42 | }
43 |
44 | func (c *Client) Write(ctx context.Context, path node.Path, val ...node.Node) error {
45 | msg, err := node.EncodeMessage(c.selectedCt, val)
46 | if err != nil {
47 | return err
48 | }
49 | resp, err := c.conn.Put(ctx, path.String(), c.selectedCt, msg)
50 | if err != nil {
51 | return err
52 | }
53 | if resp.Code() != codes.Changed {
54 | return fmt.Errorf("unexpected response code: %v", resp.Code())
55 | }
56 | return nil
57 | }
58 |
59 | func (c *Client) Read(ctx context.Context, path node.Path) ([]node.Node, error) {
60 |
61 | // accept
62 | buf := make([]byte, 2)
63 | _, _ = message.EncodeUint32(buf, uint32(c.selectedCt))
64 | acceptOption := message.Option{
65 | ID: message.Accept,
66 | Value: buf[:],
67 | }
68 |
69 | msg, err := c.conn.Get(ctx, path.String(), acceptOption)
70 | if err != nil {
71 | return nil, err
72 | }
73 | if msg.Code() != codes.Content {
74 | return nil, fmt.Errorf("unexpected response code: %v", msg.Code())
75 | }
76 | if msg.Body() == nil {
77 | return nil, core.ErrEmptyBody
78 | }
79 | return node.DecodeMessage(path, msg)
80 | }
81 |
82 | func (c *Client) Discover(ctx context.Context, path node.Path) ([]*encoding.CoreLink, error) {
83 |
84 | buf := make([]byte, 2)
85 | l, _ := message.EncodeUint32(buf, uint32(message.AppLinkFormat))
86 | r, err := c.conn.Get(ctx, path.String(),
87 | message.Option{
88 | ID: message.Accept,
89 | Value: buf[:l],
90 | })
91 | if err != nil {
92 | return nil, err
93 | }
94 | if r.Code() != codes.Content {
95 | return nil, fmt.Errorf("unexpected response code: %v", r.Code())
96 | }
97 | links := make([]*encoding.CoreLink, 0)
98 | if r.Body() != nil {
99 | if b, err := io.ReadAll(r.Body()); err == nil {
100 | links, _ = encoding.CoreLinksFromString(string(b))
101 | }
102 | }
103 | return links, nil
104 | }
105 |
106 | func (c *Client) Delete(ctx context.Context, path node.Path) (err error) {
107 | resp, err := c.conn.Delete(ctx, path.String())
108 | if err != nil {
109 | return err
110 | }
111 | if resp.Code() != codes.Deleted {
112 | return fmt.Errorf("unexpected response code: %v", resp.Code())
113 | }
114 | return nil
115 | }
116 |
117 | type Provider interface {
118 | HandleBsRequest(ctx context.Context, device *Client) error
119 | }
120 |
--------------------------------------------------------------------------------
/bootstrap/handler.go:
--------------------------------------------------------------------------------
1 | package bootstrap
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "fmt"
7 | "github.com/pion/logging"
8 | "github.com/plgd-dev/go-coap/v3/message"
9 | "github.com/plgd-dev/go-coap/v3/message/codes"
10 | "github.com/plgd-dev/go-coap/v3/mux"
11 | "github.com/yplam/lwm2m/core"
12 | "strconv"
13 | "strings"
14 | "time"
15 | )
16 |
17 | const DefaultBootstrapTimeout = 30 * time.Second
18 |
19 | var DefaultContentType = message.AppLwm2mTLV
20 |
21 | type Handler struct {
22 | timeout time.Duration
23 | logger logging.LeveledLogger
24 | provider Provider
25 | }
26 |
27 | func (h *Handler) ServeCOAP(w mux.ResponseWriter, r *mux.Message) {
28 | h.logger.Debugf("bootstrap serve coap: %v", r)
29 |
30 | ep := "" // endpoint, optional
31 | pctOpt := strconv.FormatInt(int64(message.AppLwm2mTLV), 10) // preferred content-type, optional
32 | opts := r.Options()
33 | for _, o := range opts {
34 | h.logger.Debugf("option: %s, %d", string(o.Value), o.ID)
35 | if o.ID != message.URIQuery {
36 | continue
37 | }
38 | so := strings.Split(string(o.Value), "=")
39 | if len(so) != 2 {
40 | w.SetResponse(codes.BadRequest, message.TextPlain, bytes.NewReader([]byte("bad uri query")))
41 | return
42 | }
43 | switch so[0] {
44 | case "ep":
45 | ep = so[1]
46 | case "pct":
47 | pctOpt = so[1]
48 | default:
49 | w.SetResponse(codes.BadRequest, message.TextPlain, bytes.NewReader([]byte("unsupported option")))
50 | return
51 | }
52 | }
53 |
54 | ipct, err := strconv.ParseUint(pctOpt, 10, 16)
55 | if err != nil {
56 | err = fmt.Errorf("parse pct option: %w", err)
57 | w.SetResponse(codes.BadRequest, message.TextPlain, bytes.NewReader([]byte(err.Error())))
58 | return
59 | }
60 | pct := message.MediaType(ipct)
61 |
62 | // select content type
63 | switch pct {
64 | case message.AppLwm2mTLV:
65 | case message.TextPlain:
66 | case message.AppOctets:
67 | default:
68 | h.logger.Debugf("client prefer unsupported(yet) content-type %s", pct.String())
69 | h.logger.Debugf("using %s as default", DefaultContentType.String())
70 | pct = DefaultContentType
71 | }
72 |
73 | client := &Client{
74 | Endpoint: ep,
75 | conn: w.Conn(),
76 | selectedCt: pct,
77 | }
78 |
79 | core.SetLifetime(client.conn, h.timeout)
80 |
81 | err = w.SetResponse(codes.Changed, client.selectedCt, nil)
82 | if err != nil {
83 | h.logger.Errorf("error sending response to bootstrap request: %v", err)
84 | return
85 | }
86 |
87 | // limit bootstrap procedure by timeout with ctx
88 | ctx, cancel := context.WithTimeout(context.TODO(), h.timeout)
89 | go func() {
90 | defer func() {
91 | // send finish message in any case
92 | err := client.finish(context.TODO())
93 | if err != nil {
94 | h.logger.Errorf("error sending bootstrap-finish: %v", err)
95 | }
96 | cancel()
97 | }()
98 | err := h.provider.HandleBsRequest(ctx, client)
99 | if err != nil {
100 | h.logger.Errorf("bootstrap provider err: %v", err)
101 | return
102 | }
103 | }()
104 | }
105 |
106 | func EnableHandler(r *mux.Router, p Provider, opts ...Option) {
107 | h := &Handler{
108 | provider: p,
109 | timeout: DefaultBootstrapTimeout,
110 | }
111 | for _, opt := range opts {
112 | opt(h)
113 | }
114 | if h.logger == nil {
115 | lf := logging.NewDefaultLoggerFactory()
116 | h.logger = lf.NewLogger("bootstrap")
117 | }
118 | _ = r.Handle("/bs", h)
119 | }
120 |
--------------------------------------------------------------------------------
/bootstrap/option.go:
--------------------------------------------------------------------------------
1 | package bootstrap
2 |
3 | import (
4 | "github.com/pion/logging"
5 | "time"
6 | )
7 |
8 | type Option func(h *Handler)
9 |
10 | func WithLogger(l logging.LeveledLogger) Option {
11 | return func(h *Handler) {
12 | h.logger = l
13 | }
14 | }
15 |
16 | func WithBootstrapTimeout(timeout time.Duration) Option {
17 | return func(h *Handler) {
18 | h.timeout = timeout
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/core/core.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "github.com/yplam/lwm2m/encoding"
7 | "github.com/yplam/lwm2m/node"
8 | )
9 |
10 | var (
11 | ErrUnexpectedResponseCode = errors.New("unexpected response code")
12 | ErrEmptyBody = errors.New("empty body")
13 | )
14 |
15 | type Observer interface {
16 | ObserveSync(p node.Path, onMsg ObserveFunc) error
17 | Observe(p node.Path, onMsg ObserveFunc) error
18 | ObserveObject(p node.Path, onMsg ObserveObjectFunc) error
19 | ObserveResource(p node.Path, onMsg ObserveResourceFunc) error
20 | CancelObserve(p node.Path) error
21 | }
22 |
23 | type DeviceManager interface {
24 | Read(ctx context.Context, p node.Path) ([]node.Node, error)
25 | ReadObject(ctx context.Context, p node.Path) (*node.Object, error)
26 | ReadResource(ctx context.Context, p node.Path) (*node.Resource, error)
27 | Write(ctx context.Context, p node.Path, val ...node.Node) error
28 | WriteResource(ctx context.Context, p node.Path, val *node.Resource) error
29 | WriteObjectInstance(ctx context.Context, p node.Path, val *node.ObjectInstance) error
30 | Discover(ctx context.Context, p node.Path) ([]*encoding.CoreLink, error)
31 | }
32 |
--------------------------------------------------------------------------------
/core/inactivitymonitor.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "context"
5 | dtlsServer "github.com/plgd-dev/go-coap/v3/dtls/server"
6 | "github.com/plgd-dev/go-coap/v3/mux"
7 | "github.com/plgd-dev/go-coap/v3/net/monitor/inactivity"
8 | "github.com/plgd-dev/go-coap/v3/options"
9 | tcpClient "github.com/plgd-dev/go-coap/v3/tcp/client"
10 | tcpServer "github.com/plgd-dev/go-coap/v3/tcp/server"
11 | udpClient "github.com/plgd-dev/go-coap/v3/udp/client"
12 | udpServer "github.com/plgd-dev/go-coap/v3/udp/server"
13 | "sync/atomic"
14 | "time"
15 | )
16 |
17 | // InactivityMonitorOpt notifies when a connection was inactive for a given duration.
18 | type InactivityMonitorOpt[C options.OnInactiveFunc] struct {
19 | onInactive C
20 | }
21 |
22 | func (o InactivityMonitorOpt[C]) toTCPCreateInactivityMonitor(onInactive options.TCPOnInactive) func() tcpClient.InactivityMonitor {
23 | return func() tcpClient.InactivityMonitor {
24 | return NewInactivityMonitor(onInactive)
25 | }
26 | }
27 |
28 | func (o InactivityMonitorOpt[C]) toUDPCreateInactivityMonitor(onInactive options.UDPOnInactive) func() udpClient.InactivityMonitor {
29 | return func() udpClient.InactivityMonitor {
30 | return NewInactivityMonitor(onInactive)
31 | }
32 | }
33 |
34 | func (o InactivityMonitorOpt[C]) TCPServerApply(cfg *tcpServer.Config) {
35 | switch onInactive := any(o.onInactive).(type) {
36 | case options.TCPOnInactive:
37 | cfg.CreateInactivityMonitor = o.toTCPCreateInactivityMonitor(onInactive)
38 | }
39 | }
40 |
41 | func (o InactivityMonitorOpt[C]) UDPServerApply(cfg *udpServer.Config) {
42 | switch onInactive := any(o.onInactive).(type) {
43 | case options.UDPOnInactive:
44 | cfg.CreateInactivityMonitor = o.toUDPCreateInactivityMonitor(onInactive)
45 | default:
46 | }
47 | }
48 |
49 | func (o InactivityMonitorOpt[C]) DTLSServerApply(cfg *dtlsServer.Config) {
50 | switch onInactive := any(o.onInactive).(type) {
51 | case options.UDPOnInactive:
52 | cfg.CreateInactivityMonitor = o.toUDPCreateInactivityMonitor(onInactive)
53 | }
54 | }
55 |
56 | // WithInactivityMonitor set deadline's for read operations over client connection.
57 | func WithInactivityMonitor[C options.OnInactiveFunc](onInactive C) InactivityMonitorOpt[C] {
58 | return InactivityMonitorOpt[C]{
59 | onInactive: onInactive,
60 | }
61 | }
62 |
63 | func NewInactivityMonitor[C inactivity.Conn](onInactive inactivity.OnInactiveFunc[C]) *InactivityMonitor[C] {
64 | m := &InactivityMonitor[C]{
65 | onInactive: onInactive,
66 | }
67 | m.Notify()
68 | return m
69 | }
70 |
71 | type InactivityMonitor[C inactivity.Conn] struct {
72 | lastActivity atomic.Value
73 | onInactive inactivity.OnInactiveFunc[C]
74 | }
75 |
76 | func (m *InactivityMonitor[C]) Notify() {
77 | m.lastActivity.Store(time.Now())
78 | }
79 |
80 | func (m *InactivityMonitor[C]) LastActivity() time.Time {
81 | if t, ok := m.lastActivity.Load().(time.Time); ok {
82 | return t
83 | }
84 | return time.Time{}
85 | }
86 |
87 | func CloseConn(cc inactivity.Conn) {
88 | // call cc.Close() directly to check and handle error if necessary
89 | _ = cc.Close()
90 | }
91 |
92 | func New[C inactivity.Conn](onInactive inactivity.OnInactiveFunc[C]) *InactivityMonitor[C] {
93 | m := &InactivityMonitor[C]{
94 | onInactive: onInactive,
95 | }
96 | m.Notify()
97 | return m
98 | }
99 |
100 | type lifetimeCtxKeyType string
101 |
102 | const lifetimeCtxKey lifetimeCtxKeyType = "core_lifetime"
103 |
104 | func WithLifetime(ctx context.Context, lifetime time.Duration) context.Context {
105 | return context.WithValue(ctx, lifetimeCtxKey, lifetime)
106 | }
107 |
108 | func SetLifetime(c mux.Conn, d time.Duration) {
109 | c.SetContextValue(lifetimeCtxKey, d)
110 | }
111 |
112 | func GetLifetime(ctx context.Context) *time.Duration {
113 | lifetime, ok := ctx.Value(lifetimeCtxKey).(time.Duration)
114 | if !ok {
115 | // Log this issue
116 | return nil
117 | }
118 | return &lifetime
119 | }
120 |
121 | func (m *InactivityMonitor[C]) CheckInactivity(now time.Time, cc C) {
122 | if m.onInactive == nil {
123 | return
124 | }
125 | duration := time.Second * 2
126 | if lifetime := GetLifetime(cc.Context()); lifetime != nil {
127 | duration = *lifetime
128 | }
129 | if now.After(m.LastActivity().Add(duration)) {
130 | m.onInactive(cc)
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/core/observe.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "fmt"
5 | "github.com/plgd-dev/go-coap/v3/mux"
6 | "github.com/yplam/lwm2m/node"
7 | )
8 |
9 | type ObserveFunc func(d *Device, p node.Path, notify []node.Node)
10 | type ObserveObjectFunc func(d *Device, p node.Path, notify *node.Object)
11 | type ObserveResourceFunc func(d *Device, p node.Path, notify *node.Resource)
12 |
13 | type Observation struct {
14 | o mux.Observation
15 | cb ObserveFunc
16 | }
17 |
18 | func wrapObserveResourceFunc(f ObserveResourceFunc) ObserveFunc {
19 | return func(d *Device, p node.Path, notify []node.Node) {
20 | if data, err := node.GetResourceByPath(notify, p); err == nil {
21 | f(d, p, data)
22 | }
23 | }
24 | }
25 |
26 | func wrapObserveObjectFunc(f ObserveObjectFunc) ObserveFunc {
27 | return func(d *Device, p node.Path, notify []node.Node) {
28 | if data, err := node.GetObjectByPath(notify, p); err == nil {
29 | f(d, p, data)
30 | } else {
31 | fmt.Printf("obj err %v\n", err)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/encoding/corelink_test.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "testing"
6 | )
7 |
8 | func TestCoreLinksFromString(t *testing.T) {
9 | var links []*CoreLink
10 |
11 | links, err := CoreLinksFromString(">;rt=\"oma.github.com/yplam/lwm2m\";" +
12 | "ct=11543,1/0>,3/0>,5/0>,3303/0>,3300>,3304>,3323>," +
13 | "3311>,3340>,3342>,3347>")
14 | assert.Nil(t, err)
15 | assert.Equal(t, 12, len(links))
16 | }
17 |
--------------------------------------------------------------------------------
/encoding/encoding.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import "errors"
4 |
5 | var (
6 | ErrInvalidLength = errors.New("invalid data length")
7 | ErrNotEnoughData = errors.New("not enough data")
8 | ErrNotBoolean = errors.New("not a boolean representation")
9 | ErrUnknownType = errors.New("unknown type")
10 | )
11 |
12 | type Valuer interface {
13 | StringVal() string
14 | Integer() (val int64, err error)
15 | Float() (float64, error)
16 | Boolean() (val bool, err error)
17 | Opaque() []byte
18 | Time() (int64, error)
19 | ObjectLink() (uint16, uint16, error)
20 | Raw() []byte
21 | }
22 |
--------------------------------------------------------------------------------
/encoding/opaque.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "math"
7 | )
8 |
9 | // Opaque value should be used for binary resources like firmware image
10 | // although define it for every type
11 | // with same rules as Value field in TLV
12 |
13 | type OpaqueValue struct {
14 | Value []byte
15 | }
16 |
17 | func (o *OpaqueValue) StringVal() string {
18 | return string(o.Value)
19 | }
20 |
21 | func (o *OpaqueValue) Integer() (val int64, err error) {
22 | buff := bytes.NewBuffer(o.Value)
23 | if len(o.Value) == 1 {
24 | var i1 int8
25 | err = binary.Read(buff, binary.BigEndian, &i1)
26 | val = int64(i1)
27 | } else if len(o.Value) == 2 {
28 | var i2 int16
29 | err = binary.Read(buff, binary.BigEndian, &i2)
30 | val = int64(i2)
31 | } else if len(o.Value) == 4 {
32 | var i4 int32
33 | err = binary.Read(buff, binary.BigEndian, &i4)
34 | val = int64(i4)
35 | } else if len(o.Value) == 8 {
36 | var i8 int64
37 | err = binary.Read(buff, binary.BigEndian, &i8)
38 | val = i8
39 | } else {
40 | err = ErrInvalidLength
41 | }
42 | return
43 | }
44 |
45 | func (o *OpaqueValue) Float() (float64, error) {
46 | if len(o.Value) == 4 {
47 | return float64(math.Float32frombits(binary.BigEndian.Uint32(o.Value))), nil
48 | } else if len(o.Value) == 8 {
49 | return math.Float64frombits(binary.BigEndian.Uint64(o.Value)), nil
50 | } else {
51 | return 0, ErrInvalidLength
52 | }
53 | }
54 |
55 | func (o *OpaqueValue) Boolean() (val bool, err error) {
56 | // Represented as the ASCII value 0 or 1
57 | if bytes.Equal([]byte{0x01}, o.Value) {
58 | return true, nil
59 | } else if bytes.Equal([]byte{0x00}, o.Value) {
60 | return false, nil
61 | }
62 | return false, ErrNotBoolean
63 | }
64 |
65 | func (o *OpaqueValue) Opaque() []byte {
66 | return o.Value
67 | }
68 |
69 | func (o *OpaqueValue) Time() (int64, error) {
70 | if len(o.Value) == 4 {
71 | return int64(binary.BigEndian.Uint32(o.Value)), nil
72 | } else if len(o.Value) == 8 {
73 | return int64(binary.BigEndian.Uint64(o.Value)), nil
74 | } else {
75 | return 0, ErrInvalidLength
76 | }
77 | }
78 |
79 | func (o *OpaqueValue) ObjectLink() (uint16, uint16, error) {
80 | if len(o.Value) != 4 {
81 | return 0, 0, ErrInvalidLength
82 | }
83 | _ = o.Value[3]
84 | return uint16(o.Value[0])*256 + uint16(o.Value[1]),
85 | uint16(o.Value[2])*256 + uint16(o.Value[3]), nil
86 | }
87 |
88 | func (o *OpaqueValue) Raw() []byte {
89 | return o.Value
90 | }
91 |
92 | func NewOpaqueValue(v any) (*OpaqueValue, error) {
93 | var val any
94 | switch v.(type) {
95 | case int:
96 | val = int64(v.(int))
97 | case string:
98 | val = []byte(v.(string))
99 | default:
100 | val = v
101 | }
102 | buf := new(bytes.Buffer)
103 | err := binary.Write(buf, binary.BigEndian, val)
104 | if err != nil {
105 | return nil, err
106 | }
107 | return &OpaqueValue{Value: buf.Bytes()}, nil
108 | }
109 |
--------------------------------------------------------------------------------
/encoding/opaque_test.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "testing"
6 | )
7 |
8 | func TestOpaque(t *testing.T) {
9 | { // int -> int64
10 | val, err := NewOpaqueValue(42)
11 | assert.Nil(t, err)
12 | assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a}, val.Raw())
13 | }
14 | { // int64
15 | val, err := NewOpaqueValue(int64(42))
16 | assert.Nil(t, err)
17 | assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a}, val.Raw())
18 | }
19 | { // int32
20 | val, err := NewOpaqueValue(int32(42))
21 | assert.Nil(t, err)
22 | assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x2a}, val.Raw())
23 | }
24 | { // int16
25 | val, err := NewOpaqueValue(int16(42))
26 | assert.Nil(t, err)
27 | assert.Equal(t, []byte{0x00, 0x2a}, val.Raw())
28 | }
29 | { // float #1
30 | val, err := NewOpaqueValue(42.0)
31 | assert.Nil(t, err)
32 | assert.Equal(t, []byte{0x40, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, val.Raw())
33 | }
34 | { // float #2
35 | val, err := NewOpaqueValue(3.14151617181920)
36 | assert.Nil(t, err)
37 | assert.Equal(t, []byte{0x40, 0x09, 0x21, 0xd3, 0x3b, 0xe, 0x8c, 0x74}, val.Raw())
38 | }
39 | { // string
40 | val, err := NewOpaqueValue("3.14151617181920")
41 | assert.Nil(t, err)
42 | assert.Equal(t, []byte("3.14151617181920"), val.Raw())
43 | }
44 | { // bool
45 | val, err := NewOpaqueValue(true)
46 | assert.Nil(t, err)
47 | assert.Equal(t, []byte{0x01}, val.Raw())
48 | val, err = NewOpaqueValue(false)
49 | assert.Equal(t, nil, err)
50 | assert.Equal(t, []byte{0x00}, val.Raw())
51 | }
52 | { // opaque
53 | val, err := NewOpaqueValue([]byte{0x01, 0x02, 0x03, 0x04})
54 | assert.Nil(t, err)
55 | assert.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, val.Raw())
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/encoding/textplain.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "encoding/base64"
5 | "errors"
6 | "fmt"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | type PlainTextValue struct {
12 | Value []byte
13 | }
14 |
15 | func (p *PlainTextValue) StringVal() string {
16 | return string(p.Value)
17 | }
18 |
19 | func (p *PlainTextValue) Integer() (val int64, err error) {
20 | return strconv.ParseInt(string(p.Value), 10, 64)
21 | }
22 |
23 | func (p *PlainTextValue) Float() (float64, error) {
24 | return strconv.ParseFloat(string(p.Value), 64)
25 | }
26 |
27 | func (p *PlainTextValue) Boolean() (val bool, err error) {
28 | // Represented as the ASCII value 0 or 1
29 | if string(p.Value) == "1" {
30 | return true, nil
31 | } else if string(p.Value) == "0" {
32 | return false, nil
33 | }
34 | return false, ErrNotBoolean
35 | }
36 |
37 | func (p *PlainTextValue) Opaque() []byte {
38 | // Represented as a Base64 encoding of binary bytes
39 | opaque, err := base64.StdEncoding.DecodeString(string(p.Value))
40 | if err != nil {
41 | return nil
42 | }
43 | return opaque
44 | }
45 |
46 | func (p *PlainTextValue) Time() (int64, error) {
47 | // same as integer
48 | return p.Integer()
49 | }
50 |
51 | func (p *PlainTextValue) ObjectLink() (uint16, uint16, error) {
52 | splitted := strings.Split(string(p.Value), ":")
53 | if len(splitted) != 2 {
54 | return 0, 0, errors.New("wrong ObjLink text representation")
55 | }
56 | od, err := strconv.ParseUint(splitted[0], 10, 16)
57 | if err != nil {
58 | return 0, 0, err
59 | }
60 | oid, err := strconv.ParseUint(splitted[1], 10, 16)
61 | if err != nil {
62 | return 0, 0, err
63 | }
64 | return uint16(od), uint16(oid), nil
65 | }
66 |
67 | func (p *PlainTextValue) Raw() []byte {
68 | return p.Value
69 | }
70 |
71 | func NewPlainTextRaw(data []byte) *PlainTextValue {
72 | return &PlainTextValue{Value: data}
73 | }
74 |
75 | func (p *PlainTextValue) fromInteger(val int64) error {
76 | str := fmt.Sprintf("%d", val)
77 | p.Value = []byte(str)
78 | return nil
79 | }
80 | func (p *PlainTextValue) fromFloat(val float64) error {
81 | str := fmt.Sprintf("%v", val)
82 | p.Value = []byte(str)
83 | return nil
84 | }
85 | func (p *PlainTextValue) fromBool(val bool) error {
86 | str := "0"
87 | if val {
88 | str = "1"
89 | }
90 | p.Value = []byte(str)
91 | return nil
92 | }
93 | func (p *PlainTextValue) fromTime(val int64) error {
94 | return p.fromInteger(val)
95 | }
96 | func (p *PlainTextValue) fromString(val string) error {
97 | p.Value = []byte(val)
98 | return nil
99 | }
100 | func (p *PlainTextValue) fromOpaque(val []byte) error {
101 | raw := base64.StdEncoding.EncodeToString(val)
102 | p.Value = []byte(raw)
103 | return nil
104 | }
105 | func NewPlainTextValue(val any) (*PlainTextValue, error) {
106 | var pt PlainTextValue
107 | var err error
108 | switch val.(type) {
109 | case int:
110 | err = pt.fromInteger(int64(val.(int)))
111 | case int8:
112 | err = pt.fromInteger(int64(val.(int8)))
113 | case int16:
114 | err = pt.fromInteger(int64(val.(int16)))
115 | case int32:
116 | err = pt.fromInteger(int64(val.(int32)))
117 | case int64:
118 | err = pt.fromInteger(val.(int64))
119 | case uint:
120 | err = pt.fromInteger(int64(val.(uint)))
121 | case uint8:
122 | err = pt.fromInteger(int64(val.(uint8)))
123 | case uint16:
124 | err = pt.fromInteger(int64(val.(uint16)))
125 | case uint32:
126 | err = pt.fromInteger(int64(val.(uint32)))
127 | case float32:
128 | err = pt.fromFloat(float64(val.(float32)))
129 | case float64:
130 | err = pt.fromFloat(val.(float64))
131 | case string:
132 | err = pt.fromString(val.(string))
133 | case []byte:
134 | err = pt.fromOpaque(val.([]byte))
135 | case bool:
136 | err = pt.fromBool(val.(bool))
137 | default:
138 | err = ErrUnknownType
139 | }
140 | if err != nil {
141 | return nil, err
142 | }
143 | return &pt, err
144 | }
145 |
--------------------------------------------------------------------------------
/encoding/textplain_test.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "testing"
6 | )
7 |
8 | func TestPlainText(t *testing.T) {
9 | { // int
10 | val, err := NewPlainTextValue(42)
11 | assert.Nil(t, err)
12 | assert.Equal(t, []byte("42"), val.Raw())
13 | }
14 | { // float #1
15 | val, err := NewPlainTextValue(42.21)
16 | assert.Nil(t, err)
17 | assert.Equal(t, []byte("42.21"), val.Raw())
18 | }
19 | { // float #2
20 | val, err := NewPlainTextValue(3.14151617181920)
21 | assert.Nil(t, err)
22 | assert.Equal(t, []byte("3.1415161718192"), val.Raw())
23 | }
24 | { // string
25 | val, err := NewPlainTextValue("3.14151617181920")
26 | assert.Nil(t, err)
27 | assert.Equal(t, []byte("3.14151617181920"), val.Raw())
28 | }
29 | { // bool
30 | val, err := NewPlainTextValue(true)
31 | assert.Nil(t, err)
32 | assert.Equal(t, []byte("1"), val.Raw())
33 | val, err = NewPlainTextValue(false)
34 | assert.Equal(t, nil, err)
35 | assert.Equal(t, []byte("0"), val.Raw())
36 | }
37 | { // opaque
38 | val, err := NewPlainTextValue([]byte{0x01, 0x02, 0x03, 0x04})
39 | assert.Nil(t, err)
40 | assert.Equal(t, []byte("AQIDBA=="), val.Raw())
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/examples/content-format/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/plgd-dev/go-coap/v3/message"
7 | "github.com/yplam/lwm2m/core"
8 | "github.com/yplam/lwm2m/encoding"
9 | "github.com/yplam/lwm2m/node"
10 | "github.com/yplam/lwm2m/registration"
11 | "github.com/yplam/lwm2m/server"
12 | "log"
13 | "time"
14 | )
15 |
16 | func onDeviceStateChange(e core.DeviceEvent, m core.Manager) {
17 | device := e.Device
18 | switch e.EventType {
19 | case core.DevicePostRegister:
20 | device.SetMediaTypes(message.TextPlain, message.TextPlain)
21 | if device.HasObjectWithInstance(1) {
22 | go func() {
23 | ctx := context.Background()
24 | op, _ := node.NewPathFromString("/1")
25 | oip, _ := node.NewPathFromString("/1/0")
26 | rp, _ := node.NewPathFromString("/1/0/1")
27 | if n, err := device.Discover(ctx, op); err == nil {
28 | fmt.Printf("links %v\n", n)
29 | }
30 |
31 | if v, err := device.Read(ctx, rp); err == nil {
32 | fmt.Printf("Read: %v\n", v)
33 | }
34 | time.Sleep(time.Second * 1)
35 | if v, err := device.ReadResource(ctx, rp); err == nil {
36 | fmt.Printf("Resource: %v\n", v)
37 | }
38 | time.Sleep(time.Second * 1)
39 | if v, err := device.ReadObject(ctx, op); err == nil {
40 | fmt.Printf("Object: %v\n", v)
41 | }
42 | time.Sleep(time.Second * 1)
43 | value := encoding.NewTlv(encoding.TlvSingleResource, 1, uint16(123))
44 | if value != nil {
45 | if res, err := node.NewSingleResource(rp, value); err == nil {
46 | if err = device.WriteResource(ctx, rp, res); err == nil {
47 | fmt.Printf("Write resource ok\n")
48 | } else {
49 | fmt.Println("error writing resource: ", err)
50 | }
51 | } else {
52 | fmt.Println("error creating resource", err)
53 | }
54 | }
55 | time.Sleep(time.Second * 1)
56 | if v, err := device.ReadResource(ctx, rp); err == nil {
57 | fmt.Printf("Resource: %v\n", v)
58 | }
59 | time.Sleep(time.Second * 1)
60 | value = encoding.NewTlv(encoding.TlvSingleResource, 5850, true)
61 | if value != nil {
62 | if res, err := node.NewSingleResource(rp, value); err == nil {
63 | obi := node.NewObjectInstance(0)
64 | obi.Resources[5850] = res
65 | if err = device.WriteObjectInstance(ctx, oip, obi); err == nil {
66 | fmt.Printf("Write object instance ok\n")
67 | }
68 | }
69 |
70 | }
71 | time.Sleep(time.Second * 1)
72 | if v, err := device.ReadResource(ctx, rp); err == nil {
73 | fmt.Printf("Resource: %v\n", v)
74 | }
75 | }()
76 |
77 | }
78 | default:
79 |
80 | }
81 | }
82 |
83 | func main() {
84 | r := server.DefaultRouter()
85 | manager := core.DefaultManager()
86 | manager.OnDeviceStateChange(onDeviceStateChange)
87 | registration.EnableHandler(r, manager)
88 |
89 | err := server.ListenAndServe(r,
90 | server.EnableUDPListener("udp", ":5683"))
91 | if err != nil {
92 | log.Printf("Serve lwm2m with err: %v\n", err)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/examples/dtls/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/yplam/lwm2m/core"
5 | "github.com/yplam/lwm2m/registration"
6 | "github.com/yplam/lwm2m/server"
7 | "log"
8 | )
9 |
10 | func PSKFromIdentity(hint []byte) ([]byte, error) {
11 | return []byte{
12 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
13 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
14 | }, nil
15 | }
16 |
17 | func main() {
18 | r := server.DefaultRouter()
19 | deviceManager := core.DefaultManager()
20 | registration.EnableHandler(r, deviceManager)
21 | err := server.ListenAndServe(r,
22 | server.EnableUDPListener("udp", ":5683"),
23 | server.EnableDTLSListener("udp", ":5684", PSKFromIdentity),
24 | )
25 | if err != nil {
26 | log.Printf("serve lwm2m with err: %v", err)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/readwrite/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/yplam/lwm2m/core"
7 | "github.com/yplam/lwm2m/encoding"
8 | "github.com/yplam/lwm2m/node"
9 | "github.com/yplam/lwm2m/registration"
10 | "github.com/yplam/lwm2m/server"
11 | "log"
12 | "time"
13 | )
14 |
15 | func onDeviceStateChange(e core.DeviceEvent, m core.Manager) {
16 | device := e.Device
17 | switch e.EventType {
18 | case core.DevicePostRegister:
19 | if device.HasObjectWithInstance(3347) {
20 | go func() {
21 | ctx := context.Background()
22 | op, _ := node.NewPathFromString("/3311")
23 | oip, _ := node.NewPathFromString("/3311/0")
24 | rp, _ := node.NewPathFromString("/3311/0/5850")
25 | if n, err := device.Discover(ctx, op); err == nil {
26 | fmt.Printf("links %v\n", n)
27 | }
28 |
29 | if v, err := device.Read(ctx, rp); err == nil {
30 | fmt.Printf("Read: %v\n", v)
31 | }
32 | time.Sleep(time.Second * 5)
33 | if v, err := device.ReadResource(ctx, rp); err == nil {
34 | fmt.Printf("Resource: %v\n", v)
35 | }
36 | time.Sleep(time.Second * 5)
37 | if v, err := device.ReadObject(ctx, op); err == nil {
38 | fmt.Printf("Object: %v\n", v)
39 | }
40 | time.Sleep(time.Second * 5)
41 | value := encoding.NewTlv(encoding.TlvSingleResource, 5850, false)
42 | if value != nil {
43 | if res, err := node.NewSingleResource(rp, value); err == nil {
44 | if err = device.WriteResource(ctx, rp, res); err == nil {
45 | fmt.Printf("Write resource ok\n")
46 | }
47 | }
48 | }
49 | time.Sleep(time.Second * 5)
50 | if v, err := device.ReadResource(ctx, rp); err == nil {
51 | fmt.Printf("Resource: %v\n", v)
52 | }
53 | time.Sleep(time.Second * 5)
54 | value = encoding.NewTlv(encoding.TlvSingleResource, 5850, true)
55 | if value != nil {
56 | if res, err := node.NewSingleResource(rp, value); err == nil {
57 | obi := node.NewObjectInstance(0)
58 | obi.Resources[5850] = res
59 | if err = device.WriteObjectInstance(ctx, oip, obi); err == nil {
60 | fmt.Printf("Write object instance ok\n")
61 | }
62 | }
63 |
64 | }
65 | time.Sleep(time.Second * 5)
66 | if v, err := device.ReadResource(ctx, rp); err == nil {
67 | fmt.Printf("Resource: %v\n", v)
68 | }
69 | }()
70 |
71 | }
72 | default:
73 |
74 | }
75 | }
76 |
77 | func main() {
78 | r := server.DefaultRouter()
79 | manager := core.DefaultManager()
80 | manager.OnDeviceStateChange(onDeviceStateChange)
81 | registration.EnableHandler(r, manager)
82 |
83 | err := server.ListenAndServe(r,
84 | server.EnableUDPListener("udp", ":5683"))
85 | if err != nil {
86 | log.Printf("Serve lwm2m with err: %v\n", err)
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/examples/simple/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/yplam/lwm2m/core"
5 | "github.com/yplam/lwm2m/registration"
6 | "github.com/yplam/lwm2m/server"
7 | "log"
8 | )
9 |
10 | func main() {
11 | r := server.DefaultRouter()
12 | deviceManager := core.DefaultManager()
13 | registration.EnableHandler(r, deviceManager)
14 | err := server.ListenAndServe(r,
15 | server.EnableUDPListener("udp", ":5683"),
16 | )
17 | if err != nil {
18 | log.Printf("serve lwm2m with err: %v", err)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/yplam/lwm2m
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/pion/dtls/v2 v2.2.6
7 | github.com/pion/logging v0.2.2
8 | github.com/plgd-dev/go-coap/v3 v3.1.2
9 | github.com/sirupsen/logrus v1.9.0
10 | github.com/stretchr/testify v1.8.2
11 | golang.org/x/sync v0.2.0
12 | )
13 |
14 | require (
15 | github.com/davecgh/go-spew v1.1.1 // indirect
16 | github.com/dsnet/golib/memfile v1.0.0 // indirect
17 | github.com/hashicorp/errwrap v1.1.0 // indirect
18 | github.com/hashicorp/go-multierror v1.1.1 // indirect
19 | github.com/pion/transport/v2 v2.2.0 // indirect
20 | github.com/pion/udp/v2 v2.0.1 // indirect
21 | github.com/pmezard/go-difflib v1.0.0 // indirect
22 | go.uber.org/atomic v1.10.0 // indirect
23 | golang.org/x/crypto v0.8.0 // indirect
24 | golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect
25 | golang.org/x/net v0.10.0 // indirect
26 | golang.org/x/sys v0.8.0 // indirect
27 | gopkg.in/yaml.v3 v3.0.1 // indirect
28 | )
29 |
--------------------------------------------------------------------------------
/node/definition/10241.xml:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 |
81 |
82 |
--------------------------------------------------------------------------------
/node/definition/10248.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
35 |
78 |
79 |
--------------------------------------------------------------------------------
/node/definition/10250.xml:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 |
70 |
71 |
--------------------------------------------------------------------------------
/node/definition/10253.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
35 |
72 |
73 |
--------------------------------------------------------------------------------
/node/definition/10286.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
48 |
49 |
--------------------------------------------------------------------------------
/node/definition/10322.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
87 |
88 |
--------------------------------------------------------------------------------
/node/definition/10323.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
90 |
91 |
--------------------------------------------------------------------------------
/node/definition/10324.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
77 |
78 |
--------------------------------------------------------------------------------
/node/definition/10327.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
68 |
69 |
--------------------------------------------------------------------------------
/node/definition/10328.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
80 |
81 |
--------------------------------------------------------------------------------
/node/definition/10332.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
69 |
70 |
--------------------------------------------------------------------------------
/node/definition/10333.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
88 |
89 |
--------------------------------------------------------------------------------
/node/definition/10335.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
97 |
98 |
--------------------------------------------------------------------------------
/node/definition/10336.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
84 |
85 |
--------------------------------------------------------------------------------
/node/definition/10341.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
84 |
85 |
--------------------------------------------------------------------------------
/node/definition/10342.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
84 |
85 |
--------------------------------------------------------------------------------
/node/definition/10343.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
96 |
97 |
--------------------------------------------------------------------------------
/node/definition/10344.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
96 |
97 |
--------------------------------------------------------------------------------
/node/definition/10345.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
96 |
97 |
--------------------------------------------------------------------------------
/node/definition/10346.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
96 |
97 |
--------------------------------------------------------------------------------
/node/definition/10348.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
96 |
97 |
--------------------------------------------------------------------------------
/node/definition/10349.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
84 |
85 |
--------------------------------------------------------------------------------
/node/definition/10351.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
79 |
80 |
--------------------------------------------------------------------------------
/node/definition/10353.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
87 |
88 |
--------------------------------------------------------------------------------
/node/definition/10355.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
95 |
96 |
--------------------------------------------------------------------------------
/node/definition/10357.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
95 |
96 |
--------------------------------------------------------------------------------
/node/definition/10358.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
70 |
71 |
--------------------------------------------------------------------------------
/node/definition/10359.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
78 |
79 |
--------------------------------------------------------------------------------
/node/definition/10360.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
70 |
71 |
--------------------------------------------------------------------------------
/node/definition/10361.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
92 |
93 |
--------------------------------------------------------------------------------
/node/definition/10362.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
91 |
92 |
--------------------------------------------------------------------------------
/node/definition/10363.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
57 |
58 |
--------------------------------------------------------------------------------
/node/definition/10364.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
57 |
58 |
--------------------------------------------------------------------------------
/node/definition/10365.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
57 |
58 |
--------------------------------------------------------------------------------
/node/definition/10366.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
59 |
60 |
--------------------------------------------------------------------------------
/node/definition/10368.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
96 |
97 |
--------------------------------------------------------------------------------
/node/definition/10369.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
84 |
85 |
--------------------------------------------------------------------------------
/node/definition/2049.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
35 |
59 |
60 |
--------------------------------------------------------------------------------
/node/definition/2050.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
35 |
64 |
65 |
--------------------------------------------------------------------------------
/node/definition/2054.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
35 |
69 |
70 |
--------------------------------------------------------------------------------
/node/definition/2056.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
35 |
77 |
78 |
--------------------------------------------------------------------------------
/node/definition/26.xml:
--------------------------------------------------------------------------------
1 |
2 |
58 |
59 |
60 |
93 |
94 |
--------------------------------------------------------------------------------
/node/definition/3348.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
35 |
89 |
90 |
--------------------------------------------------------------------------------
/node/definition/3400.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
35 |
88 |
89 |
--------------------------------------------------------------------------------
/node/definition/3407.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
35 |
98 |
99 |
--------------------------------------------------------------------------------
/node/definition/3408.xml:
--------------------------------------------------------------------------------
1 |
33 |
34 |
79 |
80 |
--------------------------------------------------------------------------------
/node/definition/3419.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
32 |
33 |
35 |
78 |
79 |
--------------------------------------------------------------------------------
/node/definition/3420.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
32 |
33 |
35 |
58 |
59 |
--------------------------------------------------------------------------------
/node/definition/3438.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
32 |
33 |
35 |
78 |
79 |
--------------------------------------------------------------------------------
/node/definition/3439.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
32 |
33 |
35 |
78 |
79 |
--------------------------------------------------------------------------------
/node/object.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | type Object struct {
9 | Id uint16
10 | Instances map[uint16]*ObjectInstance
11 | }
12 |
13 | func (o *Object) ID() uint16 {
14 | return o.Id
15 | }
16 |
17 | func (o *Object) String() string {
18 | var b strings.Builder
19 | b.WriteString("Object {")
20 | b.WriteString(fmt.Sprintf("id: %v, ", o.Id))
21 | b.WriteString("instance: [")
22 | for k, v := range o.Instances {
23 | b.WriteString(fmt.Sprintf("%v:", k))
24 | b.WriteString(v.String())
25 | }
26 | b.WriteString("]")
27 | b.WriteString("}")
28 | return b.String()
29 | }
30 |
31 | type ObjectInstance struct {
32 | Id uint16
33 | Resources map[uint16]*Resource
34 | }
35 |
36 | func (i *ObjectInstance) ID() uint16 {
37 | return i.Id
38 | }
39 |
40 | func (i *ObjectInstance) String() string {
41 | var b strings.Builder
42 | b.WriteString("OBI {")
43 | b.WriteString(fmt.Sprintf("id: %v, ", i.Id))
44 | b.WriteString("Resources: [")
45 | for k, v := range i.Resources {
46 | b.WriteString(fmt.Sprintf("%v:", k))
47 | b.WriteString(v.String())
48 | }
49 | b.WriteString("]")
50 | b.WriteString("}")
51 | return b.String()
52 | }
53 |
54 | func (i *ObjectInstance) SetResource(id uint16, res *Resource) {
55 | i.Resources[id] = res
56 | }
57 |
58 | func NewObject(id uint16) *Object {
59 | return &Object{
60 | Id: id,
61 | Instances: make(map[uint16]*ObjectInstance),
62 | }
63 | }
64 |
65 | func NewObjectInstance(id uint16) *ObjectInstance {
66 | return &ObjectInstance{
67 | Id: id,
68 | Resources: make(map[uint16]*Resource),
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/node/object_test.go:
--------------------------------------------------------------------------------
1 | package node
2 |
--------------------------------------------------------------------------------
/node/path_test.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "testing"
6 | )
7 |
8 | func TestNewPathFromString(t *testing.T) {
9 | p, err := NewPathFromString("/")
10 | assert.Nil(t, err)
11 | assert.Equal(t, true, p.IsRoot())
12 |
13 | p, err = NewPathFromString("/3/")
14 | assert.Nil(t, err)
15 | assert.Equal(t, true, p.IsObject())
16 | v, err := p.ObjectId()
17 | assert.Nil(t, err)
18 | assert.Equal(t, uint16(3), v)
19 | p, err = NewPathFromString("3")
20 | assert.Nil(t, err)
21 | assert.Equal(t, true, p.IsObject())
22 | assert.Equal(t, "/3", p.String())
23 |
24 | p, err = NewPathFromString("/3/4")
25 | assert.Nil(t, err)
26 | assert.Equal(t, true, p.IsObjectInstance())
27 |
28 | p, err = NewPathFromString("/3/4/5")
29 | assert.Nil(t, err)
30 | assert.Equal(t, true, p.IsResource())
31 |
32 | p, err = NewPathFromString("/3/4/5/6")
33 | assert.Nil(t, err)
34 | assert.Equal(t, true, p.IsResourceInstance())
35 | assert.Equal(t, "/3/4/5/6", p.String())
36 | }
37 |
--------------------------------------------------------------------------------
/node/resource.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "fmt"
5 | "github.com/yplam/lwm2m/encoding"
6 | "strings"
7 | )
8 |
9 | type ResourceInstance struct {
10 | id uint16
11 | resType ResourceType
12 | data encoding.Valuer
13 | path Path
14 | }
15 |
16 | func (r *ResourceInstance) Value() interface{} {
17 | switch r.resType {
18 | case R_NONE:
19 | return r.data
20 | case R_STRING:
21 | return r.data.StringVal()
22 | case R_INTEGER:
23 | if d, err := r.data.Integer(); err == nil {
24 | return d
25 | }
26 | case R_FLOAT:
27 | if d, err := r.data.Float(); err == nil {
28 | return d
29 | }
30 | case R_BOOLEAN:
31 | if d, err := r.data.Boolean(); err == nil {
32 | return d
33 | }
34 | case R_OPAQUE:
35 | return r.data.Opaque()
36 | case R_TIME:
37 | if d, err := r.data.Time(); err == nil {
38 | return d
39 | }
40 | case R_OBJLNK:
41 | if d0, d1, err := r.data.ObjectLink(); err == nil {
42 | return [2]uint16{d0, d1}
43 | }
44 | }
45 | return nil
46 | }
47 |
48 | func (r *ResourceInstance) ID() uint16 {
49 | return r.id
50 | }
51 |
52 | func (r *ResourceInstance) String() string {
53 | return fmt.Sprintf("%v", r.Value())
54 | }
55 |
56 | func (r *ResourceInstance) Data() encoding.Valuer {
57 | return r.data
58 | }
59 |
60 | func NewResourceInstance(p Path, data encoding.Valuer) (r *ResourceInstance, err error) {
61 | id, err := p.ResourceInstanceId()
62 | if err != nil {
63 | id = 0
64 | p.resourceInstanceId = 0
65 | }
66 | resType, err := GetRegistry().DetectResourceType(p)
67 | if err != nil {
68 | return
69 | }
70 | r = &ResourceInstance{
71 | id: id,
72 | resType: resType,
73 | data: data,
74 | path: p,
75 | }
76 | return
77 | }
78 |
79 | type Resource struct {
80 | id uint16
81 | isMultiple bool
82 | instances map[uint16]*ResourceInstance
83 | path Path
84 | }
85 |
86 | func (r *Resource) SetInstance(val *ResourceInstance) error {
87 | if val.path.IsResourceInstance() {
88 | riid, _ := val.path.ResourceInstanceId()
89 | r.instances[riid] = val
90 | if riid > 0 {
91 | r.isMultiple = true
92 | }
93 | return nil
94 | }
95 | return ErrPathNotMatch
96 | }
97 |
98 | func (r *Resource) InstanceCount() int {
99 | return len(r.instances)
100 | }
101 | func (r *Resource) GetInstance(index uint16) (*ResourceInstance, error) {
102 | if ins, ok := r.instances[index]; ok {
103 | return ins, nil
104 | }
105 | return nil, ErrNotFound
106 | }
107 |
108 | func (r *Resource) ID() uint16 {
109 | return r.id
110 | }
111 |
112 | func (r *Resource) String() string {
113 | var b strings.Builder
114 | b.WriteString("Resource { ")
115 | b.WriteString(fmt.Sprintf("id: %v, ", r.id))
116 | b.WriteString("instances: [ ")
117 | for k, v := range r.instances {
118 | b.WriteString(fmt.Sprintf("%v:", k))
119 | b.WriteString(v.String())
120 | }
121 | b.WriteString("] ")
122 | b.WriteString("}")
123 | return b.String()
124 | }
125 |
126 | func (r *Resource) Data() encoding.Valuer {
127 | if i, err := r.GetInstance(0); err == nil {
128 | return i.Data()
129 | }
130 | return nil
131 | }
132 |
133 | func NewResource(p Path, isMultiple bool) (r *Resource, err error) {
134 | resID, err := p.ResourceId()
135 | if err != nil {
136 | return
137 | }
138 | r = &Resource{
139 | id: resID,
140 | isMultiple: isMultiple,
141 | instances: make(map[uint16]*ResourceInstance),
142 | path: p,
143 | }
144 | return
145 | }
146 |
147 | func NewSingleResource(p Path, val encoding.Valuer) (r *Resource, err error) {
148 | r, err = NewResource(p, false)
149 | if err != nil {
150 | return
151 | }
152 | p.SetResourceInstanceId(0)
153 | ri, err := NewResourceInstance(p, val)
154 | if err != nil {
155 | return
156 | }
157 | err = r.SetInstance(ri)
158 | return
159 | }
160 |
--------------------------------------------------------------------------------
/registration/config.go:
--------------------------------------------------------------------------------
1 | package registration
2 |
3 | import "github.com/pion/logging"
4 |
5 | type config struct {
6 | logger logging.LeveledLogger
7 | }
8 |
9 | func newConfig() *config {
10 | return &config{
11 | logger: nil,
12 | }
13 | }
14 |
15 | type Option func(cfg *config)
16 |
17 | func WithLogger(l logging.LeveledLogger) Option {
18 | return func(o *config) {
19 | o.logger = l
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/server/config.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "github.com/pion/dtls/v2"
5 | "github.com/pion/logging"
6 | )
7 |
8 | type config struct {
9 | udpNetwork string
10 | udpAddr string
11 | tcpNetwork string
12 | tcpAddr string
13 | dtlsNetwork string
14 | dtlsAddr string
15 | pskCallback dtls.PSKCallback
16 | logger logging.LeveledLogger
17 | }
18 |
19 | func newServeConfig() *config {
20 | return &config{
21 | udpNetwork: "",
22 | udpAddr: "",
23 | tcpNetwork: "",
24 | tcpAddr: "",
25 | dtlsNetwork: "",
26 | dtlsAddr: "",
27 | pskCallback: nil,
28 | logger: nil,
29 | }
30 | }
31 |
32 | type Option func(cfg *config)
33 |
34 | func WithLogger(l logging.LeveledLogger) Option {
35 | return func(o *config) {
36 | o.logger = l
37 | }
38 | }
39 |
40 | func EnableUDPListener(network, addr string) Option {
41 | return func(o *config) {
42 | o.udpNetwork = network
43 | o.udpAddr = addr
44 | }
45 | }
46 |
47 | func EnableTCPListener(network, addr string) Option {
48 | return func(o *config) {
49 | o.tcpNetwork = network
50 | o.tcpAddr = addr
51 | }
52 | }
53 |
54 | func EnableDTLSListener(network, addr string, cb dtls.PSKCallback) Option {
55 | return func(o *config) {
56 | o.dtlsNetwork = network
57 | o.dtlsAddr = addr
58 | o.pskCallback = cb
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/server/dtls.go:
--------------------------------------------------------------------------------
1 | package server
2 |
--------------------------------------------------------------------------------
/server/udp.go:
--------------------------------------------------------------------------------
1 | package server
2 |
--------------------------------------------------------------------------------