| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099 |
- # Meta information
- meta:
- # Document name and ID
- id: csp
- name: Chat Server Protocol
- # References used by the structs
- references:
- # A public or secret key
- key: &key b32
- # A random cookie
- cookie: &cookie b16
- # A random nonce
- nonce: &nonce b24
- # A Threema ID
- identity: &identity b8
- # Multiple Threema IDs
- identities: &identities b8[]
- # A message ID
- message-id: &message-id u64-le
- # Multiple message IDs
- message-ids: &message-ids u64-le[]
- # A blob ID
- blob-id: &blob-id b16
- # A poll ID
- poll-id: &poll-id u64-le
- # A group ID
- group-id: &group-id u64-le
- # Virtual namespace, just containing the below docstring
- index: &index
- _doc: |-
- # Chat Server Protocol
- The Chat Server Protocol is a custom transport encrypted frame-based
- protocol, originally designed to operate on top of TCP. it uses the NaCl
- cryptography library to provide authentication, integrity and encryption.
- The login [**handshake**](ref:handshake) takes two round trips and
- establishes ephemeral encryption keys along the way. Authentication is
- solely based on the secret key associated to a Threema ID.
- After the handshake process, [**payloads**](ref:payload) can be exchanged
- bidirectionally although some payload structs may only be used in one
- direction. A client may now send and receive end-to-end encrypted
- [**messages**](ref:e2e) (wrapped in [message payload
- structs](ref:payload.container)).
- ## Terminology
- - `CK`: Client Key (permanent secret key associated to the Threema ID)
- - `SK`: Permanent Server Key
- - `TCK`: Temporary Client Key
- - `TSK`: Temporary Server Key
- - `CCK`: Client Connection Cookie
- - `SCK`: Server Connection Cookie
- - `CSN`: Client Sequence Number
- - `SSN`: Server Sequence Number
- - `ID`: The client's Threema ID
- ## General Information
- **Endianness:** All integers use little-endian encoding.
- **Encryption cipher:** XSalsa20-Poly1305, unless otherwise specified.
- **Nonce format:**
- - a 16 byte cookie (CCK/SCK), followed by
- - a monotonically increasing sequence number (CSN/SSN, u64-le).
- **Sequence number:** The sequence number starts with `1` and is counted
- separately for each direction (i.e. there is one sequence number counter for
- the client and one for the server). We will use `CSN+` and `SSN+` in this
- document to denote that the counter should be increased **after** the value
- has been inserted (i.e. semantically equivalent to `x++` in many languages).
- ## Size Limitations
- The chat server protocol currently allows for up to 8192 bytes within a
- single frame. Because we make heavy use of Protobuf messages, the overhead
- cannot be calculated reliably ahead of time. Therefore, the total amount of
- user-defined bytes should be constrained to ~7000 bytes. To achieve this,
- the maximum recommended size of each property will be defined for each
- message, so that it's total size roughly matches that constraint.
- # Handshake structs
- handshake: &handshake
- _doc: |-
- ## Handshake
- To perform authentication handshake, the following handshake structs have to
- be exchanged in this order:
- C -- client-hello -> S
- C <- server-hello -- S
- C ---- login ---- -> S
- C <-- login-ack ---- S
- Note that handshake structs have no wrapping frame container struct.
- client-hello:
- _doc: |-
- Initial message from the client, containing a server authentication
- challenge in order to establish transport layer encryption.
- Direction: Client --> Server
- fields:
- - _doc: |-
- 32 byte temporary public key (`TCK.public`).
- name: tck
- type: *key
- - _doc: |-
- 16 byte random cookie used for nonces (also acting as server
- authentication challenge).
- name: cck
- type: *cookie
- server-hello:
- _doc: |-
- Initial message from the server, containing the server's authentication
- challenge response. This concludes establishing transport layer
- encryption based on `TCK` and `TSK`.
- Direction: Client <-- Server
- When creating this message:
- 1. Ensure that CCK and SCK are not equal.
- When receiving this message:
- 1. If CCK and SCK are equal, abort the connection and these steps.
- 2. If the repeated random cookie of the client does not equal CCK,
- abort the connection and these steps.
- fields:
- - _doc: |-
- 16 byte random cookie used for nonces (also acting as client
- authentication challenge)
- name: sck
- type: *cookie
- - _doc: |-
- The server's challenge response (`server-challenge-response`),
- encrypted by:
- XSalsa20-Poly1305(
- key=X25519HSalsa20(SK.secret, TCK.public),
- nonce=SCK || u64-le(SSN+),
- )
- name: server-challenge-response-box
- type: b64
- server-challenge-response:
- _doc: |-
- Authentication challenge response from the server.
- fields:
- - _doc: |-
- 32 byte temporary public key (`TSK.public`)
- name: tsk
- type: *key
- - _doc: |-
- 16 byte repeated random cookie of the client (acting as the server's
- challenge response)
- name: cck
- type: *cookie
- login:
- _doc: |-
- Login request from the client.
- IMPORTANT: `CSN` is used and increased for `box` and then for
- `extension-box`. It must follow this exact order.
- Direction: Client --> Server
- fields:
- - _doc: |-
- The [`login-data`](ref:handshake.login-data), encrypted by:
- XSalsa20-Poly1305(
- key=X25519HSalsa20(TCK.secret, TSK.public),
- nonce=CCK || u64-le(CSN+),
- )
- name: box
- type: b144
- - _doc: |-
- An optional arbitrary amount of
- [`extension`](ref:handshake.extension)s, encrypted by:
- XSalsa20-Poly1305(
- key=X25519HSalsa20(TCK.secret, TSK.public),
- nonce=CCK || u64-le(CSN+),
- )
- These fields are only present if the
- [`extension-indicator`](ref:handshake.extension-indicator) of the
- [`login-data`](ref:handshake.login-data) field is present. If so,
- extensions should be consumed until the extension indicator `length`
- field is zero.
- name: extensions-box
- type: b*
- login-data:
- _doc: |-
- Login data of the client.
- fields:
- - _doc: |-
- Threema ID of the client.
- name: identity
- type: *identity
- - _doc: |-
- This is either the old client info field or an extension indicator.
- If the first 30 bytes of the field start with the string
- `threema-clever-extension-field`, then parse this field as an
- [`extension-indicator`](ref:handshake.extension-indicator) and parse
- `extensions-box` appropriately.
- Otherwise, this represents an old client info and the content is
- identical to the content of
- [`client-info`](ref:handshake.client-info). Since the field has a
- fixed size, the string is zero-padded.
- name: client-info-or-extension-indicator
- type: b32
- - _doc: |-
- 16 byte repeated random cookie of the server (acting as the client's
- challenge response)
- name: sck
- type: *cookie
- - _doc: |-
- 24 zero bytes (previously used as vouch nonce, now set to zero
- indicating that the new vouch format is being used)
- name: reserved1
- type: b24
- - _doc: |-
- The vouch value, calculated as follows:
- SS1 = X25519HSalsa20(CK.secret, SK.public)
- SS2 = X25519HSalsa20(CK.secret, TSK.public)
- VouchKey = BLAKE2b(key=SS1 || SS2, salt='v2', personal='3ma-csp')
- vouch = BLAKE2b(
- out-length=32,
- key=VouchKey,
- input=SCK || TCK.public,
- )
- name: vouch
- type: b32
- - _doc: |-
- 16 zero bytes (previously part of the vouch box, now set to zero for
- compatibility)
- name: reserved2
- type: b16
- extension-indicator:
- _doc: |-
- Indicates that extensions are present
- fields:
- - _doc: |-
- Magic string: `threema-clever-extension-field`
- name: magic
- type: b30
- - _doc: |-
- Amount of encrypted bytes present for extensions. Extension fields
- need to be consumed until `length` is zero.
- name: length
- type: u16-le
- extension:
- _doc: |-
- An extension field.
- fields:
- - _doc: |-
- Type of the extension. Must correspond to the encoded extension struct
- of the `payload` field:
- - `0x00`: `client-info`
- - `0x01`: `csp-device-id`
- - `0x02`: `message-payload-version`
- - `0x03`: `device-cookie`
- name: type
- type: u8
- - _doc: |-
- Length of the extension's `payload` field.
- name: length
- type: u16-le
- - _doc: |-
- Extension payload. Needs to be parsed according to the `type` field.
- name: payload
- type: b{length}
- client-info:
- _doc: |-
- Client info extension payload.
- fields:
- - _doc: |-
- Client info string in the following format (without line breaks):
- <app-version>;
- <platform>;
- <lang>/<country-code>;
- <rest>
- The `<rest>` looks like this for mobile clients (A/I/W):
- <device-model>;
- <os-version>
- The `<rest>` looks like this for web/desktop clients (Q):
- <renderer>;
- <renderer-version>;
- <os-name>;
- <os-architecture>
- The `<rest>` looks like this for Bots (B):
- <os-name>;
- <os-architecture>
- The fields may contain the following values:
- - `app-version`: Arbitrary version string, depending on the platform
- - `platform`:
- * `A`: Android
- * `I`: iOS
- * `Q`: Desktop/Web
- * `W`: Windows Phone
- * `B`: Bot
- - `lang`: ISO 639-1:2002-ish language code
- - `country-code`: ISO 3166-1-ish country code
- - `device-model`: Arbitrary smartphone model
- - `os-version`: Arbitrary OS version string
- - `renderer`: Renderer name for Desktop/Web (e.g. `Firefox` or
- `Electron`)
- - `renderer-version`: Renderer major version (e.g. `107`)
- - `os-name`: Name of the operating system (e.g. `Linux` or `Windows`)
- - `os-architecture`: Architecture of the operating system (e.g. `x64`)
- name: client-info
- type: b*
- csp-device-id:
- _doc: |-
- CSP device ID extension payload.
- fields:
- - _doc: |-
- CSP device ID, randomly generated **once** when the device got the
- Mediator device ID.
- name: csp-device-id
- type: u64-le
- message-payload-version:
- _doc: |-
- Message payload struct version to be used.
- In case this extension is not present, the server must assume that
- version `0x00` has been selected.
- In case the server receives an unknown or unsupported protocol version,
- it shall complete the handshake and then immediately send a `close-error`
- payload.
- fields:
- - _doc: |-
- Indicates the payload struct version the client will send and expects
- to receive when exchanging message payload structs with the server:
- - `0x00`: `legacy-message`
- - `0x01`: `message-with-metadata-box`
- name: version
- type: u8
- device-cookie:
- _doc: |-
- A 16 byte random value chosen by the client and stored in a secure,
- device-specific location (not included in any backups etc., not
- viewable/exportable).
- Its purpose is to allow detection when a different (rogue) device has
- connected to the chat server, e.g. because an attacker has obtained
- the secret key of a user.
- The server will store the device cookie of the last connection, and if a
- different cookie is sent by the client, it will set a flag on the identity
- and send a
- [`device-cookie-change-indication`](ref:payload.device-cookie-change-indication)
- payload to the client every time it connects. The client should then show
- a warning in form of a notification or a dialog to the user. Note that the
- normal protocol flow should continue regardless of whether the user has
- acknowledged the warning or not.
- If this extension is not sent by the client, then the server's behavior
- depends on whether it has already stored a device cookie for this
- identity or not. If not, then nothing will happen. If yes, then it will
- act as if the client had sent an all-zero device cookie.
- fields:
- - _doc: |-
- Device cookie, randomly generated **once** per device.
- name: device-cookie
- type: b16
- login-ack:
- _doc: |-
- Login acknowledgement from the server.
- Direction: Client <-- Server
- fields:
- - _doc: |-
- Reserved (16 zero bytes), encrypted by:
- XSalsa20-Poly1305(
- key=X25519HSalsa20(TSK.secret, TCK.public),
- nonce=SCK || u64-le(SSN+),
- )
- name: reserved-box
- type: b32
- # Payload structs
- payload: &payload
- _doc: |-
- ## Payload
- After the handshake process, payloads may be sent and received without any
- strictly defined order.
- Note that payload structs are mandatory to encrypt and frame. To achieve
- this, first wrap the payload struct in a
- [`container`](ref:payload.container) struct, encrypt it and wrap the
- encrypted bytes in a [`frame`](ref:payload.frame) struct.
- frame:
- _group: Header
- _doc: |-
- Contains an encrypted [payload](ref:payload#payload) wrapped in a
- [container](ref:payload.container).
- Direction: Client <-> Server
- fields:
- - _doc: |-
- Length of the `box` field.
- name: length
- type: u16-le
- - _doc: |-
- The encrypted [payload](ref:payload#payload).
- For messages from the server to the client, encrypted by:
- XSalsa20-Poly1305(
- key=X25519HSalsa20(TSK.secret, TCK.public),
- nonce=SCK || u64-le(SSN+),
- )
- For messages from the client to the server, encrypted by:
- XSalsa20-Poly1305(
- key=X25519HSalsa20(TSK.secret, TCK.public),
- nonce=CCK || u64-le(CSN+),
- )
- name: box
- type: b{length}
- container:
- _group: Header
- _doc: |-
- Contains an inner [payload](ref:payload#payload) struct.
- Direction: Client <-> Server
- fields:
- - _doc: |-
- Type of the payload. Must correspond to the encoded payload struct
- of the `data` field:
- - `0x00`: [`echo-request`](ref:payload.echo-request)
- - `0x80`: [`echo-response`](ref:payload.echo-response)
- - `0x01`: outgoing [`legacy-message`](ref:payload.legacy-message) or
- [`message-with-metadata-box`](ref:payload.message-with-metadata-box)
- - `0x81`: outgoing [`message-ack`](ref:payload.message-ack)
- - `0x02`: incoming [`legacy-message`](ref:payload.legacy-message) or
- [`message-with-metadata-box`](ref:payload.message-with-metadata-box)
- - `0x82`: incoming [`message-ack`](ref:payload.message-ack)
- - `0x03`: [`unblock-incoming-messages`](ref:payload.unblock-incoming-messages)
- - `0x20`: [`set-push-notification-token`](ref:payload.set-push-notification-token)
- - `0x21`: (obsolete, formerly used by iOS to set a push filter)
- - `0x22`: (obsolete, formerly used by iOS to set a push sound for contacts)
- - `0x23`: (obsolete, formerly used by iOS to set a push sound for groups)
- - `0x24`: high-priority token for notifications that require
- immediate delivery (e.g. for calls) using the same struct as
- [`set-push-notification-token`](ref:payload.set-push-notification-token)
- - `0x25`: [`delete-push-notification-token`](ref:payload.delete-push-notification-token)
- - `0x30`: [`set-connection-idle-timeout`](ref:payload.set-connection-idle-timeout)
- - `0x31`: (obsolete, formerly used to ensure that a push message is
- sent for all messages, regardless of the flag)
- - `0xd0`: [`queue-send-complete`](ref:payload.queue-send-complete)
- - `0xd1`: (obsolete, formerly used for a function similar to the
- device cookie)
- - `0xd2`: [`device-cookie-change-indication`](ref:payload.device-cookie-change-indication)
- - `0xd3`: [`clear-device-cookie-change-indication`](ref:payload.clear-device-cookie-change-indication)
- - `0xe0`: [`close-error`](ref:payload.close-error)
- - `0xe1`: [`alert`](ref:payload.alert)
- name: type
- type: u8
- - _doc: |-
- Reserved, currently all zeroes.
- name: reserved
- type: b3
- - _doc: |-
- Inner payload. Needs to be parsed according to the `type` field.
- name: data
- type: b*
- echo-request:
- _group: Payloads
- _doc: |-
- An echo request to be answered by a corresponding echo response.
- Can be used for connection keep-alive or RTT estimation.
- Direction: Client <-> Server
- [//]: # "TODO(SE-128)"
- fields:
- - _doc: |-
- Data to be echoed back in the echo response.
- name: data
- type: b*
- echo-response:
- _group: Payloads
- _doc: |-
- An echo response corresponding to an echo request.
- Direction: Client <-> Server
- [//]: # "TODO(SE-128)"
- fields:
- - _doc: |-
- Data echoed back from the echo request.
- name: data
- type: b*
- legacy-message:
- _group: Payloads
- _doc: |-
- An end-to-end encrypted Threema message.
- Direction: Client <-> Server
- Note: This payload is deprecated and may be phased out eventually. It
- will only be used in case the
- [`message-payload-version`](ref:handshake.message-payload-version)
- was not present during login or was explicitly set to the version
- `0x00`.
- Conversion to [`message-with-metadata-box`](ref:payload.message-with-metadata-box):
- - Copy `legacy-message.sender-nickname` to
- `message-with-metadata-box.legacy-sender-nickname`
- - Copy all other fields of `legacy-message` to their respective
- counterparts in `message-with-metadata-box`
- - Set `message-with-metadata-box.metadata-length` to `0`
- - Omit `message-with-metadata-box.metadata-container` (i.e. set it to
- contain 0 bytes)
- - Copy `legacy-message.message-nonce` to
- `message-with-metadata-box.message-and-metadata-nonce`.
- When sending or receiving this payload, convert it to a
- `message-with-metadata-box` and handle it as defined by that struct.
- [//]: # "TODO(SE-128)"
- fields:
- - &message-sender-identity
- _doc: |-
- The sender's Threema ID.
- name: sender-identity
- type: *identity
- - &message-receiver-identity
- _doc: |-
- The receiver's Threema ID.
- name: receiver-identity
- type: *identity
- - &message-message-id
- _doc: |-
- Unique message ID for each sender/receiver pair.
- Used for duplicate detection and for quotes.
- Messages sent in a group must have the same message ID for each group
- member.
- name: message-id
- type: *message-id
- - &message-created-at
- _doc: |-
- Unix timestamp in seconds for when the message has been created.
- Messages sent in a group must have the same timestamp for each group
- member.
- However, the server overrides this timestamp with the current time if
- - the declared timestamp is in the future, or
- - the _short-lived server queuing_ flag was set (`0x20`).
- Note: The original timestamp is still available in an attached
- `csp-e2e.MessageMetadata`.
- name: created-at
- type: u32-le
- - &message-flags
- _doc: |-
- Flags:
- - `0x01`: Send push notification. The server will send a push message
- to the receiver of the message. Only use this for messages that
- require a notification. For example, do not set this for delivery
- receipts.
- - `0x02`: No server queuing. Use this for messages that can be
- discarded by the chat server in case the receiver is not connected
- to the chat server, e.g. the _typing_ indicator.
- - `0x04`: No server acknowledgement. Use this for messages where reliable
- delivery and acknowledgement is not essential, e.g. the _typing_
- indicator. Will not be acknowledged by the chat server when sending.
- No acknowledgement should be sent by the receiver to the chat
- server.
- - `0x10`: Reserved (formerly _group message marker_).
- - `0x20`: Short-lived server queuing. Messages with this flag will
- only be queued for 60 seconds.
- - `0x80`: No automatic delivery receipts. A receiver of a message with this
- flag must not send automatic delivery receipt of type _received_
- (`0x01`) or _read_ (`0x02`). This is not used by the apps but can be
- used by Threema Gateway IDs which do not necessarily want a delivery
- receipt for a message.
- name: flags
- type: u8
- - &message-reserved
- _doc: |-
- Reserved, must be set to zero.
- name: reserved
- type: u8
- - _doc: |-
- Reserved for header compatibility with metadata message.
- Must be set to zero by legacy clients.
- name: reserved-metadata-length
- type: b2
- - _doc: |-
- The sender's public nickname, padded with zeroes if needed.
- name: sender-nickname
- type: b32
- - _doc: |-
- Nonce used for the message box.
- name: message-nonce
- type: *nonce
- - &message-message-box
- _doc: |-
- The message, end-to-end encrypted by:
- XSalsa20-Poly1305(
- key=X25519HSalsa20(<sender.CK>.secret, <receiver.CK>.public),
- nonce=<message-nonce>,
- )
- name: message-box
- type: b*
- message-with-metadata-box:
- _group: Payloads
- _doc: |-
- An end-to-end encrypted Threema message with additional end-to-end
- encrypted metadata.
- Direction: Client <-> Server
- Note: This payload will only be used in case the
- [`message-payload-version`](ref:handshake.message-payload-version)
- was set to version `0x01`.
- Conversion to [`legacy-message`](ref:payload.legacy-message):
- - Discard `message-with-metadata-box.metadata-length` and
- `message-with-metadata-box.metadata-container`
- - Copy `message-with-metadata-box.legacy-sender-nickname` to
- `legacy-message.sender-nickname`
- - Copy `message-with-metadata-box.message-and-metadata-nonce` to
- `legacy-message.message-nonce`.
- - Copy all other fields of `message-with-metadata-box` to their
- respective counterparts in `legacy-message`
- Creating this payload is only allowed as part of the _Bundled Messages
- Send Steps_.
- When receiving this payload:
- 1. (MD) If the device is currently not declared _leader_, exceptionally
- abort these steps and the connection.
- 2. If the nonce of `message-and-metadata-nonce` has been used before, log
- a warning, _Acknowledge_ and discard the message and abort these steps.
- 3. If `receiver-identity` does not equal the user's Threema ID, log a
- warning, _Acknowledge_ and discard the message and abort these steps.
- 4. Run the _Valid Contacts Lookup Steps_ for `sender-identity` and let
- `contact-or-init` be the result.
- 5. If `contact-or-init` indicates that the _contact is the user_ or that
- the _contact is invalid_, log a warning, _Acknowledge_ and discard the
- message and abort these steps.
- 6. If `metadata-length` is greater zero, decrypt the `metadata-container`
- and let `outer-metadata` be the result. If this fails, log a warning,
- _Acknowledge_ and discard the message and abort these steps.
- 7. Decrypt the `message-box`, decode it to a
- [`container`](ref:payload.container) struct and let `outer` be the
- result. If this fails, log a warning, _Acknowledge_ and discard the
- message and abort these steps.
- 8. If `outer.type` is `0xff`, log a warning, _Acknowledge_ and
- discard the message and abort these steps. (Legacy logic, may be
- removed in the future.)
- 9. If `outer.type` is unknown, log a notice, _Acknowledge_ and
- discard the message and abort these steps.
- 10. Decode `outer.padded-data` into the message type associated to
- `outer.type` and let `outer-message` be the result. If this fails, log
- a warning, _Acknowledge_ and discard the message and abort these
- steps.
- 11. If `outer.type` is not `0xa0`, let `inner-metadata` be
- `outer-metadata`, let `inner-type` be `outer.type` and let
- `inner-message` be `outer-message`.
- 12. If `outer.type` is `0xa0`:
- 1. Run the receive steps associated to
- `csp-e2e-fs.Envelope` with the decoded `outer-message` and let
- `inner-metadata`, `inner-type`, `inner-message` and `fs-commit-fn`
- be the result. If this fails, exceptionally abort these steps and
- the connection. If the message has been discarded, _Acknowledge_
- and abort these steps.
- 2. If `inner-metadata` is not defined, set `inner-metadata` to
- `outer-metadata`.
- 13. If `message-id` does not equal `inner-metadata.message_id`, log a
- warning, _Acknowledge_ and discard the message and abort these steps.
- 14. If `message-id` refers to a message that has been received previously
- from `sender-identity` (including group messages), log a warning,
- _Acknowledge_ and discard the message and abort these steps.
- 15. If `inner-type` is not defined (i.e. handling an FS control message),
- log a notice, _Acknowledge_ and discard the message and abort these
- steps.
- 16. If `inner-type` is unknown, log a notice, _Acknowledge_ and
- discard the message and abort these steps.
- 17. If `inner-type` is `0xa0` (i.e. FS encapsulation within FS
- encapsulation), log a warning, _Acknowledge_ and discard the message
- and abort these steps.
- 18. If `inner-type` has dedicated blocking exemption steps, run these with
- `sender-identity` and `inner-message`. If the result indicates that
- the message should be discarded, _Acknowledge_ the message and abort
- these steps.
- 19. If `inner-type` does not have dedicated blocking exemption steps and
- is not exempted from blocking, run the _Identity Blocked Steps_ for
- `sender-identity`. If the result indicates that `sender-identity` is
- blocked, _Acknowledge_ and discard the message and abort these steps.
- 20. If `sender-identity` equals `*3MAPUSH`:
- 1. If `inner-type` is not `0xfe`, log a warning,
- _Acknowledge_ and discard the message and abort these steps.
- 2. Run the receive steps associated to `inner-type` with
- `inner-message`. If this fails, exceptionally abort these steps and
- the connection. If the message has been discarded, _Acknowledge_
- the message and abort these steps.
- 21. If `sender-identity` is not a _Special Contact_:
- 1. If `inner-metadata.nickname` is defined, let `nickname` be the
- value of `inner-metadata.nickname`.¹
- 2. If `inner-metadata` is not defined and _User Profile Distribution_
- was expected for `inner-type`, let `nickname` be the result of
- decoding the plaintext `legacy-sender-nickname`.¹
- 3. If `nickname` is present, trim any excess whitespaces from the
- beginning and the end of `nickname`.
- 4. If `contact-or-init` does not contain an existing contact:
- 1. If `inner-type` does not require to create an implicit
- _direct_ contact, log a notice, _Acknowledge_ and discard the
- message and abort these steps.
- 2. (MD) Run the following sub-steps (labelled _add-contact_):
- 1. Begin a transaction with scope `CONTACT_SYNC` and the
- following precondition:
- 1. If the contact for `sender-identity` exists, abort the
- _add-contact_ sub-steps.
- 2. Reflect a `ContactSync.Create` with `contact` set from
- `contact-or-init` and the following additional properties:
- - `created_at` set to now,
- - `nickname` set to `nickname`,
- - `acquaintance_level` set to `DIRECT`,
- - all policies and categories set to their defaults.
- 3. Commit the transaction and await acknowledgement.
- 3. If the contact for `sender-identity` does not exist, persist a
- new contact from `contact-or-init` and `nickname`.
- 4. TODO(SE-510): Schedule fetching gateway-defined profile picture
- here, if contact was added and if necessary.
- 5. Lookup the contact associated to `sender-identity` and let
- `contact` be the result (at this point, `contact` must exist).
- 6. (MD) If the contact's nickname is different to `nickname`:
- 1. Begin a transaction with scope `CONTACT_SYNC` and the
- following precondition:
- 1. If the contact no longer exists, log an error and
- exceptionally abort these steps and the connection.
- 2. Reflect a `ContactSync.Update` with `contact` including the
- new `nickname`.
- 3. Commit the transaction and await acknowledgement.
- 7. Update the contact's nickname with `nickname`. Remove the
- contact's nickname if `nickname` is empty.
- 8. Run the receive steps associated to `inner-type` with
- `inner-message`. If this fails, exceptionally abort these steps and
- the connection. If the message has been discarded, _Acknowledge_
- the message and abort these steps.
- 22. (MD) If the properties associated to `inner-type` require
- reflecting incoming messages, reflect a `d2d.IncomingMessage` from
- `outer-type` and `outer-message` and the associated conversation to
- other devices and wait for reflection acknowledgement.² If this fails,
- exceptionally abort these steps and the connection.³
- 23. If the properties associated to `inner-type` require sending
- automatic delivery receipts and `flags` does not contain the _no
- automatic delivery receipts_ (`0x80`) flag, schedule a persistent task
- to run the _Bundled Messages Send Steps_ with the following
- properties:
- - `id` being a random message ID,
- - `created-at` set to the current timestamp,
- - `receivers` set to `contact`,
- - to construct a [`delivery-receipt`](ref:e2e.delivery-receipt)
- message with status _received_ (`0x01`) and the respective
- `message-id`.
- 24. _Acknowledge_ the message.
- ¹: Note that the `nickname` of `MessageMetadata` may be undefined (leading
- to no changes) or defined but explicitly empty (leading to the nickname of
- the contact being removed) which is an important semantic difference.
- Unlike the legacy nickname field which always contains a value and
- therefore cannot represent this semantic difference without having to
- check whether _User Profile Distribution_ was required for the type.
- ²: We reflect the **outer** message container depending on the unwrapped
- **inner** message type, so the forward security properties are untouched
- and all other devices need to go through the same process.
- ³: Reflection needs to happen after the message has been processed and all
- side effects have been applied. Otherwise, if the receive process is
- interrupted and another device takes over, it would discard the message as
- a duplicate.
- The following steps are defined as _Acknowledge_ steps for an incoming
- message:
- 1. If the steps for this message have already been invoked once, abort
- these steps.
- 2. If `flags` does not contain the _no server acknowledgement_ (`0x04`)
- flag, send a [`message-ack`](ref:payload.message-ack) payload to the
- chat server with the respective `message-id`.
- 3. If the properties associated to `inner-type` require protection against
- replay, mark the nonce of `message-and-metadata-nonce` as used.
- 4. If `fs-commit-fn` is defined, run it.
- [//]: # "TODO(SE-128)"
- fields:
- - *message-sender-identity
- - *message-receiver-identity
- - *message-message-id
- - *message-created-at
- - *message-flags
- - *message-reserved
- - _doc: |-
- Length of the metadata box. In case it is zero, no metadata is
- present (for compatibility with clients using
- [`legacy-message`](ref:payload.legacy-message)).
- Note: For outgoing messages, a metadata box should always be present.
- name: metadata-length
- type: u16-le
- - _doc: |-
- Backwards compatibility field for the sender's public nickname.
- Padded with zeroes if needed.
- When sending a message towards a Threema Gateway ID (starts with a
- `*`), add the same nickname as included in the encrypted metadata box.
- Otherwise, set it to all zeroes.
- Note: The backwards compatibility for Threema Gateway IDs will be
- removed eventually!
- name: legacy-sender-nickname
- type: b32
- - _doc: |-
- Metadata associated to the message. Must be ignored in case
- `metadata-length` is zero.
- Message Metadata Key (`MMK`) derivation:
- S = X25519HSalsa20(<sender.CK>.secret, <receiver.CK>.public)
- MMK = BLAKE2b(key=S, salt='mm', personal='3ma-csp')
- The encoded `csp-e2e.MessageMetadata` is then encrypted in the
- following way:
- XSalsa20-Poly1305(
- key=MMK,
- nonce=<message-with-metadata-box.message-and-metadata-nonce>,
- )
- name: metadata-container
- type: b{metadata-length}
- - _doc: |-
- Nonce used for the message and the metadata box.
- name: message-and-metadata-nonce
- type: *nonce
- - *message-message-box
- message-ack:
- _group: Payloads
- _doc: |-
- Acknowledges that a message has been received.
- Direction: Client <-> Server
- [//]: # "TODO(SE-128)"
- fields:
- - _doc: |-
- Identity of the sender for an incoming (`0x82`) message / of the
- receiver for an outgoing (`0x81`) message.
- name: identity
- type: *identity
- - _doc: |-
- Refers to the `message-id` of the acknowledged message.
- name: message-id
- type: *message-id
- unblock-incoming-messages:
- _group: Payloads
- _doc: |-
- Unblock incoming messages from the server. Sent by a multi-device capable
- client once it is nominated to receive incoming messages.
- Direction: Client --> Server
- [//]: # "TODO(SE-128)"
- set-push-notification-token:
- _group: Payloads
- _doc: |-
- Sets the push notification token to be used when sending a push message.
- Direction: Client --> Server
- fields:
- - _doc: |-
- Type of the push token:
- - `0x00`: No push
- - `0x01`: APNs Production
- - `0x02`: APNs Development
- - `0x05`: APNs Production with `mutable-content` key
- - `0x06`: APNs Development with `mutable-content` key
- - `0x11`: FCM with empty payload
- - `0x13`: HMS with empty payload
- name: type
- type: u8
- - _doc: |-
- Push token, maximum 255 bytes.
- name: token
- type: b*
- delete-push-notification-token:
- _group: Payloads
- _doc: |-
- Deletes push tokens for a Threema ID. Can be used when self-removing or
- removing another device from a device group.
- Direction: Client --> Server
- When receiving this payload:
- 1. If `csp-device-ids` is empty, delete all tokens for all devices except
- the device sending the payload and abort these steps.
- 2. Delete all tokens for the devices specified in `csp-device-ids`.
- fields:
- - _doc: |-
- Delete tokens belonging to a
- [`csp-device-id`](ref:handshake.csp-device-id) in the same device
- group.
- name: csp-device-ids
- type: u64-le[]
- set-connection-idle-timeout:
- _group: Payloads
- _doc: |-
- Request a different idle timeout than the default one of 5 minutes. The
- new setting is valid for the connection only.
- The client must ensure that it sends echo requests or other traffic
- frequently to keep the connection alive.
- Direction: Client --> Server
- [//]: # "TODO(SE-128)"
- fields:
- - _doc: |-
- Idle timeout in seconds. Minium 30s, maximum 600s.
- name: timeout
- type: u16-le
- queue-send-complete:
- _group: Payloads
- _doc: |-
- Indicates that the incoming message queue on the server has been fully
- transmitted to the client. A client should not disconnect prior to
- having received this payload.
- Direction: Client <-- Server
- [//]: # "TODO(SE-128)"
- device-cookie-change-indication:
- _group: Payloads
- _doc: |-
- Indicates to the client that a device cookie mismatch has been detected
- since the last time that the device cookie change indication has been
- cleared (using the
- [`clear-device-cookie-change-indication`](ref:clear-device-cookie-change-indication)
- payload).
- The client should display a warning in form of a notification and/or
- dialog to the user, informing them that a new and potentially unauthorized
- device has accessed the account. When the user confirms, the client should
- send a
- [`clear-device-cookie-change-indication`](ref:clear-device-cookie-change-indication)
- payload to clear the indication.
- Direction: Client <-- Server
- clear-device-cookie-change-indication:
- _group: Payloads
- _doc: |-
- Causes the server to clear the flag that triggers sending the
- [`device-cookie-change-indication`](ref:device-cookie-change-indication)
- on each connection.
- The flag will be set again by the server if another device cookie
- mismatch is detected.
- Direction: Client --> Server
- close-error:
- _group: Payloads
- _doc: |-
- Indicates that the connection has experienced an unrecoverable error and
- must be closed.
- Direction: Client <-- Server
- [//]: # "TODO(SE-128)"
- fields:
- - _doc: |-
- Indicates whether the client is allowed to reconnect automatically
- after the connection has been severed. This allows the server to
- prevent infinite loops in case of a recurring error.
- Set to `0` in case the client may not reconnect automatically or any
- other value otherwise.
- name: can-reconnect
- type: u8
- - _doc: |-
- Error message (UTF-8 encoded)
- name: message
- type: b*
- alert:
- _group: Payloads
- _doc: |-
- Generic alert that should be displayed in the client's user interface.
- Direction: Client <-- Server
- [//]: # "TODO(SE-128)"
- fields:
- - _doc: |-
- Alert message (UTF-8 encoded)
- name: message
- type: b*
- # End-to-end encrypted structs
- e2e: &e2e
- _doc: |-
- ## End-to-End Encrypted Messages
- An end-to-end encrypted message can be sent or received once the handshake
- was successful. Every end-to-end encrypted message is wrapped inside of a
- [`container`](ref:payload.container) struct that is then encrypted and
- wrapped by a payload [`legacy-message`](ref:payload.legacy-message) or
- [`message-with-metadata-box`](ref:payload.message-with-metadata-box)
- struct.
- ### Predefined Contacts
- A pedefined contacts can be added to the contact list and is automatically
- initialised with its identity, nickname, hard-coded public key and the verification
- level _fully verified_. Once a predefined contact is in the contact list, it
- is treated like any other normal contact (with editable properties like
- first and last name, etc.).
- A predefined contact may be marked _special_ meaning it follows special
- logic. These are also known as _Special Contact_s. Even though special
- contacts should not normally appear in the contact list, there's nothing
- stopping a user from adding a special contact to its contact list. While
- they are treated like normal contacts in the contact list, depending on the
- special handling logic, it may not be possible to send or receive normal
- messages from them.
- The following list contains all predefined contacts:
- - `*3MAPUSH`:
- - Nickname: Threema Push
- - Public Key:
- - Production: fd711e1a0db0e2f03fcaab6c43da2575b9513664a62a12bd0728d87f7125cc24
- - Sandbox: fd711e1a0db0e2f03fcaab6c43da2575b9513664a62a12bd0728d87f7125cc24
- - Special: Yes
- - `*3MATOKN`:
- - Nickname: Threema Token
- - Public Key:
- - Production: 04884d12d668f855d00d71fb1d9d413c95f271312f7e077846af671875c4101b
- - Special: No
- - `*3MAWORK`:
- - Nickname: Threema Work Channel
- - Public Key:
- - Production: 9aa0a72a8fb6f0cc53727fea6096f1b7b0ebefcc2650ad39a1e54837bba0bc4b
- - Sandbox: 9aa0a72a8fb6f0cc53727fea6096f1b7b0ebefcc2650ad39a1e54837bba0bc4b
- - Special: No
- - `*BETAFBK`:
- - Nickname: Threema Beta Feedback
- - Public Key:
- - Production: 5684d6dcd32a16488df8371095fc9a1fc25baeb6b97366d99fdf2aba00e2bc5c
- - Special: No
- - `*MY3DATA`:
- - Nickname: My Threema Data
- - Public Key:
- - Production: 3b01854f24736e2d0d2dc387eaf2c0273c5049052147132369bf3960d0a0bf02
- - Sandbox: 83adfee6558b68ae3cd6bbe2a33f4e4409d5624a7cea23a18975aea6272a0070
- - Special: No
- - `*SUPPORT`:
- - Nickname: Threema Support
- - Public Key:
- - Production: 0f944d18324b2132c61d8e40afce60a0ebd701bb11e89be94972d4229e94722a
- - Sandbox: 0f944d18324b2132c61d8e40afce60a0ebd701bb11e89be94972d4229e94722a
- - Special: No
- - `*THREEMA`:
- - Nickname: Threema Channel
- - Public Key:
- - Production: 3a38650c681435bd1fb8498e213a2919b09388f5803aa44640e0f706326a865c
- - Sandbox: 3a38650c681435bd1fb8498e213a2919b09388f5803aa44640e0f706326a865c
- - Special: No
- Note: OnPrem provisions predefined contacts in the associated OPPF file.
- ### Mitigating Replay
- To prevent replay attacks, a client must permanently store used nonces for
- incoming and outgoing end-to-end encrypted messages. Messages reusing
- previously used nonces must not be processed and discarded. One nonce
- store for all end-to-end encrypted messages across different contacts is
- sufficient.
- Note that it is still possible for the chat server to replay old messages to
- a device whose database has been erased (e.g. when restoring a backup).
- However, this is not applicable to forward security encrypted messages.
- ### Message ID
- Each message has an associated message ID. It is crucial to understand
- that this is not a unique identifier across multiple conversations.
- Unique identification of a message is determined by:
- - 1:1 Chats: The message ID in combination with the contact's Threema ID.
- - Group Chats: The message ID in combination with the group creator's
- Threema ID and the group ID.
- - Distribution Lists: The message ID with an artificial distribution list
- ID.
- When a message is being quoted, it may only be looked up within the
- associated conversation.
- ### Flags
- For each message, we will define _mandatory_ and _optional_ flags
- referring to the `flags` field of the payload
- [`legacy-message`](ref:payload.legacy-message) or
- [`message-with-metadata-box`](ref:payload.message-with-metadata-box)
- struct. A flag must be considered _mandatory_ unless it has been explicitly
- marked _optional_.
- ### Delivery Receipts
- There are two types of delivery receipts (sent using the
- [`delivery-receipt`](ref:e2e.delivery-receipt) message):
- - Automatic: "received" and "read"
- - Manual: "acknowledged" and "declined"
- For each message, we will define whether automatic delivery receipts should
- be sent and whether it is eligible for sending manual delivery receipts
- (e.g. acknowledge/decline). However, two general exceptions apply:
- 1. Automatic delivery receipts are not sent to group members (i.e. when
- any message struct is wrapped in a `group` message struct).
- 2. Messages whose flags include `0x80` must not trigger any automatic
- delivery receipts.
- ### Blocking
- The sender Threema ID may be blocked explicitly (i.e. blocking a specific
- Threema ID) or implicitly (blocking all unknown Threema IDs). This does not
- require special handling on the server but instead is done entirely by the
- clients.
- Note that the protocol does not distinguish between implicitly and
- explicitly blocked Threema IDs. An implicitly blocked Threema ID (i.e.
- blocking unknown contacts) must be treated the same as an explicitly blocked
- Threema ID (i.e. blocking specific contacts).
- The UI must prevent users from composing or submitting messages towards a
- blocked contact. In practise, this is only relevant for explicitly blocked
- contacts.
- The following steps are defined as the _Identity Blocked Steps_:
- 1. Let `identity` be the Threema ID to be checked.
- 2. If `identity` is a _Special Contact_, return that it is not blocked.
- 3. If `identity` is explicitly blocked, return that it is blocked.
- 4. If the settings indicate that unknown contacts should not be blocked,
- return that it is not blocked.
- 5. If `identity` is a _Predefined Contact_, return that it is not blocked.
- 6. Let `contact` be the associated contact to `identity`.
- 7. If `contact` is not defined, return that it is blocked.
- 8. If `contact` has acquaintance level _direct_, return that it is not
- blocked.
- 9. If `contact` is part of a group that is not marked as _left_, return
- that it is not blocked.
- 10. Return that it is blocked.
- ### Contact Flows
- The following steps are defined as the _Valid Contacts Lookup Steps_:
- 1. Let `identities` be the identities to look up.
- 2. Let `contact-or-inits` be an empty map of Threema IDs to a contact,
- properties to create a contact from, or _contact is the user_ or
- _contact is invalid_ marker.
- 3. Let `unknown-identities` be an empty list.
- 4. For each `identity` of `identities`:
- 1. If `identity` equals the user's Threema ID, add the information
- that the _contact is the user_ to the `contact-or-inits` map and
- abort these sub-steps.
- 2. If `identity` is a _Special Contact_, add that special contact to the
- `contact-or-inits` map and abort these sub-steps.
- 3. Lookup the contact associated to `identity` and let `contact` be the
- result.
- 4. If `contact` is defined, add `contact` to the `contact-or-inits` map
- and abort these sub-steps.
- 5. Lookup the properties to create a contact from associated to
- `identity` from the _contact lookup cache_ and let `init` be the
- result.
- 6. If `init` is defined, add `init` to the `contact-or-inits` map and
- abort these sub-steps.
- 7. Add `identity` to `unknown-identities`.
- 5. Let `directory-response` be the response of asynchronously looking up
- `unknown-identities` on the Directory Server.
- 6. If Work flavour, let `work-directory-response` be the response of
- asynchronously looking up `unknown-identities` on the Work Contacts API
- endpoint.
- 7. Await `directory-response` and `work-directory-response`.
- 8. Process the result of `directory-response`:
- 1. If the server could not be reached, exceptionally abort these steps.
- 2. If the status code is `429`, exceptionally abort these steps and add
- a minimum delay of 10s before retrying a connection.
- 3. If the status code is not `200`, exceptionally abort these steps.
- 4. For each contact entry of the result:
- 1. Remove the contact from `unknown-identities`. If it was not
- present in `unknown-identities`, log a warning and abort these
- sub-steps.
- 2. If the contact is marked as _invalid_ (never existed or has been
- revoked), add the information that the _contact is invalid_ to the
- `contact-or-inits` map and abort these sub-steps.
- 3. If the contact is a _Predefined Contact_:
- 1. If the contact's public key does not equal the _Predefined
- Contact_s public key, log a warning and exceptionally abort these
- steps.
- 2. Update the contact information with the following information:
- - set `verification_level` to `FULLY_VERIFIED`,
- - set `nickname` to the nickname of the _Predefined Contact_,
- - if _Predefined Contact_ defines a first name, set it
- accordingly,
- - if _Predefined Contact_ defines a last name, set it
- accordingly,
- 3. Add the resulting contact information to the `contact-or-inits` map
- from which a new contact can be created.
- 5. For each `identity` of `unknown-identities`:
- 1. Add the information that the _contact is invalid_ to the
- `contact-or-inits` map for `identity`.
- 6. Clear `unknown-identities`.
- 9. If `work-directory-response` is defined, process its result:
- 1. If the server could not be reached, exceptionally abort these steps.
- 2. If the status code is `401`, exceptionally abort these steps,
- notify the user that the Work credentials are invalid and request new
- ones. The connection should not be retried until new Work credentials
- have been entered and checked for validity.
- 3. If the status code is `429`, exceptionally abort these steps and add a
- minimum delay of 10s before retrying a connection.
- 4. If the status code is not `200`, exceptionally abort these steps.
- 5. For each `work-contact` of the result:
- 1. If an entry for the `work-contact`'s identity does not exist in
- `contact-or-inits`, log a warning and abort these sub-steps.
- 2. If `work-contact`'s public key does not equal `contact-or-init`'s
- public key, log a warning and exceptionally abort these steps.
- 3. Update the contact entry for `work-contact`'s identity in
- `contact-or-inits` with the following information:
- - if `verification_level` is not defined or `UNVERIFIED`, set it
- to `SERVER_VERIFIED`,
- - set `work_verification_level` to `WORK_SUBSCRIPTION_VERIFIED`,
- - if `work-contact.first-name` is defined, set the first name
- accordingly,
- - if `work-contact.last-name` is defined, set the last name
- accordingly,
- 10. TODO(SE-173): Run the contact import flow for `contact-or-inits` and
- update the `verification_level` for all whose associated phone number /
- email could be matched. Import `first_name` and `last_name` (if not
- already defined) and set `sync_state` to `IMPORTED`. Clarify precedence
- regarding Work API.
- 11. For each `init` of `contact-or-inits`:
- 1. If `init` does not contain properties to create a contact from (i.e.
- it is a contact or any of the special markers), abort these
- sub-steps.
- 2. If `init.sync_state` is not defined, set it to `INITIAL`.
- 3. If `init.verification_level` is not defined, set it to `UNVERIFIED`.
- 4. If `init.work_verification_level` is not defined, set it to `NONE`.
- 12. Update the _contact lookup cache_ with the contents of
- `contact-or-inits`. Each newly added or updated entry has an expiration
- time of 10m after which the entry is to be removed from the cache.
- 13. Return `contact-or-inits`.
- ### Groups
- Groups are handled in a decentralised manner. Messages are sent to each
- group member individually. On a technical level, a group is identified by
- **both** the Threema ID of the creator and the random group ID the creator
- chose. A group **must never** be identified by the group ID alone.
- Group messages are special containers wrapped around normal messages (it is
- actually just a common header):
- - [`group-member-container`](ref:e2e.group-member-container): For group
- message communication between members, including the creator.
- - [`group-creator-container`](ref:e2e.group-creator-container): For special
- messages that may only be sent from the creator to normal group members
- and vice versa.
- Group messages have special types in order to separate them from other
- messages. These types also define which container must be used.
- The group members are determined by the
- [`group-setup`](ref:e2e.group-setup) message and continuously updated by
- any following [`group-leave`](ref:e2e.group-leave) messages. Any following
- [`group-setup`](ref:e2e.group-setup) overrides the previous member state.
- ### Implicit Contact Creation
- When the user is added to a group, every unknown member of the group must be
- added to the contact list with acquaintance level _group_. Messages from a
- contact with any acquaintance level will not be implicitly blocked by a
- _block unknown_ setting.
- The contact remains at the acquaintance level _group_ until a 1:1
- conversation with that contact is being started by either side in which
- case the acquaintance level should be changed to _direct_.
- A contact with acquaintance level _group_ will remain indefinitely even if
- the contact is being removed from all groups of the user or if all remaining
- common groups are marked as _left_. In that case, the contact is implicitly
- marked as _deleted_ so that it is covered by _block unknown_.
- ### Notes Group
- A group is identified as a _notes_ group if all of the following criteria
- are met:
- 1. The user is the creator of the group.
- 2. The group currently has no members (besides the creator).
- 3. The group is not marked as _left_.
- Messages in a _notes_ group are synchronised across devices but are not
- sent to the chat server (since there are no other members). Therefore,
- it is ideal for "notes to self", hence the name.
- A group seamlessly transforms into a _notes_ group and out of it given the
- above criteria. Right now this can happen in three scenarios:
- - A _notes_ group is created explicitly (i.e. a group with only the user
- is being created).
- - The user is the creator of a group and one or more members are being
- added in which case the _notes_ group transforms into a regular group.
- - The user is the creator of a group whose members have just been removed
- (but the group has not been disbanded) in which case the group
- transforms into a _notes_ group.
- The UI should signal the _notes_ status of a group to the user.
- ### Group Flows
- The following steps are defined as the _Active Group Update Steps_:
- 1. If the user is not the creator of the group or the group is marked as
- _left_, log an error and abort these steps.
- 2. Let `message-ids` be a list of four pre-generated message IDs.
- 3. Let `changes` be the set of expected changes to the group which may
- contain the following properties:
- - `profile-picture` is defined if the group's profile picture is
- expected to be changed and contains either the new profile picture or
- a _remove_ mark to remove it
- - `profile-picture.blob` may contain the associated blob information
- data in case of a changed profile picture.
- - `add-members` is a set of new members to be added to the group
- - `remove-members` is a set of existing members to be removed from the
- group
- 4. Let `group` be a snapshot of the current group state.
- 5. Remove all members from `changes.add-members` that are not in
- `group.members`.
- 6. Remove all members from `changes.remove-members` that are in
- `group.members`.
- 7. Let `messages` be an empty list.
- 8. If `changes.remove-members` is not empty, add a message entry to
- `messages` to remove members to be removed with the following
- properties:
- - `id` set to the first message ID of `message-ids`,
- - `created-at` set to the current timestamp,
- - `receivers` set to `changes.remove-members`,
- - to construct a [`group-setup`](ref:e2e.group-setup) (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container))
- with an empty members set.
- 9. If `group.members` is not empty:
- 1. Add a message entry to `messages` to update the group for the
- members with the following properties:
- - `id` set to the first message ID of `message-ids`,
- - `created-at` set to the current timestamp,
- - `receivers` set to `group.members`,
- - to construct a [`group-setup`](ref:e2e.group-setup) (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container))
- from `group.members`.
- 2. Add a message entry to `messages` to announce the group's name to
- the members with the following properties:
- - `id` set to the second message ID of `message-ids`,
- - `created-at` set to the current timestamp,
- - `receivers` set to `group.members`,
- - to construct a [`group-name`](ref:e2e.group-name) (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container))
- from `group.name`.¹
- 3. If `group.profile-picture` is defined:
- 1. Let `profile-picture-blob` be `changes.profile-picture.blob`.
- 2. If `group.profile-picture` does not equal
- `changes.profile-picture`, upload `group.profile-picture` to the
- blob server with the _persist_ flag and set
- `profile-picture-blob` to the result.
- 4. Add a message entry to `messages` to announce the group's profile
- picture to the members with the following properties:
- - `id` set to the third message ID of `message-ids`,
- - `created-at` set to the current timestamp,
- - `receivers` set to `group.members`,
- - to construct a
- [`set-profile-picture`](ref:e2e.set-profile-picture) (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container)) from
- `profile-picture-blob` if `profile-picture-blob` is defined or
- [`delete-profile-picture`](ref:e2e.delete-profile-picture)
- (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container))
- otherwise.¹
- 5. Let `chosen-call` be the result of the most recent invocation of
- the _Group Call Refresh Steps_ for the group.
- 6. If `chosen-call` is defined, add a message entry to `messages` to
- announce the ongoing group call to newly added members with the
- following properties:
- - `id` set to the fourth message ID of `message-ids`,
- - `created-at` set to the `started_at` value associated to
- `chosen-call`,
- - `receivers` set to `changes.add-members`,
- - to construct a repeat of `csp-e2e.GroupCallStart` (wrapped by
- [`group-member-container`](ref:e2e.group-member-container))
- that is associated to `chosen-call`.
- 10. Run the _Bundled Messages Send Steps_ with `messages`.
- ¹: This results in the group name and the group profile picture being
- distributed to all members regardless of whether it was changed or not. This
- is deemed acceptable for the sake of implementation simplicity and
- reusability.
- The following steps are defined as the _Active Group State Resync Steps_:
- 1. If the user is not the creator of the group or the group is marked as
- _left_, log an error and abort these steps.
- 2. Let `message-ids` be a list of four pre-generated message IDs.
- 3. Let `target-members` be a set of members to receive the resync.
- 4. Remove all members from `target-members` that are not in
- `group.members`.
- 5. If `target-members` is empty, abort these steps.
- 6. Let `messages` be an empty list.
- 7. Add a message entry to `messages` to announce the group composition with
- the following properties:
- - `id` set to the first message ID of `message-ids`,
- - `created-at` set to the current timestamp,
- - `receivers` set to `target-members`,
- - to construct a [`group-setup`](ref:e2e.group-setup) (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container)) from
- `group.members`.
- 8. Add a message entry to `messages` to announce the group's name with the
- following properties:
- - `id` set to the second message ID of `message-ids`,
- - `created-at` set to the current timestamp,
- - `receivers` set to `target-members`,
- - to construct a [`group-name`](ref:e2e.group-name) (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container)) from
- `group.name`.
- 9. If `group.profile-picture` is defined, upload `group.profile-picture` to
- the blob server with the _persist_ flag and let `profile-picture-blob`
- be the result.
- 10. Add a message entry to `messages` to announce the group's profile
- picture with the following properties:
- - `id` set to the third message ID of `message-ids`,
- - `created-at` set to the current timestamp,
- - `receivers` set to `target-members`,
- - to construct a
- [`set-profile-picture`](ref:e2e.set-profile-picture) (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container)) from
- `profile-picture-blob` if `profile-picture-blob` is defined or
- [`delete-profile-picture`](ref:e2e.delete-profile-picture) (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container))
- otherwise.
- 11. Let `chosen-call` be the result of the most recent invocation of
- the _Group Call Refresh Steps_ for the group.
- 12. If `chosen-call` is defined, add a message entry to `messages` to
- announce the ongoing group call with the following properties:
- - `id` set to the fourth message ID of `message-ids`,
- - `created-at` set to the `started_at` value associated to
- `chosen-call`,
- - `receivers` set to `target-members`,
- - to construct a repeat of `csp-e2e.GroupCallStart` (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)) that is
- associated to `chosen-call`.
- 13. Run the _Bundled Messages Send Steps_ with `messages`.
- 14. For each member of `target-members`, mark the group as _recently
- resynced_ for 1h.
- #### Create/Clone Group
- The following steps must be invoked when the user wants to create or clone a
- group:
- 1. Let `init` contain the following properties to create a group:
- - `name` of the new group or an empty string
- - `profile-picture` of the new group or undefined
- - `members` is a set of initial members to be added to the group¹
- 2. Let `parameters` be the MDM parameters. If
- `parameters.th_disable_create_group` is `true`, abort these steps.
- 3. Let `group-id` be a random group ID.
- 4. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If a group with `group-id` and the user as creator exists, log an
- error and abort these steps.
- 2. If `init.members` contains a member that is not an existing
- contact, log an error and abort these steps.
- 5. If `init.profile-picture` is defined, upload `init.profile-picture` to
- the blob server with the _persist_ flag and set
- `init.profile-picture.blob` to the result.
- 6. (MD) Reflect a `GroupSync.Create` with `group` set to contain:
- - `group_identity` being `group-id` and the user as the creator,
- - `created_at` set to now,
- - `name` set to `init.name`
- - `user_state` set to `MEMBER`,
- - `profile_picture` set from `init.profile-picture.blob`,
- - `member_identities` set from `init.members`,
- - all policies and categories set to their defaults.
- 7. (MD) Commit the transaction and await acknowledgement.
- 8. Persist the newly created group from `init` and `group-id` to storage.
- 9. If `init.members` is empty, abort these steps.
- 10. Let `message-ids` be a list of four random message IDs.
- 11. Schedule a persistent task to run the following steps:
- 1. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group does not exist or the group is marked as _left_ or the
- group has no members, log a warning that a group sync race occurred
- and abort these steps.
- 2. Let `group` be a snapshot of the current group state.
- 3. If any of the following cases is true, log a warning that a group
- sync race occurred:
- - `init.name` is defined and does not equal `group.name`,
- - `init.profile-picture` does not equal `group.profile-picture`,
- - `init.members` does not equal `group.members`.
- 4. Run the _Active Group Update Steps_ with `message-ids` and the
- following expected set of `changes`:
- - `profile-picture` set to `init.profile-picture`,
- - `add-members` set to `init.members`,
- - `remove-members` set to an empty list.
- 5. (MD) Commit the transaction and await acknowledgement.
- ¹: Note that all contacts must be added before they can be added as initial
- members of the group.
- #### Update Group
- The following steps must be invoked when the user is the creator of a group
- and intends to apply a change to the group's name, profile picture or
- add/remove members to/from the group:
- 1. If the user is not the creator of the group or the group is marked as
- _left_, log an error and abort these steps.
- 2. Let `changes` be the set of changes to the group which may contain the
- following properties:
- - `name` is defined if the group's name is to be changed and contains the
- new name or an empty string
- - `profile-picture` is defined if the group's profile picture is to be
- changed and contains either the new profile picture or a _remove_ mark
- to remove it
- - `add-members` is a set of new members to be added to the group¹
- - `remove-members` is a set of existing members to be removed from the
- group¹
- 3. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If `changes.add-members` or `changes.remove-members` contains a
- member that is not an existing contact, log an error and abort these
- steps.
- 2. If the group does not exist or the group is marked as _left_, log a
- warning and abort these steps.
- 4. Let `updated-members` be a copy of the current member set of the group.
- Add all `changes.add-members` to this set that are to be added to the
- group. Remove all `changes.remove-members` from this set that are to be
- removed from the group.
- 5. If `changes.profile-picture` is defined and contains a profile picture,
- upload `changes.profile-picture` to the blob server with the _persist_
- flag and let `changes.profile-picture.blob` be the result.
- 6. (MD) Reflect a `GroupSync.Update` with `member_state_changes`
- constructed from `changes.add-members` and `changes.remove-members` and
- `group` set to contain:
- - `name` set to `changes.name`,
- - `profile_picture` set according to `changes.profile-picture` (and
- `changes.profile-picture.blob`),
- - `member_identities` set from `updated-members`.
- 7. (MD) Commit the transaction and await acknowledgement.
- 8. If the user is currently participating in a group call of this group,
- remove all `change.remove-members` participants from the group call
- (handle them as if they left the call).
- 9. Persist the `updated-members` and other `changes` to the group.
- 10. If `changes.add-members` or `changes.remove-members` is not empty, run
- the _Rejected Messages Refresh Steps_ for the group.
- 11. Let `message-ids` be a list of four random message IDs.
- 12. Schedule a persistent task to run the following steps:
- 1. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group does not exist or the group is marked as _left_, log
- a warning that a group sync race occurred and abort these steps.
- 2. Let `group` be a snapshot of the current group state.
- 3. If any of the following cases is true, log a warning that a group
- sync race occurred:
- - `changes.name` is defined and does not equal `group.name`,
- - `changes.profile-picture` contains a profile picture and does not
- equal `group.profile-picture`,
- - `changes.profile-picture` contains the _remove_ mark and
- `group.profile-picture` is defined,
- - `updated-members` does not equal `group.members`.
- 4. Run the _Active Group Update Steps_ with `message-ids` and `changes`.
- 5. (MD) Commit the transaction and await acknowledgement.
- ¹: Note that all contacts must be added before they can be added as members
- to the group. The same applies to members that are being removed, obviously.
- #### Disband/Remove Group
- The following steps must be invoked when the user is the creator of a group
- and intends to _disband_ or _disband and remove_ the group:
- 1. Let `intent` be the user's intent which can be either to _disband_ or to
- _disband and remove_ the group.
- 2. If the user is not the creator of the group or the group is marked as
- _left_, log an error and abort these steps.
- 3. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group does not exist or the group is marked as _left_, log a
- warning and abort these steps.
- 4. (MD) If `intent` is to _disband_, reflect a `GroupSync.Update` with
- `group` set to contain `user_state` set to `LEFT`.
- 5. (MD) If `intent` is to _disband and remove_, reflect a
- `GroupSync.Delete` for this group.
- 6. (MD) Commit the transaction and await acknowledgement.
- 7. If the user is participating in a group call of this group, trigger
- leaving the call.
- 8. If the `intent` is to _disband_:
- 1. Mark the group as _left_.
- 2. Persist the previous member setup so that the group can be cloned.
- 3. Run the _Rejected Messages Refresh Steps_ for the group.
- 9. Let `group` be a snapshot of the current group state.
- 10. If the `intent` is to _disband and remove_, remove the group and all
- associated messages from storage.
- 11. Let `message-id` be a random message ID.
- 12. Schedule a persistent task to run the following steps:
- 1. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group exists and is not marked as _left_, log an error that
- a major group state inconsistency has been detected¹ and abort
- these steps.
- 2. Run the _Bundled Messages Send Steps_ with the following properties:
- - `id` set to `message-id`,
- - `created-at` set to the current timestamp,
- - `receivers` set to `group.members`,
- - to construct a [`group-setup`](ref:e2e.group-setup) (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container)) with
- an empty members set.
- 3. (MD) Commit the transaction and await acknowledgement.
- ¹: Disbanding a group as the creator makes the group strictly non-reusable.
- #### Leave/Remove Group
- The following steps must be invoked when the user is not the creator of a
- group and intends to _leave_ or _leave and remove_ the group:
- 1. Let `intent` be the user's intent which can be either to _leave_ or to
- _leave and remove_ the group.
- 2. If the user is the creator of the group or the group is marked as
- _left_, log an error and abort these steps.
- 3. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group does not exist or the group is marked as _left_, log a
- warning and abort these steps.
- 4. (MD) If `intent` is to _leave_, reflect a `GroupSync.Update` with
- `group` set to contain `user_state` set to `LEFT`.
- 5. (MD) If `intent` is to _leave and remove_, reflect a `GroupSync.Delete`
- for this group.
- 6. (MD) Commit the transaction and await acknowledgement.
- 7. If the user is participating in a group call of this group, trigger
- leaving the call.
- 8. If the `intent` is to _leave_:
- 1. Mark the group as _left_.
- 2. Persist the previous member setup so that the group can be cloned.
- 3. Run the _Rejected Messages Refresh Steps_ for the group.
- 9. Let `group` be a snapshot of the current group state.
- 10. If the `intent` is to _leave and remove_, remove the group and all
- associated messages from storage.
- 11. Let `message-id` be a random message ID.
- 12. Schedule a persistent task to run the following steps:
- 1. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group exists and is not marked as _left_, log a warning
- that a group sync race occurred and abort these steps.
- 2. Run the _Bundled Messages Send Steps_ with the following properties:
- - `id` set to `message-id`,
- - `created-at` set to the current timestamp,
- - `receivers` set to `group.members`,
- - to construct a [`group-leave`](ref:e2e.group-leave) (wrapped by
- [`group-member-container`](ref:e2e.group-member-container))
- 3. (MD) Commit the transaction and await acknowledgement.
- #### Remove Group
- The following steps must be invoked when the user intends to remove a group
- that is marked as _left_.
- 1. If the group is not marked as _left_, log an error and abort these steps.
- 2. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group does not exist or the group is not marked as _left_, log
- a warning and abort these steps.
- 3. (MD) Reflect a `GroupSync.Delete` for this group.
- 4. (MD) Commit the transaction and await acknowledgement.
- 5. Remove the group and all associated messages from storage.
- #### Group Resync
- The following steps must be invoked when the user is the creator of a group
- and intends to resync the group manually:
- 1. If the user is not the creator of the group or the group is marked as
- _left_, log an error and abort these steps.
- 2. Let `message-ids` be a list of four random message IDs.
- 3. Schedule a volatile task to run the following steps:
- 1. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group does not exist or the group is marked as _left_, log a
- warning and abort these steps.
- 2. Run the _Active Group State Resync Steps_ with `message-ids` and
- `target-members` being all current group members.¹
- 3. (MD) Commit the transaction and await acknowledgement.
- ¹: This mechanic intentionally bypasses the 1h _recently resynced_ mark due
- to an explicit manual request by the user to resync the group.
- #### Update Conversation
- The following steps must be invoked when the user intends to change any
- other synchronised property of the group:
- 1. Let `change` be one of the following changes to the group as defined by
- `sync.Group`:
- - `notification_trigger_policy_override`
- - `notification_sound_policy_override`
- - `conversation_category`
- - `conversation_visibility`
- 2. Persist the `change` to the group.
- 3. (MD) Schedule a persistent task to run the following steps:
- 1. Begin a transaction with scope `GROUP_SYNC` and the following precondition:
- 1. If the group does not exist, log a warning and abort these steps.
- 2. Reflect a `GroupSync.Update` with `group` set to contain the `change`.
- 3. Commit the transaction and await acknowledgement.
- 4. Persist the `change` to the group (again).
- ### Device Flows
- #### Deactivate Multi-Device Flow
- The following steps must be invoked when the user wants to deactivate
- multi-device and continue using the current device.
- 1. Run the _Drop Devices Steps_ with the intent to _deactivate_
- multi-device and keep the user informed regarding the process status and
- any encountered issues.
- #### Drop Own Device Flow
- The following steps must be invoked when the user wants to stop using the
- current device.
- 1. If the device does not have multi-device enabled, log an error and
- abort these steps.
- 2. Begin a transaction (scope: `DROP_DEVICE`, precondition: none).
- 3. Send a `DropDevice` with this device's Device ID.
- 4. Await the corresponding `DropDeviceAck` or the connection closing with
- close code `4113`.
- 5. TODO(SE-494): Enter _read-only_ mode persistently.
- #### Drop Other Devices Flow
- The following steps must be invoked when the user wants to drop one or more
- other devices from the device group.
- 1. Let `device-ids-to-drop` be a set of Device IDs that should be
- dropped from the device group.
- 2. If this device's Device ID is contained in `device-ids-to-drop`,
- log an error and abort these steps.
- 3. Run the _Drop Devices Steps_ with the intent to _drop specific_
- `device-ids-to-drop` and keep the user informed regarding the process
- status and any encountered issues.
- #### Drop Devices Steps
- The following steps are defined as the _Drop Devices Steps_:
- 1. If the device does not have multi-device enabled¹, run the
- _Application Setup Steps_ step 2.2. through 2.6. and abort these steps.
- TODO(SE-199): This shall be removed once multi-device supports FS.
- 2. Let `intent` be the intent which can be either to _deactivate_
- multi-device or _drop specific_ devices, letting `device-ids-to-drop` be
- that set of specific Device IDs.
- 3. If `device-ids-to-drop` is defined:
- 1. If `device-ids-to-drop` is empty, log an error and abort these steps.
- 2. If `device-ids-to-drop` contains this device's Device ID, log
- an error and abort these steps.
- 4. Begin a transaction (scope: `DROP_DEVICE`, precondition: none).
- 5. Send a `GetDevicesInfo` message.
- 6. Await the `DevicesInfo` message and let `other-device-ids` be a set of
- the contained Device IDs excluding this device's Device ID.
- 7. If `device-ids-to-drop` is defined, remove each Device ID from
- `device-ids-to-drop` that is not present in `other-device-ids`.
- 8. If `device-ids-to-drop` is not defined, define it to be a copy of
- `other-device-ids`.
- 9. Send a `DropDevice` message for each Device ID of `device-ids-to-drop`.
- 10. Await all corresponding `DropDeviceAck`s of each Device ID in
- `device-ids-to-drop`.
- 11. If `device-ids-to-drop` contains the same Device IDs as
- `other-device-ids` (i.e. all other devices but this device have been
- dropped):
- 1. Send a `DropDevice` with this device's Device ID.
- 2. Await the corresponding `DropDeviceAck` or the connection closing
- with close code `4113`.
- 3. Disable multi-device and purge any existing device group data.
- 4. Run the _Application Setup Steps_ step 2.2. through 2.6.
- TODO(SE-199): This shall be removed once multi-device supports FS.
- ¹: This can happen if the steps are run within a persistent task that is
- aborted before reactivating FS successfully. TODO(SE-199): This shall be
- removed once multi-device supports FS.
- ### Sending
- The following steps are defined as the _Bundled Messages Send Steps_ and
- must always be invoked as part of a task in order to send a message:
- 1. Let `messages` be a list of messages with each having the following
- properties:
- - `id` being the associated message ID¹,
- - `created-at` timestamp,
- - `receivers` being the set of receivers for the message²,
- - the associated conversation,
- - all necessary information to construct a _canonical_ message from it,
- - all necessary information to construct a _specific_ message from it,
- given the specific receiver.
- Note: If only one set of informations to construct a message is
- provided, this is considered both the _canonical_ and the _specific_
- message construction variant.
- 2. For each `message` of `messages`:
- 1. For each `receiver` of `message.receivers`:
- 1. If `receiver` is the user, log a warning, remove `receiver` from
- `receivers` and abort these sub-steps.
- 2. If `receiver` is marked as _invalid_, remove `receiver` from
- `receivers and abort these sub-steps.
- 3. If the properties associated to `message` to be constructed for
- `receiver` given its feature mask indicates that the message is
- not exempted from blocking, run the _Identity Blocked Steps_ for
- `receiver`'s identity. If the result indicates that `receiver` is
- blocked, remove `receiver` from `receivers` and abort these
- sub-steps.
- 4. Construct the _specific_ `message` for `receiver` and attach the
- constructed message to `receiver`.
- 5. Run the _Profile Picture Distribution Steps_ with the constructed
- _specific_ message for `receiver`'s type and `receiver` and extend
- `messages` with the result.
- 3. For each `message` of `messages` attach a random nonce for `message` to
- each receiver of `message.receivers`.
- 4. (MD) Let `pending-reflect-acks` be an empty list.
- 5. (MD) For each `message` of `messages`:
- 1. If the properties associated to the _canonical_ `message` do not
- require reflecting outgoing messages, abort these sub-steps.
- 2. Construct a `d2d.OutgoingMessage` from the _canonical_ `message` for
- the associated conversation and reflect it.
- 3. Add the pending acknowledgement to `pending-reflect-acks`.
- 6. (MD) Await all `pending-reflect-acks`.
- 7. Let `pending-csp-acks` and `fs-commit-fns` be empty lists.
- 8. For each `message` of `messages`:
- 1. For each `receiver` of `message.receivers`:
- 1. If the constructed _specific_ message for `receiver` is of type
- `0xa0`, let `outer-messages` be a list including only the
- constructed message for `receiver` and `fs-commit-fns` be an empty
- list.
- 2. If the constructed _specific_ message for `receiver` is not of
- type `0xa0`, run the _FS Encapsulation Steps_ with the constructed
- _specific_ message for `receiver` and let `outer-messages` and
- `fs-commit-fns` be the result.³
- 3. For each `outer-message` of `outer-messages`:
- 1. Create a `payload.message-with-metadata-box` for `outer-message`
- with `receiver` and let `payload` be the result.
- 2. If `payload.flags` does not contain the _no server
- acknowledgement_ (`0x04`) flag, add `payload.message-id` to
- `pending-csp-acks`.
- 3. If the properties associated to `outer-message` requires
- protection against replay, mark the nonce of `outer-message` as
- used.
- 4. Send `payload`.
- 9. Await all `pending-csp-acks`.
- 10. Run each function of `fs-commit-fns`.
- 11. (MD) Let `pending-reflect-acks` be an empty list.
- 12. (MD) For each `message` of `messages`:
- 1. If the properties associated to the _canonical_ `message` is eligible
- for reflecting `OutgoingMessageUpdate.Sent`:
- 1. Create an `OutgoingMessageUpdate.Sent` for `message.id` and the
- associated conversation and reflect it.
- 2. Add the pending acknowledgement to `pending-reflect-acks`.
- 13. (MD) Await all `pending-reflect-acks`.
- 14. For each `message` of `messages`:
- 1. (MD) If the properties associated to the _canonical_ `message` was
- eligible for reflecting `OutgoingMessageUpdate.Sent`, mark it as
- _sent_ with the timestamp from the corresponding `reflect-ack`
- message.
- 2. (non-MD) Mark `message` as _sent_ with the current timestamp.
- ¹: Note that, in groups, this implicitly assigns the same message ID towards
- each group member which in fact is a requirement of the protocol.
- ²: Reflecting with `receivers` empty is a legitimate case that occurs when
- sending a message in a notes group.
- ³: Always invoking the _FS Encapsulation Steps_ ensures that an FS session
- is being initiated as soon as possible, so that messages can be protected by
- FS. Moreover, it ensures that the announced FS session version is up to date
- (a newer version potentially increasing security or making more messages
- eligible for FS protection).
- The following steps are defined as the _Messages Submit Steps_ which must be
- invoked to submit a user-created message in a conversation:
- 1. Let `messages` be a list of messages of the user to be added to
- the conversation with each having the following properties:
- - `id` set to a random message ID,
- - `created-at` set to the current timestamp,
- - `blobs` be an optional set of blobs that must be uploaded prior to
- being able to construct the message,
- - all necessary information to construct a _canonical_ message from it,
- - all necessary information to construct a _specific_ message from it,
- given the specific receiver.
- Note: If only one set of informations to construct a message is
- provided, this is considered both the _canonical_ and the _specific_
- message construction variant.
- 2. If the associated conversation is a 1:1 conversation, run the _1:1
- Messages Submit Steps_ with `messages`.
- 3. If the associated conversation is a group conversation, run the _Group
- Messages Submit Steps_ with `messages`.
- 4. If the associated conversation is a distribution list conversation, run
- the _Distribution List Messages Submit Steps_ with `messages`.
- 5. (Unreachable)
- The following steps are defined as the _1:1 Messages Submit Steps_ which
- must be invoked to submit a user-created message in a 1:1 conversation:
- 1. Let `messages` be a list of messages of the user to be added to
- the conversation with each having the following properties:
- - `id` defaulting to a random message ID,
- - `created-at` defaulting to the current timestamp,
- - `blobs` be an optional set of blobs that must be uploaded prior to
- being able to construct the message,
- - all necessary information to construct a _canonical_ message from it,
- - all necessary information to construct a _specific_ message from it,
- given the specific receiver.
- Note: If only one set of informations to construct a message is
- provided, this is considered both the _canonical_ and the _specific_
- message construction variant.
- 2. Let `receiver` be the conversation's associated contact.
- 3. If `receiver` has acquaintance level _deleted_, discard `messages`,
- log a warning and abort these steps.¹
- 4. Run the _Identity Blocked Steps_ for `receiver`'s identity'. If the
- result indicates that `receiver` is blocked, discard `messages`, log
- a warning and abort these steps.¹
- 5. For each `message` of `messages`, assign a random message ID to
- `message.id`.
- 6. Schedule a persistent task to run the following steps:²
- 1. If the contact for `receiver` no longer exists, log an error,
- discard `messages` and abort these steps.
- 2. For each `message` of `messages`:
- 1. If `message.blob` is not defined, abort these sub-steps.
- 2. Upload all `message.blobs` to the blob server and update
- `message` with the result (so that the message can be
- constructed).
- 3. (MD) If `receiver`'s acquaintance level is not _direct_ or if at
- least one of the `messages` associated properties requires to
- unarchive the conversation and the associated conversation visibility
- is set to _archived_:
- 1. Begin a transaction with scope `CONTACT_SYNC` and the following
- precondition:
- 1. If the contact for `receiver` no longer exists, log an error,
- discard `messages` and abort these steps.
- 2. Let `change` be the following changes as defined by
- `sync.Contact`:
- - `acquaintance_level` set to `DIRECT`,
- - `conversation_visibility` set to `NORMAL` if the associated
- conversation visibility is currently _archived_,
- 3. Reflect a `ContactSync.Update` with `contact` set from `change`.
- 4. Commit the transaction and await acknowledgement.
- 5. Persist the `change` to the `receiver`.³
- 4. Run the _Bundled Messages Send Steps_ for `messages` with the
- following additional properties added to each message:
- - `receivers` set to `receiver`.
- 7. If the the `receiver`'s acquaintance level is not _direct_, update it to
- _direct_.
- 8. If at least one of the `messages` associated properties requires to
- unarchive the conversation and the associated conversation visibility is
- set to _archived_, set it to _normal_.
- 9. For each `message` of `messages`, add `message` to the associated
- conversation or update a stateful message referred to by `message`
- respectively.
- 10. If at least one of the `messages` associated properties requires to
- bump the conversation's _last update_ timestamp, update it to the
- current timestamp.
- ¹: While the UI should not allow to submit a message in these states,
- another device may alter the state just prior to submission, allowing to
- hit these steps in rare circumstances.
- ²: Rationale to not check for acquaintance level _deleted_ or whether the
- receiver is blocked again is the legitimate use case of the user sending a
- final message prior to blocking or removing a contact.
- ³: This is intentionally done only for MD since the user may e.g.
- immediately archive a conversation after submitting a message. This results
- in both the message and the contact sync being queued as tasks whereas in
- the non-MD case only the message task would be queued. In the MD case, the
- conversation briefly flicks back to _unarchived_ once the message has been
- sent but it immediately flicks back to _archived_ once the contact sync task
- has been executed. But because no contact sync task is created in the non-MD
- case, the message task would leave the conversation _unarchived_ which is
- not intended by the user.
- The following steps are defined as the _Group Messages Submit Steps_ which
- must be invoked to submit a user-created message in a group conversation:
- 1. Let `messages` be a list of messages of the user to be added to
- the conversation with each having the following properties:
- - `id` set to a random message ID,
- - `created-at` set to the current timestamp,
- - `blobs` be an optional set of blobs that must be uploaded prior to
- being able to construct the message,
- - all necessary information to construct a _canonical_ message from it,
- - all necessary information to construct a _specific_ message from it,
- given the specific receiver.
- Note: If only one set of informations to construct a message is provided,
- this is considered both the _canonical_ and the _specific_ message
- construction variant.
- 2. If the group is marked as _left_, discard `messages`, log a warning and
- abort these steps.¹
- 3. For each `message` of `messages`, assign a random message ID to
- `message.id`.
- 4. Schedule a persistent task to run the following steps:²
- 1. If the group does not exist or is marked as _left_, log a warning,
- discard `messages` and abort these steps.
- 2. For each `message` of `messages`:
- 1. If `message.blob` is not defined, abort these sub-steps.
- 2. Upload all `message.blobs` to the blob server with the _persist_
- flag and update `message` with the result (so that the message
- can be constructed).
- 3. (MD) If at least one of the `messages` associated properties requires
- to unarchive the conversation and the associated conversation
- visibility is set to _archived_:
- 1. Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group does not exist or is marked as left, log a warning
- that a group sync race occurred, discard `messages` and abort
- these steps.
- 2. Let `change` be the following changes as defined by `sync.Group`:
- - `conversation_visibility` set to `NORMAL` if the associated
- conversation visibility is currently _archived_,
- 3. Reflect a `GroupSync.Update` with `group` set from `change`.
- 4. Commit the transaction and await acknowledgement.
- 5. Persist the `change` to the group.³
- 4. Run the _Bundled Messages Send Steps_ for `messages` with the
- following additional properties added to each message:
- - `receivers` set to the group's members.
- 5. If at least one of the `messages` associated properties requires to
- unarchive the conversation and the associated conversation visibility is
- set to _archived_, set it to _normal_.
- 6. For each `message` of `messages`, add `message` to the associated
- conversation or update a stateful message referred to by `message`
- respectively.
- 7. If at least one of the `messages` associated properties requires to
- bump the conversation's _last update_ timestamp, update it to the current
- timestamp.
- ¹: While the UI should not allow to submit a message in these states,
- another device may alter the state just prior to submission, allowing to
- hit these steps in rare circumstances.
- ²: Rationale to not check for acquaintance level _deleted_ or whether the
- receiver is blocked again is the legitimate use case of the user sending a
- final message prior to removing a group.
- ³: This is intentionally done only for MD since the user may e.g.
- immediately archive a conversation after submitting a message. This results
- in both the message and the group sync being queued as tasks whereas in the
- non-MD case only the message task would be queued. In the MD case, the
- conversation briefly flicks back to _unarchived_ once the message has been
- sent but it immediately flicks back to _archived_ once the group sync task
- has been executed. But because no group sync task is created in the non-MD
- case, the message task would leave the conversation _unarchived_ which is
- not intended by the user.
- The following steps are defined as the _Distribution List Messages Submit
- Steps_ which must be invoked to submit a user-composed message in a
- distribution list conversation:
- 1. Let `messages` be a list of messages of the user to be added to
- the conversation with each having the following properties:
- - `id` set to a random message ID,
- - `created-at` set to the current timestamp,
- - `blobs` be an optional set of blobs that must be uploaded prior to
- being able to construct the message,
- - all necessary information to construct a _canonical_ message from it,
- - all necessary information to construct a _specific_ message from it,
- given the specific receiver.
- Note: If only one set of informations to construct a message is
- provided, this is considered both the _canonical_ and the _specific_
- message construction variant.
- 2. For each `message` of `messages`, assign a random message ID to
- `message.id`.
- 3. Schedule a persistent task to run the following steps:
- 1. If the distribution list does not exist, log a warning, discard
- `messages` and abort these steps.
- 2. For each `message` of `messages`:
- 1. If `message.blob` is not defined, abort these sub-steps.
- 2. Upload all `message.blobs` to the blob server with the _persist_
- flag and update `message` with the result (so that the message
- can be constructed).
- 3. (non-MD) Let `members` be all current members of the distribution
- list. Remove any members with acquaintance level _deleted_ from
- `members`.¹
- 4. (MD) If at least one of the `messages` associated properties requires
- to unarchive the conversation and the associated conversation
- visibility is set to _archived_:
- 1. Begin a transaction with scope `DISTRIBUTION_LIST_SYNC` and the
- following precondition:
- 1. If the distribution list does not exist, log a warning that a
- distribution list sync race occurred, discard `messages` and
- abort these steps.
- 2. Let `members` be all current members of the distribution list.
- Remove any members with acquaintance level _deleted_ from `members`.¹
- 3. Let `change` be the following changes as defined by
- `sync.DistributionList`:
- - `member_identities` from `members`,
- - `conversation_visibility` set to `NORMAL` if the associated
- conversation visibility is currently _archived_,
- 4. Reflect a `DistributionList.Update` with `distribution_list` set from `change`.
- 5. Commit the transaction and await acknowledgement.
- 6. Persist the `change` to the distribution list.²
- 5. Run the _Bundled Messages Send Steps_ for `messages` with the
- following additional properties added to each message:
- - `receivers` from `members`.
- 4. If at least one of the `messages` associated properties requires to
- unarchive the conversation and the associated conversation visibility is
- set to _archived_, set it to _normal_.
- 5. For each `message` of `messages`, add `message` to the associated
- conversation or update a stateful message referred to by `message`
- respectively.
- 6. If at least one of the `messages` associated properties requires to
- bump the conversation's _last update_ timestamp, let `last-update-at` be
- the current timestamp.
- 7. If `last-update-at` is defined, update the associated _last update_
- timestamp of the conversation to `last-update-at`.
- 8. For each `member` of the distribution list:
- 1. Add each message of `messages` to the 1:1 conversation associated to
- `member`.
- 2. If `last-update-at` is defined, update the associated _last update_
- timestamp of the 1:1 conversation of `member` to `last-update-at`.
- ¹: There should be no receivers in a distribution list that have
- acquaintance level _deleted_, so the filtering is only done for safety.
- ²: This is intentionally done only for MD since the user may e.g.
- immediately archive a conversation after submitting a message. This results
- in both the message and the distribution list sync being queued as tasks
- whereas in the non-MD case only the message task would be queued. In the MD
- case, the conversation briefly flicks back to _unarchived_ once the message
- has been sent but it immediately flicks back to _archived_ once the
- distribution list sync task has been executed. But because no distribution
- list sync task is created in the non-MD case, the message task would leave
- the conversation _unarchived_ which is not intended by the user.
- ### Receiving
- The following steps are defined as _Common Group Receive Steps_ and will
- be applied in most cases for group messages:
- 1. Look up the group.
- 2. If the group could not be found:
- 1. If the user is the creator of the group, discard the message and abort
- these steps.
- 2. Run the _Identity Blocked Steps_ for the creator of the group. If
- the result indicates that the creator is blocked, discard the message
- and abort these steps.
- 3. Run the _Group Sync Request Steps_ for the group, discard the
- message and abort these steps.
- 3. If the group is marked as _left_:
- 1. If the user is the creator of the group, run the _Bundled Messages
- Send Steps_ with the following properties:
- - `id` set to a random message ID,
- - `created-at` set to the current timestamp,
- - `receivers` set to the sender,
- - to construct a
- [`group-setup`](ref:e2e.group-setup) (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container)) with
- an empty members set
- 2. If the user is not the creator of the group, run the _Bundled
- Messages Send Steps_ with the following properties:
- - `id` set to a random message ID,
- - `created-at` set to the current timestamp,
- - `receivers` set to the sender,
- - to construct a
- [`group-leave`](ref:e2e.group-leave) (wrapped by
- [`group-member-container`](ref:e2e.group-member-container))
- 3. Discard the message and abort these steps.
- 4. If the sender is not a member of the group:
- 1. If the user is the creator of the group, run the _Bundled Messages
- Send Steps_ with the following properties:
- - `id` set to a random message ID,
- - `created-at` set to the current timestamp,
- - `receivers` set to the sender,
- - to construct a
- [`group-setup`](ref:e2e.group-setup) (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container)) with
- an empty members set
- 2. If the user is not the creator of the group, run the _Group Sync
- Request Steps_, then discard the message and abort these steps.
- 3. Discard the message and abort these steps.
- This rule and any exceptions will be referenced/defined explicitly for each
- message.
- Note that steps are not allowed to discard messages from blocked contacts
- prior to running these steps if the message alters group state (group
- control messages), or is stateful (i.e. introduces a poll, poll vote, or a
- group call).
- ### Periodic Group Sync
- When the creator of a group...
- - is about to send a group conversation message, or
- - did just receive a group conversation message,
- it must trigger a _group sync_ for this group if the last time the
- _group sync_ was triggered is more than seven days ago.
- When a _group sync_ is triggered, the creator assumes it has received a
- [`group-sync-request`](ref:e2e.group-sync-request) from every group member
- and must now respond accordingly to each member of the group.
- A newly created group counts as an initial _group sync_ trigger. In other
- words, the first group sync of a newly created group triggers seven days
- in the future when one of the above described conditions is met.
- This provides a form of self-healing in case a device lost its group state
- (e.g. due to a backup restore) and was unable to correct this mischief.
- [//]: # "TODO(SE-40): Group states"
- ### Blobs
- Since messages have a strict maximum size limitation, large binary blobs
- are uploaded to the blob server. Blobs currently have a maximum size of
- 100 MiB.
- When Multi-Device is activated, all Blobs must be downloaded via the
- respective Blob Mirror unless explicitly stated otherwise.
- The following steps are defined as the _Blob Credentials Refresh Steps_:
- 1. Let `credentials` be the most recently used cached blob credentials.
- 2. If `credentials` is defined and is not yet expired, return `credentials`.
- 3. Request Blob Server Credentials via the Directory Server API and set
- `credentials` to the result. If no credentials could be obtained within 10s
- or the request was unsuccessful, exceptionally abort these steps.
- 4. Cache `credentials` with the provided expiration date.
- 5. Return `credentials`.
- #### Upload
- The following steps are defined as the _Blob Upload Steps_:
- 1. Let `blob` be the following properties:
- - `data` being the encrypted binary data to be uploaded,
- - `scope` being either _public_ (for public facing blobs) or _local_ (for
- device group facing blobs).
- - `persist` being a mark (primarily for usage within groups).
- 2. Run the _Blob Credentials Refresh Steps_ and let `credentials` be the
- result.
- 3. (non-MD) Upload to the blob server from `blob` and `credentials`.
- 4. (MD) Upload to the blob mirror server from `blob`, `credentials` and the
- device group information.
- 5. If the upload stream stalls for more than 10s, exceptionally abort these
- steps.
- 6. Return the resulting blob ID.
- #### Download
- The following steps are defined as the _Blob Download Steps_:
- 1. Let `blob` be the following properties:
- - `id` being the blob ID,
- - `scope` being either _public_ (for public facing blobs) or _local_ (for
- device group facing blobs).
- 2. Run the _Blob Credentials Refresh Steps_ and let `credentials` be the
- result.
- 3. (non-MD) Download the blob data from the blob server using `blob` and
- `credentials`.
- 4. (MD) Download the blob data from the blob mirror server using `blob`,
- `credentials` and the device group information.
- 5. If the download stream stalls for more than 10s, exceptionally abort
- these steps.
- 6. Schedule a volatile background task to run the following steps:
- 1. Run the _Blob Credentials Refresh Steps_ and let `credentials` be the
- result.
- 2. (non-MD) Request to mark the blob download from the blob server of
- `blob.id` as _done_ with `credentials`.
- 3. (MD) Request to mark the blob download from the blob mirror server of
- `blob.id` as _done_ with `credentials` and the device group
- information.
- 4. If no response could be obtained within 10s or the request was
- unsuccessful, log a warning.
- 7. Return the resulting blob data.
- ### Image, Audio, Video vs. File
- Images, as well as audio and video sources can be either send as special
- media messages or as files. When sending as a file, i.e. a
- [`file`](ref:e2e.file) message struct with rendering type `0x00` (file), no
- transcoding is necessary and no media type restrictions apply.
- Clients should intelligently choose between a media message and a file
- message but always leave the final choice to the user.
- The following sections describe what restrictions apply and modifications
- need to be made in case the source is sent as a media message, i.e. one
- of the specialised (deprecated) media structs or a [`file`](ref:e2e.file)
- message struct with rendering type `0x01` (media) or `0x02` (sticker).
- ### Images
- Images must be in JPEG format for the legacy
- [`deprecated-image`](ref:e2e.deprecated-image) message and for profile
- pictures.
- When using the [`file`](ref:e2e.file) message struct, the following media
- types are explicitly supported:
- - image/gif
- - image/jpeg
- - image/png
- - image/webp
- The following media types are explicitly not supported:
- - image/svg+xml
- Other media types _may_ be supported.
- Keep the format when resizing images or creating thumbnails, if possible
- (e.g. if the source is a JPEG, make the thumbnail a JPEG). When the format
- cannot be kept, use PNG for source images with transparency or
- lossless encoding (e.g. screenshots) and JPEG for images without
- transparency or lossy encoding (e.g. photos).
- Recommended maximum dimensions:
- - Small: 640x640
- - Medium: 1024x1024
- - Large: 1600x1600
- - Extra Large: 2592x2592
- - Original: As is
- ### Thumbnails
- Apply the logic described for images to all thumbnails with recommended
- maximum dimensions of 512x512.
- ### User Profile Distribution
- The shareable part of the user profile consists of the user's public
- nickname and the profile picture:
- - The nickname is sent along with outgoing messages as `sender-nickname`
- inside the [`legacy-message`](ref:payload.legacy-message) or as part of
- the metadata in
- [`message-with-metadata-box`](ref:payload.message-with-metadata-box).
- - The profile picture is distributed as described in
- [Profile Picture Distribution](ref:e2e#profile-picture-distribution).
- Whether user profile distribution should be triggered by an outgoing message
- is specified in the description of every message type below.
- ### Profile Pictures
- Apply the logic described for images to all profile pictures with
- recommended maximum dimensions of 512x512 with a square aspect ratio.
- #### Profile Picture Distribution
- Every time a message is being sent to a specific contact or a group of
- contacts, the sender needs to evaluate whether the profile picture needs to
- be sent. If the receiver of the message is a group, the evaluation needs to
- be done for each contact of that group.
- The following steps are defined as the _Profile Picture Distribution Steps_:
- 1. Let `type` be a message type associated to a message that is about to be
- sent.
- 2. Let `receiver` be the receiver of the message.
- 3. If the properties associated to `type` do not require _User Profile
- Distribution_, abort these steps without a message.
- 4. If `receiver`'s Threema ID is `ECHOECHO` or a Threema Gateway ID (starts
- with a `*`), abort these steps without a message.
- 5. Let `cache` be the most recently distributed profile picture message
- variant towards `receiver`, being either
- - a blob ID if the user's profile picture was distributed via a
- [`set-profile-picture`](ref:e2e.set-profile-picture) message, or
- - a _remove_ mark and a timestamp if the user's profile picture was
- removed via a
- [`delete-profile-picture`](ref:e2e.delete-profile-picture) message.
- 6. If the user has no profile picture or the settings indicate that the
- user's profile picture should be shared with nobody or the settings
- associated to `receiver` indicate that the profile picture should not be
- distributed to it:
- 1. If `cache` is a _remove_ mark and indicates that the most recent
- [`delete-profile-picture`](ref:e2e.delete-profile-picture) message
- towards `receiver` was sent less than seven days ago, abort these
- steps without a message.
- 2. Update the `cache` for `receiver` to a _remove_ mark with the current
- timestamp.
- 3. Return the following properties:
- - `id` being a random message ID,
- - `created-at` set to the current timestamp,
- - `receivers` set to `receiver`,
- - to construct a
- [`delete-profile-picture`](ref:e2e.delete-profile-picture).
- 7. If there is a cached profile picture of the user and the associated blob
- was uploaded more than seven days ago, remove the cached profile
- picture.
- 8. If `cache` contains a blob ID of the most recent
- [`set-profile-picture`](ref:e2e.set-profile-picture) message sent
- towards `receiver` that equals the blob ID of the cached profile
- picture, abort these steps without a message.
- 9. If there is no cached profile picture, encrypt the user's profile
- picture with a random symmetric key and upload it to the blob server.
- Store the key and the resulting blob ID as the cached profile picture of
- the user.
- 10. Update the `cache` for `receiver` to the blob ID of the cached profile
- picture.
- 11. Return the following properties:
- - `id` being a random message ID,
- - `created-at` set to the current timestamp,
- - `receivers` set to `receiver`,
- - to construct a [`set-profile-picture`](ref:e2e.set-profile-picture)
- message using the cached profile picture's blob ID and key.
- When the user changes the profile picture, run the steps associated to
- update the user's profile with the new profile picture or the newly removed
- profile picture.
- #### Profile Picture Sharing Settings
- In the client settings, there are three profile picture sharing options that
- the user can choose from:
- - Share with nobody
- - Share with everybody you write to
- - Share with selected contacts only
- The default is to share the profile picture with everyone.
- #### Contact Profile Picture Precedence
- There are three different sources of profile pictures, ordered by
- precedence:
- 1. _contact-defined_: Set by the contact, distributed through a
- [`set-profile-picture`](ref:e2e.set-profile-picture) message.
- 2. _gateway-defined_: Set by the creator in the Threema Gateway (or Threema
- Broadcast) control panel and distributed through `avatar.threema.ch`.
- Only applicable to Threema Gateway IDs (starting with a `*`).
- 3. _user-defined_: Set by the app user for this contact or imported from
- the address book. Applicable to all Threema IDs which are not Threema
- Gateway IDs.
- The following steps are defined as _Contact Profile Picture Selection Steps_
- and will be applied to determine the contact's profile picture that should
- be displayed:
- 1. Let `id` be the Threema ID of the contact.
- 2. If the _contact-defined_ picture is set for the contact, apply it and
- abort these steps.
- 3. If `id` starts with a `*` (is a Threema Gateway ID) and the
- _gateway-defined_ picture is set for the contact, apply it and abort
- these steps.
- 4. If `id` does not start with `*` and the _user-defined_ picture is set
- for the contact, apply it and abort these steps.
- 5. Apply a fallback picture.
- #### Recurring Gateway Contact Profile Picture Refresh
- For contacts with a Threema Gateway ID (starting with a `*`), the profile
- picture needs to be fetched recurringly:
- 1. Fetch the profile picture for the ID from `avatar.threema.ch`.
- 2. If no profile picture could be found, schedule the next refresh in 24h
- and abort these steps.
- 3. Store the profile picture as the _gateway-defined_ picture.
- 4. Schedule the next refresh according to the `expires` header of the HTTP
- response.
- 5. Run the _Contact Profile Picture Selection Steps_ for this contact.
- ### Audio
- Audio must be in AAC format.
- If the source is already in AAC, no transcoding is necessary. Otherwise,
- the recommended transcoding settings are: Bitrate 128 kbit/s, 2 channels.
- When recording audio (i.e. a voice message), the recommended recording
- settings are: Sample rate 44.1 kHz, bitrate 32 kbit/s, 1 channel.
- ### Video
- Videos must be encoded in H.264 and the MP4 container format.
- Recommended encoding settings for all videos:
- - Low: 480x480, scale by maintaining aspect ratio to nearest multiple
- of 16px. Video bitrate 384 kbit/s, audio bitrate 32 kbit/s (2
- channels). Baseline Profile, Level 3.1.
- - High: 848x848, scale by maintaining aspect ratio to nearest multiple
- of 16px. Video bitrate 1500 kbit/s, audio bitrate 64 kbit/s (2
- channels). Baseline Profile, Level 3.1.
- - Original: As is. Still needs transcoding in case a different codec has
- been used.
- When recording a video, the following recording settings are recommended
- to avoid post-reencoding: 1280x720 / 720x1280. Video bitrate 2000 kbit/s
- at 30 fps, audio bitrate 128 kbit/s (2 channels).
- ### Call Features
- Call features are transmitted within either a
- [`call-offer`](ref:e2e.call-offer) or a [`call-answer`](ref:e2e.call-answer)
- message. It is an optional object containing the below defined fields. If
- the object is not provided, assume an empty features object.
- - Video Support (`'video'`): Set this field to `null` or an empty object if
- video calls are enabled. If either side omits this field, video support
- is disabled for the upcoming call.
- ### Application Entrypoints
- #### Work Credentials URL
- The following steps are defined as _Application Work Credentials URL
- Entrypoint Steps_ and must be run when the application is built for the
- _Work_ flavour and is invoked by a Work credentials URL:
- 1. Decode the Work credentials URL and let `work-credentials` be the result.
- 2. Run the _Common Application Entrypoint Steps_ with `work-credentials`.
- #### OnPrem Server/License URL
- The following steps are defined as _Application OnPrem Server/License URL
- Entrypoint Steps_ and must be run when the application is built for the
- _OnPrem_ flavour and is invoked via an OnPrem server/license URL:
- 1. Decode the OnPrem server/license URL¹ and let `on-prem-server-url` and
- `work-credentials` be the result.
- 2. Run the _Common Application Entrypoint Steps_ with `on-prem-server-url`
- and `work-credentials`.
- ¹: While the OnPrem server URL only provides the path to the OPPF file, the
- OnPrem license URL additionally provides the Work credentials.
- #### Default
- The following steps are defined as the _Common Application Entrypoint Steps_
- and resemble the default entrypoint if no specific entrypoint was invoked by
- a user interaction:
- 1. If identity data exists:
- 1. (OnPrem: Refresh the OPPF file and apply its configuration to the
- application. TODO(SE-137): Specify more clearly.)
- 2. [...]
- 3. Abort these steps.
- 2. (Identity data is missing at this point.)
- 3. (If the application is built for the _Consumer_ flavour,
- request/verify the license. TODO(SE-137): Specify more clearly.)
- 4. If the application is built for the _Work_ flavour:
- 1. Let `work-credentials` be the provided parameters.
- 2. If `work-credentials` is not defined, request the user to provide
- this information and update `work-credentials` with the result.
- 3. (Verify the Work license. TODO(SE-137): Specify more clearly.)
- 5. If the application is built for the _OnPrem_ flavour:
- 1. Let `on-prem-server-url` and `work-credentials` be the provided
- parameters.
- 2. If the application is built for the regular _OnPrem_ flavour:
- 1. If the MDM parameter `th_onprem_server` is defined:
- 1. If `on-prem-server-url` is defined and its canonical¹
- representation does not equal the canonical¹ representation of
- the MDM parameter `th_onprem_server`, show an error to the user
- that an incorrect OnPrem server/license URL has been used and
- abort these steps.
- 2. Set `on-prem-server-url` to the MDM parameter `th_onprem_server`.
- 2. If `on-prem-server-url` or `work-credentials` is not defined,
- request the user to provide the missing information and update
- `on-prem-server-url` and `work-credentials` with the result.
- 3. If the application is built for the _White-Labeled OnPrem_ flavour²:
- 1. If `on-prem-server-url` is defined and its canonical¹
- representation does not equal the canonical¹ representation of the
- preconfigured OnPrem server URL, show an error to the user that an
- incorrect OnPrem server/license URL has been used and abort these
- steps.
- 2. Set `on-prem-server-url` to the preconfigured OnPrem server URL.
- 3. If `work-credentials` is not defined, request the user to provide
- the Work credentials and update `work-credentials` with the result.
- 4. (Verify the OnPrem/Work license. TODO(SE-137): Specify more
- clearly.)
- 6. Run the _Application Setup Steps_.
- ¹: The canonical URL is constructed by appending `/prov/config.oppf` if the
- URL does not end with `.oppf`.
- ²: Note that the `th_onprem_server` parameter is intentionally being ignored
- in this case.
- ### Application Setup
- The following steps are defined as _Application Setup Steps_ and must be run
- when no identity data exists (i.e. the application is installed for the
- first time or the Threema ID and associated identity data has been removed):
- 1. [...]
- 2. (The application allows to create a new Threema ID or restore a backup
- here. TODO(SE-137): Specify more clearly.)
- 3. If OnPrem sub-flavour and the MDM parameter `th_enable_remote_secret` is
- `true`, run the _Remote Secret Activate Steps_.
- 4. If application state has not been set up by the _Device Join Protocol_
- (meaning that multi-device is deactivated), run the following steps:
- 1. [...]
- 2. Update the user's feature mask on the directory server.
- 3. Let `contacts` be the list of all contacts, including those with an
- acquaintance level different than _direct_.
- 4. Call the _Work Sync_ endpoint with `contacts` and update `contacts`
- and the settings with the result.
- 5. Refresh the state, type and feature mask of all `contacts` from the
- directory server and make any changes persistent.
- 6. Let `solicited-contacts` be a copy of `contacts` filtered in the
- following way. For each `contact`:
- 1. If the `contact`'s activity state is _invalid_ (i.e. it does not
- exist or has been revoked), remove `contact` from the list and
- abort these sub-steps.
- 2. If `contact` is part of a group that is not marked as _left_, add
- `contact` to the list and abort these sub-steps.
- 3. Lookup the 1:1 conversation with `contact` and let `last-update`
- be the associated _last update_ timestamp.
- 4. If `last-update` is defined, add `contact` to the list and abort
- these sub-steps.
- 5. Remove `contact` from the list.
- 7. If FS is supported by the client, run the _FS Refresh Steps_ with
- `solicited-contacts`.
- 8. For each `contact` of `solicited-contacts` run the _Bundled Messages
- Send Steps_ with the following properties:
- - `id` being a random message ID,
- - `created-at` set to the current timestamp,
- - `receivers` set to `contact`,
- - to construct a
- [`contact-request-profile-picture`](ref:e2e.contact-request-profile-picture)
- 9. For each group not marked as _left_:
- 1. If the user is the creator of the group, trigger a _group sync_
- for that group.
- 2. If the user is not the creator of the group, run the _Group Sync
- Request Steps_ for the group.
- 10. [...]
- 5. Commit the application state with the updated `contacts` and settings,
- outer storage potentially protected by a passphrase (if provided) and
- inner storage potentially protected by RS (if created).
- ### Application Update
- The following steps are defined as _Application Update Steps_ and must be
- run as a persistent task when the application has just been updated to a new
- version or downgraded to a previous version:
- 1. [...]
- 2. Update the user's feature mask on the directory server.
- 3. Let `contacts` be the list of all contacts (regardless of the
- acquaintance level).
- 4. Refresh the state, type and feature mask of all `contacts` from the
- directory server and make any changes persistent.
- 5. For each `contact` of `contacts`:
- 1. If an associated FS session with `contact` exists and any of the FS
- states is unknown or any of the stored FS versions (local or remote)
- is unknown, terminate the FS session by running the _Bundled Messages
- Send Steps_ with the following properties:
- - `id` being a random message ID,
- - `created-at` set to the current timestamp,
- - `receivers` set to `contact`,
- - to construct a `csp-e2e-fs.Terminate` message with cause `RESET`.
- 6. [...]
- Note: Reactivation of FS due to disabling multi-device should run the
- _Application Setup Steps_ step 2.2. through 2.6. TODO(SE-199): This note
- will be removed once multi-device supports FS.
- ### Application Start
- The following steps are defined as _Application Start Steps_ and must be run
- as a blocking task when the application starts before running any further
- sequences:
- 1. [...]
- 2. Attempt to unlock the outer storage, potentially protected by a
- passphrase (if provided).
- 3. If the Remote Secret feature is active, run the _Remote Secret Monitor
- Steps_ until it yields RS and let it continue as a volatile background
- task bound to the application.
- 4. Unlock the inner storage, optionally protected by RS (if activated).
- 5. [...]
- 6. Initialise the application from the unlocked storage.
- container:
- _group: Header
- _doc: |-
- Contains an end-to-end encrypted message.
- fields:
- - _doc: |-
- Type of the message (`common.CspE2eMessageType`).
- name: type
- type: u8
- - _doc: |-
- Inner message. Needs to be parsed according to the `type` field.
- Padded with a random amount from 1 to 255 bytes in [PKCS#7
- format](https://datatracker.ietf.org/doc/html/rfc5652#section-6.3).
- Additionally, for security reasons, the total size of `padded-data`
- should be at least 32 bytes, to avoid leaking information about the
- contents.
- Example padding (hex representation):
- - 1 byte: `01`
- - 3 bytes: `030303`
- - 10 bytes: `0A0A0A0A0A0A0A0A0A0A`
- To add padding without information leaks, run the following steps:
- 1. Let `data` be the data to be padded.
- 2. Let `pad-length` be a random number between (inclusive) 1 and 255.
- 3. If the sum of the byte length of `data` and `pad-length` is less
- than 32, update `pad-length` so the sum is precisely 32.
- 4. Let `pad-byte` be the encoded unsigned 8-bit integer
- representation of `pad-length`.
- 5. Let `padded-data` be the padded data by adding `pad-length`
- trailing `pad-byte` bytes to `data`.
- To remove padding:
- 1. Let `pad-length` be the decoded unsigned 8-bit integer
- representation of the last byte of `padded-data`.
- 2. Let `data` be the unpadded data by ignoring the trailing
- `pad-length` bytes of `padded-data`.
- name: padded-data
- type: b*
- group-creator-container:
- _group: Header
- _doc: |-
- Container that is wrapped around some special group messages sent by the
- creator to normal group members and vice versa.
- fields:
- - _doc: |-
- 8 byte random group ID. Uniquely identifies the group when combined
- with the creator's Threema ID.
- name: group-id
- type: *group-id
- - _doc: |-
- Inner message struct.
- name: inner-data
- type: b*
- group-member-container:
- _group: Header
- _doc: |-
- Container that is wrapped around most messages sent by group members to
- other group members.
- fields:
- - _doc: |-
- The group creator's Threema ID.
- name: creator-identity
- type: *identity
- - _doc: |-
- 8 byte random group ID assigned to the group by the creator.
- name: group-id
- type: *group-id
- - _doc: |-
- Inner message struct.
- name: inner-data
- type: b*
- empty:
- _doc: |-
- An empty message (duh).
- Only used when encapsulated by an `csp_e2e_fs.Envelope` to announce a new
- FS version without explicit renegotiation.
- **Properties**:
- - Kind: 1:1
- - Flags: None
- - User profile distribution: No
- - Exempt from blocking: Yes
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: No
- - Outgoing: No
- - _Sent_ update: No
- - Delivery receipts: No
- - Reactions: No
- - When rejected: N/A (ignored)
- - Send to Threema Gateway ID group creator: N/A
- When the user submits this message in a 1:1, group or distribution list,
- _Rick Astley - Never Gonna Give You Up_ must be played and looped
- indefinitely on the user's device.
- When receiving this message, a mechanical finger must be unlatched from
- the device and boop the receiving user on the nose.
- text:
- _group: Conversation Messages
- _doc: |-
- A text message.
- **Properties (1:1)**:
- - Kind: 1:1
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: Yes
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: Yes
- - Delivery receipts: Yes
- - Reactions: Yes
- - When rejected: Re-send after confirmation
- - Edit applies to: Text
- - Deletable by: User and sender
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- **Properties (Group)**:
- - Kind: Group
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: Yes
- - Delivery receipts: N/A
- - Reactions: Yes
- - When rejected: Re-send after confirmation
- - Edit applies to: Text
- - Deletable by: User and sender
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: If capture is enabled
- When the user submits this message in a 1:1, group or distribution list
- conversation:
- 1. Split the provided text into multiple segments with at most 6000 UTF-8
- encoded bytes while preserving grapheme clusters at boundaries and let
- `text-segments` be the result.¹
- 2. Run the _Messages Submit Steps_ with `messages` set from
- `text-segments` (for a group, wrapped by
- [`group-member-container`](ref:e2e.group-member-container)).
- ¹: The UI may warn the user that the message will be split if the UTF-8
- encoded text exceeds 6000 bytes and request confirmation before
- submission.
- When reflected from another device as an incoming or outgoing 1:1
- message:
- 1. Add the message to the associated 1:1 conversation.
- When receiving this message as a 1:1 message:
- 1. Add the message to the associated 1:1 conversation.
- When reflected from another device as an incoming or outgoing group
- message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Add the message to the associated group conversation.
- When receiving this message as a group message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
- message has been discarded, abort these steps.
- 2. Add the message to the associated group conversation.
- fields:
- - _doc: |-
- UTF-8 encoded text.
- name: text
- type: b*
- deprecated-image:
- _group: Conversation Messages
- _doc: |-
- An image message.
- Note: This message is deprecated and may be phased out eventually. When
- sending images, use the [`file`](ref:e2e.file) message with the rendering
- type `0x01` (media).
- **Properties**:
- - Kind: 1:1
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: Yes
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: N/A
- - _Sent_ update: Yes
- - Delivery receipts: Yes
- - Reactions: Yes
- - When rejected: N/A (deprecated message is not being sent)
- - Edit applies to: N/A
- - Deletable by: User and sender
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- The image must be in JPEG format, is uploaded to the blob server and
- encrypted by:
- XSalsa20-Poly1305(
- key=X25519HSalsa20(<sender.CK>.secret, <receiver.CK>.public),
- nonce=<deprecated-image.nonce>,
- )
- This message is deprecated and may no longer be submitted.
- When reflected from another device as an incoming message:
- 1. Run the _Common Deprecated Image Receive Steps_.
- When receiving this message:
- 1. Run the _Common Deprecated Image Receive Steps_.
- The following steps are defined as the _Common Deprecated Image Receive
- Steps_:
- 1. Add the message to the associated 1:1 conversation.
- 2. If this message is eligible for auto-download, schedule downloading the
- image data from the blob server and request the blob to be removed.
- fields:
- - _doc: |-
- Blob ID to obtain the image data.
- name: image-blob-id
- type: *blob-id
- - _doc: |-
- Image size in bytes.
- name: image-size
- type: u32-le
- - _doc: |-
- Random nonce used to encrypt the image data.
- name: nonce
- type: *nonce
- deprecated-group-image:
- _group: Conversation Messages
- _doc: |-
- An image message (only used by groups).
- Note: This message is deprecated and may be phased out eventually. When
- sending images, use the [`file`](ref:e2e.file) message with the rendering
- type `0x01` (media).
- **Properties**:
- - Kind: Group
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: N/A
- - _Sent_ update: Yes
- - Delivery receipts: N/A
- - Reactions: Yes
- - When rejected: N/A (deprecated message is not being sent)
- - Edit applies to: N/A
- - Deletable by: User and sender
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: If capture is enabled
- The image must be in JPEG format, is uploaded to the blob server and
- encrypted by:
- XSalsa20-Poly1305(key=<deprecated-group-image.key>, nonce=00..01)
- This message is deprecated and may no longer be submitted.
- When reflected from another device as an incoming message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the _Common Deprecated Group Image Receive Steps_.
- When receiving this message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
- message has been discarded, abort these steps.
- 2. Run the _Common Deprecated Group Image Receive Steps_.
- The following steps are defined as the _Common Deprecated Group Image
- Receive Steps_:
- 1. Add the message to the associated group conversation.
- 2. If this message is eligible for auto-download, schedule downloading the
- image data from the blob server but do not request the blob to be
- removed.
- fields:
- - _doc: |-
- Blob ID to obtain the image data.
- name: image-blob-id
- type: *blob-id
- - _doc: |-
- Image size in bytes.
- name: image-size
- type: u32-le
- - _doc: |-
- Random symmetric key used to encrypt the image data.
- name: key
- type: *key
- location:
- _group: Conversation Messages
- _doc: |-
- A location message.
- **Properties (1:1)**:
- - Kind: 1:1
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: Yes
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: Yes
- - Delivery receipts: Yes
- - Reactions: Yes
- - When rejected: Re-send after confirmation
- - Edit applies to: N/A
- - Deletable by: User and sender
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- **Properties (Group)**:
- - Kind: Group
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: Yes
- - Delivery receipts: N/A
- - Reactions: Yes
- - When rejected: Re-send after confirmation
- - Edit applies to: N/A
- - Deletable by: User and sender
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: If capture is enabled
- When the user submits this message in a 1:1, group or distribution list
- conversation:
- 1. Let `location` be the provided location with the following properties:
- - `latitude` being a WGS-84 string,
- - `longitude` being a WGS-84 string,
- - `accuracy` being a floating point number or undefined,
- - `address` being an address of a point of interest or undefined,
- - `name` being a name for the point of interest or undefined,
- 2. If `location.name` is defined but `location.address` is not defined,
- discard `location`, log an error and abort these steps.¹
- 3. If the UTF-8 encoded bytes of `location.address` or `location.name`
- exceed 512 bytes, discard `location`, log an error and abort these
- steps.¹
- 4. Run the _Messages Submit Steps_ with `messages` set from `location`
- (for a group, wrapped by
- [`group-member-container`](ref:e2e.group-member-container)).
- ¹: The UI should prevent submission in these cases.
- When reflected from another device as an incoming or outgoing 1:1
- message:
- 1. Add the message to the associated 1:1 conversation.
- When receiving this message as a 1:1 message:
- 1. Add the message to the associated 1:1 conversation.
- When reflected from another device as an incoming or outgoing group
- message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Add the message to the associated group conversation.
- When receiving this message as a group message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
- message has been discarded, abort these steps.
- 2. Add the message to the associated group conversation.
- fields:
- - _doc: |-
- Location coordinates and meta information encoded in comma- and
- line-separated UTF-8:
- <latitude>,<longitude>[,<accuracy>]
- or
- <latitude>,<longitude>[,<accuracy>]
- <address>
- or
- <latitude>,<longitude>[,<accuracy>]
- <name>
- <address>
- Values:
- - `latitude` and `longitude` are the geographic coordinates
- represented in a WGS-84 string.
- - `accuracy` is the accuracy in meters represented by a floating
- point number formatted as a string.
- - `address` is a full address. If it contains multiple lines, each
- line feed must be escaped (literal `\n`).
- - `name` is the name of a point of interest.
- _Latitude_ and _longitude_ must always be present while _accuracy_
- is optional and should only be provided when the current location of
- the device is being sent. These values are comma-separated.
- Following values are optional and separated by line feeds (`\n`).
- This may be either:
- - a single line containing the _address_ representing the closest
- address matching the coordinates, or
- - two lines containing a point of interest _name_ and _address_
- (which means that the coordinates refer to the point of interest).
- name: location
- type: b*
- deprecated-audio:
- _group: Conversation Messages
- _doc: |-
- An audio message.
- Note: This message is deprecated and may be phased out eventually. When
- sending audio, use the [`file`](ref:e2e.file) message with the
- rendering type `0x01` (media).
- **Properties (1:1)**:
- - Kind: 1:1
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: Yes
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: N/A
- - _Sent_ update: Yes
- - Delivery receipts: Yes
- - Reactions: Yes
- - When rejected: N/A (deprecated message is not being sent)
- - Edit applies to: N/A
- - Deletable by: User and sender
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- **Properties (Group)**:
- - Kind: Group
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: N/A
- - _Sent_ update: Yes
- - Delivery receipts: N/A
- - Reactions: Yes
- - When rejected: N/A (deprecated message is not being sent)
- - Edit applies to: N/A
- - Deletable by: User and sender
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: If capture is enabled
- The audio is uploaded to the blob server and encrypted by:
- XSalsa20-Poly1305(key=<deprecated-audio.key>, nonce=00..01)
- This message is deprecated and may no longer be submitted.
- When reflected from another device as an incoming 1:1 message:
- 1. Run the _Common Deprecated Audio Receive Steps_.
- When receiving this message as a 1:1 message:
- 1. Run the _Common Deprecated Audio Receive Steps_.
- When reflected from another device as an incoming group message (wrapped
- by [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the _Common Deprecated Audio Receive Steps_.
- When receiving this message as a group message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
- message has been discarded, abort these steps.
- 2. Run the _Common Deprecated Audio Receive Steps_.
- The following steps are defined as the _Common Deprecated Audio Receive
- Steps_:
- 1. Add the message to the associated conversation.
- 2. If this message is eligible for auto-download, schedule downloading the
- audio data from the blob server. Only request the blob to be removed if
- the associated conversation is a 1:1 conversation.
- fields:
- - _doc: |-
- Audio duration in seconds.
- name: duration
- type: u16-le
- - _doc: |-
- Blob ID to obtain the audio data.
- name: audio-blob-id
- type: *blob-id
- - _doc: |-
- Audio size in bytes.
- name: audio-size
- type: u32-le
- - _doc: |-
- Random symmetric key used to encrypt the audio data.
- name: key
- type: *key
- deprecated-video:
- _group: Conversation Messages
- _doc: |-
- A video message.
- Note: This message is deprecated and may be phased out eventually. When
- sending video, use the [`file`](ref:e2e.file) message with the
- rendering type `0x01` (media).
- **Properties (1:1)**:
- - Kind: 1:1
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: Yes
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: N/A
- - _Sent_ update: Yes
- - Delivery receipts: Yes
- - Reactions: Yes
- - When rejected: N/A (deprecated message is not being sent)
- - Edit applies to: N/A
- - Deletable by: User and sender
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- **Properties (Group)**:
- - Kind: Group
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: N/A
- - _Sent_ update: Yes
- - Delivery receipts: N/A
- - Reactions: Yes
- - When rejected: N/A (deprecated message is not being sent)
- - Edit applies to: N/A
- - Deletable by: User and sender
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: If capture is enabled
- The video is uploaded to the blob server and encrypted by:
- XSalsa20-Poly1305(key=<deprecated-video.key>, nonce=00..01)
- The thumbnail must be in JPEG format, is uploaded to the blob server
- and encrypted by:
- XSalsa20-Poly1305(key=<deprecated-video.key>, nonce=00..02)
- This message is deprecated and may no longer be submitted.
- When reflected from another device as an incoming 1:1 message:
- 1. Run the _Common Deprecated Audio Receive Steps_.
- When receiving this message as a 1:1 message:
- 1. Run the _Common Deprecated Audio Receive Steps_.
- When reflected from another device as an incoming group message (wrapped
- by [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the _Common Deprecated Audio Receive Steps_.
- When receiving this message as a group message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
- message has been discarded, abort these steps.
- 2. Run the _Common Deprecated Audio Receive Steps_.
- The following steps are defined as the _Common Deprecated Video Receive
- Steps_:
- 1. Add the message to the associated conversation.
- 2. If this message is eligible for auto-download, schedule downloading the
- video data from the blob server. Only request the blob to be removed if
- the associated conversation is a 1:1 conversation.
- fields:
- - _doc: |-
- Video duration in seconds.
- name: duration
- type: u16-le
- - _doc: |-
- Blob ID to obtain the video data.
- name: video-blob-id
- type: *blob-id
- - _doc: |-
- Video size in bytes.
- name: video-size
- type: u32-le
- - _doc: |-
- Blob ID to obtain the thumbnail in JPEG format.
- name: thumbnail-blob-id
- type: *blob-id
- - _doc: |-
- Thumbnail size in bytes.
- name: thumbnail-size
- type: u32-le
- - _doc: |-
- Random symmetric key used to encrypt the video and thumbnail data.
- name: key
- type: *key
- file:
- _group: Conversation Messages
- _doc: |-
- A file or media message.
- **Properties (1:1)**:
- - Kind: 1:1
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: Yes
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: Yes
- - Delivery receipts: Yes
- - Reactions: Yes
- - When rejected: Re-send after confirmation
- - Edit applies to: Caption
- - Deletable by: User and sender
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- **Properties (Group)**:
- - Kind: Group
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: Yes
- - Delivery receipts: N/A
- - Reactions: Yes
- - When rejected: Re-send after confirmation
- - Edit applies to: Caption
- - Deletable by: User and sender
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: If capture is enabled
- The file is uploaded to the blob server and encrypted by:
- XSalsa20-Poly1305(key=<file.key>, nonce=00..01)
- The thumbnail is uploaded to the blob server and encrypted by:
- XSalsa20-Poly1305(key=<file.key>, nonce=00..02)
- When the user submits this message in a 1:1, group or distribution list
- conversation:
- 1. Let `file` be all necessary properties to construct this message.
- 2. If the UTF-8 encoded bytes of `file.name` exceed 256 bytes, discard
- `file`, log an error and abort these steps.¹
- 3. If the UTF-8 encoded bytes of `file.caption` exceed 6000 bytes, discard
- `file`, log an error and abort these steps.¹
- 4. Run the _Messages Submit Steps_ with `messages` set from `file` (for a
- group, wrapped by
- [`group-member-container`](ref:e2e.group-member-container)).
- ¹: The UI should prevent submission in these cases.
- When reflected from another device as an incoming or outgoing 1:1
- message:
- 1. Run the _Common File Receive Steps_.
- When receiving this message as a 1:1 message:
- 1. Run the _Common File Receive Steps_.
- When reflected from another device as an incoming or outgoing group
- message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the _Common File Receive Steps_.
- When receiving this message as a group message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
- message has been discarded, abort these steps.
- 2. Run the _Common File Receive Steps_.
- The following steps are defined as the _Common File Receive Steps_:
- 1. Add the message to the associated conversation.
- 2. Schedule auto-downloading the thumbnail data from the blob server. Only
- request the blob to be removed if the associated conversation is a 1:1
- conversation.
- 3. If this message is eligible for auto-download, schedule downloading the
- file data from the blob server. Only request the blob to be removed if
- the associated conversation is a 1:1 conversation.
- fields:
- - _doc: |-
- UTF-8, JSON-encoded object with the following fields:
- - Rendering type (`'j'`):
- - `0`: Render as a file.
- - `1`: Render as media (e.g. an image, audio or video).
- - `2`: Render as a sticker (for transparent images).
- If this field is not set, fall back to the value of `'i'`. If no
- value could be determined or the rendering type is unassigned,
- assume `0`.
- - Deprecated (`'i'`): Set this to the integer `1` if the rendering
- type is `1` or `2`, otherwise set this to the integer `0`.
- - Encryption key (`'k'`): Random symmetric key used to encrypt the
- blobs (file and thumbnail data) in lowercase hex string.
- - File blob ID (`'b'`): Blob ID in lowercase hex string
- representation to obtain the file data.
- - File media type (`'m'`): The media type of the file.
- - File name (`'n'`): Optional filename of the file.
- - File size (`'s'`): File size in bytes.
- - Thumbnail Blob ID (`'t'`): Optional blob containing the thumbnail
- file data.
- - Thumbnail media type (`'p'`): Media type of the thumbnail.
- If not set, assume `image/jpeg`.
- - Caption (`'d'`): Optional caption text.
- - Correlation ID (`'c'`): Optional random 32 byte ASCII string to
- collocate multiple media files.
- - Metadata (`'x'`): An optional metadata object as defined below.
- Metadata object fields depend on the media type of the file. All
- fields are optional but recommended to set in order to determine the
- layout logic while the file is being downloaded. Once the file has
- been parsed, the parsed data supersedes this object.
- For images:
- - Width (`'w'`): The width as an integer in px.
- - Height (`'h'`): The height as an integer in px.
- - Animated (`'a'`): Set this to the boolean `true` if the image is
- animated (e.g. an animated GIF).
- For audio:
- - Duration (`'d'`): The duration as a float in seconds.
- For video:
- - Width (`'w'`): The width as an integer in px.
- - Height (`'h'`): The height as an integer in px.
- - Duration (`'d'`): The duration as a float in seconds.
- Note that the rendering logic depends on three key fields which
- should be set accordingly:
- - Media type,
- - Rendering type,
- - Animated flag in the metadata object.
- name: file
- type: b*
- poll-setup:
- _group: Conversation Messages
- _doc: |-
- Creates a new poll or finalises an existing poll.
- During the lifecycle of a poll, this message will be used exactly twice:
- Once to create the poll, and once to close it.
- **Properties (1:1)**:
- - Kind: 1:1
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: Yes
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: Yes
- - Delivery receipts: Yes
- - Reactions: Yes
- - When rejected: Re-send after confirmation
- - Edit applies to: N/A
- - Deletable by: User only (TODO(SE-383))
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- **Properties (Group)**:
- - Kind: Group
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: Yes
- - Exempt from blocking: Yes
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: Yes
- - Delivery receipts: N/A
- - Reactions: Yes
- - When rejected: Re-send after confirmation
- - Edit applies to: N/A
- - Deletable by: User only (TODO(SE-383))
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: If capture is enabled
- When the user submits this message in a 1:1 or group conversation:
- 1. Let `poll` be all necessary properties to construct this message.
- 2. If the UTF-8 encoded bytes of `poll.description` exceed 256 bytes,
- discard `poll`, log an error and abort these steps.¹
- 3. If the UTF-8 encoded bytes of any of the `poll.choices` exceeds 256
- bytes, discard `poll`, log an error and abort these steps.¹
- 4. JSON encode `poll` and let `encoded-poll` be the UTF-8 encoded result.
- 5. If `encoded-poll` exceeds 6000 bytes, discard `poll` (and
- `encoded-poll`), log an error and abort these steps.¹
- 6. Run the _Messages Submit Steps_ with `messages` set from
- `encoded-poll` (for a group, wrapped by
- [`group-member-container`](ref:e2e.group-member-container)).
- ¹: The UI should prevent submission in these cases.
- When reflected from another device as an incoming or outgoing 1:1
- message:
- 1. Run the _Common Poll Setup Receive Steps_.
- When receiving this message as a 1:1 message:
- 1. Run the _Common Poll Setup Receive Steps_.
- When reflected from another device as an incoming or outgoing group
- message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the _Common Poll Setup Receive Steps_.
- When receiving this message as a group message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
- message has been discarded, abort these steps.
- 2. Run the _Common Poll Setup Receive Steps_.
- The following steps are defined as the _Common Poll Setup Receive Steps_:
- 1. Let `state` be the _State_ field of the message. Let `participants` be
- the _Participants_ field of the message.
- 2. Look up the poll with the given ID within the conversation.
- 3. If no associated poll could be found:
- 1. If `state` is `1` (closed), discard the message and abort these steps.
- 2. Add the poll to the associated conversation with the provided
- fields of the message and abort these steps.
- 4. If the associated poll is closed, discard the message and abort these steps.
- 5. If `state` is `0` (open), discard the message and abort these steps.
- 6. Close the poll with the given `participants`, ignore any other fields
- of the message.
- fields:
- - _doc: |-
- Random unique (per creator within this conversation) ID of the poll.
- name: id
- type: *poll-id
- - _doc: |-
- UTF-8, JSON-encoded object with the following fields:
- - Description (`'d'`): A short description/topic string for the poll.
- - State (`'s'`):
- - `0`: Poll is _open_ for votes.
- - `1`: Poll has been _closed_.
- A state transition from _closed_ to _open_ is illegal and must be
- ignored by the receiving client.
- - Answer type (`'a'`):
- - `0`: Single choice poll.
- - `1`: Multiple choice poll.
- Any transition from one of the types to another is illegal and
- must be ignored by the receiving client.
- - Announce type (`'t'`):
- - `0`: Announce votes in form of the `poll-vote` message
- only to the creator of the ballot. Results are invisible until
- the creator closes the vote and reports the final results.
- - `1`: Announce votes in form of the `poll-vote` message to
- everyone in the conversation. Interim results are therefore
- visible to everyone.
- Any transition from one of the types to another is illegal and
- must be ignored by the receiving client.
- - Display mode (`'u'`):
- - `0`: List mode. List choices of all participants as presented
- by this message.
- - `1`: Summary mode. Only display the total amount of votes per
- choice and the user's vote (if any).
- If the field is not present, assume _list_ mode (`0`). Any
- transition from one of the modes to another is illegal and must be
- ignored by the receiving client.
- - Choices type (`'o'`, DEPRECATED): Always set this to the integer `0`.
- - Participants (`'p'`): A list of Threema IDs that participated in
- the poll (i.e. they cast a vote). This field must only be
- present if the poll is being _closed_. In display mode _summary_,
- this field should be an empty list and must be ignored by the
- receiver.
- - Choices (`'c'`): A list of choice objects as defined below.
- Choice object fields:
- - Choice ID (`'i'`): A per-poll unique ID of the choice in form of
- an integer. Used when casting a vote.
- - Description (`'n'`): Choice description in form of a string.
- - Sort key (`'o'`, DEPRECATED): Set this to the index of the choice
- object within the _choices_ list.
- - Participant votes (`'r'`): A list of votes for this choice in the
- same order as the `participants` (i.e. mapped by their associated
- index). The integer `0` indicates that the participant did not vote
- for this choice. Any integer value other than `0` indicates that the
- participant voted for this choice. Must be of same length as
- `participants`. This field must only be present if the poll is being
- _closed_. In display mode _summary_ this should be an empty list and
- must be ignored by the receiver.
- - Total amount of votes (`'t'`): The total amount of votes for this
- choice. This field must only be present if the poll is being
- _closed_. In display mode _normal_ this field should not be
- present and must be ignored by the receiver.
- name: poll
- type: b*
- poll-vote:
- _group: Conversation Messages
- _doc: |-
- Cast a vote on a poll.
- **Properties (1:1)**:
- - Kind: 1:1
- - Flags: None
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: No¹
- - Bump _last update_: No¹
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: No
- - Reactions: No
- - When rejected: N/A (ignored)
- - Edit applies to: N/A (can just send another `poll-vote`)
- - Deletable by: User only (TODO(SE-383))
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- **Properties (Group)**:
- - Kind: Group
- - Flags: None
- - User profile distribution: Yes
- - Exempt from blocking: Yes
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: No¹
- - Bump _last update_: No¹
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: N/A
- - Reactions: No
- - When rejected: N/A (ignored)
- - Edit applies to: N/A (can just send another `poll-vote`)
- - Deletable by: User only (TODO(SE-383))
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: If capture is enabled
- ¹: A [`poll-vote`](ref:e2e.poll-vote) updates the poll state initiated by
- a corresponding [`poll-setup`](ref:e2e.poll-setup), meaning it does not
- produce a new visible message.
- When the user submits this message in a 1:1 or group conversation by
- casting a vote:
- 1. Let `vote` be all necessary properties to construct this message.
- 2. JSON encode `vote` and let `encoded-vote` be the UTF-8 encoded result.
- 3. If `encoded-vote` exceeds 6000 bytes, discard `vote` (and
- `encoded-vote`), log an error and abort these steps.¹
- 4. Run the _Messages Submit Steps_ with `messages` set from
- `encoded-vote` (for a group, wrapped by
- [`group-member-container`](ref:e2e.group-member-container)).
- ¹: The UI should prevent submission in these cases.
- When reflected from another device as an incoming or outgoing 1:1
- message:
- 1. Run the _Common Poll Vote Receive Steps_.
- When receiving this message as a 1:1 message:
- 1. Run the _Common Poll Vote Receive Steps_.
- When reflected from another device as an incoming or outgoing group
- message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the _Common Poll Vote Receive Steps_.
- When receiving this message as a group message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
- message has been discarded, abort these steps.
- 2. Run the _Common Poll Vote Receive Steps_.
- The following steps are defined as the _Common Poll Vote Receive Steps_.
- 1. Look up the poll with the given ID within the conversation.
- 2. If no associated poll could be found or if the associated poll is
- closed, discard the message and abort these steps.
- 3. Update the poll with the provided choices of the sender.
- fields:
- - _doc: |-
- The Threema ID of the creator of the poll.
- name: creator-identity
- type: *identity
- - _doc: |-
- ID of the associated poll.
- name: poll-id
- type: *poll-id
- - _doc: |-
- UTF-8, JSON-encoded list containing one or more choice tuples. Each
- choice tuple contains the following two integer values:
- - Choice ID, referring to the Choice ID defined in the
- `poll-setup` message.
- - Selected:
- - `0`: The choice has not been selected.
- - `1`: The choice has been selected.
- Note: For protocol simplicity, a vote must always include all possible
- choices, whether or not they have been selected.
- name: choices
- type: b*
- call-offer:
- _group: Conversation Messages
- _doc: |-
- Initiates a call.
- **Properties**:
- - Kind: 1:1
- - Flags:
- - `0x01`: Send push notification.
- - `0x20`: Short-lived server queuing.
- - User profile distribution: Yes
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: Yes
- - Protect against replay: Yes
- - Unarchive: Yes
- - Bump _last update_: Yes
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: No
- - Reactions: No
- - When rejected: Abort call
- - Edit applies to: N/A
- - Deletable by: User only (TODO(SE-384))
- - Include in history: No
- - Send to Threema Gateway ID group creator: N/A
- [//]: # "When submit / receiving / reflected: TODO(SE-102)"
- fields:
- - _doc: |-
- UTF-8, JSON-encoded object with the following fields:
- - Call ID (`'callId'`): Random 32 bit unsigned integer greater than 0
- that uniquely identifies a call throughout its lifetime. Assume
- `0` if not set.
- - WebRTC Offer (`'offer'`): An offer object.
- - Feature negotiation (`'features'`): Optional Call Features object.
- Offer object fields:
- - WebRTC Offer SDP type (`'sdpType'`): Set this to `'offer'` and ignore
- offers with other types.
- - WebRTC Offer SDP (`'sdp'`): Opaque string containing the SDP.
- name: offer
- type: b*
- call-answer:
- _group: Conversation Messages
- _doc: |-
- Answer or reject a call.
- **Properties**:
- - Kind: 1:1
- - Flags:
- - `0x01`: Send push notification.
- - `0x20`: Short-lived server queuing.
- - User profile distribution: Only if accepted (`action`: `1`)
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: No¹
- - Bump _last update_: No¹
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: No
- - Reactions: No
- - When rejected: Abort call
- - Edit applies to: N/A
- - Deletable by: User only (TODO(SE-384))
- - Include in history: No
- - Send to Threema Gateway ID group creator: N/A
- ¹: A [`call-answer`](ref:e2e.call-answer) updates the call state initiated
- by a corresponding [`call-offer`](ref:e2e.call-offer), meaning it does not
- produce a new visible message.
- [//]: # "When submit / receiving / reflected: TODO(SE-102)"
- fields:
- - _doc: |-
- UTF-8, JSON-encoded object with the following fields:
- - Call ID (`'callId'`): Random 32 bit unsigned integer greater than 0
- that uniquely identifies a call throughout its lifetime. Assume
- `0` if not set.
- - Required action (`'action'`):
- - `0`: The call has been rejected and needs to be aborted.
- - `1`: The call has been accepted and a connection needs to be
- established.
- - Rejection reason (`'rejectReason'`): If the call has been rejected,
- this field contains a reject reason:
- - `0`: Generic or unspecified rejection.
- - `1`: The callee is busy (another call is active).
- - `2`: The callee did not accept the call in time.
- - `3`: The callee explicitly rejected the call.
- - `4`: The callee disabled calls.
- - `5`: The callee was called during an off-hour period.
- - WebRTC Answer (`'answer'`): An answer object.
- - Feature negotiation (`'features'`): Optional Call Features object.
- Answer object fields:
- - WebRTC Answer SDP type (`'sdpType'`): Set this to `'answer'` and ignore
- answers with other types.
- - WebRTC Answer SDP (`'sdp'`): Opaque string containing the SDP.
- name: answer
- type: b*
- call-ice-candidate:
- _group: Conversation Messages
- _doc: |-
- An ICE candidate for an ongoing call.
- **Properties**:
- - Kind: 1:1
- - Flags:
- - `0x01`: Send push notification.
- - `0x20`: Short-lived server queuing.
- - User profile distribution: No
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: No¹
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: Yes
- - Outgoing: No
- - _Sent_ update: No
- - Delivery receipts: No
- - Reactions: No
- - When rejected: N/A (ignored)
- - Edit applies to: N/A
- - Deletable by: N/A
- - Include in history: No
- - Send to Threema Gateway ID group creator: N/A
- ¹: This message does not trigger any kind of reaction and adding ICE
- candidates again has no ill-effect.
- [//]: # "When submit / receiving / reflected: TODO(SE-102)"
- fields:
- - _doc: |-
- UTF-8, JSON-encoded object with the following fields:
- - Call ID (`'callId'`): Random 32 bit unsigned integer greater than 0
- that uniquely identifies a call throughout its lifetime. Assume
- `0` if not set.
- - Deprecated (`'removed'`): Always set this to `false` and ignore
- messages with this field set to `true`.
- - WebRTC Candidates (`'candidates'`): An array of candidate objects.
- Candidate object fields:
- - WebRTC Candidate SDP (`'candidate'`): Opaque string containing the
- ICE candidate SDP.
- - WebRTC MID (`'sdpMid'`): Media stream identification string or
- `null`.
- - WebRTC Media Line Index (`'sdpMLineIndex'`): Media description
- line index integer or `null`.
- - WebRTC Username Fragment (`'ufrag'`): ICE username fragment or
- `null`.
- name: candidates
- type: b*
- call-hangup:
- _group: Conversation Messages
- _doc: |-
- Hang up a call.
- **Properties**:
- - Kind: 1:1
- - Flags:
- - `0x01`: Send push notification.
- - User profile distribution: No
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: If no corresponding `call-offer` can be found¹
- - Bump _last update_: If no corresponding `call-offer` can be found¹
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: No
- - Reactions: No
- - When rejected: N/A (ignored)
- - Edit applies to: N/A
- - Deletable by: User only (TODO(SE-384))
- - Include in history: No
- - Send to Threema Gateway ID group creator: N/A
- ¹: A [`call-hangup`](ref:e2e.call-hangup) usually updates the call state
- initiated by a corresponding [`call-offer`](ref:e2e.call-offer), meaning
- it does not produce a new visible message. However, the
- [`call-offer`](ref:e2e.call-offer) uses the _Short-lived server queuing_
- flag and therefore may be lost. In that case, the
- [`call-hangup`](ref:e2e.call-hangup) does create a visible _call missed_
- message.
- [//]: # "When submit / receiving / reflected: TODO(SE-102)"
- fields:
- - _doc: |-
- UTF-8, JSON-encoded object. If this field contains zero bytes, assume
- an empty object. Contains the following fields:
- - Call ID (`'callId'`): Random 32 bit unsigned integer greater than 0
- that uniquely identifies a call throughout its lifetime. Assume
- `0` if not set.
- name: hangup
- type: b*
- call-ringing:
- _group: Conversation Messages
- _doc: |-
- Sent by the callee to indicate that the call is ringing.
- **Properties**:
- - Kind: 1:1
- - Flags:
- - `0x01`: Send push notification.
- - `0x20`: Short-lived server queuing.
- - User profile distribution: No
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: No¹
- - Bump _last update_: No¹
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: No
- - Reactions: No
- - When rejected: Abort call
- - Edit applies to: N/A
- - Deletable by: N/A
- - Include in history: No
- - Send to Threema Gateway ID group creator: N/A
- ¹: A [`call-ringing`](ref:e2e.call-ringing) updates the call state
- initiated by a corresponding [`call-offer`](ref:e2e.call-offer), meaning
- it does not produce a new visible message.
- [//]: # "When submit / receiving / reflected: TODO(SE-102)"
- fields:
- - _doc: |-
- UTF-8, JSON-encoded object. If this field contains zero bytes, assume
- an empty object. Contains the following fields:
- - Call ID (`'callId'`): Random 32 bit unsigned integer greater than 0
- that uniquely identifies a call throughout its lifetime. Assume
- `0` if not set.
- name: hangup
- type: b*
- delivery-receipt:
- _group: Status Updates
- _doc: |-
- Confirms reception or delivers detailed status updates of a message.
- **Properties (1:1)**:
- - Kind: 1:1
- - Flags: None
- - User profile distribution: Only for reactions
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Only for reactions¹
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes²
- - _Sent_ update: No
- - Delivery receipts: No, that would be silly!
- - Reactions: No (also silly)
- - When rejected: N/A (ignored)
- - Edit applies to: N/A (can just send another `delivery-receipt`)
- - Deletable by: N/A
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- ¹: Repeating a status of type _received_ or _read_ has no ill-effects.
- ²: When the message is being _read_ and _read_ receipts are disabled, an
- `d2d.IncomingMessageUpdate` will be reflected instead.
- **Properties (Group)**:
- - Kind: Group
- - Flags: None
- - User profile distribution: Only for reactions
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Only for reactions¹
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes. When the message is being _read_ and _read_ receipts
- are disabled, reflect an `d2d.IncomingMessageUpdate` (since no
- `delivery-receipt` is sent in this case).
- - _Sent_ update: No
- - Delivery receipts: No, that would be silly!
- - Reactions: No (also silly)
- - When rejected: N/A (ignored)
- - Edit applies to: N/A (can just send another `delivery-receipt`)
- - Deletable by: N/A
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: If capture is enabled
- ¹: Repeating a status of type _received_ or _read_ has no ill-effects.
- When the user opens a 1:1 conversation or marks it as _read_ by another
- mechanism:
- 1. Let `message-ids` be all message IDs associated to messages of the
- conversation that require _automatic_ delivery receipts and which are
- not yet marked as _read_.
- 2. Split the provided `message-ids` into bundles of at most 875 message
- IDs and let `message-ids-bundles` be the result.¹
- 3. Run the _Messages Submit Steps_ with `messages` set to create one or
- more [`delivery-receipt`](ref:e2e.delivery-receipt)s from
- `message-ids-bundles` with status `0x02`.
- ¹: Each message ID has 8 bytes, divided by at most 7000 bytes.
- When reflected from another device as an incoming 1:1 message:
- 1. Run the _Common Incoming Delivery Receipt Steps_.
- When reflected from another device as an outgoing 1:1 message:
- 1. Run the _Common Outgoing Delivery Receipt Steps_.
- When receiving this message as a 1:1 message:
- 1. Run the _Common Incoming Delivery Receipt Steps_.
- When reflected from another device as an incoming group message (wrapped
- by [`group-member-container`](ref:e2e.group-member-container)):
- 1. If `status` is not `0x03` or `0x04` (i.e. not a reaction), log a
- notice, discard the message and abort these steps.
- 2. Run the _Common Incoming Delivery Receipt Steps_.
- When reflected from another device as an outgoing group message (wrapped
- by [`group-member-container`](ref:e2e.group-member-container)):
- 1. If `status` is not `0x03` or `0x04` (i.e. not a reaction), log a
- notice, discard the message and abort these steps.
- 2. Run the _Common Outgoing Delivery Receipt Steps_.
- When receiving this message as a group message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
- message has been discarded, abort these steps.
- 2. If `status` is not `0x03` or `0x04` (i.e. not a reaction), log a
- notice, discard the message and abort these steps.
- 3. Run the _Common Incoming Delivery Receipt Steps_.
- The following steps are defined as the _Common Incoming Delivery Receipt
- Steps_:
- 1. For each `message-id` of `message-ids`:
- 1. Lookup the associated message for `message-id` in the associated
- conversation and let `referred-message` be the result.
- 2. If `referred-message` is not defined, abort these sub-steps.
- 3. If the associated conversation is a 1:1 conversation and the
- original sender of `referred-message` is not the user, abort these
- sub-steps.
- 4. If `status` is `0x01` or `0x02` (i.e. a delivery receipt) and
- `referred-message` allows for delivery receipts (see the associated
- _Delivery receipts_ property), apply and replace the delivery
- receipt of the sender to `referred-message` with the associated
- timestamp set to the message's (the `delivery-receipt`'s)
- `created-at`.
- 5. If `status` is `0x03` or `0x04` (i.e. a reaction) and
- `referred-message` is reactable (see the associated _Reactions_
- property), apply and replace any existing reaction of the sender to
- `referred-message` with the reaction timestamp set to the message's
- (the `delivery-receipt`'s) `created-at`.¹
- The following steps are defined as the _Common Outgoing Delivery Receipt
- Steps_:
- 1. For each `message-id` of `message-ids`:
- 1. Lookup the associated message for `message-id` in the associated
- conversation and let `referred-message` be the result.
- 2. If `referred-message` is not defined, abort these sub-steps.
- 3. If the associated conversation is a 1:1 conversation and the
- original sender of `referred-message` is the user, abort these
- sub-steps.
- 4. If `status` is `0x01` or `0x02` (i.e. a delivery receipt) and
- `referred-message` allows for delivery receipts (see the associated
- _Delivery receipts_ property), apply and replace the delivery
- receipt of the user to `referred-message` with the associated
- timestamp set to the message's (the `delivery-receipt`'s)
- `created-at`.
- 5. If `status` is `0x03` or `0x04` (i.e. a reaction) and
- `referred-message` is reactable (see the associated _Reactions_
- property), apply and replace any existing reaction of the user to
- `referred-message` with the reaction timestamp set to the message's
- (the `delivery-receipt`'s) `created-at`.¹
- ¹: Note that the deprecated reactions transmitted by a `delivery-receipt`
- always replace **all existing reactions** of the respective sender,
- including new-style reactions transmitted by a `csp_e2e.Reaction` message.
- fields:
- - _doc: |-
- Message status:
- - `0x01`: Message was received.
- - `0x02`: Message was read.
- - `0x03`: **Deprecated** Maps to the 👍 emoji (`1F44D`).
- - `0x04`: **Deprecated** Maps to the 👎 emoji (`1F44E`).
- Note that only the `0x01` and `0x02` variants are considered true
- _delivery receipts_ whereas the deprecated `0x03` and `0x04` variants
- are considered _reactions_ (just like `csp_e2e.Reaction`).
- The following replacement logic is to be applied on a message's
- status when displayed:
- 1. `0x02` replaces groups listed below,
- 2. `0x01` replaces the unlisted _created_ status.
- name: status
- type: u8
- - _doc: |-
- One or more `message-id`s whose status should be updated.
- name: message-ids
- type: *message-ids
- typing-indicator:
- _group: Status Updates
- _doc: |-
- Indicates whether a contact is currently typing.
- **Properties**:
- - Kind: 1:1
- - Flags:
- - `0x02`: No server queuing.
- - `0x04`: No server acknowledgement.
- - User profile distribution: No
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: No¹
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: Yes
- - Outgoing: No
- - _Sent_ update: No
- - Delivery receipts: No
- - Reactions: No
- - When rejected: N/A (ignored)
- - Edit applies to: N/A
- - Deletable by: N/A
- - Include in history: No
- - Send to Threema Gateway ID group creator: N/A
- ¹: It is deemed acceptable if the _typing_ indicator in the UI is replayed
- since there is no further consequence.
- When the user is currently _typing_ while composing a **new**¹ message in
- an associated conversation:
- 1. Schedule a volatile task to run the _Bundled Messages Send Steps_ with
- the following properties:
- - `id` set to a random message ID,
- - `created-at` set to the current timestamp,
- - `receivers` set to the targeted receiver,
- - to construct this message with `is-typing` set to `1`.
- 2. Start a _user is typing_ timer in the conversation to rerun these
- steps in 10s.
- When the user stopped _typing_ while composing a message in an associated
- conversation, or when the user left the conversation view:
- 1. If no _user is typing_ timer is running for the conversation, abort
- these steps.
- 2. Stop the _user is typing_ timer of the conversation.
- 3. Schedule a volatile task to run the _Bundled Messages Send Steps_ with
- the following properties:
- - `id` set to a random message ID,
- - `created-at` set to the current timestamp,
- - `receivers` set to the targeted receiver,
- - to construct this message with `is-typing` set to `0`.
- When reflected from another device as an incoming message:
- 1. Run the _Common Typing Indicator Receive Steps_.
- When receiving this message:
- 1. Run the _Common Typing Indicator Receive Steps_.
- The following steps are defined as the _Common Typing Indicator Receive
- Steps_:
- 1. If `is-typing` is `1`, start a timer to display that the sender is
- typing in the associated conversation for the next 15s.
- 2. If `is-typing` is `0`, cancel any running timer displaying that the
- sender is typing in the associated conversation.
- ¹: Editing a message may not trigger _typing_.
- fields:
- - _doc: |-
- Set to `1` in case the contact is currently typing or `0` in
- case the contact stopped typing. Other values are invalid.
- name: is-typing
- type: u8
- set-profile-picture:
- _group: Contact and Group Control
- _doc: |-
- Set the profile picture of a contact or a group.
- **Properties (1:1)**:
- - Kind: 1:1
- - Flags: None
- - User profile distribution: No (obviously)
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: No
- - Reactions: No
- - When rejected: N/A (ignored)
- - Edit applies to: N/A (can just send another `set-profile-picture`)
- - Deletable by: N/A (can just send a `delete-profile-picture`)
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- **Properties (Group)**:
- - Kind: Group
- - Flags: None
- - User profile distribution: No (obviously)
- - Exempt from blocking: Yes
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: N/A
- - Reactions: No
- - When rejected: N/A¹
- - Edit applies to: N/A (can just send another `set-profile-picture`)
- - Deletable by: N/A (can just send a `delete-profile-picture`)
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- ¹: For the group creator it will be handled as if `group-sync-request` was
- received, re-sending the group profile picture state, implicitly triggered
- by FS `Reject` receive steps.
- The profile picture must be in JPEG format, is uploaded to the blob
- server and encrypted by:
- XSalsa20-Poly1305(key=<set-profile-picture.key>, nonce=00..01)
- When reflected from another device as an outgoing 1:1 message:
- 1. Update the most recently distributed profile picture cache for the
- contact to the enclosed blob ID.
- When receiving this message as a 1:1 message:
- 1. Download the picture from the blob server but do not request the blob
- to be removed. Store the profile picture.
- 2. Store the picture as the _contact-defined_ profile picture and run the
- _Contact Profile Picture Selection Steps_.
- When receiving this message as a group message (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container)):
- 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
- message has been discarded, abort these steps.
- 2. Download the picture from the blob server but do not request the blob
- to be removed. Let `profile-picture` be the result.
- 3. Let `group` be a snapshot of the current group state.
- 4. If `group.profile-picture` is defined and equals `profile-picture`
- (i.e. no changes), discard the message and abort these steps.
- 5. (MD) Run the following sub-steps:
- 1. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group does not exist or the group is marked as _left_, log
- a warning that a group sync race occurred, discard the message
- and abort these steps.
- 2. (MD) Let `group` be a snapshot of the current group state.
- 3. (MD) If `group.profile-picture` is defined and equals
- `profile-picture`, log a warning that a group sync race occurred.
- 4. (MD) Reflect a `GroupSync.Update` with `group` set to contain
- `profile_picture` set to `profile-picture.
- 5. (MD) Commit the transaction and await acknowledgement.
- 6. Store the profile picture and and apply it to the group.
- fields:
- - _doc: |-
- Blob ID to obtain the image data.
- name: picture-blob-id
- type: *blob-id
- - _doc: |-
- Profile picture size in bytes.
- name: picture-size
- type: u32-le
- - _doc: |-
- Random symmetric key used to encrypt the image data.
- name: key
- type: *key
- delete-profile-picture:
- _group: Contact and Group Control
- _doc: |-
- Delete the profile picture of a contact.
- **Properties (1:1)**:
- - Kind: 1:1
- - Flags: None
- - User profile distribution: No (obviously)
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: No
- - Reactions: No
- - When rejected: N/A (ignored)
- - Edit applies to: N/A (can just send another `delete-profile-picture`)
- - Deletable by: N/A
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- **Properties (Group)**:
- - Kind: Group
- - Flags: None
- - User profile distribution: No (obviously)
- - Exempt from blocking: Yes
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: N/A
- - Reactions: No
- - When rejected: N/A¹
- - Edit applies to: N/A (can just send another `delete-profile-picture`)
- - Deletable by: N/A
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- ¹: For the group creator it will be handled as if `group-sync-request` was
- received, re-sending the group profile picture state, implicitly triggered
- by FS `Reject` receive steps.
- When reflected from another device as an incoming 1:1 message:
- 1. Remove the _contact-defined_ profile picture and run the _Contact
- Profile Picture Selection Steps_.
- When reflected from another device as an outgoing 1:1 message:
- 1. Update the most recently distributed profile picture cache for the
- contact to a _remove_ mark with the reflected timestamp.
- When receiving this message as a 1:1 message:
- 1. Remove the _contact-defined_ profile picture and run the _Contact
- Profile Picture Selection Steps_.
- When receiving this message as a group message (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container)):
- 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
- message has been discarded, abort these steps.
- 2. Let `group` be a snapshot of the current group state.
- 3. If `group.profile-picture` is not defined (i.e. no change), discard the
- message and abort these steps.
- 4. (MD) Run the following sub-steps:
- 1. Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group does not exist or the group is marked as _left_, log
- a warning that a group sync race occurred, discard the message
- and abort these steps.
- 2. Let `group` be a snapshot of the current group state.
- 3. If `group.profile-picture` is not defined, log a warning that a
- group sync race occurred.
- 4. Reflect a `GroupSync.Update` with `group` set to contain
- `profile_picture` set to be removed.
- 5. Commit the transaction and await acknowledgement.
- 5. Remove the profile picture of the group.
- contact-request-profile-picture:
- _group: Contact and Group Control
- _doc: |-
- Request a contact's profile picture.
- Note that this message does not result in the profile picture being sent
- immediately in reply to this message. Instead, it will be sent the next
- time that contact sends a message to the user (if one is set, and if the
- user is eligible for receiving the profile picture).
- **Properties**:
- - Kind: 1:1
- - Flags: None
- - User profile distribution: No
- - Exempt from blocking: No
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: Yes
- - Outgoing: No
- - _Sent_ update: No
- - Delivery receipts: No
- - Reactions: No
- - When rejected: N/A (ignored)
- - Edit applies to: N/A
- - Deletable by: N/A
- - Include in history: No
- - Send to Threema Gateway ID group creator: N/A
- When reflected from another device as an incoming message:
- 1. Run the _Common Request Profile Picture Receive Steps_.
- When receiving this message:
- 1. Run the _Common Request Profile Picture Receive Steps_.
- The following steps are defined as the _Common Request Profile Picture
- Receive Steps_:
- 1. Purge the most recently distributed profile picture cache for the
- sender.
- group-setup:
- _group: Contact and Group Control
- _doc: |-
- Announces the group setup to all participants. The group creator is
- always a member of the group and must not be included in the member
- list.
- This is sent by the creator to create a new group, as well as update and
- disband an existing group. The group creator sends this message to all
- current (including those to be removed) and newly added group members.
- The group creator may also send this to a single receiver in special
- cases.
- Since the group creator is not allowed to leave the group, the only way
- for it to stop being a member is by sending a `group-setup` with an
- empty members list and thereby disbanding the group.
- **Properties**:
- - Kind: Group
- - Flags: None
- - User profile distribution: Yes
- - Exempt from blocking: See dedicated steps
- - Implicit _direct_ contact creation: Yes
- - Protect against replay: Yes
- - Unarchive: No¹
- - Bump _last update_: No²
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: N/A
- - Reactions: No
- - When rejected: N/A³
- - Edit applies to: N/A (can just send another `group-setup`)
- - Deletable by: N/A (can just send another `group-setup`)
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- ¹: A newly created group's conversation visibility is implicitly _normal_
- and therefore not _archived_. For the sake of simplicity and
- sender/receiver symmetry, further updates to the group should not alter
- the conversation visibility.
- ²: A newly created group is implicitly created with _last update_ set to
- the current timestamp. For the sake of simplicity and sender/receiver
- symmetry, no further updates to the group should bump _last update_.
- ³: For the group creator it will be handled as if `group-sync-request` was
- received, re-sending the group state, implicitly triggered by FS `Reject`
- receive steps.
- The following steps are the dedicated blocking exemption steps for this
- message as a group message (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container)):
- 1. Look up the group.
- 2. If the group could be found, return that the message passed the
- blocking check.
- 3. Run the _Identity Blocked Steps_ for the creator. If the result
- indicates that the creator is not blocked, return that the message
- passed the blocking check. Otherwise return that the message needs to
- be discarded.
- When receiving this message as a group message (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container)):
- 1. Let `members` be the given member list. Remove all duplicate entries
- from `members`. Remove the sender (creator) from `members` if present.
- 2. Look up the group.
- 3. If the group could be found:
- 1. Let `group` be a snapshot of the current group state.
- 2. If the group is marked as _left_ and `members` is empty (i.e. no
- change), discard the message and abort these steps.
- 3. If the group is not marked as _left_:
- 1. Let `current-members` be a copy of `group.members`.
- 2. Add the user to `current-members`.
- 3. If `current-members` equals `members` (i.e. no change), discard
- the message and abort these steps.
- 4. If `members` does not include the user:
- 1. If the group could not be found, discard the message and abort these
- steps.
- 2. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group does not exist or the group is marked as _left_,
- discard the message and abort these steps.
- 3. (MD) Reflect a `GroupSync.Update` with `group` set to contain the
- `user_state` set to `KICKED`.
- 4. (MD) Commit the transaction and await acknowledgement.
- 5. If the user is currently participating in a group call of this
- group, trigger leaving the call.
- 6. Mark the group as _left_.
- 7. Persist the previous member setup so that the group can be cloned.
- 8. Run the _Rejected Messages Refresh Steps_ for the group.
- 5. If `members` includes the user.
- 1. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the sender (creator) contact does not exist, log an error,
- discard the message and abort these steps.
- 2. Run the _Valid Contacts Lookup Steps_ for `members` and
- overwrite `members` with the result.
- 3. For each `contact-or-init` of `members`:
- 1. If `contact-or-init` indicates that the _contact is the user_,
- remove the entry from `members` and abort these sub-steps.
- 2. If `contact-or-init` indicates that the _contact is invalid_,
- remove the entry from `members`, log a warning and abort these
- sub-steps.
- 4. (MD) Let `pending-reflect-acks` be an empty list.
- 5. For each `contact-or-init` of `members`:
- 1. If `contact-or-init` is an existing contact, abort these
- sub-steps.
- 2. (MD) Reflect a `ContactSync.Create` with `contact` set from
- `contact-or-init` and the following additional properties:
- - `created_at` set to now,
- - `acquaintance_level` set to `GROUP`,
- - all policies and categories set to their defaults.
- 3. (MD) Add the pending reflect acknowledgement to
- `pending-reflect-acks`.
- 6. (MD) Await all `pending-reflect-acks`.
- 7. Let `added-members` be a copy of `members`.
- 8. Let `group` be a snapshot of the current group state or undefined
- if the group does not exist.
- 9. If `group` is not defined:
- 1. Let `removed-members` be an empty list.
- 2. (MD) Reflect a `GroupSync.Create` with `group` set to contain:
- - `group_identity`,
- - `created_at`,
- - `name` empty,
- - `user_state` set to `MEMBER`,
- - `profile_picture` empty,
- - `member_identities` from `members`,
- - all policies and categories set to their defaults.
- 10. If `group` is defined:
- 1. Remove all members from `added-members` that are in
- `group.members`.
- 2. Let `removed-members` be a copy of `group.members`.
- 3. Remove all members from `removed-members` that are in `members`.
- 4. (MD) Reflect a `GroupSync.Update` with `member_state_changes`
- constructed from `added-members` and `removed-members` and
- `group` set to contain the following additional properties:
- - `user_state` set to `MEMBER`,
- - `member_identities` from `members`.
- 11. (MD) Commit the transaction and await acknowledgement.
- 12. If the user is currently participating in a group call of this
- group, remove all `removed-members` participants from the group
- call (handle them as if they left the call) and unblock all pending
- group call flows for `added-members`.
- 13. Persist newly added contacts from `members`.
- 14. Persist the newly created group or the member changes to the group.
- If the group was previously marked as _left_, remove the _left_
- mark.
- 15. TODO(SE-510): Schedule fetching gateway-defined profile picture
- here for each newly added contact from `members`, if necessary.
- 16. If `added-members` or `removed-members` is not empty, run the
- _Rejected Messages Refresh Steps_ for the group.
- fields:
- - _doc: |-
- A set of Threema IDs defining group membership. The creator's
- Threema ID is always inferred and must not be included in this set.
- name: members
- type: *identities
- group-name:
- _group: Contact and Group Control
- _doc: |-
- Name (or rename) a group. Sent to all group members when the group is
- being created for the first time or the group is being renamed. May also
- be sent to a single receiver as a response to a
- [`group-sync-request`](ref:e2e.group-sync-request) message.
- **Properties**:
- - Kind: Group
- - Flags: None
- - User profile distribution: No
- - Exempt from blocking: Yes
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: N/A
- - Reactions: No
- - When rejected: N/A¹
- - Edit applies to: N/A (can just send another `group-name`)
- - Deletable by: N/A (can just send an empty name)
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: N/A
- ¹: For the group creator it will be handled as if `group-sync-request` was
- received, re-sending the group name, implicitly triggered by FS `Reject`
- receive steps.
- When receiving this message as a group message (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container)):
- 1. Run the [_Common Group Receive Steps_](ref:e2e#receiving). If the
- message has been discarded, abort these steps.
- 2. Let `group` be a snapshot of the current group state.
- 3. If `group.name` equals `name` (i.e. no change), discard the message and
- abort these steps.
- 4. (MD) Run the following sub-steps:
- 1. Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group does not exist or the group is marked as _left_, log
- a warning that a group sync race occurred, discard the message
- and abort these steps.
- 2. Let `group` be a snapshot of the current group state.
- 3. If `group.name` equals `name`, log a warning that a group sync race
- occurred.
- 4. Reflect a `GroupSync.Update` with `group` set to contain
- `name` set to `name`.
- 5. Commit the transaction and await acknowledgement.
- 5. Update the group's name with `name`.
- fields:
- - _doc: |-
- UTF-8 encoded string containing the group's name.
- name: name
- type: b*
- group-leave:
- _group: Contact and Group Control
- _doc: |-
- Sent by a group member...
- * that is leaving the group. The message is sent to all other group
- members and the creator.
- * in direct reply to a group message for a group that it has marked as
- left.
- Note: The group creator is not allowed to leave the group.
- **Properties**:
- - Kind: Group
- - Flags: None
- - User profile distribution: No
- - Exempt from blocking: Yes
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: N/A
- - Reactions: No
- - When rejected: N/A¹
- - Edit applies to: N/A
- - Deletable by: N/A
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: Yes
- ¹: Re-send of `group-leave` implicitly triggered by FS `Reject` receive
- steps due to _Common Group Receive Steps_ invocation.
- When receiving this message as a group message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. If the sender is the creator of the group, log a warning, discard the
- message and abort these steps.
- 2. Look up the group.
- 3. If the group could not be found or is marked as _left_:
- 1. If the user is the creator of the group (as alleged by the
- message), discard the message and abort these steps.
- 2. Run the _Identity Blocked Steps_ for the creator of the group. If
- the result indicates that the creator is blocked, discard the
- message and abort these steps.
- 3. Run the _Group Sync Request Steps_ for the group, discard the
- message and abort these steps.
- 4. Let `group` be a snapshot of the current group state.
- 5. If `group.members` does not include the sender, discard the message and
- abort these steps.
- 6. (MD) Run the following sub-steps:
- 1. Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group does not exist or the group is marked as _left_, log
- a warning that a group sync race occurred, discard the message
- and abort these steps.
- 2. Let `group` be a snapshot of the current group state.
- 3. If `group.members` does not include the sender, log a warning that a
- group sync race occurred.
- 4. Let `updated-members` be a copy of `group.members`.
- 5. Remove the sender from `updated-members`.
- 6. Reflect a `GroupSync.Update` with `member_state_changes`
- set to the single entry of the sender leaving and `group` set to
- contain `member_identities` set from `updated-members`.
- 7. Commit the transaction and await acknowledgement.
- 7. If the user is currently participating in a group call of this group,
- remove the sender from the group call (handle it as if the sender left
- the call).
- 8. Remove the sender from the group.
- 9. Run the _Rejected Messages Refresh Steps_ for the group.
- group-sync-request:
- _group: Contact and Group Control
- _doc: |-
- Sent by a group member (or a device assuming to be part of the group) to
- the group creator.
- **Properties**:
- - Kind: Group
- - Flags: None
- - User profile distribution: No
- - Exempt from blocking: Yes
- - Implicit _direct_ contact creation: No
- - Protect against replay: Yes
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: Yes
- - Outgoing: Yes
- - _Sent_ update: No
- - Delivery receipts: N/A
- - Reactions: No
- - When rejected: N/A¹
- - Edit applies to: N/A
- - Deletable by: N/A
- - Include in history: Yes
- - Send to Threema Gateway ID group creator: Yes
- ¹: Implicitly ignored by FS `Reject` receive steps.
- The following steps are defined as the _Group Sync Request Steps_:
- 1. If the user is the creator of the group, log an error and abort these
- steps.
- 2. If the group is marked as _recently resynced_ for the user, log a
- notice and abort these steps.¹
- 3. Run the _Bundled Messages Send Steps_ with the following properties:
- - `id` set to a random message ID,
- - `created-at` set to the current timestamp,
- - `receivers` set to the creator of the group,
- - to construct this message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container))
- 4. Mark the group as _recently resynced_ for the user for 1h.
- When receiving this message as a group message (wrapped by
- [`group-member-container`](ref:e2e.group-member-container)):
- 1. Look up the group. If the group could not be found, discard the message
- and abort these steps.
- 2. If the user is not the creator of the group, discard the message and
- abort these steps.
- 3. If the group is marked as _recently resynced_ for the sender, log a
- notice, discard the message and abort these steps.¹
- 4. (MD) Begin a transaction with scope `GROUP_SYNC` and the following
- precondition:
- 1. If the group does not exist, log a warning that a group sync race
- occurred, discard the message and abort these steps.
- 5. If the group is marked as _left_ or the sender is not a member of the
- group, run the _Bundled Messages Send Steps_ with the following
- properties:
- - `id` set to a random message ID,
- - `created-at` set to the current timestamp,
- - `receivers` set to the sender,
- - to construct a [`group-setup`](ref:e2e.group-setup) (wrapped by
- [`group-creator-container`](ref:e2e.group-creator-container)) with an
- empty members set.
- 6. If the group is not marked as _left_ and the sender is a member of the
- group, run the _Active Group State Resync Steps_ with four random message
- IDs and `target-members` set to the sender.
- 7. (MD) Commit the transaction and await acknowledgement.
- ¹: This is a precaution since a `group-sync-request` is automatically
- triggered and creates an automatic response. This can easily lead to
- message loops. Limiting `group-sync-request`s to once an hour per group
- per sender/receiver breaks a potential infinite loop.
- web-session-resume:
- _group: Push Control
- _doc: |-
- A control message from Threema Web, requesting a session to be resumed.
- **Properties (1:1)**:
- - Kind: 1:1
- - Flags:
- - `0x20`: Short-lived server queuing.
- - User profile distribution: N/A (not sent by apps)
- - Exempt from blocking: Yes
- - Implicit _direct_ contact creation: N/A (blocking is circumvented)
- - Protect against replay: Yes
- - Unarchive: No
- - Bump _last update_: No
- - Reflect:
- - Incoming: No
- - Outgoing: No
- - _Sent_ update: No
- - Delivery receipts: N/A
- - Reactions: N/A
- - When rejected: N/A (not sent by clients)
- - Edit applies to: N/A
- - Deletable by: N/A
- - Include in history: No
- - Send to Threema Gateway ID group creator: N/A
- When receiving this message:
- 1. If the sender is not `*3MAPUSH`, discard the message and abort these
- steps.
- 2. Lookup the web client session associated to `wcs` and attempt to resume
- it.
- fields:
- - _doc: |-
- UTF-8, JSON-encoded object with the following fields:
- - Webclient session (`'wcs'`): SHA256 hash (hex encoded) of the
- public permanent key of the session initiator, string.
- - Affiliation ID (`'wca'`): An optional identifier for affiliating
- consecutive pushes, `string` or `null`.
- - Affiliation ID (`'wct'`): Unix epoch timestamp of the request in
- seconds, `i64`.
- - Protocol version (`'wcv'`): Version of the Threema Web protocol,
- `u16`.
- All fields must be part of the JSON object, even if their values are
- nullable.
- name: push-payload
- type: b*
- # Parsed struct namespaces (mapped into separate files)
- namespaces:
- index: *index
- handshake: *handshake
- payload: *payload
- e2e: *e2e
|