SkillAgentSearch skills...

CBOR

The most comprehensive CBOR module in the Lua universe.

Install / Use

/learn @spc476/CBOR
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

         The Concise Binary Object Represenation Lua Modules

The major module in this collection is 'org.conman.cbor', the most comprehensive CBOR module in the Lua universe, as it supports everything mentioned in RFC-8949 and the extensions published by the IANA. It's easy to use, but also allows enough control to get exactly what you want when encoding to CBOR. For example:

cbor = require "org.conman.cbor"

data =
{
  name   = "Sean Conner",
  userid = "spc476",
  login  =
  {
    active     = true,
    last_login = os.time(),
  }
}

enc = cbor.encode(data)

It's that easy. To decode is just as simple:

data = cbor.decode(enc)

For a bit more control over the encoding though, say, to include some semantic tagging, requires a bit more work, but is still quite doable. For example, if we change things slghtly:

mt =
{
  __tocbor = function(self)
    return cbor.TAG._epoch(os.time(self))
  end
}

function date()
  local now = os.date("*t")
  setmetatable(now,mt)
  return now
end

data = 
{
  name = "Sean Conner",
  userid = "spc476",
  login = 
  {
    active = true,
    last_login = date()
  }
}

enc = cbor.encode(data)

You can supply the '__tocbor' metamethod to a metatable to have the item encode itself as this example shows. This is one approach to have userdata encoded into CBOR.

Decoding tagged items is also quite easy:

tagged_items =
{
  _epoch = function(value)
    local t = os.date("*t",value)
    setmetatable(t,mt)
    return t
  end
}

-- first parameter is the data we're decoding
-- second paramter is the offset to start with (optional)
-- third parameter is a table of routines to process
-- tagged data (optional)

data = cbor.decode(enc,1,tagged_items)

The _epoch tagged type is passed to our function where we return the value from os.date(). The resulting data looks like:

data =
{
  name   = "Sean Conner",
  userid = "spc476",
  login  =
  {
    active     = true,
    last_login = 
    {
      year  = 2016,
      month = 4,
      day   = 4,
      hour  = 1,
      min   = 42,
      sec   = 53,
      wday  = 2,
      yday  = 95,
      isdst = true,
    } -- and we have a metatable that can encode this
  }
}

Of course, you could also do it like:

enc = cbor.TYPE.MAP(3)
   .. cbor.encode "name"   .. cbor.encode(data.name)
   .. cbor.encode "userid" .. cbor.encode(data.userid)
   .. cbor.encode "login"  .. cbor.TYPE.MAP(2)
   	.. cbor.encode "active" 
   	.. cbor.encode(data.login.active)
   	.. cbor.encode "last_login" 
   	.. cbor.TYPE._epoch(os.time(data.login.last_login))

if you really want to get down into the details of encoding CBOR.

Tagged types are not the only thing that can be post-processed though. If we wanted to ensure that all the field names were lower case, we can do that as well:

tagged_items =
{
  TEXT = function(value,iskey)
    if iskey then 
      value = value:lower()
    end
    return value
  end,
  
  _epoch = function(value)
    local t = os.date("*t",value)
    setmetatable(t,mt)
    return t
  end,
}

data = cbor.decode(enc,1,tagged_items)

The second parameter indicates if the given value is a key in a MAP.

This module can also deal with circular references with an additional parameter when encoding:

x = {}
x.x = x

enc = cbor.encode(x,{})

The addition of the empty table as the second parameter let's the module know that MAPs and ARRAYs might be referenced and to keep track as it's processing. The reason it's not done by default is that each MAP and ARRAY is tagged as "shareable", which adds some overhead to the output. If there are no circular references, such additions are just wasted. Just something to keep in mind.

But the above does work and can be passed to cbor.decode() without any additional parameters (decoding will deal with such references automatically).

Additionally, if the data you are sending might use repetative strings, say:

data =
{
  { filename = "cbor_c.so"    , package = "org.conman" },
  { filename = "cbor.lua"     , package = "org.conman" },
  { filename = "cbor_s.lua"   , package = "org.conman" },
  { filename = "cbormisc.lua" , package = "org.conman" },
}

You can save some space by using string references:

enc = cbor.encode(data,nil,{}) -- third parameter to use string refs

Normally, this would take 160 bytes to encode, but with string references, it saves 54 bytes. Not much in this example, but it could add up significantly.

And yes, you can request both shared references and string references in the same call.

The CBOR values null and undefined map to Lua nil, and all the baggage that comes with that. If you really need to track null and/or undefined values, you can define the following with a unique value:

cbor.null
cbor.undefined

And the value of those two fields will be checked for when encoding and the appropriate CBOR value used. Also, when decoding, the those values will be used when the CBOR null and undefined values are decoded. The safest value to use is an empty table for each one:

cbor.null      = {}
cbor.undefined = {}

That will ensure both have a unique value.

If the 'org.conman.cbor' module is too heavy for your application, you can try the 'org.conman.cbor_s' module. It's similar to the full blown 'org.conman.cbor' module, but without the full support of the various tagged data items. And it too, supports custom null and undefined values---just set

cbor_s.null
cbor_s.undefined

Our original example is the same:

cbor_s = require "org.conman.cbor_s"

data =
{
  name   = "Sean Conner",
  userid = "spc476",
  login  =
  {
    active     = true,
    last_login = os.time(),
  }
}

enc   = cbor_s.encode(data)
data2 = cbor_s.decode(enc)

But the tagged version is slightly different:

mt =
{
  __tocbor = function(self)
  
    -- ---------------------------------------------------
    -- because of limited support, we need to specify the
    -- actual number for the _epoch tag.
    -- ---------------------------------------------------
    
    return cbor.encode(os.time(self),1)
  end
}

function date()
  local now = os.date("*t")
  setmetatable(now,mt)
  return now
end

data = 
{
  name = "Sean Conner",
  userid = "spc476",
  login = 
  {
    active = true,
    last_login = date()
  }
}

enc = cbor.encode(data)

tagged_items = 
{
  [1] = function(value)	    
    local t = os.date("*t",value)
    setmetatable(t,mt)
    return t
  end,
}

data2 = cbor.decode(enc,1,tagged_items)

The first major difference is the lack of tag names (you have to use the actual values). The second difference is the lack of support for the reference tags (you can still catch them, but due to a lack of support in the underlying engine you can't really do anything with them at this time; you the full blown 'org.conman.cbor' module if you really need circular references) and string references (same issue).

Dropping down yet another level is the 'org.conman.cbor_c' module. This is the basic core of the two previous modules and only supplies two functions, cbor_c.encode() and cbor_c.decode(). The modules 'org.conman.cbor_s' and 'org.conman.cbormisc' were developed to showcase the low level usage of the 'org.conman.cbor_c' module and can be the basis for a module for even more constrained devices.


  •                              CAVEATS
    

Encoding a mixed Lua table (e.g. { 1 , 2 , 3 , foo = 1 , bar = 2 }) may not encode as expected. For best results, ensure that all Lua tables are either pure sequences (arrays, e.g. { 1 , 2 , 3 , 4 }) or are non-sequential in nature.

NOTE: The following array is problematic:

{ foo = 1 , bar = 2 , [1] = 1 }

because it is both a Lua sequence and contains non-sequence indecies, and will thus be encoded as an ARRAY. The following array:

{ foo = 1 , bar = 2 , [100] = 1 }

is NOT considered a sequence and will be encoded as a MAP.


  •                     QUICK FUNCTION REFERENCE
    
  • See http://cbor.io/spec.html for more information on CBOR
  • org.conman.cbor

Full blown CBOR support. All of RFC-8949 as well as the currently defined IANA extensions are supported with this module. This module also includes enhanced error detection and type checking. As a result, it's quite a bit larger than the org.conman.cbor_s module. This module also contains a large number of functions.

==============================================================

Usage: bool = cbor.isnumber(ctype) Desc: returns true of the given CBOR type is a number Input: type (enum/cbor) CBOR type Return: bool (boolean) true if number, false otherwise

==============================================================

Usage: bool = cbor.isinteger(ctype)
Desc: returns true if the given CBOR type is an integer Input: ctype (enum/cbor) CBOR type Return: bool (boolean) true if number, false othersise

==============================================================

Usage: bool = cbor.isfloat(ctype) Desc: returns true if the given CBOR type is a float Input: ctype (enum/cbor) CBOR type Return: bool (boolean) true if number, false otherwise

==============================================================

Usage: value,pos2,ctype = cbor.decode(packet[,pos][,conv][,ref][,iskey]) Desc: Decode CBOR encoded data Input: packet (binary) CBOR binary blob pos (integer/optional) starting point for decoding conv (table/optional) table of conversion routines ref (table/optional) reference table (see notes) iskey (boolean/optional) is a key in a MAP (see notes) Return: value (any) the decoded CBOR data pos2 (integer) offset past decoded data ctype (enum/cbor) C

Related Skills

View on GitHub
GitHub Stars22
CategoryDevelopment
Updated2mo ago
Forks5

Languages

Lua

Security Score

90/100

Audited on Jan 26, 2026

No findings