diff --git a/internal/subtle/xor.go b/internal/subtle/xor.go deleted file mode 100644 index ceb121d..0000000 --- a/internal/subtle/xor.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package subtle - -// XORBytes sets dst[i] = x[i] ^ y[i] for all i < n = min(len(x), len(y)), -// returning n, the number of bytes written to dst. -// If dst does not have length at least n, -// XORBytes panics without writing anything to dst. -// -// will use golang SDK directly later. -func XORBytes(dst, x, y []byte) int { - n := len(x) - if len(y) < n { - n = len(y) - } - if n == 0 { - return 0 - } - if n > len(dst) { - panic("subtle.XORBytes: dst too short") - } - xorBytes(&dst[0], &x[0], &y[0], n) // arch-specific - return n -} diff --git a/internal/subtle/xor_amd64.go b/internal/subtle/xor_amd64.go index b7ab835..ef4ad20 100644 --- a/internal/subtle/xor_amd64.go +++ b/internal/subtle/xor_amd64.go @@ -7,5 +7,22 @@ package subtle +// XORBytes xors the bytes in a and b. The destination should have enough +// space, otherwise XORBytes will panic. Returns the number of bytes xor'd. +func XORBytes(dst, a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + if n == 0 { + return 0 + } + if n > len(dst) { + panic("subtle.XORBytes: dst too short") + } + xorBytes(&dst[0], &a[0], &b[0], n) // amd64 must have SSE2 + return n +} + //go:noescape func xorBytes(dst, a, b *byte, n int) diff --git a/internal/subtle/xor_arm64.go b/internal/subtle/xor_arm64.go index 9d0bc8f..28664d9 100644 --- a/internal/subtle/xor_arm64.go +++ b/internal/subtle/xor_arm64.go @@ -7,5 +7,23 @@ package subtle +// XORBytes xors the bytes in a and b. The destination should have enough +// space, otherwise XORBytes will panic. Returns the number of bytes xor'd. +func XORBytes(dst, a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + if n == 0 { + return 0 + } + if n > len(dst) { + panic("subtle.XORBytes: dst too short") + } + + xorBytes(&dst[0], &a[0], &b[0], n) + return n +} + //go:noescape func xorBytes(dst, a, b *byte, n int) diff --git a/internal/subtle/xor_generic.go b/internal/subtle/xor_generic.go index ccb8fda..2128400 100644 --- a/internal/subtle/xor_generic.go +++ b/internal/subtle/xor_generic.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // -//go:build (!amd64 && !arm64) || purego +//go:build !amd64 && !arm64 || purego // +build !amd64,!arm64 purego package subtle @@ -12,48 +12,63 @@ import ( "unsafe" ) -const wordSize = unsafe.Sizeof(uintptr(0)) +const wordSize = int(unsafe.Sizeof(uintptr(0))) const supportsUnaligned = runtime.GOARCH == "386" || - runtime.GOARCH == "amd64" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "s390x" -func xorBytes(dstb, xb, yb *byte, n int) { - // xorBytes assembly is written using pointers and n. Back to slices. - dst := unsafe.Slice(dstb, n) - x := unsafe.Slice(xb, n) - y := unsafe.Slice(yb, n) +// XORBytes xors the bytes in a and b. The destination should have enough +// space, otherwise XORBytes will panic. Returns the number of bytes xor'd. +func XORBytes(dst, a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + if n == 0 { + return 0 + } + if n > len(dst) { + panic("subtle.XORBytes: dst too short") + } - if supportsUnaligned || aligned(dstb, xb, yb) { - xorLoop(words(dst), words(x), words(y)) - if uintptr(n)%wordSize == 0 { - return + switch { + case supportsUnaligned: + fastXORBytes(dst, a, b, n) + default: + // TODO(hanwen): if (dst, a, b) have common alignment + // we could still try fastXORBytes. It is not clear + // how often this happens, and it's only worth it if + // the block encryption itself is hardware + // accelerated. + safeXORBytes(dst, a, b, n) + } + return n +} + +// fastXORBytes xors in bulk. It only works on architectures that +// support unaligned read/writes. +// n needs to be smaller or equal than the length of a and b. +func fastXORBytes(dst, a, b []byte, n int) { + w := n / wordSize + if w > 0 { + dw := *(*[]uintptr)(unsafe.Pointer(&dst)) + aw := *(*[]uintptr)(unsafe.Pointer(&a)) + bw := *(*[]uintptr)(unsafe.Pointer(&b)) + for i := 0; i < w; i++ { + dw[i] = aw[i] ^ bw[i] } - done := n &^ int(wordSize-1) - dst = dst[done:] - x = x[done:] - y = y[done:] } - xorLoop(dst, x, y) -} -// aligned reports whether dst, x, and y are all word-aligned pointers. -func aligned(dst, x, y *byte) bool { - return (uintptr(unsafe.Pointer(dst))|uintptr(unsafe.Pointer(x))|uintptr(unsafe.Pointer(y)))&(wordSize-1) == 0 -} - -// words returns a []uintptr pointing at the same data as x, -// with any trailing partial word removed. -func words(x []byte) []uintptr { - return unsafe.Slice((*uintptr)(unsafe.Pointer(&x[0])), uintptr(len(x))/wordSize) -} - -func xorLoop[T byte | uintptr](dst, x, y []T) { - x = x[:len(dst)] // remove bounds check in loop - y = y[:len(dst)] // remove bounds check in loop - for i := range dst { - dst[i] = x[i] ^ y[i] + for i := (n - n%wordSize); i < n; i++ { + dst[i] = a[i] ^ b[i] + } +} + +// n needs to be smaller or equal than the length of a and b. +func safeXORBytes(dst, a, b []byte, n int) { + for i := 0; i < n; i++ { + dst[i] = a[i] ^ b[i] } }