{"id":26730933,"url":"https://github.com/daedalus/gdl39","last_synced_at":"2025-03-27T23:34:17.331Z","repository":{"id":269852953,"uuid":"908659054","full_name":"daedalus/gdl39","owner":"daedalus","description":"GDL39 Protocol","archived":false,"fork":false,"pushed_at":"2024-12-26T16:19:04.000Z","size":33,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-26T17:19:57.651Z","etag":null,"topics":["ads-b","aviation","avionics","binary-protocol","fis-b","garmin","gdl39","gps","tis-b"],"latest_commit_sha":null,"homepage":"https://web.archive.org/web/20221006165540/http://www.chartbundle.com/tech/gdl39/","language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/daedalus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-26T16:12:23.000Z","updated_at":"2024-12-26T16:19:07.000Z","dependencies_parsed_at":"2024-12-26T17:20:00.930Z","dependency_job_id":"21cc0a57-7f18-4f80-9226-1b3c32aefd8a","html_url":"https://github.com/daedalus/gdl39","commit_stats":null,"previous_names":["daedalus/gdl39"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daedalus%2Fgdl39","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daedalus%2Fgdl39/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daedalus%2Fgdl39/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/daedalus%2Fgdl39/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/daedalus","download_url":"https://codeload.github.com/daedalus/gdl39/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245944020,"owners_count":20697945,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ads-b","aviation","avionics","binary-protocol","fis-b","garmin","gdl39","gps","tis-b"],"created_at":"2025-03-27T23:33:06.020Z","updated_at":"2025-03-27T23:34:17.314Z","avatar_url":"https://github.com/daedalus.png","language":"Perl","readme":"# gdl39\nGDL39 Protocol\n\nDISCLAIMER: I'm not the author, I don't know who it is. I just uploaded this for research purposes.\n\nQuick protype, written in perl, proof of concept only.\n\nThis documents the protocol as used by the Bluetooth Serial Profile(Standard RFCOMM)\n\nGeneral:\nPackets are delimited by 0x10 id length1 length2 ext1 ext2 .... checksum 0x10 0x03\n\nIf 'id'=0 then extended ID is in use, ext1 low byte and ext2 high byte. These are included in payload length, if id is != 0 then the ext bytes are omitted. If any non delimiter byte is a 0x10 then double it, do NOT include this double in the length count.\nLength is only 2 bytes if the max packet size has been increased, at initial connection time the length is 1 byte.\n\nChecksum is exactly as specified in the Garmin protocol for all their other devices, or look at the code, above.\n\niOS Protocol:\nAfter reviewing the docs for the 5th or 6th time, it appears that as a developer you can\ndevelop to the GDL39(or any MFI device) you cannot use it without the blessing of the\nhardware vendor. So, it's doubtful that Garmin is ever going to allow that.\n\nIt appears the the iOS protocol for the GDL39 is exactly the same as the Bluetooth SPP/RFCOMM protocol but using the iOS ExternalAccessory.framework.\n\nThe GDL39 enumerates with 4 protocols(EASession protocolString):\n\ncom.garmin.gdl39_a , com.garmin.gdl39_b , com.garmin.gdl39_c , com.garmin.gdl39_d\n \nInitial testing show these 4 perform the same, perhaps to allow multiple running programs to\nconnect at the same time(untested).\n\nSerial Port:\nThe serial port defaults to 9600, with the same protocol described below working. I expect the rate can be changed using the same protocol as other\nGarmin devices, but I didn't bother coding it up to check.(The described protocol seems very similar to what is used to change the size below.)\n\nWindows Tools:\n\nThe Garmin WebUpdater seems to be able to update the device over both serial to USB and Bluetooth connections. Or at least\nthe device is recognized and says the firmware is up to date on both connections.\n\n\n\nHandshake:\n\nBy far the most obnoxious part of the GDL39 is the handshake 'protocol'.\n\nTo prove the application identity it takes a few bytes the remote end sends. Encrypts them a couple\ntimes and sends them back.\n\n(Full source is in Gdl39.pm)\n\nAfter receiving the 255 packet(identity data shorts and text), the 0,33 packet(integer ID)\na traffic response for ourselves(just uses the time contained there) and the 0,305 packet(?)\n\nThe following 'magic' is done:\nCat the 255 packet bytes 2,3\nThe year(little endian short),month,day,hour,minute(bytes)\nthe remote ID as provided(packet 0,33)\nThe 255 packet bytes 0,1\nThe 0,305 packet bytes 2+3(some sort of counter)\n\nUse this as the key to Blowfish to encrypt the string you sent as the text in packet 255 earlier\n(in response to the 249 inquiry) padded to 56 bytes with 165(decimal).\nEncrypt this with blowfish, 16 round in ECB mode, 8 bytes per block. But reverse the bytes within a block before encrypting them and then once encrypted reverse the output bytes too.\nreverse( encrypt(reverse(1,2,3,4,5,6,7,8)) \nFor each of the 7 blocks of 8 bytes each. The block ordering is kept consistent.\n\nThen feed that crypted text into another blowfish as a 56 byte key, this time 11 round(why? I have no idea)\nEncrypting the value(decimal bytes):\n (90,107,15,126,203,50,85,170)\nBut first reverse it, then encrypt it, and then reverse that output,\nthen send it to the remote in a packet type 0,34\nIf all goes well you get an ack(6) and a 0,34 back with the remote signature, just ack it and move on.\n\n\nNow, after all that, you'll want to negotiate a larger packet size, which is much simpler.\n\n(Read the Code)\n\nThen enable GPS\nSend a message type 0,306 with payload of 2 bytes, first byte is update rate(1 or 5 Hz) second byte is which sentences to enable(bit 1: GPGGA, bit 2: GPRMC, bit 3: GPGSA) normal seems to be 3(1+2) and 7 gives you all 3. These are returned in mesage type 0,307 at the update rate specified.\n\nFile Requests:\nFile requests are a 3(ish) step process.\nRequest the file ( 0,0 ) then \"00 27 00 00 00 00 00 CE 02 00 00 xx 00 00 00 00 00 00 00\"\nand then the filename appended.\nxx is the ID you'll use to correlate the requested file with the returned data.\n\nThen you'll receive a 0,1 packet with various data, byte 14 being your requested ID, byte 16 being the handle that identifies this transaction and\nbytes 2-5 the file size(if 0, something went wrong, other bytes indicate why it failed)\nYou'll ack the 0,1 with a 0,8 packet ahd the same handle number.\n\nThen you'll get data in a series of 0,2 packets. Append each one to the data until you get the full length.\nEach data packet must be acked again with 0,8.\n(Code: send_x_req, dispatch{\"0_1\"} , dispatch{\"0_2\"} )\n\n\n\nThen ask for traffic when you want it.\nBasically, just send a file request for /RAM/TRAFFIC_STATE\nThis can also be a callback(the system will let you know when traffic is ready, unconfirmed and not yet \ncoded.\n\n\nThen decode the traffic:\nTake the returned packet, use deflate to expand it and then decode.\n\nAll data little endian(except actual strings), ss short(2), ii int(4) ff float(4) dd double(8)\nbb byte(1) \naa ascii(variable)\nlat and lon are in radians.\n\n\n0000 01 00 00 00 03 00 04 00 00 66 3E 00 CE 96 46 04 04 00 00 CE 96 46 08 04 DC 07 17 00 31 31 DD DD  \n     ss 22 ss 22 bb bb bb ff 22 33 44 ff 22 33 44 bb bb bb ff 22 33 44 bb bb ss 22 ss 22 bb bb dd 22\n     major minor          age         arrival     airborne time sec    mo da year  hh    mm ss lat\n                 ads status                          csa\n                    tcas installed                      surfaceIa\n                       tcas mode\n\n0020 22 A9 D5 60 E2 EE E7 21 C3 D3 DB 5B 00 FF 7E C1 5F 3A 00 00 00 00 00 00 00 00 54 C3 B8 43 42 2B  \n     33 44 55 66 77 88 dd 22 33 44 55 66 77 88 ff 22 33 44 ff 22 33 44 ff 22 33 44 ff 22 33 44 ff 22\n                       lon                     speed       track true  hdg deg     PA feet     geom feet\n\n0040 97 43 00 00 00 00 00 00 00 00 33 33 EF 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  \n     33 44 ff 22 33 44 ff 22 33 44 ff 22 33 44 ff 22 33 44 ii 22 33 44 bb bb 22 33 44 55 66 77 88 bb\n           ac len M    ac wid M    HFOM        crab deg\n\n\n0060 00 00 00 00 00 00 00 01 00 03 3F 01 00 00 01 00 02 00 00 00 69 00 00 00 05 00 0D 00 04 02 01 01  \n     22 33 44 55 66 77 88 bb bb bb ii 22 33 44 bb bb ii 22 33 44 ii 22 33 44 ii 22 33 44 bb bb bb bb\n                          airborne             datum             id          addr        trk source\n                             shadow               unk                                       Data Link\n                                track uncertainty    num report                                Address Qualifier\n                                   valid_flags                                                    airborne\n\n0080 00 00 56 3F 00 80 96 3F 01 01 01 00 00 00 08 26 1D 5F DC 25 E2 3F 8F 84 49 A3 56 50 00 C0 F9 71  \n     ff 22 33 44 ff 22 33 44 bb bb bb ss 22 bb dd 22 33 44 55 66 77 88 dd 22 33 44 55 66 77 88 ff 22\n     state age   ID Age      air cap           lat                     lon                     closure(M/sec)\n                                csa cap\n                                   surfIA cap\n                                      surfIA prio\n        \t\t\t      \t    pad(unknown)\n\n00A0 8D 42 AB 27 49 45 AB 67 4F 45 41 0F 32 45 59 2D 65 43 87 0A 94 43 6C D2 EC 41 DD 75 12 43 B3 36  \n     33 44 ff 22 33 44 ff 22 33 44 ff 22 33 44 ff 22 33 44 ff 22 33 44 ff 22 33 44 ff 22 33 44 ff 22\n           press alt   geom alt    rel alt     rel dir     true deg distance NM    rel brg     GS kts\n\n00C0 1F 43 00 00 0F C3 00 00 8C 42 00 00 20 C4 9B 36 1F 43 7E 0A 94 43 00 00 00 00 00 00 00 00 01 01  \n     33 44 ff 22 33 44 ff 22 33 44 ff 22 33 44 ff 22 33 44 ff 22 33 44 ff 22 33 44 ff 22 33 44 bb bb\n           EW kts      NS kts      FPM         rel kts     rel deg     cpa NM      cpa Sec     rel alt src\n                                                                                                  true dir datum\n \n00E0 01 02 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 54 56 4F 49 34 34 33 20 00 00 00 00 00 00  \n     bb bb bb bb bb bb bb bb bb bb ff 22 33 44 ff 22 33 44 aa 22 33 44 55 66 77 88 aa 22 33 44 55 66 \n     range src   Emitter Cat    is_tisb                    Callsign                Flt Plan ID\n        VV Src      Emerg Pri      AC Len M    AC Wid M   \n           VV Dir      CSA Alert\n              AlertStat   trafficIa Stat\n                             corrrelation status\n0100 00 00 CF 1B 00 00 68 00 00 00 44 43 A6 00 04 02 01 01 00 C4 B5 41 00 90 B3 40 01 01 01 01 00 00  \n     77 88 ii 22 33 44             ii 22 33 44\n           Valid Flag  ID          Address.....................\n\n\nGround Station Log:\nAs before, request the file(/RAM/GROUND_STATION_LOG), re-inflate it:\n(counting from 1)\nByte 1: Count of stations.\nByte 2-5: ?\nPer station:(starting at 6 for the first)\nByte 1-4: Site\nByte 5-13: Lat(double, radians)\nByte 14-22: Lon(double, radians)\nByte 23-26: Count\nByte 27-30: Different Count\n\nWeather:\nAll weather data follows the same general format. Request the file, get the result and then de-segment it:\nFirst int is the decoded size of the data.\nSecond int is the number of segments.\nFor each segment a pair of ints, offset into the file, and the size of the segment.\nSegment 0 is some meta data, remaining segments are the actual data grouped by type. For instance\nNOTAMS may have one segment with locations, one with geometries and one with text data.\nThe segments \u003e0 all appear to be compressed, look for the usual magic number and re-inflate them.\n\nThe current prototype code breaks the data into segments and decompresses but does no further decoding.\nThe following data types are known:\n\n/RAM/REG_NEXRAD\n/RAM/CONUS_NEXRAD\n/RAM/SUA\n/RAM/W_T_ALOFT\n/RAM/AIRMET\n/RAM/SIGMET\n/RAM/METAR\n/RAM/GRPH_METAR\n/RAM/NOTAM\n/RAM/PIREP\n/RAM/TAF\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaedalus%2Fgdl39","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdaedalus%2Fgdl39","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaedalus%2Fgdl39/lists"}