#from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes #from cryptography.hazmat.backends import default_backend #from cryptography.hazmat.primitives.padding import PKCS7 #rtl_sdr -f 869.5m -s 1000000 -g 35 -p 70 - | aprs/c/lorarx -i /dev/stdin -f u8 -r 1000000 -o 118000 -v -b 6 -s 7 -s 8 -L 127.0.0.1:9002 -s 9 -s 10 -s 11 -s 12 -s -12 -s -11 -s -10 -s -9 -s -8 -s -7 -N -Q -M 869.618 -J 127.0.0.1:5200 from base64 import b64encode, b64decode import json, socket, wx, time, select, sys from random import randint #from random import randint #from struct import pack, unpack from M2Crypto.EVP import HMAC import pyaes from hashlib import sha256 from zlib import crc32 #import axolotl_curve25519 as curve #from curvexxx25519 import x25519 #------------------------- config SYMB="M," # for aprs output JSONPORT=5200 # from lorarx AXUDP=("192.168.1.46",9002) # axudp output to igate or aprsmap metainfo=True # axudp2 extension DEBUGVERB=True TXIP=("192.168.1.46",7000) HASHES={ "public" :"8b3387e9c5cdea6ac9e5edbaa115cd72", "#bot" :"", "#capitolhill" :"", "#emergency" :"", "#hamradio" :"", "#queer" :"", "#seattle" :"", "#sports" :"", "#testing" :"", "#switzerland" :"", "#switzerla" :"", "#test" :"", "#qrv" :"", "#andy73" :"", "#augsburg" :"", "#schwaben" :"", "#freiburg" :"", "#jota" :"", "#austria" :"", "#muenchen" :"", "#region-muc" :"", "#netzstatus" :"", "#hard-software" :"", "#offtopic" :"", "#warnings" :"", "#wetter" :"", "#rezepte" :"", "#rosenheim" :"", "#passau" :"", "bnc" :"f43b927977d26ef4b60b581719c3137c", "#flachwitze" :"", "#ankuendigungen" :"", "#hard-software" :"" } """ #bot eb50a1bcb3e4e5d7bf69a57c9dada211 #capitolhill 7f281916c8ec32e13c5ef687d182160a #emergency e1ad578d25108e344808f30dfdaaf926 #hamradio 83c8b01997654265938da8765cbc7db9 #queer 5754476f162d93bbee3de0efba136860 #seattle ef627a9bbbb549347fdb76bf0cd3bc14 #sports e8ee81f3aabf105d9ba2d2d4bd94fe4a #testing cde5e82cf515647dcb547a79a4f065d1 #switzerland An open channel where everybody is welcome to chat! 8ad1ce57ad257627090ed28413c1f0b7 #test 9cd8fcf22a47333b591d96a2b848b73f #bnc "f43b927977d26ef4b60b581719c3137c" """ LORASF=8 LORABW=62500 #------------------------- config MESHSIZETIME=7200 # seconds since last heard a node to use for meshsize count MAXRANDOM=20 # tickertimes random maximal wait before tx TICKERTIME=0.1 # base for dcd random waits IP=("0.0.0.0",JSONPORT) longcalls=False # True for 7..9 char calls or ssid >15 if axudp2 destination is able to read this LAYER2RETRYS=3 MAXQUELEN=20 DCDTIMELIMIT=30 PACKETMAXAGE=300 NODESNR=8 NODERELAYNODE=9 NODECNT=10 NODEHEARSME=11 NODEPUBKEY=12 DCDTABDCD=0 DCDTABRANDOM=1 DCDTABTIMEOUT=2 DCDTABDCDSUM=3 DCDTABDCDSUMTIME=4 def colorstr(col, s): return "\033["+str(30+(col&15))+";1;1m"+s+"\033[0m" def printred(col, s): if col: print(colorstr(1,s), end="\033[0m") else: print(s, end="") def vprint(t): if VERB: print(t) def veprint(t, end): if VERB: print(t, end=end) def hexstrtobytes(h): s=b"" for i in range(0, len(h), 2): s+=bytes([int(h[i:i+2], 16)]) return s def bytestohex(b): s="" for i in b: s+=hex(i)[2:4].zfill(2) return s def bytestostr(b): s="" for c in b: s+=chr(c) return s def card32(p, d): try: return int(d[p] + (d[p+1]<<8) + (d[p+2]<<16) + (d[p+3]<<24)) except: return 0 def int32(p, d): n=card32(p, d) if n>(1<<31): n-=1<<32 return n def card128(d): # 7 bit per byte l=len(d) n=0 while l>0: l-=1 n=(n<<7) + (d[l]&127) return n #def int128(d): # n=card128(d) # if n>=(1<<(len(d)*7-1)): n=-n # return n def save(nodes): fd=open(MSGFN+"~", "w") # for n in nodes: fd.write(hex(n)[2:11].upper().zfill(8) + str(nodes[n]) + "\n") # for n in nodes: fd.write(hex(n)[2:11].upper().zfill(8) + "," + nodes[n][0] + "," + nodes[n][1] + "," + str(nodes[n][2]) + "," + str(nodes[n][3]) + "\n") for n in nodes: fd.write(mactostr(n) + json.dumps(nodes[n]) + "\n") fd.close() os.rename(MSGFN+"~", MSGFN) """ def loadn(): PURGELONG=86400*7 PURGESHORT=86400*1 now=time.time() try: with open(TEXTF, "r") as fd: for s in fd: w=s[8:len(s)-1] n=json.loads(w) e=[] # c=0 for i in n: e+=[i] # if c==5: e+=[0] # c+=1 while len(e)<13: e+=[0] # e[11]="" # e+=b"" pt=PURGESHORT if e[NODELONGNAME] or (e[NODELAT] and e[NODELONG]): pt=PURGELONG elif e[NODECNT]<=1: pt=0 if e[NODETIME]+pt>=now: nodes[int(s[0:8],16)]=e except IOError: print("no database file "+ TEXTF) """ def int32bytes(n): return bytes([n&255, n>>8&255, n>>16&255, n>>24&255]) # return bytes([n>>24&255, n>>16&255, n>>8&255, n&255]) def uhex(c): return "["+hex(c)[2:4].zfill(2).upper() + "]" def cleanstr(s, withhex): h="" u=False em=b"" for c in s: if type(c)==type("A"): c=ord(c) if c==0xc3: u=True else: if em: em+=bytes([c]) if len(em)>=4: try: h+=bytes.decode(em) except: # print(" emoji-err ", end="") h+=uhex(em[0])+uhex(em[1])+uhex(em[2])+uhex(em[3]) em=b"" elif u: if c==0xa4: h+="ä" elif c==0xb6: h+="ö" elif c==0xbc: h+="ü" elif c==0x84: h+="Ä" elif c==0x96: h+="Ö" elif c==0x9c: h+="Ü" elif c==0x9f: h+="ß" else: h+=uhex(0x3c) h+=uhex(c) elif c>=128 and withhex==2: em=bytes([c]) elif c>=32 and c<128: h+=chr(c) elif withhex: h+=uhex(c) u=False return h """ def cleanstr(s, withhex): h="" for c in s: if type(c)!=type("A"): c=chr(c) if c>=" ": h+=c elif withhex: h+="["+hex(ord(c))[2:4].zfill(2).upper() + "]" return h """ def cleanaprs(s): h="" for c in s: if type(c)!=type("A"): c=chr(c) if c>=" " and c0: i-=1 r+=bytes([b[i]]) return r def macextract(data): # destination : 4 bytes # sender : 4 bytes # packetID : 4 bytes # flags : 1 byte # channelHash : 1 byte # nexthop : 1 byte # relaynode : 1 byte # data : 0-237 bytes meshPacketHex = { 'dest' : data[0:4], 'sender' : data[4:8], 'packetID' : data[8:12], 'flags' : data[12:13], 'channelHash' : data[13:14], 'nexthop' : data[14:15], 'relaynode' : data[15:16], 'data' : data[16:len(data)] } return meshPacketHex #================== crypto stuff def decrypt(ciphertext, key): h = HMAC(key, algo="sha256") ctxt=ciphertext[2:] h.update(ctxt) txt=b"" if h.final()[:2]==ciphertext[:2]: aes = pyaes.AESModeOfOperationECB(key) while len(ctxt)>=16: txt+=aes.decrypt(ctxt[:16]) ctxt=ctxt[16:] if len(ctxt)>0: txt+=aes.decrypt((ctxt+bytes([0])*15)[:16])[:len(ctxt)] # txt=hexstrtobytes(os.popen("aprs/c/meshcore/mcrypt " + bytestohex(key)+ " " + bytestohex(ciphertext)).read()) # print("key:"+bytestohex(key),bytestohex(ciphertext), "text["+txt+"]") return txt def encrypt(txt, key): ctxt=b"" aes = pyaes.AESModeOfOperationECB(key) while len(txt)>0: if len(txt)<16:txt+=bytes([0])*(16-len(txt)) #pad to full 16 byte ctxt+=aes.encrypt(txt[:16]) txt=txt[16:] h = HMAC(key, algo="sha256") h.update(ctxt) return h.final()[:2]+ctxt #====================== end crypto #====================== send def loratx(payload, sf, cr, bw, preamb): global txtime be=b64encode(payload) s="" for c in be:s+=chr(c) if VERB: print("txp:",end="") hexpr(payload[0:4], False) hexpr(payload[4:8], False) hexpr(payload[8:12], False) hexpr(payload[12:16], False) hexpr(payload[16:], False) print("") # print("t:",json.dumps({"payload":s, "cr":5, "sf":11, "bw":8}).encode("utf-8")) sock.sendto(json.dumps({"payload":s, "sf":int(sf), "cr":cr, "bw":int(bw), "preamb":preamb}).encode("ascii"), TXIP) bwhz=((1<>8 bd=bwhz//(1<>5 or routingack: # only correct repeated frame shall ack i=0 while i0 and f["mymsg"]: ack(f["packetID"], True, False) if f["mymsg"]: ack(f["packetID"], True, False) txque[i]["acked"]=True txque[i]["retrycnt"]=0 txque[i]["sendat"]=layer2delay() + time.time() # start new retry timer for collecting repeated my frames if rn and ((flags&7)+1)==(ord(f["flags"])&7) and f["mymsg"]: updatehears(rn) # store who has my tx repeated if routingack: printred(f["sender"]==mymac[0:4], "----routing ack-deque:"+mactostr(f["packetID"])) else: print("-----deque:", mactostr(f["packetID"])) i+=1 def que(frame, retries, delay, mymsg): i=0 while i=len(txque) and len(txque)>7 if n: b+=128 s+=bytes([b]) if n==0: break return s def encpos(lat, long, alt, locsrc): if alt<0: alt=0 b=postag(1,5, int32bytes(int(lat*10000000.0))) + postag(2,5, int32bytes(int(long*10000000.0))) + postag(3,0, cardvar(alt)) + postag(5,0, cardvar(locsrc)) return bytes([8,3]) + nodetag(2,True,b) #08 03 12 0d 1cc29900 15 07c551a0 2801 #08 03 12 25 0d 0000361c 150000de07 18cb04 259c4fb768 2802 589c01 7800 8001 f09dd601 9801 0ab8010e4800{ 1 5}{ 2 5}{ 3 0}{ 4 5}{ 5 0}{ 11 0}{ 15 0}{ 16 0}{ 19 5} sats: 214{ 0 1} unknown pos tag: 0 #nodeinfo:08 04 12 4a 0a 09 216138346335323730 12 0b 416e647265615f35323830 1a 04 35796170 22 06 84cca84c5270 28 05 42 20 6800fc4d33ab3e7ddb6675b2d45d2147bc2ac7592b44a600d85fbfc36e154c3e 4801 hw:[!a84c5270] n:[Andrea_5280] sn:[5yap] hw6:84cca84c5270 # ll tt ll ll tt ll tt ll tt ll #define meshtastic_User_id_tag 1 #define meshtastic_User_long_name_tag 2 #define meshtastic_User_short_name_tag 3 #define meshtastic_User_macaddr_tag 4 #define meshtastic_User_hw_model_tag 5 #define meshtastic_User_is_licensed_tag 6 #define meshtastic_User_role_tag 7 #define meshtastic_User_public_key_tag 8 #define meshtastic_User_is_unmessagable_tag 9 #text:[08][01][12][06]Servus #rx:A2EB4098 dst:FFFFFFFF mid:CE78364 [OE5DMO] 1.0dB -31Hz sf:11 rn:0c 1/7 hash:08 nh:00 text:[08][01][12]JSo, gerade Meshcore zum ersten mal ausprobiert: Mausetot in Steyr, 0 NodesH[00] sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) sock.bind(IP) def sendaprs(obj, tx, lat, long, alt, speed, heading, relay, flags, name, sname, mac, dir, dirc, heardc): ms="" axmeta={} if obj and metainfo: if longcalls: axmeta["longcalls"]=2 try: v=obj["snr"] except: pass else: ms+=" snr="+"{:.1f}".format(v)+"dB" axmeta["snr"]=v try: v=obj["afc"] except: pass else: ms+=" afc="+"{:.0f}".format(v)+"Hz" axmeta["afc"]=v # try: v=obj["preamb"] # except: pass # else: # ms+=" txd="+"{:.0f}".format(v)+"ms" # axmeta["txdel"]=v try: v=obj["level"] except: pass else: ms+=" lev="+"{:.0f}".format(v)+"dB" axmeta["level"]=v try: v=obj["fec"] except: pass else: ms+=" fec="+"{:.0f}".format(v) try: v=obj["eye"] except: pass else: ms+=" q="+"{:.0f}".format(v)+"%" axmeta["quality"]=v try: v=obj["bw"] except: pass else: ms+=" bw="+"{:.0f}".format(v)+"Hz" try: v=obj["net"] except: pass else: ms+=" net="+"{:X}".format(v) try: v=obj["rxmhz"] except: pass else: ms+=" rx="+"{:.3f}".format(v)+"MHz" try: v=obj["sf"] vi=obj["invers"] except: pass else: ms+=" sf="+"{:.0f}".format(v) if vi: ms+="(inv)" try: v=obj["cr"] except: pass else: ms+=" cr="+"{:.0f}".format(v) # try: v=obj["ver"] # except: pass # else: ms+=" vers="+v # while len(tx)<9: tx+=" " hops=flags & 7 starthops=(flags>>5) & 7 if hops>starthops: starthops=8 s=MYCALL+">APZMTC,WIDE" + str(starthops) + "-" + str(hops) astr="" if alt>-1000: astr=wx.altaprs(alt) ds="" if heading<1: heading=360 if speed>0: ds=wx.speeddir(speed, heading) s+=":)"+tx + "!" + wx.lataprs(lat) + SYMB[0] + wx.longaprs(long) + SYMB[1] + ds + astr + wx.dao(lat,long) + "rn:" + hex(relay)[2:4].zfill(2) + " hop:" + str(flags&7) + "/" + str(flags>>5) if type(dirc)!=type(0): dirc=0 s+=" h/d:" + str(heardc) + "/" + str(dirc) if name: s+=" name:[" + name + "]" if sname: s+=" s:[" + sname + "]" if mac: s+=" mac:" + mac if (flags>>3)&1: s+=" WantAck" if (flags>>4)&1: s+=" ViaMQTT" s+=ms try: wx.sendax(sock, s, AXUDP, axmeta) except: print("axudp encode error") def newnodedb(id): #time signature lat long name typ heardc directheardc path snr if not id in nodes:nodes[id]=[0,"",0.0,0.0,"",0,0,0,"",0.0] def addnode(id, sig, lat, ong, name, typ, path, snr): # hwid=card32(0, mph["sender"]) newnodedb(id) nodl=list(nodes[id]) nodl[NODETIME]=int(time.time()) nodl[NODEC]+=1 if len(path)==0: nodl[NODEDIRECTC]+=1 nodl[NODESNR]=snr def addhash(h): if h in hashes: hashes[h]+=1 else: hashes[h]=1 def addmid(payload): m=crc32(payload) & 0xffffffff if m in mids: mids.pop(mids.index(m)) # delete old entry to append new at end elif len(mids)>10000: mids.pop(0) # delete oldest mids.append(m) def havemid(payload): return (crc32(payload) & 0xffffffff) in mids def filewrln(fn, txt): with open(fn, "a") as fd: fd.write(txt+"\n") def wrtexts(t): if MSGFN: try: filewrln(MSGFN, t) except: print("-------- msg file write error") def rstr(s): p=len(s) while p>0 and s[p-1]==0: p-=1 return s[:p] def grptxt(path, fr, dat, dupe): if dupe: vprint(" dupe") elif fr: if VERB: print(" hash:"+hex(fr[0])[2:4].zfill(2), end=" ") for i in channels: # txt=decrypt(fr[1:], b64decode("izOH6cXN6mrJ5e26oRXNcg==".encode('ascii'))) if fr[0]==i["hash"]: # print(" hashmatch:", i["hash"], fr[0]) txt=decrypt(fr[1:], i["secret"]) if txt: # print("chh:", i["secret"]) # print("org:", fr[1:]) # print("enc:", encrypt(txt, i["secret"])) pt="" if path: pt="["; for b in path: pt+=hex(b)[2:4].zfill(2)+"," pt=pt[:len(pt)-1]+"]" if len(txt)>3: timestamp=int(txt[0]+(txt[1]<<8)+(txt[2]<<16)+(txt[3]<<24)) if VERB: print("["+time.ctime(timestamp)+"]", end="") if dat: if VERB: print("DATA:", end="") if VERB: hexpr(txt[5:],False) else: xtext=i["name"]+"["+cleanstr(rstr(txt[5:]),2)+"]" # cut time and zero byte if VERB: printred(1,xtext) wrtexts(time.ctime(timestamp)+" "+pt+" "+xtext) break elif VERB: printred(1, " hashmatch but no decrypt "+hex(i["hash"])[2:4].zfill(2)) """ txt=decrypt(fr[1:], b64decode("izOH6cXN6mrJ5e26oRXNcg==".encode('ascii'))) 8b3387e9c5cdea6ac9e5edbaa115cd72 if txt: printred(1," text1:["+cleanstr(txt,1)+"]") txt=decrypt(fr[1:], hexstrtobytes("8ad1ce57ad257627090ed28413c1f0b7".encode('ascii'))) if txt: printred(1," text2:["+cleanstr(txt,1)+"]") txt=decrypt(fr[1:], hexstrtobytes("9cd8fcf22a47333b591d96a2b848b73f".encode('ascii'))) if txt: printred(1," text3:["+cleanstr(txt,1)+"]") txt=decrypt(fr[1:], hexstrtobytes("f43b927977d26ef4b60b581719c3137c".encode('ascii'))) if txt: printred(1," text4:["+cleanstr(txt,1)+"]") """ def sendgrptxt(ch, txt): t=int(time.time()) fr=b"" key=b""; for c in channels: if ch==c["name"]: key=c["secret"] hash=c["hash"] break if key: timetxt=bytes([t&255, (t>>8)&255, (t>>16)&255, (t>>24)&255, 0]) + alltobytes(txt) # time + 0 + text fr=bytes([1+(5<<2), 0, hash]) + encrypt(timetxt, key) if VERB: print("encr:",bytestohex(fr)) # return fr def advert(fr, dupe): if dupe: vprint(" dupe") elif len(fr)>=109: pubkey=fr[:32] timestamp=int(fr[32]+(fr[33]<<8)+(fr[34]<<16)+(fr[35]<<24)) signature=fr[36:100] app=fr[100] lat=0 long=0 p=101 if VERB: print(" hash:"+hex(pubkey[0])[2:4].zfill(2),"[",time.ctime(timestamp),"]", end="") if (app>>4)&1 and len(fr)>=109: # has position lat= int(fr[101]+(fr[102]<<8)+(fr[103]<<16)+(fr[104]<<24))*0.000001 long=int(fr[105]+(fr[106]<<8)+(fr[107]<<16)+(fr[108]<<24))*0.000001 p+=8 if lat or long: if VERB: print(" pos:"+str(lat)+","+str(long), end="") if app&15==1: veprint(" ChatNode", end="") elif app&15==2: veprint(" Repeater", end="") elif app&15==3: veprint(" RoomServer", end="") elif app&15==4: veprint(" Sensor", end="") if (app>>7)&1: # has name name=cleanstr(fr[p:], True) if VERB: print(" [" + name + "]", end="") def decodemeshcore(payload, obj): # payload=sendgrptxt("#test","test") p=payload[0] if VERB: print() pp=p&3 transc=False if pp==0: transc=True # has transport code if VERB: print("TRANSPORT_FLOOD", end="") elif pp==1: veprint("FLOOD", end="") elif pp==2: veprint("DIRECT", end="") else: transc=True # has transport code if VERB: print("TRANSPORT_DIRECT", end="") pt=(p>>2)&15 s="UNDEF" if pt==0: s="REQ" elif pt==1: s="RESPONSE" elif pt==2: s="TXT_MSG" elif pt==3: s="ACK" elif pt==4: s="ADVERT" elif pt==5: s="GRP_TXT" elif pt==6: s="GRP_DATA" elif pt==7: s="ANON_REQ" elif pt==8: s="PATH" elif pt==9: s="TRACE" elif pt==10: s="MULTIPART" elif pt==11: s="CONTROL" elif pt==15: s="RAW_CUSTOM" if VERB: print(" "+s, end="") ppath=1 if transc and len(payload)>4: ppath=5 if VERB: print(" TRANSP:"+hex((payload[1]<<24) + (payload[2]<<16) + (payload[3]<<8) + payload[4])[2:10].zfill(8), end="") p=min(payload[ppath], len(payload)-ppath-1, 63) # path len ppath+=1 if pt!=9: if p>0: if VERB: print(" Path[", end="") for i in range(p): digi=payload[i+ppath] if VERB: print(hex(digi)[2:4].zfill(2), end="") if (i+1)!=p: veprint(",", end="") addhash(digi) if VERB: print("]", end="") elif VERB: print(colorstr(2," Path[]"), end="") dupe=havemid(payload[p+ppath:]) if pt==4:advert(payload[p+ppath:], dupe) elif pt==5 or pt==6:grptxt(payload[ppath:p+ppath], payload[p+ppath:], pt==6, dupe) if VERB: print(obj) addmid(payload[p+ppath:]) """ |3: Append(ps, "ACK"); |4: Append(ps, "ADVERT"); |5: Append(ps, "GRP_TXT"); |6: Append(ps, "GRP_DATA"); |7: Append(ps, "ANON_REQ"); |8: Append(ps, "PATH"); |9: Append(ps, "TRACE"); |10: Append(ps, "MULTIPART"); |11: Append(ps, "CONTROL"); |15: Append(ps, "RAW_CUSTOM"); ELSE Append(ps, "UNDEF") END; """ # hexpr(payload, False) # print("") MSGFN="" VERB=False argc=len(sys.argv) i=1 while i write channel messages") print("-h this") print("-v verbous") quit() elif sys.argv[i]=="-v": VERB=True elif sys.argv[i]=="-m" and i+112: sf=0 if not sf in dcdtab: dcdtab[sf]=[0,0,0,0,0] if "payload" in obj: try: payload=b64decode(obj["payload"]) # base64 coded frame except: print("base64 decode error") else: if "crc" in obj and obj["crc"]==1 and len(payload)>=4: decodemeshcore(payload, obj) # important!!!!!!: always check for crc ok before use if sf and "duration" in obj and "preamb" in obj: dcdtab[sf][DCDTABDCDSUM]+=obj["duration"]+obj["preamb"] # sum dcd time if dcdtab[sf][DCDTABDCDSUMTIME]==0: dcdtab[sf][DCDTABDCDSUMTIME]=now # set sum start time elif sf and ("dcd" in obj): dcdtab[sf][DCDTABDCD]=obj["dcd"] # do we have dcd on this sf if nowf>=fastticker: fastticker=nowf+TICKERTIME # print("dcd:",dcd) chanfree=True if LORASF in dcdtab: if dcdtab[LORASF][DCDTABDCD]: # dcd on this sf if dcdtab[LORASF][DCDTABRANDOM]==0: # dcd random time start dcdtab[LORASF][DCDTABRANDOM]=randint(0, MAXRANDOM) dcdtab[LORASF][DCDTABTIMEOUT]=now+DCDTIMELIMIT # channel busy, start random time for tx chanfree=dcdtab[LORASF][DCDTABRANDOM]<=0 or dcdtab[LORASF][DCDTABTIMEOUT]