# Copyright © 2025-26 l5yth & contributors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require "base64"
require "meshtastic"
require "openssl"

channel_name = "BerlinMesh"

# === Inputs from your packet ===
cipher_b64 = "Q1R7tgI5yXzMXu/3"
psk_b64 = "Nmh7EooP2Tsc+7pvPwXLcEDDuYhk+fBo2GLnbA1Y1sg="
packet_id = 3_915_687_257
from_id = "!9e95cf60"
channel = 35

# === Decode key and ciphertext ===
key = Base64.decode64(psk_b64)       # 32 bytes -> AES-256
ciphertext = Base64.decode64(cipher_b64)

# === Derive numeric node id from Meshtastic-style string ===
hex_str = from_id.sub(/^!/, "")          # "9e95cf60"
from_node = hex_str.to_i(16)               # 0x9e95cf60

# === Build nonce exactly like Meshtastic CryptoEngine ===
# Little-endian 64-bit packet ID + little-endian 32-bit node ID + 4 zero bytes
nonce = [packet_id].pack("Q<")            # uint64, little-endian
nonce += [from_node].pack("L<")            # uint32, little-endian
nonce += "\x00" * 4                        # extraNonce == 0 for PSK channel msgs

raise "Nonce must be 16 bytes" unless nonce.bytesize == 16
raise "Key must be 32 bytes" unless key.bytesize == 32

# === AES-256-CTR decrypt ===
cipher = OpenSSL::Cipher.new("aes-256-ctr")
cipher.decrypt
cipher.key = key
cipher.iv = nonce

plaintext = cipher.update(ciphertext) + cipher.final

# At this point `plaintext` is the raw Meshtastic protobuf payload
plaintext = plaintext.bytes.pack("C*")
data = Meshtastic::Data.decode(plaintext)
msg = data.payload.dup.force_encoding("UTF-8")
puts msg

# Gets channel number from name and psk
def channel_hash(name, psk_b64)
  name_bytes = name.b          # UTF-8 bytes
  psk_bytes = Base64.decode64(psk_b64)

  hn = name_bytes.bytes.reduce(0) { |acc, b| acc ^ b }   # XOR over name
  hp = psk_bytes.bytes.reduce(0) { |acc, b| acc ^ b }    # XOR over PSK

  (hn ^ hp) & 0xFF
end

channel_h = channel_hash(channel_name, psk_b64)
puts channel_h
puts channel == channel_h
