Merge branch 'master' into syscall-fix

This commit is contained in:
gingerBill
2024-07-19 11:43:56 +01:00
committed by GitHub
653 changed files with 66513 additions and 22262 deletions
+6 -6
View File
@@ -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
+4
View File
@@ -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
View File
@@ -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
}
+25
View File
@@ -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)
}
+1 -1
View File
@@ -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
View File
@@ -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))
+9 -7
View File
@@ -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
}
}
+1 -1
View 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
+1 -1
View File
@@ -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
View File
@@ -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)
+4 -7
View File
@@ -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
}
+13 -13
View File
@@ -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}
+4 -7
View File
@@ -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)
}
}
}
+6 -3
View File
@@ -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).
+46
View File
@@ -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
}
}
+1 -2
View File
@@ -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
}
+4 -4
View File
@@ -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))
+568
View File
@@ -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
}
+4 -4
View File
@@ -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))
}
+28
View File
@@ -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}
+86
View File
@@ -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))
}
+265
View File
@@ -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)
}
+135
View File
@@ -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)
}
+95
View File
@@ -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:])
}
+179
View File
@@ -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))
}
+136
View File
@@ -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)
}
+75
View File
@@ -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)
}
}
+43
View File
@@ -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)
}
+281
View File
@@ -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))
}
+2 -2
View File
@@ -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" (
+2 -2
View File
@@ -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" (
+21
View File
@@ -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
+201
View File
@@ -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])
}
}
}
+151
View File
@@ -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])
}
}
}
+57
View File
@@ -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
}
+58
View File
@@ -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)
}
+269
View File
@@ -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))
}
+243
View File
@@ -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
}
+41
View File
@@ -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))
}
+44
View File
@@ -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)
}
+18
View File
@@ -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)
}
+5 -3
View File
@@ -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
View File
@@ -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,
}
}
+5 -5
View File
@@ -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))
}
+5 -5
View File
@@ -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))
}
+4 -5
View File
@@ -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 -6
View File
@@ -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
}
+4 -4
View File
@@ -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
}
+4 -4
View File
@@ -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
}
+5 -5
View File
@@ -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
}
+13 -14
View File
@@ -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
}
+3 -3
View File
@@ -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_)
}
+5 -5
View File
@@ -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))
}
+8 -8
View File
@@ -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))
}
+7 -7
View File
@@ -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)
+51
View File
@@ -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
+47
View File
@@ -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)
}
+195
View File
@@ -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
}
+20
View File
@@ -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
}
+68
View File
@@ -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
View File
@@ -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()
}
}
+1 -1
View File
@@ -16,4 +16,4 @@ _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found
_last_error :: proc() -> string {
return ""
}
}
+2 -2
View File
@@ -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
}
}
+7 -9
View File
@@ -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
}
}
+137
View File
@@ -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.
+20
View File
@@ -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
View File
@@ -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
}
+2 -2
View File
@@ -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 {
+70 -69
View File
@@ -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)
}
}
+33 -5
View File
@@ -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
+58 -17
View File
@@ -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)
-1
View File
@@ -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)
+68 -73
View File
@@ -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)
}
}
}
+88
View File
@@ -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)
}
}
}
+26 -3
View File
@@ -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
}
}
+54 -69
View File
@@ -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 }
File diff suppressed because it is too large Load Diff
+5 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
}
+191
View File
@@ -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
}
+83 -63
View File
@@ -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
}
+33 -32
View File
@@ -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
}
}
+7 -7
View File
@@ -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)
}
}
+24 -10
View File
@@ -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
+28
View File
@@ -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.
+67
View File
@@ -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,
}
+46
View File
@@ -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
+333
View File
@@ -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,
}
+146
View File
@@ -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,
}
+242
View File
@@ -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)
}
+89
View File
@@ -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,
}
+131
View File
@@ -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,
}
+5 -5
View File
@@ -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