rapidyenc

package module
v0.0.0-...-3a336f6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 29, 2026 License: MIT Imports: 14 Imported by: 5

README

rapidyenc

rapidyenc is a high-performance Go library for decoding yEnc. It provides fast, memory-efficient decoding with robust error handling, supporting multiple platforms and architectures.

The decoder expects an NNTP stream of data, it will perform dot unstuffing and search for the end of responses ".\r\n" this behaviour is not currently configurable.

The module exposes the highly efficient encoding and decoding implementations provided by the C compatible library animetosho/rapidyenc taking advantage CPU features.

Features

  • Fast yEnc encoding/decoding using native C implementation via CGO.
  • Streaming interface for efficient handling of large files.
  • Cross-platform: Supports Linux, Windows, macOS on amd64 and arm64
  • Header parsing: Extracts yEnc Meta (filename, size, CRC32, etc).
  • Error detection: CRC mismatch, data corruption, and missing headers.

Experimental usage without CGO

Experimental support using simd/archsimd is available without the need for CGO, allowing safer more portable usage.

A port of the AVX2 implementations are available, unsupported platforms will use a generic and slow scalar implementation.

CGO_ENABLED=0 GOEXPERIMENT=simd

Hopefully simd/archsimd will add arm64/neon support in the future and if promoted from an experiment I expect CGO usage/support will be removed entirely.

Usage Examples

Encoding
// An io.Reader of raw data, here random data, but could be a file, bufio.Reader, etc.
raw := make([]byte, 768_000)
_, err := rand.Read(raw)
input := bytes.NewReader(raw)

// yEnc headers
meta := Meta{
    FileName:   "filename",
    FileSize:   int64(len(raw)),
    PartSize:   int64(len(raw)),
    PartNumber: 1,
    TotalParts: 1,
}

// io.Writer for output
encoded := bytes.NewBuffer(nil)

// Pass input through the Encoder
enc, err := NewEncoder(encoded, meta)
_, err = io.Copy(enc, input)

// Must close to write the =yend footer
err = enc.Close()
Decoding
// An io.Reader of encoded data
input := bytes.NewReader(raw)
output := bytes.NewBuffer(nil)

// Will read from input until io.EOF or ".\r\n"
dec := NewDecoder(input)
meta, err := dec.Next(output) // Writes decoded data to output

// if err == nil then meta contains yEnc headers

Building from Source

It may not be desirable to use the included binary blobs, I could not find a way of avoiding it as there didn't appear to be a way to pass per-file CFLAGS when using CGO. If things have changed or there is a better way please let me know.

See Makefile and build.yml for how the blobs are compiled.

Adding support for other platforms involves creating a toolchain-*.cmake file, adjust Makefile, compile and update cgo.go

Contributing

Pull requests and issues are welcome! Please open an issue for bug reports, questions, or feature requests.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrDataMissing    = errors.New("no binary data")
	ErrDataCorruption = errors.New("data corruption detected") // io.EOF or ".\r\n" reached before =yend
	ErrCrcMismatch    = errors.New("crc32 mismatch")
)

Functions

func DecodeKernel

func DecodeKernel() string

DecodeKernel returns the name of the implementation being used for decode operations

func Encode deprecated

func Encode(src []byte) ([]byte, error)

Encode yEnc encodes the src buffer without adding any =y headers

Deprecated: use Encoder as an io.WriteCloser which includes yEnc headers

func EncodeKernel

func EncodeKernel() string

EncodeKernel returns the name of the implementation being used for encode operations

Types

type Decoder

type Decoder struct {
	// contains filtered or unexported fields
}

func NewDecoder

func NewDecoder(r io.Reader, opts ...DecoderOption) *Decoder

func (*Decoder) Next

func (d *Decoder) Next() (*Response, error)

Next reads from r until a complete response is decoded. If r is a net.Conn, the caller is responsible for settings deadlines.

type DecoderOption

type DecoderOption func(d *Decoder)

func WithBufferSize

func WithBufferSize(size int) DecoderOption

WithBufferSize allows the caller to customise the size of the internal buffer used by the decoder

func WithDataFunc

func WithDataFunc(dataFunc func() []byte) DecoderOption

WithDataFunc allows a function to be called when responses need a []byte, for example from a sync.Pool to reduce GC pressure

func WithStatusLineAlreadyRead

func WithStatusLineAlreadyRead() DecoderOption

WithStatusLineAlreadyRead the decoder assumes the stream is positioned at the start of a multiline response body and the caller has already consumed the first line of the response

type Encoder

type Encoder struct {
	// contains filtered or unexported fields
}

func NewEncoder

func NewEncoder(w io.Writer, m Meta, opts ...EncoderOption) (e *Encoder, err error)

NewEncoder returns a new Encoder. Writes to the returned writer are yEnc encoded and written to w.

It is the caller's responsibility to call Close on the Encoder when done.

func (*Encoder) Close

func (e *Encoder) Close() error

Close flushes any pending output from the encoder and writes the trailing header. It is an error to call Write after calling Close.

func (*Encoder) Reset

func (e *Encoder) Reset(w io.Writer, meta Meta) error

Reset discards the Encoder e's state and makes it equivalent to the result of its original state from NewEncoder, but writing to w instead. This permits reusing a Encoder rather than allocating a new one.

func (*Encoder) Write

func (e *Encoder) Write(p []byte) (n int, err error)

Write writes a yEnc encoded form of p to the underlying io.Writer. The encoded bytes are not necessarily flushed until the Encoder is closed.

type EncoderOption

type EncoderOption func(*Encoder)

func WithLineLength

func WithLineLength(lineLength int) EncoderOption

WithLineLength configures the encoded line length

func WithRaw

func WithRaw() EncoderOption

WithRaw option encodes without writing yenc headers

type End

type End int

End is the State for incremental decoding, whether the end of the yEnc data was reached

const (
	EndNone    End = iota // end not reached
	EndControl            // \r\n=y sequence found, src points to byte after 'y'
	EndArticle            // \r\n.\r\n sequence found, src points to byte after last '\n'
)

type Format

type Format int
const (
	FormatUnknown Format = iota
	FormatYenc
	FormatUU
)

type Meta

type Meta struct {
	FileName   string
	FileSize   int64 // Total size of the file
	PartNumber int64
	TotalParts int64
	Offset     int64 // Offset of the part within the file relative to the start, like io.Seeker or io.WriterAt
	PartSize   int64 // Size of the unencoded data
}

Meta is the result of parsing the yEnc headers (ybegin, ypart, yend)

func (Meta) Begin

func (m Meta) Begin() int64

Begin is the "=ypart begin" value calculated from the Offset

func (Meta) End

func (m Meta) End() int64

End is the "=ypart end" value calculated from the Offset and PartSize

type Response

type Response struct {
	Metadata ResponseMeta
	Data     []byte
	// contains filtered or unexported fields
}

type ResponseMeta

type ResponseMeta struct {
	Meta
	BytesProduced int64
	BytesConsumed int64
	Lines         []string
	Format        Format
	EndSize       int64
	TotalParts    int64
	ExpectedCRC   uint32
	Message       string
	StatusCode    int
	CRC           uint32
}

type State

type State int

State is the current Decoder State, the values refer to the previously seen characters in the stream, which influence how some sequences need to be handled.

The shorthands represent: CR (\r), LF (\n), EQ (=), DT (.)

const (
	StateCRLF State = iota
	StateEQ
	StateCR
	StateNone
	StateCRLFDT
	StateCRLFDTCR
	StateCRLFEQ // may actually be "\r\n.=" in raw Decoder
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL