func newArmorDecoderStream(r io.Reader, params armorParams) (io.Reader, Frame, error) {
	fds := &framedDecoderStream{r: newPunctuatedReader(r, params.Punctuation), params: params}
	ret := basex.NewDecoder(params.Encoding, fds)
	return ret, fds, nil
}
newArmorDecoderStream is used to decode armored encoding. It returns a stream you
can read from, and also a Frame you can query to see what the open/close
frame markers were.
package saltpack
import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"strings"
	"github.com/keybase/saltpack/encoding/basex"
)
type armorParams struct {
	BytesPerWord int
	WordsPerLine int
	Punctuation byte
	Encoding *basex.Encoding
}
type armorEncoderStream struct {
	buf     *bytes.Buffer
	footer  string
	encoded io.Writer
	encoder io.WriteCloser
	nWords  int
	params  armorParams
}
func (s *armorEncoderStream) Write(b []byte) (n int, err error) {
	n, err = s.encoder.Write(b)
	if err != nil {
		return n, err
	}
	s.spaceAndOutputBuffer()
	return n, err
}
func (s *armorEncoderStream) spaceAndOutputBuffer() error {
	for s.buf.Len() > s.params.BytesPerWord {
		buf := s.buf.Next(s.params.BytesPerWord)
		s.nWords++
		sep := byte(' ')
		if s.nWords%s.params.WordsPerLine == 0 {
			sep = byte('\n')
		}
		if _, err := s.encoded.Write(buf); err != nil {
			return err
		}
		if _, err := s.encoded.Write([]byte{sep}); err != nil {
			return err
		}
	}
	return nil
}
func (s *armorEncoderStream) Close() (err error) {
	if err = s.encoder.Close(); err != nil {
		return err
	}
	if err := s.spaceAndOutputBuffer(); err != nil {
		return err
	}
	lst := s.buf.Bytes()
	if _, err := s.encoded.Write([]byte(lst)); err != nil {
		return err
	}
	s.nWords++
	pad := ""
	if len(lst) == s.params.BytesPerWord {
		if s.nWords%s.params.WordsPerLine == 0 {
			pad = "\n"
		} else {
			pad = " "
		}
	}
	if _, err := fmt.Fprintf(s.encoded, "%s%c %s%c\n", pad, s.params.Punctuation, s.footer, s.params.Punctuation); err != nil {
		return err
	}
	return nil
}
func newArmorEncoderStream(encoded io.Writer, header string, footer string, params armorParams) (io.WriteCloser, error) {
	ret := &armorEncoderStream{
		buf:     new(bytes.Buffer),
		encoded: encoded,
		footer:  footer,
		params:  params,
	}
	ret.encoder = basex.NewEncoder(params.Encoding, ret.buf)
	if _, err := fmt.Fprintf(encoded, "%s%c ", header, params.Punctuation); err != nil {
		return nil, err
	}
	return ret, nil
}
func armorSeal(plaintext []byte, header string, footer string, params armorParams) (string, error) {
	var buf bytes.Buffer
	enc, err := newArmorEncoderStream(&buf, header, footer, params)
	if err != nil {
		return "", err
	}
	if _, err := enc.Write(plaintext); err != nil {
		return "", err
	}
	if err := enc.Close(); err != nil {
		return "", err
	}
	return buf.String(), nil
}
type Frame interface {
	GetHeader() (string, error)
	GetFooter() (string, error)
}
type fdsState int
const (
	fdsHeader fdsState = iota
	fdsBody
	fdsFooter
	fdsEndOfStream
)
type framedDecoderStream struct {
	header []byte
	footer []byte
	state  fdsState
	params armorParams
	r      *punctuatedReader
}
func (s *framedDecoderStream) Read(p []byte) (n int, err error) {
	frameLim := 8192
	if s.state == fdsHeader {
		s.header, err = s.r.ReadUntilPunctuation(frameLim)
		if err != nil {
			return 0, err
		}
		s.state = fdsBody
	}
	if s.state == fdsBody {
		n, err = s.r.Read(p)
		if err == ErrPunctuated {
			err = nil
			s.state = fdsFooter
		}
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		if err != nil {
			return 0, err
		}
	}
	if s.state == fdsFooter {
		s.footer, err = s.r.ReadUntilPunctuation(frameLim)
		if err != nil {
			return 0, err
		}
		s.state = fdsEndOfStream
	}
	if s.state == fdsEndOfStream {
		err = s.consumeUntilEOF()
		if err == io.EOF && n > 0 {
			err = nil
		}
	}
	return n, err
}
func (s *framedDecoderStream) consumeUntilEOF() error {
	var buf [4096]byte
	for {
		n, err := s.r.Read(buf[:])
		if err != nil {
			return err
		}
		if n == 0 {
			return io.EOF
		}
		if !s.isValidByteSequence(buf[0:n]) {
			return ErrTrailingGarbage
		}
	}
}
func (s *framedDecoderStream) isValidByteSequence(p []byte) bool {
	for _, b := range p {
		if !s.params.Encoding.IsValidByte(b) {
			return false
		}
	}
	return true
}
func (s *framedDecoderStream) toASCII(buf []byte) (string, error) {
	if !s.isValidByteSequence(buf) {
		return "", makeErrBadFrame("invalid ASCII sequence")
	}
	return strings.TrimSpace(string(buf)), nil
}
func (s *framedDecoderStream) GetFooter() (string, error) { return s.toASCII(s.footer) }
func (s *framedDecoderStream) GetHeader() (string, error) { return s.toASCII(s.header) }