-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgdr.go
168 lines (143 loc) · 5.15 KB
/
gdr.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/*
* Generic Data Record - gdr.go
* Copyright (c) 2018 - 2024 TQ-Systems GmbH <[email protected]>, D-82229 Seefeld, Germany. All rights reserved.
* Author: Alexander Pögelt and the Energy Manager development team
*
* This software code contained herein is licensed under the terms and conditions of
* the TQ-Systems Product Software License Agreement Version 1.0.1 or any later version.
* You will find the corresponding license text in the LICENSE file.
* In case of any license issues please contact [email protected].
*/
//go:generate protoc --gogofaster_out=Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types:. gdr.proto
//go:generate omitemptyremover
package gdr
import (
"bytes"
"crypto/md5" // #nosec G501 -- Not used in a security context, but to detect changes of a GCR
"encoding/gob"
"encoding/json"
"fmt"
"regexp"
"strconv"
"time"
"github.com/gogo/protobuf/types"
)
var obisCodeMatch = regexp.MustCompile(`^(?:([0-9]+)-)?(?:([0-9]+):)?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:[*&]([0-9]+))?$`)
// String tranforms ObisCode into human-readable string
func (obisCode OBISCode) String() string {
return fmt.Sprintf("%d-%d:%d.%d.%d*%d", obisCode.Media, obisCode.Channel, obisCode.Indicator, obisCode.Mode, obisCode.Quantities, obisCode.Storage)
}
// DecodeOBISCodeToString transforms an integer-encoded OBIS code into a human-readable string
func DecodeOBISCodeToString(value uint64) string {
return DecodeOBISCode(value).String()
}
// Encode encodes an OBIS code as a 64bit integer
func (obisCode OBISCode) Encode() uint64 {
// uint64 = 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// All ObisCodes max 8 Bits
// Shift First ObisCode Value 5 Bytes
// Shift Second ObisCode Value 4 Bytes
// ...
return shift(obisCode.Media, 5) |
shift(obisCode.Channel, 4) |
shift(obisCode.Indicator, 3) |
shift(obisCode.Mode, 2) |
shift(obisCode.Quantities, 1) |
shift(obisCode.Storage, 0)
}
// DecodeOBISCode decodes an integer-encoded OBIS code
func DecodeOBISCode(value uint64) OBISCode {
return OBISCode{
Media: unshift(value, 5),
Channel: unshift(value, 4),
Indicator: unshift(value, 3),
Mode: unshift(value, 2),
Quantities: unshift(value, 1),
Storage: unshift(value, 0),
}
}
func parseByte(store *uint8, num string) bool {
if num == "" {
*store = 255
return true
}
v, err := strconv.ParseUint(num, 10, 8)
*store = uint8(v) // #nosec G115 - max value of store is 255
return err == nil
}
// ParseOBISCode converts an OBIS code in standard text representation
// to an OBISCode struct
func ParseOBISCode(code string) *OBISCode {
match := obisCodeMatch.FindStringSubmatch(code)
if match == nil {
return nil
}
ret := OBISCode{}
if !parseByte(&ret.Media, match[1]) ||
!parseByte(&ret.Channel, match[2]) ||
!parseByte(&ret.Indicator, match[3]) ||
!parseByte(&ret.Mode, match[4]) ||
!parseByte(&ret.Quantities, match[5]) ||
!parseByte(&ret.Storage, match[6]) {
return nil
}
return &ret
}
func shift(value uint8, shift uint) uint64 {
// Shift Value n Bytes
return uint64(value) << (8 * shift)
}
func unshift(value uint64, shift uint) uint8 {
// Unshift Value n Bytes
return uint8(value >> (8 * shift)) // #nosec G115 - max value is 255
}
// TimeToTimestamp converts a golang time to a protobuf timestamp
func TimeToTimestamp(t time.Time) *types.Timestamp {
return &types.Timestamp{
Seconds: t.Unix(),
Nanos: int32(t.Nanosecond()), // #nosec G115 - safe conversion
}
}
// CalculateGCRHash calculates the MD5 checksum of GCRs
// in order for the hash to be the same for equivalent GCRs
func CalculateGCRHash(gcrs *GCRs) ([16]byte, error) {
// make copy to prevent changes in real GCRs struct
tmpMap := make(map[string]*GCR)
if err := deepCopy(gcrs.GCRs, &tmpMap); err != nil {
return [16]byte{}, fmt.Errorf("couldn't copy GCRs map: %v", err)
}
// ignore timestamp
for key := range tmpMap {
tmpMap[key].Timestamp = nil
}
bytes, err := json.Marshal(tmpMap)
if err != nil {
return [16]byte{}, fmt.Errorf("couldn't marshal GCRs map: %v", err)
}
return md5.Sum(bytes), nil // #nosec G401 -- Not used in a security context, but to detect changes of GCRs
}
// CalculateSingleGCRHash calculates the MD5 checksum of a GCR
func CalculateSingleGCRHash(gcr *GCR) ([16]byte, error) {
// copy to prevent changes in actual GCR
var tmpGCR *GCR
if err := deepCopy(gcr, &tmpGCR); err != nil {
return [16]byte{}, fmt.Errorf("couldn't create deep copy of GCR: %v", err)
}
tmpGCR.Timestamp = nil // ignore timestamp to prevent calculating different hashes on the same configuration
bytes, err := json.Marshal(tmpGCR)
if err != nil {
return [16]byte{}, fmt.Errorf("couldn't marshal GCR: %v", err)
}
return md5.Sum(bytes), nil // #nosec G401 -- Not used in a security context, but to detect changes of a GCR
}
// using encoder and decoder to avoid problems with references in underlying structures
func deepCopy(in, out interface{}) error {
buf := new(bytes.Buffer)
if err := gob.NewEncoder(buf).Encode(in); err != nil {
return fmt.Errorf("cannot encode input: %v", err)
}
if err := gob.NewDecoder(buf).Decode(out); err != nil {
return fmt.Errorf("cannot decode output: %v", err)
}
return nil
}