Multi device sync/backup

This commit is contained in:
Bruno Rybársky 2024-09-05 20:59:46 +02:00
commit c9544a6421
13 changed files with 578 additions and 0 deletions

0
.gitignore vendored Normal file

8
.idea/.gitignore vendored Normal file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

9
.idea/homecontroller.iml Normal file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/homecontroller.iml" filepath="$PROJECT_DIR$/.idea/homecontroller.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

125
device.go Normal file

@ -0,0 +1,125 @@
package main
import (
"crypto/rand"
"encoding/binary"
"errors"
)
func getNewID(clients []StoredClient) uint32 {
newID := uint32(0)
for _, client := range clients {
if client.ID >= newID {
newID = client.ID + 1
}
}
return newID
}
func addDevice(name string, deviceType uint8, clients []StoredClient) (key []byte, usedID uint32) {
key = make([]byte, 64)
_, err := rand.Read(key)
if err != nil {
panic(err)
}
usedID = getNewID(clients)
storedClients = append(storedClients, StoredClient{
Key: key,
Name: name,
ID: usedID,
Type: deviceType,
Privileges: 0,
})
return key, usedID
}
func addController(name string, clients []StoredClient) (key []byte, usedID uint32) {
key = make([]byte, 64)
_, err := rand.Read(key)
if err != nil {
panic(err)
}
usedID = getNewID(clients)
storedClients = append(storedClients, StoredClient{
Key: key,
Name: name,
ID: usedID,
Type: 0,
Privileges: 1,
})
return key, usedID
}
func (client *Device) setChannel(id uint8, value uint8) error {
if client.channels == nil || uint8(len(client.channels)) >= id {
return errors.New("channel impossible to set")
}
var outBuffer []byte
outBuffer = append(outBuffer, id)
outBuffer = append(outBuffer, value)
return client.client.sendPacket(2, outBuffer)
}
func (client *Device) getDeviceChannelResponse(targetDeviceID uint32, id uint8) error {
if clients == nil || uint32(len(clients)) >= targetDeviceID {
return errors.New("target device is nonexistent")
}
clientDevice := clients[targetDeviceID]
if clientDevice.channels == nil || uint8(len(clientDevice.channels)) >= id {
return errors.New("channel impossible to get")
}
var outBuffer []byte
var deviceID []byte
binary.LittleEndian.PutUint32(deviceID, targetDeviceID)
outBuffer = append(outBuffer, deviceID...)
outBuffer = append(outBuffer, id)
outBuffer = append(outBuffer, clientDevice.channels[id])
return client.client.sendPacket(4, outBuffer)
}
func (client *Device) readChannel(id uint8) error {
if client.channels == nil || uint8(len(client.channels)) >= id {
return errors.New("channel impossible to get")
}
var outBuffer []byte
outBuffer = append(outBuffer, id)
return client.client.sendPacket(3, outBuffer)
}
func (client *Device) readChannels() error {
if client.channels == nil || uint8(len(client.channels)) == 0 {
return errors.New("channels impossible to get")
}
var err error
for channelID := range client.channels {
err = client.readChannel(uint8(channelID))
if err != nil {
return err
}
}
return nil
}
func (client *Device) receiveChannel(data []byte) error {
if len(data) < 1 {
return errors.New("channel data too short")
}
id := data[0]
if client.channels == nil || uint8(len(client.channels)) >= id {
return errors.New("channel impossible to get")
}
value := data[4:5][0]
client.channels[id] = value
return nil
}
var deviceChannelCount = []uint8{0, 1, 1, 2}
//0: phone
//1: switch
//2: dimmer
//3: gate

44
dimmerDevice.go Normal file

@ -0,0 +1,44 @@
package main
import "fmt"
type DimmerDevice struct {
baseDevice *Device
lastBrightness uint8
}
// init
func createDimmerDevice(device *Device) *DimmerDevice {
device.typeID = 1
return &DimmerDevice{baseDevice: device}
}
func (dimmerDevice *DimmerDevice) on() error {
if dimmerDevice.baseDevice.channels[0] != 1 {
if dimmerDevice.lastBrightness == 0 {
dimmerDevice.lastBrightness = 255
}
return dimmerDevice.baseDevice.setChannel(0, dimmerDevice.lastBrightness)
} else {
return nil
}
}
func (dimmerDevice *DimmerDevice) off() error {
if dimmerDevice.baseDevice.channels[0] != 2 {
dimmerDevice.lastBrightness = dimmerDevice.baseDevice.channels[0]
return dimmerDevice.baseDevice.setChannel(0, 1)
} else {
return nil
}
}
func (dimmerDevice *DimmerDevice) getStringState() string {
if dimmerDevice.baseDevice.channels[0] == 1 {
return "OFF"
} else if dimmerDevice.baseDevice.channels[0] > 1 {
percentVal := dimmerDevice.baseDevice.channels[0] / 255
return fmt.Sprintf("ON at %d%%", percentVal)
}
return "Unknown"
}

48
gateDevice.go Normal file

@ -0,0 +1,48 @@
package main
type GateDevice struct {
baseDevice *Device
}
// init
func createGateDevice(device *Device) *GateDevice {
device.typeID = 3
return &GateDevice{baseDevice: device}
}
//states:
//0: unknown
//1: closed
//2: closing
//3: opening
//4: open
func (gateDevice *GateDevice) open() error {
if gateDevice.baseDevice.channels[0] != 3 && gateDevice.baseDevice.channels[1] != 4 {
return gateDevice.baseDevice.setChannel(0, 3)
} else {
return nil
}
}
func (gateDevice *GateDevice) close() error {
if gateDevice.baseDevice.channels[0] != 1 && gateDevice.baseDevice.channels[1] != 2 {
return gateDevice.baseDevice.setChannel(0, 2)
} else {
return nil
}
}
func (gateDevice *GateDevice) getStringState() string {
switch gateDevice.baseDevice.channels[0] {
case 1:
return "Closed"
case 2:
return "Closing"
case 3:
return "Opening"
case 4:
return "Open"
}
return "Unknown"
}

3
go.mod Normal file

@ -0,0 +1,3 @@
module homecontroller
go 1.23.0

0
go.sum Normal file

54
main.go Normal file

@ -0,0 +1,54 @@
package main
import (
"encoding/json"
"errors"
"log"
"net"
"os"
)
var clients []*Device
var storedClients []StoredClient
func main() {
clients = make([]*Device, 0)
data, err := os.ReadFile("clients.json")
if err != nil {
panic(err)
}
err = json.Unmarshal(data, &storedClients)
if err != nil {
panic(err)
}
// Create a connection to the server
addr, err := net.ResolveTCPAddr("tcp", "0.0.0.0")
if err != nil {
log.Fatal("Error resolving address " + err.Error())
}
listen, err := net.ListenTCP("tcp", addr)
if err != nil {
log.Fatal(err)
}
// close listener
defer func(listen *net.TCPListener) {
_ = listen.Close()
}(listen)
go func() {
for {
conn, err := listen.AcceptTCP()
if err != nil {
var opErr *net.OpError
if errors.As(err, &opErr) && opErr.Err.Error() == "use of closed network connection" {
log.Println("Listener closed, stopping server.")
return
}
log.Println("Error accepting connection:", err)
continue
}
go handleRequest(conn)
}
}()
}

231
netCode.go Normal file

@ -0,0 +1,231 @@
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"errors"
"fmt"
"hash/crc32"
"net"
"time"
)
type Device struct {
cipher *cipher.Block
conn *net.TCPConn
device *Device
name string
id uint32
typeID uint8
channels []uint8
client *Device
lastHeardFrom int64
privileges uint8
}
type StoredClient struct {
Key []byte `json:"key"`
Name string `json:"name"`
ID uint32 `json:"id"`
Type uint8 `json:"type"`
Privileges uint8 `json:"privileges"`
}
// sendPacket sends a packet with the given packetID and data over the provided TCP connection.
func sendPacket(packetID uint8, data []byte, client *Device) error {
// Create a buffer with the packet ID and data
tempBuffer := append([]byte{packetID}, data...)
// Calculate the checksum of the packet ID + data
checksum := crc32.ChecksumIEEE(tempBuffer)
// Convert the checksum to a 4-byte slice
checksumBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(checksumBytes, checksum)
var encryptedTempBuffer []byte
if client.cipher != nil {
(*client.cipher).Encrypt(encryptedTempBuffer, tempBuffer)
} else {
encryptedTempBuffer = tempBuffer
}
// Calculate the total length (checksum + packet ID + data)
packetLength := uint32(len(encryptedTempBuffer) + 4)
// Prepare the output buffer with the length field
outBuffer := make([]byte, 4)
binary.LittleEndian.PutUint32(outBuffer, packetLength)
// Append the tempBuffer (checksum + packet ID + data) to outBuffer
outBuffer = append(outBuffer, checksumBytes...)
outBuffer = append(outBuffer, tempBuffer...)
// Send the packet over the connection
writeLen, err := client.conn.Write(outBuffer)
if err != nil {
return err
}
if writeLen != len(outBuffer) {
return errors.New("write length does not match output buffer length")
}
return nil
}
func (client *Device) sendPacket(packetID uint8, data []byte) error {
return sendPacket(packetID, data, client)
}
func (client *Device) receivePacket() (uint8, uint32, []byte, error) {
return receivePacket(client)
}
func updateLastSeen(client *Device) {
if client.conn != nil {
client.lastHeardFrom = time.Now().Unix()
}
}
// receivePacket receives a packet from the provided TCP connection, decrypts it, and verifies it.
func receivePacket(client *Device) (uint8, uint32, []byte, error) {
// Step 1: Read the packet length (4 bytes)
lengthBuffer := make([]byte, 4)
n, err := client.conn.Read(lengthBuffer)
if err != nil {
return 0, 0, nil, err
}
if n != len(lengthBuffer) {
return 0, 0, nil, errors.New("read length does not match length buffer")
}
packetLength := binary.LittleEndian.Uint32(lengthBuffer)
// Step 2: Allocate a buffer to hold the rest of the packet (checksum + packetID + data)
packetBuffer := make([]byte, packetLength)
n, err = client.conn.Read(packetBuffer)
if err != nil {
return 0, 0, nil, err
}
if n != int(packetLength) {
return 0, 0, nil, errors.New("read length does not match packet length")
}
// Step 3: Decrypt the packet buffer
decryptedPacketBuffer := make([]byte, len(packetBuffer))
if client.cipher != nil {
(*client.cipher).Decrypt(decryptedPacketBuffer, packetBuffer)
} else {
decryptedPacketBuffer = packetBuffer
}
// Step 4: Extract the checksum (4 bytes)
receivedChecksum := binary.LittleEndian.Uint32(decryptedPacketBuffer[:4])
// Step 5: Extract the packet ID (1 byte)
packetID := decryptedPacketBuffer[4]
// Step 6: Extract the data
data := decryptedPacketBuffer[5:]
// Step 7: Verify the checksum
calculatedChecksum := crc32.ChecksumIEEE(append([]byte{packetID}, data...))
if receivedChecksum != calculatedChecksum {
return 0, 0, nil, errors.New("checksum mismatch")
}
// Return the packet ID and data
updateLastSeen(client)
return packetID, uint32(len(data)), data, nil
}
func (client *Device) receiveConnectionRequest(data []byte) {
if len(data) < 4 {
clientID := binary.LittleEndian.Uint32(data[0:4])
for _, clientLoop := range storedClients {
if clientLoop.ID == clientID {
client.name = clientLoop.Name
client.id = clientID
if clientLoop.Privileges > 0 {
clientLoop.Type = 0
}
client.device = &Device{
typeID: clientLoop.Type,
channels: make([]uint8, deviceChannelCount[clientLoop.Type]),
client: client,
privileges: clientLoop.Privileges,
}
clientCipher, err := aes.NewCipher(clientLoop.Key)
if err != nil {
fmt.Println("Error creating AES cipher")
return
}
client.cipher = &clientCipher
}
}
}
}
func (client *Device) receiveChannelUpdate(data []byte) {
if len(data) < 2 && len(client.channels) != 0 {
channelID := data[0]
channelValue := data[1]
if channelID >= uint8(len(client.channels)) {
return
}
client.channels[channelID] = channelValue
}
}
func handleRequest(conn *net.TCPConn) {
//serverbound packets:
//0: start connection
//1: reserved
//2: channel value
//3: list devices
//4: set device channel
//5: get device channel
//6: list devices
//7: list admins
//8: add device
//9: add controller
//clientbound packets:
//2: set channel
//3: get channel
//4: channel from device
//5: added device key and id
//6: device entry
//7: admin entry
client := Device{
cipher: nil,
conn: conn,
}
clients = append(clients, &client)
defer func(conn *net.TCPConn) {
_ = conn.Close()
}(conn)
for {
packetID, _, packetData, err := receivePacket(&client)
if err != nil {
return
}
switch packetID {
case 0:
if client.cipher == nil {
client.receiveConnectionRequest(packetData)
} else {
return
}
case 1:
case 2:
if client.cipher != nil {
client.receiveChannelUpdate(packetData)
}
}
}
}

42
switchDevice.go Normal file

@ -0,0 +1,42 @@
package main
type SwitchDevice struct {
baseDevice *Device
}
// init
func createSwitchDevice(device *Device) *SwitchDevice {
device.typeID = 1
return &SwitchDevice{baseDevice: device}
}
//states:
//0: unknown
//1: on
//2: off
func (switchDevice *SwitchDevice) on() error {
if switchDevice.baseDevice.channels[0] != 1 {
return switchDevice.baseDevice.setChannel(0, 1)
} else {
return nil
}
}
func (switchDevice *SwitchDevice) off() error {
if switchDevice.baseDevice.channels[0] != 2 {
return switchDevice.baseDevice.setChannel(0, 2)
} else {
return nil
}
}
func (switchDevice *SwitchDevice) getStringState() string {
switch switchDevice.baseDevice.channels[0] {
case 1:
return "On"
case 2:
return "Off"
}
return "Unknown"
}