├── .gitignore ├── .travis.yml ├── README.markdown ├── example ├── server │ └── coap_server.go ├── client │ └── goap_client.go ├── subclient │ └── subclient.go └── subserver │ └── subserver.go ├── client.go ├── server.go ├── message_test.go └── message.go /.gitignore: -------------------------------------------------------------------------------- 1 | #* 2 | *~ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | install: go get -v -d ./... && go build -v ./... 3 | script: go test -v ./... 4 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Constrained Application Protocol Client and Server for go 2 | 3 | You can read more about CoAP in [the ietf draft][coap]. I also did 4 | some preliminary work on `SUBSCRIBE` support from 5 | [an early draft][shelby]. 6 | 7 | [shelby]: http://tools.ietf.org/html/draft-shelby-core-coap-01 8 | [coap]: http://tools.ietf.org/html/draft-ietf-core-coap-10 9 | -------------------------------------------------------------------------------- /example/server/coap_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | 7 | "github.com/dustin/go-coap" 8 | ) 9 | 10 | func main() { 11 | log.Fatal(coap.ListenAndServe("udp", ":5683", 12 | coap.FuncHandler(func(l *net.UDPConn, a *net.UDPAddr, m coap.Message) *coap.Message { 13 | log.Printf("Got message path=%q: %#v from %v", m.Path(), m, a) 14 | if m.IsConfirmable() { 15 | return &coap.Message{ 16 | Type: coap.Acknowledgement, 17 | Code: coap.Content, 18 | MessageID: m.MessageID, 19 | Options: coap.Options{ 20 | {coap.ContentType, []byte{byte(coap.TextPlain)}}, 21 | {coap.LocationPath, m.Path()}, 22 | }, 23 | Payload: []byte("hello to you!"), 24 | } 25 | } 26 | return nil 27 | }))) 28 | } 29 | -------------------------------------------------------------------------------- /example/client/goap_client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/dustin/go-coap" 7 | ) 8 | 9 | func main() { 10 | 11 | req := coap.Message{ 12 | Type: coap.Confirmable, 13 | Code: coap.GET, 14 | MessageID: 12345, 15 | Options: coap.Options{ 16 | {coap.ETag, []byte("weetag")}, 17 | {coap.MaxAge, []byte{0, 0, 0, 3}}, 18 | }, 19 | Payload: []byte("hello, world!"), 20 | } 21 | 22 | req.SetPathString("/some/path") 23 | 24 | c, err := coap.Dial("udp", "localhost:5683") 25 | if err != nil { 26 | log.Fatalf("Error dialing: %v", err) 27 | } 28 | 29 | rv, err := c.Send(req) 30 | if err != nil { 31 | log.Fatalf("Error sending request: %v", err) 32 | } 33 | 34 | if rv != nil { 35 | log.Printf("Response payload: %s", rv.Payload) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /example/subclient/subclient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/dustin/go-coap" 7 | ) 8 | 9 | func main() { 10 | 11 | req := coap.Message{ 12 | Type: coap.NonConfirmable, 13 | Code: coap.SUBSCRIBE, 14 | MessageID: 12345, 15 | Options: coap.Options{}, 16 | } 17 | 18 | req.SetPathString("/some/path") 19 | 20 | c, err := coap.Dial("udp", "localhost:5683") 21 | if err != nil { 22 | log.Fatalf("Error dialing: %v", err) 23 | } 24 | 25 | rv, err := c.Send(req) 26 | if err != nil { 27 | log.Fatalf("Error sending request: %v", err) 28 | } 29 | 30 | for err == nil { 31 | if rv != nil { 32 | if err != nil { 33 | log.Fatalf("Error receiving: %v", err) 34 | } 35 | log.Printf("Got %s", rv.Payload) 36 | } 37 | rv, err = c.Receive() 38 | 39 | } 40 | log.Printf("Done...\n") 41 | 42 | } 43 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package coap 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | const RESPONSE_TIMEOUT = time.Second * 2 9 | 10 | const RESPONSE_RANDOM_FACTOR = 1.5 11 | 12 | const MAX_RETRANSMIT = 4 13 | 14 | // A CoAP client connection. 15 | type Conn struct { 16 | conn *net.UDPConn 17 | } 18 | 19 | // Get a CoAP client. 20 | func Dial(n, addr string) (*Conn, error) { 21 | uaddr, err := net.ResolveUDPAddr(n, addr) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | s, err := net.DialUDP("udp", nil, uaddr) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | return &Conn{s}, nil 32 | } 33 | 34 | // Duration a message. Get a response if there is one. 35 | func (c *Conn) Send(req Message) (*Message, error) { 36 | err := Transmit(c.conn, nil, req) 37 | if err == nil { 38 | return nil, err 39 | } 40 | 41 | rv, err := Receive(c.conn) 42 | 43 | return &rv, nil 44 | } 45 | 46 | // Receive a message. 47 | func (c *Conn) Receive() (*Message, error) { 48 | rv, err := Receive(c.conn) 49 | return &rv, err 50 | } 51 | -------------------------------------------------------------------------------- /example/subserver/subserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | "github.com/dustin/go-coap" 10 | ) 11 | 12 | func periodicTransmitter(l *net.UDPConn, a *net.UDPAddr, m coap.Message) { 13 | subded := time.Now() 14 | 15 | for { 16 | msg := coap.Message{ 17 | Type: coap.Acknowledgement, 18 | Code: coap.Content, 19 | MessageID: m.MessageID, 20 | Options: coap.Options{ 21 | {coap.ContentType, []byte{byte(coap.TextPlain)}}, 22 | {coap.LocationPath, m.Path()}, 23 | }, 24 | Payload: []byte(fmt.Sprintf("Been running for %v", time.Since(subded))), 25 | } 26 | 27 | log.Printf("Transmitting %v", msg) 28 | err := coap.Transmit(l, a, msg) 29 | if err != nil { 30 | log.Printf("Error on transmitter, stopping: %v", err) 31 | return 32 | } 33 | 34 | time.Sleep(time.Second) 35 | } 36 | } 37 | 38 | func main() { 39 | log.Fatal(coap.ListenAndServe("udp", ":5683", 40 | coap.FuncHandler(func(l *net.UDPConn, a *net.UDPAddr, m coap.Message) *coap.Message { 41 | log.Printf("Got message path=%q: %#v from %v", m.Path(), m, a) 42 | if m.Code == coap.SUBSCRIBE { 43 | go periodicTransmitter(l, a, m) 44 | } 45 | return nil 46 | }))) 47 | } 48 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | // CoAP Client and Server in Go 2 | package coap 3 | 4 | import ( 5 | "log" 6 | "net" 7 | "time" 8 | ) 9 | 10 | const maxPktLen = 1500 11 | 12 | // Handle CoAP messages. 13 | type RequestHandler interface { 14 | // Handle the message and optionally return a response message. 15 | Handle(l *net.UDPConn, a *net.UDPAddr, m Message) *Message 16 | } 17 | 18 | type funcHandler func(l *net.UDPConn, a *net.UDPAddr, m Message) *Message 19 | 20 | func (f funcHandler) Handle(l *net.UDPConn, a *net.UDPAddr, m Message) *Message { 21 | return f(l, a, m) 22 | } 23 | 24 | // Build a handler from a function. 25 | func FuncHandler(f func(l *net.UDPConn, a *net.UDPAddr, m Message) *Message) RequestHandler { 26 | return funcHandler(f) 27 | } 28 | 29 | func handlePacket(l *net.UDPConn, data []byte, u *net.UDPAddr, 30 | rh RequestHandler) { 31 | 32 | msg, err := parseMessage(data) 33 | if err != nil { 34 | log.Printf("Error parsing %v", err) 35 | return 36 | } 37 | 38 | rv := rh.Handle(l, u, msg) 39 | if rv != nil { 40 | log.Printf("Transmitting %#v", rv) 41 | Transmit(l, u, *rv) 42 | } 43 | } 44 | 45 | // Transmit a message. 46 | func Transmit(l *net.UDPConn, a *net.UDPAddr, m Message) error { 47 | d, err := encodeMessage(m) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | if a == nil { 53 | _, err = l.Write(d) 54 | } else { 55 | _, err = l.WriteTo(d, a) 56 | } 57 | if err != nil { 58 | return err 59 | } 60 | return err 61 | } 62 | 63 | // Receive a message. 64 | func Receive(l *net.UDPConn) (Message, error) { 65 | l.SetReadDeadline(time.Now().Add(RESPONSE_TIMEOUT)) 66 | 67 | data := make([]byte, maxPktLen) 68 | nr, _, err := l.ReadFromUDP(data) 69 | if err != nil { 70 | return Message{}, err 71 | } 72 | return parseMessage(data[:nr]) 73 | } 74 | 75 | // Bind to the given address and serve requests forever. 76 | func ListenAndServe(n, addr string, rh RequestHandler) error { 77 | uaddr, err := net.ResolveUDPAddr(n, addr) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | l, err := net.ListenUDP(n, uaddr) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | for { 88 | buf := make([]byte, maxPktLen) 89 | 90 | nr, addr, err := l.ReadFromUDP(buf) 91 | if err == nil { 92 | go handlePacket(l, buf[:nr], addr, rh) 93 | } 94 | } 95 | 96 | panic("Unreachable") 97 | } 98 | -------------------------------------------------------------------------------- /message_test.go: -------------------------------------------------------------------------------- 1 | package coap 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestEncodeMessageSmall(t *testing.T) { 10 | req := Message{ 11 | Type: Confirmable, 12 | Code: GET, 13 | MessageID: 12345, 14 | Options: Options{ 15 | Option{ETag, []byte("weetag")}, 16 | Option{MaxAge, 3}, 17 | }, 18 | } 19 | 20 | data, err := encodeMessage(req) 21 | if err != nil { 22 | t.Fatalf("Error encoding request: %v", err) 23 | } 24 | 25 | // Inspected by hand. 26 | exp := []byte{ 27 | 0x42, 0x1, 0x30, 0x39, 0x21, 0x3, 28 | 0x26, 0x77, 0x65, 0x65, 0x74, 0x61, 0x67, 29 | } 30 | if !reflect.DeepEqual(exp, data) { 31 | t.Fatalf("Expected\n%#v\ngot\n%#v", exp, data) 32 | } 33 | } 34 | 35 | func TestEncodeMessageVerySmall(t *testing.T) { 36 | req := Message{ 37 | Type: Confirmable, 38 | Code: GET, 39 | MessageID: 12345, 40 | } 41 | req.SetPathString("x") 42 | 43 | data, err := encodeMessage(req) 44 | if err != nil { 45 | t.Fatalf("Error encoding request: %v", err) 46 | } 47 | 48 | // Inspected by hand. 49 | exp := []byte{ 50 | 0x41, 0x1, 0x30, 0x39, 0x91, 0x78, 51 | } 52 | if !reflect.DeepEqual(exp, data) { 53 | t.Fatalf("Expected\n%#v\ngot\n%#v", exp, data) 54 | } 55 | } 56 | 57 | func TestEncodeSeveral(t *testing.T) { 58 | tests := map[string][]string{ 59 | "a": []string{"a"}, 60 | "axe": []string{"axe"}, 61 | "a/b/c/d/e/f/h/g/i/j": []string{"a", "b", "c", "d", "e", 62 | "f", "h", "g", "i", "j"}, 63 | } 64 | for p, a := range tests { 65 | m := &Message{Type: Confirmable, Code: GET, MessageID: 12345} 66 | m.SetPathString(p) 67 | b, err := encodeMessage(*m) 68 | if err != nil { 69 | t.Errorf("Error encoding %#v", p) 70 | t.Fail() 71 | continue 72 | } 73 | m2, err := parseMessage(b) 74 | if err != nil { 75 | t.Fatalf("Can't parse my own message at %#v: %v", p, err) 76 | } 77 | 78 | if !reflect.DeepEqual(m2.Path(), a) { 79 | t.Errorf("Expected %#v, got %#v", a, m2.Path()) 80 | t.Fail() 81 | } 82 | } 83 | } 84 | 85 | func TestEncodeLargePath(t *testing.T) { 86 | req := Message{ 87 | Type: Confirmable, 88 | Code: GET, 89 | MessageID: 12345, 90 | } 91 | req.SetPathString("this_path_is_longer_than_fifteen_bytes") 92 | 93 | if req.PathString() != "this_path_is_longer_than_fifteen_bytes" { 94 | t.Fatalf("Didn't get back the same path I posted: %v", 95 | req.PathString()) 96 | } 97 | 98 | data, err := encodeMessage(req) 99 | if err != nil { 100 | t.Fatalf("Error encoding request: %v", err) 101 | } 102 | 103 | // Inspected by hand. 104 | exp := []byte{ 105 | 0x41, 0x1, 0x30, 0x39, 0x9f, 0x17, 0x74, 0x68, 106 | 0x69, 0x73, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 107 | 0x69, 0x73, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x65, 108 | 0x72, 0x5f, 0x74, 0x68, 0x61, 0x6e, 0x5f, 0x66, 109 | 0x69, 0x66, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x62, 110 | 0x79, 0x74, 0x65, 0x73} 111 | if !reflect.DeepEqual(exp, data) { 112 | t.Fatalf("Expected\n%#v\ngot\n%#v", exp, data) 113 | } 114 | } 115 | 116 | func TestDecodeLargePath(t *testing.T) { 117 | data := []byte{ 118 | 0x41, 0x1, 0x30, 0x39, 0x9f, 0x17, 0x74, 0x68, 119 | 0x69, 0x73, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 120 | 0x69, 0x73, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x65, 121 | 0x72, 0x5f, 0x74, 0x68, 0x61, 0x6e, 0x5f, 0x66, 122 | 0x69, 0x66, 0x74, 0x65, 0x65, 0x6e, 0x5f, 0x62, 123 | 0x79, 0x74, 0x65, 0x73} 124 | 125 | req, err := parseMessage(data) 126 | if err != nil { 127 | t.Fatalf("Error parsing request: %v", err) 128 | } 129 | 130 | path := "this_path_is_longer_than_fifteen_bytes" 131 | 132 | exp := Message{ 133 | Type: Confirmable, 134 | Code: GET, 135 | MessageID: 12345, 136 | Options: Options{ 137 | {URIPath, path}, 138 | }, 139 | } 140 | 141 | if fmt.Sprintf("%#v", exp) != fmt.Sprintf("%#v", req) { 142 | t.Fatalf("Expected\n%#v\ngot\n%#v", exp, req) 143 | } 144 | } 145 | 146 | func TestDecodeMessageSmaller(t *testing.T) { 147 | data := []byte{ 148 | 0x42, 0x1, 0x30, 0x39, 0x21, 0x3, 149 | 0x26, 0x77, 0x65, 0x65, 0x74, 0x61, 0x67, 150 | } 151 | 152 | req, err := parseMessage(data) 153 | if err != nil { 154 | t.Fatalf("Error parsing request: %v", err) 155 | } 156 | 157 | exp := Message{ 158 | Type: Confirmable, 159 | Code: GET, 160 | MessageID: 12345, 161 | Options: Options{ 162 | Option{MaxAge, uint32(3)}, 163 | Option{ETag, []byte("weetag")}, 164 | }, 165 | } 166 | 167 | if fmt.Sprintf("%#v", exp) != fmt.Sprintf("%#v", req) { 168 | t.Fatalf("Expected\n%#v\ngot\n%#v", exp, req) 169 | } 170 | } 171 | 172 | func TestByteEncoding(t *testing.T) { 173 | tests := []struct { 174 | Value uint32 175 | Expected []byte 176 | }{ 177 | {0, []byte{}}, 178 | {13, []byte{13}}, 179 | {1024, []byte{4, 0}}, 180 | {984284, []byte{0x0f, 0x04, 0xdc}}, 181 | {823958824, []byte{0x31, 0x1c, 0x9d, 0x28}}, 182 | } 183 | 184 | for _, v := range tests { 185 | got := encodeInt(v.Value) 186 | if !reflect.DeepEqual(got, v.Expected) { 187 | t.Fatalf("Expected %#v, got %#v for %v", 188 | v.Expected, got, v.Value) 189 | } 190 | } 191 | } 192 | 193 | func TestByteDecoding(t *testing.T) { 194 | tests := []struct { 195 | Value uint32 196 | Bytes []byte 197 | }{ 198 | {0, []byte{}}, 199 | {0, []byte{0}}, 200 | {0, []byte{0, 0}}, 201 | {0, []byte{0, 0, 0}}, 202 | {0, []byte{0, 0, 0, 0}}, 203 | {13, []byte{13}}, 204 | {13, []byte{0, 13}}, 205 | {13, []byte{0, 0, 13}}, 206 | {13, []byte{0, 0, 0, 13}}, 207 | {1024, []byte{4, 0}}, 208 | {1024, []byte{4, 0}}, 209 | {1024, []byte{0, 4, 0}}, 210 | {1024, []byte{0, 0, 4, 0}}, 211 | {984284, []byte{0x0f, 0x04, 0xdc}}, 212 | {984284, []byte{0, 0x0f, 0x04, 0xdc}}, 213 | {823958824, []byte{0x31, 0x1c, 0x9d, 0x28}}, 214 | } 215 | 216 | for _, v := range tests { 217 | got := decodeInt(v.Bytes) 218 | if v.Value != got { 219 | t.Fatalf("Expected %v, got %v for %#v", 220 | v.Value, got, v.Bytes) 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /message.go: -------------------------------------------------------------------------------- 1 | package coap 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "sort" 9 | "strings" 10 | ) 11 | 12 | type COAPType uint8 13 | 14 | const ( 15 | Confirmable = COAPType(0) 16 | NonConfirmable = COAPType(1) 17 | Acknowledgement = COAPType(2) 18 | Reset = COAPType(3) 19 | ) 20 | 21 | const ( 22 | GET = 1 23 | POST = 2 24 | PUT = 3 25 | DELETE = 4 26 | SUBSCRIBE = 5 27 | ) 28 | 29 | const ( 30 | Created = 65 31 | Deleted = 66 32 | Valid = 67 33 | Changed = 68 34 | Content = 69 35 | BadRequest = 128 36 | Unauthorized = 129 37 | BadOption = 130 38 | Forbidden = 131 39 | NotFound = 132 40 | MethodNotAllowed = 133 41 | NotAcceptable = 134 42 | PreconditionFailed = 140 43 | RequestEntityTooLarge = 141 44 | UnsupportedMediaType = 143 45 | InternalServerError = 160 46 | NotImplemented = 161 47 | BadGateway = 162 48 | ServiceUnavailable = 163 49 | GatewayTimeout = 164 50 | ProxyingNotSupported = 165 51 | ) 52 | 53 | var TooManyOptions = errors.New("Too many options") 54 | var OptionTooLong = errors.New("Option is too long") 55 | 56 | type OptionID uint8 57 | 58 | const ( 59 | ContentType = OptionID(1) 60 | MaxAge = OptionID(2) 61 | ProxyURI = OptionID(3) 62 | ETag = OptionID(4) 63 | URIHost = OptionID(5) 64 | LocationPath = OptionID(6) 65 | URIPort = OptionID(7) 66 | LocationQuery = OptionID(8) 67 | URIPath = OptionID(9) 68 | Token = OptionID(11) 69 | Accept = OptionID(12) 70 | IfMatch = OptionID(13) 71 | UriQuery = OptionID(15) 72 | IfNoneMatch = OptionID(21) 73 | ) 74 | 75 | type MediaType byte 76 | 77 | const ( 78 | TextPlain = MediaType(0) // text/plain;charset=utf-8 79 | AppLinkFormat = MediaType(40) // application/link-format 80 | AppXML = MediaType(41) // application/xml 81 | AppOctets = MediaType(42) // application/octet-stream 82 | AppExi = MediaType(47) // application/exi 83 | AppJSON = MediaType(50) // application/json 84 | ) 85 | 86 | /* 87 | +-----+---+---+----------------+--------+---------+-------------+ 88 | | No. | C | R | Name | Format | Length | Default | 89 | +-----+---+---+----------------+--------+---------+-------------+ 90 | | 1 | x | | Content-Type | uint | 0-2 B | (none) | 91 | | 2 | | | Max-Age | uint | 0-4 B | 60 | 92 | | 3 | x | x | Proxy-Uri | string | 1-270 B | (none) | 93 | | 4 | | x | ETag | opaque | 1-8 B | (none) | 94 | | 5 | x | | Uri-Host | string | 1-270 B | (see below) | 95 | | 6 | | x | Location-Path | string | 0-270 B | (none) | 96 | | 7 | x | | Uri-Port | uint | 0-2 B | (see below) | 97 | | 8 | | x | Location-Query | string | 0-270 B | (none) | 98 | | 9 | x | x | Uri-Path | string | 0-270 B | (none) | 99 | | 11 | x | | Token | opaque | 1-8 B | (empty) | 100 | | 12 | | x | Accept | uint | 0-2 B | (none) | 101 | | 13 | x | x | If-Match | opaque | 0-8 B | (none) | 102 | | 15 | x | x | Uri-Query | string | 0-270 B | (none) | 103 | | 21 | x | | If-None-Match | empty | 0 B | (none) | 104 | +-----+---+---+----------------+--------+---------+-------------+ 105 | */ 106 | 107 | type Option struct { 108 | ID OptionID 109 | Value interface{} 110 | } 111 | 112 | func encodeInt(v uint32) []byte { 113 | switch { 114 | case v == 0: 115 | return []byte{} 116 | case v < 256: 117 | return []byte{byte(v)} 118 | case v < 65536: 119 | rv := []byte{0, 0} 120 | binary.BigEndian.PutUint16(rv, uint16(v)) 121 | return rv 122 | case v < 16777216: 123 | rv := []byte{0, 0, 0, 0} 124 | binary.BigEndian.PutUint32(rv, uint32(v)) 125 | return rv[1:] 126 | default: 127 | rv := []byte{0, 0, 0, 0} 128 | binary.BigEndian.PutUint32(rv, uint32(v)) 129 | return rv 130 | } 131 | panic("Has to be one of those") 132 | } 133 | 134 | func decodeInt(b []byte) uint32 { 135 | tmp := []byte{0, 0, 0, 0} 136 | copy(tmp[4-len(b):], b) 137 | return binary.BigEndian.Uint32(tmp) 138 | } 139 | 140 | func (o Option) toBytes() []byte { 141 | var v uint32 142 | 143 | switch i := o.Value.(type) { 144 | case string: 145 | return []byte(i) 146 | case []byte: 147 | return i 148 | case int: 149 | v = uint32(i) 150 | case int32: 151 | v = uint32(i) 152 | case uint: 153 | v = uint32(i) 154 | case uint32: 155 | v = i 156 | default: 157 | panic(fmt.Errorf("Invalid type for option %x", o.ID)) 158 | } 159 | 160 | return encodeInt(v) 161 | } 162 | 163 | type Options []Option 164 | 165 | func (o Options) Len() int { 166 | return len(o) 167 | } 168 | 169 | func (o Options) Less(i, j int) bool { 170 | if o[i].ID == o[j].ID { 171 | return i < j 172 | } 173 | return o[i].ID < o[j].ID 174 | } 175 | 176 | func (o Options) Swap(i, j int) { 177 | o[i], o[j] = o[j], o[i] 178 | } 179 | 180 | func (o Options) Minus(oid OptionID) Options { 181 | rv := Options{} 182 | for _, opt := range o { 183 | if opt.ID != oid { 184 | rv = append(rv, opt) 185 | } 186 | } 187 | return rv 188 | } 189 | 190 | // A CoAP message. 191 | type Message struct { 192 | Type COAPType 193 | Code uint8 194 | MessageID uint16 195 | 196 | Options Options 197 | 198 | Payload []byte 199 | } 200 | 201 | // Return True if this message is confirmable. 202 | func (m Message) IsConfirmable() bool { 203 | return m.Type == Confirmable 204 | } 205 | 206 | // Get the Path set on this message if any. 207 | func (m Message) Path() []string { 208 | rv := []string{} 209 | for _, o := range m.Options { 210 | if o.ID == URIPath { 211 | rv = append(rv, o.Value.(string)) 212 | } 213 | } 214 | return rv 215 | } 216 | 217 | // Get a path as a / separated string. 218 | func (m Message) PathString() string { 219 | return strings.Join(m.Path(), "/") 220 | } 221 | 222 | // Set a path by a / separated string. 223 | func (m *Message) SetPathString(s string) { 224 | m.SetPath(strings.Split(s, "/")) 225 | } 226 | 227 | // Update or add a LocationPath attribute on this message. 228 | func (m *Message) SetPath(s []string) { 229 | m.Options = m.Options.Minus(URIPath) 230 | for _, p := range s { 231 | m.Options = append(m.Options, Option{URIPath, p}) 232 | } 233 | } 234 | 235 | func encodeMessage(r Message) ([]byte, error) { 236 | if len(r.Options) > 14 { 237 | return []byte{}, TooManyOptions 238 | } 239 | 240 | tmpbuf := []byte{0, 0} 241 | binary.BigEndian.PutUint16(tmpbuf, r.MessageID) 242 | 243 | /* 244 | 0 1 2 3 245 | 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 246 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 247 | |Ver| T | OC | Code | Message ID | 248 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 249 | | Options (if any) ... 250 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 251 | | Payload (if any) ... 252 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 253 | */ 254 | 255 | buf := bytes.Buffer{} 256 | buf.Write([]byte{ 257 | (1 << 6) | (uint8(r.Type) << 4) | uint8(0xf&len(r.Options)), 258 | byte(r.Code), 259 | tmpbuf[0], tmpbuf[1], 260 | }) 261 | 262 | /* 263 | 0 1 2 3 4 5 6 7 264 | +---+---+---+---+---+---+---+---+ 265 | | Option Delta | Length | for 0..14 266 | +---+---+---+---+---+---+---+---+ 267 | | Option Value ... 268 | +---+---+---+---+---+---+---+---+ 269 | for 15..270: 270 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 271 | | Option Delta | 1 1 1 1 | Length - 15 | 272 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 273 | | Option Value ... 274 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 275 | */ 276 | 277 | sort.Sort(&r.Options) 278 | 279 | prev := 0 280 | for _, o := range r.Options { 281 | b := o.toBytes() 282 | if len(b) > 15 { 283 | buf.Write([]byte{ 284 | byte(int(o.ID)-prev)<<4 | 15, 285 | byte(len(b) - 15), 286 | }) 287 | } else { 288 | buf.Write([]byte{byte(int(o.ID)-prev)<<4 | byte(len(b))}) 289 | } 290 | if int(o.ID)-prev > 15 { 291 | return []byte{}, errors.New("Gap too large") 292 | } 293 | 294 | buf.Write(b) 295 | prev = int(o.ID) 296 | } 297 | 298 | buf.Write(r.Payload) 299 | 300 | return buf.Bytes(), nil 301 | } 302 | 303 | func parseMessage(data []byte) (rv Message, err error) { 304 | if len(data) < 6 { 305 | return rv, errors.New("Short packet") 306 | } 307 | 308 | if data[0]>>6 != 1 { 309 | return rv, errors.New("Invalid version") 310 | } 311 | 312 | rv.Type = COAPType((data[0] >> 4) & 0x3) 313 | opCount := int(data[0] & 0xf) 314 | if opCount > 14 { 315 | return rv, TooManyOptions 316 | } 317 | 318 | rv.Code = data[1] 319 | rv.MessageID = binary.BigEndian.Uint16(data[2:4]) 320 | 321 | b := data[4:] 322 | prev := 0 323 | for i := 0; i < opCount && len(b) > 0; i++ { 324 | oid := OptionID(prev + int(b[0]>>4)) 325 | l := int(b[0] & 0xf) 326 | b = b[1:] 327 | if l > 14 { 328 | l += int(b[0]) 329 | b = b[1:] 330 | } 331 | if len(b) < l { 332 | return rv, errors.New("Truncated") 333 | } 334 | var opval interface{} = b[:l] 335 | switch oid { 336 | case ContentType, 337 | MaxAge, 338 | URIPort, 339 | Accept: 340 | opval = decodeInt(b[:l]) 341 | case ProxyURI, URIHost, LocationPath, LocationQuery, URIPath, UriQuery: 342 | opval = string(b[:l]) 343 | } 344 | 345 | option := Option{ 346 | ID: oid, 347 | Value: opval, 348 | } 349 | b = b[l:] 350 | prev = int(option.ID) 351 | 352 | rv.Options = append(rv.Options, option) 353 | } 354 | 355 | rv.Payload = b 356 | return rv, nil 357 | } 358 | --------------------------------------------------------------------------------