{"id":25949799,"url":"https://github.com/cisco-open/ruby-ctypes","last_synced_at":"2026-02-20T01:33:46.853Z","repository":{"id":278031887,"uuid":"931151665","full_name":"cisco-open/ruby-ctypes","owner":"cisco-open","description":"Ruby gem for manipuliating binary data using C datatype semantics","archived":false,"fork":false,"pushed_at":"2025-03-25T17:26:23.000Z","size":104,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-30T19:54:57.407Z","etag":null,"topics":["datatypes","ruby","rubygem"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cisco-open.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-02-11T19:53:52.000Z","updated_at":"2025-11-14T05:10:18.000Z","dependencies_parsed_at":"2025-02-17T16:41:35.711Z","dependency_job_id":"06b25775-3aa6-4125-819d-9f2255f2e3dd","html_url":"https://github.com/cisco-open/ruby-ctypes","commit_stats":null,"previous_names":["cisco-open/ruby-ctypes"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/cisco-open/ruby-ctypes","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cisco-open%2Fruby-ctypes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cisco-open%2Fruby-ctypes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cisco-open%2Fruby-ctypes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cisco-open%2Fruby-ctypes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cisco-open","download_url":"https://codeload.github.com/cisco-open/ruby-ctypes/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cisco-open%2Fruby-ctypes/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29638633,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T22:32:43.237Z","status":"ssl_error","status_checked_at":"2026-02-19T22:32:38.330Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["datatypes","ruby","rubygem"],"created_at":"2025-03-04T12:29:14.824Z","updated_at":"2026-02-20T01:33:46.820Z","avatar_url":"https://github.com/cisco-open.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CTypes Ruby Gem\n\n[![Version](https://img.shields.io/gem/v/ctypes.svg)](https://rubygems.org/gems/ctypes)\n[![GitHub](https://img.shields.io/badge/github-elf__utils-blue.svg)](http://github.com/cisco-open/ruby-ctypes)\n[![Documentation](https://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://rubydoc.info/gems/ctypes/frames)\n\n[![Contributor-Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-fbab2c.svg)](CODE_OF_CONDUCT.md)\n[![Maintainer](https://img.shields.io/badge/Maintainer-Cisco-00bceb.svg)](https://opensource.cisco.com)\n\nManipulate common C types in Ruby.\n\n- unpack complex binary data into ruby types, modify, and repack them as binary \n- bounds checking on types (when packing)\n- complex types supported\n    - structs with flexible array members\n    - arrays terminated by specific values\n    - strings terminated by a specific byte sequence\n- flexible endian support\n    - default endian globally configurable; defaults to host endian\n    - individual types can have fixed-endian\n    - structs support per attribute endian\n- minimal reserved words for Union and Struct types\n    - want to avoid colliding with struct \u0026 union field names so you don't have\n      to rename fields like `len`\n- reloadable type definitions (pry `reload-code` friendly)\n    - useful for using REPL-based development\n\n## Comparisons\n- BinData gem:\n    - Tightly coupled with file I/O\n    - no support for non-blocking I/O (non-blocking network sockets)\n    - reserves common struct attribute names such as `len`\n    - does not support reloading of types (pry `reload-code`)\n- Fiddle gem:\n    - only supports native endian\n    - no support for dynamically sized \u0026 terminated types\n\n## Installation\n\nInstall the gem and add to the application's Gemfile by executing:\n\n    $ bundle add ctypes\n\nIf bundler is not being used to manage dependencies, install the gem by executing:\n\n    $ gem install ctypes\n\n## Usage\n\n### Basic types\n```ruby\nrequire \"ctypes\"\n\n# load optional helpers for common types\ninclude CTypes::Helpers\n\n# common integer types all defined: uint64, int64, ..., uint8, int8\n# can be used to pack and unpack values\nuint32.pack(0xfeedface)                     # =\u003e \"\\xce\\xfa\\xed\\xfe\"\nuint32.pack(0xfeedface, endian: :big)       # =\u003e \"\\xfe\\xed\\xfa\\xce\"\nuint32.unpack(\"\\xce\\xfa\\xed\\xfe\")           # =\u003e 0xfeedface\nuint32.unpack(\"\\xfe\\xed\\xfa\\xce\", endian: :big)\n                                            # =\u003e 0xfeedface\n\n# `unpack_one` can be used to manually unpack sequential types from a string. # We recommend using `CTypes::Struct` for complex types, but this approach\n# can be useful when exploring binary data.\nbuf = \"\\xaa\\xbb\\xcc\\xdd\\x11\\x22\"\nword, buf = uint32.unpack_one(buf)          # =\u003e [0xddccbbaa, \"\\x11\\x22\"]\nhword, buf = uint16.unpack_one(buf)         # =\u003e [0x2211, \"\"]\n\n# create fixed-endian types from existing types\nu32be = uint32.with_endian(:big)\nu32be.pack(0xfeedface)                      # =\u003e \"\\xfe\\xed\\xfa\\xce\"\n\n# c strings (char[], uint8[], int8[]) supported by string\nstring.unpack(\"hello world\\0\\0\\0\\0\")        # =\u003e \"hello world\"\nstring.pack(\"hello world\")                  # =\u003e \"hello world\"\n\n# note: by default strings are greedy; they will consume all bytes in the\n# input, but only return the bytes up to the first null byte\nstring.unpack(\"first\\0second\\0\")            # =\u003e [\"first\", \"\"]\n\n# to unpack null-terminated strings use string.terminated\n_, rest = string.terminated.unpack(\"first\\0second\\0\")\n                                            # =\u003e [\"first\", \"second\\x00\"]\nstring.terminated.unpack(rest)              # =\u003e [\"second\", \"\"]\nstring.terminated.pack(\"first\")             # =\u003e \"first\\0\"\n\n# other bytes can be used to terminate strings\nt = string.terminated(\"\\xff\")\nt.unpack(\"test\\xff\")                        # =\u003e \"test\"\nt.pack(\"hello\\0world\")                      # =\u003e \"hello\\x00world\\xFF\"\n\n# along with byte sequences\nt = string.terminated(\"STOP\")\nt.unpack(\"this is the messageSTOPnext messageSTOP\")\n                                            # =\u003e \"this is the message\"\nt.pack(\"this is a reply\")                   # =\u003e \"this is a replySTOP\"\n\n# fixed-width string (char[16])\nstring(16).pack(\"hello world\")              # =\u003e \"hello world\\0\\0\\0\\0\\0\"\nstring(16).unpack(\"hello world\\0\\0\\0\\0\\0\")  # =\u003e \"hello world\\0\\0\\0\\0\\0\"\nstring(16).unpack(\"hello world\")            # =\u003e Exception raised\n\n# fixed-width string, but preserve null bytes when unpacking\nchar_16 = string(16, trim: false)\nchar_16.unpack(\"hello world\\0\\0\\0\\0\\0\")     # =\u003e \"hello world\\0\\0\\0\\0\\0\"\nchar_16.pack(\"hello world\")                 # =\u003e \"hello world\\0\\0\\0\\0\\0\"\n\n```\n\n### Arrays\n```ruby\nrequire \"ctypes\"\ninclude CTypes::Helpers\n\n# fixed-length arrays\npair = array(uint32, 2)\npair.unpack(\"\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\")\n                                            # =\u003e [0x04030201, 0x08070605]\npair.unpack(\"\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\xff\\xff\\xff\\xff\")\n                                            # =\u003e [0x04030201, 0x08070605]\n\n# dynamic length (greedy) arrays\nbytes = array(uint8)\nbytes.unpack(\"hello\")                       # =\u003e [104, 101, 108, 108, 111]\nbytes.unpack(\"\\1\\2\\3\")                      # =\u003e [1, 2, 3]\nbytes.pack([4,5,6])                         # =\u003e \"\\4\\5\\6\"\n\n# any type can be converted to a fixed-endian type\nbe_pair = pair.with_endian(:big)\nbe_pair.unpack(\"\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\")\n                                            # =\u003e [0x01020304, 0x05060708]\n\n# and it can be done for the inner type too\nbe_pair_inner = array(uint8.with_endian(:big))\nbe_pair_inner.unpack(\"\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\")\n                                            # =\u003e [0x01020304, 0x05060708]\n\n# array of null-terminated strings, terminated by an empty string\nstrings = array(string.terminated(\"\\0\"), terminator: \"\")\nstrings.unpack(\"first\\0second\\0third\\0\\0\")\n                                            # =\u003e [\"first\", \"second\", \"third\"]\n\n# array of integers, terminated by -1\nints = array(int8, terminator: -1)\nints.pack([1, 2, 3, 4])                     # =\u003e \"\\x01\\x02\\x03\\x04\\xFF\"\nints.unpack(\"\\x01\\x02\\x03\\x04\\xFFtail\")     # =\u003e [1, 2, 3, 4]\nints.unpack_one(\"\\x01\\x02\\x03\\x04\\xFFtail\") # =\u003e [[1, 2, 3, 4], \"tail\"]\n\n# array of structs; terminated by the :end type\ntype = struct do\n  attribute :type, enum(uint8, %i[record end])\n  attribute :value, uint32\nend\nrecords = array(type, terminator: {type: :end, value: 0})\nrecords.pack([{type: :record, value: 0xffff}])\n                            # =\u003e \"\\x00\\xFF\\xFF\\x00\\x00\\x01\\x00\\x00\\x00\\x00\"\nrecords.unpack(\"\\x00\\xFF\\xFF\\x00\\x00\\x01\\x00\\x00\\x00\\x00\")\n                            # =\u003e struct {\n                            #       .type = :record,\n                            #       .value = 65535 (0xffff), }\n```\n\n### Enums\n```ruby\nrequire \"ctypes\"\ninclude CTypes::Helpers\n\n# default enum is uint32, start numbering at zero\nstate = enum(%i[invalid running sleep blocked])\nstate.pack(:running)                        # =\u003e \"\\1\\0\\0\\0\"\n\n# can use other integer types\nstate = enum(uint8, %i[invalid running sleep blocked])\nstate.pack(:running)                        # =\u003e \"\\1\"\n\n# can be sparse\nstate = enum(uint8, {invalid: 0, running: 5, sleep: 6, blocked: 0xff})\nstate.pack(:blocked)                        # =\u003e \"\\xff\"\n\n# same as above with block syntax\nstate = enum(uint8) do |e|\n  e \u003c\u003c :invalid\n  e \u003c\u003c {running: 5}\n  e \u003c\u003c :sleep # assigned value 6\n  e \u003c\u003c {blocked: 0xff}\nend\nstate.pack(:blocked)                        # =\u003e \"\\xff\"\n```\n\n### Structures\n```ruby\n# Declare a TLV struct.  Size of each structure is determined by the `len`\n# field.\nclass TLV \u003c CTypes::Struct\n  layout do\n    endian :big     # all fields will use network-byte order\n    attribute :type, enum(uint8, %i[invalid hello read write goodbye])\n    attribute :len, uint32\n    attribute :value, string\n    # dynamically determine the size of the struct when unpacking\n    size { |struct| offsetof(:value) + struct[:len] }\n  end\nend\n\n# pack the tlv struct\nversion = \"v1.0\"\nTLV.pack({type: :hello, len: version.size, value: version})\n                                    # =\u003e \"\\x01\\x04\\x00\\x00\\x00v1.0\"\n\n# unpack a binary structure\nmsg = TLV.unpack(\"\\x01\\x04\\x00\\x00\\x00v1.0\")\nmsg.type                            # =\u003e :hello\nmsg.value                           # =\u003e \"v1.0\"\n\n# modify the structure and repack into binary representation\nmsg.type = :goodbye\nmsg.len = 0\nmsg.to_binstr                       # =\u003e \"\\x04\\x00\\x00\\x00\\x00\"\n```\n\n### Unions\nNote: because the underlying memory for union values is not shared between each\nmember, accessing multiple members in a union does have a performance penalty\nto pack the existing member and unpack the new member.  This penalty can be\navoided for read-only unions by freezing the union instance.\n\n```ruby\nclass Msg \u003c CTypes::Union\n  layout do\n    endian :big # network byte-order\n\n    type = enum(uint8, {invalid: 0, hello: 1, read: 2})\n    member :hello, struct({type:, version: string})\n    member :read, struct({type:, offset: uint64, len: uint64})\n    member :type, type\n  end\nend\n\n# provide only one member when packing\nMsg.pack({hello: {type: :hello, version: \"v1.0\"}})    # =\u003e \"\\x01v1.0\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\nMsg.pack({read: {type: :read, offset: 0xfeed, len: 0xdddd}}) # =\u003e \"\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\xFE\\xED\\x00\\x00\\x00\\x00\\x00\\x00\\xDD\\xDD\"\n\n# unpack a message and access member values\nmsg = Msg.unpack(\"\\x02\" +\n                 \"\\xfe\\xfe\\xfe\\xfe\\xfe\\xfe\\xfe\\xfe\" +\n                 \"\\xab\\xab\\xab\\xab\\xab\\xab\\xab\\xab\")\nmsg.type                      # =\u003e :read\nmsg.read.offset               # =\u003e 18374403900871474942\nmsg.read.len                  # =\u003e 12370169555311111083\n\n# modify and pack into binary\nmsg.hello.type = :hello\nmsg.hello.version = \"v1.0\"\nmsg.to_binstr                 # =\u003e \"\\x01v1.0\\xFE\\xFE\\xFE\\xFE\\xAB\\xAB\\xAB\\xAB\\xAB\\xAB\\xAB\\xAB\"\n```\n\n### Terminated\nSome greedy dynamic length types are terminated with byte sequences, or\nvariable byte sequences.  To handle these types we use CTypes::Terminated.\n\n```ruby\n# string.terminated returns a CTypes::Terminated instance\ntelegram = string.terminated(\"STOP\")\ntelegrams = array(telegram)\ntelegrams.unpack(\"hello worldSTOPnext messageSTOP\")\n                              # =\u003e [\"hello world\", \"next message\"]\n\n\n# record is an id along with an array of data bytes\nrecord = struct({id: uint8, data: array(uint8)})\n# each record is terminated with the byte sequence \\xff\\xee (for reasons?)\nterm = \"\\xff\\xee\"\n# create a terminated type for the record (yea, it is ugly right now)\nterminated_record = CTypes::Terminated\n    .new(type: record,\n         locate: proc { |b,_| [b.index(term), term.size] },\n         terminate: term)\n# and then an array of terminated records type\nrecords = array(terminated_record)\n\n# now pack \u0026 unpack as needed\nrecords.pack([\n    {id: 1, data: [1, 2, 3, 4]},\n    {id: 2, data: [5, 5]},\n    {id: 3}\n])          # =\u003e \"\\x01\\x01\\x02\\x03\\x04\\xFF\\xEE\\x02\\x05\\x05\\xFF\\xEE\\x03\\xFF\\xEE\"\nrecords.unpack(\"\\x01\\x01\\x02\\x03\\x04\\xFF\\xEE\\x02\\x05\\x05\\xFF\\xEE\\x03\\xFF\\xEE\")\n            # =\u003e [#\u003cstruct id=1, data=[1, 2, 3, 4]\u003e,\n            #     #\u003cstruct id=2, data=[5, 5]\u003e,\n            #     #\u003cstruct id=3, data=[]\u003e]\n```\n\n### Custom Types\nCustom types can be created then used within other CTypes. The following is an\ncustom CTypes implementation of the DWARF ULEB128 datatype.  It is a compressed\nrepresentation of a 128-bit integer that uses 7 bits per byte for the encoded\nvalue, with the highest bit set on the last byte of the value. The bytes are\nstored in little endian order.\n\n```ruby\nmodule ULEB128\n  extend CTypes::Type\n\n  # declare the underlying DRY type; it must have a default value, and may\n  # have constraints set\n  @dry_type = Dry::Types[\"integer\"].default(0)\n\n  # as this is a dynamically sized type, let's set size to be the minimum size\n  # for the type (1 byte), and ensure .fixed_size? returns false\n  @size = 1\n  def self.fixed_size?\n    false\n  end\n\n  # provide a method for packing the ruby value into the binary representation\n  def self.pack(value, endian: default_endian, validate: true)\n    return \"\\x80\" if value == 0\n    buf = String.new\n    while value != 0\n      buf \u003c\u003c (value \u0026 0x7f)\n      value \u003e\u003e= 7\n    end\n    buf[-1] = (buf[-1].ord | 0x80).chr\n    buf\n  end\n\n  # provide a method for unpacking an instance of this type from a String, and\n  # returning both the unpacked value, and any unused input\n  def self.unpack_one(buf, endian: default_endian)\n    value = 0\n    shift = 0\n    len = 0\n    buf.each_byte do |b|\n      len += 1\n      value |= ((b \u0026 0x7f) \u003c\u003c shift)\n      return value, buf[len...] if (b \u0026 0x80) != 0\n      shift += 7\n    end\n    raise TerminatorNotFoundError\n  end\nend\n\n# now the type can be used like any other type\nULEB128.unpack_one(\"\\x7f\\x7f\\x83XXX\")       # =\u003e [0xffff, \"XXX\"]\nULEB128.unpack(\"\\x7f\\x7f\\x83\")              # =\u003e 0xffff\nULEB128.unpack(\"\\x81XXX\")                   # =\u003e 1\nULEB128.pack(0)                             # =\u003e \"\\x80\"\nULEB128.pack(1)                             # =\u003e \"\\x81\"\nULEB128.pack(0xffff)                        # =\u003e \"\\x7F\\x7F\\x83\"\n\n# use it in an array\nlist = array(ULEB128)\nlist.unpack(\"\\x7f\\x7f\\x83\\x81\\x80\")         # =\u003e [65535, 1, 0]\n\n# or a struct\nt = struct(id: uint32, value: ULEB128)\nt.unpack(\"\\1\\0\\0\\0\\x7f\\x7f\\x83XXX\")         # =\u003e #\u003cstruct id=1, value=65535\u003e\n```\n\n## Roadmap\n\nSee the [open issues](https://github.com/cisco-open/ruby-ctypes/issues) for a\nlist of proposed features (and known issues).\n\n## Development\n\nAfter checking out the repo, run `bundle install` to install dependencies.\nThen, run `rake spec` to run the tests. You can also run `bin/console` for an\ninteractive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To\nrelease a new version, update the version number in `version.rb`, and then run\n`bundle exec rake release`, which will create a git tag for the version, push\ngit commits and the created tag, and push the `.gem` file to\n[rubygems.org](https://rubygems.org).\n\n## Contributing\n\nContributions are what make the open source community such an amazing place to\nlearn, inspire, and create. Any contributions you make are **greatly\nappreciated**. For detailed contributing guidelines, please see\n[CONTRIBUTING.md](CONTRIBUTING.md)\n\n## License\n\nThe gem is available as open source under the terms of the\n[MIT License](https://opensource.org/licenses/MIT).\nLicense. See [LICENSE.txt](LICENSE.txt) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcisco-open%2Fruby-ctypes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcisco-open%2Fruby-ctypes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcisco-open%2Fruby-ctypes/lists"}