Initial release.
This commit is contained in:
brent saner 2024-07-09 23:40:20 -04:00
parent 7ce62f8107
commit d4bb259b83
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
27 changed files with 4673 additions and 12 deletions

19
.githooks/pre-commit/01-docgen Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash

docsdir="${PWD}"

if ! command -v asciidoctor &> /dev/null;
then
exit 0
fi

mkdir -p "${docsdir}"

for f in $(find . -maxdepth 1 -type f -iname "*.adoc"); do
filename=$(basename -- "${f}")
nosuffix="${filename%.*}"

asciidoctor -o "${docsdir}/${nosuffix}.html" "${f}"
git add "${docsdir}/${nosuffix}.html"
done
echo "Regenerated docs"

29
.gitignore vendored
View File

@ -1,7 +1,24 @@
# ---> Go
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
*.7z
*.bak
*.deb
*.jar
*.rar
*.run
*.sig
*.tar
*.tar.bz2
*.tar.gz
*.tar.xz
*.tbz
*.tbz2
*.tgz
*.txz
*.zip
.*.swp
.editix
.idea/

# https://github.com/github/gitignore/blob/master/Go.gitignore
# Binaries for programs and plugins
*.exe
*.exe~
@ -17,7 +34,3 @@

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work


View File

@ -1,4 +1,4 @@
Copyright (c) 2024 r00t2.
Copyright (c) 2024 Brent Saner (r00t^2).

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:


59
README.adoc Normal file
View File

@ -0,0 +1,59 @@
////
Go WireProto API Documentation © 2024 by Brent Saner is licensed under Creative Commons Attribution-ShareAlike 4.0 International. To view a copy of this license, visit https://creativecommons.org/licenses/by-sa/4.0/
////

= Go WireProto API Documentation
Brent Saner <bts@square-r00t.net>
Last rendered {localdatetime}
:doctype: book
:docinfo: shared
:data-uri:
:imagesdir: images
:sectlinks:
:sectnums:
:sectnumlevels: 7
:toc: preamble
:toc2: left
:idprefix:
:toclevels: 7
:source-highlighter: rouge
:docinfo: shared
:this_protover: 1
:this_protover_hex: 0x00000001

[id="ref"]
== Reference
In addition to the documentation found in this document and https://wireproto.io/[the specification^], library usage documentation can be found at https://pkg.go.dev/go.pkg.dev/r00t2.io/WireProto[the Golang module documentation page^]:

++++
<a href="https://pkg.go.dev/go.pkg.dev/r00t2.io/wireproto">
<img src="https://pkg.go.dev/badge/go.pkg.dev/r00t2.io/wireproto.svg"
alt="Go Reference">
</a>
++++

[id="lic"]
== License
This library is licensed for use, inclusion, and distribution under the https://opensource.org/license/bsd-3-clause["3-Clause BSD" license^].

.Full License
[%collapsible]
====
[source,plain]
----
include::LICENSE[]
----
====

[id="todo"]
== TODO
The following are a wishlist or things planned that may come in later versions.

* More clear errors
** Currently during e.g. `UnmarshalBinary` calls, just an `io.EOF` will be returned if the buffer is exhausted early. This may be able to be a little more context-helpful by using the `Err*` errors.
* Confirmation of read/write sizes in buffers
** We know the sizes they *should* be, there's no reason to not confirm it.
* Goroutines
** This of course won't work for serializing and keeping *order* of children (e.g. RG => Record); that'd still need to be ordered, but it will allow for parallel parsing *of* those children. Should benchmark, though; it may not be worth it.
* `context.Context` support for `Read*` and `Write*` funcs
** This is a relatively low priority as the passed `net.Conn` will likely return an error if its own context is canceled. This can be handled in the caller downstream.

669
README.html Normal file
View File

@ -0,0 +1,669 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 2.0.23">
<meta name="author" content="Brent Saner">
<title>Go WireProto API Documentation</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
<style>
/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
/* Uncomment the following line when using as a custom stylesheet */
/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
html{font-family:sans-serif;-webkit-text-size-adjust:100%}
a{background:none}
a:focus{outline:thin dotted}
a:active,a:hover{outline:0}
h1{font-size:2em;margin:.67em 0}
b,strong{font-weight:bold}
abbr{font-size:.9em}
abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
dfn{font-style:italic}
hr{height:0}
mark{background:#ff0;color:#000}
code,kbd,pre,samp{font-family:monospace;font-size:1em}
pre{white-space:pre-wrap}
q{quotes:"\201C" "\201D" "\2018" "\2019"}
small{font-size:80%}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sup{top:-.5em}
sub{bottom:-.25em}
img{border:0}
svg:not(:root){overflow:hidden}
figure{margin:0}
audio,video{display:inline-block}
audio:not([controls]){display:none;height:0}
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
legend{border:0;padding:0}
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
button,input{line-height:normal}
button,select{text-transform:none}
button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
button[disabled],html input[disabled]{cursor:default}
input[type=checkbox],input[type=radio]{padding:0}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
textarea{overflow:auto;vertical-align:top}
table{border-collapse:collapse;border-spacing:0}
*,::before,::after{box-sizing:border-box}
html,body{font-size:100%}
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
a:hover{cursor:pointer}
img,object,embed{max-width:100%;height:auto}
object,embed{height:100%}
img{-ms-interpolation-mode:bicubic}
.left{float:left!important}
.right{float:right!important}
.text-left{text-align:left!important}
.text-right{text-align:right!important}
.text-center{text-align:center!important}
.text-justify{text-align:justify!important}
.hide{display:none}
img,object,svg{display:inline-block;vertical-align:middle}
textarea{height:auto;min-height:50px}
select{width:100%}
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
a{color:#2156a5;text-decoration:underline;line-height:inherit}
a:hover,a:focus{color:#1d4b8f}
a img{border:0}
p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
p aside{font-size:.875em;line-height:1.35;font-style:italic}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
h1{font-size:2.125em}
h2{font-size:1.6875em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
h4,h5{font-size:1.125em}
h6{font-size:1em}
hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
em,i{font-style:italic;line-height:inherit}
strong,b{font-weight:bold;line-height:inherit}
small{font-size:60%;line-height:inherit}
code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
ul,ol{margin-left:1.5em}
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
ul.circle{list-style-type:circle}
ul.disc{list-style-type:disc}
ul.square{list-style-type:square}
ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
dl dt{margin-bottom:.3125em;font-weight:bold}
dl dd{margin-bottom:1.25em}
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
h1{font-size:2.75em}
h2{font-size:2.3125em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
h4{font-size:1.4375em}}
table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
table thead,table tfoot{background:#f7f8f7}
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
table tr.even,table tr.alt{background:#f8f8f7}
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
.center{margin-left:auto;margin-right:auto}
.stretch{width:100%}
.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
.clearfix::after,.float-group::after{clear:both}
:not(pre).nobreak{word-wrap:normal}
:not(pre).nowrap{white-space:nowrap}
:not(pre).pre-wrap{white-space:pre-wrap}
:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
pre>code{display:block}
pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
em em{font-style:normal}
strong strong{font-weight:400}
.keyseq{color:rgba(51,51,51,.8)}
kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
.keyseq kbd:first-child{margin-left:0}
.keyseq kbd:last-child{margin-right:0}
.menuseq,.menuref{color:#000}
.menuseq b:not(.caret),.menuref{font-weight:inherit}
.menuseq{word-spacing:-.02em}
.menuseq b.caret{font-size:1.25em;line-height:.8}
.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
b.button::before{content:"[";padding:0 3px 0 2px}
b.button::after{content:"]";padding:0 2px 0 3px}
p a>code:hover{color:rgba(0,0,0,.9)}
#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
#content{margin-top:1.25em}
#content::before{content:none}
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
#header>h1:only-child{border-bottom:1px solid #dddddf;padding-bottom:8px}
#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
#header .details span:first-child{margin-left:-.125em}
#header .details span.email a{color:rgba(0,0,0,.85)}
#header .details br{display:none}
#header .details br+span::before{content:"\00a0\2013\00a0"}
#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
#header .details br+span#revremark::before{content:"\00a0|\00a0"}
#header #revnumber{text-transform:capitalize}
#header #revnumber::after{content:"\00a0"}
#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
#toc>ul{margin-left:.125em}
#toc ul.sectlevel0>li>a{font-style:italic}
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
#toc li{line-height:1.3334;margin-top:.3334em}
#toc a{text-decoration:none}
#toc a:active{text-decoration:underline}
#toctitle{color:#7a2518;font-size:1.2em}
@media screen and (min-width:768px){#toctitle{font-size:1.375em}
body.toc2{padding-left:15em;padding-right:0}
body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
#toc.toc2 ul ul{margin-left:0;padding-left:1em}
#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
body.toc2.toc-right{padding-left:0;padding-right:15em}
body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
#toc.toc2{width:20em}
#toc.toc2 #toctitle{font-size:1.375em}
#toc.toc2>ul{font-size:.95em}
#toc.toc2 ul ul{padding-left:1.25em}
body.toc2.toc-right{padding-left:0;padding-right:20em}}
#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
#content #toc>:first-child{margin-top:0}
#content #toc>:last-child{margin-bottom:0}
#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
#content{margin-bottom:.625em}
.sect1{padding-bottom:.625em}
@media screen and (min-width:768px){#content{margin-bottom:1.25em}
.sect1{padding-bottom:1.25em}}
.sect1:last-child{padding-bottom:0}
.sect1+.sect1{border-top:1px solid #e7e7e9}
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
details{margin-left:1.25rem}
details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
details>summary::-webkit-details-marker{display:none}
details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
.admonitionblock>table td.icon{text-align:center;width:80px}
.admonitionblock>table td.icon img{max-width:none}
.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
.listingblock>.content{position:relative}
.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
.listingblock:hover code[data-lang]::before{display:block}
.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
.listingblock pre.highlightjs{padding:0}
.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
.listingblock pre.prettyprint{border-width:0}
.prettyprint{background:#f7f7f8}
pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
pre.prettyprint li code[data-lang]::before{opacity:1}
pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
table.linenotable td.code{padding-left:.75em}
table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
pre.pygments span.linenos{display:inline-block;margin-right:.75em}
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
.quoteblock blockquote{margin:0;padding:0;border:0}
.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
.verseblock{margin:0 1em 1.25em}
.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
.verseblock pre strong{font-weight:400}
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
.quoteblock .attribution br,.verseblock .attribution br{display:none}
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
.quoteblock.abstract{margin:0 1em 1.25em;display:block}
.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
p.tableblock:last-child{margin-bottom:0}
td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
td.tableblock>.content>:last-child{margin-bottom:-1.25em}
table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
table.grid-all>*>tr>*{border-width:1px}
table.grid-cols>*>tr>*{border-width:0 1px}
table.grid-rows>*>tr>*{border-width:1px 0}
table.frame-all{border-width:1px}
table.frame-ends{border-width:1px 0}
table.frame-sides{border-width:0 1px}
table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
th.halign-left,td.halign-left{text-align:left}
th.halign-right,td.halign-right{text-align:right}
th.halign-center,td.halign-center{text-align:center}
th.valign-top,td.valign-top{vertical-align:top}
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
th.valign-middle,td.valign-middle{vertical-align:middle}
table thead th,table tfoot th{font-weight:bold}
tbody tr th{background:#f7f8f7}
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
p.tableblock>code:only-child{background:none;padding:0}
p.tableblock{font-size:1em}
ol{margin-left:1.75em}
ul li ol{margin-left:1.5em}
dl dd{margin-left:1.125em}
dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
ul.unstyled,ol.unstyled{margin-left:0}
li>p:empty:only-child::before{content:"";display:inline-block}
ul.checklist>li>p:first-child{margin-left:-1em}
ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
ul.inline>li{margin-left:1.25em}
.unstyled dl dt{font-weight:400;font-style:normal}
ol.arabic{list-style-type:decimal}
ol.decimal{list-style-type:decimal-leading-zero}
ol.loweralpha{list-style-type:lower-alpha}
ol.upperalpha{list-style-type:upper-alpha}
ol.lowerroman{list-style-type:lower-roman}
ol.upperroman{list-style-type:upper-roman}
ol.lowergreek{list-style-type:lower-greek}
.hdlist>table,.colist>table{border:0;background:none}
.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
td.hdlist1{font-weight:bold;padding-bottom:1.25em}
td.hdlist2{word-wrap:anywhere}
.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
.colist td:not([class]):first-child img{max-width:none}
.colist td:not([class]):last-child{padding:.25em 0}
.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
.imageblock.left{margin:.25em .625em 1.25em 0}
.imageblock.right{margin:.25em 0 1.25em .625em}
.imageblock>.title{margin-bottom:0}
.imageblock.thumb,.imageblock.th{border-width:6px}
.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
.image.left{margin-right:.625em}
.image.right{margin-left:.625em}
a.image{text-decoration:none;display:inline-block}
a.image object{pointer-events:none}
sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
sup.footnote a,sup.footnoteref a{text-decoration:none}
sup.footnote a:active,sup.footnoteref a:active,#footnotes .footnote a:first-of-type:active{text-decoration:underline}
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
#footnotes .footnote:last-of-type{margin-bottom:0}
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
div.unbreakable{page-break-inside:avoid}
.big{font-size:larger}
.small{font-size:smaller}
.underline{text-decoration:underline}
.overline{text-decoration:overline}
.line-through{text-decoration:line-through}
.aqua{color:#00bfbf}
.aqua-background{background:#00fafa}
.black{color:#000}
.black-background{background:#000}
.blue{color:#0000bf}
.blue-background{background:#0000fa}
.fuchsia{color:#bf00bf}
.fuchsia-background{background:#fa00fa}
.gray{color:#606060}
.gray-background{background:#7d7d7d}
.green{color:#006000}
.green-background{background:#007d00}
.lime{color:#00bf00}
.lime-background{background:#00fa00}
.maroon{color:#600000}
.maroon-background{background:#7d0000}
.navy{color:#000060}
.navy-background{background:#00007d}
.olive{color:#606000}
.olive-background{background:#7d7d00}
.purple{color:#600060}
.purple-background{background:#7d007d}
.red{color:#bf0000}
.red-background{background:#fa0000}
.silver{color:#909090}
.silver-background{background:#bcbcbc}
.teal{color:#006060}
.teal-background{background:#007d7d}
.white{color:#bfbfbf}
.white-background{background:#fafafa}
.yellow{color:#bfbf00}
.yellow-background{background:#fafa00}
span.icon>.fa{cursor:default}
a span.icon>.fa{cursor:inherit}
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
.conum[data-value] *{color:#fff!important}
.conum[data-value]+b{display:none}
.conum[data-value]::after{content:attr(data-value)}
pre .conum[data-value]{position:relative;top:-.125em}
b.conum *{color:inherit!important}
.conum:not([data-value]):empty{display:none}
dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
p{margin-bottom:1.25rem}
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
.print-only{display:none!important}
@page{margin:1.25cm .75cm}
@media print{*{box-shadow:none!important;text-shadow:none!important}
html{font-size:80%}
a{color:inherit!important;text-decoration:underline!important}
a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
abbr[title]{border-bottom:1px dotted}
abbr[title]::after{content:" (" attr(title) ")"}
pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
thead{display:table-header-group}
svg{max-width:100%}
p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
#header,#content,#footnotes,#footer{max-width:none}
#toc,.sidebarblock,.exampleblock>.content{background:none!important}
#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
body.book #header{text-align:center}
body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
body.book #header .details{border:0!important;display:block;padding:0!important}
body.book #header .details span:first-child{margin-left:0!important}
body.book #header .details br{display:block}
body.book #header .details br+span::before{content:none!important}
body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
.listingblock code[data-lang]::before{display:block}
#footer{padding:0 .9375em}
.hide-on-print{display:none!important}
.print-only{display:block!important}
.hide-for-print{display:none!important}
.show-for-print{display:inherit!important}}
@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
.sect1{padding:0!important}
.sect1+.sect1{border:0}
#footer{background:none}
#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
</style>
<style>
pre.rouge table td { padding: 5px; }
pre.rouge table pre { margin: 0; }
pre.rouge, pre.rouge .w {
color: #24292f;
background-color: #f6f8fa;
}
pre.rouge .k, pre.rouge .kd, pre.rouge .kn, pre.rouge .kp, pre.rouge .kr, pre.rouge .kt, pre.rouge .kv {
color: #cf222e;
}
pre.rouge .gr {
color: #f6f8fa;
}
pre.rouge .gd {
color: #82071e;
background-color: #ffebe9;
}
pre.rouge .nb {
color: #953800;
}
pre.rouge .nc {
color: #953800;
}
pre.rouge .no {
color: #953800;
}
pre.rouge .nn {
color: #953800;
}
pre.rouge .sr {
color: #116329;
}
pre.rouge .na {
color: #116329;
}
pre.rouge .nt {
color: #116329;
}
pre.rouge .gi {
color: #116329;
background-color: #dafbe1;
}
pre.rouge .kc {
color: #0550ae;
}
pre.rouge .l, pre.rouge .ld, pre.rouge .m, pre.rouge .mb, pre.rouge .mf, pre.rouge .mh, pre.rouge .mi, pre.rouge .il, pre.rouge .mo, pre.rouge .mx {
color: #0550ae;
}
pre.rouge .sb {
color: #0550ae;
}
pre.rouge .bp {
color: #0550ae;
}
pre.rouge .ne {
color: #0550ae;
}
pre.rouge .nl {
color: #0550ae;
}
pre.rouge .py {
color: #0550ae;
}
pre.rouge .nv, pre.rouge .vc, pre.rouge .vg, pre.rouge .vi, pre.rouge .vm {
color: #0550ae;
}
pre.rouge .o, pre.rouge .ow {
color: #0550ae;
}
pre.rouge .gh {
color: #0550ae;
font-weight: bold;
}
pre.rouge .gu {
color: #0550ae;
font-weight: bold;
}
pre.rouge .s, pre.rouge .sa, pre.rouge .sc, pre.rouge .dl, pre.rouge .sd, pre.rouge .s2, pre.rouge .se, pre.rouge .sh, pre.rouge .sx, pre.rouge .s1, pre.rouge .ss {
color: #0a3069;
}
pre.rouge .nd {
color: #8250df;
}
pre.rouge .nf, pre.rouge .fm {
color: #8250df;
}
pre.rouge .err {
color: #f6f8fa;
background-color: #82071e;
}
pre.rouge .c, pre.rouge .ch, pre.rouge .cd, pre.rouge .cm, pre.rouge .cp, pre.rouge .cpf, pre.rouge .c1, pre.rouge .cs {
color: #6e7781;
}
pre.rouge .gl {
color: #6e7781;
}
pre.rouge .gt {
color: #6e7781;
}
pre.rouge .ni {
color: #24292f;
}
pre.rouge .si {
color: #24292f;
}
pre.rouge .ge {
color: #24292f;
font-style: italic;
}
pre.rouge .gs {
color: #24292f;
font-weight: bold;
}
</style>
</head>
<body class="book toc2 toc-left">
<div id="header">
<h1>Go WireProto API Documentation</h1>
<div class="details">
<span id="author" class="author">Brent Saner</span><br>
<span id="email" class="email"><a href="mailto:bts@square-r00t.net">bts@square-r00t.net</a></span><br>
<span id="revdate">Last rendered 2024-07-10 00:18:53 -0400</span>
</div>
<div id="toc" class="toc2">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#ref">1. Reference</a></li>
<li><a href="#lic">2. License</a></li>
<li><a href="#todo">3. TODO</a></li>
</ul>
</div>
</div>
<div id="content">
<div class="sect1">
<h2 id="ref"><a class="link" href="#ref">1. Reference</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>In addition to the documentation found in this document and <a href="https://wireproto.io/" target="_blank" rel="noopener">the specification</a>, library usage documentation can be found at <a href="https://pkg.go.dev/go.pkg.dev/r00t2.io/WireProto" target="_blank" rel="noopener">the Golang module documentation page</a>:</p>
</div>
<a href="https://pkg.go.dev/go.pkg.dev/r00t2.io/wireproto">
<img src="https://pkg.go.dev/badge/go.pkg.dev/r00t2.io/wireproto.svg"
alt="Go Reference">
</a>
</div>
</div>
<div class="sect1">
<h2 id="lic"><a class="link" href="#lic">2. License</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>This library is licensed for use, inclusion, and distribution under the <a href="https://opensource.org/license/bsd-3-clause" target="_blank" rel="noopener">"3-Clause BSD" license</a>.</p>
</div>
<details>
<summary class="title">Full License</summary>
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="plain">Copyright (c) 2024 Brent Saner (r00t^2).

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</code></pre>
</div>
</div>
</div>
</details>
</div>
</div>
<div class="sect1">
<h2 id="todo"><a class="link" href="#todo">3. TODO</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>The following are a wishlist or things planned that may come in later versions.</p>
</div>
<div class="ulist">
<ul>
<li>
<p>More clear errors</p>
<div class="ulist">
<ul>
<li>
<p>Currently during e.g. <code>UnmarshalBinary</code> calls, just an <code>io.EOF</code> will be returned if the buffer is exhausted early. This may be able to be a little more context-helpful by using the <code>Err*</code> errors.</p>
</li>
</ul>
</div>
</li>
<li>
<p>Confirmation of read/write sizes in buffers</p>
<div class="ulist">
<ul>
<li>
<p>We know the sizes they <strong>should</strong> be, there&#8217;s no reason to not confirm it.</p>
</li>
</ul>
</div>
</li>
<li>
<p>Goroutines</p>
<div class="ulist">
<ul>
<li>
<p>This of course won&#8217;t work for serializing and keeping <strong>order</strong> of children (e.g. RG &#8658; Record); that&#8217;d still need to be ordered, but it will allow for parallel parsing <strong>of</strong> those children. Should benchmark, though; it may not be worth it.</p>
</li>
</ul>
</div>
</li>
<li>
<p><code>context.Context</code> support for <code>Read*</code> and <code>Write*</code> funcs</p>
<div class="ulist">
<ul>
<li>
<p>This is a relatively low priority as the passed <code>net.Conn</code> will likely return an error if its own context is canceled. This can be handled in the caller downstream.</p>
</li>
</ul>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2024-07-09 16:54:13 -0400
</div>
</div>
</body>
</html>

View File

@ -1,3 +0,0 @@
# go_wireproto_sha1

A very efficient and flexible message protocol designed for close-to-the-wire transactions.

99
consts.go Normal file
View File

@ -0,0 +1,99 @@
package wireproto

import (
`encoding/binary`
)

var (
// IndentChars is used when rendering a Model; it indicates the leading indent.
IndentChars string = IndentDefault
// SeparatorChars is used when rendering a Model; it indicates the separation between the value and the comment.
SeparatorChars string = SeparatorDefault
)

const (
IndentDefault string = "\t"
SeparatorDefault string = " "
maxByteLine int = 12 // split if a value is more than this number of bytes..
)

const (
indentR uint = iota
indentRG
indentRec
indentKvp
)
const indentOrigRec uint = 2

const (
// ProtoVersion specifies the protocol version for the specification of a Message.
ProtoVersion uint32 = 1
)

const (
// PackedNumSize is the size (length of bytes) of a packed unsigned integer.
PackedNumSize int = 4 // They're all uint32's.
// CksumPackedSize is the size (length of bytes) of the checksum algorithm used.
CksumPackedSize int = 4 // CRC32 is a big-endian uint32, but if we use a different algo we need to change this.
)

// See https://square-r00t.net/ascii.html for further details.
const (
AsciiNUL uint8 = iota // 0x00
AsciiSOH // 0x01
AsciiSTX // 0x02
AsciiETX // 0x03
AsciiEOT // 0x04
AsciiENQ // 0x05
AsciiACK // 0x06
AsciiBEL // 0x07
AsciiBS // 0x08
AsciiHT // 0x09
AsciiLF // 0x0a
AsciiVT // 0x0b
AsciiFF // 0x0c
AsciiCR // 0x0d
AsciiSO // 0x0e
AsciiSI // 0x0f
AsciiDLE // 0x10
AsciiDC1 // 0x11
AsciiDC2 // 0x12
AsciiDC3 // 0x13
AsciiDC4 // 0x14
AsciiNAK // 0x15
AsciiSYN // 0x16
AsciiETB // 0x17
AsciiCAN // 0x18
AsciiEM // 0x19
AsciiSUB // 0x1a
AsciiESC // 0x1b
AsciiFS // 0x1c
AsciiGS // 0x1d
AsciiRS // 0x1e
AsciiUS // 0x1f
)

const (
RespStatusByteOK uint8 = AsciiACK
RespStatusByteErr = AsciiNAK
)

var (
byteOrder binary.ByteOrder = binary.BigEndian
)

var (
respStatusOK []byte = []byte{RespStatusByteOK}
respStatusErr []byte = []byte{RespStatusByteErr}
// hdrCKSUM *must* be *exactly* as long as hdrMSGSTART and *must not* match hdrMSGSTART.
hdrCKSUM []byte = []byte{AsciiESC}
hdrMSGSTART []byte = []byte{AsciiSOH}
hdrBODYSTART []byte = []byte{AsciiSTX}
hdrBODYEND []byte = []byte{AsciiETX}
hdrMSGEND []byte = []byte{AsciiEOT}
endSeq []byte = []byte{AsciiETX, AsciiEOT}
)

const (
WriteChunkSize int = 1024
)

11
doc.go Normal file
View File

@ -0,0 +1,11 @@
/*

# WireProto - A Fast, Efficient, and Safe Bytes-on-the-Wire Message Format

For more details and the actual specification, visit [WireProto].

[WireProto]: https://wireproto.io/


*/
package wireproto

14
errs.go Normal file
View File

@ -0,0 +1,14 @@
package wireproto

import (
"errors"
)

var (
ErrBadCksum error = errors.New("checksums do not match")
ErrBadHdr error = errors.New("a header mismatch occurred")
ErrBadNumRecords error = errors.New("the number of child objects does not match the count")
ErrCustomUnmarshal error = errors.New("an error occurred during custom unmarshaling")
ErrInvalidNums error = errors.New("invalid number of results")
ErrNotEnoughFields error = errors.New("not enough fields/parameters in request")
)

604
funcs.go Normal file
View File

@ -0,0 +1,604 @@
package wireproto

import (
`bytes`
`encoding/binary`
`fmt`
`io`
`net`
`slices`
)

// GetByteOrder returns the byte order ("endianness") used by the library.
func GetByteOrder() (order binary.ByteOrder) {

order = byteOrder

return
}

// GetHdrs returns a map of a header name/status indicator and its byte sequence.
func GetHdrs() (hdrs map[string][]byte) {

hdrs = map[string][]byte{
"OK": respStatusOK,
"ERR": respStatusErr,
"CKSUM": hdrCKSUM,
"MSGSTART": hdrMSGSTART,
"BODYSTART": hdrBODYSTART,
"BODYEND": hdrBODYEND,
"MSGEND": hdrMSGEND,
}

return
}

// ReadConnRequest returns a Request from a net.Conn.
func ReadConnRequest(conn net.Conn) (req *Request, err error) {

var b []byte
var size int
var buf *bytes.Buffer = new(bytes.Buffer)

// First check for a checksum.
b = make([]byte, len(hdrMSGSTART))
if _, err = conn.Read(b); err != nil {
return
}
if _, err = buf.Write(b); err != nil {
return
}
if bytes.Equal(b, hdrCKSUM) {
// A checksum was found. Continue reading in the checksum and MSGSTART.
if _, err = io.CopyN(buf, conn, int64(CksumPackedSize+len(hdrMSGSTART))); err != nil {
return
}
}
// Otherwise no checksum was found.

// Protocol version, BODYSTART, RG count.
if _, err = io.CopyN(buf, conn, int64(PackedNumSize+len(hdrBODYSTART)+PackedNumSize)); err != nil {
return
}

// RG size.
b = make([]byte, PackedNumSize)
if _, err = conn.Read(b); err != nil {
return
}
if _, err = buf.Write(b); err != nil {
return
}
size = UnpackInt(b)

// Now copy in the RG, BODYEND, MSGEND.
if _, err = io.CopyN(buf, conn, int64(size+len(hdrBODYEND)+len(hdrMSGEND))); err != nil {
return
}

// Unmarshal.
req = new(Request)
if err = req.UnmarshalBinary(buf.Bytes()); err != nil {
return
}

return
}

// ReadConnResponse returns a Response from a net.Conn.
func ReadConnResponse(conn net.Conn) (resp *Response, err error) {

var b []byte
var size int
var buf *bytes.Buffer = new(bytes.Buffer)

// First get the checksum.
b = make([]byte, len(hdrCKSUM))
if _, err = conn.Read(b); err != nil {
return
}
if _, err = buf.Write(b); err != nil {
return
}
if !bytes.Equal(b, hdrCKSUM) {
err = ErrBadHdr
return
}
// A checksum was found. Continue reading in the checksum, MSGSTART, protocol version, BODYSTART, and RG count.
if _, err = io.CopyN(
buf, conn, int64(CksumPackedSize+len(hdrMSGSTART)+PackedNumSize+len(hdrBODYSTART)+PackedNumSize),
); err != nil {
return
}

// RG size.
b = make([]byte, PackedNumSize)
if _, err = conn.Read(b); err != nil {
return
}
if _, err = buf.Write(b); err != nil {
return
}
size = UnpackInt(b)

// Now copy in the RG, BODYEND, MSGEND.
if _, err = io.CopyN(buf, conn, int64(size+len(hdrBODYEND)+len(hdrMSGEND))); err != nil {
return
}

// Unmarshal.
resp = new(Response)
if err = resp.UnmarshalBinary(buf.Bytes()); err != nil {
return
}

return
}

// PackInt is a more generic form of PackUint32 as a convenience wrapper.
func PackInt(i int) (b []byte) {

b = make([]byte, PackedNumSize)

byteOrder.PutUint32(b, uint32(i))

return
}

// PackUint32 uses the package-wide byteOrder to pack a uint32 into a series of bytes.
func PackUint32(u uint32) (b []byte) {

b = make([]byte, PackedNumSize)

byteOrder.PutUint32(b, u)

return
}

// UnpackInt is a more generic form of UnpackUint32 as a convenience wrapper. The same caveat applies for UnpackUint32.
func UnpackInt(b []byte) (i int) {

var u uint32

u = UnpackUint32(b)
i = int(u)

return
}

// UnpackUint32 returns a uint32 from byteslice b. The byteslice *MUST* be PackedNumSize (4) bytes long, or u will always be 0.
func UnpackUint32(b []byte) (u uint32) {

if b == nil || len(b) != PackedNumSize {
return
}
u = byteOrder.Uint32(b)

return
}

/*
WriteConnRequest writes a Request to a net.Conn.

If chunkSize == 0, no chunking will be performed. This may be unreliable on some connections.

If chunkSize > 0, chunking will be performed based on chunkSize.

If chunkSize < 0, chunking will be done based on WriteChunkSize number of bytes, which should be sensible for most connections.
*/
func WriteConnRequest(req *Request, conn net.Conn, chunkSize int) (err error) {

if err = WriteRequest(req, conn, chunkSize); err != nil {
return
}

return
}

/*
WriteConnRequestSegmented writes a Request to a net.Conn, chunking it by each logical section.

This is slower than a continuous stream/fixed-chunk stream to the end (see WriteConnRequest),
but will be more manageable over high-latency connections and will prevent clients from being
overwhelmed on a large Request.
*/
func WriteConnRequestSegmented(req *Request, conn net.Conn) (err error) {

if err = WriteRequestSegmented(req, conn); err != nil {
return
}

return
}

/*
WriteConnResponse writes a Response to a net.Conn.

If chunkSize == 0, no chunking will be performed. This may be unreliable on some connections.

If chunkSize > 0, chunking will be performed based on chunkSize.

If chunkSize < 0, chunking will be done based on WriteChunkSize number of bytes, which should be sensible for most connections.
*/
func WriteConnResponse(resp *Response, conn net.Conn, chunkSize int) (err error) {

if err = WriteResponse(resp, conn, chunkSize); err != nil {
return
}

return
}

/*
WriteConnResponseSegmented writes a Response to a net.Conn, chunking it by each logical section.

This is slower than a continuous stream/fixed-chunk stream to the end (see WriteConnResponse),
but will be more manageable over high-latency connections and will prevent clients from being
overwhelmed on a large Response.
*/
func WriteConnResponseSegmented(resp *Response, conn net.Conn) (err error) {

if err = WriteResponseSegmented(resp, conn); err != nil {
return
}

return
}

/*
WriteRequest writes a Request to an io.Writer.

If chunkSize == 0, no chunking will be performed. This may be unreliable for some io.Writers.

If chunkSize > 0, chunking will be performed based on chunkSize.

If chunkSize < 0, chunking will be done based on WriteChunkSize number of bytes, which should be sensible for most io.Writers.
*/
func WriteRequest(req *Request, w io.Writer, chunkSize int) (err error) {

var b []byte
var buf *bytes.Buffer

if req == nil {
return
}

if b, err = req.MarshalBinary(); err != nil {
return
}

if chunkSize == 0 {
if _, err = w.Write(b); err != nil {
return
}
return
}

if chunkSize < 0 {
chunkSize = WriteChunkSize
}

buf = bytes.NewBuffer(b)

for buf.Len() != 0 {
if buf.Len() < chunkSize {
chunkSize = buf.Len()
}
if _, err = io.CopyN(w, buf, int64(chunkSize)); err != nil {
return
}
}

return
}

/*
WriteRequestSegmented writes a Request to an io.Writer, chunking it by each logical section.

This is slower than a continuous stream/fixed-chunk stream to the end (see WriteRequest),
but will be more manageable for slower io.Writers.
*/
func WriteRequestSegmented(req *Request, w io.Writer) (err error) {

var size int

if req == nil {
return
}

for _, rg := range req.RecordGroups {
size += rg.Size()
}

if req.Checksum != nil {
if _, err = w.Write(hdrCKSUM); err != nil {
return
}
if _, err = w.Write(PackUint32(*req.Checksum)); err != nil {
return
}
}
if _, err = w.Write(hdrMSGSTART); err != nil {
return
}

if _, err = w.Write(PackUint32(req.ProtocolVersion)); err != nil {
return
}

if _, err = w.Write(PackInt(len(req.RecordGroups))); err != nil {
return
}
if _, err = w.Write(PackInt(size)); err != nil {
return
}

for _, rg := range req.RecordGroups {
size = 0
for _, rec := range rg.Records {
size += rec.Size()
}
if _, err = w.Write(PackInt(len(rg.Records))); err != nil {
return
}
if _, err = w.Write(PackInt(size)); err != nil {
return
}
for _, rec := range rg.Records {
size = 0
for _, kvp := range rec.Pairs {
size += kvp.Size()
}
if _, err = w.Write(PackInt(len(rec.Pairs))); err != nil {
return
}
if _, err = w.Write(PackInt(size)); err != nil {
return
}
for _, kvp := range rec.Pairs {
if _, err = w.Write(PackInt(kvp.Name.Size())); err != nil {
return
}
if _, err = w.Write(PackInt(kvp.Value.Size())); err != nil {
return
}
if _, err = w.Write(kvp.Name); err != nil {
return
}
if _, err = w.Write(kvp.Value); err != nil {
return
}
}
}
}

if _, err = w.Write(hdrBODYEND); err != nil {
return
}
if _, err = w.Write(hdrMSGEND); err != nil {
return
}

return
}

/*
WriteResponse writes a Response to an io.Writer.

If chunkSize == 0, no chunking will be performed. This may be unreliable for some io.Writers.

If chunkSize > 0, chunking will be performed based on chunkSize.

If chunkSize < 0, chunking will be done based on WriteChunkSize number of bytes, which should be sensible for most io.Writers.
*/
func WriteResponse(resp *Response, w io.Writer, chunkSize int) (err error) {

var b []byte
var buf *bytes.Buffer

if resp == nil {
return
}

if b, err = resp.MarshalBinary(); err != nil {
return
}

if chunkSize == 0 {
if _, err = w.Write(b); err != nil {
return
}
return
}

if chunkSize < 0 {
chunkSize = WriteChunkSize
}

buf = bytes.NewBuffer(b)

for buf.Len() != 0 {
if buf.Len() < chunkSize {
chunkSize = buf.Len()
}
if _, err = io.CopyN(w, buf, int64(chunkSize)); err != nil {
return
}
}

return
}

/*
WriteResponseSegmented writes a Response to an io.Writer, chunking it by each logical section.

This is slower than a continuous stream/fixed-chunk stream to the end (see WriteResponse),
but will be more manageable for slower io.Writers.
*/
func WriteResponseSegmented(resp *Response, w io.Writer) (err error) {

var size int

if resp == nil {
return
}

for _, rg := range resp.RecordGroups {
size += rg.Size()
}

if _, err = w.Write([]byte{resp.Status}); err != nil {
return
}

if _, err = w.Write(hdrCKSUM); err != nil {
return
}
if _, err = w.Write(PackUint32(resp.Checksum)); err != nil {
return
}

if _, err = w.Write(hdrMSGSTART); err != nil {
return
}

if _, err = w.Write(PackUint32(resp.ProtocolVersion)); err != nil {
return
}

if _, err = w.Write(PackInt(len(resp.RecordGroups))); err != nil {
return
}
if _, err = w.Write(PackInt(size)); err != nil {
return
}

for _, rg := range resp.RecordGroups {
size = 0
for _, rec := range rg.Records {
size += rec.Size()
}
if _, err = w.Write(PackInt(len(rg.Records))); err != nil {
return
}
if _, err = w.Write(PackInt(size)); err != nil {
return
}
for _, rec := range rg.Records {
size = 0
for _, kvp := range rec.Pairs {
size += kvp.Size()
}
if _, err = w.Write(PackInt(len(rec.Pairs))); err != nil {
return
}
if _, err = w.Write(PackInt(size)); err != nil {
return
}
if _, err = w.Write(PackInt(rec.OriginalRecord.Size())); err != nil {
return
}
for _, kvp := range rec.Pairs {
if _, err = w.Write(PackInt(kvp.Name.Size())); err != nil {
return
}
if _, err = w.Write(PackInt(kvp.Value.Size())); err != nil {
return
}
if _, err = w.Write(kvp.Name); err != nil {
return
}
if _, err = w.Write(kvp.Value); err != nil {
return
}
}
size = 0
for _, kvp := range rec.OriginalRecord.Pairs {
size += kvp.Size()
}
if _, err = w.Write(PackInt(len(rec.OriginalRecord.Pairs))); err != nil {
return
}
if _, err = w.Write(PackInt(size)); err != nil {
return
}
for _, kvp := range rec.OriginalRecord.Pairs {
if _, err = w.Write(PackInt(kvp.Name.Size())); err != nil {
return
}
if _, err = w.Write(PackInt(kvp.Value.Size())); err != nil {
return
}
if _, err = w.Write(kvp.Name); err != nil {
return
}
if _, err = w.Write(kvp.Value); err != nil {
return
}
}
}
}

if _, err = w.Write(hdrBODYEND); err != nil {
return
}
if _, err = w.Write(hdrMSGEND); err != nil {
return
}

return
}

// chunkByteLine splits b into a chunked slice of no more than maxByteLine per 1st-level element.
func chunkByteLine(b []byte) (chunked [][]byte) {

chunked = make([][]byte, 0, (len(b)+maxByteLine-1)/maxByteLine)

// slices.Chunk requires Golang 1.23+
for chunk := range slices.Chunk(b, maxByteLine) {
chunked = append(chunked, chunk)
}

return
}

// cksumBytes returns a byte-packed representation of the checksum.
func cksumBytes(cksum uint32) (packed []byte) {

packed = make([]byte, CksumPackedSize)
byteOrder.PutUint32(packed, cksum)

return
}

/*
padBytesRight is used when rendering Model objects.

val is assumed to *not* be in hex format already; it should be the raw []byte representation.
*/
func padBytesRight(val []byte, length int) (out string) {

out = fmt.Sprintf("%-*x", length, val)

return
}

/*
padIntRight is used when rendering Model objects.
*/
func padIntRight(val int, length int) (out string) {

out = padBytesRight(PackUint32(uint32(val)), length)

return
}

/*
padStrRight is used when rendering Model objects.
*/
func padStrRight(val string, length int) (out string) {

out = padBytesRight([]byte(val), length)

return
}

75
funcs_fieldname.go Normal file
View File

@ -0,0 +1,75 @@
package wireproto

// MarshalBinary renders a FieldName into a byte-packed format.
func (f *FieldName) MarshalBinary() (data []byte, err error) {

if f == nil {
data = []byte{}
return
}

data = []byte(*f)

return
}

// MarshalText renders a FieldName into plaintext.
func (f *FieldName) MarshalText() (data []byte, err error) {

if f == nil {
data = []byte{}
return
}

data = []byte(*f)

return
}

// UnmarshalBinary populates a FieldName from packed bytes.
func (f *FieldName) UnmarshalBinary(data []byte) (err error) {

if data == nil || len(data) == 0 {
return
}

*f = FieldName(data)

return
}

// UnmarshalText populates a FieldName from plaintext.
func (f *FieldName) UnmarshalText(data []byte) (err error) {

if data == nil || len(data) == 0 {
return
}

*f = FieldName(data)

return
}

// Size returns the FieldName's calculated size (in bytes).
func (f *FieldName) Size() (size int) {

if f == nil {
return
}

size = len(*f)

return
}

// String returns a string representation of a FieldName.
func (f *FieldName) String() (s string) {

if f == nil {
return
}

s = string([]byte(*f))

return
}

80
funcs_fieldvalue.go Normal file
View File

@ -0,0 +1,80 @@
package wireproto

/*
MarshalBinary renders a FieldValue into a byte-packed format.

Unlike other types (aside from FieldName), it does *not* include its allocator!
*/
func (f *FieldValue) MarshalBinary() (data []byte, err error) {

if f == nil {
data = []byte{}
return
}

data = []byte(*f)

return
}

// MarshalText renders a FieldValue into plaintext.
func (f *FieldValue) MarshalText() (data []byte, err error) {

if f == nil {
data = []byte{}
return
}

data = []byte(*f)

return
}

// UnmarshalBinary populates a FieldValue from packed bytes.
func (f *FieldValue) UnmarshalBinary(data []byte) (err error) {

if data == nil || len(data) == 0 {
return
}

// Strip the header; remainder should be value.
*f = FieldValue(data)

return
}

// UnmarshalText populates a FieldValue from plaintext.
func (f *FieldValue) UnmarshalText(data []byte) (err error) {

if data == nil || len(data) == 0 {
return
}

*f = FieldValue(data)

return
}

// Size returns the FieldValue's calculated size (in bytes).
func (f *FieldValue) Size() (size int) {

if f == nil {
return
}

size = len(*f)

return
}

// String returns a string representation of a FieldValue.
func (f *FieldValue) String() (s string) {

if f == nil {
return
}

s = string([]byte(*f))

return
}

268
funcs_fieldvaluepair.go Normal file
View File

@ -0,0 +1,268 @@
package wireproto

import (
`bytes`
`fmt`
`strings`

`github.com/google/uuid`
)

// ConnId returns a copy of the connection ID.
func (f *FieldValuePair) ConnId() (conn uuid.UUID) {

if f.connId == nil {
return
}

conn = *f.connId

return
}

// GetParent returns the parent Record, if set.
func (f *FieldValuePair) GetParent() (rec Record) {

rec = f.parent

return
}

// MarshalBinary renders a FieldValuePair into a byte-packed format.
func (f *FieldValuePair) MarshalBinary() (data []byte, err error) {

var b []byte
var buf *bytes.Buffer = new(bytes.Buffer)

_ = f.Size()

if _, err = buf.Write(PackInt(f.Name.Size())); err != nil {
return
}
if _, err = buf.Write(PackInt(f.Value.Size())); err != nil {
return
}

if b, err = f.Name.MarshalBinary(); err != nil {
return
}
buf.Write(b)
if b, err = f.Value.MarshalBinary(); err != nil {
return
}
buf.Write(b)

data = buf.Bytes()

return
}

// Model returns an indented string representation of the model.
func (f *FieldValuePair) Model() (out string) {

out = f.ModelCustom(IndentChars, SeparatorChars, indentKvp)

return
}

// ModelCustom is like Model with user-defined formatting.
func (f *FieldValuePair) ModelCustom(indent, sep string, level uint) (out string) {

var fnSize int
var fvSize int
var splitFn [][]byte // for multi-chunk hex
var splitFv [][]byte // """
var sb strings.Builder
var maxLen int = 8 // len of size uint32 hex

// This one is the biggest pain because it has to handle arbitrary lengths.

fnSize = f.Name.Size() * 2 // 2x because Hex string
fvSize = f.Value.Size() * 2 // """
if fnSize > maxLen {
if f.Name.Size() <= maxByteLine {
maxLen = fnSize
} else {
maxLen = maxByteLine * 2
}
}
if fvSize > maxLen {
if f.Value.Size() <= maxByteLine {
maxLen = fvSize
} else {
maxLen = maxByteLine * 2
}
}

splitFn = chunkByteLine(f.Name)
splitFv = chunkByteLine(f.Value)

// SIZES
// Field Name
// e.g. "\t\t\t00000003 // Field Name Size (3)"
sb.WriteString(strings.Repeat(indent, int(level))) // \t\t\t
sb.WriteString(padIntRight(f.Name.Size(), maxLen)) // "00000003 "
sb.WriteString(sep) // " "
sb.WriteString(fmt.Sprintf("// Field Name Size (%d)\n", f.Name.Size())) // "// Field Name Size (3)". The extra space is intentional.
// Field Value
// e.g. "\t\t\t00000018 // Field Value Size (24)"
sb.WriteString(strings.Repeat(indent, int(level))) // \t\t\t
sb.WriteString(padIntRight(f.Value.Size(), maxLen)) // "000000018 "
sb.WriteString(sep) // " "
sb.WriteString(fmt.Sprintf("// Field Value Size (%d)\n", f.Value.Size())) // "// Field Value Size (24)".

// VALUES
// Name
// e.g. `\t\t\t666f6f // "foo"`
for idx, chunk := range splitFn {
sb.WriteString(strings.Repeat(indent, int(level))) // "\t\t\t"
if idx == 0 {
sb.WriteString(padBytesRight(chunk, maxLen)) // "666f6f "
sb.WriteString(sep) // " "
sb.WriteString(fmt.Sprintf("// \"%s\"", f.Name)) // `// "foo"`
} else {
sb.WriteString(fmt.Sprintf("%x", chunk)) // "666f6f"
}
sb.WriteString("\n")
}
// Value
/*
e.g.:
\t\t\t736f6d65206c6f6e6765722076616c75 // "some longer value string"
\t\t\t6520737472696e67
*/
for idx, chunk := range splitFv {
sb.WriteString(strings.Repeat(indent, int(level))) // "\t\t\t"
if idx == 0 {
sb.WriteString(padBytesRight(chunk, maxLen)) // "736f6d65206c6f6e6765722076616c75"
sb.WriteString(sep) // " "
sb.WriteString(fmt.Sprintf("// \"%s\"", f.Value)) // `// "some longer value string"`
} else {
sb.WriteString(fmt.Sprintf("%x", chunk)) // "6520737472696e67"
}
sb.WriteString("\n")
}

out = sb.String()

return
}

/*
ToMap returns a map of map[name]value.
While an interface{} in the map, the value is actually a FieldValue.
*/
func (f *FieldValuePair) ToMap() (m map[string]interface{}) {

m = map[string]interface{}{
f.Name.String(): f.Value,
}

return
}

// UnmarshalBinary populates a FieldValuePair from packed bytes.
func (f *FieldValuePair) UnmarshalBinary(data []byte) (err error) {

if data == nil || len(data) == 0 {
return
}

var b []byte
var nmSize, valSize int
var buf *bytes.Reader = bytes.NewReader(data)

if f == nil {
*f = FieldValuePair{}
}
f.common = &common{}
f.size = 0

// Get the name/value sizes.
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
nmSize = UnpackInt(b)

b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
valSize = UnpackInt(b)

// Get the name.
if nmSize != 0 {
b = make([]byte, nmSize)
if _, err = buf.Read(b); err != nil {
return
}
if err = f.Name.UnmarshalBinary(b); err != nil {
return
}
} else {
f.Name = nil
}

// Get the value.
if valSize != 0 {
b = make([]byte, valSize)
if _, err = buf.Read(b); err != nil {
return
}
if err = f.Value.UnmarshalBinary(b); err != nil {
return
}
} else {
f.Value = nil
}

_ = f.Size()

return
}

// Size returns the FieldValuePair's calculated size (in bytes) and updates the size field.
func (f *FieldValuePair) Size() (size int) {

if f == nil {
return
}

// Count and Size uint32s
size += PackedNumSize * 2

size += f.Name.Size()
size += f.Value.Size()

if f.common == nil {
f.common = new(common)
}

f.common.size = uint32(size)

return
}

// getIdx returns the FVP index in the parent Record.
func (f *FieldValuePair) getIdx() (idx int) {

var fvps []FVP

idx = -1

if f == nil || f.parent == nil {
return
}

fvps = f.parent.getFvps()

for i, fv := range fvps {
if fv == f {
idx = i
return
}
}

return
}

421
funcs_request.go Normal file
View File

@ -0,0 +1,421 @@
package wireproto

import (
`bytes`
`cmp`
`fmt`
`hash/crc32`
`io`
`strings`

`github.com/google/uuid`
)

// ConnId returns a copy of the connection ID.
func (r *Request) ConnId() (conn uuid.UUID) {

conn = r.connId

return
}

// MarshalBinary renders a Request into a byte-packed format.
func (r *Request) MarshalBinary() (data []byte, err error) {

var b []byte
var msgSize int
var cksum uint32
var buf *bytes.Buffer = new(bytes.Buffer)
var msgBuf *bytes.Buffer = new(bytes.Buffer)

_ = r.Size()

for _, i := range r.RecordGroups {
msgSize += i.Size()
}

// The message "body" - we do this first so we can checksum (if not nil).
if _, err = msgBuf.Write(hdrBODYSTART); err != nil {
return
}
// Record group count
if _, err = msgBuf.Write(PackInt(len(r.RecordGroups))); err != nil {
return
}
// And size.
if _, err = msgBuf.Write(PackInt(msgSize)); err != nil {
return
}
for _, i := range r.RecordGroups {
if b, err = i.MarshalBinary(); err != nil {
return
}
if _, err = msgBuf.Write(b); err != nil {
return
}
}
if _, err = msgBuf.Write(hdrBODYEND); err != nil {
return
}

// Now the surrounding request.

// Checksum - update and serialize if not null.
if r.Checksum != nil {
cksum = crc32.ChecksumIEEE(msgBuf.Bytes())
*r.Checksum = cksum
if _, err = buf.Write(hdrCKSUM); err != nil {
return
}
if _, err = buf.Write(cksumBytes(*r.Checksum)); err != nil {
return
}
}

// Message start
if _, err = buf.Write(hdrMSGSTART); err != nil {
return
}

// Protocol version
if _, err = buf.Write(PackUint32(r.ProtocolVersion)); err != nil {
return
}

// Then copy the msgBuf in.
if _, err = msgBuf.WriteTo(buf); err != nil {
return
}
// And then the message end.
if _, err = buf.Write(hdrMSGEND); err != nil {
return
}

data = buf.Bytes()

return
}

// Model returns an indented string representation of the model.
func (r *Request) Model() (out string) {

out = r.ModelCustom(IndentChars, SeparatorChars, indentR)

return
}

func (r *Request) ModelCustom(indent, sep string, level uint) (out string) {

var maxFtr int
var size int
var sb strings.Builder

for _, rg := range r.RecordGroups {
size += rg.Size()
}

// Checksum (optional for Request)
sb.WriteString(strings.Repeat(indent, int(level)))
if r.Checksum == nil {
sb.WriteString(strings.Repeat("-", 8))
sb.WriteString(sep)
sb.WriteString("// (No Checksum Present)\n")
} else {
// HDR: CKSUM
sb.WriteString(padBytesRight(hdrCKSUM, 8))
sb.WriteString(sep)
sb.WriteString("// HDR:CKSUM\n")
// Checksum
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padBytesRight(cksumBytes(*r.Checksum), 8))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Checksum Value (%d)\n", *r.Checksum))
}

// Header: MSGSTART
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padBytesRight(hdrMSGSTART, 8))
sb.WriteString(sep)
sb.WriteString("// HDR:MSGSTART\n")

// Protocol Version
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padIntRight(int(r.ProtocolVersion), 8))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Protocol Version (%d)\n", r.ProtocolVersion))

// Header: BODYSTART
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padBytesRight(hdrBODYSTART, 8))
sb.WriteString(sep)
sb.WriteString("// HDR:BODYSTART\n")

// Count
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padIntRight(len(r.RecordGroups), 8))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Record Group Count (%d)\n", len(r.RecordGroups)))
// Size
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padIntRight(size, 8))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Record Groups Size (%d)\n", size))

// VALUES
for idx, rg := range r.RecordGroups {
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(fmt.Sprintf("// Record Group %d (%d bytes)\n", idx+1, rg.Size()))
sb.WriteString(rg.ModelCustom(indent, sep, level+1))
}

// Make the footers a little more nicely aligned.
switch cmp.Compare(len(hdrBODYEND), len(hdrMSGEND)) {
case -1:
maxFtr = len(hdrMSGEND)
case 1, 0:
maxFtr = len(hdrBODYEND)
}

// Footer: BODYEND
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padBytesRight(hdrBODYEND, maxFtr))
sb.WriteString(sep)
sb.WriteString("// HDR:BODYEND\n")

// Footer: MSGEND
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padBytesRight(hdrMSGEND, maxFtr))
sb.WriteString(sep)
sb.WriteString("// HDR:MSGEND\n")

out = sb.String()

return
}

// Resolve associates children with parents.
func (r *Request) Resolve() {
for idx, i := range r.RecordGroups {
i.parent = r
i.rgIdx = idx
i.Resolve()
}
}

// SetConnID allows for setting a connection ID. This is largely just used for debugging purposes.
func (r *Request) SetConnID(connId uuid.UUID) {

r.connId = connId

for _, rg := range r.RecordGroups {
rg.connId = connId
for _, rec := range rg.Records {
rec.connId = connId
for _, kvp := range rec.Pairs {
kvp.connId = &connId
}
}
}

}

// Size returns the Request's calculated size (in bytes) and updates the size field if 0.
func (r *Request) Size() (size int) {

if r == nil {
return
}

// Checksum
if r.Checksum != nil {
size += len(hdrCKSUM)
size += CksumPackedSize
}

// Message header
size += len(hdrMSGSTART)

// Protocol version
size += PackedNumSize

// Count and Size uint32s
size += PackedNumSize * 2

// Message begin
size += len(hdrBODYSTART)

for _, p := range r.RecordGroups {
size += p.Size()
}

// Message end
size += len(hdrBODYEND)

// And closing sequence.
size += len(hdrMSGEND)

if r.common == nil {
r.common = new(common)
}

r.size = uint32(size)

return
}

// ToMap returns a slice of slice of slice of FVP maps for this Message.
func (r *Request) ToMap() (m [][][]map[string]interface{}) {

m = make([][][]map[string]interface{}, len(r.RecordGroups))
for idx, rg := range r.RecordGroups {
m[idx] = rg.ToMap()
}

return
}

// UnmarshalBinary populates a Request from packed bytes.
func (r *Request) UnmarshalBinary(data []byte) (err error) {

if data == nil || len(data) == 0 {
return
}

var b []byte
var rgCnt, bodySize int
var rgSize int
var rgBuf *bytes.Buffer
var buf *bytes.Reader = bytes.NewReader(data)
var msgBuf *bytes.Buffer = new(bytes.Buffer)

if r == nil {
*r = Request{}
}
r.common = &common{}
r.size = 0

// Check for a checksum.
b = make([]byte, len(hdrMSGSTART))
if _, err = buf.Read(b); err != nil {
return
}
if bytes.Equal(b, hdrCKSUM) {
// A checksum header was found.
b = make([]byte, CksumPackedSize)
if _, err = buf.Read(b); err != nil {
return
}
r.Checksum = new(uint32)
*r.Checksum = byteOrder.Uint32(b)
// Since we've only read the checksum, we now also have to read in the MSGSTART...
b = make([]byte, len(hdrMSGSTART))
if _, err = buf.Read(b); err != nil {
return
}
// But we don't need to do anything with it.
} else {
// We've already read MSGSTART as part of the checksum check.
r.Checksum = nil
}

// Get the protocol version.
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
r.ProtocolVersion = UnpackUint32(b)

// Skip over the BODYSTART (but write it to msgBuf).
if _, err = io.CopyN(msgBuf, buf, int64(len(hdrBODYSTART))); err != nil {
return
}

// Get the count of record groups
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
rgCnt = UnpackInt(b)
if _, err = msgBuf.Write(b); err != nil {
return
}
// And their size
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
bodySize = UnpackInt(b)
if _, err = msgBuf.Write(b); err != nil {
return
}

// And the record groups themselves.
if _, err = io.CopyN(msgBuf, buf, int64(bodySize+len(hdrBODYEND))); err != nil {
return
}

// Validate the checksum.
if r.Checksum != nil {
if crc32.ChecksumIEEE(msgBuf.Bytes()) != *r.Checksum {
err = ErrBadCksum
return
}
}

// Now that we've validated the checksum (if provided), we trim the msgBuf to only RGs.
// Skip over the BODYSTART, record group count, and record group size.
if _, err = msgBuf.Read(make([]byte, len(hdrBODYSTART)+(PackedNumSize*2))); err != nil {
return
}
// Then truncate.
msgBuf.Truncate(bodySize)

r.RecordGroups = make([]*RequestRecordGroup, rgCnt)

for idx := 0; idx < rgCnt; idx++ {
rgBuf = new(bytes.Buffer)

// The RG unmarshaler handles the record count, but we need to read it to discard it in msgBuf.
if _, err = io.CopyN(rgBuf, msgBuf, int64(PackedNumSize)); err != nil {
return
}

b = make([]byte, PackedNumSize)
if _, err = msgBuf.Read(b); err != nil {
return
}
if _, err = rgBuf.Write(b); err != nil {
return
}
rgSize = UnpackInt(b)

if _, err = io.CopyN(rgBuf, msgBuf, int64(rgSize)); err != nil {
return
}

r.RecordGroups[idx] = new(RequestRecordGroup)
if err = r.RecordGroups[idx].UnmarshalBinary(rgBuf.Bytes()); err != nil {
return
}
}

_ = r.Size()

return
}

// getIdx is a NOOP for Messages, but is used for Model conformance.
func (r *Request) getIdx() (idx int) {
return
}

// getRecordGroups returns the RecordGroups in this Message.
func (r *Request) getRecordGroups() (recordGroups []RecordGroup) {

recordGroups = make([]RecordGroup, len(r.RecordGroups))
for idx, rg := range r.RecordGroups {
recordGroups[idx] = rg
}

return
}

254
funcs_requestrecord.go Normal file
View File

@ -0,0 +1,254 @@
package wireproto

import (
`bytes`
`fmt`
`io`
`strings`

`github.com/google/uuid`
)

// ConnId returns a copy of the connection ID.
func (r *RequestRecord) ConnId() (conn uuid.UUID) {

conn = r.connId

return
}

// GetParent returns the parent RecordGroup.
func (r *RequestRecord) GetParent() (rg RecordGroup) {

rg = r.parent

return
}

// MarshalBinary renders a RequestRecord into a byte-packed format.
func (r *RequestRecord) MarshalBinary() (data []byte, err error) {

var b []byte
var recSize int
var buf *bytes.Buffer = new(bytes.Buffer)

_ = r.Size()

if _, err = buf.Write(PackInt(len(r.Pairs))); err != nil {
return
}
for _, i := range r.Pairs {
recSize += i.Size()
}
if _, err = buf.Write(PackInt(recSize)); err != nil {
return
}

for _, i := range r.Pairs {
if b, err = i.MarshalBinary(); err != nil {
return
}
if _, err = buf.Write(b); err != nil {
return
}
}

data = buf.Bytes()

return
}

// Model returns an indented string representation of the model.
func (r *RequestRecord) Model() (out string) {

out = r.ModelCustom(IndentChars, SeparatorChars, indentRec)

return
}

// ModelCustom is like Model with user-defined formatting.
func (r *RequestRecord) ModelCustom(indent, sep string, level uint) (out string) {

var size int
var sb strings.Builder

for _, p := range r.Pairs {
size += p.Size()
}

// Count
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(fmt.Sprintf("%x", PackUint32(uint32(len(r.Pairs)))))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Field/Value Count (%d)\n", len(r.Pairs)))
// Size
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(fmt.Sprintf("%x", PackUint32(uint32(size))))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Record Size (%d)\n", size))

// VALUES
for idx, p := range r.Pairs {
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(
fmt.Sprintf(
"// Record Group %d, Record %d, Field/Value %d (%d bytes)\n",
r.rgIdx+1, r.rIdx+1, idx+1, p.Size(),
),
)
sb.WriteString(p.ModelCustom(indent, sep, level+1))
}

out = sb.String()

return
}

// Resolve associates children with parents.
func (r *RequestRecord) Resolve() {
for idx, i := range r.Pairs {
i.parent = r
i.rgIdx = r.rgIdx
i.rIdx = idx
i.fvpidx = idx
// KVP have no Resolve() method.
}
}

// Size returns the RequestRecord's calculated size (in bytes) and updates the size field if 0.
func (r *RequestRecord) Size() (size int) {

if r == nil {
return
}

// Count and Size uint32s
size += PackedNumSize * 2

for _, i := range r.Pairs {
size += i.Size()
}

if r.common == nil || r.size == 0 {
r.common = &common{
size: uint32(size),
}
}

r.size = uint32(size)

return
}

// ToMap returns a slice of FVP maps for this Record.
func (r *RequestRecord) ToMap() (m []map[string]interface{}) {

m = make([]map[string]interface{}, len(r.Pairs))
for idx, p := range r.Pairs {
m[idx] = p.ToMap()
}

return
}

// UnmarshalBinary populates a RequestRecord from packed bytes.
func (r *RequestRecord) UnmarshalBinary(data []byte) (err error) {

if data == nil || len(data) == 0 {
return
}

var b []byte
var cnt, size int
var fvpNmSize, fvpValSize int
var fvpBuf *bytes.Buffer
var buf *bytes.Reader = bytes.NewReader(data)

if r == nil {
*r = RequestRecord{}
}
if r.common == nil {
r.common = new(common)
}
r.size = 0

// FVP count.
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
cnt = UnpackInt(b)

// Size of record.
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
size = UnpackInt(b)

// And now we handle the FVPs themselves.
b = make([]byte, size)
if _, err = buf.Read(b); err != nil {
return
}
buf = bytes.NewReader(b)

r.Pairs = make([]*FieldValuePair, cnt)

for idx := 0; idx < cnt; idx++ {
fvpBuf = new(bytes.Buffer)

// Unlike parents, the FVP needs both allocators because they're both size allocators.
// FVP Name
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
if _, err = fvpBuf.Write(b); err != nil {
return
}
fvpNmSize = UnpackInt(b)
// FVP Value
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
if _, err = fvpBuf.Write(b); err != nil {
return
}
fvpValSize = UnpackInt(b)

if _, err = io.CopyN(fvpBuf, buf, int64(fvpNmSize+fvpValSize)); err != nil {
return
}

r.Pairs[idx] = new(FieldValuePair)
if err = r.Pairs[idx].UnmarshalBinary(fvpBuf.Bytes()); err != nil {
return
}
}

_ = r.Size()
_ = size

return
}

// getFvps returns this Record's FVP.
func (r *RequestRecord) getFvps() (fvp []FVP) {

fvp = make([]FVP, len(r.Pairs))
for idx, p := range r.Pairs {
fvp[idx] = p
}

return
}

// getIdx returns the Record index in the parent RecordGroup.
func (r *RequestRecord) getIdx() (idx int) {

idx = r.rIdx

return
}

250
funcs_requestrecordgroup.go Normal file
View File

@ -0,0 +1,250 @@
package wireproto

import (
`bytes`
`fmt`
`io`
`strings`

`github.com/google/uuid`
)

// ConnId returns a copy of the connection ID.
func (r *RequestRecordGroup) ConnId() (conn uuid.UUID) {

conn = r.connId

return
}

// GetParent returns this RecordGroup's Message.
func (r *RequestRecordGroup) GetParent() (msg Message) {

msg = r.parent

return
}

// MarshalBinary renders a RequestRecordGroup into a byte-packed format.
func (r *RequestRecordGroup) MarshalBinary() (data []byte, err error) {

var b []byte
var rgSize int
var buf *bytes.Buffer = new(bytes.Buffer)

_ = r.Size()

for _, i := range r.Records {
rgSize += i.Size()
}

// Count
if _, err = buf.Write(PackInt(len(r.Records))); err != nil {
return
}
// Size
if _, err = buf.Write(PackInt(rgSize)); err != nil {
return
}

for _, i := range r.Records {
if b, err = i.MarshalBinary(); err != nil {
return
}
if _, err = buf.Write(b); err != nil {
return
}
}

data = buf.Bytes()

return
}

// Model returns an indented string representation of the model.
func (r *RequestRecordGroup) Model() (out string) {

out = r.ModelCustom(IndentChars, SeparatorChars, indentRG)

return
}

// ModelCustom is like Model with user-defined formatting.
func (r *RequestRecordGroup) ModelCustom(indent, sep string, level uint) (out string) {

var size int
var sb strings.Builder

for _, rec := range r.Records {
size += rec.Size()
}

// Count
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(fmt.Sprintf("%x", PackUint32(uint32(len(r.Records)))))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Record Count (%d)\n", len(r.Records)))
// Size
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(fmt.Sprintf("%x", PackUint32(uint32(size))))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Record Group Size (%d)\n", size))

// VALUES
for idx, rec := range r.Records {
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(
fmt.Sprintf(
"// Record Group %d, Record %d (%d bytes)\n",
r.rgIdx+1, idx+1, rec.Size(),
),
)
sb.WriteString(rec.ModelCustom(indent, sep, level+1))
}

out = sb.String()

return
}

// Resolve associates children with parents.
func (r *RequestRecordGroup) Resolve() {
for idx, i := range r.Records {
i.parent = r
i.rgIdx = r.rgIdx
i.rIdx = idx
i.Resolve()
}
}

// Size returns the RequestRecordGroup's calculated size (in bytes) and updates the size field if 0.
func (r *RequestRecordGroup) Size() (size int) {

if r == nil {
return
}

// Count and Size uint32s
size += PackedNumSize * 2

for _, p := range r.Records {
size += p.Size()
}

if r.common == nil || r.size == 0 {
r.common = new(common)
}

r.common.size = uint32(size)

return
}

// ToMap returns a slice of slice of FVP maps for this RecordGroup.
func (r *RequestRecordGroup) ToMap() (m [][]map[string]interface{}) {

m = make([][]map[string]interface{}, len(r.Records))
for idx, rec := range r.Records {
m[idx] = rec.ToMap()
}

return
}

// UnmarshalBinary populates a RequestRecordGroup from packed bytes.
func (r *RequestRecordGroup) UnmarshalBinary(data []byte) (err error) {

if data == nil || len(data) == 0 {
return
}

var b []byte
var cnt, size int
var recSize int
var recBuf *bytes.Buffer
var buf *bytes.Reader = bytes.NewReader(data)

if r == nil {
*r = RequestRecordGroup{}
}
if r.common == nil {
r.common = new(common)
}
r.size = 0

// The record count.
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
cnt = UnpackInt(b)

// The record group size.
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
size = UnpackInt(b)

b = make([]byte, size)
if _, err = buf.Read(b); err != nil {
return
}

// Get a new buf for the actual records.
buf = bytes.NewReader(b)

r.Records = make([]*RequestRecord, cnt)

for idx := 0; idx < cnt; idx++ {
recBuf = new(bytes.Buffer)

// We skip over the KVP count; that's handled in the record Unmarshaler.
// We *do*, however, need to save it to the recBuf.
if _, err = io.CopyN(recBuf, buf, int64(PackedNumSize)); err != nil {
return
}

// Size of the actual record
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
if _, err = recBuf.Write(b); err != nil {
return
}
recSize = UnpackInt(b)

if _, err = io.CopyN(recBuf, buf, int64(recSize)); err != nil {
return
}

r.Records[idx] = new(RequestRecord)
if err = r.Records[idx].UnmarshalBinary(recBuf.Bytes()); err != nil {
return
}
}

_ = r.Size()

return
}

// getIdx returns the RecordGroup index in the parent Message.
func (r *RequestRecordGroup) getIdx() (idx int) {

idx = r.rgIdx

return
}

// getRecords returns the Records in this RecordGroup.
func (r *RequestRecordGroup) getRecords() (records []Record) {

records = make([]Record, len(r.Records))
for idx, rec := range r.Records {
records[idx] = rec
}

return
}

485
funcs_response.go Normal file
View File

@ -0,0 +1,485 @@
package wireproto

import (
`bytes`
`cmp`
`fmt`
`hash/crc32`
`io`
`strings`

`r00t2.io/goutils/multierr`
)

// GenChecksum (re-)generates and returns the checksum. The body that is checksummed is returned in buf.
func (r *Response) GenChecksum() (cksum uint32, buf *bytes.Buffer, err error) {

var b []byte
var size int

buf = new(bytes.Buffer)

_ = r.Size()

for _, p := range r.RecordGroups {
size += p.Size()
}

if _, err = buf.Write(hdrBODYSTART); err != nil {
return
}

if _, err = buf.Write(PackInt(len(r.RecordGroups))); err != nil {
return
}
if _, err = buf.Write(PackInt(size)); err != nil {
return
}

for _, rg := range r.RecordGroups {
if b, err = rg.MarshalBinary(); err != nil {
return
}
if _, err = buf.Write(b); err != nil {
return
}
}

if _, err = buf.Write(hdrBODYEND); err != nil {
return
}

cksum = crc32.ChecksumIEEE(buf.Bytes())

r.Checksum = cksum

return
}

// RespStat trawls the KVP for a field name with "error" and updates the status to reflect an error was found.
func (r *Response) RespStat() {

r.Status = RespStatusByteOK

for _, rg := range r.RecordGroups {
for _, rec := range rg.Records {
for _, kvp := range rec.Pairs {
if strings.ToLower(kvp.Name.String()) == "error" {
r.Status = RespStatusByteErr
return
}
}
}
}

}

// MarshalBinary renders a Response into a byte-packed format.
func (r *Response) MarshalBinary() (data []byte, err error) {

var b []byte
var msgSize int
var hasErr bool
var mErr *multierr.MultiError = multierr.NewMultiError(nil)
var buf *bytes.Buffer = new(bytes.Buffer)
var msgBuf *bytes.Buffer = new(bytes.Buffer)

_ = r.Size()

for _, i := range r.RecordGroups {
msgSize += i.Size()
}

// The message "body" - we do this first so we can checksum .
if _, err = msgBuf.Write(hdrBODYSTART); err != nil {
return
}
// Record group count
if _, err = msgBuf.Write(PackInt(len(r.RecordGroups))); err != nil {
return
}
// And size.
if _, err = msgBuf.Write(PackInt(msgSize)); err != nil {
return
}
for _, i := range r.RecordGroups {
if b, err = i.MarshalBinary(); err != nil {
mErr.AddError(err)
err = nil
hasErr = true
}
if _, err = msgBuf.Write(b); err != nil {
mErr.AddError(err)
err = nil
hasErr = true
}
}
if _, err = msgBuf.Write(hdrBODYEND); err != nil {
mErr.AddError(err)
err = nil
hasErr = true
}

// Now we write the response as a whole.

// Status
if r.Status == RespStatusByteOK && hasErr {
r.Status = RespStatusByteErr
}
if _, err = buf.Write([]byte{r.Status}); err != nil {
return
}

// Checksum -- ALWAYS present for responses!
if _, _, err = r.GenChecksum(); err != nil {
return
}
if _, err = buf.Write(hdrCKSUM); err != nil {
return
}
if _, err = buf.Write(cksumBytes(r.Checksum)); err != nil {
return
}

// Message start
if _, err = buf.Write(hdrMSGSTART); err != nil {
return
}

// Protocol version
if _, err = buf.Write(PackUint32(r.ProtocolVersion)); err != nil {
return
}

// Then copy the msgBuf in.
if _, err = msgBuf.WriteTo(buf); err != nil {
return
}
// And then the message end.
if _, err = buf.Write(hdrMSGEND); err != nil {
return
}

data = buf.Bytes()

if !mErr.IsEmpty() {
err = mErr
return
}

return
}

// Model returns an indented string representation of the model.
func (r *Response) Model() (out string) {

out = r.ModelCustom(IndentChars, SeparatorChars, indentR)

return
}

// ModelCustom is like Model with user-defined formatting.
func (r *Response) ModelCustom(indent, sep string, level uint) (out string) {

var maxFtr int
var size int
var sb strings.Builder

for _, rg := range r.RecordGroups {
size += rg.Size()
}

_, _, _ = r.GenChecksum()

// HDR: RESPSTART/RESPERR (RESPSTATUS)
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padBytesRight([]byte{byte(r.Status)}, 8))
sb.WriteString(sep)
switch r.Status {
case RespStatusByteOK:
sb.WriteString("// HDR:RESPSTART (Status: OK)\n")
case RespStatusByteErr:
sb.WriteString("// HDR:RESPERR (Status: Error)\n")
}

// HDR: CKSUM
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padBytesRight(hdrCKSUM, 8))
sb.WriteString(sep)
sb.WriteString("// HDR:CKSUM\n")
// Checksum
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padBytesRight(cksumBytes(r.Checksum), 8))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Checksum Value (%d)\n", r.Checksum))

// Header: MSGSTART
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padBytesRight(hdrMSGSTART, 8))
sb.WriteString(sep)
sb.WriteString("// HDR:MSGSTART\n")

// Protocol Version
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padIntRight(int(r.ProtocolVersion), 8))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Protocol Version (%d)\n", r.ProtocolVersion))

// Header: BODYSTART
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padBytesRight(hdrBODYSTART, 8))
sb.WriteString(sep)
sb.WriteString("// HDR:BODYSTART\n")

// Count
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padIntRight(len(r.RecordGroups), 8))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Record Group Count (%d)\n", len(r.RecordGroups)))
// Size
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padIntRight(size, 8))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Record Groups Size (%d)\n", size))

// VALUES
for idx, rg := range r.RecordGroups {
sb.WriteString(fmt.Sprintf("// Record Group %d (%d)\n", idx+1, rg.Size()))
sb.WriteString(rg.ModelCustom(indent, sep, level+1))
}

// Make the footers a little more nicely aligned.
switch cmp.Compare(len(hdrBODYEND), len(hdrMSGEND)) {
case -1:
maxFtr = len(hdrMSGEND)
case 1, 0:
maxFtr = len(hdrBODYEND)
}

// Footer: BODYEND
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padBytesRight(hdrBODYEND, maxFtr))
sb.WriteString(sep)
sb.WriteString("// HDR:BODYEND\n")

// Footer: MSGEND
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(padBytesRight(hdrMSGEND, maxFtr))
sb.WriteString(sep)
sb.WriteString("// HDR:MSGEND\n")

out = sb.String()

return
}

// Resolve associates children with parents.
func (r *Response) Resolve() {
for idx, i := range r.RecordGroups {
i.parent = r
i.rgIdx = idx
i.Resolve()
}
}

// Size returns the Response's calculated size (in bytes) and updates the size field if 0.
func (r *Response) Size() (size int) {

if r == nil {
return
}

// Response Status
size += 1

// Checksum
size += len(hdrCKSUM)
size += CksumPackedSize

// Message header
size += len(hdrMSGSTART)

// Protocol version
size += PackedNumSize

// Count and Size uint32s
size += PackedNumSize * 2

// Message begin
size += len(hdrBODYSTART)

for _, p := range r.RecordGroups {
size += p.Size()
}

// Message end
size += len(hdrBODYEND)

// And closing sequence.
size += len(hdrMSGEND)

if r.common == nil || r.size == 0 {
r.common = new(common)
}

r.size = uint32(size)

return
}

// ToMap returns a slice of slice of slice of FVP maps for this Message.
func (r *Response) ToMap() (m [][][]map[string]interface{}) {

m = make([][][]map[string]interface{}, len(r.RecordGroups))
for idx, rg := range r.RecordGroups {
m[idx] = rg.ToMap()
}

return
}

// UnmarshalBinary populates a Response from packed bytes.
func (r *Response) UnmarshalBinary(data []byte) (err error) {

if data == nil || len(data) == 0 {
return
}

var b []byte
var rgCnt, bodySize int
var rgSize int
var rgBuf *bytes.Buffer
var buf *bytes.Reader = bytes.NewReader(data)
var msgBuf *bytes.Buffer = new(bytes.Buffer)

if r == nil {
*r = Response{}
}
r.common = new(common)
r.size = 0

// Get the status.
if r.Status, err = buf.ReadByte(); err != nil {
return
}

// And the checksum -- responses *always* have checksums per spec!
// Toss the checksum header (after confirming).
b = make([]byte, len(hdrCKSUM))
if _, err = buf.Read(b); err != nil {
return
}
if !bytes.Equal(b, hdrCKSUM) {
err = ErrBadHdr
return
}
// And get the checksum.
b = make([]byte, CksumPackedSize)
if _, err = buf.Read(b); err != nil {
return
}
r.Checksum = UnpackUint32(b)

// Read (and toss) the message start header.
if _, err = buf.Read(make([]byte, len(hdrMSGSTART))); err != nil {
return
}

// Get the protocol version.
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
r.ProtocolVersion = UnpackUint32(b)

// Skip over the BODYSTART (but write it to msgBuf).
if _, err = io.CopyN(msgBuf, buf, int64(len(hdrBODYSTART))); err != nil {
return
}
// Get the count of record groups
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
rgCnt = UnpackInt(b)
if _, err = msgBuf.Write(b); err != nil {
return
}
// Get the size of record groups
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
bodySize = UnpackInt(b)
if _, err = msgBuf.Write(b); err != nil {
return
}

// And the record groups themselves.
if _, err = io.CopyN(msgBuf, buf, int64(bodySize+len(hdrBODYEND))); err != nil {
return
}

// Now validate the checksum before continuing.
if crc32.ChecksumIEEE(msgBuf.Bytes()) != r.Checksum {
err = ErrBadCksum
return
}

// Now that we've validated the checksum, we trim the msgBuf to only RGs.
// Skip over the BODYSTART, record group count, and record group size.
if _, err = msgBuf.Read(make([]byte, len(hdrBODYSTART)+(PackedNumSize*2))); err != nil {
return
}
// Then truncate.
msgBuf.Truncate(bodySize)

r.RecordGroups = make([]*ResponseRecordGroup, rgCnt)

for idx := 0; idx < rgCnt; idx++ {
rgBuf = new(bytes.Buffer)

// The RG unmarshaler handles the record count, but we need to read it into msgBuf.
if _, err = io.CopyN(rgBuf, msgBuf, int64(PackedNumSize)); err != nil {
return
}

b = make([]byte, PackedNumSize)
if _, err = msgBuf.Read(b); err != nil {
return
}
if _, err = rgBuf.Write(b); err != nil {
return
}
rgSize = UnpackInt(b)

if _, err = io.CopyN(rgBuf, msgBuf, int64(rgSize)); err != nil {
return
}

r.RecordGroups[idx] = new(ResponseRecordGroup)
if err = r.RecordGroups[idx].UnmarshalBinary(rgBuf.Bytes()); err != nil {
return
}
}

_ = r.Size()

return
}

// getIdx is a NOOP for Messages, but is used for Model conformance.
func (r *Response) getIdx() (idx int) {
return
}

// getRecordGroups returns the RecordGroups in this Message.
func (r *Response) getRecordGroups() (recordGroups []RecordGroup) {

recordGroups = make([]RecordGroup, len(r.RecordGroups))
for idx, rg := range r.RecordGroups {
recordGroups[idx] = rg
}

return
}

393
funcs_responserecord.go Normal file
View File

@ -0,0 +1,393 @@
package wireproto

import (
`bytes`
`fmt`
`io`
`strings`
)

// GetParent returns the parent RecordGroup.
func (r *ResponseRecord) GetParent() (rg RecordGroup) {

rg = r.parent

return
}

// MarshalBinary renders a ResponseRecord into a byte-packed format.
func (r *ResponseRecord) MarshalBinary() (data []byte, err error) {

var b []byte
var recSize int
var buf *bytes.Buffer = new(bytes.Buffer)

_ = r.Size()

// KVP Count
if _, err = buf.Write(PackInt(len(r.Pairs))); err != nil {
return
}
for _, i := range r.Pairs {
recSize += i.Size()
}
// KVP Size
if _, err = buf.Write(PackInt(recSize)); err != nil {
return
}
// Original/Request Size
if _, err = buf.Write(PackInt(r.OriginalRecord.Size())); err != nil {
return
}

for _, i := range r.Pairs {
if b, err = i.MarshalBinary(); err != nil {
return
}
if _, err = buf.Write(b); err != nil {
return
}
}

if r.OriginalRecord != nil {
if b, err = r.OriginalRecord.MarshalBinary(); err != nil {
return
}
if _, err = buf.Write(b); err != nil {
return
}
}

data = buf.Bytes()

return
}

// Model returns an indented string representation of the model.
func (r *ResponseRecord) Model() (out string) {

out = r.ModelCustom(IndentChars, SeparatorChars, indentRec)

return
}

// ModelCustom is like Model with user-defined formatting.
func (r *ResponseRecord) ModelCustom(indent, sep string, level uint) (out string) {

var size int
var sb strings.Builder

for _, p := range r.Pairs {
size += p.Size()
}

// Count
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(fmt.Sprintf("%x", PackUint32(uint32(len(r.Pairs)))))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Field/Value Count (%d)\n", len(r.Pairs)))
// Size
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(fmt.Sprintf("%x", PackUint32(uint32(size))))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Record Size (%d)\n", size))
// Request Record Size
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(fmt.Sprintf("%x", PackUint32(uint32(r.OriginalRecord.Size()))))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Request Record Size (%d)\n", r.OriginalRecord.Size()))

// VALUES
for idx, p := range r.Pairs {
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(
fmt.Sprintf(
"// Record Group %d, Record %d, Field/Value %d (%d bytes)\n",
r.rgIdx+1, r.rIdx+1, idx+1, p.Size(),
),
)
sb.WriteString(p.ModelCustom(indent, sep, level+1))
}

// REQUEST
sb.WriteString(strings.Repeat(indent, int(level)))
if r.OriginalRecord == nil {
sb.WriteString("// (No Original Request Record Attached)\n")
} else {
sb.WriteString(
fmt.Sprintf("// Record Group %d, Record %d (REQUEST RECORD) (%d bytes)\n", r.rgIdx+1, r.rIdx+1, r.OriginalRecord.Size()),
)
sb.WriteString(r.OriginalRecord.ModelCustom(indent, sep, level+1))
}

out = sb.String()

return
}

// Resolve associates children with parents.
func (r *ResponseRecord) Resolve() {
for idx, i := range r.Pairs {
i.parent = r
i.rgIdx = r.rgIdx
i.rIdx = idx
i.fvpidx = idx
// KVP have no Resolve() method.
}
}

/*
Size returns the ResponseRecord's calculated size (in bytes) and updates the size field if 0.

Note that it *includes* the size of ResponseRecord.OriginalRecord and its allocator.
*/
func (r *ResponseRecord) Size() (size int) {

if r == nil {
return
}

// Count and Size uint32s, plus Size for response record.
size += PackedNumSize * 3

for _, p := range r.Pairs {
size += p.Size()
}

if r.OriginalRecord != nil {
size += r.OriginalRecord.Size()
}

if r.common == nil {
r.common = new(common)
}

r.size = uint32(size)

return
}

/*
SizeNoResp is like Size but does not include the size of the ResponseRecord.OriginalRecord nor its allocator.

It does *not* update the internal size field.
*/
func (r *ResponseRecord) SizeNoResp() (size int) {

if r == nil {
return
}

// Count and Size uint32s
size += PackedNumSize * 2

for _, p := range r.Pairs {
size += p.Size()
}

return
}

// ToMap returns a slice of FVP maps for this Record.
func (r *ResponseRecord) ToMap() (m []map[string]interface{}) {

m = make([]map[string]interface{}, len(r.Pairs))
for idx, p := range r.Pairs {
m[idx] = p.ToMap()
}

return
}

// UnmarshalBinary populates a ResponseRecord from packed bytes.
func (r *ResponseRecord) UnmarshalBinary(data []byte) (err error) {

if data == nil || len(data) == 0 {
return
}

var b []byte
var cnt, size, reqSize int
var fvpNmSize, fvpValSize int
var fvpBuf *bytes.Buffer
var recBuf *bytes.Buffer = new(bytes.Buffer)
var origBuf *bytes.Buffer = new(bytes.Buffer)
var buf *bytes.Reader = bytes.NewReader(data)

if r == nil {
*r = ResponseRecord{}
}
if r.common == nil {
r.common = new(common)
}
r.size = 0

// FVP count.
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
cnt = UnpackInt(b)

// Size of record.
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
size = UnpackInt(b)

// Size of original record.
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
reqSize = UnpackInt(b)

// And split out the FVPs and original record.
if _, err = io.CopyN(recBuf, buf, int64(size)); err != nil {
return
}
if _, err = io.CopyN(origBuf, buf, int64(reqSize)); err != nil {
return
}

r.Pairs = make([]*FieldValuePair, cnt)

for idx := 0; idx < cnt; idx++ {
fvpBuf = new(bytes.Buffer)

// Field name size
b = make([]byte, PackedNumSize)
if _, err = recBuf.Read(b); err != nil {
return
}
if _, err = fvpBuf.Write(b); err != nil {
return
}
fvpNmSize = UnpackInt(b)

// Field value size
b = make([]byte, PackedNumSize)
if _, err = recBuf.Read(b); err != nil {
return
}
if _, err = fvpBuf.Write(b); err != nil {
return
}
fvpValSize = UnpackInt(b)

if _, err = io.CopyN(fvpBuf, recBuf, int64(fvpNmSize+fvpValSize)); err != nil {
return
}

r.Pairs[idx] = new(FieldValuePair)
if err = r.Pairs[idx].UnmarshalBinary(fvpBuf.Bytes()); err != nil {
return
}
}

if reqSize != 0 {
r.OriginalRecord = new(RequestRecord)
if err = r.OriginalRecord.UnmarshalBinary(origBuf.Bytes()); err != nil {
return
}
}

_ = r.Size()

return
}

// getFvps returns this Record's FVP.
func (r *ResponseRecord) getFvps() (fvp []FVP) {

fvp = make([]FVP, len(r.Pairs))
for idx, p := range r.Pairs {
fvp[idx] = p
}

return
}

// getIdx returns the Record index in the parent RecordGroup.
func (r *ResponseRecord) getIdx() (idx int) {

idx = r.rIdx

return
}

/*
recToFVPs returns a slice of FieldValuePair from a Record. Mostly used for ResponseFieldValuePair.OriginalRecord.

data should be a Record data structure.
*/
func (r *ResponseRecord) recToFVPs(data []byte) (fvps []*FieldValuePair, err error) {

var b []byte
var fvpCnt, recSize int
var fvpNmSize, fvpValSize int
var fvpBuf *bytes.Buffer
var buf *bytes.Reader = bytes.NewReader(data)

// Number of FVPs
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
fvpCnt = UnpackInt(b)
fvps = make([]*FieldValuePair, fvpCnt)

// Size of record
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
recSize = UnpackInt(b)

// And create a new buffer from the remainder.
b = make([]byte, recSize)
if _, err = buf.Read(b); err != nil {
return
}
buf = bytes.NewReader(b)

for idx := 0; idx < fvpCnt; idx++ {
fvpBuf = new(bytes.Buffer)

// Get the fvp name size
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
if _, err = fvpBuf.Write(b); err != nil {
return
}
fvpNmSize = UnpackInt(b)

// Get the fvp value size
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
if _, err = fvpBuf.Write(b); err != nil {
return
}
fvpValSize = UnpackInt(b)

b = make([]byte, fvpNmSize+fvpValSize)
if _, err = buf.Read(b); err != nil {
return
}
if _, err = fvpBuf.Write(b); err != nil {
return
}

fvps[idx] = new(FieldValuePair)
if err = fvps[idx].UnmarshalBinary(fvpBuf.Bytes()); err != nil {
return
}
}

return
}

View File

@ -0,0 +1,251 @@
package wireproto

import (
`bytes`
`fmt`
`io`
`strings`
)

// GetParent returns this RecordGroup's Message.
func (r *ResponseRecordGroup) GetParent() (msg Message) {

msg = r.parent

return
}

// MarshalBinary renders a ResponseRecordGroup into a byte-packed format.
func (r *ResponseRecordGroup) MarshalBinary() (data []byte, err error) {

var b []byte
var rgSize int
var buf *bytes.Buffer = new(bytes.Buffer)

_ = r.Size()

for _, i := range r.Records {
rgSize += i.Size()
}

// Count
if _, err = buf.Write(PackInt(len(r.Records))); err != nil {
return
}
// Size
if _, err = buf.Write(PackInt(rgSize)); err != nil {
return
}

for _, i := range r.Records {
if b, err = i.MarshalBinary(); err != nil {
return
}
if _, err = buf.Write(b); err != nil {
return
}
}

data = buf.Bytes()

return
}

// Model returns an indented string representation of the model.
func (r *ResponseRecordGroup) Model() (out string) {

out = r.ModelCustom(IndentChars, SeparatorChars, indentRG)

return
}

// ModelCustom is like Model with user-defined formatting.
func (r *ResponseRecordGroup) ModelCustom(indent, sep string, level uint) (out string) {

var sb strings.Builder
var size int

for _, rec := range r.Records {
size += rec.Size()
}

// Count
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(fmt.Sprintf("%x", PackUint32(uint32(len(r.Records)))))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Record Count (%d)\n", len(r.Records)))
// Size
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(fmt.Sprintf("%x", PackUint32(uint32(size))))
sb.WriteString(sep)
sb.WriteString(fmt.Sprintf("// Record Group Size (%d)\n", size))

// VALUES
for idx, rec := range r.Records {
sb.WriteString(strings.Repeat(indent, int(level)))
sb.WriteString(
fmt.Sprintf(
"// Record Group %d, Record %d (%d bytes)\n",
r.rgIdx+1, idx+1, rec.Size(),
),
)
sb.WriteString(rec.ModelCustom(indent, sep, level+1))
}

out = sb.String()

return
}

// Resolve associates children with parents.
func (r *ResponseRecordGroup) Resolve() {
for idx, i := range r.Records {
i.parent = r
i.rgIdx = r.rgIdx
i.rIdx = idx
i.Resolve()
}
}

// Size returns the ResponseRecordGroup's calculated size (in bytes) and updates the size field if 0.
func (r *ResponseRecordGroup) Size() (size int) {

if r == nil {
return
}

// Count and Size uint32s
size += PackedNumSize * 2

for _, p := range r.Records {
size += p.Size()
}

if r.common == nil {
r.common = new(common)
}

r.common.size = uint32(size)

return
}

// ToMap returns a slice of slice of FVP maps for this RecordGroup.
func (r *ResponseRecordGroup) ToMap() (m [][]map[string]interface{}) {

m = make([][]map[string]interface{}, len(r.Records))
for idx, rec := range r.Records {
m[idx] = rec.ToMap()
}

return
}

// UnmarshalBinary populates a ResponseRecordGroup from packed bytes.
func (r *ResponseRecordGroup) UnmarshalBinary(data []byte) (err error) {

if data == nil || len(data) == 0 {
return
}

var b []byte
var cnt, size int
var recSize int
var recBuf *bytes.Buffer
var buf *bytes.Reader = bytes.NewReader(data)

if r == nil {
*r = ResponseRecordGroup{}
}
if r.common == nil {
r.common = new(common)
}
r.size = 0

// The record count.
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
cnt = UnpackInt(b)

// The record group size.
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
size = UnpackInt(b)

b = make([]byte, size)
if _, err = buf.Read(b); err != nil {
return
}

// Get a new buf for the actual records.
buf = bytes.NewReader(b)

r.Records = make([]*ResponseRecord, cnt)

for idx := 0; idx < cnt; idx++ {
recBuf = new(bytes.Buffer)

// We skip over the KVP count; that's handled in the record Unmarshaler.
// We *do*, however, need to save it to the recBuf.
if _, err = io.CopyN(recBuf, buf, int64(PackedNumSize)); err != nil {
return
}

// Size of the actual record
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
if _, err = recBuf.Write(b); err != nil {
return
}
recSize = UnpackInt(b)

// And add the size of the response record.
b = make([]byte, PackedNumSize)
if _, err = buf.Read(b); err != nil {
return
}
if _, err = recBuf.Write(b); err != nil {
return
}
recSize += UnpackInt(b)

// Then the record (and original record).
if _, err = io.CopyN(recBuf, buf, int64(recSize)); err != nil {
return
}

r.Records[idx] = new(ResponseRecord)
if err = r.Records[idx].UnmarshalBinary(recBuf.Bytes()); err != nil {
return
}
}

_ = r.Size()

return
}

// getIdx returns the RecordGroup index in the parent Message.
func (r *ResponseRecordGroup) getIdx() (idx int) {

idx = r.rgIdx

return
}

// getRecords returns the Records in this RecordGroup.
func (r *ResponseRecordGroup) getRecords() (records []Record) {

records = make([]Record, len(r.Records))
for idx, rec := range r.Records {
records[idx] = rec
}

return
}

328
funcs_test.go Normal file
View File

@ -0,0 +1,328 @@
package wireproto

import (
`bytes`
`encoding/hex`
`encoding/json`
`fmt`
"testing"

// `github.com/davecgh/go-spew/spew`
)

var (
testSimpleReqEncoded []byte = []byte{
0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00,
0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x31, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 0x03, 0x04,
}
testSimpleRespEncoded []byte = []byte{
0x06, 0x1b, 0xce, 0xfd, 0x07, 0x20, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x10,
0x64, 0x61, 0x74, 0x61, 0x31, 0x3c, 0x61, 0x72, 0x62, 0x69, 0x74, 0x72, 0x61, 0x72, 0x79, 0x20,
0x64, 0x61, 0x74, 0x61, 0x3e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x31, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x32, 0x03, 0x04,
}
testMultiReqEncoded []byte = []byte{
0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x31, 0x41, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x41, 0x31, 0x41, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69,
0x65, 0x6c, 0x64, 0x41, 0x31, 0x42, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x41, 0x31, 0x42, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69,
0x65, 0x6c, 0x64, 0x41, 0x32, 0x41, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x41, 0x32, 0x41, 0x00, 0x00,
0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x32, 0x42, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x41, 0x32, 0x42, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69,
0x65, 0x6c, 0x64, 0x42, 0x31, 0x41, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x31, 0x41, 0x00, 0x00,
0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x31, 0x42, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x42, 0x31, 0x42, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x32, 0x41, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x42, 0x32, 0x41, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69,
0x65, 0x6c, 0x64, 0x42, 0x32, 0x42, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x32, 0x42, 0x03, 0x04,
}
testMultiRespEncoded []byte = []byte{
0x06, 0x1b, 0xae, 0x88, 0xbe, 0xd2, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x01, 0x98, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x10,
0x64, 0x61, 0x74, 0x61, 0x41, 0x31, 0x3c, 0x61, 0x72, 0x62, 0x69, 0x74, 0x72, 0x61, 0x72, 0x79,
0x20, 0x64, 0x61, 0x74, 0x61, 0x3e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x31, 0x41, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x41, 0x31, 0x41, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69,
0x65, 0x6c, 0x64, 0x41, 0x31, 0x42, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x41, 0x31, 0x42, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00,
0x00, 0x10, 0x64, 0x61, 0x74, 0x61, 0x41, 0x32, 0x3c, 0x61, 0x72, 0x62, 0x69, 0x74, 0x72, 0x61,
0x72, 0x79, 0x20, 0x64, 0x61, 0x74, 0x61, 0x3e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x30,
0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x32, 0x41,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x41, 0x32, 0x41, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08,
0x66, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x32, 0x42, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x41, 0x32, 0x42,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1e,
0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x10, 0x64, 0x61, 0x74, 0x61,
0x42, 0x31, 0x3c, 0x61, 0x72, 0x62, 0x69, 0x74, 0x72, 0x61, 0x72, 0x79, 0x20, 0x64, 0x61, 0x74,
0x61, 0x3e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00,
0x00, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x31, 0x41, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42,
0x31, 0x41, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42,
0x31, 0x42, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x31, 0x42, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x1e, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x10, 0x64, 0x61,
0x74, 0x61, 0x42, 0x32, 0x3c, 0x61, 0x72, 0x62, 0x69, 0x74, 0x72, 0x61, 0x72, 0x79, 0x20, 0x64,
0x61, 0x74, 0x61, 0x3e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x08, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x32, 0x41, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x42, 0x32, 0x41, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x66, 0x69, 0x65, 0x6c,
0x64, 0x42, 0x32, 0x42, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x32, 0x42, 0x03, 0x04,
}
)

const (
testPrintModel bool = true
)

func TestSimpleRequestMarshal(t *testing.T) {

var err error
var b []byte

testSimpleReq.Resolve()

if b, err = testSimpleReq.MarshalBinary(); err != nil {
t.Fatalf("Failed marshaling request: %v", err)
}

t.Logf("Target:\n%v", hex.EncodeToString(testSimpleReqEncoded))
t.Logf("Marshaled:\n%v", hex.EncodeToString(b))
if !bytes.Equal(b, testSimpleReqEncoded) {
t.Fatal("Marshaled request binary does not match")
}

_ = b
}

func TestSimpleRequestUnmarshal(t *testing.T) {

var err error
var b []byte
var b2 []byte
var req *Request = new(Request)

testSimpleReq.Resolve()

if err = req.UnmarshalBinary(testSimpleReqEncoded); err != nil {
t.Fatalf("Error when unmarshaling: %v", err)
}

// if !reflect.DeepEqual(req, testSimpleReq) {
// t.Fatalf("Unmarshaled request and canonical request do not match!")
// }

// t.Log(spew.Sdump(req))
if b, err = json.MarshalIndent(req, "", " "); err != nil {
t.Fatalf("Failed to marshal to JSON: %v", err)
}
if b2, err = json.MarshalIndent(testSimpleReq, "", " "); err != nil {
t.Fatalf("Failed to marshal to JSON: %v", err)
}

if !bytes.Equal(b, b2) {
// t.Logf("Unmarshaled JSON conversion:\n%v\nCanonical:\n%v", hex.EncodeToString(b), hex.EncodeToString(b2))
t.Logf("Unmarshaled JSON conversion:\n%v\nCanonical:\n%v", string(b), string(b2))
t.Fatalf("Unmarshaled request and canonical request do not match!")
}

t.Logf(string(b))

_, _ = b, b2
}

func TestSimpleResponseMarshal(t *testing.T) {

var err error
var b []byte

testSimpleResp.Resolve()

if b, err = testSimpleResp.MarshalBinary(); err != nil {
t.Fatalf("Failed marshaling request: %v", err)
}

t.Logf("Target:\n%v", hex.EncodeToString(testSimpleRespEncoded))
t.Logf("Marshaled:\n%v", hex.EncodeToString(b))
if !bytes.Equal(b, testSimpleRespEncoded) {
t.Fatal("Marshaled request binary does not match")
}

_ = b
}

func TestSimpleResponseUnmarshal(t *testing.T) {

var err error
var b []byte
var b2 []byte
var resp *Response = new(Response)

testSimpleResp.Resolve()

if err = resp.UnmarshalBinary(testSimpleRespEncoded); err != nil {
t.Fatalf("Error when unmarshaling: %v", err)
}

// if !reflect.DeepEqual(resp, testSimpleResp) {
// t.Fatalf("Unmarshaled response and canonical response do not match!")
// }

// t.Log(spew.Sdump(resp))
if b, err = json.MarshalIndent(resp, "", " "); err != nil {
t.Fatalf("Failed to marshal to JSON: %v", err)
}
if b2, err = json.MarshalIndent(testSimpleResp, "", " "); err != nil {
t.Fatalf("Failed to marshal to JSON: %v", err)
}

if !bytes.Equal(b, b2) {
// t.Logf("Unmarshaled JSON conversion:\n%v\nCanonical:\n%v", hex.EncodeToString(b), hex.EncodeToString(b2))
t.Logf("Unmarshaled JSON conversion:\n%v\nCanonical:\n%v", string(b), string(b2))
t.Fatalf("Unmarshaled response and canonical response do not match!")
}

t.Logf(string(b))

_, _ = b, b2
}

func TestMultiRequestMarshal(t *testing.T) {

var err error
var b []byte

testMultiReq.Resolve()

if b, err = testMultiReq.MarshalBinary(); err != nil {
t.Fatalf("Failed marshaling request: %v", err)
}

t.Logf("Target:\n%v", hex.EncodeToString(testMultiReqEncoded))
t.Logf("Marshaled:\n%v", hex.EncodeToString(b))
if !bytes.Equal(b, testMultiReqEncoded) {
t.Fatal("Marshaled request binary does not match")
}

_ = b
}

func TestMultiRequestUnmarshal(t *testing.T) {

var err error
var b []byte
var b2 []byte
var req *Request = new(Request)

testMultiReq.Resolve()

if err = req.UnmarshalBinary(testMultiReqEncoded); err != nil {
t.Fatalf("Error when unmarshaling: %v", err)
}

// if !reflect.DeepEqual(req, testMultiReq) {
// t.Fatalf("Unmarshaled request and canonical request do not match!")
// }

// t.Log(spew.Sdump(req))
if b, err = json.MarshalIndent(req, "", " "); err != nil {
t.Fatalf("Failed to marshal to JSON: %v", err)
}
if b2, err = json.MarshalIndent(testMultiReq, "", " "); err != nil {
t.Fatalf("Failed to marshal to JSON: %v", err)
}

if !bytes.Equal(b, b2) {
// t.Logf("Unmarshaled JSON conversion:\n%v\nCanonical:\n%v", hex.EncodeToString(b), hex.EncodeToString(b2))
t.Logf("Unmarshaled JSON conversion:\n%v\nCanonical:\n%v", string(b), string(b2))
t.Fatalf("Unmarshaled request and canonical request do not match!")
}

t.Logf(string(b))

_, _ = b, b2
}

func TestMultiResponseMarshal(t *testing.T) {

var err error
var b []byte

testMultiResp.Resolve()

if b, err = testMultiResp.MarshalBinary(); err != nil {
t.Fatalf("Failed marshaling request: %v", err)
}

t.Logf("Target:\n%v", hex.EncodeToString(testMultiRespEncoded))
t.Logf("Marshaled:\n%v", hex.EncodeToString(b))
if !bytes.Equal(b, testMultiRespEncoded) {
t.Fatal("Marshaled request binary does not match")
}

_ = b
}

func TestMultiResponseUnmarshal(t *testing.T) {

var err error
var b []byte
var b2 []byte
var resp *Response = new(Response)

testMultiResp.Resolve()

if err = resp.UnmarshalBinary(testMultiRespEncoded); err != nil {
t.Fatalf("Error when unmarshaling: %v", err)
}

// if !reflect.DeepEqual(resp, testMultiResp) {
// t.Fatalf("Unmarshaled response and canonical response do not match!")
// }

// t.Log(spew.Sdump(resp))
if b, err = json.MarshalIndent(resp, "", " "); err != nil {
t.Fatalf("Failed to marshal to JSON: %v", err)
}
if b2, err = json.MarshalIndent(testMultiResp, "", " "); err != nil {
t.Fatalf("Failed to marshal to JSON: %v", err)
}

if !bytes.Equal(b, b2) {
// t.Logf("Unmarshaled JSON conversion:\n%v\nCanonical:\n%v", hex.EncodeToString(b), hex.EncodeToString(b2))
t.Logf("Unmarshaled JSON conversion:\n%v\nCanonical:\n%v", string(b), string(b2))
t.Fatalf("Unmarshaled response and canonical response do not match!")
}

t.Logf(string(b))

_, _ = b, b2
}

func TestModels(t *testing.T) {

testSimpleReq.Resolve()
testSimpleResp.Resolve()
testMultiReq.Resolve()
testMultiResp.Resolve()

if testPrintModel {
fmt.Println("// REQUEST (Simple)")
fmt.Println(testSimpleReq.Model())

fmt.Println("// RESPONSE (Simple)")
fmt.Println(testSimpleResp.Model())

fmt.Println("// REQUEST (Complex)")
fmt.Println(testMultiReq.Model())

fmt.Println("// RESPONSE (Complex)")
fmt.Println(testMultiResp.Model())
}
}

8
go.mod Normal file
View File

@ -0,0 +1,8 @@
module r00t2.io/wireproto

go 1.23

require (
github.com/google/uuid v1.6.0
r00t2.io/goutils v1.7.0
)

8
go.sum Normal file
View File

@ -0,0 +1,8 @@
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
r00t2.io/goutils v1.7.0 h1:iQluWlkOyBwOKaK94D5QSnSMYpGKtMb/5WjefmdfHgI=
r00t2.io/goutils v1.7.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=

66
test_obj_multi_req.go Normal file
View File

@ -0,0 +1,66 @@
package wireproto

var (
// REQUEST (Complex)
testMultiReq *Request = &Request{
ProtocolVersion: ProtoVersion,
RecordGroups: []*RequestRecordGroup{
&RequestRecordGroup{
Records: []*RequestRecord{
&RequestRecord{
Pairs: []*FieldValuePair{
&FieldValuePair{
Name: []byte("fieldA1A"),
Value: []byte("valueA1A"),
},
&FieldValuePair{
Name: []byte("fieldA1B"),
Value: []byte("valueA1B"),
},
},
},
&RequestRecord{
Pairs: []*FieldValuePair{
&FieldValuePair{
Name: []byte("fieldA2A"),
Value: []byte("valueA2A"),
},
&FieldValuePair{
Name: []byte("fieldA2B"),
Value: []byte("valueA2B"),
},
},
},
},
},
&RequestRecordGroup{
Records: []*RequestRecord{
&RequestRecord{
Pairs: []*FieldValuePair{
&FieldValuePair{
Name: []byte("fieldB1A"),
Value: []byte("valueB1A"),
},
&FieldValuePair{
Name: []byte("fieldB1B"),
Value: []byte("valueB1B"),
},
},
},
&RequestRecord{
Pairs: []*FieldValuePair{
&FieldValuePair{
Name: []byte("fieldB2A"),
Value: []byte("valueB2A"),
},
&FieldValuePair{
Name: []byte("fieldB2B"),
Value: []byte("valueB2B"),
},
},
},
},
},
},
}
)

56
test_obj_multi_resp.go Normal file
View File

@ -0,0 +1,56 @@
package wireproto

var (
// RESPONSE (Complex)
testMultiResp *Response = &Response{
Status: AsciiACK,
Checksum: 2928197330, // 0xae88bed2
ProtocolVersion: ProtoVersion,
RecordGroups: []*ResponseRecordGroup{
&ResponseRecordGroup{
Records: []*ResponseRecord{
&ResponseRecord{
Pairs: []*FieldValuePair{
&FieldValuePair{
Name: []byte("dataA1"),
Value: []byte("<arbitrary data>"),
},
},
OriginalRecord: testMultiReq.RecordGroups[0].Records[0],
},
&ResponseRecord{
Pairs: []*FieldValuePair{
&FieldValuePair{
Name: []byte("dataA2"),
Value: []byte("<arbitrary data>"),
},
},
OriginalRecord: testMultiReq.RecordGroups[0].Records[1],
},
},
},
&ResponseRecordGroup{
Records: []*ResponseRecord{
&ResponseRecord{
Pairs: []*FieldValuePair{
&FieldValuePair{
Name: []byte("dataB1"),
Value: []byte("<arbitrary data>"),
},
},
OriginalRecord: testMultiReq.RecordGroups[1].Records[0],
},
&ResponseRecord{
Pairs: []*FieldValuePair{
&FieldValuePair{
Name: []byte("dataB2"),
Value: []byte("<arbitrary data>"),
},
},
OriginalRecord: testMultiReq.RecordGroups[1].Records[1],
},
},
},
},
}
)

26
test_obj_simple_req.go Normal file
View File

@ -0,0 +1,26 @@
package wireproto

var (
// REQUEST (Simple)
testSimpleReq *Request = &Request{
ProtocolVersion: ProtoVersion,
RecordGroups: []*RequestRecordGroup{
&RequestRecordGroup{
Records: []*RequestRecord{
&RequestRecord{
Pairs: []*FieldValuePair{
&FieldValuePair{
Name: []byte("field1"),
Value: []byte("value1"),
},
&FieldValuePair{
Name: []byte("field2"),
Value: []byte("value2"),
},
},
},
},
},
},
}
)

25
test_obj_simple_resp.go Normal file
View File

@ -0,0 +1,25 @@
package wireproto

var (
// RESPONSE (Simple)
testSimpleResp *Response = &Response{
Status: AsciiACK,
Checksum: 3472688928, // 0xcefd0720
ProtocolVersion: ProtoVersion,
RecordGroups: []*ResponseRecordGroup{
&ResponseRecordGroup{
Records: []*ResponseRecord{
&ResponseRecord{
Pairs: []*FieldValuePair{
&FieldValuePair{
Name: []byte("data1"),
Value: []byte("<arbitrary data>"),
},
},
OriginalRecord: testSimpleReq.RecordGroups[0].Records[0],
},
},
},
},
}
)

182
types.go Normal file
View File

@ -0,0 +1,182 @@
package wireproto

import (
`encoding/xml`

`github.com/google/uuid`
)

/*
Fields, commonly referred to in this library as "fv", "fvpair", etc. consist of both a name and a value.

This not intended to be a map conceptually so much as a "type" of data and the data itself, but it certainly can be represented as map-like.
*/
type (
// FieldName represents the name/identifier of a field.
FieldName []byte
// FieldValue is the value (data) of a specified field
FieldValue []byte
)

type common struct {
size uint32
}

type (
FVP interface {
Model
GetParent() (rec Record)
ToMap() (m map[string]interface{})
}
Record interface {
Model
GetParent() (rg RecordGroup)
ToMap() (m []map[string]interface{})
getFvps() (fvp []FVP)
}
RecordGroup interface {
Model
GetParent() (msg Message)
ToMap() (m [][]map[string]interface{})
getRecords() (records []Record)
}
Message interface {
Model
ToMap() (m [][][]map[string]interface{})
getRecordGroups() (recordGroups []RecordGroup)
}
Model interface {
Model() (out string)
ModelCustom(indent, sep string, level uint) (out string)
getIdx() (idx int)
}
)

/*
Request contains a Request message as sent to a remote target. It conforms to Message.

Either end ("client" or "server") may send a request, but it should represent the beginning of a complete/new transaction (a Message).
*/
type Request struct {
XMLName xml.Name `xml:"request" json:"-" yaml:"-" toml:"-" validate:"-"`
*common
/*
Checksum is a CRC32 (see specification) checksum of the message.

It is optional (but recommended) for requests.
*/
Checksum *uint32 `json:"cksum,omitempty" xml:"cksum,attr,omitempty" yaml:"Checksum,omitempty" toml:"Checksum,omitempty" validate:"omitempty"`
// ProtocolVersion specifies the specification version of this message.
ProtocolVersion uint32 `json:"proto_ver" xml:"protoVer,attr" yaml:"Protocol Version" toml:"ProtocolVersion" validate:"required"`
/*
RecordGroups contains grouped sets (RequestRecordGroup) of RequestRecord.
At least one is required.

Most implementations are likely to use only a single RequestRecordGroup in RecordGroups,
but multiple may be included. They are assumed to be seperate contexts/queries/commands/etc. if
multiple groups are included.
*/
RecordGroups []*RequestRecordGroup `json:"rg" xml:"recordGroups>recordGroup" yaml:"Record Groups" toml:"RecordGroups" validate:"required,dive"`
resp *ResponseRecord // only if part of a Response.
connId uuid.UUID
}

// RequestRecordGroup contains one or more related RequestRecord. It conforms to RecordGroup.
type RequestRecordGroup struct {
XMLName xml.Name `xml:"recordGroup" json:"-" yaml:"-" toml:"-" validate:"-"`
*common
// Records contains the sets of one or more related RequestRecord.
Records []*RequestRecord `json:"rec" xml:"records>record" yaml:"Records" toml:"Records" validate:"required,dive"`
connId uuid.UUID
parent *Request
rgIdx int
}

/*
RequestRecord contains a record of one or more related FieldValuePair. It conforms to Record.

It is designed such that a single RequestRecord is responded with a single ResponseRecord.
*/
type RequestRecord struct {
XMLName xml.Name `xml:"record" json:"-" yaml:"-" toml:"-" validate:"-"`
*common
// Pairs contains one or more related FieldValuePair.
Pairs []*FieldValuePair `json:"fvp" xml:"fvPairs>fvPair" yaml:"Field/Value Pairs" toml:"FieldValuePairs" validate:"required,dive"`
connId uuid.UUID
parent *RequestRecordGroup
rgIdx int
rIdx int
}

// Response contains an entire response message to a Request. It conforms to Message.
type Response struct {
XMLName xml.Name `xml:"response" json:"-" yaml:"-" toml:"-" validate:"-"`
*common
/*
Status is a short identifier of the status for the Request as executed.
It is the very first byte on the wire to allow the remote end to easily check whether an error has occurred.
(Detailed error messages should be contained in the RecordGroups.)

TODO: custom type, marshal/unmarshalers?
*/
Status uint8 `json:"status" xml:"status,attr" yaml:"Status" toml:"Status" validate:"required"`
/*
Checksum is a CRC32 (see specification) checksum of the message.

It is required for responses.
*/
Checksum uint32 `json:"cksum" xml:"cksum,attr" yaml:"Checksum" toml:"Checksum" validate:"required"`
// ProtocolVersion specifies the specification version of this message.
ProtocolVersion uint32 `json:"proto_ver" xml:"protoVer,attr" yaml:"Protocol Version" toml:"ProtocolVersion" validate:"required"`
/*
RecordGroups contains grouped sets (ResponseRecordGroup) of ResponseRecord.
At least one is required.

Most implementations are likely to use only a single ResponseRecordGroup in RecordGroups,
but multiple may be included. They are assumed to be seperate contexts/queries/commands/etc. if
multiple groups are included.
*/
RecordGroups []*ResponseRecordGroup `json:"rg" xml:"recordGroups>recordGroup" yaml:"Record Groups"`
}

// ResponseRecordGroup contains related ResponseRecord objects. It conforms to RecordGroup.
type ResponseRecordGroup struct {
XMLName xml.Name `xml:"recordGroup" json:"-" yaml:"-" toml:"-" validate:"-"`
*common
// Records contains one or more related ResponseRecord.
Records []*ResponseRecord `json:"rec" xml:"records>record" yaml:"Records" toml:"Records" validate:"required,dive"`
parent *Response
rgIdx int
}

// ResponseRecord contains the response to a single RequestRecord. It conforms to Record.
type ResponseRecord struct {
XMLName xml.Name `xml:"record" json:"-" yaml:"-" toml:"-" validate:"-"`
*common
Pairs []*FieldValuePair `json:"fvp" xml:"fvPairs>fvPair" yaml:"Field/Value Pairs" toml:"FieldValuePairs" validate:"required,dive"`
/*
OriginalRecord contains the associcated RequestRecord.
*/
OriginalRecord *RequestRecord `json:"orig_req" xml:"originalReq" yaml:"Original Request" toml:"OriginalRequest"`
parent *ResponseRecordGroup
rgIdx int
rIdx int
}

// FieldValuePair contains a single "key" (identifier) and value. It is found in both a RequestRecord and a ResponseRecord. It conforms to FVP.
type FieldValuePair struct {
XMLName xml.Name `xml:"fvPair" json:"-" yaml:"-" toml:"-" validate:"-"`
*common
// Name should be treated as a "type" or "identifier" for the data in Value.
Name FieldName `json:"name,omitempty" xml:"name,attr,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty" validate:"omitempty"`
/*
Value containts arbitrary data.
Content, validation/verification, parsing, etc. of the data is left to downstream implementations and is out of spec for this protocol.
*/
Value FieldValue `json:"value,omitempty" xml:"value,chardata,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty" validate:"omitempty"`
connId *uuid.UUID
parent Record
rgIdx int
rIdx int
fvpidx int
}