mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-01 12:18:15 +00:00
Merge branch 'master' into syscall-fix
This commit is contained in:
@@ -29,12 +29,12 @@ MIN_READ_BUFFER_SIZE :: 16
|
||||
@(private)
|
||||
DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128
|
||||
|
||||
reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) {
|
||||
reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator, loc := #caller_location) {
|
||||
size := size
|
||||
size = max(size, MIN_READ_BUFFER_SIZE)
|
||||
reader_reset(b, rd)
|
||||
b.buf_allocator = allocator
|
||||
b.buf = make([]byte, size, allocator)
|
||||
b.buf = make([]byte, size, allocator, loc)
|
||||
}
|
||||
|
||||
reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) {
|
||||
@@ -81,7 +81,7 @@ _reader_read_new_chunk :: proc(b: ^Reader) -> io.Error {
|
||||
for i := b.max_consecutive_empty_reads; i > 0; i -= 1 {
|
||||
n, err := io.read(b.rd, b.buf[b.w:])
|
||||
if n < 0 {
|
||||
return .Negative_Read
|
||||
return err if err != nil else .Negative_Read
|
||||
}
|
||||
b.w += n
|
||||
if err != nil {
|
||||
@@ -189,7 +189,7 @@ reader_read :: proc(b: ^Reader, p: []byte) -> (n: int, err: io.Error) {
|
||||
if len(p) >= len(b.buf) {
|
||||
n, b.err = io.read(b.rd, p)
|
||||
if n < 0 {
|
||||
return 0, .Negative_Read
|
||||
return 0, b.err if b.err != nil else .Negative_Read
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
@@ -202,7 +202,7 @@ reader_read :: proc(b: ^Reader, p: []byte) -> (n: int, err: io.Error) {
|
||||
b.r, b.w = 0, 0
|
||||
n, b.err = io.read(b.rd, b.buf)
|
||||
if n < 0 {
|
||||
return 0, .Negative_Read
|
||||
return 0, b.err if b.err != nil else .Negative_Read
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, _reader_consume_err(b)
|
||||
@@ -290,7 +290,7 @@ reader_write_to :: proc(b: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) {
|
||||
write_buf :: proc(b: ^Reader, w: io.Writer) -> (i64, io.Error) {
|
||||
n, err := io.write(w, b.buf[b.r:b.w])
|
||||
if n < 0 {
|
||||
return 0, .Negative_Write
|
||||
return 0, err if err != nil else .Negative_Write
|
||||
}
|
||||
b.r += n
|
||||
return i64(n), err
|
||||
|
||||
@@ -95,6 +95,10 @@ writer_write :: proc(b: ^Writer, p: []byte) -> (n: int, err: io.Error) {
|
||||
m: int
|
||||
if writer_buffered(b) == 0 {
|
||||
m, b.err = io.write(b.wr, p)
|
||||
if m < 0 && b.err == nil {
|
||||
b.err = .Negative_Write
|
||||
break
|
||||
}
|
||||
} else {
|
||||
m = copy(b.buf[b.n:], p)
|
||||
b.n += m
|
||||
|
||||
+36
-36
@@ -27,19 +27,19 @@ Read_Op :: enum i8 {
|
||||
}
|
||||
|
||||
|
||||
buffer_init :: proc(b: ^Buffer, buf: []byte) {
|
||||
resize(&b.buf, len(buf))
|
||||
buffer_init :: proc(b: ^Buffer, buf: []byte, loc := #caller_location) {
|
||||
resize(&b.buf, len(buf), loc=loc)
|
||||
copy(b.buf[:], buf)
|
||||
}
|
||||
|
||||
buffer_init_string :: proc(b: ^Buffer, s: string) {
|
||||
resize(&b.buf, len(s))
|
||||
buffer_init_string :: proc(b: ^Buffer, s: string, loc := #caller_location) {
|
||||
resize(&b.buf, len(s), loc=loc)
|
||||
copy(b.buf[:], s)
|
||||
}
|
||||
|
||||
buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator) {
|
||||
buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator, loc := #caller_location) {
|
||||
if b.buf == nil {
|
||||
b.buf = make([dynamic]byte, len, cap, allocator)
|
||||
b.buf = make([dynamic]byte, len, cap, allocator, loc)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -96,28 +96,28 @@ buffer_truncate :: proc(b: ^Buffer, n: int) {
|
||||
}
|
||||
|
||||
@(private)
|
||||
_buffer_try_grow :: proc(b: ^Buffer, n: int) -> (int, bool) {
|
||||
_buffer_try_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> (int, bool) {
|
||||
if l := len(b.buf); n <= cap(b.buf)-l {
|
||||
resize(&b.buf, l+n)
|
||||
resize(&b.buf, l+n, loc=loc)
|
||||
return l, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
@(private)
|
||||
_buffer_grow :: proc(b: ^Buffer, n: int) -> int {
|
||||
_buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> int {
|
||||
m := buffer_length(b)
|
||||
if m == 0 && b.off != 0 {
|
||||
buffer_reset(b)
|
||||
}
|
||||
if i, ok := _buffer_try_grow(b, n); ok {
|
||||
if i, ok := _buffer_try_grow(b, n, loc=loc); ok {
|
||||
return i
|
||||
}
|
||||
|
||||
if b.buf == nil && n <= SMALL_BUFFER_SIZE {
|
||||
// Fixes #2756 by preserving allocator if already set on Buffer via init_buffer_allocator
|
||||
reserve(&b.buf, SMALL_BUFFER_SIZE)
|
||||
resize(&b.buf, n)
|
||||
reserve(&b.buf, SMALL_BUFFER_SIZE, loc=loc)
|
||||
resize(&b.buf, n, loc=loc)
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -127,31 +127,31 @@ _buffer_grow :: proc(b: ^Buffer, n: int) -> int {
|
||||
} else if c > max(int) - c - n {
|
||||
panic("bytes.Buffer: too large")
|
||||
} else {
|
||||
resize(&b.buf, 2*c + n)
|
||||
resize(&b.buf, 2*c + n, loc=loc)
|
||||
copy(b.buf[:], b.buf[b.off:])
|
||||
}
|
||||
b.off = 0
|
||||
resize(&b.buf, m+n)
|
||||
resize(&b.buf, m+n, loc=loc)
|
||||
return m
|
||||
}
|
||||
|
||||
buffer_grow :: proc(b: ^Buffer, n: int) {
|
||||
buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) {
|
||||
if n < 0 {
|
||||
panic("bytes.buffer_grow: negative count")
|
||||
}
|
||||
m := _buffer_grow(b, n)
|
||||
resize(&b.buf, m)
|
||||
m := _buffer_grow(b, n, loc=loc)
|
||||
resize(&b.buf, m, loc=loc)
|
||||
}
|
||||
|
||||
buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
|
||||
buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int, loc := #caller_location) -> (n: int, err: io.Error) {
|
||||
b.last_read = .Invalid
|
||||
if offset < 0 {
|
||||
err = .Invalid_Offset
|
||||
return
|
||||
}
|
||||
_, ok := _buffer_try_grow(b, offset+len(p))
|
||||
_, ok := _buffer_try_grow(b, offset+len(p), loc=loc)
|
||||
if !ok {
|
||||
_ = _buffer_grow(b, offset+len(p))
|
||||
_ = _buffer_grow(b, offset+len(p), loc=loc)
|
||||
}
|
||||
if len(b.buf) <= offset {
|
||||
return 0, .Short_Write
|
||||
@@ -160,47 +160,47 @@ buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.
|
||||
}
|
||||
|
||||
|
||||
buffer_write :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) {
|
||||
buffer_write :: proc(b: ^Buffer, p: []byte, loc := #caller_location) -> (n: int, err: io.Error) {
|
||||
b.last_read = .Invalid
|
||||
m, ok := _buffer_try_grow(b, len(p))
|
||||
m, ok := _buffer_try_grow(b, len(p), loc=loc)
|
||||
if !ok {
|
||||
m = _buffer_grow(b, len(p))
|
||||
m = _buffer_grow(b, len(p), loc=loc)
|
||||
}
|
||||
return copy(b.buf[m:], p), nil
|
||||
}
|
||||
|
||||
buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io.Error) {
|
||||
return buffer_write(b, ([^]byte)(ptr)[:size])
|
||||
buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int, loc := #caller_location) -> (n: int, err: io.Error) {
|
||||
return buffer_write(b, ([^]byte)(ptr)[:size], loc=loc)
|
||||
}
|
||||
|
||||
buffer_write_string :: proc(b: ^Buffer, s: string) -> (n: int, err: io.Error) {
|
||||
buffer_write_string :: proc(b: ^Buffer, s: string, loc := #caller_location) -> (n: int, err: io.Error) {
|
||||
b.last_read = .Invalid
|
||||
m, ok := _buffer_try_grow(b, len(s))
|
||||
m, ok := _buffer_try_grow(b, len(s), loc=loc)
|
||||
if !ok {
|
||||
m = _buffer_grow(b, len(s))
|
||||
m = _buffer_grow(b, len(s), loc=loc)
|
||||
}
|
||||
return copy(b.buf[m:], s), nil
|
||||
}
|
||||
|
||||
buffer_write_byte :: proc(b: ^Buffer, c: byte) -> io.Error {
|
||||
buffer_write_byte :: proc(b: ^Buffer, c: byte, loc := #caller_location) -> io.Error {
|
||||
b.last_read = .Invalid
|
||||
m, ok := _buffer_try_grow(b, 1)
|
||||
m, ok := _buffer_try_grow(b, 1, loc=loc)
|
||||
if !ok {
|
||||
m = _buffer_grow(b, 1)
|
||||
m = _buffer_grow(b, 1, loc=loc)
|
||||
}
|
||||
b.buf[m] = c
|
||||
return nil
|
||||
}
|
||||
|
||||
buffer_write_rune :: proc(b: ^Buffer, r: rune) -> (n: int, err: io.Error) {
|
||||
buffer_write_rune :: proc(b: ^Buffer, r: rune, loc := #caller_location) -> (n: int, err: io.Error) {
|
||||
if r < utf8.RUNE_SELF {
|
||||
buffer_write_byte(b, byte(r))
|
||||
buffer_write_byte(b, byte(r), loc=loc)
|
||||
return 1, nil
|
||||
}
|
||||
b.last_read = .Invalid
|
||||
m, ok := _buffer_try_grow(b, utf8.UTF_MAX)
|
||||
m, ok := _buffer_try_grow(b, utf8.UTF_MAX, loc=loc)
|
||||
if !ok {
|
||||
m = _buffer_grow(b, utf8.UTF_MAX)
|
||||
m = _buffer_grow(b, utf8.UTF_MAX, loc=loc)
|
||||
}
|
||||
res: [4]byte
|
||||
res, n = utf8.encode_rune(r)
|
||||
@@ -359,7 +359,7 @@ buffer_read_from :: proc(b: ^Buffer, r: io.Reader) -> (n: i64, err: io.Error) #n
|
||||
resize(&b.buf, i)
|
||||
m, e := io.read(r, b.buf[i:cap(b.buf)])
|
||||
if m < 0 {
|
||||
err = .Negative_Read
|
||||
err = e if e != nil else .Negative_Read
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1167,3 +1167,28 @@ fields_proc :: proc(s: []byte, f: proc(rune) -> bool, allocator := context.alloc
|
||||
|
||||
return subslices[:]
|
||||
}
|
||||
|
||||
// alias returns true iff a and b have a non-zero length, and any part of
|
||||
// a overlaps with b.
|
||||
alias :: proc "contextless" (a, b: []byte) -> bool {
|
||||
a_len, b_len := len(a), len(b)
|
||||
if a_len == 0 || b_len == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
a_start, b_start := uintptr(raw_data(a)), uintptr(raw_data(b))
|
||||
a_end, b_end := a_start + uintptr(a_len-1), b_start + uintptr(b_len-1)
|
||||
|
||||
return a_start <= b_end && b_start <= a_end
|
||||
}
|
||||
|
||||
// alias_inexactly returns true iff a and b have a non-zero length,
|
||||
// the base pointer of a and b are NOT equal, and any part of a overlaps
|
||||
// with b (ie: `alias(a, b)` with an exception that returns false for
|
||||
// `a == b`, `b = a[:len(a)-69]` and similar conditions).
|
||||
alias_inexactly :: proc "contextless" (a, b: []byte) -> bool {
|
||||
if raw_data(a) == raw_data(b) {
|
||||
return false
|
||||
}
|
||||
return alias(a, b)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ when ODIN_OS == .FreeBSD {
|
||||
ERANGE :: 34
|
||||
}
|
||||
|
||||
when ODIN_OS == .OpenBSD {
|
||||
when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
|
||||
@(private="file")
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
|
||||
+1
-14
@@ -34,20 +34,7 @@ when ODIN_OS == .Windows {
|
||||
SIGTERM :: 15
|
||||
}
|
||||
|
||||
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD {
|
||||
SIG_ERR :: rawptr(~uintptr(0))
|
||||
SIG_DFL :: rawptr(uintptr(0))
|
||||
SIG_IGN :: rawptr(uintptr(1))
|
||||
|
||||
SIGABRT :: 6
|
||||
SIGFPE :: 8
|
||||
SIGILL :: 4
|
||||
SIGINT :: 2
|
||||
SIGSEGV :: 11
|
||||
SIGTERM :: 15
|
||||
}
|
||||
|
||||
when ODIN_OS == .Darwin {
|
||||
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Darwin {
|
||||
SIG_ERR :: rawptr(~uintptr(0))
|
||||
SIG_DFL :: rawptr(uintptr(0))
|
||||
SIG_IGN :: rawptr(uintptr(1))
|
||||
|
||||
@@ -83,7 +83,7 @@ when ODIN_OS == .Linux {
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .OpenBSD {
|
||||
when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
|
||||
fpos_t :: distinct i64
|
||||
|
||||
_IOFBF :: 0
|
||||
@@ -102,10 +102,12 @@ when ODIN_OS == .OpenBSD {
|
||||
SEEK_END :: 2
|
||||
|
||||
foreign libc {
|
||||
stderr: ^FILE
|
||||
stdin: ^FILE
|
||||
stdout: ^FILE
|
||||
__sF: [3]FILE
|
||||
}
|
||||
|
||||
stdin: ^FILE = &__sF[0]
|
||||
stdout: ^FILE = &__sF[1]
|
||||
stderr: ^FILE = &__sF[2]
|
||||
}
|
||||
|
||||
when ODIN_OS == .FreeBSD {
|
||||
@@ -127,9 +129,9 @@ when ODIN_OS == .FreeBSD {
|
||||
SEEK_END :: 2
|
||||
|
||||
foreign libc {
|
||||
stderr: ^FILE
|
||||
stdin: ^FILE
|
||||
stdout: ^FILE
|
||||
@(link_name="__stderrp") stderr: ^FILE
|
||||
@(link_name="__stdinp") stdin: ^FILE
|
||||
@(link_name="__stdoutp") stdout: ^FILE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ when ODIN_OS == .Windows {
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku {
|
||||
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku {
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
// 7.27.2 Time manipulation functions
|
||||
|
||||
@@ -22,7 +22,7 @@ when ODIN_OS == .Windows {
|
||||
wctrans_t :: distinct int
|
||||
wctype_t :: distinct u32
|
||||
|
||||
} else when ODIN_OS == .OpenBSD {
|
||||
} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
|
||||
wctrans_t :: distinct rawptr
|
||||
wctype_t :: distinct rawptr
|
||||
|
||||
|
||||
+28
-28
@@ -34,13 +34,13 @@ COMPRESS_OUTPUT_ALLOCATE_MIN :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MIN, 1 << 2
|
||||
*/
|
||||
when size_of(uintptr) == 8 {
|
||||
|
||||
// For 64-bit platforms, we set the default max buffer size to 4 GiB,
|
||||
// which is GZIP and PKZIP's max payload size.
|
||||
// For 64-bit platforms, we set the default max buffer size to 4 GiB,
|
||||
// which is GZIP and PKZIP's max payload size.
|
||||
COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 32))
|
||||
} else {
|
||||
|
||||
// For 32-bit platforms, we set the default max buffer size to 512 MiB.
|
||||
COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 29))
|
||||
COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 29))
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ input_size_from_stream :: proc(z: ^Context_Stream_Input) -> (res: i64, err: Erro
|
||||
|
||||
input_size :: proc{input_size_from_memory, input_size_from_stream}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int) -> (res: []u8, err: io.Error) {
|
||||
#no_bounds_check {
|
||||
if len(z.input_data) >= size {
|
||||
@@ -203,7 +203,7 @@ read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int) -> (res: []u8, err: io.Error) {
|
||||
// TODO: REMOVE ALL USE OF context.temp_allocator here
|
||||
// there is literally no need for it
|
||||
@@ -214,13 +214,13 @@ read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int
|
||||
|
||||
read_slice :: proc{read_slice_from_memory, read_slice_from_stream}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_data :: #force_inline proc(z: ^$C, $T: typeid) -> (res: T, err: io.Error) {
|
||||
b := read_slice(z, size_of(T)) or_return
|
||||
return (^T)(&b[0])^, nil
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_u8_from_memory :: #force_inline proc(z: ^Context_Memory_Input) -> (res: u8, err: io.Error) {
|
||||
#no_bounds_check {
|
||||
if len(z.input_data) >= 1 {
|
||||
@@ -232,7 +232,7 @@ read_u8_from_memory :: #force_inline proc(z: ^Context_Memory_Input) -> (res: u8,
|
||||
return 0, .EOF
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_u8_from_stream :: #force_inline proc(z: ^Context_Stream_Input) -> (res: u8, err: io.Error) {
|
||||
b := read_slice_from_stream(z, 1) or_return
|
||||
return b[0], nil
|
||||
@@ -242,7 +242,7 @@ read_u8 :: proc{read_u8_from_memory, read_u8_from_stream}
|
||||
|
||||
// You would typically only use this at the end of Inflate, to drain bits from the code buffer
|
||||
// preferentially.
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_u8_prefer_code_buffer_lsb :: #force_inline proc(z: ^$C) -> (res: u8, err: io.Error) {
|
||||
if z.num_bits >= 8 {
|
||||
res = u8(read_bits_no_refill_lsb(z, 8))
|
||||
@@ -257,7 +257,7 @@ read_u8_prefer_code_buffer_lsb :: #force_inline proc(z: ^$C) -> (res: u8, err: i
|
||||
return
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid) -> (res: T, err: io.Error) {
|
||||
size :: size_of(T)
|
||||
|
||||
@@ -275,7 +275,7 @@ peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
|
||||
size :: size_of(T)
|
||||
|
||||
@@ -293,7 +293,7 @@ peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input,
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid) -> (res: T, err: io.Error) {
|
||||
size :: size_of(T)
|
||||
|
||||
@@ -317,7 +317,7 @@ peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid
|
||||
return res, .None
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_data_at_offset_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
|
||||
size :: size_of(T)
|
||||
|
||||
@@ -352,14 +352,14 @@ peek_data :: proc{peek_data_from_memory, peek_data_from_stream, peek_data_at_off
|
||||
|
||||
|
||||
// Sliding window read back
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_back_byte :: #force_inline proc(z: ^$C, offset: i64) -> (res: u8, err: io.Error) {
|
||||
// Look back into the sliding window.
|
||||
return z.output.buf[z.bytes_written - offset], .None
|
||||
}
|
||||
|
||||
// Generalized bit reader LSB
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width := i8(48)) {
|
||||
refill := u64(width)
|
||||
b := u64(0)
|
||||
@@ -385,7 +385,7 @@ refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width :=
|
||||
}
|
||||
|
||||
// Generalized bit reader LSB
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
refill_lsb_from_stream :: proc(z: ^Context_Stream_Input, width := i8(24)) {
|
||||
refill := u64(width)
|
||||
|
||||
@@ -414,13 +414,13 @@ refill_lsb_from_stream :: proc(z: ^Context_Stream_Input, width := i8(24)) {
|
||||
refill_lsb :: proc{refill_lsb_from_memory, refill_lsb_from_stream}
|
||||
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
consume_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) {
|
||||
z.code_buffer >>= width
|
||||
z.num_bits -= u64(width)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
consume_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) {
|
||||
z.code_buffer >>= width
|
||||
z.num_bits -= u64(width)
|
||||
@@ -428,7 +428,7 @@ consume_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, wid
|
||||
|
||||
consume_bits_lsb :: proc{consume_bits_lsb_from_memory, consume_bits_lsb_from_stream}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
|
||||
if z.num_bits < u64(width) {
|
||||
refill_lsb(z)
|
||||
@@ -436,7 +436,7 @@ peek_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width:
|
||||
return u32(z.code_buffer &~ (~u64(0) << width))
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
|
||||
if z.num_bits < u64(width) {
|
||||
refill_lsb(z)
|
||||
@@ -446,13 +446,13 @@ peek_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width:
|
||||
|
||||
peek_bits_lsb :: proc{peek_bits_lsb_from_memory, peek_bits_lsb_from_stream}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
|
||||
assert(z.num_bits >= u64(width))
|
||||
return u32(z.code_buffer &~ (~u64(0) << width))
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
|
||||
assert(z.num_bits >= u64(width))
|
||||
return u32(z.code_buffer &~ (~u64(0) << width))
|
||||
@@ -460,14 +460,14 @@ peek_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Inp
|
||||
|
||||
peek_bits_no_refill_lsb :: proc{peek_bits_no_refill_lsb_from_memory, peek_bits_no_refill_lsb_from_stream}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
|
||||
k := #force_inline peek_bits_lsb(z, width)
|
||||
#force_inline consume_bits_lsb(z, width)
|
||||
return k
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
|
||||
k := peek_bits_lsb(z, width)
|
||||
consume_bits_lsb(z, width)
|
||||
@@ -476,14 +476,14 @@ read_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width:
|
||||
|
||||
read_bits_lsb :: proc{read_bits_lsb_from_memory, read_bits_lsb_from_stream}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
|
||||
k := #force_inline peek_bits_no_refill_lsb(z, width)
|
||||
#force_inline consume_bits_lsb(z, width)
|
||||
return k
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
|
||||
k := peek_bits_no_refill_lsb(z, width)
|
||||
consume_bits_lsb(z, width)
|
||||
@@ -493,14 +493,14 @@ read_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Inp
|
||||
read_bits_no_refill_lsb :: proc{read_bits_no_refill_lsb_from_memory, read_bits_no_refill_lsb_from_stream}
|
||||
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
discard_to_next_byte_lsb_from_memory :: proc(z: ^Context_Memory_Input) {
|
||||
discard := u8(z.num_bits & 7)
|
||||
#force_inline consume_bits_lsb(z, discard)
|
||||
}
|
||||
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
discard_to_next_byte_lsb_from_stream :: proc(z: ^Context_Stream_Input) {
|
||||
discard := u8(z.num_bits & 7)
|
||||
consume_bits_lsb(z, discard)
|
||||
|
||||
@@ -98,7 +98,7 @@ decompress_slice_to_output_buffer :: proc(input: []u8, output: []u8, model := DE
|
||||
validate_model(model) or_return
|
||||
|
||||
for inp < inp_end {
|
||||
val := transmute(i8)input[inp]
|
||||
val := i8(input[inp])
|
||||
mark := int(-1)
|
||||
|
||||
for val < 0 {
|
||||
@@ -274,12 +274,9 @@ compress_string_to_buffer :: proc(input: string, output: []u8, model := DEFAULT_
|
||||
out_ptr := raw_data(output[out:])
|
||||
|
||||
switch pack.bytes_packed {
|
||||
case 4:
|
||||
intrinsics.unaligned_store(transmute(^u32)out_ptr, code)
|
||||
case 2:
|
||||
intrinsics.unaligned_store(transmute(^u16)out_ptr, u16(code))
|
||||
case 1:
|
||||
intrinsics.unaligned_store(transmute(^u8)out_ptr, u8(code))
|
||||
case 4: intrinsics.unaligned_store((^u32)(out_ptr), code)
|
||||
case 2: intrinsics.unaligned_store((^u16)(out_ptr), u16(code))
|
||||
case 1: intrinsics.unaligned_store( (^u8)(out_ptr), u8(code))
|
||||
case:
|
||||
return out, .Unknown_Compression_Method
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ Huffman_Table :: struct {
|
||||
}
|
||||
|
||||
// Implementation starts here
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
z_bit_reverse :: #force_inline proc(n: u16, bits: u8) -> (r: u16) {
|
||||
assert(bits <= 16)
|
||||
// NOTE: Can optimize with llvm.bitreverse.i64 or some bit twiddling
|
||||
@@ -136,7 +136,7 @@ z_bit_reverse :: #force_inline proc(n: u16, bits: u8) -> (r: u16) {
|
||||
}
|
||||
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
grow_buffer :: proc(buf: ^[dynamic]u8) -> (err: compress.Error) {
|
||||
/*
|
||||
That we get here at all means that we didn't pass an expected output size,
|
||||
@@ -154,7 +154,7 @@ grow_buffer :: proc(buf: ^[dynamic]u8) -> (err: compress.Error) {
|
||||
TODO: Make these return compress.Error.
|
||||
*/
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
write_byte :: #force_inline proc(z: ^$C, c: u8) -> (err: io.Error) #no_bounds_check {
|
||||
/*
|
||||
Resize if needed.
|
||||
@@ -173,7 +173,7 @@ write_byte :: #force_inline proc(z: ^$C, c: u8) -> (err: io.Error) #no_bounds_ch
|
||||
return .None
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
repl_byte :: proc(z: ^$C, count: u16, c: u8) -> (err: io.Error) #no_bounds_check {
|
||||
/*
|
||||
TODO(Jeroen): Once we have a magic ring buffer, we can just peek/write into it
|
||||
@@ -201,7 +201,7 @@ repl_byte :: proc(z: ^$C, count: u16, c: u8) -> (err: io.Error) #no_bounds_check
|
||||
return .None
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
repl_bytes :: proc(z: ^$C, count: u16, distance: u16) -> (err: io.Error) {
|
||||
/*
|
||||
TODO(Jeroen): Once we have a magic ring buffer, we can just peek/write into it
|
||||
@@ -234,8 +234,8 @@ allocate_huffman_table :: proc(allocator := context.allocator) -> (z: ^Huffman_T
|
||||
return new(Huffman_Table, allocator), nil
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
|
||||
@(optimization_mode="favor_size")
|
||||
build_huffman :: #force_no_inline proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
|
||||
sizes: [HUFFMAN_MAX_BITS+1]int
|
||||
next_code: [HUFFMAN_MAX_BITS+1]int
|
||||
|
||||
@@ -293,7 +293,7 @@ build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check {
|
||||
code := u16(compress.peek_bits_lsb(z,16))
|
||||
|
||||
@@ -324,7 +324,7 @@ decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Erro
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
decode_huffman :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check {
|
||||
if z.num_bits < 16 {
|
||||
if z.num_bits > 63 {
|
||||
@@ -344,7 +344,7 @@ decode_huffman :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bo
|
||||
return decode_huffman_slowpath(z, t)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err: Error) #no_bounds_check {
|
||||
#no_bounds_check for {
|
||||
value, e := decode_huffman(z, z_repeat)
|
||||
@@ -413,7 +413,7 @@ parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err:
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := false, expected_output_size := -1, allocator := context.allocator) -> (err: Error) #no_bounds_check {
|
||||
/*
|
||||
ctx.output must be a bytes.Buffer for now. We'll add a separate implementation that writes to a stream.
|
||||
@@ -486,7 +486,7 @@ inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := f
|
||||
|
||||
// TODO: Check alignment of reserve/resize.
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.allocator) -> (err: Error) #no_bounds_check {
|
||||
context.allocator = allocator
|
||||
expected_output_size := expected_output_size
|
||||
@@ -670,4 +670,4 @@ inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := fals
|
||||
return inflate_raw(&ctx, expected_output_size=expected_output_size)
|
||||
}
|
||||
|
||||
inflate :: proc{inflate_from_context, inflate_from_byte_array}
|
||||
inflate :: proc{inflate_from_context, inflate_from_byte_array}
|
||||
@@ -5,13 +5,10 @@ The implementation is non-intrusive, and non-recursive.
|
||||
*/
|
||||
package container_avl
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
@(require) import "base:intrinsics"
|
||||
@(require) import "base:runtime"
|
||||
import "core:slice"
|
||||
|
||||
_ :: intrinsics
|
||||
_ :: runtime
|
||||
|
||||
// Originally based on the CC0 implementation by Eric Biggers
|
||||
// See: https://github.com/ebiggers/avl_tree/
|
||||
|
||||
@@ -90,7 +87,7 @@ init_cmp :: proc(
|
||||
init_ordered :: proc(
|
||||
t: ^$T/Tree($Value),
|
||||
node_allocator := context.allocator,
|
||||
) where intrinsics.type_is_ordered_numeric(Value) {
|
||||
) where intrinsics.type_is_ordered(Value) {
|
||||
init_cmp(t, slice.cmp_proc(Value), node_allocator)
|
||||
}
|
||||
|
||||
@@ -675,4 +672,4 @@ iterator_first :: proc "contextless" (it: ^Iterator($Value)) {
|
||||
if it._cur != nil {
|
||||
it._next = node_next_or_prev_in_order(it._cur, it._direction)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,8 +210,11 @@ set :: proc(ba: ^Bit_Array, #any_int index: uint, set_to: bool = true, allocator
|
||||
|
||||
ba.max_index = max(idx, ba.max_index)
|
||||
|
||||
if set_to{ ba.bits[leg_index] |= 1 << uint(bit_index) }
|
||||
else { ba.bits[leg_index] &= ~(1 << uint(bit_index)) }
|
||||
if set_to {
|
||||
ba.bits[leg_index] |= 1 << uint(bit_index)
|
||||
} else {
|
||||
ba.bits[leg_index] &~= 1 << uint(bit_index)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -253,7 +256,7 @@ Inputs:
|
||||
- index: Which bit in the array
|
||||
*/
|
||||
unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check {
|
||||
b.bits[bit >> INDEX_SHIFT] &= ~(1 << uint(bit & INDEX_MASK))
|
||||
b.bits[bit >> INDEX_SHIFT] &~= 1 << uint(bit & INDEX_MASK)
|
||||
}
|
||||
/*
|
||||
A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative).
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Package list implements an intrusive doubly-linked list.
|
||||
|
||||
An intrusive container requires a `Node` to be embedded in your own structure, like this:
|
||||
|
||||
My_String :: struct {
|
||||
node: list.Node,
|
||||
value: string,
|
||||
}
|
||||
|
||||
Embedding the members of a `list.Node` in your structure with the `using` keyword is also allowed:
|
||||
|
||||
My_String :: struct {
|
||||
using node: list.Node,
|
||||
value: string,
|
||||
}
|
||||
|
||||
Here is a full example:
|
||||
|
||||
package test
|
||||
|
||||
import "core:fmt"
|
||||
import "core:container/intrusive/list"
|
||||
|
||||
main :: proc() {
|
||||
l: list.List
|
||||
|
||||
one := My_String{value="Hello"}
|
||||
two := My_String{value="World"}
|
||||
|
||||
list.push_back(&l, &one.node)
|
||||
list.push_back(&l, &two.node)
|
||||
|
||||
iter := list.iterator_head(l, My_String, "node")
|
||||
for s in list.iterate_next(&iter) {
|
||||
fmt.println(s.value)
|
||||
}
|
||||
}
|
||||
|
||||
My_String :: struct {
|
||||
node: list.Node,
|
||||
value: string,
|
||||
}
|
||||
|
||||
*/
|
||||
package container_intrusive_list
|
||||
@@ -18,11 +18,18 @@ List :: struct {
|
||||
tail: ^Node,
|
||||
}
|
||||
|
||||
|
||||
// The list link you must include in your own structure.
|
||||
Node :: struct {
|
||||
prev, next: ^Node,
|
||||
}
|
||||
|
||||
/*
|
||||
Inserts a new element at the front of the list with O(1) time complexity.
|
||||
|
||||
**Inputs**
|
||||
- list: The container list
|
||||
- node: The node member of the user-defined element structure
|
||||
*/
|
||||
push_front :: proc "contextless" (list: ^List, node: ^Node) {
|
||||
if list.head != nil {
|
||||
list.head.prev = node
|
||||
@@ -33,7 +40,13 @@ push_front :: proc "contextless" (list: ^List, node: ^Node) {
|
||||
node.prev, node.next = nil, nil
|
||||
}
|
||||
}
|
||||
/*
|
||||
Inserts a new element at the back of the list with O(1) time complexity.
|
||||
|
||||
**Inputs**
|
||||
- list: The container list
|
||||
- node: The node member of the user-defined element structure
|
||||
*/
|
||||
push_back :: proc "contextless" (list: ^List, node: ^Node) {
|
||||
if list.tail != nil {
|
||||
list.tail.next = node
|
||||
@@ -45,6 +58,13 @@ push_back :: proc "contextless" (list: ^List, node: ^Node) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Removes an element from a list with O(1) time complexity.
|
||||
|
||||
**Inputs**
|
||||
- list: The container list
|
||||
- node: The node member of the user-defined element structure to be removed
|
||||
*/
|
||||
remove :: proc "contextless" (list: ^List, node: ^Node) {
|
||||
if node != nil {
|
||||
if node.next != nil {
|
||||
@@ -61,7 +81,13 @@ remove :: proc "contextless" (list: ^List, node: ^Node) {
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
Removes from the given list all elements that satisfy a condition with O(N) time complexity.
|
||||
|
||||
**Inputs**
|
||||
- list: The container list
|
||||
- to_erase: The condition procedure. It should return `true` if a node should be removed, `false` otherwise
|
||||
*/
|
||||
remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) {
|
||||
for node := list.head; node != nil; {
|
||||
next := node.next
|
||||
@@ -82,7 +108,13 @@ remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) {
|
||||
node = next
|
||||
}
|
||||
}
|
||||
/*
|
||||
Removes from the given list all elements that satisfy a condition with O(N) time complexity.
|
||||
|
||||
**Inputs**
|
||||
- list: The container list
|
||||
- to_erase: The _contextless_ condition procedure. It should return `true` if a node should be removed, `false` otherwise
|
||||
*/
|
||||
remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^Node) -> bool) {
|
||||
for node := list.head; node != nil; {
|
||||
next := node.next
|
||||
@@ -104,12 +136,26 @@ remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^N
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Checks whether the given list does not contain any element.
|
||||
|
||||
**Inputs**
|
||||
- list: The container list
|
||||
|
||||
**Returns** `true` if `list` is empty, `false` otherwise
|
||||
*/
|
||||
is_empty :: proc "contextless" (list: ^List) -> bool {
|
||||
return list.head == nil
|
||||
}
|
||||
|
||||
/*
|
||||
Removes and returns the element at the front of the list with O(1) time complexity.
|
||||
|
||||
**Inputs**
|
||||
- list: The container list
|
||||
|
||||
**Returns** The node member of the user-defined element structure, or `nil` if the list is empty
|
||||
*/
|
||||
pop_front :: proc "contextless" (list: ^List) -> ^Node {
|
||||
link := list.head
|
||||
if link == nil {
|
||||
@@ -130,6 +176,14 @@ pop_front :: proc "contextless" (list: ^List) -> ^Node {
|
||||
return link
|
||||
|
||||
}
|
||||
/*
|
||||
Removes and returns the element at the back of the list with O(1) time complexity.
|
||||
|
||||
**Inputs**
|
||||
- list: The container list
|
||||
|
||||
**Returns** The node member of the user-defined element structure, or `nil` if the list is empty
|
||||
*/
|
||||
pop_back :: proc "contextless" (list: ^List) -> ^Node {
|
||||
link := list.tail
|
||||
if link == nil {
|
||||
@@ -151,29 +205,102 @@ pop_back :: proc "contextless" (list: ^List) -> ^Node {
|
||||
}
|
||||
|
||||
|
||||
|
||||
Iterator :: struct($T: typeid) {
|
||||
curr: ^Node,
|
||||
offset: uintptr,
|
||||
}
|
||||
|
||||
/*
|
||||
Creates an iterator pointing at the head of the given list. For an example, see `iterate_next`.
|
||||
|
||||
**Inputs**
|
||||
- list: The container list
|
||||
- T: The type of the list's elements
|
||||
- field_name: The name of the node field in the `T` structure
|
||||
|
||||
**Returns** An iterator pointing at the head of `list`
|
||||
|
||||
*/
|
||||
iterator_head :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T)
|
||||
where intrinsics.type_has_field(T, field_name),
|
||||
intrinsics.type_field_type(T, field_name) == Node {
|
||||
return {list.head, offset_of_by_string(T, field_name)}
|
||||
}
|
||||
/*
|
||||
Creates an iterator pointing at the tail of the given list. For an example, see `iterate_prev`.
|
||||
|
||||
**Inputs**
|
||||
- list: The container list
|
||||
- T: The type of the list's elements
|
||||
- field_name: The name of the node field in the `T` structure
|
||||
|
||||
**Returns** An iterator pointing at the tail of `list`
|
||||
|
||||
*/
|
||||
iterator_tail :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T)
|
||||
where intrinsics.type_has_field(T, field_name),
|
||||
intrinsics.type_field_type(T, field_name) == Node {
|
||||
return {list.tail, offset_of_by_string(T, field_name)}
|
||||
}
|
||||
/*
|
||||
Creates an iterator pointing at the specified node of a list.
|
||||
|
||||
**Inputs**
|
||||
- node: a list node
|
||||
- T: The type of the list's elements
|
||||
- field_name: The name of the node field in the `T` structure
|
||||
|
||||
**Returns** An iterator pointing at `node`
|
||||
|
||||
*/
|
||||
iterator_from_node :: proc "contextless" (node: ^Node, $T: typeid, $field_name: string) -> Iterator(T)
|
||||
where intrinsics.type_has_field(T, field_name),
|
||||
intrinsics.type_field_type(T, field_name) == Node {
|
||||
return {node, offset_of_by_string(T, field_name)}
|
||||
}
|
||||
|
||||
/*
|
||||
Retrieves the next element in a list and advances the iterator.
|
||||
|
||||
**Inputs**
|
||||
- it: The iterator
|
||||
|
||||
**Returns**
|
||||
- ptr: The next list element
|
||||
- ok: `true` if the element is valid (the iterator could advance), `false` otherwise
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:container/intrusive/list"
|
||||
|
||||
iterate_next_example :: proc() {
|
||||
l: list.List
|
||||
|
||||
one := My_Struct{value=1}
|
||||
two := My_Struct{value=2}
|
||||
|
||||
list.push_back(&l, &one.node)
|
||||
list.push_back(&l, &two.node)
|
||||
|
||||
it := list.iterator_head(l, My_Struct, "node")
|
||||
for num in list.iterate_next(&it) {
|
||||
fmt.println(num.value)
|
||||
}
|
||||
}
|
||||
|
||||
My_Struct :: struct {
|
||||
node : list.Node,
|
||||
value: int,
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
1
|
||||
2
|
||||
|
||||
*/
|
||||
iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
|
||||
node := it.curr
|
||||
if node == nil {
|
||||
@@ -183,7 +310,47 @@ iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
|
||||
|
||||
return (^T)(uintptr(node) - it.offset), true
|
||||
}
|
||||
/*
|
||||
Retrieves the previous element in a list and recede the iterator.
|
||||
|
||||
**Inputs**
|
||||
- it: The iterator
|
||||
|
||||
**Returns**
|
||||
- ptr: The previous list element
|
||||
- ok: `true` if the element is valid (the iterator could recede), `false` otherwise
|
||||
|
||||
Example:
|
||||
|
||||
import "core:fmt"
|
||||
import "core:container/intrusive/list"
|
||||
|
||||
iterate_next_example :: proc() {
|
||||
l: list.List
|
||||
|
||||
one := My_Struct{value=1}
|
||||
two := My_Struct{value=2}
|
||||
|
||||
list.push_back(&l, &one.node)
|
||||
list.push_back(&l, &two.node)
|
||||
|
||||
it := list.iterator_tail(l, My_Struct, "node")
|
||||
for num in list.iterate_prev(&it) {
|
||||
fmt.println(num.value)
|
||||
}
|
||||
}
|
||||
|
||||
My_Struct :: struct {
|
||||
node : list.Node,
|
||||
value: int,
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
2
|
||||
1
|
||||
|
||||
*/
|
||||
iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
|
||||
node := it.curr
|
||||
if node == nil {
|
||||
@@ -192,4 +359,4 @@ iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
|
||||
it.curr = node.prev
|
||||
|
||||
return (^T)(uintptr(node) - it.offset), true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,7 @@ set :: proc(c: ^$C/Cache($Key, $Value), key: Key, value: Value) -> runtime.Alloc
|
||||
if c.count == c.capacity {
|
||||
e = c.tail
|
||||
_remove_node(c, e)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
c.count += 1
|
||||
e = new(Node(Key, Value), c.node_allocator) or_return
|
||||
}
|
||||
|
||||
@@ -95,11 +95,11 @@ front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
|
||||
}
|
||||
|
||||
back :: proc(q: ^$Q/Queue($T)) -> T {
|
||||
idx := (q.offset+uint(q.len))%builtin.len(q.data)
|
||||
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
|
||||
return q.data[idx]
|
||||
}
|
||||
back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
|
||||
idx := (q.offset+uint(q.len))%builtin.len(q.data)
|
||||
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
|
||||
return &q.data[idx]
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// Push multiple elements to the front of the queue
|
||||
// Push multiple elements to the back of the queue
|
||||
push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error) {
|
||||
n := uint(builtin.len(elems))
|
||||
if space(q^) < int(n) {
|
||||
@@ -241,7 +241,7 @@ clear :: proc(q: ^$Q/Queue($T)) {
|
||||
}
|
||||
|
||||
|
||||
// Internal growinh procedure
|
||||
// Internal growing procedure
|
||||
_grow :: proc(q: ^$Q/Queue($T), min_capacity: uint = 0) -> runtime.Allocator_Error {
|
||||
new_capacity := max(min_capacity, uint(8), uint(builtin.len(q.data))*2)
|
||||
n := uint(builtin.len(q.data))
|
||||
|
||||
@@ -0,0 +1,568 @@
|
||||
// This package implements a red-black tree
|
||||
package container_rbtree
|
||||
|
||||
@(require) import "base:intrinsics"
|
||||
@(require) import "base:runtime"
|
||||
import "core:slice"
|
||||
|
||||
// Originally based on the CC0 implementation from literateprograms.org
|
||||
// But with API design mimicking `core:container/avl` for ease of use.
|
||||
|
||||
// Direction specifies the traversal direction for a tree iterator.
|
||||
Direction :: enum i8 {
|
||||
// Backward is the in-order backwards direction.
|
||||
Backward = -1,
|
||||
// Forward is the in-order forwards direction.
|
||||
Forward = 1,
|
||||
}
|
||||
|
||||
Ordering :: slice.Ordering
|
||||
|
||||
// Tree is a red-black tree
|
||||
Tree :: struct($Key: typeid, $Value: typeid) {
|
||||
// user_data is a parameter that will be passed to the on_remove
|
||||
// callback.
|
||||
user_data: rawptr,
|
||||
// on_remove is an optional callback that can be called immediately
|
||||
// after a node is removed from the tree.
|
||||
on_remove: proc(key: Key, value: Value, user_data: rawptr),
|
||||
|
||||
_root: ^Node(Key, Value),
|
||||
_node_allocator: runtime.Allocator,
|
||||
_cmp_fn: proc(Key, Key) -> Ordering,
|
||||
_size: int,
|
||||
}
|
||||
|
||||
// Node is a red-black tree node.
|
||||
//
|
||||
// WARNING: It is unsafe to mutate value if the node is part of a tree
|
||||
// if doing so will alter the Node's sort position relative to other
|
||||
// elements in the tree.
|
||||
Node :: struct($Key: typeid, $Value: typeid) {
|
||||
key: Key,
|
||||
value: Value,
|
||||
|
||||
_parent: ^Node(Key, Value),
|
||||
_left: ^Node(Key, Value),
|
||||
_right: ^Node(Key, Value),
|
||||
_color: Color,
|
||||
}
|
||||
|
||||
// Might store this in the node pointer in the future, but that'll require a decent amount of rework to pass ^^N instead of ^N
|
||||
Color :: enum uintptr {Black = 0, Red = 1}
|
||||
|
||||
// Iterator is a tree iterator.
|
||||
//
|
||||
// WARNING: It is unsafe to modify the tree while iterating, except via
|
||||
// the iterator_remove method.
|
||||
Iterator :: struct($Key: typeid, $Value: typeid) {
|
||||
_tree: ^Tree(Key, Value),
|
||||
_cur: ^Node(Key, Value),
|
||||
_next: ^Node(Key, Value),
|
||||
_direction: Direction,
|
||||
_called_next: bool,
|
||||
}
|
||||
|
||||
// init initializes a tree.
|
||||
init :: proc {
|
||||
init_ordered,
|
||||
init_cmp,
|
||||
}
|
||||
|
||||
// init_cmp initializes a tree.
|
||||
init_cmp :: proc(t: ^$T/Tree($Key, $Value), cmp_fn: proc(a, b: Key) -> Ordering, node_allocator := context.allocator) {
|
||||
t._root = nil
|
||||
t._node_allocator = node_allocator
|
||||
t._cmp_fn = cmp_fn
|
||||
t._size = 0
|
||||
}
|
||||
|
||||
// init_ordered initializes a tree containing ordered keys, with
|
||||
// a comparison function that results in an ascending order sort.
|
||||
init_ordered :: proc(t: ^$T/Tree($Key, $Value), node_allocator := context.allocator) where intrinsics.type_is_ordered(Key) {
|
||||
init_cmp(t, slice.cmp_proc(Key), node_allocator)
|
||||
}
|
||||
|
||||
// destroy de-initializes a tree.
|
||||
destroy :: proc(t: ^$T/Tree($Key, $Value), call_on_remove: bool = true) {
|
||||
iter := iterator(t, .Forward)
|
||||
for _ in iterator_next(&iter) {
|
||||
iterator_remove(&iter, call_on_remove)
|
||||
}
|
||||
}
|
||||
|
||||
len :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> (node_count: int) {
|
||||
return t._size
|
||||
}
|
||||
|
||||
// first returns the first node in the tree (in-order) or nil iff
|
||||
// the tree is empty.
|
||||
first :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) {
|
||||
return tree_first_or_last_in_order(t, Direction.Backward)
|
||||
}
|
||||
|
||||
// last returns the last element in the tree (in-order) or nil iff
|
||||
// the tree is empty.
|
||||
last :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) {
|
||||
return tree_first_or_last_in_order(t, Direction.Forward)
|
||||
}
|
||||
|
||||
// find finds the key in the tree, and returns the corresponding node, or nil iff the value is not present.
|
||||
find :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) {
|
||||
node = t._root
|
||||
for node != nil {
|
||||
switch t._cmp_fn(key, node.key) {
|
||||
case .Equal: return node
|
||||
case .Less: node = node._left
|
||||
case .Greater: node = node._right
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// find_value finds the key in the tree, and returns the corresponding value, or nil iff the value is not present.
|
||||
find_value :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok {
|
||||
if n := find(t, key); n != nil {
|
||||
return n.value, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// find_or_insert attempts to insert the value into the tree, and returns
|
||||
// the node, a boolean indicating if the value was inserted, and the
|
||||
// node allocator error if relevant. If the value is already present, the existing node is updated.
|
||||
find_or_insert :: proc(t: ^$T/Tree($Key, $Value), key: Key, value: Value) -> (n: ^Node(Key, Value), inserted: bool, err: runtime.Allocator_Error) {
|
||||
n_ptr := &t._root
|
||||
for n_ptr^ != nil {
|
||||
n = n_ptr^
|
||||
switch t._cmp_fn(key, n.key) {
|
||||
case .Less:
|
||||
n_ptr = &n._left
|
||||
case .Greater:
|
||||
n_ptr = &n._right
|
||||
case .Equal:
|
||||
return
|
||||
}
|
||||
}
|
||||
_parent := n
|
||||
|
||||
n = new_clone(Node(Key, Value){key=key, value=value, _parent=_parent, _color=.Red}, t._node_allocator) or_return
|
||||
n_ptr^ = n
|
||||
insert_case1(t, n)
|
||||
t._size += 1
|
||||
return n, true, nil
|
||||
}
|
||||
|
||||
// remove removes a node or value from the tree, and returns true iff the
|
||||
// removal was successful. While the node's value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove :: proc {
|
||||
remove_key,
|
||||
remove_node,
|
||||
}
|
||||
|
||||
// remove_value removes a value from the tree, and returns true iff the
|
||||
// removal was successful. While the node's key + value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove_key :: proc(t: ^$T/Tree($Key, $Value), key: Key, call_on_remove := true) -> bool {
|
||||
n := find(t, key)
|
||||
if n == nil {
|
||||
return false // Key not found, nothing to do
|
||||
}
|
||||
return remove_node(t, n, call_on_remove)
|
||||
}
|
||||
|
||||
// remove_node removes a node from the tree, and returns true iff the
|
||||
// removal was successful. While the node's key + value will be left intact,
|
||||
// the node itself will be freed via the tree's node allocator.
|
||||
remove_node :: proc(t: ^$T/Tree($Key, $Value), node: ^$N/Node(Key, Value), call_on_remove := true) -> (found: bool) {
|
||||
if node._parent == node || (node._parent == nil && t._root != node) {
|
||||
return false // Don't touch self-parented or dangling nodes.
|
||||
}
|
||||
node := node
|
||||
if node._left != nil && node._right != nil {
|
||||
// Copy key + value from predecessor and delete it instead
|
||||
predecessor := maximum_node(node._left)
|
||||
node.key = predecessor.key
|
||||
node.value = predecessor.value
|
||||
node = predecessor
|
||||
}
|
||||
|
||||
child := node._right == nil ? node._left : node._right
|
||||
if node_color(node) == .Black {
|
||||
node._color = node_color(child)
|
||||
remove_case1(t, node)
|
||||
}
|
||||
replace_node(t, node, child)
|
||||
if node._parent == nil && child != nil {
|
||||
child._color = .Black // root should be black
|
||||
}
|
||||
|
||||
if call_on_remove && t.on_remove != nil {
|
||||
t.on_remove(node.key, node.value, t.user_data)
|
||||
}
|
||||
free(node, t._node_allocator)
|
||||
t._size -= 1
|
||||
return true
|
||||
}
|
||||
|
||||
// iterator returns a tree iterator in the specified direction.
|
||||
iterator :: proc "contextless" (t: ^$T/Tree($Key, $Value), direction: Direction) -> Iterator(Key, Value) {
|
||||
it: Iterator(Key, Value)
|
||||
it._tree = cast(^Tree(Key, Value))t
|
||||
it._direction = direction
|
||||
|
||||
iterator_first(&it)
|
||||
|
||||
return it
|
||||
}
|
||||
|
||||
// iterator_from_pos returns a tree iterator in the specified direction,
|
||||
// spanning the range [pos, last] (inclusive).
|
||||
iterator_from_pos :: proc "contextless" (t: ^$T/Tree($Key, $Value), pos: ^Node(Key, Value), direction: Direction) -> Iterator(Key, Value) {
|
||||
it: Iterator(Key, Value)
|
||||
it._tree = transmute(^Tree(Key, Value))t
|
||||
it._direction = direction
|
||||
it._next = nil
|
||||
it._called_next = false
|
||||
|
||||
if it._cur = pos; pos != nil {
|
||||
it._next = node_next_or_prev_in_order(it._cur, it._direction)
|
||||
}
|
||||
|
||||
return it
|
||||
}
|
||||
|
||||
// iterator_get returns the node currently pointed to by the iterator,
|
||||
// or nil iff the node has been removed, the tree is empty, or the end
|
||||
// of the tree has been reached.
|
||||
iterator_get :: proc "contextless" (it: ^$I/Iterator($Key, $Value)) -> ^Node(Key, Value) {
|
||||
return it._cur
|
||||
}
|
||||
|
||||
// iterator_remove removes the node currently pointed to by the iterator,
|
||||
// and returns true iff the removal was successful. Semantics are the
|
||||
// same as the Tree remove.
|
||||
iterator_remove :: proc(it: ^$I/Iterator($Key, $Value), call_on_remove: bool = true) -> bool {
|
||||
if it._cur == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ok := remove_node(it._tree, it._cur , call_on_remove)
|
||||
if ok {
|
||||
it._cur = nil
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// iterator_next advances the iterator and returns the (node, true) or
|
||||
// or (nil, false) iff the end of the tree has been reached.
|
||||
//
|
||||
// Note: The first call to iterator_next will return the first node instead
|
||||
// of advancing the iterator.
|
||||
iterator_next :: proc "contextless" (it: ^$I/Iterator($Key, $Value)) -> (^Node(Key, Value), bool) {
|
||||
// This check is needed so that the first element gets returned from
|
||||
// a brand-new iterator, and so that the somewhat contrived case where
|
||||
// iterator_remove is called before the first call to iterator_next
|
||||
// returns the correct value.
|
||||
if !it._called_next {
|
||||
it._called_next = true
|
||||
|
||||
// There can be the contrived case where iterator_remove is
|
||||
// called before ever calling iterator_next, which needs to be
|
||||
// handled as an actual call to next.
|
||||
//
|
||||
// If this happens it._cur will be nil, so only return the
|
||||
// first value, if it._cur is valid.
|
||||
if it._cur != nil {
|
||||
return it._cur, true
|
||||
}
|
||||
}
|
||||
|
||||
if it._next == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
it._cur = it._next
|
||||
it._next = node_next_or_prev_in_order(it._cur, it._direction)
|
||||
|
||||
return it._cur, true
|
||||
}
|
||||
|
||||
@(private)
|
||||
tree_first_or_last_in_order :: proc "contextless" (t: ^$T/Tree($Key, $Value), direction: Direction) -> ^Node(Key, Value) {
|
||||
first, sign := t._root, i8(direction)
|
||||
if first != nil {
|
||||
for {
|
||||
tmp := node_get_child(first, sign)
|
||||
if tmp == nil {
|
||||
break
|
||||
}
|
||||
first = tmp
|
||||
}
|
||||
}
|
||||
return first
|
||||
}
|
||||
|
||||
@(private)
|
||||
node_get_child :: #force_inline proc "contextless" (n: ^Node($Key, $Value), sign: i8) -> ^Node(Key, Value) {
|
||||
if sign < 0 {
|
||||
return n._left
|
||||
}
|
||||
return n._right
|
||||
}
|
||||
|
||||
@(private)
|
||||
node_next_or_prev_in_order :: proc "contextless" (n: ^Node($Key, $Value), direction: Direction) -> ^Node(Key, Value) {
|
||||
next, tmp: ^Node(Key, Value)
|
||||
sign := i8(direction)
|
||||
|
||||
if next = node_get_child(n, +sign); next != nil {
|
||||
for {
|
||||
tmp = node_get_child(next, -sign)
|
||||
if tmp == nil {
|
||||
break
|
||||
}
|
||||
next = tmp
|
||||
}
|
||||
} else {
|
||||
tmp, next = n, n._parent
|
||||
for next != nil && tmp == node_get_child(next, +sign) {
|
||||
tmp, next = next, next._parent
|
||||
}
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
@(private)
|
||||
iterator_first :: proc "contextless" (it: ^Iterator($Key, $Value)) {
|
||||
// This is private because behavior when the user manually calls
|
||||
// iterator_first followed by iterator_next is unintuitive, since
|
||||
// the first call to iterator_next MUST return the first node
|
||||
// instead of advancing so that `for node in iterator_next(&next)`
|
||||
// works as expected.
|
||||
|
||||
switch it._direction {
|
||||
case .Forward:
|
||||
it._cur = tree_first_or_last_in_order(it._tree, .Backward)
|
||||
case .Backward:
|
||||
it._cur = tree_first_or_last_in_order(it._tree, .Forward)
|
||||
}
|
||||
|
||||
it._next = nil
|
||||
it._called_next = false
|
||||
|
||||
if it._cur != nil {
|
||||
it._next = node_next_or_prev_in_order(it._cur, it._direction)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
grand_parent :: proc(n: ^$N/Node($Key, $Value)) -> (g: ^N) {
|
||||
return n._parent._parent
|
||||
}
|
||||
|
||||
@(private)
|
||||
sibling :: proc(n: ^$N/Node($Key, $Value)) -> (s: ^N) {
|
||||
if n == n._parent._left {
|
||||
return n._parent._right
|
||||
} else {
|
||||
return n._parent._left
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
uncle :: proc(n: ^$N/Node($Key, $Value)) -> (u: ^N) {
|
||||
return sibling(n._parent)
|
||||
}
|
||||
|
||||
@(private)
|
||||
rotate__left :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
r := n._right
|
||||
replace_node(t, n, r)
|
||||
n._right = r._left
|
||||
if r._left != nil {
|
||||
r._left._parent = n
|
||||
}
|
||||
r._left = n
|
||||
n._parent = r
|
||||
}
|
||||
|
||||
@(private)
|
||||
rotate__right :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
l := n._left
|
||||
replace_node(t, n, l)
|
||||
n._left = l._right
|
||||
if l._right != nil {
|
||||
l._right._parent = n
|
||||
}
|
||||
l._right = n
|
||||
n._parent = l
|
||||
}
|
||||
|
||||
@(private)
|
||||
replace_node :: proc(t: ^$T/Tree($Key, $Value), old_n: ^$N/Node(Key, Value), new_n: ^N) {
|
||||
if old_n._parent == nil {
|
||||
t._root = new_n
|
||||
} else {
|
||||
if (old_n == old_n._parent._left) {
|
||||
old_n._parent._left = new_n
|
||||
} else {
|
||||
old_n._parent._right = new_n
|
||||
}
|
||||
}
|
||||
if new_n != nil {
|
||||
new_n._parent = old_n._parent
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
insert_case1 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if n._parent == nil {
|
||||
n._color = .Black
|
||||
} else {
|
||||
insert_case2(t, n)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
insert_case2 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if node_color(n._parent) == .Black {
|
||||
return // Tree is still valid
|
||||
} else {
|
||||
insert_case3(t, n)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
insert_case3 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if node_color(uncle(n)) == .Red {
|
||||
n._parent._color = .Black
|
||||
uncle(n)._color = .Black
|
||||
grand_parent(n)._color = .Red
|
||||
insert_case1(t, grand_parent(n))
|
||||
} else {
|
||||
insert_case4(t, n)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
insert_case4 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
n := n
|
||||
if n == n._parent._right && n._parent == grand_parent(n)._left {
|
||||
rotate__left(t, n._parent)
|
||||
n = n._left
|
||||
} else if n == n._parent._left && n._parent == grand_parent(n)._right {
|
||||
rotate__right(t, n._parent)
|
||||
n = n._right
|
||||
}
|
||||
insert_case5(t, n)
|
||||
}
|
||||
|
||||
@(private)
|
||||
insert_case5 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
n._parent._color = .Black
|
||||
grand_parent(n)._color = .Red
|
||||
if n == n._parent._left && n._parent == grand_parent(n)._left {
|
||||
rotate__right(t, grand_parent(n))
|
||||
} else {
|
||||
rotate__left(t, grand_parent(n))
|
||||
}
|
||||
}
|
||||
|
||||
// The maximum_node() helper function just walks _right until it reaches the last non-leaf:
|
||||
@(private)
|
||||
maximum_node :: proc(n: ^$N/Node($Key, $Value)) -> (max_node: ^N) {
|
||||
n := n
|
||||
for n._right != nil {
|
||||
n = n._right
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@(private)
|
||||
remove_case1 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if n._parent == nil {
|
||||
return
|
||||
} else {
|
||||
remove_case2(t, n)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
remove_case2 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if node_color(sibling(n)) == .Red {
|
||||
n._parent._color = .Red
|
||||
sibling(n)._color = .Black
|
||||
if n == n._parent._left {
|
||||
rotate__left(t, n._parent)
|
||||
} else {
|
||||
rotate__right(t, n._parent)
|
||||
}
|
||||
}
|
||||
remove_case3(t, n)
|
||||
}
|
||||
|
||||
@(private)
|
||||
remove_case3 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if node_color(n._parent) == .Black &&
|
||||
node_color(sibling(n)) == .Black &&
|
||||
node_color(sibling(n)._left) == .Black &&
|
||||
node_color(sibling(n)._right) == .Black {
|
||||
sibling(n)._color = .Red
|
||||
remove_case1(t, n._parent)
|
||||
} else {
|
||||
remove_case4(t, n)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
remove_case4 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if node_color(n._parent) == .Red &&
|
||||
node_color(sibling(n)) == .Black &&
|
||||
node_color(sibling(n)._left) == .Black &&
|
||||
node_color(sibling(n)._right) == .Black {
|
||||
sibling(n)._color = .Red
|
||||
n._parent._color = .Black
|
||||
} else {
|
||||
remove_case5(t, n)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
remove_case5 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
if n == n._parent._left &&
|
||||
node_color(sibling(n)) == .Black &&
|
||||
node_color(sibling(n)._left) == .Red &&
|
||||
node_color(sibling(n)._right) == .Black {
|
||||
sibling(n)._color = .Red
|
||||
sibling(n)._left._color = .Black
|
||||
rotate__right(t, sibling(n))
|
||||
} else if n == n._parent._right &&
|
||||
node_color(sibling(n)) == .Black &&
|
||||
node_color(sibling(n)._right) == .Red &&
|
||||
node_color(sibling(n)._left) == .Black {
|
||||
sibling(n)._color = .Red
|
||||
sibling(n)._right._color = .Black
|
||||
rotate__left(t, sibling(n))
|
||||
}
|
||||
remove_case6(t, n)
|
||||
}
|
||||
|
||||
@(private)
|
||||
remove_case6 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||
sibling(n)._color = node_color(n._parent)
|
||||
n._parent._color = .Black
|
||||
if n == n._parent._left {
|
||||
sibling(n)._right._color = .Black
|
||||
rotate__left(t, n._parent)
|
||||
} else {
|
||||
sibling(n)._left._color = .Black
|
||||
rotate__right(t, n._parent)
|
||||
}
|
||||
}
|
||||
|
||||
node_color :: proc(n: ^$N/Node($Key, $Value)) -> (c: Color) {
|
||||
return n == nil ? .Black : n._color
|
||||
}
|
||||
@@ -119,20 +119,20 @@ consume :: proc "odin" (a: ^$A/Small_Array($N, $T), count: int, loc := #caller_l
|
||||
}
|
||||
|
||||
ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
|
||||
runtime.bounds_check_error_loc(loc, index, a.len)
|
||||
if index+1 < a.len {
|
||||
runtime.bounds_check_error_loc(loc, index, a.len)
|
||||
if index+1 < a.len {
|
||||
copy(a.data[index:], a.data[index+1:])
|
||||
}
|
||||
a.len -= 1
|
||||
}
|
||||
|
||||
unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
|
||||
runtime.bounds_check_error_loc(loc, index, a.len)
|
||||
runtime.bounds_check_error_loc(loc, index, a.len)
|
||||
n := a.len-1
|
||||
if index != n {
|
||||
a.data[index] = a.data[n]
|
||||
}
|
||||
a.len -= 1
|
||||
a.len -= 1
|
||||
}
|
||||
|
||||
clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) {
|
||||
|
||||
@@ -61,7 +61,7 @@ add_dependency :: proc(sorter: ^$S/Sorter($K), key, dependency: K) -> bool {
|
||||
}
|
||||
find.dependents[key] = true
|
||||
|
||||
find = &sorter.relations[key]
|
||||
find = &sorter.relations[key]
|
||||
if find == nil {
|
||||
find = map_insert(&sorter.relations, key, make_relations(sorter))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package _aes
|
||||
|
||||
// KEY_SIZE_128 is the AES-128 key size in bytes.
|
||||
KEY_SIZE_128 :: 16
|
||||
// KEY_SIZE_192 is the AES-192 key size in bytes.
|
||||
KEY_SIZE_192 :: 24
|
||||
// KEY_SIZE_256 is the AES-256 key size in bytes.
|
||||
KEY_SIZE_256 :: 32
|
||||
|
||||
// BLOCK_SIZE is the AES block size in bytes.
|
||||
BLOCK_SIZE :: 16
|
||||
|
||||
// ROUNDS_128 is the number of rounds for AES-128.
|
||||
ROUNDS_128 :: 10
|
||||
// ROUNDS_192 is the number of rounds for AES-192.
|
||||
ROUNDS_192 :: 12
|
||||
// ROUNDS_256 is the number of rounds for AES-256.
|
||||
ROUNDS_256 :: 14
|
||||
|
||||
// GHASH_KEY_SIZE is the GHASH key size in bytes.
|
||||
GHASH_KEY_SIZE :: 16
|
||||
// GHASH_BLOCK_SIZE is the GHASH block size in bytes.
|
||||
GHASH_BLOCK_SIZE :: 16
|
||||
// GHASH_TAG_SIZE is the GHASH tag size in bytes.
|
||||
GHASH_TAG_SIZE :: 16
|
||||
|
||||
// RCON is the AES keyschedule round constants.
|
||||
RCON := [10]byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36}
|
||||
@@ -0,0 +1,86 @@
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:mem"
|
||||
|
||||
STRIDE :: 4
|
||||
|
||||
// Context is a keyed AES (ECB) instance.
|
||||
Context :: struct {
|
||||
_sk_exp: [120]u64,
|
||||
_num_rounds: int,
|
||||
}
|
||||
|
||||
// init initializes a context for AES with the provided key.
|
||||
init :: proc(ctx: ^Context, key: []byte) {
|
||||
skey: [30]u64 = ---
|
||||
|
||||
ctx._num_rounds = keysched(skey[:], key)
|
||||
skey_expand(ctx._sk_exp[:], skey[:], ctx._num_rounds)
|
||||
}
|
||||
|
||||
// encrypt_block sets `dst` to `AES-ECB-Encrypt(src)`.
|
||||
encrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
|
||||
q: [8]u64
|
||||
load_blockx1(&q, src)
|
||||
_encrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
|
||||
store_blockx1(dst, &q)
|
||||
}
|
||||
|
||||
// encrypt_block sets `dst` to `AES-ECB-Decrypt(src)`.
|
||||
decrypt_block :: proc(ctx: ^Context, dst, src: []byte) {
|
||||
q: [8]u64
|
||||
load_blockx1(&q, src)
|
||||
_decrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
|
||||
store_blockx1(dst, &q)
|
||||
}
|
||||
|
||||
// encrypt_blocks sets `dst` to `AES-ECB-Encrypt(src[0], .. src[n])`.
|
||||
encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
|
||||
q: [8]u64 = ---
|
||||
src, dst := src, dst
|
||||
|
||||
n := len(src)
|
||||
for n > 4 {
|
||||
load_blocks(&q, src[0:4])
|
||||
_encrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
|
||||
store_blocks(dst[0:4], &q)
|
||||
|
||||
src = src[4:]
|
||||
dst = dst[4:]
|
||||
n -= 4
|
||||
}
|
||||
if n > 0 {
|
||||
load_blocks(&q, src)
|
||||
_encrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
|
||||
store_blocks(dst, &q)
|
||||
}
|
||||
}
|
||||
|
||||
// decrypt_blocks sets dst to `AES-ECB-Decrypt(src[0], .. src[n])`.
|
||||
decrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) {
|
||||
q: [8]u64 = ---
|
||||
src, dst := src, dst
|
||||
|
||||
n := len(src)
|
||||
for n > 4 {
|
||||
load_blocks(&q, src[0:4])
|
||||
_decrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
|
||||
store_blocks(dst[0:4], &q)
|
||||
|
||||
src = src[4:]
|
||||
dst = dst[4:]
|
||||
n -= 4
|
||||
}
|
||||
if n > 0 {
|
||||
load_blocks(&q, src)
|
||||
_decrypt(&q, ctx._sk_exp[:], ctx._num_rounds)
|
||||
store_blocks(dst, &q)
|
||||
}
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
mem.zero_explicit(ctx, size_of(ctx))
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
|
||||
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
// Bitsliced AES for 64-bit general purpose (integer) registers. Each
|
||||
// invocation will process up to 4 blocks at a time. This implementation
|
||||
// is derived from the BearSSL ct64 code, and distributed under a 1-clause
|
||||
// BSD license with permission from the original author.
|
||||
//
|
||||
// WARNING: "hic sunt dracones"
|
||||
//
|
||||
// This package also deliberately exposes enough internals to be able to
|
||||
// function as a replacement for `AESENC` and `AESDEC` from AES-NI, to
|
||||
// allow the implementation of non-AES primitives that use the AES round
|
||||
// function such as AEGIS and Deoxys-II. This should ONLY be done when
|
||||
// implementing something other than AES itself.
|
||||
|
||||
sub_bytes :: proc "contextless" (q: ^[8]u64) {
|
||||
// This S-box implementation is a straightforward translation of
|
||||
// the circuit described by Boyar and Peralta in "A new
|
||||
// combinational logic minimization technique with applications
|
||||
// to cryptology" (https://eprint.iacr.org/2009/191.pdf).
|
||||
//
|
||||
// Note that variables x* (input) and s* (output) are numbered
|
||||
// in "reverse" order (x0 is the high bit, x7 is the low bit).
|
||||
|
||||
x0 := q[7]
|
||||
x1 := q[6]
|
||||
x2 := q[5]
|
||||
x3 := q[4]
|
||||
x4 := q[3]
|
||||
x5 := q[2]
|
||||
x6 := q[1]
|
||||
x7 := q[0]
|
||||
|
||||
// Top linear transformation.
|
||||
y14 := x3 ~ x5
|
||||
y13 := x0 ~ x6
|
||||
y9 := x0 ~ x3
|
||||
y8 := x0 ~ x5
|
||||
t0 := x1 ~ x2
|
||||
y1 := t0 ~ x7
|
||||
y4 := y1 ~ x3
|
||||
y12 := y13 ~ y14
|
||||
y2 := y1 ~ x0
|
||||
y5 := y1 ~ x6
|
||||
y3 := y5 ~ y8
|
||||
t1 := x4 ~ y12
|
||||
y15 := t1 ~ x5
|
||||
y20 := t1 ~ x1
|
||||
y6 := y15 ~ x7
|
||||
y10 := y15 ~ t0
|
||||
y11 := y20 ~ y9
|
||||
y7 := x7 ~ y11
|
||||
y17 := y10 ~ y11
|
||||
y19 := y10 ~ y8
|
||||
y16 := t0 ~ y11
|
||||
y21 := y13 ~ y16
|
||||
y18 := x0 ~ y16
|
||||
|
||||
// Non-linear section.
|
||||
t2 := y12 & y15
|
||||
t3 := y3 & y6
|
||||
t4 := t3 ~ t2
|
||||
t5 := y4 & x7
|
||||
t6 := t5 ~ t2
|
||||
t7 := y13 & y16
|
||||
t8 := y5 & y1
|
||||
t9 := t8 ~ t7
|
||||
t10 := y2 & y7
|
||||
t11 := t10 ~ t7
|
||||
t12 := y9 & y11
|
||||
t13 := y14 & y17
|
||||
t14 := t13 ~ t12
|
||||
t15 := y8 & y10
|
||||
t16 := t15 ~ t12
|
||||
t17 := t4 ~ t14
|
||||
t18 := t6 ~ t16
|
||||
t19 := t9 ~ t14
|
||||
t20 := t11 ~ t16
|
||||
t21 := t17 ~ y20
|
||||
t22 := t18 ~ y19
|
||||
t23 := t19 ~ y21
|
||||
t24 := t20 ~ y18
|
||||
|
||||
t25 := t21 ~ t22
|
||||
t26 := t21 & t23
|
||||
t27 := t24 ~ t26
|
||||
t28 := t25 & t27
|
||||
t29 := t28 ~ t22
|
||||
t30 := t23 ~ t24
|
||||
t31 := t22 ~ t26
|
||||
t32 := t31 & t30
|
||||
t33 := t32 ~ t24
|
||||
t34 := t23 ~ t33
|
||||
t35 := t27 ~ t33
|
||||
t36 := t24 & t35
|
||||
t37 := t36 ~ t34
|
||||
t38 := t27 ~ t36
|
||||
t39 := t29 & t38
|
||||
t40 := t25 ~ t39
|
||||
|
||||
t41 := t40 ~ t37
|
||||
t42 := t29 ~ t33
|
||||
t43 := t29 ~ t40
|
||||
t44 := t33 ~ t37
|
||||
t45 := t42 ~ t41
|
||||
z0 := t44 & y15
|
||||
z1 := t37 & y6
|
||||
z2 := t33 & x7
|
||||
z3 := t43 & y16
|
||||
z4 := t40 & y1
|
||||
z5 := t29 & y7
|
||||
z6 := t42 & y11
|
||||
z7 := t45 & y17
|
||||
z8 := t41 & y10
|
||||
z9 := t44 & y12
|
||||
z10 := t37 & y3
|
||||
z11 := t33 & y4
|
||||
z12 := t43 & y13
|
||||
z13 := t40 & y5
|
||||
z14 := t29 & y2
|
||||
z15 := t42 & y9
|
||||
z16 := t45 & y14
|
||||
z17 := t41 & y8
|
||||
|
||||
// Bottom linear transformation.
|
||||
t46 := z15 ~ z16
|
||||
t47 := z10 ~ z11
|
||||
t48 := z5 ~ z13
|
||||
t49 := z9 ~ z10
|
||||
t50 := z2 ~ z12
|
||||
t51 := z2 ~ z5
|
||||
t52 := z7 ~ z8
|
||||
t53 := z0 ~ z3
|
||||
t54 := z6 ~ z7
|
||||
t55 := z16 ~ z17
|
||||
t56 := z12 ~ t48
|
||||
t57 := t50 ~ t53
|
||||
t58 := z4 ~ t46
|
||||
t59 := z3 ~ t54
|
||||
t60 := t46 ~ t57
|
||||
t61 := z14 ~ t57
|
||||
t62 := t52 ~ t58
|
||||
t63 := t49 ~ t58
|
||||
t64 := z4 ~ t59
|
||||
t65 := t61 ~ t62
|
||||
t66 := z1 ~ t63
|
||||
s0 := t59 ~ t63
|
||||
s6 := t56 ~ ~t62
|
||||
s7 := t48 ~ ~t60
|
||||
t67 := t64 ~ t65
|
||||
s3 := t53 ~ t66
|
||||
s4 := t51 ~ t66
|
||||
s5 := t47 ~ t65
|
||||
s1 := t64 ~ ~s3
|
||||
s2 := t55 ~ ~t67
|
||||
|
||||
q[7] = s0
|
||||
q[6] = s1
|
||||
q[5] = s2
|
||||
q[4] = s3
|
||||
q[3] = s4
|
||||
q[2] = s5
|
||||
q[1] = s6
|
||||
q[0] = s7
|
||||
}
|
||||
|
||||
orthogonalize :: proc "contextless" (q: ^[8]u64) {
|
||||
CL2 :: 0x5555555555555555
|
||||
CH2 :: 0xAAAAAAAAAAAAAAAA
|
||||
q[0], q[1] = (q[0] & CL2) | ((q[1] & CL2) << 1), ((q[0] & CH2) >> 1) | (q[1] & CH2)
|
||||
q[2], q[3] = (q[2] & CL2) | ((q[3] & CL2) << 1), ((q[2] & CH2) >> 1) | (q[3] & CH2)
|
||||
q[4], q[5] = (q[4] & CL2) | ((q[5] & CL2) << 1), ((q[4] & CH2) >> 1) | (q[5] & CH2)
|
||||
q[6], q[7] = (q[6] & CL2) | ((q[7] & CL2) << 1), ((q[6] & CH2) >> 1) | (q[7] & CH2)
|
||||
|
||||
CL4 :: 0x3333333333333333
|
||||
CH4 :: 0xCCCCCCCCCCCCCCCC
|
||||
q[0], q[2] = (q[0] & CL4) | ((q[2] & CL4) << 2), ((q[0] & CH4) >> 2) | (q[2] & CH4)
|
||||
q[1], q[3] = (q[1] & CL4) | ((q[3] & CL4) << 2), ((q[1] & CH4) >> 2) | (q[3] & CH4)
|
||||
q[4], q[6] = (q[4] & CL4) | ((q[6] & CL4) << 2), ((q[4] & CH4) >> 2) | (q[6] & CH4)
|
||||
q[5], q[7] = (q[5] & CL4) | ((q[7] & CL4) << 2), ((q[5] & CH4) >> 2) | (q[7] & CH4)
|
||||
|
||||
CL8 :: 0x0F0F0F0F0F0F0F0F
|
||||
CH8 :: 0xF0F0F0F0F0F0F0F0
|
||||
q[0], q[4] = (q[0] & CL8) | ((q[4] & CL8) << 4), ((q[0] & CH8) >> 4) | (q[4] & CH8)
|
||||
q[1], q[5] = (q[1] & CL8) | ((q[5] & CL8) << 4), ((q[1] & CH8) >> 4) | (q[5] & CH8)
|
||||
q[2], q[6] = (q[2] & CL8) | ((q[6] & CL8) << 4), ((q[2] & CH8) >> 4) | (q[6] & CH8)
|
||||
q[3], q[7] = (q[3] & CL8) | ((q[7] & CL8) << 4), ((q[3] & CH8) >> 4) | (q[7] & CH8)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
interleave_in :: proc "contextless" (w: []u32) -> (q0, q1: u64) #no_bounds_check {
|
||||
if len(w) < 4 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
x0, x1, x2, x3 := u64(w[0]), u64(w[1]), u64(w[2]), u64(w[3])
|
||||
x0 |= (x0 << 16)
|
||||
x1 |= (x1 << 16)
|
||||
x2 |= (x2 << 16)
|
||||
x3 |= (x3 << 16)
|
||||
x0 &= 0x0000FFFF0000FFFF
|
||||
x1 &= 0x0000FFFF0000FFFF
|
||||
x2 &= 0x0000FFFF0000FFFF
|
||||
x3 &= 0x0000FFFF0000FFFF
|
||||
x0 |= (x0 << 8)
|
||||
x1 |= (x1 << 8)
|
||||
x2 |= (x2 << 8)
|
||||
x3 |= (x3 << 8)
|
||||
x0 &= 0x00FF00FF00FF00FF
|
||||
x1 &= 0x00FF00FF00FF00FF
|
||||
x2 &= 0x00FF00FF00FF00FF
|
||||
x3 &= 0x00FF00FF00FF00FF
|
||||
q0 = x0 | (x2 << 8)
|
||||
q1 = x1 | (x3 << 8)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
interleave_out :: proc "contextless" (q0, q1: u64) -> (w0, w1, w2, w3: u32) {
|
||||
x0 := q0 & 0x00FF00FF00FF00FF
|
||||
x1 := q1 & 0x00FF00FF00FF00FF
|
||||
x2 := (q0 >> 8) & 0x00FF00FF00FF00FF
|
||||
x3 := (q1 >> 8) & 0x00FF00FF00FF00FF
|
||||
x0 |= (x0 >> 8)
|
||||
x1 |= (x1 >> 8)
|
||||
x2 |= (x2 >> 8)
|
||||
x3 |= (x3 >> 8)
|
||||
x0 &= 0x0000FFFF0000FFFF
|
||||
x1 &= 0x0000FFFF0000FFFF
|
||||
x2 &= 0x0000FFFF0000FFFF
|
||||
x3 &= 0x0000FFFF0000FFFF
|
||||
w0 = u32(x0) | u32(x0 >> 16)
|
||||
w1 = u32(x1) | u32(x1 >> 16)
|
||||
w2 = u32(x2) | u32(x2 >> 16)
|
||||
w3 = u32(x3) | u32(x3 >> 16)
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
rotr32 :: #force_inline proc "contextless" (x: u64) -> u64 {
|
||||
return (x << 32) | (x >> 32)
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
|
||||
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
inv_sub_bytes :: proc "contextless" (q: ^[8]u64) {
|
||||
// AES S-box is:
|
||||
// S(x) = A(I(x)) ^ 0x63
|
||||
// where I() is inversion in GF(256), and A() is a linear
|
||||
// transform (0 is formally defined to be its own inverse).
|
||||
// Since inversion is an involution, the inverse S-box can be
|
||||
// computed from the S-box as:
|
||||
// iS(x) = B(S(B(x ^ 0x63)) ^ 0x63)
|
||||
// where B() is the inverse of A(). Indeed, for any y in GF(256):
|
||||
// iS(S(y)) = B(A(I(B(A(I(y)) ^ 0x63 ^ 0x63))) ^ 0x63 ^ 0x63) = y
|
||||
//
|
||||
// Note: we reuse the implementation of the forward S-box,
|
||||
// instead of duplicating it here, so that total code size is
|
||||
// lower. By merging the B() transforms into the S-box circuit
|
||||
// we could make faster CBC decryption, but CBC decryption is
|
||||
// already quite faster than CBC encryption because we can
|
||||
// process four blocks in parallel.
|
||||
|
||||
q0 := ~q[0]
|
||||
q1 := ~q[1]
|
||||
q2 := q[2]
|
||||
q3 := q[3]
|
||||
q4 := q[4]
|
||||
q5 := ~q[5]
|
||||
q6 := ~q[6]
|
||||
q7 := q[7]
|
||||
q[7] = q1 ~ q4 ~ q6
|
||||
q[6] = q0 ~ q3 ~ q5
|
||||
q[5] = q7 ~ q2 ~ q4
|
||||
q[4] = q6 ~ q1 ~ q3
|
||||
q[3] = q5 ~ q0 ~ q2
|
||||
q[2] = q4 ~ q7 ~ q1
|
||||
q[1] = q3 ~ q6 ~ q0
|
||||
q[0] = q2 ~ q5 ~ q7
|
||||
|
||||
sub_bytes(q)
|
||||
|
||||
q0 = ~q[0]
|
||||
q1 = ~q[1]
|
||||
q2 = q[2]
|
||||
q3 = q[3]
|
||||
q4 = q[4]
|
||||
q5 = ~q[5]
|
||||
q6 = ~q[6]
|
||||
q7 = q[7]
|
||||
q[7] = q1 ~ q4 ~ q6
|
||||
q[6] = q0 ~ q3 ~ q5
|
||||
q[5] = q7 ~ q2 ~ q4
|
||||
q[4] = q6 ~ q1 ~ q3
|
||||
q[3] = q5 ~ q0 ~ q2
|
||||
q[2] = q4 ~ q7 ~ q1
|
||||
q[1] = q3 ~ q6 ~ q0
|
||||
q[0] = q2 ~ q5 ~ q7
|
||||
}
|
||||
|
||||
inv_shift_rows :: proc "contextless" (q: ^[8]u64) {
|
||||
for x, i in q {
|
||||
q[i] =
|
||||
(x & 0x000000000000FFFF) |
|
||||
((x & 0x000000000FFF0000) << 4) |
|
||||
((x & 0x00000000F0000000) >> 12) |
|
||||
((x & 0x000000FF00000000) << 8) |
|
||||
((x & 0x0000FF0000000000) >> 8) |
|
||||
((x & 0x000F000000000000) << 12) |
|
||||
((x & 0xFFF0000000000000) >> 4)
|
||||
}
|
||||
}
|
||||
|
||||
inv_mix_columns :: proc "contextless" (q: ^[8]u64) {
|
||||
q0 := q[0]
|
||||
q1 := q[1]
|
||||
q2 := q[2]
|
||||
q3 := q[3]
|
||||
q4 := q[4]
|
||||
q5 := q[5]
|
||||
q6 := q[6]
|
||||
q7 := q[7]
|
||||
r0 := (q0 >> 16) | (q0 << 48)
|
||||
r1 := (q1 >> 16) | (q1 << 48)
|
||||
r2 := (q2 >> 16) | (q2 << 48)
|
||||
r3 := (q3 >> 16) | (q3 << 48)
|
||||
r4 := (q4 >> 16) | (q4 << 48)
|
||||
r5 := (q5 >> 16) | (q5 << 48)
|
||||
r6 := (q6 >> 16) | (q6 << 48)
|
||||
r7 := (q7 >> 16) | (q7 << 48)
|
||||
|
||||
q[0] = q5 ~ q6 ~ q7 ~ r0 ~ r5 ~ r7 ~ rotr32(q0 ~ q5 ~ q6 ~ r0 ~ r5)
|
||||
q[1] = q0 ~ q5 ~ r0 ~ r1 ~ r5 ~ r6 ~ r7 ~ rotr32(q1 ~ q5 ~ q7 ~ r1 ~ r5 ~ r6)
|
||||
q[2] = q0 ~ q1 ~ q6 ~ r1 ~ r2 ~ r6 ~ r7 ~ rotr32(q0 ~ q2 ~ q6 ~ r2 ~ r6 ~ r7)
|
||||
q[3] = q0 ~ q1 ~ q2 ~ q5 ~ q6 ~ r0 ~ r2 ~ r3 ~ r5 ~ rotr32(q0 ~ q1 ~ q3 ~ q5 ~ q6 ~ q7 ~ r0 ~ r3 ~ r5 ~ r7)
|
||||
q[4] = q1 ~ q2 ~ q3 ~ q5 ~ r1 ~ r3 ~ r4 ~ r5 ~ r6 ~ r7 ~ rotr32(q1 ~ q2 ~ q4 ~ q5 ~ q7 ~ r1 ~ r4 ~ r5 ~ r6)
|
||||
q[5] = q2 ~ q3 ~ q4 ~ q6 ~ r2 ~ r4 ~ r5 ~ r6 ~ r7 ~ rotr32(q2 ~ q3 ~ q5 ~ q6 ~ r2 ~ r5 ~ r6 ~ r7)
|
||||
q[6] = q3 ~ q4 ~ q5 ~ q7 ~ r3 ~ r5 ~ r6 ~ r7 ~ rotr32(q3 ~ q4 ~ q6 ~ q7 ~ r3 ~ r6 ~ r7)
|
||||
q[7] = q4 ~ q5 ~ q6 ~ r4 ~ r6 ~ r7 ~ rotr32(q4 ~ q5 ~ q7 ~ r4 ~ r7)
|
||||
}
|
||||
|
||||
@(private)
|
||||
_decrypt :: proc "contextless" (q: ^[8]u64, skey: []u64, num_rounds: int) {
|
||||
add_round_key(q, skey[num_rounds << 3:])
|
||||
for u := num_rounds - 1; u > 0; u -= 1 {
|
||||
inv_shift_rows(q)
|
||||
inv_sub_bytes(q)
|
||||
add_round_key(q, skey[u << 3:])
|
||||
inv_mix_columns(q)
|
||||
}
|
||||
inv_shift_rows(q)
|
||||
inv_sub_bytes(q)
|
||||
add_round_key(q, skey)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
|
||||
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
add_round_key :: proc "contextless" (q: ^[8]u64, sk: []u64) #no_bounds_check {
|
||||
if len(sk) < 8 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
q[0] ~= sk[0]
|
||||
q[1] ~= sk[1]
|
||||
q[2] ~= sk[2]
|
||||
q[3] ~= sk[3]
|
||||
q[4] ~= sk[4]
|
||||
q[5] ~= sk[5]
|
||||
q[6] ~= sk[6]
|
||||
q[7] ~= sk[7]
|
||||
}
|
||||
|
||||
shift_rows :: proc "contextless" (q: ^[8]u64) {
|
||||
for x, i in q {
|
||||
q[i] =
|
||||
(x & 0x000000000000FFFF) |
|
||||
((x & 0x00000000FFF00000) >> 4) |
|
||||
((x & 0x00000000000F0000) << 12) |
|
||||
((x & 0x0000FF0000000000) >> 8) |
|
||||
((x & 0x000000FF00000000) << 8) |
|
||||
((x & 0xF000000000000000) >> 12) |
|
||||
((x & 0x0FFF000000000000) << 4)
|
||||
}
|
||||
}
|
||||
|
||||
mix_columns :: proc "contextless" (q: ^[8]u64) {
|
||||
q0 := q[0]
|
||||
q1 := q[1]
|
||||
q2 := q[2]
|
||||
q3 := q[3]
|
||||
q4 := q[4]
|
||||
q5 := q[5]
|
||||
q6 := q[6]
|
||||
q7 := q[7]
|
||||
r0 := (q0 >> 16) | (q0 << 48)
|
||||
r1 := (q1 >> 16) | (q1 << 48)
|
||||
r2 := (q2 >> 16) | (q2 << 48)
|
||||
r3 := (q3 >> 16) | (q3 << 48)
|
||||
r4 := (q4 >> 16) | (q4 << 48)
|
||||
r5 := (q5 >> 16) | (q5 << 48)
|
||||
r6 := (q6 >> 16) | (q6 << 48)
|
||||
r7 := (q7 >> 16) | (q7 << 48)
|
||||
|
||||
q[0] = q7 ~ r7 ~ r0 ~ rotr32(q0 ~ r0)
|
||||
q[1] = q0 ~ r0 ~ q7 ~ r7 ~ r1 ~ rotr32(q1 ~ r1)
|
||||
q[2] = q1 ~ r1 ~ r2 ~ rotr32(q2 ~ r2)
|
||||
q[3] = q2 ~ r2 ~ q7 ~ r7 ~ r3 ~ rotr32(q3 ~ r3)
|
||||
q[4] = q3 ~ r3 ~ q7 ~ r7 ~ r4 ~ rotr32(q4 ~ r4)
|
||||
q[5] = q4 ~ r4 ~ r5 ~ rotr32(q5 ~ r5)
|
||||
q[6] = q5 ~ r5 ~ r6 ~ rotr32(q6 ~ r6)
|
||||
q[7] = q6 ~ r6 ~ r7 ~ rotr32(q7 ~ r7)
|
||||
}
|
||||
|
||||
@(private)
|
||||
_encrypt :: proc "contextless" (q: ^[8]u64, skey: []u64, num_rounds: int) {
|
||||
add_round_key(q, skey)
|
||||
for u in 1 ..< num_rounds {
|
||||
sub_bytes(q)
|
||||
shift_rows(q)
|
||||
mix_columns(q)
|
||||
add_round_key(q, skey[u << 3:])
|
||||
}
|
||||
sub_bytes(q)
|
||||
shift_rows(q)
|
||||
add_round_key(q, skey[num_rounds << 3:])
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
|
||||
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto/_aes"
|
||||
import "core:encoding/endian"
|
||||
import "core:mem"
|
||||
|
||||
@(private, require_results)
|
||||
sub_word :: proc "contextless" (x: u32) -> u32 {
|
||||
q := [8]u64{u64(x), 0, 0, 0, 0, 0, 0, 0}
|
||||
|
||||
orthogonalize(&q)
|
||||
sub_bytes(&q)
|
||||
orthogonalize(&q)
|
||||
ret := u32(q[0])
|
||||
|
||||
mem.zero_explicit(&q[0], size_of(u64))
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
keysched :: proc(comp_skey: []u64, key: []byte) -> int {
|
||||
num_rounds, key_len := 0, len(key)
|
||||
switch key_len {
|
||||
case _aes.KEY_SIZE_128:
|
||||
num_rounds = _aes.ROUNDS_128
|
||||
case _aes.KEY_SIZE_192:
|
||||
num_rounds = _aes.ROUNDS_192
|
||||
case _aes.KEY_SIZE_256:
|
||||
num_rounds = _aes.ROUNDS_256
|
||||
case:
|
||||
panic("crypto/aes: invalid AES key size")
|
||||
}
|
||||
|
||||
skey: [60]u32 = ---
|
||||
nk, nkf := key_len >> 2, (num_rounds + 1) << 2
|
||||
for i in 0 ..< nk {
|
||||
skey[i] = endian.unchecked_get_u32le(key[i << 2:])
|
||||
}
|
||||
tmp := skey[(key_len >> 2) - 1]
|
||||
for i, j, k := nk, 0, 0; i < nkf; i += 1 {
|
||||
if j == 0 {
|
||||
tmp = (tmp << 24) | (tmp >> 8)
|
||||
tmp = sub_word(tmp) ~ u32(_aes.RCON[k])
|
||||
} else if nk > 6 && j == 4 {
|
||||
tmp = sub_word(tmp)
|
||||
}
|
||||
tmp ~= skey[i - nk]
|
||||
skey[i] = tmp
|
||||
if j += 1; j == nk {
|
||||
j = 0
|
||||
k += 1
|
||||
}
|
||||
}
|
||||
|
||||
q: [8]u64 = ---
|
||||
for i, j := 0, 0; i < nkf; i, j = i + 4, j + 2 {
|
||||
q[0], q[4] = interleave_in(skey[i:])
|
||||
q[1] = q[0]
|
||||
q[2] = q[0]
|
||||
q[3] = q[0]
|
||||
q[5] = q[4]
|
||||
q[6] = q[4]
|
||||
q[7] = q[4]
|
||||
orthogonalize(&q)
|
||||
comp_skey[j + 0] =
|
||||
(q[0] & 0x1111111111111111) |
|
||||
(q[1] & 0x2222222222222222) |
|
||||
(q[2] & 0x4444444444444444) |
|
||||
(q[3] & 0x8888888888888888)
|
||||
comp_skey[j + 1] =
|
||||
(q[4] & 0x1111111111111111) |
|
||||
(q[5] & 0x2222222222222222) |
|
||||
(q[6] & 0x4444444444444444) |
|
||||
(q[7] & 0x8888888888888888)
|
||||
}
|
||||
|
||||
mem.zero_explicit(&skey, size_of(skey))
|
||||
mem.zero_explicit(&q, size_of(q))
|
||||
|
||||
return num_rounds
|
||||
}
|
||||
|
||||
@(private)
|
||||
skey_expand :: proc "contextless" (skey, comp_skey: []u64, num_rounds: int) {
|
||||
n := (num_rounds + 1) << 1
|
||||
for u, v := 0, 0; u < n; u, v = u + 1, v + 4 {
|
||||
x0 := comp_skey[u]
|
||||
x1, x2, x3 := x0, x0, x0
|
||||
x0 &= 0x1111111111111111
|
||||
x1 &= 0x2222222222222222
|
||||
x2 &= 0x4444444444444444
|
||||
x3 &= 0x8888888888888888
|
||||
x1 >>= 1
|
||||
x2 >>= 2
|
||||
x3 >>= 3
|
||||
skey[v + 0] = (x0 << 4) - x0
|
||||
skey[v + 1] = (x1 << 4) - x1
|
||||
skey[v + 2] = (x2 << 4) - x2
|
||||
skey[v + 3] = (x3 << 4) - x3
|
||||
}
|
||||
}
|
||||
|
||||
orthogonalize_roundkey :: proc "contextless" (qq: []u64, key: []byte) {
|
||||
if len(qq) < 8 || len(key) != 16 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
skey: [4]u32 = ---
|
||||
skey[0] = endian.unchecked_get_u32le(key[0:])
|
||||
skey[1] = endian.unchecked_get_u32le(key[4:])
|
||||
skey[2] = endian.unchecked_get_u32le(key[8:])
|
||||
skey[3] = endian.unchecked_get_u32le(key[12:])
|
||||
|
||||
q: [8]u64 = ---
|
||||
q[0], q[4] = interleave_in(skey[:])
|
||||
q[1] = q[0]
|
||||
q[2] = q[0]
|
||||
q[3] = q[0]
|
||||
q[5] = q[4]
|
||||
q[6] = q[4]
|
||||
q[7] = q[4]
|
||||
orthogonalize(&q)
|
||||
|
||||
comp_skey: [2]u64 = ---
|
||||
comp_skey[0] =
|
||||
(q[0] & 0x1111111111111111) |
|
||||
(q[1] & 0x2222222222222222) |
|
||||
(q[2] & 0x4444444444444444) |
|
||||
(q[3] & 0x8888888888888888)
|
||||
comp_skey[1] =
|
||||
(q[4] & 0x1111111111111111) |
|
||||
(q[5] & 0x2222222222222222) |
|
||||
(q[6] & 0x4444444444444444) |
|
||||
(q[7] & 0x8888888888888888)
|
||||
|
||||
for x, u in comp_skey {
|
||||
x0 := x
|
||||
x1, x2, x3 := x0, x0, x0
|
||||
x0 &= 0x1111111111111111
|
||||
x1 &= 0x2222222222222222
|
||||
x2 &= 0x4444444444444444
|
||||
x3 &= 0x8888888888888888
|
||||
x1 >>= 1
|
||||
x2 >>= 2
|
||||
x3 >>= 3
|
||||
qq[u * 4 + 0] = (x0 << 4) - x0
|
||||
qq[u * 4 + 1] = (x1 << 4) - x1
|
||||
qq[u * 4 + 2] = (x2 << 4) - x2
|
||||
qq[u * 4 + 3] = (x3 << 4) - x3
|
||||
}
|
||||
|
||||
mem.zero_explicit(&skey, size_of(skey))
|
||||
mem.zero_explicit(&q, size_of(q))
|
||||
mem.zero_explicit(&comp_skey, size_of(comp_skey))
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
|
||||
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto/_aes"
|
||||
import "core:encoding/endian"
|
||||
|
||||
@(private = "file")
|
||||
bmul64 :: proc "contextless" (x, y: u64) -> u64 {
|
||||
x0 := x & 0x1111111111111111
|
||||
x1 := x & 0x2222222222222222
|
||||
x2 := x & 0x4444444444444444
|
||||
x3 := x & 0x8888888888888888
|
||||
y0 := y & 0x1111111111111111
|
||||
y1 := y & 0x2222222222222222
|
||||
y2 := y & 0x4444444444444444
|
||||
y3 := y & 0x8888888888888888
|
||||
z0 := (x0 * y0) ~ (x1 * y3) ~ (x2 * y2) ~ (x3 * y1)
|
||||
z1 := (x0 * y1) ~ (x1 * y0) ~ (x2 * y3) ~ (x3 * y2)
|
||||
z2 := (x0 * y2) ~ (x1 * y1) ~ (x2 * y0) ~ (x3 * y3)
|
||||
z3 := (x0 * y3) ~ (x1 * y2) ~ (x2 * y1) ~ (x3 * y0)
|
||||
z0 &= 0x1111111111111111
|
||||
z1 &= 0x2222222222222222
|
||||
z2 &= 0x4444444444444444
|
||||
z3 &= 0x8888888888888888
|
||||
return z0 | z1 | z2 | z3
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
rev64 :: proc "contextless" (x: u64) -> u64 {
|
||||
x := x
|
||||
x = ((x & 0x5555555555555555) << 1) | ((x >> 1) & 0x5555555555555555)
|
||||
x = ((x & 0x3333333333333333) << 2) | ((x >> 2) & 0x3333333333333333)
|
||||
x = ((x & 0x0F0F0F0F0F0F0F0F) << 4) | ((x >> 4) & 0x0F0F0F0F0F0F0F0F)
|
||||
x = ((x & 0x00FF00FF00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF00FF00FF)
|
||||
x = ((x & 0x0000FFFF0000FFFF) << 16) | ((x >> 16) & 0x0000FFFF0000FFFF)
|
||||
return (x << 32) | (x >> 32)
|
||||
}
|
||||
|
||||
// ghash calculates the GHASH of data, with the key `key`, and input `dst`
|
||||
// and `data`, and stores the resulting digest in `dst`.
|
||||
//
|
||||
// Note: `dst` is both an input and an output, to support easy implementation
|
||||
// of GCM.
|
||||
ghash :: proc "contextless" (dst, key, data: []byte) {
|
||||
if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
buf := data
|
||||
l := len(buf)
|
||||
|
||||
y1 := endian.unchecked_get_u64be(dst[0:])
|
||||
y0 := endian.unchecked_get_u64be(dst[8:])
|
||||
h1 := endian.unchecked_get_u64be(key[0:])
|
||||
h0 := endian.unchecked_get_u64be(key[8:])
|
||||
h0r := rev64(h0)
|
||||
h1r := rev64(h1)
|
||||
h2 := h0 ~ h1
|
||||
h2r := h0r ~ h1r
|
||||
|
||||
src: []byte
|
||||
for l > 0 {
|
||||
if l >= _aes.GHASH_BLOCK_SIZE {
|
||||
src = buf
|
||||
buf = buf[_aes.GHASH_BLOCK_SIZE:]
|
||||
l -= _aes.GHASH_BLOCK_SIZE
|
||||
} else {
|
||||
tmp: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
copy(tmp[:], buf)
|
||||
src = tmp[:]
|
||||
l = 0
|
||||
}
|
||||
y1 ~= endian.unchecked_get_u64be(src)
|
||||
y0 ~= endian.unchecked_get_u64be(src[8:])
|
||||
|
||||
y0r := rev64(y0)
|
||||
y1r := rev64(y1)
|
||||
y2 := y0 ~ y1
|
||||
y2r := y0r ~ y1r
|
||||
|
||||
z0 := bmul64(y0, h0)
|
||||
z1 := bmul64(y1, h1)
|
||||
z2 := bmul64(y2, h2)
|
||||
z0h := bmul64(y0r, h0r)
|
||||
z1h := bmul64(y1r, h1r)
|
||||
z2h := bmul64(y2r, h2r)
|
||||
z2 ~= z0 ~ z1
|
||||
z2h ~= z0h ~ z1h
|
||||
z0h = rev64(z0h) >> 1
|
||||
z1h = rev64(z1h) >> 1
|
||||
z2h = rev64(z2h) >> 1
|
||||
|
||||
v0 := z0
|
||||
v1 := z0h ~ z2
|
||||
v2 := z1 ~ z2h
|
||||
v3 := z1h
|
||||
|
||||
v3 = (v3 << 1) | (v2 >> 63)
|
||||
v2 = (v2 << 1) | (v1 >> 63)
|
||||
v1 = (v1 << 1) | (v0 >> 63)
|
||||
v0 = (v0 << 1)
|
||||
|
||||
v2 ~= v0 ~ (v0 >> 1) ~ (v0 >> 2) ~ (v0 >> 7)
|
||||
v1 ~= (v0 << 63) ~ (v0 << 62) ~ (v0 << 57)
|
||||
v3 ~= v1 ~ (v1 >> 1) ~ (v1 >> 2) ~ (v1 >> 7)
|
||||
v2 ~= (v1 << 63) ~ (v1 << 62) ~ (v1 << 57)
|
||||
|
||||
y0 = v2
|
||||
y1 = v3
|
||||
}
|
||||
|
||||
endian.unchecked_put_u64be(dst[0:], y1)
|
||||
endian.unchecked_put_u64be(dst[8:], y0)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package aes_ct64
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto/_aes"
|
||||
import "core:encoding/endian"
|
||||
|
||||
load_blockx1 :: proc "contextless" (q: ^[8]u64, src: []byte) {
|
||||
if len(src) != _aes.BLOCK_SIZE {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
w: [4]u32 = ---
|
||||
w[0] = endian.unchecked_get_u32le(src[0:])
|
||||
w[1] = endian.unchecked_get_u32le(src[4:])
|
||||
w[2] = endian.unchecked_get_u32le(src[8:])
|
||||
w[3] = endian.unchecked_get_u32le(src[12:])
|
||||
q[0], q[4] = interleave_in(w[:])
|
||||
orthogonalize(q)
|
||||
}
|
||||
|
||||
store_blockx1 :: proc "contextless" (dst: []byte, q: ^[8]u64) {
|
||||
if len(dst) != _aes.BLOCK_SIZE {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
orthogonalize(q)
|
||||
w0, w1, w2, w3 := interleave_out(q[0], q[4])
|
||||
endian.unchecked_put_u32le(dst[0:], w0)
|
||||
endian.unchecked_put_u32le(dst[4:], w1)
|
||||
endian.unchecked_put_u32le(dst[8:], w2)
|
||||
endian.unchecked_put_u32le(dst[12:], w3)
|
||||
}
|
||||
|
||||
load_blocks :: proc "contextless" (q: ^[8]u64, src: [][]byte) {
|
||||
if n := len(src); n > STRIDE || n == 0 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
w: [4]u32 = ---
|
||||
for s, i in src {
|
||||
if len(s) != _aes.BLOCK_SIZE {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
w[0] = endian.unchecked_get_u32le(s[0:])
|
||||
w[1] = endian.unchecked_get_u32le(s[4:])
|
||||
w[2] = endian.unchecked_get_u32le(s[8:])
|
||||
w[3] = endian.unchecked_get_u32le(s[12:])
|
||||
q[i], q[i + 4] = interleave_in(w[:])
|
||||
}
|
||||
orthogonalize(q)
|
||||
}
|
||||
|
||||
store_blocks :: proc "contextless" (dst: [][]byte, q: ^[8]u64) {
|
||||
if n := len(dst); n > STRIDE || n == 0 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
orthogonalize(q)
|
||||
for d, i in dst {
|
||||
// Allow storing [0,4] blocks.
|
||||
if d == nil {
|
||||
break
|
||||
}
|
||||
if len(d) != _aes.BLOCK_SIZE {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
w0, w1, w2, w3 := interleave_out(q[i], q[i + 4])
|
||||
endian.unchecked_put_u32le(d[0:], w0)
|
||||
endian.unchecked_put_u32le(d[4:], w1)
|
||||
endian.unchecked_put_u32le(d[8:], w2)
|
||||
endian.unchecked_put_u32le(d[12:], w3)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//+build amd64
|
||||
package aes_hw_intel
|
||||
|
||||
import "core:sys/info"
|
||||
|
||||
// is_supporte returns true iff hardware accelerated AES
|
||||
// is supported.
|
||||
is_supported :: proc "contextless" () -> bool {
|
||||
features, ok := info.cpu_features.?
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Note: Everything with AES-NI and PCLMULQDQ has support for
|
||||
// the required SSE extxtensions.
|
||||
req_features :: info.CPU_Features{
|
||||
.sse2,
|
||||
.ssse3,
|
||||
.sse41,
|
||||
.aes,
|
||||
.pclmulqdq,
|
||||
}
|
||||
return features >= req_features
|
||||
}
|
||||
|
||||
// Context is a keyed AES (ECB) instance.
|
||||
Context :: struct {
|
||||
// Note: The ideal thing to do is for the expanded round keys to be
|
||||
// arrays of `__m128i`, however that implies alignment (or using AVX).
|
||||
//
|
||||
// All the people using e-waste processors that don't support an
|
||||
// insturction set that has been around for over 10 years are why
|
||||
// we can't have nice things.
|
||||
_sk_exp_enc: [15][16]byte,
|
||||
_sk_exp_dec: [15][16]byte,
|
||||
_num_rounds: int,
|
||||
}
|
||||
|
||||
// init initializes a context for AES with the provided key.
|
||||
init :: proc(ctx: ^Context, key: []byte) {
|
||||
keysched(ctx, key)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
// Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
|
||||
|
||||
//+build amd64
|
||||
package aes_hw_intel
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto/_aes"
|
||||
import "core:simd"
|
||||
import "core:simd/x86"
|
||||
|
||||
@(private = "file")
|
||||
GHASH_STRIDE_HW :: 4
|
||||
@(private = "file")
|
||||
GHASH_STRIDE_BYTES_HW :: GHASH_STRIDE_HW * _aes.GHASH_BLOCK_SIZE
|
||||
|
||||
// GHASH is defined over elements of GF(2^128) with "full little-endian"
|
||||
// representation: leftmost byte is least significant, and, within each
|
||||
// byte, leftmost _bit_ is least significant. The natural ordering in
|
||||
// x86 is "mixed little-endian": bytes are ordered from least to most
|
||||
// significant, but bits within a byte are in most-to-least significant
|
||||
// order. Going to full little-endian representation would require
|
||||
// reversing bits within each byte, which is doable but expensive.
|
||||
//
|
||||
// Instead, we go to full big-endian representation, by swapping bytes
|
||||
// around, which is done with a single _mm_shuffle_epi8() opcode (it
|
||||
// comes with SSSE3; all CPU that offer pclmulqdq also have SSSE3). We
|
||||
// can use a full big-endian representation because in a carryless
|
||||
// multiplication, we have a nice bit reversal property:
|
||||
//
|
||||
// rev_128(x) * rev_128(y) = rev_255(x * y)
|
||||
//
|
||||
// So by using full big-endian, we still get the right result, except
|
||||
// that it is right-shifted by 1 bit. The left-shift is relatively
|
||||
// inexpensive, and it can be mutualised.
|
||||
//
|
||||
// Since SSE2 opcodes do not have facilities for shitfting full 128-bit
|
||||
// values with bit precision, we have to break down values into 64-bit
|
||||
// chunks. We number chunks from 0 to 3 in left to right order.
|
||||
|
||||
@(private = "file")
|
||||
byteswap_index := transmute(x86.__m128i)simd.i8x16{
|
||||
// Note: simd.i8x16 is reverse order from x86._mm_set_epi8.
|
||||
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
|
||||
}
|
||||
|
||||
@(private = "file", require_results, enable_target_feature = "sse2,ssse3")
|
||||
byteswap :: #force_inline proc "contextless" (x: x86.__m128i) -> x86.__m128i {
|
||||
return x86._mm_shuffle_epi8(x, byteswap_index)
|
||||
}
|
||||
|
||||
// From a 128-bit value kw, compute kx as the XOR of the two 64-bit
|
||||
// halves of kw (into the right half of kx; left half is unspecified),
|
||||
// and return kx.
|
||||
@(private = "file", require_results, enable_target_feature = "sse2")
|
||||
bk :: #force_inline proc "contextless" (kw: x86.__m128i) -> x86.__m128i {
|
||||
return x86._mm_xor_si128(kw, x86._mm_shuffle_epi32(kw, 0x0e))
|
||||
}
|
||||
|
||||
// Combine two 64-bit values (k0:k1) into a 128-bit (kw) value and
|
||||
// the XOR of the two values (kx), and return (kw, kx).
|
||||
@(private = "file", enable_target_feature = "sse2")
|
||||
pbk :: #force_inline proc "contextless" (k0, k1: x86.__m128i) -> (x86.__m128i, x86.__m128i) {
|
||||
kw := x86._mm_unpacklo_epi64(k1, k0)
|
||||
kx := x86._mm_xor_si128(k0, k1)
|
||||
return kw, kx
|
||||
}
|
||||
|
||||
// Left-shift by 1 bit a 256-bit value (in four 64-bit words).
|
||||
@(private = "file", require_results, enable_target_feature = "sse2")
|
||||
sl_256 :: #force_inline proc "contextless" (x0, x1, x2, x3: x86.__m128i) -> (x86.__m128i, x86.__m128i, x86.__m128i, x86.__m128i) {
|
||||
x0, x1, x2, x3 := x0, x1, x2, x3
|
||||
|
||||
x0 = x86._mm_or_si128(x86._mm_slli_epi64(x0, 1), x86._mm_srli_epi64(x1, 63))
|
||||
x1 = x86._mm_or_si128(x86._mm_slli_epi64(x1, 1), x86._mm_srli_epi64(x2, 63))
|
||||
x2 = x86._mm_or_si128(x86._mm_slli_epi64(x2, 1), x86._mm_srli_epi64(x3, 63))
|
||||
x3 = x86._mm_slli_epi64(x3, 1)
|
||||
|
||||
return x0, x1, x2, x3
|
||||
}
|
||||
|
||||
// Perform reduction in GF(2^128).
|
||||
@(private = "file", require_results, enable_target_feature = "sse2")
|
||||
reduce_f128 :: #force_inline proc "contextless" (x0, x1, x2, x3: x86.__m128i) -> (x86.__m128i, x86.__m128i) {
|
||||
x0, x1, x2 := x0, x1, x2
|
||||
|
||||
x1 = x86._mm_xor_si128(
|
||||
x1,
|
||||
x86._mm_xor_si128(
|
||||
x86._mm_xor_si128(
|
||||
x3,
|
||||
x86._mm_srli_epi64(x3, 1)),
|
||||
x86._mm_xor_si128(
|
||||
x86._mm_srli_epi64(x3, 2),
|
||||
x86._mm_srli_epi64(x3, 7))))
|
||||
x2 = x86._mm_xor_si128(
|
||||
x86._mm_xor_si128(
|
||||
x2,
|
||||
x86._mm_slli_epi64(x3, 63)),
|
||||
x86._mm_xor_si128(
|
||||
x86._mm_slli_epi64(x3, 62),
|
||||
x86._mm_slli_epi64(x3, 57)))
|
||||
x0 = x86._mm_xor_si128(
|
||||
x0,
|
||||
x86._mm_xor_si128(
|
||||
x86._mm_xor_si128(
|
||||
x2,
|
||||
x86._mm_srli_epi64(x2, 1)),
|
||||
x86._mm_xor_si128(
|
||||
x86._mm_srli_epi64(x2, 2),
|
||||
x86._mm_srli_epi64(x2, 7))))
|
||||
x1 = x86._mm_xor_si128(
|
||||
x86._mm_xor_si128(
|
||||
x1,
|
||||
x86._mm_slli_epi64(x2, 63)),
|
||||
x86._mm_xor_si128(
|
||||
x86._mm_slli_epi64(x2, 62),
|
||||
x86._mm_slli_epi64(x2, 57)))
|
||||
|
||||
return x0, x1
|
||||
}
|
||||
|
||||
// Square value kw in GF(2^128) into (dw,dx).
|
||||
@(private = "file", require_results, enable_target_feature = "sse2,pclmul")
|
||||
square_f128 :: #force_inline proc "contextless" (kw: x86.__m128i) -> (x86.__m128i, x86.__m128i) {
|
||||
z1 := x86._mm_clmulepi64_si128(kw, kw, 0x11)
|
||||
z3 := x86._mm_clmulepi64_si128(kw, kw, 0x00)
|
||||
z0 := x86._mm_shuffle_epi32(z1, 0x0E)
|
||||
z2 := x86._mm_shuffle_epi32(z3, 0x0E)
|
||||
z0, z1, z2, z3 = sl_256(z0, z1, z2, z3)
|
||||
z0, z1 = reduce_f128(z0, z1, z2, z3)
|
||||
return pbk(z0, z1)
|
||||
}
|
||||
|
||||
// ghash calculates the GHASH of data, with the key `key`, and input `dst`
|
||||
// and `data`, and stores the resulting digest in `dst`.
|
||||
//
|
||||
// Note: `dst` is both an input and an output, to support easy implementation
|
||||
// of GCM.
|
||||
@(enable_target_feature = "sse2,ssse3,pclmul")
|
||||
ghash :: proc "contextless" (dst, key, data: []byte) #no_bounds_check {
|
||||
if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE {
|
||||
intrinsics.trap()
|
||||
}
|
||||
|
||||
// Note: BearSSL opts to copy the remainder into a zero-filled
|
||||
// 64-byte buffer. We do something slightly more simple.
|
||||
|
||||
// Load key and dst (h and y).
|
||||
yw := intrinsics.unaligned_load((^x86.__m128i)(raw_data(dst)))
|
||||
h1w := intrinsics.unaligned_load((^x86.__m128i)(raw_data(key)))
|
||||
yw = byteswap(yw)
|
||||
h1w = byteswap(h1w)
|
||||
h1x := bk(h1w)
|
||||
|
||||
// Process 4 blocks at a time
|
||||
buf := data
|
||||
l := len(buf)
|
||||
if l >= GHASH_STRIDE_BYTES_HW {
|
||||
// Compute h2 = h^2
|
||||
h2w, h2x := square_f128(h1w)
|
||||
|
||||
// Compute h3 = h^3 = h*(h^2)
|
||||
t1 := x86._mm_clmulepi64_si128(h1w, h2w, 0x11)
|
||||
t3 := x86._mm_clmulepi64_si128(h1w, h2w, 0x00)
|
||||
t2 := x86._mm_xor_si128(
|
||||
x86._mm_clmulepi64_si128(h1x, h2x, 0x00),
|
||||
x86._mm_xor_si128(t1, t3))
|
||||
t0 := x86._mm_shuffle_epi32(t1, 0x0E)
|
||||
t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E))
|
||||
t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E))
|
||||
t0, t1, t2, t3 = sl_256(t0, t1, t2, t3)
|
||||
t0, t1 = reduce_f128(t0, t1, t2, t3)
|
||||
h3w, h3x := pbk(t0, t1)
|
||||
|
||||
// Compute h4 = h^4 = (h^2)^2
|
||||
h4w, h4x := square_f128(h2w)
|
||||
|
||||
for l >= GHASH_STRIDE_BYTES_HW {
|
||||
aw0 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf)))
|
||||
aw1 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[16:])))
|
||||
aw2 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[32:])))
|
||||
aw3 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[48:])))
|
||||
aw0 = byteswap(aw0)
|
||||
aw1 = byteswap(aw1)
|
||||
aw2 = byteswap(aw2)
|
||||
aw3 = byteswap(aw3)
|
||||
buf, l = buf[GHASH_STRIDE_BYTES_HW:], l - GHASH_STRIDE_BYTES_HW
|
||||
|
||||
aw0 = x86._mm_xor_si128(aw0, yw)
|
||||
ax1 := bk(aw1)
|
||||
ax2 := bk(aw2)
|
||||
ax3 := bk(aw3)
|
||||
ax0 := bk(aw0)
|
||||
|
||||
t1 = x86._mm_xor_si128(
|
||||
x86._mm_xor_si128(
|
||||
x86._mm_clmulepi64_si128(aw0, h4w, 0x11),
|
||||
x86._mm_clmulepi64_si128(aw1, h3w, 0x11)),
|
||||
x86._mm_xor_si128(
|
||||
x86._mm_clmulepi64_si128(aw2, h2w, 0x11),
|
||||
x86._mm_clmulepi64_si128(aw3, h1w, 0x11)))
|
||||
t3 = x86._mm_xor_si128(
|
||||
x86._mm_xor_si128(
|
||||
x86._mm_clmulepi64_si128(aw0, h4w, 0x00),
|
||||
x86._mm_clmulepi64_si128(aw1, h3w, 0x00)),
|
||||
x86._mm_xor_si128(
|
||||
x86._mm_clmulepi64_si128(aw2, h2w, 0x00),
|
||||
x86._mm_clmulepi64_si128(aw3, h1w, 0x00)))
|
||||
t2 = x86._mm_xor_si128(
|
||||
x86._mm_xor_si128(
|
||||
x86._mm_clmulepi64_si128(ax0, h4x, 0x00),
|
||||
x86._mm_clmulepi64_si128(ax1, h3x, 0x00)),
|
||||
x86._mm_xor_si128(
|
||||
x86._mm_clmulepi64_si128(ax2, h2x, 0x00),
|
||||
x86._mm_clmulepi64_si128(ax3, h1x, 0x00)))
|
||||
t2 = x86._mm_xor_si128(t2, x86._mm_xor_si128(t1, t3))
|
||||
t0 = x86._mm_shuffle_epi32(t1, 0x0E)
|
||||
t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E))
|
||||
t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E))
|
||||
t0, t1, t2, t3 = sl_256(t0, t1, t2, t3)
|
||||
t0, t1 = reduce_f128(t0, t1, t2, t3)
|
||||
yw = x86._mm_unpacklo_epi64(t1, t0)
|
||||
}
|
||||
}
|
||||
|
||||
// Process 1 block at a time
|
||||
src: []byte
|
||||
for l > 0 {
|
||||
if l >= _aes.GHASH_BLOCK_SIZE {
|
||||
src = buf
|
||||
buf = buf[_aes.GHASH_BLOCK_SIZE:]
|
||||
l -= _aes.GHASH_BLOCK_SIZE
|
||||
} else {
|
||||
tmp: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
copy(tmp[:], buf)
|
||||
src = tmp[:]
|
||||
l = 0
|
||||
}
|
||||
|
||||
aw := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src)))
|
||||
aw = byteswap(aw)
|
||||
|
||||
aw = x86._mm_xor_si128(aw, yw)
|
||||
ax := bk(aw)
|
||||
|
||||
t1 := x86._mm_clmulepi64_si128(aw, h1w, 0x11)
|
||||
t3 := x86._mm_clmulepi64_si128(aw, h1w, 0x00)
|
||||
t2 := x86._mm_clmulepi64_si128(ax, h1x, 0x00)
|
||||
t2 = x86._mm_xor_si128(t2, x86._mm_xor_si128(t1, t3))
|
||||
t0 := x86._mm_shuffle_epi32(t1, 0x0E)
|
||||
t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E))
|
||||
t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E))
|
||||
t0, t1, t2, t3 = sl_256(t0, t1, t2, t3)
|
||||
t0, t1 = reduce_f128(t0, t1, t2, t3)
|
||||
yw = x86._mm_unpacklo_epi64(t1, t0)
|
||||
}
|
||||
|
||||
// Write back the hash (dst, aka y)
|
||||
yw = byteswap(yw)
|
||||
intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), yw)
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
// Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
|
||||
// All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “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 AUTHORS 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.
|
||||
|
||||
//+build amd64
|
||||
package aes_hw_intel
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto/_aes"
|
||||
import "core:mem"
|
||||
import "core:simd/x86"
|
||||
|
||||
// Intel AES-NI based implementation. Inspiration taken from BearSSL.
|
||||
//
|
||||
// Note: This assumes that the SROA optimization pass is enabled to be
|
||||
// anything resembling performat otherwise, LLVM will not elide a massive
|
||||
// number of redundant loads/stores it generates for every intrinsic call.
|
||||
|
||||
@(private = "file", require_results, enable_target_feature = "sse2")
|
||||
expand_step128 :: #force_inline proc(k1, k2: x86.__m128i) -> x86.__m128i {
|
||||
k1, k2 := k1, k2
|
||||
|
||||
k2 = x86._mm_shuffle_epi32(k2, 0xff)
|
||||
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
|
||||
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
|
||||
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
|
||||
return x86._mm_xor_si128(k1, k2)
|
||||
}
|
||||
|
||||
@(private = "file", require_results, enable_target_feature = "sse,sse2")
|
||||
expand_step192a :: #force_inline proc (k1_, k2_: ^x86.__m128i, k3: x86.__m128i) -> (x86.__m128i, x86.__m128i) {
|
||||
k1, k2, k3 := k1_^, k2_^, k3
|
||||
|
||||
k3 = x86._mm_shuffle_epi32(k3, 0x55)
|
||||
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
|
||||
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
|
||||
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
|
||||
k1 = x86._mm_xor_si128(k1, k3)
|
||||
|
||||
tmp := k2
|
||||
k2 = x86._mm_xor_si128(k2, x86._mm_slli_si128(k2, 0x04))
|
||||
k2 = x86._mm_xor_si128(k2, x86._mm_shuffle_epi32(k1, 0xff))
|
||||
|
||||
k1_, k2_ := k1_, k2_
|
||||
k1_^, k2_^ = k1, k2
|
||||
|
||||
r1 := transmute(x86.__m128i)(x86._mm_shuffle_ps(transmute(x86.__m128)(tmp), transmute(x86.__m128)(k1), 0x44))
|
||||
r2 := transmute(x86.__m128i)(x86._mm_shuffle_ps(transmute(x86.__m128)(k1), transmute(x86.__m128)(k2), 0x4e))
|
||||
|
||||
return r1, r2
|
||||
}
|
||||
|
||||
@(private = "file", require_results, enable_target_feature = "sse2")
|
||||
expand_step192b :: #force_inline proc (k1_, k2_: ^x86.__m128i, k3: x86.__m128i) -> x86.__m128i {
|
||||
k1, k2, k3 := k1_^, k2_^, k3
|
||||
|
||||
k3 = x86._mm_shuffle_epi32(k3, 0x55)
|
||||
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
|
||||
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
|
||||
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
|
||||
k1 = x86._mm_xor_si128(k1, k3)
|
||||
|
||||
k2 = x86._mm_xor_si128(k2, x86._mm_slli_si128(k2, 0x04))
|
||||
k2 = x86._mm_xor_si128(k2, x86._mm_shuffle_epi32(k1, 0xff))
|
||||
|
||||
k1_, k2_ := k1_, k2_
|
||||
k1_^, k2_^ = k1, k2
|
||||
|
||||
return k1
|
||||
}
|
||||
|
||||
@(private = "file", require_results, enable_target_feature = "sse2")
|
||||
expand_step256b :: #force_inline proc(k1, k2: x86.__m128i) -> x86.__m128i {
|
||||
k1, k2 := k1, k2
|
||||
|
||||
k2 = x86._mm_shuffle_epi32(k2, 0xaa)
|
||||
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
|
||||
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
|
||||
k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04))
|
||||
return x86._mm_xor_si128(k1, k2)
|
||||
}
|
||||
|
||||
@(private = "file", enable_target_feature = "aes")
|
||||
derive_dec_keys :: proc(ctx: ^Context, sks: ^[15]x86.__m128i, num_rounds: int) {
|
||||
intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[0]), sks[num_rounds])
|
||||
for i in 1 ..< num_rounds {
|
||||
tmp := x86._mm_aesimc_si128(sks[i])
|
||||
intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[num_rounds - i]), tmp)
|
||||
}
|
||||
intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[num_rounds]), sks[0])
|
||||
}
|
||||
|
||||
@(private, enable_target_feature = "sse,sse2,aes")
|
||||
keysched :: proc(ctx: ^Context, key: []byte) {
|
||||
sks: [15]x86.__m128i = ---
|
||||
|
||||
// Compute the encryption keys.
|
||||
num_rounds, key_len := 0, len(key)
|
||||
switch key_len {
|
||||
case _aes.KEY_SIZE_128:
|
||||
sks[0] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key)))
|
||||
sks[1] = expand_step128(sks[0], x86._mm_aeskeygenassist_si128(sks[0], 0x01))
|
||||
sks[2] = expand_step128(sks[1], x86._mm_aeskeygenassist_si128(sks[1], 0x02))
|
||||
sks[3] = expand_step128(sks[2], x86._mm_aeskeygenassist_si128(sks[2], 0x04))
|
||||
sks[4] = expand_step128(sks[3], x86._mm_aeskeygenassist_si128(sks[3], 0x08))
|
||||
sks[5] = expand_step128(sks[4], x86._mm_aeskeygenassist_si128(sks[4], 0x10))
|
||||
sks[6] = expand_step128(sks[5], x86._mm_aeskeygenassist_si128(sks[5], 0x20))
|
||||
sks[7] = expand_step128(sks[6], x86._mm_aeskeygenassist_si128(sks[6], 0x40))
|
||||
sks[8] = expand_step128(sks[7], x86._mm_aeskeygenassist_si128(sks[7], 0x80))
|
||||
sks[9] = expand_step128(sks[8], x86._mm_aeskeygenassist_si128(sks[8], 0x1b))
|
||||
sks[10] = expand_step128(sks[9], x86._mm_aeskeygenassist_si128(sks[9], 0x36))
|
||||
num_rounds = _aes.ROUNDS_128
|
||||
case _aes.KEY_SIZE_192:
|
||||
k0 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(key)))
|
||||
k1 := x86.__m128i{
|
||||
intrinsics.unaligned_load((^i64)(raw_data(key[16:]))),
|
||||
0,
|
||||
}
|
||||
sks[0] = k0
|
||||
sks[1], sks[2] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x01))
|
||||
sks[3] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x02))
|
||||
sks[4], sks[5] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x04))
|
||||
sks[6] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x08))
|
||||
sks[7], sks[8] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x10))
|
||||
sks[9] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x20))
|
||||
sks[10], sks[11] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x40))
|
||||
sks[12] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x80))
|
||||
num_rounds = _aes.ROUNDS_192
|
||||
case _aes.KEY_SIZE_256:
|
||||
sks[0] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key)))
|
||||
sks[1] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key[16:])))
|
||||
sks[2] = expand_step128(sks[0], x86._mm_aeskeygenassist_si128(sks[1], 0x01))
|
||||
sks[3] = expand_step256b(sks[1], x86._mm_aeskeygenassist_si128(sks[2], 0x01))
|
||||
sks[4] = expand_step128(sks[2], x86._mm_aeskeygenassist_si128(sks[3], 0x02))
|
||||
sks[5] = expand_step256b(sks[3], x86._mm_aeskeygenassist_si128(sks[4], 0x02))
|
||||
sks[6] = expand_step128(sks[4], x86._mm_aeskeygenassist_si128(sks[5], 0x04))
|
||||
sks[7] = expand_step256b(sks[5], x86._mm_aeskeygenassist_si128(sks[6], 0x04))
|
||||
sks[8] = expand_step128(sks[6], x86._mm_aeskeygenassist_si128(sks[7], 0x08))
|
||||
sks[9] = expand_step256b(sks[7], x86._mm_aeskeygenassist_si128(sks[8], 0x08))
|
||||
sks[10] = expand_step128(sks[8], x86._mm_aeskeygenassist_si128(sks[9], 0x10))
|
||||
sks[11] = expand_step256b(sks[9], x86._mm_aeskeygenassist_si128(sks[10], 0x10))
|
||||
sks[12] = expand_step128(sks[10], x86._mm_aeskeygenassist_si128(sks[11], 0x20))
|
||||
sks[13] = expand_step256b(sks[11], x86._mm_aeskeygenassist_si128(sks[12], 0x20))
|
||||
sks[14] = expand_step128(sks[12], x86._mm_aeskeygenassist_si128(sks[13], 0x40))
|
||||
num_rounds = _aes.ROUNDS_256
|
||||
case:
|
||||
panic("crypto/aes: invalid AES key size")
|
||||
}
|
||||
for i in 0 ..= num_rounds {
|
||||
intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_enc[i]), sks[i])
|
||||
}
|
||||
|
||||
// Compute the decryption keys. GCM and CTR do not need this, however
|
||||
// ECB, CBC, OCB3, etc do.
|
||||
derive_dec_keys(ctx, &sks, num_rounds)
|
||||
|
||||
ctx._num_rounds = num_rounds
|
||||
|
||||
mem.zero_explicit(&sks, size_of(sks))
|
||||
}
|
||||
@@ -110,7 +110,7 @@ ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool {
|
||||
if len(b) != 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
b_ := transmute(^[32]byte)(raw_data(b))
|
||||
b_ := (^[32]byte)(raw_data(b))
|
||||
|
||||
// Do the work in a scratch element, so that ge is unchanged on
|
||||
// failure.
|
||||
@@ -169,7 +169,7 @@ ge_bytes :: proc "contextless" (ge: ^Group_Element, dst: []byte) {
|
||||
if len(dst) != 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
dst_ := transmute(^[32]byte)(raw_data(dst))
|
||||
dst_ := (^[32]byte)(raw_data(dst))
|
||||
|
||||
// Convert the element to affine (x, y) representation.
|
||||
x, y, z_inv: field.Tight_Field_Element = ---, ---, ---
|
||||
|
||||
@@ -28,7 +28,7 @@ sc_set_bytes :: proc "contextless" (sc: ^Scalar, b: []byte) -> bool {
|
||||
if len(b) != 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
b_ := transmute(^[32]byte)(raw_data(b))
|
||||
b_ := (^[32]byte)(raw_data(b))
|
||||
return field.fe_from_bytes(sc, b_)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ sc_set_bytes_rfc8032 :: proc "contextless" (sc: ^Scalar, b: []byte) {
|
||||
if len(b) != 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
b_ := transmute(^[32]byte)(raw_data(b))
|
||||
b_ := (^[32]byte)(raw_data(b))
|
||||
field.fe_from_bytes_rfc8032(sc, b_)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ import "core:mem"
|
||||
fe_relax_cast :: #force_inline proc "contextless" (
|
||||
arg1: ^Tight_Field_Element,
|
||||
) -> ^Loose_Field_Element {
|
||||
return transmute(^Loose_Field_Element)(arg1)
|
||||
return (^Loose_Field_Element)(arg1)
|
||||
}
|
||||
|
||||
fe_tighten_cast :: #force_inline proc "contextless" (
|
||||
arg1: ^Loose_Field_Element,
|
||||
) -> ^Tight_Field_Element {
|
||||
return transmute(^Tight_Field_Element)(arg1)
|
||||
return (^Tight_Field_Element)(arg1)
|
||||
}
|
||||
|
||||
fe_clear :: proc "contextless" (
|
||||
|
||||
@@ -7,13 +7,13 @@ import "core:mem"
|
||||
fe_relax_cast :: #force_inline proc "contextless" (
|
||||
arg1: ^Tight_Field_Element,
|
||||
) -> ^Loose_Field_Element {
|
||||
return transmute(^Loose_Field_Element)(arg1)
|
||||
return (^Loose_Field_Element)(arg1)
|
||||
}
|
||||
|
||||
fe_tighten_cast :: #force_inline proc "contextless" (
|
||||
arg1: ^Loose_Field_Element,
|
||||
) -> ^Tight_Field_Element {
|
||||
return transmute(^Tight_Field_Element)(arg1)
|
||||
return (^Tight_Field_Element)(arg1)
|
||||
}
|
||||
|
||||
fe_from_bytes :: #force_inline proc "contextless" (
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
package aes implements the AES block cipher and some common modes.
|
||||
|
||||
See:
|
||||
- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf
|
||||
- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf
|
||||
- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
|
||||
*/
|
||||
package aes
|
||||
|
||||
import "core:crypto/_aes"
|
||||
|
||||
// KEY_SIZE_128 is the AES-128 key size in bytes.
|
||||
KEY_SIZE_128 :: _aes.KEY_SIZE_128
|
||||
// KEY_SIZE_192 is the AES-192 key size in bytes.
|
||||
KEY_SIZE_192 :: _aes.KEY_SIZE_192
|
||||
// KEY_SIZE_256 is the AES-256 key size in bytes.
|
||||
KEY_SIZE_256 :: _aes.KEY_SIZE_256
|
||||
|
||||
// BLOCK_SIZE is the AES block size in bytes.
|
||||
BLOCK_SIZE :: _aes.BLOCK_SIZE
|
||||
@@ -0,0 +1,201 @@
|
||||
package aes
|
||||
|
||||
import "core:bytes"
|
||||
import "core:crypto/_aes/ct64"
|
||||
import "core:encoding/endian"
|
||||
import "core:math/bits"
|
||||
import "core:mem"
|
||||
|
||||
// CTR_IV_SIZE is the size of the CTR mode IV in bytes.
|
||||
CTR_IV_SIZE :: 16
|
||||
|
||||
// Context_CTR is a keyed AES-CTR instance.
|
||||
Context_CTR :: struct {
|
||||
_impl: Context_Impl,
|
||||
_buffer: [BLOCK_SIZE]byte,
|
||||
_off: int,
|
||||
_ctr_hi: u64,
|
||||
_ctr_lo: u64,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// init_ctr initializes a Context_CTR with the provided key and IV.
|
||||
init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := Implementation.Hardware) {
|
||||
if len(iv) != CTR_IV_SIZE {
|
||||
panic("crypto/aes: invalid CTR IV size")
|
||||
}
|
||||
|
||||
init_impl(&ctx._impl, key, impl)
|
||||
ctx._off = BLOCK_SIZE
|
||||
ctx._ctr_hi = endian.unchecked_get_u64be(iv[0:])
|
||||
ctx._ctr_lo = endian.unchecked_get_u64be(iv[8:])
|
||||
ctx._is_initialized = true
|
||||
}
|
||||
|
||||
// xor_bytes_ctr XORs each byte in src with bytes taken from the AES-CTR
|
||||
// keystream, and writes the resulting output to dst. dst and src MUST
|
||||
// alias exactly or not at all.
|
||||
xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
src, dst := src, dst
|
||||
if dst_len := len(dst); dst_len < len(src) {
|
||||
src = src[:dst_len]
|
||||
}
|
||||
|
||||
if bytes.alias_inexactly(dst, src) {
|
||||
panic("crypto/aes: dst and src alias inexactly")
|
||||
}
|
||||
|
||||
for remaining := len(src); remaining > 0; {
|
||||
// Process multiple blocks at once
|
||||
if ctx._off == BLOCK_SIZE {
|
||||
if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 {
|
||||
direct_bytes := nr_blocks * BLOCK_SIZE
|
||||
ctr_blocks(ctx, dst, src, nr_blocks)
|
||||
remaining -= direct_bytes
|
||||
if remaining == 0 {
|
||||
return
|
||||
}
|
||||
dst = dst[direct_bytes:]
|
||||
src = src[direct_bytes:]
|
||||
}
|
||||
|
||||
// If there is a partial block, generate and buffer 1 block
|
||||
// worth of keystream.
|
||||
ctr_blocks(ctx, ctx._buffer[:], nil, 1)
|
||||
ctx._off = 0
|
||||
}
|
||||
|
||||
// Process partial blocks from the buffered keystream.
|
||||
to_xor := min(BLOCK_SIZE - ctx._off, remaining)
|
||||
buffered_keystream := ctx._buffer[ctx._off:]
|
||||
for i := 0; i < to_xor; i = i + 1 {
|
||||
dst[i] = buffered_keystream[i] ~ src[i]
|
||||
}
|
||||
ctx._off += to_xor
|
||||
dst = dst[to_xor:]
|
||||
src = src[to_xor:]
|
||||
remaining -= to_xor
|
||||
}
|
||||
}
|
||||
|
||||
// keystream_bytes_ctr fills dst with the raw AES-CTR keystream output.
|
||||
keystream_bytes_ctr :: proc(ctx: ^Context_CTR, dst: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
dst := dst
|
||||
for remaining := len(dst); remaining > 0; {
|
||||
// Process multiple blocks at once
|
||||
if ctx._off == BLOCK_SIZE {
|
||||
if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 {
|
||||
direct_bytes := nr_blocks * BLOCK_SIZE
|
||||
ctr_blocks(ctx, dst, nil, nr_blocks)
|
||||
remaining -= direct_bytes
|
||||
if remaining == 0 {
|
||||
return
|
||||
}
|
||||
dst = dst[direct_bytes:]
|
||||
}
|
||||
|
||||
// If there is a partial block, generate and buffer 1 block
|
||||
// worth of keystream.
|
||||
ctr_blocks(ctx, ctx._buffer[:], nil, 1)
|
||||
ctx._off = 0
|
||||
}
|
||||
|
||||
// Process partial blocks from the buffered keystream.
|
||||
to_copy := min(BLOCK_SIZE - ctx._off, remaining)
|
||||
buffered_keystream := ctx._buffer[ctx._off:]
|
||||
copy(dst[:to_copy], buffered_keystream[:to_copy])
|
||||
ctx._off += to_copy
|
||||
dst = dst[to_copy:]
|
||||
remaining -= to_copy
|
||||
}
|
||||
}
|
||||
|
||||
// reset_ctr sanitizes the Context_CTR. The Context_CTR must be
|
||||
// re-initialized to be used again.
|
||||
reset_ctr :: proc "contextless" (ctx: ^Context_CTR) {
|
||||
reset_impl(&ctx._impl)
|
||||
ctx._off = 0
|
||||
ctx._ctr_hi = 0
|
||||
ctx._ctr_lo = 0
|
||||
mem.zero_explicit(&ctx._buffer, size_of(ctx._buffer))
|
||||
ctx._is_initialized = false
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) #no_bounds_check {
|
||||
// Use the optimized hardware implementation if available.
|
||||
if _, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
|
||||
ctr_blocks_hw(ctx, dst, src, nr_blocks)
|
||||
return
|
||||
}
|
||||
|
||||
// Portable implementation.
|
||||
ct64_inc_ctr := #force_inline proc "contextless" (dst: []byte, hi, lo: u64) -> (u64, u64) {
|
||||
endian.unchecked_put_u64be(dst[0:], hi)
|
||||
endian.unchecked_put_u64be(dst[8:], lo)
|
||||
|
||||
hi, lo := hi, lo
|
||||
carry: u64
|
||||
lo, carry = bits.add_u64(lo, 1, 0)
|
||||
hi, _ = bits.add_u64(hi, 0, carry)
|
||||
return hi, lo
|
||||
}
|
||||
|
||||
impl := &ctx._impl.(ct64.Context)
|
||||
src, dst := src, dst
|
||||
nr_blocks := nr_blocks
|
||||
ctr_hi, ctr_lo := ctx._ctr_hi, ctx._ctr_lo
|
||||
|
||||
tmp: [ct64.STRIDE][BLOCK_SIZE]byte = ---
|
||||
ctrs: [ct64.STRIDE][]byte = ---
|
||||
for i in 0 ..< ct64.STRIDE {
|
||||
ctrs[i] = tmp[i][:]
|
||||
}
|
||||
for nr_blocks > 0 {
|
||||
n := min(ct64.STRIDE, nr_blocks)
|
||||
blocks := ctrs[:n]
|
||||
|
||||
for i in 0 ..< n {
|
||||
ctr_hi, ctr_lo = ct64_inc_ctr(blocks[i], ctr_hi, ctr_lo)
|
||||
}
|
||||
ct64.encrypt_blocks(impl, blocks, blocks)
|
||||
|
||||
xor_blocks(dst, src, blocks)
|
||||
|
||||
if src != nil {
|
||||
src = src[n * BLOCK_SIZE:]
|
||||
}
|
||||
dst = dst[n * BLOCK_SIZE:]
|
||||
nr_blocks -= n
|
||||
}
|
||||
|
||||
// Write back the counter.
|
||||
ctx._ctr_hi, ctx._ctr_lo = ctr_hi, ctr_lo
|
||||
|
||||
mem.zero_explicit(&tmp, size_of(tmp))
|
||||
}
|
||||
|
||||
@(private)
|
||||
xor_blocks :: #force_inline proc "contextless" (dst, src: []byte, blocks: [][]byte) {
|
||||
// Note: This would be faster `core:simd` was used, however if
|
||||
// performance of this implementation matters to where that
|
||||
// optimization would be worth it, use chacha20poly1305, or a
|
||||
// CPU that isn't e-waste.
|
||||
#no_bounds_check {
|
||||
if src != nil {
|
||||
for i in 0 ..< len(blocks) {
|
||||
off := i * BLOCK_SIZE
|
||||
for j in 0 ..< BLOCK_SIZE {
|
||||
blocks[i][j] ~= src[off + j]
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0 ..< len(blocks) {
|
||||
copy(dst[i * BLOCK_SIZE:], blocks[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
//+build amd64
|
||||
package aes
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto/_aes"
|
||||
import "core:math/bits"
|
||||
import "core:mem"
|
||||
import "core:simd/x86"
|
||||
|
||||
@(private)
|
||||
CTR_STRIDE_HW :: 4
|
||||
@(private)
|
||||
CTR_STRIDE_BYTES_HW :: CTR_STRIDE_HW * BLOCK_SIZE
|
||||
|
||||
@(private, enable_target_feature = "sse2,aes")
|
||||
ctr_blocks_hw :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) #no_bounds_check {
|
||||
hw_ctx := ctx._impl.(Context_Impl_Hardware)
|
||||
|
||||
sks: [15]x86.__m128i = ---
|
||||
for i in 0 ..= hw_ctx._num_rounds {
|
||||
sks[i] = intrinsics.unaligned_load((^x86.__m128i)(&hw_ctx._sk_exp_enc[i]))
|
||||
}
|
||||
|
||||
hw_inc_ctr := #force_inline proc "contextless" (hi, lo: u64) -> (x86.__m128i, u64, u64) {
|
||||
ret := x86.__m128i{
|
||||
i64(intrinsics.byte_swap(hi)),
|
||||
i64(intrinsics.byte_swap(lo)),
|
||||
}
|
||||
|
||||
hi, lo := hi, lo
|
||||
carry: u64
|
||||
|
||||
lo, carry = bits.add_u64(lo, 1, 0)
|
||||
hi, _ = bits.add_u64(hi, 0, carry)
|
||||
return ret, hi, lo
|
||||
}
|
||||
|
||||
// The latency of AESENC depends on mfg and microarchitecture:
|
||||
// - 7 -> up to Broadwell
|
||||
// - 4 -> AMD and Skylake - Cascade Lake
|
||||
// - 3 -> Ice Lake and newer
|
||||
//
|
||||
// This implementation does 4 blocks at once, since performance
|
||||
// should be "adequate" across most CPUs.
|
||||
|
||||
src, dst := src, dst
|
||||
nr_blocks := nr_blocks
|
||||
ctr_hi, ctr_lo := ctx._ctr_hi, ctx._ctr_lo
|
||||
|
||||
blks: [CTR_STRIDE_HW]x86.__m128i = ---
|
||||
for nr_blocks >= CTR_STRIDE_HW {
|
||||
#unroll for i in 0..< CTR_STRIDE_HW {
|
||||
blks[i], ctr_hi, ctr_lo = hw_inc_ctr(ctr_hi, ctr_lo)
|
||||
}
|
||||
|
||||
#unroll for i in 0 ..< CTR_STRIDE_HW {
|
||||
blks[i] = x86._mm_xor_si128(blks[i], sks[0])
|
||||
}
|
||||
#unroll for i in 1 ..= 9 {
|
||||
#unroll for j in 0 ..< CTR_STRIDE_HW {
|
||||
blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
|
||||
}
|
||||
}
|
||||
switch hw_ctx._num_rounds {
|
||||
case _aes.ROUNDS_128:
|
||||
#unroll for i in 0 ..< CTR_STRIDE_HW {
|
||||
blks[i] = x86._mm_aesenclast_si128(blks[i], sks[10])
|
||||
}
|
||||
case _aes.ROUNDS_192:
|
||||
#unroll for i in 10 ..= 11 {
|
||||
#unroll for j in 0 ..< CTR_STRIDE_HW {
|
||||
blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
|
||||
}
|
||||
}
|
||||
#unroll for i in 0 ..< CTR_STRIDE_HW {
|
||||
blks[i] = x86._mm_aesenclast_si128(blks[i], sks[12])
|
||||
}
|
||||
case _aes.ROUNDS_256:
|
||||
#unroll for i in 10 ..= 13 {
|
||||
#unroll for j in 0 ..< CTR_STRIDE_HW {
|
||||
blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
|
||||
}
|
||||
}
|
||||
#unroll for i in 0 ..< CTR_STRIDE_HW {
|
||||
blks[i] = x86._mm_aesenclast_si128(blks[i], sks[14])
|
||||
}
|
||||
}
|
||||
|
||||
xor_blocks_hw(dst, src, blks[:])
|
||||
|
||||
if src != nil {
|
||||
src = src[CTR_STRIDE_BYTES_HW:]
|
||||
}
|
||||
dst = dst[CTR_STRIDE_BYTES_HW:]
|
||||
nr_blocks -= CTR_STRIDE_HW
|
||||
}
|
||||
|
||||
// Handle the remainder.
|
||||
for nr_blocks > 0 {
|
||||
blks[0], ctr_hi, ctr_lo = hw_inc_ctr(ctr_hi, ctr_lo)
|
||||
|
||||
blks[0] = x86._mm_xor_si128(blks[0], sks[0])
|
||||
#unroll for i in 1 ..= 9 {
|
||||
blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
|
||||
}
|
||||
switch hw_ctx._num_rounds {
|
||||
case _aes.ROUNDS_128:
|
||||
blks[0] = x86._mm_aesenclast_si128(blks[0], sks[10])
|
||||
case _aes.ROUNDS_192:
|
||||
#unroll for i in 10 ..= 11 {
|
||||
blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
|
||||
}
|
||||
blks[0] = x86._mm_aesenclast_si128(blks[0], sks[12])
|
||||
case _aes.ROUNDS_256:
|
||||
#unroll for i in 10 ..= 13 {
|
||||
blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
|
||||
}
|
||||
blks[0] = x86._mm_aesenclast_si128(blks[0], sks[14])
|
||||
}
|
||||
|
||||
xor_blocks_hw(dst, src, blks[:1])
|
||||
|
||||
if src != nil {
|
||||
src = src[BLOCK_SIZE:]
|
||||
}
|
||||
dst = dst[BLOCK_SIZE:]
|
||||
nr_blocks -= 1
|
||||
}
|
||||
|
||||
// Write back the counter.
|
||||
ctx._ctr_hi, ctx._ctr_lo = ctr_hi, ctr_lo
|
||||
|
||||
mem.zero_explicit(&blks, size_of(blks))
|
||||
mem.zero_explicit(&sks, size_of(sks))
|
||||
}
|
||||
|
||||
@(private, enable_target_feature = "sse2")
|
||||
xor_blocks_hw :: proc(dst, src: []byte, blocks: []x86.__m128i) {
|
||||
#no_bounds_check {
|
||||
if src != nil {
|
||||
for i in 0 ..< len(blocks) {
|
||||
off := i * BLOCK_SIZE
|
||||
tmp := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src[off:])))
|
||||
blocks[i] = x86._mm_xor_si128(blocks[i], tmp)
|
||||
}
|
||||
}
|
||||
for i in 0 ..< len(blocks) {
|
||||
intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst[i * BLOCK_SIZE:])), blocks[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package aes
|
||||
|
||||
import "core:crypto/_aes/ct64"
|
||||
|
||||
// Context_ECB is a keyed AES-ECB instance.
|
||||
//
|
||||
// WARNING: Using ECB mode is strongly discouraged unless it is being
|
||||
// used to implement higher level constructs.
|
||||
Context_ECB :: struct {
|
||||
_impl: Context_Impl,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// init_ecb initializes a Context_ECB with the provided key.
|
||||
init_ecb :: proc(ctx: ^Context_ECB, key: []byte, impl := Implementation.Hardware) {
|
||||
init_impl(&ctx._impl, key, impl)
|
||||
ctx._is_initialized = true
|
||||
}
|
||||
|
||||
// encrypt_ecb encrypts the BLOCK_SIZE buffer src, and writes the result to dst.
|
||||
encrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE {
|
||||
panic("crypto/aes: invalid buffer size(s)")
|
||||
}
|
||||
|
||||
switch &impl in ctx._impl {
|
||||
case ct64.Context:
|
||||
ct64.encrypt_block(&impl, dst, src)
|
||||
case Context_Impl_Hardware:
|
||||
encrypt_block_hw(&impl, dst, src)
|
||||
}
|
||||
}
|
||||
|
||||
// decrypt_ecb decrypts the BLOCK_SIZE buffer src, and writes the result to dst.
|
||||
decrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE {
|
||||
panic("crypto/aes: invalid buffer size(s)")
|
||||
}
|
||||
|
||||
switch &impl in ctx._impl {
|
||||
case ct64.Context:
|
||||
ct64.decrypt_block(&impl, dst, src)
|
||||
case Context_Impl_Hardware:
|
||||
decrypt_block_hw(&impl, dst, src)
|
||||
}
|
||||
}
|
||||
|
||||
// reset_ecb sanitizes the Context_ECB. The Context_ECB must be
|
||||
// re-initialized to be used again.
|
||||
reset_ecb :: proc "contextless" (ctx: ^Context_ECB) {
|
||||
reset_impl(&ctx._impl)
|
||||
ctx._is_initialized = false
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//+build amd64
|
||||
package aes
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto/_aes"
|
||||
import "core:simd/x86"
|
||||
|
||||
@(private, enable_target_feature = "sse2,aes")
|
||||
encrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) {
|
||||
blk := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src)))
|
||||
|
||||
blk = x86._mm_xor_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[0])))
|
||||
#unroll for i in 1 ..= 9 {
|
||||
blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i])))
|
||||
}
|
||||
switch ctx._num_rounds {
|
||||
case _aes.ROUNDS_128:
|
||||
blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[10])))
|
||||
case _aes.ROUNDS_192:
|
||||
#unroll for i in 10 ..= 11 {
|
||||
blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i])))
|
||||
}
|
||||
blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[12])))
|
||||
case _aes.ROUNDS_256:
|
||||
#unroll for i in 10 ..= 13 {
|
||||
blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i])))
|
||||
}
|
||||
blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[14])))
|
||||
}
|
||||
|
||||
intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), blk)
|
||||
}
|
||||
|
||||
@(private, enable_target_feature = "sse2,aes")
|
||||
decrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) {
|
||||
blk := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src)))
|
||||
|
||||
blk = x86._mm_xor_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[0])))
|
||||
#unroll for i in 1 ..= 9 {
|
||||
blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i])))
|
||||
}
|
||||
switch ctx._num_rounds {
|
||||
case _aes.ROUNDS_128:
|
||||
blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[10])))
|
||||
case _aes.ROUNDS_192:
|
||||
#unroll for i in 10 ..= 11 {
|
||||
blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i])))
|
||||
}
|
||||
blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[12])))
|
||||
case _aes.ROUNDS_256:
|
||||
#unroll for i in 10 ..= 13 {
|
||||
blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i])))
|
||||
}
|
||||
blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[14])))
|
||||
}
|
||||
|
||||
intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), blk)
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package aes
|
||||
|
||||
import "core:bytes"
|
||||
import "core:crypto"
|
||||
import "core:crypto/_aes"
|
||||
import "core:crypto/_aes/ct64"
|
||||
import "core:encoding/endian"
|
||||
import "core:mem"
|
||||
|
||||
// GCM_NONCE_SIZE is the default size of the GCM nonce in bytes.
|
||||
GCM_NONCE_SIZE :: 12
|
||||
// GCM_NONCE_SIZE_MAX is the maximum size of the GCM nonce in bytes.
|
||||
GCM_NONCE_SIZE_MAX :: 0x2000000000000000 // floor((2^64 - 1) / 8) bits
|
||||
// GCM_TAG_SIZE is the size of a GCM tag in bytes.
|
||||
GCM_TAG_SIZE :: _aes.GHASH_TAG_SIZE
|
||||
|
||||
@(private)
|
||||
GCM_A_MAX :: max(u64) / 8 // 2^64 - 1 bits -> bytes
|
||||
@(private)
|
||||
GCM_P_MAX :: 0xfffffffe0 // 2^39 - 256 bits -> bytes
|
||||
|
||||
// Context_GCM is a keyed AES-GCM instance.
|
||||
Context_GCM :: struct {
|
||||
_impl: Context_Impl,
|
||||
_is_initialized: bool,
|
||||
}
|
||||
|
||||
// init_gcm initializes a Context_GCM with the provided key.
|
||||
init_gcm :: proc(ctx: ^Context_GCM, key: []byte, impl := Implementation.Hardware) {
|
||||
init_impl(&ctx._impl, key, impl)
|
||||
ctx._is_initialized = true
|
||||
}
|
||||
|
||||
// seal_gcm encrypts the plaintext and authenticates the aad and ciphertext,
|
||||
// with the provided Context_GCM and nonce, stores the output in dst and tag.
|
||||
//
|
||||
// dst and plaintext MUST alias exactly or not at all.
|
||||
seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
gcm_validate_common_slice_sizes(tag, nonce, aad, plaintext)
|
||||
if len(dst) != len(plaintext) {
|
||||
panic("crypto/aes: invalid destination ciphertext size")
|
||||
}
|
||||
if bytes.alias_inexactly(dst, plaintext) {
|
||||
panic("crypto/aes: dst and plaintext alias inexactly")
|
||||
}
|
||||
|
||||
if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
|
||||
gcm_seal_hw(&impl, dst, tag, nonce, aad, plaintext)
|
||||
return
|
||||
}
|
||||
|
||||
h: [_aes.GHASH_KEY_SIZE]byte
|
||||
j0: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
j0_enc: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
s: [_aes.GHASH_TAG_SIZE]byte
|
||||
init_ghash_ct64(ctx, &h, &j0, &j0_enc, nonce)
|
||||
|
||||
// Note: Our GHASH implementation handles appending padding.
|
||||
ct64.ghash(s[:], h[:], aad)
|
||||
gctr_ct64(ctx, dst, &s, plaintext, &h, &j0, true)
|
||||
final_ghash_ct64(&s, &h, &j0_enc, len(aad), len(plaintext))
|
||||
copy(tag, s[:])
|
||||
|
||||
mem.zero_explicit(&h, len(h))
|
||||
mem.zero_explicit(&j0, len(j0))
|
||||
mem.zero_explicit(&j0_enc, len(j0_enc))
|
||||
}
|
||||
|
||||
// open_gcm authenticates the aad and ciphertext, and decrypts the ciphertext,
|
||||
// with the provided Context_GCM, nonce, and tag, and stores the output in dst,
|
||||
// returning true iff the authentication was successful. If authentication
|
||||
// fails, the destination buffer will be zeroed.
|
||||
//
|
||||
// dst and plaintext MUST alias exactly or not at all.
|
||||
open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
gcm_validate_common_slice_sizes(tag, nonce, aad, ciphertext)
|
||||
if len(dst) != len(ciphertext) {
|
||||
panic("crypto/aes: invalid destination plaintext size")
|
||||
}
|
||||
if bytes.alias_inexactly(dst, ciphertext) {
|
||||
panic("crypto/aes: dst and ciphertext alias inexactly")
|
||||
}
|
||||
|
||||
if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw {
|
||||
return gcm_open_hw(&impl, dst, nonce, aad, ciphertext, tag)
|
||||
}
|
||||
|
||||
h: [_aes.GHASH_KEY_SIZE]byte
|
||||
j0: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
j0_enc: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
s: [_aes.GHASH_TAG_SIZE]byte
|
||||
init_ghash_ct64(ctx, &h, &j0, &j0_enc, nonce)
|
||||
|
||||
ct64.ghash(s[:], h[:], aad)
|
||||
gctr_ct64(ctx, dst, &s, ciphertext, &h, &j0, false)
|
||||
final_ghash_ct64(&s, &h, &j0_enc, len(aad), len(ciphertext))
|
||||
|
||||
ok := crypto.compare_constant_time(s[:], tag) == 1
|
||||
if !ok {
|
||||
mem.zero_explicit(raw_data(dst), len(dst))
|
||||
}
|
||||
|
||||
mem.zero_explicit(&h, len(h))
|
||||
mem.zero_explicit(&j0, len(j0))
|
||||
mem.zero_explicit(&j0_enc, len(j0_enc))
|
||||
mem.zero_explicit(&s, len(s))
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// reset_ctr sanitizes the Context_GCM. The Context_GCM must be
|
||||
// re-initialized to be used again.
|
||||
reset_gcm :: proc "contextless" (ctx: ^Context_GCM) {
|
||||
reset_impl(&ctx._impl)
|
||||
ctx._is_initialized = false
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) {
|
||||
if len(tag) != GCM_TAG_SIZE {
|
||||
panic("crypto/aes: invalid GCM tag size")
|
||||
}
|
||||
|
||||
// The specification supports nonces in the range [1, 2^64) bits.
|
||||
if l := len(nonce); l == 0 || u64(l) >= GCM_NONCE_SIZE_MAX {
|
||||
panic("crypto/aes: invalid GCM nonce size")
|
||||
}
|
||||
|
||||
if aad_len := u64(len(aad)); aad_len > GCM_A_MAX {
|
||||
panic("crypto/aes: oversized GCM aad")
|
||||
}
|
||||
if text_len := u64(len(text)); text_len > GCM_P_MAX {
|
||||
panic("crypto/aes: oversized GCM src data")
|
||||
}
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
init_ghash_ct64 :: proc(
|
||||
ctx: ^Context_GCM,
|
||||
h: ^[_aes.GHASH_KEY_SIZE]byte,
|
||||
j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
nonce: []byte,
|
||||
) {
|
||||
impl := &ctx._impl.(ct64.Context)
|
||||
|
||||
// 1. Let H = CIPH(k, 0^128)
|
||||
ct64.encrypt_block(impl, h[:], h[:])
|
||||
|
||||
// Define a block, J0, as follows:
|
||||
if l := len(nonce); l == GCM_NONCE_SIZE {
|
||||
// if len(IV) = 96, then let J0 = IV || 0^31 || 1
|
||||
copy(j0[:], nonce)
|
||||
j0[_aes.GHASH_BLOCK_SIZE - 1] = 1
|
||||
} else {
|
||||
// If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV),
|
||||
// and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64).
|
||||
ct64.ghash(j0[:], h[:], nonce)
|
||||
|
||||
tmp: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
endian.unchecked_put_u64be(tmp[8:], u64(l) * 8)
|
||||
ct64.ghash(j0[:], h[:], tmp[:])
|
||||
}
|
||||
|
||||
// ECB encrypt j0, so that we can just XOR with the tag. In theory
|
||||
// this could be processed along with the final GCTR block, to
|
||||
// potentially save a call to AES-ECB, but... just use AES-NI.
|
||||
ct64.encrypt_block(impl, j0_enc[:], j0[:])
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
final_ghash_ct64 :: proc(
|
||||
s: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
h: ^[_aes.GHASH_KEY_SIZE]byte,
|
||||
j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
a_len: int,
|
||||
t_len: int,
|
||||
) {
|
||||
blk: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
endian.unchecked_put_u64be(blk[0:], u64(a_len) * 8)
|
||||
endian.unchecked_put_u64be(blk[8:], u64(t_len) * 8)
|
||||
|
||||
ct64.ghash(s[:], h[:], blk[:])
|
||||
for i in 0 ..< len(s) {
|
||||
s[i] ~= j0[i]
|
||||
}
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
gctr_ct64 :: proc(
|
||||
ctx: ^Context_GCM,
|
||||
dst: []byte,
|
||||
s: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
src: []byte,
|
||||
h: ^[_aes.GHASH_KEY_SIZE]byte,
|
||||
nonce: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
is_seal: bool,
|
||||
) #no_bounds_check {
|
||||
ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 {
|
||||
endian.unchecked_put_u32be(dst[12:], ctr)
|
||||
return ctr + 1
|
||||
}
|
||||
|
||||
// Setup the counter blocks.
|
||||
tmp, tmp2: [ct64.STRIDE][BLOCK_SIZE]byte = ---, ---
|
||||
ctrs, blks: [ct64.STRIDE][]byte = ---, ---
|
||||
ctr := endian.unchecked_get_u32be(nonce[GCM_NONCE_SIZE:]) + 1
|
||||
for i in 0 ..< ct64.STRIDE {
|
||||
// Setup scratch space for the keystream.
|
||||
blks[i] = tmp2[i][:]
|
||||
|
||||
// Pre-copy the IV to all the counter blocks.
|
||||
ctrs[i] = tmp[i][:]
|
||||
copy(ctrs[i], nonce[:GCM_NONCE_SIZE])
|
||||
}
|
||||
|
||||
impl := &ctx._impl.(ct64.Context)
|
||||
src, dst := src, dst
|
||||
|
||||
nr_blocks := len(src) / BLOCK_SIZE
|
||||
for nr_blocks > 0 {
|
||||
n := min(ct64.STRIDE, nr_blocks)
|
||||
l := n * BLOCK_SIZE
|
||||
|
||||
if !is_seal {
|
||||
ct64.ghash(s[:], h[:], src[:l])
|
||||
}
|
||||
|
||||
// The keystream is written to a separate buffer, as we will
|
||||
// reuse the first 96-bits of each counter.
|
||||
for i in 0 ..< n {
|
||||
ctr = ct64_inc_ctr32(ctrs[i], ctr)
|
||||
}
|
||||
ct64.encrypt_blocks(impl, blks[:n], ctrs[:n])
|
||||
|
||||
xor_blocks(dst, src, blks[:n])
|
||||
|
||||
if is_seal {
|
||||
ct64.ghash(s[:], h[:], dst[:l])
|
||||
}
|
||||
|
||||
src = src[l:]
|
||||
dst = dst[l:]
|
||||
nr_blocks -= n
|
||||
}
|
||||
if l := len(src); l > 0 {
|
||||
if !is_seal {
|
||||
ct64.ghash(s[:], h[:], src[:l])
|
||||
}
|
||||
|
||||
ct64_inc_ctr32(ctrs[0], ctr)
|
||||
ct64.encrypt_block(impl, ctrs[0], ctrs[0])
|
||||
|
||||
for i in 0 ..< l {
|
||||
dst[i] = src[i] ~ ctrs[0][i]
|
||||
}
|
||||
|
||||
if is_seal {
|
||||
ct64.ghash(s[:], h[:], dst[:l])
|
||||
}
|
||||
}
|
||||
|
||||
mem.zero_explicit(&tmp, size_of(tmp))
|
||||
mem.zero_explicit(&tmp2, size_of(tmp2))
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
//+build amd64
|
||||
package aes
|
||||
|
||||
import "base:intrinsics"
|
||||
import "core:crypto"
|
||||
import "core:crypto/_aes"
|
||||
import "core:crypto/_aes/hw_intel"
|
||||
import "core:encoding/endian"
|
||||
import "core:mem"
|
||||
import "core:simd/x86"
|
||||
|
||||
@(private)
|
||||
gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) {
|
||||
h: [_aes.GHASH_KEY_SIZE]byte
|
||||
j0: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
j0_enc: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
s: [_aes.GHASH_TAG_SIZE]byte
|
||||
init_ghash_hw(ctx, &h, &j0, &j0_enc, nonce)
|
||||
|
||||
// Note: Our GHASH implementation handles appending padding.
|
||||
hw_intel.ghash(s[:], h[:], aad)
|
||||
gctr_hw(ctx, dst, &s, plaintext, &h, &j0, true)
|
||||
final_ghash_hw(&s, &h, &j0_enc, len(aad), len(plaintext))
|
||||
copy(tag, s[:])
|
||||
|
||||
mem.zero_explicit(&h, len(h))
|
||||
mem.zero_explicit(&j0, len(j0))
|
||||
mem.zero_explicit(&j0_enc, len(j0_enc))
|
||||
}
|
||||
|
||||
@(private)
|
||||
gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
|
||||
h: [_aes.GHASH_KEY_SIZE]byte
|
||||
j0: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
j0_enc: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
s: [_aes.GHASH_TAG_SIZE]byte
|
||||
init_ghash_hw(ctx, &h, &j0, &j0_enc, nonce)
|
||||
|
||||
hw_intel.ghash(s[:], h[:], aad)
|
||||
gctr_hw(ctx, dst, &s, ciphertext, &h, &j0, false)
|
||||
final_ghash_hw(&s, &h, &j0_enc, len(aad), len(ciphertext))
|
||||
|
||||
ok := crypto.compare_constant_time(s[:], tag) == 1
|
||||
if !ok {
|
||||
mem.zero_explicit(raw_data(dst), len(dst))
|
||||
}
|
||||
|
||||
mem.zero_explicit(&h, len(h))
|
||||
mem.zero_explicit(&j0, len(j0))
|
||||
mem.zero_explicit(&j0_enc, len(j0_enc))
|
||||
mem.zero_explicit(&s, len(s))
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
init_ghash_hw :: proc(
|
||||
ctx: ^Context_Impl_Hardware,
|
||||
h: ^[_aes.GHASH_KEY_SIZE]byte,
|
||||
j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
nonce: []byte,
|
||||
) {
|
||||
// 1. Let H = CIPH(k, 0^128)
|
||||
encrypt_block_hw(ctx, h[:], h[:])
|
||||
|
||||
// Define a block, J0, as follows:
|
||||
if l := len(nonce); l == GCM_NONCE_SIZE {
|
||||
// if len(IV) = 96, then let J0 = IV || 0^31 || 1
|
||||
copy(j0[:], nonce)
|
||||
j0[_aes.GHASH_BLOCK_SIZE - 1] = 1
|
||||
} else {
|
||||
// If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV),
|
||||
// and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64).
|
||||
hw_intel.ghash(j0[:], h[:], nonce)
|
||||
|
||||
tmp: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
endian.unchecked_put_u64be(tmp[8:], u64(l) * 8)
|
||||
hw_intel.ghash(j0[:], h[:], tmp[:])
|
||||
}
|
||||
|
||||
// ECB encrypt j0, so that we can just XOR with the tag.
|
||||
encrypt_block_hw(ctx, j0_enc[:], j0[:])
|
||||
}
|
||||
|
||||
@(private = "file", enable_target_feature = "sse2")
|
||||
final_ghash_hw :: proc(
|
||||
s: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
h: ^[_aes.GHASH_KEY_SIZE]byte,
|
||||
j0: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
a_len: int,
|
||||
t_len: int,
|
||||
) {
|
||||
blk: [_aes.GHASH_BLOCK_SIZE]byte
|
||||
endian.unchecked_put_u64be(blk[0:], u64(a_len) * 8)
|
||||
endian.unchecked_put_u64be(blk[8:], u64(t_len) * 8)
|
||||
|
||||
hw_intel.ghash(s[:], h[:], blk[:])
|
||||
j0_vec := intrinsics.unaligned_load((^x86.__m128i)(j0))
|
||||
s_vec := intrinsics.unaligned_load((^x86.__m128i)(s))
|
||||
s_vec = x86._mm_xor_si128(s_vec, j0_vec)
|
||||
intrinsics.unaligned_store((^x86.__m128i)(s), s_vec)
|
||||
}
|
||||
|
||||
@(private = "file", enable_target_feature = "sse2,sse4.1,aes")
|
||||
gctr_hw :: proc(
|
||||
ctx: ^Context_Impl_Hardware,
|
||||
dst: []byte,
|
||||
s: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
src: []byte,
|
||||
h: ^[_aes.GHASH_KEY_SIZE]byte,
|
||||
nonce: ^[_aes.GHASH_BLOCK_SIZE]byte,
|
||||
is_seal: bool,
|
||||
) #no_bounds_check {
|
||||
sks: [15]x86.__m128i = ---
|
||||
for i in 0 ..= ctx._num_rounds {
|
||||
sks[i] = intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i]))
|
||||
}
|
||||
|
||||
// Setup the counter block
|
||||
ctr_blk := intrinsics.unaligned_load((^x86.__m128i)(nonce))
|
||||
ctr := endian.unchecked_get_u32be(nonce[GCM_NONCE_SIZE:]) + 1
|
||||
|
||||
src, dst := src, dst
|
||||
|
||||
// Note: Instead of doing GHASH and CTR separately, it is more
|
||||
// performant to interleave (stitch) the two operations together.
|
||||
// This results in an unreadable mess, so we opt for simplicity
|
||||
// as performance is adequate.
|
||||
|
||||
blks: [CTR_STRIDE_HW]x86.__m128i = ---
|
||||
nr_blocks := len(src) / BLOCK_SIZE
|
||||
for nr_blocks >= CTR_STRIDE_HW {
|
||||
if !is_seal {
|
||||
hw_intel.ghash(s[:], h[:], src[:CTR_STRIDE_BYTES_HW])
|
||||
}
|
||||
|
||||
#unroll for i in 0 ..< CTR_STRIDE_HW {
|
||||
blks[i], ctr = hw_inc_ctr32(&ctr_blk, ctr)
|
||||
}
|
||||
|
||||
#unroll for i in 0 ..< CTR_STRIDE_HW {
|
||||
blks[i] = x86._mm_xor_si128(blks[i], sks[0])
|
||||
}
|
||||
#unroll for i in 1 ..= 9 {
|
||||
#unroll for j in 0 ..< CTR_STRIDE_HW {
|
||||
blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
|
||||
}
|
||||
}
|
||||
switch ctx._num_rounds {
|
||||
case _aes.ROUNDS_128:
|
||||
#unroll for i in 0 ..< CTR_STRIDE_HW {
|
||||
blks[i] = x86._mm_aesenclast_si128(blks[i], sks[10])
|
||||
}
|
||||
case _aes.ROUNDS_192:
|
||||
#unroll for i in 10 ..= 11 {
|
||||
#unroll for j in 0 ..< CTR_STRIDE_HW {
|
||||
blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
|
||||
}
|
||||
}
|
||||
#unroll for i in 0 ..< CTR_STRIDE_HW {
|
||||
blks[i] = x86._mm_aesenclast_si128(blks[i], sks[12])
|
||||
}
|
||||
case _aes.ROUNDS_256:
|
||||
#unroll for i in 10 ..= 13 {
|
||||
#unroll for j in 0 ..< CTR_STRIDE_HW {
|
||||
blks[j] = x86._mm_aesenc_si128(blks[j], sks[i])
|
||||
}
|
||||
}
|
||||
#unroll for i in 0 ..< CTR_STRIDE_HW {
|
||||
blks[i] = x86._mm_aesenclast_si128(blks[i], sks[14])
|
||||
}
|
||||
}
|
||||
|
||||
xor_blocks_hw(dst, src, blks[:])
|
||||
|
||||
if is_seal {
|
||||
hw_intel.ghash(s[:], h[:], dst[:CTR_STRIDE_BYTES_HW])
|
||||
}
|
||||
|
||||
src = src[CTR_STRIDE_BYTES_HW:]
|
||||
dst = dst[CTR_STRIDE_BYTES_HW:]
|
||||
nr_blocks -= CTR_STRIDE_HW
|
||||
}
|
||||
|
||||
// Handle the remainder.
|
||||
for n := len(src); n > 0; {
|
||||
l := min(n, BLOCK_SIZE)
|
||||
if !is_seal {
|
||||
hw_intel.ghash(s[:], h[:], src[:l])
|
||||
}
|
||||
|
||||
blks[0], ctr = hw_inc_ctr32(&ctr_blk, ctr)
|
||||
|
||||
blks[0] = x86._mm_xor_si128(blks[0], sks[0])
|
||||
#unroll for i in 1 ..= 9 {
|
||||
blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
|
||||
}
|
||||
switch ctx._num_rounds {
|
||||
case _aes.ROUNDS_128:
|
||||
blks[0] = x86._mm_aesenclast_si128(blks[0], sks[10])
|
||||
case _aes.ROUNDS_192:
|
||||
#unroll for i in 10 ..= 11 {
|
||||
blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
|
||||
}
|
||||
blks[0] = x86._mm_aesenclast_si128(blks[0], sks[12])
|
||||
case _aes.ROUNDS_256:
|
||||
#unroll for i in 10 ..= 13 {
|
||||
blks[0] = x86._mm_aesenc_si128(blks[0], sks[i])
|
||||
}
|
||||
blks[0] = x86._mm_aesenclast_si128(blks[0], sks[14])
|
||||
}
|
||||
|
||||
if l == BLOCK_SIZE {
|
||||
xor_blocks_hw(dst, src, blks[:1])
|
||||
} else {
|
||||
blk: [BLOCK_SIZE]byte
|
||||
copy(blk[:], src)
|
||||
xor_blocks_hw(blk[:], blk[:], blks[:1])
|
||||
copy(dst, blk[:l])
|
||||
}
|
||||
if is_seal {
|
||||
hw_intel.ghash(s[:], h[:], dst[:l])
|
||||
}
|
||||
|
||||
dst = dst[l:]
|
||||
src = src[l:]
|
||||
n -= l
|
||||
}
|
||||
|
||||
mem.zero_explicit(&blks, size_of(blks))
|
||||
mem.zero_explicit(&sks, size_of(sks))
|
||||
}
|
||||
|
||||
// BUG: Sticking this in gctr_hw (like the other implementations) crashes
|
||||
// the compiler.
|
||||
//
|
||||
// src/check_expr.cpp(7892): Assertion Failure: `c->curr_proc_decl->entity`
|
||||
@(private = "file", enable_target_feature = "sse4.1")
|
||||
hw_inc_ctr32 :: #force_inline proc "contextless" (src: ^x86.__m128i, ctr: u32) -> (x86.__m128i, u32) {
|
||||
ret := x86._mm_insert_epi32(src^, i32(intrinsics.byte_swap(ctr)), 3)
|
||||
return ret, ctr + 1
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package aes
|
||||
|
||||
import "core:crypto/_aes/ct64"
|
||||
import "core:mem"
|
||||
import "core:reflect"
|
||||
|
||||
@(private)
|
||||
Context_Impl :: union {
|
||||
ct64.Context,
|
||||
Context_Impl_Hardware,
|
||||
}
|
||||
|
||||
// Implementation is an AES implementation. Most callers will not need
|
||||
// to use this as the package will automatically select the most performant
|
||||
// implementation available (See `is_hardware_accelerated()`).
|
||||
Implementation :: enum {
|
||||
Portable,
|
||||
Hardware,
|
||||
}
|
||||
|
||||
@(private)
|
||||
init_impl :: proc(ctx: ^Context_Impl, key: []byte, impl: Implementation) {
|
||||
impl := impl
|
||||
if !is_hardware_accelerated() {
|
||||
impl = .Portable
|
||||
}
|
||||
|
||||
switch impl {
|
||||
case .Portable:
|
||||
reflect.set_union_variant_typeid(ctx^, typeid_of(ct64.Context))
|
||||
ct64.init(&ctx.(ct64.Context), key)
|
||||
case .Hardware:
|
||||
reflect.set_union_variant_typeid(ctx^, typeid_of(Context_Impl_Hardware))
|
||||
init_impl_hw(&ctx.(Context_Impl_Hardware), key)
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
reset_impl :: proc "contextless" (ctx: ^Context_Impl) {
|
||||
mem.zero_explicit(ctx, size_of(Context_Impl))
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//+build !amd64
|
||||
package aes
|
||||
|
||||
@(private = "file")
|
||||
ERR_HW_NOT_SUPPORTED :: "crypto/aes: hardware implementation unsupported"
|
||||
|
||||
// is_hardware_accelerated returns true iff hardware accelerated AES
|
||||
// is supported.
|
||||
is_hardware_accelerated :: proc "contextless" () -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@(private)
|
||||
Context_Impl_Hardware :: struct {}
|
||||
|
||||
@(private)
|
||||
init_impl_hw :: proc(ctx: ^Context_Impl_Hardware, key: []byte) {
|
||||
panic(ERR_HW_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
@(private)
|
||||
encrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) {
|
||||
panic(ERR_HW_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
@(private)
|
||||
decrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) {
|
||||
panic(ERR_HW_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
@(private)
|
||||
ctr_blocks_hw :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) {
|
||||
panic(ERR_HW_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
@(private)
|
||||
gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) {
|
||||
panic(ERR_HW_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
@(private)
|
||||
gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool {
|
||||
panic(ERR_HW_NOT_SUPPORTED)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//+build amd64
|
||||
package aes
|
||||
|
||||
import "core:crypto/_aes/hw_intel"
|
||||
|
||||
// is_hardware_accelerated returns true iff hardware accelerated AES
|
||||
// is supported.
|
||||
is_hardware_accelerated :: proc "contextless" () -> bool {
|
||||
return hw_intel.is_supported()
|
||||
}
|
||||
|
||||
@(private)
|
||||
Context_Impl_Hardware :: hw_intel.Context
|
||||
|
||||
@(private, enable_target_feature = "sse2,aes")
|
||||
init_impl_hw :: proc(ctx: ^Context_Impl_Hardware, key: []byte) {
|
||||
hw_intel.init(ctx, key)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ See:
|
||||
*/
|
||||
package chacha20
|
||||
|
||||
import "core:bytes"
|
||||
import "core:encoding/endian"
|
||||
import "core:math/bits"
|
||||
import "core:mem"
|
||||
@@ -121,14 +122,15 @@ seek :: proc(ctx: ^Context, block_nr: u64) {
|
||||
xor_bytes :: proc(ctx: ^Context, dst, src: []byte) {
|
||||
assert(ctx._is_initialized)
|
||||
|
||||
// TODO: Enforcing that dst and src alias exactly or not at all
|
||||
// is a good idea, though odd aliasing should be extremely uncommon.
|
||||
|
||||
src, dst := src, dst
|
||||
if dst_len := len(dst); dst_len < len(src) {
|
||||
src = src[:dst_len]
|
||||
}
|
||||
|
||||
if bytes.alias_inexactly(dst, src) {
|
||||
panic("crypto/chacha20: dst and src alias inexactly")
|
||||
}
|
||||
|
||||
for remaining := len(src); remaining > 0; {
|
||||
// Process multiple blocks at once
|
||||
if ctx._off == _BLOCK_SIZE {
|
||||
|
||||
+27
-4
@@ -4,6 +4,7 @@ helper routines.
|
||||
*/
|
||||
package crypto
|
||||
|
||||
import "base:runtime"
|
||||
import "core:mem"
|
||||
|
||||
// compare_constant_time returns 1 iff a and b are equal, 0 otherwise.
|
||||
@@ -49,6 +50,9 @@ compare_byte_ptrs_constant_time :: proc "contextless" (a, b: ^byte, n: int) -> i
|
||||
// the system entropy source. This routine will block if the system entropy
|
||||
// source is not ready yet. All system entropy source failures are treated
|
||||
// as catastrophic, resulting in a panic.
|
||||
//
|
||||
// Support for the system entropy source can be checked with the
|
||||
// `HAS_RAND_BYTES` boolean constant.
|
||||
rand_bytes :: proc (dst: []byte) {
|
||||
// zero-fill the buffer first
|
||||
mem.zero_explicit(raw_data(dst), len(dst))
|
||||
@@ -56,8 +60,27 @@ rand_bytes :: proc (dst: []byte) {
|
||||
_rand_bytes(dst)
|
||||
}
|
||||
|
||||
// has_rand_bytes returns true iff the target has support for accessing the
|
||||
// system entropty source.
|
||||
has_rand_bytes :: proc () -> bool {
|
||||
return _has_rand_bytes()
|
||||
// random_generator returns a `runtime.Random_Generator` backed by the
|
||||
// system entropy source.
|
||||
//
|
||||
// Support for the system entropy source can be checked with the
|
||||
// `HAS_RAND_BYTES` boolean constant.
|
||||
random_generator :: proc() -> runtime.Random_Generator {
|
||||
return {
|
||||
procedure = proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) {
|
||||
switch mode {
|
||||
case .Read:
|
||||
rand_bytes(p)
|
||||
case .Reset:
|
||||
// do nothing
|
||||
case .Query_Info:
|
||||
if len(p) != size_of(runtime.Random_Generator_Query_Info) {
|
||||
return
|
||||
}
|
||||
info := (^runtime.Random_Generator_Query_Info)(raw_data(p))
|
||||
info^ += {.Uniform, .Cryptographic, .External_Entropy}
|
||||
}
|
||||
},
|
||||
data = nil,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ init_256 :: proc(ctx: ^Context, key, domain_sep: []byte) {
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
assert(ctx.is_initialized)
|
||||
|
||||
shake.write(transmute(^shake.Context)(ctx), data)
|
||||
shake.write((^shake.Context)(ctx), data)
|
||||
}
|
||||
|
||||
// final finalizes the Context, writes the tag to dst, and calls reset
|
||||
@@ -75,7 +75,7 @@ final :: proc(ctx: ^Context, dst: []byte) {
|
||||
panic("crypto/kmac: invalid KMAC tag_size, too short")
|
||||
}
|
||||
|
||||
_sha3.final_cshake(transmute(^_sha3.Context)(ctx), dst)
|
||||
_sha3.final_cshake((^_sha3.Context)(ctx), dst)
|
||||
}
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
@@ -84,7 +84,7 @@ clone :: proc(ctx, other: ^Context) {
|
||||
return
|
||||
}
|
||||
|
||||
shake.clone(transmute(^shake.Context)(ctx), transmute(^shake.Context)(other))
|
||||
shake.clone((^shake.Context)(ctx), (^shake.Context)(other))
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
@@ -94,7 +94,7 @@ reset :: proc(ctx: ^Context) {
|
||||
return
|
||||
}
|
||||
|
||||
shake.reset(transmute(^shake.Context)(ctx))
|
||||
shake.reset((^shake.Context)(ctx))
|
||||
}
|
||||
|
||||
@(private)
|
||||
@@ -107,7 +107,7 @@ _init_kmac :: proc(ctx: ^Context, key, s: []byte, sec_strength: int) {
|
||||
panic("crypto/kmac: invalid KMAC key, too short")
|
||||
}
|
||||
|
||||
ctx_ := transmute(^_sha3.Context)(ctx)
|
||||
ctx_ := (^_sha3.Context)(ctx)
|
||||
_sha3.init_cshake(ctx_, N_KMAC, s, sec_strength)
|
||||
_sha3.bytepad(ctx_, [][]byte{key}, _sha3.rate_cshake(sec_strength))
|
||||
}
|
||||
|
||||
@@ -66,12 +66,12 @@ init_512 :: proc(ctx: ^Context) {
|
||||
@(private)
|
||||
_init :: proc(ctx: ^Context) {
|
||||
ctx.dsbyte = _sha3.DS_KECCAK
|
||||
_sha3.init(transmute(^_sha3.Context)(ctx))
|
||||
_sha3.init((^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
_sha3.update(transmute(^_sha3.Context)(ctx), data)
|
||||
_sha3.update((^_sha3.Context)(ctx), data)
|
||||
}
|
||||
|
||||
// final finalizes the Context, writes the digest to hash, and calls
|
||||
@@ -80,16 +80,16 @@ update :: proc(ctx: ^Context, data: []byte) {
|
||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||
// which is useful for for calculating rolling digests.
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
_sha3.final(transmute(^_sha3.Context)(ctx), hash, finalize_clone)
|
||||
_sha3.final((^_sha3.Context)(ctx), hash, finalize_clone)
|
||||
}
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
_sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other))
|
||||
_sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other))
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
_sha3.reset(transmute(^_sha3.Context)(ctx))
|
||||
_sha3.reset((^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
//+build freebsd, openbsd
|
||||
//+build freebsd, openbsd, netbsd
|
||||
package crypto
|
||||
|
||||
foreign import libc "system:c"
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
foreign libc {
|
||||
arc4random_buf :: proc(buf: [^]byte, nbytes: uint) ---
|
||||
}
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
arc4random_buf(raw_data(dst), len(dst))
|
||||
}
|
||||
|
||||
_has_rand_bytes :: proc() -> bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -5,14 +5,13 @@ import "core:fmt"
|
||||
import CF "core:sys/darwin/CoreFoundation"
|
||||
import Sec "core:sys/darwin/Security"
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
err := Sec.RandomCopyBytes(count=len(dst), bytes=raw_data(dst))
|
||||
if err != .Success {
|
||||
msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err))
|
||||
panic(fmt.tprintf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg))
|
||||
msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err))
|
||||
fmt.panicf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg)
|
||||
}
|
||||
}
|
||||
|
||||
_has_rand_bytes :: proc() -> bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
//+build !windows
|
||||
//+build !openbsd
|
||||
//+build !freebsd
|
||||
//+build !netbsd
|
||||
//+build !darwin
|
||||
//+build !js
|
||||
package crypto
|
||||
|
||||
HAS_RAND_BYTES :: false
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
unimplemented("crypto: rand_bytes not supported on this OS")
|
||||
}
|
||||
|
||||
_has_rand_bytes :: proc() -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -6,8 +6,12 @@ foreign odin_env {
|
||||
env_rand_bytes :: proc "contextless" (buf: []byte) ---
|
||||
}
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
@(private)
|
||||
_MAX_PER_CALL_BYTES :: 65536 // 64kiB
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
dst := dst
|
||||
|
||||
@@ -18,7 +22,3 @@ _rand_bytes :: proc(dst: []byte) {
|
||||
dst = dst[to_read:]
|
||||
}
|
||||
}
|
||||
|
||||
_has_rand_bytes :: proc() -> bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -4,8 +4,12 @@ import "core:fmt"
|
||||
|
||||
import "core:sys/linux"
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
@(private)
|
||||
_MAX_PER_CALL_BYTES :: 33554431 // 2^25 - 1
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc (dst: []byte) {
|
||||
dst := dst
|
||||
l := len(dst)
|
||||
@@ -28,13 +32,9 @@ _rand_bytes :: proc (dst: []byte) {
|
||||
// All other failures are things that should NEVER happen
|
||||
// unless the kernel interface changes (ie: the Linux
|
||||
// developers break userland).
|
||||
panic(fmt.tprintf("crypto: getrandom failed: %v", errno))
|
||||
fmt.panicf("crypto: getrandom failed: %v", errno)
|
||||
}
|
||||
l -= n_read
|
||||
dst = dst[n_read:]
|
||||
}
|
||||
}
|
||||
|
||||
_has_rand_bytes :: proc() -> bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -4,24 +4,23 @@ import win32 "core:sys/windows"
|
||||
import "core:os"
|
||||
import "core:fmt"
|
||||
|
||||
HAS_RAND_BYTES :: true
|
||||
|
||||
@(private)
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
ret := (os.Errno)(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG))
|
||||
if ret != os.ERROR_NONE {
|
||||
switch ret {
|
||||
case os.ERROR_INVALID_HANDLE:
|
||||
// The handle to the first parameter is invalid.
|
||||
// This should not happen here, since we explicitly pass nil to it
|
||||
panic("crypto: BCryptGenRandom Invalid handle for hAlgorithm")
|
||||
case os.ERROR_INVALID_PARAMETER:
|
||||
// One of the parameters was invalid
|
||||
panic("crypto: BCryptGenRandom Invalid parameter")
|
||||
case:
|
||||
// Unknown error
|
||||
panic(fmt.tprintf("crypto: BCryptGenRandom failed: %d\n", ret))
|
||||
case os.ERROR_INVALID_HANDLE:
|
||||
// The handle to the first parameter is invalid.
|
||||
// This should not happen here, since we explicitly pass nil to it
|
||||
panic("crypto: BCryptGenRandom Invalid handle for hAlgorithm")
|
||||
case os.ERROR_INVALID_PARAMETER:
|
||||
// One of the parameters was invalid
|
||||
panic("crypto: BCryptGenRandom Invalid parameter")
|
||||
case:
|
||||
// Unknown error
|
||||
fmt.panicf("crypto: BCryptGenRandom failed: %d\n", ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_has_rand_bytes :: proc() -> bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
b_ := transmute(^[32]byte)(raw_data(b))
|
||||
b_ := (^[32]byte)(raw_data(b))
|
||||
|
||||
s: field.Tight_Field_Element = ---
|
||||
defer field.fe_clear(&s)
|
||||
@@ -297,7 +297,7 @@ ge_bytes :: proc(ge: ^Group_Element, dst: []byte) {
|
||||
// 2. Return the 32-byte little-endian encoding of s. More
|
||||
// specifically, this is the encoding of the canonical
|
||||
// representation of s as an integer between 0 and p-1, inclusive.
|
||||
dst_ := transmute(^[32]byte)(raw_data(dst))
|
||||
dst_ := (^[32]byte)(raw_data(dst))
|
||||
field.fe_to_bytes(dst_, &tmp)
|
||||
|
||||
field.fe_clear_vec([]^field.Tight_Field_Element{&u1, &u2, &tmp, &z_inv, &ix0, &iy0, &x, &y})
|
||||
@@ -417,7 +417,7 @@ ge_is_identity :: proc(ge: ^Group_Element) -> int {
|
||||
|
||||
@(private)
|
||||
ge_map :: proc "contextless" (ge: ^Group_Element, b: []byte) {
|
||||
b_ := transmute(^[32]byte)(raw_data(b))
|
||||
b_ := (^[32]byte)(raw_data(b))
|
||||
|
||||
// The MAP function is defined on 32-byte strings as:
|
||||
//
|
||||
|
||||
@@ -46,7 +46,7 @@ sc_set_bytes_wide :: proc(sc: ^Scalar, b: []byte) {
|
||||
panic("crypto/ristretto255: invalid wide input size")
|
||||
}
|
||||
|
||||
b_ := transmute(^[WIDE_SCALAR_SIZE]byte)(raw_data(b))
|
||||
b_ := (^[WIDE_SCALAR_SIZE]byte)(raw_data(b))
|
||||
grp.sc_set_bytes_wide(sc, b_)
|
||||
}
|
||||
|
||||
|
||||
@@ -68,12 +68,12 @@ init_512 :: proc(ctx: ^Context) {
|
||||
@(private)
|
||||
_init :: proc(ctx: ^Context) {
|
||||
ctx.dsbyte = _sha3.DS_SHA3
|
||||
_sha3.init(transmute(^_sha3.Context)(ctx))
|
||||
_sha3.init((^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
_sha3.update(transmute(^_sha3.Context)(ctx), data)
|
||||
_sha3.update((^_sha3.Context)(ctx), data)
|
||||
}
|
||||
|
||||
// final finalizes the Context, writes the digest to hash, and calls
|
||||
@@ -82,16 +82,16 @@ update :: proc(ctx: ^Context, data: []byte) {
|
||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||
// which is useful for for calculating rolling digests.
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
_sha3.final(transmute(^_sha3.Context)(ctx), hash, finalize_clone)
|
||||
_sha3.final((^_sha3.Context)(ctx), hash, finalize_clone)
|
||||
}
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
_sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other))
|
||||
_sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other))
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
_sha3.reset(transmute(^_sha3.Context)(ctx))
|
||||
_sha3.reset((^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
@@ -24,35 +24,35 @@ Context :: distinct _sha3.Context
|
||||
|
||||
// init_128 initializes a Context for SHAKE128.
|
||||
init_128 :: proc(ctx: ^Context) {
|
||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, nil, 128)
|
||||
_sha3.init_cshake((^_sha3.Context)(ctx), nil, nil, 128)
|
||||
}
|
||||
|
||||
// init_256 initializes a Context for SHAKE256.
|
||||
init_256 :: proc(ctx: ^Context) {
|
||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, nil, 256)
|
||||
_sha3.init_cshake((^_sha3.Context)(ctx), nil, nil, 256)
|
||||
}
|
||||
|
||||
// init_cshake_128 initializes a Context for cSHAKE128.
|
||||
init_cshake_128 :: proc(ctx: ^Context, domain_sep: []byte) {
|
||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, domain_sep, 128)
|
||||
_sha3.init_cshake((^_sha3.Context)(ctx), nil, domain_sep, 128)
|
||||
}
|
||||
|
||||
// init_cshake_256 initializes a Context for cSHAKE256.
|
||||
init_cshake_256 :: proc(ctx: ^Context, domain_sep: []byte) {
|
||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, domain_sep, 256)
|
||||
_sha3.init_cshake((^_sha3.Context)(ctx), nil, domain_sep, 256)
|
||||
}
|
||||
|
||||
// write writes more data into the SHAKE instance. This MUST not be called
|
||||
// after any reads have been done, and attempts to do so will panic.
|
||||
write :: proc(ctx: ^Context, data: []byte) {
|
||||
_sha3.update(transmute(^_sha3.Context)(ctx), data)
|
||||
_sha3.update((^_sha3.Context)(ctx), data)
|
||||
}
|
||||
|
||||
// read reads output from the SHAKE instance. There is no practical upper
|
||||
// limit to the amount of data that can be read from SHAKE. After read has
|
||||
// been called one or more times, further calls to write will panic.
|
||||
read :: proc(ctx: ^Context, dst: []byte) {
|
||||
ctx_ := transmute(^_sha3.Context)(ctx)
|
||||
ctx_ := (^_sha3.Context)(ctx)
|
||||
if !ctx.is_finalized {
|
||||
_sha3.shake_xof(ctx_)
|
||||
}
|
||||
@@ -62,11 +62,11 @@ read :: proc(ctx: ^Context, dst: []byte) {
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
_sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other))
|
||||
_sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other))
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
_sha3.reset(transmute(^_sha3.Context)(ctx))
|
||||
_sha3.reset((^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
@@ -13,19 +13,19 @@ Context :: distinct _sha3.Context
|
||||
|
||||
// init_128 initializes a Context for TupleHash128 or TupleHashXOF128.
|
||||
init_128 :: proc(ctx: ^Context, domain_sep: []byte) {
|
||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 128)
|
||||
_sha3.init_cshake((^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 128)
|
||||
}
|
||||
|
||||
// init_256 initializes a Context for TupleHash256 or TupleHashXOF256.
|
||||
init_256 :: proc(ctx: ^Context, domain_sep: []byte) {
|
||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 256)
|
||||
_sha3.init_cshake((^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 256)
|
||||
}
|
||||
|
||||
// write_element writes a tuple element into the TupleHash or TupleHashXOF
|
||||
// instance. This MUST not be called after any reads have been done, and
|
||||
// any attempts to do so will panic.
|
||||
write_element :: proc(ctx: ^Context, data: []byte) {
|
||||
_, _ = _sha3.encode_string(transmute(^_sha3.Context)(ctx), data)
|
||||
_, _ = _sha3.encode_string((^_sha3.Context)(ctx), data)
|
||||
}
|
||||
|
||||
// final finalizes the Context, writes the digest to hash, and calls
|
||||
@@ -34,7 +34,7 @@ write_element :: proc(ctx: ^Context, data: []byte) {
|
||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||
// which is useful for for calculating rolling digests.
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
_sha3.final_cshake(transmute(^_sha3.Context)(ctx), hash, finalize_clone)
|
||||
_sha3.final_cshake((^_sha3.Context)(ctx), hash, finalize_clone)
|
||||
}
|
||||
|
||||
// read reads output from the TupleHashXOF instance. There is no practical
|
||||
@@ -42,7 +42,7 @@ final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
// After read has been called one or more times, further calls to
|
||||
// write_element will panic.
|
||||
read :: proc(ctx: ^Context, dst: []byte) {
|
||||
ctx_ := transmute(^_sha3.Context)(ctx)
|
||||
ctx_ := (^_sha3.Context)(ctx)
|
||||
if !ctx.is_finalized {
|
||||
_sha3.encode_byte_len(ctx_, 0, false) // right_encode
|
||||
_sha3.shake_xof(ctx_)
|
||||
@@ -53,13 +53,13 @@ read :: proc(ctx: ^Context, dst: []byte) {
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
_sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other))
|
||||
_sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other))
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
_sha3.reset(transmute(^_sha3.Context)(ctx))
|
||||
_sha3.reset((^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
@(private)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
A debug stack trace library. Only works when debug symbols are enabled `-debug`.
|
||||
|
||||
Example:
|
||||
import "base:runtime"
|
||||
import "core:debug/trace"
|
||||
|
||||
import "core:fmt"
|
||||
|
||||
global_trace_ctx: trace.Context
|
||||
|
||||
debug_trace_assertion_failure_proc :: proc(prefix, message: string, loc := #caller_location) -> ! {
|
||||
runtime.print_caller_location(loc)
|
||||
runtime.print_string(" ")
|
||||
runtime.print_string(prefix)
|
||||
if len(message) > 0 {
|
||||
runtime.print_string(": ")
|
||||
runtime.print_string(message)
|
||||
}
|
||||
runtime.print_byte('\n')
|
||||
|
||||
ctx := &trace_ctx
|
||||
if !trace.in_resolve(ctx) {
|
||||
buf: [64]trace.Frame
|
||||
runtime.print_string("Debug Trace:\n")
|
||||
frames := trace.frames(ctx, 1, buf[:])
|
||||
for f, i in frames {
|
||||
fl := trace.resolve(ctx, f, context.temp_allocator)
|
||||
if fl.loc.file_path == "" && fl.loc.line == 0 {
|
||||
continue
|
||||
}
|
||||
runtime.print_caller_location(fl.loc)
|
||||
runtime.print_string(" - frame ")
|
||||
runtime.print_int(i)
|
||||
runtime.print_byte('\n')
|
||||
}
|
||||
}
|
||||
runtime.trap()
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
trace.init(&global_trace_ctx)
|
||||
defer trace.destroy(&global_trace_ctx)
|
||||
|
||||
context.assertion_failure_proc = debug_trace_assertion_failure_proc
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
*/
|
||||
package debug_trace
|
||||
@@ -0,0 +1,47 @@
|
||||
package debug_trace
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
|
||||
Frame :: distinct uintptr
|
||||
|
||||
Frame_Location :: struct {
|
||||
using loc: runtime.Source_Code_Location,
|
||||
allocator: runtime.Allocator,
|
||||
}
|
||||
|
||||
delete_frame_location :: proc(fl: Frame_Location) -> runtime.Allocator_Error {
|
||||
allocator := fl.allocator
|
||||
delete(fl.loc.procedure, allocator) or_return
|
||||
delete(fl.loc.file_path, allocator) or_return
|
||||
return nil
|
||||
}
|
||||
|
||||
Context :: struct {
|
||||
in_resolve: bool, // atomic
|
||||
impl: _Context,
|
||||
}
|
||||
|
||||
init :: proc(ctx: ^Context) -> bool {
|
||||
return _init(ctx)
|
||||
}
|
||||
|
||||
destroy :: proc(ctx: ^Context) -> bool {
|
||||
return _destroy(ctx)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
frames :: proc(ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame {
|
||||
return _frames(ctx, skip, frames_buffer)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: Frame_Location) {
|
||||
return _resolve(ctx, frame, allocator)
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
in_resolve :: proc "contextless" (ctx: ^Context) -> bool {
|
||||
return intrinsics.atomic_load(&ctx.in_resolve)
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
//+private file
|
||||
//+build linux, darwin
|
||||
package debug_trace
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
import "core:fmt"
|
||||
import "core:c"
|
||||
|
||||
// NOTE: Relies on C++23 which adds <stacktrace> and becomes ABI and that can be used
|
||||
foreign import stdcpplibbacktrace "system:stdc++_libbacktrace"
|
||||
|
||||
foreign import libdl "system:dl"
|
||||
|
||||
backtrace_state :: struct {}
|
||||
backtrace_error_callback :: proc "c" (data: rawptr, msg: cstring, errnum: c.int)
|
||||
backtrace_simple_callback :: proc "c" (data: rawptr, pc: uintptr) -> c.int
|
||||
backtrace_full_callback :: proc "c" (data: rawptr, pc: uintptr, filename: cstring, lineno: c.int, function: cstring) -> c.int
|
||||
backtrace_syminfo_callback :: proc "c" (data: rawptr, pc: uintptr, symname: cstring, symval: uintptr, symsize: uintptr)
|
||||
|
||||
@(default_calling_convention="c", link_prefix="__glibcxx_")
|
||||
foreign stdcpplibbacktrace {
|
||||
backtrace_create_state :: proc(
|
||||
filename: cstring,
|
||||
threaded: c.int,
|
||||
error_callback: backtrace_error_callback,
|
||||
data: rawptr,
|
||||
) -> ^backtrace_state ---
|
||||
backtrace_simple :: proc(
|
||||
state: ^backtrace_state,
|
||||
skip: c.int,
|
||||
callback: backtrace_simple_callback,
|
||||
error_callback: backtrace_error_callback,
|
||||
data: rawptr,
|
||||
) -> c.int ---
|
||||
backtrace_pcinfo :: proc(
|
||||
state: ^backtrace_state,
|
||||
pc: uintptr,
|
||||
callback: backtrace_full_callback,
|
||||
error_callback: backtrace_error_callback,
|
||||
data: rawptr,
|
||||
) -> c.int ---
|
||||
backtrace_syminfo :: proc(
|
||||
state: ^backtrace_state,
|
||||
addr: uintptr,
|
||||
callback: backtrace_syminfo_callback,
|
||||
error_callback: backtrace_error_callback,
|
||||
data: rawptr,
|
||||
) -> c.int ---
|
||||
|
||||
// NOTE(bill): this is technically an internal procedure, but it is exposed
|
||||
backtrace_free :: proc(
|
||||
state: ^backtrace_state,
|
||||
p: rawptr,
|
||||
size: c.size_t, // unused
|
||||
error_callback: backtrace_error_callback, // unused
|
||||
data: rawptr, // unused
|
||||
) ---
|
||||
}
|
||||
|
||||
Dl_info :: struct {
|
||||
dli_fname: cstring,
|
||||
dli_fbase: rawptr,
|
||||
dli_sname: cstring,
|
||||
dli_saddr: rawptr,
|
||||
}
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libdl {
|
||||
dladdr :: proc(addr: rawptr, info: ^Dl_info) -> c.int ---
|
||||
}
|
||||
|
||||
@(private="package")
|
||||
_Context :: struct {
|
||||
state: ^backtrace_state,
|
||||
}
|
||||
|
||||
@(private="package")
|
||||
_init :: proc(ctx: ^Context) -> (ok: bool) {
|
||||
defer if !ok { destroy(ctx) }
|
||||
|
||||
ctx.impl.state = backtrace_create_state("odin-debug-trace", 1, nil, ctx)
|
||||
return ctx.impl.state != nil
|
||||
}
|
||||
|
||||
@(private="package")
|
||||
_destroy :: proc(ctx: ^Context) -> bool {
|
||||
if ctx != nil {
|
||||
backtrace_free(ctx.impl.state, nil, 0, nil, nil)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@(private="package")
|
||||
_frames :: proc "contextless" (ctx: ^Context, skip: uint, frames_buffer: []Frame) -> (frames: []Frame) {
|
||||
Backtrace_Context :: struct {
|
||||
ctx: ^Context,
|
||||
frames: []Frame,
|
||||
frame_count: int,
|
||||
}
|
||||
|
||||
btc := &Backtrace_Context{
|
||||
ctx = ctx,
|
||||
frames = frames_buffer,
|
||||
}
|
||||
backtrace_simple(
|
||||
ctx.impl.state,
|
||||
c.int(skip + 2),
|
||||
proc "c" (user: rawptr, address: uintptr) -> c.int {
|
||||
btc := (^Backtrace_Context)(user)
|
||||
address := Frame(address)
|
||||
if address == 0 {
|
||||
return 1
|
||||
}
|
||||
if btc.frame_count == len(btc.frames) {
|
||||
return 1
|
||||
}
|
||||
btc.frames[btc.frame_count] = address
|
||||
btc.frame_count += 1
|
||||
return 0
|
||||
},
|
||||
nil,
|
||||
btc,
|
||||
)
|
||||
|
||||
if btc.frame_count > 0 {
|
||||
frames = btc.frames[:btc.frame_count]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private="package")
|
||||
_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> Frame_Location {
|
||||
intrinsics.atomic_store(&ctx.in_resolve, true)
|
||||
defer intrinsics.atomic_store(&ctx.in_resolve, false)
|
||||
|
||||
Backtrace_Context :: struct {
|
||||
rt_ctx: runtime.Context,
|
||||
allocator: runtime.Allocator,
|
||||
frame: Frame_Location,
|
||||
}
|
||||
|
||||
btc := &Backtrace_Context{
|
||||
rt_ctx = context,
|
||||
allocator = allocator,
|
||||
}
|
||||
done := backtrace_pcinfo(
|
||||
ctx.impl.state,
|
||||
uintptr(frame),
|
||||
proc "c" (data: rawptr, address: uintptr, file: cstring, line: c.int, symbol: cstring) -> c.int {
|
||||
btc := (^Backtrace_Context)(data)
|
||||
context = btc.rt_ctx
|
||||
|
||||
frame := &btc.frame
|
||||
|
||||
if file != nil {
|
||||
frame.file_path = strings.clone_from_cstring(file, btc.allocator)
|
||||
} else if info: Dl_info; dladdr(rawptr(address), &info) != 0 && info.dli_fname != "" {
|
||||
frame.file_path = strings.clone_from_cstring(info.dli_fname, btc.allocator)
|
||||
}
|
||||
if symbol != nil {
|
||||
frame.procedure = strings.clone_from_cstring(symbol, btc.allocator)
|
||||
} else if info: Dl_info; dladdr(rawptr(address), &info) != 0 && info.dli_sname != "" {
|
||||
frame.procedure = strings.clone_from_cstring(info.dli_sname, btc.allocator)
|
||||
} else {
|
||||
frame.procedure = fmt.aprintf("(procedure: 0x%x)", allocator=btc.allocator)
|
||||
}
|
||||
frame.line = i32(line)
|
||||
return 0
|
||||
},
|
||||
nil,
|
||||
btc,
|
||||
)
|
||||
if done != 0 {
|
||||
return btc.frame
|
||||
}
|
||||
|
||||
// NOTE(bill): pcinfo cannot resolve, but it might be possible to get the procedure name at least
|
||||
backtrace_syminfo(
|
||||
ctx.impl.state,
|
||||
uintptr(frame),
|
||||
proc "c" (data: rawptr, address: uintptr, symbol: cstring, _ignore0, _ignore1: uintptr) {
|
||||
if symbol != nil {
|
||||
btc := (^Backtrace_Context)(data)
|
||||
context = btc.rt_ctx
|
||||
btc.frame.procedure = strings.clone_from_cstring(symbol, btc.allocator)
|
||||
}
|
||||
},
|
||||
nil,
|
||||
btc,
|
||||
)
|
||||
|
||||
return btc.frame
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//+build !windows !linux !darwin
|
||||
package debug_trace
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
_Context :: struct {
|
||||
}
|
||||
|
||||
_init :: proc(ctx: ^Context) -> (ok: bool) {
|
||||
return true
|
||||
}
|
||||
_destroy :: proc(ctx: ^Context) -> bool {
|
||||
return true
|
||||
}
|
||||
_frames :: proc(ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame {
|
||||
return nil
|
||||
}
|
||||
_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: Frame_Location) {
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//+private
|
||||
//+build windows
|
||||
package debug_trace
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:fmt"
|
||||
|
||||
_Context :: struct {
|
||||
hProcess: win32.HANDLE,
|
||||
lock: win32.SRWLOCK,
|
||||
}
|
||||
|
||||
_init :: proc "contextless" (ctx: ^Context) -> (ok: bool) {
|
||||
defer if !ok { _destroy(ctx) }
|
||||
ctx.impl.hProcess = win32.GetCurrentProcess()
|
||||
win32.SymInitialize(ctx.impl.hProcess, nil, true) or_return
|
||||
win32.SymSetOptions(win32.SYMOPT_LOAD_LINES)
|
||||
return true
|
||||
}
|
||||
|
||||
_destroy :: proc "contextless" (ctx: ^Context) -> bool {
|
||||
if ctx != nil {
|
||||
win32.SymCleanup(ctx.impl.hProcess)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
_frames :: proc "contextless" (ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame {
|
||||
frame_count := win32.RtlCaptureStackBackTrace(u32(skip) + 2, u32(len(frames_buffer)), ([^]rawptr)(&frames_buffer[0]), nil)
|
||||
for i in 0..<frame_count {
|
||||
// NOTE: Return address is one after the call instruction so subtract a byte to
|
||||
// end up back inside the call instruction which is needed for SymFromAddr.
|
||||
frames_buffer[i] -= 1
|
||||
}
|
||||
return frames_buffer[:frame_count]
|
||||
}
|
||||
|
||||
|
||||
_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (fl: Frame_Location) {
|
||||
intrinsics.atomic_store(&ctx.in_resolve, true)
|
||||
defer intrinsics.atomic_store(&ctx.in_resolve, false)
|
||||
|
||||
// NOTE(bill): Dbghelp is not thread-safe
|
||||
win32.AcquireSRWLockExclusive(&ctx.impl.lock)
|
||||
defer win32.ReleaseSRWLockExclusive(&ctx.impl.lock)
|
||||
|
||||
data: [size_of(win32.SYMBOL_INFOW) + size_of([256]win32.WCHAR)]byte
|
||||
symbol := (^win32.SYMBOL_INFOW)(&data[0])
|
||||
symbol.SizeOfStruct = size_of(symbol)
|
||||
symbol.MaxNameLen = 255
|
||||
if win32.SymFromAddrW(ctx.impl.hProcess, win32.DWORD64(frame), &{}, symbol) {
|
||||
fl.procedure, _ = win32.wstring_to_utf8(&symbol.Name[0], -1, allocator)
|
||||
} else {
|
||||
fl.procedure = fmt.aprintf("(procedure: 0x%x)", frame, allocator=allocator)
|
||||
}
|
||||
|
||||
line: win32.IMAGEHLP_LINE64
|
||||
line.SizeOfStruct = size_of(line)
|
||||
if win32.SymGetLineFromAddrW64(ctx.impl.hProcess, win32.DWORD64(frame), &{}, &line) {
|
||||
fl.file_path, _ = win32.wstring_to_utf8(line.FileName, -1, allocator)
|
||||
fl.line = i32(line.LineNumber)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
+5
-13
@@ -16,15 +16,12 @@ Library :: distinct rawptr
|
||||
Loads a dynamic library from the filesystem. The paramater `global_symbols` makes the symbols in the loaded
|
||||
library available to resolve references in subsequently loaded libraries.
|
||||
|
||||
The paramater `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`.
|
||||
The parameter `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`.
|
||||
On `windows` this paramater is ignored.
|
||||
|
||||
The underlying behaviour is platform specific.
|
||||
On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlopen`.
|
||||
On `windows` refer to `LoadLibraryW`.
|
||||
|
||||
**Implicit Allocators**
|
||||
`context.temp_allocator`
|
||||
On `windows` refer to `LoadLibraryW`. Also temporarily needs an allocator to convert a string.
|
||||
|
||||
Example:
|
||||
import "core:dynlib"
|
||||
@@ -79,10 +76,7 @@ Loads the address of a procedure/variable from a dynamic library.
|
||||
|
||||
The underlying behaviour is platform specific.
|
||||
On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlsym`.
|
||||
On `windows` refer to `GetProcAddress`.
|
||||
|
||||
**Implicit Allocators**
|
||||
`context.temp_allocator`
|
||||
On `windows` refer to `GetProcAddress`. Also temporarily needs an allocator to convert a string.
|
||||
|
||||
Example:
|
||||
import "core:dynlib"
|
||||
@@ -177,9 +171,7 @@ initialize_symbols :: proc(
|
||||
return count, count > 0
|
||||
}
|
||||
|
||||
/*
|
||||
Returns an error message for the last failed procedure call.
|
||||
*/
|
||||
// Returns an error message for the last failed procedure call.
|
||||
last_error :: proc() -> string {
|
||||
return _last_error()
|
||||
}
|
||||
}
|
||||
@@ -16,4 +16,4 @@ _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found
|
||||
|
||||
_last_error :: proc() -> string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//+build linux, darwin, freebsd, openbsd
|
||||
//+build linux, darwin, freebsd, openbsd, netbsd
|
||||
//+private
|
||||
package dynlib
|
||||
|
||||
@@ -26,4 +26,4 @@ _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found
|
||||
_last_error :: proc() -> string {
|
||||
err := os.dlerror()
|
||||
return "unknown" if err == "" else err
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,12 @@ package dynlib
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:strings"
|
||||
import "base:runtime"
|
||||
import "core:reflect"
|
||||
|
||||
_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||
_load_library :: proc(path: string, global_symbols := false, allocator := context.temp_allocator) -> (Library, bool) {
|
||||
// NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wide_path := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
wide_path := win32.utf8_to_wstring(path, allocator)
|
||||
defer free(wide_path, allocator)
|
||||
handle := cast(Library)win32.LoadLibraryW(wide_path)
|
||||
return handle, handle != nil
|
||||
}
|
||||
@@ -21,9 +19,9 @@ _unload_library :: proc(library: Library) -> bool {
|
||||
return bool(ok)
|
||||
}
|
||||
|
||||
_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
c_str := strings.clone_to_cstring(symbol, context.temp_allocator)
|
||||
_symbol_address :: proc(library: Library, symbol: string, allocator := context.temp_allocator) -> (ptr: rawptr, found: bool) {
|
||||
c_str := strings.clone_to_cstring(symbol, allocator)
|
||||
defer delete(c_str, allocator)
|
||||
ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str)
|
||||
found = ptr != nil
|
||||
return
|
||||
@@ -33,4 +31,4 @@ _last_error :: proc() -> string {
|
||||
err := win32.System_Error(win32.GetLastError())
|
||||
err_msg := reflect.enum_string(err)
|
||||
return "unknown" if err_msg == "" else err_msg
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package ansi
|
||||
|
||||
BEL :: "\a" // Bell
|
||||
BS :: "\b" // Backspace
|
||||
ESC :: "\e" // Escape
|
||||
|
||||
// Fe Escape sequences
|
||||
|
||||
CSI :: ESC + "[" // Control Sequence Introducer
|
||||
OSC :: ESC + "]" // Operating System Command
|
||||
ST :: ESC + "\\" // String Terminator
|
||||
|
||||
// CSI sequences
|
||||
|
||||
CUU :: "A" // Cursor Up
|
||||
CUD :: "B" // Cursor Down
|
||||
CUF :: "C" // Cursor Forward
|
||||
CUB :: "D" // Cursor Back
|
||||
CNL :: "E" // Cursor Next Line
|
||||
CPL :: "F" // Cursor Previous Line
|
||||
CHA :: "G" // Cursor Horizontal Absolute
|
||||
CUP :: "H" // Cursor Position
|
||||
ED :: "J" // Erase in Display
|
||||
EL :: "K" // Erase in Line
|
||||
SU :: "S" // Scroll Up
|
||||
SD :: "T" // Scroll Down
|
||||
HVP :: "f" // Horizontal Vertical Position
|
||||
SGR :: "m" // Select Graphic Rendition
|
||||
AUX_ON :: "5i" // AUX Port On
|
||||
AUX_OFF :: "4i" // AUX Port Off
|
||||
DSR :: "6n" // Device Status Report
|
||||
|
||||
// CSI: private sequences
|
||||
|
||||
SCP :: "s" // Save Current Cursor Position
|
||||
RCP :: "u" // Restore Saved Cursor Position
|
||||
DECAWM_ON :: "?7h" // Auto Wrap Mode (Enabled)
|
||||
DECAWM_OFF :: "?7l" // Auto Wrap Mode (Disabled)
|
||||
DECTCEM_SHOW :: "?25h" // Text Cursor Enable Mode (Visible)
|
||||
DECTCEM_HIDE :: "?25l" // Text Cursor Enable Mode (Invisible)
|
||||
|
||||
// SGR sequences
|
||||
|
||||
RESET :: "0"
|
||||
BOLD :: "1"
|
||||
FAINT :: "2"
|
||||
ITALIC :: "3" // Not widely supported.
|
||||
UNDERLINE :: "4"
|
||||
BLINK_SLOW :: "5"
|
||||
BLINK_RAPID :: "6" // Not widely supported.
|
||||
INVERT :: "7" // Also known as reverse video.
|
||||
HIDE :: "8" // Not widely supported.
|
||||
STRIKE :: "9"
|
||||
FONT_PRIMARY :: "10"
|
||||
FONT_ALT1 :: "11"
|
||||
FONT_ALT2 :: "12"
|
||||
FONT_ALT3 :: "13"
|
||||
FONT_ALT4 :: "14"
|
||||
FONT_ALT5 :: "15"
|
||||
FONT_ALT6 :: "16"
|
||||
FONT_ALT7 :: "17"
|
||||
FONT_ALT8 :: "18"
|
||||
FONT_ALT9 :: "19"
|
||||
FONT_FRAKTUR :: "20" // Rarely supported.
|
||||
UNDERLINE_DOUBLE :: "21" // May be interpreted as "disable bold."
|
||||
NO_BOLD_FAINT :: "22"
|
||||
NO_ITALIC_BLACKLETTER :: "23"
|
||||
NO_UNDERLINE :: "24"
|
||||
NO_BLINK :: "25"
|
||||
PROPORTIONAL_SPACING :: "26"
|
||||
NO_REVERSE :: "27"
|
||||
NO_HIDE :: "28"
|
||||
NO_STRIKE :: "29"
|
||||
|
||||
FG_BLACK :: "30"
|
||||
FG_RED :: "31"
|
||||
FG_GREEN :: "32"
|
||||
FG_YELLOW :: "33"
|
||||
FG_BLUE :: "34"
|
||||
FG_MAGENTA :: "35"
|
||||
FG_CYAN :: "36"
|
||||
FG_WHITE :: "37"
|
||||
FG_COLOR :: "38"
|
||||
FG_COLOR_8_BIT :: "38;5" // Followed by ";n" where n is in 0..=255
|
||||
FG_COLOR_24_BIT :: "38;2" // Followed by ";r;g;b" where r,g,b are in 0..=255
|
||||
FG_DEFAULT :: "39"
|
||||
|
||||
BG_BLACK :: "40"
|
||||
BG_RED :: "41"
|
||||
BG_GREEN :: "42"
|
||||
BG_YELLOW :: "43"
|
||||
BG_BLUE :: "44"
|
||||
BG_MAGENTA :: "45"
|
||||
BG_CYAN :: "46"
|
||||
BG_WHITE :: "47"
|
||||
BG_COLOR :: "48"
|
||||
BG_COLOR_8_BIT :: "48;5" // Followed by ";n" where n is in 0..=255
|
||||
BG_COLOR_24_BIT :: "48;2" // Followed by ";r;g;b" where r,g,b are in 0..=255
|
||||
BG_DEFAULT :: "49"
|
||||
|
||||
NO_PROPORTIONAL_SPACING :: "50"
|
||||
FRAMED :: "51"
|
||||
ENCIRCLED :: "52"
|
||||
OVERLINED :: "53"
|
||||
NO_FRAME_ENCIRCLE :: "54"
|
||||
NO_OVERLINE :: "55"
|
||||
|
||||
// SGR: non-standard bright colors
|
||||
|
||||
FG_BRIGHT_BLACK :: "90" // Also known as grey.
|
||||
FG_BRIGHT_RED :: "91"
|
||||
FG_BRIGHT_GREEN :: "92"
|
||||
FG_BRIGHT_YELLOW :: "93"
|
||||
FG_BRIGHT_BLUE :: "94"
|
||||
FG_BRIGHT_MAGENTA :: "95"
|
||||
FG_BRIGHT_CYAN :: "96"
|
||||
FG_BRIGHT_WHITE :: "97"
|
||||
|
||||
BG_BRIGHT_BLACK :: "100" // Also known as grey.
|
||||
BG_BRIGHT_RED :: "101"
|
||||
BG_BRIGHT_GREEN :: "102"
|
||||
BG_BRIGHT_YELLOW :: "103"
|
||||
BG_BRIGHT_BLUE :: "104"
|
||||
BG_BRIGHT_MAGENTA :: "105"
|
||||
BG_BRIGHT_CYAN :: "106"
|
||||
BG_BRIGHT_WHITE :: "107"
|
||||
|
||||
// Fp Escape sequences
|
||||
|
||||
DECSC :: ESC + "7" // DEC Save Cursor
|
||||
DECRC :: ESC + "8" // DEC Restore Cursor
|
||||
|
||||
// OSC sequences
|
||||
|
||||
WINDOW_TITLE :: "2" // Followed by ";<text>" ST.
|
||||
HYPERLINK :: "8" // Followed by ";[params];<URI>" ST. Closed by OSC HYPERLINK ";;" ST.
|
||||
CLIPBOARD :: "52" // Followed by ";c;<Base64-encoded string>" ST.
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
package ansi implements constant references to many widely-supported ANSI
|
||||
escape codes, primarily used in terminal emulators for enhanced graphics, such
|
||||
as colors, text styling, and animated displays.
|
||||
|
||||
For example, you can print out a line of cyan text like this:
|
||||
fmt.println(ansi.CSI + ansi.FG_CYAN + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR)
|
||||
|
||||
Multiple SGR (Select Graphic Rendition) codes can be joined by semicolons:
|
||||
fmt.println(ansi.CSI + ansi.BOLD + ";" + ansi.FG_BLUE + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR)
|
||||
|
||||
If your terminal supports 24-bit true color mode, you can also do this:
|
||||
fmt.println(ansi.CSI + ansi.FG_COLOR_24_BIT + ";0;255;255" + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR)
|
||||
|
||||
For more information, see:
|
||||
1. https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
2. https://www.vt100.net/docs/vt102-ug/chapter5.html
|
||||
3. https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
*/
|
||||
package ansi
|
||||
+116
-116
@@ -8,141 +8,141 @@ package encoding_base32
|
||||
// truncate it from the encoded output.
|
||||
|
||||
ENC_TABLE := [32]byte {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', '2', '3', '4', '5', '6', '7',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', '2', '3', '4', '5', '6', '7',
|
||||
}
|
||||
|
||||
PADDING :: '='
|
||||
|
||||
DEC_TABLE := [?]u8 {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 26, 27, 28, 29, 30, 31, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 26, 27, 28, 29, 30, 31, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
}
|
||||
|
||||
encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> string {
|
||||
out_length := (len(data) + 4) / 5 * 8
|
||||
out := make([]byte, out_length)
|
||||
_encode(out, data)
|
||||
return string(out)
|
||||
out_length := (len(data) + 4) / 5 * 8
|
||||
out := make([]byte, out_length)
|
||||
_encode(out, data)
|
||||
return string(out)
|
||||
}
|
||||
|
||||
@private
|
||||
_encode :: proc(out, data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) {
|
||||
out := out
|
||||
data := data
|
||||
out := out
|
||||
data := data
|
||||
|
||||
for len(data) > 0 {
|
||||
carry: byte
|
||||
switch len(data) {
|
||||
case:
|
||||
out[7] = ENC_TABLE[data[4] & 0x1f]
|
||||
carry = data[4] >> 5
|
||||
fallthrough
|
||||
case 4:
|
||||
out[6] = ENC_TABLE[carry | (data[3] << 3) & 0x1f]
|
||||
out[5] = ENC_TABLE[(data[3] >> 2) & 0x1f]
|
||||
carry = data[3] >> 7
|
||||
fallthrough
|
||||
case 3:
|
||||
out[4] = ENC_TABLE[carry | (data[2] << 1) & 0x1f]
|
||||
carry = (data[2] >> 4) & 0x1f
|
||||
fallthrough
|
||||
case 2:
|
||||
out[3] = ENC_TABLE[carry | (data[1] << 4) & 0x1f]
|
||||
out[2] = ENC_TABLE[(data[1] >> 1) & 0x1f]
|
||||
carry = (data[1] >> 6) & 0x1f
|
||||
fallthrough
|
||||
case 1:
|
||||
out[1] = ENC_TABLE[carry | (data[0] << 2) & 0x1f]
|
||||
out[0] = ENC_TABLE[data[0] >> 3]
|
||||
}
|
||||
for len(data) > 0 {
|
||||
carry: byte
|
||||
switch len(data) {
|
||||
case:
|
||||
out[7] = ENC_TABLE[data[4] & 0x1f]
|
||||
carry = data[4] >> 5
|
||||
fallthrough
|
||||
case 4:
|
||||
out[6] = ENC_TABLE[carry | (data[3] << 3) & 0x1f]
|
||||
out[5] = ENC_TABLE[(data[3] >> 2) & 0x1f]
|
||||
carry = data[3] >> 7
|
||||
fallthrough
|
||||
case 3:
|
||||
out[4] = ENC_TABLE[carry | (data[2] << 1) & 0x1f]
|
||||
carry = (data[2] >> 4) & 0x1f
|
||||
fallthrough
|
||||
case 2:
|
||||
out[3] = ENC_TABLE[carry | (data[1] << 4) & 0x1f]
|
||||
out[2] = ENC_TABLE[(data[1] >> 1) & 0x1f]
|
||||
carry = (data[1] >> 6) & 0x1f
|
||||
fallthrough
|
||||
case 1:
|
||||
out[1] = ENC_TABLE[carry | (data[0] << 2) & 0x1f]
|
||||
out[0] = ENC_TABLE[data[0] >> 3]
|
||||
}
|
||||
|
||||
if len(data) < 5 {
|
||||
out[7] = byte(PADDING)
|
||||
if len(data) < 4 {
|
||||
out[6] = byte(PADDING)
|
||||
out[5] = byte(PADDING)
|
||||
if len(data) < 3 {
|
||||
out[4] = byte(PADDING)
|
||||
if len(data) < 2 {
|
||||
out[3] = byte(PADDING)
|
||||
out[2] = byte(PADDING)
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
data = data[5:]
|
||||
out = out[8:]
|
||||
}
|
||||
if len(data) < 5 {
|
||||
out[7] = byte(PADDING)
|
||||
if len(data) < 4 {
|
||||
out[6] = byte(PADDING)
|
||||
out[5] = byte(PADDING)
|
||||
if len(data) < 3 {
|
||||
out[4] = byte(PADDING)
|
||||
if len(data) < 2 {
|
||||
out[3] = byte(PADDING)
|
||||
out[2] = byte(PADDING)
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
data = data[5:]
|
||||
out = out[8:]
|
||||
}
|
||||
}
|
||||
|
||||
decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> []byte #no_bounds_check{
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
outi := 0
|
||||
data := data
|
||||
outi := 0
|
||||
data := data
|
||||
|
||||
out := make([]byte, len(data) / 8 * 5, allocator)
|
||||
end := false
|
||||
for len(data) > 0 && !end {
|
||||
dbuf : [8]byte
|
||||
dlen := 8
|
||||
out := make([]byte, len(data) / 8 * 5, allocator)
|
||||
end := false
|
||||
for len(data) > 0 && !end {
|
||||
dbuf : [8]byte
|
||||
dlen := 8
|
||||
|
||||
for j := 0; j < 8; {
|
||||
if len(data) == 0 {
|
||||
dlen, end = j, true
|
||||
break
|
||||
}
|
||||
input := data[0]
|
||||
data = data[1:]
|
||||
if input == byte(PADDING) && j >= 2 && len(data) < 8 {
|
||||
assert(!(len(data) + j < 8 - 1), "Corrupted input")
|
||||
for k := 0; k < 8-1-j; k +=1 {
|
||||
assert(len(data) < k || data[k] == byte(PADDING), "Corrupted input")
|
||||
}
|
||||
dlen, end = j, true
|
||||
assert(dlen != 1 && dlen != 3 && dlen != 6, "Corrupted input")
|
||||
break
|
||||
}
|
||||
dbuf[j] = DEC_TABLE[input]
|
||||
assert(dbuf[j] != 0xff, "Corrupted input")
|
||||
j += 1
|
||||
}
|
||||
for j := 0; j < 8; {
|
||||
if len(data) == 0 {
|
||||
dlen, end = j, true
|
||||
break
|
||||
}
|
||||
input := data[0]
|
||||
data = data[1:]
|
||||
if input == byte(PADDING) && j >= 2 && len(data) < 8 {
|
||||
assert(!(len(data) + j < 8 - 1), "Corrupted input")
|
||||
for k := 0; k < 8-1-j; k +=1 {
|
||||
assert(len(data) < k || data[k] == byte(PADDING), "Corrupted input")
|
||||
}
|
||||
dlen, end = j, true
|
||||
assert(dlen != 1 && dlen != 3 && dlen != 6, "Corrupted input")
|
||||
break
|
||||
}
|
||||
dbuf[j] = DEC_TABLE[input]
|
||||
assert(dbuf[j] != 0xff, "Corrupted input")
|
||||
j += 1
|
||||
}
|
||||
|
||||
switch dlen {
|
||||
case 8:
|
||||
out[outi + 4] = dbuf[6] << 5 | dbuf[7]
|
||||
fallthrough
|
||||
case 7:
|
||||
out[outi + 3] = dbuf[4] << 7 | dbuf[5] << 2 | dbuf[6] >> 3
|
||||
fallthrough
|
||||
case 5:
|
||||
out[outi + 2] = dbuf[3] << 4 | dbuf[4] >> 1
|
||||
fallthrough
|
||||
case 4:
|
||||
out[outi + 1] = dbuf[1] << 6 | dbuf[2] << 1 | dbuf[3] >> 4
|
||||
fallthrough
|
||||
case 2:
|
||||
out[outi + 0] = dbuf[0] << 3 | dbuf[1] >> 2
|
||||
}
|
||||
outi += 5
|
||||
}
|
||||
return out
|
||||
switch dlen {
|
||||
case 8:
|
||||
out[outi + 4] = dbuf[6] << 5 | dbuf[7]
|
||||
fallthrough
|
||||
case 7:
|
||||
out[outi + 3] = dbuf[4] << 7 | dbuf[5] << 2 | dbuf[6] >> 3
|
||||
fallthrough
|
||||
case 5:
|
||||
out[outi + 2] = dbuf[3] << 4 | dbuf[4] >> 1
|
||||
fallthrough
|
||||
case 4:
|
||||
out[outi + 1] = dbuf[1] << 6 | dbuf[2] << 1 | dbuf[3] >> 4
|
||||
fallthrough
|
||||
case 2:
|
||||
out[outi + 0] = dbuf[0] << 3 | dbuf[1] >> 2
|
||||
}
|
||||
outi += 5
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -320,8 +320,8 @@ to_diagnostic_format :: proc {
|
||||
|
||||
// Turns the given CBOR value into a human-readable string.
|
||||
// See docs on the proc group `diagnose` for more info.
|
||||
to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator) -> (string, mem.Allocator_Error) #optional_allocator_error {
|
||||
b := strings.builder_make(allocator)
|
||||
to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator, loc := #caller_location) -> (string, mem.Allocator_Error) #optional_allocator_error {
|
||||
b := strings.builder_make(allocator, loc)
|
||||
w := strings.to_stream(&b)
|
||||
err := to_diagnostic_format_writer(w, val, padding)
|
||||
if err == .EOF {
|
||||
|
||||
@@ -95,24 +95,25 @@ decode :: decode_from
|
||||
|
||||
// Decodes the given string as CBOR.
|
||||
// See docs on the proc group `decode` for more information.
|
||||
decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
|
||||
decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
|
||||
r: strings.Reader
|
||||
strings.reader_init(&r, s)
|
||||
return decode_from_reader(strings.reader_to_stream(&r), flags, allocator)
|
||||
return decode_from_reader(strings.reader_to_stream(&r), flags, allocator, loc)
|
||||
}
|
||||
|
||||
// Reads a CBOR value from the given reader.
|
||||
// See docs on the proc group `decode` for more information.
|
||||
decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
|
||||
decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
|
||||
return decode_from_decoder(
|
||||
Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r },
|
||||
allocator=allocator,
|
||||
loc = loc,
|
||||
)
|
||||
}
|
||||
|
||||
// Reads a CBOR value from the given decoder.
|
||||
// See docs on the proc group `decode` for more information.
|
||||
decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
|
||||
decode_from_decoder :: proc(d: Decoder, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
d := d
|
||||
@@ -121,13 +122,13 @@ decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: V
|
||||
d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC
|
||||
}
|
||||
|
||||
v, err = _decode_from_decoder(d)
|
||||
v, err = _decode_from_decoder(d, {}, allocator, loc)
|
||||
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
|
||||
if err == .EOF { err = .Unexpected_EOF }
|
||||
return
|
||||
}
|
||||
|
||||
_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value, err: Decode_Error) {
|
||||
_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0), allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
|
||||
hdr := hdr
|
||||
r := d.reader
|
||||
if hdr == Header(0) { hdr = _decode_header(r) or_return }
|
||||
@@ -161,11 +162,11 @@ _decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value,
|
||||
switch maj {
|
||||
case .Unsigned: return _decode_tiny_u8(add)
|
||||
case .Negative: return Negative_U8(_decode_tiny_u8(add) or_return), nil
|
||||
case .Bytes: return _decode_bytes_ptr(d, add)
|
||||
case .Text: return _decode_text_ptr(d, add)
|
||||
case .Array: return _decode_array_ptr(d, add)
|
||||
case .Map: return _decode_map_ptr(d, add)
|
||||
case .Tag: return _decode_tag_ptr(d, add)
|
||||
case .Bytes: return _decode_bytes_ptr(d, add, .Bytes, allocator, loc)
|
||||
case .Text: return _decode_text_ptr(d, add, allocator, loc)
|
||||
case .Array: return _decode_array_ptr(d, add, allocator, loc)
|
||||
case .Map: return _decode_map_ptr(d, add, allocator, loc)
|
||||
case .Tag: return _decode_tag_ptr(d, add, allocator, loc)
|
||||
case .Other: return _decode_tiny_simple(add)
|
||||
case: return nil, .Bad_Major
|
||||
}
|
||||
@@ -203,27 +204,27 @@ encode :: encode_into
|
||||
|
||||
// Encodes the CBOR value into binary CBOR allocated on the given allocator.
|
||||
// See the docs on the proc group `encode_into` for more info.
|
||||
encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (data: []byte, err: Encode_Error) {
|
||||
b := strings.builder_make(allocator) or_return
|
||||
encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (data: []byte, err: Encode_Error) {
|
||||
b := strings.builder_make(allocator, loc) or_return
|
||||
encode_into_builder(&b, v, flags, temp_allocator) or_return
|
||||
return b.buf[:], nil
|
||||
}
|
||||
|
||||
// Encodes the CBOR value into binary CBOR written to the given builder.
|
||||
// See the docs on the proc group `encode_into` for more info.
|
||||
encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error {
|
||||
return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator)
|
||||
encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error {
|
||||
return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator, loc=loc)
|
||||
}
|
||||
|
||||
// Encodes the CBOR value into binary CBOR written to the given writer.
|
||||
// See the docs on the proc group `encode_into` for more info.
|
||||
encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error {
|
||||
return encode_into_encoder(Encoder{flags, w, temp_allocator}, v)
|
||||
encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error {
|
||||
return encode_into_encoder(Encoder{flags, w, temp_allocator}, v, loc=loc)
|
||||
}
|
||||
|
||||
// Encodes the CBOR value into binary CBOR written to the given encoder.
|
||||
// See the docs on the proc group `encode_into` for more info.
|
||||
encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error {
|
||||
encode_into_encoder :: proc(e: Encoder, v: Value, loc := #caller_location) -> Encode_Error {
|
||||
e := e
|
||||
|
||||
if e.temp_allocator.procedure == nil {
|
||||
@@ -232,7 +233,7 @@ encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error {
|
||||
|
||||
if .Self_Described_CBOR in e.flags {
|
||||
_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag) or_return
|
||||
e.flags &~= { .Self_Described_CBOR }
|
||||
e.flags -= { .Self_Described_CBOR }
|
||||
}
|
||||
|
||||
switch v_spec in v {
|
||||
@@ -366,21 +367,21 @@ _encode_u64_exact :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> (er
|
||||
return
|
||||
}
|
||||
|
||||
_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: ^Bytes, err: Decode_Error) {
|
||||
v = new(Bytes) or_return
|
||||
defer if err != nil { free(v) }
|
||||
_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: ^Bytes, err: Decode_Error) {
|
||||
v = new(Bytes, allocator, loc) or_return
|
||||
defer if err != nil { free(v, allocator, loc) }
|
||||
|
||||
v^ = _decode_bytes(d, add, type) or_return
|
||||
v^ = _decode_bytes(d, add, type, allocator, loc) or_return
|
||||
return
|
||||
}
|
||||
|
||||
_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator) -> (v: Bytes, err: Decode_Error) {
|
||||
_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: Bytes, err: Decode_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
add := add
|
||||
n, scap := _decode_len_str(d, add) or_return
|
||||
|
||||
buf := strings.builder_make(0, scap) or_return
|
||||
buf := strings.builder_make(0, scap, allocator, loc) or_return
|
||||
defer if err != nil { strings.builder_destroy(&buf) }
|
||||
buf_stream := strings.to_stream(&buf)
|
||||
|
||||
@@ -422,44 +423,44 @@ _decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := c
|
||||
_encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: Encode_Error) {
|
||||
assert(len(val) >= 0)
|
||||
_encode_u64(e, u64(len(val)), major) or_return
|
||||
_, err = io.write_full(e.writer, val[:])
|
||||
_, err = io.write_full(e.writer, val[:])
|
||||
return
|
||||
}
|
||||
|
||||
_decode_text_ptr :: proc(d: Decoder, add: Add) -> (v: ^Text, err: Decode_Error) {
|
||||
v = new(Text) or_return
|
||||
_decode_text_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Text, err: Decode_Error) {
|
||||
v = new(Text, allocator, loc) or_return
|
||||
defer if err != nil { free(v) }
|
||||
|
||||
v^ = _decode_text(d, add) or_return
|
||||
v^ = _decode_text(d, add, allocator, loc) or_return
|
||||
return
|
||||
}
|
||||
|
||||
_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator) -> (v: Text, err: Decode_Error) {
|
||||
return (Text)(_decode_bytes(d, add, .Text, allocator) or_return), nil
|
||||
_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Text, err: Decode_Error) {
|
||||
return (Text)(_decode_bytes(d, add, .Text, allocator, loc) or_return), nil
|
||||
}
|
||||
|
||||
_encode_text :: proc(e: Encoder, val: Text) -> Encode_Error {
|
||||
return _encode_bytes(e, transmute([]byte)val, .Text)
|
||||
return _encode_bytes(e, transmute([]byte)val, .Text)
|
||||
}
|
||||
|
||||
_decode_array_ptr :: proc(d: Decoder, add: Add) -> (v: ^Array, err: Decode_Error) {
|
||||
v = new(Array) or_return
|
||||
_decode_array_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Array, err: Decode_Error) {
|
||||
v = new(Array, allocator, loc) or_return
|
||||
defer if err != nil { free(v) }
|
||||
|
||||
v^ = _decode_array(d, add) or_return
|
||||
v^ = _decode_array(d, add, allocator, loc) or_return
|
||||
return
|
||||
}
|
||||
|
||||
_decode_array :: proc(d: Decoder, add: Add) -> (v: Array, err: Decode_Error) {
|
||||
_decode_array :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Array, err: Decode_Error) {
|
||||
n, scap := _decode_len_container(d, add) or_return
|
||||
array := make([dynamic]Value, 0, scap) or_return
|
||||
array := make([dynamic]Value, 0, scap, allocator, loc) or_return
|
||||
defer if err != nil {
|
||||
for entry in array { destroy(entry) }
|
||||
delete(array)
|
||||
for entry in array { destroy(entry, allocator) }
|
||||
delete(array, loc)
|
||||
}
|
||||
|
||||
for i := 0; n == -1 || i < n; i += 1 {
|
||||
val, verr := _decode_from_decoder(d)
|
||||
val, verr := _decode_from_decoder(d, {}, allocator, loc)
|
||||
if n == -1 && verr == .Break {
|
||||
break
|
||||
} else if verr != nil {
|
||||
@@ -479,45 +480,45 @@ _decode_array :: proc(d: Decoder, add: Add) -> (v: Array, err: Decode_Error) {
|
||||
_encode_array :: proc(e: Encoder, arr: Array) -> Encode_Error {
|
||||
assert(len(arr) >= 0)
|
||||
_encode_u64(e, u64(len(arr)), .Array)
|
||||
for val in arr {
|
||||
encode(e, val) or_return
|
||||
}
|
||||
return nil
|
||||
for val in arr {
|
||||
encode(e, val) or_return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_decode_map_ptr :: proc(d: Decoder, add: Add) -> (v: ^Map, err: Decode_Error) {
|
||||
v = new(Map) or_return
|
||||
_decode_map_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Map, err: Decode_Error) {
|
||||
v = new(Map, allocator, loc) or_return
|
||||
defer if err != nil { free(v) }
|
||||
|
||||
v^ = _decode_map(d, add) or_return
|
||||
v^ = _decode_map(d, add, allocator, loc) or_return
|
||||
return
|
||||
}
|
||||
|
||||
_decode_map :: proc(d: Decoder, add: Add) -> (v: Map, err: Decode_Error) {
|
||||
_decode_map :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Map, err: Decode_Error) {
|
||||
n, scap := _decode_len_container(d, add) or_return
|
||||
items := make([dynamic]Map_Entry, 0, scap) or_return
|
||||
items := make([dynamic]Map_Entry, 0, scap, allocator, loc) or_return
|
||||
defer if err != nil {
|
||||
for entry in items {
|
||||
destroy(entry.key)
|
||||
destroy(entry.value)
|
||||
}
|
||||
delete(items)
|
||||
delete(items, loc)
|
||||
}
|
||||
|
||||
for i := 0; n == -1 || i < n; i += 1 {
|
||||
key, kerr := _decode_from_decoder(d)
|
||||
key, kerr := _decode_from_decoder(d, {}, allocator, loc)
|
||||
if n == -1 && kerr == .Break {
|
||||
break
|
||||
} else if kerr != nil {
|
||||
return nil, kerr
|
||||
}
|
||||
|
||||
value := _decode_from_decoder(d) or_return
|
||||
value := _decode_from_decoder(d, {}, allocator, loc) or_return
|
||||
|
||||
append(&items, Map_Entry{
|
||||
key = key,
|
||||
value = value,
|
||||
}) or_return
|
||||
}, loc) or_return
|
||||
}
|
||||
|
||||
if .Shrink_Excess in d.flags { shrink(&items) }
|
||||
@@ -575,23 +576,23 @@ _encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) {
|
||||
encode(e, entry.entry.value) or_return
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
_decode_tag_ptr :: proc(d: Decoder, add: Add) -> (v: Value, err: Decode_Error) {
|
||||
tag := _decode_tag(d, add) or_return
|
||||
_decode_tag_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
|
||||
tag := _decode_tag(d, add, allocator, loc) or_return
|
||||
if t, ok := tag.?; ok {
|
||||
defer if err != nil { destroy(t.value) }
|
||||
tp := new(Tag) or_return
|
||||
tp := new(Tag, allocator, loc) or_return
|
||||
tp^ = t
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
// no error, no tag, this was the self described CBOR tag, skip it.
|
||||
return _decode_from_decoder(d)
|
||||
return _decode_from_decoder(d, {}, allocator, loc)
|
||||
}
|
||||
|
||||
_decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error) {
|
||||
_decode_tag :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Maybe(Tag), err: Decode_Error) {
|
||||
num := _decode_uint_as_u64(d.reader, add) or_return
|
||||
|
||||
// CBOR can be wrapped in a tag that decoders can use to see/check if the binary data is CBOR.
|
||||
@@ -602,7 +603,7 @@ _decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error)
|
||||
|
||||
t := Tag{
|
||||
number = num,
|
||||
value = _decode_from_decoder(d) or_return,
|
||||
value = _decode_from_decoder(d, {}, allocator, loc) or_return,
|
||||
}
|
||||
|
||||
if nested, ok := t.value.(^Tag); ok {
|
||||
@@ -625,7 +626,7 @@ _decode_uint_as_u64 :: proc(r: io.Reader, add: Add) -> (nr: u64, err: Decode_Err
|
||||
|
||||
_encode_tag :: proc(e: Encoder, val: Tag) -> Encode_Error {
|
||||
_encode_u64(e, val.number, .Tag) or_return
|
||||
return encode(e, val.value)
|
||||
return encode(e, val.value)
|
||||
}
|
||||
|
||||
_decode_simple :: proc(r: io.Reader) -> (v: Simple, err: io.Error) {
|
||||
@@ -738,16 +739,16 @@ _encode_nil :: proc(w: io.Writer) -> io.Error {
|
||||
// Streaming
|
||||
|
||||
encode_stream_begin :: proc(w: io.Writer, major: Major) -> (err: io.Error) {
|
||||
assert(major >= Major(.Bytes) && major <= Major(.Map), "illegal stream type")
|
||||
assert(major >= Major(.Bytes) && major <= Major(.Map), "illegal stream type")
|
||||
|
||||
header := (u8(major) << 5) | u8(Add.Length_Unknown)
|
||||
_, err = io.write_full(w, {header})
|
||||
header := (u8(major) << 5) | u8(Add.Length_Unknown)
|
||||
_, err = io.write_full(w, {header})
|
||||
return
|
||||
}
|
||||
|
||||
encode_stream_end :: proc(w: io.Writer) -> io.Error {
|
||||
header := (u8(Major.Other) << 5) | u8(Add.Break)
|
||||
_, err := io.write_full(w, {header})
|
||||
header := (u8(Major.Other) << 5) | u8(Add.Break)
|
||||
_, err := io.write_full(w, {header})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -756,8 +757,8 @@ encode_stream_text :: _encode_text
|
||||
encode_stream_array_item :: encode
|
||||
|
||||
encode_stream_map_entry :: proc(e: Encoder, key: Value, val: Value) -> Encode_Error {
|
||||
encode(e, key) or_return
|
||||
return encode(e, val)
|
||||
encode(e, key) or_return
|
||||
return encode(e, val)
|
||||
}
|
||||
|
||||
// For `Bytes` and `Text` strings: Decodes the number of items the header says follows.
|
||||
@@ -883,4 +884,4 @@ _encode_deterministic_f64 :: proc(w: io.Writer, v: f64) -> io.Error {
|
||||
}
|
||||
|
||||
return _encode_f64_exact(w, v)
|
||||
}
|
||||
}
|
||||
@@ -77,8 +77,11 @@ You can look at the default tags provided for pointers on how these implementati
|
||||
Example:
|
||||
package main
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
import "core:encoding/cbor"
|
||||
import "core:fmt"
|
||||
import "core:reflect"
|
||||
import "core:time"
|
||||
|
||||
Possibilities :: union {
|
||||
@@ -93,9 +96,32 @@ Example:
|
||||
ignore_this: ^Data `cbor:"-"`, // Ignored by implementation.
|
||||
renamed: f32 `cbor:"renamed :)"`, // Renamed when encoded.
|
||||
my_union: Possibilities, // Union support.
|
||||
|
||||
my_raw: [8]u32 `cbor_tag:"raw"`, // Custom tag that just writes the value as bytes.
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
// Example custom tag implementation that instead of breaking down all parts,
|
||||
// just writes the value as a big byte blob. This is an advanced feature but very powerful.
|
||||
RAW_TAG_NR :: 200
|
||||
cbor.tag_register_number({
|
||||
marshal = proc(_: ^cbor.Tag_Implementation, e: cbor.Encoder, v: any) -> cbor.Marshal_Error {
|
||||
cbor._encode_u8(e.writer, RAW_TAG_NR, .Tag) or_return
|
||||
return cbor.err_conv(cbor._encode_bytes(e, reflect.as_bytes(v)))
|
||||
},
|
||||
unmarshal = proc(_: ^cbor.Tag_Implementation, d: cbor.Decoder, _: cbor.Tag_Number, v: any) -> (cbor.Unmarshal_Error) {
|
||||
hdr := cbor._decode_header(d.reader) or_return
|
||||
maj, add := cbor._header_split(hdr)
|
||||
if maj != .Bytes {
|
||||
return .Bad_Tag_Value
|
||||
}
|
||||
|
||||
bytes := cbor.err_conv(cbor._decode_bytes(d, add, maj)) or_return
|
||||
intrinsics.mem_copy_non_overlapping(v.data, raw_data(bytes), len(bytes))
|
||||
return nil
|
||||
},
|
||||
}, RAW_TAG_NR, "raw")
|
||||
|
||||
now := time.Time{_nsec = 1701117968 * 1e9}
|
||||
|
||||
data := Data{
|
||||
@@ -105,21 +131,22 @@ Example:
|
||||
ignore_this = &Data{},
|
||||
renamed = 123123.125,
|
||||
my_union = 3,
|
||||
my_raw = {1=1, 2=2, 3=3},
|
||||
}
|
||||
|
||||
|
||||
// Marshal the struct into binary CBOR.
|
||||
binary, err := cbor.marshal(data, cbor.ENCODE_FULLY_DETERMINISTIC)
|
||||
assert(err == nil)
|
||||
fmt.assertf(err == nil, "marshal error: %v", err)
|
||||
defer delete(binary)
|
||||
|
||||
|
||||
// Decode the binary data into a `cbor.Value`.
|
||||
decoded, derr := cbor.decode(string(binary))
|
||||
assert(derr == nil)
|
||||
fmt.assertf(derr == nil, "decode error: %v", derr)
|
||||
defer cbor.destroy(decoded)
|
||||
|
||||
// Turn the CBOR into a human readable representation defined as the diagnostic format in [[RFC 8949 Section 8;https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation]].
|
||||
diagnosis, eerr := cbor.to_diagnostic_format(decoded)
|
||||
assert(eerr == nil)
|
||||
fmt.assertf(eerr == nil, "to diagnostic error: %v", eerr)
|
||||
defer delete(diagnosis)
|
||||
|
||||
fmt.println(diagnosis)
|
||||
@@ -127,6 +154,7 @@ Example:
|
||||
|
||||
Output:
|
||||
{
|
||||
"my_raw": 200(h'00001000200030000000000000000000'),
|
||||
"my_union": 1010([
|
||||
"int",
|
||||
3
|
||||
|
||||
@@ -45,8 +45,8 @@ marshal :: marshal_into
|
||||
|
||||
// Marshals the given value into a CBOR byte stream (allocated using the given allocator).
|
||||
// See docs on the `marshal_into` proc group for more info.
|
||||
marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (bytes: []byte, err: Marshal_Error) {
|
||||
b, alloc_err := strings.builder_make(allocator)
|
||||
marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (bytes: []byte, err: Marshal_Error) {
|
||||
b, alloc_err := strings.builder_make(allocator, loc=loc)
|
||||
// The builder as a stream also returns .EOF if it ran out of memory so this is consistent.
|
||||
if alloc_err != nil {
|
||||
return nil, .EOF
|
||||
@@ -85,7 +85,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
||||
|
||||
if .Self_Described_CBOR in e.flags {
|
||||
err_conv(_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag)) or_return
|
||||
e.flags &~= { .Self_Described_CBOR }
|
||||
e.flags -= { .Self_Described_CBOR }
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
@@ -97,11 +97,14 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
||||
return impl->marshal(e, v)
|
||||
}
|
||||
|
||||
ti := runtime.type_info_base(type_info_of(v.id))
|
||||
a := any{v.data, ti.id}
|
||||
ti := runtime.type_info_core(type_info_of(v.id))
|
||||
return _marshal_into_encoder(e, v, ti)
|
||||
}
|
||||
|
||||
_marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (err: Marshal_Error) {
|
||||
a := any{v.data, ti.id}
|
||||
#partial switch info in ti.variant {
|
||||
case runtime.Type_Info_Named:
|
||||
case runtime.Type_Info_Named, runtime.Type_Info_Enum, runtime.Type_Info_Bit_Field:
|
||||
unreachable()
|
||||
|
||||
case runtime.Type_Info_Pointer:
|
||||
@@ -223,18 +226,38 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
||||
}
|
||||
|
||||
err_conv(_encode_u64(e, u64(info.count), .Array)) or_return
|
||||
|
||||
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
|
||||
for i in 0..<info.count {
|
||||
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
||||
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
|
||||
for i in 0..<info.count {
|
||||
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
||||
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
|
||||
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
|
||||
}
|
||||
return
|
||||
|
||||
case runtime.Type_Info_Enumerated_Array:
|
||||
// index := runtime.type_info_base(info.index).variant.(runtime.Type_Info_Enum)
|
||||
err_conv(_encode_u64(e, u64(info.count), .Array)) or_return
|
||||
|
||||
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
|
||||
for i in 0..<info.count {
|
||||
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
||||
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
|
||||
for i in 0..<info.count {
|
||||
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
||||
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
|
||||
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
|
||||
}
|
||||
return
|
||||
|
||||
@@ -246,9 +269,19 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
||||
|
||||
array := (^mem.Raw_Dynamic_Array)(v.data)
|
||||
err_conv(_encode_u64(e, u64(array.len), .Array)) or_return
|
||||
|
||||
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
|
||||
for i in 0..<array.len {
|
||||
data := uintptr(array.data) + uintptr(i*info.elem_size)
|
||||
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
|
||||
for i in 0..<array.len {
|
||||
data := uintptr(array.data) + uintptr(i*info.elem_size)
|
||||
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
|
||||
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
|
||||
}
|
||||
return
|
||||
|
||||
@@ -260,9 +293,19 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
||||
|
||||
array := (^mem.Raw_Slice)(v.data)
|
||||
err_conv(_encode_u64(e, u64(array.len), .Array)) or_return
|
||||
|
||||
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
|
||||
for i in 0..<array.len {
|
||||
data := uintptr(array.data) + uintptr(i*info.elem_size)
|
||||
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
|
||||
for i in 0..<array.len {
|
||||
data := uintptr(array.data) + uintptr(i*info.elem_size)
|
||||
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
|
||||
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
|
||||
}
|
||||
return
|
||||
|
||||
@@ -308,7 +351,8 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
||||
builder := strings.builder_from_slice(res[:])
|
||||
e.writer = strings.to_stream(&builder)
|
||||
|
||||
assert(_encode_u64(e, u64(len(str)), .Text) == nil)
|
||||
err := _encode_u64(e, u64(len(str)), .Text)
|
||||
assert(err == nil)
|
||||
res[9] = u8(len(builder.buf))
|
||||
assert(res[9] < 10)
|
||||
return
|
||||
@@ -463,7 +507,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
||||
}
|
||||
|
||||
n: u64; {
|
||||
for _, i in info.names {
|
||||
for _, i in info.names[:info.field_count] {
|
||||
if field_name(info, i) != "-" {
|
||||
n += 1
|
||||
}
|
||||
@@ -479,7 +523,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
||||
entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return
|
||||
defer delete(entries)
|
||||
|
||||
for _, i in info.names {
|
||||
for _, i in info.names[:info.field_count] {
|
||||
fname := field_name(info, i)
|
||||
if fname == "-" {
|
||||
continue
|
||||
@@ -497,7 +541,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
||||
marshal_entry(e, info, v, entry.name, entry.field) or_return
|
||||
}
|
||||
} else {
|
||||
for _, i in info.names {
|
||||
for _, i in info.names[:info.field_count] {
|
||||
fname := field_name(info, i)
|
||||
if fname == "-" {
|
||||
continue
|
||||
@@ -542,9 +586,6 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
||||
|
||||
return marshal_into(e, any{v.data, vti.id})
|
||||
|
||||
case runtime.Type_Info_Enum:
|
||||
return marshal_into(e, any{v.data, info.base.id})
|
||||
|
||||
case runtime.Type_Info_Bit_Set:
|
||||
// Store bit_set as big endian just like the protocol.
|
||||
do_byte_swap := !reflect.bit_set_is_big_endian(v)
|
||||
|
||||
@@ -95,7 +95,6 @@ tag_register_number :: proc(impl: Tag_Implementation, nr: Tag_Number, id: string
|
||||
}
|
||||
|
||||
// Controls initialization of default tag implementations.
|
||||
// JS and WASI default to a panic allocator so we don't want to do it on those.
|
||||
INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, !ODIN_DEFAULT_TO_PANIC_ALLOCATOR && !ODIN_DEFAULT_TO_NIL_ALLOCATOR)
|
||||
|
||||
@(private, init, disabled=!INITIALIZE_DEFAULT_TAGS)
|
||||
|
||||
@@ -31,8 +31,8 @@ unmarshal :: proc {
|
||||
unmarshal_from_string,
|
||||
}
|
||||
|
||||
unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
|
||||
err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator)
|
||||
unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||
err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator, loc)
|
||||
|
||||
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
|
||||
if err == .EOF { err = .Unexpected_EOF }
|
||||
@@ -40,21 +40,21 @@ unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{},
|
||||
}
|
||||
|
||||
// Unmarshals from a string, see docs on the proc group `Unmarshal` for more info.
|
||||
unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
|
||||
unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||
sr: strings.Reader
|
||||
r := strings.to_reader(&sr, s)
|
||||
|
||||
err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator)
|
||||
err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator, loc)
|
||||
|
||||
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
|
||||
if err == .EOF { err = .Unexpected_EOF }
|
||||
return
|
||||
}
|
||||
|
||||
unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
|
||||
unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||
d := d
|
||||
|
||||
err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator)
|
||||
err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator, loc)
|
||||
|
||||
// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
|
||||
if err == .EOF { err = .Unexpected_EOF }
|
||||
@@ -62,7 +62,7 @@ unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.alloca
|
||||
|
||||
}
|
||||
|
||||
_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator) -> Unmarshal_Error {
|
||||
_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> Unmarshal_Error {
|
||||
context.allocator = allocator
|
||||
context.temp_allocator = temp_allocator
|
||||
v := v
|
||||
@@ -78,10 +78,10 @@ _unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocat
|
||||
}
|
||||
|
||||
data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id}
|
||||
return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return))
|
||||
return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return), allocator, temp_allocator, loc)
|
||||
}
|
||||
|
||||
_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Error) {
|
||||
_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||
v := v
|
||||
ti := reflect.type_info_base(type_info_of(v.id))
|
||||
r := d.reader
|
||||
@@ -96,7 +96,8 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
|
||||
ti = reflect.type_info_base(variant)
|
||||
if !reflect.is_pointer_internally(variant) {
|
||||
tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id}
|
||||
assert(_assign_int(tag, 1))
|
||||
assigned := _assign_int(tag, 1)
|
||||
assert(assigned)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +105,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
|
||||
// Allow generic unmarshal by doing it into a `Value`.
|
||||
switch &dst in v {
|
||||
case Value:
|
||||
dst = err_conv(_decode_from_decoder(d, hdr)) or_return
|
||||
dst = err_conv(_decode_from_decoder(d, hdr, allocator, loc)) or_return
|
||||
return
|
||||
}
|
||||
|
||||
@@ -273,13 +274,13 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
|
||||
|
||||
// NOTE: Because this is a special type and not to be treated as a general integer,
|
||||
// We only put the value of it in fields that are explicitly of type `Simple`.
|
||||
switch &dst in v {
|
||||
case Simple:
|
||||
dst = decoded
|
||||
return
|
||||
case:
|
||||
return _unsupported(v, hdr, add)
|
||||
}
|
||||
switch &dst in v {
|
||||
case Simple:
|
||||
dst = decoded
|
||||
return
|
||||
case:
|
||||
return _unsupported(v, hdr, add)
|
||||
}
|
||||
|
||||
case .Tag:
|
||||
switch &dst in v {
|
||||
@@ -308,7 +309,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
|
||||
if impl, ok := _tag_implementations_nr[nr]; ok {
|
||||
return impl->unmarshal(d, nr, v)
|
||||
} else if nr == TAG_OBJECT_TYPE {
|
||||
return _unmarshal_union(d, v, ti, hdr)
|
||||
return _unmarshal_union(d, v, ti, hdr, loc=loc)
|
||||
} else {
|
||||
// Discard the tag info and unmarshal as its value.
|
||||
return _unmarshal_value(d, v, _decode_header(r) or_return)
|
||||
@@ -316,19 +317,19 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
|
||||
|
||||
return _unsupported(v, hdr, add)
|
||||
|
||||
case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add)
|
||||
case .Text: return _unmarshal_string(d, v, ti, hdr, add)
|
||||
case .Array: return _unmarshal_array(d, v, ti, hdr, add)
|
||||
case .Map: return _unmarshal_map(d, v, ti, hdr, add)
|
||||
case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add, allocator=allocator, loc=loc)
|
||||
case .Text: return _unmarshal_string(d, v, ti, hdr, add, allocator=allocator, loc=loc)
|
||||
case .Array: return _unmarshal_array(d, v, ti, hdr, add, allocator=allocator, loc=loc)
|
||||
case .Map: return _unmarshal_map(d, v, ti, hdr, add, allocator=allocator, loc=loc)
|
||||
|
||||
case: return .Bad_Major
|
||||
}
|
||||
}
|
||||
|
||||
_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
|
||||
_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||
#partial switch t in ti.variant {
|
||||
case reflect.Type_Info_String:
|
||||
bytes := err_conv(_decode_bytes(d, add)) or_return
|
||||
bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return
|
||||
|
||||
if t.is_cstring {
|
||||
raw := (^cstring)(v.data)
|
||||
@@ -347,7 +348,7 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
|
||||
if elem_base.id != byte { return _unsupported(v, hdr) }
|
||||
|
||||
bytes := err_conv(_decode_bytes(d, add)) or_return
|
||||
bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return
|
||||
raw := (^mem.Raw_Slice)(v.data)
|
||||
raw^ = transmute(mem.Raw_Slice)bytes
|
||||
return
|
||||
@@ -357,12 +358,12 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
|
||||
if elem_base.id != byte { return _unsupported(v, hdr) }
|
||||
|
||||
bytes := err_conv(_decode_bytes(d, add)) or_return
|
||||
bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return
|
||||
raw := (^mem.Raw_Dynamic_Array)(v.data)
|
||||
raw.data = raw_data(bytes)
|
||||
raw.len = len(bytes)
|
||||
raw.cap = len(bytes)
|
||||
raw.allocator = context.allocator
|
||||
raw.allocator = allocator
|
||||
return
|
||||
|
||||
case reflect.Type_Info_Array:
|
||||
@@ -385,10 +386,10 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
return _unsupported(v, hdr)
|
||||
}
|
||||
|
||||
_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
|
||||
_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||
#partial switch t in ti.variant {
|
||||
case reflect.Type_Info_String:
|
||||
text := err_conv(_decode_text(d, add)) or_return
|
||||
text := err_conv(_decode_text(d, add, allocator, loc)) or_return
|
||||
|
||||
if t.is_cstring {
|
||||
raw := (^cstring)(v.data)
|
||||
@@ -403,8 +404,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade
|
||||
|
||||
// Enum by its variant name.
|
||||
case reflect.Type_Info_Enum:
|
||||
text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return
|
||||
defer delete(text, context.temp_allocator)
|
||||
text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return
|
||||
defer delete(text, temp_allocator, loc)
|
||||
|
||||
for name, i in t.names {
|
||||
if name == text {
|
||||
@@ -414,8 +415,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade
|
||||
}
|
||||
|
||||
case reflect.Type_Info_Rune:
|
||||
text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return
|
||||
defer delete(text, context.temp_allocator)
|
||||
text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return
|
||||
defer delete(text, temp_allocator, loc)
|
||||
|
||||
r := (^rune)(v.data)
|
||||
dr, n := utf8.decode_rune(text)
|
||||
@@ -430,13 +431,15 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade
|
||||
return _unsupported(v, hdr)
|
||||
}
|
||||
|
||||
_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
|
||||
_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||
assign_array :: proc(
|
||||
d: Decoder,
|
||||
da: ^mem.Raw_Dynamic_Array,
|
||||
elemt: ^reflect.Type_Info,
|
||||
length: int,
|
||||
growable := true,
|
||||
allocator := context.allocator,
|
||||
loc := #caller_location,
|
||||
) -> (out_of_space: bool, err: Unmarshal_Error) {
|
||||
for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 {
|
||||
elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size))
|
||||
@@ -450,13 +453,13 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
if !growable { return true, .Out_Of_Memory }
|
||||
|
||||
cap := 2 * da.cap
|
||||
ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap)
|
||||
ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap, loc)
|
||||
|
||||
// NOTE: Might be lying here, but it is at least an allocator error.
|
||||
if !ok { return false, .Out_Of_Memory }
|
||||
}
|
||||
|
||||
err = _unmarshal_value(d, elem, hdr)
|
||||
err = _unmarshal_value(d, elem, hdr, allocator=allocator, loc=loc)
|
||||
if length == -1 && err == .Break { break }
|
||||
if err != nil { return }
|
||||
|
||||
@@ -469,10 +472,10 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
// Allow generically storing the values array.
|
||||
switch &dst in v {
|
||||
case ^Array:
|
||||
dst = err_conv(_decode_array_ptr(d, add)) or_return
|
||||
dst = err_conv(_decode_array_ptr(d, add, allocator=allocator, loc=loc)) or_return
|
||||
return
|
||||
case Array:
|
||||
dst = err_conv(_decode_array(d, add)) or_return
|
||||
dst = err_conv(_decode_array(d, add, allocator=allocator, loc=loc)) or_return
|
||||
return
|
||||
}
|
||||
|
||||
@@ -480,8 +483,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
case reflect.Type_Info_Slice:
|
||||
length, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||
|
||||
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return
|
||||
defer if err != nil { mem.free_bytes(data) }
|
||||
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return
|
||||
defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
|
||||
|
||||
da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator }
|
||||
|
||||
@@ -489,7 +492,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
|
||||
if .Shrink_Excess in d.flags {
|
||||
// Ignoring an error here, but this is not critical to succeed.
|
||||
_ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len)
|
||||
_ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len, loc=loc)
|
||||
}
|
||||
|
||||
raw := (^mem.Raw_Slice)(v.data)
|
||||
@@ -500,8 +503,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
case reflect.Type_Info_Dynamic_Array:
|
||||
length, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||
|
||||
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return
|
||||
defer if err != nil { mem.free_bytes(data) }
|
||||
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, loc=loc) or_return
|
||||
defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
|
||||
|
||||
raw := (^mem.Raw_Dynamic_Array)(v.data)
|
||||
raw.data = raw_data(data)
|
||||
@@ -513,47 +516,41 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
|
||||
if .Shrink_Excess in d.flags {
|
||||
// Ignoring an error here, but this is not critical to succeed.
|
||||
_ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len)
|
||||
_ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len, loc=loc)
|
||||
}
|
||||
return
|
||||
|
||||
case reflect.Type_Info_Array:
|
||||
_, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||
length := min(scap, t.count)
|
||||
|
||||
length, _ := err_conv(_decode_len_container(d, add)) or_return
|
||||
if length > t.count {
|
||||
return _unsupported(v, hdr)
|
||||
}
|
||||
|
||||
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator }
|
||||
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
|
||||
|
||||
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
|
||||
if out_of_space { return _unsupported(v, hdr) }
|
||||
return
|
||||
|
||||
case reflect.Type_Info_Enumerated_Array:
|
||||
_, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||
length := min(scap, t.count)
|
||||
|
||||
length, _ := err_conv(_decode_len_container(d, add)) or_return
|
||||
if length > t.count {
|
||||
return _unsupported(v, hdr)
|
||||
}
|
||||
|
||||
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator }
|
||||
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
|
||||
|
||||
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
|
||||
if out_of_space { return _unsupported(v, hdr) }
|
||||
return
|
||||
|
||||
case reflect.Type_Info_Complex:
|
||||
_, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||
length := min(scap, 2)
|
||||
|
||||
length, _ := err_conv(_decode_len_container(d, add)) or_return
|
||||
if length > 2 {
|
||||
return _unsupported(v, hdr)
|
||||
}
|
||||
|
||||
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, context.allocator }
|
||||
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, allocator }
|
||||
|
||||
info: ^runtime.Type_Info
|
||||
switch ti.id {
|
||||
@@ -568,14 +565,12 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
return
|
||||
|
||||
case reflect.Type_Info_Quaternion:
|
||||
_, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||
length := min(scap, 4)
|
||||
|
||||
length, _ := err_conv(_decode_len_container(d, add)) or_return
|
||||
if length > 4 {
|
||||
return _unsupported(v, hdr)
|
||||
}
|
||||
|
||||
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, context.allocator }
|
||||
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, allocator }
|
||||
|
||||
info: ^runtime.Type_Info
|
||||
switch ti.id {
|
||||
@@ -593,17 +588,17 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
}
|
||||
}
|
||||
|
||||
_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
|
||||
_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||
r := d.reader
|
||||
decode_key :: proc(d: Decoder, v: any, allocator := context.allocator) -> (k: string, err: Unmarshal_Error) {
|
||||
decode_key :: proc(d: Decoder, v: any, allocator := context.allocator, loc := #caller_location) -> (k: string, err: Unmarshal_Error) {
|
||||
entry_hdr := _decode_header(d.reader) or_return
|
||||
entry_maj, entry_add := _header_split(entry_hdr)
|
||||
#partial switch entry_maj {
|
||||
case .Text:
|
||||
k = err_conv(_decode_text(d, entry_add, allocator)) or_return
|
||||
k = err_conv(_decode_text(d, entry_add, allocator=allocator, loc=loc)) or_return
|
||||
return
|
||||
case .Bytes:
|
||||
bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator)) or_return
|
||||
bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator, loc=loc)) or_return
|
||||
k = string(bytes)
|
||||
return
|
||||
case:
|
||||
@@ -615,23 +610,23 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
|
||||
// Allow generically storing the map array.
|
||||
switch &dst in v {
|
||||
case ^Map:
|
||||
dst = err_conv(_decode_map_ptr(d, add)) or_return
|
||||
dst = err_conv(_decode_map_ptr(d, add, allocator=allocator, loc=loc)) or_return
|
||||
return
|
||||
case Map:
|
||||
dst = err_conv(_decode_map(d, add)) or_return
|
||||
dst = err_conv(_decode_map(d, add, allocator=allocator, loc=loc)) or_return
|
||||
return
|
||||
}
|
||||
|
||||
#partial switch t in ti.variant {
|
||||
case reflect.Type_Info_Struct:
|
||||
if t.is_raw_union {
|
||||
if .raw_union in t.flags {
|
||||
return _unsupported(v, hdr)
|
||||
}
|
||||
|
||||
length, _ := err_conv(_decode_len_container(d, add)) or_return
|
||||
unknown := length == -1
|
||||
fields := reflect.struct_fields_zipped(ti.id)
|
||||
|
||||
|
||||
for idx := 0; idx < len(fields) && (unknown || idx < length); idx += 1 {
|
||||
// Decode key, keys can only be strings.
|
||||
key: string
|
||||
@@ -644,7 +639,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
|
||||
key = keyv
|
||||
}
|
||||
defer delete(key, context.temp_allocator)
|
||||
|
||||
|
||||
// Find matching field.
|
||||
use_field_idx := -1
|
||||
{
|
||||
@@ -754,7 +749,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
|
||||
// Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which
|
||||
// contains an array of exactly two elements, the first is a textual representation of the following
|
||||
// CBOR value's type.
|
||||
_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header) -> (err: Unmarshal_Error) {
|
||||
_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, loc := #caller_location) -> (err: Unmarshal_Error) {
|
||||
r := d.reader
|
||||
#partial switch t in ti.variant {
|
||||
case reflect.Type_Info_Union:
|
||||
@@ -792,7 +787,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
case reflect.Type_Info_Named:
|
||||
if vti.name == target_name {
|
||||
reflect.set_union_variant_raw_tag(v, tag)
|
||||
return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return)
|
||||
return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc)
|
||||
}
|
||||
|
||||
case:
|
||||
@@ -804,7 +799,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
|
||||
if variant_name == target_name {
|
||||
reflect.set_union_variant_raw_tag(v, tag)
|
||||
return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return)
|
||||
return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
//+build ignore
|
||||
package encoding_csv
|
||||
|
||||
import "core:fmt"
|
||||
import "core:encoding/csv"
|
||||
import "core:os"
|
||||
|
||||
// Requires keeping the entire CSV file in memory at once
|
||||
iterate_csv_from_string :: proc(filename: string) {
|
||||
r: csv.Reader
|
||||
r.trim_leading_space = true
|
||||
r.reuse_record = true // Without it you have to delete(record)
|
||||
r.reuse_record_buffer = true // Without it you have to each of the fields within it
|
||||
defer csv.reader_destroy(&r)
|
||||
|
||||
if csv_data, ok := os.read_entire_file(filename); ok {
|
||||
csv.reader_init_with_string(&r, string(csv_data))
|
||||
defer delete(csv_data)
|
||||
} else {
|
||||
fmt.printfln("Unable to open file: %v", filename)
|
||||
return
|
||||
}
|
||||
|
||||
for r, i, err in csv.iterator_next(&r) {
|
||||
if err != nil { /* Do something with error */ }
|
||||
for f, j in r {
|
||||
fmt.printfln("Record %v, field %v: %q", i, j, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reads the CSV as it's processed (with a small buffer)
|
||||
iterate_csv_from_stream :: proc(filename: string) {
|
||||
fmt.printfln("Hellope from %v", filename)
|
||||
r: csv.Reader
|
||||
r.trim_leading_space = true
|
||||
r.reuse_record = true // Without it you have to delete(record)
|
||||
r.reuse_record_buffer = true // Without it you have to each of the fields within it
|
||||
defer csv.reader_destroy(&r)
|
||||
|
||||
handle, errno := os.open(filename)
|
||||
if errno != os.ERROR_NONE {
|
||||
fmt.printfln("Error opening file: %v", filename)
|
||||
return
|
||||
}
|
||||
defer os.close(handle)
|
||||
csv.reader_init(&r, os.stream_from_handle(handle))
|
||||
|
||||
for r, i in csv.iterator_next(&r) {
|
||||
for f, j in r {
|
||||
fmt.printfln("Record %v, field %v: %q", i, j, f)
|
||||
}
|
||||
}
|
||||
fmt.printfln("Error: %v", csv.iterator_last_error(r))
|
||||
}
|
||||
|
||||
// Read all records at once
|
||||
read_csv_from_string :: proc(filename: string) {
|
||||
r: csv.Reader
|
||||
r.trim_leading_space = true
|
||||
r.reuse_record = true // Without it you have to delete(record)
|
||||
r.reuse_record_buffer = true // Without it you have to each of the fields within it
|
||||
defer csv.reader_destroy(&r)
|
||||
|
||||
if csv_data, ok := os.read_entire_file(filename); ok {
|
||||
csv.reader_init_with_string(&r, string(csv_data))
|
||||
defer delete(csv_data)
|
||||
} else {
|
||||
fmt.printfln("Unable to open file: %v", filename)
|
||||
return
|
||||
}
|
||||
|
||||
records, err := csv.read_all(&r)
|
||||
if err != nil { /* Do something with CSV parse error */ }
|
||||
|
||||
defer {
|
||||
for rec in records {
|
||||
delete(rec)
|
||||
}
|
||||
delete(records)
|
||||
}
|
||||
|
||||
for r, i in records {
|
||||
for f, j in r {
|
||||
fmt.printfln("Record %v, field %v: %q", i, j, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,9 @@ Reader :: struct {
|
||||
field_indices: [dynamic]int,
|
||||
last_record: [dynamic]string,
|
||||
sr: strings.Reader, // used by reader_init_with_string
|
||||
|
||||
// Set and used by the iterator. Query using `iterator_last_error`
|
||||
last_iterator_error: Error,
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +124,27 @@ reader_destroy :: proc(r: ^Reader) {
|
||||
bufio.reader_destroy(&r.r)
|
||||
}
|
||||
|
||||
/*
|
||||
Returns a record at a time.
|
||||
|
||||
for record, row_idx in csv.iterator_next(&r) { ... }
|
||||
|
||||
TIP: If you process the results within the loop and don't need to own the results,
|
||||
you can set the Reader's `reuse_record` and `reuse_record_reuse_record_buffer` to true;
|
||||
you won't need to delete the record or its fields.
|
||||
*/
|
||||
iterator_next :: proc(r: ^Reader) -> (record: []string, idx: int, err: Error, more: bool) {
|
||||
record, r.last_iterator_error = read(r)
|
||||
return record, r.line_count - 1, r.last_iterator_error, r.last_iterator_error == nil
|
||||
}
|
||||
|
||||
// Get last CSV parse error if we ignored it in the iterator loop
|
||||
//
|
||||
// for record, row_idx in csv.iterator_next(&r) { ... }
|
||||
iterator_last_error :: proc(r: Reader) -> (err: Error) {
|
||||
return r.last_iterator_error
|
||||
}
|
||||
|
||||
// read reads a single record (a slice of fields) from r
|
||||
//
|
||||
// All \r\n sequences are normalized to \n, including multi-line field
|
||||
@@ -147,7 +171,7 @@ is_io_error :: proc(err: Error, io_err: io.Error) -> bool {
|
||||
|
||||
// read_all reads all the remaining records from r.
|
||||
// Each record is a slice of fields.
|
||||
// read_all is defined to read until an EOF, and does not treat, and does not treat EOF as an error
|
||||
// read_all is defined to read until an EOF, and does not treat EOF as an error
|
||||
@(require_results)
|
||||
read_all :: proc(r: ^Reader, allocator := context.allocator) -> ([][]string, Error) {
|
||||
context.allocator = allocator
|
||||
@@ -460,5 +484,4 @@ _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.all
|
||||
r.fields_per_record = len(dst)
|
||||
}
|
||||
return dst[:], err
|
||||
|
||||
}
|
||||
}
|
||||
@@ -56,38 +56,27 @@ CDATA_END :: "]]>"
|
||||
COMMENT_START :: "<!--"
|
||||
COMMENT_END :: "-->"
|
||||
|
||||
/*
|
||||
Default: CDATA and comments are passed through unchanged.
|
||||
*/
|
||||
// Default: CDATA and comments are passed through unchanged.
|
||||
XML_Decode_Option :: enum u8 {
|
||||
/*
|
||||
Do not decode & entities. It decodes by default.
|
||||
If given, overrides `Decode_CDATA`.
|
||||
*/
|
||||
// Do not decode & entities. It decodes by default. If given, overrides `Decode_CDATA`.
|
||||
No_Entity_Decode,
|
||||
|
||||
/*
|
||||
CDATA is unboxed.
|
||||
*/
|
||||
// CDATA is unboxed.
|
||||
Unbox_CDATA,
|
||||
|
||||
/*
|
||||
Unboxed CDATA is decoded as well.
|
||||
Ignored if `.Unbox_CDATA` is not given.
|
||||
*/
|
||||
// Unboxed CDATA is decoded as well. Ignored if `.Unbox_CDATA` is not given.
|
||||
Decode_CDATA,
|
||||
|
||||
/*
|
||||
Comments are stripped.
|
||||
*/
|
||||
// Comments are stripped.
|
||||
Comment_Strip,
|
||||
|
||||
// Normalize whitespace
|
||||
Normalize_Whitespace,
|
||||
}
|
||||
XML_Decode_Options :: bit_set[XML_Decode_Option; u8]
|
||||
|
||||
/*
|
||||
Decode a string that may include SGML/XML/HTML entities.
|
||||
The caller has to free the result.
|
||||
*/
|
||||
// Decode a string that may include SGML/XML/HTML entities.
|
||||
// The caller has to free the result.
|
||||
decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := context.allocator) -> (decoded: string, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
@@ -100,14 +89,14 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
|
||||
t := Tokenizer{src=input}
|
||||
in_data := false
|
||||
|
||||
prev: rune = ' '
|
||||
|
||||
loop: for {
|
||||
advance(&t) or_return
|
||||
if t.r < 0 { break loop }
|
||||
|
||||
/*
|
||||
Below here we're never inside a CDATA tag.
|
||||
At most we'll see the start of one, but that doesn't affect the logic.
|
||||
*/
|
||||
// Below here we're never inside a CDATA tag. At most we'll see the start of one,
|
||||
// but that doesn't affect the logic.
|
||||
switch t.r {
|
||||
case '<':
|
||||
/*
|
||||
@@ -126,9 +115,7 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
|
||||
in_data = _handle_xml_special(&t, &builder, options) or_return
|
||||
|
||||
case ']':
|
||||
/*
|
||||
If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
|
||||
*/
|
||||
// If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
|
||||
if in_data {
|
||||
if t.read_offset + len(CDATA_END) < len(t.src) {
|
||||
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
|
||||
@@ -143,22 +130,16 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
|
||||
|
||||
case:
|
||||
if in_data && .Decode_CDATA not_in options {
|
||||
/*
|
||||
Unboxed, but undecoded.
|
||||
*/
|
||||
// Unboxed, but undecoded.
|
||||
write_rune(&builder, t.r)
|
||||
continue
|
||||
}
|
||||
|
||||
if t.r == '&' {
|
||||
if entity, entity_err := _extract_xml_entity(&t); entity_err != .None {
|
||||
/*
|
||||
We read to the end of the string without closing the entity.
|
||||
Pass through as-is.
|
||||
*/
|
||||
// We read to the end of the string without closing the entity. Pass through as-is.
|
||||
write_string(&builder, entity)
|
||||
} else {
|
||||
|
||||
if .No_Entity_Decode not_in options {
|
||||
if decoded, ok := xml_decode_entity(entity); ok {
|
||||
write_rune(&builder, decoded)
|
||||
@@ -166,19 +147,41 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Literal passthrough because the decode failed or we want entities not decoded.
|
||||
*/
|
||||
// Literal passthrough because the decode failed or we want entities not decoded.
|
||||
write_string(&builder, "&")
|
||||
write_string(&builder, entity)
|
||||
write_string(&builder, ";")
|
||||
}
|
||||
} else {
|
||||
write_rune(&builder, t.r)
|
||||
// Handle AV Normalization: https://www.w3.org/TR/2006/REC-xml11-20060816/#AVNormalize
|
||||
if .Normalize_Whitespace in options {
|
||||
switch t.r {
|
||||
case ' ', '\r', '\n', '\t':
|
||||
if prev != ' ' {
|
||||
write_rune(&builder, ' ')
|
||||
prev = ' '
|
||||
}
|
||||
case:
|
||||
write_rune(&builder, t.r)
|
||||
prev = t.r
|
||||
}
|
||||
} else {
|
||||
// https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-line-ends
|
||||
switch t.r {
|
||||
case '\n', 0x85, 0x2028:
|
||||
write_rune(&builder, '\n')
|
||||
case '\r': // Do nothing until next character
|
||||
case:
|
||||
if prev == '\r' { // Turn a single carriage return into a \n
|
||||
write_rune(&builder, '\n')
|
||||
}
|
||||
write_rune(&builder, t.r)
|
||||
}
|
||||
prev = t.r
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strings.clone(strings.to_string(builder), allocator), err
|
||||
}
|
||||
|
||||
@@ -253,24 +256,18 @@ xml_decode_entity :: proc(entity: string) -> (decoded: rune, ok: bool) {
|
||||
return rune(val), true
|
||||
|
||||
case:
|
||||
/*
|
||||
Named entity.
|
||||
*/
|
||||
// Named entity.
|
||||
return named_xml_entity_to_rune(entity)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Private XML helper to extract `&<stuff>;` entity.
|
||||
*/
|
||||
// Private XML helper to extract `&<stuff>;` entity.
|
||||
@(private="file")
|
||||
_extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) {
|
||||
assert(t != nil && t.r == '&')
|
||||
|
||||
/*
|
||||
All of these would be in the ASCII range.
|
||||
Even if one is not, it doesn't matter. All characters we need to compare to extract are.
|
||||
*/
|
||||
// All of these would be in the ASCII range.
|
||||
// Even if one is not, it doesn't matter. All characters we need to compare to extract are.
|
||||
|
||||
length := len(t.src)
|
||||
found := false
|
||||
@@ -292,9 +289,7 @@ _extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) {
|
||||
return string(t.src[t.offset : t.read_offset]), .Invalid_Entity_Encoding
|
||||
}
|
||||
|
||||
/*
|
||||
Private XML helper for CDATA and comments.
|
||||
*/
|
||||
// Private XML helper for CDATA and comments.
|
||||
@(private="file")
|
||||
_handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: XML_Decode_Options) -> (in_data: bool, err: Error) {
|
||||
assert(t != nil && t.r == '<')
|
||||
@@ -304,20 +299,14 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X
|
||||
t.read_offset += len(CDATA_START) - 1
|
||||
|
||||
if .Unbox_CDATA in options && .Decode_CDATA in options {
|
||||
/*
|
||||
We're unboxing _and_ decoding CDATA
|
||||
*/
|
||||
// We're unboxing _and_ decoding CDATA
|
||||
return true, .None
|
||||
}
|
||||
|
||||
/*
|
||||
CDATA is passed through.
|
||||
*/
|
||||
// CDATA is passed through.
|
||||
offset := t.offset
|
||||
|
||||
/*
|
||||
Scan until end of CDATA.
|
||||
*/
|
||||
// Scan until end of CDATA.
|
||||
for {
|
||||
advance(t) or_return
|
||||
if t.r < 0 { return true, .CDATA_Not_Terminated }
|
||||
@@ -341,14 +330,10 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X
|
||||
|
||||
} else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START {
|
||||
t.read_offset += len(COMMENT_START)
|
||||
/*
|
||||
Comment is passed through by default.
|
||||
*/
|
||||
// Comment is passed through by default.
|
||||
offset := t.offset
|
||||
|
||||
/*
|
||||
Scan until end of Comment.
|
||||
*/
|
||||
// Scan until end of Comment.
|
||||
for {
|
||||
advance(t) or_return
|
||||
if t.r < 0 { return true, .Comment_Not_Terminated }
|
||||
|
||||
+4816
-4816
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,8 @@ package encoding_hex
|
||||
|
||||
import "core:strings"
|
||||
|
||||
encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds_check {
|
||||
dst := make([]byte, len(src) * 2, allocator)
|
||||
encode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> []byte #no_bounds_check {
|
||||
dst := make([]byte, len(src) * 2, allocator, loc)
|
||||
for i, j := 0, 0; i < len(src); i += 1 {
|
||||
v := src[i]
|
||||
dst[j] = HEXTABLE[v>>4]
|
||||
@@ -15,12 +15,12 @@ encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds
|
||||
}
|
||||
|
||||
|
||||
decode :: proc(src: []byte, allocator := context.allocator) -> (dst: []byte, ok: bool) #no_bounds_check {
|
||||
decode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> (dst: []byte, ok: bool) #no_bounds_check {
|
||||
if len(src) % 2 == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
dst = make([]byte, len(src) / 2, allocator)
|
||||
dst = make([]byte, len(src) / 2, allocator, loc)
|
||||
for i, j := 0, 1; j < len(src); j += 2 {
|
||||
p := src[j-1]
|
||||
q := src[j]
|
||||
@@ -69,5 +69,4 @@ hex_digit :: proc(char: byte) -> (u8, bool) {
|
||||
case 'A' ..= 'F': return char - 'A' + 10, true
|
||||
case: return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+16
-15
@@ -160,34 +160,35 @@ CONVENTION_SOFT_TRANSFORM :: "transform"
|
||||
|
||||
/* destroy procedures */
|
||||
|
||||
meta_destroy :: proc(meta: Meta, allocator := context.allocator) {
|
||||
meta_destroy :: proc(meta: Meta, allocator := context.allocator, loc := #caller_location) {
|
||||
if nested, ok := meta.value.([]Meta); ok {
|
||||
for m in nested {
|
||||
meta_destroy(m)
|
||||
meta_destroy(m, loc=loc)
|
||||
}
|
||||
delete(nested, allocator)
|
||||
delete(nested, allocator, loc=loc)
|
||||
}
|
||||
}
|
||||
nodes_destroy :: proc(nodes: []Node, allocator := context.allocator) {
|
||||
nodes_destroy :: proc(nodes: []Node, allocator := context.allocator, loc := #caller_location) {
|
||||
for node in nodes {
|
||||
for meta in node.meta_data {
|
||||
meta_destroy(meta)
|
||||
meta_destroy(meta, loc=loc)
|
||||
}
|
||||
delete(node.meta_data, allocator)
|
||||
delete(node.meta_data, allocator, loc=loc)
|
||||
|
||||
switch n in node.content {
|
||||
case Node_Geometry:
|
||||
delete(n.corner_stack, allocator)
|
||||
delete(n.edge_stack, allocator)
|
||||
delete(n.face_stack, allocator)
|
||||
delete(n.corner_stack, allocator, loc=loc)
|
||||
delete(n.vertex_stack, allocator, loc=loc)
|
||||
delete(n.edge_stack, allocator, loc=loc)
|
||||
delete(n.face_stack, allocator, loc=loc)
|
||||
case Node_Image:
|
||||
delete(n.image_stack, allocator)
|
||||
delete(n.image_stack, allocator, loc=loc)
|
||||
}
|
||||
}
|
||||
delete(nodes, allocator)
|
||||
delete(nodes, allocator, loc=loc)
|
||||
}
|
||||
|
||||
file_destroy :: proc(file: File) {
|
||||
nodes_destroy(file.nodes, file.allocator)
|
||||
delete(file.backing, file.allocator)
|
||||
}
|
||||
file_destroy :: proc(file: File, loc := #caller_location) {
|
||||
nodes_destroy(file.nodes, file.allocator, loc=loc)
|
||||
delete(file.backing, file.allocator, loc=loc)
|
||||
}
|
||||
+20
-22
@@ -11,24 +11,21 @@ Read_Error :: enum {
|
||||
Unable_To_Read_File,
|
||||
}
|
||||
|
||||
read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) {
|
||||
read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
data, ok := os.read_entire_file(filename, allocator, loc)
|
||||
if !ok {
|
||||
err = .Unable_To_Read_File
|
||||
delete(data, allocator, loc)
|
||||
return
|
||||
}
|
||||
defer if !ok {
|
||||
delete(data)
|
||||
} else {
|
||||
file.backing = data
|
||||
}
|
||||
file, err = read(data, filename, print_error, allocator)
|
||||
file, err = read(data, filename, print_error, allocator, loc)
|
||||
file.backing = data
|
||||
return
|
||||
}
|
||||
|
||||
read :: proc(data: []byte, filename := "<input>", print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) {
|
||||
read :: proc(data: []byte, filename := "<input>", print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) {
|
||||
Reader :: struct {
|
||||
filename: string,
|
||||
data: []byte,
|
||||
@@ -79,8 +76,8 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
||||
return string(data[:len]), nil
|
||||
}
|
||||
|
||||
read_meta :: proc(r: ^Reader, capacity: u32le) -> (meta_data: []Meta, err: Read_Error) {
|
||||
meta_data = make([]Meta, int(capacity))
|
||||
read_meta :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (meta_data: []Meta, err: Read_Error) {
|
||||
meta_data = make([]Meta, int(capacity), allocator=allocator)
|
||||
count := 0
|
||||
defer meta_data = meta_data[:count]
|
||||
for &m in meta_data {
|
||||
@@ -111,10 +108,10 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
||||
return
|
||||
}
|
||||
|
||||
read_layer_stack :: proc(r: ^Reader, capacity: u32le) -> (layers: Layer_Stack, err: Read_Error) {
|
||||
read_layer_stack :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (layers: Layer_Stack, err: Read_Error) {
|
||||
stack_count := read_value(r, u32le) or_return
|
||||
layer_count := 0
|
||||
layers = make(Layer_Stack, stack_count)
|
||||
layers = make(Layer_Stack, stack_count, allocator=allocator, loc=loc)
|
||||
defer layers = layers[:layer_count]
|
||||
for &layer in layers {
|
||||
layer.name = read_name(r) or_return
|
||||
@@ -170,7 +167,8 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
||||
|
||||
node_count := 0
|
||||
file.header = header^
|
||||
file.nodes = make([]Node, header.internal_node_count)
|
||||
file.nodes = make([]Node, header.internal_node_count, allocator=allocator, loc=loc)
|
||||
file.allocator = allocator
|
||||
defer if err != nil {
|
||||
nodes_destroy(file.nodes)
|
||||
file.nodes = nil
|
||||
@@ -198,15 +196,15 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
||||
case .Geometry:
|
||||
g: Node_Geometry
|
||||
|
||||
g.vertex_count = read_value(r, u32le) or_return
|
||||
g.vertex_stack = read_layer_stack(r, g.vertex_count) or_return
|
||||
g.edge_corner_count = read_value(r, u32le) or_return
|
||||
g.corner_stack = read_layer_stack(r, g.edge_corner_count) or_return
|
||||
g.vertex_count = read_value(r, u32le) or_return
|
||||
g.vertex_stack = read_layer_stack(r, g.vertex_count, loc=loc) or_return
|
||||
g.edge_corner_count = read_value(r, u32le) or_return
|
||||
g.corner_stack = read_layer_stack(r, g.edge_corner_count, loc=loc) or_return
|
||||
if header.version > 2 {
|
||||
g.edge_stack = read_layer_stack(r, g.edge_corner_count) or_return
|
||||
g.edge_stack = read_layer_stack(r, g.edge_corner_count, loc=loc) or_return
|
||||
}
|
||||
g.face_count = read_value(r, u32le) or_return
|
||||
g.face_stack = read_layer_stack(r, g.face_count) or_return
|
||||
g.face_count = read_value(r, u32le) or_return
|
||||
g.face_stack = read_layer_stack(r, g.face_count, loc=loc) or_return
|
||||
|
||||
node.content = g
|
||||
|
||||
@@ -233,4 +231,4 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package encoding_ini
|
||||
|
||||
import "base:runtime"
|
||||
import "base:intrinsics"
|
||||
import "core:strings"
|
||||
import "core:strconv"
|
||||
import "core:io"
|
||||
import "core:os"
|
||||
import "core:fmt"
|
||||
_ :: fmt
|
||||
|
||||
Options :: struct {
|
||||
comment: string,
|
||||
key_lower_case: bool,
|
||||
}
|
||||
|
||||
DEFAULT_OPTIONS :: Options {
|
||||
comment = ";",
|
||||
key_lower_case = false,
|
||||
}
|
||||
|
||||
Iterator :: struct {
|
||||
section: string,
|
||||
_src: string,
|
||||
options: Options,
|
||||
}
|
||||
|
||||
iterator_from_string :: proc(src: string, options := DEFAULT_OPTIONS) -> Iterator {
|
||||
return {
|
||||
section = "",
|
||||
options = options,
|
||||
_src = src,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Returns the raw `key` and `value`. `ok` will be false if no more key=value pairs cannot be found.
|
||||
// They key and value may be quoted, which may require the use of `strconv.unquote_string`.
|
||||
iterate :: proc(it: ^Iterator) -> (key, value: string, ok: bool) {
|
||||
for line_ in strings.split_lines_iterator(&it._src) {
|
||||
line := strings.trim_space(line_)
|
||||
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if line[0] == '[' {
|
||||
end_idx := strings.index_byte(line, ']')
|
||||
if end_idx < 0 {
|
||||
end_idx = len(line)
|
||||
}
|
||||
it.section = line[1:end_idx]
|
||||
continue
|
||||
}
|
||||
|
||||
if it.options.comment != "" && strings.has_prefix(line, it.options.comment) {
|
||||
continue
|
||||
}
|
||||
|
||||
equal := strings.index(line, " =") // check for things keys that `ctrl+= = zoom_in`
|
||||
quote := strings.index_byte(line, '"')
|
||||
if equal < 0 || quote > 0 && quote < equal {
|
||||
equal = strings.index_byte(line, '=')
|
||||
if equal < 0 {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
equal += 1
|
||||
}
|
||||
|
||||
key = strings.trim_space(line[:equal])
|
||||
value = strings.trim_space(line[equal+1:])
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
it.section = ""
|
||||
return
|
||||
}
|
||||
|
||||
Map :: distinct map[string]map[string]string
|
||||
|
||||
load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error) {
|
||||
unquote :: proc(val: string) -> (string, runtime.Allocator_Error) {
|
||||
if len(val) > 0 && (val[0] == '"' || val[0] == '\'') {
|
||||
v, allocated, ok := strconv.unquote_string(val)
|
||||
if !ok {
|
||||
return strings.clone(val)
|
||||
}
|
||||
if allocated {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return strings.clone(val)
|
||||
}
|
||||
|
||||
context.allocator = allocator
|
||||
|
||||
it := iterator_from_string(src, options)
|
||||
|
||||
for key, value in iterate(&it) {
|
||||
section := it.section
|
||||
if section not_in m {
|
||||
section = strings.clone(section) or_return
|
||||
m[section] = {}
|
||||
}
|
||||
|
||||
// store key-value pair
|
||||
pairs := &m[section]
|
||||
new_key := unquote(key) or_return
|
||||
if options.key_lower_case {
|
||||
old_key := new_key
|
||||
new_key = strings.to_lower(key) or_return
|
||||
delete(old_key) or_return
|
||||
}
|
||||
pairs[new_key], err = unquote(value) or_return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error, ok: bool) {
|
||||
data := os.read_entire_file(path, allocator) or_return
|
||||
defer delete(data, allocator)
|
||||
m, err = load_map_from_string(string(data), allocator, options)
|
||||
ok = err == nil
|
||||
defer if !ok {
|
||||
delete_map(m)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
save_map_to_string :: proc(m: Map, allocator: runtime.Allocator) -> (data: string) {
|
||||
b := strings.builder_make(allocator)
|
||||
_, _ = write_map(strings.to_writer(&b), m)
|
||||
return strings.to_string(b)
|
||||
}
|
||||
|
||||
delete_map :: proc(m: Map) {
|
||||
allocator := m.allocator
|
||||
for section, pairs in m {
|
||||
for key, value in pairs {
|
||||
delete(key, allocator)
|
||||
delete(value, allocator)
|
||||
}
|
||||
delete(section)
|
||||
delete(pairs)
|
||||
}
|
||||
delete(m)
|
||||
}
|
||||
|
||||
write_section :: proc(w: io.Writer, name: string, n_written: ^int = nil) -> (n: int, err: io.Error) {
|
||||
defer if n_written != nil { n_written^ += n }
|
||||
io.write_byte (w, '[', &n) or_return
|
||||
io.write_string(w, name, &n) or_return
|
||||
io.write_byte (w, ']', &n) or_return
|
||||
return
|
||||
}
|
||||
|
||||
write_pair :: proc(w: io.Writer, key: string, value: $T, n_written: ^int = nil) -> (n: int, err: io.Error) {
|
||||
defer if n_written != nil { n_written^ += n }
|
||||
io.write_string(w, key, &n) or_return
|
||||
io.write_string(w, " = ", &n) or_return
|
||||
when intrinsics.type_is_string(T) {
|
||||
val := string(value)
|
||||
if len(val) > 0 && (val[0] == ' ' || val[len(val)-1] == ' ') {
|
||||
io.write_quoted_string(w, val, n_written=&n) or_return
|
||||
} else {
|
||||
io.write_string(w, val, &n) or_return
|
||||
}
|
||||
} else {
|
||||
n += fmt.wprint(w, value)
|
||||
}
|
||||
io.write_byte(w, '\n', &n) or_return
|
||||
return
|
||||
}
|
||||
|
||||
write_map :: proc(w: io.Writer, m: Map) -> (n: int, err: io.Error) {
|
||||
section_index := 0
|
||||
for section, pairs in m {
|
||||
if section_index == 0 && section == "" {
|
||||
// ignore section
|
||||
} else {
|
||||
write_section(w, section, &n) or_return
|
||||
}
|
||||
for key, value in pairs {
|
||||
write_pair(w, key, value, &n) or_return
|
||||
}
|
||||
section_index += 1
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -25,7 +25,7 @@ Marshal_Options :: struct {
|
||||
spec: Specification,
|
||||
|
||||
// Use line breaks & tabs/spaces
|
||||
pretty: bool,
|
||||
pretty: bool,
|
||||
|
||||
// Use spaces for indentation instead of tabs
|
||||
use_spaces: bool,
|
||||
@@ -34,7 +34,7 @@ Marshal_Options :: struct {
|
||||
spaces: int,
|
||||
|
||||
// Output uint as hex in JSON5 & MJSON
|
||||
write_uint_as_hex: bool,
|
||||
write_uint_as_hex: bool,
|
||||
|
||||
// If spec is MJSON and this is true, then keys will be quoted.
|
||||
//
|
||||
@@ -62,8 +62,8 @@ Marshal_Options :: struct {
|
||||
mjson_skipped_first_braces_end: bool,
|
||||
}
|
||||
|
||||
marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator) -> (data: []byte, err: Marshal_Error) {
|
||||
b := strings.builder_make(allocator)
|
||||
marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Marshal_Error) {
|
||||
b := strings.builder_make(allocator, loc)
|
||||
defer if err != nil {
|
||||
strings.builder_destroy(&b)
|
||||
}
|
||||
@@ -100,45 +100,14 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
|
||||
case runtime.Type_Info_Integer:
|
||||
buf: [40]byte
|
||||
u: u128
|
||||
switch i in a {
|
||||
case i8: u = u128(i)
|
||||
case i16: u = u128(i)
|
||||
case i32: u = u128(i)
|
||||
case i64: u = u128(i)
|
||||
case i128: u = u128(i)
|
||||
case int: u = u128(i)
|
||||
case u8: u = u128(i)
|
||||
case u16: u = u128(i)
|
||||
case u32: u = u128(i)
|
||||
case u64: u = u128(i)
|
||||
case u128: u = u128(i)
|
||||
case uint: u = u128(i)
|
||||
case uintptr: u = u128(i)
|
||||
|
||||
case i16le: u = u128(i)
|
||||
case i32le: u = u128(i)
|
||||
case i64le: u = u128(i)
|
||||
case u16le: u = u128(i)
|
||||
case u32le: u = u128(i)
|
||||
case u64le: u = u128(i)
|
||||
case u128le: u = u128(i)
|
||||
|
||||
case i16be: u = u128(i)
|
||||
case i32be: u = u128(i)
|
||||
case i64be: u = u128(i)
|
||||
case u16be: u = u128(i)
|
||||
case u32be: u = u128(i)
|
||||
case u64be: u = u128(i)
|
||||
case u128be: u = u128(i)
|
||||
}
|
||||
u := cast_any_int_to_u128(a)
|
||||
|
||||
s: string
|
||||
|
||||
// allow uints to be printed as hex
|
||||
if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) {
|
||||
switch i in a {
|
||||
case u8, u16, u32, u64, u128:
|
||||
case u8, u16, u32, u64, u128:
|
||||
s = strconv.append_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix })
|
||||
|
||||
case:
|
||||
@@ -239,7 +208,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
case runtime.Type_Info_Array:
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
for i in 0..<info.count {
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||
}
|
||||
@@ -248,7 +217,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
case runtime.Type_Info_Enumerated_Array:
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
for i in 0..<info.count {
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||
}
|
||||
@@ -258,7 +227,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
array := cast(^mem.Raw_Dynamic_Array)v.data
|
||||
for i in 0..<array.len {
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
data := uintptr(array.data) + uintptr(i*info.elem_size)
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||
}
|
||||
@@ -268,7 +237,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
slice := cast(^mem.Raw_Slice)v.data
|
||||
for i in 0..<slice.len {
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
data := uintptr(slice.data) + uintptr(i*info.elem_size)
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||
}
|
||||
@@ -290,7 +259,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
for bucket_index in 0..<map_cap {
|
||||
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
|
||||
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
i += 1
|
||||
|
||||
key := rawptr(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index))
|
||||
@@ -310,7 +279,12 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
case cstring: name = string(s)
|
||||
}
|
||||
opt_write_key(w, opt, name) or_return
|
||||
|
||||
case runtime.Type_Info_Integer:
|
||||
buf: [40]byte
|
||||
u := cast_any_int_to_u128(ka)
|
||||
name = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil)
|
||||
|
||||
opt_write_key(w, opt, name) or_return
|
||||
case: return .Unsupported_Type
|
||||
}
|
||||
}
|
||||
@@ -356,7 +330,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
slice.sort_by(sorted[:], proc(i, j: Entry) -> bool { return i.key < j.key })
|
||||
|
||||
for s, i in sorted {
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
opt_write_key(w, opt, s.key) or_return
|
||||
marshal_to_writer(w, s.value, opt) or_return
|
||||
}
|
||||
@@ -387,17 +361,17 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
case runtime.Type_Info_Pointer,
|
||||
runtime.Type_Info_Multi_Pointer,
|
||||
runtime.Type_Info_Procedure:
|
||||
return (^rawptr)(v.data)^ == nil
|
||||
return (^rawptr)(v.data)^ == nil
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
return (^runtime.Raw_Dynamic_Array)(v.data).len == 0
|
||||
return (^runtime.Raw_Dynamic_Array)(v.data).len == 0
|
||||
case runtime.Type_Info_Slice:
|
||||
return (^runtime.Raw_Slice)(v.data).len == 0
|
||||
return (^runtime.Raw_Slice)(v.data).len == 0
|
||||
case runtime.Type_Info_Union,
|
||||
runtime.Type_Info_Bit_Set,
|
||||
runtime.Type_Info_Soa_Pointer:
|
||||
return reflect.is_nil(v)
|
||||
case runtime.Type_Info_Map:
|
||||
return (^runtime.Raw_Map)(v.data).len == 0
|
||||
return (^runtime.Raw_Map)(v.data).len == 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -405,10 +379,16 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
marshal_struct_fields :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: Marshal_Error) {
|
||||
ti := runtime.type_info_base(type_info_of(v.id))
|
||||
info := ti.variant.(runtime.Type_Info_Struct)
|
||||
for name, i in info.names {
|
||||
first_iteration := true
|
||||
for name, i in info.names[:info.field_count] {
|
||||
omitempty := false
|
||||
|
||||
json_name, extra := json_name_from_tag_value(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json"))
|
||||
|
||||
if json_name == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
for flag in strings.split_iterator(&extra, ",") {
|
||||
switch flag {
|
||||
case "omitempty":
|
||||
@@ -420,11 +400,12 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
data := rawptr(uintptr(v.data) + info.offsets[i])
|
||||
the_value := any{data, id}
|
||||
|
||||
if is_omitempty(the_value) {
|
||||
if omitempty && is_omitempty(the_value) {
|
||||
continue
|
||||
}
|
||||
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, first_iteration) or_return
|
||||
first_iteration = false
|
||||
if json_name != "" {
|
||||
opt_write_key(w, opt, json_name) or_return
|
||||
} else {
|
||||
@@ -469,12 +450,15 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
case: panic("Invalid union tag type")
|
||||
}
|
||||
|
||||
if v.data == nil || tag == 0 {
|
||||
io.write_string(w, "null") or_return
|
||||
} else {
|
||||
id := info.variants[tag-1].id
|
||||
return marshal_to_writer(w, any{v.data, id}, opt)
|
||||
if !info.no_nil {
|
||||
if tag == 0 {
|
||||
io.write_string(w, "null") or_return
|
||||
return nil
|
||||
}
|
||||
tag -= 1
|
||||
}
|
||||
id := info.variants[tag].id
|
||||
return marshal_to_writer(w, any{v.data, id}, opt)
|
||||
|
||||
case runtime.Type_Info_Enum:
|
||||
if !opt.use_enum_names || len(info.names) == 0 {
|
||||
@@ -536,8 +520,6 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
case: panic("unknown bit_size size")
|
||||
}
|
||||
io.write_u64(w, bit_data) or_return
|
||||
|
||||
return .Unsupported_Type
|
||||
}
|
||||
|
||||
return
|
||||
@@ -587,10 +569,10 @@ opt_write_start :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: i
|
||||
}
|
||||
|
||||
// insert comma separation and write indentations
|
||||
opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int) -> (err: io.Error) {
|
||||
opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, first_iteration: bool) -> (err: io.Error) {
|
||||
switch opt.spec {
|
||||
case .JSON, .JSON5:
|
||||
if iteration > 0 {
|
||||
case .JSON, .JSON5:
|
||||
if !first_iteration {
|
||||
io.write_byte(w, ',') or_return
|
||||
|
||||
if opt.pretty {
|
||||
@@ -600,8 +582,8 @@ opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int)
|
||||
|
||||
opt_write_indentation(w, opt) or_return
|
||||
|
||||
case .MJSON:
|
||||
if iteration > 0 {
|
||||
case .MJSON:
|
||||
if !first_iteration {
|
||||
// on pretty no commas necessary
|
||||
if opt.pretty {
|
||||
io.write_byte(w, '\n') or_return
|
||||
@@ -654,3 +636,41 @@ opt_write_indentation :: proc(w: io.Writer, opt: ^Marshal_Options) -> (err: io.E
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
cast_any_int_to_u128 :: proc(any_int_value: any) -> u128 {
|
||||
u: u128 = 0
|
||||
switch i in any_int_value {
|
||||
case i8: u = u128(i)
|
||||
case i16: u = u128(i)
|
||||
case i32: u = u128(i)
|
||||
case i64: u = u128(i)
|
||||
case i128: u = u128(i)
|
||||
case int: u = u128(i)
|
||||
case u8: u = u128(i)
|
||||
case u16: u = u128(i)
|
||||
case u32: u = u128(i)
|
||||
case u64: u = u128(i)
|
||||
case u128: u = u128(i)
|
||||
case uint: u = u128(i)
|
||||
case uintptr: u = u128(i)
|
||||
|
||||
case i16le: u = u128(i)
|
||||
case i32le: u = u128(i)
|
||||
case i64le: u = u128(i)
|
||||
case u16le: u = u128(i)
|
||||
case u32le: u = u128(i)
|
||||
case u64le: u = u128(i)
|
||||
case u128le: u = u128(i)
|
||||
|
||||
case i16be: u = u128(i)
|
||||
case i32be: u = u128(i)
|
||||
case i64be: u = u128(i)
|
||||
case u16be: u = u128(i)
|
||||
case u32be: u = u128(i)
|
||||
case u64be: u = u128(i)
|
||||
case u128be: u = u128(i)
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
@@ -28,27 +28,27 @@ make_parser_from_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, par
|
||||
}
|
||||
|
||||
|
||||
parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) {
|
||||
return parse_string(string(data), spec, parse_integers, allocator)
|
||||
parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) {
|
||||
return parse_string(string(data), spec, parse_integers, allocator, loc)
|
||||
}
|
||||
|
||||
parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) {
|
||||
parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) {
|
||||
context.allocator = allocator
|
||||
p := make_parser_from_string(data, spec, parse_integers, allocator)
|
||||
|
||||
switch p.spec {
|
||||
case .JSON:
|
||||
return parse_object(&p)
|
||||
return parse_object(&p, loc)
|
||||
case .JSON5:
|
||||
return parse_value(&p)
|
||||
return parse_value(&p, loc)
|
||||
case .SJSON:
|
||||
#partial switch p.curr_token.kind {
|
||||
case .Ident, .String:
|
||||
return parse_object_body(&p, .EOF)
|
||||
return parse_object_body(&p, .EOF, loc)
|
||||
}
|
||||
return parse_value(&p)
|
||||
return parse_value(&p, loc)
|
||||
}
|
||||
return parse_object(&p)
|
||||
return parse_object(&p, loc)
|
||||
}
|
||||
|
||||
token_end_pos :: proc(tok: Token) -> Pos {
|
||||
@@ -106,7 +106,7 @@ parse_comma :: proc(p: ^Parser) -> (do_break: bool) {
|
||||
return false
|
||||
}
|
||||
|
||||
parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
parse_value :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
|
||||
err = .None
|
||||
token := p.curr_token
|
||||
#partial switch token.kind {
|
||||
@@ -142,13 +142,13 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
|
||||
case .String:
|
||||
advance_token(p)
|
||||
return unquote_string(token, p.spec, p.allocator)
|
||||
return unquote_string(token, p.spec, p.allocator, loc)
|
||||
|
||||
case .Open_Brace:
|
||||
return parse_object(p)
|
||||
return parse_object(p, loc)
|
||||
|
||||
case .Open_Bracket:
|
||||
return parse_array(p)
|
||||
return parse_array(p, loc)
|
||||
|
||||
case:
|
||||
if p.spec != .JSON {
|
||||
@@ -176,7 +176,7 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
return
|
||||
}
|
||||
|
||||
parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
parse_array :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
|
||||
err = .None
|
||||
expect_token(p, .Open_Bracket) or_return
|
||||
|
||||
@@ -184,14 +184,14 @@ parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
array.allocator = p.allocator
|
||||
defer if err != nil {
|
||||
for elem in array {
|
||||
destroy_value(elem)
|
||||
destroy_value(elem, loc=loc)
|
||||
}
|
||||
delete(array)
|
||||
delete(array, loc)
|
||||
}
|
||||
|
||||
for p.curr_token.kind != .Close_Bracket {
|
||||
elem := parse_value(p) or_return
|
||||
append(&array, elem)
|
||||
elem := parse_value(p, loc) or_return
|
||||
append(&array, elem, loc)
|
||||
|
||||
if parse_comma(p) {
|
||||
break
|
||||
@@ -228,38 +228,39 @@ clone_string :: proc(s: string, allocator: mem.Allocator, loc := #caller_locatio
|
||||
return
|
||||
}
|
||||
|
||||
parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator) -> (key: string, err: Error) {
|
||||
parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator, loc := #caller_location) -> (key: string, err: Error) {
|
||||
tok := p.curr_token
|
||||
if p.spec != .JSON {
|
||||
if allow_token(p, .Ident) {
|
||||
return clone_string(tok.text, key_allocator)
|
||||
return clone_string(tok.text, key_allocator, loc)
|
||||
}
|
||||
}
|
||||
if tok_err := expect_token(p, .String); tok_err != nil {
|
||||
err = .Expected_String_For_Object_Key
|
||||
return
|
||||
}
|
||||
return unquote_string(tok, p.spec, key_allocator)
|
||||
return unquote_string(tok, p.spec, key_allocator, loc)
|
||||
}
|
||||
|
||||
parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, err: Error) {
|
||||
obj.allocator = p.allocator
|
||||
parse_object_body :: proc(p: ^Parser, end_token: Token_Kind, loc := #caller_location) -> (obj: Object, err: Error) {
|
||||
obj = make(Object, allocator=p.allocator, loc=loc)
|
||||
|
||||
defer if err != nil {
|
||||
for key, elem in obj {
|
||||
delete(key, p.allocator)
|
||||
destroy_value(elem)
|
||||
delete(key, p.allocator, loc)
|
||||
destroy_value(elem, loc=loc)
|
||||
}
|
||||
delete(obj)
|
||||
delete(obj, loc)
|
||||
}
|
||||
|
||||
for p.curr_token.kind != end_token {
|
||||
key := parse_object_key(p, p.allocator) or_return
|
||||
key := parse_object_key(p, p.allocator, loc) or_return
|
||||
parse_colon(p) or_return
|
||||
elem := parse_value(p) or_return
|
||||
elem := parse_value(p, loc) or_return
|
||||
|
||||
if key in obj {
|
||||
err = .Duplicate_Object_Key
|
||||
delete(key, p.allocator)
|
||||
delete(key, p.allocator, loc)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -267,7 +268,7 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er
|
||||
// inserting empty key/values into the object and for those we do not
|
||||
// want to allocate anything
|
||||
if key != "" {
|
||||
reserve_error := reserve(&obj, len(obj) + 1)
|
||||
reserve_error := reserve(&obj, len(obj) + 1, loc)
|
||||
if reserve_error == mem.Allocator_Error.Out_Of_Memory {
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
@@ -281,9 +282,9 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er
|
||||
return obj, .None
|
||||
}
|
||||
|
||||
parse_object :: proc(p: ^Parser) -> (value: Value, err: Error) {
|
||||
parse_object :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
|
||||
expect_token(p, .Open_Brace) or_return
|
||||
obj := parse_object_body(p, .Close_Brace) or_return
|
||||
obj := parse_object_body(p, .Close_Brace, loc) or_return
|
||||
expect_token(p, .Close_Brace) or_return
|
||||
return obj, .None
|
||||
}
|
||||
@@ -480,4 +481,4 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a
|
||||
}
|
||||
|
||||
return string(b[:w]), nil
|
||||
}
|
||||
}
|
||||
@@ -89,22 +89,22 @@ Error :: enum {
|
||||
|
||||
|
||||
|
||||
destroy_value :: proc(value: Value, allocator := context.allocator) {
|
||||
destroy_value :: proc(value: Value, allocator := context.allocator, loc := #caller_location) {
|
||||
context.allocator = allocator
|
||||
#partial switch v in value {
|
||||
case Object:
|
||||
for key, elem in v {
|
||||
delete(key)
|
||||
destroy_value(elem)
|
||||
delete(key, loc=loc)
|
||||
destroy_value(elem, loc=loc)
|
||||
}
|
||||
delete(v)
|
||||
delete(v, loc=loc)
|
||||
case Array:
|
||||
for elem in v {
|
||||
destroy_value(elem)
|
||||
destroy_value(elem, loc=loc)
|
||||
}
|
||||
delete(v)
|
||||
delete(v, loc=loc)
|
||||
case String:
|
||||
delete(v)
|
||||
delete(v, loc=loc)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@ json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) {
|
||||
json_name = value
|
||||
if comma_index := strings.index_byte(json_name, ','); comma_index >= 0 {
|
||||
json_name = json_name[:comma_index]
|
||||
extra = json_name[comma_index:]
|
||||
extra = value[1 + comma_index:]
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -368,7 +368,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
|
||||
|
||||
#partial switch t in ti.variant {
|
||||
case reflect.Type_Info_Struct:
|
||||
if t.is_raw_union {
|
||||
if .raw_union in t.flags {
|
||||
return UNSUPPORTED_TYPE
|
||||
}
|
||||
|
||||
@@ -475,7 +475,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
|
||||
}
|
||||
|
||||
case reflect.Type_Info_Map:
|
||||
if !reflect.is_string(t.key) {
|
||||
if !reflect.is_string(t.key) && !reflect.is_integer(t.key) {
|
||||
return UNSUPPORTED_TYPE
|
||||
}
|
||||
raw_map := (^mem.Raw_Map)(v.data)
|
||||
@@ -492,25 +492,39 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
|
||||
key, _ := parse_object_key(p, p.allocator)
|
||||
unmarshal_expect_token(p, .Colon)
|
||||
|
||||
|
||||
|
||||
mem.zero_slice(elem_backing)
|
||||
if uerr := unmarshal_value(p, map_backing_value); uerr != nil {
|
||||
delete(key, p.allocator)
|
||||
return uerr
|
||||
}
|
||||
|
||||
key_ptr := rawptr(&key)
|
||||
key_ptr: rawptr
|
||||
|
||||
key_cstr: cstring
|
||||
if reflect.is_cstring(t.key) {
|
||||
key_cstr = cstring(raw_data(key))
|
||||
key_ptr = &key_cstr
|
||||
#partial switch tk in t.key.variant {
|
||||
case runtime.Type_Info_String:
|
||||
key_ptr = rawptr(&key)
|
||||
key_cstr: cstring
|
||||
if reflect.is_cstring(t.key) {
|
||||
key_cstr = cstring(raw_data(key))
|
||||
key_ptr = &key_cstr
|
||||
}
|
||||
case runtime.Type_Info_Integer:
|
||||
i, ok := strconv.parse_i128(key)
|
||||
if !ok { return UNSUPPORTED_TYPE }
|
||||
key_ptr = rawptr(&i)
|
||||
case: return UNSUPPORTED_TYPE
|
||||
}
|
||||
|
||||
|
||||
set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data)
|
||||
if set_ptr == nil {
|
||||
delete(key, p.allocator)
|
||||
}
|
||||
|
||||
// there's no need to keep string value on the heap, since it was copied into map
|
||||
if reflect.is_integer(t.key) {
|
||||
delete(key, p.allocator)
|
||||
}
|
||||
|
||||
if parse_comma(p) {
|
||||
break map_loop
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Feoramund
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,67 @@
|
||||
package uuid
|
||||
|
||||
// A RFC 4122 Universally Unique Identifier
|
||||
Identifier :: distinct [16]u8
|
||||
|
||||
EXPECTED_LENGTH :: 8 + 4 + 4 + 4 + 12 + 4
|
||||
|
||||
VERSION_BYTE_INDEX :: 6
|
||||
VARIANT_BYTE_INDEX :: 8
|
||||
|
||||
// The number of 100-nanosecond intervals between 1582-10-15 and 1970-01-01.
|
||||
HNS_INTERVALS_BETWEEN_GREG_AND_UNIX :: 141427 * 24 * 60 * 60 * 1000 * 1000 * 10
|
||||
|
||||
VERSION_7_TIME_MASK :: 0xffffffff_ffff0000_00000000_00000000
|
||||
VERSION_7_TIME_SHIFT :: 80
|
||||
VERSION_7_COUNTER_MASK :: 0x00000000_00000fff_00000000_00000000
|
||||
VERSION_7_COUNTER_SHIFT :: 64
|
||||
|
||||
@(private)
|
||||
NO_CSPRNG_ERROR :: "The context random generator is not cryptographic. See the documentation for an example of how to set one up."
|
||||
@(private)
|
||||
BIG_CLOCK_ERROR :: "The clock sequence can only hold 14 bits of data, therefore no number greater than 16,383 (0x3FFF)."
|
||||
@(private)
|
||||
VERSION_7_BIG_COUNTER_ERROR :: "This implementation of the version 7 UUID counter can only hold 12 bits of data, therefore no number greater than 4,095 (0xFFF)."
|
||||
|
||||
Read_Error :: enum {
|
||||
None,
|
||||
Invalid_Length,
|
||||
Invalid_Hexadecimal,
|
||||
Invalid_Separator,
|
||||
}
|
||||
|
||||
Variant_Type :: enum {
|
||||
Unknown,
|
||||
Reserved_Apollo_NCS, // 0b0xx
|
||||
RFC_4122, // 0b10x
|
||||
Reserved_Microsoft_COM, // 0b110
|
||||
Reserved_Future, // 0b111
|
||||
}
|
||||
|
||||
// Name string is a fully-qualified domain name.
|
||||
@(rodata)
|
||||
Namespace_DNS := Identifier {
|
||||
0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1,
|
||||
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||
}
|
||||
|
||||
// Name string is a URL.
|
||||
@(rodata)
|
||||
Namespace_URL := Identifier {
|
||||
0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1,
|
||||
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||
}
|
||||
|
||||
// Name string is an ISO OID.
|
||||
@(rodata)
|
||||
Namespace_OID := Identifier {
|
||||
0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1,
|
||||
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||
}
|
||||
|
||||
// Name string is an X.500 DN (in DER or a text output format).
|
||||
@(rodata)
|
||||
Namespace_X500 := Identifier {
|
||||
0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1,
|
||||
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
package uuid implements Universally Unique Identifiers according to the
|
||||
standard originally outlined in RFC 4122 with additions from RFC 9562.
|
||||
|
||||
The UUIDs are textually represented and read in the following string format:
|
||||
`00000000-0000-v000-V000-000000000000`
|
||||
|
||||
`v` is where the version bits reside, and `V` is where the variant bits reside.
|
||||
The meaning of the other bits is version-dependent.
|
||||
|
||||
Outside of string representations, UUIDs are represented in memory by a 128-bit
|
||||
structure organized as an array of 16 bytes.
|
||||
|
||||
|
||||
Of the UUID versions which may make use of random number generation, a
|
||||
requirement is placed upon them that the underlying generator be
|
||||
cryptographically-secure, per RFC 9562's suggestion.
|
||||
|
||||
- Version 1 without a node argument.
|
||||
- Version 4 in all cases.
|
||||
- Version 6 without either a clock or node argument.
|
||||
- Version 7 in all cases.
|
||||
|
||||
Here's an example of how to set up one:
|
||||
|
||||
import "core:crypto"
|
||||
import "core:encoding/uuid"
|
||||
|
||||
main :: proc() {
|
||||
my_uuid: uuid.Identifier
|
||||
|
||||
{
|
||||
// This scope will have a CSPRNG.
|
||||
context.random_generator = crypto.random_generator()
|
||||
my_uuid = uuid.generate_v7()
|
||||
}
|
||||
|
||||
// Back to the default random number generator.
|
||||
}
|
||||
|
||||
|
||||
For more information on the specifications, see here:
|
||||
- https://www.rfc-editor.org/rfc/rfc4122.html
|
||||
- https://www.rfc-editor.org/rfc/rfc9562.html
|
||||
*/
|
||||
package uuid
|
||||
@@ -0,0 +1,333 @@
|
||||
package uuid
|
||||
|
||||
import "base:runtime"
|
||||
import "core:crypto/hash"
|
||||
import "core:math/rand"
|
||||
import "core:time"
|
||||
|
||||
/*
|
||||
Generate a version 1 UUID.
|
||||
|
||||
Inputs:
|
||||
- clock_seq: The clock sequence, a number which must be initialized to a random number once in the lifetime of a system.
|
||||
- node: An optional 48-bit spatially unique identifier, specified to be the IEEE 802 address of the system.
|
||||
If one is not provided or available, 48 bits of random state will take its place.
|
||||
- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v1 :: proc(clock_seq: u16, node: Maybe([6]u8) = nil, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||
assert(clock_seq <= 0x3FFF, BIG_CLOCK_ERROR)
|
||||
unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100
|
||||
|
||||
uuid_timestamp := cast(u64le)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals)
|
||||
uuid_timestamp_octets := transmute([8]u8)uuid_timestamp
|
||||
|
||||
result[0] = uuid_timestamp_octets[0]
|
||||
result[1] = uuid_timestamp_octets[1]
|
||||
result[2] = uuid_timestamp_octets[2]
|
||||
result[3] = uuid_timestamp_octets[3]
|
||||
result[4] = uuid_timestamp_octets[4]
|
||||
result[5] = uuid_timestamp_octets[5]
|
||||
|
||||
result[6] = uuid_timestamp_octets[6] >> 4
|
||||
result[7] = uuid_timestamp_octets[6] << 4 | uuid_timestamp_octets[7]
|
||||
|
||||
if realized_node, ok := node.?; ok {
|
||||
mutable_node := realized_node
|
||||
runtime.mem_copy_non_overlapping(&result[10], &mutable_node[0], 6)
|
||||
} else {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
bytes_generated := rand.read(result[10:])
|
||||
assert(bytes_generated == 6, "RNG failed to generate 6 bytes for UUID v1.")
|
||||
}
|
||||
|
||||
result[VERSION_BYTE_INDEX] |= 0x10
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
result[8] |= cast(u8)(clock_seq & 0x3F00 >> 8)
|
||||
result[9] = cast(u8)clock_seq
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 4 UUID.
|
||||
|
||||
This UUID will be pseudorandom, save for 6 pre-determined version and variant bits.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v4 :: proc() -> (result: Identifier) {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
bytes_generated := rand.read(result[:])
|
||||
assert(bytes_generated == 16, "RNG failed to generate 16 bytes for UUID v4.")
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x40
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 6 UUID.
|
||||
|
||||
Inputs:
|
||||
- clock_seq: The clock sequence from version 1, now made optional.
|
||||
If unspecified, it will be replaced with random bits.
|
||||
- node: An optional 48-bit spatially unique identifier, specified to be the IEEE 802 address of the system.
|
||||
If one is not provided or available, 48 bits of random state will take its place.
|
||||
- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v6 :: proc(clock_seq: Maybe(u16) = nil, node: Maybe([6]u8) = nil, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||
unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100
|
||||
|
||||
uuid_timestamp := cast(u128be)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals)
|
||||
|
||||
result = transmute(Identifier)(
|
||||
uuid_timestamp & 0x0FFFFFFF_FFFFF000 << 68 |
|
||||
uuid_timestamp & 0x00000000_00000FFF << 64
|
||||
)
|
||||
|
||||
if realized_clock_seq, ok := clock_seq.?; ok {
|
||||
assert(realized_clock_seq <= 0x3FFF, BIG_CLOCK_ERROR)
|
||||
result[8] |= cast(u8)(realized_clock_seq & 0x3F00 >> 8)
|
||||
result[9] = cast(u8)realized_clock_seq
|
||||
} else {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
temporary: [2]u8
|
||||
bytes_generated := rand.read(temporary[:])
|
||||
assert(bytes_generated == 2, "RNG failed to generate 2 bytes for UUID v1.")
|
||||
result[8] |= temporary[0] & 0x3F
|
||||
result[9] = temporary[1]
|
||||
}
|
||||
|
||||
if realized_node, ok := node.?; ok {
|
||||
mutable_node := realized_node
|
||||
runtime.mem_copy_non_overlapping(&result[10], &mutable_node[0], 6)
|
||||
} else {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
bytes_generated := rand.read(result[10:])
|
||||
assert(bytes_generated == 6, "RNG failed to generate 6 bytes for UUID v1.")
|
||||
}
|
||||
|
||||
result[VERSION_BYTE_INDEX] |= 0x60
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 7 UUID.
|
||||
|
||||
This UUID will be pseudorandom, save for 6 pre-determined version and variant
|
||||
bits and a 48-bit timestamp.
|
||||
|
||||
It is designed with time-based sorting in mind, such as for database usage, as
|
||||
the highest bits are allocated from the timestamp of when it is created.
|
||||
|
||||
Inputs:
|
||||
- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v7_basic :: proc(timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
unix_time_in_milliseconds := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 1e6
|
||||
|
||||
result = transmute(Identifier)(cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT)
|
||||
|
||||
bytes_generated := rand.read(result[6:])
|
||||
assert(bytes_generated == 10, "RNG failed to generate 10 bytes for UUID v7.")
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x70
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 7 UUID that has an incremented counter.
|
||||
|
||||
This UUID will be pseudorandom, save for 6 pre-determined version and variant
|
||||
bits, a 48-bit timestamp, and 12 bits of counter state.
|
||||
|
||||
It is designed with time-based sorting in mind, such as for database usage, as
|
||||
the highest bits are allocated from the timestamp of when it is created.
|
||||
|
||||
This procedure is preferable if you are generating hundreds or thousands of
|
||||
UUIDs as a batch within the span of a millisecond. Do note that the counter
|
||||
only has 12 bits of state, thus `counter` cannot exceed the number 4,095.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:uuid"
|
||||
|
||||
// Create a batch of UUIDs all at once.
|
||||
batch: [dynamic]uuid.Identifier
|
||||
|
||||
for i: u16 = 0; i < 1000; i += 1 {
|
||||
my_uuid := uuid.generate_v7_counter(i)
|
||||
append(&batch, my_uuid)
|
||||
}
|
||||
|
||||
Inputs:
|
||||
- counter: A 12-bit value which should be incremented each time a UUID is generated in a batch.
|
||||
- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v7_with_counter :: proc(counter: u16, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
assert(counter <= 0x0fff, VERSION_7_BIG_COUNTER_ERROR)
|
||||
unix_time_in_milliseconds := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 1e6
|
||||
|
||||
result = transmute(Identifier)(
|
||||
cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT |
|
||||
cast(u128be)counter << VERSION_7_COUNTER_SHIFT
|
||||
)
|
||||
|
||||
bytes_generated := rand.read(result[8:])
|
||||
assert(bytes_generated == 8, "RNG failed to generate 8 bytes for UUID v7.")
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x70
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
generate_v7 :: proc {
|
||||
generate_v7_basic,
|
||||
generate_v7_with_counter,
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 8 UUID using a specific hashing algorithm.
|
||||
|
||||
This UUID is generated by hashing a name with a namespace.
|
||||
|
||||
Note that all version 8 UUIDs are for experimental or vendor-specific use
|
||||
cases, per the specification. This use case in particular is for offering a
|
||||
non-legacy alternative to UUID versions 3 and 5.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in this package.
|
||||
- name: The byte slice which will be hashed with the namespace.
|
||||
- algorithm: A hashing algorithm from `core:crypto/hash`.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
|
||||
Example:
|
||||
import "core:crypto/hash"
|
||||
import "core:encoding/uuid"
|
||||
import "core:fmt"
|
||||
|
||||
main :: proc() {
|
||||
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
|
||||
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
|
||||
fmt.println(my_uuid_string)
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
3730f688-4bff-8dce-9cbf-74a3960c5703
|
||||
|
||||
*/
|
||||
generate_v8_hash_bytes :: proc(
|
||||
namespace: Identifier,
|
||||
name: []byte,
|
||||
algorithm: hash.Algorithm,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
// 128 bytes should be enough for the foreseeable future.
|
||||
digest: [128]byte
|
||||
|
||||
assert(hash.DIGEST_SIZES[algorithm] >= 16, "Per RFC 9562, the hashing algorithm used must generate a digest of 128 bits or larger.")
|
||||
assert(hash.DIGEST_SIZES[algorithm] < len(digest), "Digest size is too small for this algorithm. The buffer must be increased.")
|
||||
|
||||
hash_context: hash.Context
|
||||
hash.init(&hash_context, algorithm)
|
||||
|
||||
mutable_namespace := namespace
|
||||
hash.update(&hash_context, mutable_namespace[:])
|
||||
hash.update(&hash_context, name[:])
|
||||
hash.final(&hash_context, digest[:])
|
||||
|
||||
runtime.mem_copy_non_overlapping(&result, &digest, 16)
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x80
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 8 UUID using a specific hashing algorithm.
|
||||
|
||||
This UUID is generated by hashing a name with a namespace.
|
||||
|
||||
Note that all version 8 UUIDs are for experimental or vendor-specific use
|
||||
cases, per the specification. This use case in particular is for offering a
|
||||
non-legacy alternative to UUID versions 3 and 5.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in this package.
|
||||
- name: The string which will be hashed with the namespace.
|
||||
- algorithm: A hashing algorithm from `core:crypto/hash`.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
|
||||
Example:
|
||||
import "core:crypto/hash"
|
||||
import "core:encoding/uuid"
|
||||
import "core:fmt"
|
||||
|
||||
main :: proc() {
|
||||
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
|
||||
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
|
||||
fmt.println(my_uuid_string)
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
3730f688-4bff-8dce-9cbf-74a3960c5703
|
||||
|
||||
*/
|
||||
generate_v8_hash_string :: proc(
|
||||
namespace: Identifier,
|
||||
name: string,
|
||||
algorithm: hash.Algorithm,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
return generate_v8_hash_bytes(namespace, transmute([]byte)name, algorithm)
|
||||
}
|
||||
|
||||
generate_v8_hash :: proc {
|
||||
generate_v8_hash_bytes,
|
||||
generate_v8_hash_string,
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
package uuid/legacy implements versions 3 and 5 of UUID generation, both of
|
||||
which are using hashing algorithms (MD5 and SHA1, respectively) that are known
|
||||
these days to no longer be secure.
|
||||
*/
|
||||
package uuid_legacy
|
||||
|
||||
import "base:runtime"
|
||||
import "core:crypto/legacy/md5"
|
||||
import "core:crypto/legacy/sha1"
|
||||
import "core:encoding/uuid"
|
||||
|
||||
Identifier :: uuid.Identifier
|
||||
VERSION_BYTE_INDEX :: uuid.VERSION_BYTE_INDEX
|
||||
VARIANT_BYTE_INDEX :: uuid.VARIANT_BYTE_INDEX
|
||||
|
||||
|
||||
/*
|
||||
Generate a version 3 UUID.
|
||||
|
||||
This UUID is generated with a MD5 hash of a name and a namespace.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||
- name: The byte slice which will be hashed with the namespace.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v3_bytes :: proc(
|
||||
namespace: Identifier,
|
||||
name: []byte,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
namespace := namespace
|
||||
|
||||
ctx: md5.Context
|
||||
md5.init(&ctx)
|
||||
md5.update(&ctx, namespace[:])
|
||||
md5.update(&ctx, name)
|
||||
md5.final(&ctx, result[:])
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x30
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 3 UUID.
|
||||
|
||||
This UUID is generated with a MD5 hash of a name and a namespace.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||
- name: The string which will be hashed with the namespace.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v3_string :: proc(
|
||||
namespace: Identifier,
|
||||
name: string,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
return generate_v3_bytes(namespace, transmute([]byte)name)
|
||||
}
|
||||
|
||||
generate_v3 :: proc {
|
||||
generate_v3_bytes,
|
||||
generate_v3_string,
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 5 UUID.
|
||||
|
||||
This UUID is generated with a SHA1 hash of a name and a namespace.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||
- name: The byte slice which will be hashed with the namespace.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v5_bytes :: proc(
|
||||
namespace: Identifier,
|
||||
name: []byte,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
namespace := namespace
|
||||
digest: [sha1.DIGEST_SIZE]byte
|
||||
|
||||
ctx: sha1.Context
|
||||
sha1.init(&ctx)
|
||||
sha1.update(&ctx, namespace[:])
|
||||
sha1.update(&ctx, name)
|
||||
sha1.final(&ctx, digest[:])
|
||||
|
||||
runtime.mem_copy_non_overlapping(&result, &digest, 16)
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x50
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 5 UUID.
|
||||
|
||||
This UUID is generated with a SHA1 hash of a name and a namespace.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||
- name: The string which will be hashed with the namespace.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v5_string :: proc(
|
||||
namespace: Identifier,
|
||||
name: string,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
return generate_v5_bytes(namespace, transmute([]byte)name)
|
||||
}
|
||||
|
||||
generate_v5 :: proc {
|
||||
generate_v5_bytes,
|
||||
generate_v5_string,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
package uuid
|
||||
|
||||
import "base:runtime"
|
||||
import "core:time"
|
||||
|
||||
/*
|
||||
Convert a string to a UUID.
|
||||
|
||||
Inputs:
|
||||
- str: A string in the 8-4-4-4-12 format.
|
||||
|
||||
Returns:
|
||||
- id: The converted identifier, or `nil` if there is an error.
|
||||
- error: A description of the error, or `nil` if successful.
|
||||
*/
|
||||
read :: proc "contextless" (str: string) -> (id: Identifier, error: Read_Error) #no_bounds_check {
|
||||
// Only exact-length strings are acceptable.
|
||||
if len(str) != EXPECTED_LENGTH {
|
||||
return {}, .Invalid_Length
|
||||
}
|
||||
|
||||
// Check ahead to see if the separators are in the right places.
|
||||
if str[8] != '-' || str[13] != '-' || str[18] != '-' || str[23] != '-' {
|
||||
return {}, .Invalid_Separator
|
||||
}
|
||||
|
||||
read_nibble :: proc "contextless" (nibble: u8) -> u8 {
|
||||
switch nibble {
|
||||
case '0' ..= '9':
|
||||
return nibble - '0'
|
||||
case 'A' ..= 'F':
|
||||
return nibble - 'A' + 10
|
||||
case 'a' ..= 'f':
|
||||
return nibble - 'a' + 10
|
||||
case:
|
||||
// Return an error value.
|
||||
return 0xFF
|
||||
}
|
||||
}
|
||||
|
||||
index := 0
|
||||
octet_index := 0
|
||||
|
||||
CHUNKS :: [5]int{8, 4, 4, 4, 12}
|
||||
|
||||
for chunk in CHUNKS {
|
||||
for i := index; i < index + chunk; i += 2 {
|
||||
high := read_nibble(str[i])
|
||||
low := read_nibble(str[i + 1])
|
||||
|
||||
if high | low > 0xF {
|
||||
return {}, .Invalid_Hexadecimal
|
||||
}
|
||||
|
||||
id[octet_index] = low | high << 4
|
||||
octet_index += 1
|
||||
}
|
||||
|
||||
index += chunk + 1
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Get the version of a UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- number: The version number.
|
||||
*/
|
||||
version :: proc "contextless" (id: Identifier) -> (number: int) #no_bounds_check {
|
||||
return cast(int)(id[VERSION_BYTE_INDEX] & 0xF0 >> 4)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the variant of a UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- variant: The variant type.
|
||||
*/
|
||||
variant :: proc "contextless" (id: Identifier) -> (variant: Variant_Type) #no_bounds_check {
|
||||
switch {
|
||||
case id[VARIANT_BYTE_INDEX] & 0x80 == 0:
|
||||
return .Reserved_Apollo_NCS
|
||||
case id[VARIANT_BYTE_INDEX] & 0xC0 == 0x80:
|
||||
return .RFC_4122
|
||||
case id[VARIANT_BYTE_INDEX] & 0xE0 == 0xC0:
|
||||
return .Reserved_Microsoft_COM
|
||||
case id[VARIANT_BYTE_INDEX] & 0xF0 == 0xE0:
|
||||
return .Reserved_Future
|
||||
case:
|
||||
return .Unknown
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Get the clock sequence of a version 1 or version 6 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- clock_seq: The 14-bit clock sequence field.
|
||||
*/
|
||||
clock_seq :: proc "contextless" (id: Identifier) -> (clock_seq: u16) {
|
||||
return cast(u16)id[9] | cast(u16)id[8] & 0x3F << 8
|
||||
}
|
||||
|
||||
/*
|
||||
Get the node of a version 1 or version 6 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- node: The 48-bit spatially unique identifier.
|
||||
*/
|
||||
node :: proc "contextless" (id: Identifier) -> (node: [6]u8) {
|
||||
mutable_id := id
|
||||
runtime.mem_copy_non_overlapping(&node, &mutable_id[10], 6)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Get the raw timestamp of a version 1 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
|
||||
*/
|
||||
raw_time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
|
||||
timestamp_octets: [8]u8
|
||||
|
||||
timestamp_octets[0] = id[0]
|
||||
timestamp_octets[1] = id[1]
|
||||
timestamp_octets[2] = id[2]
|
||||
timestamp_octets[3] = id[3]
|
||||
timestamp_octets[4] = id[4]
|
||||
timestamp_octets[5] = id[5]
|
||||
|
||||
timestamp_octets[6] = id[6] << 4 | id[7] >> 4
|
||||
timestamp_octets[7] = id[7] & 0xF
|
||||
|
||||
return cast(u64)transmute(u64le)timestamp_octets
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Get the timestamp of a version 1 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp of the UUID.
|
||||
*/
|
||||
time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
|
||||
return time.from_nanoseconds(cast(i64)(raw_time_v1(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the raw timestamp of a version 6 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
|
||||
*/
|
||||
raw_time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
|
||||
temporary := transmute(u128be)id
|
||||
|
||||
timestamp |= cast(u64)(temporary & 0xFFFFFFFF_FFFF0000_00000000_00000000 >> 68)
|
||||
timestamp |= cast(u64)(temporary & 0x00000000_00000FFF_00000000_00000000 >> 64)
|
||||
|
||||
return timestamp
|
||||
}
|
||||
|
||||
/*
|
||||
Get the timestamp of a version 6 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
|
||||
*/
|
||||
time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
|
||||
return time.from_nanoseconds(cast(i64)(raw_time_v6(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the raw timestamp of a version 7 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp, in milliseconds since the UNIX epoch.
|
||||
*/
|
||||
raw_time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
|
||||
time_bits := transmute(u128be)id & VERSION_7_TIME_MASK
|
||||
return cast(u64)(time_bits >> VERSION_7_TIME_SHIFT)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the timestamp of a version 7 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp, in milliseconds since the UNIX epoch.
|
||||
*/
|
||||
time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
|
||||
return time.from_nanoseconds(cast(i64)raw_time_v7(id) * 1e6)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the 12-bit counter value of a version 7 UUID.
|
||||
|
||||
The UUID must have been generated with a counter, otherwise this procedure will
|
||||
return random bits.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- counter: The 12-bit counter value.
|
||||
*/
|
||||
counter_v7 :: proc "contextless" (id: Identifier) -> (counter: u16) {
|
||||
counter_bits := transmute(u128be)id & VERSION_7_COUNTER_MASK
|
||||
return cast(u16)(counter_bits >> VERSION_7_COUNTER_SHIFT)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package uuid
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
/*
|
||||
Stamp a 128-bit integer as being a valid version 8 UUID.
|
||||
|
||||
Per the specification, all version 8 UUIDs are either for experimental or
|
||||
vendor-specific purposes. This procedure allows for converting arbitrary data
|
||||
into custom UUIDs.
|
||||
|
||||
Inputs:
|
||||
- integer: Any integer type.
|
||||
|
||||
Returns:
|
||||
- result: A valid version 8 UUID.
|
||||
*/
|
||||
stamp_v8_int :: proc(#any_int integer: u128) -> (result: Identifier) {
|
||||
result = transmute(Identifier)cast(u128be)integer
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x80
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Stamp an array of 16 bytes as being a valid version 8 UUID.
|
||||
|
||||
Per the specification, all version 8 UUIDs are either for experimental or
|
||||
vendor-specific purposes. This procedure allows for converting arbitrary data
|
||||
into custom UUIDs.
|
||||
|
||||
Inputs:
|
||||
- array: An array of 16 bytes.
|
||||
|
||||
Returns:
|
||||
- result: A valid version 8 UUID.
|
||||
*/
|
||||
stamp_v8_array :: proc(array: [16]u8) -> (result: Identifier) {
|
||||
result = Identifier(array)
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x80
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Stamp a slice of bytes as being a valid version 8 UUID.
|
||||
|
||||
If the slice is less than 16 bytes long, the data available will be used.
|
||||
If it is longer than 16 bytes, only the first 16 will be used.
|
||||
|
||||
This procedure does not modify the underlying slice.
|
||||
|
||||
Per the specification, all version 8 UUIDs are either for experimental or
|
||||
vendor-specific purposes. This procedure allows for converting arbitrary data
|
||||
into custom UUIDs.
|
||||
|
||||
Inputs:
|
||||
- slice: A slice of bytes.
|
||||
|
||||
Returns:
|
||||
- result: A valid version 8 UUID.
|
||||
*/
|
||||
stamp_v8_slice :: proc(slice: []u8) -> (result: Identifier) {
|
||||
runtime.mem_copy_non_overlapping(&result, &slice[0], min(16, len(slice)))
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x80
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
stamp_v8 :: proc {
|
||||
stamp_v8_int,
|
||||
stamp_v8_array,
|
||||
stamp_v8_slice,
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package uuid
|
||||
|
||||
import "base:runtime"
|
||||
import "core:io"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
|
||||
/*
|
||||
Write a UUID in the 8-4-4-4-12 format.
|
||||
|
||||
This procedure performs error checking with every byte written.
|
||||
|
||||
If you can guarantee beforehand that your stream has enough space to hold the
|
||||
UUID (32 bytes), then it is better to use `unsafe_write` instead as that will
|
||||
be faster.
|
||||
|
||||
Inputs:
|
||||
- w: A writable stream.
|
||||
- id: The identifier to convert.
|
||||
|
||||
Returns:
|
||||
- error: An `io` error, if one occurred, otherwise `nil`.
|
||||
*/
|
||||
write :: proc(w: io.Writer, id: Identifier) -> (error: io.Error) #no_bounds_check {
|
||||
write_octet :: proc (w: io.Writer, octet: u8) -> io.Error #no_bounds_check {
|
||||
high_nibble := octet >> 4
|
||||
low_nibble := octet & 0xF
|
||||
|
||||
io.write_byte(w, strconv.digits[high_nibble]) or_return
|
||||
io.write_byte(w, strconv.digits[low_nibble]) or_return
|
||||
return nil
|
||||
}
|
||||
|
||||
for index in 0 ..< 4 { write_octet(w, id[index]) or_return }
|
||||
io.write_byte(w, '-') or_return
|
||||
for index in 4 ..< 6 { write_octet(w, id[index]) or_return }
|
||||
io.write_byte(w, '-') or_return
|
||||
for index in 6 ..< 8 { write_octet(w, id[index]) or_return }
|
||||
io.write_byte(w, '-') or_return
|
||||
for index in 8 ..< 10 { write_octet(w, id[index]) or_return }
|
||||
io.write_byte(w, '-') or_return
|
||||
for index in 10 ..< 16 { write_octet(w, id[index]) or_return }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Write a UUID in the 8-4-4-4-12 format.
|
||||
|
||||
This procedure performs no error checking on the underlying stream.
|
||||
|
||||
Inputs:
|
||||
- w: A writable stream.
|
||||
- id: The identifier to convert.
|
||||
*/
|
||||
unsafe_write :: proc(w: io.Writer, id: Identifier) #no_bounds_check {
|
||||
write_octet :: proc (w: io.Writer, octet: u8) #no_bounds_check {
|
||||
high_nibble := octet >> 4
|
||||
low_nibble := octet & 0xF
|
||||
|
||||
io.write_byte(w, strconv.digits[high_nibble])
|
||||
io.write_byte(w, strconv.digits[low_nibble])
|
||||
}
|
||||
|
||||
for index in 0 ..< 4 { write_octet(w, id[index]) }
|
||||
io.write_byte(w, '-')
|
||||
for index in 4 ..< 6 { write_octet(w, id[index]) }
|
||||
io.write_byte(w, '-')
|
||||
for index in 6 ..< 8 { write_octet(w, id[index]) }
|
||||
io.write_byte(w, '-')
|
||||
for index in 8 ..< 10 { write_octet(w, id[index]) }
|
||||
io.write_byte(w, '-')
|
||||
for index in 10 ..< 16 { write_octet(w, id[index]) }
|
||||
}
|
||||
|
||||
/*
|
||||
Convert a UUID to a string in the 8-4-4-4-12 format.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- id: The identifier to convert.
|
||||
- allocator: (default: context.allocator)
|
||||
- loc: The caller location for debugging purposes (default: #caller_location)
|
||||
|
||||
Returns:
|
||||
- str: The allocated and converted string.
|
||||
- error: An optional allocator error if one occured, `nil` otherwise.
|
||||
*/
|
||||
to_string_allocated :: proc(
|
||||
id: Identifier,
|
||||
allocator := context.allocator,
|
||||
loc := #caller_location,
|
||||
) -> (
|
||||
str: string,
|
||||
error: runtime.Allocator_Error,
|
||||
) #optional_allocator_error {
|
||||
buf := make([]byte, EXPECTED_LENGTH, allocator, loc) or_return
|
||||
builder := strings.builder_from_bytes(buf[:])
|
||||
unsafe_write(strings.to_writer(&builder), id)
|
||||
return strings.to_string(builder), nil
|
||||
}
|
||||
|
||||
/*
|
||||
Convert a UUID to a string in the 8-4-4-4-12 format.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier to convert.
|
||||
- buffer: A byte buffer to store the result. Must be at least 32 bytes large.
|
||||
- loc: The caller location for debugging purposes (default: #caller_location)
|
||||
|
||||
Returns:
|
||||
- str: The converted string which will be stored in `buffer`.
|
||||
*/
|
||||
to_string_buffer :: proc(
|
||||
id: Identifier,
|
||||
buffer: []byte,
|
||||
loc := #caller_location,
|
||||
) -> (
|
||||
str: string,
|
||||
) {
|
||||
assert(len(buffer) >= EXPECTED_LENGTH, "The buffer provided is not at least 32 bytes large.", loc)
|
||||
builder := strings.builder_from_bytes(buffer)
|
||||
unsafe_write(strings.to_writer(&builder), id)
|
||||
return strings.to_string(builder)
|
||||
}
|
||||
|
||||
to_string :: proc {
|
||||
to_string_allocated,
|
||||
to_string_buffer,
|
||||
}
|
||||
@@ -33,7 +33,7 @@ print :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error
|
||||
written += fmt.wprintf(writer, "[DOCTYPE] %v\n", doc.doctype.ident)
|
||||
|
||||
if len(doc.doctype.rest) > 0 {
|
||||
fmt.wprintf(writer, "\t%v\n", doc.doctype.rest)
|
||||
fmt.wprintf(writer, "\t%v\n", doc.doctype.rest)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ print :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error
|
||||
}
|
||||
|
||||
if len(doc.elements) > 0 {
|
||||
fmt.wprintln(writer, " --- ")
|
||||
print_element(writer, doc, 0)
|
||||
fmt.wprintln(writer, " --- ")
|
||||
}
|
||||
fmt.wprintln(writer, " --- ")
|
||||
print_element(writer, doc, 0)
|
||||
fmt.wprintln(writer, " --- ")
|
||||
}
|
||||
|
||||
return written, .None
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user