Skip to content

Commit

Permalink
feat: implement inverter timesync parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
smlx committed Dec 22, 2023
1 parent 51e9e35 commit 05a9cb8
Show file tree
Hide file tree
Showing 5 changed files with 381 additions and 10 deletions.
7 changes: 7 additions & 0 deletions mitm/outbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var (
meterMetrics1 = PacketType{0x03, 0x45}
meterTimeSyncRespAck = PacketType{0x03, 0x10}
// DNS G3 inverter outbound packet types
inverterTimeSync = PacketType{0x01, 0x03}
inverterMetrics0 = PacketType{0x01, 0x04}
inverterMetrics1 = PacketType{0x01, 0x45}
// protocol constants
Expand Down Expand Up @@ -137,6 +138,12 @@ func (h *OutboundPacketHandler) HandlePacket(
fmt.Errorf("couldn't handle inverter metrics packet: %v", err)
}
return nil, nil, nil
case inverterTimeSync:
if err := handleInverterTimeSyncPacket(bodyData, log); err != nil {
return nil, nil,
fmt.Errorf("couldn't handle inverter time sync packet: %v", err)
}
return nil, nil, nil
default:
spew.Dump(header)
if err := handleUnknownOutboundPacket(bodyData, log); err != nil {
Expand Down
31 changes: 31 additions & 0 deletions mitm/outboundinverter.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,34 @@ func handleInverterMetrics1Packet(
// TODO: record metrics
return nil
}

// handleInverterTimeSyncPacket handles time sync request packets.
func handleInverterTimeSyncPacket(
data []byte,
log *slog.Logger,
) error {
var timeSync OutboundInverterTimeSyncPacket
err := timeSync.UnmarshalBinary(data)
if err != nil {
return fmt.Errorf("couldn't unmarshal metrics: %v", err)
}
di, ok := deviceInfo[timeSync.DeviceID]
if !ok {
return fmt.Errorf("unknown device ID: %v", timeSync.DeviceID)
}
log.Debug("outbound metrics",
slog.String("device", di[0]),
slog.String("model", di[1]),
slog.String("serial", string(timeSync.DeviceSerial[:])))
labels := prometheus.Labels{
"device": di[0],
"model": di[1],
"serial": string(timeSync.DeviceSerial[:]),
}
fmt.Println(timeSync.Timestamp.Time())
spew.Dump(labels)
spew.Dump(timeSync)
// record metrics
// TODO: record metrics
return nil
}
271 changes: 262 additions & 9 deletions mitm/outboundmarshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,25 +120,25 @@ func (p *OutboundMeterMetricsPacket) UnmarshalBinary(data []byte) error {
return nil
}

// OutboundTimeSync is the cleartext body of an outbound time sync packet.
type OutboundTimeSync struct {
// OutboundMeterTimeSync is the cleartext body of an outbound time sync packet.
type OutboundMeterTimeSync struct {
PacketType [7]byte // 0x00-0x06 Packet type?
OutboundAddr [40]byte // 0x07-0x2e Outbound TCP addr. Null-terminated ASCII.
UnknownBytes1 [6]byte // 0x2f-0x34 Unknown
UnknownBytes2 [16]byte // 0x35-0x44 Serial number? ASCII.
UnknownInt0 int32 // 0x45-0x48 Unknown
UnknownBytes3 [4]byte // 0x49-0x4c Unknown
UnknownBytes4 [19]byte // 0x4d-0x5f Version numbers? Null-terminated ASCII.
Version [19]byte // 0x4d-0x5f Version numbers? Null-terminated ASCII.
}

// OutboundTimeSyncPacket represents the body of an outbound time sync packet.
type OutboundTimeSyncPacket struct {
// OutboundMeterTimeSyncPacket represents the body of an outbound time sync packet.
type OutboundMeterTimeSyncPacket struct {
OutboundEnvelopeTS
OutboundTimeSync
OutboundMeterTimeSync
}

// UnmarshalBinary implements binary.Unmarshaler
func (p *OutboundTimeSyncPacket) UnmarshalBinary(data []byte) error {
func (p *OutboundMeterTimeSyncPacket) UnmarshalBinary(data []byte) error {
envData, bodyData := data[:binary.Size(p.OutboundEnvelopeTS)],
data[binary.Size(p.OutboundEnvelopeTS):]
envBuf := bytes.NewBuffer(envData)
Expand All @@ -150,11 +150,11 @@ func (p *OutboundTimeSyncPacket) UnmarshalBinary(data []byte) error {
if err != nil {
return fmt.Errorf("couldn't decrypt ciphertext: %v", err)
}
if len(cleartext) != binary.Size(p.OutboundTimeSync) {
if len(cleartext) != binary.Size(p.OutboundMeterTimeSync) {
return fmt.Errorf("invalid cleartext length: %d", len(cleartext))
}
buf := bytes.NewBuffer(cleartext)
if err := binary.Read(buf, binary.BigEndian, &p.OutboundTimeSync); err != nil {
if err := binary.Read(buf, binary.BigEndian, &p.OutboundMeterTimeSync); err != nil {
return fmt.Errorf("couldn't read cleartext: %v", err)
}
return nil
Expand Down Expand Up @@ -443,3 +443,256 @@ func (p *OutboundInverterMetrics1Packet) UnmarshalBinary(data []byte) error {
}
return nil
}

// OutboundInverterTimeSync is the cleartext body of an outbound metrics packet.
type OutboundInverterTimeSync struct {
PacketType [7]byte
UnknownInt0 int32
UnknownInt1 int16
UnknownInt2 int16
UnknownInt3 int16
UnknownBytes1 [8]byte // fixed 0x00
UnknownInt4 int16
UnknownInt5 int16
Version [16]byte // Version number? Null-terminated ASCII.
UnknownInt6 int16
UnknownInt7 int16
UnknownInt8 int16
UnknownInt9 int16
UnknownInt10 int16
UnknownInt11 int32
UnknownInt12 int16
UnknownInt13 int32
UnknownBytes2 [6]byte // fixed 0x00
UnknownInt14 int16
UnknownInt15 int16
UnknownInt16 int32
UnknownInt17 int16
UnknownBytes3 [20]byte // fixed 0x00
UnknownInt18 int16
UnknownInt19 int16
UnknownBytes4 [16]byte // fixed 0x00
UnknownInt20 int16
UnknownInt21 int16
UnknownInt22 int16
UnknownInt23 int16
UnknownInt24 int16
UnknownInt25 int16
UnknownInt26 int16
UnknownInt27 int16
UnknownBytes5 [74]byte // fixed 0x00
UnknownInt28 int16
UnknownInt29 int16
UnknownInt30 int16
UnknownInt31 int16
UnknownInt32 int16
UnknownInt33 int16
UnknownInt34 int16
UnknownInt35 int32
UnknownInt36 int32
UnknownInt37 int16
UnknownBytes6 [4]byte // fixed 0xff
UnknownBytes7 [8]byte // fixed 0x00
UnknownInt38 int16
UnknownInt39 int16
OutboundDomain [52]byte // Outbound domain name. ASCII.
UnknownInt40 int16
UnknownInt41 int16
UnknownInt42 int16
UnknownInt43 int16
UnknownBytes8 [4]byte // fixed 0xff
UnknownInt44 int16
UnknownInt45 int16
UnknownInt46 int16
UnknownInt47 int16
DeviceModel [32]byte // Device model. Null-terminated ASCII.
UnknownInt48 int16
UnknownInt49 int16
UnknownBytes9 [4]byte // fixed 0x00
UnknownInt50 int16
UnknownInt51 int16
UnknownInt52 int16
UnknownInt53 int16
UnknownInt54 int16
UnknownInt55 int16
UnknownInt56 int16
UnknownInt57 int16
UnknownInt58 int16
UnknownInt59 int16
UnknownInt60 int32
UnknownInt61 int32
UnknownInt62 int32
UnknownInt63 int32
UnknownBytes10 [4]byte // fixed 0xff
UnknownBytes11 [4]byte // fixed 0x00
UnknownInt64 int16
UnknownInt65 int16
UnknownBytes12 [14]byte // fixed 0xff
UnknownInt66 int16
UnknownInt67 int16
UnknownInt68 int16
UnknownBytes13 [4]byte // fixed 0xff
UnknownInt69 int32
UnknownInt70 int32
UnknownInt71 int16
UnknownInt72 int16
UnknownInt73 int16
UnknownInt74 int16
UnknownInt75 int32
UnknownInt76 int32
UnknownInt77 int16
UnknownInt78 int16
UnknownInt79 int16
UnknownInt80 int16
UnknownInt81 int16
UnknownInt82 int16
UnknownInt83 int16
UnknownInt84 int16
UnknownInt85 int16
UnknownInt86 int16
UnknownInt87 int16
UnknownInt88 int16
UnknownInt89 int16
UnknownInt90 int16
UnknownInt91 int16
UnknownInt92 int16
UnknownInt93 int16
UnknownInt94 int16
UnknownInt95 int16
UnknownInt96 int16
UnknownInt97 int16
UnknownInt98 int16
UnknownInt99 int16
UnknownInt100 int16
UnknownInt101 int16
UnknownInt102 int16
UnknownInt103 int16
UnknownInt104 int16
UnknownInt105 int16
UnknownInt106 int16
UnknownInt107 int16
UnknownInt108 int16
UnknownInt109 int16
UnknownInt110 int16
UnknownInt111 int16
UnknownInt112 int16
UnknownInt113 int16
UnknownInt114 int32
UnknownInt115 int16
UnknownInt116 int16
UnknownInt117 int16
UnknownInt118 int16
UnknownInt119 int16
UnknownInt120 int16
UnknownInt121 int16
UnknownInt122 int16
UnknownInt123 int16
UnknownInt124 int16
UnknownInt125 int16
UnknownInt126 int32
UnknownInt127 int16
UnknownInt128 int16
UnknownInt129 int16
UnknownInt130 int16
UnknownInt131 int16
UnknownInt132 int16
UnknownInt133 int16
UnknownInt134 int16
UnknownInt135 int16
UnknownInt136 int16
UnknownInt137 int16
UnknownInt138 int16
UnknownInt139 int16
UnknownInt140 int32
UnknownInt141 int16
UnknownInt142 int16
UnknownInt143 int16
UnknownInt144 int16
UnknownBytes14 [12]byte // fixed 0xff
UnknownInt145 int32
UnknownInt146 int32
UnknownBytes15 [8]byte // fixed 0xff
UnknownBytes16 [48]byte // fixed 0xff
UnknownInt147 int32
UnknownInt148 int32
UnknownInt149 int16
UnknownInt150 int16
UnknownInt151 int32
UnknownInt152 int16
UnknownInt153 int16
UnknownInt154 int16
UnknownInt155 int16
UnknownInt156 int16
UnknownInt157 int16
UnknownInt158 int16
UnknownInt159 int16
UnknownInt160 int16
UnknownInt161 int16
UnknownInt162 int16
UnknownInt163 int16
UnknownInt164 int16
UnknownInt165 int16
UnknownInt166 int16
UnknownInt167 int16
UnknownInt168 int16
UnknownInt169 int16
UnknownInt170 int32
UnknownBytes18 [3]byte // fixed 0xff
}

// OutboundInverterTimeSyncPacket represents the body of an outbound time sync
// packet.
type OutboundInverterTimeSyncPacket struct {
OutboundEnvelopeTS
OutboundInverterTimeSync
}

// MarshalBinary implements binary.Marshaler
func (p *OutboundInverterTimeSyncPacket) MarshalBinary() ([]byte, error) {
var buf bytes.Buffer
// marshal envelope
err := binary.Write(&buf, binary.BigEndian, p.OutboundEnvelopeTS)
if err != nil {
return nil, fmt.Errorf("couldn't marshal %T: %v", p.OutboundEnvelopeTS, err)
}
// marshal metrics
var cleartextBuf bytes.Buffer
err = binary.Write(&cleartextBuf, binary.BigEndian, p.OutboundInverterTimeSync)
if err != nil {
return nil,
fmt.Errorf("couldn't marshal %T: %v", p.OutboundInverterTimeSync, err)
}
// encrypt metrics
ciphertext, err := encryptCleartext(p.OutboundEnvelopeTS.IV[:],
cleartextBuf.Bytes())
if err != nil {
return nil, fmt.Errorf("couldn't encrypt metrics: %v", err)
}
// ignore error since it is defined to always be nil
_, _ = buf.Write(ciphertext)
return buf.Bytes(), nil
}

// UnmarshalBinary implements binary.Unmarshaler
func (p *OutboundInverterTimeSyncPacket) UnmarshalBinary(data []byte) error {
envData, bodyData := data[:binary.Size(p.OutboundEnvelopeTS)],
data[binary.Size(p.OutboundEnvelopeTS):]
envBuf := bytes.NewBuffer(envData)
err := binary.Read(envBuf, binary.BigEndian, &p.OutboundEnvelopeTS)
if err != nil {
return fmt.Errorf("couldn't read unmarshal %T: %v", p.OutboundEnvelopeTS, err)
}
cleartext, err := decryptCiphertext(p.OutboundEnvelopeTS.IV[:], bodyData)
if err != nil {
return fmt.Errorf("couldn't decrypt ciphertext: %v", err)
}
if len(cleartext) != binary.Size(p.OutboundInverterTimeSync) {
return fmt.Errorf("invalid cleartext length: %d", len(cleartext))
}
buf := bytes.NewBuffer(cleartext)
err = binary.Read(buf, binary.BigEndian, &p.OutboundInverterTimeSync)
if err != nil {
return fmt.Errorf("couldn't read cleartext: %v", err)
}
return nil
}
Loading

0 comments on commit 05a9cb8

Please sign in to comment.