package codes import ( "errors" "strconv" ) var codeToString = map[Code]string{ Empty: "Empty", GET: "GET", POST: "POST", PUT: "PUT", DELETE: "DELETE", Created: "Created", Deleted: "Deleted", Valid: "Valid", Changed: "Changed", Content: "Content", BadRequest: "BadRequest", Unauthorized: "Unauthorized", BadOption: "BadOption", Forbidden: "Forbidden", NotFound: "NotFound", MethodNotAllowed: "MethodNotAllowed", NotAcceptable: "NotAcceptable", PreconditionFailed: "PreconditionFailed", RequestEntityTooLarge: "RequestEntityTooLarge", UnsupportedMediaType: "UnsupportedMediaType", TooManyRequests: "TooManyRequests", InternalServerError: "InternalServerError", NotImplemented: "NotImplemented", BadGateway: "BadGateway", ServiceUnavailable: "ServiceUnavailable", GatewayTimeout: "GatewayTimeout", ProxyingNotSupported: "ProxyingNotSupported", CSM: "Capabilities and Settings Messages", Ping: "Ping", Pong: "Pong", Release: "Release", Abort: "Abort", } func (c Code) String() string { val, ok := codeToString[c] if ok { return val } return "Code(" + strconv.FormatInt(int64(c), 10) + ")" } func ToCode(v string) (Code, error) { for key, val := range codeToString { if v == val { return key, nil } } return 0, errors.New("not found") }
package codes import ( "errors" "fmt" "strconv" "github.com/plgd-dev/go-coap/v3/pkg/math" ) // A Code is an unsigned 16-bit coap code as defined in the coap spec. type Code uint16 // Request Codes const ( GET Code = 1 POST Code = 2 PUT Code = 3 DELETE Code = 4 ) // Response Codes const ( Empty Code = 0 Created Code = 65 Deleted Code = 66 Valid Code = 67 Changed Code = 68 Content Code = 69 Continue Code = 95 BadRequest Code = 128 Unauthorized Code = 129 BadOption Code = 130 Forbidden Code = 131 NotFound Code = 132 MethodNotAllowed Code = 133 NotAcceptable Code = 134 RequestEntityIncomplete Code = 136 PreconditionFailed Code = 140 RequestEntityTooLarge Code = 141 UnsupportedMediaType Code = 143 TooManyRequests Code = 157 InternalServerError Code = 160 NotImplemented Code = 161 BadGateway Code = 162 ServiceUnavailable Code = 163 GatewayTimeout Code = 164 ProxyingNotSupported Code = 165 ) // Signaling Codes for TCP const ( CSM Code = 225 Ping Code = 226 Pong Code = 227 Release Code = 228 Abort Code = 229 ) const _maxCode = 255 var _maxCodeLen int var strToCode = map[string]Code{ `"GET"`: GET, `"POST"`: POST, `"PUT"`: PUT, `"DELETE"`: DELETE, `"Created"`: Created, `"Deleted"`: Deleted, `"Valid"`: Valid, `"Changed"`: Changed, `"Content"`: Content, `"BadRequest"`: BadRequest, `"Unauthorized"`: Unauthorized, `"BadOption"`: BadOption, `"Forbidden"`: Forbidden, `"NotFound"`: NotFound, `"MethodNotAllowed"`: MethodNotAllowed, `"NotAcceptable"`: NotAcceptable, `"PreconditionFailed"`: PreconditionFailed, `"RequestEntityTooLarge"`: RequestEntityTooLarge, `"UnsupportedMediaType"`: UnsupportedMediaType, `"TooManyRequests"`: TooManyRequests, `"InternalServerError"`: InternalServerError, `"NotImplemented"`: NotImplemented, `"BadGateway"`: BadGateway, `"ServiceUnavailable"`: ServiceUnavailable, `"GatewayTimeout"`: GatewayTimeout, `"ProxyingNotSupported"`: ProxyingNotSupported, `"Capabilities and Settings Messages"`: CSM, `"Ping"`: Ping, `"Pong"`: Pong, `"Release"`: Release, `"Abort"`: Abort, } func getMaxCodeLen() int { // max uint32 as string binary representation: "0b" + 32 digits maxLen := 34 for k := range strToCode { kLen := len(k) if kLen > maxLen { maxLen = kLen } } return maxLen } func init() { _maxCodeLen = getMaxCodeLen() } // UnmarshalJSON unmarshals b into the Code. func (c *Code) UnmarshalJSON(b []byte) error { // From json.Unmarshaler: By convention, to approximate the behavior of // Unmarshal itself, Unmarshalers implement UnmarshalJSON([]byte("null")) as // a no-op. if string(b) == "null" { return nil } if c == nil { return errors.New("nil receiver passed to UnmarshalJSON") } if len(b) > _maxCodeLen { return fmt.Errorf("invalid code: input too large(length=%d)", len(b)) } if ci, err := strconv.ParseUint(string(b), 10, 32); err == nil { if ci >= _maxCode { return fmt.Errorf("invalid code: %q", ci) } *c = math.CastTo[Code](ci) return nil } if jc, ok := strToCode[string(b)]; ok { *c = jc return nil } return fmt.Errorf("invalid code: %v", b) }
package message import ( "encoding/binary" "github.com/plgd-dev/go-coap/v3/pkg/math" ) func EncodeUint32(buf []byte, value uint32) (int, error) { switch { case value == 0: return 0, nil case value <= max1ByteNumber: if len(buf) < 1 { return 1, ErrTooSmall } buf[0] = byte(value) return 1, nil case value <= max2ByteNumber: if len(buf) < 2 { return 2, ErrTooSmall } binary.BigEndian.PutUint16(buf, math.CastTo[uint16](value)) return 2, nil case value <= max3ByteNumber: if len(buf) < 3 { return 3, ErrTooSmall } rv := make([]byte, 4) binary.BigEndian.PutUint32(rv, value) copy(buf, rv[1:]) return 3, nil default: if len(buf) < 4 { return 4, ErrTooSmall } binary.BigEndian.PutUint32(buf, value) return 4, nil } } func DecodeUint32(buf []byte) (uint32, int, error) { if len(buf) > 4 { buf = buf[:4] } tmp := []byte{0, 0, 0, 0} copy(tmp[4-len(buf):], buf) value := binary.BigEndian.Uint32(tmp) return value, len(buf), nil }
package message import ( "encoding/binary" "errors" "hash/crc64" "io" ) // GetETag calculates ETag from payload via CRC64 func GetETag(r io.ReadSeeker) ([]byte, error) { if r == nil { return make([]byte, 8), nil } c64 := crc64.New(crc64.MakeTable(crc64.ISO)) orig, err := r.Seek(0, io.SeekCurrent) if err != nil { return nil, err } _, err = r.Seek(0, io.SeekStart) if err != nil { return nil, err } buf := make([]byte, 4096) for { bufR := buf n, errR := r.Read(bufR) if errors.Is(errR, io.EOF) { break } if errR != nil { return nil, errR } bufR = bufR[:n] c64.Write(bufR) } _, err = r.Seek(orig, io.SeekStart) if err != nil { return nil, err } b := make([]byte, 8) binary.BigEndian.PutUint64(b, c64.Sum64()) return b, nil }
package message import ( "crypto/rand" "encoding/hex" "hash/crc64" ) type Token []byte func (t Token) String() string { return hex.EncodeToString(t) } func (t Token) Hash() uint64 { return crc64.Checksum(t, crc64.MakeTable(crc64.ISO)) } // GetToken generates a random token by a given length func GetToken() (Token, error) { b := make(Token, 8) _, err := rand.Read(b) // Note that err == nil only if we read len(b) bytes. if err != nil { return nil, err } return b, nil }
package message import ( "crypto/rand" "encoding/binary" "math" "sync/atomic" "time" pkgMath "github.com/plgd-dev/go-coap/v3/pkg/math" pkgRand "github.com/plgd-dev/go-coap/v3/pkg/rand" ) var weakRng = pkgRand.NewRand(time.Now().UnixNano()) var msgID = uint32(RandMID()) // GetMID generates a message id for UDP. (0 <= mid <= 65535) func GetMID() int32 { return int32(pkgMath.CastTo[uint16](atomic.AddUint32(&msgID, 1))) } func RandMID() int32 { b := make([]byte, 4) _, err := rand.Read(b) if err != nil { // fallback to cryptographically insecure pseudo-random generator return int32(pkgMath.CastTo[uint16](weakRng.Uint32() >> 16)) } return int32(pkgMath.CastTo[uint16](binary.BigEndian.Uint32(b))) } // ValidateMID validates a message id for UDP. (0 <= mid <= 65535) func ValidateMID(mid int32) bool { return mid >= 0 && mid <= math.MaxUint16 }
package message import ( "fmt" "github.com/plgd-dev/go-coap/v3/message/codes" ) // MaxTokenSize maximum of token size that can be used in message const MaxTokenSize = 8 type Message struct { Token Token Options Options Code codes.Code Payload []byte // For DTLS and UDP messages MessageID int32 // uint16 is valid, all other values are invalid, -1 is used for unset Type Type // uint8 is valid, all other values are invalid, -1 is used for unset } func (r *Message) String() string { if r == nil { return "nil" } buf := fmt.Sprintf("Code: %v, Token: %v", r.Code, r.Token) path, err := r.Options.Path() if err == nil { buf = fmt.Sprintf("%s, Path: %v", buf, path) } cf, err := r.Options.ContentFormat() if err == nil { buf = fmt.Sprintf("%s, ContentFormat: %v", buf, cf) } queries, err := r.Options.Queries() if err == nil { buf = fmt.Sprintf("%s, Queries: %+v", buf, queries) } if ValidateType(r.Type) { buf = fmt.Sprintf("%s, Type: %v", buf, r.Type) } if ValidateMID(r.MessageID) { buf = fmt.Sprintf("%s, MessageID: %v", buf, r.MessageID) } if len(r.Payload) > 0 { buf = fmt.Sprintf("%s, PayloadLen: %v", buf, len(r.Payload)) } return buf } // IsPing returns true if the message is a ping. func (r *Message) IsPing(isTCP bool) bool { if isTCP { return r.Code == codes.Ping } return r.Code == codes.Empty && r.Type == Confirmable && len(r.Token) == 0 && len(r.Options) == 0 && len(r.Payload) == 0 }
package message import ( "encoding/binary" "errors" "strconv" "github.com/plgd-dev/go-coap/v3/pkg/math" "golang.org/x/exp/constraints" ) const ( max1ByteNumber = uint32(^uint8(0)) max2ByteNumber = uint32(^uint16(0)) max3ByteNumber = uint32(0xffffff) ) const ( ExtendOptionByteCode = 13 ExtendOptionByteAddend = 13 ExtendOptionWordCode = 14 ExtendOptionWordAddend = 269 ExtendOptionError = 15 ) // OptionID identifies an option in a message. type OptionID uint16 /* +-----+----+---+---+---+----------------+--------+--------+---------+ | No. | C | U | N | R | Name | Format | Length | Default | +-----+----+---+---+---+----------------+--------+--------+---------+ | 1 | x | | | x | If-Match | opaque | 0-8 | (none) | | 3 | x | x | - | | Uri-Host | string | 1-255 | (see | | | | | | | | | | below) | | 4 | | | | x | ETag | opaque | 1-8 | (none) | | 5 | x | | | | If-None-Match | empty | 0 | (none) | | 7 | x | x | - | | Uri-Port | uint | 0-2 | (see | | | | | | | | | | below) | | 8 | | | | x | Location-Path | string | 0-255 | (none) | | 11 | x | x | - | x | Uri-Path | string | 0-255 | (none) | | 12 | | | | | Content-Format | uint | 0-2 | (none) | | 14 | | x | - | | Max-Age | uint | 0-4 | 60 | | 15 | x | x | - | x | Uri-Query | string | 0-255 | (none) | | 17 | x | | | | Accept | uint | 0-2 | (none) | | 20 | | | | x | Location-Query | string | 0-255 | (none) | | 23 | x | x | - | - | Block2 | uint | 0-3 | (none) | | 27 | x | x | - | - | Block1 | uint | 0-3 | (none) | | 28 | | | x | | Size2 | uint | 0-4 | (none) | | 35 | x | x | - | | Proxy-Uri | string | 1-1034 | (none) | | 39 | x | x | - | | Proxy-Scheme | string | 1-255 | (none) | | 60 | | | x | | Size1 | uint | 0-4 | (none) | +-----+----+---+---+---+----------------+--------+--------+---------+ C=Critical, U=Unsafe, N=NoCacheKey, R=Repeatable */ // 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 NoResponse OptionID = 258 ) var optionIDToString = map[OptionID]string{ IfMatch: "IfMatch", URIHost: "URIHost", ETag: "ETag", IfNoneMatch: "IfNoneMatch", Observe: "Observe", URIPort: "URIPort", LocationPath: "LocationPath", URIPath: "URIPath", ContentFormat: "ContentFormat", MaxAge: "MaxAge", URIQuery: "URIQuery", Accept: "Accept", LocationQuery: "LocationQuery", Block2: "Block2", Block1: "Block1", Size2: "Size2", ProxyURI: "ProxyURI", ProxyScheme: "ProxyScheme", Size1: "Size1", NoResponse: "NoResponse", } func (o OptionID) String() string { str, ok := optionIDToString[o] if !ok { return "Option(" + strconv.FormatInt(int64(o), 10) + ")" } return str } func ToOptionID(v string) (OptionID, error) { for key, val := range optionIDToString { if val == v { return key, nil } } return 0, errors.New("not found") } // Option value format (RFC7252 section 3.2) type ValueFormat uint8 const ( ValueUnknown ValueFormat = iota ValueEmpty ValueOpaque ValueUint ValueString ) type OptionDef struct { MinLen uint32 MaxLen uint32 ValueFormat ValueFormat } var CoapOptionDefs = map[OptionID]OptionDef{ IfMatch: {ValueFormat: ValueOpaque, MinLen: 0, MaxLen: 8}, URIHost: {ValueFormat: ValueString, MinLen: 1, MaxLen: 255}, ETag: {ValueFormat: ValueOpaque, MinLen: 1, MaxLen: 8}, IfNoneMatch: {ValueFormat: ValueEmpty, MinLen: 0, MaxLen: 0}, Observe: {ValueFormat: ValueUint, MinLen: 0, MaxLen: 3}, URIPort: {ValueFormat: ValueUint, MinLen: 0, MaxLen: 2}, LocationPath: {ValueFormat: ValueString, MinLen: 0, MaxLen: 255}, URIPath: {ValueFormat: ValueString, MinLen: 0, MaxLen: 255}, ContentFormat: {ValueFormat: ValueUint, MinLen: 0, MaxLen: 2}, MaxAge: {ValueFormat: ValueUint, MinLen: 0, MaxLen: 4}, URIQuery: {ValueFormat: ValueString, MinLen: 0, MaxLen: 255}, Accept: {ValueFormat: ValueUint, MinLen: 0, MaxLen: 2}, LocationQuery: {ValueFormat: ValueString, MinLen: 0, MaxLen: 255}, Block2: {ValueFormat: ValueUint, MinLen: 0, MaxLen: 3}, Block1: {ValueFormat: ValueUint, MinLen: 0, MaxLen: 3}, Size2: {ValueFormat: ValueUint, MinLen: 0, MaxLen: 4}, ProxyURI: {ValueFormat: ValueString, MinLen: 1, MaxLen: 1034}, ProxyScheme: {ValueFormat: ValueString, MinLen: 1, MaxLen: 255}, Size1: {ValueFormat: ValueUint, MinLen: 0, MaxLen: 4}, NoResponse: {ValueFormat: ValueUint, MinLen: 0, MaxLen: 1}, } // MediaType specifies the content format of a message. type MediaType uint16 // Content formats. var ( TextPlain MediaType // text/plain; charset=utf-8 AppCoseEncrypt0 MediaType = 16 // application/cose; cose-type="cose-encrypt0" (RFC 8152) AppCoseMac0 MediaType = 17 // application/cose; cose-type="cose-mac0" (RFC 8152) AppCoseSign1 MediaType = 18 // application/cose; cose-type="cose-sign1" (RFC 8152) AppLinkFormat MediaType = 40 // application/link-format AppXML MediaType = 41 // application/xml AppOctets MediaType = 42 // application/octet-stream AppExi MediaType = 47 // application/exi AppJSON MediaType = 50 // application/json AppJSONPatch MediaType = 51 // application/json-patch+json (RFC6902) AppJSONMergePatch MediaType = 52 // application/merge-patch+json (RFC7396) AppCBOR MediaType = 60 // application/cbor (RFC 7049) AppCWT MediaType = 61 // application/cwt AppCoseEncrypt MediaType = 96 // application/cose; cose-type="cose-encrypt" (RFC 8152) AppCoseMac MediaType = 97 // application/cose; cose-type="cose-mac" (RFC 8152) AppCoseSign MediaType = 98 // application/cose; cose-type="cose-sign" (RFC 8152) AppCoseKey MediaType = 101 // application/cose-key (RFC 8152) AppCoseKeySet MediaType = 102 // application/cose-key-set (RFC 8152) AppSenmlJSON MediaType = 110 // application/senml+json AppSenmlCbor MediaType = 112 // application/senml+cbor AppCoapGroup MediaType = 256 // coap-group+json (RFC 7390) AppSenmlEtchJSON MediaType = 320 // application/senml-etch+json AppSenmlEtchCbor MediaType = 322 // application/senml-etch+cbor AppOcfCbor MediaType = 10000 // application/vnd.ocf+cbor AppLwm2mTLV MediaType = 11542 // application/vnd.oma.lwm2m+tlv AppLwm2mJSON MediaType = 11543 // application/vnd.oma.lwm2m+json AppLwm2mCbor MediaType = 11544 // application/vnd.oma.lwm2m+cbor ) var mediaTypeToString = map[MediaType]string{ TextPlain: "text/plain; charset=utf-8", AppCoseEncrypt0: "application/cose; cose-type=\"cose-encrypt0\"", AppCoseMac0: "application/cose; cose-type=\"cose-mac0\"", AppCoseSign1: "application/cose; cose-type=\"cose-sign1\"", AppLinkFormat: "application/link-format", AppXML: "application/xml", AppOctets: "application/octet-stream", AppExi: "application/exi", AppJSON: "application/json", AppJSONPatch: "application/json-patch+json", AppJSONMergePatch: "application/merge-patch+json", AppCBOR: "application/cbor", AppCWT: "application/cwt", AppCoseEncrypt: "application/cose; cose-type=\"cose-encrypt\"", AppCoseMac: "application/cose; cose-type=\"cose-mac\"", AppCoseSign: "application/cose; cose-type=\"cose-sign\"", AppCoseKey: "application/cose-key", AppCoseKeySet: "application/cose-key-set", AppSenmlJSON: "application/senml+json", AppSenmlCbor: "application/senml+cbor", AppCoapGroup: "coap-group+json", AppSenmlEtchJSON: "application/senml-etch+json", AppSenmlEtchCbor: "application/senml-etch+cbor", AppOcfCbor: "application/vnd.ocf+cbor", AppLwm2mTLV: "application/vnd.oma.lwm2m+tlv", AppLwm2mJSON: "application/vnd.oma.lwm2m+json", AppLwm2mCbor: "application/vnd.oma.lwm2m+cbor", } func (c MediaType) String() string { str, ok := mediaTypeToString[c] if !ok { return "MediaType(" + strconv.FormatInt(int64(c), 10) + ")" } return str } func ToMediaType(v string) (MediaType, error) { for key, val := range mediaTypeToString { if val == v { return key, nil } } return 0, errors.New("not found") } func MediaTypeFromNumber[T constraints.Integer](v T) (MediaType, error) { mt, err := math.SafeCastTo[MediaType](v) if err != nil { return MediaType(0), err } _, ok := mediaTypeToString[mt] if !ok { return MediaType(0), errors.New("invalid value") } return mt, nil } func extendOpt(opt int) (int, int) { ext := 0 if opt >= ExtendOptionByteAddend { if opt >= ExtendOptionWordAddend { ext = opt - ExtendOptionWordAddend opt = ExtendOptionWordCode } else { ext = opt - ExtendOptionByteAddend opt = ExtendOptionByteCode } } return opt, ext } // VerifyOptLen checks whether valueLen is within (min, max) length limits for given option. func VerifyOptLen(optID OptionID, valueLen int) bool { def := CoapOptionDefs[optID] if valueLen < int(def.MinLen) || valueLen > int(def.MaxLen) { return false } return true } func marshalOptionHeaderExt(buf []byte, opt, ext int) (int, error) { switch opt { case ExtendOptionByteCode: if len(buf) > 0 { buf[0] = byte(ext) return 1, nil } return 1, ErrTooSmall case ExtendOptionWordCode: if len(buf) > 1 { binary.BigEndian.PutUint16(buf, math.CastTo[uint16](ext)) return 2, nil } return 2, ErrTooSmall } return 0, nil } func marshalOptionHeader(buf []byte, delta, length int) (int, error) { size := 0 d, dx := extendOpt(delta) l, lx := extendOpt(length) if len(buf) > 0 { buf[0] = byte(d<<4) | byte(l) size++ } else { buf = nil size++ } var lenBuf int var err error if buf == nil { lenBuf, err = marshalOptionHeaderExt(nil, d, dx) } else { lenBuf, err = marshalOptionHeaderExt(buf[size:], d, dx) } switch { case err == nil: case errors.Is(err, ErrTooSmall): buf = nil default: return -1, err } size += lenBuf if buf == nil { lenBuf, err = marshalOptionHeaderExt(nil, l, lx) } else { lenBuf, err = marshalOptionHeaderExt(buf[size:], l, lx) } switch { case err == nil: case errors.Is(err, ErrTooSmall): buf = nil default: return -1, err } size += lenBuf if buf == nil { return size, ErrTooSmall } return size, nil } type Option struct { Value []byte ID OptionID } func (o Option) MarshalValue(buf []byte) (int, error) { if len(buf) < len(o.Value) { return len(o.Value), ErrTooSmall } copy(buf, o.Value) return len(o.Value), nil } func (o *Option) UnmarshalValue(buf []byte) (int, error) { o.Value = buf return len(buf), nil } func (o Option) Marshal(buf []byte, previousID OptionID) (int, error) { /* 0 1 2 3 4 5 6 7 +---------------+---------------+ | | | | Option Delta | Option Length | 1 byte | | | +---------------+---------------+ \ \ / Option Delta / 0-2 bytes \ (extended) \ +-------------------------------+ \ \ / Option Length / 0-2 bytes \ (extended) \ +-------------------------------+ \ \ / / \ \ / Option Value / 0 or more bytes \ \ / / \ \ +-------------------------------+ */ delta := int(o.ID) - int(previousID) lenBuf, err := o.MarshalValue(nil) switch { case err == nil, errors.Is(err, ErrTooSmall): default: return -1, err } // header marshal lenBuf, err = marshalOptionHeader(buf, delta, lenBuf) switch { case err == nil: case errors.Is(err, ErrTooSmall): buf = nil default: return -1, err } length := lenBuf if buf == nil { lenBuf, err = o.MarshalValue(nil) } else { lenBuf, err = o.MarshalValue(buf[length:]) } switch { case err == nil: case errors.Is(err, ErrTooSmall): buf = nil default: return -1, err } length += lenBuf if buf == nil { return length, ErrTooSmall } return length, nil } func parseExtOpt(data []byte, opt int) (int, int, error) { processed := 0 switch opt { case ExtendOptionByteCode: if len(data) < 1 { return 0, -1, ErrOptionTruncated } opt = int(data[0]) + ExtendOptionByteAddend processed = 1 case ExtendOptionWordCode: if len(data) < 2 { return 0, -1, ErrOptionTruncated } opt = int(binary.BigEndian.Uint16(data[:2])) + ExtendOptionWordAddend processed = 2 } return processed, opt, nil } func (o *Option) Unmarshal(data []byte, optionDefs map[OptionID]OptionDef, optionID OptionID) (int, error) { if def, ok := optionDefs[optionID]; ok { if def.ValueFormat == ValueUnknown { // Skip unrecognized options (RFC7252 section 5.4.1) return len(data), nil } dataLen := math.CastTo[uint32](len(data)) if dataLen < def.MinLen || dataLen > def.MaxLen { // Skip options with illegal value length (RFC7252 section 5.4.3) return len(data), nil } } o.ID = optionID proc, err := o.UnmarshalValue(data) if err != nil { return -1, err } return proc, err }
package message import ( "errors" "fmt" "strings" "github.com/plgd-dev/go-coap/v3/pkg/math" ) // Options Container of COAP Options, It must be always sort'ed after modification. type Options []Option const maxPathValue = 255 // GetPathBufferSize gets the size of the buffer required to store path in URI-Path options. // // If the path cannot be stored an error is returned. func GetPathBufferSize(path string) (int, error) { size := 0 for start := 0; start < len(path); { subPath := path[start:] segmentSize := strings.Index(subPath, "/") if segmentSize == 0 { start++ continue } if segmentSize < 0 { segmentSize = len(subPath) } if segmentSize > maxPathValue { return -1, ErrInvalidValueLength } size += segmentSize start += segmentSize + 1 } return size, nil } func setPath(options Options, optionID OptionID, buf []byte, path string) (Options, int, error) { if len(path) == 0 { return options, 0, nil } o := options.Remove(optionID) if path[0] == '/' { path = path[1:] } requiredSize, err := GetPathBufferSize(path) if err != nil { return options, -1, err } if requiredSize > len(buf) { return options, -1, ErrTooSmall } encoded := 0 for start := 0; start < len(path); { subPath := path[start:] end := strings.Index(subPath, "/") if end == 0 { start++ continue } if end < 0 { end = len(subPath) } data := buf[encoded:] var enc int var err error o, enc, err = o.AddString(data, optionID, subPath[:end]) if err != nil { return o, -1, err } encoded += enc start += end + 1 } return o, encoded, nil } // SetPath splits path by '/' to URIPath options and copies it to buffer. // // Returns modified options, number of used buf bytes and error if occurs. // // @note the url encoded into URIHost, URIPort, URIPath is expected to be // absolute (https://www.rfc-editor.org/rfc/rfc7252.txt) func (options Options) SetPath(buf []byte, path string) (Options, int, error) { return setPath(options, URIPath, buf, path) } // SetLocationPath splits path by '/' to LocationPath options and copies it to buffer. // // Returns modified options, number of used buf bytes and error if occurs. // // @note the url encoded into LocationPath is expected to be // absolute (https://www.rfc-editor.org/rfc/rfc7252.txt) func (options Options) SetLocationPath(buf []byte, path string) (Options, int, error) { return setPath(options, LocationPath, buf, path) } func (options Options) path(buf []byte, id OptionID) (int, error) { firstIdx, lastIdx, err := options.Find(id) if err != nil { return -1, err } var needed int for i := firstIdx; i < lastIdx; i++ { needed += len(options[i].Value) needed++ } if len(buf) < needed { return needed, ErrTooSmall } for i := firstIdx; i < lastIdx; i++ { buf[0] = '/' buf = buf[1:] copy(buf, options[i].Value) buf = buf[len(options[i].Value):] } return needed, nil } // Path joins URIPath options by '/' to the buf. // // Returns number of used buf bytes or error when occurs. func (options Options) Path() (string, error) { buf := make([]byte, 32) m, err := options.path(buf, URIPath) if errors.Is(err, ErrTooSmall) { buf = append(buf, make([]byte, m)...) m, err = options.path(buf, URIPath) } if err != nil { return "", err } buf = buf[:m] return string(buf), nil } // LocationPath joins Location-Path options by '/' to the buf. // // Returns number of used buf bytes or error when occurs. func (options Options) LocationPath() (string, error) { buf := make([]byte, 32) m, err := options.path(buf, LocationPath) if errors.Is(err, ErrTooSmall) { buf = append(buf, make([]byte, m)...) m, err = options.path(buf, LocationPath) } if err != nil { return "", err } buf = buf[:m] return string(buf), nil } // SetString replaces/stores string option to options. // // Returns modified options, number of used buf bytes and error if occurs. func (options Options) SetString(buf []byte, id OptionID, str string) (Options, int, error) { data := []byte(str) return options.SetBytes(buf, id, data) } // AddString appends string option to options. // // Returns modified options, number of used buf bytes and error if occurs. func (options Options) AddString(buf []byte, id OptionID, str string) (Options, int, error) { data := []byte(str) return options.AddBytes(buf, id, data) } // HasOption returns true is option exist in options. func (options Options) HasOption(id OptionID) bool { _, _, err := options.Find(id) return err == nil } // GetUint32s gets all options with same id. func (options Options) GetUint32s(id OptionID, r []uint32) (int, error) { firstIdx, lastIdx, err := options.Find(id) if err != nil { return 0, err } if len(r) < lastIdx-firstIdx { return lastIdx - firstIdx, ErrTooSmall } var idx int for i := firstIdx; i <= lastIdx; i++ { val, _, err := DecodeUint32(options[i].Value) if err == nil { r[idx] = val idx++ } } return idx, nil } // GetUint32 gets the uin32 value of the first option with the given ID. func (options Options) GetUint32(id OptionID) (uint32, error) { firstIdx, _, err := options.Find(id) if err != nil { return 0, err } val, _, err := DecodeUint32(options[firstIdx].Value) return val, err } // ContentFormat gets the content format of body. func (options Options) ContentFormat() (MediaType, error) { v, err := options.GetUint32(ContentFormat) return math.CastTo[MediaType](v), err } // GetString gets the string value of the first option with the given ID. func (options Options) GetString(id OptionID) (string, error) { firstIdx, _, err := options.Find(id) if err != nil { return "", err } return string(options[firstIdx].Value), nil } // GetStrings gets string array of all options with the given id. func (options Options) GetStrings(id OptionID, r []string) (int, error) { firstIdx, lastIdx, err := options.Find(id) if err != nil { return 0, err } if len(r) < lastIdx-firstIdx { return lastIdx - firstIdx, ErrTooSmall } var idx int for i := firstIdx; i < lastIdx; i++ { r[idx] = string(options[i].Value) idx++ } return idx, nil } // Queries gets URIQuery parameters. func (options Options) Queries() ([]string, error) { q := make([]string, 4) n, err := options.GetStrings(URIQuery, q) if errors.Is(err, ErrTooSmall) { q = append(q, make([]string, n-len(q))...) n, err = options.GetStrings(URIQuery, q) } if err != nil { return nil, err } return q[:n], nil } // SetBytes replaces/stores bytes of a option to options. // // Returns modified options, number of used buf bytes and error if occurs. func (options Options) SetBytes(buf []byte, id OptionID, data []byte) (Options, int, error) { if len(buf) < len(data) { return options, len(data), ErrTooSmall } if id == URIPath && len(data) > maxPathValue { return options, -1, ErrInvalidValueLength } copy(buf, data) return options.Set(Option{ID: id, Value: buf[:len(data)]}), len(data), nil } // AddBytes appends bytes of a option to options. // // Returns modified options, number of used buf bytes and error if occurs. func (options Options) AddBytes(buf []byte, id OptionID, data []byte) (Options, int, error) { if len(buf) < len(data) { return options, len(data), ErrTooSmall } if id == URIPath && len(data) > maxPathValue { return options, -1, ErrInvalidValueLength } copy(buf, data) return options.Add(Option{ID: id, Value: buf[:len(data)]}), len(data), nil } // GetBytes gets bytes of the first option with given id. func (options Options) GetBytes(id OptionID) ([]byte, error) { firstIdx, _, err := options.Find(id) if err != nil { return nil, err } return options[firstIdx].Value, nil } // GetBytess gets array of bytes of all options with the same id. func (options Options) GetBytess(id OptionID, r [][]byte) (int, error) { firstIdx, lastIdx, err := options.Find(id) if err != nil { return 0, err } if len(r) < lastIdx-firstIdx { return lastIdx - firstIdx, ErrTooSmall } var idx int for i := firstIdx; i < lastIdx; i++ { r[idx] = options[i].Value idx++ } return idx, nil } // AddUint32 appends uint32 option to options. // // Returns modified options, number of used buf bytes and error if occurs. func (options Options) AddUint32(buf []byte, id OptionID, value uint32) (Options, int, error) { enc, err := EncodeUint32(buf, value) if err != nil { return options, enc, err } o := options.Add(Option{ID: id, Value: buf[:enc]}) return o, enc, err } // SetUint32 replaces/stores uint32 option to options. // // Returns modified options, number of used buf bytes and error if occurs. func (options Options) SetUint32(buf []byte, id OptionID, value uint32) (Options, int, error) { enc, err := EncodeUint32(buf, value) if err != nil { return options, enc, err } o := options.Set(Option{ID: id, Value: buf[:enc]}) return o, enc, err } // SetContentFormat sets ContentFormat option. func (options Options) SetContentFormat(buf []byte, contentFormat MediaType) (Options, int, error) { return options.SetUint32(buf, ContentFormat, uint32(contentFormat)) } // SetObserve sets Observe option. func (options Options) SetObserve(buf []byte, observe uint32) (Options, int, error) { return options.SetUint32(buf, Observe, observe) } // Observe gets Observe option. func (options Options) Observe() (uint32, error) { return options.GetUint32(Observe) } // SetAccept sets accept option. func (options Options) SetAccept(buf []byte, contentFormat MediaType) (Options, int, error) { return options.SetUint32(buf, Accept, uint32(contentFormat)) } // Accept gets accept option. func (options Options) Accept() (MediaType, error) { v, err := options.GetUint32(Accept) return math.CastTo[MediaType](v), err } // Find returns range of type options. First number is index and second number is index of next option type. func (options Options) Find(id OptionID) (int, int, error) { idxPre, idxPost := options.findPosition(id) if idxPre == -1 && idxPost == 0 { return -1, -1, ErrOptionNotFound } if idxPre == len(options)-1 && idxPost == -1 { return -1, -1, ErrOptionNotFound } if idxPre < idxPost && idxPost-idxPre == 1 { return -1, -1, ErrOptionNotFound } idxPre++ if idxPost < 0 { idxPost = len(options) } return idxPre, idxPost, nil } // findPosition returns opened interval, -1 at means minIdx insert at 0, -1 maxIdx at maxIdx means append. func (options Options) findPosition(id OptionID) (minIdx int, maxIdx int) { if len(options) == 0 { return -1, 0 } pivot := 0 maxIdx = len(options) minIdx = 0 for { switch { case id == options[pivot].ID || (maxIdx-minIdx)/2 == 0: for maxIdx = pivot; maxIdx < len(options) && options[maxIdx].ID <= id; { maxIdx++ } if maxIdx == len(options) { maxIdx = -1 } for minIdx = pivot; minIdx >= 0 && options[minIdx].ID >= id; { minIdx-- } return minIdx, maxIdx case id < options[pivot].ID: maxIdx = pivot pivot = maxIdx - (maxIdx-minIdx)/2 case id > options[pivot].ID: minIdx = pivot pivot = minIdx + (maxIdx-minIdx)/2 } } } // Set replaces/stores option at options. // // Returns modified options. func (options Options) Set(opt Option) Options { idxPre, idxPost := options.findPosition(opt.ID) if idxPre == -1 && idxPost == -1 { // append options = append(options[:0], opt) return options } var insertPosition int var updateTo int var updateFrom int optsLength := len(options) switch { case idxPre == -1 && idxPost >= 0: insertPosition = 0 updateTo = 1 updateFrom = idxPost case idxPre == idxPost: insertPosition = idxPre updateFrom = idxPre updateTo = idxPre + 1 case idxPre >= 0: insertPosition = idxPre + 1 updateTo = idxPre + 2 updateFrom = idxPost if updateFrom < 0 { updateFrom = len(options) } if updateTo == updateFrom { options[insertPosition] = opt return options } } if len(options) == cap(options) { options = append(options, Option{}) } else { options = options[:len(options)+1] } // replace + move updateIdx := updateTo if updateFrom < updateTo { for i := optsLength; i > updateFrom; i-- { options[i] = options[i-1] updateIdx++ } } else { for i := updateFrom; i < optsLength; i++ { options[updateIdx] = options[i] updateIdx++ } } options[insertPosition] = opt options = options[:updateIdx] return options } // Add appends option to options. func (options Options) Add(opt Option) Options { _, idxPost := options.findPosition(opt.ID) if idxPost == -1 { idxPost = len(options) } if len(options) == cap(options) { options = append(options, Option{}) } else { options = options[:len(options)+1] } for i := len(options) - 1; i > idxPost; i-- { options[i] = options[i-1] } options[idxPost] = opt return options } // Remove removes all options with ID. func (options Options) Remove(id OptionID) Options { idxPre, idxPost, err := options.Find(id) if err != nil { return options } updateIdx := idxPre for i := idxPost; i < len(options); i++ { options[updateIdx] = options[i] updateIdx++ } length := len(options) - (idxPost - idxPre) options = options[:length] return options } // Marshal marshals options to buf. // // Returns the number of used buf bytes. func (options Options) Marshal(buf []byte) (int, error) { previousID := OptionID(0) length := 0 for _, o := range options { // return coap.error but calculate length if length > len(buf) { buf = nil } var optionLength int var err error if buf != nil { optionLength, err = o.Marshal(buf[length:], previousID) } else { optionLength, err = o.Marshal(nil, previousID) } previousID = o.ID switch { case err == nil: case errors.Is(err, ErrTooSmall): buf = nil default: return -1, err } length += optionLength } if buf == nil { return length, ErrTooSmall } return length, nil } // Unmarshal unmarshals data bytes to options and returns the number of consumed bytes. func (options *Options) Unmarshal(data []byte, optionDefs map[OptionID]OptionDef) (int, error) { prev := 0 processed := 0 for len(data) > 0 { if data[0] == 0xff { processed++ break } delta := int(data[0] >> 4) length := int(data[0] & 0x0f) if delta == ExtendOptionError || length == ExtendOptionError { return -1, ErrOptionUnexpectedExtendMarker } data = data[1:] processed++ proc, delta, err := parseExtOpt(data, delta) if err != nil { return -1, err } processed += proc data = data[proc:] proc, length, err = parseExtOpt(data, length) if err != nil { return -1, err } processed += proc data = data[proc:] if len(data) < length { return -1, ErrOptionTruncated } option := Option{} oid, err := math.SafeCastTo[OptionID](prev + delta) if err != nil { return -1, fmt.Errorf("%w: %w", ErrOptionNotFound, err) } proc, err = option.Unmarshal(data[:length], optionDefs, oid) if err != nil { return -1, err } if cap(*options) == len(*options) { return -1, ErrOptionsTooSmall } if option.ID != 0 { (*options) = append(*options, option) } processed += proc data = data[proc:] prev = int(oid) } return processed, nil } // ResetOptionsTo resets options to in options. // // Returns modified options, number of used buf bytes and error if occurs. func (options Options) ResetOptionsTo(buf []byte, in Options) (Options, int, error) { opts := options[:0] used := 0 for idx, o := range in { if len(buf) < len(o.Value) { for i := idx; i < len(in); i++ { used += len(in[i].Value) } return options, used, ErrTooSmall } copy(buf, o.Value) used += len(o.Value) opts = opts.Add(Option{ ID: o.ID, Value: buf[:len(o.Value)], }) buf = buf[len(o.Value):] } return opts, used, nil } // Clone create duplicates of options. func (options Options) Clone() (Options, error) { opts := make(Options, 0, len(options)) buf := make([]byte, 64) opts, used, err := opts.ResetOptionsTo(buf, options) if errors.Is(err, ErrTooSmall) { buf = append(buf, make([]byte, used-len(buf))...) opts, _, err = opts.ResetOptionsTo(buf, options) } if err != nil { return nil, err } return opts, nil }
package message import ( "math" "strconv" ) // Type represents the message type. // It's only part of CoAP UDP messages. // Reliable transports like TCP do not have a type. type Type int16 const ( // Used for unset Unset Type = -1 // Confirmable messages require acknowledgements. Confirmable Type = 0 // NonConfirmable messages do not require acknowledgements. NonConfirmable Type = 1 // Acknowledgement is a message indicating a response to confirmable message. Acknowledgement Type = 2 // Reset indicates a permanent negative acknowledgement. Reset Type = 3 ) var typeToString = map[Type]string{ Unset: "Unset", Confirmable: "Confirmable", NonConfirmable: "NonConfirmable", Acknowledgement: "Acknowledgement", Reset: "Reset", } func (t Type) String() string { val, ok := typeToString[t] if ok { return val } return "Type(" + strconv.FormatInt(int64(t), 10) + ")" } // ValidateType validates the type for UDP. (0 <= typ <= 255) func ValidateType(typ Type) bool { return typ >= 0 && typ <= math.MaxUint8 }
package coder import ( "encoding/binary" "errors" "github.com/plgd-dev/go-coap/v3/message" "github.com/plgd-dev/go-coap/v3/message/codes" "github.com/plgd-dev/go-coap/v3/pkg/math" ) var DefaultCoder = new(Coder) const ( MessageLength13Base = 13 MessageLength14Base = 269 MessageLength15Base = 65805 messageMaxLen = 0x7fff0000 // Large number that works in 32-bit builds ) type Coder struct{} type MessageHeader struct { Token []byte Length uint32 MessageLength uint32 Code codes.Code } func (c *Coder) Size(m message.Message) (int, error) { size, err := c.Encode(m, nil) if errors.Is(err, message.ErrTooSmall) { err = nil } return size, err } func getHeader(messageLength int) (uint8, []byte) { if messageLength < MessageLength13Base { return math.CastTo[uint8](messageLength), nil } if messageLength < MessageLength14Base { extLen := messageLength - MessageLength13Base extLenBytes := []byte{math.CastTo[uint8](extLen)} return 13, extLenBytes } if messageLength < MessageLength15Base { extLen := messageLength - MessageLength14Base extLenBytes := make([]byte, 2) binary.BigEndian.PutUint16(extLenBytes, math.CastTo[uint16](extLen)) return 14, extLenBytes } if messageLength < messageMaxLen { extLen := messageLength - MessageLength15Base extLenBytes := make([]byte, 4) binary.BigEndian.PutUint32(extLenBytes, math.CastTo[uint32](extLen)) return 15, extLenBytes } return 0, nil } func (c *Coder) Encode(m message.Message, buf []byte) (int, error) { /* A CoAP Message message lomessage.OKs like: 0 1 2 3 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Len | TKL | Extended Length ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Code | TKL bytes ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options (if any) ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1 1 1 1 1 1 1 1| Payload (if any) ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ The size of the Extended Length field is inferred from the value of the Len field as follows: | Len value | Extended Length size | Total length | +------------+-----------------------+---------------------------+ | 0-12 | 0 | Len | | 13 | 1 | Extended Length + 13 | | 14 | 2 | Extended Length + 269 | | 15 | 4 | Extended Length + 65805 | */ if len(m.Token) > message.MaxTokenSize { return -1, message.ErrInvalidTokenLen } payloadLen := len(m.Payload) if payloadLen > 0 { // for separator 0xff payloadLen++ } optionsLen, err := m.Options.Marshal(nil) if !errors.Is(err, message.ErrTooSmall) { return -1, err } bufLen := payloadLen + optionsLen lenNib, extLenBytes := getHeader(bufLen) var hdr [1 + 4 + message.MaxTokenSize + 1]byte hdrLen := 1 + len(extLenBytes) + len(m.Token) + 1 hdrOff := 0 copyToHdr := func(offset int, data []byte) int { if len(data) > 0 { copy(hdr[hdrOff:hdrOff+len(data)], data) offset += len(data) } return offset } // Length and TKL nibbles. hdr[hdrOff] = math.CastTo[uint8](len(m.Token)) | (lenNib << 4) hdrOff++ // Extended length, if present. hdrOff = copyToHdr(hdrOff, extLenBytes) // Code. hdr[hdrOff] = byte(m.Code) hdrOff++ // Token. copyToHdr(hdrOff, m.Token) bufLen += hdrLen if len(buf) < bufLen { return bufLen, message.ErrTooSmall } copy(buf, hdr[:hdrLen]) optionsLen, err = m.Options.Marshal(buf[hdrLen:]) switch { case err == nil: case errors.Is(err, message.ErrTooSmall): return bufLen, err default: return -1, err } if len(m.Payload) > 0 { copy(buf[hdrLen+optionsLen:], []byte{0xff}) copy(buf[hdrLen+optionsLen+1:], m.Payload) } return bufLen, nil } func (c *Coder) DecodeHeader(data []byte, h *MessageHeader) (int, error) { hdrOff := uint32(0) if len(data) == 0 { return -1, message.ErrShortRead } firstByte := data[0] data = data[1:] hdrOff++ lenNib := (firstByte & 0xf0) >> 4 tkl := firstByte & 0x0f var opLen int switch { case lenNib < MessageLength13Base: opLen = int(lenNib) case lenNib == 13: if len(data) < 1 { return -1, message.ErrShortRead } extLen := data[0] data = data[1:] hdrOff++ opLen = MessageLength13Base + int(extLen) case lenNib == 14: if len(data) < 2 { return -1, message.ErrShortRead } extLen := binary.BigEndian.Uint16(data) data = data[2:] hdrOff += 2 opLen = MessageLength14Base + int(extLen) case lenNib == 15: if len(data) < 4 { return -1, message.ErrShortRead } extLen := binary.BigEndian.Uint32(data) data = data[4:] hdrOff += 4 opLen = MessageLength15Base + int(extLen) } h.MessageLength = hdrOff + 1 + uint32(tkl) + math.CastTo[uint32](opLen) if len(data) < 1 { return -1, message.ErrShortRead } h.Code = codes.Code(data[0]) data = data[1:] hdrOff++ if len(data) < int(tkl) { return -1, message.ErrShortRead } if tkl > 0 { h.Token = data[:tkl] } hdrOff += uint32(tkl) h.Length = hdrOff return int(h.Length), nil } func (c *Coder) DecodeWithHeader(data []byte, header MessageHeader, m *message.Message) (int, error) { optionDefs := message.CoapOptionDefs processed := header.Length switch header.Code { case codes.CSM: optionDefs = message.TCPSignalCSMOptionDefs case codes.Ping, codes.Pong: optionDefs = message.TCPSignalPingPongOptionDefs case codes.Release: optionDefs = message.TCPSignalReleaseOptionDefs case codes.Abort: optionDefs = message.TCPSignalAbortOptionDefs } proc, err := m.Options.Unmarshal(data, optionDefs) if err != nil { return -1, err } data = data[proc:] processed += math.CastTo[uint32](proc) if len(data) > 0 { m.Payload = data } processed += math.CastTo[uint32](len(data)) m.Code = header.Code m.Token = header.Token return int(processed), nil } func (c *Coder) Decode(data []byte, m *message.Message) (int, error) { var header MessageHeader _, err := c.DecodeHeader(data, &header) if err != nil { return -1, err } if math.CastTo[uint32](len(data)) < header.MessageLength { return -1, message.ErrShortRead } return c.DecodeWithHeader(data[header.Length:], header, m) }
package coder import ( "encoding/binary" "errors" "fmt" "github.com/plgd-dev/go-coap/v3/message" "github.com/plgd-dev/go-coap/v3/message/codes" "github.com/plgd-dev/go-coap/v3/pkg/math" ) var DefaultCoder = new(Coder) type Coder struct{} func (c *Coder) Size(m message.Message) (int, error) { if len(m.Token) > message.MaxTokenSize { return -1, message.ErrInvalidTokenLen } size := 4 + len(m.Token) payloadLen := len(m.Payload) optionsLen, err := m.Options.Marshal(nil) if !errors.Is(err, message.ErrTooSmall) { return -1, err } if payloadLen > 0 { // for separator 0xff payloadLen++ } size += payloadLen + optionsLen return size, nil } func (c *Coder) Encode(m message.Message, buf []byte) (int, error) { /* 0 1 2 3 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Ver| T | TKL | Code | Message ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Token (if any, TKL bytes) ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options (if any) ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1 1 1 1 1 1 1 1| Payload (if any) ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ if !message.ValidateMID(m.MessageID) { return -1, fmt.Errorf("invalid MessageID(%v)", m.MessageID) } if !message.ValidateType(m.Type) { return -1, fmt.Errorf("invalid Type(%v)", m.Type) } size, err := c.Size(m) if err != nil { return -1, err } if len(buf) < size { return size, message.ErrTooSmall } tmpbuf := []byte{0, 0} // safe: checked by message.ValidateMID above binary.BigEndian.PutUint16(tmpbuf, math.CastTo[uint16](m.MessageID)) buf[0] = (1 << 6) | byte(m.Type)<<4 | byte(0xf&len(m.Token)) buf[1] = byte(m.Code) buf[2] = tmpbuf[0] buf[3] = tmpbuf[1] buf = buf[4:] if len(m.Token) > message.MaxTokenSize { return -1, message.ErrInvalidTokenLen } copy(buf, m.Token) buf = buf[len(m.Token):] optionsLen, err := m.Options.Marshal(buf) switch { case err == nil: case errors.Is(err, message.ErrTooSmall): return size, err default: return -1, err } buf = buf[optionsLen:] if len(m.Payload) > 0 { buf[0] = 0xff buf = buf[1:] } copy(buf, m.Payload) return size, nil } func (c *Coder) Decode(data []byte, m *message.Message) (int, error) { size := len(data) if size < 4 { return -1, ErrMessageTruncated } if data[0]>>6 != 1 { return -1, ErrMessageInvalidVersion } typ := message.Type((data[0] >> 4) & 0x3) tokenLen := int(data[0] & 0xf) if tokenLen > 8 { return -1, message.ErrInvalidTokenLen } code := codes.Code(data[1]) messageID := binary.BigEndian.Uint16(data[2:4]) data = data[4:] if len(data) < tokenLen { return -1, ErrMessageTruncated } token := data[:tokenLen] if len(token) == 0 { token = nil } data = data[tokenLen:] optionDefs := message.CoapOptionDefs proc, err := m.Options.Unmarshal(data, optionDefs) if err != nil { return -1, err } data = data[proc:] if len(data) == 0 { data = nil } m.Payload = data m.Code = code m.Token = token m.Type = typ m.MessageID = int32(messageID) return size, nil }