zmq4/protocol.go

275 lines
5.7 KiB
Go

// Copyright 2018 The go-zeromq Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package zmq4
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"strings"
)
var (
errGreeting = errors.New("zmq4: invalid greeting received")
errSecMech = errors.New("zmq4: invalid security mechanism")
errBadSec = errors.New("zmq4: invalid or unsupported security mechanism")
ErrBadCmd = errors.New("zmq4: invalid command name")
ErrBadFrame = errors.New("zmq4: invalid frame")
errOverflow = errors.New("zmq4: overflow")
errEmptyAppMDKey = errors.New("zmq4: empty application metadata key")
errDupAppMDKey = errors.New("zmq4: duplicate application metadata key")
errBoolCnv = errors.New("zmq4: invalid byte to bool conversion")
)
const (
sigHeader = 0xFF
sigFooter = 0x7F
majorVersion uint8 = 3
minorVersion uint8 = 0
hasMoreBitFlag = 0x1
isLongBitFlag = 0x2
isCommandBitFlag = 0x4
zmtpMsgLen = 64
)
var (
defaultVersion = [2]uint8{
majorVersion,
minorVersion,
}
)
const (
maxUint = ^uint(0)
maxInt = int(maxUint >> 1)
maxUint64 = ^uint64(0)
maxInt64 = int64(maxUint64 >> 1)
)
func asString(slice []byte) string {
i := bytes.IndexByte(slice, 0)
if i < 0 {
i = len(slice)
}
return string(slice[:i])
}
func asBool(b byte) (bool, error) {
switch b {
case 0x00:
return false, nil
case 0x01:
return true, nil
}
return false, errBoolCnv
}
type greeting struct {
Sig struct {
Header byte
_ [8]byte
Footer byte
}
Version [2]uint8
Mechanism [20]byte
Server byte
_ [31]byte
}
func (g *greeting) read(r io.Reader) error {
var data [zmtpMsgLen]byte
_, err := io.ReadFull(r, data[:])
if err != nil {
return fmt.Errorf("could not read ZMTP greeting: %w", err)
}
g.unmarshal(data[:])
if g.Sig.Header != sigHeader {
return fmt.Errorf("invalid ZMTP signature header: %w", errGreeting)
}
if g.Sig.Footer != sigFooter {
return fmt.Errorf("invalid ZMTP signature footer: %w", errGreeting)
}
if !g.validate(defaultVersion) {
return fmt.Errorf(
"invalid ZMTP version (got=%v, want=%v): %w",
g.Version, defaultVersion, errGreeting,
)
}
return nil
}
func (g *greeting) unmarshal(data []byte) {
_ = data[:zmtpMsgLen]
g.Sig.Header = data[0]
g.Sig.Footer = data[9]
g.Version[0] = data[10]
g.Version[1] = data[11]
copy(g.Mechanism[:], data[12:32])
g.Server = data[32]
}
func (g *greeting) write(w io.Writer) error {
_, err := w.Write(g.marshal())
return err
}
func (g *greeting) marshal() []byte {
var buf [zmtpMsgLen]byte
buf[0] = g.Sig.Header
// padding 1 ignored
buf[9] = g.Sig.Footer
buf[10] = g.Version[0]
buf[11] = g.Version[1]
copy(buf[12:32], g.Mechanism[:])
buf[32] = g.Server
// padding 2 ignored
return buf[:]
}
func (g *greeting) validate(ref [2]uint8) bool {
switch {
case g.Version == ref:
return true
case g.Version[0] > ref[0] ||
g.Version[0] == ref[0] && g.Version[1] > ref[1]:
// accept higher protocol values
return true
case g.Version[0] < ref[0] ||
g.Version[0] == ref[0] && g.Version[1] < ref[1]:
// FIXME(sbinet): handle version negotiations as per
// https://rfc.zeromq.org/spec:23/ZMTP/#version-negotiation
return false
default:
return false
}
}
const (
sysSockType = "Socket-Type"
sysSockID = "Identity"
)
// Metadata is describing a Conn's metadata information.
type Metadata map[string]string
// MarshalZMTP marshals MetaData to ZMTP encoded data.
func (md Metadata) MarshalZMTP() ([]byte, error) {
buf := new(bytes.Buffer)
keys := make(map[string]struct{})
for k, v := range md {
if len(k) == 0 {
return nil, errEmptyAppMDKey
}
key := strings.ToLower(k)
if _, dup := keys[key]; dup {
return nil, errDupAppMDKey
}
keys[key] = struct{}{}
switch k {
case sysSockID, sysSockType:
if _, err := io.Copy(buf, Property{K: k, V: v}); err != nil {
return nil, err
}
default:
if _, err := io.Copy(buf, Property{K: "X-" + key, V: v}); err != nil {
return nil, err
}
}
}
return buf.Bytes(), nil
}
// UnmarshalZMTP unmarshals MetaData from a ZMTP encoded data.
func (md *Metadata) UnmarshalZMTP(p []byte) error {
i := 0
for i < len(p) {
var kv Property
n, err := kv.Write(p[i:])
if err != nil {
return err
}
i += n
name := toTitle(kv.K)
(*md)[name] = kv.V
}
return nil
}
// Property describes a Conn metadata's entry.
// The on-wire respresentation of Property is specified by:
//
// https://rfc.zeromq.org/spec:23/ZMTP/
type Property struct {
K string
V string
}
func (prop Property) Read(data []byte) (n int, err error) {
klen := len(prop.K)
vlen := len(prop.V)
size := 1 + klen + 4 + vlen
_ = data[:size] // help with bound check elision
data[n] = byte(klen)
n++
n += copy(data[n:n+klen], toTitle(prop.K))
binary.BigEndian.PutUint32(data[n:n+4], uint32(vlen))
n += 4
n += copy(data[n:n+vlen], prop.V)
return n, io.EOF
}
func (prop *Property) Write(data []byte) (n int, err error) {
klen := int(data[n])
n++
if klen > len(data) {
return n, io.ErrUnexpectedEOF
}
prop.K = toTitle(string(data[n : n+klen]))
n += klen
v := binary.BigEndian.Uint32(data[n : n+4])
n += 4
if uint64(v) > uint64(maxInt) {
return n, errOverflow
}
vlen := int(v)
if n+vlen > len(data) {
return n, io.ErrUnexpectedEOF
}
prop.V = string(data[n : n+vlen])
n += vlen
return n, nil
}
type flag byte
func (fl flag) hasMore() bool { return fl&hasMoreBitFlag == hasMoreBitFlag }
func (fl flag) isLong() bool { return fl&isLongBitFlag == isLongBitFlag }
func (fl flag) isCommand() bool { return fl&isCommandBitFlag == isCommandBitFlag }
func toTitle(s string) string {
return strings.Title(s)
//return cases.Title(language.Und, cases.NoLower).String(s)
}