complete the marshal/unmarshal of events using the new pool enabled tag codecs
This commit is contained in:
@@ -63,11 +63,12 @@ func NostrUnescape(dst []byte) (b []byte) {
|
||||
c := dst[r]
|
||||
switch {
|
||||
|
||||
// nip-01 specifies the following single letter C-style escapes for control
|
||||
// codes under 0x20.
|
||||
// nip-01 specifies the following single letter C-style escapes for
|
||||
// control codes under 0x20.
|
||||
//
|
||||
// no others are specified but must be preserved, so only these can be
|
||||
// safely decoded at runtime as they must be re-encoded when marshalled.
|
||||
// no others are specified but must be preserved, so only these can
|
||||
// be safely decoded at runtime as they must be re-encoded when
|
||||
// marshalled.
|
||||
case c == '"':
|
||||
dst[w] = '"'
|
||||
w++
|
||||
@@ -90,8 +91,8 @@ func NostrUnescape(dst []byte) (b []byte) {
|
||||
dst[w] = '\r'
|
||||
w++
|
||||
|
||||
// special cases for non-nip-01 specified json escapes (must be preserved for ID
|
||||
// generation).
|
||||
// special cases for non-nip-01 specified json escapes (must be
|
||||
// preserved for ID generation).
|
||||
case c == 'u':
|
||||
dst[w] = '\\'
|
||||
w++
|
||||
@@ -103,7 +104,8 @@ func NostrUnescape(dst []byte) (b []byte) {
|
||||
dst[w] = '/'
|
||||
w++
|
||||
|
||||
// special case for octal escapes (must be preserved for ID generation).
|
||||
// special case for octal escapes (must be preserved for ID
|
||||
// generation).
|
||||
case c >= '0' && c <= '9':
|
||||
dst[w] = '\\'
|
||||
w++
|
||||
|
||||
255
pkg/encoders/text/helpers.go
Normal file
255
pkg/encoders/text/helpers.go
Normal file
@@ -0,0 +1,255 @@
|
||||
package text
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/templexxx/xhex"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/errorf"
|
||||
"next.orly.dev/pkg/encoders/hex"
|
||||
"next.orly.dev/pkg/utils"
|
||||
)
|
||||
|
||||
// JSONKey generates the JSON format for an object key and terminates with the semicolon.
|
||||
func JSONKey(dst, k []byte) (b []byte) {
|
||||
dst = append(dst, '"')
|
||||
dst = append(dst, k...)
|
||||
dst = append(dst, '"', ':')
|
||||
b = dst
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalHex takes a byte string that should contain a quoted hexadecimal
|
||||
// encoded value, decodes it using a SIMD hex codec and returns the decoded
|
||||
// bytes in a newly allocated buffer.
|
||||
func UnmarshalHex(b []byte) (h []byte, rem []byte, err error) {
|
||||
rem = b[:]
|
||||
var inQuote bool
|
||||
var start int
|
||||
for i := 0; i < len(b); i++ {
|
||||
if !inQuote {
|
||||
if b[i] == '"' {
|
||||
inQuote = true
|
||||
start = i + 1
|
||||
}
|
||||
} else if b[i] == '"' {
|
||||
hexStr := b[start:i]
|
||||
rem = b[i+1:]
|
||||
l := len(hexStr)
|
||||
if l%2 != 0 {
|
||||
err = errorf.E(
|
||||
"invalid length for hex: %d, %0x",
|
||||
len(hexStr), hexStr,
|
||||
)
|
||||
return
|
||||
}
|
||||
// Allocate a new buffer for the decoded data
|
||||
h = make([]byte, l/2)
|
||||
if err = xhex.Decode(h, hexStr); chk.E(err) {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
if !inQuote {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalQuoted performs an in-place unquoting of NIP-01 quoted byte string.
|
||||
func UnmarshalQuoted(b []byte) (content, rem []byte, err error) {
|
||||
if len(b) == 0 {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
rem = b[:]
|
||||
for ; len(rem) >= 0; rem = rem[1:] {
|
||||
// advance to open quotes
|
||||
if rem[0] == '"' {
|
||||
rem = rem[1:]
|
||||
content = rem
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(rem) == 0 {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
var escaping bool
|
||||
var contentLen int
|
||||
for len(rem) > 0 {
|
||||
if rem[0] == '\\' {
|
||||
if !escaping {
|
||||
escaping = true
|
||||
contentLen++
|
||||
rem = rem[1:]
|
||||
} else {
|
||||
escaping = false
|
||||
contentLen++
|
||||
rem = rem[1:]
|
||||
}
|
||||
} else if rem[0] == '"' {
|
||||
if !escaping {
|
||||
rem = rem[1:]
|
||||
content = content[:contentLen]
|
||||
content = NostrUnescape(content)
|
||||
return
|
||||
}
|
||||
contentLen++
|
||||
rem = rem[1:]
|
||||
escaping = false
|
||||
} else {
|
||||
escaping = false
|
||||
switch rem[0] {
|
||||
// none of these characters are allowed inside a JSON string:
|
||||
//
|
||||
// backspace, tab, newline, form feed or carriage return.
|
||||
case '\b', '\t', '\n', '\f', '\r':
|
||||
err = errorf.E(
|
||||
"invalid character '%s' in quoted string",
|
||||
NostrEscape(nil, rem[:1]),
|
||||
)
|
||||
return
|
||||
}
|
||||
contentLen++
|
||||
rem = rem[1:]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func MarshalHexArray(dst []byte, ha [][]byte) (b []byte) {
|
||||
dst = append(dst, '[')
|
||||
for i := range ha {
|
||||
dst = AppendQuote(dst, ha[i], hex.EncAppend)
|
||||
if i != len(ha)-1 {
|
||||
dst = append(dst, ',')
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
b = dst
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalHexArray unpacks a JSON array containing strings with hexadecimal, and checks all
|
||||
// values have the specified byte size.
|
||||
func UnmarshalHexArray(b []byte, size int) (t [][]byte, rem []byte, err error) {
|
||||
rem = b
|
||||
var openBracket bool
|
||||
for ; len(rem) > 0; rem = rem[1:] {
|
||||
if rem[0] == '[' {
|
||||
openBracket = true
|
||||
} else if openBracket {
|
||||
if rem[0] == ',' {
|
||||
continue
|
||||
} else if rem[0] == ']' {
|
||||
rem = rem[1:]
|
||||
return
|
||||
} else if rem[0] == '"' {
|
||||
var h []byte
|
||||
if h, rem, err = UnmarshalHex(rem); chk.E(err) {
|
||||
return
|
||||
}
|
||||
if len(h) != size {
|
||||
err = errorf.E(
|
||||
"invalid hex array size, got %d expect %d",
|
||||
2*len(h), 2*size,
|
||||
)
|
||||
return
|
||||
}
|
||||
t = append(t, h)
|
||||
if rem[0] == ']' {
|
||||
rem = rem[1:]
|
||||
// done
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalStringArray unpacks a JSON array containing strings.
|
||||
func UnmarshalStringArray(b []byte) (t [][]byte, rem []byte, err error) {
|
||||
rem = b
|
||||
var openBracket bool
|
||||
for ; len(rem) > 0; rem = rem[1:] {
|
||||
if rem[0] == '[' {
|
||||
openBracket = true
|
||||
} else if openBracket {
|
||||
if rem[0] == ',' {
|
||||
continue
|
||||
} else if rem[0] == ']' {
|
||||
rem = rem[1:]
|
||||
return
|
||||
} else if rem[0] == '"' {
|
||||
var h []byte
|
||||
if h, rem, err = UnmarshalQuoted(rem); chk.E(err) {
|
||||
return
|
||||
}
|
||||
t = append(t, h)
|
||||
if rem[0] == ']' {
|
||||
rem = rem[1:]
|
||||
// done
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func True() []byte { return []byte("true") }
|
||||
func False() []byte { return []byte("false") }
|
||||
|
||||
func MarshalBool(src []byte, truth bool) []byte {
|
||||
if truth {
|
||||
return append(src, True()...)
|
||||
}
|
||||
return append(src, False()...)
|
||||
}
|
||||
|
||||
func UnmarshalBool(src []byte) (rem []byte, truth bool, err error) {
|
||||
rem = src
|
||||
t, f := True(), False()
|
||||
for i := range rem {
|
||||
if rem[i] == t[0] {
|
||||
if len(rem) < i+len(t) {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
if utils.FastEqual(t, rem[i:i+len(t)]) {
|
||||
truth = true
|
||||
rem = rem[i+len(t):]
|
||||
return
|
||||
}
|
||||
}
|
||||
if rem[i] == f[0] {
|
||||
if len(rem) < i+len(f) {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
if utils.FastEqual(f, rem[i:i+len(f)]) {
|
||||
rem = rem[i+len(f):]
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// if a truth value is not found in the string it will run to the end
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
|
||||
func Comma(b []byte) (rem []byte, err error) {
|
||||
rem = b
|
||||
for i := range rem {
|
||||
if rem[i] == ',' {
|
||||
rem = rem[i:]
|
||||
return
|
||||
}
|
||||
}
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
54
pkg/encoders/text/helpers_test.go
Normal file
54
pkg/encoders/text/helpers_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package text
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"lol.mleku.dev/chk"
|
||||
"lukechampine.com/frand"
|
||||
"next.orly.dev/pkg/crypto/sha256"
|
||||
"next.orly.dev/pkg/encoders/hex"
|
||||
"next.orly.dev/pkg/utils"
|
||||
)
|
||||
|
||||
func TestUnmarshalHexArray(t *testing.T) {
|
||||
var ha [][]byte
|
||||
h := make([]byte, sha256.Size)
|
||||
frand.Read(h)
|
||||
var dst []byte
|
||||
for _ = range 20 {
|
||||
hh := sha256.Sum256(h)
|
||||
h = hh[:]
|
||||
ha = append(ha, h)
|
||||
}
|
||||
dst = append(dst, '[')
|
||||
for i := range ha {
|
||||
dst = AppendQuote(dst, ha[i], hex.EncAppend)
|
||||
if i != len(ha)-1 {
|
||||
dst = append(dst, ',')
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
var ha2 [][]byte
|
||||
var rem []byte
|
||||
var err error
|
||||
if ha2, rem, err = UnmarshalHexArray(dst, sha256.Size); chk.E(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ha2) != len(ha) {
|
||||
t.Fatalf(
|
||||
"failed to unmarshal, got %d fields, expected %d", len(ha2),
|
||||
len(ha),
|
||||
)
|
||||
}
|
||||
if len(rem) > 0 {
|
||||
t.Fatalf("failed to unmarshal, remnant afterwards '%s'", rem)
|
||||
}
|
||||
for i := range ha2 {
|
||||
if !utils.FastEqual(ha[i], ha2[i]) {
|
||||
t.Fatalf(
|
||||
"failed to unmarshal at element %d; got %x, expected %x",
|
||||
i, ha[i], ha2[i],
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
88
pkg/encoders/text/wrap.go
Normal file
88
pkg/encoders/text/wrap.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package text
|
||||
|
||||
// AppendBytesClosure is a function type for appending data from a source to a destination and
|
||||
// returning the appended-to slice.
|
||||
type AppendBytesClosure func(dst, src []byte) []byte
|
||||
|
||||
// AppendClosure is a simple append where the caller appends to the destination and returns the
|
||||
// appended-to slice.
|
||||
type AppendClosure func(dst []byte) []byte
|
||||
|
||||
// Unquote removes the quotes around a slice of bytes.
|
||||
func Unquote(b []byte) []byte { return b[1 : len(b)-1] }
|
||||
|
||||
// Noop simply appends the source to the destination slice and returns it.
|
||||
func Noop(dst, src []byte) []byte { return append(dst, src...) }
|
||||
|
||||
// AppendQuote appends a source of bytes, that have been processed by an AppendBytesClosure and
|
||||
// returns the appended-to slice.
|
||||
func AppendQuote(dst, src []byte, ac AppendBytesClosure) []byte {
|
||||
dst = append(dst, '"')
|
||||
dst = ac(dst, src)
|
||||
dst = append(dst, '"')
|
||||
return dst
|
||||
}
|
||||
|
||||
// Quote simply quotes a provided source and attaches it to the provided destination slice.
|
||||
func Quote(dst, src []byte) []byte { return AppendQuote(dst, src, Noop) }
|
||||
|
||||
// AppendSingleQuote appends a provided AppendBytesClosure's output from a given source of
|
||||
// bytes, wrapped in single quotes ”.
|
||||
func AppendSingleQuote(dst, src []byte, ac AppendBytesClosure) []byte {
|
||||
dst = append(dst, '\'')
|
||||
dst = ac(dst, src)
|
||||
dst = append(dst, '\'')
|
||||
return dst
|
||||
}
|
||||
|
||||
// AppendBackticks appends a provided AppendBytesClosure's output from a given source of
|
||||
// bytes, wrapped in backticks “.
|
||||
func AppendBackticks(dst, src []byte, ac AppendBytesClosure) []byte {
|
||||
dst = append(dst, '`')
|
||||
dst = ac(dst, src)
|
||||
dst = append(dst, '`')
|
||||
return dst
|
||||
}
|
||||
|
||||
// AppendBrace appends a provided AppendBytesClosure's output from a given source of
|
||||
// bytes, wrapped in braces ().
|
||||
func AppendBrace(dst, src []byte, ac AppendBytesClosure) []byte {
|
||||
dst = append(dst, '(')
|
||||
dst = ac(dst, src)
|
||||
dst = append(dst, ')')
|
||||
return dst
|
||||
}
|
||||
|
||||
// AppendParenthesis appends a provided AppendBytesClosure's output from a given source of
|
||||
// bytes, wrapped in parentheses {}.
|
||||
func AppendParenthesis(dst, src []byte, ac AppendBytesClosure) []byte {
|
||||
dst = append(dst, '{')
|
||||
dst = ac(dst, src)
|
||||
dst = append(dst, '}')
|
||||
return dst
|
||||
}
|
||||
|
||||
// AppendBracket appends a provided AppendBytesClosure's output from a given source of
|
||||
// bytes, wrapped in brackets [].
|
||||
func AppendBracket(dst, src []byte, ac AppendBytesClosure) []byte {
|
||||
dst = append(dst, '[')
|
||||
dst = ac(dst, src)
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
}
|
||||
|
||||
// AppendList appends an input source bytes processed by an AppendBytesClosure and separates
|
||||
// elements with the given separator byte.
|
||||
func AppendList(
|
||||
dst []byte, src [][]byte, separator byte,
|
||||
ac AppendBytesClosure,
|
||||
) []byte {
|
||||
last := len(src) - 1
|
||||
for i := range src {
|
||||
dst = append(dst, ac(dst, src[i])...)
|
||||
if i < last {
|
||||
dst = append(dst, separator)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
Reference in New Issue
Block a user