Documentation
¶
Overview ¶
Package yencsimd decodes yEnc articles from Usenet (NNTP), in pure Go, with SIMD on amd64 and arm64 when built with GOEXPERIMENT=simd.
Most programs need one of two functions:
- Decode(article) for an article in memory: it returns the data and its Meta (name, size, part) and checks size and CRC32.
- NewReader(r) to stream an article from an NNTP connection: pass the connection's *bufio.Reader after the "222" status line (with net/textproto: tp.R, not tp.DotReader()).
Both expect the article as the server sends it: CRLF lines, dot-stuffed, ending with a "." line.
DecodeIncremental is the low-level step underneath, a drop-in for rapidyenc's rapidyenc_decode_incremental: it decodes the yEnc data in chunks of any size, undoes the dot-stuffing and stops at "=y" (the =yend line) or at the end of the article. Parsing the =y lines and the CRC are then up to the caller.
The byte-wise decoder is a small state machine that matches rapidyenc's scalar reference (decoder.cc, do_decode_end_scalar<isRaw=true>) state for state. With GOEXPERIMENT=simd, vector loops decode 16, 32 or 64 bytes per step: AVX, AVX2 or AVX-512 VBMI2 on amd64, NEON on arm64; elsewhere it is plain Go, 16 bytes per step (two 64-bit words, SWAR), or one on CPUs that are big-endian or slow at unaligned loads.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNoYbegin: the article ended before a "=ybegin" line. ErrNoYbegin = errors.New("yencsimd: no =ybegin line") // ErrTruncated: the article ended inside the yEnc data, before "=yend". ErrTruncated = errors.New("yencsimd: article ends before =yend") // ErrSize: the decoded size differs from the =ybegin/=ypart/=yend sizes. ErrSize = errors.New("yencsimd: size mismatch") // ErrCRC: the CRC32 of the decoded data differs from the =yend line. ErrCRC = errors.New("yencsimd: crc32 mismatch") )
Errors returned by Reader and Decode.
var ErrDstTooSmall = errors.New("yencsimd: destination must be at least the length of source")
ErrDstTooSmall is returned when dst is shorter than src.
Functions ¶
Types ¶
type End ¶
type End int
End reports whether, and how, decoding stopped early.
func DecodeIncremental ¶
DecodeIncremental decodes src into dst, starting in *state, and sets *state to the state after the last byte it read. It stops after an end sequence (see End). dst must be at least len(src): the output is never longer than the input. dst and src must not overlap: the decoder writes scratch bytes past the output position, so in-place decoding is not supported.
Example ¶
The low-level step: decode yEnc data (the lines between =ybegin and =yend) in chunks of any size. Reader is built on this.
package main
import (
"fmt"
yencsimd "github.com/go-while/go-yenc-simd"
)
func main() {
body := []byte("r\x8f\x96\x96\x99VJ\xa3o\x98\x8dK\r\n=yend size=12\r\n")
st := yencsimd.StateCRLF // the data starts at a line start
dst := make([]byte, len(body))
n, used, end, _ := yencsimd.DecodeIncremental(dst, body, &st)
fmt.Printf("%q, stopped at %q (end=%v)\n", dst[:n], body[used-2:used+3], end == yencsimd.EndControl)
}
Output: "Hello, yEnc!", stopped at "=yend" (end=true)
type Meta ¶
type Meta struct {
Name string // file name (=ybegin name=)
Size int64 // size of the whole file (=ybegin size=)
Part int // part number (=ybegin part=), 0 for a single-part post
Total int // number of parts (=ybegin total=), 0 if not given
Begin int64 // first byte of this part in the file, 1-based (=ypart begin=); 1 for single-part
End int64 // last byte of this part, inclusive (=ypart end=); Size for single-part
CRC32 uint32 // CRC32 the trailer gives for this part (pcrc32=, or crc32= for single-part)
HasCRC bool // whether the trailer had one; without it the CRC isn't checked
}
Meta is what the yEnc header and trailer lines say about the data. Name, Size, Part, Total, Begin and End are set once Read returned the first data (or an error); CRC32 and HasCRC once Read returned io.EOF.
func Decode ¶
Decode decodes one yEnc article held in memory (as Reader expects it: CRLF lines, dot-stuffed, optionally with headers before =ybegin) and checks its size and CRC32. It returns the same errors as Reader, with the data decoded up to the error.
The data is decoded in one step into a buffer as long as the rest of the article after the header lines (the output is never longer), so the returned slice has some spare capacity: about 3% for a typical post.
Example ¶
Decode a whole article you already have in memory.
package main
import (
"fmt"
yencsimd "github.com/go-while/go-yenc-simd"
)
// The article as a server sends it after "222 ... body follows":
// "Hello, yEnc!" encoded, dot-stuffed, ending in a "." line.
const article = "=ybegin line=128 size=12 name=hello.txt\r\n" +
"r\x8f\x96\x96\x99VJ\xa3o\x98\x8dK\r\n" +
"=yend size=12 crc32=0337ab3d\r\n" +
".\r\n"
func main() {
data, meta, err := yencsimd.Decode([]byte(article))
if err != nil {
fmt.Println("error:", err) // ErrCRC, ErrSize, ErrTruncated or ErrNoYbegin
return
}
fmt.Printf("%s: %q (%d bytes)\n", meta.Name, data, meta.Size)
}
Output: hello.txt: "Hello, yEnc!" (12 bytes)
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader decodes one yEnc article from an NNTP connection or a buffer.
It expects the article the way it comes off the wire after a "222" (BODY) or "220" (ARTICLE) status line: lines end in CRLF, lines starting with "." are dot-stuffed, and the article ends with a line holding a single ".". Header lines and anything else before "=ybegin" are skipped. Do not put a net/textproto DotReader in front of it: that already removes the dot-stuffing, and Reader would remove it a second time.
Read returns the decoded data. At the end it checks the sizes and the CRC32 and returns io.EOF, or ErrSize / ErrCRC / ErrTruncated / ErrNoYbegin. By then it has read through the closing "." line (or to the end of the input), so an NNTP connection is ready for its next command. A malformed header (a part without an =ypart line, or another =y line in the data) gives a plain error; after it the input is still inside the article, so drop the connection or read to the "." line yourself.
func NewReader ¶
NewReader returns a Reader that decodes one article from r. If r is a *bufio.Reader it is used directly (net/textproto's Reader.R is one), so no bytes of the next response are read ahead and lost.
Each step decodes what the bufio.Reader has buffered, so its buffer size sets how much work a step does and how often the connection is read: for an NNTP connection give textproto a bufio.NewReaderSize(conn, 64<<10) rather than the 4 KiB default; Reader never reads past the article.
Example ¶
Stream an article straight from an NNTP connection (net/textproto). Pass the connection's bufio.Reader (tp.R), not tp.DotReader(): Reader handles the dot-stuffing and the closing "." line itself, and leaves the connection at the next response.
package main
import (
"bufio"
"fmt"
"io"
"net/textproto"
"strings"
yencsimd "github.com/go-while/go-yenc-simd"
)
// The article as a server sends it after "222 ... body follows":
// "Hello, yEnc!" encoded, dot-stuffed, ending in a "." line.
const article = "=ybegin line=128 size=12 name=hello.txt\r\n" +
"r\x8f\x96\x96\x99VJ\xa3o\x98\x8dK\r\n" +
"=yend size=12 crc32=0337ab3d\r\n" +
".\r\n"
func main() {
// a stand-in for the server's reply to "BODY <id>"
tp := textproto.NewReader(bufio.NewReader(strings.NewReader("222 0 <id@example.test> body\r\n" + article)))
if _, _, err := tp.ReadCodeLine(222); err != nil {
fmt.Println("error:", err)
return
}
d := yencsimd.NewReader(tp.R)
var out strings.Builder
if _, err := io.Copy(&out, d); err != nil {
fmt.Println("error:", err)
return
}
fmt.Printf("%s: %q\n", d.Meta().Name, out.String())
}
Output: hello.txt: "Hello, yEnc!"
func (*Reader) Read ¶
Read implements io.Reader. When p can take all that is buffered, or holds 8 KiB or more, it decodes straight into p, so io.Copy (32 KiB pieces) and io.ReadAll (once its buffer has grown) get the data without a copy. Read may use p[n:] as scratch space, as io.Reader allows: bytes of p past n are not preserved.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
yencbench
command
Command yencbench prints how fast go-yenc-simd decodes on this machine, for every kernel the CPU can run:
|
Command yencbench prints how fast go-yenc-simd decodes on this machine, for every kernel the CPU can run: |
|
Package yencbench measures how fast yencsimd decodes on this machine, with the same inputs as the package's own benchmarks.
|
Package yencbench measures how fast yencsimd decodes on this machine, with the same inputs as the package's own benchmarks. |