123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- package coap
- import (
- "bytes"
- "encoding/binary"
- "errors"
- "fmt"
- "reflect"
- "sort"
- "strconv"
- "strings"
- )
- // COAPType 代表消息类型
- type COAPType uint8
- // MaxTokenSize 最大token size
- const MaxTokenSize = 8
- const (
- // CON 需要被确认的请求
- CON COAPType = 0
- // NON 不需要被确认的请求
- NON COAPType = 1
- // ACK 应答消息,接受到CON消息的响应
- ACK COAPType = 2
- // RST 复位消息,当接收者接受到的消息包含一个错误,接受者解析消息或者不再关心发送者发送的内容,那么复位消息将会被发送
- RST COAPType = 3
- )
- // COAPCode 请求方法的类型
- type COAPCode uint8
- // request codes
- const (
- GET COAPCode = iota + 1
- POST
- PUT
- DELETE
- )
- // Response codes
- const (
- Empty COAPCode = 0
- Created COAPCode = 65
- Deleted COAPCode = 66
- Valid COAPCode = 67
- Changed COAPCode = 68
- Content COAPCode = 69
- Continue COAPCode = 95
- BadRequest COAPCode = 128
- Unauthorized COAPCode = 129
- BadOption COAPCode = 130
- Forbidden COAPCode = 131
- NotFound COAPCode = 132
- MethodNotAllowed COAPCode = 133
- NotAcceptable COAPCode = 134
- RequestEntityIncomplete COAPCode = 136
- PreconditionFailed COAPCode = 140
- RequestEntityTooLarge COAPCode = 141
- UnsupportedMediaType COAPCode = 143
- InternalServerError COAPCode = 160
- NotImplemented COAPCode = 161
- BadGateway COAPCode = 162
- ServiceUnavailable COAPCode = 163
- GatewayTimeout COAPCode = 164
- ProxyingNotSupported COAPCode = 165
- )
- // MediaType 请求消息的媒体类型 对应Content-Format
- type MediaType uint16
- // Content formats.
- const (
- TextPlain MediaType = 0 // text/plain;charset=utf-8
- AppXML MediaType = 41 // application/xml
- AppOctets MediaType = 42 // application/octet-stream
- AppExi MediaType = 47 // application/exi
- AppJSON MediaType = 50 // application/json
- AppCBOR MediaType = 60 //application/cbor (RFC 7049)
- )
- func (c MediaType) String() string {
- switch c {
- case TextPlain:
- return "text/plain;charset=utf-8"
- case AppXML:
- return "application/xml"
- case AppOctets:
- return "application/octet-stream"
- case AppExi:
- return "application/exi"
- case AppJSON:
- return "application/json"
- case AppCBOR:
- return "application/cbor (RFC 7049)"
- }
- return "Unknown media type: 0x" + strconv.FormatInt(int64(c), 16)
- }
- // OptionID Option编号
- type OptionID uint8
- // Option IDs.
- const (
- IfMatch OptionID = 1
- URIHost OptionID = 3
- ETag OptionID = 4
- IfNoneMatch OptionID = 5
- Observe OptionID = 6
- URIPort OptionID = 7
- LocationPath OptionID = 8
- URIPath OptionID = 11
- ContentFormat OptionID = 12
- MaxAge OptionID = 14
- URIQuery OptionID = 15
- Accept OptionID = 17
- LocationQuery OptionID = 20
- Block2 OptionID = 23
- Block1 OptionID = 27
- Size2 OptionID = 28
- ProxyURI OptionID = 35
- ProxyScheme OptionID = 39
- Size1 OptionID = 60
- )
- // Option value format (RFC7252 section 3.2)
- type valueFormat uint8
- const (
- valueUnknown valueFormat = iota
- valueEmpty
- valueOpaque
- valueUint
- valueString
- )
- type optionDef struct {
- valueFormat valueFormat
- minLen int
- maxLen int
- }
- var coapOptionDefs = map[OptionID]optionDef{
- IfMatch: optionDef{valueFormat: valueOpaque, minLen: 0, maxLen: 8},
- URIHost: optionDef{valueFormat: valueString, minLen: 1, maxLen: 255},
- ETag: optionDef{valueFormat: valueOpaque, minLen: 1, maxLen: 8},
- IfNoneMatch: optionDef{valueFormat: valueEmpty, minLen: 0, maxLen: 0},
- Observe: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 3},
- URIPort: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 2},
- LocationPath: optionDef{valueFormat: valueString, minLen: 0, maxLen: 255},
- URIPath: optionDef{valueFormat: valueString, minLen: 0, maxLen: 255},
- ContentFormat: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 2},
- MaxAge: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 4},
- URIQuery: optionDef{valueFormat: valueString, minLen: 0, maxLen: 255},
- Accept: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 2},
- LocationQuery: optionDef{valueFormat: valueString, minLen: 0, maxLen: 255},
- Block2: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 3},
- Block1: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 3},
- Size2: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 4},
- ProxyURI: optionDef{valueFormat: valueString, minLen: 1, maxLen: 1034},
- ProxyScheme: optionDef{valueFormat: valueString, minLen: 1, maxLen: 255},
- Size1: optionDef{valueFormat: valueUint, minLen: 0, maxLen: 4},
- }
- type option struct {
- ID OptionID
- Value interface{}
- }
- func encodeInt(v uint32) []byte {
- switch {
- case v == 0:
- return nil
- case v < 256:
- return []byte{byte(v)}
- case v < 65536:
- rv := []byte{0, 0}
- binary.BigEndian.PutUint16(rv, uint16(v))
- return rv
- case v < 16777216:
- rv := []byte{0, 0, 0, 0}
- binary.BigEndian.PutUint32(rv, uint32(v))
- return rv[1:]
- default:
- rv := []byte{0, 0, 0, 0}
- binary.BigEndian.PutUint32(rv, uint32(v))
- return rv
- }
- }
- func decodeInt(b []byte) uint32 {
- tmp := []byte{0, 0, 0, 0}
- copy(tmp[4-len(b):], b)
- return binary.BigEndian.Uint32(tmp)
- }
- func (o option) toBytes() []byte {
- var v uint32
- switch i := o.Value.(type) {
- case string:
- return []byte(i)
- case []byte:
- return i
- case MediaType:
- v = uint32(i)
- case int:
- v = uint32(i)
- case int32:
- v = uint32(i)
- case uint:
- v = uint32(i)
- case uint32:
- v = i
- default:
- panic(fmt.Errorf("invalid type for option %x: %T (%v)",
- o.ID, o.Value, o.Value))
- }
- return encodeInt(v)
- }
- type options []option
- func (o options) Len() int {
- return len(o)
- }
- func (o options) Less(i, j int) bool {
- if o[i].ID == o[j].ID {
- return i < j
- }
- return o[i].ID < o[j].ID
- }
- func (o options) Swap(i, j int) {
- o[i], o[j] = o[j], o[i]
- }
- func (o options) Remove(oid OptionID) options {
- idx := 0
- for i := 0; i < len(o); i++ {
- if o[i].ID != oid {
- o[idx] = o[i]
- idx++
- }
- }
- return o[:idx]
- }
- const (
- extoptByteCode = 13
- extoptByteAddend = 13
- extoptWordCode = 14
- extoptWordAddend = 269
- extoptError = 15
- )
- // Message interface
- type Message interface {
- Encode() ([]byte, error)
- Decode(data []byte) error
- Option(opid OptionID) interface{}
- Path() []string
- PathString() string
- SetPath([]string)
- SetPathString(s string)
- AddOption(opid OptionID, val interface{})
- RemoveOption(opid OptionID)
- IsConfirmable() bool
- OptionStrings(opid OptionID) []string
- GetMessageID() uint16
- GetToken() []byte
- GetCode() COAPCode
- GetPayload() []byte
- }
- // BaseMessage COAP 消息体
- type BaseMessage struct {
- Type COAPType
- Code COAPCode
- MessageID uint16
- Token []byte
- Payload []byte
- Opts options
- }
- // GetToken get token
- func (m *BaseMessage) GetToken() []byte {
- return m.Token
- }
- // GetMessageID get message id
- func (m *BaseMessage) GetMessageID() uint16 {
- return m.MessageID
- }
- // GetCode get code
- func (m *BaseMessage) GetCode() COAPCode {
- return m.Code
- }
- // Encode 消息打包
- func (m *BaseMessage) Encode() ([]byte, error) {
- tmpbuf := []byte{0, 0}
- binary.BigEndian.PutUint16(tmpbuf, m.MessageID)
- buf := bytes.Buffer{}
- buf.Write([]byte{
- (1 << 6) | (uint8(m.Type) << 4) | uint8(0xf&len(m.Token)),
- byte(m.Code),
- tmpbuf[0], tmpbuf[1],
- })
- buf.Write(m.Token)
- extendOpt := func(opt int) (int, int) {
- ext := 0
- if opt >= extoptByteAddend {
- if opt >= extoptWordAddend {
- ext = opt - extoptWordAddend
- opt = extoptWordCode
- } else {
- ext = opt - extoptByteAddend
- opt = extoptByteCode
- }
- }
- return opt, ext
- }
- writeOptHeader := func(delta, length int) {
- d, dx := extendOpt(delta)
- l, lx := extendOpt(length)
- buf.WriteByte(byte(d<<4) | byte(l))
- tmp := []byte{0, 0}
- writeExt := func(opt, ext int) {
- switch opt {
- case extoptByteCode:
- buf.WriteByte(byte(ext))
- case extoptWordCode:
- binary.BigEndian.PutUint16(tmp, uint16(ext))
- buf.Write(tmp)
- }
- }
- writeExt(d, dx)
- writeExt(l, lx)
- }
- sort.Stable(&m.Opts)
- prev := 0
- for _, o := range m.Opts {
- b := o.toBytes()
- writeOptHeader(int(o.ID)-prev, len(b))
- buf.Write(b)
- prev = int(o.ID)
- }
- if len(m.Payload) > 0 {
- buf.Write([]byte{0xff})
- }
- buf.Write(m.Payload)
- return buf.Bytes(), nil
- }
- // Decode 消息解包
- func (m *BaseMessage) Decode(data []byte) error {
- if len(data) < 4 {
- return errors.New("short packet")
- }
- if data[0]>>6 != 1 {
- return errors.New("invalid version")
- }
- m.Type = COAPType((data[0] >> 4) & 0x3)
- tokenLen := int(data[0] & 0xf)
- if tokenLen > 8 {
- return ErrInvalidTokenLen
- }
- m.Code = COAPCode(data[1])
- m.MessageID = binary.BigEndian.Uint16(data[2:4])
- if tokenLen > 0 {
- m.Token = make([]byte, tokenLen)
- }
- if len(data) < 4+tokenLen {
- return errors.New("truncated")
- }
- copy(m.Token, data[4:4+tokenLen])
- b := data[4+tokenLen:]
- prev := 0
- parseExtOpt := func(opt int) (int, error) {
- switch opt {
- case extoptByteCode:
- if len(b) < 1 {
- return -1, errors.New("truncated")
- }
- opt = int(b[0]) + extoptByteAddend
- b = b[1:]
- case extoptWordCode:
- if len(b) < 2 {
- return -1, errors.New("truncated")
- }
- opt = int(binary.BigEndian.Uint16(b[:2])) + extoptWordAddend
- b = b[2:]
- }
- return opt, nil
- }
- for len(b) > 0 {
- if b[0] == 0xff {
- b = b[1:]
- break
- }
- delta := int(b[0] >> 4)
- length := int(b[0] & 0x0f)
- if delta == extoptError || length == extoptError {
- return errors.New("unexpected extended option marker")
- }
- b = b[1:]
- delta, err := parseExtOpt(delta)
- if err != nil {
- return err
- }
- length, err = parseExtOpt(length)
- if err != nil {
- return err
- }
- if len(b) < length {
- return errors.New("truncated")
- }
- oid := OptionID(prev + delta)
- opval := parseOptionValue(oid, b[:length])
- b = b[length:]
- prev = int(oid)
- if opval != nil {
- m.Opts = append(m.Opts, option{ID: oid, Value: opval})
- }
- }
- m.Payload = b
- return nil
- }
- // IsConfirmable 如果是CON类型的消息类型,返回true
- func (m *BaseMessage) IsConfirmable() bool {
- return m.Type == CON
- }
- // GetPayload get payload
- func (m *BaseMessage) GetPayload() []byte {
- return m.Payload
- }
- // Option get option by id
- func (m *BaseMessage) Option(o OptionID) interface{} {
- for _, v := range m.Opts {
- if o == v.ID {
- return v.Value
- }
- }
- return nil
- }
- // Options 获取所的option value
- func (m *BaseMessage) Options(o OptionID) []interface{} {
- var rv []interface{}
- for _, v := range m.Opts {
- if o == v.ID {
- rv = append(rv, v.Value)
- }
- }
- return rv
- }
- // OptionStrings get option strings by id
- func (m *BaseMessage) OptionStrings(o OptionID) []string {
- var rv []string
- for _, o := range m.Options(o) {
- rv = append(rv, o.(string))
- }
- return rv
- }
- // Path 获取URIPath
- func (m *BaseMessage) Path() []string {
- return m.OptionStrings(URIPath)
- }
- // PathString gets a path as a / separated string.
- func (m *BaseMessage) PathString() string {
- return strings.Join(m.Path(), "/")
- }
- // SetPathString sets a path by a / separated string.
- func (m *BaseMessage) SetPathString(s string) {
- switch s {
- case "", "/":
- //root path is not set as option
- return
- default:
- if s[0] == '/' {
- s = s[1:]
- }
- m.SetPath(strings.Split(s, "/"))
- }
- }
- //RemoveOption remove a given opid
- func (m *BaseMessage) RemoveOption(opID OptionID) {
- m.Opts = m.Opts.Remove(opID)
- }
- // AddOption ``
- func (m *BaseMessage) AddOption(opID OptionID, val interface{}) {
- iv := reflect.ValueOf(val)
- if (iv.Kind() == reflect.Slice || iv.Kind() == reflect.Array) &&
- iv.Type().Elem().Kind() == reflect.String {
- for i := 0; i < iv.Len(); i++ {
- m.Opts = append(m.Opts, option{opID, iv.Index(i).Interface()})
- }
- return
- }
- m.Opts = append(m.Opts, option{opID, val})
- }
- // SetPath ``
- func (m *BaseMessage) SetPath(s []string) {
- m.SetOption(URIPath, s)
- }
- // SetOption sets an option, discarding any previous value
- func (m *BaseMessage) SetOption(opID OptionID, val interface{}) {
- m.RemoveOption(opID)
- m.AddOption(opID, val)
- }
- func parseOptionValue(optionID OptionID, valueBuf []byte) interface{} {
- def := coapOptionDefs[optionID]
- if def.valueFormat == valueUnknown {
- // Skip unrecognized options (RFC7252 section 5.4.1)
- return nil
- }
- if len(valueBuf) < def.minLen || len(valueBuf) > def.maxLen {
- // Skip options with illegal value length (RFC7252 section 5.4.3)
- return nil
- }
- switch def.valueFormat {
- case valueUint:
- intValue := decodeInt(valueBuf)
- if optionID == ContentFormat || optionID == Accept {
- return MediaType(intValue)
- }
- return intValue
- case valueString:
- return string(valueBuf)
- case valueOpaque, valueEmpty:
- return valueBuf
- }
- // Skip unrecognized options (should never be reached)
- return nil
- }
- // ParseMessage parse []byte to message
- func ParseMessage(data []byte) (Message, error) {
- rv := &BaseMessage{}
- return rv, rv.Decode(data)
- }
|