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) }