hmsg

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2020 License: MIT Imports: 9 Imported by: 0

README

hmsg

GoDoc Go Report Card

hmsg is a library for writing and reading length- and checksum-prefixed messages. Its primary purpose is to act as a first step in ensuring message integrity between systems and, to a limited extent, authenticity (assuming you use an HMAC or similar).

Install

$ go get go.spiff.io/hmsg

Usage

Almost all interaction with hmsg is done through a Messenger:

hashFun := hmsg.HMAC("some-key", sha1.New)
// Allocate a new Messenger for max-1000-bytes messages with an HMAC-SHA1 hash
m, err := hmsg.NewMessenger(1000, hashFun)
if err != nil {
    // NewMessenger only fails if the max size is too small to hold the hash and
    // length prefixes, or if the hash function is invalid in some way.
    panic(fmt.Errorf("Failed to create messenger: %v", err))
}

Once you have a Messenger, you can use it to write and read messages. In a server, you might use the same messenger across multiple connections (the Messenger type doesn't have state, so it's safe to share) to read messages before dispatching them and writing a response message back:

type Server struct {
    messenger *hmsg.Messenger
}

// HandleConn receives messages from a connection, parses them as JSON, and
// dispatches them. If dispatch succeeds, it sends the reply back as a JSON
// message.
func (s *Server) HandleConn(conn net.Conn) error {
    defer conn.Close()
    for {
        p, err := s.messenger.ReadMsg(conn)
        if err != nil {
            return err
        }

        var req Request
        if err = json.Unmarshal(p, &req); err != nil {
            return err
        }

        rep, err := s.Dispatch(&req)
        if err != nil {
            return err
        }

        p, err = json.Marshal(rep)
        if err != nil {
            return err
        }

        if err = s.messenger.WriteMsg(conn, p); err != nil {
            return err
        }
    }
}

A similar process would be involved in a client connection as well.

Stability

hmsg is relatively stable but may undergo breaking changes if required. For this reason, it is recommended you pin uses of hmsg a specific commit. Version tags may also be available at different points in the project's history.

The hmsg API may undergo breaking changes prior to version 1.0.0. After 1.0.0, all breaking changes will involve a major version change (i.e., to make breaking changes after 1.0.0, hmsg would need to release 2.0.0).

License

hmsg is licensed under the MIT license.

Documentation

Overview

Package hmsg implements reading and writing of length- and checksum-prefixed messages.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsVerifyError

func IsVerifyError(err error) bool

IsVerifyError returns whether err is a *VerifyError.

func MaxMessageSize

func MaxMessageSize(payloadSize int64, hashfn HashFunc) int64

MaxMessageSize returns the maximum message size needed to contain a payload of payloadSize, checksum for hashfn, and the length prefix of the message.

func NullHash

func NullHash() hash.Hash

NullHash is a hash function with no size and no hash. It can be used in debugging to accept a hash as part of a message, but should not be used for real messages.

Types

type HashFunc

type HashFunc func() hash.Hash

HashFunc is a function that returns a new hash.Hash for use in a Messenger. Returned hashes are not reused.

func HMAC

func HMAC(key string, hashfn HashFunc) HashFunc

HMAC returns a HashFunc for an HMAC hash function with a given key and hash function.

func HMACBytes

func HMACBytes(key []byte, hashfn HashFunc) HashFunc

HMACBytes returns a HashFunc for an HMAC hash function with a given key and hash function. Unlike HMAC, this function makes a copy of a byte slice for its key.

type Messenger

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

Messenger reads and writes messages with length and checksum prefixes.

A Messenger is safe to use across multiple goroutines and does not keep any state.

Example
package main

import (
	"bytes"
	"crypto/sha1"
	"fmt"

	"go.spiff.io/hmsg"
)

func main() {
	// Allocate a new Messenger for sending messages up to 1000 bytes long.
	//
	// For this, we'll use HMAC-SHA1 to verify messages, but you can also
	// use plain MD5, CRC32, SHA1, or other hashes.
	m, err := hmsg.NewMessenger(1000, hmsg.HMAC("super-secret-key", sha1.New))
	if err != nil {
		panic(fmt.Sprint("Error creating messenger: ", err))
	}

	const message = "Hello, World"

	// Create a buffer to write a message -- in practice, this might be
	// a TCP connection or something similar.
	buf := &bytes.Buffer{}

	// Write a message to the buffer. The message includes its length and
	// checksum, which is used to verify the message when we read it.
	if err := m.WriteMsg(buf, []byte(message)); err != nil {
		panic(fmt.Sprint("Error writing message: ", err))
	}

	// Read the message. If it had been tampered with, ReadMsg would return
	// a *VerifyError.
	received, err := m.ReadMsg(buf)
	if err != nil {
		panic(fmt.Sprint("Error receiving message: ", err))
	}

	fmt.Printf("Received message: %q\n", string(received))

}
Output:

Received message: "Hello, World"

func NewMessenger

func NewMessenger(maxMsgSize int64, hashfn HashFunc) (*Messenger, error)

NewMessenger allocates a new Messenger for reading and writing length- and checksum-prefixed messages.

maxMsgSize specifies the maximum size of a message in bytes, and must be greater than the minimum size needed to contain a message length prefix for maxMsgSize and its checksum. If maxMsgSize would be invalid, or the hash function does not return a valid message size (or is nil), NewMessenger returns an error.

If maxMsgSize is zero (or less), the upper limit for message size is math.MaxInt64. The upper limit may change depending on the target system, so it's recommended that you specify your own size limit.

func (*Messenger) HashFunc

func (m *Messenger) HashFunc() HashFunc

HashFunc returns the hash function the Messenger was created with.

func (*Messenger) MaxMessageSize

func (m *Messenger) MaxMessageSize() int64

MaxMessageSize returns the maximum message size. Message size includes the length prefix and checksum size.

If the Messenger was not given an explicit message size, MaxMessageSize returns a reasonable maximum for the target system (currently math.MaxInt64, but this may change).

func (*Messenger) MaxPayloadSize

func (m *Messenger) MaxPayloadSize() int64

MaxPayloadSize returns the maximum payload size. This is the size of the largest payload that can be sent. The result of MaxPayloadSize is always less than or equal to MaxMessageSize.

func (*Messenger) MsgBytes

func (m *Messenger) MsgBytes(p []byte) ([]byte, error)

MsgBytes is a convenience function to encode a message for p and return a new byte slice containing the encoded message. See WriteMsg for more details.

func (*Messenger) ReadMsg

func (m *Messenger) ReadMsg(r io.Reader) ([]byte, error)

ReadMsg reads length, checksum, and payload from r. This function is identical to ReadMsgTo, except that it will always allocate a new byte slice for the message. See ReadMsgTo for more detail.

func (*Messenger) ReadMsgBytes

func (m *Messenger) ReadMsgBytes(p []byte) ([]byte, error)

ReadMsgBytes is a convenience function to read a message from the payload p. It returns a new byte slice for the message, if successfully read. See ReadMsgTo formore detail.

func (*Messenger) ReadMsgTo

func (m *Messenger) ReadMsgTo(p []byte, r io.Reader) ([]byte, error)

ReadMsgTo reads length, checksum, and payload from r and, if the message is valid, stores the payload in the slice p and returns the payload.

If the checksum of the payload does not match when filtered through Messenger's hash function, it returns a *VerifyError. This is to prevent acceptance of corrupt, spoofed, or otherwise invalid messages (depending on the checksum).

If p is not large enough to accommodate the message payload, a large enough byte slice is allocated to hold it.

Too-small and too-large messages will also return errors. All other errors arise from reading from r.

func (*Messenger) WriteMsg

func (m *Messenger) WriteMsg(w io.Writer, p []byte) error

WriteMsg writes the payload, p, prefixed by its length and checksum, to the writer w.

If the total length of the message (length prefix, checksum, and payload p) exceeds the Messenger's maximum message size, then the message is not written and WriteMsg returns an error.

All other errors arise from writing to w.

type VerifyError

type VerifyError struct {
	Got  []byte
	Want []byte
}

VerifyError is returned when a receive's message's checksum does not match its contents.

func (*VerifyError) Error

func (c *VerifyError) Error() string

Error implements error.

Directories

Path Synopsis
example

Jump to

Keyboard shortcuts

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