package generator import ( "fmt" "io" "strconv" "strings" ) type startingPoint int const ( tokenMaster = 0x6D // char m tokenSeparator = 0x2F // char / tokenHardened = 0x27 // char ' tokenDot = 0x2E // char . hardenedStart = 0x80000000 // 2^31 ) const ( startingPointMaster startingPoint = iota + 1 startingPointCurrent startingPointParent ) type parseFunc = func() error type pathDecoder struct { s string r *strings.Reader f parseFunc pos int path []uint32 start startingPoint currentToken string currentTokenHardened bool } func newPathDecoder(path string) (*pathDecoder, error) { d := &pathDecoder{ s: path, r: strings.NewReader(path), } if err := d.reset(); err != nil { return nil, err } return d, nil } func (d *pathDecoder) reset() error { _, err := d.r.Seek(0, io.SeekStart) if err != nil { return err } d.pos = 0 d.start = startingPointCurrent d.f = d.parseStart d.path = make([]uint32, 0) d.resetCurrentToken() return nil } func (d *pathDecoder) resetCurrentToken() { d.currentToken = "" d.currentTokenHardened = false } func (d *pathDecoder) parse() (startingPoint, []uint32, error) { for { err := d.f() if err != nil { if err == io.EOF { err = nil } else { err = fmt.Errorf("error parsing derivation path %s; at position %d, %s", d.s, d.pos, err.Error()) } return d.start, d.path, err } } } func (d *pathDecoder) readByte() (byte, error) { b, err := d.r.ReadByte() if err != nil { return b, err } d.pos++ return b, nil } func (d *pathDecoder) unreadByte() error { err := d.r.UnreadByte() if err != nil { return err } d.pos-- return nil } func (d *pathDecoder) parseStart() error { b, err := d.readByte() if err != nil { return err } if b == tokenMaster { d.start = startingPointMaster d.f = d.parseSeparator return nil } if b == tokenDot { b2, err := d.readByte() if err != nil { return err } if b2 == tokenDot { d.f = d.parseSeparator d.start = startingPointParent return nil } d.f = d.parseSeparator d.start = startingPointCurrent return d.unreadByte() } d.f = d.parseSegment return d.unreadByte() } func (d *pathDecoder) saveSegment() error { if len(d.currentToken) > 0 { i, err := strconv.ParseUint(d.currentToken, 10, 32) if err != nil { return err } if i >= hardenedStart { d.pos -= len(d.currentToken) - 1 return fmt.Errorf("index must be lower than 2^31, got %d", i) } if d.currentTokenHardened { i += hardenedStart } d.path = append(d.path, uint32(i)) } d.f = d.parseSegment d.resetCurrentToken() return nil } func (d *pathDecoder) parseSeparator() error { b, err := d.readByte() if err != nil { return err } if b == tokenSeparator { return d.saveSegment() } return fmt.Errorf("expected %s, got %s", string(tokenSeparator), string(b)) } func (d *pathDecoder) parseSegment() error { b, err := d.readByte() if err == io.EOF { if len(d.currentToken) == 0 { return fmt.Errorf("expected number, got EOF") } if newErr := d.saveSegment(); newErr != nil { return newErr } return err } if err != nil { return err } if len(d.currentToken) > 0 && b == tokenSeparator { return d.saveSegment() } if len(d.currentToken) > 0 && b == tokenHardened { d.currentTokenHardened = true d.f = d.parseSeparator return nil } if b < 0x30 || b > 0x39 { return fmt.Errorf("expected number, got %s", string(b)) } d.currentToken = fmt.Sprintf("%s%s", d.currentToken, string(b)) return nil } func decodePath(str string) (startingPoint, []uint32, error) { d, err := newPathDecoder(str) if err != nil { return 0, nil, err } return d.parse() }