> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getmelody.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Haptic Frames

> Binary framing protocol used for all WebSocket communication between devices, apps, and the Haptics server.

All communication between devices, applications, and the Haptics server occurs over the **WebSocket** protocol. Because a WebSocket stream is a continuous flow of bytes, we wrap every discrete message in a small binary frame — the **HapticFrame**.

<Info>
  **Public Protobuf schema:** [schema.proto](https://github.com/infomediji/haptic-docs/blob/master/proto/schema.proto)

  **Reference implementation:** [haptic\_frame.go — Read](https://github.com/infomediji/haptic-docs/blob/master/client-example-go/protocol/haptic_frame.go#L94) · [Write](https://github.com/infomediji/haptic-docs/blob/master/client-example-go/protocol/haptic_frame.go#L70)
</Info>

***

## Frame Layout

```text theme={null}
HapticFrame:
1b - Header | 2b - Frame Length | Nb - Data

Header: 1 byte identifying the application message (frame type). See the frame type list.
Length: Total size of the message, including the Header and Length fields.
Data:   Arbitrary payload, max 1024 bytes. Preferred encoding: Protobuf.
```

* **Header** — one byte, the `FrameType`.
* **Length** — `uint16`, little-endian, counts the full frame (header + length + data).
* **Data** — up to `1024` bytes; typically a marshaled Protobuf message.

***

## Read

```go theme={null}
const (
	headerLength   = 3
	maxDataLength  = 1021
	maxFrameLength = 1024
)

var (
	ErrUnexpectedFrameType = errors.New("unexpected frame type")
	ErrUnknownFrameType    = errors.New("unknown frame type")
)

type HapticFrame struct {
	Type       entities.FrameType
	Data       []byte
	dataLength uint16
}

func NewHapticFrame(t entities.FrameType, data []byte) *HapticFrame {
	return &HapticFrame{
		Type: t,
		Data: data,
	}
}

// ReadNextFrame reads a single frame from the stream.
// It returns an error if the frame is invalid or the stream is closed.
func ReadNextFrame(s io.Reader) (*HapticFrame, error) {
	frame := &HapticFrame{}

	// Read the frame type
	if err := frame.readType(s); err != nil {
		return nil, err
	}

	// Read the frame length
	if err := frame.readLength(s); err != nil {
		return nil, err
	}

	// Read the frame data
	buf, n, err := readN(s, int(frame.dataLength))
	if err != nil {
		return nil, err
	}

	if n == int(frame.dataLength) {
		frame.Data = buf
		return frame, nil
	}

	return nil, fmt.Errorf("invalid frame length %d, expected %d", n, frame.dataLength)
}

// readType reads the frame type (always the first byte of the frame).
func (f *HapticFrame) readType(s io.Reader) error {
	buf, n, err := readN(s, 1)
	if err != nil {
		return err
	}

	if n == 1 {
		ft := entities.FrameType(buf[0])
		if ft != entities.FrameType_UNKNOWN && ft <= entities.FrameType_CONNECTION_INFO {
			f.Type = ft
			return nil
		}
	}

	return ErrUnknownFrameType
}

func (f *HapticFrame) Length() uint16 {
	if f.dataLength == 0 {
		f.dataLength = uint16(len(f.Data))
	}
	return f.dataLength
}

// readLength reads the frame length (second and third bytes of the frame).
func (f *HapticFrame) readLength(s io.Reader) error {
	buf, n, err := readN(s, 2)
	if err != nil {
		return err
	}

	if n == 2 {
		f.dataLength = binary.LittleEndian.Uint16(buf) - headerLength
		if f.dataLength > 0 && f.dataLength <= maxDataLength {
			return nil
		}
	}

	return ErrInvalidLength(int(f.dataLength))
}

// readN reads until n bytes have been read.
func readN(s io.Reader, n int) ([]byte, int, error) {
	k := 0
	buf := make([]byte, n)

	for k < n {
		kk, err := s.Read(buf[k:])
		if err != nil {
			return nil, k, err
		}
		k += kk
	}

	return buf, k, nil
}
```

***

## Write

```go theme={null}
// WriteFrame writes a single frame to the stream.
// It prepends the frame type and length to the frame data.
func (f *HapticFrame) WriteFrame(w io.Writer) error {
	buf := make([]byte, headerLength)
	buf[0] = byte(f.Type)

	length := uint16(len(f.Data) + headerLength)
	if length == headerLength || length > maxDataLength {
		return ErrInvalidLength(int(length))
	}
	binary.LittleEndian.PutUint16(buf[1:], length)

	// Write the frame header
	if _, err := w.Write(buf); err != nil {
		return err
	}

	// Write the frame data
	_, err := w.Write(f.Data)
	return err
}

func ErrInvalidLength(len int) error {
	return fmt.Errorf("invalid frame length %d", len)
}
```

<Tip>
  The complete example and tests live in [haptic\_protocol.go](https://github.com/infomediji/haptics-server/blob/master/pkg/sdk/protocol/haptic_protocol.go).
</Tip>

***

Once you can read and write HapticFrames, you can exchange arbitrary binary messages with the server or other clients. We use **Protobuf** as the message format — see the [schema](https://github.com/infomediji/haptic-docs/blob/master/proto/schema.proto). Continue with the [Subscription Flow](/melody/streaming/subscription) and [Publication Flow](/melody/streaming/publication).
