func (c *client) setIDKey(key []byte) error {
	c.lock.Lock()
	defer c.lock.Unlock()
	if err := c.Key.SetKey(key); err != nil {
		return err
	}
	return c.save()
}
setIDKey replaces the current ID key with a new one
package e4
import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"sync"
	"time"
	miscreant "github.com/miscreant/miscreant.go"
	"golang.org/x/crypto/ed25519"
	e4crypto "github.com/teserakt-io/e4go/crypto"
	"github.com/teserakt-io/e4go/keys"
)
const (
	idTopicPrefix = "e4/"
)
var (
	ErrTopicKeyNotFound = errors.New("topic key not found")
	ErrUnsupportedOperation = errors.New("this operation is not supported")
)
type Client interface {
	ProtectMessage(payload []byte, topic string) ([]byte, error)
	Unprotect(protected []byte, topic string) ([]byte, error)
	IsReceivingTopic(topic string) bool
	GetReceivingTopic() string
	setIDKey(key []byte) error
	setPubKey(key ed25519.PublicKey, clientID []byte) error
	removePubKey(clientID []byte) error
	resetPubKeys() error
	getPubKeys() (map[string]ed25519.PublicKey, error)
	setTopicKey(key, topicHash []byte) error
	removeTopic(topicHash []byte) error
	resetTopics() error
	setC2Key(newC2PubKey e4crypto.Curve25519PublicKey) error
}
type client struct {
	ID []byte
	TopicKeys map[string]keys.TopicKey
	Key keys.KeyMaterial
	ReceivingTopic string
	store ReadWriteSeeker
	lock  sync.RWMutex
}
var _ Client = (*client)(nil)
type ClientConfig interface {
	genNewClient(store ReadWriteSeeker) (*client, error)
}
type SymIDAndKey struct {
	ID  []byte
	Key []byte
}
type SymNameAndPassword struct {
	Name     string
	Password string
}
type PubIDAndKey struct {
	ID       []byte
	Key      e4crypto.Ed25519PrivateKey
	C2PubKey e4crypto.Curve25519PublicKey
}
type PubNameAndPassword struct {
	Name     string
	Password string
	C2PubKey e4crypto.Curve25519PublicKey
}
var _ ClientConfig = (*SymIDAndKey)(nil)
var _ ClientConfig = (*SymNameAndPassword)(nil)
var _ ClientConfig = (*PubIDAndKey)(nil)
var _ ClientConfig = (*PubNameAndPassword)(nil)
func (ik *SymIDAndKey) genNewClient(store ReadWriteSeeker) (*client, error) {
	var newID []byte
	if len(ik.ID) == 0 {
		newID = e4crypto.RandomID()
	} else {
		newID = make([]byte, len(ik.ID))
		copy(newID, ik.ID)
	}
	symKeyMaterial, err := keys.NewSymKeyMaterial(ik.Key)
	if err != nil {
		return nil, fmt.Errorf("failed to created symkey from key: %v", err)
	}
	return newClient(newID, symKeyMaterial, store)
}
func (np *SymNameAndPassword) genNewClient(store ReadWriteSeeker) (*client, error) {
	id := e4crypto.HashIDAlias(np.Name)
	key, err := e4crypto.DeriveSymKey(np.Password)
	if err != nil {
		return nil, fmt.Errorf("failed to derive key from password: %v", err)
	}
	symKeyMaterial, err := keys.NewSymKeyMaterial(key)
	if err != nil {
		return nil, fmt.Errorf("failed to created symkey from key: %v", err)
	}
	return newClient(id, symKeyMaterial, store)
}
func (ik *PubIDAndKey) genNewClient(store ReadWriteSeeker) (*client, error) {
	var newID []byte
	if len(ik.ID) == 0 {
		newID = e4crypto.RandomID()
	} else {
		newID = make([]byte, len(ik.ID))
		copy(newID, ik.ID)
	}
	pubKeyMaterialKey, err := keys.NewPubKeyMaterial(newID, ik.Key, ik.C2PubKey)
	if err != nil {
		return nil, fmt.Errorf("failed to create ed25519key from key: %v", err)
	}
	return newClient(newID, pubKeyMaterialKey, store)
}
func (np *PubNameAndPassword) genNewClient(store ReadWriteSeeker) (*client, error) {
	id := e4crypto.HashIDAlias(np.Name)
	key, err := e4crypto.Ed25519PrivateKeyFromPassword(np.Password)
	if err != nil {
		return nil, fmt.Errorf("failed to create ed25519 key from password: %v", err)
	}
	pubKeyMaterialKey, err := keys.NewPubKeyMaterial(id, key, np.C2PubKey)
	if err != nil {
		return nil, fmt.Errorf("failed to create ed25519key from key: %v", err)
	}
	return newClient(id, pubKeyMaterialKey, store)
}
func (np *PubNameAndPassword) PubKey() (e4crypto.Ed25519PublicKey, error) {
	key, err := e4crypto.Ed25519PrivateKeyFromPassword(np.Password)
	if err != nil {
		return nil, fmt.Errorf("failed to create ed25519 key from password: %v", err)
	}
	edKey, ok := ed25519.PrivateKey(key).Public().(ed25519.PublicKey)
	if !ok {
		return nil, errors.New("failed to cast key to ed25519.PublicKey")
	}
	return edKey, nil
}
type ClientOption interface {
	apply(c *client)
}
type withReceivingTopic string
func (w withReceivingTopic) apply(c *client) {
	c.ReceivingTopic = string(w)
}
func WithReceivingTopic(topic string) ClientOption {
	return withReceivingTopic(topic)
}
func NewClient(config ClientConfig, store ReadWriteSeeker, opts ...ClientOption) (Client, error) {
	c, err := config.genNewClient(store)
	if err != nil {
		return nil, err
	}
	for _, opt := range opts {
		opt.apply(c)
	}
	return c, nil
}
func newClient(id []byte, clientKey keys.KeyMaterial, store ReadWriteSeeker) (*client, error) {
	if len(id) == 0 {
		return nil, errors.New("client id must not be empty")
	}
	c := &client{
		Key:            clientKey,
		TopicKeys:      make(map[string]keys.TopicKey),
		ReceivingTopic: TopicForID(id),
		store: store,
	}
	c.ID = make([]byte, len(id))
	copy(c.ID, id)
	log.SetPrefix("e4client\t")
	if err := c.save(); err != nil {
		return nil, err
	}
	return c, nil
}
func LoadClient(store ReadWriteSeeker) (Client, error) {
	c := &client{}
	decoder := json.NewDecoder(store)
	err := decoder.Decode(c)
	if err != nil {
		return nil, err
	}
	if _, err := store.Seek(0, io.SeekStart); err != nil {
		return nil, err
	}
	c.store = store
	return c, nil
}
func (c *client) save() error {
	encoder := json.NewEncoder(c.store)
	if err := encoder.Encode(c); err != nil {
		log.Printf("failed to save client: %v", err)
		return err
	}
	if _, err := c.store.Seek(0, io.SeekStart); err != nil {
		log.Printf("failed to save client: %v", err)
		return err
	}
	return nil
}
func (c *client) UnmarshalJSON(data []byte) error {
	m := make(map[string]json.RawMessage)
	if err := json.Unmarshal(data, &m); err != nil {
		return fmt.Errorf("failed to unmarshal client from json: %v", err)
	}
	if rawKey, ok := m["Key"]; ok {
		clientKey, err := keys.FromRawJSON(rawKey)
		if err != nil {
			return fmt.Errorf("failed to unmarshal client key: %v", err)
		}
		c.Key = clientKey
	}
	if rawReceivingTopic, ok := m["ReceivingTopic"]; ok {
		if err := json.Unmarshal(rawReceivingTopic, &c.ReceivingTopic); err != nil {
			return fmt.Errorf("failed to unmarshal client receiving topic: %v", err)
		}
	}
	if rawTopicKeys, ok := m["TopicKeys"]; ok {
		if err := json.Unmarshal(rawTopicKeys, &c.TopicKeys); err != nil {
			return fmt.Errorf("failed to unmarshal client topicKeys: %v", err)
		}
	}
	if rawID, ok := m["ID"]; ok {
		if err := json.Unmarshal(rawID, &c.ID); err != nil {
			return fmt.Errorf("failed to unmarshal client ID: %v", err)
		}
	}
	return nil
}
func (c *client) ProtectMessage(payload []byte, topic string) ([]byte, error) {
	topicHash := hex.EncodeToString(e4crypto.HashTopic(topic))
	c.lock.RLock()
	topicKey, ok := c.TopicKeys[topicHash]
	c.lock.RUnlock()
	if !ok {
		return nil, ErrTopicKeyNotFound
	}
	protected, err := c.Key.ProtectMessage(payload, topicKey)
	if err != nil {
		return nil, err
	}
	return protected, nil
}
func (c *client) Unprotect(protected []byte, topic string) ([]byte, error) {
	if topic == c.ReceivingTopic {
		command, err := c.Key.UnprotectCommand(protected)
		if err != nil {
			return nil, err
		}
		err = processCommand(c, command)
		if err != nil {
			return nil, err
		}
		return nil, nil
	}
	topicHash := e4crypto.HashTopic(topic)
	c.lock.RLock()
	key, ok := c.TopicKeys[hex.EncodeToString(topicHash)]
	c.lock.RUnlock()
	if !ok {
		return nil, ErrTopicKeyNotFound
	}
	message, err := c.Key.UnprotectMessage(protected, key)
	if err == nil {
		return message, nil
	}
	if err != miscreant.ErrNotAuthentic {
		return nil, err
	}
	hashOfHash := hex.EncodeToString(e4crypto.HashTopic(string(topicHash)))
	topicKeyTs, ok := c.TopicKeys[hashOfHash]
	if !ok {
		return nil, miscreant.ErrNotAuthentic
	}
	if len(topicKeyTs) != e4crypto.KeyLen+e4crypto.TimestampLen {
		return nil, errors.New("invalid old topic key length")
	}
	topicKey := make([]byte, e4crypto.KeyLen)
	copy(topicKey, topicKeyTs[:e4crypto.KeyLen])
	timestamp := topicKeyTs[e4crypto.KeyLen:]
	if err := e4crypto.ValidateTimestampKey(timestamp); err != nil {
		return nil, err
	}
	return c.Key.UnprotectMessage(protected, topicKey)
}
func (c *client) IsReceivingTopic(topic string) bool {
	return topic == c.ReceivingTopic
}
func (c *client) GetReceivingTopic() string {
	return c.ReceivingTopic
}
func (c *client) setTopicKey(key, topicHash []byte) error {
	if err := e4crypto.ValidateTopicHash(topicHash); err != nil {
		return fmt.Errorf("invalid topic hash: %v", err)
	}
	c.lock.Lock()
	defer c.lock.Unlock()
	topicHashHex := hex.EncodeToString(topicHash)
	topicKey, ok := c.TopicKeys[topicHashHex]
	if ok {
		if !bytes.Equal(topicKey, key) {
			hashOfHash := e4crypto.HashTopic(string(topicHash))
			timestamp := make([]byte, e4crypto.TimestampLen)
			binary.LittleEndian.PutUint64(timestamp, uint64(time.Now().Unix()))
			topicKey = append(topicKey, timestamp...)
			c.TopicKeys[hex.EncodeToString(hashOfHash)] = topicKey
		}
	}
	newKey := make([]byte, e4crypto.KeyLen)
	copy(newKey, key)
	c.TopicKeys[topicHashHex] = newKey
	return c.save()
}
func (c *client) removeTopic(topicHash []byte) error {
	if err := e4crypto.ValidateTopicHash(topicHash); err != nil {
		return fmt.Errorf("invalid topic hash: %v", err)
	}
	c.lock.Lock()
	defer c.lock.Unlock()
	delete(c.TopicKeys, hex.EncodeToString(topicHash))
	hashOfHash := e4crypto.HashTopic(string(topicHash))
	delete(c.TopicKeys, hex.EncodeToString(hashOfHash))
	return c.save()
}
func (c *client) resetTopics() error {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.TopicKeys = make(map[string]keys.TopicKey)
	return c.save()
}
func (c *client) getPubKeys() (map[string]ed25519.PublicKey, error) {
	c.lock.RLock()
	defer c.lock.RUnlock()
	pkStore, ok := c.Key.(keys.PubKeyStore)
	if !ok {
		return nil, ErrUnsupportedOperation
	}
	return pkStore.GetPubKeys(), nil
}
func (c *client) setPubKey(key ed25519.PublicKey, clientID []byte) error {
	c.lock.Lock()
	defer c.lock.Unlock()
	pkStore, ok := c.Key.(keys.PubKeyStore)
	if !ok {
		return ErrUnsupportedOperation
	}
	if err := e4crypto.ValidateID(clientID); err != nil {
		return fmt.Errorf("invalid client ID: %v", err)
	}
	pkStore.AddPubKey(clientID, key)
	return c.save()
}
func (c *client) removePubKey(clientID []byte) error {
	c.lock.Lock()
	defer c.lock.Unlock()
	pkStore, ok := c.Key.(keys.PubKeyStore)
	if !ok {
		return ErrUnsupportedOperation
	}
	if err := e4crypto.ValidateID(clientID); err != nil {
		return fmt.Errorf("invalid client ID: %v", err)
	}
	err := pkStore.RemovePubKey(clientID)
	if err != nil {
		return err
	}
	return c.save()
}
func (c *client) resetPubKeys() error {
	c.lock.Lock()
	defer c.lock.Unlock()
	pkStore, ok := c.Key.(keys.PubKeyStore)
	if !ok {
		return ErrUnsupportedOperation
	}
	pkStore.ResetPubKeys()
	return c.save()
}