refactor/kvconf 3421f16

codec.go RawHistoryFolder

1package auth
2
3import (
4	"encoding/binary"
5	"errors"
6	"io"
7
8	"github.com/bnema/neferafk/internal/ports"
9)
10
11var ErrProtocol = errors.New("invalid authentication frame")
12
13const frameHeader = 22 // magic, kind, generic code, generation, attempt
14
15func payloadLimit(kind ports.AuthKind) int {
16	if kind == ports.AuthAttempt || kind == ports.AuthSecret {
17		return ports.AuthMaxSecret
18	}
19	return ports.AuthMaxMetadata
20}
21
22func validRole(f ports.AuthFrame, n int) bool {
23	if f.Generation == 0 || n < 0 || n > payloadLimit(f.Kind) {
24		return false
25	}
26	if f.Kind == ports.AuthReady {
27		if f.Attempt != 0 {
28			return false
29		}
30		switch f.Code {
31		case ports.AuthPassword, ports.AuthPIN, ports.AuthFallbackPassword:
32			return n == 0
33		case ports.AuthUnavailable:
34			return n == 0 || n == 1
35		default:
36			return false
37		}
38	}
39	if f.Attempt == 0 {
40		return false
41	}
42	switch f.Kind {
43	case ports.AuthAttempt, ports.AuthSecret:
44		return f.Code == 0
45	case ports.AuthPrompt, ports.AuthStatus:
46		return f.Code == ports.AuthPassword && n == 0
47	case ports.AuthResult:
48		return n == 0 && (f.Code == ports.AuthSuccess || f.Code == ports.AuthDenied || f.Code == ports.AuthUnavailable || f.Code == ports.AuthInvalid)
49	case ports.AuthCancel:
50		return f.Code == ports.AuthInvalid && n == 0
51	default:
52		return false
53	}
54}
55
56func validFrame(f ports.AuthFrame) bool {
57	if !validRole(f, len(f.Payload)) {
58		return false
59	}
60	return len(f.Payload) != 1 || f.Kind != ports.AuthReady || f.Payload[0] == byte(ports.AuthFallbackToPassword)
61}
62
63// ReadFrame validates the fixed header before allocating any bounded payload.
64// Partial reads wipe the allocated payload; no error includes input bytes.
65func ReadFrame(r io.Reader) (ports.AuthFrame, error) {
66	var prefix [4]byte
67	if _, err := io.ReadFull(r, prefix[:]); err != nil {
68		return ports.AuthFrame{}, err
69	}
70	n := int(binary.BigEndian.Uint32(prefix[:]))
71	if n < frameHeader || n > frameHeader+ports.AuthMaxMetadata {
72		return ports.AuthFrame{}, ErrProtocol
73	}
74	var h [frameHeader]byte
75	if _, err := io.ReadFull(r, h[:]); err != nil {
76		return ports.AuthFrame{}, err
77	}
78	f := ports.AuthFrame{Kind: ports.AuthKind(h[4]), Code: ports.AuthCode(h[5]), Generation: binary.BigEndian.Uint64(h[6:14]), Attempt: binary.BigEndian.Uint64(h[14:22])}
79	if string(h[:4]) != "NAW1" || !validRole(f, n-frameHeader) {
80		return ports.AuthFrame{}, ErrProtocol
81	}
82	f.Payload = make([]byte, n-frameHeader)
83	if _, err := io.ReadFull(r, f.Payload); err != nil {
84		clear(f.Payload)
85		return ports.AuthFrame{}, err
86	}
87	if !validFrame(f) {
88		clear(f.Payload)
89		return ports.AuthFrame{}, ErrProtocol
90	}
91	return f, nil
92}
93
94func WriteFrame(w io.Writer, f ports.AuthFrame) error {
95	if !validFrame(f) {
96		return ErrProtocol
97	}
98	var h [4 + frameHeader]byte
99	binary.BigEndian.PutUint32(h[:4], uint32(frameHeader+len(f.Payload)))
100	copy(h[4:8], "NAW1")
101	h[8], h[9] = byte(f.Kind), byte(f.Code)
102	binary.BigEndian.PutUint64(h[10:18], f.Generation)
103	binary.BigEndian.PutUint64(h[18:26], f.Attempt)
104	if err := writeAll(w, h[:]); err != nil {
105		return err
106	}
107	return writeAll(w, f.Payload)
108}
109
110func writeAll(w io.Writer, b []byte) error {
111	for len(b) != 0 {
112		n, err := w.Write(b)
113		if n < 0 || n > len(b) {
114			return ErrProtocol
115		}
116		b = b[n:]
117		if err != nil {
118			return err
119		}
120		if n == 0 {
121			return io.ErrShortWrite
122		}
123	}
124	return nil
125}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 9

↑↓ moveenter openesc close