commit 53d2b1674fe55192d77e6d1ca3c59a27f07b4d40 Author: Fabian Schlenz Date: Wed Jun 29 10:59:33 2016 +0200 First commit: Just a collection of library sources from Github. Compiles, but doesn't work. diff --git a/additional/schema-v51.json b/additional/schema-v51.json new file mode 100644 index 0000000..a128b8f --- /dev/null +++ b/additional/schema-v51.json @@ -0,0 +1 @@ +{"constructors":[{"id":"-1132882121","predicate":"boolFalse","params":[],"type":"Bool"},{"id":"-1720552011","predicate":"boolTrue","params":[],"type":"Bool"},{"id":"1072550713","predicate":"true","params":[],"type":"True"},{"id":"481674261","predicate":"vector","params":[],"type":"Vector t"},{"id":"-994444869","predicate":"error","params":[{"name":"code","type":"int"},{"name":"text","type":"string"}],"type":"Error"},{"id":"1450380236","predicate":"null","params":[],"type":"Null"},{"id":"2134579434","predicate":"inputPeerEmpty","params":[],"type":"InputPeer"},{"id":"2107670217","predicate":"inputPeerSelf","params":[],"type":"InputPeer"},{"id":"396093539","predicate":"inputPeerChat","params":[{"name":"chat_id","type":"int"}],"type":"InputPeer"},{"id":"-1182234929","predicate":"inputUserEmpty","params":[],"type":"InputUser"},{"id":"-138301121","predicate":"inputUserSelf","params":[],"type":"InputUser"},{"id":"-208488460","predicate":"inputPhoneContact","params":[{"name":"client_id","type":"long"},{"name":"phone","type":"string"},{"name":"first_name","type":"string"},{"name":"last_name","type":"string"}],"type":"InputContact"},{"id":"-181407105","predicate":"inputFile","params":[{"name":"id","type":"long"},{"name":"parts","type":"int"},{"name":"name","type":"string"},{"name":"md5_checksum","type":"string"}],"type":"InputFile"},{"id":"-1771768449","predicate":"inputMediaEmpty","params":[],"type":"InputMedia"},{"id":"-139464256","predicate":"inputMediaUploadedPhoto","params":[{"name":"file","type":"InputFile"},{"name":"caption","type":"string"}],"type":"InputMedia"},{"id":"-373312269","predicate":"inputMediaPhoto","params":[{"name":"id","type":"InputPhoto"},{"name":"caption","type":"string"}],"type":"InputMedia"},{"id":"-104578748","predicate":"inputMediaGeoPoint","params":[{"name":"geo_point","type":"InputGeoPoint"}],"type":"InputMedia"},{"id":"-1494984313","predicate":"inputMediaContact","params":[{"name":"phone_number","type":"string"},{"name":"first_name","type":"string"},{"name":"last_name","type":"string"}],"type":"InputMedia"},{"id":"480546647","predicate":"inputChatPhotoEmpty","params":[],"type":"InputChatPhoto"},{"id":"-1809496270","predicate":"inputChatUploadedPhoto","params":[{"name":"file","type":"InputFile"},{"name":"crop","type":"InputPhotoCrop"}],"type":"InputChatPhoto"},{"id":"-1293828344","predicate":"inputChatPhoto","params":[{"name":"id","type":"InputPhoto"},{"name":"crop","type":"InputPhotoCrop"}],"type":"InputChatPhoto"},{"id":"-457104426","predicate":"inputGeoPointEmpty","params":[],"type":"InputGeoPoint"},{"id":"-206066487","predicate":"inputGeoPoint","params":[{"name":"lat","type":"double"},{"name":"long","type":"double"}],"type":"InputGeoPoint"},{"id":"483901197","predicate":"inputPhotoEmpty","params":[],"type":"InputPhoto"},{"id":"-74070332","predicate":"inputPhoto","params":[{"name":"id","type":"long"},{"name":"access_hash","type":"long"}],"type":"InputPhoto"},{"id":"342061462","predicate":"inputFileLocation","params":[{"name":"volume_id","type":"long"},{"name":"local_id","type":"int"},{"name":"secret","type":"long"}],"type":"InputFileLocation"},{"id":"-1377390588","predicate":"inputPhotoCropAuto","params":[],"type":"InputPhotoCrop"},{"id":"-644787419","predicate":"inputPhotoCrop","params":[{"name":"crop_left","type":"double"},{"name":"crop_top","type":"double"},{"name":"crop_width","type":"double"}],"type":"InputPhotoCrop"},{"id":"1996904104","predicate":"inputAppEvent","params":[{"name":"time","type":"double"},{"name":"type","type":"string"},{"name":"peer","type":"long"},{"name":"data","type":"string"}],"type":"InputAppEvent"},{"id":"-1649296275","predicate":"peerUser","params":[{"name":"user_id","type":"int"}],"type":"Peer"},{"id":"-1160714821","predicate":"peerChat","params":[{"name":"chat_id","type":"int"}],"type":"Peer"},{"id":"-1432995067","predicate":"storage.fileUnknown","params":[],"type":"storage.FileType"},{"id":"8322574","predicate":"storage.fileJpeg","params":[],"type":"storage.FileType"},{"id":"-891180321","predicate":"storage.fileGif","params":[],"type":"storage.FileType"},{"id":"172975040","predicate":"storage.filePng","params":[],"type":"storage.FileType"},{"id":"-1373745011","predicate":"storage.filePdf","params":[],"type":"storage.FileType"},{"id":"1384777335","predicate":"storage.fileMp3","params":[],"type":"storage.FileType"},{"id":"1258941372","predicate":"storage.fileMov","params":[],"type":"storage.FileType"},{"id":"1086091090","predicate":"storage.filePartial","params":[],"type":"storage.FileType"},{"id":"-1278304028","predicate":"storage.fileMp4","params":[],"type":"storage.FileType"},{"id":"276907596","predicate":"storage.fileWebp","params":[],"type":"storage.FileType"},{"id":"2086234950","predicate":"fileLocationUnavailable","params":[{"name":"volume_id","type":"long"},{"name":"local_id","type":"int"},{"name":"secret","type":"long"}],"type":"FileLocation"},{"id":"1406570614","predicate":"fileLocation","params":[{"name":"dc_id","type":"int"},{"name":"volume_id","type":"long"},{"name":"local_id","type":"int"},{"name":"secret","type":"long"}],"type":"FileLocation"},{"id":"537022650","predicate":"userEmpty","params":[{"name":"id","type":"int"}],"type":"User"},{"id":"1326562017","predicate":"userProfilePhotoEmpty","params":[],"type":"UserProfilePhoto"},{"id":"-715532088","predicate":"userProfilePhoto","params":[{"name":"photo_id","type":"long"},{"name":"photo_small","type":"FileLocation"},{"name":"photo_big","type":"FileLocation"}],"type":"UserProfilePhoto"},{"id":"164646985","predicate":"userStatusEmpty","params":[],"type":"UserStatus"},{"id":"-306628279","predicate":"userStatusOnline","params":[{"name":"expires","type":"int"}],"type":"UserStatus"},{"id":"9203775","predicate":"userStatusOffline","params":[{"name":"was_online","type":"int"}],"type":"UserStatus"},{"id":"-1683826688","predicate":"chatEmpty","params":[{"name":"id","type":"int"}],"type":"Chat"},{"id":"-652419756","predicate":"chat","params":[{"name":"flags","type":"#"},{"name":"creator","type":"flags.0?true"},{"name":"kicked","type":"flags.1?true"},{"name":"left","type":"flags.2?true"},{"name":"admins_enabled","type":"flags.3?true"},{"name":"admin","type":"flags.4?true"},{"name":"deactivated","type":"flags.5?true"},{"name":"id","type":"int"},{"name":"title","type":"string"},{"name":"photo","type":"ChatPhoto"},{"name":"participants_count","type":"int"},{"name":"date","type":"int"},{"name":"version","type":"int"},{"name":"migrated_to","type":"flags.6?InputChannel"}],"type":"Chat"},{"id":"120753115","predicate":"chatForbidden","params":[{"name":"id","type":"int"},{"name":"title","type":"string"}],"type":"Chat"},{"id":"771925524","predicate":"chatFull","params":[{"name":"id","type":"int"},{"name":"participants","type":"ChatParticipants"},{"name":"chat_photo","type":"Photo"},{"name":"notify_settings","type":"PeerNotifySettings"},{"name":"exported_invite","type":"ExportedChatInvite"},{"name":"bot_info","type":"Vector"}],"type":"ChatFull"},{"id":"-925415106","predicate":"chatParticipant","params":[{"name":"user_id","type":"int"},{"name":"inviter_id","type":"int"},{"name":"date","type":"int"}],"type":"ChatParticipant"},{"id":"-57668565","predicate":"chatParticipantsForbidden","params":[{"name":"flags","type":"#"},{"name":"chat_id","type":"int"},{"name":"self_participant","type":"flags.0?ChatParticipant"}],"type":"ChatParticipants"},{"id":"1061556205","predicate":"chatParticipants","params":[{"name":"chat_id","type":"int"},{"name":"participants","type":"Vector"},{"name":"version","type":"int"}],"type":"ChatParticipants"},{"id":"935395612","predicate":"chatPhotoEmpty","params":[],"type":"ChatPhoto"},{"id":"1632839530","predicate":"chatPhoto","params":[{"name":"photo_small","type":"FileLocation"},{"name":"photo_big","type":"FileLocation"}],"type":"ChatPhoto"},{"id":"-2082087340","predicate":"messageEmpty","params":[{"name":"id","type":"int"}],"type":"Message"},{"id":"-1063525281","predicate":"message","params":[{"name":"flags","type":"#"},{"name":"unread","type":"flags.0?true"},{"name":"out","type":"flags.1?true"},{"name":"mentioned","type":"flags.4?true"},{"name":"media_unread","type":"flags.5?true"},{"name":"silent","type":"flags.13?true"},{"name":"post","type":"flags.14?true"},{"name":"id","type":"int"},{"name":"from_id","type":"flags.8?int"},{"name":"to_id","type":"Peer"},{"name":"fwd_from","type":"flags.2?MessageFwdHeader"},{"name":"via_bot_id","type":"flags.11?int"},{"name":"reply_to_msg_id","type":"flags.3?int"},{"name":"date","type":"int"},{"name":"message","type":"string"},{"name":"media","type":"flags.9?MessageMedia"},{"name":"reply_markup","type":"flags.6?ReplyMarkup"},{"name":"entities","type":"flags.7?Vector"},{"name":"views","type":"flags.10?int"},{"name":"edit_date","type":"flags.15?int"}],"type":"Message"},{"id":"-1642487306","predicate":"messageService","params":[{"name":"flags","type":"#"},{"name":"unread","type":"flags.0?true"},{"name":"out","type":"flags.1?true"},{"name":"mentioned","type":"flags.4?true"},{"name":"media_unread","type":"flags.5?true"},{"name":"silent","type":"flags.13?true"},{"name":"post","type":"flags.14?true"},{"name":"id","type":"int"},{"name":"from_id","type":"flags.8?int"},{"name":"to_id","type":"Peer"},{"name":"reply_to_msg_id","type":"flags.3?int"},{"name":"date","type":"int"},{"name":"action","type":"MessageAction"}],"type":"Message"},{"id":"1038967584","predicate":"messageMediaEmpty","params":[],"type":"MessageMedia"},{"id":"1032643901","predicate":"messageMediaPhoto","params":[{"name":"photo","type":"Photo"},{"name":"caption","type":"string"}],"type":"MessageMedia"},{"id":"1457575028","predicate":"messageMediaGeo","params":[{"name":"geo","type":"GeoPoint"}],"type":"MessageMedia"},{"id":"1585262393","predicate":"messageMediaContact","params":[{"name":"phone_number","type":"string"},{"name":"first_name","type":"string"},{"name":"last_name","type":"string"},{"name":"user_id","type":"int"}],"type":"MessageMedia"},{"id":"-1618676578","predicate":"messageMediaUnsupported","params":[],"type":"MessageMedia"},{"id":"-1230047312","predicate":"messageActionEmpty","params":[],"type":"MessageAction"},{"id":"-1503425638","predicate":"messageActionChatCreate","params":[{"name":"title","type":"string"},{"name":"users","type":"Vector"}],"type":"MessageAction"},{"id":"-1247687078","predicate":"messageActionChatEditTitle","params":[{"name":"title","type":"string"}],"type":"MessageAction"},{"id":"2144015272","predicate":"messageActionChatEditPhoto","params":[{"name":"photo","type":"Photo"}],"type":"MessageAction"},{"id":"-1780220945","predicate":"messageActionChatDeletePhoto","params":[],"type":"MessageAction"},{"id":"1217033015","predicate":"messageActionChatAddUser","params":[{"name":"users","type":"Vector"}],"type":"MessageAction"},{"id":"-1297179892","predicate":"messageActionChatDeleteUser","params":[{"name":"user_id","type":"int"}],"type":"MessageAction"},{"id":"-1042448310","predicate":"dialog","params":[{"name":"peer","type":"Peer"},{"name":"top_message","type":"int"},{"name":"read_inbox_max_id","type":"int"},{"name":"unread_count","type":"int"},{"name":"notify_settings","type":"PeerNotifySettings"}],"type":"Dialog"},{"id":"590459437","predicate":"photoEmpty","params":[{"name":"id","type":"long"}],"type":"Photo"},{"id":"-840088834","predicate":"photo","params":[{"name":"id","type":"long"},{"name":"access_hash","type":"long"},{"name":"date","type":"int"},{"name":"sizes","type":"Vector"}],"type":"Photo"},{"id":"236446268","predicate":"photoSizeEmpty","params":[{"name":"type","type":"string"}],"type":"PhotoSize"},{"id":"2009052699","predicate":"photoSize","params":[{"name":"type","type":"string"},{"name":"location","type":"FileLocation"},{"name":"w","type":"int"},{"name":"h","type":"int"},{"name":"size","type":"int"}],"type":"PhotoSize"},{"id":"-374917894","predicate":"photoCachedSize","params":[{"name":"type","type":"string"},{"name":"location","type":"FileLocation"},{"name":"w","type":"int"},{"name":"h","type":"int"},{"name":"bytes","type":"bytes"}],"type":"PhotoSize"},{"id":"286776671","predicate":"geoPointEmpty","params":[],"type":"GeoPoint"},{"id":"541710092","predicate":"geoPoint","params":[{"name":"long","type":"double"},{"name":"lat","type":"double"}],"type":"GeoPoint"},{"id":"-2128698738","predicate":"auth.checkedPhone","params":[{"name":"phone_registered","type":"Bool"}],"type":"auth.CheckedPhone"},{"id":"1577067778","predicate":"auth.sentCode","params":[{"name":"flags","type":"#"},{"name":"phone_registered","type":"flags.0?true"},{"name":"type","type":"auth.SentCodeType"},{"name":"phone_code_hash","type":"string"},{"name":"next_type","type":"flags.1?auth.CodeType"},{"name":"timeout","type":"flags.2?int"}],"type":"auth.SentCode"},{"id":"-16553231","predicate":"auth.authorization","params":[{"name":"user","type":"User"}],"type":"auth.Authorization"},{"id":"-543777747","predicate":"auth.exportedAuthorization","params":[{"name":"id","type":"int"},{"name":"bytes","type":"bytes"}],"type":"auth.ExportedAuthorization"},{"id":"-1195615476","predicate":"inputNotifyPeer","params":[{"name":"peer","type":"InputPeer"}],"type":"InputNotifyPeer"},{"id":"423314455","predicate":"inputNotifyUsers","params":[],"type":"InputNotifyPeer"},{"id":"1251338318","predicate":"inputNotifyChats","params":[],"type":"InputNotifyPeer"},{"id":"-1540769658","predicate":"inputNotifyAll","params":[],"type":"InputNotifyPeer"},{"id":"-265263912","predicate":"inputPeerNotifyEventsEmpty","params":[],"type":"InputPeerNotifyEvents"},{"id":"-395694988","predicate":"inputPeerNotifyEventsAll","params":[],"type":"InputPeerNotifyEvents"},{"id":"949182130","predicate":"inputPeerNotifySettings","params":[{"name":"flags","type":"#"},{"name":"show_previews","type":"flags.0?true"},{"name":"silent","type":"flags.1?true"},{"name":"mute_until","type":"int"},{"name":"sound","type":"string"}],"type":"InputPeerNotifySettings"},{"id":"-1378534221","predicate":"peerNotifyEventsEmpty","params":[],"type":"PeerNotifyEvents"},{"id":"1830677896","predicate":"peerNotifyEventsAll","params":[],"type":"PeerNotifyEvents"},{"id":"1889961234","predicate":"peerNotifySettingsEmpty","params":[],"type":"PeerNotifySettings"},{"id":"-1697798976","predicate":"peerNotifySettings","params":[{"name":"flags","type":"#"},{"name":"show_previews","type":"flags.0?true"},{"name":"silent","type":"flags.1?true"},{"name":"mute_until","type":"int"},{"name":"sound","type":"string"}],"type":"PeerNotifySettings"},{"id":"-2122045747","predicate":"peerSettings","params":[{"name":"flags","type":"#"},{"name":"report_spam","type":"flags.0?true"}],"type":"PeerSettings"},{"id":"-860866985","predicate":"wallPaper","params":[{"name":"id","type":"int"},{"name":"title","type":"string"},{"name":"sizes","type":"Vector"},{"name":"color","type":"int"}],"type":"WallPaper"},{"id":"1490799288","predicate":"inputReportReasonSpam","params":[],"type":"ReportReason"},{"id":"505595789","predicate":"inputReportReasonViolence","params":[],"type":"ReportReason"},{"id":"777640226","predicate":"inputReportReasonPornography","params":[],"type":"ReportReason"},{"id":"-512463606","predicate":"inputReportReasonOther","params":[{"name":"text","type":"string"}],"type":"ReportReason"},{"id":"1496513539","predicate":"userFull","params":[{"name":"flags","type":"#"},{"name":"blocked","type":"flags.0?true"},{"name":"user","type":"User"},{"name":"about","type":"flags.1?string"},{"name":"link","type":"contacts.Link"},{"name":"profile_photo","type":"flags.2?Photo"},{"name":"notify_settings","type":"PeerNotifySettings"},{"name":"bot_info","type":"flags.3?BotInfo"}],"type":"UserFull"},{"id":"-116274796","predicate":"contact","params":[{"name":"user_id","type":"int"},{"name":"mutual","type":"Bool"}],"type":"Contact"},{"id":"-805141448","predicate":"importedContact","params":[{"name":"user_id","type":"int"},{"name":"client_id","type":"long"}],"type":"ImportedContact"},{"id":"1444661369","predicate":"contactBlocked","params":[{"name":"user_id","type":"int"},{"name":"date","type":"int"}],"type":"ContactBlocked"},{"id":"-748155807","predicate":"contactStatus","params":[{"name":"user_id","type":"int"},{"name":"status","type":"UserStatus"}],"type":"ContactStatus"},{"id":"986597452","predicate":"contacts.link","params":[{"name":"my_link","type":"ContactLink"},{"name":"foreign_link","type":"ContactLink"},{"name":"user","type":"User"}],"type":"contacts.Link"},{"id":"-1219778094","predicate":"contacts.contactsNotModified","params":[],"type":"contacts.Contacts"},{"id":"1871416498","predicate":"contacts.contacts","params":[{"name":"contacts","type":"Vector"},{"name":"users","type":"Vector"}],"type":"contacts.Contacts"},{"id":"-1387117803","predicate":"contacts.importedContacts","params":[{"name":"imported","type":"Vector"},{"name":"retry_contacts","type":"Vector"},{"name":"users","type":"Vector"}],"type":"contacts.ImportedContacts"},{"id":"471043349","predicate":"contacts.blocked","params":[{"name":"blocked","type":"Vector"},{"name":"users","type":"Vector"}],"type":"contacts.Blocked"},{"id":"-1878523231","predicate":"contacts.blockedSlice","params":[{"name":"count","type":"int"},{"name":"blocked","type":"Vector"},{"name":"users","type":"Vector"}],"type":"contacts.Blocked"},{"id":"364538944","predicate":"messages.dialogs","params":[{"name":"dialogs","type":"Vector"},{"name":"messages","type":"Vector"},{"name":"chats","type":"Vector"},{"name":"users","type":"Vector"}],"type":"messages.Dialogs"},{"id":"1910543603","predicate":"messages.dialogsSlice","params":[{"name":"count","type":"int"},{"name":"dialogs","type":"Vector"},{"name":"messages","type":"Vector"},{"name":"chats","type":"Vector"},{"name":"users","type":"Vector"}],"type":"messages.Dialogs"},{"id":"-1938715001","predicate":"messages.messages","params":[{"name":"messages","type":"Vector"},{"name":"chats","type":"Vector"},{"name":"users","type":"Vector"}],"type":"messages.Messages"},{"id":"189033187","predicate":"messages.messagesSlice","params":[{"name":"count","type":"int"},{"name":"messages","type":"Vector"},{"name":"chats","type":"Vector"},{"name":"users","type":"Vector"}],"type":"messages.Messages"},{"id":"1694474197","predicate":"messages.chats","params":[{"name":"chats","type":"Vector"}],"type":"messages.Chats"},{"id":"-438840932","predicate":"messages.chatFull","params":[{"name":"full_chat","type":"ChatFull"},{"name":"chats","type":"Vector"},{"name":"users","type":"Vector"}],"type":"messages.ChatFull"},{"id":"-1269012015","predicate":"messages.affectedHistory","params":[{"name":"pts","type":"int"},{"name":"pts_count","type":"int"},{"name":"offset","type":"int"}],"type":"messages.AffectedHistory"},{"id":"1474492012","predicate":"inputMessagesFilterEmpty","params":[],"type":"MessagesFilter"},{"id":"-1777752804","predicate":"inputMessagesFilterPhotos","params":[],"type":"MessagesFilter"},{"id":"-1614803355","predicate":"inputMessagesFilterVideo","params":[],"type":"MessagesFilter"},{"id":"1458172132","predicate":"inputMessagesFilterPhotoVideo","params":[],"type":"MessagesFilter"},{"id":"-648121413","predicate":"inputMessagesFilterPhotoVideoDocuments","params":[],"type":"MessagesFilter"},{"id":"-1629621880","predicate":"inputMessagesFilterDocument","params":[],"type":"MessagesFilter"},{"id":"2129714567","predicate":"inputMessagesFilterUrl","params":[],"type":"MessagesFilter"},{"id":"-3644025","predicate":"inputMessagesFilterGif","params":[],"type":"MessagesFilter"},{"id":"522914557","predicate":"updateNewMessage","params":[{"name":"message","type":"Message"},{"name":"pts","type":"int"},{"name":"pts_count","type":"int"}],"type":"Update"},{"id":"1318109142","predicate":"updateMessageID","params":[{"name":"id","type":"int"},{"name":"random_id","type":"long"}],"type":"Update"},{"id":"-1576161051","predicate":"updateDeleteMessages","params":[{"name":"messages","type":"Vector"},{"name":"pts","type":"int"},{"name":"pts_count","type":"int"}],"type":"Update"},{"id":"1548249383","predicate":"updateUserTyping","params":[{"name":"user_id","type":"int"},{"name":"action","type":"SendMessageAction"}],"type":"Update"},{"id":"-1704596961","predicate":"updateChatUserTyping","params":[{"name":"chat_id","type":"int"},{"name":"user_id","type":"int"},{"name":"action","type":"SendMessageAction"}],"type":"Update"},{"id":"125178264","predicate":"updateChatParticipants","params":[{"name":"participants","type":"ChatParticipants"}],"type":"Update"},{"id":"469489699","predicate":"updateUserStatus","params":[{"name":"user_id","type":"int"},{"name":"status","type":"UserStatus"}],"type":"Update"},{"id":"-1489818765","predicate":"updateUserName","params":[{"name":"user_id","type":"int"},{"name":"first_name","type":"string"},{"name":"last_name","type":"string"},{"name":"username","type":"string"}],"type":"Update"},{"id":"-1791935732","predicate":"updateUserPhoto","params":[{"name":"user_id","type":"int"},{"name":"date","type":"int"},{"name":"photo","type":"UserProfilePhoto"},{"name":"previous","type":"Bool"}],"type":"Update"},{"id":"628472761","predicate":"updateContactRegistered","params":[{"name":"user_id","type":"int"},{"name":"date","type":"int"}],"type":"Update"},{"id":"-1657903163","predicate":"updateContactLink","params":[{"name":"user_id","type":"int"},{"name":"my_link","type":"ContactLink"},{"name":"foreign_link","type":"ContactLink"}],"type":"Update"},{"id":"-1895411046","predicate":"updateNewAuthorization","params":[{"name":"auth_key_id","type":"long"},{"name":"date","type":"int"},{"name":"device","type":"string"},{"name":"location","type":"string"}],"type":"Update"},{"id":"-1519637954","predicate":"updates.state","params":[{"name":"pts","type":"int"},{"name":"qts","type":"int"},{"name":"date","type":"int"},{"name":"seq","type":"int"},{"name":"unread_count","type":"int"}],"type":"updates.State"},{"id":"1567990072","predicate":"updates.differenceEmpty","params":[{"name":"date","type":"int"},{"name":"seq","type":"int"}],"type":"updates.Difference"},{"id":"16030880","predicate":"updates.difference","params":[{"name":"new_messages","type":"Vector"},{"name":"new_encrypted_messages","type":"Vector"},{"name":"other_updates","type":"Vector"},{"name":"chats","type":"Vector"},{"name":"users","type":"Vector"},{"name":"state","type":"updates.State"}],"type":"updates.Difference"},{"id":"-1459938943","predicate":"updates.differenceSlice","params":[{"name":"new_messages","type":"Vector"},{"name":"new_encrypted_messages","type":"Vector"},{"name":"other_updates","type":"Vector"},{"name":"chats","type":"Vector"},{"name":"users","type":"Vector"},{"name":"intermediate_state","type":"updates.State"}],"type":"updates.Difference"},{"id":"-484987010","predicate":"updatesTooLong","params":[],"type":"Updates"},{"id":"-1857044719","predicate":"updateShortMessage","params":[{"name":"flags","type":"#"},{"name":"unread","type":"flags.0?true"},{"name":"out","type":"flags.1?true"},{"name":"mentioned","type":"flags.4?true"},{"name":"media_unread","type":"flags.5?true"},{"name":"silent","type":"flags.13?true"},{"name":"id","type":"int"},{"name":"user_id","type":"int"},{"name":"message","type":"string"},{"name":"pts","type":"int"},{"name":"pts_count","type":"int"},{"name":"date","type":"int"},{"name":"fwd_from","type":"flags.2?MessageFwdHeader"},{"name":"via_bot_id","type":"flags.11?int"},{"name":"reply_to_msg_id","type":"flags.3?int"},{"name":"entities","type":"flags.7?Vector"}],"type":"Updates"},{"id":"377562760","predicate":"updateShortChatMessage","params":[{"name":"flags","type":"#"},{"name":"unread","type":"flags.0?true"},{"name":"out","type":"flags.1?true"},{"name":"mentioned","type":"flags.4?true"},{"name":"media_unread","type":"flags.5?true"},{"name":"silent","type":"flags.13?true"},{"name":"id","type":"int"},{"name":"from_id","type":"int"},{"name":"chat_id","type":"int"},{"name":"message","type":"string"},{"name":"pts","type":"int"},{"name":"pts_count","type":"int"},{"name":"date","type":"int"},{"name":"fwd_from","type":"flags.2?MessageFwdHeader"},{"name":"via_bot_id","type":"flags.11?int"},{"name":"reply_to_msg_id","type":"flags.3?int"},{"name":"entities","type":"flags.7?Vector"}],"type":"Updates"},{"id":"2027216577","predicate":"updateShort","params":[{"name":"update","type":"Update"},{"name":"date","type":"int"}],"type":"Updates"},{"id":"1918567619","predicate":"updatesCombined","params":[{"name":"updates","type":"Vector"},{"name":"users","type":"Vector"},{"name":"chats","type":"Vector"},{"name":"date","type":"int"},{"name":"seq_start","type":"int"},{"name":"seq","type":"int"}],"type":"Updates"},{"id":"1957577280","predicate":"updates","params":[{"name":"updates","type":"Vector"},{"name":"users","type":"Vector"},{"name":"chats","type":"Vector"},{"name":"date","type":"int"},{"name":"seq","type":"int"}],"type":"Updates"},{"id":"-1916114267","predicate":"photos.photos","params":[{"name":"photos","type":"Vector"},{"name":"users","type":"Vector"}],"type":"photos.Photos"},{"id":"352657236","predicate":"photos.photosSlice","params":[{"name":"count","type":"int"},{"name":"photos","type":"Vector"},{"name":"users","type":"Vector"}],"type":"photos.Photos"},{"id":"539045032","predicate":"photos.photo","params":[{"name":"photo","type":"Photo"},{"name":"users","type":"Vector"}],"type":"photos.Photo"},{"id":"157948117","predicate":"upload.file","params":[{"name":"type","type":"storage.FileType"},{"name":"mtime","type":"int"},{"name":"bytes","type":"bytes"}],"type":"upload.File"},{"id":"98092748","predicate":"dcOption","params":[{"name":"flags","type":"#"},{"name":"ipv6","type":"flags.0?true"},{"name":"media_only","type":"flags.1?true"},{"name":"tcpo_only","type":"flags.2?true"},{"name":"id","type":"int"},{"name":"ip_address","type":"string"},{"name":"port","type":"int"}],"type":"DcOption"},{"id":"830271220","predicate":"config","params":[{"name":"date","type":"int"},{"name":"expires","type":"int"},{"name":"test_mode","type":"Bool"},{"name":"this_dc","type":"int"},{"name":"dc_options","type":"Vector"},{"name":"chat_size_max","type":"int"},{"name":"megagroup_size_max","type":"int"},{"name":"forwarded_count_max","type":"int"},{"name":"online_update_period_ms","type":"int"},{"name":"offline_blur_timeout_ms","type":"int"},{"name":"offline_idle_timeout_ms","type":"int"},{"name":"online_cloud_timeout_ms","type":"int"},{"name":"notify_cloud_delay_ms","type":"int"},{"name":"notify_default_delay_ms","type":"int"},{"name":"chat_big_size","type":"int"},{"name":"push_chat_period_ms","type":"int"},{"name":"push_chat_limit","type":"int"},{"name":"saved_gifs_limit","type":"int"},{"name":"edit_time_limit","type":"int"},{"name":"disabled_features","type":"Vector"}],"type":"Config"},{"id":"-1910892683","predicate":"nearestDc","params":[{"name":"country","type":"string"},{"name":"this_dc","type":"int"},{"name":"nearest_dc","type":"int"}],"type":"NearestDc"},{"id":"-1987579119","predicate":"help.appUpdate","params":[{"name":"id","type":"int"},{"name":"critical","type":"Bool"},{"name":"url","type":"string"},{"name":"text","type":"string"}],"type":"help.AppUpdate"},{"id":"-1000708810","predicate":"help.noAppUpdate","params":[],"type":"help.AppUpdate"},{"id":"415997816","predicate":"help.inviteText","params":[{"name":"message","type":"string"}],"type":"help.InviteText"},{"id":"1662091044","predicate":"wallPaperSolid","params":[{"name":"id","type":"int"},{"name":"title","type":"string"},{"name":"bg_color","type":"int"},{"name":"color","type":"int"}],"type":"WallPaper"},{"id":"314359194","predicate":"updateNewEncryptedMessage","params":[{"name":"message","type":"EncryptedMessage"},{"name":"qts","type":"int"}],"type":"Update"},{"id":"386986326","predicate":"updateEncryptedChatTyping","params":[{"name":"chat_id","type":"int"}],"type":"Update"},{"id":"-1264392051","predicate":"updateEncryption","params":[{"name":"chat","type":"EncryptedChat"},{"name":"date","type":"int"}],"type":"Update"},{"id":"956179895","predicate":"updateEncryptedMessagesRead","params":[{"name":"chat_id","type":"int"},{"name":"max_date","type":"int"},{"name":"date","type":"int"}],"type":"Update"},{"id":"-1417756512","predicate":"encryptedChatEmpty","params":[{"name":"id","type":"int"}],"type":"EncryptedChat"},{"id":"1006044124","predicate":"encryptedChatWaiting","params":[{"name":"id","type":"int"},{"name":"access_hash","type":"long"},{"name":"date","type":"int"},{"name":"admin_id","type":"int"},{"name":"participant_id","type":"int"}],"type":"EncryptedChat"},{"id":"-931638658","predicate":"encryptedChatRequested","params":[{"name":"id","type":"int"},{"name":"access_hash","type":"long"},{"name":"date","type":"int"},{"name":"admin_id","type":"int"},{"name":"participant_id","type":"int"},{"name":"g_a","type":"bytes"}],"type":"EncryptedChat"},{"id":"-94974410","predicate":"encryptedChat","params":[{"name":"id","type":"int"},{"name":"access_hash","type":"long"},{"name":"date","type":"int"},{"name":"admin_id","type":"int"},{"name":"participant_id","type":"int"},{"name":"g_a_or_b","type":"bytes"},{"name":"key_fingerprint","type":"long"}],"type":"EncryptedChat"},{"id":"332848423","predicate":"encryptedChatDiscarded","params":[{"name":"id","type":"int"}],"type":"EncryptedChat"},{"id":"-247351839","predicate":"inputEncryptedChat","params":[{"name":"chat_id","type":"int"},{"name":"access_hash","type":"long"}],"type":"InputEncryptedChat"},{"id":"-1038136962","predicate":"encryptedFileEmpty","params":[],"type":"EncryptedFile"},{"id":"1248893260","predicate":"encryptedFile","params":[{"name":"id","type":"long"},{"name":"access_hash","type":"long"},{"name":"size","type":"int"},{"name":"dc_id","type":"int"},{"name":"key_fingerprint","type":"int"}],"type":"EncryptedFile"},{"id":"406307684","predicate":"inputEncryptedFileEmpty","params":[],"type":"InputEncryptedFile"},{"id":"1690108678","predicate":"inputEncryptedFileUploaded","params":[{"name":"id","type":"long"},{"name":"parts","type":"int"},{"name":"md5_checksum","type":"string"},{"name":"key_fingerprint","type":"int"}],"type":"InputEncryptedFile"},{"id":"1511503333","predicate":"inputEncryptedFile","params":[{"name":"id","type":"long"},{"name":"access_hash","type":"long"}],"type":"InputEncryptedFile"},{"id":"-182231723","predicate":"inputEncryptedFileLocation","params":[{"name":"id","type":"long"},{"name":"access_hash","type":"long"}],"type":"InputFileLocation"},{"id":"-317144808","predicate":"encryptedMessage","params":[{"name":"random_id","type":"long"},{"name":"chat_id","type":"int"},{"name":"date","type":"int"},{"name":"bytes","type":"bytes"},{"name":"file","type":"EncryptedFile"}],"type":"EncryptedMessage"},{"id":"594758406","predicate":"encryptedMessageService","params":[{"name":"random_id","type":"long"},{"name":"chat_id","type":"int"},{"name":"date","type":"int"},{"name":"bytes","type":"bytes"}],"type":"EncryptedMessage"},{"id":"-1058912715","predicate":"messages.dhConfigNotModified","params":[{"name":"random","type":"bytes"}],"type":"messages.DhConfig"},{"id":"740433629","predicate":"messages.dhConfig","params":[{"name":"g","type":"int"},{"name":"p","type":"bytes"},{"name":"version","type":"int"},{"name":"random","type":"bytes"}],"type":"messages.DhConfig"},{"id":"1443858741","predicate":"messages.sentEncryptedMessage","params":[{"name":"date","type":"int"}],"type":"messages.SentEncryptedMessage"},{"id":"-1802240206","predicate":"messages.sentEncryptedFile","params":[{"name":"date","type":"int"},{"name":"file","type":"EncryptedFile"}],"type":"messages.SentEncryptedMessage"},{"id":"-95482955","predicate":"inputFileBig","params":[{"name":"id","type":"long"},{"name":"parts","type":"int"},{"name":"name","type":"string"}],"type":"InputFile"},{"id":"767652808","predicate":"inputEncryptedFileBigUploaded","params":[{"name":"id","type":"long"},{"name":"parts","type":"int"},{"name":"key_fingerprint","type":"int"}],"type":"InputEncryptedFile"},{"id":"-364179876","predicate":"updateChatParticipantAdd","params":[{"name":"chat_id","type":"int"},{"name":"user_id","type":"int"},{"name":"inviter_id","type":"int"},{"name":"date","type":"int"},{"name":"version","type":"int"}],"type":"Update"},{"id":"1851755554","predicate":"updateChatParticipantDelete","params":[{"name":"chat_id","type":"int"},{"name":"user_id","type":"int"},{"name":"version","type":"int"}],"type":"Update"},{"id":"-1906403213","predicate":"updateDcOptions","params":[{"name":"dc_options","type":"Vector"}],"type":"Update"},{"id":"495530093","predicate":"inputMediaUploadedDocument","params":[{"name":"file","type":"InputFile"},{"name":"mime_type","type":"string"},{"name":"attributes","type":"Vector"},{"name":"caption","type":"string"}],"type":"InputMedia"},{"id":"-1386138479","predicate":"inputMediaUploadedThumbDocument","params":[{"name":"file","type":"InputFile"},{"name":"thumb","type":"InputFile"},{"name":"mime_type","type":"string"},{"name":"attributes","type":"Vector"},{"name":"caption","type":"string"}],"type":"InputMedia"},{"id":"444068508","predicate":"inputMediaDocument","params":[{"name":"id","type":"InputDocument"},{"name":"caption","type":"string"}],"type":"InputMedia"},{"id":"-203411800","predicate":"messageMediaDocument","params":[{"name":"document","type":"Document"},{"name":"caption","type":"string"}],"type":"MessageMedia"},{"id":"1928391342","predicate":"inputDocumentEmpty","params":[],"type":"InputDocument"},{"id":"410618194","predicate":"inputDocument","params":[{"name":"id","type":"long"},{"name":"access_hash","type":"long"}],"type":"InputDocument"},{"id":"1313188841","predicate":"inputDocumentFileLocation","params":[{"name":"id","type":"long"},{"name":"access_hash","type":"long"}],"type":"InputFileLocation"},{"id":"922273905","predicate":"documentEmpty","params":[{"name":"id","type":"long"}],"type":"Document"},{"id":"-106717361","predicate":"document","params":[{"name":"id","type":"long"},{"name":"access_hash","type":"long"},{"name":"date","type":"int"},{"name":"mime_type","type":"string"},{"name":"size","type":"int"},{"name":"thumb","type":"PhotoSize"},{"name":"dc_id","type":"int"},{"name":"attributes","type":"Vector"}],"type":"Document"},{"id":"398898678","predicate":"help.support","params":[{"name":"phone_number","type":"string"},{"name":"user","type":"User"}],"type":"help.Support"},{"id":"-1613493288","predicate":"notifyPeer","params":[{"name":"peer","type":"Peer"}],"type":"NotifyPeer"},{"id":"-1261946036","predicate":"notifyUsers","params":[],"type":"NotifyPeer"},{"id":"-1073230141","predicate":"notifyChats","params":[],"type":"NotifyPeer"},{"id":"1959820384","predicate":"notifyAll","params":[],"type":"NotifyPeer"},{"id":"-2131957734","predicate":"updateUserBlocked","params":[{"name":"user_id","type":"int"},{"name":"blocked","type":"Bool"}],"type":"Update"},{"id":"-1094555409","predicate":"updateNotifySettings","params":[{"name":"peer","type":"NotifyPeer"},{"name":"notify_settings","type":"PeerNotifySettings"}],"type":"Update"},{"id":"381645902","predicate":"sendMessageTypingAction","params":[],"type":"SendMessageAction"},{"id":"-44119819","predicate":"sendMessageCancelAction","params":[],"type":"SendMessageAction"},{"id":"-1584933265","predicate":"sendMessageRecordVideoAction","params":[],"type":"SendMessageAction"},{"id":"-378127636","predicate":"sendMessageUploadVideoAction","params":[{"name":"progress","type":"int"}],"type":"SendMessageAction"},{"id":"-718310409","predicate":"sendMessageRecordAudioAction","params":[],"type":"SendMessageAction"},{"id":"-212740181","predicate":"sendMessageUploadAudioAction","params":[{"name":"progress","type":"int"}],"type":"SendMessageAction"},{"id":"-774682074","predicate":"sendMessageUploadPhotoAction","params":[{"name":"progress","type":"int"}],"type":"SendMessageAction"},{"id":"-1441998364","predicate":"sendMessageUploadDocumentAction","params":[{"name":"progress","type":"int"}],"type":"SendMessageAction"},{"id":"393186209","predicate":"sendMessageGeoLocationAction","params":[],"type":"SendMessageAction"},{"id":"1653390447","predicate":"sendMessageChooseContactAction","params":[],"type":"SendMessageAction"},{"id":"446822276","predicate":"contacts.found","params":[{"name":"results","type":"Vector"},{"name":"chats","type":"Vector"},{"name":"users","type":"Vector"}],"type":"contacts.Found"},{"id":"942527460","predicate":"updateServiceNotification","params":[{"name":"type","type":"string"},{"name":"message","type":"string"},{"name":"media","type":"MessageMedia"},{"name":"popup","type":"Bool"}],"type":"Update"},{"id":"-496024847","predicate":"userStatusRecently","params":[],"type":"UserStatus"},{"id":"129960444","predicate":"userStatusLastWeek","params":[],"type":"UserStatus"},{"id":"2011940674","predicate":"userStatusLastMonth","params":[],"type":"UserStatus"},{"id":"-298113238","predicate":"updatePrivacy","params":[{"name":"key","type":"PrivacyKey"},{"name":"rules","type":"Vector"}],"type":"Update"},{"id":"1335282456","predicate":"inputPrivacyKeyStatusTimestamp","params":[],"type":"InputPrivacyKey"},{"id":"-1137792208","predicate":"privacyKeyStatusTimestamp","params":[],"type":"PrivacyKey"},{"id":"218751099","predicate":"inputPrivacyValueAllowContacts","params":[],"type":"InputPrivacyRule"},{"id":"407582158","predicate":"inputPrivacyValueAllowAll","params":[],"type":"InputPrivacyRule"},{"id":"320652927","predicate":"inputPrivacyValueAllowUsers","params":[{"name":"users","type":"Vector"}],"type":"InputPrivacyRule"},{"id":"195371015","predicate":"inputPrivacyValueDisallowContacts","params":[],"type":"InputPrivacyRule"},{"id":"-697604407","predicate":"inputPrivacyValueDisallowAll","params":[],"type":"InputPrivacyRule"},{"id":"-1877932953","predicate":"inputPrivacyValueDisallowUsers","params":[{"name":"users","type":"Vector"}],"type":"InputPrivacyRule"},{"id":"-123988","predicate":"privacyValueAllowContacts","params":[],"type":"PrivacyRule"},{"id":"1698855810","predicate":"privacyValueAllowAll","params":[],"type":"PrivacyRule"},{"id":"1297858060","predicate":"privacyValueAllowUsers","params":[{"name":"users","type":"Vector"}],"type":"PrivacyRule"},{"id":"-125240806","predicate":"privacyValueDisallowContacts","params":[],"type":"PrivacyRule"},{"id":"-1955338397","predicate":"privacyValueDisallowAll","params":[],"type":"PrivacyRule"},{"id":"209668535","predicate":"privacyValueDisallowUsers","params":[{"name":"users","type":"Vector"}],"type":"PrivacyRule"},{"id":"1430961007","predicate":"account.privacyRules","params":[{"name":"rules","type":"Vector"},{"name":"users","type":"Vector"}],"type":"account.PrivacyRules"},{"id":"-1194283041","predicate":"accountDaysTTL","params":[{"name":"days","type":"int"}],"type":"AccountDaysTTL"},{"id":"314130811","predicate":"updateUserPhone","params":[{"name":"user_id","type":"int"},{"name":"phone","type":"string"}],"type":"Update"},{"id":"1815593308","predicate":"documentAttributeImageSize","params":[{"name":"w","type":"int"},{"name":"h","type":"int"}],"type":"DocumentAttribute"},{"id":"297109817","predicate":"documentAttributeAnimated","params":[],"type":"DocumentAttribute"},{"id":"978674434","predicate":"documentAttributeSticker","params":[{"name":"alt","type":"string"},{"name":"stickerset","type":"InputStickerSet"}],"type":"DocumentAttribute"},{"id":"1494273227","predicate":"documentAttributeVideo","params":[{"name":"duration","type":"int"},{"name":"w","type":"int"},{"name":"h","type":"int"}],"type":"DocumentAttribute"},{"id":"-1739392570","predicate":"documentAttributeAudio","params":[{"name":"flags","type":"#"},{"name":"voice","type":"flags.10?true"},{"name":"duration","type":"int"},{"name":"title","type":"flags.0?string"},{"name":"performer","type":"flags.1?string"},{"name":"waveform","type":"flags.2?bytes"}],"type":"DocumentAttribute"},{"id":"358154344","predicate":"documentAttributeFilename","params":[{"name":"file_name","type":"string"}],"type":"DocumentAttribute"},{"id":"-244016606","predicate":"messages.stickersNotModified","params":[],"type":"messages.Stickers"},{"id":"-1970352846","predicate":"messages.stickers","params":[{"name":"hash","type":"string"},{"name":"stickers","type":"Vector"}],"type":"messages.Stickers"},{"id":"313694676","predicate":"stickerPack","params":[{"name":"emoticon","type":"string"},{"name":"documents","type":"Vector"}],"type":"StickerPack"},{"id":"-395967805","predicate":"messages.allStickersNotModified","params":[],"type":"messages.AllStickers"},{"id":"-302170017","predicate":"messages.allStickers","params":[{"name":"hash","type":"int"},{"name":"sets","type":"Vector"}],"type":"messages.AllStickers"},{"id":"-1369215196","predicate":"disabledFeature","params":[{"name":"feature","type":"string"},{"name":"description","type":"string"}],"type":"DisabledFeature"},{"id":"-1721631396","predicate":"updateReadHistoryInbox","params":[{"name":"peer","type":"Peer"},{"name":"max_id","type":"int"},{"name":"pts","type":"int"},{"name":"pts_count","type":"int"}],"type":"Update"},{"id":"791617983","predicate":"updateReadHistoryOutbox","params":[{"name":"peer","type":"Peer"},{"name":"max_id","type":"int"},{"name":"pts","type":"int"},{"name":"pts_count","type":"int"}],"type":"Update"},{"id":"-2066640507","predicate":"messages.affectedMessages","params":[{"name":"pts","type":"int"},{"name":"pts_count","type":"int"}],"type":"messages.AffectedMessages"},{"id":"1599050311","predicate":"contactLinkUnknown","params":[],"type":"ContactLink"},{"id":"-17968211","predicate":"contactLinkNone","params":[],"type":"ContactLink"},{"id":"646922073","predicate":"contactLinkHasPhone","params":[],"type":"ContactLink"},{"id":"-721239344","predicate":"contactLinkContact","params":[],"type":"ContactLink"},{"id":"2139689491","predicate":"updateWebPage","params":[{"name":"webpage","type":"WebPage"},{"name":"pts","type":"int"},{"name":"pts_count","type":"int"}],"type":"Update"},{"id":"-350980120","predicate":"webPageEmpty","params":[{"name":"id","type":"long"}],"type":"WebPage"},{"id":"-981018084","predicate":"webPagePending","params":[{"name":"id","type":"long"},{"name":"date","type":"int"}],"type":"WebPage"},{"id":"-897446185","predicate":"webPage","params":[{"name":"flags","type":"#"},{"name":"id","type":"long"},{"name":"url","type":"string"},{"name":"display_url","type":"string"},{"name":"type","type":"flags.0?string"},{"name":"site_name","type":"flags.1?string"},{"name":"title","type":"flags.2?string"},{"name":"description","type":"flags.3?string"},{"name":"photo","type":"flags.4?Photo"},{"name":"embed_url","type":"flags.5?string"},{"name":"embed_type","type":"flags.5?string"},{"name":"embed_width","type":"flags.6?int"},{"name":"embed_height","type":"flags.6?int"},{"name":"duration","type":"flags.7?int"},{"name":"author","type":"flags.8?string"},{"name":"document","type":"flags.9?Document"}],"type":"WebPage"},{"id":"-1557277184","predicate":"messageMediaWebPage","params":[{"name":"webpage","type":"WebPage"}],"type":"MessageMedia"},{"id":"2079516406","predicate":"authorization","params":[{"name":"hash","type":"long"},{"name":"flags","type":"int"},{"name":"device_model","type":"string"},{"name":"platform","type":"string"},{"name":"system_version","type":"string"},{"name":"api_id","type":"int"},{"name":"app_name","type":"string"},{"name":"app_version","type":"string"},{"name":"date_created","type":"int"},{"name":"date_active","type":"int"},{"name":"ip","type":"string"},{"name":"country","type":"string"},{"name":"region","type":"string"}],"type":"Authorization"},{"id":"307276766","predicate":"account.authorizations","params":[{"name":"authorizations","type":"Vector"}],"type":"account.Authorizations"},{"id":"-1764049896","predicate":"account.noPassword","params":[{"name":"new_salt","type":"bytes"},{"name":"email_unconfirmed_pattern","type":"string"}],"type":"account.Password"},{"id":"2081952796","predicate":"account.password","params":[{"name":"current_salt","type":"bytes"},{"name":"new_salt","type":"bytes"},{"name":"hint","type":"string"},{"name":"has_recovery","type":"Bool"},{"name":"email_unconfirmed_pattern","type":"string"}],"type":"account.Password"},{"id":"-1212732749","predicate":"account.passwordSettings","params":[{"name":"email","type":"string"}],"type":"account.PasswordSettings"},{"id":"-2037289493","predicate":"account.passwordInputSettings","params":[{"name":"flags","type":"#"},{"name":"new_salt","type":"flags.0?bytes"},{"name":"new_password_hash","type":"flags.0?bytes"},{"name":"hint","type":"flags.0?string"},{"name":"email","type":"flags.1?string"}],"type":"account.PasswordInputSettings"},{"id":"326715557","predicate":"auth.passwordRecovery","params":[{"name":"email_pattern","type":"string"}],"type":"auth.PasswordRecovery"},{"id":"673687578","predicate":"inputMediaVenue","params":[{"name":"geo_point","type":"InputGeoPoint"},{"name":"title","type":"string"},{"name":"address","type":"string"},{"name":"provider","type":"string"},{"name":"venue_id","type":"string"}],"type":"InputMedia"},{"id":"2031269663","predicate":"messageMediaVenue","params":[{"name":"geo","type":"GeoPoint"},{"name":"title","type":"string"},{"name":"address","type":"string"},{"name":"provider","type":"string"},{"name":"venue_id","type":"string"}],"type":"MessageMedia"},{"id":"-1551583367","predicate":"receivedNotifyMessage","params":[{"name":"id","type":"int"},{"name":"flags","type":"int"}],"type":"ReceivedNotifyMessage"},{"id":"1776236393","predicate":"chatInviteEmpty","params":[],"type":"ExportedChatInvite"},{"id":"-64092740","predicate":"chatInviteExported","params":[{"name":"link","type":"string"}],"type":"ExportedChatInvite"},{"id":"1516793212","predicate":"chatInviteAlready","params":[{"name":"chat","type":"Chat"}],"type":"ChatInvite"},{"id":"-1813406880","predicate":"chatInvite","params":[{"name":"flags","type":"#"},{"name":"channel","type":"flags.0?true"},{"name":"broadcast","type":"flags.1?true"},{"name":"public","type":"flags.2?true"},{"name":"megagroup","type":"flags.3?true"},{"name":"title","type":"string"}],"type":"ChatInvite"},{"id":"-123931160","predicate":"messageActionChatJoinedByLink","params":[{"name":"inviter_id","type":"int"}],"type":"MessageAction"},{"id":"1757493555","predicate":"updateReadMessagesContents","params":[{"name":"messages","type":"Vector"},{"name":"pts","type":"int"},{"name":"pts_count","type":"int"}],"type":"Update"},{"id":"-4838507","predicate":"inputStickerSetEmpty","params":[],"type":"InputStickerSet"},{"id":"-1645763991","predicate":"inputStickerSetID","params":[{"name":"id","type":"long"},{"name":"access_hash","type":"long"}],"type":"InputStickerSet"},{"id":"-2044933984","predicate":"inputStickerSetShortName","params":[{"name":"short_name","type":"string"}],"type":"InputStickerSet"},{"id":"-852477119","predicate":"stickerSet","params":[{"name":"flags","type":"#"},{"name":"installed","type":"flags.0?true"},{"name":"disabled","type":"flags.1?true"},{"name":"official","type":"flags.2?true"},{"name":"id","type":"long"},{"name":"access_hash","type":"long"},{"name":"title","type":"string"},{"name":"short_name","type":"string"},{"name":"count","type":"int"},{"name":"hash","type":"int"}],"type":"StickerSet"},{"id":"-1240849242","predicate":"messages.stickerSet","params":[{"name":"set","type":"StickerSet"},{"name":"packs","type":"Vector"},{"name":"documents","type":"Vector"}],"type":"messages.StickerSet"},{"id":"-787638374","predicate":"user","params":[{"name":"flags","type":"#"},{"name":"self","type":"flags.10?true"},{"name":"contact","type":"flags.11?true"},{"name":"mutual_contact","type":"flags.12?true"},{"name":"deleted","type":"flags.13?true"},{"name":"bot","type":"flags.14?true"},{"name":"bot_chat_history","type":"flags.15?true"},{"name":"bot_nochats","type":"flags.16?true"},{"name":"verified","type":"flags.17?true"},{"name":"restricted","type":"flags.18?true"},{"name":"min","type":"flags.20?true"},{"name":"bot_inline_geo","type":"flags.21?true"},{"name":"id","type":"int"},{"name":"access_hash","type":"flags.0?long"},{"name":"first_name","type":"flags.1?string"},{"name":"last_name","type":"flags.2?string"},{"name":"username","type":"flags.3?string"},{"name":"phone","type":"flags.4?string"},{"name":"photo","type":"flags.5?UserProfilePhoto"},{"name":"status","type":"flags.6?UserStatus"},{"name":"bot_info_version","type":"flags.14?int"},{"name":"restriction_reason","type":"flags.18?string"},{"name":"bot_inline_placeholder","type":"flags.19?string"}],"type":"User"},{"id":"-1032140601","predicate":"botCommand","params":[{"name":"command","type":"string"},{"name":"description","type":"string"}],"type":"BotCommand"},{"id":"-1729618630","predicate":"botInfo","params":[{"name":"user_id","type":"int"},{"name":"description","type":"string"},{"name":"commands","type":"Vector"}],"type":"BotInfo"},{"id":"-1560655744","predicate":"keyboardButton","params":[{"name":"text","type":"string"}],"type":"KeyboardButton"},{"id":"2002815875","predicate":"keyboardButtonRow","params":[{"name":"buttons","type":"Vector"}],"type":"KeyboardButtonRow"},{"id":"-1606526075","predicate":"replyKeyboardHide","params":[{"name":"flags","type":"#"},{"name":"selective","type":"flags.2?true"}],"type":"ReplyMarkup"},{"id":"-200242528","predicate":"replyKeyboardForceReply","params":[{"name":"flags","type":"#"},{"name":"single_use","type":"flags.1?true"},{"name":"selective","type":"flags.2?true"}],"type":"ReplyMarkup"},{"id":"889353612","predicate":"replyKeyboardMarkup","params":[{"name":"flags","type":"#"},{"name":"resize","type":"flags.0?true"},{"name":"single_use","type":"flags.1?true"},{"name":"selective","type":"flags.2?true"},{"name":"rows","type":"Vector"}],"type":"ReplyMarkup"},{"id":"2072935910","predicate":"inputPeerUser","params":[{"name":"user_id","type":"int"},{"name":"access_hash","type":"long"}],"type":"InputPeer"},{"id":"-668391402","predicate":"inputUser","params":[{"name":"user_id","type":"int"},{"name":"access_hash","type":"long"}],"type":"InputUser"},{"id":"-1350696044","predicate":"help.appChangelogEmpty","params":[],"type":"help.AppChangelog"},{"id":"1181279933","predicate":"help.appChangelog","params":[{"name":"text","type":"string"}],"type":"help.AppChangelog"},{"id":"-1148011883","predicate":"messageEntityUnknown","params":[{"name":"offset","type":"int"},{"name":"length","type":"int"}],"type":"MessageEntity"},{"id":"-100378723","predicate":"messageEntityMention","params":[{"name":"offset","type":"int"},{"name":"length","type":"int"}],"type":"MessageEntity"},{"id":"1868782349","predicate":"messageEntityHashtag","params":[{"name":"offset","type":"int"},{"name":"length","type":"int"}],"type":"MessageEntity"},{"id":"1827637959","predicate":"messageEntityBotCommand","params":[{"name":"offset","type":"int"},{"name":"length","type":"int"}],"type":"MessageEntity"},{"id":"1859134776","predicate":"messageEntityUrl","params":[{"name":"offset","type":"int"},{"name":"length","type":"int"}],"type":"MessageEntity"},{"id":"1692693954","predicate":"messageEntityEmail","params":[{"name":"offset","type":"int"},{"name":"length","type":"int"}],"type":"MessageEntity"},{"id":"-1117713463","predicate":"messageEntityBold","params":[{"name":"offset","type":"int"},{"name":"length","type":"int"}],"type":"MessageEntity"},{"id":"-2106619040","predicate":"messageEntityItalic","params":[{"name":"offset","type":"int"},{"name":"length","type":"int"}],"type":"MessageEntity"},{"id":"681706865","predicate":"messageEntityCode","params":[{"name":"offset","type":"int"},{"name":"length","type":"int"}],"type":"MessageEntity"},{"id":"1938967520","predicate":"messageEntityPre","params":[{"name":"offset","type":"int"},{"name":"length","type":"int"},{"name":"language","type":"string"}],"type":"MessageEntity"},{"id":"1990644519","predicate":"messageEntityTextUrl","params":[{"name":"offset","type":"int"},{"name":"length","type":"int"},{"name":"url","type":"string"}],"type":"MessageEntity"},{"id":"301019932","predicate":"updateShortSentMessage","params":[{"name":"flags","type":"#"},{"name":"unread","type":"flags.0?true"},{"name":"out","type":"flags.1?true"},{"name":"id","type":"int"},{"name":"pts","type":"int"},{"name":"pts_count","type":"int"},{"name":"date","type":"int"},{"name":"media","type":"flags.9?MessageMedia"},{"name":"entities","type":"flags.7?Vector"}],"type":"Updates"},{"id":"-292807034","predicate":"inputChannelEmpty","params":[],"type":"InputChannel"},{"id":"-1343524562","predicate":"inputChannel","params":[{"name":"channel_id","type":"int"},{"name":"access_hash","type":"long"}],"type":"InputChannel"},{"id":"-1109531342","predicate":"peerChannel","params":[{"name":"channel_id","type":"int"}],"type":"Peer"},{"id":"548253432","predicate":"inputPeerChannel","params":[{"name":"channel_id","type":"int"},{"name":"access_hash","type":"long"}],"type":"InputPeer"},{"id":"-1588737454","predicate":"channel","params":[{"name":"flags","type":"#"},{"name":"creator","type":"flags.0?true"},{"name":"kicked","type":"flags.1?true"},{"name":"left","type":"flags.2?true"},{"name":"editor","type":"flags.3?true"},{"name":"moderator","type":"flags.4?true"},{"name":"broadcast","type":"flags.5?true"},{"name":"verified","type":"flags.7?true"},{"name":"megagroup","type":"flags.8?true"},{"name":"restricted","type":"flags.9?true"},{"name":"democracy","type":"flags.10?true"},{"name":"signatures","type":"flags.11?true"},{"name":"min","type":"flags.12?true"},{"name":"id","type":"int"},{"name":"access_hash","type":"flags.13?long"},{"name":"title","type":"string"},{"name":"username","type":"flags.6?string"},{"name":"photo","type":"ChatPhoto"},{"name":"date","type":"int"},{"name":"version","type":"int"},{"name":"restriction_reason","type":"flags.9?string"}],"type":"Chat"},{"id":"763724588","predicate":"channelForbidden","params":[{"name":"id","type":"int"},{"name":"access_hash","type":"long"},{"name":"title","type":"string"}],"type":"Chat"},{"id":"2131196633","predicate":"contacts.resolvedPeer","params":[{"name":"peer","type":"Peer"},{"name":"chats","type":"Vector"},{"name":"users","type":"Vector"}],"type":"contacts.ResolvedPeer"},{"id":"-1749097118","predicate":"channelFull","params":[{"name":"flags","type":"#"},{"name":"can_view_participants","type":"flags.3?true"},{"name":"can_set_username","type":"flags.6?true"},{"name":"id","type":"int"},{"name":"about","type":"string"},{"name":"participants_count","type":"flags.0?int"},{"name":"admins_count","type":"flags.1?int"},{"name":"kicked_count","type":"flags.2?int"},{"name":"read_inbox_max_id","type":"int"},{"name":"unread_count","type":"int"},{"name":"unread_important_count","type":"int"},{"name":"chat_photo","type":"Photo"},{"name":"notify_settings","type":"PeerNotifySettings"},{"name":"exported_invite","type":"ExportedChatInvite"},{"name":"bot_info","type":"Vector"},{"name":"migrated_from_chat_id","type":"flags.4?int"},{"name":"migrated_from_max_id","type":"flags.4?int"},{"name":"pinned_msg_id","type":"flags.5?int"}],"type":"ChatFull"},{"id":"1535415986","predicate":"dialogChannel","params":[{"name":"peer","type":"Peer"},{"name":"top_message","type":"int"},{"name":"top_important_message","type":"int"},{"name":"read_inbox_max_id","type":"int"},{"name":"unread_count","type":"int"},{"name":"unread_important_count","type":"int"},{"name":"notify_settings","type":"PeerNotifySettings"},{"name":"pts","type":"int"}],"type":"Dialog"},{"id":"182649427","predicate":"messageRange","params":[{"name":"min_id","type":"int"},{"name":"max_id","type":"int"}],"type":"MessageRange"},{"id":"-399216813","predicate":"messageGroup","params":[{"name":"min_id","type":"int"},{"name":"max_id","type":"int"},{"name":"count","type":"int"},{"name":"date","type":"int"}],"type":"MessageGroup"},{"id":"-1139861572","predicate":"messages.channelMessages","params":[{"name":"flags","type":"#"},{"name":"pts","type":"int"},{"name":"count","type":"int"},{"name":"messages","type":"Vector"},{"name":"collapsed","type":"flags.0?Vector"},{"name":"chats","type":"Vector"},{"name":"users","type":"Vector"}],"type":"messages.Messages"},{"id":"-1781355374","predicate":"messageActionChannelCreate","params":[{"name":"title","type":"string"}],"type":"MessageAction"},{"id":"-352032773","predicate":"updateChannelTooLong","params":[{"name":"flags","type":"#"},{"name":"channel_id","type":"int"},{"name":"pts","type":"flags.0?int"}],"type":"Update"},{"id":"-1227598250","predicate":"updateChannel","params":[{"name":"channel_id","type":"int"}],"type":"Update"},{"id":"-1016324548","predicate":"updateChannelGroup","params":[{"name":"channel_id","type":"int"},{"name":"group","type":"MessageGroup"}],"type":"Update"},{"id":"1656358105","predicate":"updateNewChannelMessage","params":[{"name":"message","type":"Message"},{"name":"pts","type":"int"},{"name":"pts_count","type":"int"}],"type":"Update"},{"id":"1108669311","predicate":"updateReadChannelInbox","params":[{"name":"channel_id","type":"int"},{"name":"max_id","type":"int"}],"type":"Update"},{"id":"-1015733815","predicate":"updateDeleteChannelMessages","params":[{"name":"channel_id","type":"int"},{"name":"messages","type":"Vector"},{"name":"pts","type":"int"},{"name":"pts_count","type":"int"}],"type":"Update"},{"id":"-1734268085","predicate":"updateChannelMessageViews","params":[{"name":"channel_id","type":"int"},{"name":"id","type":"int"},{"name":"views","type":"int"}],"type":"Update"},{"id":"1041346555","predicate":"updates.channelDifferenceEmpty","params":[{"name":"flags","type":"#"},{"name":"final","type":"flags.0?true"},{"name":"pts","type":"int"},{"name":"timeout","type":"flags.1?int"}],"type":"updates.ChannelDifference"},{"id":"1578530374","predicate":"updates.channelDifferenceTooLong","params":[{"name":"flags","type":"#"},{"name":"final","type":"flags.0?true"},{"name":"pts","type":"int"},{"name":"timeout","type":"flags.1?int"},{"name":"top_message","type":"int"},{"name":"top_important_message","type":"int"},{"name":"read_inbox_max_id","type":"int"},{"name":"unread_count","type":"int"},{"name":"unread_important_count","type":"int"},{"name":"messages","type":"Vector"},{"name":"chats","type":"Vector"},{"name":"users","type":"Vector"}],"type":"updates.ChannelDifference"},{"id":"543450958","predicate":"updates.channelDifference","params":[{"name":"flags","type":"#"},{"name":"final","type":"flags.0?true"},{"name":"pts","type":"int"},{"name":"timeout","type":"flags.1?int"},{"name":"new_messages","type":"Vector"},{"name":"other_updates","type":"Vector"},{"name":"chats","type":"Vector"},{"name":"users","type":"Vector"}],"type":"updates.ChannelDifference"},{"id":"-1798033689","predicate":"channelMessagesFilterEmpty","params":[],"type":"ChannelMessagesFilter"},{"id":"-847783593","predicate":"channelMessagesFilter","params":[{"name":"flags","type":"#"},{"name":"important_only","type":"flags.0?true"},{"name":"exclude_new_messages","type":"flags.1?true"},{"name":"ranges","type":"Vector"}],"type":"ChannelMessagesFilter"},{"id":"-100588754","predicate":"channelMessagesFilterCollapsed","params":[],"type":"ChannelMessagesFilter"},{"id":"367766557","predicate":"channelParticipant","params":[{"name":"user_id","type":"int"},{"name":"date","type":"int"}],"type":"ChannelParticipant"},{"id":"-1557620115","predicate":"channelParticipantSelf","params":[{"name":"user_id","type":"int"},{"name":"inviter_id","type":"int"},{"name":"date","type":"int"}],"type":"ChannelParticipant"},{"id":"-1861910545","predicate":"channelParticipantModerator","params":[{"name":"user_id","type":"int"},{"name":"inviter_id","type":"int"},{"name":"date","type":"int"}],"type":"ChannelParticipant"},{"id":"-1743180447","predicate":"channelParticipantEditor","params":[{"name":"user_id","type":"int"},{"name":"inviter_id","type":"int"},{"name":"date","type":"int"}],"type":"ChannelParticipant"},{"id":"-1933187430","predicate":"channelParticipantKicked","params":[{"name":"user_id","type":"int"},{"name":"kicked_by","type":"int"},{"name":"date","type":"int"}],"type":"ChannelParticipant"},{"id":"-471670279","predicate":"channelParticipantCreator","params":[{"name":"user_id","type":"int"}],"type":"ChannelParticipant"},{"id":"-566281095","predicate":"channelParticipantsRecent","params":[],"type":"ChannelParticipantsFilter"},{"id":"-1268741783","predicate":"channelParticipantsAdmins","params":[],"type":"ChannelParticipantsFilter"},{"id":"1010285434","predicate":"channelParticipantsKicked","params":[],"type":"ChannelParticipantsFilter"},{"id":"-1299865402","predicate":"channelRoleEmpty","params":[],"type":"ChannelParticipantRole"},{"id":"-1776756363","predicate":"channelRoleModerator","params":[],"type":"ChannelParticipantRole"},{"id":"-2113143156","predicate":"channelRoleEditor","params":[],"type":"ChannelParticipantRole"},{"id":"-177282392","predicate":"channels.channelParticipants","params":[{"name":"count","type":"int"},{"name":"participants","type":"Vector"},{"name":"users","type":"Vector"}],"type":"channels.ChannelParticipants"},{"id":"-791039645","predicate":"channels.channelParticipant","params":[{"name":"participant","type":"ChannelParticipant"},{"name":"users","type":"Vector"}],"type":"channels.ChannelParticipant"},{"id":"-636267638","predicate":"chatParticipantCreator","params":[{"name":"user_id","type":"int"}],"type":"ChatParticipant"},{"id":"-489233354","predicate":"chatParticipantAdmin","params":[{"name":"user_id","type":"int"},{"name":"inviter_id","type":"int"},{"name":"date","type":"int"}],"type":"ChatParticipant"},{"id":"1855224129","predicate":"updateChatAdmins","params":[{"name":"chat_id","type":"int"},{"name":"enabled","type":"Bool"},{"name":"version","type":"int"}],"type":"Update"},{"id":"-1232070311","predicate":"updateChatParticipantAdmin","params":[{"name":"chat_id","type":"int"},{"name":"user_id","type":"int"},{"name":"is_admin","type":"Bool"},{"name":"version","type":"int"}],"type":"Update"},{"id":"1371385889","predicate":"messageActionChatMigrateTo","params":[{"name":"channel_id","type":"int"}],"type":"MessageAction"},{"id":"-1336546578","predicate":"messageActionChannelMigrateFrom","params":[{"name":"title","type":"string"},{"name":"chat_id","type":"int"}],"type":"MessageAction"},{"id":"-1328445861","predicate":"channelParticipantsBots","params":[],"type":"ChannelParticipantsFilter"},{"id":"-236044656","predicate":"help.termsOfService","params":[{"name":"text","type":"string"}],"type":"help.TermsOfService"},{"id":"1753886890","predicate":"updateNewStickerSet","params":[{"name":"stickerset","type":"messages.StickerSet"}],"type":"Update"},{"id":"-253774767","predicate":"updateStickerSetsOrder","params":[{"name":"order","type":"Vector"}],"type":"Update"},{"id":"1135492588","predicate":"updateStickerSets","params":[],"type":"Update"},{"id":"372165663","predicate":"foundGif","params":[{"name":"url","type":"string"},{"name":"thumb_url","type":"string"},{"name":"content_url","type":"string"},{"name":"content_type","type":"string"},{"name":"w","type":"int"},{"name":"h","type":"int"}],"type":"FoundGif"},{"id":"-1670052855","predicate":"foundGifCached","params":[{"name":"url","type":"string"},{"name":"photo","type":"Photo"},{"name":"document","type":"Document"}],"type":"FoundGif"},{"id":"1212395773","predicate":"inputMediaGifExternal","params":[{"name":"url","type":"string"},{"name":"q","type":"string"}],"type":"InputMedia"},{"id":"1158290442","predicate":"messages.foundGifs","params":[{"name":"next_offset","type":"int"},{"name":"results","type":"Vector"}],"type":"messages.FoundGifs"},{"id":"-402498398","predicate":"messages.savedGifsNotModified","params":[],"type":"messages.SavedGifs"},{"id":"772213157","predicate":"messages.savedGifs","params":[{"name":"hash","type":"int"},{"name":"gifs","type":"Vector"}],"type":"messages.SavedGifs"},{"id":"-1821035490","predicate":"updateSavedGifs","params":[],"type":"Update"},{"id":"691006739","predicate":"inputBotInlineMessageMediaAuto","params":[{"name":"flags","type":"#"},{"name":"caption","type":"string"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"}],"type":"InputBotInlineMessage"},{"id":"1036876423","predicate":"inputBotInlineMessageText","params":[{"name":"flags","type":"#"},{"name":"no_webpage","type":"flags.0?true"},{"name":"message","type":"string"},{"name":"entities","type":"flags.1?Vector"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"}],"type":"InputBotInlineMessage"},{"id":"750510426","predicate":"inputBotInlineResult","params":[{"name":"flags","type":"#"},{"name":"id","type":"string"},{"name":"type","type":"string"},{"name":"title","type":"flags.1?string"},{"name":"description","type":"flags.2?string"},{"name":"url","type":"flags.3?string"},{"name":"thumb_url","type":"flags.4?string"},{"name":"content_url","type":"flags.5?string"},{"name":"content_type","type":"flags.5?string"},{"name":"w","type":"flags.6?int"},{"name":"h","type":"flags.6?int"},{"name":"duration","type":"flags.7?int"},{"name":"send_message","type":"InputBotInlineMessage"}],"type":"InputBotInlineResult"},{"id":"175419739","predicate":"botInlineMessageMediaAuto","params":[{"name":"flags","type":"#"},{"name":"caption","type":"string"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"}],"type":"BotInlineMessage"},{"id":"-1937807902","predicate":"botInlineMessageText","params":[{"name":"flags","type":"#"},{"name":"no_webpage","type":"flags.0?true"},{"name":"message","type":"string"},{"name":"entities","type":"flags.1?Vector"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"}],"type":"BotInlineMessage"},{"id":"-1679053127","predicate":"botInlineResult","params":[{"name":"flags","type":"#"},{"name":"id","type":"string"},{"name":"type","type":"string"},{"name":"title","type":"flags.1?string"},{"name":"description","type":"flags.2?string"},{"name":"url","type":"flags.3?string"},{"name":"thumb_url","type":"flags.4?string"},{"name":"content_url","type":"flags.5?string"},{"name":"content_type","type":"flags.5?string"},{"name":"w","type":"flags.6?int"},{"name":"h","type":"flags.6?int"},{"name":"duration","type":"flags.7?int"},{"name":"send_message","type":"BotInlineMessage"}],"type":"BotInlineResult"},{"id":"627509670","predicate":"messages.botResults","params":[{"name":"flags","type":"#"},{"name":"gallery","type":"flags.0?true"},{"name":"query_id","type":"long"},{"name":"next_offset","type":"flags.1?string"},{"name":"switch_pm","type":"flags.2?InlineBotSwitchPM"},{"name":"results","type":"Vector"}],"type":"messages.BotResults"},{"id":"1417832080","predicate":"updateBotInlineQuery","params":[{"name":"flags","type":"#"},{"name":"query_id","type":"long"},{"name":"user_id","type":"int"},{"name":"query","type":"string"},{"name":"geo","type":"flags.0?GeoPoint"},{"name":"offset","type":"string"}],"type":"Update"},{"id":"239663460","predicate":"updateBotInlineSend","params":[{"name":"flags","type":"#"},{"name":"user_id","type":"int"},{"name":"query","type":"string"},{"name":"geo","type":"flags.0?GeoPoint"},{"name":"id","type":"string"},{"name":"msg_id","type":"flags.1?InputBotInlineMessageID"}],"type":"Update"},{"id":"1358283666","predicate":"inputMessagesFilterVoice","params":[],"type":"MessagesFilter"},{"id":"928101534","predicate":"inputMessagesFilterMusic","params":[],"type":"MessagesFilter"},{"id":"-1107622874","predicate":"inputPrivacyKeyChatInvite","params":[],"type":"InputPrivacyKey"},{"id":"1343122938","predicate":"privacyKeyChatInvite","params":[],"type":"PrivacyKey"},{"id":"524838915","predicate":"exportedMessageLink","params":[{"name":"link","type":"string"}],"type":"ExportedMessageLink"},{"id":"-947462709","predicate":"messageFwdHeader","params":[{"name":"flags","type":"#"},{"name":"from_id","type":"flags.0?int"},{"name":"date","type":"int"},{"name":"channel_id","type":"flags.1?int"},{"name":"channel_post","type":"flags.2?int"}],"type":"MessageFwdHeader"},{"id":"457133559","predicate":"updateEditChannelMessage","params":[{"name":"message","type":"Message"},{"name":"pts","type":"int"},{"name":"pts_count","type":"int"}],"type":"Update"},{"id":"-1738988427","predicate":"updateChannelPinnedMessage","params":[{"name":"channel_id","type":"int"},{"name":"id","type":"int"}],"type":"Update"},{"id":"-1799538451","predicate":"messageActionPinMessage","params":[],"type":"MessageAction"},{"id":"1923290508","predicate":"auth.codeTypeSms","params":[],"type":"auth.CodeType"},{"id":"1948046307","predicate":"auth.codeTypeCall","params":[],"type":"auth.CodeType"},{"id":"577556219","predicate":"auth.codeTypeFlashCall","params":[],"type":"auth.CodeType"},{"id":"1035688326","predicate":"auth.sentCodeTypeApp","params":[{"name":"length","type":"int"}],"type":"auth.SentCodeType"},{"id":"-1073693790","predicate":"auth.sentCodeTypeSms","params":[{"name":"length","type":"int"}],"type":"auth.SentCodeType"},{"id":"1398007207","predicate":"auth.sentCodeTypeCall","params":[{"name":"length","type":"int"}],"type":"auth.SentCodeType"},{"id":"-1425815847","predicate":"auth.sentCodeTypeFlashCall","params":[{"name":"pattern","type":"string"}],"type":"auth.SentCodeType"},{"id":"629866245","predicate":"keyboardButtonUrl","params":[{"name":"text","type":"string"},{"name":"url","type":"string"}],"type":"KeyboardButton"},{"id":"1748655686","predicate":"keyboardButtonCallback","params":[{"name":"text","type":"string"},{"name":"data","type":"bytes"}],"type":"KeyboardButton"},{"id":"-1318425559","predicate":"keyboardButtonRequestPhone","params":[{"name":"text","type":"string"}],"type":"KeyboardButton"},{"id":"-59151553","predicate":"keyboardButtonRequestGeoLocation","params":[{"name":"text","type":"string"}],"type":"KeyboardButton"},{"id":"-367298028","predicate":"keyboardButtonSwitchInline","params":[{"name":"text","type":"string"},{"name":"query","type":"string"}],"type":"KeyboardButton"},{"id":"1218642516","predicate":"replyInlineMarkup","params":[{"name":"rows","type":"Vector"}],"type":"ReplyMarkup"},{"id":"308605382","predicate":"messages.botCallbackAnswer","params":[{"name":"flags","type":"#"},{"name":"alert","type":"flags.1?true"},{"name":"message","type":"flags.0?string"}],"type":"messages.BotCallbackAnswer"},{"id":"-1500747636","predicate":"updateBotCallbackQuery","params":[{"name":"query_id","type":"long"},{"name":"user_id","type":"int"},{"name":"peer","type":"Peer"},{"name":"msg_id","type":"int"},{"name":"data","type":"bytes"}],"type":"Update"},{"id":"649453030","predicate":"messages.messageEditData","params":[{"name":"flags","type":"#"},{"name":"caption","type":"flags.0?true"}],"type":"messages.MessageEditData"},{"id":"-469536605","predicate":"updateEditMessage","params":[{"name":"message","type":"Message"},{"name":"pts","type":"int"},{"name":"pts_count","type":"int"}],"type":"Update"},{"id":"-190472735","predicate":"inputBotInlineMessageMediaGeo","params":[{"name":"flags","type":"#"},{"name":"geo_point","type":"InputGeoPoint"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"}],"type":"InputBotInlineMessage"},{"id":"-1431327288","predicate":"inputBotInlineMessageMediaVenue","params":[{"name":"flags","type":"#"},{"name":"geo_point","type":"InputGeoPoint"},{"name":"title","type":"string"},{"name":"address","type":"string"},{"name":"provider","type":"string"},{"name":"venue_id","type":"string"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"}],"type":"InputBotInlineMessage"},{"id":"766443943","predicate":"inputBotInlineMessageMediaContact","params":[{"name":"flags","type":"#"},{"name":"phone_number","type":"string"},{"name":"first_name","type":"string"},{"name":"last_name","type":"string"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"}],"type":"InputBotInlineMessage"},{"id":"982505656","predicate":"botInlineMessageMediaGeo","params":[{"name":"flags","type":"#"},{"name":"geo","type":"GeoPoint"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"}],"type":"BotInlineMessage"},{"id":"1130767150","predicate":"botInlineMessageMediaVenue","params":[{"name":"flags","type":"#"},{"name":"geo","type":"GeoPoint"},{"name":"title","type":"string"},{"name":"address","type":"string"},{"name":"provider","type":"string"},{"name":"venue_id","type":"string"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"}],"type":"BotInlineMessage"},{"id":"904770772","predicate":"botInlineMessageMediaContact","params":[{"name":"flags","type":"#"},{"name":"phone_number","type":"string"},{"name":"first_name","type":"string"},{"name":"last_name","type":"string"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"}],"type":"BotInlineMessage"},{"id":"-1462213465","predicate":"inputBotInlineResultPhoto","params":[{"name":"id","type":"string"},{"name":"type","type":"string"},{"name":"photo","type":"InputPhoto"},{"name":"send_message","type":"InputBotInlineMessage"}],"type":"InputBotInlineResult"},{"id":"-459324","predicate":"inputBotInlineResultDocument","params":[{"name":"flags","type":"#"},{"name":"id","type":"string"},{"name":"type","type":"string"},{"name":"title","type":"flags.1?string"},{"name":"description","type":"flags.2?string"},{"name":"document","type":"InputDocument"},{"name":"send_message","type":"InputBotInlineMessage"}],"type":"InputBotInlineResult"},{"id":"400266251","predicate":"botInlineMediaResult","params":[{"name":"flags","type":"#"},{"name":"id","type":"string"},{"name":"type","type":"string"},{"name":"photo","type":"flags.0?Photo"},{"name":"document","type":"flags.1?Document"},{"name":"title","type":"flags.2?string"},{"name":"description","type":"flags.3?string"},{"name":"send_message","type":"BotInlineMessage"}],"type":"BotInlineResult"},{"id":"-1995686519","predicate":"inputBotInlineMessageID","params":[{"name":"dc_id","type":"int"},{"name":"id","type":"long"},{"name":"access_hash","type":"long"}],"type":"InputBotInlineMessageID"},{"id":"750622127","predicate":"updateInlineBotCallbackQuery","params":[{"name":"query_id","type":"long"},{"name":"user_id","type":"int"},{"name":"msg_id","type":"InputBotInlineMessageID"},{"name":"data","type":"bytes"}],"type":"Update"},{"id":"1008755359","predicate":"inlineBotSwitchPM","params":[{"name":"text","type":"string"},{"name":"start_param","type":"string"}],"type":"InlineBotSwitchPM"}],"methods":[{"id":"-878758099","method":"invokeAfterMsg","params":[{"name":"msg_id","type":"long"},{"name":"query","type":"!X"}],"type":"X"},{"id":"1036301552","method":"invokeAfterMsgs","params":[{"name":"msg_ids","type":"Vector"},{"name":"query","type":"!X"}],"type":"X"},{"id":"1877286395","method":"auth.checkPhone","params":[{"name":"phone_number","type":"string"}],"type":"auth.CheckedPhone"},{"id":"-855805745","method":"auth.sendCode","params":[{"name":"flags","type":"#"},{"name":"allow_flashcall","type":"flags.0?true"},{"name":"phone_number","type":"string"},{"name":"current_number","type":"flags.0?Bool"},{"name":"api_id","type":"int"},{"name":"api_hash","type":"string"},{"name":"lang_code","type":"string"}],"type":"auth.SentCode"},{"id":"453408308","method":"auth.signUp","params":[{"name":"phone_number","type":"string"},{"name":"phone_code_hash","type":"string"},{"name":"phone_code","type":"string"},{"name":"first_name","type":"string"},{"name":"last_name","type":"string"}],"type":"auth.Authorization"},{"id":"-1126886015","method":"auth.signIn","params":[{"name":"phone_number","type":"string"},{"name":"phone_code_hash","type":"string"},{"name":"phone_code","type":"string"}],"type":"auth.Authorization"},{"id":"1461180992","method":"auth.logOut","params":[],"type":"Bool"},{"id":"-1616179942","method":"auth.resetAuthorizations","params":[],"type":"Bool"},{"id":"1998331287","method":"auth.sendInvites","params":[{"name":"phone_numbers","type":"Vector"},{"name":"message","type":"string"}],"type":"Bool"},{"id":"-440401971","method":"auth.exportAuthorization","params":[{"name":"dc_id","type":"int"}],"type":"auth.ExportedAuthorization"},{"id":"-470837741","method":"auth.importAuthorization","params":[{"name":"id","type":"int"},{"name":"bytes","type":"bytes"}],"type":"auth.Authorization"},{"id":"-841733627","method":"auth.bindTempAuthKey","params":[{"name":"perm_auth_key_id","type":"long"},{"name":"nonce","type":"long"},{"name":"expires_at","type":"int"},{"name":"encrypted_message","type":"bytes"}],"type":"Bool"},{"id":"1147957548","method":"account.registerDevice","params":[{"name":"token_type","type":"int"},{"name":"token","type":"string"},{"name":"device_model","type":"string"},{"name":"system_version","type":"string"},{"name":"app_version","type":"string"},{"name":"app_sandbox","type":"Bool"},{"name":"lang_code","type":"string"}],"type":"Bool"},{"id":"1707432768","method":"account.unregisterDevice","params":[{"name":"token_type","type":"int"},{"name":"token","type":"string"}],"type":"Bool"},{"id":"-2067899501","method":"account.updateNotifySettings","params":[{"name":"peer","type":"InputNotifyPeer"},{"name":"settings","type":"InputPeerNotifySettings"}],"type":"Bool"},{"id":"313765169","method":"account.getNotifySettings","params":[{"name":"peer","type":"InputNotifyPeer"}],"type":"PeerNotifySettings"},{"id":"-612493497","method":"account.resetNotifySettings","params":[],"type":"Bool"},{"id":"2018596725","method":"account.updateProfile","params":[{"name":"flags","type":"#"},{"name":"first_name","type":"flags.0?string"},{"name":"last_name","type":"flags.1?string"},{"name":"about","type":"flags.2?string"}],"type":"User"},{"id":"1713919532","method":"account.updateStatus","params":[{"name":"offline","type":"Bool"}],"type":"Bool"},{"id":"-1068696894","method":"account.getWallPapers","params":[],"type":"Vector"},{"id":"-1374118561","method":"account.reportPeer","params":[{"name":"peer","type":"InputPeer"},{"name":"reason","type":"ReportReason"}],"type":"Bool"},{"id":"227648840","method":"users.getUsers","params":[{"name":"id","type":"Vector"}],"type":"Vector"},{"id":"-902781519","method":"users.getFullUser","params":[{"name":"id","type":"InputUser"}],"type":"UserFull"},{"id":"-995929106","method":"contacts.getStatuses","params":[],"type":"Vector"},{"id":"583445000","method":"contacts.getContacts","params":[{"name":"hash","type":"string"}],"type":"contacts.Contacts"},{"id":"-634342611","method":"contacts.importContacts","params":[{"name":"contacts","type":"Vector"},{"name":"replace","type":"Bool"}],"type":"contacts.ImportedContacts"},{"id":"-1902823612","method":"contacts.deleteContact","params":[{"name":"id","type":"InputUser"}],"type":"contacts.Link"},{"id":"1504393374","method":"contacts.deleteContacts","params":[{"name":"id","type":"Vector"}],"type":"Bool"},{"id":"858475004","method":"contacts.block","params":[{"name":"id","type":"InputUser"}],"type":"Bool"},{"id":"-448724803","method":"contacts.unblock","params":[{"name":"id","type":"InputUser"}],"type":"Bool"},{"id":"-176409329","method":"contacts.getBlocked","params":[{"name":"offset","type":"int"},{"name":"limit","type":"int"}],"type":"contacts.Blocked"},{"id":"-2065352905","method":"contacts.exportCard","params":[],"type":"Vector"},{"id":"1340184318","method":"contacts.importCard","params":[{"name":"export_card","type":"Vector"}],"type":"User"},{"id":"1109588596","method":"messages.getMessages","params":[{"name":"id","type":"Vector"}],"type":"messages.Messages"},{"id":"1799878989","method":"messages.getDialogs","params":[{"name":"offset_date","type":"int"},{"name":"offset_id","type":"int"},{"name":"offset_peer","type":"InputPeer"},{"name":"limit","type":"int"}],"type":"messages.Dialogs"},{"id":"-1347868602","method":"messages.getHistory","params":[{"name":"peer","type":"InputPeer"},{"name":"offset_id","type":"int"},{"name":"offset_date","type":"int"},{"name":"add_offset","type":"int"},{"name":"limit","type":"int"},{"name":"max_id","type":"int"},{"name":"min_id","type":"int"}],"type":"messages.Messages"},{"id":"-732523960","method":"messages.search","params":[{"name":"flags","type":"#"},{"name":"important_only","type":"flags.0?true"},{"name":"peer","type":"InputPeer"},{"name":"q","type":"string"},{"name":"filter","type":"MessagesFilter"},{"name":"min_date","type":"int"},{"name":"max_date","type":"int"},{"name":"offset","type":"int"},{"name":"max_id","type":"int"},{"name":"limit","type":"int"}],"type":"messages.Messages"},{"id":"238054714","method":"messages.readHistory","params":[{"name":"peer","type":"InputPeer"},{"name":"max_id","type":"int"}],"type":"messages.AffectedMessages"},{"id":"-1212072999","method":"messages.deleteHistory","params":[{"name":"peer","type":"InputPeer"},{"name":"max_id","type":"int"}],"type":"messages.AffectedHistory"},{"id":"-1510897371","method":"messages.deleteMessages","params":[{"name":"id","type":"Vector"}],"type":"messages.AffectedMessages"},{"id":"94983360","method":"messages.receivedMessages","params":[{"name":"max_id","type":"int"}],"type":"Vector"},{"id":"-1551737264","method":"messages.setTyping","params":[{"name":"peer","type":"InputPeer"},{"name":"action","type":"SendMessageAction"}],"type":"Bool"},{"id":"-91733382","method":"messages.sendMessage","params":[{"name":"flags","type":"#"},{"name":"no_webpage","type":"flags.1?true"},{"name":"broadcast","type":"flags.4?true"},{"name":"silent","type":"flags.5?true"},{"name":"background","type":"flags.6?true"},{"name":"peer","type":"InputPeer"},{"name":"reply_to_msg_id","type":"flags.0?int"},{"name":"message","type":"string"},{"name":"random_id","type":"long"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"},{"name":"entities","type":"flags.3?Vector"}],"type":"Updates"},{"id":"-923703407","method":"messages.sendMedia","params":[{"name":"flags","type":"#"},{"name":"broadcast","type":"flags.4?true"},{"name":"silent","type":"flags.5?true"},{"name":"background","type":"flags.6?true"},{"name":"peer","type":"InputPeer"},{"name":"reply_to_msg_id","type":"flags.0?int"},{"name":"media","type":"InputMedia"},{"name":"random_id","type":"long"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"}],"type":"Updates"},{"id":"1888354709","method":"messages.forwardMessages","params":[{"name":"flags","type":"#"},{"name":"broadcast","type":"flags.4?true"},{"name":"silent","type":"flags.5?true"},{"name":"background","type":"flags.6?true"},{"name":"from_peer","type":"InputPeer"},{"name":"id","type":"Vector"},{"name":"random_id","type":"Vector"},{"name":"to_peer","type":"InputPeer"}],"type":"Updates"},{"id":"-820669733","method":"messages.reportSpam","params":[{"name":"peer","type":"InputPeer"}],"type":"Bool"},{"id":"-1460572005","method":"messages.hideReportSpam","params":[{"name":"peer","type":"InputPeer"}],"type":"Bool"},{"id":"913498268","method":"messages.getPeerSettings","params":[{"name":"peer","type":"InputPeer"}],"type":"PeerSettings"},{"id":"1013621127","method":"messages.getChats","params":[{"name":"id","type":"Vector"}],"type":"messages.Chats"},{"id":"998448230","method":"messages.getFullChat","params":[{"name":"chat_id","type":"int"}],"type":"messages.ChatFull"},{"id":"-599447467","method":"messages.editChatTitle","params":[{"name":"chat_id","type":"int"},{"name":"title","type":"string"}],"type":"Updates"},{"id":"-900957736","method":"messages.editChatPhoto","params":[{"name":"chat_id","type":"int"},{"name":"photo","type":"InputChatPhoto"}],"type":"Updates"},{"id":"-106911223","method":"messages.addChatUser","params":[{"name":"chat_id","type":"int"},{"name":"user_id","type":"InputUser"},{"name":"fwd_limit","type":"int"}],"type":"Updates"},{"id":"-530505962","method":"messages.deleteChatUser","params":[{"name":"chat_id","type":"int"},{"name":"user_id","type":"InputUser"}],"type":"Updates"},{"id":"164303470","method":"messages.createChat","params":[{"name":"users","type":"Vector"},{"name":"title","type":"string"}],"type":"Updates"},{"id":"-304838614","method":"updates.getState","params":[],"type":"updates.State"},{"id":"168039573","method":"updates.getDifference","params":[{"name":"pts","type":"int"},{"name":"date","type":"int"},{"name":"qts","type":"int"}],"type":"updates.Difference"},{"id":"-285902432","method":"photos.updateProfilePhoto","params":[{"name":"id","type":"InputPhoto"},{"name":"crop","type":"InputPhotoCrop"}],"type":"UserProfilePhoto"},{"id":"-720397176","method":"photos.uploadProfilePhoto","params":[{"name":"file","type":"InputFile"},{"name":"caption","type":"string"},{"name":"geo_point","type":"InputGeoPoint"},{"name":"crop","type":"InputPhotoCrop"}],"type":"photos.Photo"},{"id":"-2016444625","method":"photos.deletePhotos","params":[{"name":"id","type":"Vector"}],"type":"Vector"},{"id":"-1291540959","method":"upload.saveFilePart","params":[{"name":"file_id","type":"long"},{"name":"file_part","type":"int"},{"name":"bytes","type":"bytes"}],"type":"Bool"},{"id":"-475607115","method":"upload.getFile","params":[{"name":"location","type":"InputFileLocation"},{"name":"offset","type":"int"},{"name":"limit","type":"int"}],"type":"upload.File"},{"id":"-990308245","method":"help.getConfig","params":[],"type":"Config"},{"id":"531836966","method":"help.getNearestDc","params":[],"type":"NearestDc"},{"id":"-938300290","method":"help.getAppUpdate","params":[{"name":"device_model","type":"string"},{"name":"system_version","type":"string"},{"name":"app_version","type":"string"},{"name":"lang_code","type":"string"}],"type":"help.AppUpdate"},{"id":"1862465352","method":"help.saveAppLog","params":[{"name":"events","type":"Vector"}],"type":"Bool"},{"id":"-1532407418","method":"help.getInviteText","params":[{"name":"lang_code","type":"string"}],"type":"help.InviteText"},{"id":"-1848823128","method":"photos.getUserPhotos","params":[{"name":"user_id","type":"InputUser"},{"name":"offset","type":"int"},{"name":"max_id","type":"long"},{"name":"limit","type":"int"}],"type":"photos.Photos"},{"id":"865483769","method":"messages.forwardMessage","params":[{"name":"peer","type":"InputPeer"},{"name":"id","type":"int"},{"name":"random_id","type":"long"}],"type":"Updates"},{"id":"-1082919718","method":"messages.sendBroadcast","params":[{"name":"contacts","type":"Vector"},{"name":"random_id","type":"Vector"},{"name":"message","type":"string"},{"name":"media","type":"InputMedia"}],"type":"Updates"},{"id":"651135312","method":"messages.getDhConfig","params":[{"name":"version","type":"int"},{"name":"random_length","type":"int"}],"type":"messages.DhConfig"},{"id":"-162681021","method":"messages.requestEncryption","params":[{"name":"user_id","type":"InputUser"},{"name":"random_id","type":"int"},{"name":"g_a","type":"bytes"}],"type":"EncryptedChat"},{"id":"1035731989","method":"messages.acceptEncryption","params":[{"name":"peer","type":"InputEncryptedChat"},{"name":"g_b","type":"bytes"},{"name":"key_fingerprint","type":"long"}],"type":"EncryptedChat"},{"id":"-304536635","method":"messages.discardEncryption","params":[{"name":"chat_id","type":"int"}],"type":"Bool"},{"id":"2031374829","method":"messages.setEncryptedTyping","params":[{"name":"peer","type":"InputEncryptedChat"},{"name":"typing","type":"Bool"}],"type":"Bool"},{"id":"2135648522","method":"messages.readEncryptedHistory","params":[{"name":"peer","type":"InputEncryptedChat"},{"name":"max_date","type":"int"}],"type":"Bool"},{"id":"-1451792525","method":"messages.sendEncrypted","params":[{"name":"peer","type":"InputEncryptedChat"},{"name":"random_id","type":"long"},{"name":"data","type":"bytes"}],"type":"messages.SentEncryptedMessage"},{"id":"-1701831834","method":"messages.sendEncryptedFile","params":[{"name":"peer","type":"InputEncryptedChat"},{"name":"random_id","type":"long"},{"name":"data","type":"bytes"},{"name":"file","type":"InputEncryptedFile"}],"type":"messages.SentEncryptedMessage"},{"id":"852769188","method":"messages.sendEncryptedService","params":[{"name":"peer","type":"InputEncryptedChat"},{"name":"random_id","type":"long"},{"name":"data","type":"bytes"}],"type":"messages.SentEncryptedMessage"},{"id":"1436924774","method":"messages.receivedQueue","params":[{"name":"max_qts","type":"int"}],"type":"Vector"},{"id":"-562337987","method":"upload.saveBigFilePart","params":[{"name":"file_id","type":"long"},{"name":"file_part","type":"int"},{"name":"file_total_parts","type":"int"},{"name":"bytes","type":"bytes"}],"type":"Bool"},{"id":"1769565673","method":"initConnection","params":[{"name":"api_id","type":"int"},{"name":"device_model","type":"string"},{"name":"system_version","type":"string"},{"name":"app_version","type":"string"},{"name":"lang_code","type":"string"},{"name":"query","type":"!X"}],"type":"X"},{"id":"-1663104819","method":"help.getSupport","params":[],"type":"help.Support"},{"id":"916930423","method":"messages.readMessageContents","params":[{"name":"id","type":"Vector"}],"type":"messages.AffectedMessages"},{"id":"655677548","method":"account.checkUsername","params":[{"name":"username","type":"string"}],"type":"Bool"},{"id":"1040964988","method":"account.updateUsername","params":[{"name":"username","type":"string"}],"type":"User"},{"id":"301470424","method":"contacts.search","params":[{"name":"q","type":"string"},{"name":"limit","type":"int"}],"type":"contacts.Found"},{"id":"-623130288","method":"account.getPrivacy","params":[{"name":"key","type":"InputPrivacyKey"}],"type":"account.PrivacyRules"},{"id":"-906486552","method":"account.setPrivacy","params":[{"name":"key","type":"InputPrivacyKey"},{"name":"rules","type":"Vector"}],"type":"account.PrivacyRules"},{"id":"1099779595","method":"account.deleteAccount","params":[{"name":"reason","type":"string"}],"type":"Bool"},{"id":"150761757","method":"account.getAccountTTL","params":[],"type":"AccountDaysTTL"},{"id":"608323678","method":"account.setAccountTTL","params":[{"name":"ttl","type":"AccountDaysTTL"}],"type":"Bool"},{"id":"-627372787","method":"invokeWithLayer","params":[{"name":"layer","type":"int"},{"name":"query","type":"!X"}],"type":"X"},{"id":"-113456221","method":"contacts.resolveUsername","params":[{"name":"username","type":"string"}],"type":"contacts.ResolvedPeer"},{"id":"149257707","method":"account.sendChangePhoneCode","params":[{"name":"flags","type":"#"},{"name":"allow_flashcall","type":"flags.0?true"},{"name":"phone_number","type":"string"},{"name":"current_number","type":"flags.0?Bool"}],"type":"auth.SentCode"},{"id":"1891839707","method":"account.changePhone","params":[{"name":"phone_number","type":"string"},{"name":"phone_code_hash","type":"string"},{"name":"phone_code","type":"string"}],"type":"User"},{"id":"-1373446075","method":"messages.getStickers","params":[{"name":"emoticon","type":"string"},{"name":"hash","type":"string"}],"type":"messages.Stickers"},{"id":"479598769","method":"messages.getAllStickers","params":[{"name":"hash","type":"int"}],"type":"messages.AllStickers"},{"id":"954152242","method":"account.updateDeviceLocked","params":[{"name":"period","type":"int"}],"type":"Bool"},{"id":"1738800940","method":"auth.importBotAuthorization","params":[{"name":"flags","type":"int"},{"name":"api_id","type":"int"},{"name":"api_hash","type":"string"},{"name":"bot_auth_token","type":"string"}],"type":"auth.Authorization"},{"id":"623001124","method":"messages.getWebPagePreview","params":[{"name":"message","type":"string"}],"type":"MessageMedia"},{"id":"-484392616","method":"account.getAuthorizations","params":[],"type":"account.Authorizations"},{"id":"-545786948","method":"account.resetAuthorization","params":[{"name":"hash","type":"long"}],"type":"Bool"},{"id":"1418342645","method":"account.getPassword","params":[],"type":"account.Password"},{"id":"-1131605573","method":"account.getPasswordSettings","params":[{"name":"current_password_hash","type":"bytes"}],"type":"account.PasswordSettings"},{"id":"-92517498","method":"account.updatePasswordSettings","params":[{"name":"current_password_hash","type":"bytes"},{"name":"new_settings","type":"account.PasswordInputSettings"}],"type":"Bool"},{"id":"174260510","method":"auth.checkPassword","params":[{"name":"password_hash","type":"bytes"}],"type":"auth.Authorization"},{"id":"-661144474","method":"auth.requestPasswordRecovery","params":[],"type":"auth.PasswordRecovery"},{"id":"1319464594","method":"auth.recoverPassword","params":[{"name":"code","type":"string"}],"type":"auth.Authorization"},{"id":"-1080796745","method":"invokeWithoutUpdates","params":[{"name":"query","type":"!X"}],"type":"X"},{"id":"2106086025","method":"messages.exportChatInvite","params":[{"name":"chat_id","type":"int"}],"type":"ExportedChatInvite"},{"id":"1051570619","method":"messages.checkChatInvite","params":[{"name":"hash","type":"string"}],"type":"ChatInvite"},{"id":"1817183516","method":"messages.importChatInvite","params":[{"name":"hash","type":"string"}],"type":"Updates"},{"id":"639215886","method":"messages.getStickerSet","params":[{"name":"stickerset","type":"InputStickerSet"}],"type":"messages.StickerSet"},{"id":"2066793382","method":"messages.installStickerSet","params":[{"name":"stickerset","type":"InputStickerSet"},{"name":"disabled","type":"Bool"}],"type":"Bool"},{"id":"-110209570","method":"messages.uninstallStickerSet","params":[{"name":"stickerset","type":"InputStickerSet"}],"type":"Bool"},{"id":"-421563528","method":"messages.startBot","params":[{"name":"bot","type":"InputUser"},{"name":"peer","type":"InputPeer"},{"name":"random_id","type":"long"},{"name":"start_param","type":"string"}],"type":"Updates"},{"id":"1537966002","method":"help.getAppChangelog","params":[{"name":"device_model","type":"string"},{"name":"system_version","type":"string"},{"name":"app_version","type":"string"},{"name":"lang_code","type":"string"}],"type":"help.AppChangelog"},{"id":"-993483427","method":"messages.getMessagesViews","params":[{"name":"peer","type":"InputPeer"},{"name":"id","type":"Vector"},{"name":"increment","type":"Bool"}],"type":"Vector"},{"id":"-1445735863","method":"channels.getDialogs","params":[{"name":"offset","type":"int"},{"name":"limit","type":"int"}],"type":"messages.Dialogs"},{"id":"-1891021902","method":"channels.getImportantHistory","params":[{"name":"channel","type":"InputChannel"},{"name":"offset_id","type":"int"},{"name":"offset_date","type":"int"},{"name":"add_offset","type":"int"},{"name":"limit","type":"int"},{"name":"max_id","type":"int"},{"name":"min_id","type":"int"}],"type":"messages.Messages"},{"id":"-871347913","method":"channels.readHistory","params":[{"name":"channel","type":"InputChannel"},{"name":"max_id","type":"int"}],"type":"Bool"},{"id":"-2067661490","method":"channels.deleteMessages","params":[{"name":"channel","type":"InputChannel"},{"name":"id","type":"Vector"}],"type":"messages.AffectedMessages"},{"id":"-787622117","method":"channels.deleteUserHistory","params":[{"name":"channel","type":"InputChannel"},{"name":"user_id","type":"InputUser"}],"type":"messages.AffectedHistory"},{"id":"-32999408","method":"channels.reportSpam","params":[{"name":"channel","type":"InputChannel"},{"name":"user_id","type":"InputUser"},{"name":"id","type":"Vector"}],"type":"Bool"},{"id":"-1814580409","method":"channels.getMessages","params":[{"name":"channel","type":"InputChannel"},{"name":"id","type":"Vector"}],"type":"messages.Messages"},{"id":"618237842","method":"channels.getParticipants","params":[{"name":"channel","type":"InputChannel"},{"name":"filter","type":"ChannelParticipantsFilter"},{"name":"offset","type":"int"},{"name":"limit","type":"int"}],"type":"channels.ChannelParticipants"},{"id":"1416484774","method":"channels.getParticipant","params":[{"name":"channel","type":"InputChannel"},{"name":"user_id","type":"InputUser"}],"type":"channels.ChannelParticipant"},{"id":"176122811","method":"channels.getChannels","params":[{"name":"id","type":"Vector"}],"type":"messages.Chats"},{"id":"141781513","method":"channels.getFullChannel","params":[{"name":"channel","type":"InputChannel"}],"type":"messages.ChatFull"},{"id":"-192332417","method":"channels.createChannel","params":[{"name":"flags","type":"#"},{"name":"broadcast","type":"flags.0?true"},{"name":"megagroup","type":"flags.1?true"},{"name":"title","type":"string"},{"name":"about","type":"string"}],"type":"Updates"},{"id":"333610782","method":"channels.editAbout","params":[{"name":"channel","type":"InputChannel"},{"name":"about","type":"string"}],"type":"Bool"},{"id":"-344583728","method":"channels.editAdmin","params":[{"name":"channel","type":"InputChannel"},{"name":"user_id","type":"InputUser"},{"name":"role","type":"ChannelParticipantRole"}],"type":"Updates"},{"id":"1450044624","method":"channels.editTitle","params":[{"name":"channel","type":"InputChannel"},{"name":"title","type":"string"}],"type":"Updates"},{"id":"-248621111","method":"channels.editPhoto","params":[{"name":"channel","type":"InputChannel"},{"name":"photo","type":"InputChatPhoto"}],"type":"Updates"},{"id":"-1432183160","method":"channels.toggleComments","params":[{"name":"channel","type":"InputChannel"},{"name":"enabled","type":"Bool"}],"type":"Updates"},{"id":"283557164","method":"channels.checkUsername","params":[{"name":"channel","type":"InputChannel"},{"name":"username","type":"string"}],"type":"Bool"},{"id":"890549214","method":"channels.updateUsername","params":[{"name":"channel","type":"InputChannel"},{"name":"username","type":"string"}],"type":"Bool"},{"id":"615851205","method":"channels.joinChannel","params":[{"name":"channel","type":"InputChannel"}],"type":"Updates"},{"id":"-130635115","method":"channels.leaveChannel","params":[{"name":"channel","type":"InputChannel"}],"type":"Updates"},{"id":"429865580","method":"channels.inviteToChannel","params":[{"name":"channel","type":"InputChannel"},{"name":"users","type":"Vector"}],"type":"Updates"},{"id":"-1502421484","method":"channels.kickFromChannel","params":[{"name":"channel","type":"InputChannel"},{"name":"user_id","type":"InputUser"},{"name":"kicked","type":"Bool"}],"type":"Updates"},{"id":"-950663035","method":"channels.exportInvite","params":[{"name":"channel","type":"InputChannel"}],"type":"ExportedChatInvite"},{"id":"-1072619549","method":"channels.deleteChannel","params":[{"name":"channel","type":"InputChannel"}],"type":"Updates"},{"id":"-1154295872","method":"updates.getChannelDifference","params":[{"name":"channel","type":"InputChannel"},{"name":"filter","type":"ChannelMessagesFilter"},{"name":"pts","type":"int"},{"name":"limit","type":"int"}],"type":"updates.ChannelDifference"},{"id":"-326379039","method":"messages.toggleChatAdmins","params":[{"name":"chat_id","type":"int"},{"name":"enabled","type":"Bool"}],"type":"Updates"},{"id":"-1444503762","method":"messages.editChatAdmin","params":[{"name":"chat_id","type":"int"},{"name":"user_id","type":"InputUser"},{"name":"is_admin","type":"Bool"}],"type":"Bool"},{"id":"363051235","method":"messages.migrateChat","params":[{"name":"chat_id","type":"int"}],"type":"Updates"},{"id":"-1640190800","method":"messages.searchGlobal","params":[{"name":"q","type":"string"},{"name":"offset_date","type":"int"},{"name":"offset_peer","type":"InputPeer"},{"name":"offset_id","type":"int"},{"name":"limit","type":"int"}],"type":"messages.Messages"},{"id":"936873859","method":"help.getTermsOfService","params":[{"name":"lang_code","type":"string"}],"type":"help.TermsOfService"},{"id":"-1613775824","method":"messages.reorderStickerSets","params":[{"name":"order","type":"Vector"}],"type":"Bool"},{"id":"864953444","method":"messages.getDocumentByHash","params":[{"name":"sha256","type":"bytes"},{"name":"size","type":"int"},{"name":"mime_type","type":"string"}],"type":"Document"},{"id":"-1080395925","method":"messages.searchGifs","params":[{"name":"q","type":"string"},{"name":"offset","type":"int"}],"type":"messages.FoundGifs"},{"id":"-2084618926","method":"messages.getSavedGifs","params":[{"name":"hash","type":"int"}],"type":"messages.SavedGifs"},{"id":"846868683","method":"messages.saveGif","params":[{"name":"id","type":"InputDocument"},{"name":"unsave","type":"Bool"}],"type":"Bool"},{"id":"1364105629","method":"messages.getInlineBotResults","params":[{"name":"flags","type":"#"},{"name":"bot","type":"InputUser"},{"name":"peer","type":"InputPeer"},{"name":"geo_point","type":"flags.0?InputGeoPoint"},{"name":"query","type":"string"},{"name":"offset","type":"string"}],"type":"messages.BotResults"},{"id":"-346119674","method":"messages.setInlineBotResults","params":[{"name":"flags","type":"#"},{"name":"gallery","type":"flags.0?true"},{"name":"private","type":"flags.1?true"},{"name":"query_id","type":"long"},{"name":"results","type":"Vector"},{"name":"cache_time","type":"int"},{"name":"next_offset","type":"flags.2?string"},{"name":"switch_pm","type":"flags.3?InlineBotSwitchPM"}],"type":"Bool"},{"id":"-1318189314","method":"messages.sendInlineBotResult","params":[{"name":"flags","type":"#"},{"name":"broadcast","type":"flags.4?true"},{"name":"silent","type":"flags.5?true"},{"name":"background","type":"flags.6?true"},{"name":"peer","type":"InputPeer"},{"name":"reply_to_msg_id","type":"flags.0?int"},{"name":"random_id","type":"long"},{"name":"query_id","type":"long"},{"name":"id","type":"string"}],"type":"Updates"},{"id":"1231065863","method":"channels.toggleInvites","params":[{"name":"channel","type":"InputChannel"},{"name":"enabled","type":"Bool"}],"type":"Updates"},{"id":"-934882771","method":"channels.exportMessageLink","params":[{"name":"channel","type":"InputChannel"},{"name":"id","type":"int"}],"type":"ExportedMessageLink"},{"id":"527021574","method":"channels.toggleSignatures","params":[{"name":"channel","type":"InputChannel"},{"name":"enabled","type":"Bool"}],"type":"Updates"},{"id":"-1490162350","method":"channels.updatePinnedMessage","params":[{"name":"flags","type":"#"},{"name":"silent","type":"flags.0?true"},{"name":"channel","type":"InputChannel"},{"name":"id","type":"int"}],"type":"Updates"},{"id":"1056025023","method":"auth.resendCode","params":[{"name":"phone_number","type":"string"},{"name":"phone_code_hash","type":"string"}],"type":"auth.SentCode"},{"id":"520357240","method":"auth.cancelCode","params":[{"name":"phone_number","type":"string"},{"name":"phone_code_hash","type":"string"}],"type":"Bool"},{"id":"-39416522","method":"messages.getMessageEditData","params":[{"name":"peer","type":"InputPeer"},{"name":"id","type":"int"}],"type":"messages.MessageEditData"},{"id":"-829299510","method":"messages.editMessage","params":[{"name":"flags","type":"#"},{"name":"no_webpage","type":"flags.1?true"},{"name":"peer","type":"InputPeer"},{"name":"id","type":"int"},{"name":"message","type":"flags.11?string"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"},{"name":"entities","type":"flags.3?Vector"}],"type":"Updates"},{"id":"319564933","method":"messages.editInlineBotMessage","params":[{"name":"flags","type":"#"},{"name":"no_webpage","type":"flags.1?true"},{"name":"id","type":"InputBotInlineMessageID"},{"name":"message","type":"flags.11?string"},{"name":"reply_markup","type":"flags.2?ReplyMarkup"},{"name":"entities","type":"flags.3?Vector"}],"type":"Bool"},{"id":"-1494659324","method":"messages.getBotCallbackAnswer","params":[{"name":"peer","type":"InputPeer"},{"name":"msg_id","type":"int"},{"name":"data","type":"bytes"}],"type":"messages.BotCallbackAnswer"},{"id":"1209817370","method":"messages.setBotCallbackAnswer","params":[{"name":"flags","type":"#"},{"name":"alert","type":"flags.1?true"},{"name":"query_id","type":"long"},{"name":"message","type":"flags.0?string"}],"type":"Bool"}]} diff --git a/additional/tl-builder.jar b/additional/tl-builder.jar new file mode 100644 index 0000000..e0e55c2 Binary files /dev/null and b/additional/tl-builder.jar differ diff --git a/additional/tl-core-1.0.19.jar b/additional/tl-core-1.0.19.jar new file mode 100644 index 0000000..8cf762f Binary files /dev/null and b/additional/tl-core-1.0.19.jar differ diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..08518af --- /dev/null +++ b/build.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/classes/com/droidkit/actors/Actor$1.class b/build/classes/com/droidkit/actors/Actor$1.class new file mode 100644 index 0000000..4f3c75c Binary files /dev/null and b/build/classes/com/droidkit/actors/Actor$1.class differ diff --git a/build/classes/com/droidkit/actors/Actor$2.class b/build/classes/com/droidkit/actors/Actor$2.class new file mode 100644 index 0000000..8aa49c7 Binary files /dev/null and b/build/classes/com/droidkit/actors/Actor$2.class differ diff --git a/build/classes/com/droidkit/actors/Actor.class b/build/classes/com/droidkit/actors/Actor.class new file mode 100644 index 0000000..a9866e2 Binary files /dev/null and b/build/classes/com/droidkit/actors/Actor.class differ diff --git a/build/classes/com/droidkit/actors/ActorContext.class b/build/classes/com/droidkit/actors/ActorContext.class new file mode 100644 index 0000000..e707820 Binary files /dev/null and b/build/classes/com/droidkit/actors/ActorContext.class differ diff --git a/build/classes/com/droidkit/actors/ActorCreator.class b/build/classes/com/droidkit/actors/ActorCreator.class new file mode 100644 index 0000000..12de8a0 Binary files /dev/null and b/build/classes/com/droidkit/actors/ActorCreator.class differ diff --git a/build/classes/com/droidkit/actors/ActorRef.class b/build/classes/com/droidkit/actors/ActorRef.class new file mode 100644 index 0000000..be338ba Binary files /dev/null and b/build/classes/com/droidkit/actors/ActorRef.class differ diff --git a/build/classes/com/droidkit/actors/ActorScope.class b/build/classes/com/droidkit/actors/ActorScope.class new file mode 100644 index 0000000..ade5614 Binary files /dev/null and b/build/classes/com/droidkit/actors/ActorScope.class differ diff --git a/build/classes/com/droidkit/actors/ActorSelection.class b/build/classes/com/droidkit/actors/ActorSelection.class new file mode 100644 index 0000000..bac2bde Binary files /dev/null and b/build/classes/com/droidkit/actors/ActorSelection.class differ diff --git a/build/classes/com/droidkit/actors/ActorSystem.class b/build/classes/com/droidkit/actors/ActorSystem.class new file mode 100644 index 0000000..80a2933 Binary files /dev/null and b/build/classes/com/droidkit/actors/ActorSystem.class differ diff --git a/build/classes/com/droidkit/actors/ActorTime.class b/build/classes/com/droidkit/actors/ActorTime.class new file mode 100644 index 0000000..288a1c3 Binary files /dev/null and b/build/classes/com/droidkit/actors/ActorTime.class differ diff --git a/build/classes/com/droidkit/actors/CurrentActor.class b/build/classes/com/droidkit/actors/CurrentActor.class new file mode 100644 index 0000000..06e75b3 Binary files /dev/null and b/build/classes/com/droidkit/actors/CurrentActor.class differ diff --git a/build/classes/com/droidkit/actors/Props.class b/build/classes/com/droidkit/actors/Props.class new file mode 100644 index 0000000..73ed430 Binary files /dev/null and b/build/classes/com/droidkit/actors/Props.class differ diff --git a/build/classes/com/droidkit/actors/ReflectedActor$Event.class b/build/classes/com/droidkit/actors/ReflectedActor$Event.class new file mode 100644 index 0000000..524db76 Binary files /dev/null and b/build/classes/com/droidkit/actors/ReflectedActor$Event.class differ diff --git a/build/classes/com/droidkit/actors/ReflectedActor$NamedEvent.class b/build/classes/com/droidkit/actors/ReflectedActor$NamedEvent.class new file mode 100644 index 0000000..d2434e9 Binary files /dev/null and b/build/classes/com/droidkit/actors/ReflectedActor$NamedEvent.class differ diff --git a/build/classes/com/droidkit/actors/ReflectedActor.class b/build/classes/com/droidkit/actors/ReflectedActor.class new file mode 100644 index 0000000..10b44a6 Binary files /dev/null and b/build/classes/com/droidkit/actors/ReflectedActor.class differ diff --git a/build/classes/com/droidkit/actors/dispatch/AbstractDispatchQueue.class b/build/classes/com/droidkit/actors/dispatch/AbstractDispatchQueue.class new file mode 100644 index 0000000..6aca497 Binary files /dev/null and b/build/classes/com/droidkit/actors/dispatch/AbstractDispatchQueue.class differ diff --git a/build/classes/com/droidkit/actors/dispatch/AbstractDispatcher$1.class b/build/classes/com/droidkit/actors/dispatch/AbstractDispatcher$1.class new file mode 100644 index 0000000..3ab4e24 Binary files /dev/null and b/build/classes/com/droidkit/actors/dispatch/AbstractDispatcher$1.class differ diff --git a/build/classes/com/droidkit/actors/dispatch/AbstractDispatcher.class b/build/classes/com/droidkit/actors/dispatch/AbstractDispatcher.class new file mode 100644 index 0000000..5b06c06 Binary files /dev/null and b/build/classes/com/droidkit/actors/dispatch/AbstractDispatcher.class differ diff --git a/build/classes/com/droidkit/actors/dispatch/Dispatch.class b/build/classes/com/droidkit/actors/dispatch/Dispatch.class new file mode 100644 index 0000000..1d546d3 Binary files /dev/null and b/build/classes/com/droidkit/actors/dispatch/Dispatch.class differ diff --git a/build/classes/com/droidkit/actors/dispatch/QueueListener.class b/build/classes/com/droidkit/actors/dispatch/QueueListener.class new file mode 100644 index 0000000..4d7de1b Binary files /dev/null and b/build/classes/com/droidkit/actors/dispatch/QueueListener.class differ diff --git a/build/classes/com/droidkit/actors/dispatch/RunnableDispatcher.class b/build/classes/com/droidkit/actors/dispatch/RunnableDispatcher.class new file mode 100644 index 0000000..f6ea85c Binary files /dev/null and b/build/classes/com/droidkit/actors/dispatch/RunnableDispatcher.class differ diff --git a/build/classes/com/droidkit/actors/dispatch/SimpleDispatchQueue$Message.class b/build/classes/com/droidkit/actors/dispatch/SimpleDispatchQueue$Message.class new file mode 100644 index 0000000..01d02ad Binary files /dev/null and b/build/classes/com/droidkit/actors/dispatch/SimpleDispatchQueue$Message.class differ diff --git a/build/classes/com/droidkit/actors/dispatch/SimpleDispatchQueue.class b/build/classes/com/droidkit/actors/dispatch/SimpleDispatchQueue.class new file mode 100644 index 0000000..cc5d925 Binary files /dev/null and b/build/classes/com/droidkit/actors/dispatch/SimpleDispatchQueue.class differ diff --git a/build/classes/com/droidkit/actors/dispatch/ThreadPoolDispatcher$1.class b/build/classes/com/droidkit/actors/dispatch/ThreadPoolDispatcher$1.class new file mode 100644 index 0000000..357a156 Binary files /dev/null and b/build/classes/com/droidkit/actors/dispatch/ThreadPoolDispatcher$1.class differ diff --git a/build/classes/com/droidkit/actors/dispatch/ThreadPoolDispatcher$DispatcherThread.class b/build/classes/com/droidkit/actors/dispatch/ThreadPoolDispatcher$DispatcherThread.class new file mode 100644 index 0000000..cd007cf Binary files /dev/null and b/build/classes/com/droidkit/actors/dispatch/ThreadPoolDispatcher$DispatcherThread.class differ diff --git a/build/classes/com/droidkit/actors/dispatch/ThreadPoolDispatcher.class b/build/classes/com/droidkit/actors/dispatch/ThreadPoolDispatcher.class new file mode 100644 index 0000000..c0c10c3 Binary files /dev/null and b/build/classes/com/droidkit/actors/dispatch/ThreadPoolDispatcher.class differ diff --git a/build/classes/com/droidkit/actors/mailbox/AbsActorDispatcher.class b/build/classes/com/droidkit/actors/mailbox/AbsActorDispatcher.class new file mode 100644 index 0000000..06db240 Binary files /dev/null and b/build/classes/com/droidkit/actors/mailbox/AbsActorDispatcher.class differ diff --git a/build/classes/com/droidkit/actors/mailbox/ActorDispatcher$1.class b/build/classes/com/droidkit/actors/mailbox/ActorDispatcher$1.class new file mode 100644 index 0000000..0f2e845 Binary files /dev/null and b/build/classes/com/droidkit/actors/mailbox/ActorDispatcher$1.class differ diff --git a/build/classes/com/droidkit/actors/mailbox/ActorDispatcher.class b/build/classes/com/droidkit/actors/mailbox/ActorDispatcher.class new file mode 100644 index 0000000..9160e5c Binary files /dev/null and b/build/classes/com/droidkit/actors/mailbox/ActorDispatcher.class differ diff --git a/build/classes/com/droidkit/actors/mailbox/Envelope.class b/build/classes/com/droidkit/actors/mailbox/Envelope.class new file mode 100644 index 0000000..5d3a784 Binary files /dev/null and b/build/classes/com/droidkit/actors/mailbox/Envelope.class differ diff --git a/build/classes/com/droidkit/actors/mailbox/Mailbox.class b/build/classes/com/droidkit/actors/mailbox/Mailbox.class new file mode 100644 index 0000000..5d909e5 Binary files /dev/null and b/build/classes/com/droidkit/actors/mailbox/Mailbox.class differ diff --git a/build/classes/com/droidkit/actors/mailbox/MailboxesQueue.class b/build/classes/com/droidkit/actors/mailbox/MailboxesQueue.class new file mode 100644 index 0000000..a867e21 Binary files /dev/null and b/build/classes/com/droidkit/actors/mailbox/MailboxesQueue.class differ diff --git a/build/classes/com/droidkit/actors/messages/DeadLetter.class b/build/classes/com/droidkit/actors/messages/DeadLetter.class new file mode 100644 index 0000000..e574618 Binary files /dev/null and b/build/classes/com/droidkit/actors/messages/DeadLetter.class differ diff --git a/build/classes/com/droidkit/actors/messages/NamedMessage.class b/build/classes/com/droidkit/actors/messages/NamedMessage.class new file mode 100644 index 0000000..3a5e966 Binary files /dev/null and b/build/classes/com/droidkit/actors/messages/NamedMessage.class differ diff --git a/build/classes/com/droidkit/actors/messages/PoisonPill.class b/build/classes/com/droidkit/actors/messages/PoisonPill.class new file mode 100644 index 0000000..7d8f2bf Binary files /dev/null and b/build/classes/com/droidkit/actors/messages/PoisonPill.class differ diff --git a/build/classes/com/droidkit/actors/messages/StartActor.class b/build/classes/com/droidkit/actors/messages/StartActor.class new file mode 100644 index 0000000..0b586de Binary files /dev/null and b/build/classes/com/droidkit/actors/messages/StartActor.class differ diff --git a/build/classes/com/droidkit/actors/tasks/ActorAskImpl$1.class b/build/classes/com/droidkit/actors/tasks/ActorAskImpl$1.class new file mode 100644 index 0000000..85581d8 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/ActorAskImpl$1.class differ diff --git a/build/classes/com/droidkit/actors/tasks/ActorAskImpl$AskContainer.class b/build/classes/com/droidkit/actors/tasks/ActorAskImpl$AskContainer.class new file mode 100644 index 0000000..ee2f2b4 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/ActorAskImpl$AskContainer.class differ diff --git a/build/classes/com/droidkit/actors/tasks/ActorAskImpl$CombineContainer.class b/build/classes/com/droidkit/actors/tasks/ActorAskImpl$CombineContainer.class new file mode 100644 index 0000000..358400d Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/ActorAskImpl$CombineContainer.class differ diff --git a/build/classes/com/droidkit/actors/tasks/ActorAskImpl.class b/build/classes/com/droidkit/actors/tasks/ActorAskImpl.class new file mode 100644 index 0000000..95875fa Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/ActorAskImpl.class differ diff --git a/build/classes/com/droidkit/actors/tasks/AskCallback.class b/build/classes/com/droidkit/actors/tasks/AskCallback.class new file mode 100644 index 0000000..1b724d6 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/AskCallback.class differ diff --git a/build/classes/com/droidkit/actors/tasks/AskCancelledException.class b/build/classes/com/droidkit/actors/tasks/AskCancelledException.class new file mode 100644 index 0000000..320a7f2 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/AskCancelledException.class differ diff --git a/build/classes/com/droidkit/actors/tasks/AskFuture.class b/build/classes/com/droidkit/actors/tasks/AskFuture.class new file mode 100644 index 0000000..4fca8b6 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/AskFuture.class differ diff --git a/build/classes/com/droidkit/actors/tasks/AskTimeoutException.class b/build/classes/com/droidkit/actors/tasks/AskTimeoutException.class new file mode 100644 index 0000000..4b2ac91 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/AskTimeoutException.class differ diff --git a/build/classes/com/droidkit/actors/tasks/TaskActor$1.class b/build/classes/com/droidkit/actors/tasks/TaskActor$1.class new file mode 100644 index 0000000..10ab337 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/TaskActor$1.class differ diff --git a/build/classes/com/droidkit/actors/tasks/TaskActor$Error.class b/build/classes/com/droidkit/actors/tasks/TaskActor$Error.class new file mode 100644 index 0000000..9795250 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/TaskActor$Error.class differ diff --git a/build/classes/com/droidkit/actors/tasks/TaskActor$Result.class b/build/classes/com/droidkit/actors/tasks/TaskActor$Result.class new file mode 100644 index 0000000..4c3d761 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/TaskActor$Result.class differ diff --git a/build/classes/com/droidkit/actors/tasks/TaskActor$TaskListener.class b/build/classes/com/droidkit/actors/tasks/TaskActor$TaskListener.class new file mode 100644 index 0000000..30bddac Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/TaskActor$TaskListener.class differ diff --git a/build/classes/com/droidkit/actors/tasks/TaskActor.class b/build/classes/com/droidkit/actors/tasks/TaskActor.class new file mode 100644 index 0000000..f631503 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/TaskActor.class differ diff --git a/build/classes/com/droidkit/actors/tasks/messages/TaskCancel.class b/build/classes/com/droidkit/actors/tasks/messages/TaskCancel.class new file mode 100644 index 0000000..40181e8 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/messages/TaskCancel.class differ diff --git a/build/classes/com/droidkit/actors/tasks/messages/TaskError.class b/build/classes/com/droidkit/actors/tasks/messages/TaskError.class new file mode 100644 index 0000000..fdba360 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/messages/TaskError.class differ diff --git a/build/classes/com/droidkit/actors/tasks/messages/TaskRequest.class b/build/classes/com/droidkit/actors/tasks/messages/TaskRequest.class new file mode 100644 index 0000000..71054dc Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/messages/TaskRequest.class differ diff --git a/build/classes/com/droidkit/actors/tasks/messages/TaskResult.class b/build/classes/com/droidkit/actors/tasks/messages/TaskResult.class new file mode 100644 index 0000000..99f5676 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/messages/TaskResult.class differ diff --git a/build/classes/com/droidkit/actors/tasks/messages/TaskTimeout.class b/build/classes/com/droidkit/actors/tasks/messages/TaskTimeout.class new file mode 100644 index 0000000..c9a3e32 Binary files /dev/null and b/build/classes/com/droidkit/actors/tasks/messages/TaskTimeout.class differ diff --git a/build/classes/de/fabianonline/telegram_backup/Main$1.class b/build/classes/de/fabianonline/telegram_backup/Main$1.class new file mode 100644 index 0000000..dfcd0c0 Binary files /dev/null and b/build/classes/de/fabianonline/telegram_backup/Main$1.class differ diff --git a/build/classes/de/fabianonline/telegram_backup/Main.class b/build/classes/de/fabianonline/telegram_backup/Main.class new file mode 100644 index 0000000..9d3ae45 Binary files /dev/null and b/build/classes/de/fabianonline/telegram_backup/Main.class differ diff --git a/build/classes/de/fabianonline/telegram_backup/MyStorage.class b/build/classes/de/fabianonline/telegram_backup/MyStorage.class new file mode 100644 index 0000000..71764d6 Binary files /dev/null and b/build/classes/de/fabianonline/telegram_backup/MyStorage.class differ diff --git a/build/classes/org/telegram/api/engine/ApiCallback.class b/build/classes/org/telegram/api/engine/ApiCallback.class new file mode 100644 index 0000000..eea5cea Binary files /dev/null and b/build/classes/org/telegram/api/engine/ApiCallback.class differ diff --git a/build/classes/org/telegram/api/engine/AppInfo.class b/build/classes/org/telegram/api/engine/AppInfo.class new file mode 100644 index 0000000..94f96f1 Binary files /dev/null and b/build/classes/org/telegram/api/engine/AppInfo.class differ diff --git a/build/classes/org/telegram/api/engine/GzipRequest.class b/build/classes/org/telegram/api/engine/GzipRequest.class new file mode 100644 index 0000000..b3a1005 Binary files /dev/null and b/build/classes/org/telegram/api/engine/GzipRequest.class differ diff --git a/build/classes/org/telegram/api/engine/Logger.class b/build/classes/org/telegram/api/engine/Logger.class new file mode 100644 index 0000000..e4a4998 Binary files /dev/null and b/build/classes/org/telegram/api/engine/Logger.class differ diff --git a/build/classes/org/telegram/api/engine/LoggerInterface.class b/build/classes/org/telegram/api/engine/LoggerInterface.class new file mode 100644 index 0000000..bd9feb6 Binary files /dev/null and b/build/classes/org/telegram/api/engine/LoggerInterface.class differ diff --git a/build/classes/org/telegram/api/engine/RpcCallback.class b/build/classes/org/telegram/api/engine/RpcCallback.class new file mode 100644 index 0000000..d8b3bd5 Binary files /dev/null and b/build/classes/org/telegram/api/engine/RpcCallback.class differ diff --git a/build/classes/org/telegram/api/engine/RpcCallbackEx.class b/build/classes/org/telegram/api/engine/RpcCallbackEx.class new file mode 100644 index 0000000..f3bfa11 Binary files /dev/null and b/build/classes/org/telegram/api/engine/RpcCallbackEx.class differ diff --git a/build/classes/org/telegram/api/engine/RpcException.class b/build/classes/org/telegram/api/engine/RpcException.class new file mode 100644 index 0000000..fb14b98 Binary files /dev/null and b/build/classes/org/telegram/api/engine/RpcException.class differ diff --git a/build/classes/org/telegram/api/engine/TelegramApi$1.class b/build/classes/org/telegram/api/engine/TelegramApi$1.class new file mode 100644 index 0000000..7da56a2 Binary files /dev/null and b/build/classes/org/telegram/api/engine/TelegramApi$1.class differ diff --git a/build/classes/org/telegram/api/engine/TelegramApi$2.class b/build/classes/org/telegram/api/engine/TelegramApi$2.class new file mode 100644 index 0000000..991f030 Binary files /dev/null and b/build/classes/org/telegram/api/engine/TelegramApi$2.class differ diff --git a/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread$1.class b/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread$1.class new file mode 100644 index 0000000..f9abbc8 Binary files /dev/null and b/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread$1.class differ diff --git a/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread$2.class b/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread$2.class new file mode 100644 index 0000000..3c0bf5d Binary files /dev/null and b/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread$2.class differ diff --git a/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread$3.class b/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread$3.class new file mode 100644 index 0000000..9303664 Binary files /dev/null and b/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread$3.class differ diff --git a/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread$4.class b/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread$4.class new file mode 100644 index 0000000..bc06368 Binary files /dev/null and b/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread$4.class differ diff --git a/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread.class b/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread.class new file mode 100644 index 0000000..dd1f6ba Binary files /dev/null and b/build/classes/org/telegram/api/engine/TelegramApi$ConnectionThread.class differ diff --git a/build/classes/org/telegram/api/engine/TelegramApi$ProtoCallback.class b/build/classes/org/telegram/api/engine/TelegramApi$ProtoCallback.class new file mode 100644 index 0000000..a2aa00f Binary files /dev/null and b/build/classes/org/telegram/api/engine/TelegramApi$ProtoCallback.class differ diff --git a/build/classes/org/telegram/api/engine/TelegramApi$RpcCallbackWrapper.class b/build/classes/org/telegram/api/engine/TelegramApi$RpcCallbackWrapper.class new file mode 100644 index 0000000..7a91116 Binary files /dev/null and b/build/classes/org/telegram/api/engine/TelegramApi$RpcCallbackWrapper.class differ diff --git a/build/classes/org/telegram/api/engine/TelegramApi$SenderThread.class b/build/classes/org/telegram/api/engine/TelegramApi$SenderThread.class new file mode 100644 index 0000000..de760a5 Binary files /dev/null and b/build/classes/org/telegram/api/engine/TelegramApi$SenderThread.class differ diff --git a/build/classes/org/telegram/api/engine/TelegramApi$TimeoutThread.class b/build/classes/org/telegram/api/engine/TelegramApi$TimeoutThread.class new file mode 100644 index 0000000..775b680 Binary files /dev/null and b/build/classes/org/telegram/api/engine/TelegramApi$TimeoutThread.class differ diff --git a/build/classes/org/telegram/api/engine/TelegramApi.class b/build/classes/org/telegram/api/engine/TelegramApi.class new file mode 100644 index 0000000..56c8ef3 Binary files /dev/null and b/build/classes/org/telegram/api/engine/TelegramApi.class differ diff --git a/build/classes/org/telegram/api/engine/TimeoutException.class b/build/classes/org/telegram/api/engine/TimeoutException.class new file mode 100644 index 0000000..f585171 Binary files /dev/null and b/build/classes/org/telegram/api/engine/TimeoutException.class differ diff --git a/build/classes/org/telegram/api/engine/file/DownloadListener.class b/build/classes/org/telegram/api/engine/file/DownloadListener.class new file mode 100644 index 0000000..a6af71f Binary files /dev/null and b/build/classes/org/telegram/api/engine/file/DownloadListener.class differ diff --git a/build/classes/org/telegram/api/engine/file/Downloader$1.class b/build/classes/org/telegram/api/engine/file/Downloader$1.class new file mode 100644 index 0000000..e0b4df5 Binary files /dev/null and b/build/classes/org/telegram/api/engine/file/Downloader$1.class differ diff --git a/build/classes/org/telegram/api/engine/file/Downloader$DownloadBlock.class b/build/classes/org/telegram/api/engine/file/Downloader$DownloadBlock.class new file mode 100644 index 0000000..b4abaf9 Binary files /dev/null and b/build/classes/org/telegram/api/engine/file/Downloader$DownloadBlock.class differ diff --git a/build/classes/org/telegram/api/engine/file/Downloader$DownloadFileThread.class b/build/classes/org/telegram/api/engine/file/Downloader$DownloadFileThread.class new file mode 100644 index 0000000..cee2398 Binary files /dev/null and b/build/classes/org/telegram/api/engine/file/Downloader$DownloadFileThread.class differ diff --git a/build/classes/org/telegram/api/engine/file/Downloader$DownloadTask.class b/build/classes/org/telegram/api/engine/file/Downloader$DownloadTask.class new file mode 100644 index 0000000..b4ed037 Binary files /dev/null and b/build/classes/org/telegram/api/engine/file/Downloader$DownloadTask.class differ diff --git a/build/classes/org/telegram/api/engine/file/Downloader.class b/build/classes/org/telegram/api/engine/file/Downloader.class new file mode 100644 index 0000000..f338678 Binary files /dev/null and b/build/classes/org/telegram/api/engine/file/Downloader.class differ diff --git a/build/classes/org/telegram/api/engine/file/UploadListener.class b/build/classes/org/telegram/api/engine/file/UploadListener.class new file mode 100644 index 0000000..364e928 Binary files /dev/null and b/build/classes/org/telegram/api/engine/file/UploadListener.class differ diff --git a/build/classes/org/telegram/api/engine/file/Uploader$1.class b/build/classes/org/telegram/api/engine/file/Uploader$1.class new file mode 100644 index 0000000..f984f45 Binary files /dev/null and b/build/classes/org/telegram/api/engine/file/Uploader$1.class differ diff --git a/build/classes/org/telegram/api/engine/file/Uploader$UploadBlock.class b/build/classes/org/telegram/api/engine/file/Uploader$UploadBlock.class new file mode 100644 index 0000000..8e99f3a Binary files /dev/null and b/build/classes/org/telegram/api/engine/file/Uploader$UploadBlock.class differ diff --git a/build/classes/org/telegram/api/engine/file/Uploader$UploadFileThread.class b/build/classes/org/telegram/api/engine/file/Uploader$UploadFileThread.class new file mode 100644 index 0000000..89843a8 Binary files /dev/null and b/build/classes/org/telegram/api/engine/file/Uploader$UploadFileThread.class differ diff --git a/build/classes/org/telegram/api/engine/file/Uploader$UploadResult.class b/build/classes/org/telegram/api/engine/file/Uploader$UploadResult.class new file mode 100644 index 0000000..c9fc4ca Binary files /dev/null and b/build/classes/org/telegram/api/engine/file/Uploader$UploadResult.class differ diff --git a/build/classes/org/telegram/api/engine/file/Uploader$UploadTask.class b/build/classes/org/telegram/api/engine/file/Uploader$UploadTask.class new file mode 100644 index 0000000..400c4ed Binary files /dev/null and b/build/classes/org/telegram/api/engine/file/Uploader$UploadTask.class differ diff --git a/build/classes/org/telegram/api/engine/file/Uploader.class b/build/classes/org/telegram/api/engine/file/Uploader.class new file mode 100644 index 0000000..2589f64 Binary files /dev/null and b/build/classes/org/telegram/api/engine/file/Uploader.class differ diff --git a/build/classes/org/telegram/api/engine/storage/AbsApiState.class b/build/classes/org/telegram/api/engine/storage/AbsApiState.class new file mode 100644 index 0000000..4ec4375 Binary files /dev/null and b/build/classes/org/telegram/api/engine/storage/AbsApiState.class differ diff --git a/build/classes/org/telegram/mtproto/CallWrapper.class b/build/classes/org/telegram/mtproto/CallWrapper.class new file mode 100644 index 0000000..b3ba5d1 Binary files /dev/null and b/build/classes/org/telegram/mtproto/CallWrapper.class differ diff --git a/build/classes/org/telegram/mtproto/MTProto$1.class b/build/classes/org/telegram/mtproto/MTProto$1.class new file mode 100644 index 0000000..dc6f3fa Binary files /dev/null and b/build/classes/org/telegram/mtproto/MTProto$1.class differ diff --git a/build/classes/org/telegram/mtproto/MTProto$2.class b/build/classes/org/telegram/mtproto/MTProto$2.class new file mode 100644 index 0000000..6f5083e Binary files /dev/null and b/build/classes/org/telegram/mtproto/MTProto$2.class differ diff --git a/build/classes/org/telegram/mtproto/MTProto$3.class b/build/classes/org/telegram/mtproto/MTProto$3.class new file mode 100644 index 0000000..ac21c15 Binary files /dev/null and b/build/classes/org/telegram/mtproto/MTProto$3.class differ diff --git a/build/classes/org/telegram/mtproto/MTProto$InternalActionsActor.class b/build/classes/org/telegram/mtproto/MTProto$InternalActionsActor.class new file mode 100644 index 0000000..50135cc Binary files /dev/null and b/build/classes/org/telegram/mtproto/MTProto$InternalActionsActor.class differ diff --git a/build/classes/org/telegram/mtproto/MTProto$RequestPingDelay.class b/build/classes/org/telegram/mtproto/MTProto$RequestPingDelay.class new file mode 100644 index 0000000..7479df5 Binary files /dev/null and b/build/classes/org/telegram/mtproto/MTProto$RequestPingDelay.class differ diff --git a/build/classes/org/telegram/mtproto/MTProto$RequestSalt.class b/build/classes/org/telegram/mtproto/MTProto$RequestSalt.class new file mode 100644 index 0000000..3f19762 Binary files /dev/null and b/build/classes/org/telegram/mtproto/MTProto$RequestSalt.class differ diff --git a/build/classes/org/telegram/mtproto/MTProto$ResponseActor.class b/build/classes/org/telegram/mtproto/MTProto$ResponseActor.class new file mode 100644 index 0000000..5d5d6e0 Binary files /dev/null and b/build/classes/org/telegram/mtproto/MTProto$ResponseActor.class differ diff --git a/build/classes/org/telegram/mtproto/MTProto.class b/build/classes/org/telegram/mtproto/MTProto.class new file mode 100644 index 0000000..3372075 Binary files /dev/null and b/build/classes/org/telegram/mtproto/MTProto.class differ diff --git a/build/classes/org/telegram/mtproto/MTProtoCallback.class b/build/classes/org/telegram/mtproto/MTProtoCallback.class new file mode 100644 index 0000000..f6fd177 Binary files /dev/null and b/build/classes/org/telegram/mtproto/MTProtoCallback.class differ diff --git a/build/classes/org/telegram/mtproto/ServerException.class b/build/classes/org/telegram/mtproto/ServerException.class new file mode 100644 index 0000000..91164f2 Binary files /dev/null and b/build/classes/org/telegram/mtproto/ServerException.class differ diff --git a/build/classes/org/telegram/mtproto/TransportSecurityException.class b/build/classes/org/telegram/mtproto/TransportSecurityException.class new file mode 100644 index 0000000..af2b75d Binary files /dev/null and b/build/classes/org/telegram/mtproto/TransportSecurityException.class differ diff --git a/build/classes/org/telegram/mtproto/backoff/ExponentalBackoff.class b/build/classes/org/telegram/mtproto/backoff/ExponentalBackoff.class new file mode 100644 index 0000000..be48529 Binary files /dev/null and b/build/classes/org/telegram/mtproto/backoff/ExponentalBackoff.class differ diff --git a/build/classes/org/telegram/mtproto/log/LogInterface.class b/build/classes/org/telegram/mtproto/log/LogInterface.class new file mode 100644 index 0000000..f1880ed Binary files /dev/null and b/build/classes/org/telegram/mtproto/log/LogInterface.class differ diff --git a/build/classes/org/telegram/mtproto/log/Logger.class b/build/classes/org/telegram/mtproto/log/Logger.class new file mode 100644 index 0000000..0cef296 Binary files /dev/null and b/build/classes/org/telegram/mtproto/log/Logger.class differ diff --git a/build/classes/org/telegram/mtproto/pq/Authorizer.class b/build/classes/org/telegram/mtproto/pq/Authorizer.class new file mode 100644 index 0000000..25ebac0 Binary files /dev/null and b/build/classes/org/telegram/mtproto/pq/Authorizer.class differ diff --git a/build/classes/org/telegram/mtproto/pq/PqAuth.class b/build/classes/org/telegram/mtproto/pq/PqAuth.class new file mode 100644 index 0000000..5654be8 Binary files /dev/null and b/build/classes/org/telegram/mtproto/pq/PqAuth.class differ diff --git a/build/classes/org/telegram/mtproto/schedule/PrepareSchedule.class b/build/classes/org/telegram/mtproto/schedule/PrepareSchedule.class new file mode 100644 index 0000000..c63ce58 Binary files /dev/null and b/build/classes/org/telegram/mtproto/schedule/PrepareSchedule.class differ diff --git a/build/classes/org/telegram/mtproto/schedule/PreparedPackage.class b/build/classes/org/telegram/mtproto/schedule/PreparedPackage.class new file mode 100644 index 0000000..9dbd87d Binary files /dev/null and b/build/classes/org/telegram/mtproto/schedule/PreparedPackage.class differ diff --git a/build/classes/org/telegram/mtproto/schedule/Scheduller$SchedullerPackage.class b/build/classes/org/telegram/mtproto/schedule/Scheduller$SchedullerPackage.class new file mode 100644 index 0000000..2a56939 Binary files /dev/null and b/build/classes/org/telegram/mtproto/schedule/Scheduller$SchedullerPackage.class differ diff --git a/build/classes/org/telegram/mtproto/schedule/Scheduller.class b/build/classes/org/telegram/mtproto/schedule/Scheduller.class new file mode 100644 index 0000000..17a6323 Binary files /dev/null and b/build/classes/org/telegram/mtproto/schedule/Scheduller.class differ diff --git a/build/classes/org/telegram/mtproto/schedule/SchedullerListener.class b/build/classes/org/telegram/mtproto/schedule/SchedullerListener.class new file mode 100644 index 0000000..21db261 Binary files /dev/null and b/build/classes/org/telegram/mtproto/schedule/SchedullerListener.class differ diff --git a/build/classes/org/telegram/mtproto/secure/CryptoUtils$1.class b/build/classes/org/telegram/mtproto/secure/CryptoUtils$1.class new file mode 100644 index 0000000..fd4b33f Binary files /dev/null and b/build/classes/org/telegram/mtproto/secure/CryptoUtils$1.class differ diff --git a/build/classes/org/telegram/mtproto/secure/CryptoUtils$2.class b/build/classes/org/telegram/mtproto/secure/CryptoUtils$2.class new file mode 100644 index 0000000..daa048c Binary files /dev/null and b/build/classes/org/telegram/mtproto/secure/CryptoUtils$2.class differ diff --git a/build/classes/org/telegram/mtproto/secure/CryptoUtils.class b/build/classes/org/telegram/mtproto/secure/CryptoUtils.class new file mode 100644 index 0000000..73a1588 Binary files /dev/null and b/build/classes/org/telegram/mtproto/secure/CryptoUtils.class differ diff --git a/build/classes/org/telegram/mtproto/secure/Entropy.class b/build/classes/org/telegram/mtproto/secure/Entropy.class new file mode 100644 index 0000000..6fa0d42 Binary files /dev/null and b/build/classes/org/telegram/mtproto/secure/Entropy.class differ diff --git a/build/classes/org/telegram/mtproto/secure/KeyParameter.class b/build/classes/org/telegram/mtproto/secure/KeyParameter.class new file mode 100644 index 0000000..d8fd167 Binary files /dev/null and b/build/classes/org/telegram/mtproto/secure/KeyParameter.class differ diff --git a/build/classes/org/telegram/mtproto/secure/Keys$Key.class b/build/classes/org/telegram/mtproto/secure/Keys$Key.class new file mode 100644 index 0000000..29062ce Binary files /dev/null and b/build/classes/org/telegram/mtproto/secure/Keys$Key.class differ diff --git a/build/classes/org/telegram/mtproto/secure/Keys.class b/build/classes/org/telegram/mtproto/secure/Keys.class new file mode 100644 index 0000000..b9659f3 Binary files /dev/null and b/build/classes/org/telegram/mtproto/secure/Keys.class differ diff --git a/build/classes/org/telegram/mtproto/secure/aes/AESFastEngine.class b/build/classes/org/telegram/mtproto/secure/aes/AESFastEngine.class new file mode 100644 index 0000000..5ca206e Binary files /dev/null and b/build/classes/org/telegram/mtproto/secure/aes/AESFastEngine.class differ diff --git a/build/classes/org/telegram/mtproto/secure/aes/AESImplementation.class b/build/classes/org/telegram/mtproto/secure/aes/AESImplementation.class new file mode 100644 index 0000000..049e3e8 Binary files /dev/null and b/build/classes/org/telegram/mtproto/secure/aes/AESImplementation.class differ diff --git a/build/classes/org/telegram/mtproto/secure/aes/DefaultAESImplementation.class b/build/classes/org/telegram/mtproto/secure/aes/DefaultAESImplementation.class new file mode 100644 index 0000000..7aaf2fc Binary files /dev/null and b/build/classes/org/telegram/mtproto/secure/aes/DefaultAESImplementation.class differ diff --git a/build/classes/org/telegram/mtproto/secure/pq/PQImplementation.class b/build/classes/org/telegram/mtproto/secure/pq/PQImplementation.class new file mode 100644 index 0000000..a3601ae Binary files /dev/null and b/build/classes/org/telegram/mtproto/secure/pq/PQImplementation.class differ diff --git a/build/classes/org/telegram/mtproto/secure/pq/PQLopatin.class b/build/classes/org/telegram/mtproto/secure/pq/PQLopatin.class new file mode 100644 index 0000000..581aed3 Binary files /dev/null and b/build/classes/org/telegram/mtproto/secure/pq/PQLopatin.class differ diff --git a/build/classes/org/telegram/mtproto/secure/pq/PQSolver.class b/build/classes/org/telegram/mtproto/secure/pq/PQSolver.class new file mode 100644 index 0000000..fc3af89 Binary files /dev/null and b/build/classes/org/telegram/mtproto/secure/pq/PQSolver.class differ diff --git a/build/classes/org/telegram/mtproto/state/AbsMTProtoState.class b/build/classes/org/telegram/mtproto/state/AbsMTProtoState.class new file mode 100644 index 0000000..8441805 Binary files /dev/null and b/build/classes/org/telegram/mtproto/state/AbsMTProtoState.class differ diff --git a/build/classes/org/telegram/mtproto/state/ConnectionInfo.class b/build/classes/org/telegram/mtproto/state/ConnectionInfo.class new file mode 100644 index 0000000..2ec3b8a Binary files /dev/null and b/build/classes/org/telegram/mtproto/state/ConnectionInfo.class differ diff --git a/build/classes/org/telegram/mtproto/state/KnownSalt.class b/build/classes/org/telegram/mtproto/state/KnownSalt.class new file mode 100644 index 0000000..c4559b0 Binary files /dev/null and b/build/classes/org/telegram/mtproto/state/KnownSalt.class differ diff --git a/build/classes/org/telegram/mtproto/state/MemoryProtoState.class b/build/classes/org/telegram/mtproto/state/MemoryProtoState.class new file mode 100644 index 0000000..095fb68 Binary files /dev/null and b/build/classes/org/telegram/mtproto/state/MemoryProtoState.class differ diff --git a/build/classes/org/telegram/mtproto/time/TimeOverlord.class b/build/classes/org/telegram/mtproto/time/TimeOverlord.class new file mode 100644 index 0000000..765f88a Binary files /dev/null and b/build/classes/org/telegram/mtproto/time/TimeOverlord.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTBadMessage.class b/build/classes/org/telegram/mtproto/tl/MTBadMessage.class new file mode 100644 index 0000000..5cbc0bc Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTBadMessage.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTBadMessageNotification.class b/build/classes/org/telegram/mtproto/tl/MTBadMessageNotification.class new file mode 100644 index 0000000..5ff1d6d Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTBadMessageNotification.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTBadServerSalt.class b/build/classes/org/telegram/mtproto/tl/MTBadServerSalt.class new file mode 100644 index 0000000..ebd99a4 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTBadServerSalt.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTFutureSalt.class b/build/classes/org/telegram/mtproto/tl/MTFutureSalt.class new file mode 100644 index 0000000..12a903a Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTFutureSalt.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTFutureSalts.class b/build/classes/org/telegram/mtproto/tl/MTFutureSalts.class new file mode 100644 index 0000000..974aefb Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTFutureSalts.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTGetFutureSalts.class b/build/classes/org/telegram/mtproto/tl/MTGetFutureSalts.class new file mode 100644 index 0000000..a59bb29 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTGetFutureSalts.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTInvokeAfter.class b/build/classes/org/telegram/mtproto/tl/MTInvokeAfter.class new file mode 100644 index 0000000..fc1cd98 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTInvokeAfter.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTMessage.class b/build/classes/org/telegram/mtproto/tl/MTMessage.class new file mode 100644 index 0000000..156ab46 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTMessage.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTMessageDetailedInfo.class b/build/classes/org/telegram/mtproto/tl/MTMessageDetailedInfo.class new file mode 100644 index 0000000..3947a6f Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTMessageDetailedInfo.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTMessagesContainer$1.class b/build/classes/org/telegram/mtproto/tl/MTMessagesContainer$1.class new file mode 100644 index 0000000..2fc6002 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTMessagesContainer$1.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTMessagesContainer.class b/build/classes/org/telegram/mtproto/tl/MTMessagesContainer.class new file mode 100644 index 0000000..df063a2 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTMessagesContainer.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTMsgsAck.class b/build/classes/org/telegram/mtproto/tl/MTMsgsAck.class new file mode 100644 index 0000000..309b3c4 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTMsgsAck.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTNeedResendMessage.class b/build/classes/org/telegram/mtproto/tl/MTNeedResendMessage.class new file mode 100644 index 0000000..40c8aac Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTNeedResendMessage.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTNewMessageDetailedInfo.class b/build/classes/org/telegram/mtproto/tl/MTNewMessageDetailedInfo.class new file mode 100644 index 0000000..b4a8158 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTNewMessageDetailedInfo.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTNewSessionCreated.class b/build/classes/org/telegram/mtproto/tl/MTNewSessionCreated.class new file mode 100644 index 0000000..743cd2b Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTNewSessionCreated.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTPing.class b/build/classes/org/telegram/mtproto/tl/MTPing.class new file mode 100644 index 0000000..bc13568 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTPing.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTPingDelayDisconnect.class b/build/classes/org/telegram/mtproto/tl/MTPingDelayDisconnect.class new file mode 100644 index 0000000..9bb19c2 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTPingDelayDisconnect.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTPong.class b/build/classes/org/telegram/mtproto/tl/MTPong.class new file mode 100644 index 0000000..fafd238 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTPong.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTProtoContext$1.class b/build/classes/org/telegram/mtproto/tl/MTProtoContext$1.class new file mode 100644 index 0000000..05d968c Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTProtoContext$1.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTProtoContext$ContextHolder.class b/build/classes/org/telegram/mtproto/tl/MTProtoContext$ContextHolder.class new file mode 100644 index 0000000..fa0b226 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTProtoContext$ContextHolder.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTProtoContext.class b/build/classes/org/telegram/mtproto/tl/MTProtoContext.class new file mode 100644 index 0000000..114a508 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTProtoContext.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTRpcError.class b/build/classes/org/telegram/mtproto/tl/MTRpcError.class new file mode 100644 index 0000000..1cab0a8 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTRpcError.class differ diff --git a/build/classes/org/telegram/mtproto/tl/MTRpcResult.class b/build/classes/org/telegram/mtproto/tl/MTRpcResult.class new file mode 100644 index 0000000..7992c96 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/MTRpcResult.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/ClientDhInner.class b/build/classes/org/telegram/mtproto/tl/pq/ClientDhInner.class new file mode 100644 index 0000000..a31bf9e Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/ClientDhInner.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/DhGenFailure.class b/build/classes/org/telegram/mtproto/tl/pq/DhGenFailure.class new file mode 100644 index 0000000..d46945e Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/DhGenFailure.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/DhGenOk.class b/build/classes/org/telegram/mtproto/tl/pq/DhGenOk.class new file mode 100644 index 0000000..a8dbdd9 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/DhGenOk.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/DhGenResult.class b/build/classes/org/telegram/mtproto/tl/pq/DhGenResult.class new file mode 100644 index 0000000..b2ce80d Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/DhGenResult.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/DhGenRetry.class b/build/classes/org/telegram/mtproto/tl/pq/DhGenRetry.class new file mode 100644 index 0000000..3d07e2d Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/DhGenRetry.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/PQInner.class b/build/classes/org/telegram/mtproto/tl/pq/PQInner.class new file mode 100644 index 0000000..be00c2e Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/PQInner.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/ReqDhParams.class b/build/classes/org/telegram/mtproto/tl/pq/ReqDhParams.class new file mode 100644 index 0000000..346bc4d Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/ReqDhParams.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/ReqPQ.class b/build/classes/org/telegram/mtproto/tl/pq/ReqPQ.class new file mode 100644 index 0000000..e7ed04f Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/ReqPQ.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/ReqSetDhClientParams.class b/build/classes/org/telegram/mtproto/tl/pq/ReqSetDhClientParams.class new file mode 100644 index 0000000..06ddbc8 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/ReqSetDhClientParams.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/ResPQ.class b/build/classes/org/telegram/mtproto/tl/pq/ResPQ.class new file mode 100644 index 0000000..5072365 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/ResPQ.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/ServerDhFailure.class b/build/classes/org/telegram/mtproto/tl/pq/ServerDhFailure.class new file mode 100644 index 0000000..c99e231 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/ServerDhFailure.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/ServerDhInner.class b/build/classes/org/telegram/mtproto/tl/pq/ServerDhInner.class new file mode 100644 index 0000000..0d0cad0 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/ServerDhInner.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/ServerDhOk.class b/build/classes/org/telegram/mtproto/tl/pq/ServerDhOk.class new file mode 100644 index 0000000..ca89b92 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/ServerDhOk.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/ServerDhParams.class b/build/classes/org/telegram/mtproto/tl/pq/ServerDhParams.class new file mode 100644 index 0000000..eaa7db5 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/ServerDhParams.class differ diff --git a/build/classes/org/telegram/mtproto/tl/pq/TLInitContext.class b/build/classes/org/telegram/mtproto/tl/pq/TLInitContext.class new file mode 100644 index 0000000..c36bec9 Binary files /dev/null and b/build/classes/org/telegram/mtproto/tl/pq/TLInitContext.class differ diff --git a/build/classes/org/telegram/mtproto/transport/ConnectionType.class b/build/classes/org/telegram/mtproto/transport/ConnectionType.class new file mode 100644 index 0000000..7c256de Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/ConnectionType.class differ diff --git a/build/classes/org/telegram/mtproto/transport/PlainTcpConnection.class b/build/classes/org/telegram/mtproto/transport/PlainTcpConnection.class new file mode 100644 index 0000000..a5e5af5 Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/PlainTcpConnection.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TcpContext$1.class b/build/classes/org/telegram/mtproto/transport/TcpContext$1.class new file mode 100644 index 0000000..6af948c Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TcpContext$1.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TcpContext$DieThread.class b/build/classes/org/telegram/mtproto/transport/TcpContext$DieThread.class new file mode 100644 index 0000000..5f2dd09 Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TcpContext$DieThread.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TcpContext$Package.class b/build/classes/org/telegram/mtproto/transport/TcpContext$Package.class new file mode 100644 index 0000000..902ee22 Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TcpContext$Package.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TcpContext$ReaderThread.class b/build/classes/org/telegram/mtproto/transport/TcpContext$ReaderThread.class new file mode 100644 index 0000000..8338845 Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TcpContext$ReaderThread.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TcpContext$WriterThread.class b/build/classes/org/telegram/mtproto/transport/TcpContext$WriterThread.class new file mode 100644 index 0000000..05a5d9d Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TcpContext$WriterThread.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TcpContext.class b/build/classes/org/telegram/mtproto/transport/TcpContext.class new file mode 100644 index 0000000..119bcc8 Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TcpContext.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TcpContextCallback.class b/build/classes/org/telegram/mtproto/transport/TcpContextCallback.class new file mode 100644 index 0000000..3e7a1dc Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TcpContextCallback.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportPool$EncryptedMessage.class b/build/classes/org/telegram/mtproto/transport/TransportPool$EncryptedMessage.class new file mode 100644 index 0000000..2f77ab3 Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportPool$EncryptedMessage.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportPool.class b/build/classes/org/telegram/mtproto/transport/TransportPool.class new file mode 100644 index 0000000..10c909f Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportPool.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportPoolCallback.class b/build/classes/org/telegram/mtproto/transport/TransportPoolCallback.class new file mode 100644 index 0000000..41b485a Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportPoolCallback.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportRate$1.class b/build/classes/org/telegram/mtproto/transport/TransportRate$1.class new file mode 100644 index 0000000..108f2ee Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportRate$1.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportRate$Transport.class b/build/classes/org/telegram/mtproto/transport/TransportRate$Transport.class new file mode 100644 index 0000000..cef04d6 Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportRate$Transport.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportRate.class b/build/classes/org/telegram/mtproto/transport/TransportRate.class new file mode 100644 index 0000000..642466b Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportRate.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportTcpPool$1.class b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$1.class new file mode 100644 index 0000000..8698fad Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$1.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportTcpPool$2.class b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$2.class new file mode 100644 index 0000000..1b396a3 Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$2.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportTcpPool$CheckConnections.class b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$CheckConnections.class new file mode 100644 index 0000000..a9d98ed Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$CheckConnections.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportTcpPool$CheckDestroy.class b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$CheckDestroy.class new file mode 100644 index 0000000..0b6cd5a Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$CheckDestroy.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportTcpPool$ConnectionActor.class b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$ConnectionActor.class new file mode 100644 index 0000000..51a3cde Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$ConnectionActor.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportTcpPool$Schedule.class b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$Schedule.class new file mode 100644 index 0000000..ebea9ed Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$Schedule.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportTcpPool$SchedullerActor.class b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$SchedullerActor.class new file mode 100644 index 0000000..15e5a70 Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$SchedullerActor.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportTcpPool$TcpListener.class b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$TcpListener.class new file mode 100644 index 0000000..2b20c4a Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportTcpPool$TcpListener.class differ diff --git a/build/classes/org/telegram/mtproto/transport/TransportTcpPool.class b/build/classes/org/telegram/mtproto/transport/TransportTcpPool.class new file mode 100644 index 0000000..05f9805 Binary files /dev/null and b/build/classes/org/telegram/mtproto/transport/TransportTcpPool.class differ diff --git a/build/classes/org/telegram/mtproto/util/BytesCache.class b/build/classes/org/telegram/mtproto/util/BytesCache.class new file mode 100644 index 0000000..0522533 Binary files /dev/null and b/build/classes/org/telegram/mtproto/util/BytesCache.class differ diff --git a/build/classes/org/telegram/mtproto/util/TimeUtil.class b/build/classes/org/telegram/mtproto/util/TimeUtil.class new file mode 100644 index 0000000..f98ad59 Binary files /dev/null and b/build/classes/org/telegram/mtproto/util/TimeUtil.class differ diff --git a/build/jar/telegram_backup.jar b/build/jar/telegram_backup.jar new file mode 100644 index 0000000..bccb72f Binary files /dev/null and b/build/jar/telegram_backup.jar differ diff --git a/lib/tl-api-v23.jar b/lib/tl-api-v23.jar new file mode 100644 index 0000000..3d6dede Binary files /dev/null and b/lib/tl-api-v23.jar differ diff --git a/lib/tl-core-1.1.0.jar b/lib/tl-core-1.1.0.jar new file mode 100644 index 0000000..ac31a12 Binary files /dev/null and b/lib/tl-core-1.1.0.jar differ diff --git a/src/com/droidkit/actors/Actor.java b/src/com/droidkit/actors/Actor.java new file mode 100644 index 0000000..4149be3 --- /dev/null +++ b/src/com/droidkit/actors/Actor.java @@ -0,0 +1,297 @@ +package com.droidkit.actors; + +import com.droidkit.actors.mailbox.Mailbox; +import com.droidkit.actors.messages.DeadLetter; +import com.droidkit.actors.messages.NamedMessage; +import com.droidkit.actors.tasks.*; +import com.droidkit.actors.tasks.messages.TaskError; +import com.droidkit.actors.tasks.messages.TaskResult; +import com.droidkit.actors.tasks.messages.TaskTimeout; + +import java.lang.reflect.Array; +import java.util.UUID; + +/** + * Actor object + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class Actor { + + private UUID uuid; + private String path; + + private ActorContext context; + private Mailbox mailbox; + + private ActorAskImpl askPattern; + + public Actor() { + + } + + /** + *

INTERNAL API

+ * Initialization of actor + * + * @param uuid uuid of actor + * @param path path of actor + * @param context context of actor + * @param mailbox mailbox of actor + */ + public final void initActor(UUID uuid, String path, ActorContext context, Mailbox mailbox) { + this.uuid = uuid; + this.path = path; + this.context = context; + this.mailbox = mailbox; + this.askPattern = new ActorAskImpl(self()); + } + + /** + * Actor System + * + * @return Actor System + */ + public final ActorSystem system() { + return context.getSystem(); + } + + /** + * Self actor reference + * + * @return self reference + */ + public final ActorRef self() { + return context.getSelf(); + } + + /** + * Actor context + * + * @return context + */ + protected final ActorContext context() { + return context; + } + + /** + * Sender of last received message + * + * @return sender's ActorRef + */ + public final ActorRef sender() { + return context.sender(); + } + + /** + * Actor UUID + * + * @return uuid + */ + protected final UUID getUuid() { + return uuid; + } + + /** + * Actor path + * + * @return path + */ + protected final String getPath() { + return path; + } + + /** + * Actor mailbox + * + * @return mailbox + */ + public final Mailbox getMailbox() { + return mailbox; + } + + /** + * Called before first message receiving + */ + public void preStart() { + + } + + public final void onReceiveGlobal(Object message) { + if (message instanceof DeadLetter) { + if (askPattern.onDeadLetter((DeadLetter) message)) { + return; + } + } else if (message instanceof TaskResult) { + if (askPattern.onTaskResult((TaskResult) message)) { + return; + } + } else if (message instanceof TaskTimeout) { + if (askPattern.onTaskTimeout((TaskTimeout) message)) { + return; + } + } else if (message instanceof TaskError) { + if (askPattern.onTaskError((TaskError) message)) { + return; + } + } + onReceive(message); + } + + /** + * Receiving of message + * + * @param message message + */ + public void onReceive(Object message) { + + } + + /** + * Called after actor shutdown + */ + public void postStop() { + + } + + /** + * Reply message to sender of last message + * + * @param message reply message + */ + public void reply(Object message) { + if (context.sender() != null) { + context.sender().send(message, self()); + } + } + + public AskFuture combine(AskFuture... futures) { + return askPattern.combine(futures); + } + + public AskFuture combine(AskCallback callback, AskFuture... futures) { + AskFuture future = combine(futures); + future.addListener(callback); + return future; + } + + public AskFuture combine(final String name, final Class clazz, AskFuture... futures) { + return combine(new AskCallback() { + @Override + public void onResult(Object[] result) { + T[] res = (T[]) Array.newInstance(clazz, result.length); + for (int i = 0; i < result.length; i++) { + res[i] = (T) result[i]; + } + self().send(new NamedMessage(name, res)); + } + + @Override + public void onError(Throwable throwable) { + self().send(new NamedMessage(name, throwable)); + } + }, futures); + } + + public AskFuture combine(final String name, AskFuture... futures) { + return combine(new AskCallback() { + @Override + public void onResult(Object[] result) { + self().send(new NamedMessage(name, result)); + } + + @Override + public void onError(Throwable throwable) { + self().send(new NamedMessage(name, throwable)); + } + }, futures); + } + + /** + * Ask TaskActor for result + * + * @param selection ActorSelection of task + * @return Future + */ + public AskFuture ask(ActorSelection selection) { + return askPattern.ask(system().actorOf(selection), 0, null); + } + + /** + * Ask TaskActor for result + * + * @param selection ActorSelection of task + * @param timeout timeout of task + * @return Future + */ + public AskFuture ask(ActorSelection selection, long timeout) { + return askPattern.ask(system().actorOf(selection), timeout, null); + } + + /** + * Ask TaskActor for result + * + * @param selection ActorSelection of task + * @param callback callback for ask + * @return Future + */ + public AskFuture ask(ActorSelection selection, AskCallback callback) { + return askPattern.ask(system().actorOf(selection), 0, callback); + } + + /** + * Ask TaskActor for result + * + * @param selection ActorSelection of task + * @param timeout timeout of task + * @param callback callback for ask + * @return Future + */ + public AskFuture ask(ActorSelection selection, long timeout, AskCallback callback) { + return askPattern.ask(system().actorOf(selection), timeout, callback); + } + + /** + * Ask TaskActor for result + * + * @param ref ActorRef of task + * @return Future + */ + public AskFuture ask(ActorRef ref) { + return askPattern.ask(ref, 0, null); + } + + /** + * Ask TaskActor for result + * + * @param ref ActorRef of task + * @param timeout timeout of task + * @return Future + */ + public AskFuture ask(ActorRef ref, long timeout) { + return askPattern.ask(ref, timeout, null); + } + + /** + * Ask TaskActor for result + * + * @param ref ActorRef of task + * @param callback callback for ask + * @return Future + */ + public AskFuture ask(ActorRef ref, AskCallback callback) { + return askPattern.ask(ref, 0, callback); + } + + /** + * Ask TaskActor for result + * + * @param ref ActorRef of task + * @param timeout timeout of task + * @param callback callback for ask + * @return Future + */ + public AskFuture ask(ActorRef ref, long timeout, AskCallback callback) { + return askPattern.ask(ref, timeout, callback); + } +} \ No newline at end of file diff --git a/src/com/droidkit/actors/ActorContext.java b/src/com/droidkit/actors/ActorContext.java new file mode 100644 index 0000000..6034a22 --- /dev/null +++ b/src/com/droidkit/actors/ActorContext.java @@ -0,0 +1,59 @@ +package com.droidkit.actors; + +/** + * Context of actor + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class ActorContext { + private final ActorScope actorScope; + + /** + *

INTERNAL API

+ * Creating of actor context + * + * @param scope actor scope + */ + public ActorContext(ActorScope scope) { + this.actorScope = scope; + } + + /** + * Actor Reference + * + * @return reference + */ + public ActorRef getSelf() { + return actorScope.getActorRef(); + } + + /** + * Actor system + * + * @return Actor system + */ + public ActorSystem getSystem() { + return actorScope.getActorSystem(); + } + + + /** + * Sender of last received message + * + * @return sender's ActorRef + */ + public ActorRef sender() { + return actorScope.getSender(); + } + + /** + * Stopping actor + */ + public void stopSelf() { + try { + actorScope.shutdownActor(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/com/droidkit/actors/ActorCreator.java b/src/com/droidkit/actors/ActorCreator.java new file mode 100644 index 0000000..2378127 --- /dev/null +++ b/src/com/droidkit/actors/ActorCreator.java @@ -0,0 +1,15 @@ +package com.droidkit.actors; + +/** + * Object for manual actors creating + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public interface ActorCreator { + /** + * Create actor + * + * @return Actor + */ + public T create(); +} diff --git a/src/com/droidkit/actors/ActorRef.java b/src/com/droidkit/actors/ActorRef.java new file mode 100644 index 0000000..6a23870 --- /dev/null +++ b/src/com/droidkit/actors/ActorRef.java @@ -0,0 +1,121 @@ +package com.droidkit.actors; + +import com.droidkit.actors.mailbox.AbsActorDispatcher; + +import java.util.UUID; + +/** + * Reference to Actor that allows to send messages to real Actor + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class ActorRef { + private ActorSystem system; + private AbsActorDispatcher dispatcher; + private UUID uuid; + private String path; + + public UUID getUuid() { + return uuid; + } + + public String getPath() { + return path; + } + + /** + *

INTERNAL API

+ * Creating actor reference + * + * @param system actor system + * @param dispatcher dispatcher of actor + * @param path path of actor + * @param uuid uuid of actor + */ + public ActorRef(ActorSystem system, AbsActorDispatcher dispatcher, UUID uuid, String path) { + this.system = system; + this.dispatcher = dispatcher; + this.uuid = uuid; + this.path = path; + } + + /** + * Send message with empty sender + * + * @param message message + */ + public void send(Object message) { + send(message, null); + } + + /** + * Send message with specified sender + * + * @param message message + * @param sender sender + */ + public void send(Object message, ActorRef sender) { + send(message, 0, sender); + } + + /** + * Send message with empty sender and delay + * + * @param message message + * @param delay delay + */ + public void send(Object message, long delay) { + send(message, delay, null); + } + + /** + * Send message + * + * @param message message + * @param delay delay + * @param sender sender + */ + public void send(Object message, long delay, ActorRef sender) { + dispatcher.sendMessage(path, message, ActorTime.currentTime() + delay, sender); + } + + /** + * Send message once + * + * @param message message + */ + public void sendOnce(Object message) { + send(message, null); + } + + /** + * Send message once + * + * @param message message + * @param sender sender + */ + public void sendOnce(Object message, ActorRef sender) { + sendOnce(message, 0, sender); + } + + /** + * Send message once + * + * @param message message + * @param delay delay + */ + public void sendOnce(Object message, long delay) { + sendOnce(message, delay, null); + } + + /** + * Send message once + * + * @param message message + * @param delay delay + * @param sender sender + */ + public void sendOnce(Object message, long delay, ActorRef sender) { + dispatcher.sendMessageOnce(path, message, ActorTime.currentTime() + delay, sender); + } +} diff --git a/src/com/droidkit/actors/ActorScope.java b/src/com/droidkit/actors/ActorScope.java new file mode 100644 index 0000000..80b5d51 --- /dev/null +++ b/src/com/droidkit/actors/ActorScope.java @@ -0,0 +1,128 @@ +package com.droidkit.actors; + +import com.droidkit.actors.mailbox.AbsActorDispatcher; +import com.droidkit.actors.mailbox.Mailbox; + +import java.util.UUID; + +/** + *

INTERNAL API

+ * Actor Scope contains states of actor, UUID, Path, Props and Actor (if created). + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class ActorScope { + + public static final int STATE_STARTING = 0; + public static final int STATE_RUNNING = 1; + public static final int STATE_SHUTDOWN = 2; + + private final UUID uuid; + private final String path; + private final Props props; + + private final ActorRef actorRef; + private final Mailbox mailbox; + + private final AbsActorDispatcher dispatcher; + + private final ActorSystem actorSystem; + + private int state; + + private Actor actor; + + private ActorRef sender; + + public ActorScope(ActorSystem actorSystem, Mailbox mailbox, ActorRef actorRef, AbsActorDispatcher dispatcher, UUID uuid, String path, Props props) { + this.actorSystem = actorSystem; + this.mailbox = mailbox; + this.actorRef = actorRef; + this.dispatcher = dispatcher; + this.uuid = uuid; + this.path = path; + this.props = props; + this.state = STATE_STARTING; + } + + public AbsActorDispatcher getDispatcher() { + return dispatcher; + } + + public int getState() { + return state; + } + + public UUID getUuid() { + return uuid; + } + + public String getPath() { + return path; + } + + public Props getProps() { + return props; + } + + public ActorRef getActorRef() { + return actorRef; + } + + public Mailbox getMailbox() { + return mailbox; + } + + public Actor getActor() { + return actor; + } + + public ActorSystem getActorSystem() { + return actorSystem; + } + + public ActorRef getSender() { + return sender; + } + + public void setSender(ActorRef sender) { + this.sender = sender; + } + + /** + * Create actor + * + * @throws Exception + */ + public void createActor() throws Exception { + if (state == STATE_STARTING) { + actor = props.create(); + CurrentActor.setCurrentActor(actor); + actor.initActor(getUuid(), getPath(), new ActorContext(this), getMailbox()); + actor.preStart(); + } else if (state == STATE_RUNNING) { + throw new RuntimeException("Actor already created"); + } else if (state == STATE_SHUTDOWN) { + throw new RuntimeException("Actor shutdown"); + } else { + throw new RuntimeException("Unknown ActorScope state"); + } + } + + /** + * Shutdown actor + * + * @throws Exception + */ + public void shutdownActor() throws Exception { + if (state == STATE_STARTING || state == STATE_RUNNING || + state == STATE_SHUTDOWN) { + actorSystem.removeActor(this); + dispatcher.disconnectScope(this); + actor.postStop(); + actor = null; + } else { + throw new RuntimeException("Unknown ActorScope state"); + } + } +} diff --git a/src/com/droidkit/actors/ActorSelection.java b/src/com/droidkit/actors/ActorSelection.java new file mode 100644 index 0000000..2f65941 --- /dev/null +++ b/src/com/droidkit/actors/ActorSelection.java @@ -0,0 +1,24 @@ +package com.droidkit.actors; + +/** + * Actor selection: group and path of actor + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class ActorSelection { + private final Props props; + private final String path; + + public ActorSelection(Props props, String path) { + this.props = props; + this.path = path; + } + + public Props getProps() { + return props; + } + + public String getPath() { + return path; + } +} diff --git a/src/com/droidkit/actors/ActorSystem.java b/src/com/droidkit/actors/ActorSystem.java new file mode 100644 index 0000000..02b0d96 --- /dev/null +++ b/src/com/droidkit/actors/ActorSystem.java @@ -0,0 +1,129 @@ +package com.droidkit.actors; + +import com.droidkit.actors.mailbox.AbsActorDispatcher; +import com.droidkit.actors.mailbox.ActorDispatcher; + +import java.util.HashMap; + +/** + * Entry point for Actor Model, creates all actors and dispatchers + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class ActorSystem { + + private static final ActorSystem mainSystem = new ActorSystem(); + + /** + * Main actor system + * + * @return ActorSystem + */ + public static ActorSystem system() { + return mainSystem; + } + + private static final String DEFAULT_DISPATCHER = "default"; + + private final HashMap dispatchers = new HashMap(); + private final HashMap actors = new HashMap(); + + /** + * Creating new actor system + */ + public ActorSystem() { + addDispatcher(DEFAULT_DISPATCHER); + } + + /** + * Adding dispatcher with threads count = {@code Runtime.getRuntime().availableProcessors()} + * + * @param dispatcherId dispatcher id + */ + public void addDispatcher(String dispatcherId) { + addDispatcher(dispatcherId, new ActorDispatcher(this, Runtime.getRuntime().availableProcessors())); + } + + /** + * Registering custom dispatcher + * + * @param dispatcherId dispatcher id + * @param dispatcher dispatcher object + */ + public void addDispatcher(String dispatcherId, AbsActorDispatcher dispatcher) { + synchronized (dispatchers) { + if (dispatchers.containsKey(dispatcherId)) { + return; + } + dispatchers.put(dispatcherId, dispatcher); + } + } + + public ActorRef actorOf(ActorSelection selection) { + return actorOf(selection.getProps(), selection.getPath()); + } + + /** + * Creating or getting existing actor from actor class + * + * @param actor Actor Class + * @param path Actor Path + * @param Actor Class + * @return ActorRef + */ + public ActorRef actorOf(Class actor, String path) { + return actorOf(Props.create(actor), path); + } + + /** + * Creating or getting existing actor from actor props + * + * @param props Actor Props + * @param path Actor Path + * @return ActorRef + */ + public ActorRef actorOf(Props props, String path) { + // TODO: Remove lock + synchronized (actors) { + // Searching for already created actor + ActorScope scope = actors.get(path); + + // If already created - return ActorRef + if (scope != null) { + return scope.getActorRef(); + } + + // Finding dispatcher for actor + String dispatcherId = props.getDispatcher() == null ? DEFAULT_DISPATCHER : props.getDispatcher(); + + AbsActorDispatcher mailboxesDispatcher; + synchronized (dispatchers) { + if (!dispatchers.containsKey(dispatcherId)) { + throw new RuntimeException("Unknown dispatcherId '" + dispatcherId + "'"); + } + mailboxesDispatcher = dispatchers.get(dispatcherId); + } + + // Creating actor scope + scope = mailboxesDispatcher.createScope(path, props); + + // Saving actor in collection + actors.put(path, scope); + + return scope.getActorRef(); + } + } + + /** + * WARRING! Call only during processing message in actor! + * + * @param scope Actor Scope + */ + void removeActor(ActorScope scope) { + synchronized (actors) { + if (actors.get(scope.getPath()) == scope) { + actors.remove(scope.getPath()); + } + } + } +} \ No newline at end of file diff --git a/src/com/droidkit/actors/ActorTime.java b/src/com/droidkit/actors/ActorTime.java new file mode 100644 index 0000000..5d6ceeb --- /dev/null +++ b/src/com/droidkit/actors/ActorTime.java @@ -0,0 +1,27 @@ +package com.droidkit.actors; + +/** + * Time used by actor system, uses System.nanoTime() inside + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class ActorTime { + + private static long lastTime = 0; + private static final Object timeLock = new Object(); + + /** + * Getting current actor system time + * + * @return actor system time + */ + public static long currentTime() { + long res = System.nanoTime() / 1000000; + synchronized (timeLock) { + if (lastTime < res) { + lastTime = res; + } + return lastTime; + } + } +} \ No newline at end of file diff --git a/src/com/droidkit/actors/CurrentActor.java b/src/com/droidkit/actors/CurrentActor.java new file mode 100644 index 0000000..a2eec58 --- /dev/null +++ b/src/com/droidkit/actors/CurrentActor.java @@ -0,0 +1,19 @@ +package com.droidkit.actors; + +/** + *

INTERNAL API!

+ * Keeps current actor for thread. Will be used for better implementations of patterns. + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class CurrentActor { + private static ThreadLocal currentActor = new ThreadLocal(); + + public static void setCurrentActor(Actor actor) { + currentActor.set(actor); + } + + public static Actor getCurrentActor() { + return currentActor.get(); + } +} diff --git a/src/com/droidkit/actors/Props.java b/src/com/droidkit/actors/Props.java new file mode 100644 index 0000000..db884e1 --- /dev/null +++ b/src/com/droidkit/actors/Props.java @@ -0,0 +1,88 @@ +package com.droidkit.actors; + +/** + *

Props is a configuration class to specify options for the creation of actors, think of it as an immutable and + * thus freely shareable recipe for creating an actor including associated dispatcher information.

+ * For more information you may read about Akka Props. + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public final class Props { + private static final int TYPE_DEFAULT = 1; + private static final int TYPE_CREATOR = 2; + + private final Class aClass; + private final Object[] args; + private final int type; + private final ActorCreator creator; + + private final String dispatcher; + + private Props(Class aClass, Object[] args, int type, String dispatcher, ActorCreator creator) { + this.aClass = aClass; + this.args = args; + this.type = type; + this.creator = creator; + this.dispatcher = dispatcher; + } + + /** + * Creating actor from Props + * + * @return Actor + * @throws Exception + */ + public T create() throws Exception { + if (type == TYPE_DEFAULT) { + if (args == null || args.length == 0) { + return aClass.newInstance(); + } + } else if (type == TYPE_CREATOR) { + return creator.create(); + } + + throw new RuntimeException("Unsupported create method"); + } + + /** + * Getting dispatcher id if available + * + * @return + */ + public String getDispatcher() { + return dispatcher; + } + + /** + * Changing dispatcher + * + * @param dispatcher dispatcher id + * @return this + */ + public Props changeDispatcher(String dispatcher) { + return new Props(aClass, args, type, dispatcher, creator); + } + + /** + * Create props from class + * + * @param tClass Actor class + * @param Actor class + * @return Props object + */ + public static Props create(Class tClass) { + return new Props(tClass, null, TYPE_DEFAULT, null, null); + } + + /** + * Create props from Actor creator + * + * @param clazz Actor class + * @param creator Actor creator class + * @param Actor class + * @return Props object + */ + public static Props create(Class clazz, ActorCreator creator) { + return new Props(clazz, null, TYPE_CREATOR, null, creator); + } +} diff --git a/src/com/droidkit/actors/ReflectedActor.java b/src/com/droidkit/actors/ReflectedActor.java new file mode 100644 index 0000000..775b90b --- /dev/null +++ b/src/com/droidkit/actors/ReflectedActor.java @@ -0,0 +1,137 @@ +package com.droidkit.actors; + +import com.droidkit.actors.messages.NamedMessage; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashSet; + +/** + * ReflectedActor is Actor that uses java reflection for processing of messages + * For each message developer must create method named "onReceive" with one argument + * with type of message + * For special message {@link com.droidkit.actors.messages.NamedMessage} you can create method + * named like {@code onDownloadReceive}. First letter in {@code Download} will be lowed and ReflectedActor + * will use this as {@code download} for name of message. + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class ReflectedActor extends Actor { + + private ArrayList events = new ArrayList(); + private ArrayList namedEvents = new ArrayList(); + + @Override + public final void preStart() { + Method[] methods = getClass().getDeclaredMethods(); + for (Method m : methods) { + if (m.getName().startsWith("onReceive") && m.getParameterTypes().length == 1) { + if (m.getName().equals("onReceive") && m.getParameterTypes()[0] == Object.class) { + continue; + } + events.add(new Event(m.getParameterTypes()[0], m)); + continue; + } + if (m.getName().startsWith("on") && m.getName().endsWith("Receive")) { + String methodName = m.getName(); + String name = methodName.substring("on".length(), methodName.length() - "Receive".length()); + if (name.length() > 0) { + name = name.substring(0, 1).toLowerCase() + name.substring(1); + namedEvents.add(new NamedEvent(name, m.getParameterTypes()[0], m)); + continue; + } + + } + } + preStartImpl(); + } + + /** + * Replacement for preStart + */ + public void preStartImpl() { + + } + + @Override + public void onReceive(Object message) { + if (message instanceof NamedMessage) { + NamedMessage named = (NamedMessage) message; + for (NamedEvent event : namedEvents) { + if (event.name.equals(named.getName())) { + if (event.check(named.getMessage())) { + try { + event.method.invoke(this, named.getMessage()); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + } + } + } + + for (Event event : events) { + if (event.check(message)) { + try { + event.method.invoke(this, message); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + return; + } + } + } + + class NamedEvent { + private String name; + private Class arg; + private Method method; + + NamedEvent(String name, Class arg, Method method) { + this.name = name; + this.arg = arg; + this.method = method; + } + + public String getName() { + return name; + } + + public Class getArg() { + return arg; + } + + public Method getMethod() { + return method; + } + + public boolean check(Object obj) { + if (arg.isAssignableFrom(obj.getClass())) { + return true; + } + return false; + } + } + + class Event { + private Class arg; + private Method method; + + Event(Class arg, Method method) { + this.arg = arg; + this.method = method; + } + + public boolean check(Object obj) { + if (arg.isAssignableFrom(obj.getClass())) { + return true; + } + return false; + } + } +} diff --git a/src/com/droidkit/actors/dispatch/AbstractDispatchQueue.java b/src/com/droidkit/actors/dispatch/AbstractDispatchQueue.java new file mode 100644 index 0000000..5ae209c --- /dev/null +++ b/src/com/droidkit/actors/dispatch/AbstractDispatchQueue.java @@ -0,0 +1,84 @@ +package com.droidkit.actors.dispatch; + +/** + * Queue for dispatching messages for {@link ThreadPoolDispatcher}. + * Implementation MUST BE thread-safe. + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public abstract class AbstractDispatchQueue { + + /** + * Value used for result of waitDelay when dispatcher need to wait forever + */ + protected static final long FOREVER = Long.MAX_VALUE; + + private QueueListener listener; + + /** + * Fetch message for dispatching and removing it from dispatch queue + * + * @param time current time from ActorTime + * @return message or null if there is no message for processing + */ + public abstract T dispatch(long time); + + /** + * Expected delay for nearest message. + * You might provide most accurate value as you can, + * this will minimize unnecessary thread work. + * For example, if you will return zero here then thread will + * loop continuously and consume processor time. + * + * @param time current time from ActorTime + * @return delay in ms + */ + public abstract long waitDelay(long time); + + /** + * Implementation of adding message to queue + * + * @param message + * @param atTime + */ + protected abstract void putToQueueImpl(T message, long atTime); + + /** + * Adding message to queue + * + * @param message message + * @param atTime time (use {@link com.droidkit.actors.ActorTime#currentTime()} for currentTime) + */ + public final void putToQueue(T message, long atTime) { + putToQueueImpl(message, atTime); + notifyQueueChanged(); + } + + /** + * Notification about queue change. + */ + protected void notifyQueueChanged() { + QueueListener lListener = listener; + if (lListener != null) { + lListener.onQueueChanged(); + } + } + + /** + * Getting of current queue listener + * + * @return queue listener + */ + public QueueListener getListener() { + return listener; + } + + /** + * Setting queue listener + * + * @param listener queue listener + */ + public void setListener(QueueListener listener) { + this.listener = listener; + } +} \ No newline at end of file diff --git a/src/com/droidkit/actors/dispatch/AbstractDispatcher.java b/src/com/droidkit/actors/dispatch/AbstractDispatcher.java new file mode 100644 index 0000000..ae0da79 --- /dev/null +++ b/src/com/droidkit/actors/dispatch/AbstractDispatcher.java @@ -0,0 +1,47 @@ +package com.droidkit.actors.dispatch; + +/** + * Abstract thread dispatcher for messages + */ +public abstract class AbstractDispatcher> { + final private Q queue; + final Dispatch dispatch; + + protected AbstractDispatcher(Q queue, Dispatch dispatch) { + this.queue = queue; + this.dispatch = dispatch; + this.queue.setListener(new QueueListener() { + @Override + public void onQueueChanged() { + notifyDispatcher(); + } + }); + } + + /** + * Queue used for dispatching + * + * @return queue + */ + public Q getQueue() { + return queue; + } + + /** + * Actual execution of action + * + * @param message action + */ + protected void dispatchMessage(T message) { + if (dispatch != null) { + dispatch.dispatchMessage(message); + } + } + + /** + * Notification about queue change + */ + protected void notifyDispatcher() { + + } +} diff --git a/src/com/droidkit/actors/dispatch/Dispatch.java b/src/com/droidkit/actors/dispatch/Dispatch.java new file mode 100644 index 0000000..845b2e7 --- /dev/null +++ b/src/com/droidkit/actors/dispatch/Dispatch.java @@ -0,0 +1,8 @@ +package com.droidkit.actors.dispatch; + +/** + * Used as callback for message processing + */ +public interface Dispatch { + void dispatchMessage(T message); +} diff --git a/src/com/droidkit/actors/dispatch/QueueListener.java b/src/com/droidkit/actors/dispatch/QueueListener.java new file mode 100644 index 0000000..b8b6ef8 --- /dev/null +++ b/src/com/droidkit/actors/dispatch/QueueListener.java @@ -0,0 +1,10 @@ +package com.droidkit.actors.dispatch; + +/** + * Listener for monitoring queue changes in dispatchers + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public interface QueueListener { + public void onQueueChanged(); +} \ No newline at end of file diff --git a/src/com/droidkit/actors/dispatch/RunnableDispatcher.java b/src/com/droidkit/actors/dispatch/RunnableDispatcher.java new file mode 100644 index 0000000..b034562 --- /dev/null +++ b/src/com/droidkit/actors/dispatch/RunnableDispatcher.java @@ -0,0 +1,70 @@ +package com.droidkit.actors.dispatch; + +import static com.droidkit.actors.ActorTime.currentTime; + +/** + * RunnableDispatcher is used for executing various Runnable in background + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class RunnableDispatcher extends ThreadPoolDispatcher> { + + /** + * Creating of dispatcher with one thread + */ + public RunnableDispatcher() { + this(1); + } + + /** + * Creating of dispatcher with {@code threadsCount} threads + * + * @param threadsCount number of threads + */ + public RunnableDispatcher(int threadsCount) { + super(threadsCount, new SimpleDispatchQueue()); + } + + /** + * Creating of dispatcher with {@code threadsCount} threads and {@code priority} + * + * @param threadsCount number of threads + * @param priority priority of threads + */ + public RunnableDispatcher(int threadsCount, int priority) { + super(threadsCount, priority, new SimpleDispatchQueue()); + } + + @Override + protected void dispatchMessage(Runnable object) { + object.run(); + } + + /** + * Post action to queue + * + * @param action action + */ + public void postAction(Runnable action) { + postAction(action, 0); + } + + /** + * Post action to queue with delay + * + * @param action action + * @param delay delay + */ + public void postAction(Runnable action, long delay) { + getQueue().putToQueue(action, currentTime() + delay); + } + + /** + * Removing action from queue + * + * @param action action + */ + public void removeAction(Runnable action) { + getQueue().removeFromQueue(action); + } +} \ No newline at end of file diff --git a/src/com/droidkit/actors/dispatch/SimpleDispatchQueue.java b/src/com/droidkit/actors/dispatch/SimpleDispatchQueue.java new file mode 100644 index 0000000..2cf4918 --- /dev/null +++ b/src/com/droidkit/actors/dispatch/SimpleDispatchQueue.java @@ -0,0 +1,116 @@ +package com.droidkit.actors.dispatch; + +import java.util.ArrayList; +import java.util.Map; +import java.util.TreeMap; + +/** + * Simple queue implementation for dispatchers + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class SimpleDispatchQueue extends AbstractDispatchQueue { + + protected final TreeMap messages = new TreeMap(); + + protected final ArrayList freeMessages = new ArrayList(); + + /** + * Removing message from queue + * + * @param t message + */ + public void removeFromQueue(T t) { + synchronized (messages) { + for (Map.Entry messageEntry : messages.entrySet()) { + if (messageEntry.getValue().equals(t)) { + Message message = messages.remove(messageEntry.getKey()); + recycle(message); + notifyQueueChanged(); + return; + } + } + } + } + + @Override + public T dispatch(long time) { + synchronized (messages) { + if (messages.size() > 0) { + long firstKey = messages.firstKey(); + if (firstKey < time) { + Message message = messages.remove(firstKey); + T res = message.action; + recycle(message); + return res; + } + } + } + return null; + } + + @Override + public long waitDelay(long time) { + synchronized (messages) { + if (messages.size() > 0) { + long firstKey = messages.firstKey(); + if (firstKey < time) { + return 0; + } else { + return time - firstKey; + } + } + } + return FOREVER; + } + + @Override + public void putToQueueImpl(T action, long atTime) { + Message message = obtainMessage(); + message.setMessage(action, atTime); + synchronized (messages) { + while (messages.containsKey(atTime)) { + atTime++; + } + messages.put(atTime, message); + } + } + + /** + * Getting new message object for writing to queue + * + * @return Message object + */ + protected Message obtainMessage() { + synchronized (freeMessages) { + if (freeMessages.size() > 0) { + return freeMessages.remove(0); + } + } + return new Message(); + } + + /** + * Saving message object to free cache + * + * @param message Message object + */ + protected void recycle(Message message) { + synchronized (freeMessages) { + freeMessages.add(message); + } + } + + /** + * Holder for messages + */ + protected class Message { + public long destTime; + public T action; + + public void setMessage(T action, long destTime) { + this.action = action; + this.destTime = destTime; + } + } +} \ No newline at end of file diff --git a/src/com/droidkit/actors/dispatch/ThreadPoolDispatcher.java b/src/com/droidkit/actors/dispatch/ThreadPoolDispatcher.java new file mode 100644 index 0000000..b78de1d --- /dev/null +++ b/src/com/droidkit/actors/dispatch/ThreadPoolDispatcher.java @@ -0,0 +1,129 @@ +package com.droidkit.actors.dispatch; + +import static com.droidkit.actors.ActorTime.currentTime; + +/** + * ThreadPoolDispatcher is used for dispatching messages on it's own threads. + * Class is completely thread-safe. + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class ThreadPoolDispatcher> extends AbstractDispatcher { + + private final Thread[] threads; + + private boolean isClosed = false; + + /** + * Dispatcher constructor. Create threads with NORM_PRIORITY. + * + * @param count thread count + * @param queue queue for messages + * (see {@link com.droidkit.actors.dispatch.AbstractDispatchQueue} for more information) + * @param dispatch Dispatch for message processing + */ + public ThreadPoolDispatcher(int count, Q queue, Dispatch dispatch) { + this(count, Thread.NORM_PRIORITY, queue, dispatch); + } + + /** + * Dispatcher constructor. Create threads with NORM_PRIORITY. + * Should override dispatchMessage for message processing. + * + * @param count thread count + * @param queue queue for messages + * (see {@link com.droidkit.actors.dispatch.AbstractDispatchQueue} for more information) + */ + public ThreadPoolDispatcher(int count, Q queue) { + this(count, Thread.NORM_PRIORITY, queue, null); + } + + /** + * Dispatcher constructor. Create threads with NORM_PRIORITY. + * Should override dispatchMessage for message processing. + * + * @param count thread count + * @param priority thread priority + * @param queue queue for messages + * (see {@link com.droidkit.actors.dispatch.AbstractDispatchQueue} for more information) + */ + public ThreadPoolDispatcher(int count, int priority, Q queue) { + this(count, priority, queue, null); + } + + + /** + * Dispatcher constructor + * + * @param count thread count + * @param priority thread priority + * @param queue queue for messages + * (see {@link com.droidkit.actors.dispatch.AbstractDispatchQueue} for more information) + * @param dispatch Dispatch for message processing + */ + public ThreadPoolDispatcher(int count, int priority, final Q queue, Dispatch dispatch) { + super(queue, dispatch); + + this.threads = new Thread[count]; + for (int i = 0; i < count; i++) { + this.threads[i] = new DispatcherThread(); + this.threads[i].setPriority(priority); + this.threads[i].start(); + } + } + + /** + * Closing of dispatcher no one actions will be executed after calling this method. + */ + public void close() { + isClosed = true; + notifyDispatcher(); + } + + /** + * Notification about queue change + */ + @Override + protected void notifyDispatcher() { + if (threads != null) { + synchronized (threads) { + threads.notifyAll(); + } + } + } + + /** + * Thread class for dispatching + */ + private class DispatcherThread extends Thread { + @Override + public void run() { + while (!isClosed) { + T action = getQueue().dispatch(currentTime()); + if (action == null) { + synchronized (threads) { + try { + long delay = getQueue().waitDelay(currentTime()); + if (delay > 0) { + threads.wait(delay); + } + continue; + } catch (InterruptedException e) { + e.printStackTrace(); + return; + } + } + } + + try { + dispatchMessage(action); + } catch (Throwable t) { + // Possibly danger situation, but i hope this will not corrupt JVM + // For example: on Android we could always continue execution after OutOfMemoryError + // Anyway, better to catch all errors manually in dispatchMessage + t.printStackTrace(); + } + } + } + } +} \ No newline at end of file diff --git a/src/com/droidkit/actors/mailbox/AbsActorDispatcher.java b/src/com/droidkit/actors/mailbox/AbsActorDispatcher.java new file mode 100644 index 0000000..951dd91 --- /dev/null +++ b/src/com/droidkit/actors/mailbox/AbsActorDispatcher.java @@ -0,0 +1,131 @@ +package com.droidkit.actors.mailbox; + +import com.droidkit.actors.*; +import com.droidkit.actors.dispatch.AbstractDispatcher; +import com.droidkit.actors.messages.DeadLetter; +import com.droidkit.actors.messages.PoisonPill; +import com.droidkit.actors.messages.StartActor; + +import java.util.HashMap; +import java.util.UUID; + +/** + * Abstract Actor Dispatcher, used for dispatching messages for actors + */ +public abstract class AbsActorDispatcher { + + private final HashMap mailboxes = new HashMap(); + private final HashMap scopes = new HashMap(); + private final HashMap actorProps = new HashMap(); + + private final ActorSystem actorSystem; + private AbstractDispatcher dispatcher; + + public AbsActorDispatcher(ActorSystem actorSystem) { + this.actorSystem = actorSystem; + } + + protected void initDispatcher(AbstractDispatcher dispatcher) { + if (this.dispatcher != null) { + throw new RuntimeException("Double dispatcher init"); + } + this.dispatcher = dispatcher; + } + + public final ActorScope createScope(String path, Props props) { + // TODO: add path check + + Mailbox mailbox = new Mailbox(dispatcher.getQueue()); + UUID uuid = UUID.randomUUID(); + ActorRef ref = new ActorRef(actorSystem, this, uuid, path); + ActorScope scope = new ActorScope(actorSystem, mailbox, ref, this, UUID.randomUUID(), path, props); + + synchronized (mailboxes) { + mailboxes.put(mailbox, scope); + scopes.put(scope.getPath(), scope); + actorProps.put(path, props); + } + + // Sending init message + scope.getActorRef().send(StartActor.INSTANCE); + return scope; + } + + public final void disconnectScope(ActorScope scope) { + synchronized (mailboxes) { + mailboxes.remove(scope.getMailbox()); + scopes.remove(scope.getPath()); + } + for (Envelope envelope : scope.getMailbox().allEnvelopes()) { + if (envelope.getSender() != null) { + envelope.getSender().send(new DeadLetter(envelope.getMessage())); + } + } + } + + public final void sendMessage(String path, Object message, long time, ActorRef sender) { + synchronized (mailboxes) { + if (!scopes.containsKey(path)) { + if (sender != null) { + sender.send(new DeadLetter(message)); + } + } else { + Mailbox mailbox = scopes.get(path).getMailbox(); + mailbox.schedule(new Envelope(message, mailbox, sender), time); + } + } + } + + public final void sendMessageOnce(String path, Object message, long time, ActorRef sender) { + synchronized (mailboxes) { + if (!scopes.containsKey(path)) { + if (sender != null) { + sender.send(new DeadLetter(message)); + } + } else { + Mailbox mailbox = scopes.get(path).getMailbox(); + mailbox.scheduleOnce(new Envelope(message, mailbox, sender), time); + } + } + } + + + /** + * Processing of envelope + * + * @param envelope envelope + */ + protected void processEnvelope(Envelope envelope) { + ActorScope actor = null; + synchronized (mailboxes) { + actor = mailboxes.get(envelope.getMailbox()); + } + if (actor == null) { + //TODO: add logging + return; + } + + try { + if (envelope.getMessage() == StartActor.INSTANCE) { + try { + actor.createActor(); + } catch (Exception e) { + e.printStackTrace(); + } + } else if (envelope.getMessage() == PoisonPill.INSTANCE) { + try { + actor.shutdownActor(); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + CurrentActor.setCurrentActor(actor.getActor()); + actor.setSender(envelope.getSender()); + actor.getActor().onReceiveGlobal(envelope.getMessage()); + } + } finally { + dispatcher.getQueue().unlockMailbox(envelope.getMailbox()); + CurrentActor.setCurrentActor(null); + } + } +} diff --git a/src/com/droidkit/actors/mailbox/ActorDispatcher.java b/src/com/droidkit/actors/mailbox/ActorDispatcher.java new file mode 100644 index 0000000..c6d168e --- /dev/null +++ b/src/com/droidkit/actors/mailbox/ActorDispatcher.java @@ -0,0 +1,25 @@ +package com.droidkit.actors.mailbox; + +import com.droidkit.actors.ActorSystem; +import com.droidkit.actors.dispatch.Dispatch; +import com.droidkit.actors.dispatch.ThreadPoolDispatcher; + +/** + * Basic ActorDispatcher backed by ThreadPoolDispatcher + */ +public class ActorDispatcher extends AbsActorDispatcher { + public ActorDispatcher(ActorSystem actorSystem, int threadsCount) { + this(actorSystem, threadsCount, Thread.MIN_PRIORITY); + } + + public ActorDispatcher(ActorSystem actorSystem, int threadsCount, int priority) { + super(actorSystem); + initDispatcher(new ThreadPoolDispatcher(threadsCount, priority, new MailboxesQueue(), + new Dispatch() { + @Override + public void dispatchMessage(Envelope message) { + processEnvelope(message); + } + })); + } +} diff --git a/src/com/droidkit/actors/mailbox/Envelope.java b/src/com/droidkit/actors/mailbox/Envelope.java new file mode 100644 index 0000000..cfe28ca --- /dev/null +++ b/src/com/droidkit/actors/mailbox/Envelope.java @@ -0,0 +1,54 @@ +package com.droidkit.actors.mailbox; + +import com.droidkit.actors.ActorRef; + +/** + * Actor system envelope + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class Envelope { + private final Object message; + private final ActorRef sender; + private final Mailbox mailbox; + + /** + * Creating of envelope + * + * @param message message + * @param mailbox mailbox + * @param sender sender reference + */ + public Envelope(Object message, Mailbox mailbox, ActorRef sender) { + this.message = message; + this.sender = sender; + this.mailbox = mailbox; + } + + /** + * Message in envelope + * + * @return message + */ + public Object getMessage() { + return message; + } + + /** + * Mailbox for envelope + * + * @return mailbox + */ + public Mailbox getMailbox() { + return mailbox; + } + + /** + * Sender of message + * + * @return sender reference + */ + public ActorRef getSender() { + return sender; + } +} diff --git a/src/com/droidkit/actors/mailbox/Mailbox.java b/src/com/droidkit/actors/mailbox/Mailbox.java new file mode 100644 index 0000000..8202462 --- /dev/null +++ b/src/com/droidkit/actors/mailbox/Mailbox.java @@ -0,0 +1,88 @@ +package com.droidkit.actors.mailbox; + +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Actor mailbox, queue of envelopes. + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class Mailbox { + private final ConcurrentHashMap envelopes = new ConcurrentHashMap(); + + private MailboxesQueue queue; + + /** + * Creating mailbox + * + * @param queue MailboxesQueue + */ + public Mailbox(MailboxesQueue queue) { + this.queue = queue; + } + + /** + * Send envelope at time + * + * @param envelope envelope + * @param time time + */ + public void schedule(Envelope envelope, long time) { + if (envelope.getMailbox() != this) { + throw new RuntimeException("envelope.mailbox != this mailbox"); + } + + long id = queue.sendEnvelope(envelope, time); + + envelopes.put(id, envelope); + } + + /** + * Send envelope once at time + * + * @param envelope envelope + * @param time time + */ + public void scheduleOnce(Envelope envelope, long time) { + if (envelope.getMailbox() != this) { + throw new RuntimeException("envelope.mailbox != this mailbox"); + } + + Iterator> iterator = envelopes.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (isEqualEnvelope(entry.getValue(), envelope)) { + queue.removeEnvelope(entry.getKey()); + iterator.remove(); + } + } + + schedule(envelope, time); + } + + void removeEnvelope(long key) { + envelopes.remove(key); + } + + /** + * Override this if you need to change filtering for scheduleOnce behaviour. + * By default it check equality only of class names. + * + * @param a + * @param b + * @return is equal + */ + protected boolean isEqualEnvelope(Envelope a, Envelope b) { + return a.getMessage().getClass() == b.getMessage().getClass(); + } + + public synchronized Envelope[] allEnvelopes() { + return envelopes.values().toArray(new Envelope[0]); + } + + public synchronized int getMailboxSize() { + return envelopes.size(); + } +} \ No newline at end of file diff --git a/src/com/droidkit/actors/mailbox/MailboxesQueue.java b/src/com/droidkit/actors/mailbox/MailboxesQueue.java new file mode 100644 index 0000000..e6adbbf --- /dev/null +++ b/src/com/droidkit/actors/mailbox/MailboxesQueue.java @@ -0,0 +1,145 @@ +package com.droidkit.actors.mailbox; + +import com.droidkit.actors.dispatch.AbstractDispatchQueue; + +import java.util.HashSet; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Queue of multiple mailboxes for MailboxesDispatcher + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class MailboxesQueue extends AbstractDispatchQueue { + + private static final long MULTIPLE = 10000L; + + private final TreeMap timeShift = new TreeMap(); + private final TreeMap envelopes = new TreeMap(); + private final HashSet blocked = new HashSet(); + + /** + * Locking mailbox from processing messages from it + * + * @param mailbox mailbox for locking + */ + public void lockMailbox(Mailbox mailbox) { + synchronized (blocked) { + blocked.add(mailbox); + } + notifyQueueChanged(); + } + + /** + * Unlocking mailbox + * + * @param mailbox mailbox for unlocking + */ + public void unlockMailbox(Mailbox mailbox) { + synchronized (blocked) { + blocked.remove(mailbox); + } + notifyQueueChanged(); + } + + /** + * Sending envelope + * + * @param envelope envelope + * @param time time (see {@link com.droidkit.actors.ActorTime#currentTime()}}) + * @return envelope real time + */ + public long sendEnvelope(Envelope envelope, long time) { + long shift = 0; + synchronized (envelopes) { + if (timeShift.containsKey(time)) { + shift = timeShift.get(time); + } + while (envelopes.containsKey(time * MULTIPLE + shift)) { + shift++; + } + + if (shift != 0) { + timeShift.put(time, shift); + } + envelopes.put(time * MULTIPLE + shift, envelope); + } + notifyQueueChanged(); + return time * MULTIPLE + shift; + } + + /** + * Removing envelope from queue + * + * @param id envelope id + */ + public void removeEnvelope(long id) { + synchronized (envelopes) { + envelopes.remove(id); + } + notifyQueueChanged(); + } + + /** + * getting first available envelope + * MUST BE wrapped with envelopes and blocked sync + * + * @return envelope entry + */ + private Map.Entry firstEnvelope() { + for (Map.Entry entry : envelopes.entrySet()) { + if (!blocked.contains(entry.getValue().getMailbox())) { + return entry; + } + } + return null; + } + + @Override + public Envelope dispatch(long time) { + time = time * MULTIPLE; + synchronized (envelopes) { + synchronized (blocked) { + Map.Entry envelope = firstEnvelope(); + if (envelope != null) { + if (envelope.getKey() < time) { + envelopes.remove(envelope.getKey()); + envelope.getValue().getMailbox().removeEnvelope(envelope.getKey()); + //TODO: Better design + // Locking of mailbox before dispatch return + blocked.add(envelope.getValue().getMailbox()); + return envelope.getValue(); + } + } + } + } + return null; + } + + @Override + public long waitDelay(long time) { + time = time * MULTIPLE; + synchronized (envelopes) { + synchronized (blocked) { + Map.Entry envelope = firstEnvelope(); + + if (envelope != null) { + if (envelope.getKey() <= time) { + return 0; + } else { + return (time - envelope.getKey()) / MULTIPLE; + } + } + } + } + return FOREVER; + } + + @Override + protected void putToQueueImpl(Envelope message, long atTime) { + sendEnvelope(message, atTime); + } +} diff --git a/src/com/droidkit/actors/messages/DeadLetter.java b/src/com/droidkit/actors/messages/DeadLetter.java new file mode 100644 index 0000000..4d90dfe --- /dev/null +++ b/src/com/droidkit/actors/messages/DeadLetter.java @@ -0,0 +1,21 @@ +package com.droidkit.actors.messages; + +/** + * DeadLetter sent whet message was not processed by target actor + */ +public class DeadLetter { + private Object message; + + public DeadLetter(Object message) { + this.message = message; + } + + public Object getMessage() { + return message; + } + + @Override + public String toString() { + return "DeadLetter(" + message + ")"; + } +} diff --git a/src/com/droidkit/actors/messages/NamedMessage.java b/src/com/droidkit/actors/messages/NamedMessage.java new file mode 100644 index 0000000..bd1ec93 --- /dev/null +++ b/src/com/droidkit/actors/messages/NamedMessage.java @@ -0,0 +1,22 @@ +package com.droidkit.actors.messages; + +/** + * Simple named message + */ +public class NamedMessage { + private String name; + private Object message; + + public NamedMessage(String name, Object message) { + this.name = name; + this.message = message; + } + + public String getName() { + return name; + } + + public Object getMessage() { + return message; + } +} diff --git a/src/com/droidkit/actors/messages/PoisonPill.java b/src/com/droidkit/actors/messages/PoisonPill.java new file mode 100644 index 0000000..8fc0f58 --- /dev/null +++ b/src/com/droidkit/actors/messages/PoisonPill.java @@ -0,0 +1,13 @@ +package com.droidkit.actors.messages; + +/** + * PoisonPill message for killing actors + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public final class PoisonPill { + public static final PoisonPill INSTANCE = new PoisonPill(); + + private PoisonPill() { + } +} diff --git a/src/com/droidkit/actors/messages/StartActor.java b/src/com/droidkit/actors/messages/StartActor.java new file mode 100644 index 0000000..7b657da --- /dev/null +++ b/src/com/droidkit/actors/messages/StartActor.java @@ -0,0 +1,13 @@ +package com.droidkit.actors.messages; + +/** + * Message for starting actors + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public final class StartActor { + public static final StartActor INSTANCE = new StartActor(); + + private StartActor() { + } +} diff --git a/src/com/droidkit/actors/tasks/ActorAskImpl.java b/src/com/droidkit/actors/tasks/ActorAskImpl.java new file mode 100644 index 0000000..cd70da4 --- /dev/null +++ b/src/com/droidkit/actors/tasks/ActorAskImpl.java @@ -0,0 +1,165 @@ +package com.droidkit.actors.tasks; + +import com.droidkit.actors.ActorRef; +import com.droidkit.actors.messages.DeadLetter; +import com.droidkit.actors.tasks.messages.*; + +import java.util.HashMap; + +/** + * Implementation of Ask pattern + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class ActorAskImpl { + + private HashMap asks = new HashMap(); + private int nextReqId = 1; + private ActorRef self; + + public ActorAskImpl(ActorRef self) { + this.self = self; + } + + public AskFuture combine(AskFuture... futures) { + final AskFuture resultFuture = new AskFuture(this, 0); + final CombineContainer container = new CombineContainer(futures.length); + for (int i = 0; i < futures.length; i++) { + final int index = i; + container.futures[index] = futures[index]; + container.callbacks[index] = new AskCallback() { + @Override + public void onResult(Object result) { + container.completed[index] = true; + container.results[index] = result; + boolean isCompleted = true; + for (boolean c : container.completed) { + if (!c) { + isCompleted = false; + break; + } + } + + if (isCompleted && !container.isCompleted) { + container.isCompleted = true; + for (int i = 0; i < container.futures.length; i++) { + container.futures[i].removeListener(container.callbacks[i]); + } + resultFuture.onResult(container.results); + } + } + + @Override + public void onError(Throwable throwable) { + if (!container.isCompleted) { + container.isCompleted = true; + for (int i = 0; i < container.futures.length; i++) { + container.futures[i].removeListener(container.callbacks[i]); + container.futures[i].cancel(); + } + resultFuture.onError(throwable); + } + + } + }; + container.futures[index].addListener(container.callbacks[index]); + } + return resultFuture; + } + + public AskFuture ask(ActorRef ref, long timeout, AskCallback callback) { + int reqId = nextReqId++; + AskFuture future = new AskFuture(this, reqId); + if (callback != null) { + future.addListener(callback); + } + AskContainer container = new AskContainer(future, ref, reqId); + asks.put(reqId, container); + ref.send(new TaskRequest(reqId), self); + if (timeout > 0) { + self.send(new TaskTimeout(reqId), timeout); + } + return future; + } + + public boolean onTaskResult(TaskResult result) { + AskContainer container = asks.remove(result.getRequestId()); + if (container != null) { + container.future.onResult(result.getRes()); + return true; + } + + return false; + } + + public boolean onTaskError(TaskError error) { + AskContainer container = asks.remove(error.getRequestId()); + if (container != null) { + container.future.onError(error.getThrowable()); + return true; + } + + return false; + } + + public boolean onTaskTimeout(TaskTimeout taskTimeout) { + AskContainer container = asks.remove(taskTimeout.getRequestId()); + if (container != null) { + container.future.onTimeout(); + return true; + } + + return false; + } + + public boolean onTaskCancelled(int reqId) { + AskContainer container = asks.remove(reqId); + if (container != null) { + container.ref.send(new TaskCancel(reqId), self); + return true; + } + + return false; + } + + public boolean onDeadLetter(DeadLetter letter) { + if (letter.getMessage() instanceof TaskRequest) { + TaskRequest request = (TaskRequest) letter.getMessage(); + AskContainer container = asks.remove(request.getRequestId()); + if (container != null) { + // Mimic dead letter with timeout exception + container.future.onError(new AskTimeoutException()); + return true; + } + } + + return false; + } + + private class AskContainer { + public final AskFuture future; + public final ActorRef ref; + public final int requestId; + + private AskContainer(AskFuture future, ActorRef ref, int requestId) { + this.future = future; + this.ref = ref; + this.requestId = requestId; + } + } + + private class CombineContainer { + public boolean isCompleted = false; + public Object[] results; + public boolean[] completed; + public AskFuture[] futures; + public AskCallback[] callbacks; + + public CombineContainer(int count) { + results = new Object[count]; + completed = new boolean[count]; + callbacks = new AskCallback[count]; + futures = new AskFuture[count]; + } + } +} diff --git a/src/com/droidkit/actors/tasks/AskCallback.java b/src/com/droidkit/actors/tasks/AskCallback.java new file mode 100644 index 0000000..0af7bd5 --- /dev/null +++ b/src/com/droidkit/actors/tasks/AskCallback.java @@ -0,0 +1,12 @@ +package com.droidkit.actors.tasks; + +/** + * Callback for Ask pattern + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public interface AskCallback { + public void onResult(T result); + + public void onError(Throwable throwable); +} diff --git a/src/com/droidkit/actors/tasks/AskCancelledException.java b/src/com/droidkit/actors/tasks/AskCancelledException.java new file mode 100644 index 0000000..3e11c6b --- /dev/null +++ b/src/com/droidkit/actors/tasks/AskCancelledException.java @@ -0,0 +1,10 @@ +package com.droidkit.actors.tasks; + +/** + * Exception about cancelling task + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class AskCancelledException extends Exception { + +} diff --git a/src/com/droidkit/actors/tasks/AskFuture.java b/src/com/droidkit/actors/tasks/AskFuture.java new file mode 100644 index 0000000..6d4600f --- /dev/null +++ b/src/com/droidkit/actors/tasks/AskFuture.java @@ -0,0 +1,105 @@ +package com.droidkit.actors.tasks; + +import java.util.LinkedList; + +/** + * Modified future for ask pattern of Actors + * Created to work only in actor threads + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class AskFuture { + + private LinkedList callbacks = new LinkedList(); + + private ActorAskImpl askImpl; + private int reqId; + + private boolean isCompleted = false; + private boolean isCanceled = false; + private boolean isError = false; + private T result = null; + private Throwable error = null; + + AskFuture(ActorAskImpl askImpl, int reqId) { + this.askImpl = askImpl; + this.reqId = reqId; + } + + public void addListener(AskCallback callback) { + callbacks.add(callback); + } + + public void removeListener(AskCallback callback) { + callbacks.remove(callback); + } + + public boolean isCompleted() { + return isCompleted; + } + + public boolean isError() { + return isError; + } + + public boolean isCanceled() { + return isCanceled; + } + + public Throwable error() { + return error; + } + + public T result() { + return result; + } + + public void cancel() { + if (isCompleted) { + return; + } + isCompleted = true; + isError = false; + isCanceled = true; + + for (AskCallback callback : callbacks) { + callback.onError(new AskCancelledException()); + } + + askImpl.onTaskCancelled(reqId); + } + + void onError(Throwable throwable) { + if (isCompleted) { + return; + } + isCompleted = true; + isCanceled = false; + isError = true; + error = throwable; + result = null; + + for (AskCallback callback : callbacks) { + callback.onError(throwable); + } + } + + void onResult(T res) { + if (isCompleted) { + return; + } + isCompleted = true; + isCanceled = false; + isError = false; + error = null; + result = res; + + for (AskCallback callback : callbacks) { + callback.onResult(res); + } + } + + void onTimeout() { + onError(new AskTimeoutException()); + } +} diff --git a/src/com/droidkit/actors/tasks/AskTimeoutException.java b/src/com/droidkit/actors/tasks/AskTimeoutException.java new file mode 100644 index 0000000..6793130 --- /dev/null +++ b/src/com/droidkit/actors/tasks/AskTimeoutException.java @@ -0,0 +1,9 @@ +package com.droidkit.actors.tasks; + +/** + * Exception for Ask timeout + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class AskTimeoutException extends Exception { +} diff --git a/src/com/droidkit/actors/tasks/TaskActor.java b/src/com/droidkit/actors/tasks/TaskActor.java new file mode 100644 index 0000000..250636b --- /dev/null +++ b/src/com/droidkit/actors/tasks/TaskActor.java @@ -0,0 +1,177 @@ +package com.droidkit.actors.tasks; + +import com.droidkit.actors.Actor; +import com.droidkit.actors.ActorRef; +import com.droidkit.actors.messages.PoisonPill; +import com.droidkit.actors.tasks.messages.TaskCancel; +import com.droidkit.actors.tasks.messages.TaskError; +import com.droidkit.actors.tasks.messages.TaskRequest; +import com.droidkit.actors.tasks.messages.TaskResult; + +import java.util.HashSet; + +/** + * Actor for performing various async tasks + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public abstract class TaskActor extends Actor { + private final HashSet requests = new HashSet(); + + private T result; + private boolean isCompleted; + private boolean isCompletedSuccess; + private long dieTimeout = 300; + + /** + * Timeout for dying after task complete + * + * @param timeOut timeout in ms + */ + public void setTimeOut(long timeOut) { + dieTimeout = timeOut; + } + + @Override + public void preStart() { + startTask(); + } + + @Override + public void onReceive(Object message) { + if (message instanceof TaskRequest) { + TaskRequest request = (TaskRequest) message; + if (isCompleted) { + if (isCompletedSuccess) { + reply(result); + } + } else { + TaskListener listener = new TaskListener(request.getRequestId(), sender()); + requests.add(listener); + } + } else if (message instanceof TaskCancel) { + if (isCompleted) { + return; + } + TaskCancel cancel = (TaskCancel) message; + TaskListener listener = new TaskListener(cancel.getRequestId(), sender()); + requests.remove(listener); + if (requests.size() == 0) { + onTaskObsolete(); + context().stopSelf(); + } + } else if (message instanceof Result) { + if (!isCompleted) { + Result res = (Result) message; + isCompleted = true; + isCompletedSuccess = true; + result = (T) res.getRes(); + for (TaskListener request : requests) { + request.getSender().send(new TaskResult(request.getRequestId(), result)); + } + self().send(PoisonPill.INSTANCE, dieTimeout); + } + } else if (message instanceof Error) { + if (!isCompleted) { + isCompleted = true; + Error error = (Error) message; + for (TaskListener request : requests) { + request.getSender().send(new TaskError(request.getRequestId(), error.getError())); + } + context().stopSelf(); + } + } + } + + /** + * Starting of task execution + */ + public abstract void startTask(); + + /** + * Called before killing actor after clearing TaskListeners + */ + public void onTaskObsolete() { + + } + + /** + * Call this method in any thread after task complete + * + * @param res result of task + */ + public void complete(T res) { + self().send(new Result(res)); + } + + /** + * Call this method in any thread after task exception + * + * @param t exception + */ + public void error(Throwable t) { + self().send(new Error(t)); + } + + private static class Error { + private Throwable error; + + private Error(Throwable error) { + this.error = error; + } + + public Throwable getError() { + return error; + } + } + + private static class Result { + private Object res; + + private Result(Object res) { + this.res = res; + } + + public Object getRes() { + return res; + } + } + + private static class TaskListener { + private int requestId; + private ActorRef sender; + + private TaskListener(int requestId, ActorRef sender) { + this.requestId = requestId; + this.sender = sender; + } + + public int getRequestId() { + return requestId; + } + + public ActorRef getSender() { + return sender; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TaskListener that = (TaskListener) o; + + if (requestId != that.requestId) return false; + if (!sender.equals(that.sender)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = requestId; + result = 31 * result + sender.hashCode(); + return result; + } + } +} \ No newline at end of file diff --git a/src/com/droidkit/actors/tasks/messages/TaskCancel.java b/src/com/droidkit/actors/tasks/messages/TaskCancel.java new file mode 100644 index 0000000..4312e0a --- /dev/null +++ b/src/com/droidkit/actors/tasks/messages/TaskCancel.java @@ -0,0 +1,18 @@ +package com.droidkit.actors.tasks.messages; + +/** + * Message about task cancelling + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class TaskCancel { + private int requestId; + + public TaskCancel(int requestId) { + this.requestId = requestId; + } + + public int getRequestId() { + return requestId; + } +} diff --git a/src/com/droidkit/actors/tasks/messages/TaskError.java b/src/com/droidkit/actors/tasks/messages/TaskError.java new file mode 100644 index 0000000..be6ec8a --- /dev/null +++ b/src/com/droidkit/actors/tasks/messages/TaskError.java @@ -0,0 +1,24 @@ +package com.droidkit.actors.tasks.messages; + +/** + * Message about task error + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class TaskError { + private final int requestId; + private final Throwable throwable; + + public TaskError(int requestId, Throwable throwable) { + this.requestId = requestId; + this.throwable = throwable; + } + + public int getRequestId() { + return requestId; + } + + public Throwable getThrowable() { + return throwable; + } +} diff --git a/src/com/droidkit/actors/tasks/messages/TaskRequest.java b/src/com/droidkit/actors/tasks/messages/TaskRequest.java new file mode 100644 index 0000000..c512f66 --- /dev/null +++ b/src/com/droidkit/actors/tasks/messages/TaskRequest.java @@ -0,0 +1,18 @@ +package com.droidkit.actors.tasks.messages; + +/** + * Message about requesting task + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class TaskRequest { + private final int requestId; + + public TaskRequest(int requestId) { + this.requestId = requestId; + } + + public int getRequestId() { + return requestId; + } +} diff --git a/src/com/droidkit/actors/tasks/messages/TaskResult.java b/src/com/droidkit/actors/tasks/messages/TaskResult.java new file mode 100644 index 0000000..21d0f04 --- /dev/null +++ b/src/com/droidkit/actors/tasks/messages/TaskResult.java @@ -0,0 +1,25 @@ +package com.droidkit.actors.tasks.messages; + +/** + * Message with task result + * + * @param type of task result + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class TaskResult { + private final int requestId; + private final T res; + + public TaskResult(int requestId, T res) { + this.requestId = requestId; + this.res = res; + } + + public T getRes() { + return res; + } + + public int getRequestId() { + return requestId; + } +} diff --git a/src/com/droidkit/actors/tasks/messages/TaskTimeout.java b/src/com/droidkit/actors/tasks/messages/TaskTimeout.java new file mode 100644 index 0000000..b104574 --- /dev/null +++ b/src/com/droidkit/actors/tasks/messages/TaskTimeout.java @@ -0,0 +1,18 @@ +package com.droidkit.actors.tasks.messages; + +/** + * Message about Task timeout + * + * @author Stepan Ex3NDR Korshakov (me@ex3ndr.com) + */ +public class TaskTimeout { + private final int requestId; + + public TaskTimeout(int requestId) { + this.requestId = requestId; + } + + public int getRequestId() { + return requestId; + } +} diff --git a/src/de/fabianonline/telegram_backup/Main.java b/src/de/fabianonline/telegram_backup/Main.java new file mode 100644 index 0000000..ded5d90 --- /dev/null +++ b/src/de/fabianonline/telegram_backup/Main.java @@ -0,0 +1,37 @@ +package de.fabianonline.telegram_backup; + +import de.fabianonline.telegram_backup.MyStorage; +import org.telegram.api.engine.AppInfo; +import org.telegram.api.engine.ApiCallback; +import org.telegram.api.engine.TelegramApi; +import org.telegram.api.TLConfig; +import org.telegram.api.TLAbsUpdates; +import org.telegram.api.requests.*; +import java.io.IOException; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello World"); + + MyStorage storage = new MyStorage(); + + AppInfo appinfo = new AppInfo(32860, "Desktop", "0.0.1", "0.0.1", "EN"); + + ApiCallback callback = new ApiCallback() { + public void onApiDies(TelegramApi api) { System.out.println("onApiDies"); } + public void onUpdatesInvalidated(TelegramApi api) { System.out.println("onUpdatesInvalidated"); } + public void onUpdate(TLAbsUpdates updates) { System.out.println("onUpdate"); } + public void onAuthCancelled(TelegramApi api) { System.out.println("onAuthCancelled"); } + + }; + + TelegramApi api = new TelegramApi(storage, appinfo, callback); + + try { + TLConfig config = api.doRpcCall(new TLRequestHelpGetConfig()); + } catch(IOException ex) { + System.out.println("IOException caught."); + } + + } +} diff --git a/src/de/fabianonline/telegram_backup/MyStorage.java b/src/de/fabianonline/telegram_backup/MyStorage.java new file mode 100644 index 0000000..2520663 --- /dev/null +++ b/src/de/fabianonline/telegram_backup/MyStorage.java @@ -0,0 +1,42 @@ +package de.fabianonline.telegram_backup; + +import org.telegram.api.engine.storage.AbsApiState; +import org.telegram.api.TLConfig; +import org.telegram.mtproto.state.AbsMTProtoState; +import org.telegram.mtproto.state.ConnectionInfo; +import org.telegram.mtproto.state.KnownSalt; +import java.util.HashMap; + +public class MyStorage implements AbsApiState { + private int primaryDc = 2; + private HashMap isAuthenticated = new HashMap(); + private HashMap authKeys = new HashMap(); + + public int getPrimaryDc() { return primaryDc; } + public void setPrimaryDc(int newDc) { this.primaryDc = newDc; } + public boolean isAuthenticated(int dc) { + boolean temp = this.isAuthenticated.get(dc); + if (temp) return true; + return false; + } + public void setAuthenticated(int dc, boolean val) { this.isAuthenticated.put(dc, val); } + + public void updateSettings(TLConfig config) { System.out.println("Call to updateSettings"); } + + public byte[] getAuthKey(int dc) { return this.authKeys.get(dc); } + + public void putAuthKey(int dc, byte[] key) { this.authKeys.put(dc, key); } + + public ConnectionInfo[] getAvailableConnections(int dc) { + switch(dc) { + case 2: return new ConnectionInfo[]{new ConnectionInfo(1, 1, "149.154.167.50", 443)}; + } + return new ConnectionInfo[0]; + } + + public AbsMTProtoState getMtProtoState(int dc) { System.out.println("Call to getMtProtoState"); return null; } + + public void resetAuth() { this.isAuthenticated.clear(); this.authKeys.clear(); } + + public void reset() { this.resetAuth(); this.primaryDc=2; } +} diff --git a/src/org/telegram/api/engine/ApiCallback.java b/src/org/telegram/api/engine/ApiCallback.java new file mode 100644 index 0000000..eda227a --- /dev/null +++ b/src/org/telegram/api/engine/ApiCallback.java @@ -0,0 +1,17 @@ +package org.telegram.api.engine; + +import org.telegram.api.TLAbsUpdates; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 11.11.13 + * Time: 7:42 + */ +public interface ApiCallback { + public void onAuthCancelled(TelegramApi api); + + public void onUpdatesInvalidated(TelegramApi api); + + public void onUpdate(TLAbsUpdates updates); +} diff --git a/src/org/telegram/api/engine/AppInfo.java b/src/org/telegram/api/engine/AppInfo.java new file mode 100644 index 0000000..228cc39 --- /dev/null +++ b/src/org/telegram/api/engine/AppInfo.java @@ -0,0 +1,43 @@ +package org.telegram.api.engine; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 10.11.13 + * Time: 2:31 + */ +public class AppInfo { + protected int apiId; + protected String deviceModel; + protected String systemVersion; + protected String appVersion; + protected String langCode; + + public AppInfo(int apiId, String deviceModel, String systemVersion, String appVersion, String langCode) { + this.apiId = apiId; + this.deviceModel = deviceModel; + this.systemVersion = systemVersion; + this.appVersion = appVersion; + this.langCode = langCode; + } + + public int getApiId() { + return apiId; + } + + public String getDeviceModel() { + return deviceModel; + } + + public String getSystemVersion() { + return systemVersion; + } + + public String getAppVersion() { + return appVersion; + } + + public String getLangCode() { + return langCode; + } +} diff --git a/src/org/telegram/api/engine/GzipRequest.java b/src/org/telegram/api/engine/GzipRequest.java new file mode 100644 index 0000000..cd1369a --- /dev/null +++ b/src/org/telegram/api/engine/GzipRequest.java @@ -0,0 +1,60 @@ +package org.telegram.api.engine; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLGzipObject; +import org.telegram.tl.TLMethod; +import org.telegram.tl.TLObject; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.GZIPOutputStream; + +import static org.telegram.tl.StreamingUtils.*; +import static org.telegram.tl.StreamingUtils.writeByteArray; + +/** + * Created by ex3ndr on 07.12.13. + */ +public class GzipRequest extends TLMethod { + + private static final int CLASS_ID = TLGzipObject.CLASS_ID; + + private TLMethod method; + + public GzipRequest(TLMethod method) { + this.method = method; + } + + @Override + public T deserializeResponse(InputStream stream, TLContext context) throws IOException { + return method.deserializeResponse(stream, context); + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + ByteArrayOutputStream resOutput = new ByteArrayOutputStream(); + GZIPOutputStream gzipOutputStream = new GZIPOutputStream(resOutput); + method.serialize(gzipOutputStream); + gzipOutputStream.flush(); + gzipOutputStream.close(); + byte[] body = resOutput.toByteArray(); + writeTLBytes(body, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + throw new IOException("Unsupported operation"); + } + + @Override + public String toString() { + return "gzip<" + method + ">"; + } +} diff --git a/src/org/telegram/api/engine/Logger.java b/src/org/telegram/api/engine/Logger.java new file mode 100644 index 0000000..ca56ccd --- /dev/null +++ b/src/org/telegram/api/engine/Logger.java @@ -0,0 +1,39 @@ +package org.telegram.api.engine; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 11.11.13 + * Time: 4:48 + */ +public class Logger { + private static LoggerInterface logInterface; + + public static void registerInterface(LoggerInterface logInterface) { + Logger.logInterface = logInterface; + } + + public static void w(String tag, String message) { + if (logInterface != null) { + logInterface.w(tag, message); + } else { + System.out.println(tag + ":" + message); + } + } + + public static void d(String tag, String message) { + if (logInterface != null) { + logInterface.d(tag, message); + } else { + System.out.println(tag + ":" + message); + } + } + + public static void e(String tag, Throwable t) { + if (logInterface != null) { + logInterface.e(tag, t); + } else { + t.printStackTrace(); + } + } +} diff --git a/src/org/telegram/api/engine/LoggerInterface.java b/src/org/telegram/api/engine/LoggerInterface.java new file mode 100644 index 0000000..6e36d1c --- /dev/null +++ b/src/org/telegram/api/engine/LoggerInterface.java @@ -0,0 +1,15 @@ +package org.telegram.api.engine; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 11.11.13 + * Time: 4:48 + */ +public interface LoggerInterface { + void w(String tag, String message); + + void d(String tag, String message); + + void e(String tag, Throwable t); +} diff --git a/src/org/telegram/api/engine/RpcCallback.java b/src/org/telegram/api/engine/RpcCallback.java new file mode 100644 index 0000000..db4298c --- /dev/null +++ b/src/org/telegram/api/engine/RpcCallback.java @@ -0,0 +1,15 @@ +package org.telegram.api.engine; + +import org.telegram.tl.TLObject; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 05.11.13 + * Time: 14:10 + */ +public interface RpcCallback { + public void onResult(T result); + + public void onError(int errorCode, String message); +} diff --git a/src/org/telegram/api/engine/RpcCallbackEx.java b/src/org/telegram/api/engine/RpcCallbackEx.java new file mode 100644 index 0000000..565ba47 --- /dev/null +++ b/src/org/telegram/api/engine/RpcCallbackEx.java @@ -0,0 +1,13 @@ +package org.telegram.api.engine; + +import org.telegram.tl.TLObject; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 09.11.13 + * Time: 18:06 + */ +public interface RpcCallbackEx extends RpcCallback { + public void onConfirmed(); +} diff --git a/src/org/telegram/api/engine/RpcException.java b/src/org/telegram/api/engine/RpcException.java new file mode 100644 index 0000000..6fb04f5 --- /dev/null +++ b/src/org/telegram/api/engine/RpcException.java @@ -0,0 +1,56 @@ +package org.telegram.api.engine; + +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 05.11.13 + * Time: 13:59 + */ +public class RpcException extends IOException { + private static final Pattern REGEXP_PATTERN = Pattern.compile("[A-Z_0-9]+"); + + private static String getErrorTag(String srcMessage) { + if (srcMessage == null) { + return "UNKNOWN"; + } + Matcher matcher = REGEXP_PATTERN.matcher(srcMessage); + if (matcher.find()) { + return matcher.group(); + } + return "UNKNOWN"; + } + + private static String getErrorMessage(String srcMessage) { + if (srcMessage == null) { + return "Unknown error"; + } + int index = srcMessage.indexOf(":"); + if (index > 0) { + return srcMessage.substring(index); + } else { + return srcMessage; + } + } + + private int errorCode; + + private String errorTag; + + public RpcException(int errorCode, String message) { + super(getErrorMessage(message)); + this.errorCode = errorCode; + this.errorTag = getErrorTag(message); + } + + public int getErrorCode() { + return errorCode; + } + + public String getErrorTag() { + return errorTag; + } +} diff --git a/src/org/telegram/api/engine/TelegramApi.java b/src/org/telegram/api/engine/TelegramApi.java new file mode 100644 index 0000000..59e4446 --- /dev/null +++ b/src/org/telegram/api/engine/TelegramApi.java @@ -0,0 +1,1129 @@ +package org.telegram.api.engine; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.telegram.api.TLAbsUpdates; +import org.telegram.api.TLApiContext; +import org.telegram.api.TLConfig; +import org.telegram.api.auth.TLExportedAuthorization; +import org.telegram.api.engine.file.Downloader; +import org.telegram.api.engine.file.Uploader; +import org.telegram.api.engine.storage.AbsApiState; +import org.telegram.api.requests.TLRequestAuthExportAuthorization; +import org.telegram.api.requests.TLRequestAuthImportAuthorization; +import org.telegram.api.requests.TLRequestHelpGetConfig; +import org.telegram.api.requests.TLRequestInitConnection; +import org.telegram.api.requests.TLRequestInvokeWithLayer; +import org.telegram.api.requests.TLRequestUploadGetFile; +import org.telegram.api.requests.TLRequestUploadSaveBigFilePart; +import org.telegram.api.requests.TLRequestUploadSaveFilePart; +import org.telegram.api.upload.TLFile; +import org.telegram.mtproto.CallWrapper; +import org.telegram.mtproto.MTProto; +import org.telegram.mtproto.MTProtoCallback; +import org.telegram.mtproto.pq.Authorizer; +import org.telegram.mtproto.pq.PqAuth; +import org.telegram.mtproto.state.ConnectionInfo; +import org.telegram.mtproto.util.BytesCache; +import org.telegram.tl.TLBool; +import org.telegram.tl.TLBoolTrue; +import org.telegram.tl.TLBytes; +import org.telegram.tl.TLMethod; +import org.telegram.tl.TLObject; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 04.11.13 + * Time: 21:54 + */ +public class TelegramApi { + + private static final AtomicInteger rpcCallIndex = new AtomicInteger(0); + + private static final AtomicInteger instanceIndex = new AtomicInteger(1000); + + private final String TAG; + + private final int INSTANCE_INDEX; + + private static final int CHANNELS_MAIN = 1; + private static final int CHANNELS_FS = 2; + + private static final int DEFAULT_TIMEOUT_CHECK = 15000; + private static final int DEFAULT_TIMEOUT = 15000; + private static final int FILE_TIMEOUT = 45000; + + private boolean isClosed; + + private int primaryDc; + + private MTProto mainProto; + + private final HashMap dcProtos = new HashMap(); + private final HashMap dcSync = new HashMap(); + + private ProtoCallback callback; + + private SenderThread senderThread; + + private final HashMap callbacks = new HashMap(); + private final HashMap sentRequests = new HashMap(); + + private TLApiContext apiContext; + + private TimeoutThread timeoutThread; + private final TreeMap timeoutTimes = new TreeMap(); + + private ConnectionThread dcThread; + private final TreeMap dcRequired = new TreeMap(); + + private HashSet registeredInApi = new HashSet(); + + private AbsApiState state; + private AppInfo appInfo; + + private ApiCallback apiCallback; + + private Downloader downloader; + + private Uploader uploader; + + public TelegramApi(AbsApiState state, AppInfo _appInfo, ApiCallback _apiCallback) { + this.INSTANCE_INDEX = instanceIndex.incrementAndGet(); + this.TAG = "TelegramApi#" + INSTANCE_INDEX; + + long start = System.currentTimeMillis(); + this.apiCallback = _apiCallback; + this.appInfo = _appInfo; + this.state = state; + this.primaryDc = state.getPrimaryDc(); + this.isClosed = false; + this.callback = new ProtoCallback(); + Logger.d(TAG, "Phase 0 in " + (System.currentTimeMillis() - start) + " ms"); + + start = System.currentTimeMillis(); + this.apiContext = new TLApiContext() { + private AtomicInteger integer = new AtomicInteger(0); + + @Override + public TLObject deserializeMessage(int clazzId, InputStream stream) throws IOException { + if (integer.incrementAndGet() % 10 == 9) { + Thread.yield(); + } + return super.deserializeMessage(clazzId, stream); + } + + @Override + public TLBytes allocateBytes(int size) { + return new TLBytes(BytesCache.getInstance().allocate(size), 0, size); + } + + @Override + public void releaseBytes(TLBytes unused) { + BytesCache.getInstance().put(unused.getData()); + } + }; + + Logger.d(TAG, "Phase 1 in " + (System.currentTimeMillis() - start) + " ms"); + + start = System.currentTimeMillis(); + this.timeoutThread = new TimeoutThread(); + this.timeoutThread.start(); + + this.dcThread = new ConnectionThread(); + this.dcThread.start(); + + this.senderThread = new SenderThread(); + this.senderThread.start(); + Logger.d(TAG, "Phase 2 in " + (System.currentTimeMillis() - start) + " ms"); + + start = System.currentTimeMillis(); + this.downloader = new Downloader(this); + this.uploader = new Uploader(this); + Logger.d(TAG, "Phase 3 in " + (System.currentTimeMillis() - start) + " ms"); + } + + public Downloader getDownloader() { + return downloader; + } + + public Uploader getUploader() { + return uploader; + } + + public void switchToDc(int dcId) { + if (this.mainProto != null) { + this.mainProto.close(); + } + this.mainProto = null; + this.primaryDc = dcId; + this.state.setPrimaryDc(dcId); + synchronized (dcRequired) { + dcRequired.notifyAll(); + } + } + + @Override + public String toString() { + return "api#" + INSTANCE_INDEX; + } + + private TLMethod wrapForDc(int dcId, TLMethod method) { + if (!registeredInApi.contains(dcId)) { + method = new TLRequestInitConnection(appInfo.getApiId(), appInfo.getDeviceModel(), + appInfo.getSystemVersion(), appInfo.getAppVersion(), appInfo.getLangCode(), method); + } + return new TLRequestInvokeWithLayer(23, method); + } + + public AbsApiState getState() { + return state; + } + + public TLApiContext getApiContext() { + return apiContext; + } + + protected void onMessageArrived(TLObject object) { + if (object instanceof TLAbsUpdates) { + Logger.d(TAG, "<< update " + object.toString()); + apiCallback.onUpdate((TLAbsUpdates) object); + } else { + Logger.d(TAG, "<< unknown object " + object.toString()); + } + } + + public boolean isClosed() { + return isClosed; + } + + public void close() { + if (!this.isClosed) { + apiCallback.onAuthCancelled(this); + this.isClosed = true; + if (this.timeoutThread != null) { + this.timeoutThread.interrupt(); + this.timeoutThread = null; + } + mainProto.close(); + } + } + + public void resetNetworkBackoff() { + if (mainProto != null) { + mainProto.resetNetworkBackoff(); + } + for (MTProto mtProto : dcProtos.values()) { + mtProto.resetNetworkBackoff(); + } + } + + public void resetConnectionInfo() { + mainProto.reloadConnectionInformation(); + synchronized (dcProtos) { + for (MTProto proto : dcProtos.values()) { + proto.reloadConnectionInformation(); + } + } + } + + // Basic sync and async methods + + private void doRpcCall(TLMethod method, int timeout, RpcCallback callback, int destDc) { + doRpcCall(method, timeout, callback, destDc, true); + } + + private void doRpcCall(TLMethod method, int timeout, RpcCallback callback, int destDc, + boolean authRequired) { + if (isClosed) { + if (callback != null) { + callback.onError(0, null); + } + return; + } + int localRpcId = rpcCallIndex.getAndIncrement(); + synchronized (callbacks) { + RpcCallbackWrapper wrapper = new RpcCallbackWrapper(localRpcId, method, callback); + wrapper.dcId = destDc; + wrapper.timeout = timeout; + wrapper.isAuthRequred = authRequired; + + callbacks.put(localRpcId, wrapper); + + if (callback != null) { + long timeoutTime = System.nanoTime() + timeout * 1000 * 1000L; + synchronized (timeoutTimes) { + while (timeoutTimes.containsKey(timeoutTime)) { + timeoutTime++; + } + timeoutTimes.put(timeoutTime, localRpcId); + timeoutTimes.notifyAll(); + } + wrapper.timeoutTime = timeoutTime; + } + + if (authRequired) { + checkDcAuth(destDc); + } else { + checkDc(destDc); + } + + callbacks.notifyAll(); + } + + Logger.d(TAG, ">> #" + +localRpcId + ": " + method.toString()); + } + + private T doRpcCall(TLMethod method, int timeout, int destDc) throws IOException { + return doRpcCall(method, timeout, destDc, true); + } + + private T doRpcCall(TLMethod method, int timeout, int destDc, boolean authRequired) throws IOException { + if (isClosed) { + throw new TimeoutException(); + } + final Object waitObj = new Object(); + final Object[] res = new Object[3]; + final boolean[] completed = new boolean[1]; + completed[0] = false; + + doRpcCall(method, timeout, new RpcCallback() { + @Override + public void onResult(T result) { + synchronized (waitObj) { + if (completed[0]) { + return; + } + completed[0] = true; + res[0] = result; + res[1] = null; + res[2] = null; + waitObj.notifyAll(); + } + } + + @Override + public void onError(int errorCode, String message) { + synchronized (waitObj) { + if (completed[0]) { + return; + } + completed[0] = true; + res[0] = null; + res[1] = errorCode; + res[2] = message; + waitObj.notifyAll(); + } + } + }, destDc, authRequired); + + synchronized (waitObj) { + try { + waitObj.wait(timeout); + completed[0] = true; + } catch (InterruptedException e) { + throw new TimeoutException(); + } + } + + if (res[0] == null) { + if (res[1] != null) { + Integer code = (Integer) res[1]; + if (code == 0) { + throw new TimeoutException(); + } else { + throw new RpcException(code, (String) res[2]); + } + } else { + throw new TimeoutException(); + } + } else { + return (T) res[0]; + } + } + + // Public async methods + public void doRpcCallWeak(TLMethod method) { + doRpcCallWeak(method, DEFAULT_TIMEOUT); + } + + public void doRpcCallWeak(TLMethod method, int timeout) { + doRpcCall(method, timeout, (RpcCallback) null); + } + + public void doRpcCall(TLMethod method, RpcCallback callback) { + doRpcCall(method, DEFAULT_TIMEOUT, callback); + } + + public void doRpcCall(TLMethod method, int timeout, RpcCallback callback) { + doRpcCall(method, timeout, callback, 0); + } + + // Public sync methods + + public T doRpcCall(TLMethod method) throws IOException { + return doRpcCall(method, DEFAULT_TIMEOUT); + } + + public T doRpcCall(TLMethod method, int timeout) throws IOException { + return doRpcCall(method, timeout, 0); + } + + public T doRpcCallSide(TLMethod method) throws IOException { + return doRpcCall(method, DEFAULT_TIMEOUT, primaryDc, true); + } + + public T doRpcCallSide(TLMethod method, int timeout) throws IOException { + return doRpcCall(method, timeout, primaryDc, true); + } + + public T doRpcCallSideGzip(TLMethod method, int timeout) throws IOException { + return doRpcCall(new GzipRequest(method), timeout, primaryDc, true); + } + + public T doRpcCallGzip(TLMethod method, int timeout) throws IOException { + return doRpcCall(new GzipRequest(method), timeout, 0); + } + + public T doRpcCallNonAuth(TLMethod method) throws IOException { + return doRpcCallNonAuth(method, DEFAULT_TIMEOUT, primaryDc); + } + + public T doRpcCallNonAuth(TLMethod method, int dcId) throws IOException { + return doRpcCallNonAuth(method, DEFAULT_TIMEOUT, dcId); + } + + public T doRpcCallNonAuth(TLMethod method, int timeout, int dcId) throws IOException { + return doRpcCall(method, timeout, dcId, false); + } + + public void doRpcCallNonAuth(TLMethod method, int timeout, RpcCallback callback) { + doRpcCall(method, timeout, callback, 0, false); + } + + public boolean doSaveFilePart(long _fileId, int _filePart, byte[] _bytes) throws IOException { + TLBool res = doRpcCall( + new TLRequestUploadSaveFilePart(_fileId, _filePart, _bytes), + FILE_TIMEOUT, + primaryDc, + true); + return res instanceof TLBoolTrue; + } + + public boolean doSaveBigFilePart(long _fileId, int _filePart, int _totalParts, byte[] _bytes) throws IOException { + TLBool res = doRpcCall( + new TLRequestUploadSaveBigFilePart(_fileId, _filePart, _totalParts, _bytes), + FILE_TIMEOUT, + primaryDc); + return res instanceof TLBoolTrue; + } + + public TLFile doGetFile(int dcId, org.telegram.api.TLAbsInputFileLocation _location, int _offset, int _limit) throws IOException { + return doRpcCall(new TLRequestUploadGetFile(_location, _offset, _limit), FILE_TIMEOUT, dcId); + } + + private void checkDcAuth(int dcId) { + if (dcId != 0) { + synchronized (dcProtos) { + if (!dcProtos.containsKey(dcId)) { + synchronized (dcRequired) { + dcRequired.put(dcId, true); + dcRequired.notifyAll(); + } + } else if (!state.isAuthenticated(dcId)) { + synchronized (dcRequired) { + dcRequired.put(dcId, true); + dcRequired.notifyAll(); + } + } + } + } + } + + private void checkDc(int dcId) { + if (dcId != 0) { + synchronized (dcProtos) { + if (!dcProtos.containsKey(dcId)) { + synchronized (dcRequired) { + if (!dcRequired.containsKey(dcId)) { + dcRequired.put(dcId, false); + } + dcRequired.notifyAll(); + } + } + } + } else if (mainProto == null) { + synchronized (dcRequired) { + dcRequired.notifyAll(); + } + } + } + + private class ProtoCallback implements MTProtoCallback { + + @Override + public void onSessionCreated(MTProto proto) { + if (isClosed) { + return; + } + + Logger.w(TAG, proto + ": onSessionCreated"); + + if (proto == mainProto) { + registeredInApi.add(primaryDc); + } else { + for (Map.Entry p : dcProtos.entrySet()) { + if (p.getValue() == proto) { + registeredInApi.add(p.getKey()); + break; + } + } + } + + apiCallback.onUpdatesInvalidated(TelegramApi.this); + } + + @Override + public void onAuthInvalidated(MTProto proto) { + if (isClosed) { + return; + } + + if (proto == mainProto) { + synchronized (dcRequired) { + mainProto.close(); + mainProto = null; + state.setAuthenticated(primaryDc, false); + dcRequired.notifyAll(); + } + + synchronized (dcProtos) { + for (Map.Entry p : dcProtos.entrySet()) { + p.getValue().close(); + state.setAuthenticated(p.getKey(), false); + } + } + + apiCallback.onAuthCancelled(TelegramApi.this); + } else { + synchronized (dcProtos) { + for (Map.Entry p : dcProtos.entrySet()) { + if (p.getValue() == proto) { + state.setAuthenticated(p.getKey(), false); + dcProtos.remove(p.getKey()); + break; + } + } + } + synchronized (dcRequired) { + dcRequired.notifyAll(); + } + } + } + + @Override + public void onApiMessage(byte[] message, MTProto proto) { + if (isClosed) { + return; + } + + if (proto == mainProto) { + registeredInApi.add(primaryDc); + } else { + for (Map.Entry p : dcProtos.entrySet()) { + if (p.getValue() == proto) { + registeredInApi.add(p.getKey()); + break; + } + } + } + + try { + TLObject object = apiContext.deserializeMessage(message); + onMessageArrived(object); + } catch (Throwable t) { + Logger.e(TAG, t); + } + } + + @Override + public void onRpcResult(int callId, byte[] response, MTProto proto) { + if (isClosed) { + return; + } + + if (proto == mainProto) { + registeredInApi.add(primaryDc); + } else { + for (Map.Entry p : dcProtos.entrySet()) { + if (p.getValue() == proto) { + registeredInApi.add(p.getKey()); + break; + } + } + } + + try { + RpcCallbackWrapper currentCallback = null; + synchronized (callbacks) { + if (sentRequests.containsKey(callId)) { + currentCallback = callbacks.remove(sentRequests.remove(callId)); + } + } + if (currentCallback != null && currentCallback.method != null) { + long start = System.currentTimeMillis(); + TLObject object = currentCallback.method.deserializeResponse(response, apiContext); + Logger.d(TAG, "<< #" + +currentCallback.id + " deserialized " + object + " in " + (System.currentTimeMillis() - start) + " ms"); + + synchronized (currentCallback) { + if (currentCallback.isCompleted) { + Logger.d(TAG, "<< #" + +currentCallback.id + " ignored " + object + " in " + currentCallback.elapsed() + " ms"); + return; + } else { + currentCallback.isCompleted = true; + } + } + Logger.d(TAG, "<< #" + +currentCallback.id + " " + object + " in " + currentCallback.elapsed() + " ms"); + + synchronized (timeoutTimes) { + timeoutTimes.remove(currentCallback.timeoutTime); + } + if (currentCallback.callback != null) { + currentCallback.callback.onResult(object); + } + } + } catch (Throwable t) { + Logger.e(TAG, t); + } + } + + @Override + public void onRpcError(int callId, int errorCode, String message, MTProto proto) { + if (isClosed) { + return; + } + + if (errorCode == 400 && message != null && + (message.startsWith("CONNECTION_NOT_INITED") || message.startsWith("CONNECTION_LAYER_INVALID"))) { + Logger.w(TAG, proto + ": (!)Error #400 " + message); + + int dc = -1; + if (proto == mainProto) { + dc = primaryDc; + } else { + for (Map.Entry p : dcProtos.entrySet()) { + if (p.getValue() == proto) { + dc = p.getKey(); + break; + } + } + } + if (dc < 0) { + return; + } + registeredInApi.remove(dc); + + RpcCallbackWrapper currentCallback; + synchronized (callbacks) { + currentCallback = callbacks.remove(sentRequests.remove(callId)); + if (currentCallback != null) { + currentCallback.isSent = false; + callbacks.notifyAll(); + } + } + + return; + } else { + if (proto == mainProto) { + registeredInApi.add(primaryDc); + } else { + for (Map.Entry p : dcProtos.entrySet()) { + if (p.getValue() == proto) { + registeredInApi.add(p.getKey()); + break; + } + } + } + } + + try { + RpcCallbackWrapper currentCallback = null; + synchronized (callbacks) { + if (sentRequests.containsKey(callId)) { + currentCallback = callbacks.remove(sentRequests.remove(callId)); + } + } + if (currentCallback != null) { + synchronized (currentCallback) { + if (currentCallback.isCompleted) { + Logger.d(TAG, "<< #" + +currentCallback.id + " ignored error #" + errorCode + " " + message + " in " + currentCallback.elapsed() + " ms"); + return; + } else { + currentCallback.isCompleted = true; + } + } + Logger.d(TAG, "<< #" + +currentCallback.id + " error #" + errorCode + " " + message + " in " + currentCallback.elapsed() + " ms"); + synchronized (timeoutTimes) { + timeoutTimes.remove(currentCallback.timeoutTime); + } + if (currentCallback.callback != null) { + currentCallback.callback.onError(errorCode, message); + } + } + } catch (Throwable t) { + Logger.e(TAG, t); + } + } + + @Override + public void onConfirmed(int callId) { + RpcCallbackWrapper currentCallback = null; + synchronized (callbacks) { + if (sentRequests.containsKey(callId)) { + currentCallback = callbacks.get(sentRequests.get(callId)); + } + } + if (currentCallback != null) { + Logger.d(TAG, "<< #" + +currentCallback.id + " confirmed in " + currentCallback.elapsed() + " ms"); + synchronized (currentCallback) { + if (currentCallback.isCompleted || currentCallback.isConfirmed) { + return; + } else { + currentCallback.isConfirmed = true; + } + } + if (currentCallback.callback instanceof RpcCallbackEx) { + ((RpcCallbackEx) currentCallback.callback).onConfirmed(); + } + } + } + } + + private class SenderThread extends Thread { + public SenderThread() { + setName("Sender#" + hashCode()); + } + + @Override + public void run() { + setPriority(Thread.MIN_PRIORITY); + while (!isClosed) { + Logger.d(TAG, "Sender iteration"); + RpcCallbackWrapper wrapper = null; + synchronized (callbacks) { + for (RpcCallbackWrapper w : callbacks.values()) { + if (!w.isSent) { + if (w.dcId == 0 && mainProto != null) { + if (state.isAuthenticated(primaryDc) || !w.isAuthRequred) { + wrapper = w; + break; + } + } + if (w.dcId != 0 && dcProtos.containsKey(w.dcId)) { + if (state.isAuthenticated(w.dcId) || !w.isAuthRequred) { + wrapper = w; + break; + } + } + } + } + if (wrapper == null) { + try { + callbacks.wait(); + } catch (InterruptedException e) { + Logger.e(TAG, e); + return; + } + continue; + } + } + + if (mainProto == null) { + continue; + } + + if (wrapper.dcId == 0) { + if (!state.isAuthenticated(primaryDc) && wrapper.isAuthRequred) { + continue; + } + synchronized (callbacks) { + boolean isHighPriority = wrapper.callback != null && wrapper.callback instanceof RpcCallbackEx; + int rpcId = mainProto.sendRpcMessage(wrapper.method, wrapper.timeout, isHighPriority); + sentRequests.put(rpcId, wrapper.id); + wrapper.isSent = true; + Logger.d(TAG, "#> #" + wrapper.id + " sent to MTProto #" + mainProto.getInstanceIndex() + " with id #" + rpcId); + } + } else { + if (!dcProtos.containsKey(wrapper.dcId) || (!state.isAuthenticated(wrapper.dcId) && wrapper.isAuthRequred)) { + continue; + } + + MTProto proto = dcProtos.get(wrapper.dcId); + synchronized (callbacks) { + boolean isHighPriority = wrapper.callback != null && wrapper.callback instanceof RpcCallbackEx; + int rpcId = proto.sendRpcMessage(wrapper.method, wrapper.timeout, isHighPriority); + sentRequests.put(rpcId, wrapper.id); + wrapper.isSent = true; + Logger.d(TAG, "#> #" + wrapper.id + " sent to MTProto #" + proto.getInstanceIndex() + " with id #" + rpcId); + } + } + } + } + } + + private class ConnectionThread extends Thread { + public ConnectionThread() { + setName("Connection#" + hashCode()); + } + + private MTProto waitForDc(final int dcId) throws IOException { + Logger.d(TAG, "#" + dcId + ": waitForDc"); + if (isClosed) { + Logger.w(TAG, "#" + dcId + ": Api is closed"); + throw new TimeoutException(); + } + +// if (!state.isAuthenticated(primaryDc)) { +// Logger.w(TAG, "#" + dcId + ": Dc is not authenticated"); +// throw new TimeoutException(); +// } + + Object syncObj; + synchronized (dcSync) { + syncObj = dcSync.get(dcId); + if (syncObj == null) { + syncObj = new Object(); + dcSync.put(dcId, syncObj); + } + } + + synchronized (syncObj) { + MTProto proto; + synchronized (dcProtos) { + proto = dcProtos.get(dcId); + if (proto != null) { + if (proto.isClosed()) { + Logger.d(TAG, "#" + dcId + "proto removed because of death"); + dcProtos.remove(dcId); + proto = null; + } + } + } + + if (proto == null) { + Logger.d(TAG, "#" + dcId + ": Creating proto for dc"); + ConnectionInfo[] connectionInfo = state.getAvailableConnections(dcId); + + if (connectionInfo.length == 0) { + Logger.w(TAG, "#" + dcId + ": Unable to find proper dc config"); + TLConfig config = doRpcCall(new TLRequestHelpGetConfig()); + state.updateSettings(config); + resetConnectionInfo(); + connectionInfo = state.getAvailableConnections(dcId); + } + + if (connectionInfo.length == 0) { + Logger.w(TAG, "#" + dcId + ": Still unable to find proper dc config"); + throw new TimeoutException(); + } + + if (state.getAuthKey(dcId) != null) { + byte[] authKey = state.getAuthKey(dcId); + if (authKey == null) { + throw new TimeoutException(); + } + proto = new MTProto(state.getMtProtoState(dcId), callback, + new CallWrapper() { + @Override + public TLObject wrapObject(TLMethod srcRequest) { + return wrapForDc(dcId, srcRequest); + } + }, CHANNELS_FS, MTProto.MODE_GENERAL); + + dcProtos.put(dcId, proto); + return proto; + } else { + Logger.w(TAG, "#" + dcId + ": Creating key"); + Authorizer authorizer = new Authorizer(); + PqAuth auth = authorizer.doAuth(connectionInfo); + if (auth == null) { + Logger.w(TAG, "#" + dcId + ": Timed out"); + throw new TimeoutException(); + } + state.putAuthKey(dcId, auth.getAuthKey()); + state.setAuthenticated(dcId, false); + state.getMtProtoState(dcId).initialServerSalt(auth.getServerSalt()); + + byte[] authKey = state.getAuthKey(dcId); + if (authKey == null) { + Logger.w(TAG, "#" + dcId + ": auth key == null"); + throw new TimeoutException(); + } + + proto = new MTProto(state.getMtProtoState(dcId), callback, + new CallWrapper() { + @Override + public TLObject wrapObject(TLMethod srcRequest) { + return wrapForDc(dcId, srcRequest); + } + }, CHANNELS_FS, MTProto.MODE_GENERAL); + + dcProtos.put(dcId, proto); + + return proto; + } + } else { + Logger.w(TAG, "#" + dcId + ": returning proper proto"); + return proto; + } + } + } + + private MTProto waitForAuthDc(final int dcId) throws IOException { + Logger.d(TAG, "#" + dcId + ": waitForAuthDc"); + if (isClosed) { + Logger.w(TAG, "#" + dcId + ": Api is closed"); + throw new TimeoutException(); + } + + MTProto proto = waitForDc(dcId); + + if (!state.isAuthenticated(dcId)) { + Logger.w(TAG, "#" + dcId + ": exporting auth"); + TLExportedAuthorization exAuth = doRpcCall(new TLRequestAuthExportAuthorization(dcId)); + + Logger.w(TAG, "#" + dcId + ": importing auth"); + doRpcCallNonAuth(new TLRequestAuthImportAuthorization(exAuth.getId(), exAuth.getBytes()), DEFAULT_TIMEOUT, dcId); + + state.setAuthenticated(dcId, true); + } + + return proto; + } + + @Override + public void run() { + setPriority(Thread.MIN_PRIORITY); + while (!isClosed) { + Logger.d(TAG, "Connection iteration"); + if (mainProto == null) { + if (state.getAuthKey(primaryDc) == null) { + try { + long start = System.currentTimeMillis(); + waitForDc(primaryDc); + mainProto = new MTProto(state.getMtProtoState(primaryDc), callback, + new CallWrapper() { + @Override + public TLObject wrapObject(TLMethod srcRequest) { + return wrapForDc(primaryDc, srcRequest); + } + }, CHANNELS_MAIN, MTProto.MODE_GENERAL); + Logger.d(TAG, "#MTProto #" + mainProto.getInstanceIndex() + " created in " + (System.currentTimeMillis() - start) + " ms"); + } catch (IOException e) { + Logger.e(TAG, e); + try { + Thread.sleep(1000); + continue; + } catch (InterruptedException e1) { + Logger.e(TAG, e1); + return; + } + } + } else { + long start = System.currentTimeMillis(); + mainProto = new MTProto(state.getMtProtoState(primaryDc), callback, + new CallWrapper() { + @Override + public TLObject wrapObject(TLMethod srcRequest) { + return wrapForDc(primaryDc, srcRequest); + } + }, CHANNELS_MAIN, MTProto.MODE_GENERAL); + Logger.d(TAG, "#MTProto #" + mainProto.getInstanceIndex() + " created in " + (System.currentTimeMillis() - start) + " ms"); + } + synchronized (callbacks) { + callbacks.notifyAll(); + } + continue; + } + + Integer dcId = null; + Boolean authRequired = null; + synchronized (dcRequired) { + if (dcRequired.isEmpty()) { + dcId = null; + authRequired = null; + } else { + try { + dcId = dcRequired.firstKey(); + } catch (Exception e) { + Logger.e(TAG, e); + } + } + + if (dcId == null) { + try { + dcRequired.wait(); + } catch (InterruptedException e) { + // e.printStackTrace(); + } + continue; + } + + authRequired = dcRequired.remove(dcId); + } + + if (dcProtos.containsKey(dcId)) { + if (authRequired && !state.isAuthenticated(dcId) && state.isAuthenticated(primaryDc)) { + try { + waitForAuthDc(dcId); + synchronized (callbacks) { + callbacks.notifyAll(); + } + } catch (IOException e) { + try { + Thread.sleep(1000); + continue; + } catch (InterruptedException e1) { + Logger.e(TAG, e1); + return; + } + } + } + } else { + try { + if (authRequired && !state.isAuthenticated(dcId) && state.isAuthenticated(primaryDc)) { + waitForAuthDc(dcId); + } else { + waitForDc(dcId); + } + synchronized (callbacks) { + callbacks.notifyAll(); + } + } catch (IOException e) { + Logger.e(TAG, e); + } + } + } + } + } + + private class TimeoutThread extends Thread { + public TimeoutThread() { + setName("Timeout#" + hashCode()); + } + + @Override + public void run() { + while (!isClosed) { + Logger.d(TAG, "Timeout Iteration"); + Long key = null; + Integer id = null; + synchronized (timeoutTimes) { + if (timeoutTimes.isEmpty()) { + key = null; + } else { + try { + key = timeoutTimes.firstKey(); + } catch (Exception e) { + Logger.e(TAG, e); + } + } + + if (key == null) { + try { + timeoutTimes.wait(); + } catch (InterruptedException e) { + // e.printStackTrace(); + } + continue; + } + + long delta = (key - System.nanoTime()) / (1000 * 1000); + if (delta > 0) { + try { + timeoutTimes.wait(delta); + } catch (InterruptedException e) { + // e.printStackTrace(); + } + continue; + } + + id = timeoutTimes.remove(key); + if (id == null) { + continue; + } + } + + RpcCallbackWrapper currentCallback; + synchronized (callbacks) { + currentCallback = callbacks.remove(id); + } + if (currentCallback != null) { + synchronized (currentCallback) { + if (currentCallback.isCompleted) { + Logger.d(TAG, "RPC #" + id + ": Timeout ignored"); + return; + } else { + currentCallback.isCompleted = true; + } + } + Logger.d(TAG, "RPC #" + id + ": Timeout (" + currentCallback.elapsed() + " ms)"); + currentCallback.callback.onError(0, null); + } else { + Logger.d(TAG, "RPC #" + id + ": Timeout ignored2"); + } + } + synchronized (timeoutTimes) { + for (Map.Entry entry : timeoutTimes.entrySet()) { + RpcCallbackWrapper currentCallback; + synchronized (callbacks) { + currentCallback = callbacks.remove(entry.getValue()); + } + if (currentCallback != null) { + synchronized (currentCallback) { + if (currentCallback.isCompleted) { + return; + } else { + currentCallback.isCompleted = true; + } + } + Logger.d(TAG, "RPC #" + entry.getValue() + ": Timeout (" + currentCallback.elapsed() + " ms)"); + currentCallback.callback.onError(0, null); + } + } + } + } + } + + private class RpcCallbackWrapper { + public int id; + public long requestTime = System.currentTimeMillis(); + public boolean isSent = false; + public boolean isCompleted = false; + public boolean isConfirmed = false; + public RpcCallback callback; + public long timeoutTime; + public long timeout; + public TLMethod method; + + public boolean isAuthRequred; + public int dcId; + + private RpcCallbackWrapper(int id, TLMethod method, RpcCallback callback) { + this.id = id; + this.method = method; + this.callback = callback; + } + + public long elapsed() { + return System.currentTimeMillis() - requestTime; + } + } +} diff --git a/src/org/telegram/api/engine/TimeoutException.java b/src/org/telegram/api/engine/TimeoutException.java new file mode 100644 index 0000000..13acc0f --- /dev/null +++ b/src/org/telegram/api/engine/TimeoutException.java @@ -0,0 +1,26 @@ +package org.telegram.api.engine; + +import java.io.IOException; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 06.11.13 + * Time: 1:27 + */ +public class TimeoutException extends IOException { + public TimeoutException() { + } + + public TimeoutException(String s) { + super(s); + } + + public TimeoutException(String s, Throwable throwable) { + super(s, throwable); + } + + public TimeoutException(Throwable throwable) { + super(throwable); + } +} diff --git a/src/org/telegram/api/engine/file/DownloadListener.java b/src/org/telegram/api/engine/file/DownloadListener.java new file mode 100644 index 0000000..b633f47 --- /dev/null +++ b/src/org/telegram/api/engine/file/DownloadListener.java @@ -0,0 +1,12 @@ +package org.telegram.api.engine.file; + +/** + * Created by ex3ndr on 18.11.13. + */ +public interface DownloadListener { + public void onPartDownloaded(int percent, int downloadedSize); + + public void onDownloaded(); + + public void onFailed(); +} diff --git a/src/org/telegram/api/engine/file/Downloader.java b/src/org/telegram/api/engine/file/Downloader.java new file mode 100644 index 0000000..ac25966 --- /dev/null +++ b/src/org/telegram/api/engine/file/Downloader.java @@ -0,0 +1,368 @@ +package org.telegram.api.engine.file; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.ArrayList; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; + +import org.telegram.api.TLAbsInputFileLocation; +import org.telegram.api.engine.Logger; +import org.telegram.api.engine.TelegramApi; +import org.telegram.api.upload.TLFile; + +/** + * Created by ex3ndr on 18.11.13. + */ +public class Downloader { + public static final int FILE_QUEUED = 0; + public static final int FILE_DOWNLOADING = 1; + public static final int FILE_COMPLETED = 2; + public static final int FILE_CANCELED = 3; + public static final int FILE_FAILURE = 4; + + private final AtomicInteger fileIds = new AtomicInteger(1); + + private final String TAG; + + private TelegramApi api; + + private static final long DOWNLOAD_TIMEOUT = 30 * 1000; + + private static final long DEFAULT_DELAY = 15 * 1000; + + private static final int BLOCK_SIZE = 16 * 1024; + + private static final int PARALLEL_DOWNLOAD_COUNT = 2; + + private static final int PARALLEL_PARTS_COUNT = 4; + + private static final int BLOCK_QUEUED = 0; + private static final int BLOCK_DOWNLOADING = 1; + private static final int BLOCK_COMPLETED = 2; + + private ArrayList tasks = new ArrayList(); + + private ArrayList threads = new ArrayList(); + + private final Object threadLocker = new Object(); + + private Random rnd = new Random(); + + public Downloader(TelegramApi api) { + this.TAG = api.toString() + "#Downloader"; + this.api = api; + + for (int i = 0; i < PARALLEL_PARTS_COUNT; i++) { + DownloadFileThread thread = new DownloadFileThread(); + thread.start(); + threads.add(thread); + } + } + + public TelegramApi getApi() { + return api; + } + + private synchronized DownloadTask getTask(int taskId) { + for (DownloadTask task : tasks) { + if (task.taskId == taskId) { + return task; + } + } + return null; + } + + public synchronized void cancelTask(int taskId) { + DownloadTask task = getTask(taskId); + if (task != null && task.state != FILE_COMPLETED) { + task.state = FILE_CANCELED; + Logger.d(TAG, "File #" + task.taskId + "| Canceled"); + } + updateFileQueueStates(); + } + + public synchronized int getTaskState(int taskId) { + DownloadTask task = getTask(taskId); + if (task != null) { + return task.state; + } + + return FILE_CANCELED; + } + + public void waitForTask(int taskId) { + while (true) { + int state = getTaskState(taskId); + if ((state == FILE_COMPLETED) || (state == FILE_FAILURE) || (state == FILE_CANCELED)) { + return; + } + synchronized (threadLocker) { + try { + threadLocker.wait(DEFAULT_DELAY); + } catch (InterruptedException e) { + Logger.e(TAG, e); + return; + } + } + } + } + + public synchronized int requestTask(int dcId, TLAbsInputFileLocation location, int size, String destFile, DownloadListener listener) { + int blockSize = BLOCK_SIZE; + int totalBlockCount = (int) Math.ceil(((double) size) / blockSize); + + DownloadTask task = new DownloadTask(); + task.listener = listener; + task.blockSize = blockSize; + task.destFile = destFile; + try { + task.file = new RandomAccessFile(destFile, "rw"); + task.file.setLength(size); + } catch (FileNotFoundException e) { + Logger.e(TAG, e); + } catch (IOException e) { + Logger.e(TAG, e); + } + task.taskId = fileIds.getAndIncrement(); + task.dcId = dcId; + task.location = location; + task.size = size; + task.blocks = new DownloadBlock[totalBlockCount]; + for (int i = 0; i < totalBlockCount; i++) { + task.blocks[i] = new DownloadBlock(); + task.blocks[i].task = task; + task.blocks[i].index = i; + task.blocks[i].state = BLOCK_QUEUED; + } + task.state = FILE_QUEUED; + task.queueTime = System.nanoTime(); + tasks.add(task); + + Logger.d(TAG, "File #" + task.taskId + "| Requested"); + + updateFileQueueStates(); + + return task.taskId; + } + + private synchronized DownloadTask[] getActiveTasks() { + ArrayList res = new ArrayList(); + for (DownloadTask task : tasks) { + if (task.state == FILE_DOWNLOADING) { + res.add(task); + } + } + return res.toArray(new DownloadTask[res.size()]); + } + + private synchronized void updateFileQueueStates() { + DownloadTask[] activeTasks = getActiveTasks(); + outer: + for (DownloadTask task : activeTasks) { + for (DownloadBlock block : task.blocks) { + if (block.state != BLOCK_COMPLETED) { + continue outer; + } + } + onTaskCompleted(task); + } + activeTasks = getActiveTasks(); + + int count = activeTasks.length; + while (count < PARALLEL_DOWNLOAD_COUNT) { + long mintime = Long.MAX_VALUE; + DownloadTask minTask = null; + for (DownloadTask task : tasks) { + if (task.state == FILE_QUEUED && task.queueTime < mintime) { + minTask = task; + } + } + + if (minTask == null) { + break; + } + minTask.state = FILE_DOWNLOADING; + Logger.d(TAG, "File #" + minTask.taskId + "| Downloading"); + } + + synchronized (threadLocker) { + threadLocker.notifyAll(); + } + } + + private synchronized void onTaskCompleted(DownloadTask task) { + if (task.state != FILE_COMPLETED) { + Logger.d(TAG, "File #" + task.taskId + "| Completed in " + (System.nanoTime() - task.queueTime) / (1000 * 1000L) + " ms"); + task.state = FILE_COMPLETED; + try { + if (task.file != null) { + task.file.close(); + task.file = null; + } + } catch (IOException e) { + Logger.e(TAG, e); + } + } + updateFileQueueStates(); + } + + private synchronized void onTaskFailure(DownloadTask task) { + if (task.state != FILE_FAILURE) { + Logger.d(TAG, "File #" + task.taskId + "| Failure in " + (System.nanoTime() - task.queueTime) / (1000 * 1000L) + " ms"); + task.state = FILE_FAILURE; + try { + if (task.file != null) { + task.file.close(); + task.file = null; + } + } catch (IOException e) { + Logger.e(TAG, e); + } + } + updateFileQueueStates(); + } + + private synchronized DownloadTask fetchTask() { + DownloadTask[] activeTasks = getActiveTasks(); + if (activeTasks.length == 0) { + return null; + } else if (activeTasks.length == 1) { + return activeTasks[0]; + } else { + return activeTasks[rnd.nextInt(activeTasks.length)]; + } + } + + private synchronized DownloadBlock fetchBlock() { + DownloadTask task = fetchTask(); + if (task == null) { + return null; + } + + for (int i = 0; i < task.blocks.length; i++) { + if (task.blocks[i].state == BLOCK_QUEUED) { + task.blocks[i].state = BLOCK_DOWNLOADING; + if (task.lastSuccessBlock == 0) { + task.lastSuccessBlock = System.nanoTime(); + } + return task.blocks[i]; + } + } + + return null; + } + + private synchronized void onBlockDownloaded(DownloadBlock block, byte[] data) { + try { + if (block.task.file != null) { + block.task.file.seek(block.index * block.task.blockSize); + block.task.file.write(data); + } else { + return; + } + } catch (IOException e) { + Logger.e(TAG, e); + } + block.task.lastSuccessBlock = System.nanoTime(); + block.state = BLOCK_COMPLETED; + if (block.task.listener != null) { + int downloadedCount = 0; + for (DownloadBlock b : block.task.blocks) { + if (b.state == BLOCK_COMPLETED) { + downloadedCount++; + } + } + + int percent = downloadedCount * 100 / block.task.blocks.length; + block.task.listener.onPartDownloaded(percent, downloadedCount); + } + updateFileQueueStates(); + } + + private synchronized void onBlockFailure(DownloadBlock block) { + block.state = BLOCK_QUEUED; + if (block.task.lastSuccessBlock != 0 && (System.nanoTime() - block.task.lastSuccessBlock > DOWNLOAD_TIMEOUT * 1000L * 1000L)) { + onTaskFailure(block.task); + } + updateFileQueueStates(); + } + + private class DownloadTask { + + public DownloadListener listener; + public long lastNotifyTime; + + public int taskId; + + public int blockSize; + + public int dcId; + public TLAbsInputFileLocation location; + public int size; + + public long queueTime; + + public int state; + + public DownloadBlock[] blocks; + + public String destFile; + + public RandomAccessFile file; + + public long lastSuccessBlock; + } + + private class DownloadBlock { + public DownloadTask task; + public int state; + public int index; + } + + private class DownloadFileThread extends Thread { + + public DownloadFileThread() { + setName("DownloadFileThread#" + hashCode()); + } + + @Override + public void run() { + setPriority(Thread.MIN_PRIORITY); + while (true) { + Logger.d(TAG, "DownloadFileThread iteration"); + try { + Thread.sleep(50); + } catch (InterruptedException e) { + e.printStackTrace(); + return; + } + DownloadBlock block = fetchBlock(); + if (block == null) { + synchronized (threadLocker) { + try { + threadLocker.wait(); + continue; + } catch (InterruptedException e) { + Logger.e(TAG, e); + return; + } + } + } + + long start = System.nanoTime(); + Logger.d(TAG, "Block #" + block.index + " of #" + block.task.taskId + "| Starting"); + try { + TLFile file = api.doGetFile(block.task.dcId, block.task.location, block.index * block.task.blockSize, block.task.blockSize); + Logger.d(TAG, "Block #" + block.index + " of #" + block.task.taskId + "| Downloaded in " + (System.nanoTime() - start) / (1000 * 1000L) + " ms"); + onBlockDownloaded(block, file.getBytes()); + } catch (IOException e) { + Logger.d(TAG, "Block #" + block.index + " of #" + block.task.taskId + "| Failure"); + Logger.e(TAG, e); + onBlockFailure(block); + } + } + } + } +} diff --git a/src/org/telegram/api/engine/file/UploadListener.java b/src/org/telegram/api/engine/file/UploadListener.java new file mode 100644 index 0000000..b0611cd --- /dev/null +++ b/src/org/telegram/api/engine/file/UploadListener.java @@ -0,0 +1,8 @@ +package org.telegram.api.engine.file; + +/** + * Created by ex3ndr on 19.11.13. + */ +public interface UploadListener { + public void onPartUploaded(int percent, int downloadedSize); +} diff --git a/src/org/telegram/api/engine/file/Uploader.java b/src/org/telegram/api/engine/file/Uploader.java new file mode 100644 index 0000000..714e2f6 --- /dev/null +++ b/src/org/telegram/api/engine/file/Uploader.java @@ -0,0 +1,413 @@ +package org.telegram.api.engine.file; + +import org.telegram.api.engine.Logger; +import org.telegram.api.engine.TelegramApi; +import org.telegram.mtproto.secure.CryptoUtils; +import org.telegram.mtproto.secure.Entropy; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.ArrayList; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by ex3ndr on 19.11.13. + */ +public class Uploader { + + private static final int KB = 1024; + private static final int MB = 1024 * KB; + + private final AtomicInteger fileIds = new AtomicInteger(1); + + public static final int FILE_QUEUED = 0; + public static final int FILE_IN_PROGRESS = 1; + public static final int FILE_COMPLETED = 2; + public static final int FILE_CANCELED = 3; + + private static final int BLOCK_QUEUED = 0; + private static final int BLOCK_DOWNLOADING = 1; + private static final int BLOCK_COMPLETED = 2; + + private static final int PARALLEL_DOWNLOAD_COUNT = 2; + + private static final int PARALLEL_PARTS_COUNT = 4; + + private static final int[] BLOCK_SIZES = new int[]{8 * KB, 16 * KB, 32 * KB, 64 * KB, 128 * KB, 256 * KB, 512 * KB}; + + private static final long DEFAULT_DELAY = 15 * 1000; + + private static final int BIG_FILE_MIN = 10 * 1024 * 1024; + + private static final int MAX_BLOCK_COUNT = 3000; + + private final String TAG; + + private TelegramApi api; + + private ArrayList tasks = new ArrayList(); + + private ArrayList threads = new ArrayList(); + + private final Object threadLocker = new Object(); + + private Random rnd = new Random(); + + public Uploader(TelegramApi api) { + this.TAG = api.toString() + "#Uploader"; + this.api = api; + + for (int i = 0; i < PARALLEL_PARTS_COUNT; i++) { + UploadFileThread thread = new UploadFileThread(); + thread.start(); + threads.add(thread); + } + } + + public TelegramApi getApi() { + return api; + } + + private synchronized UploadTask getTask(int taskId) { + for (UploadTask task : tasks) { + if (task.taskId == taskId) { + return task; + } + } + return null; + } + + public synchronized void cancelTask(int taskId) { + UploadTask task = getTask(taskId); + if (task != null && task.state != FILE_COMPLETED) { + task.state = FILE_CANCELED; + Logger.d(TAG, "File #" + task.taskId + "| Canceled"); + } + updateFileQueueStates(); + } + + public synchronized int getTaskState(int taskId) { + UploadTask task = getTask(taskId); + if (task != null) { + return task.state; + } + + return FILE_CANCELED; + } + + public void waitForTask(int taskId) { + while (true) { + int state = getTaskState(taskId); + if ((state == FILE_COMPLETED) || (state == FILE_CANCELED)) { + return; + } + synchronized (threadLocker) { + try { + threadLocker.wait(DEFAULT_DELAY); + } catch (InterruptedException e) { + Logger.e(TAG, e); + return; + } + } + } + } + + public UploadResult getUploadResult(int taskId) { + UploadTask task = getTask(taskId); + if (task == null) { + return null; + } + if (task.state != FILE_COMPLETED) { + return null; + } + + return new UploadResult(task.uniqId, task.blocks.length, task.hash, task.usedBigFile); + } + + public synchronized int requestTask(String srcFile, UploadListener listener) { + UploadTask task = new UploadTask(); + task.taskId = fileIds.getAndIncrement(); + task.uniqId = Entropy.generateRandomId(); + task.listener = listener; + task.srcFile = srcFile; + try { + task.file = new RandomAccessFile(srcFile, "r"); + task.size = (int) task.file.length(); + if (task.size >= BIG_FILE_MIN) { + task.usedBigFile = true; + Logger.d(TAG, "File #" + task.uniqId + "| Using big file method"); + } else { + task.usedBigFile = false; + } + long start = System.currentTimeMillis(); + Logger.d(TAG, "File #" + task.uniqId + "| Calculating hash"); + task.hash = CryptoUtils.MD5(task.file); + Logger.d(TAG, "File #" + task.uniqId + "| Hash " + task.hash + " in " + (System.currentTimeMillis() - start) + " ms"); + } catch (FileNotFoundException e) { + Logger.e(TAG, e); + } catch (IOException e) { + Logger.e(TAG, e); + } + + task.blockSize = BLOCK_SIZES[BLOCK_SIZES.length - 1]; + for (int size : BLOCK_SIZES) { + int totalBlockCount = (int) Math.ceil(((double) task.size) / size); + if (totalBlockCount < MAX_BLOCK_COUNT) { + task.blockSize = size; + break; + } + } + + Logger.d(TAG, "File #" + task.uniqId + "| Using block size: " + task.blockSize); + + int totalBlockCount = (int) Math.ceil(((double) task.size) / task.blockSize); + task.blocks = new UploadBlock[totalBlockCount]; + for (int i = 0; i < totalBlockCount; i++) { + task.blocks[i] = new UploadBlock(); + task.blocks[i].task = task; + task.blocks[i].index = i; + task.blocks[i].state = BLOCK_QUEUED; + } + task.state = FILE_QUEUED; + task.queueTime = System.nanoTime(); + tasks.add(task); + + Logger.d(TAG, "File #" + task.uniqId + "| Requested"); + + updateFileQueueStates(); + + return task.taskId; + } + + private synchronized UploadTask[] getActiveTasks() { + ArrayList res = new ArrayList(); + for (UploadTask task : tasks) { + if (task.state == FILE_IN_PROGRESS) { + res.add(task); + } + } + return res.toArray(new UploadTask[res.size()]); + } + + private synchronized void updateFileQueueStates() { + UploadTask[] activeTasks = getActiveTasks(); + outer: + for (UploadTask task : activeTasks) { + for (UploadBlock block : task.blocks) { + if (block.state != BLOCK_COMPLETED) { + continue outer; + } + } + onTaskCompleted(task); + } + activeTasks = getActiveTasks(); + + int count = activeTasks.length; + while (count < PARALLEL_DOWNLOAD_COUNT) { + long mintime = Long.MAX_VALUE; + UploadTask minTask = null; + for (UploadTask task : tasks) { + if (task.state == FILE_QUEUED && task.queueTime < mintime) { + minTask = task; + } + } + + if (minTask == null) { + break; + } + minTask.state = FILE_IN_PROGRESS; + Logger.d(TAG, "File #" + minTask.uniqId + "| Uploading"); + } + + synchronized (threadLocker) { + threadLocker.notifyAll(); + } + } + + private synchronized void onTaskCompleted(UploadTask task) { + if (task.state != FILE_COMPLETED) { + Logger.d(TAG, "File #" + task.uniqId + "| Completed in " + (System.nanoTime() - task.queueTime) / (1000 * 1000L) + " ms"); + task.state = FILE_COMPLETED; + try { + if (task.file != null) { + task.file.close(); + task.file = null; + } + } catch (IOException e) { + Logger.e(TAG, e); + } + } + updateFileQueueStates(); + } + + private synchronized UploadTask fetchTask() { + UploadTask[] activeTasks = getActiveTasks(); + if (activeTasks.length == 0) { + return null; + } else if (activeTasks.length == 1) { + return activeTasks[0]; + } else { + return activeTasks[rnd.nextInt(activeTasks.length)]; + } + } + + private synchronized UploadBlock fetchBlock() { + UploadTask task = fetchTask(); + if (task == null) { + return null; + } + + for (int i = 0; i < task.blocks.length; i++) { + if (task.blocks[i].state == BLOCK_QUEUED) { + task.blocks[i].state = BLOCK_DOWNLOADING; + byte[] block = new byte[Math.min(task.size - task.blockSize * i, task.blockSize)]; + try { + task.file.seek(task.blockSize * i); + task.file.readFully(block); + } catch (IOException e) { + Logger.e(TAG, e); + } + task.blocks[i].workData = block; + return task.blocks[i]; + } + } + + return null; + } + + private synchronized void onBlockUploaded(UploadBlock block) { + block.state = BLOCK_COMPLETED; + if (block.task.listener != null) { + int downloadedCount = 0; + for (UploadBlock b : block.task.blocks) { + if (b.state == BLOCK_COMPLETED) { + downloadedCount++; + } + } + int percent = downloadedCount * 100 / block.task.blocks.length; + block.task.listener.onPartUploaded(percent, downloadedCount); + } + updateFileQueueStates(); + } + + private synchronized void onBlockFailure(UploadBlock block) { + block.state = BLOCK_QUEUED; + updateFileQueueStates(); + } + + public static class UploadResult { + private long fileId; + private boolean usedBigFile; + private int partsCount; + private String hash; + + public UploadResult(long fileId, int partsCount, String hash, boolean usedBigFile) { + this.fileId = fileId; + this.partsCount = partsCount; + this.hash = hash; + this.usedBigFile = usedBigFile; + } + + public long getFileId() { + return fileId; + } + + public boolean isUsedBigFile() { + return usedBigFile; + } + + public int getPartsCount() { + return partsCount; + } + + public String getHash() { + return hash; + } + } + + private class UploadTask { + + public UploadListener listener; + + public boolean usedBigFile; + + public long uniqId; + + public int taskId; + + public int blockSize; + + public long queueTime; + + public int state; + + public int size; + + public UploadBlock[] blocks; + + public String srcFile; + + public RandomAccessFile file; + + public String hash; + } + + private class UploadBlock { + public UploadTask task; + public int state; + public int index; + public byte[] workData; + } + + private class UploadFileThread extends Thread { + + public UploadFileThread() { + setName("UploadFileThread#" + hashCode()); + } + + @Override + public void run() { + setPriority(Thread.MIN_PRIORITY); + while (true) { + Logger.d(TAG, "UploadFileThread iteration"); + try { + Thread.sleep(50); + } catch (InterruptedException e) { + e.printStackTrace(); + return; + } + UploadBlock block = fetchBlock(); + if (block == null) { + synchronized (threadLocker) { + try { + threadLocker.wait(); + continue; + } catch (InterruptedException e) { + Logger.e(TAG, e); + return; + } + } + } + + long start = System.nanoTime(); + Logger.d(TAG, "Block #" + block.index + " of #" + block.task.uniqId + "| Starting"); + try { + if (block.task.usedBigFile) { + api.doSaveBigFilePart(block.task.uniqId, block.index, block.task.blocks.length, block.workData); + } else { + api.doSaveFilePart(block.task.uniqId, block.index, block.workData); + } + block.workData = null; + Logger.d(TAG, "Block #" + block.index + " of #" + block.task.uniqId + "| Uploaded in " + (System.nanoTime() - start) / (1000 * 1000L) + " ms"); + onBlockUploaded(block); + } catch (IOException e) { + Logger.d(TAG, "Block #" + block.index + " of #" + block.task.uniqId + "| Failure"); + Logger.e(TAG, e); + onBlockFailure(block); + } + } + } + } +} diff --git a/src/org/telegram/api/engine/storage/AbsApiState.java b/src/org/telegram/api/engine/storage/AbsApiState.java new file mode 100644 index 0000000..ca19b52 --- /dev/null +++ b/src/org/telegram/api/engine/storage/AbsApiState.java @@ -0,0 +1,41 @@ +package org.telegram.api.engine.storage; + +import org.telegram.api.TLConfig; +import org.telegram.mtproto.state.AbsMTProtoState; +import org.telegram.mtproto.state.ConnectionInfo; +import org.telegram.mtproto.state.KnownSalt; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 07.11.13 + * Time: 10:19 + */ +public interface AbsApiState { + + int getPrimaryDc(); + + public void setPrimaryDc(int dc); + + boolean isAuthenticated(int dcId); + + void setAuthenticated(int dcId, boolean auth); + + void updateSettings(TLConfig config); + + + byte[] getAuthKey(int dcId); + + void putAuthKey(int dcId, byte[] key); + + ConnectionInfo[] getAvailableConnections(int dcId); + + AbsMTProtoState getMtProtoState(int dcId); + + void resetAuth(); + + void reset(); +} diff --git a/src/org/telegram/mtproto/CallWrapper.java b/src/org/telegram/mtproto/CallWrapper.java new file mode 100644 index 0000000..3d71efc --- /dev/null +++ b/src/org/telegram/mtproto/CallWrapper.java @@ -0,0 +1,14 @@ +package org.telegram.mtproto; + +import org.telegram.tl.TLMethod; +import org.telegram.tl.TLObject; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 07.11.13 + * Time: 3:56 + */ +public interface CallWrapper { + public TLObject wrapObject(TLMethod srcRequest); +} diff --git a/src/org/telegram/mtproto/MTProto.java b/src/org/telegram/mtproto/MTProto.java new file mode 100644 index 0000000..3f4de69 --- /dev/null +++ b/src/org/telegram/mtproto/MTProto.java @@ -0,0 +1,561 @@ +package org.telegram.mtproto; + +import com.droidkit.actors.*; +import org.telegram.mtproto.log.Logger; +import org.telegram.mtproto.schedule.Scheduller; +import org.telegram.mtproto.secure.Entropy; +import org.telegram.mtproto.state.AbsMTProtoState; +import org.telegram.mtproto.state.KnownSalt; +import org.telegram.mtproto.time.TimeOverlord; +import org.telegram.mtproto.tl.*; +import org.telegram.mtproto.transport.*; +import org.telegram.mtproto.util.BytesCache; +import org.telegram.tl.DeserializeException; +import org.telegram.tl.TLMethod; +import org.telegram.tl.TLObject; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.telegram.mtproto.secure.CryptoUtils.*; +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 8:14 + */ +public class MTProto { + + public static final int MODE_GENERAL = 0; + public static final int MODE_GENERAL_LOW_MODE = 1; + public static final int MODE_FILE = 2; + public static final int MODE_PUSH = 3; + + private static final AtomicInteger instanceIndex = new AtomicInteger(1000); + + private static final int MESSAGES_CACHE = 100; + private static final int MESSAGES_CACHE_MIN = 10; + + private static final int MAX_INTERNAL_FLOOD_WAIT = 10;//10 sec + + private static final int PING_INTERVAL_REQUEST = 60000;// 1 min + private static final int PING_INTERVAL = 75;//75 secs + + private static final int PING_PUSH_REQUEST = 9 * 60 * 1000; // 5 Min + + private static final int ERROR_MSG_ID_TOO_SMALL = 16; + private static final int ERROR_MSG_ID_TOO_BIG = 17; + private static final int ERROR_MSG_ID_BITS = 18; + private static final int ERROR_CONTAINER_MSG_ID_INCORRECT = 19; + private static final int ERROR_TOO_OLD = 20; + private static final int ERROR_SEQ_NO_TOO_SMALL = 32; + private static final int ERROR_SEQ_NO_TOO_BIG = 33; + private static final int ERROR_SEQ_EXPECTED_EVEN = 34; + private static final int ERROR_SEQ_EXPECTED_ODD = 35; + private static final int ERROR_BAD_SERVER_SALT = 48; + private static final int ERROR_BAD_CONTAINER = 64; + + private static final int PING_TIMEOUT = 60 * 1000; + private static final int RESEND_TIMEOUT = 60 * 1000; + + private static final int FUTURE_REQUEST_COUNT = 64; + private static final int FUTURE_MINIMAL = 5; + private static final long FUTURE_TIMEOUT = 5 * 60 * 60 * 1000;//5 min + private static final long FUTURE_NO_TIME_TIMEOUT = 15 * 60 * 1000;//15 sec + + private final String TAG; + private final int INSTANCE_INDEX; + + private MTProtoContext protoContext; + private ActorSystem actorSystem; + + private byte[] authKey; + private byte[] authKeyId; + private byte[] session; + private AbsMTProtoState state; + + private int desiredConnectionCount; + private TransportPool transportPool; + + private int mode = MODE_GENERAL; + private final Scheduller scheduller; + private final ArrayList receivedMessages = new ArrayList(); + private final ActorRef responseActor; + private final ActorRef actionsActor; + private MTProtoCallback callback; + + private boolean isClosed; + + public MTProto(AbsMTProtoState state, + MTProtoCallback callback, + CallWrapper callWrapper, + int connectionsCount, + int mode) { + this.INSTANCE_INDEX = instanceIndex.incrementAndGet(); + this.TAG = "MTProto#" + INSTANCE_INDEX; + this.mode = mode; + this.actorSystem = new ActorSystem(); + this.actorSystem.addDispatcher("connector"); + this.responseActor = actorSystem.actorOf(processor()); + this.actionsActor = actorSystem.actorOf(internal()); + + this.state = state; + this.callback = callback; + this.authKey = state.getAuthKey(); + this.authKeyId = substring(SHA1(authKey), 12, 8); + this.protoContext = MTProtoContext.getInstance(); + this.desiredConnectionCount = connectionsCount; + this.session = Entropy.generateSeed(8); + this.scheduller = new Scheduller(this, callWrapper); + this.scheduller.postMessage(new MTPing(Entropy.generateRandomId()), false, Long.MAX_VALUE); + + this.transportPool = new TransportTcpPool(this, new TransportPoolCallback() { + @Override + public void onMTMessage(MTMessage message) { + responseActor.send(message); + } + + @Override + public void onFastConfirm(int hash) { + // We might not send this to response actor for providing faster confirmation + int[] ids = scheduller.mapFastConfirm(hash); + for (int id : ids) { + MTProto.this.callback.onConfirmed(id); + } + } + }, desiredConnectionCount); + switch (mode) { + case MODE_GENERAL: + case MODE_PUSH: + transportPool.switchMode(TransportPool.MODE_DEFAULT); + break; + case MODE_GENERAL_LOW_MODE: + case MODE_FILE: + transportPool.switchMode(TransportPool.MODE_LOWMODE); + break; + + } + + this.actionsActor.sendOnce(new RequestPingDelay()); + this.actionsActor.sendOnce(new RequestSalt()); + } + + public AbsMTProtoState getState() { + return state; + } + + public void resetNetworkBackoff() { + this.transportPool.resetConnectionBackoff(); + this.actionsActor.sendOnce(new RequestPingDelay()); + } + + public void reloadConnectionInformation() { + this.transportPool.reloadConnectionInformation(); + this.actionsActor.sendOnce(new RequestPingDelay()); + } + + public int getInstanceIndex() { + return INSTANCE_INDEX; + } + + public Scheduller getScheduller() { + return scheduller; + } + + public byte[] getSession() { + return session; + } + + public byte[] getAuthKeyId() { + return authKeyId; + } + + public byte[] getAuthKey() { + return authKey; + } + + public ActorSystem getActorSystem() { + return actorSystem; + } + + public boolean isClosed() { + return isClosed; + } + + public int sendRpcMessage(TLMethod request, long timeout, boolean highPriority) { + int id = scheduller.postMessage(request, true, timeout, highPriority); + Logger.d(TAG, "sendMessage #" + id + " " + request.toString() + " with timeout " + timeout + " ms"); + return id; + } + + public void forgetMessage(int id) { + scheduller.forgetMessage(id); + } + + public void switchMode(int mode) { + if (this.mode != mode) { + this.mode = mode; + + switch (mode) { + case MODE_GENERAL: + case MODE_PUSH: + transportPool.switchMode(TransportPool.MODE_DEFAULT); + break; + case MODE_GENERAL_LOW_MODE: + case MODE_FILE: + transportPool.switchMode(TransportPool.MODE_LOWMODE); + break; + + } + + this.actionsActor.sendOnce(new RequestPingDelay()); + } + } + + public void close() { + if (!isClosed) { + this.isClosed = true; + // TODO: implement + // this.actorSystem.close(); + this.transportPool.close(); + } + } + + // Finding message type + private void onMTMessage(MTMessage mtMessage) { + if (mtMessage.getSeqNo() % 2 == 1) { + scheduller.confirmMessage(mtMessage.getMessageId()); + } + if (!needProcessing(mtMessage.getMessageId())) { + if (Logger.LOG_IGNORED) { + Logger.d(TAG, "Ignoring messages #" + mtMessage.getMessageId()); + } + return; + } + try { + TLObject intMessage = protoContext.deserializeMessage(new ByteArrayInputStream(mtMessage.getContent())); + onMTProtoMessage(mtMessage.getMessageId(), intMessage); + } catch (DeserializeException e) { + callback.onApiMessage(mtMessage.getContent(), this); + } catch (IOException e) { + Logger.e(TAG, e); + // ??? + } + } + + private void onMTProtoMessage(long msgId, TLObject object) { + Logger.d(TAG, "MTProtoMessage: " + object.toString()); + + if (object instanceof MTBadMessage) { + MTBadMessage badMessage = (MTBadMessage) object; + Logger.d(TAG, "BadMessage: " + badMessage.getErrorCode() + " #" + badMessage.getBadMsgId()); + scheduller.onMessageConfirmed(badMessage.getBadMsgId()); + long time = scheduller.getMessageIdGenerationTime(badMessage.getBadMsgId()); + if (time != 0) { + if (badMessage.getErrorCode() == ERROR_MSG_ID_TOO_BIG + || badMessage.getErrorCode() == ERROR_MSG_ID_TOO_SMALL) { + long delta = System.nanoTime() / 1000000 - time; + TimeOverlord.getInstance().onForcedServerTimeArrived((msgId >> 32) * 1000, delta); + if (badMessage.getErrorCode() == ERROR_MSG_ID_TOO_BIG) { + scheduller.resetMessageId(); + } + scheduller.resendAsNewMessage(badMessage.getBadMsgId()); + } else if (badMessage.getErrorCode() == ERROR_SEQ_NO_TOO_BIG || badMessage.getErrorCode() == ERROR_SEQ_NO_TOO_SMALL) { + if (scheduller.isMessageFromCurrentGeneration(badMessage.getBadMsgId())) { + Logger.d(TAG, "Resetting session"); + session = Entropy.generateSeed(8); + transportPool.onSessionChanged(session); + scheduller.resetSession(); + } + scheduller.resendAsNewMessage(badMessage.getBadMsgId()); + } else if (badMessage.getErrorCode() == ERROR_BAD_SERVER_SALT) { + long salt = ((MTBadServerSalt) badMessage).getNewSalt(); + // Sync time + long delta = System.nanoTime() / 1000000 - time; + TimeOverlord.getInstance().onMethodExecuted(badMessage.getBadMsgId(), msgId, delta); + state.badServerSalt(salt); + Logger.d(TAG, "Reschedule messages because bad_server_salt #" + badMessage.getBadMsgId()); + scheduller.resendAsNewMessage(badMessage.getBadMsgId()); + this.actionsActor.sendOnce(new RequestSalt()); + } else if (badMessage.getErrorCode() == ERROR_BAD_CONTAINER || + badMessage.getErrorCode() == ERROR_CONTAINER_MSG_ID_INCORRECT) { + scheduller.resendMessage(badMessage.getBadMsgId()); + } else if (badMessage.getErrorCode() == ERROR_TOO_OLD) { + scheduller.resendAsNewMessage(badMessage.getBadMsgId()); + } else { + if (Logger.LOG_IGNORED) { + Logger.d(TAG, "Ignored BadMsg #" + badMessage.getErrorCode() + " (" + badMessage.getBadMsgId() + ", " + badMessage.getBadMsqSeqno() + ")"); + } + scheduller.forgetMessageByMsgId(badMessage.getBadMsgId()); + } + } else { + if (Logger.LOG_IGNORED) { + Logger.d(TAG, "Unknown package #" + badMessage.getBadMsgId()); + } + } + } else if (object instanceof MTMsgsAck) { + MTMsgsAck ack = (MTMsgsAck) object; + String log = ""; + for (Long ackMsgId : ack.getMessages()) { + scheduller.onMessageConfirmed(ackMsgId); + if (log.length() > 0) { + log += ", "; + } + log += ackMsgId; + int id = scheduller.mapSchedullerId(ackMsgId); + if (id > 0) { + callback.onConfirmed(id); + } + } + Logger.d(TAG, "msgs_ack: " + log); + } else if (object instanceof MTRpcResult) { + MTRpcResult result = (MTRpcResult) object; + + Logger.d(TAG, "rpc_result: " + result.getMessageId()); + + int id = scheduller.mapSchedullerId(result.getMessageId()); + if (id > 0) { + int responseConstructor = readInt(result.getContent()); + if (responseConstructor == MTRpcError.CLASS_ID) { + try { + MTRpcError error = (MTRpcError) protoContext.deserializeMessage(result.getContent()); + BytesCache.getInstance().put(result.getContent()); + + if (error.getErrorCode() == 420) { + if (error.getErrorTag().startsWith("FLOOD_WAIT_")) { + // Secs + int delay = Integer.parseInt(error.getErrorTag().substring("FLOOD_WAIT_".length())); + if (delay <= MAX_INTERNAL_FLOOD_WAIT) { + scheduller.resendAsNewMessageDelayed(result.getMessageId(), delay * 1000); + return; + } + } + } + if (error.getErrorCode() == 401) { + if (error.getErrorTag().equals("AUTH_KEY_UNREGISTERED") || + error.getErrorTag().equals("AUTH_KEY_INVALID") || + error.getErrorTag().equals("USER_DEACTIVATED") || + error.getErrorTag().equals("SESSION_REVOKED") || + error.getErrorTag().equals("SESSION_EXPIRED")) { + Logger.w(TAG, "Auth key invalidated: " + error.getErrorTag()); + callback.onAuthInvalidated(this); + close(); + return; + } + } + + callback.onRpcError(id, error.getErrorCode(), error.getMessage(), this); + scheduller.forgetMessage(id); + } catch (IOException e) { + Logger.e(TAG, e); + return; + } + } else { + Logger.d(TAG, "rpc_result: " + result.getMessageId() + " #" + Integer.toHexString(responseConstructor)); + callback.onRpcResult(id, result.getContent(), this); + BytesCache.getInstance().put(result.getContent()); + scheduller.forgetMessage(id); + } + } else { + if (Logger.LOG_IGNORED) { + Logger.d(TAG, "ignored rpc_result: " + result.getMessageId()); + } + BytesCache.getInstance().put(result.getContent()); + } + scheduller.onMessageConfirmed(result.getMessageId()); + long time = scheduller.getMessageIdGenerationTime(result.getMessageId()); + if (time != 0) { + long delta = System.nanoTime() / 1000000 - time; + TimeOverlord.getInstance().onMethodExecuted(result.getMessageId(), msgId, delta); + } + } else if (object instanceof MTPong) { + MTPong pong = (MTPong) object; + if (Logger.LOG_PING) { + Logger.d(TAG, "pong: " + pong.getPingId()); + } + scheduller.onMessageConfirmed(pong.getMessageId()); + scheduller.forgetMessageByMsgId(pong.getMessageId()); + long time = scheduller.getMessageIdGenerationTime(pong.getMessageId()); + if (time != 0) { + long delta = System.nanoTime() / 1000000 - time; + TimeOverlord.getInstance().onMethodExecuted(pong.getMessageId(), msgId, delta); + } + } else if (object instanceof MTFutureSalts) { + MTFutureSalts salts = (MTFutureSalts) object; + scheduller.onMessageConfirmed(salts.getRequestId()); + scheduller.forgetMessageByMsgId(salts.getRequestId()); + + long time = scheduller.getMessageIdGenerationTime(salts.getRequestId()); + + if (time > 0) { + KnownSalt[] knownSalts = new KnownSalt[salts.getSalts().size()]; + for (int i = 0; i < knownSalts.length; i++) { + MTFutureSalt salt = salts.getSalts().get(i); + knownSalts[i] = new KnownSalt(salt.getValidSince(), salt.getValidUntil(), salt.getSalt()); + } + + long delta = System.nanoTime() / 1000000 - time; + TimeOverlord.getInstance().onForcedServerTimeArrived(salts.getNow(), delta); + state.mergeKnownSalts(salts.getNow(), knownSalts); + } + } else if (object instanceof MTMessageDetailedInfo) { + MTMessageDetailedInfo detailedInfo = (MTMessageDetailedInfo) object; + Logger.d(TAG, "msg_detailed_info: " + detailedInfo.getMsgId() + ", answer: " + detailedInfo.getAnswerMsgId()); + if (receivedMessages.contains(detailedInfo.getAnswerMsgId())) { + scheduller.confirmMessage(detailedInfo.getAnswerMsgId()); + } else { + int id = scheduller.mapSchedullerId(detailedInfo.getMsgId()); + if (id > 0) { + scheduller.postMessage(new MTNeedResendMessage(new long[]{detailedInfo.getAnswerMsgId()}), false, RESEND_TIMEOUT); + } else { + scheduller.confirmMessage(detailedInfo.getAnswerMsgId()); + scheduller.forgetMessageByMsgId(detailedInfo.getMsgId()); + } + } + } else if (object instanceof MTNewMessageDetailedInfo) { + MTNewMessageDetailedInfo detailedInfo = (MTNewMessageDetailedInfo) object; + Logger.d(TAG, "msg_new_detailed_info: " + detailedInfo.getAnswerMsgId()); + if (receivedMessages.contains(detailedInfo.getAnswerMsgId())) { + scheduller.confirmMessage(detailedInfo.getAnswerMsgId()); + } else { + scheduller.postMessage(new MTNeedResendMessage(new long[]{detailedInfo.getAnswerMsgId()}), false, RESEND_TIMEOUT); + } + } else if (object instanceof MTNewSessionCreated) { + callback.onSessionCreated(this); + } else { + if (Logger.LOG_IGNORED) { + Logger.d(TAG, "Ignored MTProto message " + object.toString()); + } + } + } + + private boolean needProcessing(long messageId) { + synchronized (receivedMessages) { + if (receivedMessages.contains(messageId)) { + return false; + } + + if (receivedMessages.size() > MESSAGES_CACHE_MIN) { + boolean isSmallest = true; + for (Long l : receivedMessages) { + if (messageId > l) { + isSmallest = false; + break; + } + } + + if (isSmallest) { + return false; + } + } + + while (receivedMessages.size() >= MESSAGES_CACHE - 1) { + receivedMessages.remove(0); + } + receivedMessages.add(messageId); + } + + return true; + } + + private ActorSelection internal() { + return new ActorSelection(Props.create(InternalActionsActor.class, new ActorCreator() { + @Override + public InternalActionsActor create() { + return new InternalActionsActor(MTProto.this); + } + }), "internal_" + INSTANCE_INDEX); + } + + private ActorSelection processor() { + return new ActorSelection(Props.create(ResponseActor.class, new ActorCreator() { + @Override + public ResponseActor create() { + return new ResponseActor(MTProto.this); + } + }), "response_" + INSTANCE_INDEX); + } + + private static class RequestSalt { + } + + private static class RequestPingDelay { + + } + + private static class InternalActionsActor extends Actor { + + private int lastPingMessage = -1; + private final MTProto proto; + + public InternalActionsActor(MTProto proto) { + this.proto = proto; + } + + @Override + public void onReceive(Object message) { + if (message instanceof RequestSalt) { + onRequestSaltsMessage(); + } else if (message instanceof RequestPingDelay) { + onPingDelayMessage(); + } + } + + public void onRequestSaltsMessage() { + // Logger.d(TAG, "Salt check timeout"); + if (TimeOverlord.getInstance().getTimeAccuracy() > 1000) { + // Logger.d(TAG, "Time is not accurate: " + TimeOverlord.getInstance().getTimeAccuracy()); + self().send(new RequestSalt(), FUTURE_NO_TIME_TIMEOUT); + return; + } + int count = proto.state.maximumCachedSalts((int) (TimeOverlord.getInstance().getServerTime() / 1000)); + if (count < FUTURE_MINIMAL) { + // Logger.d(TAG, "Too few actual salts: " + count + ", requesting news"); + proto.scheduller.postMessage(new MTGetFutureSalts(FUTURE_REQUEST_COUNT), false, FUTURE_TIMEOUT); + } + self().send(new RequestSalt(), FUTURE_TIMEOUT); + } + + public void onPingDelayMessage() { + if (lastPingMessage >= 0) { + proto.forgetMessage(lastPingMessage); + lastPingMessage = -1; + } + if (proto.mode == MODE_GENERAL) { + // Logger.d(TAG, "Ping delay disconnect for " + PING_INTERVAL + " sec"); + lastPingMessage = proto.scheduller.postMessage(new MTPingDelayDisconnect(Entropy.generateRandomId(), PING_INTERVAL), + false, PING_INTERVAL_REQUEST); + self().send(new RequestPingDelay(), PING_INTERVAL_REQUEST); + } else if (proto.mode == MODE_PUSH) { + lastPingMessage = proto.scheduller.postMessage(new MTPing(Entropy.generateRandomId()), false, PING_INTERVAL_REQUEST); + self().send(new RequestPingDelay(), PING_PUSH_REQUEST); + } + } + } + + private static class ResponseActor extends Actor { + private final MTProto proto; + + private ResponseActor(MTProto proto) { + this.proto = proto; + } + + @Override + public void onReceive(Object message) { + if (message instanceof MTMessage) { + MTMessage mtMessage = (MTMessage) message; + proto.onMTMessage(mtMessage); + BytesCache.getInstance().put(mtMessage.getContent()); + } + } + } + + @Override + public String toString() { + return "mtproto#" + INSTANCE_INDEX; + } +} \ No newline at end of file diff --git a/src/org/telegram/mtproto/MTProtoCallback.java b/src/org/telegram/mtproto/MTProtoCallback.java new file mode 100644 index 0000000..fa6e7c0 --- /dev/null +++ b/src/org/telegram/mtproto/MTProtoCallback.java @@ -0,0 +1,21 @@ +package org.telegram.mtproto; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 04.11.13 + * Time: 22:11 + */ +public interface MTProtoCallback { + public void onSessionCreated(MTProto proto); + + public void onAuthInvalidated(MTProto proto); + + public void onApiMessage(byte[] message, MTProto proto); + + public void onRpcResult(int callId, byte[] response, MTProto proto); + + public void onRpcError(int callId, int errorCode, String message, MTProto proto); + + public void onConfirmed(int callId); +} diff --git a/src/org/telegram/mtproto/ServerException.java b/src/org/telegram/mtproto/ServerException.java new file mode 100644 index 0000000..ab1a9b4 --- /dev/null +++ b/src/org/telegram/mtproto/ServerException.java @@ -0,0 +1,26 @@ +package org.telegram.mtproto; + +import java.io.IOException; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 6:47 + */ +public class ServerException extends IOException { + public ServerException() { + } + + public ServerException(String s) { + super(s); + } + + public ServerException(String s, Throwable throwable) { + super(s, throwable); + } + + public ServerException(Throwable throwable) { + super(throwable); + } +} diff --git a/src/org/telegram/mtproto/TransportSecurityException.java b/src/org/telegram/mtproto/TransportSecurityException.java new file mode 100644 index 0000000..87f5204 --- /dev/null +++ b/src/org/telegram/mtproto/TransportSecurityException.java @@ -0,0 +1,26 @@ +package org.telegram.mtproto; + +import java.io.IOException; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 6:44 + */ +public class TransportSecurityException extends IOException { + public TransportSecurityException() { + } + + public TransportSecurityException(String s) { + super(s); + } + + public TransportSecurityException(String s, Throwable throwable) { + super(s, throwable); + } + + public TransportSecurityException(Throwable throwable) { + super(throwable); + } +} diff --git a/src/org/telegram/mtproto/backoff/ExponentalBackoff.java b/src/org/telegram/mtproto/backoff/ExponentalBackoff.java new file mode 100644 index 0000000..31593ae --- /dev/null +++ b/src/org/telegram/mtproto/backoff/ExponentalBackoff.java @@ -0,0 +1,64 @@ +package org.telegram.mtproto.backoff; + +import org.telegram.mtproto.log.Logger; + +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by ex3ndr on 27.12.13. + */ +public class ExponentalBackoff { + + private static final int MIN_DELAY = 100; + private static final int MAX_DELAY = 15000; + private static final int MAX_FAILURE_COUNT = 50; + + private Random rnd = new Random(); + + private AtomicInteger currentFailureCount = new AtomicInteger(); + + private final String TAG; + + public ExponentalBackoff(String logTag) { + this.TAG = logTag; + } + + public void onFailure() throws InterruptedException { + int val = currentFailureCount.incrementAndGet(); + if (val > 50) { + currentFailureCount.compareAndSet(val, MAX_FAILURE_COUNT); + val = MAX_FAILURE_COUNT; + } + + int delay = MIN_DELAY + ((MAX_DELAY - MIN_DELAY) / MAX_FAILURE_COUNT) * val; + + synchronized (this) { + Logger.d(TAG, "onFailure: wait " + delay + " ms"); + wait(delay); + } + } + + public void onFailureNoWait() { + Logger.d(TAG, "onFailureNoWait"); + int val = currentFailureCount.incrementAndGet(); + if (val > 50) { + currentFailureCount.compareAndSet(val, MAX_FAILURE_COUNT); + val = MAX_FAILURE_COUNT; + } + } + + public void onSuccess() { + Logger.d(TAG, "onSuccess"); + reset(); + } + + public void reset() { + Logger.d(TAG, "reset"); + currentFailureCount.set(0); + + synchronized (this) { + notifyAll(); + } + } +} diff --git a/src/org/telegram/mtproto/log/LogInterface.java b/src/org/telegram/mtproto/log/LogInterface.java new file mode 100644 index 0000000..401a13b --- /dev/null +++ b/src/org/telegram/mtproto/log/LogInterface.java @@ -0,0 +1,15 @@ +package org.telegram.mtproto.log; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 10.11.13 + * Time: 2:11 + */ +public interface LogInterface { + void w(String tag, String message); + + void d(String tag, String message); + + void e(String tag, Throwable t); +} diff --git a/src/org/telegram/mtproto/log/Logger.java b/src/org/telegram/mtproto/log/Logger.java new file mode 100644 index 0000000..3ba2f6e --- /dev/null +++ b/src/org/telegram/mtproto/log/Logger.java @@ -0,0 +1,44 @@ +package org.telegram.mtproto.log; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 3:54 + */ +public class Logger { + + public static final boolean LOG_THREADS = true; + public static final boolean LOG_IGNORED = true; + public static final boolean LOG_PING = true; + + private static LogInterface logInterface; + + public static void registerInterface(LogInterface logInterface) { + Logger.logInterface = logInterface; + } + + public static void w(String tag, String message) { + if (logInterface != null) { + logInterface.w(tag, message); + } else { + System.out.println(tag + ":" + message); + } + } + + public static void d(String tag, String message) { + if (logInterface != null) { + logInterface.d(tag, message); + } else { + System.out.println(tag + ":" + message); + } + } + + public static void e(String tag, Throwable t) { + if (logInterface != null) { + logInterface.e(tag, t); + } else { + t.printStackTrace(); + } + } +} diff --git a/src/org/telegram/mtproto/pq/Authorizer.java b/src/org/telegram/mtproto/pq/Authorizer.java new file mode 100644 index 0000000..144ac11 --- /dev/null +++ b/src/org/telegram/mtproto/pq/Authorizer.java @@ -0,0 +1,225 @@ +package org.telegram.mtproto.pq; + +import org.telegram.mtproto.ServerException; +import org.telegram.mtproto.TransportSecurityException; +import org.telegram.mtproto.log.Logger; +import org.telegram.mtproto.secure.CryptoUtils; +import org.telegram.mtproto.secure.Entropy; +import org.telegram.mtproto.secure.Keys; +import org.telegram.mtproto.secure.pq.PQSolver; +import org.telegram.mtproto.state.ConnectionInfo; +import org.telegram.mtproto.time.TimeOverlord; +import org.telegram.mtproto.tl.pq.*; +import org.telegram.mtproto.transport.ConnectionType; +import org.telegram.mtproto.transport.PlainTcpConnection; +import org.telegram.mtproto.transport.TransportRate; +import org.telegram.tl.TLMethod; +import org.telegram.tl.TLObject; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; + +import static org.telegram.mtproto.secure.CryptoUtils.*; +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 4:11 + */ +public class Authorizer { + private static final String TAG = "Authorizer"; + private static final int AUTH_ATTEMPT_COUNT = 5; + private static final int AUTH_RETRY_COUNT = 5; + + private PlainTcpConnection context; + private TLInitContext initContext; + + public Authorizer() { + initContext = new TLInitContext(); + } + + private T executeMethod(TLMethod object) throws IOException { + long requestMessageId = TimeOverlord.getInstance().createWeakMessageId(); + long start = System.nanoTime(); + byte[] data = object.serialize(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + writeLong(0, out); // Empty AUTH_ID + writeLong(requestMessageId, out); // MessageID + writeInt(data.length, out); + writeByteArray(data, out); + byte[] response = context.executeMethod(out.toByteArray()); + ByteArrayInputStream in = new ByteArrayInputStream(response); + long authId = readLong(in); + if (authId != 0) { + throw new IOException("Auth id might be equal to zero"); + } + long messageId = readLong(in); + // TimeOverlord.getInstance().onMethodExecuted(requestMessageId, messageId, (System.nanoTime() - start) / 1000000); + int length = readInt(in); + byte[] messageResponse = readBytes(length, in); + return object.deserializeResponse(messageResponse, initContext); + } + + private PqAuth authAttempt() throws IOException { + // PQ-Auth start + byte[] nonce = Entropy.generateSeed(16); + ResPQ resPQ = executeMethod(new ReqPQ(nonce)); + byte[] serverNonce = resPQ.getServerNonce(); + + long fingerprint = 0; + Keys.Key publicKey = null; + outer: + for (Long srcFingerprint : resPQ.getFingerprints()) { + for (Keys.Key key : Keys.AVAILABLE_KEYS) { + if (srcFingerprint.equals(key.getFingerprint())) { + fingerprint = srcFingerprint; + publicKey = key; + break outer; + } + } + } + + if (fingerprint == 0) { + throw new IOException("Unknown public keys"); + } + + BigInteger pq = loadBigInt(resPQ.getPq()); + BigInteger p = null; + try { + long start = System.currentTimeMillis(); + p = PQSolver.solvePq(pq); + Logger.d(TAG, "Solved PQ in " + (System.currentTimeMillis() - start) + " ms"); + } catch (Exception e) { + throw new IOException(); + } + BigInteger q = pq.divide(p); + + byte[] newNonce = Entropy.generateSeed(32); + + PQInner inner = new PQInner(resPQ.getPq(), fromBigInt(p), fromBigInt(q), nonce, + serverNonce, newNonce); + + byte[] pqInner = inner.serialize(); + + // PQ INNER + byte[] hash = CryptoUtils.SHA1(pqInner); + byte[] seed = Entropy.generateSeed(255 - hash.length - pqInner.length); + byte[] dataWithHash = concat(hash, pqInner, seed); + + byte[] encrypted = CryptoUtils.RSA(dataWithHash, publicKey.getPublicKey(), publicKey.getExponent()); + + long start = System.nanoTime(); + ServerDhParams dhParams = executeMethod(new ReqDhParams(nonce, serverNonce, fromBigInt(p), fromBigInt(q), + fingerprint, encrypted)); + long dhParamsDuration = (System.nanoTime() - start) / (1000 * 1000); + + if (dhParams instanceof ServerDhFailure) { + ServerDhFailure hdFailure = (ServerDhFailure) dhParams; + if (arrayEq(hdFailure.getNewNonceHash(), SHA1(newNonce))) { + throw new ServerException("Received server_DH_params_fail#79cb045d"); + } else { + throw new TransportSecurityException("Received server_DH_params_fail#79cb045d with incorrect hash"); + } + } + + // PQ-Auth end + // DH-Auth start + ServerDhOk serverDhParams = (ServerDhOk) dhParams; + + byte[] encryptedAnswer = serverDhParams.getEncryptedAnswer(); + + byte[] tmpAesKey = concat(SHA1(newNonce, serverNonce), substring(SHA1(serverNonce, newNonce), 0, 12)); + byte[] tmpAesIv = concat(concat(substring(SHA1(serverNonce, newNonce), 12, 8), SHA1(newNonce, newNonce)), + substring(newNonce, 0, 4)); + + byte[] answer = AES256IGEDecrypt(encryptedAnswer, tmpAesIv, tmpAesKey); + ByteArrayInputStream stream = new ByteArrayInputStream(answer); + byte[] answerHash = readBytes(20, stream); // Hash + ServerDhInner dhInner = (ServerDhInner) initContext.deserializeMessage(stream); + if (!arrayEq(answerHash, SHA1(dhInner.serialize()))) { + throw new TransportSecurityException(); + } + + TimeOverlord.getInstance().onServerTimeArrived(dhInner.getServerTime() * 1000L, dhParamsDuration); + + for (int i = 0; i < AUTH_RETRY_COUNT; i++) { + BigInteger b = loadBigInt(Entropy.generateSeed(256)); + BigInteger g = new BigInteger(dhInner.getG() + ""); + BigInteger dhPrime = loadBigInt(dhInner.getDhPrime()); + BigInteger gb = g.modPow(b, dhPrime); + + BigInteger authKeyVal = loadBigInt(dhInner.getG_a()).modPow(b, dhPrime); + byte[] authKey = alignKeyZero(fromBigInt(authKeyVal), 256); + byte[] authAuxHash = substring(SHA1(authKey), 0, 8); + + ClientDhInner clientDHInner = new ClientDhInner(nonce, serverNonce, i, fromBigInt(gb)); + byte[] innerData = clientDHInner.serialize(); + byte[] innerDataWithHash = align(concat(SHA1(innerData), innerData), 16); + byte[] dataWithHashEnc = AES256IGEEncrypt(innerDataWithHash, tmpAesIv, tmpAesKey); + + DhGenResult result = executeMethod(new ReqSetDhClientParams(nonce, serverNonce, dataWithHashEnc)); + + if (result instanceof DhGenOk) { + byte[] newNonceHash = substring(SHA1(newNonce, new byte[]{1}, authAuxHash), 4, 16); + + if (!arrayEq(result.getNewNonceHash(), newNonceHash)) + throw new TransportSecurityException(); + + long serverSalt = readLong(xor(substring(newNonce, 0, 8), substring(serverNonce, 0, 8)), 0); + + return new PqAuth(authKey, serverSalt, context.getSocket()); + } else if (result instanceof DhGenRetry) { + byte[] newNonceHash = substring(SHA1(newNonce, new byte[]{2}, authAuxHash), 4, 16); + + if (!arrayEq(result.getNewNonceHash(), newNonceHash)) + throw new TransportSecurityException(); + + } else if (result instanceof DhGenFailure) { + byte[] newNonceHash = substring(SHA1(newNonce, new byte[]{3}, authAuxHash), 4, 16); + + if (!arrayEq(result.getNewNonceHash(), newNonceHash)) + throw new TransportSecurityException(); + + throw new ServerException(); + } + } + throw new ServerException(); + } + + public PqAuth doAuth(ConnectionInfo[] infos) { + TransportRate rate = new TransportRate(infos); + for (int i = 0; i < AUTH_ATTEMPT_COUNT; i++) { + ConnectionType connectionType = rate.tryConnection(); + try { + context = new PlainTcpConnection(connectionType.getHost(), connectionType.getPort()); + rate.onConnectionSuccess(connectionType.getId()); + } catch (IOException e) { + Logger.e(TAG, e); + rate.onConnectionFailure(connectionType.getId()); + continue; + } + + try { + return authAttempt(); + } catch (IOException e) { + Logger.e(TAG, e); + } finally { + if (context != null) { + context.destroy(); + context = null; + } + } + + try { + Thread.sleep(300); + } catch (InterruptedException e) { + return null; + } + } + return null; + } +} diff --git a/src/org/telegram/mtproto/pq/PqAuth.java b/src/org/telegram/mtproto/pq/PqAuth.java new file mode 100644 index 0000000..35428d4 --- /dev/null +++ b/src/org/telegram/mtproto/pq/PqAuth.java @@ -0,0 +1,33 @@ +package org.telegram.mtproto.pq; + +import java.net.Socket; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 8:14 + */ +public class PqAuth { + private byte[] authKey; + private long serverSalt; + private Socket socket; + + public PqAuth(byte[] authKey, long serverSalt, Socket socket) { + this.authKey = authKey; + this.serverSalt = serverSalt; + this.socket = socket; + } + + public byte[] getAuthKey() { + return authKey; + } + + public long getServerSalt() { + return serverSalt; + } + + public Socket getSocket() { + return socket; + } +} diff --git a/src/org/telegram/mtproto/schedule/PrepareSchedule.java b/src/org/telegram/mtproto/schedule/PrepareSchedule.java new file mode 100644 index 0000000..b4e4df9 --- /dev/null +++ b/src/org/telegram/mtproto/schedule/PrepareSchedule.java @@ -0,0 +1,34 @@ +package org.telegram.mtproto.schedule; + +/** + * Created by ex3ndr on 29.12.13. + */ +public class PrepareSchedule { + private long delay; + private int[] allowedContexts; + private boolean doWait; + + public boolean isDoWait() { + return doWait; + } + + public void setDoWait(boolean doWait) { + this.doWait = doWait; + } + + public long getDelay() { + return delay; + } + + public void setDelay(long delay) { + this.delay = delay; + } + + public int[] getAllowedContexts() { + return allowedContexts; + } + + public void setAllowedContexts(int[] allowedContexts) { + this.allowedContexts = allowedContexts; + } +} diff --git a/src/org/telegram/mtproto/schedule/PreparedPackage.java b/src/org/telegram/mtproto/schedule/PreparedPackage.java new file mode 100644 index 0000000..14e9efa --- /dev/null +++ b/src/org/telegram/mtproto/schedule/PreparedPackage.java @@ -0,0 +1,37 @@ +package org.telegram.mtproto.schedule; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 19:59 + */ +public class PreparedPackage { + private boolean isHighPriority; + private int seqNo; + private long messageId; + private byte[] content; + + public PreparedPackage(int seqNo, long messageId, byte[] content, boolean isHighPriority) { + this.seqNo = seqNo; + this.messageId = messageId; + this.content = content; + this.isHighPriority = isHighPriority; + } + + public boolean isHighPriority() { + return isHighPriority; + } + + public int getSeqNo() { + return seqNo; + } + + public long getMessageId() { + return messageId; + } + + public byte[] getContent() { + return content; + } +} diff --git a/src/org/telegram/mtproto/schedule/Scheduller.java b/src/org/telegram/mtproto/schedule/Scheduller.java new file mode 100644 index 0000000..ab5088c --- /dev/null +++ b/src/org/telegram/mtproto/schedule/Scheduller.java @@ -0,0 +1,627 @@ +package org.telegram.mtproto.schedule; + +import org.telegram.mtproto.CallWrapper; +import org.telegram.mtproto.MTProto; +import org.telegram.mtproto.log.Logger; +import org.telegram.mtproto.time.TimeOverlord; +import org.telegram.mtproto.tl.*; +import org.telegram.tl.TLMethod; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 8:51 + */ +public class Scheduller { + + private final String TAG;// = "MTProtoScheduller"; + + // Share identity values across all connections to avoid collisions + private static final AtomicInteger messagesIds = new AtomicInteger(1); + private static final ConcurrentHashMap idGenerationTime = new ConcurrentHashMap(); + + // private static final int SCHEDULLER_TIMEOUT = 15 * 1000;//15 sec + private static final int SCHEDULLER_TIMEOUT = 24 * 60 * 60 * 1000;//24 hours + + private static final long CONFIRM_TIMEOUT = 60 * 1000;//60 sec + + private static final int MAX_WORKLOAD_SIZE = 1024; + private static final int BIG_MESSAGE_SIZE = 1024; + private static final long RETRY_TIMEOUT = 15 * 1000; + + private static final int MAX_ACK_COUNT = 16; + + private SortedMap messages = Collections.synchronizedSortedMap(new TreeMap()); + private HashSet currentMessageGeneration = new HashSet(); + private HashSet confirmedMessages = new HashSet(); + private CopyOnWriteArrayList schedullerListeners = new CopyOnWriteArrayList(); + + private long firstConfirmTime = 0; + + private long lastMessageId = 0; + private long lastDependId = 0; + private int seqNo = 0; + + private CallWrapper wrapper; + + public Scheduller(MTProto mtProto, CallWrapper wrapper) { + TAG = "MTProto#" + mtProto.getInstanceIndex() + "#Scheduller"; + this.wrapper = wrapper; + } + + public void addListener(SchedullerListener listener) { + schedullerListeners.remove(listener); + schedullerListeners.add(listener); + } + + public void removeListener(SchedullerListener listener) { + schedullerListeners.remove(listener); + } + + private void notifyChanged() { + for (SchedullerListener listener : schedullerListeners) { + listener.onSchedullerUpdated(this); + } + } + + private synchronized long generateMessageId() { + long messageId = TimeOverlord.getInstance().createWeakMessageId(); + if (messageId <= lastMessageId) { + messageId = lastMessageId = lastMessageId + 4; + } + while (idGenerationTime.containsKey(messageId)) { + messageId += 4; + } + lastMessageId = messageId; + idGenerationTime.put(messageId, getCurrentTime()); + currentMessageGeneration.add(messageId); + return messageId; + } + + private synchronized int generateSeqNoWeak() { + return seqNo * 2; + } + + private synchronized int generateSeqNo() { + int res = seqNo * 2 + 1; + seqNo++; + return res; + } + + private synchronized void generateParams(SchedullerPackage schedullerPackage) { + schedullerPackage.messageId = generateMessageId(); + schedullerPackage.seqNo = generateSeqNo(); + schedullerPackage.idGenerationTime = getCurrentTime(); + schedullerPackage.relatedMessageIds.add(schedullerPackage.messageId); + schedullerPackage.generatedMessageIds.add(schedullerPackage.messageId); + } + + private long getCurrentTime() { + return System.nanoTime() / 1000000; + } + + public long getMessageIdGenerationTime(long msgId) { + if (idGenerationTime.containsKey(msgId)) { + return idGenerationTime.get(msgId); + } + return 0; + } + + public int postMessageDelayed(TLObject object, boolean isRpc, long timeout, int delay, int contextId, boolean highPrioroty) { + int id = messagesIds.incrementAndGet(); + SchedullerPackage schedullerPackage = new SchedullerPackage(id); + schedullerPackage.object = object; + schedullerPackage.addTime = getCurrentTime(); + schedullerPackage.scheduleTime = schedullerPackage.addTime + delay; + schedullerPackage.expiresTime = schedullerPackage.scheduleTime + timeout; + schedullerPackage.ttlTime = schedullerPackage.scheduleTime + timeout * 2; + schedullerPackage.isRpc = isRpc; + schedullerPackage.queuedToChannel = contextId; + schedullerPackage.priority = highPrioroty ? PRIORITY_HIGH : PRIORITY_NORMAL; + schedullerPackage.isDepend = highPrioroty; + schedullerPackage.supportTag = object.toString(); + schedullerPackage.serverErrorCount = 0; + messages.put(id, schedullerPackage); + notifyChanged(); + return id; + } + + public int postMessage(TLObject object, boolean isApi, long timeout) { + return postMessageDelayed(object, isApi, timeout, 0, -1, false); + } + + public int postMessage(TLObject object, boolean isApi, long timeout, boolean highPrioroty) { + return postMessageDelayed(object, isApi, timeout, 0, -1, highPrioroty); + } + + public synchronized void prepareScheduller(PrepareSchedule prepareSchedule, int[] connectionIds) { + long time = getCurrentTime(); + + // Clear packages for unknown channels + outer: + for (SchedullerPackage schedullerPackage : messages.values().toArray(new SchedullerPackage[0])) { + if (schedullerPackage.queuedToChannel != -1) { + for (int id : connectionIds) { + if (schedullerPackage.queuedToChannel == id) { + continue outer; + } + } + forgetMessage(schedullerPackage.id); + } + } + + // If there are no connections provide default delay + if (connectionIds.length == 0) { + prepareSchedule.setDelay(SCHEDULLER_TIMEOUT); + prepareSchedule.setAllowedContexts(connectionIds); + prepareSchedule.setDoWait(true); + return; + } + + long minDelay = SCHEDULLER_TIMEOUT; + boolean allConnections = false; + boolean doWait = true; + HashSet supportedConnections = new HashSet(); + for (SchedullerPackage schedullerPackage : messages.values().toArray(new SchedullerPackage[0])) { + boolean isPendingPackage = false; + long packageTime = 0; + + if (schedullerPackage.state == STATE_QUEUED) { + isPendingPackage = true; + if (schedullerPackage.scheduleTime <= time) { + packageTime = 0; + } else { + packageTime = Math.max(schedullerPackage.scheduleTime - time, 0); + } + } else if (schedullerPackage.state == STATE_SENT) { + if (getCurrentTime() <= schedullerPackage.expiresTime) { + if (time - schedullerPackage.lastAttemptTime >= RETRY_TIMEOUT) { + isPendingPackage = true; + packageTime = 0; + } + } + } + + if (isPendingPackage) { + if (schedullerPackage.queuedToChannel == -1) { + allConnections = true; + } else { + supportedConnections.add(schedullerPackage.queuedToChannel); + } + + if (packageTime == 0) { + minDelay = 0; + doWait = false; + } else { + minDelay = Math.min(packageTime, minDelay); + } + } + } + prepareSchedule.setDoWait(doWait); + prepareSchedule.setDelay(minDelay); + + if (allConnections) { + prepareSchedule.setAllowedContexts(connectionIds); + } else { + Integer[] allowedBoxed = supportedConnections.toArray(new Integer[0]); + int[] allowed = new int[allowedBoxed.length]; + for (int i = 0; i < allowed.length; i++) { + allowed[i] = allowedBoxed[i]; + } + prepareSchedule.setAllowedContexts(allowed); + } + } + + public synchronized void registerFastConfirm(long msgId, int fastConfirm) { + for (SchedullerPackage schedullerPackage : messages.values().toArray(new SchedullerPackage[0])) { + boolean contains = false; + for (Long relatedMsgId : schedullerPackage.relatedMessageIds) { + if (relatedMsgId == msgId) { + contains = true; + break; + } + } + if (contains) { + schedullerPackage.relatedFastConfirm.add(fastConfirm); + } + } + } + + public int mapSchedullerId(long msgId) { + for (SchedullerPackage schedullerPackage : messages.values().toArray(new SchedullerPackage[0])) { + if (schedullerPackage.generatedMessageIds.contains(msgId)) { + return schedullerPackage.id; + } + } + return 0; + } + + public void resetMessageId() { + lastMessageId = 0; + lastDependId = 0; + } + + public void resetSession() { + lastMessageId = 0; + lastDependId = 0; + seqNo = 0; + currentMessageGeneration.clear(); + for (SchedullerPackage schedullerPackage : messages.values().toArray(new SchedullerPackage[0])) { + schedullerPackage.idGenerationTime = 0; + schedullerPackage.dependMessageId = 0; + schedullerPackage.messageId = 0; + schedullerPackage.seqNo = 0; + } + notifyChanged(); + } + + public boolean isMessageFromCurrentGeneration(long msgId) { + return currentMessageGeneration.contains(msgId); + } + + public void resendAsNewMessage(long msgId) { + resendAsNewMessageDelayed(msgId, 0); + } + + public void resendAsNewMessageDelayed(long msgId, int delay) { + for (SchedullerPackage schedullerPackage : messages.values().toArray(new SchedullerPackage[0])) { + if (schedullerPackage.relatedMessageIds.contains(msgId)) { + schedullerPackage.idGenerationTime = 0; + schedullerPackage.dependMessageId = 0; + schedullerPackage.messageId = 0; + schedullerPackage.seqNo = 0; + schedullerPackage.state = STATE_QUEUED; + schedullerPackage.scheduleTime = getCurrentTime() + delay; + Logger.d(TAG, "Resending as new #" + schedullerPackage.id); + } + } + notifyChanged(); + } + + public void resendMessage(long msgId) { + for (SchedullerPackage schedullerPackage : messages.values().toArray(new SchedullerPackage[0])) { + if (schedullerPackage.relatedMessageIds.contains(msgId)) { + // schedullerPackage.relatedMessageIds.clear(); + schedullerPackage.state = STATE_QUEUED; + schedullerPackage.lastAttemptTime = 0; + } + } + notifyChanged(); + } + + public int[] mapFastConfirm(int fastConfirm) { + ArrayList res = new ArrayList(); + for (SchedullerPackage schedullerPackage : messages.values().toArray(new SchedullerPackage[0])) { + if (schedullerPackage.state == STATE_SENT) { + if (schedullerPackage.relatedFastConfirm.contains(fastConfirm)) { + res.add(schedullerPackage.id); + } + } + } + int[] res2 = new int[res.size()]; + for (int i = 0; i < res2.length; i++) { + res2[i] = res.get(i); + } + return res2; + } + + public void onMessageConfirmed(long msgId) { + for (SchedullerPackage schedullerPackage : messages.values().toArray(new SchedullerPackage[0])) { + if (schedullerPackage.state == STATE_SENT) { + if (schedullerPackage.relatedMessageIds.contains(msgId)) { + schedullerPackage.state = STATE_CONFIRMED; + } + } + } + } + + public void confirmMessage(long msgId) { + synchronized (confirmedMessages) { + confirmedMessages.add(msgId); + if (firstConfirmTime == 0) { + firstConfirmTime = getCurrentTime(); + } + } + } + + public synchronized void forgetMessageByMsgId(long msgId) { + int scId = mapSchedullerId(msgId); + if (scId > 0) { + forgetMessage(scId); + } + } + + public synchronized void forgetMessage(int id) { + Logger.d(TAG, "Forgetting message: #" + id); + messages.remove(id); + } + + private synchronized ArrayList actualPackages(int contextId) { + ArrayList foundedPackages = new ArrayList(); + long time = getCurrentTime(); + for (SchedullerPackage schedullerPackage : messages.values().toArray(new SchedullerPackage[0])) { + if (schedullerPackage.queuedToChannel != -1 && contextId != schedullerPackage.queuedToChannel) { + continue; + } + boolean isPendingPackage = false; + + if (schedullerPackage.ttlTime <= getCurrentTime()) { + forgetMessage(schedullerPackage.id); + continue; + } + + if (schedullerPackage.state == STATE_QUEUED) { + if (schedullerPackage.scheduleTime <= time) { + isPendingPackage = true; + } + } else if (schedullerPackage.state == STATE_SENT) { + if (getCurrentTime() <= schedullerPackage.expiresTime) { + if (getCurrentTime() - schedullerPackage.lastAttemptTime >= RETRY_TIMEOUT) { + isPendingPackage = true; + } + } + } + + if (isPendingPackage) { + if (schedullerPackage.serialized == null) { + try { + if (schedullerPackage.isRpc) { + schedullerPackage.serialized = wrapper.wrapObject((TLMethod) schedullerPackage.object).serialize(); + } else { + schedullerPackage.serialized = schedullerPackage.object.serialize(); + } + } catch (IOException e) { + Logger.e(TAG, e); + forgetMessage(schedullerPackage.id); + continue; + } + } + + foundedPackages.add(schedullerPackage); + } + } + return foundedPackages; + } + + public synchronized boolean hasRequests() { + for (SchedullerPackage schedullerPackage : messages.values().toArray(new SchedullerPackage[0])) { + if (schedullerPackage.isRpc) + return true; + } + + return false; + } + + + public synchronized PreparedPackage doSchedule(int contextId, boolean isInited) { + ArrayList foundedPackages = actualPackages(contextId); + + synchronized (confirmedMessages) { + if (foundedPackages.size() == 0 && + (confirmedMessages.size() <= MAX_ACK_COUNT || (System.nanoTime() - firstConfirmTime) < CONFIRM_TIMEOUT)) { + return null; + } + } + + boolean useHighPriority = false; + + for (SchedullerPackage p : foundedPackages) { + if (p.priority == PRIORITY_HIGH) { + useHighPriority = true; + break; + } + } + + ArrayList packages = new ArrayList(); + + if (useHighPriority) { + Logger.d("Scheduller", "Using high priority scheduling"); + int totalSize = 0; + for (SchedullerPackage p : foundedPackages) { + if (p.priority == PRIORITY_HIGH) { + packages.add(p); + totalSize += p.serialized.length; + if (totalSize > MAX_WORKLOAD_SIZE) { + break; + } + } + } + } else { + int totalSize = 0; + for (SchedullerPackage p : foundedPackages) { + packages.add(p); + Logger.d("Scheduller", "Prepare package: " + p.supportTag + " of size " + p.serialized.length); + totalSize += p.serialized.length; + Logger.d("Scheduller", "Total size: " + totalSize); + if (totalSize > MAX_WORKLOAD_SIZE) { + break; + } + } + } + + Logger.d(TAG, "Iteration: count: " + packages.size() + ", confirm:" + confirmedMessages.size()); + Logger.d(TAG, "Building package"); + if (foundedPackages.size() == 0 && confirmedMessages.size() != 0) { + Long[] msgIds; + synchronized (confirmedMessages) { + msgIds = confirmedMessages.toArray(new Long[confirmedMessages.size()]); + confirmedMessages.clear(); + } + MTMsgsAck ack = new MTMsgsAck(msgIds); + Logger.d(TAG, "Single msg_ack"); + try { + return new PreparedPackage(generateSeqNoWeak(), generateMessageId(), ack.serialize(), useHighPriority); + } catch (IOException e) { + Logger.e(TAG, e); + return null; + } + } else if (foundedPackages.size() == 1 && confirmedMessages.size() == 0) { + SchedullerPackage schedullerPackage = foundedPackages.get(0); + schedullerPackage.state = STATE_SENT; + if (schedullerPackage.idGenerationTime == 0) { + generateParams(schedullerPackage); + } + Logger.d(TAG, "Single package: #" + schedullerPackage.id + " " + schedullerPackage.supportTag + " (" + schedullerPackage.messageId + ", " + schedullerPackage.seqNo + ")"); + schedullerPackage.writtenToChannel = contextId; + schedullerPackage.lastAttemptTime = getCurrentTime(); + return new PreparedPackage(schedullerPackage.seqNo, schedullerPackage.messageId, schedullerPackage.serialized, useHighPriority); + } else { + MTMessagesContainer container = new MTMessagesContainer(); + if ((confirmedMessages.size() > 0 && !useHighPriority) || (!isInited)) { + try { + Long[] msgIds; + synchronized (confirmedMessages) { + msgIds = confirmedMessages.toArray(new Long[0]); + confirmedMessages.clear(); + } + MTMsgsAck ack = new MTMsgsAck(msgIds); + Logger.d(TAG, "Adding msg_ack: " + msgIds.length); + container.getMessages().add(new MTMessage(generateMessageId(), generateSeqNoWeak(), ack.serialize())); + } catch (IOException e) { + Logger.e(TAG, e); + } + } + for (SchedullerPackage schedullerPackage : packages) { + schedullerPackage.state = STATE_SENT; + if (schedullerPackage.idGenerationTime == 0) { + generateParams(schedullerPackage); + } + + if (schedullerPackage.isDepend) { + if (schedullerPackage.dependMessageId == 0) { + if (lastDependId > 0) { + schedullerPackage.dependMessageId = lastDependId; + } else { + schedullerPackage.dependMessageId = -1; + } + } + + lastDependId = schedullerPackage.messageId; + } + schedullerPackage.writtenToChannel = contextId; + schedullerPackage.lastAttemptTime = getCurrentTime(); + if (schedullerPackage.isDepend && schedullerPackage.dependMessageId > 0) { + + Logger.d(TAG, "Adding package: #" + schedullerPackage.id + " " + schedullerPackage.supportTag + " (" + schedullerPackage.messageId + " on " + schedullerPackage.dependMessageId + ", " + schedullerPackage.seqNo + ")"); + + MTInvokeAfter invokeAfter = new MTInvokeAfter(schedullerPackage.dependMessageId, schedullerPackage.serialized); + try { + container.getMessages().add(new MTMessage(schedullerPackage.messageId, schedullerPackage.seqNo, invokeAfter.serialize())); + } catch (IOException e) { + Logger.e(TAG, e); + // Never happens + } + } else { + Logger.d(TAG, "Adding package: #" + schedullerPackage.id + " " + schedullerPackage.supportTag + " (" + schedullerPackage.messageId + ", " + schedullerPackage.seqNo + ")"); + container.getMessages().add(new MTMessage(schedullerPackage.messageId, schedullerPackage.seqNo, schedullerPackage.serialized)); + } + } + + long containerMessageId = generateMessageId(); + int containerSeq = generateSeqNoWeak(); + + for (SchedullerPackage schedullerPackage : packages) { + schedullerPackage.relatedMessageIds.add(containerMessageId); + } + + Logger.d(TAG, "Sending Package (" + containerMessageId + ", " + containerSeq + ")"); + + try { + return new PreparedPackage(containerSeq, containerMessageId, container.serialize(), useHighPriority); + } catch (IOException e) { + // Might not happens + Logger.e(TAG, e); + return null; + } + } + } + + public synchronized void onConnectionDies(int connectionId) { + Logger.d(TAG, "Connection dies " + connectionId); + for (SchedullerPackage schedullerPackage : messages.values().toArray(new SchedullerPackage[0])) { + if (schedullerPackage.writtenToChannel != connectionId) { + continue; + } + + if (schedullerPackage.queuedToChannel != -1) { + Logger.d(TAG, "Removing: #" + schedullerPackage.id + " " + schedullerPackage.supportTag); + forgetMessage(schedullerPackage.id); + } else { + if (schedullerPackage.isRpc) { + if (schedullerPackage.state == STATE_CONFIRMED || schedullerPackage.state == STATE_QUEUED) { + Logger.d(TAG, "Re-schedule: #" + schedullerPackage.id + " " + schedullerPackage.supportTag); + schedullerPackage.state = STATE_QUEUED; + schedullerPackage.lastAttemptTime = 0; + } + } else { + if (schedullerPackage.state == STATE_SENT) { + Logger.d(TAG, "Re-schedule: #" + schedullerPackage.id + " " + schedullerPackage.supportTag); + schedullerPackage.state = STATE_QUEUED; + schedullerPackage.lastAttemptTime = 0; + } + } + + } + } + notifyChanged(); + } + + private static final int PRIORITY_HIGH = 1; + private static final int PRIORITY_NORMAL = 0; + + private static final int STATE_QUEUED = 0; + private static final int STATE_SENT = 1; + private static final int STATE_CONFIRMED = 2; + + private class SchedullerPackage { + + public SchedullerPackage(int id) { + this.id = id; + } + + public String supportTag; + + public int id; + + public TLObject object; + public byte[] serialized; + + public long addTime; + public long scheduleTime; + public long expiresTime; + public long ttlTime; + public long lastAttemptTime; + + public int writtenToChannel = -1; + + public int queuedToChannel = -1; + + public int state = STATE_QUEUED; + + public int priority = PRIORITY_NORMAL; + + public boolean isDepend; + + public boolean isSent; + + public long idGenerationTime; + public long dependMessageId; + public long messageId; + public int seqNo; + public HashSet relatedFastConfirm = new HashSet(); + public HashSet relatedMessageIds = new HashSet(); + public HashSet generatedMessageIds = new HashSet(); + + public int serverErrorCount; + + public boolean isRpc; + } +} diff --git a/src/org/telegram/mtproto/schedule/SchedullerListener.java b/src/org/telegram/mtproto/schedule/SchedullerListener.java new file mode 100644 index 0000000..7cbc609 --- /dev/null +++ b/src/org/telegram/mtproto/schedule/SchedullerListener.java @@ -0,0 +1,8 @@ +package org.telegram.mtproto.schedule; + +/** + * Created by ex3ndr on 03.04.14. + */ +public interface SchedullerListener { + public void onSchedullerUpdated(Scheduller scheduller); +} diff --git a/src/org/telegram/mtproto/secure/CryptoUtils.java b/src/org/telegram/mtproto/secure/CryptoUtils.java new file mode 100644 index 0000000..83bcf38 --- /dev/null +++ b/src/org/telegram/mtproto/secure/CryptoUtils.java @@ -0,0 +1,277 @@ +package org.telegram.mtproto.secure; + +import org.telegram.mtproto.secure.aes.AESImplementation; +import org.telegram.mtproto.secure.aes.DefaultAESImplementation; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.io.*; +import java.math.BigInteger; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; + +/** + * Author: Korshakov Stepan + * Created: 18.07.13 3:54 + */ +public class CryptoUtils { + + private static final ThreadLocal md5 = new ThreadLocal() { + @Override + protected MessageDigest initialValue() { + MessageDigest crypt = null; + try { + crypt = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return crypt; + } + }; + + private static final ThreadLocal sha1 = new ThreadLocal() { + @Override + protected MessageDigest initialValue() { + MessageDigest crypt = null; + try { + crypt = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return crypt; + } + }; + + private static AESImplementation currentImplementation = new DefaultAESImplementation(); + + public static void setAESImplementation(AESImplementation implementation) { + currentImplementation = implementation; + } + + public static byte[] RSA(byte[] src, BigInteger key, BigInteger exponent) { + try { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(new RSAPublicKeySpec(key, exponent)); + Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + return cipher.doFinal(src); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (NoSuchPaddingException e) { + e.printStackTrace(); + } catch (BadPaddingException e) { + e.printStackTrace(); + } catch (IllegalBlockSizeException e) { + e.printStackTrace(); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + } + return null; + } + + public static void AES256IGEDecryptBig(byte[] src, byte[] dest, int len, byte[] iv, byte[] key) { + currentImplementation.AES256IGEDecrypt(src, dest, len, iv, key); + } + + public static byte[] AES256IGEDecrypt(byte[] src, byte[] iv, byte[] key) { + byte[] res = new byte[src.length]; + currentImplementation.AES256IGEDecrypt(src, res, src.length, iv, key); + return res; + } + + public static void AES256IGEDecrypt(File src, File dest, byte[] iv, byte[] key) throws IOException { + currentImplementation.AES256IGEDecrypt(src.getAbsolutePath(), dest.getAbsolutePath(), iv, key); + } + + public static void AES256IGEEncrypt(File src, File dest, byte[] iv, byte[] key) throws IOException { + currentImplementation.AES256IGEEncrypt(src.getAbsolutePath(), dest.getAbsolutePath(), iv, key); + } + + public static byte[] AES256IGEEncrypt(byte[] src, byte[] iv, byte[] key) { + byte[] res = new byte[src.length]; + currentImplementation.AES256IGEEncrypt(src, res, src.length, iv, key); + return res; + } + + public static String MD5(byte[] src) { + try { + MessageDigest crypt = MessageDigest.getInstance("MD5"); + crypt.reset(); + crypt.update(src); + return ToHex(crypt.digest()); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + return null; + } + + public static String MD5(RandomAccessFile randomAccessFile) { + try { + MessageDigest crypt = md5.get(); + crypt.reset(); + byte[] block = new byte[8 * 1024]; + for (int i = 0; i < randomAccessFile.length(); i += 8 * 1024) { + int len = (int) Math.min(block.length, randomAccessFile.length() - i); + randomAccessFile.readFully(block, 0, len); + crypt.update(block, 0, len); + } + return ToHex(crypt.digest()); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + + public static byte[] MD5Raw(byte[] src) { + MessageDigest crypt = md5.get(); + crypt.reset(); + crypt.update(src); + return crypt.digest(); + } + + public static String ToHex(byte[] src) { + String res = ""; + for (int i = 0; i < src.length; i++) { + res += String.format("%02X", src[i] & 0xFF); + } + return res.toLowerCase(); + } + + public static byte[] SHA1(InputStream in) throws IOException { + MessageDigest crypt = sha1.get(); + crypt.reset(); + // Transfer bytes from in to out + byte[] buf = new byte[4 * 1024]; + int len; + while ((len = in.read(buf)) > 0) { + Thread.yield(); + // out.write(buf, 0, len); + crypt.update(buf, 0, len); + } + in.close(); + return crypt.digest(); + } + + public static byte[] SHA1(String fileName) throws IOException { + MessageDigest crypt = sha1.get(); + crypt.reset(); + FileInputStream in = new FileInputStream(fileName); + // Transfer bytes from in to out + byte[] buf = new byte[4 * 1024]; + int len; + while ((len = in.read(buf)) > 0) { + Thread.yield(); + // out.write(buf, 0, len); + crypt.update(buf, 0, len); + } + in.close(); + return crypt.digest(); + } + + public static byte[] SHA1(byte[] src) { + MessageDigest crypt = sha1.get(); + crypt.reset(); + crypt.update(src); + return crypt.digest(); + } + + public static byte[] SHA1(byte[]... src1) { + MessageDigest crypt = sha1.get(); + crypt.reset(); + for (int i = 0; i < src1.length; i++) { + crypt.update(src1[i]); + } + return crypt.digest(); + } + + public static boolean arrayEq(byte[] a, byte[] b) { + if (a.length != b.length) { + return false; + } + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) + return false; + } + return true; + } + + public static byte[] concat(byte[]... v) { + int len = 0; + for (int i = 0; i < v.length; i++) { + len += v[i].length; + } + byte[] res = new byte[len]; + int offset = 0; + for (int i = 0; i < v.length; i++) { + System.arraycopy(v[i], 0, res, offset, v[i].length); + offset += v[i].length; + } + return res; + } + + public static byte[] substring(byte[] src, int start, int len) { + byte[] res = new byte[len]; + System.arraycopy(src, start, res, 0, len); + return res; + } + + public static byte[] align(byte[] src, int factor) { + if (src.length % factor == 0) { + return src; + } + int padding = factor - src.length % factor; + + return concat(src, Entropy.generateSeed(padding)); + } + + public static byte[] alignKeyZero(byte[] src, int size) { + if (src.length == size) { + return src; + } + + if (src.length > size) { + return substring(src, src.length - size, size); + } else { + return concat(new byte[size - src.length], src); + } + } + + public static byte[] xor(byte[] a, byte[] b) { + byte[] res = new byte[a.length]; + for (int i = 0; i < a.length; i++) { + res[i] = (byte) (a[i] ^ b[i]); + } + return res; + } + + public static BigInteger loadBigInt(byte[] data) { + return new BigInteger(1, data); + } + + public static byte[] fromBigInt(BigInteger val) { + byte[] res = val.toByteArray(); + if (res[0] == 0) { + byte[] res2 = new byte[res.length - 1]; + System.arraycopy(res, 1, res2, 0, res2.length); + return res2; + } else { + return res; + } + } + + public static boolean isZero(byte[] src) { + for (int i = 0; i < src.length; i++) { + if (src[i] != 0) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/src/org/telegram/mtproto/secure/Entropy.java b/src/org/telegram/mtproto/secure/Entropy.java new file mode 100644 index 0000000..4f86929 --- /dev/null +++ b/src/org/telegram/mtproto/secure/Entropy.java @@ -0,0 +1,45 @@ +package org.telegram.mtproto.secure; + +import java.security.SecureRandom; + +import static org.telegram.mtproto.secure.CryptoUtils.xor; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 4:05 + */ +public final class Entropy { + private static SecureRandom random = new SecureRandom(); + + public static byte[] generateSeed(int size) { + synchronized (random) { + return random.generateSeed(size); + } + } + + public static byte[] generateSeed(byte[] sourceSeed) { + synchronized (random) { + return xor(random.generateSeed(sourceSeed.length), sourceSeed); + } + } + + public static long generateRandomId() { + synchronized (random) { + return random.nextLong(); + } + } + + public static int randomInt() { + synchronized (random) { + return random.nextInt(); + } + } + + public static void feedEntropy(byte[] data) { + synchronized (random) { + random.setSeed(data); + } + } +} diff --git a/src/org/telegram/mtproto/secure/KeyParameter.java b/src/org/telegram/mtproto/secure/KeyParameter.java new file mode 100644 index 0000000..f548479 --- /dev/null +++ b/src/org/telegram/mtproto/secure/KeyParameter.java @@ -0,0 +1,19 @@ +package org.telegram.mtproto.secure; + +public class KeyParameter { + private byte[] key; + + public KeyParameter(byte[] key) { + this(key, 0, key.length); + } + + public KeyParameter(byte[] key, int keyOff, int keyLen) { + this.key = new byte[keyLen]; + + System.arraycopy(key, keyOff, this.key, 0, keyLen); + } + + public byte[] getKey() { + return key; + } +} diff --git a/src/org/telegram/mtproto/secure/Keys.java b/src/org/telegram/mtproto/secure/Keys.java new file mode 100644 index 0000000..8fb4520 --- /dev/null +++ b/src/org/telegram/mtproto/secure/Keys.java @@ -0,0 +1,46 @@ +package org.telegram.mtproto.secure; + +import java.math.BigInteger; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 6:04 + */ +public class Keys { + public static class Key { + private BigInteger publicKey; + private BigInteger exponent; + private long fingerprint; + + public Key(String publicKey, String exponent, long fingerprint) { + this.publicKey = new BigInteger(publicKey, 16); + this.exponent = new BigInteger(exponent, 16); + this.fingerprint = fingerprint; + } + + public BigInteger getPublicKey() { + return publicKey; + } + + public BigInteger getExponent() { + return exponent; + } + + public long getFingerprint() { + return fingerprint; + } + } + + public static final Key[] AVAILABLE_KEYS = new Key[]{ + new Key("0c6aeda78b02a251db4b6441031f467fa871faed32526c436524b1fb3b5dca28efb8c089dd1b46d92c895993d87108254951c5f001a0f055f3063dcd14d431a300eb9e29517e359a1c9537e5e87ab1b116faecf5d17546ebc21db234d9d336a693efcb2b6fbcca1e7d1a0be414dca408a11609b9c4269a920b09fed1f9a1597be02761430f09e4bc48fcafbe289054c99dba51b6b5eb7d9c3a2ab4e490545b4676bd620e93804bcac93bf94f73f92c729ca899477ff17625ef14a934d51dc11d5f8650a3364586b3a52fcff2fedec8a8406cac4e751705a472e55707e3c8cd5594342b119c6c3293532d85dbe9271ed54a2fd18b4dc79c04a30951107d5639397", + "010001", 0x9a996a1db11c729bL), + new Key("0b1066749655935f0a5936f517034c943bea7f3365a8931ae52c8bcb14856f004b83d26cf2839be0f22607470d67481771c1ce5ec31de16b20bbaa4ecd2f7d2ecf6b6356f27501c226984263edc046b89fb6d3981546b01d7bd34fedcfcc1058e2d494bda732ff813e50e1c6ae249890b225f82b22b1e55fcb063dc3c0e18e91c28d0c4aa627dec8353eee6038a95a4fd1ca984eb09f94aeb7a2220635a8ceb450ea7e61d915cdb4eecedaa083aa3801daf071855ec1fb38516cb6c2996d2d60c0ecbcfa57e4cf1fb0ed39b2f37e94ab4202ecf595e167b3ca62669a6da520859fb6d6c6203dfdfc79c75ec3ee97da8774b2da903e3435f2cd294670a75a526c1", + "010001", 0xb05b2a6f70cdea78L), + new Key("0c150023e2f70db7985ded064759cfecf0af328e69a41daf4d6f01b538135a6f91f8f8b2a0ec9ba9720ce352efcf6c5680ffc424bd634864902de0b4bd6d49f4e580230e3ae97d95c8b19442b3c0a10d8f5633fecedd6926a7f6dab0ddb7d457f9ea81b8465fcd6fffeed114011df91c059caedaf97625f6c96ecc74725556934ef781d866b34f011fce4d835a090196e9a5f0e4449af7eb697ddb9076494ca5f81104a305b6dd27665722c46b60e5df680fb16b210607ef217652e60236c255f6a28315f4083a96791d7214bf64c1df4fd0db1944fb26a2a57031b32eee64ad15a8ba68885cde74a5bfc920f6abf59ba5c75506373e7130f9042da922179251f", + "010001", 0xc3b42b026ce86b21L), + new Key("0c2a8c55b4a62e2b78a19b91cf692bcdc4ba7c23fe4d06f194e2a0c30f6d9996f7d1a2bcc89bc1ac4333d44359a6c433252d1a8402d9970378b5912b75bc8cc3fa76710a025bcb9032df0b87d7607cc53b928712a174ea2a80a8176623588119d42ffce40205c6d72160860d8d80b22a8b8651907cf388effbef29cd7cf2b4eb8a872052da1351cfe7fec214ce48304ea472bd66329d60115b3420d08f6894b0410b6ab9450249967617670c932f7cbdb5d6fbcce1e492c595f483109999b2661fcdeec31b196429b7834c7211a93c6789d9ee601c18c39e521fda9d7264e61e518add6f0712d2d5228204b851e13c4f322e5c5431c3b7f31089668486aadc59f", + "010001", 0x71e025b6c76033e3L) + }; +} diff --git a/src/org/telegram/mtproto/secure/aes/AESFastEngine.java b/src/org/telegram/mtproto/secure/aes/AESFastEngine.java new file mode 100644 index 0000000..17a61fa --- /dev/null +++ b/src/org/telegram/mtproto/secure/aes/AESFastEngine.java @@ -0,0 +1,833 @@ +package org.telegram.mtproto.secure.aes; + +import org.telegram.mtproto.secure.KeyParameter; + +/** + * an implementation of the AES (Rijndael), from FIPS-197. + *

+ * For further details see: http://csrc.nist.gov/encryption/aes/. + *

+ * This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at + * http://fp.gladman.plus.com/cryptography_technology/rijndael/ + *

+ * There are three levels of tradeoff of speed vs memory + * Because java has no preprocessor, they are written as three separate classes from which to choose + *

+ * The fastest uses 8Kbytes of static tables to precompute round calculations, 4 256 word tables for encryption + * and 4 for decryption. + *

+ * The middle performance version uses only one 256 word table for each, for a total of 2Kbytes, + * adding 12 rotate operations per round to compute the values contained in the other tables from + * the contents of the first + *

+ * The slowest version uses no static tables at all and computes the values in each round + *

+ * This file contains the fast version with 8Kbytes of static tables for round precomputation + */ +public class AESFastEngine { + // The S box + private static final byte[] S = { + (byte) 99, (byte) 124, (byte) 119, (byte) 123, (byte) 242, (byte) 107, (byte) 111, (byte) 197, + (byte) 48, (byte) 1, (byte) 103, (byte) 43, (byte) 254, (byte) 215, (byte) 171, (byte) 118, + (byte) 202, (byte) 130, (byte) 201, (byte) 125, (byte) 250, (byte) 89, (byte) 71, (byte) 240, + (byte) 173, (byte) 212, (byte) 162, (byte) 175, (byte) 156, (byte) 164, (byte) 114, (byte) 192, + (byte) 183, (byte) 253, (byte) 147, (byte) 38, (byte) 54, (byte) 63, (byte) 247, (byte) 204, + (byte) 52, (byte) 165, (byte) 229, (byte) 241, (byte) 113, (byte) 216, (byte) 49, (byte) 21, + (byte) 4, (byte) 199, (byte) 35, (byte) 195, (byte) 24, (byte) 150, (byte) 5, (byte) 154, + (byte) 7, (byte) 18, (byte) 128, (byte) 226, (byte) 235, (byte) 39, (byte) 178, (byte) 117, + (byte) 9, (byte) 131, (byte) 44, (byte) 26, (byte) 27, (byte) 110, (byte) 90, (byte) 160, + (byte) 82, (byte) 59, (byte) 214, (byte) 179, (byte) 41, (byte) 227, (byte) 47, (byte) 132, + (byte) 83, (byte) 209, (byte) 0, (byte) 237, (byte) 32, (byte) 252, (byte) 177, (byte) 91, + (byte) 106, (byte) 203, (byte) 190, (byte) 57, (byte) 74, (byte) 76, (byte) 88, (byte) 207, + (byte) 208, (byte) 239, (byte) 170, (byte) 251, (byte) 67, (byte) 77, (byte) 51, (byte) 133, + (byte) 69, (byte) 249, (byte) 2, (byte) 127, (byte) 80, (byte) 60, (byte) 159, (byte) 168, + (byte) 81, (byte) 163, (byte) 64, (byte) 143, (byte) 146, (byte) 157, (byte) 56, (byte) 245, + (byte) 188, (byte) 182, (byte) 218, (byte) 33, (byte) 16, (byte) 255, (byte) 243, (byte) 210, + (byte) 205, (byte) 12, (byte) 19, (byte) 236, (byte) 95, (byte) 151, (byte) 68, (byte) 23, + (byte) 196, (byte) 167, (byte) 126, (byte) 61, (byte) 100, (byte) 93, (byte) 25, (byte) 115, + (byte) 96, (byte) 129, (byte) 79, (byte) 220, (byte) 34, (byte) 42, (byte) 144, (byte) 136, + (byte) 70, (byte) 238, (byte) 184, (byte) 20, (byte) 222, (byte) 94, (byte) 11, (byte) 219, + (byte) 224, (byte) 50, (byte) 58, (byte) 10, (byte) 73, (byte) 6, (byte) 36, (byte) 92, + (byte) 194, (byte) 211, (byte) 172, (byte) 98, (byte) 145, (byte) 149, (byte) 228, (byte) 121, + (byte) 231, (byte) 200, (byte) 55, (byte) 109, (byte) 141, (byte) 213, (byte) 78, (byte) 169, + (byte) 108, (byte) 86, (byte) 244, (byte) 234, (byte) 101, (byte) 122, (byte) 174, (byte) 8, + (byte) 186, (byte) 120, (byte) 37, (byte) 46, (byte) 28, (byte) 166, (byte) 180, (byte) 198, + (byte) 232, (byte) 221, (byte) 116, (byte) 31, (byte) 75, (byte) 189, (byte) 139, (byte) 138, + (byte) 112, (byte) 62, (byte) 181, (byte) 102, (byte) 72, (byte) 3, (byte) 246, (byte) 14, + (byte) 97, (byte) 53, (byte) 87, (byte) 185, (byte) 134, (byte) 193, (byte) 29, (byte) 158, + (byte) 225, (byte) 248, (byte) 152, (byte) 17, (byte) 105, (byte) 217, (byte) 142, (byte) 148, + (byte) 155, (byte) 30, (byte) 135, (byte) 233, (byte) 206, (byte) 85, (byte) 40, (byte) 223, + (byte) 140, (byte) 161, (byte) 137, (byte) 13, (byte) 191, (byte) 230, (byte) 66, (byte) 104, + (byte) 65, (byte) 153, (byte) 45, (byte) 15, (byte) 176, (byte) 84, (byte) 187, (byte) 22, + }; + + // The inverse S-box + private static final byte[] Si = { + (byte) 82, (byte) 9, (byte) 106, (byte) 213, (byte) 48, (byte) 54, (byte) 165, (byte) 56, + (byte) 191, (byte) 64, (byte) 163, (byte) 158, (byte) 129, (byte) 243, (byte) 215, (byte) 251, + (byte) 124, (byte) 227, (byte) 57, (byte) 130, (byte) 155, (byte) 47, (byte) 255, (byte) 135, + (byte) 52, (byte) 142, (byte) 67, (byte) 68, (byte) 196, (byte) 222, (byte) 233, (byte) 203, + (byte) 84, (byte) 123, (byte) 148, (byte) 50, (byte) 166, (byte) 194, (byte) 35, (byte) 61, + (byte) 238, (byte) 76, (byte) 149, (byte) 11, (byte) 66, (byte) 250, (byte) 195, (byte) 78, + (byte) 8, (byte) 46, (byte) 161, (byte) 102, (byte) 40, (byte) 217, (byte) 36, (byte) 178, + (byte) 118, (byte) 91, (byte) 162, (byte) 73, (byte) 109, (byte) 139, (byte) 209, (byte) 37, + (byte) 114, (byte) 248, (byte) 246, (byte) 100, (byte) 134, (byte) 104, (byte) 152, (byte) 22, + (byte) 212, (byte) 164, (byte) 92, (byte) 204, (byte) 93, (byte) 101, (byte) 182, (byte) 146, + (byte) 108, (byte) 112, (byte) 72, (byte) 80, (byte) 253, (byte) 237, (byte) 185, (byte) 218, + (byte) 94, (byte) 21, (byte) 70, (byte) 87, (byte) 167, (byte) 141, (byte) 157, (byte) 132, + (byte) 144, (byte) 216, (byte) 171, (byte) 0, (byte) 140, (byte) 188, (byte) 211, (byte) 10, + (byte) 247, (byte) 228, (byte) 88, (byte) 5, (byte) 184, (byte) 179, (byte) 69, (byte) 6, + (byte) 208, (byte) 44, (byte) 30, (byte) 143, (byte) 202, (byte) 63, (byte) 15, (byte) 2, + (byte) 193, (byte) 175, (byte) 189, (byte) 3, (byte) 1, (byte) 19, (byte) 138, (byte) 107, + (byte) 58, (byte) 145, (byte) 17, (byte) 65, (byte) 79, (byte) 103, (byte) 220, (byte) 234, + (byte) 151, (byte) 242, (byte) 207, (byte) 206, (byte) 240, (byte) 180, (byte) 230, (byte) 115, + (byte) 150, (byte) 172, (byte) 116, (byte) 34, (byte) 231, (byte) 173, (byte) 53, (byte) 133, + (byte) 226, (byte) 249, (byte) 55, (byte) 232, (byte) 28, (byte) 117, (byte) 223, (byte) 110, + (byte) 71, (byte) 241, (byte) 26, (byte) 113, (byte) 29, (byte) 41, (byte) 197, (byte) 137, + (byte) 111, (byte) 183, (byte) 98, (byte) 14, (byte) 170, (byte) 24, (byte) 190, (byte) 27, + (byte) 252, (byte) 86, (byte) 62, (byte) 75, (byte) 198, (byte) 210, (byte) 121, (byte) 32, + (byte) 154, (byte) 219, (byte) 192, (byte) 254, (byte) 120, (byte) 205, (byte) 90, (byte) 244, + (byte) 31, (byte) 221, (byte) 168, (byte) 51, (byte) 136, (byte) 7, (byte) 199, (byte) 49, + (byte) 177, (byte) 18, (byte) 16, (byte) 89, (byte) 39, (byte) 128, (byte) 236, (byte) 95, + (byte) 96, (byte) 81, (byte) 127, (byte) 169, (byte) 25, (byte) 181, (byte) 74, (byte) 13, + (byte) 45, (byte) 229, (byte) 122, (byte) 159, (byte) 147, (byte) 201, (byte) 156, (byte) 239, + (byte) 160, (byte) 224, (byte) 59, (byte) 77, (byte) 174, (byte) 42, (byte) 245, (byte) 176, + (byte) 200, (byte) 235, (byte) 187, (byte) 60, (byte) 131, (byte) 83, (byte) 153, (byte) 97, + (byte) 23, (byte) 43, (byte) 4, (byte) 126, (byte) 186, (byte) 119, (byte) 214, (byte) 38, + (byte) 225, (byte) 105, (byte) 20, (byte) 99, (byte) 85, (byte) 33, (byte) 12, (byte) 125, + }; + + // vector used in calculating key schedule (powers of x in GF(256)) + private static final int[] rcon = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91}; + + // precomputation tables of calculations for rounds + private static final int[] T0 = + { + 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6, 0x0df2f2ff, + 0xbd6b6bd6, 0xb16f6fde, 0x54c5c591, 0x50303060, 0x03010102, + 0xa96767ce, 0x7d2b2b56, 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, + 0x9a7676ec, 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa, + 0x15fafaef, 0xeb5959b2, 0xc947478e, 0x0bf0f0fb, 0xecadad41, + 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45, 0xbf9c9c23, 0xf7a4a453, + 0x967272e4, 0x5bc0c09b, 0xc2b7b775, 0x1cfdfde1, 0xae93933d, + 0x6a26264c, 0x5a36366c, 0x413f3f7e, 0x02f7f7f5, 0x4fcccc83, + 0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x08f1f1f9, 0x937171e2, + 0x73d8d8ab, 0x53313162, 0x3f15152a, 0x0c040408, 0x52c7c795, + 0x65232346, 0x5ec3c39d, 0x28181830, 0xa1969637, 0x0f05050a, + 0xb59a9a2f, 0x0907070e, 0x36121224, 0x9b80801b, 0x3de2e2df, + 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea, 0x1b090912, + 0x9e83831d, 0x742c2c58, 0x2e1a1a34, 0x2d1b1b36, 0xb26e6edc, + 0xee5a5ab4, 0xfba0a05b, 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, + 0xceb3b37d, 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413, + 0xf55353a6, 0x68d1d1b9, 0x00000000, 0x2cededc1, 0x60202040, + 0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6, 0xbe6a6ad4, 0x46cbcb8d, + 0xd9bebe67, 0x4b393972, 0xde4a4a94, 0xd44c4c98, 0xe85858b0, + 0x4acfcf85, 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed, + 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511, 0xcf45458a, + 0x10f9f9e9, 0x06020204, 0x817f7ffe, 0xf05050a0, 0x443c3c78, + 0xba9f9f25, 0xe3a8a84b, 0xf35151a2, 0xfea3a35d, 0xc0404080, + 0x8a8f8f05, 0xad92923f, 0xbc9d9d21, 0x48383870, 0x04f5f5f1, + 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142, 0x30101020, + 0x1affffe5, 0x0ef3f3fd, 0x6dd2d2bf, 0x4ccdcd81, 0x140c0c18, + 0x35131326, 0x2fececc3, 0xe15f5fbe, 0xa2979735, 0xcc444488, + 0x3917172e, 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a, + 0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6, 0xa06060c0, + 0x98818119, 0xd14f4f9e, 0x7fdcdca3, 0x66222244, 0x7e2a2a54, + 0xab90903b, 0x8388880b, 0xca46468c, 0x29eeeec7, 0xd3b8b86b, + 0x3c141428, 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad, + 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14, 0xdb494992, + 0x0a06060c, 0x6c242448, 0xe45c5cb8, 0x5dc2c29f, 0x6ed3d3bd, + 0xefacac43, 0xa66262c4, 0xa8919139, 0xa4959531, 0x37e4e4d3, + 0x8b7979f2, 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda, + 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949, 0xb46c6cd8, + 0xfa5656ac, 0x07f4f4f3, 0x25eaeacf, 0xaf6565ca, 0x8e7a7af4, + 0xe9aeae47, 0x18080810, 0xd5baba6f, 0x887878f0, 0x6f25254a, + 0x722e2e5c, 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697, + 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e, 0xdd4b4b96, + 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f, 0x907070e0, 0x423e3e7c, + 0xc4b5b571, 0xaa6666cc, 0xd8484890, 0x05030306, 0x01f6f6f7, + 0x120e0e1c, 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969, + 0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27, 0x38e1e1d9, + 0x13f8f8eb, 0xb398982b, 0x33111122, 0xbb6969d2, 0x70d9d9a9, + 0x898e8e07, 0xa7949433, 0xb69b9b2d, 0x221e1e3c, 0x92878715, + 0x20e9e9c9, 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5, + 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a, 0xdabfbf65, + 0x31e6e6d7, 0xc6424284, 0xb86868d0, 0xc3414182, 0xb0999929, + 0x772d2d5a, 0x110f0f1e, 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, + 0x3a16162c}; + + private static final int[] T1 = + { + 0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d, 0xf2f2ff0d, + 0x6b6bd6bd, 0x6f6fdeb1, 0xc5c59154, 0x30306050, 0x01010203, + 0x6767cea9, 0x2b2b567d, 0xfefee719, 0xd7d7b562, 0xabab4de6, + 0x7676ec9a, 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87, + 0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b, 0xadad41ec, + 0xd4d4b367, 0xa2a25ffd, 0xafaf45ea, 0x9c9c23bf, 0xa4a453f7, + 0x7272e496, 0xc0c09b5b, 0xb7b775c2, 0xfdfde11c, 0x93933dae, + 0x26264c6a, 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f, + 0x3434685c, 0xa5a551f4, 0xe5e5d134, 0xf1f1f908, 0x7171e293, + 0xd8d8ab73, 0x31316253, 0x15152a3f, 0x0404080c, 0xc7c79552, + 0x23234665, 0xc3c39d5e, 0x18183028, 0x969637a1, 0x05050a0f, + 0x9a9a2fb5, 0x07070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d, + 0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f, 0x0909121b, + 0x83831d9e, 0x2c2c5874, 0x1a1a342e, 0x1b1b362d, 0x6e6edcb2, + 0x5a5ab4ee, 0xa0a05bfb, 0x5252a4f6, 0x3b3b764d, 0xd6d6b761, + 0xb3b37dce, 0x2929527b, 0xe3e3dd3e, 0x2f2f5e71, 0x84841397, + 0x5353a6f5, 0xd1d1b968, 0x00000000, 0xededc12c, 0x20204060, + 0xfcfce31f, 0xb1b179c8, 0x5b5bb6ed, 0x6a6ad4be, 0xcbcb8d46, + 0xbebe67d9, 0x3939724b, 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8, + 0xcfcf854a, 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16, + 0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194, 0x45458acf, + 0xf9f9e910, 0x02020406, 0x7f7ffe81, 0x5050a0f0, 0x3c3c7844, + 0x9f9f25ba, 0xa8a84be3, 0x5151a2f3, 0xa3a35dfe, 0x404080c0, + 0x8f8f058a, 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104, + 0xbcbc63df, 0xb6b677c1, 0xdadaaf75, 0x21214263, 0x10102030, + 0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d, 0xcdcd814c, 0x0c0c1814, + 0x13132635, 0xececc32f, 0x5f5fbee1, 0x979735a2, 0x444488cc, + 0x17172e39, 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47, + 0x6464c8ac, 0x5d5dbae7, 0x1919322b, 0x7373e695, 0x6060c0a0, + 0x81811998, 0x4f4f9ed1, 0xdcdca37f, 0x22224466, 0x2a2a547e, + 0x90903bab, 0x88880b83, 0x46468cca, 0xeeeec729, 0xb8b86bd3, + 0x1414283c, 0xdedea779, 0x5e5ebce2, 0x0b0b161d, 0xdbdbad76, + 0xe0e0db3b, 0x32326456, 0x3a3a744e, 0x0a0a141e, 0x494992db, + 0x06060c0a, 0x2424486c, 0x5c5cb8e4, 0xc2c29f5d, 0xd3d3bd6e, + 0xacac43ef, 0x6262c4a6, 0x919139a8, 0x959531a4, 0xe4e4d337, + 0x7979f28b, 0xe7e7d532, 0xc8c88b43, 0x37376e59, 0x6d6ddab7, + 0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0, 0x6c6cd8b4, + 0x5656acfa, 0xf4f4f307, 0xeaeacf25, 0x6565caaf, 0x7a7af48e, + 0xaeae47e9, 0x08081018, 0xbaba6fd5, 0x7878f088, 0x25254a6f, + 0x2e2e5c72, 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751, + 0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21, 0x4b4b96dd, + 0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85, 0x7070e090, 0x3e3e7c42, + 0xb5b571c4, 0x6666ccaa, 0x484890d8, 0x03030605, 0xf6f6f701, + 0x0e0e1c12, 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0, + 0x86861791, 0xc1c19958, 0x1d1d3a27, 0x9e9e27b9, 0xe1e1d938, + 0xf8f8eb13, 0x98982bb3, 0x11112233, 0x6969d2bb, 0xd9d9a970, + 0x8e8e0789, 0x949433a7, 0x9b9b2db6, 0x1e1e3c22, 0x87871592, + 0xe9e9c920, 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a, + 0x8c8c038f, 0xa1a159f8, 0x89890980, 0x0d0d1a17, 0xbfbf65da, + 0xe6e6d731, 0x424284c6, 0x6868d0b8, 0x414182c3, 0x999929b0, + 0x2d2d5a77, 0x0f0f1e11, 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, + 0x16162c3a}; + + private static final int[] T2 = + { + 0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b, 0xf2ff0df2, + 0x6bd6bd6b, 0x6fdeb16f, 0xc59154c5, 0x30605030, 0x01020301, + 0x67cea967, 0x2b567d2b, 0xfee719fe, 0xd7b562d7, 0xab4de6ab, + 0x76ec9a76, 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d, + 0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0, 0xad41ecad, + 0xd4b367d4, 0xa25ffda2, 0xaf45eaaf, 0x9c23bf9c, 0xa453f7a4, + 0x72e49672, 0xc09b5bc0, 0xb775c2b7, 0xfde11cfd, 0x933dae93, + 0x264c6a26, 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc, + 0x34685c34, 0xa551f4a5, 0xe5d134e5, 0xf1f908f1, 0x71e29371, + 0xd8ab73d8, 0x31625331, 0x152a3f15, 0x04080c04, 0xc79552c7, + 0x23466523, 0xc39d5ec3, 0x18302818, 0x9637a196, 0x050a0f05, + 0x9a2fb59a, 0x070e0907, 0x12243612, 0x801b9b80, 0xe2df3de2, + 0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75, 0x09121b09, + 0x831d9e83, 0x2c58742c, 0x1a342e1a, 0x1b362d1b, 0x6edcb26e, + 0x5ab4ee5a, 0xa05bfba0, 0x52a4f652, 0x3b764d3b, 0xd6b761d6, + 0xb37dceb3, 0x29527b29, 0xe3dd3ee3, 0x2f5e712f, 0x84139784, + 0x53a6f553, 0xd1b968d1, 0x00000000, 0xedc12ced, 0x20406020, + 0xfce31ffc, 0xb179c8b1, 0x5bb6ed5b, 0x6ad4be6a, 0xcb8d46cb, + 0xbe67d9be, 0x39724b39, 0x4a94de4a, 0x4c98d44c, 0x58b0e858, + 0xcf854acf, 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb, + 0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485, 0x458acf45, + 0xf9e910f9, 0x02040602, 0x7ffe817f, 0x50a0f050, 0x3c78443c, + 0x9f25ba9f, 0xa84be3a8, 0x51a2f351, 0xa35dfea3, 0x4080c040, + 0x8f058a8f, 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5, + 0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, 0x21426321, 0x10203010, + 0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2, 0xcd814ccd, 0x0c18140c, + 0x13263513, 0xecc32fec, 0x5fbee15f, 0x9735a297, 0x4488cc44, + 0x172e3917, 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d, + 0x64c8ac64, 0x5dbae75d, 0x19322b19, 0x73e69573, 0x60c0a060, + 0x81199881, 0x4f9ed14f, 0xdca37fdc, 0x22446622, 0x2a547e2a, + 0x903bab90, 0x880b8388, 0x468cca46, 0xeec729ee, 0xb86bd3b8, + 0x14283c14, 0xdea779de, 0x5ebce25e, 0x0b161d0b, 0xdbad76db, + 0xe0db3be0, 0x32645632, 0x3a744e3a, 0x0a141e0a, 0x4992db49, + 0x060c0a06, 0x24486c24, 0x5cb8e45c, 0xc29f5dc2, 0xd3bd6ed3, + 0xac43efac, 0x62c4a662, 0x9139a891, 0x9531a495, 0xe4d337e4, + 0x79f28b79, 0xe7d532e7, 0xc88b43c8, 0x376e5937, 0x6ddab76d, + 0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9, 0x6cd8b46c, + 0x56acfa56, 0xf4f307f4, 0xeacf25ea, 0x65caaf65, 0x7af48e7a, + 0xae47e9ae, 0x08101808, 0xba6fd5ba, 0x78f08878, 0x254a6f25, + 0x2e5c722e, 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6, + 0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f, 0x4b96dd4b, + 0xbd61dcbd, 0x8b0d868b, 0x8a0f858a, 0x70e09070, 0x3e7c423e, + 0xb571c4b5, 0x66ccaa66, 0x4890d848, 0x03060503, 0xf6f701f6, + 0x0e1c120e, 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9, + 0x86179186, 0xc19958c1, 0x1d3a271d, 0x9e27b99e, 0xe1d938e1, + 0xf8eb13f8, 0x982bb398, 0x11223311, 0x69d2bb69, 0xd9a970d9, + 0x8e07898e, 0x9433a794, 0x9b2db69b, 0x1e3c221e, 0x87159287, + 0xe9c920e9, 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf, + 0x8c038f8c, 0xa159f8a1, 0x89098089, 0x0d1a170d, 0xbf65dabf, + 0xe6d731e6, 0x4284c642, 0x68d0b868, 0x4182c341, 0x9929b099, + 0x2d5a772d, 0x0f1e110f, 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, + 0x162c3a16}; + + private static final int[] T3 = + { + 0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b, 0xff0df2f2, + 0xd6bd6b6b, 0xdeb16f6f, 0x9154c5c5, 0x60503030, 0x02030101, + 0xcea96767, 0x567d2b2b, 0xe719fefe, 0xb562d7d7, 0x4de6abab, + 0xec9a7676, 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d, + 0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0, 0x41ecadad, + 0xb367d4d4, 0x5ffda2a2, 0x45eaafaf, 0x23bf9c9c, 0x53f7a4a4, + 0xe4967272, 0x9b5bc0c0, 0x75c2b7b7, 0xe11cfdfd, 0x3dae9393, + 0x4c6a2626, 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc, + 0x685c3434, 0x51f4a5a5, 0xd134e5e5, 0xf908f1f1, 0xe2937171, + 0xab73d8d8, 0x62533131, 0x2a3f1515, 0x080c0404, 0x9552c7c7, + 0x46652323, 0x9d5ec3c3, 0x30281818, 0x37a19696, 0x0a0f0505, + 0x2fb59a9a, 0x0e090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2, + 0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575, 0x121b0909, + 0x1d9e8383, 0x58742c2c, 0x342e1a1a, 0x362d1b1b, 0xdcb26e6e, + 0xb4ee5a5a, 0x5bfba0a0, 0xa4f65252, 0x764d3b3b, 0xb761d6d6, + 0x7dceb3b3, 0x527b2929, 0xdd3ee3e3, 0x5e712f2f, 0x13978484, + 0xa6f55353, 0xb968d1d1, 0x00000000, 0xc12ceded, 0x40602020, + 0xe31ffcfc, 0x79c8b1b1, 0xb6ed5b5b, 0xd4be6a6a, 0x8d46cbcb, + 0x67d9bebe, 0x724b3939, 0x94de4a4a, 0x98d44c4c, 0xb0e85858, + 0x854acfcf, 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb, + 0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585, 0x8acf4545, + 0xe910f9f9, 0x04060202, 0xfe817f7f, 0xa0f05050, 0x78443c3c, + 0x25ba9f9f, 0x4be3a8a8, 0xa2f35151, 0x5dfea3a3, 0x80c04040, + 0x058a8f8f, 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5, + 0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, 0x42632121, 0x20301010, + 0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2, 0x814ccdcd, 0x18140c0c, + 0x26351313, 0xc32fecec, 0xbee15f5f, 0x35a29797, 0x88cc4444, + 0x2e391717, 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d, + 0xc8ac6464, 0xbae75d5d, 0x322b1919, 0xe6957373, 0xc0a06060, + 0x19988181, 0x9ed14f4f, 0xa37fdcdc, 0x44662222, 0x547e2a2a, + 0x3bab9090, 0x0b838888, 0x8cca4646, 0xc729eeee, 0x6bd3b8b8, + 0x283c1414, 0xa779dede, 0xbce25e5e, 0x161d0b0b, 0xad76dbdb, + 0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a, 0x92db4949, + 0x0c0a0606, 0x486c2424, 0xb8e45c5c, 0x9f5dc2c2, 0xbd6ed3d3, + 0x43efacac, 0xc4a66262, 0x39a89191, 0x31a49595, 0xd337e4e4, + 0xf28b7979, 0xd532e7e7, 0x8b43c8c8, 0x6e593737, 0xdab76d6d, + 0x018c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9, 0xd8b46c6c, + 0xacfa5656, 0xf307f4f4, 0xcf25eaea, 0xcaaf6565, 0xf48e7a7a, + 0x47e9aeae, 0x10180808, 0x6fd5baba, 0xf0887878, 0x4a6f2525, + 0x5c722e2e, 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6, + 0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f, 0x96dd4b4b, + 0x61dcbdbd, 0x0d868b8b, 0x0f858a8a, 0xe0907070, 0x7c423e3e, + 0x71c4b5b5, 0xccaa6666, 0x90d84848, 0x06050303, 0xf701f6f6, + 0x1c120e0e, 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9, + 0x17918686, 0x9958c1c1, 0x3a271d1d, 0x27b99e9e, 0xd938e1e1, + 0xeb13f8f8, 0x2bb39898, 0x22331111, 0xd2bb6969, 0xa970d9d9, + 0x07898e8e, 0x33a79494, 0x2db69b9b, 0x3c221e1e, 0x15928787, + 0xc920e9e9, 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf, + 0x038f8c8c, 0x59f8a1a1, 0x09808989, 0x1a170d0d, 0x65dabfbf, + 0xd731e6e6, 0x84c64242, 0xd0b86868, 0x82c34141, 0x29b09999, + 0x5a772d2d, 0x1e110f0f, 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, + 0x2c3a1616}; + + private static final int[] Tinv0 = + { + 0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a, 0xcb6bab3b, + 0xf1459d1f, 0xab58faac, 0x9303e34b, 0x55fa3020, 0xf66d76ad, + 0x9176cc88, 0x254c02f5, 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, + 0x8fa362b5, 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d, + 0x02752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b, 0xe75f8f03, + 0x959c9215, 0xeb7a6dbf, 0xda595295, 0x2d83bed4, 0xd3217458, + 0x2969e049, 0x44c8c98e, 0x6a89c275, 0x78798ef4, 0x6b3e5899, + 0xdd71b927, 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d, + 0x184adf63, 0x82311ae5, 0x60335197, 0x457f5362, 0xe07764b1, + 0x84ae6bbb, 0x1ca081fe, 0x942b08f9, 0x58684870, 0x19fd458f, + 0x876cde94, 0xb7f87b52, 0x23d373ab, 0xe2024b72, 0x578f1fe3, + 0x2aab5566, 0x0728ebb2, 0x03c2b52f, 0x9a7bc586, 0xa50837d3, + 0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed, 0x2b1ccf8a, + 0x92b479a7, 0xf0f207f3, 0xa1e2694e, 0xcdf4da65, 0xd5be0506, + 0x1f6234d1, 0x8afea6c4, 0x9d532e34, 0xa055f3a2, 0x32e18a05, + 0x75ebf6a4, 0x39ec830b, 0xaaef6040, 0x069f715e, 0x51106ebd, + 0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d, 0xb58d5491, + 0x055dc471, 0x6fd40604, 0xff155060, 0x24fb9819, 0x97e9bdd6, + 0xcc434089, 0x779ed967, 0xbd42e8b0, 0x888b8907, 0x385b19e7, + 0xdbeec879, 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x00000000, + 0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c, 0xfbff0efd, + 0x5638850f, 0x1ed5ae3d, 0x27392d36, 0x64d90f0a, 0x21a65c68, + 0xd1545b9b, 0x3a2e3624, 0xb1670a0c, 0x0fe75793, 0xd296eeb4, + 0x9e919b1b, 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c, + 0x0aba93e2, 0xe52aa0c0, 0x43e0223c, 0x1d171b12, 0x0b0d090e, + 0xadc78bf2, 0xb9a8b62d, 0xc8a91e14, 0x8519f157, 0x4c0775af, + 0xbbdd99ee, 0xfd607fa3, 0x9f2601f7, 0xbcf5725c, 0xc53b6644, + 0x347efb5b, 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8, + 0xcadc31d7, 0x10856342, 0x40229713, 0x2011c684, 0x7d244a85, + 0xf83dbbd2, 0x1132f9ae, 0x6da129c7, 0x4b2f9e1d, 0xf330b2dc, + 0xec52860d, 0xd0e3c177, 0x6c16b32b, 0x99b970a9, 0xfa489411, + 0x2264e947, 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, 0xef903322, + 0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498, 0xcf81f5a6, + 0x28de7aa5, 0x268eb7da, 0xa4bfad3f, 0xe49d3a2c, 0x0d927850, + 0x9bcc5f6a, 0x62467e54, 0xc2138df6, 0xe8b8d890, 0x5ef7392e, + 0xf5afc382, 0xbe805d9f, 0x7c93d069, 0xa92dd56f, 0xb31225cf, + 0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb, 0x097826cd, + 0xf418596e, 0x01b79aec, 0xa89a4f83, 0x656e95e6, 0x7ee6ffaa, + 0x08cfbc21, 0xe6e815ef, 0xd99be7ba, 0xce366f4a, 0xd4099fea, + 0xd67cb029, 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235, + 0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733, 0x4a9804f1, + 0xf7daec41, 0x0e50cd7f, 0x2ff69117, 0x8dd64d76, 0x4db0ef43, + 0x544daacc, 0xdf0496e4, 0xe3b5d19e, 0x1b886a4c, 0xb81f2cc1, + 0x7f516546, 0x04ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb, + 0x5a1d67b3, 0x52d2db92, 0x335610e9, 0x1347d66d, 0x8c61d79a, + 0x7a0ca137, 0x8e14f859, 0x893c13eb, 0xee27a9ce, 0x35c961b7, + 0xede51ce1, 0x3cb1477a, 0x59dfd29c, 0x3f73f255, 0x79ce1418, + 0xbf37c773, 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478, + 0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2, 0x72c31d16, + 0x0c25e2bc, 0x8b493c28, 0x41950dff, 0x7101a839, 0xdeb30c08, + 0x9ce4b4d8, 0x90c15664, 0x6184cb7b, 0x70b632d5, 0x745c6c48, + 0x4257b8d0}; + + private static final int[] Tinv1 = + { + 0xa7f45150, 0x65417e53, 0xa4171ac3, 0x5e273a96, 0x6bab3bcb, + 0x459d1ff1, 0x58faacab, 0x03e34b93, 0xfa302055, 0x6d76adf6, + 0x76cc8891, 0x4c02f525, 0xd7e54ffc, 0xcb2ac5d7, 0x44352680, + 0xa362b58f, 0x5ab1de49, 0x1bba2567, 0x0eea4598, 0xc0fe5de1, + 0x752fc302, 0xf04c8112, 0x97468da3, 0xf9d36bc6, 0x5f8f03e7, + 0x9c921595, 0x7a6dbfeb, 0x595295da, 0x83bed42d, 0x217458d3, + 0x69e04929, 0xc8c98e44, 0x89c2756a, 0x798ef478, 0x3e58996b, + 0x71b927dd, 0x4fe1beb6, 0xad88f017, 0xac20c966, 0x3ace7db4, + 0x4adf6318, 0x311ae582, 0x33519760, 0x7f536245, 0x7764b1e0, + 0xae6bbb84, 0xa081fe1c, 0x2b08f994, 0x68487058, 0xfd458f19, + 0x6cde9487, 0xf87b52b7, 0xd373ab23, 0x024b72e2, 0x8f1fe357, + 0xab55662a, 0x28ebb207, 0xc2b52f03, 0x7bc5869a, 0x0837d3a5, + 0x872830f2, 0xa5bf23b2, 0x6a0302ba, 0x8216ed5c, 0x1ccf8a2b, + 0xb479a792, 0xf207f3f0, 0xe2694ea1, 0xf4da65cd, 0xbe0506d5, + 0x6234d11f, 0xfea6c48a, 0x532e349d, 0x55f3a2a0, 0xe18a0532, + 0xebf6a475, 0xec830b39, 0xef6040aa, 0x9f715e06, 0x106ebd51, + 0x8a213ef9, 0x06dd963d, 0x053eddae, 0xbde64d46, 0x8d5491b5, + 0x5dc47105, 0xd406046f, 0x155060ff, 0xfb981924, 0xe9bdd697, + 0x434089cc, 0x9ed96777, 0x42e8b0bd, 0x8b890788, 0x5b19e738, + 0xeec879db, 0x0a7ca147, 0x0f427ce9, 0x1e84f8c9, 0x00000000, + 0x86800983, 0xed2b3248, 0x70111eac, 0x725a6c4e, 0xff0efdfb, + 0x38850f56, 0xd5ae3d1e, 0x392d3627, 0xd90f0a64, 0xa65c6821, + 0x545b9bd1, 0x2e36243a, 0x670a0cb1, 0xe757930f, 0x96eeb4d2, + 0x919b1b9e, 0xc5c0804f, 0x20dc61a2, 0x4b775a69, 0x1a121c16, + 0xba93e20a, 0x2aa0c0e5, 0xe0223c43, 0x171b121d, 0x0d090e0b, + 0xc78bf2ad, 0xa8b62db9, 0xa91e14c8, 0x19f15785, 0x0775af4c, + 0xdd99eebb, 0x607fa3fd, 0x2601f79f, 0xf5725cbc, 0x3b6644c5, + 0x7efb5b34, 0x29438b76, 0xc623cbdc, 0xfcedb668, 0xf1e4b863, + 0xdc31d7ca, 0x85634210, 0x22971340, 0x11c68420, 0x244a857d, + 0x3dbbd2f8, 0x32f9ae11, 0xa129c76d, 0x2f9e1d4b, 0x30b2dcf3, + 0x52860dec, 0xe3c177d0, 0x16b32b6c, 0xb970a999, 0x489411fa, + 0x64e94722, 0x8cfca8c4, 0x3ff0a01a, 0x2c7d56d8, 0x903322ef, + 0x4e4987c7, 0xd138d9c1, 0xa2ca8cfe, 0x0bd49836, 0x81f5a6cf, + 0xde7aa528, 0x8eb7da26, 0xbfad3fa4, 0x9d3a2ce4, 0x9278500d, + 0xcc5f6a9b, 0x467e5462, 0x138df6c2, 0xb8d890e8, 0xf7392e5e, + 0xafc382f5, 0x805d9fbe, 0x93d0697c, 0x2dd56fa9, 0x1225cfb3, + 0x99acc83b, 0x7d1810a7, 0x639ce86e, 0xbb3bdb7b, 0x7826cd09, + 0x18596ef4, 0xb79aec01, 0x9a4f83a8, 0x6e95e665, 0xe6ffaa7e, + 0xcfbc2108, 0xe815efe6, 0x9be7bad9, 0x366f4ace, 0x099fead4, + 0x7cb029d6, 0xb2a431af, 0x233f2a31, 0x94a5c630, 0x66a235c0, + 0xbc4e7437, 0xca82fca6, 0xd090e0b0, 0xd8a73315, 0x9804f14a, + 0xdaec41f7, 0x50cd7f0e, 0xf691172f, 0xd64d768d, 0xb0ef434d, + 0x4daacc54, 0x0496e4df, 0xb5d19ee3, 0x886a4c1b, 0x1f2cc1b8, + 0x5165467f, 0xea5e9d04, 0x358c015d, 0x7487fa73, 0x410bfb2e, + 0x1d67b35a, 0xd2db9252, 0x5610e933, 0x47d66d13, 0x61d79a8c, + 0x0ca1377a, 0x14f8598e, 0x3c13eb89, 0x27a9ceee, 0xc961b735, + 0xe51ce1ed, 0xb1477a3c, 0xdfd29c59, 0x73f2553f, 0xce141879, + 0x37c773bf, 0xcdf753ea, 0xaafd5f5b, 0x6f3ddf14, 0xdb447886, + 0xf3afca81, 0xc468b93e, 0x3424382c, 0x40a3c25f, 0xc31d1672, + 0x25e2bc0c, 0x493c288b, 0x950dff41, 0x01a83971, 0xb30c08de, + 0xe4b4d89c, 0xc1566490, 0x84cb7b61, 0xb632d570, 0x5c6c4874, + 0x57b8d042}; + + private static final int[] Tinv2 = + { + 0xf45150a7, 0x417e5365, 0x171ac3a4, 0x273a965e, 0xab3bcb6b, + 0x9d1ff145, 0xfaacab58, 0xe34b9303, 0x302055fa, 0x76adf66d, + 0xcc889176, 0x02f5254c, 0xe54ffcd7, 0x2ac5d7cb, 0x35268044, + 0x62b58fa3, 0xb1de495a, 0xba25671b, 0xea45980e, 0xfe5de1c0, + 0x2fc30275, 0x4c8112f0, 0x468da397, 0xd36bc6f9, 0x8f03e75f, + 0x9215959c, 0x6dbfeb7a, 0x5295da59, 0xbed42d83, 0x7458d321, + 0xe0492969, 0xc98e44c8, 0xc2756a89, 0x8ef47879, 0x58996b3e, + 0xb927dd71, 0xe1beb64f, 0x88f017ad, 0x20c966ac, 0xce7db43a, + 0xdf63184a, 0x1ae58231, 0x51976033, 0x5362457f, 0x64b1e077, + 0x6bbb84ae, 0x81fe1ca0, 0x08f9942b, 0x48705868, 0x458f19fd, + 0xde94876c, 0x7b52b7f8, 0x73ab23d3, 0x4b72e202, 0x1fe3578f, + 0x55662aab, 0xebb20728, 0xb52f03c2, 0xc5869a7b, 0x37d3a508, + 0x2830f287, 0xbf23b2a5, 0x0302ba6a, 0x16ed5c82, 0xcf8a2b1c, + 0x79a792b4, 0x07f3f0f2, 0x694ea1e2, 0xda65cdf4, 0x0506d5be, + 0x34d11f62, 0xa6c48afe, 0x2e349d53, 0xf3a2a055, 0x8a0532e1, + 0xf6a475eb, 0x830b39ec, 0x6040aaef, 0x715e069f, 0x6ebd5110, + 0x213ef98a, 0xdd963d06, 0x3eddae05, 0xe64d46bd, 0x5491b58d, + 0xc471055d, 0x06046fd4, 0x5060ff15, 0x981924fb, 0xbdd697e9, + 0x4089cc43, 0xd967779e, 0xe8b0bd42, 0x8907888b, 0x19e7385b, + 0xc879dbee, 0x7ca1470a, 0x427ce90f, 0x84f8c91e, 0x00000000, + 0x80098386, 0x2b3248ed, 0x111eac70, 0x5a6c4e72, 0x0efdfbff, + 0x850f5638, 0xae3d1ed5, 0x2d362739, 0x0f0a64d9, 0x5c6821a6, + 0x5b9bd154, 0x36243a2e, 0x0a0cb167, 0x57930fe7, 0xeeb4d296, + 0x9b1b9e91, 0xc0804fc5, 0xdc61a220, 0x775a694b, 0x121c161a, + 0x93e20aba, 0xa0c0e52a, 0x223c43e0, 0x1b121d17, 0x090e0b0d, + 0x8bf2adc7, 0xb62db9a8, 0x1e14c8a9, 0xf1578519, 0x75af4c07, + 0x99eebbdd, 0x7fa3fd60, 0x01f79f26, 0x725cbcf5, 0x6644c53b, + 0xfb5b347e, 0x438b7629, 0x23cbdcc6, 0xedb668fc, 0xe4b863f1, + 0x31d7cadc, 0x63421085, 0x97134022, 0xc6842011, 0x4a857d24, + 0xbbd2f83d, 0xf9ae1132, 0x29c76da1, 0x9e1d4b2f, 0xb2dcf330, + 0x860dec52, 0xc177d0e3, 0xb32b6c16, 0x70a999b9, 0x9411fa48, + 0xe9472264, 0xfca8c48c, 0xf0a01a3f, 0x7d56d82c, 0x3322ef90, + 0x4987c74e, 0x38d9c1d1, 0xca8cfea2, 0xd498360b, 0xf5a6cf81, + 0x7aa528de, 0xb7da268e, 0xad3fa4bf, 0x3a2ce49d, 0x78500d92, + 0x5f6a9bcc, 0x7e546246, 0x8df6c213, 0xd890e8b8, 0x392e5ef7, + 0xc382f5af, 0x5d9fbe80, 0xd0697c93, 0xd56fa92d, 0x25cfb312, + 0xacc83b99, 0x1810a77d, 0x9ce86e63, 0x3bdb7bbb, 0x26cd0978, + 0x596ef418, 0x9aec01b7, 0x4f83a89a, 0x95e6656e, 0xffaa7ee6, + 0xbc2108cf, 0x15efe6e8, 0xe7bad99b, 0x6f4ace36, 0x9fead409, + 0xb029d67c, 0xa431afb2, 0x3f2a3123, 0xa5c63094, 0xa235c066, + 0x4e7437bc, 0x82fca6ca, 0x90e0b0d0, 0xa73315d8, 0x04f14a98, + 0xec41f7da, 0xcd7f0e50, 0x91172ff6, 0x4d768dd6, 0xef434db0, + 0xaacc544d, 0x96e4df04, 0xd19ee3b5, 0x6a4c1b88, 0x2cc1b81f, + 0x65467f51, 0x5e9d04ea, 0x8c015d35, 0x87fa7374, 0x0bfb2e41, + 0x67b35a1d, 0xdb9252d2, 0x10e93356, 0xd66d1347, 0xd79a8c61, + 0xa1377a0c, 0xf8598e14, 0x13eb893c, 0xa9ceee27, 0x61b735c9, + 0x1ce1ede5, 0x477a3cb1, 0xd29c59df, 0xf2553f73, 0x141879ce, + 0xc773bf37, 0xf753eacd, 0xfd5f5baa, 0x3ddf146f, 0x447886db, + 0xafca81f3, 0x68b93ec4, 0x24382c34, 0xa3c25f40, 0x1d1672c3, + 0xe2bc0c25, 0x3c288b49, 0x0dff4195, 0xa8397101, 0x0c08deb3, + 0xb4d89ce4, 0x566490c1, 0xcb7b6184, 0x32d570b6, 0x6c48745c, + 0xb8d04257}; + + private static final int[] Tinv3 = + { + 0x5150a7f4, 0x7e536541, 0x1ac3a417, 0x3a965e27, 0x3bcb6bab, + 0x1ff1459d, 0xacab58fa, 0x4b9303e3, 0x2055fa30, 0xadf66d76, + 0x889176cc, 0xf5254c02, 0x4ffcd7e5, 0xc5d7cb2a, 0x26804435, + 0xb58fa362, 0xde495ab1, 0x25671bba, 0x45980eea, 0x5de1c0fe, + 0xc302752f, 0x8112f04c, 0x8da39746, 0x6bc6f9d3, 0x03e75f8f, + 0x15959c92, 0xbfeb7a6d, 0x95da5952, 0xd42d83be, 0x58d32174, + 0x492969e0, 0x8e44c8c9, 0x756a89c2, 0xf478798e, 0x996b3e58, + 0x27dd71b9, 0xbeb64fe1, 0xf017ad88, 0xc966ac20, 0x7db43ace, + 0x63184adf, 0xe582311a, 0x97603351, 0x62457f53, 0xb1e07764, + 0xbb84ae6b, 0xfe1ca081, 0xf9942b08, 0x70586848, 0x8f19fd45, + 0x94876cde, 0x52b7f87b, 0xab23d373, 0x72e2024b, 0xe3578f1f, + 0x662aab55, 0xb20728eb, 0x2f03c2b5, 0x869a7bc5, 0xd3a50837, + 0x30f28728, 0x23b2a5bf, 0x02ba6a03, 0xed5c8216, 0x8a2b1ccf, + 0xa792b479, 0xf3f0f207, 0x4ea1e269, 0x65cdf4da, 0x06d5be05, + 0xd11f6234, 0xc48afea6, 0x349d532e, 0xa2a055f3, 0x0532e18a, + 0xa475ebf6, 0x0b39ec83, 0x40aaef60, 0x5e069f71, 0xbd51106e, + 0x3ef98a21, 0x963d06dd, 0xddae053e, 0x4d46bde6, 0x91b58d54, + 0x71055dc4, 0x046fd406, 0x60ff1550, 0x1924fb98, 0xd697e9bd, + 0x89cc4340, 0x67779ed9, 0xb0bd42e8, 0x07888b89, 0xe7385b19, + 0x79dbeec8, 0xa1470a7c, 0x7ce90f42, 0xf8c91e84, 0x00000000, + 0x09838680, 0x3248ed2b, 0x1eac7011, 0x6c4e725a, 0xfdfbff0e, + 0x0f563885, 0x3d1ed5ae, 0x3627392d, 0x0a64d90f, 0x6821a65c, + 0x9bd1545b, 0x243a2e36, 0x0cb1670a, 0x930fe757, 0xb4d296ee, + 0x1b9e919b, 0x804fc5c0, 0x61a220dc, 0x5a694b77, 0x1c161a12, + 0xe20aba93, 0xc0e52aa0, 0x3c43e022, 0x121d171b, 0x0e0b0d09, + 0xf2adc78b, 0x2db9a8b6, 0x14c8a91e, 0x578519f1, 0xaf4c0775, + 0xeebbdd99, 0xa3fd607f, 0xf79f2601, 0x5cbcf572, 0x44c53b66, + 0x5b347efb, 0x8b762943, 0xcbdcc623, 0xb668fced, 0xb863f1e4, + 0xd7cadc31, 0x42108563, 0x13402297, 0x842011c6, 0x857d244a, + 0xd2f83dbb, 0xae1132f9, 0xc76da129, 0x1d4b2f9e, 0xdcf330b2, + 0x0dec5286, 0x77d0e3c1, 0x2b6c16b3, 0xa999b970, 0x11fa4894, + 0x472264e9, 0xa8c48cfc, 0xa01a3ff0, 0x56d82c7d, 0x22ef9033, + 0x87c74e49, 0xd9c1d138, 0x8cfea2ca, 0x98360bd4, 0xa6cf81f5, + 0xa528de7a, 0xda268eb7, 0x3fa4bfad, 0x2ce49d3a, 0x500d9278, + 0x6a9bcc5f, 0x5462467e, 0xf6c2138d, 0x90e8b8d8, 0x2e5ef739, + 0x82f5afc3, 0x9fbe805d, 0x697c93d0, 0x6fa92dd5, 0xcfb31225, + 0xc83b99ac, 0x10a77d18, 0xe86e639c, 0xdb7bbb3b, 0xcd097826, + 0x6ef41859, 0xec01b79a, 0x83a89a4f, 0xe6656e95, 0xaa7ee6ff, + 0x2108cfbc, 0xefe6e815, 0xbad99be7, 0x4ace366f, 0xead4099f, + 0x29d67cb0, 0x31afb2a4, 0x2a31233f, 0xc63094a5, 0x35c066a2, + 0x7437bc4e, 0xfca6ca82, 0xe0b0d090, 0x3315d8a7, 0xf14a9804, + 0x41f7daec, 0x7f0e50cd, 0x172ff691, 0x768dd64d, 0x434db0ef, + 0xcc544daa, 0xe4df0496, 0x9ee3b5d1, 0x4c1b886a, 0xc1b81f2c, + 0x467f5165, 0x9d04ea5e, 0x015d358c, 0xfa737487, 0xfb2e410b, + 0xb35a1d67, 0x9252d2db, 0xe9335610, 0x6d1347d6, 0x9a8c61d7, + 0x377a0ca1, 0x598e14f8, 0xeb893c13, 0xceee27a9, 0xb735c961, + 0xe1ede51c, 0x7a3cb147, 0x9c59dfd2, 0x553f73f2, 0x1879ce14, + 0x73bf37c7, 0x53eacdf7, 0x5f5baafd, 0xdf146f3d, 0x7886db44, + 0xca81f3af, 0xb93ec468, 0x382c3424, 0xc25f40a3, 0x1672c31d, + 0xbc0c25e2, 0x288b493c, 0xff41950d, 0x397101a8, 0x08deb30c, + 0xd89ce4b4, 0x6490c156, 0x7b6184cb, 0xd570b632, 0x48745c6c, + 0xd04257b8}; + + private int shift( + int r, + int shift) { + return (r >>> shift) | (r << -shift); + } + + /* multiply four bytes in GF(2^8) by 'x' {02} in parallel */ + + private static final int m1 = 0x80808080; + private static final int m2 = 0x7f7f7f7f; + private static final int m3 = 0x0000001b; + + private int FFmulX(int x) { + return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3)); + } + + /* + The following defines provide alternative definitions of FFmulX that might + give improved performance if a fast 32-bit multiply is not available. + + private int FFmulX(int x) { int u = x & m1; u |= (u >> 1); return ((x & m2) << 1) ^ ((u >>> 3) | (u >>> 6)); } + private static final int m4 = 0x1b1b1b1b; + private int FFmulX(int x) { int u = x & m1; return ((x & m2) << 1) ^ ((u - (u >>> 7)) & m4); } + + */ + + private int inv_mcol(int x) { + int f2 = FFmulX(x); + int f4 = FFmulX(f2); + int f8 = FFmulX(f4); + int f9 = x ^ f8; + + return f2 ^ f4 ^ f8 ^ shift(f2 ^ f9, 8) ^ shift(f4 ^ f9, 16) ^ shift(f9, 24); + } + + + private int subWord(int x) { + return (S[x & 255] & 255 | ((S[(x >> 8) & 255] & 255) << 8) | ((S[(x >> 16) & 255] & 255) << 16) | S[(x >> 24) & 255] << 24); + } + + /** + * Calculate the necessary round keys + * The number of calculations depends on key size and block size + * AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits + * This code is written assuming those are the only possible values + */ + private int[][] generateWorkingKey( + byte[] key, + boolean forEncryption) { + int KC = key.length / 4; // key length in words + int t; + + if (((KC != 4) && (KC != 6) && (KC != 8)) || ((KC * 4) != key.length)) { + throw new IllegalArgumentException("Key length not 128/192/256 bits."); + } + + ROUNDS = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes + int[][] W = new int[ROUNDS + 1][4]; // 4 words in a block + + // + // copy the key into the round key array + // + + t = 0; + int i = 0; + while (i < key.length) { + W[t >> 2][t & 3] = (key[i] & 0xff) | ((key[i + 1] & 0xff) << 8) | ((key[i + 2] & 0xff) << 16) | (key[i + 3] << 24); + i += 4; + t++; + } + + // + // while not enough round key material calculated + // calculate new values + // + int k = (ROUNDS + 1) << 2; + for (i = KC; (i < k); i++) { + int temp = W[(i - 1) >> 2][(i - 1) & 3]; + if ((i % KC) == 0) { + temp = subWord(shift(temp, 8)) ^ rcon[(i / KC) - 1]; + } else if ((KC > 6) && ((i % KC) == 4)) { + temp = subWord(temp); + } + + W[i >> 2][i & 3] = W[(i - KC) >> 2][(i - KC) & 3] ^ temp; + } + + if (!forEncryption) { + for (int j = 1; j < ROUNDS; j++) { + for (i = 0; i < 4; i++) { + W[j][i] = inv_mcol(W[j][i]); + } + } + } + + return W; + } + + private int ROUNDS; + private int[][] WorkingKey = null; + private int C0, C1, C2, C3; + private boolean forEncryption; + + private static final int BLOCK_SIZE = 16; + + /** + * default constructor - 128 bit block size. + */ + public AESFastEngine() { + } + + /** + * initialise an AES cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @throws IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + KeyParameter params) { + + WorkingKey = generateWorkingKey(params.getKey(), forEncryption); + this.forEncryption = forEncryption; + } + + public String getAlgorithmName() { + return "AES"; + } + + public int getBlockSize() { + return BLOCK_SIZE; + } + + public int processBlock( + byte[] in, + int inOff, + byte[] out, + int outOff) { + if (WorkingKey == null) { + throw new IllegalStateException("AES engine not initialised"); + } + + if ((inOff + (32 / 2)) > in.length) { + throw new RuntimeException("input buffer too short"); + } + + if ((outOff + (32 / 2)) > out.length) { + throw new RuntimeException("output buffer too short"); + } + + if (forEncryption) { + unpackBlock(in, inOff); + encryptBlock(WorkingKey); + packBlock(out, outOff); + } else { + unpackBlock(in, inOff); + decryptBlock(WorkingKey); + packBlock(out, outOff); + } + + return BLOCK_SIZE; + } + + public void reset() { + } + + private void unpackBlock( + byte[] bytes, + int off) { + int index = off; + + C0 = (bytes[index++] & 0xff); + C0 |= (bytes[index++] & 0xff) << 8; + C0 |= (bytes[index++] & 0xff) << 16; + C0 |= bytes[index++] << 24; + + C1 = (bytes[index++] & 0xff); + C1 |= (bytes[index++] & 0xff) << 8; + C1 |= (bytes[index++] & 0xff) << 16; + C1 |= bytes[index++] << 24; + + C2 = (bytes[index++] & 0xff); + C2 |= (bytes[index++] & 0xff) << 8; + C2 |= (bytes[index++] & 0xff) << 16; + C2 |= bytes[index++] << 24; + + C3 = (bytes[index++] & 0xff); + C3 |= (bytes[index++] & 0xff) << 8; + C3 |= (bytes[index++] & 0xff) << 16; + C3 |= bytes[index++] << 24; + } + + private void packBlock( + byte[] bytes, + int off) { + int index = off; + + bytes[index++] = (byte) C0; + bytes[index++] = (byte) (C0 >> 8); + bytes[index++] = (byte) (C0 >> 16); + bytes[index++] = (byte) (C0 >> 24); + + bytes[index++] = (byte) C1; + bytes[index++] = (byte) (C1 >> 8); + bytes[index++] = (byte) (C1 >> 16); + bytes[index++] = (byte) (C1 >> 24); + + bytes[index++] = (byte) C2; + bytes[index++] = (byte) (C2 >> 8); + bytes[index++] = (byte) (C2 >> 16); + bytes[index++] = (byte) (C2 >> 24); + + bytes[index++] = (byte) C3; + bytes[index++] = (byte) (C3 >> 8); + bytes[index++] = (byte) (C3 >> 16); + bytes[index++] = (byte) (C3 >> 24); + } + + private void encryptBlock(int[][] KW) { + int r, r0, r1, r2, r3; + + C0 ^= KW[0][0]; + C1 ^= KW[0][1]; + C2 ^= KW[0][2]; + C3 ^= KW[0][3]; + + r = 1; + while (r < ROUNDS - 1) { + r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; + r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; + r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; + r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; + C0 = T0[r0 & 255] ^ T1[(r1 >> 8) & 255] ^ T2[(r2 >> 16) & 255] ^ T3[(r3 >> 24) & 255] ^ KW[r][0]; + C1 = T0[r1 & 255] ^ T1[(r2 >> 8) & 255] ^ T2[(r3 >> 16) & 255] ^ T3[(r0 >> 24) & 255] ^ KW[r][1]; + C2 = T0[r2 & 255] ^ T1[(r3 >> 8) & 255] ^ T2[(r0 >> 16) & 255] ^ T3[(r1 >> 24) & 255] ^ KW[r][2]; + C3 = T0[r3 & 255] ^ T1[(r0 >> 8) & 255] ^ T2[(r1 >> 16) & 255] ^ T3[(r2 >> 24) & 255] ^ KW[r++][3]; + } + + r0 = T0[C0 & 255] ^ T1[(C1 >> 8) & 255] ^ T2[(C2 >> 16) & 255] ^ T3[(C3 >> 24) & 255] ^ KW[r][0]; + r1 = T0[C1 & 255] ^ T1[(C2 >> 8) & 255] ^ T2[(C3 >> 16) & 255] ^ T3[(C0 >> 24) & 255] ^ KW[r][1]; + r2 = T0[C2 & 255] ^ T1[(C3 >> 8) & 255] ^ T2[(C0 >> 16) & 255] ^ T3[(C1 >> 24) & 255] ^ KW[r][2]; + r3 = T0[C3 & 255] ^ T1[(C0 >> 8) & 255] ^ T2[(C1 >> 16) & 255] ^ T3[(C2 >> 24) & 255] ^ KW[r++][3]; + + // the final round's table is a simple function of S so we don't use a whole other four tables for it + + C0 = (S[r0 & 255] & 255) ^ ((S[(r1 >> 8) & 255] & 255) << 8) ^ ((S[(r2 >> 16) & 255] & 255) << 16) ^ (S[(r3 >> 24) & 255] << 24) ^ KW[r][0]; + C1 = (S[r1 & 255] & 255) ^ ((S[(r2 >> 8) & 255] & 255) << 8) ^ ((S[(r3 >> 16) & 255] & 255) << 16) ^ (S[(r0 >> 24) & 255] << 24) ^ KW[r][1]; + C2 = (S[r2 & 255] & 255) ^ ((S[(r3 >> 8) & 255] & 255) << 8) ^ ((S[(r0 >> 16) & 255] & 255) << 16) ^ (S[(r1 >> 24) & 255] << 24) ^ KW[r][2]; + C3 = (S[r3 & 255] & 255) ^ ((S[(r0 >> 8) & 255] & 255) << 8) ^ ((S[(r1 >> 16) & 255] & 255) << 16) ^ (S[(r2 >> 24) & 255] << 24) ^ KW[r][3]; + + } + + private void decryptBlock(int[][] KW) { + int r0, r1, r2, r3; + + C0 ^= KW[ROUNDS][0]; + C1 ^= KW[ROUNDS][1]; + C2 ^= KW[ROUNDS][2]; + C3 ^= KW[ROUNDS][3]; + + int r = ROUNDS - 1; + + while (r > 1) { + r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] ^ KW[r][0]; + r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] ^ KW[r][1]; + r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] ^ KW[r][2]; + r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] ^ KW[r--][3]; + C0 = Tinv0[r0 & 255] ^ Tinv1[(r3 >> 8) & 255] ^ Tinv2[(r2 >> 16) & 255] ^ Tinv3[(r1 >> 24) & 255] ^ KW[r][0]; + C1 = Tinv0[r1 & 255] ^ Tinv1[(r0 >> 8) & 255] ^ Tinv2[(r3 >> 16) & 255] ^ Tinv3[(r2 >> 24) & 255] ^ KW[r][1]; + C2 = Tinv0[r2 & 255] ^ Tinv1[(r1 >> 8) & 255] ^ Tinv2[(r0 >> 16) & 255] ^ Tinv3[(r3 >> 24) & 255] ^ KW[r][2]; + C3 = Tinv0[r3 & 255] ^ Tinv1[(r2 >> 8) & 255] ^ Tinv2[(r1 >> 16) & 255] ^ Tinv3[(r0 >> 24) & 255] ^ KW[r--][3]; + } + + r0 = Tinv0[C0 & 255] ^ Tinv1[(C3 >> 8) & 255] ^ Tinv2[(C2 >> 16) & 255] ^ Tinv3[(C1 >> 24) & 255] ^ KW[r][0]; + r1 = Tinv0[C1 & 255] ^ Tinv1[(C0 >> 8) & 255] ^ Tinv2[(C3 >> 16) & 255] ^ Tinv3[(C2 >> 24) & 255] ^ KW[r][1]; + r2 = Tinv0[C2 & 255] ^ Tinv1[(C1 >> 8) & 255] ^ Tinv2[(C0 >> 16) & 255] ^ Tinv3[(C3 >> 24) & 255] ^ KW[r][2]; + r3 = Tinv0[C3 & 255] ^ Tinv1[(C2 >> 8) & 255] ^ Tinv2[(C1 >> 16) & 255] ^ Tinv3[(C0 >> 24) & 255] ^ KW[r][3]; + + // the final round's table is a simple function of Si so we don't use a whole other four tables for it + + C0 = (Si[r0 & 255] & 255) ^ ((Si[(r3 >> 8) & 255] & 255) << 8) ^ ((Si[(r2 >> 16) & 255] & 255) << 16) ^ (Si[(r1 >> 24) & 255] << 24) ^ KW[0][0]; + C1 = (Si[r1 & 255] & 255) ^ ((Si[(r0 >> 8) & 255] & 255) << 8) ^ ((Si[(r3 >> 16) & 255] & 255) << 16) ^ (Si[(r2 >> 24) & 255] << 24) ^ KW[0][1]; + C2 = (Si[r2 & 255] & 255) ^ ((Si[(r1 >> 8) & 255] & 255) << 8) ^ ((Si[(r0 >> 16) & 255] & 255) << 16) ^ (Si[(r3 >> 24) & 255] << 24) ^ KW[0][2]; + C3 = (Si[r3 & 255] & 255) ^ ((Si[(r2 >> 8) & 255] & 255) << 8) ^ ((Si[(r1 >> 16) & 255] & 255) << 16) ^ (Si[(r0 >> 24) & 255] << 24) ^ KW[0][3]; + } +} diff --git a/src/org/telegram/mtproto/secure/aes/AESImplementation.java b/src/org/telegram/mtproto/secure/aes/AESImplementation.java new file mode 100644 index 0000000..512c038 --- /dev/null +++ b/src/org/telegram/mtproto/secure/aes/AESImplementation.java @@ -0,0 +1,16 @@ +package org.telegram.mtproto.secure.aes; + +import java.io.IOException; + +/** + * Created by ex3ndr on 12.02.14. + */ +public abstract interface AESImplementation { + public void AES256IGEDecrypt(byte[] src, byte[] dest, int len, byte[] iv, byte[] key); + + public void AES256IGEEncrypt(byte[] src, byte[] dest, int len, byte[] iv, byte[] key); + + public void AES256IGEEncrypt(String sourceFile, String destFile, byte[] iv, byte[] key) throws IOException; + + public void AES256IGEDecrypt(String sourceFile, String destFile, byte[] iv, byte[] key) throws IOException; +} diff --git a/src/org/telegram/mtproto/secure/aes/DefaultAESImplementation.java b/src/org/telegram/mtproto/secure/aes/DefaultAESImplementation.java new file mode 100644 index 0000000..5363893 --- /dev/null +++ b/src/org/telegram/mtproto/secure/aes/DefaultAESImplementation.java @@ -0,0 +1,155 @@ +package org.telegram.mtproto.secure.aes; + +import org.telegram.mtproto.secure.KeyParameter; + +import java.io.*; + +import static org.telegram.mtproto.secure.CryptoUtils.substring; + +/** + * Created by ex3ndr on 12.02.14. + */ +public class DefaultAESImplementation implements AESImplementation { + + @Override + public void AES256IGEDecrypt(byte[] src, byte[] dest, int len, byte[] iv, byte[] key) { + AESFastEngine engine = new AESFastEngine(); + engine.init(false, new KeyParameter(key)); + + int blocksCount = len / 16; + + byte[] curIvX = iv; + byte[] curIvY = iv; + int curIvXOffset = 16; + int curIvYOffset = 0; + + for (int i = 0; i < blocksCount; i++) { + int offset = i * 16; + + for (int j = 0; j < 16; j++) { + dest[offset + j] = (byte) (src[offset + j] ^ curIvX[curIvXOffset + j]); + } + engine.processBlock(dest, offset, dest, offset); + for (int j = 0; j < 16; j++) { + dest[offset + j] = (byte) (dest[offset + j] ^ curIvY[curIvYOffset + j]); + } + + curIvY = src; + curIvYOffset = offset; + curIvX = dest; + curIvXOffset = offset; + + if (i % 31 == 32) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + @Override + public void AES256IGEEncrypt(byte[] src, byte[] dest, int len, byte[] iv, byte[] key) { + AESFastEngine engine = new AESFastEngine(); + engine.init(true, new KeyParameter(key)); + + int blocksCount = len / 16; + + byte[] curIvX = iv; + byte[] curIvY = iv; + int curIvXOffset = 16; + int curIvYOffset = 0; + + for (int i = 0; i < blocksCount; i++) { + + int offset = i * 16; + + for (int j = 0; j < 16; j++) { + dest[offset + j] = (byte) (src[offset + j] ^ curIvY[curIvYOffset + j]); + } + engine.processBlock(dest, offset, dest, offset); + for (int j = 0; j < 16; j++) { + dest[offset + j] = (byte) (dest[offset + j] ^ curIvX[curIvXOffset + j]); + } + + curIvX = src; + curIvXOffset = offset; + curIvY = dest; + curIvYOffset = offset; + } + } + + @Override + public void AES256IGEEncrypt(String sourceFile, String destFile, byte[] iv, byte[] key) throws IOException { + + File src = new File(sourceFile); + File dest = new File(destFile); + + AESFastEngine engine = new AESFastEngine(); + engine.init(true, new KeyParameter(key)); + + byte[] curIvX = substring(iv, 16, 16); + byte[] curIvY = substring(iv, 0, 16); + + BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(src)); + BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(dest)); + byte[] buffer = new byte[16]; + int count; + while ((count = inputStream.read(buffer)) > 0) { + byte[] outData = new byte[16]; + for (int j = 0; j < 16; j++) { + outData[j] = (byte) (buffer[j] ^ curIvY[j]); + } + engine.processBlock(outData, 0, outData, 0); + for (int j = 0; j < 16; j++) { + outData[j] = (byte) (outData[j] ^ curIvX[j]); + } + + curIvX = buffer; + curIvY = outData; + buffer = new byte[16]; + + outputStream.write(outData); + } + outputStream.flush(); + outputStream.close(); + inputStream.close(); + } + + @Override + public void AES256IGEDecrypt(String sourceFile, String destFile, byte[] iv, byte[] key) throws IOException { + File src = new File(sourceFile); + File dest = new File(destFile); + + AESFastEngine engine = new AESFastEngine(); + engine.init(false, new KeyParameter(key)); + + byte[] curIvX = substring(iv, 16, 16); + byte[] curIvY = substring(iv, 0, 16); + + BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(src)); + BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(dest)); + byte[] buffer = new byte[16]; + int count; + while ((count = inputStream.read(buffer)) > 0) { + byte[] outData = new byte[16]; + for (int j = 0; j < 16; j++) { + outData[j] = (byte) (buffer[j] ^ curIvX[j]); + } + engine.processBlock(outData, 0, outData, 0); + for (int j = 0; j < 16; j++) { + outData[j] = (byte) (outData[j] ^ curIvY[j]); + } + + curIvY = buffer; + curIvX = outData; + buffer = new byte[16]; + + outputStream.write(outData); + } + outputStream.flush(); + outputStream.close(); + inputStream.close(); + } +} diff --git a/src/org/telegram/mtproto/secure/pq/PQImplementation.java b/src/org/telegram/mtproto/secure/pq/PQImplementation.java new file mode 100644 index 0000000..efcdb43 --- /dev/null +++ b/src/org/telegram/mtproto/secure/pq/PQImplementation.java @@ -0,0 +1,8 @@ +package org.telegram.mtproto.secure.pq; + +/** + * Created by ex3ndr on 12.02.14. + */ +public interface PQImplementation { + public long findDivider(long src); +} diff --git a/src/org/telegram/mtproto/secure/pq/PQLopatin.java b/src/org/telegram/mtproto/secure/pq/PQLopatin.java new file mode 100644 index 0000000..85c2921 --- /dev/null +++ b/src/org/telegram/mtproto/secure/pq/PQLopatin.java @@ -0,0 +1,73 @@ +package org.telegram.mtproto.secure.pq; + +import java.util.Random; + +/** + * Created by ex3ndr on 12.02.14. + */ +public class PQLopatin implements PQImplementation { + @Override + public long findDivider(long src) { + return findSmallMultiplierLopatin(src); + } + + private long GCD(long a, long b) { + while (a != 0 && b != 0) { + while ((b & 1) == 0) { + b >>= 1; + } + while ((a & 1) == 0) { + a >>= 1; + } + if (a > b) { + a -= b; + } else { + b -= a; + } + } + return b == 0 ? a : b; + } + + private long findSmallMultiplierLopatin(long what) { + Random r = new Random(); + long g = 0; + int it = 0; + for (int i = 0; i < 3; i++) { + int q = (r.nextInt(128) & 15) + 17; + long x = r.nextInt(1000000000) + 1, y = x; + int lim = 1 << (i + 18); + for (int j = 1; j < lim; j++) { + ++it; + long a = x, b = x, c = q; + while (b != 0) { + if ((b & 1) != 0) { + c += a; + if (c >= what) { + c -= what; + } + } + a += a; + if (a >= what) { + a -= what; + } + b >>= 1; + } + x = c; + long z = x < y ? y - x : x - y; + g = GCD(z, what); + if (g != 1) { + break; + } + if ((j & (j - 1)) == 0) { + y = x; + } + } + if (g > 1) { + break; + } + } + + long p = what / g; + return Math.min(p, g); + } +} diff --git a/src/org/telegram/mtproto/secure/pq/PQSolver.java b/src/org/telegram/mtproto/secure/pq/PQSolver.java new file mode 100644 index 0000000..d1c4455 --- /dev/null +++ b/src/org/telegram/mtproto/secure/pq/PQSolver.java @@ -0,0 +1,22 @@ +package org.telegram.mtproto.secure.pq; + +import java.math.BigInteger; + +/** + * Created by ex3ndr on 12.02.14. + */ +public class PQSolver { + private static PQImplementation currentImplementation = new PQLopatin(); + + public static void setCurrentImplementation(PQImplementation implementation) { + currentImplementation = implementation; + } + + public static BigInteger solvePq(BigInteger src) { + return new BigInteger("" + currentImplementation.findDivider(src.longValue())); + } + + private PQSolver() { + + } +} diff --git a/src/org/telegram/mtproto/state/AbsMTProtoState.java b/src/org/telegram/mtproto/state/AbsMTProtoState.java new file mode 100644 index 0000000..3a9f107 --- /dev/null +++ b/src/org/telegram/mtproto/state/AbsMTProtoState.java @@ -0,0 +1,84 @@ +package org.telegram.mtproto.state; + +import org.telegram.mtproto.time.TimeOverlord; + +import java.util.HashMap; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 07.11.13 + * Time: 7:15 + */ +public abstract class AbsMTProtoState { + + public abstract byte[] getAuthKey(); + + public abstract ConnectionInfo[] getAvailableConnections(); + + public abstract KnownSalt[] readKnownSalts(); + + protected abstract void writeKnownSalts(KnownSalt[] salts); + + public void mergeKnownSalts(int currentTime, KnownSalt[] salts) { + KnownSalt[] knownSalts = readKnownSalts(); + HashMap ids = new HashMap(); + for (KnownSalt s : knownSalts) { + if (s.getValidUntil() < currentTime) { + continue; + } + ids.put(s.getSalt(), s); + } + for (KnownSalt s : salts) { + if (s.getValidUntil() < currentTime) { + continue; + } + ids.put(s.getSalt(), s); + } + writeKnownSalts(ids.values().toArray(new KnownSalt[0])); + } + + public void addCurrentSalt(long salt) { + int time = (int) (TimeOverlord.getInstance().getServerTime() / 1000); + mergeKnownSalts(time, new KnownSalt[]{new KnownSalt(time, time + 30 * 60, salt)}); + } + + public void badServerSalt(long salt) { + int time = (int) (TimeOverlord.getInstance().getServerTime() / 1000); + writeKnownSalts(new KnownSalt[]{new KnownSalt(time, time + 30 * 60, salt)}); + } + + public void initialServerSalt(long salt) { + int time = (int) (TimeOverlord.getInstance().getServerTime() / 1000); + writeKnownSalts(new KnownSalt[]{new KnownSalt(time, time + 30 * 60, salt)}); + } + + public long findActualSalt(int time) { + KnownSalt[] knownSalts = readKnownSalts(); + for (KnownSalt salt : knownSalts) { + if (salt.getValidSince() <= time && time <= salt.getValidUntil()) { + return salt.getSalt(); + } + } + + return 0; + } + + public int maximumCachedSalts(int time) { + int count = 0; + for (KnownSalt salt : readKnownSalts()) { + if (salt.getValidSince() > time) { + count++; + } + } + return count; + } + + public int maximumCachedSaltsTime() { + int max = 0; + for (KnownSalt salt : readKnownSalts()) { + max = Math.max(max, salt.getValidUntil()); + } + return max; + } +} diff --git a/src/org/telegram/mtproto/state/ConnectionInfo.java b/src/org/telegram/mtproto/state/ConnectionInfo.java new file mode 100644 index 0000000..c865848 --- /dev/null +++ b/src/org/telegram/mtproto/state/ConnectionInfo.java @@ -0,0 +1,37 @@ +package org.telegram.mtproto.state; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 07.11.13 + * Time: 7:26 + */ +public class ConnectionInfo { + private int id; + private int priority; + private String address; + private int port; + + public ConnectionInfo(int id, int priority, String address, int port) { + this.id = id; + this.priority = priority; + this.address = address; + this.port = port; + } + + public int getPriority() { + return priority; + } + + public String getAddress() { + return address; + } + + public int getPort() { + return port; + } + + public int getId() { + return id; + } +} diff --git a/src/org/telegram/mtproto/state/KnownSalt.java b/src/org/telegram/mtproto/state/KnownSalt.java new file mode 100644 index 0000000..268532a --- /dev/null +++ b/src/org/telegram/mtproto/state/KnownSalt.java @@ -0,0 +1,31 @@ +package org.telegram.mtproto.state; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 07.11.13 + * Time: 7:16 + */ +public class KnownSalt { + private int validSince; + private int validUntil; + private long salt; + + public KnownSalt(int validSince, int validUntil, long salt) { + this.validSince = validSince; + this.validUntil = validUntil; + this.salt = salt; + } + + public int getValidSince() { + return validSince; + } + + public int getValidUntil() { + return validUntil; + } + + public long getSalt() { + return salt; + } +} diff --git a/src/org/telegram/mtproto/state/MemoryProtoState.java b/src/org/telegram/mtproto/state/MemoryProtoState.java new file mode 100644 index 0000000..3b4caee --- /dev/null +++ b/src/org/telegram/mtproto/state/MemoryProtoState.java @@ -0,0 +1,42 @@ +package org.telegram.mtproto.state; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 07.11.13 + * Time: 7:21 + */ +public class MemoryProtoState extends AbsMTProtoState { + + private KnownSalt[] salts = new KnownSalt[0]; + + private String address; + private int port; + private byte[] authKey; + + public MemoryProtoState(byte[] authKey, String address, int port) { + this.authKey = authKey; + this.port = port; + this.address = address; + } + + @Override + public byte[] getAuthKey() { + return authKey; + } + + @Override + public ConnectionInfo[] getAvailableConnections() { + return new ConnectionInfo[]{new ConnectionInfo(0, 0, address, port)}; + } + + @Override + public KnownSalt[] readKnownSalts() { + return salts; + } + + @Override + protected void writeKnownSalts(KnownSalt[] salts) { + this.salts = salts; + } +} diff --git a/src/org/telegram/mtproto/time/TimeOverlord.java b/src/org/telegram/mtproto/time/TimeOverlord.java new file mode 100644 index 0000000..e8bf1bb --- /dev/null +++ b/src/org/telegram/mtproto/time/TimeOverlord.java @@ -0,0 +1,80 @@ +package org.telegram.mtproto.time; + +import org.telegram.mtproto.log.Logger; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 02.11.13 + * Time: 21:35 + */ +public class TimeOverlord { + private static TimeOverlord instance; + + public static synchronized TimeOverlord getInstance() { + if (instance == null) { + instance = new TimeOverlord(); + } + return instance; + } + + private long nanotimeShift; + + private long timeAccuracy = Long.MAX_VALUE; + protected long timeDelta; + + private TimeOverlord() { + nanotimeShift = System.currentTimeMillis() - System.nanoTime() / 1000; + } + + public long createWeakMessageId() { + return (getServerTime() / 1000) << 32; + } + + public long getLocalTime() { + return System.currentTimeMillis(); + } + + public long getServerTime() { + return getLocalTime() + timeDelta; + } + + public long getTimeAccuracy() { + return timeAccuracy; + } + + public long getTimeDelta() { + return timeDelta; + } + + public void setTimeDelta(long timeDelta, long timeAccuracy) { + this.timeDelta = timeDelta; + this.timeAccuracy = timeAccuracy; + } + + public void onForcedServerTimeArrived(long serverTime, long duration) { + timeDelta = serverTime - getLocalTime(); + timeAccuracy = duration; + } + + public void onServerTimeArrived(long serverTime, long duration) { + if (duration < 0) { + return; + } + if (duration < timeAccuracy) { + timeDelta = serverTime - getLocalTime(); + timeAccuracy = duration; + } else if (Math.abs(getLocalTime() - serverTime) > (duration / 2 + timeAccuracy / 2)) { + timeDelta = serverTime - getLocalTime(); + timeAccuracy = duration; + } + } + + public void onMethodExecuted(long sentId, long responseId, long duration) { + if (duration < 0) { + return; + } + + onServerTimeArrived((responseId >> 32) * 1000, duration); + } +} \ No newline at end of file diff --git a/src/org/telegram/mtproto/tl/MTBadMessage.java b/src/org/telegram/mtproto/tl/MTBadMessage.java new file mode 100644 index 0000000..59f15d2 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTBadMessage.java @@ -0,0 +1,27 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLObject; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 21:40 + */ +public abstract class MTBadMessage extends TLObject { + protected long badMsgId; + protected int badMsqSeqno; + protected int errorCode; + + public long getBadMsgId() { + return badMsgId; + } + + public int getBadMsqSeqno() { + return badMsqSeqno; + } + + public int getErrorCode() { + return errorCode; + } +} diff --git a/src/org/telegram/mtproto/tl/MTBadMessageNotification.java b/src/org/telegram/mtproto/tl/MTBadMessageNotification.java new file mode 100644 index 0000000..b075970 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTBadMessageNotification.java @@ -0,0 +1,55 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 8:47 + */ +public class MTBadMessageNotification extends MTBadMessage { + + public static final int CLASS_ID = 0xa7eff811; + + public MTBadMessageNotification(long badMsgId, int badMsqSeqno, int errorCode) { + this.badMsgId = badMsgId; + this.badMsqSeqno = badMsqSeqno; + this.errorCode = errorCode; + } + + public MTBadMessageNotification() { + + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeLong(badMsgId, stream); + writeInt(badMsqSeqno, stream); + writeInt(errorCode, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + badMsgId = readLong(stream); + badMsqSeqno = readInt(stream); + errorCode = readInt(stream); + } + + @Override + public String toString() { + return "bad_msg_notification#a7eff811"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTBadServerSalt.java b/src/org/telegram/mtproto/tl/MTBadServerSalt.java new file mode 100644 index 0000000..0d874a6 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTBadServerSalt.java @@ -0,0 +1,64 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 8:45 + */ +public class MTBadServerSalt extends MTBadMessage { + + public static final int CLASS_ID = 0xedab447b; + + private long newSalt; + + public MTBadServerSalt(long messageId, int seqNo, int errorNo, long newSalt) { + this.badMsgId = messageId; + this.badMsqSeqno = seqNo; + this.errorCode = errorNo; + this.newSalt = newSalt; + } + + public MTBadServerSalt() { + + } + + public long getNewSalt() { + return newSalt; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeLong(badMsgId, stream); + writeInt(badMsqSeqno, stream); + writeInt(errorCode, stream); + writeLong(newSalt, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + badMsgId = readLong(stream); + badMsqSeqno = readInt(stream); + errorCode = readInt(stream); + newSalt = readLong(stream); + } + + @Override + public String toString() { + return "bad_server_salt#edab447b"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTFutureSalt.java b/src/org/telegram/mtproto/tl/MTFutureSalt.java new file mode 100644 index 0000000..e17f06d --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTFutureSalt.java @@ -0,0 +1,71 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 07.11.13 + * Time: 8:00 + */ +public class MTFutureSalt extends TLObject { + + public static final int CLASS_ID = 0x0949d9dc; + + private int validSince; + private int validUntil; + private long salt; + + public MTFutureSalt(int validSince, int validUntil, long salt) { + this.validSince = validSince; + this.validUntil = validUntil; + this.salt = salt; + } + + public MTFutureSalt() { + + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + public int getValidSince() { + return validSince; + } + + public int getValidUntil() { + return validUntil; + } + + public long getSalt() { + return salt; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeInt(validSince, stream); + writeInt(validUntil, stream); + writeLong(salt, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + validSince = readInt(stream); + validUntil = readInt(stream); + salt = readLong(stream); + } + + @Override + public String toString() { + return "future_salt#0949d9dc"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTFutureSalts.java b/src/org/telegram/mtproto/tl/MTFutureSalts.java new file mode 100644 index 0000000..bcf6212 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTFutureSalts.java @@ -0,0 +1,80 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; +import org.telegram.tl.TLVector; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 07.11.13 + * Time: 7:58 + */ +public class MTFutureSalts extends TLObject { + public static final int CLASS_ID = 0xae500895; + + private long requestId; + private int now; + private TLVector salts = new TLVector(); + + public MTFutureSalts(long requestId, int now, TLVector salts) { + this.requestId = requestId; + this.now = now; + this.salts = salts; + } + + public MTFutureSalts() { + + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + public long getRequestId() { + return requestId; + } + + public int getNow() { + return now; + } + + public TLVector getSalts() { + return salts; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeLong(requestId, stream); + writeInt(now, stream); + writeInt(salts.size(), stream); + for (MTFutureSalt salt : salts) { + salt.serializeBody(stream); + } + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + requestId = readLong(stream); + now = readInt(stream); + int count = readInt(stream); + salts.clear(); + for (int i = 0; i < count; i++) { + MTFutureSalt salt = new MTFutureSalt(); + salt.deserializeBody(stream, context); + salts.add(salt); + } + } + + @Override + public String toString() { + return "future_salts#ae500895"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTGetFutureSalts.java b/src/org/telegram/mtproto/tl/MTGetFutureSalts.java new file mode 100644 index 0000000..e8f8489 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTGetFutureSalts.java @@ -0,0 +1,52 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.readInt; +import static org.telegram.tl.StreamingUtils.writeInt; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 07.11.13 + * Time: 7:56 + */ +public class MTGetFutureSalts extends TLObject { + + public static final int CLASS_ID = 0xb921bd04; + + private int num; + + public MTGetFutureSalts(int num) { + this.num = num; + } + + public MTGetFutureSalts() { + + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeInt(num, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + num = readInt(stream); + } + + @Override + public String toString() { + return "get_future_salts#b921bd04"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTInvokeAfter.java b/src/org/telegram/mtproto/tl/MTInvokeAfter.java new file mode 100644 index 0000000..a903611 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTInvokeAfter.java @@ -0,0 +1,50 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created by ex3ndr on 16.12.13. + */ +public class MTInvokeAfter extends TLObject { + public static final int CLASS_ID = 0xcb9f372d; + + private long dependMsgId; + + private byte[] request; + + public MTInvokeAfter(long dependMsgId, byte[] request) { + this.dependMsgId = dependMsgId; + this.request = request; + } + + public long getDependMsgId() { + return dependMsgId; + } + + public byte[] getRequest() { + return request; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeLong(dependMsgId, stream); + writeByteArray(request, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + throw new UnsupportedOperationException("Unable to deserialize invokeAfterMsg#cb9f372d"); + } +} diff --git a/src/org/telegram/mtproto/tl/MTMessage.java b/src/org/telegram/mtproto/tl/MTMessage.java new file mode 100644 index 0000000..322d956 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTMessage.java @@ -0,0 +1,98 @@ +package org.telegram.mtproto.tl; + +import org.telegram.mtproto.util.BytesCache; +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 20:46 + */ +public class MTMessage extends TLObject { + private long messageId; + private int seqNo; + private byte[] content; + private int contentLen; + + public MTMessage(long messageId, int seqNo, byte[] content) { + this(messageId, seqNo, content, content.length); + } + + public MTMessage(long messageId, int seqNo, byte[] content, int contentLen) { + this.messageId = messageId; + this.seqNo = seqNo; + this.content = content; + this.contentLen = contentLen; + } + + public MTMessage() { + + } + + @Override + public int getClassId() { + return 0; + } + + public long getMessageId() { + return messageId; + } + + public void setMessageId(long messageId) { + this.messageId = messageId; + } + + public int getSeqNo() { + return seqNo; + } + + public void setSeqNo(int seqNo) { + this.seqNo = seqNo; + } + + public byte[] getContent() { + return content; + } + + public void setContent(byte[] content) { + this.content = content; + } + + public int getContentLen() { + return contentLen; + } + + public void setContentLen(int contentLen) { + this.contentLen = contentLen; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeLong(messageId, stream); + writeInt(seqNo, stream); + writeInt(content.length, stream); + writeByteArray(content, 0, contentLen, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + messageId = readLong(stream); + seqNo = readInt(stream); + int size = readInt(stream); + content = BytesCache.getInstance().allocate(size); + readBytes(content, 0, size, stream); + } + + @Override + public String toString() { + return "message"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTMessageDetailedInfo.java b/src/org/telegram/mtproto/tl/MTMessageDetailedInfo.java new file mode 100644 index 0000000..847fc72 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTMessageDetailedInfo.java @@ -0,0 +1,78 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 07.11.13 + * Time: 8:40 + */ +public class MTMessageDetailedInfo extends TLObject { + public static final int CLASS_ID = 0x276d3ec6; + + private long msgId; + private long answerMsgId; + private int bytes; + private int state; + + public MTMessageDetailedInfo(long msgId, long answerMsgId, int bytes, int state) { + this.msgId = msgId; + this.answerMsgId = answerMsgId; + this.bytes = bytes; + this.state = state; + } + + public MTMessageDetailedInfo() { + + } + + public long getMsgId() { + return msgId; + } + + public long getAnswerMsgId() { + return answerMsgId; + } + + public int getBytes() { + return bytes; + } + + public int getState() { + return state; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeLong(msgId, stream); + writeLong(answerMsgId, stream); + writeInt(bytes, stream); + writeInt(state, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + msgId = readLong(stream); + answerMsgId = readLong(stream); + bytes = readInt(stream); + state = readInt(stream); + } + + @Override + public String toString() { + return "msg_detailed_info#276d3ec6"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTMessagesContainer.java b/src/org/telegram/mtproto/tl/MTMessagesContainer.java new file mode 100644 index 0000000..3c820b4 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTMessagesContainer.java @@ -0,0 +1,74 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; +import org.telegram.tl.TLVector; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.TreeSet; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 20:53 + */ +public class MTMessagesContainer extends TLObject { + + public static final int CLASS_ID = 0x73f1f8dc; + + private TreeSet messages = new TreeSet(new Comparator() { + @Override + public int compare(MTMessage mtMessage, MTMessage mtMessage2) { + return (int) Math.signum(mtMessage.getMessageId() - mtMessage2.getMessageId()); + } + }); + + public MTMessagesContainer(MTMessage[] messages) { + Collections.addAll(this.messages, messages); + } + + public MTMessagesContainer() { + + } + + public TreeSet getMessages() { + return messages; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeInt(messages.size(), stream); + for (MTMessage message : messages) { + message.serializeBody(stream); + } + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + int size = readInt(stream); + messages.clear(); + for (int i = 0; i < size; i++) { + MTMessage message = new MTMessage(); + message.deserializeBody(stream, context); + messages.add(message); + } + } + + @Override + public String toString() { + return "msg_container#73f1f8dc"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTMsgsAck.java b/src/org/telegram/mtproto/tl/MTMsgsAck.java new file mode 100644 index 0000000..69a1693 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTMsgsAck.java @@ -0,0 +1,69 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLLongVector; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 8:30 + */ +public class MTMsgsAck extends TLObject { + + public static final int CLASS_ID = 0x62d6b459; + + private TLLongVector messages; + + public MTMsgsAck(TLLongVector messages) { + this.messages = messages; + } + + public MTMsgsAck() { + this.messages = new TLLongVector(); + } + + public MTMsgsAck(long[] msgIds) { + this.messages = new TLLongVector(); + for (long id : msgIds) { + this.messages.add(id); + } + } + + public MTMsgsAck(Long[] msgIds) { + this.messages = new TLLongVector(); + Collections.addAll(this.messages, msgIds); + } + + public TLLongVector getMessages() { + return messages; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeTLVector(messages, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + messages = readTLLongVector(stream, context); + } + + @Override + public String toString() { + return "msgs_ack#62d6b459"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTNeedResendMessage.java b/src/org/telegram/mtproto/tl/MTNeedResendMessage.java new file mode 100644 index 0000000..f3e1656 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTNeedResendMessage.java @@ -0,0 +1,70 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLLongVector; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; + +import static org.telegram.tl.StreamingUtils.readTLLongVector; +import static org.telegram.tl.StreamingUtils.writeTLVector; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 07.11.13 + * Time: 8:50 + */ +public class MTNeedResendMessage extends TLObject { + + public static final int CLASS_ID = 0x7d861a08; + + private TLLongVector messages; + + public MTNeedResendMessage(TLLongVector messages) { + this.messages = messages; + } + + public MTNeedResendMessage() { + this.messages = new TLLongVector(); + } + + public MTNeedResendMessage(long[] msgIds) { + this.messages = new TLLongVector(); + for (long id : msgIds) { + this.messages.add(id); + } + } + + public MTNeedResendMessage(Long[] msgIds) { + this.messages = new TLLongVector(); + Collections.addAll(this.messages, msgIds); + } + + public TLLongVector getMessages() { + return messages; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeTLVector(messages, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + messages = readTLLongVector(stream, context); + } + + @Override + public String toString() { + return "msg_resend_req#7d861a08"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTNewMessageDetailedInfo.java b/src/org/telegram/mtproto/tl/MTNewMessageDetailedInfo.java new file mode 100644 index 0000000..07749e1 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTNewMessageDetailedInfo.java @@ -0,0 +1,71 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 07.11.13 + * Time: 8:37 + */ +public class MTNewMessageDetailedInfo extends TLObject { + + public static final int CLASS_ID = 0x809db6df; + + private long answerMsgId; + private int bytes; + private int status; + + public MTNewMessageDetailedInfo(long answerMsgId, int bytes, int status) { + this.answerMsgId = answerMsgId; + this.bytes = bytes; + this.status = status; + } + + public MTNewMessageDetailedInfo() { + + } + + public long getAnswerMsgId() { + return answerMsgId; + } + + public int getBytes() { + return bytes; + } + + public int getStatus() { + return status; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeLong(answerMsgId, stream); + writeInt(bytes, stream); + writeInt(status, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + answerMsgId = readLong(stream); + bytes = readInt(stream); + status = readInt(stream); + } + + @Override + public String toString() { + return "msg_new_detailed_info#809db6df"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTNewSessionCreated.java b/src/org/telegram/mtproto/tl/MTNewSessionCreated.java new file mode 100644 index 0000000..ce0a2df --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTNewSessionCreated.java @@ -0,0 +1,71 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 8:35 + */ +public class MTNewSessionCreated extends TLObject { + + public static final int CLASS_ID = 0x9ec20908; + + private long firstMsgId; + private byte[] uniqId; + private byte[] serverSalt; + + public MTNewSessionCreated(long firstMsgId, byte[] uniqId, byte[] serverSalt) { + this.firstMsgId = firstMsgId; + this.uniqId = uniqId; + this.serverSalt = serverSalt; + } + + public MTNewSessionCreated() { + + } + + public long getFirstMsgId() { + return firstMsgId; + } + + public byte[] getUniqId() { + return uniqId; + } + + public byte[] getServerSalt() { + return serverSalt; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeLong(firstMsgId, stream); + writeByteArray(uniqId, stream); + writeByteArray(serverSalt, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + firstMsgId = readLong(stream); + uniqId = readBytes(8, stream); + serverSalt = readBytes(8, stream); + } + + @Override + public String toString() { + return "new_session_created#9ec20908"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTPing.java b/src/org/telegram/mtproto/tl/MTPing.java new file mode 100644 index 0000000..19d45a9 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTPing.java @@ -0,0 +1,50 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 8:22 + */ +public class MTPing extends TLObject { + public static final int CLASS_ID = 0x7abe77ec; + + private long pingId; + + public MTPing(long pingId) { + this.pingId = pingId; + } + + public MTPing() { + + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeLong(pingId, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + pingId = readLong(stream); + } + + @Override + public String toString() { + return "ping#7abe77ec"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTPingDelayDisconnect.java b/src/org/telegram/mtproto/tl/MTPingDelayDisconnect.java new file mode 100644 index 0000000..390d94b --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTPingDelayDisconnect.java @@ -0,0 +1,62 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 8:22 + */ +public class MTPingDelayDisconnect extends TLObject { + public static final int CLASS_ID = 0xf3427b8c; + + private long pingId; + private int disconnectDelay; + + public MTPingDelayDisconnect(long pingId, int disconnectDelay) { + this.pingId = pingId; + this.disconnectDelay = disconnectDelay; + } + + public MTPingDelayDisconnect() { + + } + + public long getPingId() { + return pingId; + } + + public int getDisconnectDelay() { + return disconnectDelay; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeLong(pingId, stream); + writeInt(disconnectDelay, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + pingId = readLong(stream); + disconnectDelay = readInt(stream); + } + + @Override + public String toString() { + return "ping_delay_disconnect#f3427b8c"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTPong.java b/src/org/telegram/mtproto/tl/MTPong.java new file mode 100644 index 0000000..f89ed64 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTPong.java @@ -0,0 +1,62 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 8:24 + */ +public class MTPong extends TLObject { + + public static final int CLASS_ID = 0x347773c5; + + private long messageId; + private long pingId; + + public MTPong(long messageId, long pingId) { + this.messageId = messageId; + this.pingId = pingId; + } + + public MTPong() { + } + + public long getMessageId() { + return messageId; + } + + public long getPingId() { + return pingId; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeLong(messageId, stream); + writeLong(pingId, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + messageId = readLong(stream); + pingId = readLong(stream); + } + + @Override + public String toString() { + return "pong#347773c5"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTProtoContext.java b/src/org/telegram/mtproto/tl/MTProtoContext.java new file mode 100644 index 0000000..b6f734f --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTProtoContext.java @@ -0,0 +1,45 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 8:22 + */ +public class MTProtoContext extends TLContext { + + // High performance singleton + private static class ContextHolder { + public static final MTProtoContext HOLDER_INSTANCE = new MTProtoContext(); + } + + public static MTProtoContext getInstance() { + return ContextHolder.HOLDER_INSTANCE; + } + + private MTProtoContext() { + + } + + @Override + protected void init() { + registerClass(MTPing.CLASS_ID, MTPing.class); + registerClass(MTPingDelayDisconnect.CLASS_ID, MTPingDelayDisconnect.class); + registerClass(MTPong.CLASS_ID, MTPong.class); + registerClass(MTMsgsAck.CLASS_ID, MTMsgsAck.class); + registerClass(MTNewSessionCreated.CLASS_ID, MTNewSessionCreated.class); + registerClass(MTBadMessageNotification.CLASS_ID, MTBadMessageNotification.class); + registerClass(MTBadServerSalt.CLASS_ID, MTBadServerSalt.class); + registerClass(MTNewMessageDetailedInfo.CLASS_ID, MTNewMessageDetailedInfo.class); + registerClass(MTMessageDetailedInfo.CLASS_ID, MTMessageDetailedInfo.class); + registerClass(MTNeedResendMessage.CLASS_ID, MTNeedResendMessage.class); + registerClass(MTMessagesContainer.CLASS_ID, MTMessagesContainer.class); + registerClass(MTRpcError.CLASS_ID, MTRpcError.class); + registerClass(MTRpcResult.CLASS_ID, MTRpcResult.class); + registerClass(MTGetFutureSalts.CLASS_ID, MTGetFutureSalts.class); + registerClass(MTFutureSalt.CLASS_ID, MTFutureSalt.class); + registerClass(MTFutureSalts.CLASS_ID, MTFutureSalts.class); + } +} diff --git a/src/org/telegram/mtproto/tl/MTRpcError.java b/src/org/telegram/mtproto/tl/MTRpcError.java new file mode 100644 index 0000000..fc24bc9 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTRpcError.java @@ -0,0 +1,79 @@ +package org.telegram.mtproto.tl; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 21:42 + */ +public class MTRpcError extends TLObject { + + private static final Pattern REGEXP_PATTERN = Pattern.compile("[A-Z_0-9]+"); + + public static final int CLASS_ID = 0x2144ca19; + + private int errorCode; + + private String message; + + public MTRpcError(int errorCode, String message) { + this.errorCode = errorCode; + this.message = message; + } + + public MTRpcError() { + + } + + public String getErrorTag() { + if (message == null) { + return "DEFAULT"; + } + Matcher matcher = REGEXP_PATTERN.matcher(message); + if (matcher.find()) { + return matcher.group(); + } + return "DEFAULT"; + } + + public int getErrorCode() { + return errorCode; + } + + public String getMessage() { + return message; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeInt(errorCode, stream); + writeTLString(message, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + errorCode = readInt(stream); + message = readTLString(stream); + } + + @Override + public String toString() { + return "rpc_error#2144ca19"; + } +} diff --git a/src/org/telegram/mtproto/tl/MTRpcResult.java b/src/org/telegram/mtproto/tl/MTRpcResult.java new file mode 100644 index 0000000..17078b6 --- /dev/null +++ b/src/org/telegram/mtproto/tl/MTRpcResult.java @@ -0,0 +1,72 @@ +package org.telegram.mtproto.tl; + +import org.telegram.mtproto.util.BytesCache; +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 22:06 + */ +public class MTRpcResult extends TLObject { + + public static final int CLASS_ID = 0xf35c6d01; + + private long messageId; + private byte[] content; + private int contentLen; + + public MTRpcResult(long messageId, byte[] content, int contentLen) { + this.messageId = messageId; + this.content = content; + this.contentLen = contentLen; + } + + public MTRpcResult() { + + } + + public long getMessageId() { + return messageId; + } + + public byte[] getContent() { + return content; + } + + public int getContentLen() { + return contentLen; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeLong(messageId, stream); + writeByteArray(content, 0, contentLen, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + messageId = readLong(stream); + int contentSize = stream.available(); + content = BytesCache.getInstance().allocate(contentSize); + readBytes(content, 0, contentSize, stream); + } + + @Override + public String toString() { + return "rpc_result#f35c6d01"; + } +} diff --git a/src/org/telegram/mtproto/tl/pq/ClientDhInner.java b/src/org/telegram/mtproto/tl/pq/ClientDhInner.java new file mode 100644 index 0000000..ac25561 --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/ClientDhInner.java @@ -0,0 +1,73 @@ +package org.telegram.mtproto.tl.pq; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 7:32 + */ +public class ClientDhInner extends TLObject { + protected byte[] nonce; + protected byte[] serverNonce; + protected long retryId; + protected byte[] gb; + + public static final int CLASS_ID = 0x6643b654; + + public ClientDhInner(byte[] nonce, byte[] serverNonce, long retryId, byte[] gb) { + this.nonce = nonce; + this.serverNonce = serverNonce; + this.retryId = retryId; + this.gb = gb; + } + + public ClientDhInner() { + + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + public byte[] getNonce() { + return nonce; + } + + public byte[] getServerNonce() { + return serverNonce; + } + + public long getRetryId() { + return retryId; + } + + public byte[] getGb() { + return gb; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeByteArray(nonce, stream); + writeByteArray(serverNonce, stream); + writeLong(retryId, stream); + writeTLBytes(gb, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + nonce = readBytes(16, stream); + serverNonce = readBytes(16, stream); + retryId = readLong(stream); + gb = readTLBytes(stream); + } +} diff --git a/src/org/telegram/mtproto/tl/pq/DhGenFailure.java b/src/org/telegram/mtproto/tl/pq/DhGenFailure.java new file mode 100644 index 0000000..86df27b --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/DhGenFailure.java @@ -0,0 +1,28 @@ +package org.telegram.mtproto.tl.pq; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 7:20 + */ +public class DhGenFailure extends DhGenResult { + public static final int CLASS_ID = 0xa69dae02; + + public DhGenFailure(byte[] nonce, byte[] serverNonce, byte[] newNonceHash) { + super(nonce, serverNonce, newNonceHash); + } + + public DhGenFailure() { + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public String toString() { + return "dh_gen_fail#a69dae02"; + } +} diff --git a/src/org/telegram/mtproto/tl/pq/DhGenOk.java b/src/org/telegram/mtproto/tl/pq/DhGenOk.java new file mode 100644 index 0000000..8f9c862 --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/DhGenOk.java @@ -0,0 +1,36 @@ +package org.telegram.mtproto.tl.pq; + +import org.telegram.tl.TLContext; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 7:20 + */ +public class DhGenOk extends DhGenResult { + public static final int CLASS_ID = 0x3bcbf734; + + public DhGenOk(byte[] nonce, byte[] serverNonce, byte[] newNonceHash) { + super(nonce, serverNonce, newNonceHash); + } + + public DhGenOk() { + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public String toString() { + return "dh_gen_ok#3bcbf734"; + } +} diff --git a/src/org/telegram/mtproto/tl/pq/DhGenResult.java b/src/org/telegram/mtproto/tl/pq/DhGenResult.java new file mode 100644 index 0000000..d16e90e --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/DhGenResult.java @@ -0,0 +1,60 @@ +package org.telegram.mtproto.tl.pq; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.readBytes; +import static org.telegram.tl.StreamingUtils.writeByteArray; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 7:19 + */ +public abstract class DhGenResult extends TLObject { + protected byte[] nonce; + protected byte[] serverNonce; + protected byte[] newNonceHash; + + protected DhGenResult(byte[] nonce, byte[] serverNonce, byte[] newNonceHash) { + this.nonce = nonce; + this.serverNonce = serverNonce; + this.newNonceHash = newNonceHash; + } + + + public DhGenResult() { + + } + + public byte[] getNonce() { + return nonce; + } + + public byte[] getServerNonce() { + return serverNonce; + } + + public byte[] getNewNonceHash() { + return newNonceHash; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeByteArray(nonce, stream); + writeByteArray(serverNonce, stream); + writeByteArray(newNonceHash, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + nonce = readBytes(16, stream); + serverNonce = readBytes(16, stream); + newNonceHash = readBytes(16, stream); + } +} diff --git a/src/org/telegram/mtproto/tl/pq/DhGenRetry.java b/src/org/telegram/mtproto/tl/pq/DhGenRetry.java new file mode 100644 index 0000000..8b30510 --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/DhGenRetry.java @@ -0,0 +1,28 @@ +package org.telegram.mtproto.tl.pq; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 7:20 + */ +public class DhGenRetry extends DhGenResult { + public static final int CLASS_ID = 0x46dc1fb9; + + public DhGenRetry(byte[] nonce, byte[] serverNonce, byte[] newNonceHash) { + super(nonce, serverNonce, newNonceHash); + } + + public DhGenRetry() { + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public String toString() { + return "dh_gen_retry#46dc1fb9"; + } +} diff --git a/src/org/telegram/mtproto/tl/pq/PQInner.java b/src/org/telegram/mtproto/tl/pq/PQInner.java new file mode 100644 index 0000000..f1120ba --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/PQInner.java @@ -0,0 +1,89 @@ +package org.telegram.mtproto.tl.pq; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 6:20 + */ +public class PQInner extends TLObject { + public static final int CLASS_ID = 0x83c95aec; + + protected byte[] pq; + protected byte[] p; + protected byte[] q; + protected byte[] nonce; + protected byte[] serverNonce; + protected byte[] newNonce; + + public PQInner(byte[] pq, byte[] p, byte[] q, byte[] nonce, byte[] serverNonce, byte[] newNonce) { + this.pq = pq; + this.p = p; + this.q = q; + this.nonce = nonce; + this.serverNonce = serverNonce; + this.newNonce = newNonce; + } + + public PQInner() { + + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + public byte[] getPq() { + return pq; + } + + public byte[] getP() { + return p; + } + + public byte[] getQ() { + return q; + } + + public byte[] getNonce() { + return nonce; + } + + public byte[] getServerNonce() { + return serverNonce; + } + + public byte[] getNewNonce() { + return newNonce; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeTLBytes(pq, stream); + writeTLBytes(p, stream); + writeTLBytes(q, stream); + writeByteArray(nonce, stream); + writeByteArray(serverNonce, stream); + writeByteArray(newNonce, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + pq = readTLBytes(stream); + p = readTLBytes(stream); + q = readTLBytes(stream); + nonce = readBytes(16, stream); + serverNonce = readBytes(16, stream); + newNonce = readBytes(32, stream); + } +} diff --git a/src/org/telegram/mtproto/tl/pq/ReqDhParams.java b/src/org/telegram/mtproto/tl/pq/ReqDhParams.java new file mode 100644 index 0000000..b125c0f --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/ReqDhParams.java @@ -0,0 +1,111 @@ +package org.telegram.mtproto.tl.pq; + +import org.telegram.tl.DeserializeException; +import org.telegram.tl.TLContext; +import org.telegram.tl.TLMethod; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 6:26 + */ +public class ReqDhParams extends TLMethod { + + public static final int CLASS_ID = 0xd712e4be; + + @Override + public ServerDhParams deserializeResponse(InputStream stream, TLContext context) throws IOException { + TLObject response = context.deserializeMessage(stream); + + if (response == null) { + throw new DeserializeException("Unable to deserialize response"); + } + if (!(response instanceof ServerDhParams)) { + throw new DeserializeException("Response has incorrect type"); + } + + return (ServerDhParams) response; + } + + protected byte[] nonce; + protected byte[] serverNonce; + protected byte[] p; + protected byte[] q; + protected long fingerPrint; + protected byte[] encryptedData; + + public ReqDhParams(byte[] nonce, byte[] serverNonce, byte[] p, byte[] q, long fingerPrint, byte[] encryptedData) { + this.nonce = nonce; + this.serverNonce = serverNonce; + this.p = p; + this.q = q; + this.fingerPrint = fingerPrint; + this.encryptedData = encryptedData; + } + + public ReqDhParams() { + + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + public byte[] getNonce() { + return nonce; + } + + public byte[] getServerNonce() { + return serverNonce; + } + + public byte[] getP() { + return p; + } + + public byte[] getQ() { + return q; + } + + public long getFingerPrint() { + return fingerPrint; + } + + public byte[] getEncryptedData() { + return encryptedData; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeByteArray(nonce, stream); + writeByteArray(serverNonce, stream); + writeTLBytes(p, stream); + writeTLBytes(q, stream); + writeLong(fingerPrint, stream); + writeTLBytes(encryptedData, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + nonce = readBytes(16, stream); + serverNonce = readBytes(16, stream); + p = readTLBytes(stream); + q = readTLBytes(stream); + fingerPrint = readLong(stream); + encryptedData = readTLBytes(stream); + } + + @Override + public String toString() { + return "req_DH_params#d712e4be"; + } +} diff --git a/src/org/telegram/mtproto/tl/pq/ReqPQ.java b/src/org/telegram/mtproto/tl/pq/ReqPQ.java new file mode 100644 index 0000000..683db08 --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/ReqPQ.java @@ -0,0 +1,80 @@ +package org.telegram.mtproto.tl.pq; + +import static org.telegram.tl.StreamingUtils.*; + +import org.telegram.tl.DeserializeException; +import org.telegram.tl.TLContext; +import org.telegram.tl.TLMethod; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 4:17 + */ +public class ReqPQ extends TLMethod { + + public static final int CLASS_ID = 0x60469778; + + protected byte[] nonce; + + public ReqPQ(byte[] nonce) { + if (nonce == null || nonce.length != 16) { + throw new IllegalArgumentException("nonce might be not null and 16 bytes length"); + } + this.nonce = nonce; + } + + public ReqPQ() { + + } + + @Override + public ResPQ deserializeResponse(InputStream stream, TLContext context) throws IOException { + TLObject response = context.deserializeMessage(stream); + if (response == null) { + throw new DeserializeException("Unable to deserialize response"); + } + if (!(response instanceof ResPQ)) { + throw new DeserializeException("Response has incorrect type"); + } + + return (ResPQ) response; + } + + public byte[] getNonce() { + return nonce; + } + + public void setNonce(byte[] nonce) { + if (nonce == null || nonce.length != 16) { + throw new IllegalArgumentException("nonce might be not null and 16 bytes length"); + } + this.nonce = nonce; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeByteArray(nonce, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + nonce = readBytes(16, stream); + } + + @Override + public String toString() { + return "req_pq#60469778"; + } +} diff --git a/src/org/telegram/mtproto/tl/pq/ReqSetDhClientParams.java b/src/org/telegram/mtproto/tl/pq/ReqSetDhClientParams.java new file mode 100644 index 0000000..4e640fc --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/ReqSetDhClientParams.java @@ -0,0 +1,85 @@ +package org.telegram.mtproto.tl.pq; + +import org.telegram.tl.DeserializeException; +import org.telegram.tl.TLContext; +import org.telegram.tl.TLMethod; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 7:31 + */ +public class ReqSetDhClientParams extends TLMethod { + + public static final int CLASS_ID = 0xf5045f1f; + + @Override + public DhGenResult deserializeResponse(InputStream stream, TLContext context) throws IOException { + TLObject response = context.deserializeMessage(stream); + if (response == null) { + throw new DeserializeException("Unable to deserialize response"); + } + if (!(response instanceof DhGenResult)) { + throw new DeserializeException("Response has incorrect type"); + } + return (DhGenResult) response; + } + + protected byte[] nonce; + protected byte[] serverNonce; + protected byte[] encrypted; + + public ReqSetDhClientParams(byte[] nonce, byte[] serverNonce, byte[] encrypted) { + this.nonce = nonce; + this.serverNonce = serverNonce; + this.encrypted = encrypted; + } + + public ReqSetDhClientParams() { + + } + + public byte[] getNonce() { + return nonce; + } + + public byte[] getServerNonce() { + return serverNonce; + } + + public byte[] getEncrypted() { + return encrypted; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeByteArray(nonce, stream); + writeByteArray(serverNonce, stream); + writeTLBytes(encrypted, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + nonce = readBytes(16, stream); + serverNonce = readBytes(16, stream); + encrypted = readTLBytes(stream); + } + + @Override + public String toString() { + return "set_client_DH_params#f5045f1f"; + } +} \ No newline at end of file diff --git a/src/org/telegram/mtproto/tl/pq/ResPQ.java b/src/org/telegram/mtproto/tl/pq/ResPQ.java new file mode 100644 index 0000000..def1202 --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/ResPQ.java @@ -0,0 +1,95 @@ +package org.telegram.mtproto.tl.pq; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLLongVector; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 5:31 + */ +public class ResPQ extends TLObject { + + public static final int CLASS_ID = 0x05162463; + + protected byte[] nonce; + protected byte[] serverNonce; + protected byte[] pq; + protected TLLongVector fingerprints; + + public ResPQ(byte[] nonce, byte[] serverNonce, byte[] pq, TLLongVector fingerprints) { + this.nonce = nonce; + this.serverNonce = serverNonce; + this.pq = pq; + this.fingerprints = fingerprints; + } + + public ResPQ() { + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + public byte[] getNonce() { + return nonce; + } + + public void setNonce(byte[] nonce) { + this.nonce = nonce; + } + + public byte[] getServerNonce() { + return serverNonce; + } + + public void setServerNonce(byte[] serverNonce) { + this.serverNonce = serverNonce; + } + + public byte[] getPq() { + return pq; + } + + public void setPq(byte[] pq) { + this.pq = pq; + } + + public TLLongVector getFingerprints() { + return fingerprints; + } + + public void setFingerprints(TLLongVector fingerprints) { + this.fingerprints = fingerprints; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeByteArray(nonce, stream); + writeByteArray(serverNonce, stream); + writeTLBytes(pq, stream); + writeTLVector(fingerprints, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + nonce = readBytes(16, stream); + serverNonce = readBytes(16, stream); + pq = readTLBytes(stream); + fingerprints = readTLLongVector(stream, context); + } + + @Override + public String toString() { + return "resPQ#05162463"; + } +} diff --git a/src/org/telegram/mtproto/tl/pq/ServerDhFailure.java b/src/org/telegram/mtproto/tl/pq/ServerDhFailure.java new file mode 100644 index 0000000..f9405af --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/ServerDhFailure.java @@ -0,0 +1,82 @@ +package org.telegram.mtproto.tl.pq; + +import org.telegram.tl.TLContext; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 6:31 + */ +public class ServerDhFailure extends ServerDhParams { + + public static final int CLASS_ID = 0x79cb045d; + + protected byte[] nonce; + protected byte[] serverNonce; + protected byte[] newNonceHash; + + public ServerDhFailure(byte[] nonce, byte[] serverNonce, byte[] newNonceHash) { + this.nonce = nonce; + this.serverNonce = serverNonce; + this.newNonceHash = newNonceHash; + } + + public ServerDhFailure() { + + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + public byte[] getNonce() { + return nonce; + } + + public void setNonce(byte[] nonce) { + this.nonce = nonce; + } + + public byte[] getServerNonce() { + return serverNonce; + } + + public void setServerNonce(byte[] serverNonce) { + this.serverNonce = serverNonce; + } + + public byte[] getNewNonceHash() { + return newNonceHash; + } + + public void setNewNonceHash(byte[] newNonceHash) { + this.newNonceHash = newNonceHash; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeByteArray(nonce, stream); + writeByteArray(serverNonce, stream); + writeByteArray(newNonceHash, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + nonce = readBytes(16, stream); + serverNonce = readBytes(16, stream); + newNonceHash = readBytes(16, stream); + } + + @Override + public String toString() { + return "server_DH_params_fail#79cb045d"; + } +} diff --git a/src/org/telegram/mtproto/tl/pq/ServerDhInner.java b/src/org/telegram/mtproto/tl/pq/ServerDhInner.java new file mode 100644 index 0000000..4ba88ae --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/ServerDhInner.java @@ -0,0 +1,89 @@ +package org.telegram.mtproto.tl.pq; + +import org.telegram.tl.TLContext; +import org.telegram.tl.TLObject; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 6:56 + */ +public class ServerDhInner extends TLObject { + public static final int CLASS_ID = 0xb5890dba; + + protected byte[] nonce; + protected byte[] serverNonce; + protected int g; + protected byte[] dhPrime; + protected byte[] g_a; + protected int serverTime; + + public ServerDhInner(byte[] nonce, byte[] serverNonce, int g, byte[] dhPrime, byte[] g_a, int serverTime) { + this.nonce = nonce; + this.serverNonce = serverNonce; + this.g = g; + this.dhPrime = dhPrime; + this.g_a = g_a; + this.serverTime = serverTime; + } + + public ServerDhInner() { + + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + public byte[] getNonce() { + return nonce; + } + + public byte[] getServerNonce() { + return serverNonce; + } + + public int getG() { + return g; + } + + public byte[] getDhPrime() { + return dhPrime; + } + + public byte[] getG_a() { + return g_a; + } + + public int getServerTime() { + return serverTime; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeByteArray(nonce, stream); + writeByteArray(serverNonce, stream); + writeInt(g, stream); + writeTLBytes(dhPrime, stream); + writeTLBytes(g_a, stream); + writeInt(serverTime, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + nonce = readBytes(16, stream); + serverNonce = readBytes(16, stream); + g = readInt(stream); + dhPrime = readTLBytes(stream); + g_a = readTLBytes(stream); + serverTime = readInt(stream); + } +} diff --git a/src/org/telegram/mtproto/tl/pq/ServerDhOk.java b/src/org/telegram/mtproto/tl/pq/ServerDhOk.java new file mode 100644 index 0000000..2587736 --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/ServerDhOk.java @@ -0,0 +1,70 @@ +package org.telegram.mtproto.tl.pq; + +import org.telegram.tl.TLContext; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.telegram.tl.StreamingUtils.*; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 6:29 + */ +public class ServerDhOk extends ServerDhParams { + + public static final int CLASS_ID = 0xd0e8075c; + + protected byte[] nonce; + protected byte[] serverNonce; + protected byte[] encryptedAnswer; + + public ServerDhOk(byte[] nonce, byte[] serverNonce, byte[] encryptedAnswer) { + this.nonce = nonce; + this.serverNonce = serverNonce; + this.encryptedAnswer = encryptedAnswer; + } + + public ServerDhOk() { + + } + + public byte[] getNonce() { + return nonce; + } + + public byte[] getServerNonce() { + return serverNonce; + } + + public byte[] getEncryptedAnswer() { + return encryptedAnswer; + } + + @Override + public int getClassId() { + return CLASS_ID; + } + + @Override + public void serializeBody(OutputStream stream) throws IOException { + writeByteArray(nonce, stream); + writeByteArray(serverNonce, stream); + writeTLBytes(encryptedAnswer, stream); + } + + @Override + public void deserializeBody(InputStream stream, TLContext context) throws IOException { + nonce = readBytes(16, stream); + serverNonce = readBytes(16, stream); + encryptedAnswer = readTLBytes(stream); + } + + @Override + public String toString() { + return "server_DH_params_ok#d0e8075c"; + } +} diff --git a/src/org/telegram/mtproto/tl/pq/ServerDhParams.java b/src/org/telegram/mtproto/tl/pq/ServerDhParams.java new file mode 100644 index 0000000..9f5e984 --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/ServerDhParams.java @@ -0,0 +1,12 @@ +package org.telegram.mtproto.tl.pq; + +import org.telegram.tl.TLObject; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 6:27 + */ +public abstract class ServerDhParams extends TLObject { +} diff --git a/src/org/telegram/mtproto/tl/pq/TLInitContext.java b/src/org/telegram/mtproto/tl/pq/TLInitContext.java new file mode 100644 index 0000000..2d6c2f2 --- /dev/null +++ b/src/org/telegram/mtproto/tl/pq/TLInitContext.java @@ -0,0 +1,26 @@ +package org.telegram.mtproto.tl.pq; + +import org.telegram.tl.TLContext; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 5:29 + */ +public class TLInitContext extends TLContext { + @Override + protected void init() { + registerClass(ReqPQ.CLASS_ID, ReqPQ.class); + registerClass(ResPQ.CLASS_ID, ResPQ.class); + registerClass(ReqDhParams.CLASS_ID, ReqDhParams.class); + registerClass(ServerDhOk.CLASS_ID, ServerDhOk.class); + registerClass(ServerDhFailure.CLASS_ID, ServerDhFailure.class); + registerClass(ServerDhInner.CLASS_ID, ServerDhInner.class); + registerClass(DhGenOk.CLASS_ID, DhGenOk.class); + registerClass(DhGenFailure.CLASS_ID, DhGenFailure.class); + registerClass(DhGenRetry.CLASS_ID, DhGenRetry.class); + registerClass(ReqSetDhClientParams.CLASS_ID, ReqSetDhClientParams.class); + registerClass(ClientDhInner.CLASS_ID, ClientDhInner.class); + } +} diff --git a/src/org/telegram/mtproto/transport/ConnectionType.java b/src/org/telegram/mtproto/transport/ConnectionType.java new file mode 100644 index 0000000..2d6ad92 --- /dev/null +++ b/src/org/telegram/mtproto/transport/ConnectionType.java @@ -0,0 +1,36 @@ +package org.telegram.mtproto.transport; + +/** + * Created by ex3ndr on 26.11.13. + */ +public class ConnectionType { + public static final int TYPE_TCP = 0; + + private int id; + private String host; + private int port; + private int connectionType; + + public ConnectionType(int id, String host, int port, int connectionType) { + this.id = id; + this.host = host; + this.port = port; + this.connectionType = connectionType; + } + + public int getId() { + return id; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public int getConnectionType() { + return connectionType; + } +} diff --git a/src/org/telegram/mtproto/transport/PlainTcpConnection.java b/src/org/telegram/mtproto/transport/PlainTcpConnection.java new file mode 100644 index 0000000..5963a1b --- /dev/null +++ b/src/org/telegram/mtproto/transport/PlainTcpConnection.java @@ -0,0 +1,91 @@ +package org.telegram.mtproto.transport; + +import org.telegram.mtproto.log.Logger; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.Socket; + +import static org.telegram.tl.StreamingUtils.readBytes; +import static org.telegram.tl.StreamingUtils.writeByte; +import static org.telegram.tl.StreamingUtils.writeByteArray; + +/** + * Created with IntelliJ IDEA. + * User: ex3ndr + * Date: 03.11.13 + * Time: 5:04 + */ +public class PlainTcpConnection { + private static final String TAG = "PlainTcpConnection"; + + private static final int CONNECTION_TIMEOUT = 5 * 1000; + + private Socket socket; + + private boolean isBroken; + + public PlainTcpConnection(String ip, int port) throws IOException { + this.socket = new Socket(); + this.socket.connect(new InetSocketAddress(ip, port), CONNECTION_TIMEOUT); + this.socket.setKeepAlive(true); + this.socket.setTcpNoDelay(true); + this.socket.getOutputStream().write(0xef); + this.isBroken = false; + } + + public Socket getSocket() { + return socket; + } + + private byte[] readMessage() throws IOException { + InputStream stream = socket.getInputStream(); + int headerLen = readByte(stream); + + if (headerLen == 0x7F) { + headerLen = readByte(stream) + (readByte(stream) << 8) + (readByte(stream) << 16); + } + int len = headerLen * 4; + return readBytes(len, stream); + } + + private void writeMessage(byte[] request) throws IOException { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + if (request.length / 4 >= 0x7F) { + int len = request.length / 4; + writeByte(0x7F, stream); + writeByte(len & 0xFF, stream); + writeByte((len >> 8) & 0xFF, stream); + writeByte((len >> 16) & 0xFF, stream); + } else { + writeByte(request.length / 4, stream); + } + writeByteArray(request, stream); + byte[] pkg = stream.toByteArray(); + socket.getOutputStream().write(pkg, 0, pkg.length); + socket.getOutputStream().flush(); + } + + public byte[] executeMethod(byte[] request) throws IOException { + writeMessage(request); + return readMessage(); + } + + public void destroy() { + try { + socket.close(); + } catch (IOException e) { + Logger.e(TAG, e); + } + } + + private int readByte(InputStream stream) throws IOException { + int res = stream.read(); + if (res < 0) { + throw new IOException(); + } + return res; + } +} diff --git a/src/org/telegram/mtproto/transport/TcpContext.java b/src/org/telegram/mtproto/transport/TcpContext.java new file mode 100644 index 0000000..165d2b3 --- /dev/null +++ b/src/org/telegram/mtproto/transport/TcpContext.java @@ -0,0 +1,652 @@ +package org.telegram.mtproto.transport; + +import org.telegram.mtproto.MTProto; +import org.telegram.mtproto.log.Logger; +import org.telegram.mtproto.util.BytesCache; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.CRC32; + +/** + * Author: Korshakov Stepan + * Created: 13.08.13 14:56 + */ +public class TcpContext { + private static final boolean LOG_OPERATIONS = false; + private static final int MAX_PACKED_SIZE = 1024 * 1024 * 1024;//1 MB + + private class Package { + public Package() { + + } + + private Package(byte[] data, boolean useFastConfirm) { + this.data = data; + this.useFastConfirm = useFastConfirm; + } + + public byte[] data; + public boolean useFastConfirm; + } + + private final String TAG; + + private static final AtomicInteger contextLastId = new AtomicInteger(1); + + private static final int CONNECTION_TIMEOUT = 5 * 1000; + + private static final int READ_TIMEOUT = 1000; + + private static final int READ_DIE_TIMEOUT = 5 * 1000; // 5 sec + + private final String ip; + private final int port; + private final boolean useChecksum; + + private int sentPackets; + private int receivedPackets; + + private boolean isClosed; + private boolean isBroken; + + private Socket socket; + + private ReaderThread readerThread; + + private WriterThread writerThread; + + private DieThread dieThread; + + private TcpContextCallback callback; + + private final int contextId; + + private long lastWriteEvent = 0; + + public TcpContext(MTProto proto, String ip, int port, boolean checksum, TcpContextCallback callback) throws IOException { + try { + this.contextId = contextLastId.incrementAndGet(); + this.TAG = "MTProto#" + proto.getInstanceIndex() + "#Transport" + contextId; + this.ip = ip; + this.port = port; + this.useChecksum = checksum; + this.socket = new Socket(); + this.socket.connect(new InetSocketAddress(ip, port), CONNECTION_TIMEOUT); + this.socket.setKeepAlive(true); + this.socket.setTcpNoDelay(true); + if (!useChecksum) { + socket.getOutputStream().write(0xef); + } + this.isClosed = false; + this.isBroken = false; + this.callback = callback; + this.readerThread = new ReaderThread(); + this.writerThread = new WriterThread(); + this.dieThread = new DieThread(); + this.readerThread.start(); + this.writerThread.start(); + this.dieThread.start(); + } catch (IOException e) { + throw e; + } catch (Throwable t) { + throw new IOException(); + } + } + + public int getContextId() { + return contextId; + } + + public String getIp() { + return ip; + } + + public int getPort() { + return port; + } + + public boolean isUseChecksum() { + return useChecksum; + } + + public int getSentPackets() { + return sentPackets; + } + + public int getReceivedPackets() { + return receivedPackets; + } + + public boolean isClosed() { + return isClosed; + } + + public boolean isBroken() { + return isBroken; + } + + public void postMessage(byte[] data, boolean useFastConfirm) { + writerThread.pushPackage(new Package(data, useFastConfirm)); + } + + public synchronized void close() { + if (!isClosed) { + Logger.w(TAG, "Manual context closing"); + isClosed = true; + isBroken = false; + try { + readerThread.interrupt(); + } catch (Exception e) { + Logger.e(TAG, e); + } + try { + writerThread.interrupt(); + } catch (Exception e) { + Logger.e(TAG, e); + } + + try { + dieThread.interrupt(); + } catch (Exception e) { + Logger.e(TAG, e); + } + } + } + + private synchronized void onMessage(byte[] data, int len) { + if (isClosed) { + return; + } + + callback.onRawMessage(data, 0, len, this); + } + + private synchronized void onError(int errorCode) { + if (isClosed) { + return; + } + + callback.onError(errorCode, this); + } + + private synchronized void breakContext() { + if (!isClosed) { + Logger.w(TAG, "Breaking context"); + isClosed = true; + isBroken = true; + try { + readerThread.interrupt(); + } catch (Exception e) { + Logger.e(TAG, e); + } + try { + writerThread.interrupt(); + } catch (Exception e) { + Logger.e(TAG, e); + } + + try { + dieThread.interrupt(); + } catch (Exception e) { + Logger.e(TAG, e); + } + } + + callback.onChannelBroken(this); + } + + private class ReaderThread extends Thread { + private ReaderThread() { + setPriority(Thread.MIN_PRIORITY); + setName(TAG + "#Reader" + hashCode()); + } + + @Override + public void run() { + try { + while (!isClosed && !isInterrupted()) { + if (LOG_OPERATIONS) { + Logger.d(TAG, "Reader Iteration"); + } + try { + if (socket.isClosed()) { + breakContext(); + return; + } + if (!socket.isConnected()) { + breakContext(); + return; + } + + InputStream stream = socket.getInputStream(); + + byte[] pkg = null; + int pkgLen; + if (useChecksum) { + int length = readInt(stream); + if (arrayEq(intToBytes(length), "HTTP".getBytes())) { + Logger.d(TAG, "Received HTTP package"); + breakContext(); + return; + } + Logger.d(TAG, "Start reading message: " + length); + if (length <= 0) { + breakContext(); + return; + } + + if ((length >> 31) != 0) { + Logger.d(TAG, "fast confirm: " + Integer.toHexString(length)); + callback.onFastConfirm(length); + continue; + } + + if (length >= MAX_PACKED_SIZE) { + Logger.d(TAG, "Too big package"); + breakContext(); + return; + } + + int packetIndex = readInt(stream); + if (length == 4) { + onError(packetIndex); + Logger.d(TAG, "Received error: " + packetIndex); + breakContext(); + return; + } + pkgLen = length - 12; + pkg = readBytes(pkgLen, stream); + int readCrc = readInt(stream); + CRC32 crc32 = new CRC32(); + crc32.update(intToBytes(length)); + crc32.update(intToBytes(packetIndex)); + crc32.update(pkg, 0, pkgLen); + if (readCrc != (int) crc32.getValue()) { + Logger.d(TAG, "Incorrect CRC"); + breakContext(); + return; + } + + if (receivedPackets != packetIndex) { + Logger.d(TAG, "Incorrect packet index"); + breakContext(); + return; + } + + receivedPackets++; + } else { + int length = readByte(stream); + + Logger.d(TAG, "Start reading message: pre"); + + if (length >> 7 != 0) { + length = (length << 24) + (readByte(stream) << 16) + (readByte(stream) << 8) + (readByte(stream) << 0); + Logger.d(TAG, "fast confirm: " + Integer.toHexString(length)); + callback.onFastConfirm(length); + continue; + } else { + if (length == 0x7F) { + length = readByte(stream) + (readByte(stream) << 8) + (readByte(stream) << 16); + } + int len = length * 4; + + Logger.d(TAG, "Start reading message: " + len); + + if (length == 4) { + int error = readInt(stream); + onError(error); + Logger.d(TAG, "Received error: " + error); + breakContext(); + return; + } + + if (length >= MAX_PACKED_SIZE) { + Logger.d(TAG, "Too big package"); + breakContext(); + return; + } + + pkgLen = len; + pkg = readBytes(len, READ_TIMEOUT, stream); + } + } + try { + onMessage(pkg, pkgLen); + } catch (Throwable t) { + Logger.e(TAG, t); + Logger.d(TAG, "Message processing error"); + breakContext(); + return; + } finally { + if (pkg != null) { + BytesCache.getInstance().put(pkg); + } + } + } catch (IOException e) { + Logger.e(TAG, e); + breakContext(); + return; + } + } + } catch (Throwable e) { + Logger.e(TAG, e); + breakContext(); + } + } + } + + private class WriterThread extends Thread { + private final ConcurrentLinkedQueue packages = new ConcurrentLinkedQueue(); + + public WriterThread() { + setPriority(Thread.MIN_PRIORITY); + setName(TAG + "#Writer" + hashCode()); + } + + public void pushPackage(Package p) { + packages.add(p); + synchronized (packages) { + packages.notifyAll(); + } + } + + @Override + public void run() { + while (!isBroken) { + if (LOG_OPERATIONS) { + Logger.d(TAG, "Writer Iteration"); + } + Package p; + synchronized (packages) { + p = packages.poll(); + if (p == null) { + try { + packages.wait(); + } catch (InterruptedException e) { + // e.printStackTrace(); + return; + } + p = packages.poll(); + } + } + if (p == null) { + if (isBroken) { + return; + } else { + continue; + } + } + + if (LOG_OPERATIONS) { + Logger.d(TAG, "Writing data"); + } + + try { + + byte[] data = p.data; + boolean useConfimFlag = p.useFastConfirm; + + if (useChecksum) { + OutputStream stream = socket.getOutputStream(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + int len = data.length + 12; + if (useConfimFlag) { + len |= (1 << 31); + } + writeInt(len, outputStream); + writeInt(sentPackets, outputStream); + writeByteArray(data, outputStream); + CRC32 crc32 = new CRC32(); + crc32.update(intToBytes(len)); + crc32.update(intToBytes(sentPackets)); + crc32.update(data); + writeInt((int) (crc32.getValue() & 0xFFFFFFFF), outputStream); + writeByteArray(outputStream.toByteArray(), stream); + onWrite(); + stream.flush(); + } else { + OutputStream stream = socket.getOutputStream(); + if (useConfimFlag) { + if (data.length / 4 >= 0x7F) { + int len = data.length / 4; + writeByte(0xFF, stream); + writeByte(len & 0xFF, stream); + writeByte((len >> 8) & 0xFF, stream); + writeByte((len >> 16) & 0xFF, stream); + } else { + writeByte((data.length / 4) | (1 << 7), stream); + } + } else { + if (data.length / 4 >= 0x7F) { + int len = data.length / 4; + writeByte(0x7F, stream); + writeByte(len & 0xFF, stream); + writeByte((len >> 8) & 0xFF, stream); + writeByte((len >> 16) & 0xFF, stream); + } else { + writeByte(data.length / 4, stream); + } + } + writeByteArray(data, stream); + onWrite(); + stream.flush(); + } + sentPackets++; + } catch (Exception e) { + Logger.e(TAG, e); + breakContext(); + } + + if (LOG_OPERATIONS) { + Logger.d(TAG, "End writing data"); + } + } + } + } + + private void onWrite() { + lastWriteEvent = System.nanoTime(); + notifyDieThread(); + } + + private void onRead() { + lastWriteEvent = 0; + notifyDieThread(); + } + + private void notifyDieThread() { +// synchronized (dieThread) { +// dieThread.notifyAll(); +// } + } + + private class DieThread extends Thread { + public DieThread() { + setPriority(Thread.MIN_PRIORITY); + setName(TAG + "#DieThread" + hashCode()); + } + + @Override + public void run() { + while (!isBroken) { + if (Logger.LOG_THREADS) { + Logger.d(TAG, "DieThread iteration"); + } + if (lastWriteEvent != 0) { + long delta = (System.nanoTime() - lastWriteEvent) / (1000 * 1000); + if (delta >= READ_DIE_TIMEOUT) { + Logger.d(TAG, "Dies by timeout"); + breakContext(); + } else { + try { + int waitDelta = (int) (READ_DIE_TIMEOUT - delta); + // Logger.d(TAG, "DieThread wait: " + waitDelta); + sleep(Math.max(waitDelta, 1000)); + // Logger.d(TAG, "DieThread start wait end"); + } catch (InterruptedException e) { + Logger.d(TAG, "DieThread exit"); + return; + } + } + } else { + try { + // Logger.d(TAG, "DieThread start common wait"); + sleep(READ_DIE_TIMEOUT); + // Logger.d(TAG, "DieThread end common wait"); + } catch (InterruptedException e) { + Logger.d(TAG, "DieThread exit"); + return; + } + } + } + Logger.d(TAG, "DieThread exit"); + } + } + + + private void writeByteArray(byte[] data, OutputStream stream) throws IOException { + stream.write(data); + } + + private byte[] intToBytes(int value) { + return new byte[]{ + (byte) (value & 0xFF), + (byte) ((value >> 8) & 0xFF), + (byte) ((value >> 16) & 0xFF), + (byte) ((value >> 24) & 0xFF)}; + } + + private void writeInt(int value, OutputStream stream) throws IOException { + stream.write((byte) (value & 0xFF)); + stream.write((byte) ((value >> 8) & 0xFF)); + stream.write((byte) ((value >> 16) & 0xFF)); + stream.write((byte) ((value >> 24) & 0xFF)); + } + + private void writeByte(int v, OutputStream stream) throws IOException { + stream.write(v); + } + + private void writeByte(byte v, OutputStream stream) throws IOException { + stream.write(v); + } + + private byte[] readBytes(int count, int timeout, InputStream stream) throws IOException { + byte[] res = BytesCache.getInstance().allocate(count); + int offset = 0; + long start = System.nanoTime(); + while (offset < count) { + int readed = stream.read(res, offset, count - offset); + Thread.yield(); + if (readed > 0) { + offset += readed; + onRead(); + } else if (readed < 0) { + throw new IOException(); + } else { + if (System.nanoTime() - start > timeout * 1000000L) { + throw new IOException(); + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Logger.e(TAG, e); + throw new IOException(); + } + } + } + return res; + } + + private byte[] readBytes(int count, InputStream stream) throws IOException { + byte[] res = BytesCache.getInstance().allocate(count); + int offset = 0; + while (offset < count) { + int readed = stream.read(res, offset, count - offset); + Thread.yield(); + if (readed > 0) { + offset += readed; + onRead(); + } else if (readed < 0) { + throw new IOException(); + } else { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Logger.e(TAG, e); + throw new IOException(); + } + } + } + return res; + } + + private int readInt(InputStream stream) throws IOException { + int a = stream.read(); + if (a < 0) { + throw new IOException(); + } + onRead(); + + int b = stream.read(); + if (b < 0) { + throw new IOException(); + } + onRead(); + + int c = stream.read(); + if (c < 0) { + throw new IOException(); + } + onRead(); + + int d = stream.read(); + if (d < 0) { + throw new IOException(); + } + onRead(); + + return a + (b << 8) + (c << 16) + (d << 24); + } + + private int readInt(byte[] src) { + return readInt(src, 0); + } + + private int readInt(byte[] src, int offset) { + int a = src[offset + 0] & 0xFF; + int b = src[offset + 1] & 0xFF; + int c = src[offset + 2] & 0xFF; + int d = src[offset + 3] & 0xFF; + + return a + (b << 8) + (c << 16) + (d << 24); + } + + private int readByte(InputStream stream) throws IOException { + int res = stream.read(); + if (res < 0) { + throw new IOException(); + } + onRead(); + return res; + } + + private boolean arrayEq(byte[] a, byte[] b) { + if (a.length != b.length) { + return false; + } + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) + return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/org/telegram/mtproto/transport/TcpContextCallback.java b/src/org/telegram/mtproto/transport/TcpContextCallback.java new file mode 100644 index 0000000..2ebb017 --- /dev/null +++ b/src/org/telegram/mtproto/transport/TcpContextCallback.java @@ -0,0 +1,15 @@ +package org.telegram.mtproto.transport; + +/** + * Author: Korshakov Stepan + * Created: 13.08.13 15:35 + */ +public interface TcpContextCallback { + public void onRawMessage(byte[] data, int offset, int len, TcpContext context); + + public void onError(int errorCode, TcpContext context); + + public void onChannelBroken(TcpContext context); + + public void onFastConfirm(int hash); +} diff --git a/src/org/telegram/mtproto/transport/TransportPool.java b/src/org/telegram/mtproto/transport/TransportPool.java new file mode 100644 index 0000000..d44f289 --- /dev/null +++ b/src/org/telegram/mtproto/transport/TransportPool.java @@ -0,0 +1,264 @@ +package org.telegram.mtproto.transport; + +import org.telegram.mtproto.MTProto; +import org.telegram.mtproto.schedule.Scheduller; +import org.telegram.mtproto.schedule.SchedullerListener; +import org.telegram.mtproto.state.AbsMTProtoState; +import org.telegram.mtproto.time.TimeOverlord; +import org.telegram.mtproto.tl.MTMessage; +import org.telegram.mtproto.tl.MTMessagesContainer; +import org.telegram.mtproto.tl.MTProtoContext; +import org.telegram.mtproto.util.BytesCache; +import org.telegram.tl.StreamingUtils; +import org.telegram.tl.TLObject; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import static org.telegram.mtproto.secure.CryptoUtils.*; +import static org.telegram.mtproto.secure.CryptoUtils.align; +import static org.telegram.tl.StreamingUtils.*; +import static org.telegram.tl.StreamingUtils.writeByteArray; + +/** + * Created by ex3ndr on 03.04.14. + */ +public abstract class TransportPool implements SchedullerListener { + + public static final int MODE_DEFAULT = 0; + public static final int MODE_LOWMODE = 1; + + protected MTProto proto; + protected AbsMTProtoState state; + protected Scheduller scheduller; + protected int mode = MODE_DEFAULT; + protected MTProtoContext context; + private TransportPoolCallback transportPoolCallback; + + private byte[] authKey; + private byte[] authKeyId; + private byte[] session; + + protected boolean isClosed = false; + + public TransportPool(MTProto proto, TransportPoolCallback transportPoolCallback) { + this.proto = proto; + this.scheduller = proto.getScheduller(); + this.scheduller.addListener(this); + this.transportPoolCallback = transportPoolCallback; + this.context = MTProtoContext.getInstance(); + this.authKey = proto.getAuthKey(); + this.authKeyId = proto.getAuthKeyId(); + this.session = proto.getSession(); + this.state = proto.getState(); + } + + public synchronized void close() { + isClosed = true; + } + + public void switchMode(int mode) { + if (this.mode != mode) { + this.mode = mode; + onModeChanged(); + } + } + + protected void onModeChanged() { + + } + + public void onSessionChanged(byte[] session) { + if (isClosed) { + return; + } + this.session = session; + } + + public abstract void reloadConnectionInformation(); + + public abstract void resetConnectionBackoff(); + + protected synchronized void onMTMessage(MTMessage message) { + if (isClosed) { + return; + } + if (readInt(message.getContent()) == MTMessagesContainer.CLASS_ID) { + try { + TLObject object = context.deserializeMessage(new ByteArrayInputStream(message.getContent())); + if (object instanceof MTMessagesContainer) { + MTMessagesContainer container = (MTMessagesContainer) object; + for (MTMessage mtMessage : container.getMessages()) { + transportPoolCallback.onMTMessage(mtMessage); + } + } + } catch (IOException e) { + // Ignore this + // Logger.e(TAG, e); + } finally { + BytesCache.getInstance().put(message.getContent()); + } + } else { + transportPoolCallback.onMTMessage(message); + } + + } + + protected synchronized void onFastConfirm(int hash) { + if (isClosed) { + return; + } + transportPoolCallback.onFastConfirm(hash); + } + + @Override + public void onSchedullerUpdated(Scheduller scheduller) { + + } + + + private byte[] optimizedSHA(byte[] serverSalt, byte[] session, long msgId, int seq, int len, byte[] data, int datalen) { + try { + MessageDigest crypt = MessageDigest.getInstance("SHA-1"); + crypt.reset(); + crypt.update(serverSalt); + crypt.update(session); + crypt.update(longToBytes(msgId)); + crypt.update(intToBytes(seq)); + crypt.update(intToBytes(len)); + crypt.update(data, 0, datalen); + return crypt.digest(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + return null; + } + + protected MTMessage decrypt(byte[] data, int offset, int len) throws IOException { + ByteArrayInputStream stream = new ByteArrayInputStream(data); + stream.skip(offset); + byte[] msgAuthKey = readBytes(8, stream); + for (int i = 0; i < authKeyId.length; i++) { + if (msgAuthKey[i] != authKeyId[i]) { + // Logger.w(TAG, "Unsupported msgAuthKey"); + throw new SecurityException(); + } + } + byte[] msgKey = readBytes(16, stream); + + byte[] sha1_a = SHA1(msgKey, substring(authKey, 8, 32)); + byte[] sha1_b = SHA1(substring(authKey, 40, 16), msgKey, substring(authKey, 56, 16)); + byte[] sha1_c = SHA1(substring(authKey, 72, 32), msgKey); + byte[] sha1_d = SHA1(msgKey, substring(authKey, 104, 32)); + + byte[] aesKey = concat(substring(sha1_a, 0, 8), substring(sha1_b, 8, 12), substring(sha1_c, 4, 12)); + byte[] aesIv = concat(substring(sha1_a, 8, 12), substring(sha1_b, 0, 8), substring(sha1_c, 16, 4), substring(sha1_d, 0, 8)); + + int totalLen = len - 8 - 16; + byte[] encMessage = BytesCache.getInstance().allocate(totalLen); + readBytes(encMessage, 0, totalLen, stream); + + byte[] rawMessage = BytesCache.getInstance().allocate(totalLen); + // long decryptStart = System.currentTimeMillis(); + AES256IGEDecryptBig(encMessage, rawMessage, totalLen, aesIv, aesKey); + // Logger.d(TAG, "Decrypted in " + (System.currentTimeMillis() - decryptStart) + " ms"); + BytesCache.getInstance().put(encMessage); + + ByteArrayInputStream bodyStream = new ByteArrayInputStream(rawMessage); + byte[] serverSalt = readBytes(8, bodyStream); + byte[] session = readBytes(8, bodyStream); + long messageId = readLong(bodyStream); + int mes_seq = StreamingUtils.readInt(bodyStream); + + int msg_len = StreamingUtils.readInt(bodyStream); + + int bodySize = totalLen - 32; + + if (msg_len % 4 != 0) { + throw new SecurityException(); + } + + if (msg_len > bodySize) { + throw new SecurityException(); + } + + if (msg_len - bodySize > 15) { + throw new SecurityException(); + } + + byte[] message = BytesCache.getInstance().allocate(msg_len); + readBytes(message, 0, msg_len, bodyStream); + + BytesCache.getInstance().put(rawMessage); + + byte[] checkHash = optimizedSHA(serverSalt, session, messageId, mes_seq, msg_len, message, msg_len); + + if (!arrayEq(substring(checkHash, 4, 16), msgKey)) { + throw new SecurityException(); + } + + if (!arrayEq(session, this.session)) { + return null; + } + +// if (TimeOverlord.getInstance().getTimeAccuracy() < 10) { +// long time = (messageId >> 32); +// long serverTime = TimeOverlord.getInstance().getServerTime(); +// +// if (serverTime + 30 < time) { +// Logger.w(TAG, "Incorrect message time: " + time + " with server time: " + serverTime); +// // return null; +// } +// +// if (time < serverTime - 300) { +// Logger.w(TAG, "Incorrect message time: " + time + " with server time: " + serverTime); +// // return null; +// } +// } + + return new MTMessage(messageId, mes_seq, message, message.length); + } + + protected EncryptedMessage encrypt(int seqNo, long messageId, byte[] content) throws IOException { + long salt = state.findActualSalt((int) (TimeOverlord.getInstance().getServerTime() / 1000)); + ByteArrayOutputStream messageBody = new ByteArrayOutputStream(); + writeLong(salt, messageBody); + writeByteArray(session, messageBody); + writeLong(messageId, messageBody); + writeInt(seqNo, messageBody); + writeInt(content.length, messageBody); + writeByteArray(content, messageBody); + + byte[] innerData = messageBody.toByteArray(); + byte[] msgKey = substring(SHA1(innerData), 4, 16); + int fastConfirm = readInt(SHA1(innerData)) | (1 << 31); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + writeByteArray(authKeyId, out); + writeByteArray(msgKey, out); + + byte[] sha1_a = SHA1(msgKey, substring(authKey, 0, 32)); + byte[] sha1_b = SHA1(substring(authKey, 32, 16), msgKey, substring(authKey, 48, 16)); + byte[] sha1_c = SHA1(substring(authKey, 64, 32), msgKey); + byte[] sha1_d = SHA1(msgKey, substring(authKey, 96, 32)); + + byte[] aesKey = concat(substring(sha1_a, 0, 8), substring(sha1_b, 8, 12), substring(sha1_c, 4, 12)); + byte[] aesIv = concat(substring(sha1_a, 8, 12), substring(sha1_b, 0, 8), substring(sha1_c, 16, 4), substring(sha1_d, 0, 8)); + + byte[] encoded = AES256IGEEncrypt(align(innerData, 16), aesIv, aesKey); + writeByteArray(encoded, out); + EncryptedMessage res = new EncryptedMessage(); + res.data = out.toByteArray(); + res.fastConfirm = fastConfirm; + return res; + } + + protected class EncryptedMessage { + public byte[] data; + public int fastConfirm; + } +} diff --git a/src/org/telegram/mtproto/transport/TransportPoolCallback.java b/src/org/telegram/mtproto/transport/TransportPoolCallback.java new file mode 100644 index 0000000..3753eb9 --- /dev/null +++ b/src/org/telegram/mtproto/transport/TransportPoolCallback.java @@ -0,0 +1,12 @@ +package org.telegram.mtproto.transport; + +import org.telegram.mtproto.tl.MTMessage; + +/** + * Created by ex3ndr on 03.04.14. + */ +public interface TransportPoolCallback { + public void onMTMessage(MTMessage message); + + public void onFastConfirm(int hash); +} diff --git a/src/org/telegram/mtproto/transport/TransportRate.java b/src/org/telegram/mtproto/transport/TransportRate.java new file mode 100644 index 0000000..b039c9a --- /dev/null +++ b/src/org/telegram/mtproto/transport/TransportRate.java @@ -0,0 +1,100 @@ +package org.telegram.mtproto.transport; + +import org.telegram.mtproto.log.Logger; +import org.telegram.mtproto.state.ConnectionInfo; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Random; + +/** + * Created by ex3ndr on 26.11.13. + */ +public class TransportRate { + + private static final String TAG = "TransportRate"; + + private HashMap transports = new HashMap(); + + private Random rnd = new Random(); + + public TransportRate(ConnectionInfo[] connectionInfos) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < connectionInfos.length; i++) { + min = Math.min(connectionInfos[i].getPriority(), min); + max = Math.max(connectionInfos[i].getPriority(), max); + } + for (int i = 0; i < connectionInfos.length; i++) { + transports.put(connectionInfos[i].getId(), + new Transport(new ConnectionType(connectionInfos[i].getId(), connectionInfos[i].getAddress(), connectionInfos[i].getPort(), ConnectionType.TYPE_TCP), + connectionInfos[i].getPriority() - min + 1)); + } + normalize(); + } + + public synchronized ConnectionType tryConnection() { + float value = rnd.nextFloat(); + Transport[] currentTransports = transports.values().toArray(new Transport[0]); + Arrays.sort(currentTransports, new Comparator() { + @Override + public int compare(Transport transport, Transport transport2) { + return -Float.compare(transport.getRate(), transport2.getRate()); + } + }); + ConnectionType type = currentTransports[0].getConnectionType(); + Logger.d(TAG, "tryConnection #" + type.getId()); + return type; + } + + public synchronized void onConnectionFailure(int id) { + Logger.d(TAG, "onConnectionFailure #" + id); + transports.get(id).rate *= 0.5; + normalize(); + } + + public synchronized void onConnectionSuccess(int id) { + Logger.d(TAG, "onConnectionSuccess #" + id); + transports.get(id).rate *= 1.5; + normalize(); + } + + private synchronized void normalize() { + float sum = 0; + for (Integer id : transports.keySet()) { + sum += transports.get(id).rate; + } + for (Integer id : transports.keySet()) { + Transport transport = transports.get(id); + transport.rate /= sum; + Logger.d(TAG, "Transport: #" + transport.connectionType.getId() + " " + transport.connectionType.getHost() + ":" + transport.getConnectionType().getPort() + " #" + transport.getRate()); + } + } + + private class Transport { + private ConnectionType connectionType; + private float rate; + + private Transport(ConnectionType connectionType, float rate) { + this.connectionType = connectionType; + this.rate = rate; + } + + public ConnectionType getConnectionType() { + return connectionType; + } + + public void setConnectionType(ConnectionType connectionType) { + this.connectionType = connectionType; + } + + public float getRate() { + return rate; + } + + public void setRate(float rate) { + this.rate = rate; + } + } +} diff --git a/src/org/telegram/mtproto/transport/TransportTcpPool.java b/src/org/telegram/mtproto/transport/TransportTcpPool.java new file mode 100644 index 0000000..70fe08c --- /dev/null +++ b/src/org/telegram/mtproto/transport/TransportTcpPool.java @@ -0,0 +1,390 @@ +package org.telegram.mtproto.transport; + +import com.droidkit.actors.*; +import org.telegram.mtproto.MTProto; +import org.telegram.mtproto.backoff.ExponentalBackoff; +import org.telegram.mtproto.log.Logger; +import org.telegram.mtproto.schedule.PrepareSchedule; +import org.telegram.mtproto.schedule.PreparedPackage; +import org.telegram.mtproto.schedule.Scheduller; +import org.telegram.mtproto.secure.Entropy; +import org.telegram.mtproto.tl.MTMessage; +import org.telegram.mtproto.tl.MTPing; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; + +import static org.telegram.mtproto.util.TimeUtil.getUnixTime; + +/** + * Created by ex3ndr on 03.04.14. + */ +public class TransportTcpPool extends TransportPool { + + private static final boolean LOG_SCHEDULLER = true; + + private final String TAG; + private static final boolean USE_CHECKSUM = false; + private static final int LOW_TIME_DIE_CHECK = 30 * 1000; // 30 sec + + private int desiredConnectionCount; + + private final HashSet contexts = new HashSet(); + private final HashMap contextConnectionId = new HashMap(); + private final HashSet connectedContexts = new HashSet(); + private final HashSet initedContext = new HashSet(); + + private static final int PING_TIMEOUT = 60 * 1000; + + private ActorSystem actorSystem; + + private TransportRate connectionRate; + + private TcpListener tcpListener; + + private ActorRef connectionActor; + private ActorRef scheduleActor; +// private ConnectionActor.ConnectorMessenger connectionActor; +// private SchedullerActor.Messenger scheduleActor; + + private ExponentalBackoff exponentalBackoff; + + public TransportTcpPool(MTProto proto, TransportPoolCallback callback, int connectionCount) { + super(proto, callback); + TAG = "TransportTcpPool#" + proto.getInstanceIndex(); + this.exponentalBackoff = new ExponentalBackoff(TAG); + this.desiredConnectionCount = connectionCount; + this.actorSystem = proto.getActorSystem(); + this.tcpListener = new TcpListener(); + this.connectionRate = new TransportRate(proto.getState().getAvailableConnections()); + + connectionActor = actorSystem.actorOf(connection()); + scheduleActor = actorSystem.actorOf(scheduller()); + } + + @Override + public void onSchedullerUpdated(Scheduller scheduller) { + if (LOG_SCHEDULLER) { + Logger.d(TAG, "onSchedullerUpdated"); + } + scheduleActor.send(new Schedule()); + synchronized (contexts) { + if (contexts.size() == 0) { + this.connectionActor.send(new CheckConnections()); + } + } + this.connectionActor.sendOnce(new CheckDestroy(), LOW_TIME_DIE_CHECK); + } + + @Override + public void reloadConnectionInformation() { + this.connectionRate = new TransportRate(proto.getState().getAvailableConnections()); + } + + @Override + public void resetConnectionBackoff() { + exponentalBackoff.reset(); + } + + @Override + protected void onModeChanged() { + this.scheduleActor.send(new Schedule()); + this.connectionActor.send(new CheckConnections()); + this.connectionActor.sendOnce(new CheckDestroy(), LOW_TIME_DIE_CHECK); + } + + private ActorSelection connection() { + return new ActorSelection(Props.create(ConnectionActor.class, new ActorCreator() { + @Override + public ConnectionActor create() { + return new ConnectionActor(TransportTcpPool.this); + } + }).changeDispatcher("connection"), + "tcp_connection_" + proto.getInstanceIndex()); + } + + private ActorSelection scheduller() { + return new ActorSelection(Props.create(SchedullerActor.class, new ActorCreator() { + @Override + public SchedullerActor create() { + return new SchedullerActor(TransportTcpPool.this); + } + }), "tcp_scheduller_" + proto.getInstanceIndex()); + } + + private static final class CheckDestroy { + + } + + private static final class CheckConnections { + + } + + private static final class Schedule { + + } + + private static class ConnectionActor extends Actor { + private TransportTcpPool pool; + + private ConnectionActor(TransportTcpPool pool) { + this.pool = pool; + } + + @Override + public void preStart() { + self().send(new CheckConnections()); + } + + @Override + public void onReceive(Object message) { + if (message instanceof CheckDestroy) { + onCheckDestroyMessage(); + } else if (message instanceof CheckConnections) { + onCheckMessage(); + } + } + + protected void onCheckDestroyMessage() { + if (pool.mode == MODE_LOWMODE) { + if (pool.scheduller.hasRequests()) { + self().send(new CheckDestroy(), LOW_TIME_DIE_CHECK); + return; + } + + // Logger.d(TAG, "Destroying contexts"); + synchronized (pool.contexts) { + for (TcpContext c : pool.contexts) { + c.close(); + } + pool.contexts.clear(); + } + } + } + + protected void onCheckMessage() { + try { + if (pool.mode == MODE_LOWMODE) { + if (!pool.scheduller.hasRequests()) { + // Logger.d(TAG, "Ignoring context check: scheduller is empty in low mode."); + return; + } + } + + synchronized (pool.contexts) { + if (pool.contexts.size() >= pool.desiredConnectionCount) { + // Logger.d(TAG, "Ignoring context check: already created enough contexts."); + return; + } + } + + ConnectionType type = pool.connectionRate.tryConnection(); + // Logger.d(TAG, "Creating context for #" + type.getId() + " " + type.getHost() + ":" + type.getPort()); + try { + TcpContext context = new TcpContext(pool.proto, type.getHost(), type.getPort(), USE_CHECKSUM, pool.tcpListener); + // Logger.d(TAG, "Context created."); + synchronized (pool.contexts) { + pool.contexts.add(context); + pool.contextConnectionId.put(context.getContextId(), type.getId()); + } + pool.scheduller.postMessageDelayed(new MTPing(Entropy.generateRandomId()), false, PING_TIMEOUT, 0, context.getContextId(), false); + } catch (IOException e) { + // Logger.d(TAG, "Context create failure."); + pool.connectionRate.onConnectionFailure(type.getId()); + throw e; + } + + // messenger().check(); + self().send(new CheckConnections()); + } catch (Exception e) { + self().send(new CheckConnections(), 1000); + } + } + } + + private static class SchedullerActor extends Actor { + private TransportTcpPool pool; + private PrepareSchedule prepareSchedule = new PrepareSchedule(); + private int roundRobin = 0; + + private SchedullerActor(TransportTcpPool pool) { + this.pool = pool; + } + + @Override + public void preStart() { + self().send(new Schedule()); + } + + @Override + public void onReceive(Object message) { + if (message instanceof Schedule) { + onScheduleMessage(); + } + } + + public void onScheduleMessage() { + // Logger.d(TAG, "onScheduleMessage"); + int[] contextIds; + synchronized (pool.contexts) { + TcpContext[] currentContexts = pool.contexts.toArray(new TcpContext[0]); + contextIds = new int[currentContexts.length]; + for (int i = 0; i < contextIds.length; i++) { + contextIds[i] = currentContexts[i].getContextId(); + } + } + + pool.scheduller.prepareScheduller(prepareSchedule, contextIds); + if (prepareSchedule.isDoWait()) { +// if (LOG_SCHEDULLER) { +// Logger.d(TAG, "Scheduller:wait " + prepareSchedule.getDelay()); +// } + self().sendOnce(new Schedule(), prepareSchedule.getDelay()); + return; + } + + TcpContext context = null; + synchronized (pool.contexts) { + TcpContext[] currentContexts = pool.contexts.toArray(new TcpContext[0]); + outer: + for (int i = 0; i < currentContexts.length; i++) { + int index = (i + roundRobin + 1) % currentContexts.length; + for (int allowed : prepareSchedule.getAllowedContexts()) { + if (currentContexts[index].getContextId() == allowed) { + context = currentContexts[index]; + break outer; + } + } + + } + + if (currentContexts.length != 0) { + roundRobin = (roundRobin + 1) % currentContexts.length; + } + } + + if (context == null) { +// if (LOG_SCHEDULLER) { +// Logger.d(TAG, "Scheduller: no context"); +// } +// messenger().schedule(); + self().sendOnce(new Schedule()); + return; + } + +// if (LOG_SCHEDULLER) { +// Logger.d(TAG, "doSchedule"); +// } + + long start = System.currentTimeMillis(); + PreparedPackage preparedPackage = pool.scheduller.doSchedule(context.getContextId(), pool.initedContext.contains(context.getContextId())); +// if (LOG_SCHEDULLER) { +// Logger.d(TAG, "Schedulled in " + (System.currentTimeMillis() - start) + " ms"); +// } + if (preparedPackage == null) { +// if (LOG_SCHEDULLER) { +// Logger.d(TAG, "No packages for scheduling"); +// } +// messenger().schedule(); + self().sendOnce(new Schedule()); + return; + } + +// if (LOG_SCHEDULLER) { +// Logger.d(TAG, "MessagePushed (#" + context.getContextId() + "): time:" + getUnixTime(preparedPackage.getMessageId())); +// Logger.d(TAG, "MessagePushed (#" + context.getContextId() + "): seqNo:" + preparedPackage.getSeqNo() + ", msgId" + preparedPackage.getMessageId()); +// } + + try { + EncryptedMessage msg = pool.encrypt(preparedPackage.getSeqNo(), preparedPackage.getMessageId(), preparedPackage.getContent()); + if (preparedPackage.isHighPriority()) { + pool.scheduller.registerFastConfirm(preparedPackage.getMessageId(), msg.fastConfirm); + } + if (!context.isClosed()) { + context.postMessage(msg.data, preparedPackage.isHighPriority()); + pool.initedContext.add(context.getContextId()); + } else { + pool.scheduller.onConnectionDies(context.getContextId()); + } + } catch (IOException e) { + // Logger.e(TAG, e); + } + +// if (LOG_SCHEDULLER) { +// Logger.d(TAG, "doSchedule end"); +// } + + self().sendOnce(new Schedule()); + } + } + + private class TcpListener implements TcpContextCallback { + + @Override + public void onRawMessage(byte[] data, int offset, int len, TcpContext context) { + if (isClosed) { + return; + } + try { + MTMessage decrypted = decrypt(data, offset, len); + if (decrypted == null) { + Logger.d(TAG, "message ignored"); + return; + } + if (!connectedContexts.contains(context.getContextId())) { + connectedContexts.add(context.getContextId()); + exponentalBackoff.onSuccess(); + connectionRate.onConnectionSuccess(contextConnectionId.get(context.getContextId())); + } + + onMTMessage(decrypted); + } catch (IOException e) { + Logger.e(TAG, e); + synchronized (contexts) { + context.close(); + if (!connectedContexts.contains(context.getContextId())) { + exponentalBackoff.onFailureNoWait(); + connectionRate.onConnectionFailure(contextConnectionId.get(context.getContextId())); + } + contexts.remove(context); + connectionActor.send(new CheckConnections()); + scheduller.onConnectionDies(context.getContextId()); + } + } + } + + @Override + public void onError(int errorCode, TcpContext context) { + // Fully maintained at transport level: TcpContext dies + } + + @Override + public void onChannelBroken(TcpContext context) { + if (isClosed) { + return; + } + int contextId = context.getContextId(); + Logger.d(TAG, "onChannelBroken (#" + contextId + ")"); + synchronized (contexts) { + contexts.remove(context); + if (!connectedContexts.contains(contextId)) { + if (contextConnectionId.containsKey(contextId)) { + exponentalBackoff.onFailureNoWait(); + connectionRate.onConnectionFailure(contextConnectionId.get(contextId)); + } + } + connectionActor.send(new CheckConnections()); + } + scheduller.onConnectionDies(context.getContextId()); + } + + @Override + public void onFastConfirm(int hash) { + if (isClosed) { + return; + } + TransportTcpPool.this.onFastConfirm(hash); + } + } +} \ No newline at end of file diff --git a/src/org/telegram/mtproto/util/BytesCache.java b/src/org/telegram/mtproto/util/BytesCache.java new file mode 100644 index 0000000..8044fe9 --- /dev/null +++ b/src/org/telegram/mtproto/util/BytesCache.java @@ -0,0 +1,103 @@ +package org.telegram.mtproto.util; + +import org.telegram.mtproto.log.Logger; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentSkipListSet; + +/** + * Created by ex3ndr on 04.02.14. + */ +public class BytesCache { + + public static BytesCache getInstance() { + return instance; + } + + private static BytesCache instance = new BytesCache("GlobalByteCache"); + + private final int[] SIZES = new int[]{64, 128, 3072, 20 * 1024, 40 * 1024}; + private final int MAX_SIZE = 40 * 1024; + private final boolean TRACK_ALLOCATIONS = false; + + private HashMap> fastBuffers = new HashMap>(); + private HashSet mainFilter = new HashSet(); + private HashSet byteBuffer = new HashSet(); + private WeakHashMap references = new WeakHashMap(); + + private final String TAG; + + public BytesCache(String logTag) { + TAG = logTag; + for (int i = 0; i < SIZES.length; i++) { + fastBuffers.put(SIZES[i], new HashSet()); + } + } + + public synchronized void put(byte[] data) { + references.remove(data); + + if (mainFilter.add(data)) { + for (Integer i : SIZES) { + if (data.length == i) { + fastBuffers.get(i).add(data); + return; + } + } + if (data.length <= MAX_SIZE) { + return; + } + byteBuffer.add(data); + } + } + + public synchronized byte[] allocate(int minSize) { + if (minSize <= MAX_SIZE) { + for (int i = 0; i < SIZES.length; i++) { + if (minSize < SIZES[i]) { + if (!fastBuffers.get(SIZES[i]).isEmpty()) { + Iterator interator = fastBuffers.get(SIZES[i]).iterator(); + byte[] res = interator.next(); + interator.remove(); + + mainFilter.remove(res); + + if (TRACK_ALLOCATIONS) { + references.put(res, Thread.currentThread().getStackTrace()); + } + + return res; + } + + return new byte[SIZES[i]]; + } + } + } else { + byte[] res = null; + for (byte[] cached : byteBuffer) { + if (cached.length < minSize) { + continue; + } + if (res == null) { + res = cached; + } else if (res.length > cached.length) { + res = cached; + } + } + + if (res != null) { + byteBuffer.remove(res); + mainFilter.remove(res); + if (TRACK_ALLOCATIONS) { + references.put(res, Thread.currentThread().getStackTrace()); + } + return res; + } + } + + return new byte[minSize]; + } +} diff --git a/src/org/telegram/mtproto/util/TimeUtil.java b/src/org/telegram/mtproto/util/TimeUtil.java new file mode 100644 index 0000000..3ec4335 --- /dev/null +++ b/src/org/telegram/mtproto/util/TimeUtil.java @@ -0,0 +1,10 @@ +package org.telegram.mtproto.util; + +/** + * Created by ex3ndr on 13.11.13. + */ +public class TimeUtil { + public static int getUnixTime(long msgId) { + return (int) (msgId >> 32); + } +} diff --git a/temp/droidkit-actors-0.2.48/.gitignore b/temp/droidkit-actors-0.2.48/.gitignore new file mode 100644 index 0000000..724c2cf --- /dev/null +++ b/temp/droidkit-actors-0.2.48/.gitignore @@ -0,0 +1,7 @@ +actors/build +actors-sample/build +.idea +.gradle +*.iml + +local.properties diff --git a/temp/droidkit-actors-0.2.48/LICENSE b/temp/droidkit-actors-0.2.48/LICENSE new file mode 100644 index 0000000..326fe25 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/temp/droidkit-actors-0.2.48/README.md b/temp/droidkit-actors-0.2.48/README.md new file mode 100644 index 0000000..a8e5421 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/README.md @@ -0,0 +1,81 @@ +DroidKit Actors +=============== +Lightweight java implementation of actor model for small applications. Designed for Android applications. +Read more about actors on [Wikipedia](http://en.wikipedia.org/wiki/Actor_model) + +QuickStart +=============== +### Add dependency to your gradle project +``` +compile 'com.droidkit:actors:0.1.+' +``` + +### Create log Actor +``` +import android.util.Log; +import com.droidkit.actors.Actor; + +public class LogActor extends Actor { + + @Override + public void preStart() { + Log.d("LOGACTOR#" + hashCode(), "preStart"); + } + + @Override + public void onReceive(Object message) { + Log.d("LOGACTOR#" + hashCode(), message + ""); + } + + @Override + public void postStop() { + Log.d("LOGACTOR#" + hashCode(), "postStop"); + } +} +``` + +### Use main actor system +Actor system is entry point to actor model, it contains all configurations, dispatchers and actors. +Dispatcher is a queue + worker threads for this queue. + +By default ActorSystem has static main ActorSystem and in most cases you can use it in two ways: +``` +void a() { + ActorSystem.system() +} +``` +or +``` +import static com.droidkit.actors.ActorSystem.system; + +void a() { + system() +} +``` +### or create your Actor system +``` +ActorSystem system = new ActorSystem(); +// Add additional dispatcher with threads number == cores count +system.addDispatcher("images"); +// Add additional dispather with 3 threads with minimal priority +system.addDispatcher("images", new MailboxesDispatcher(system, 2, Thread.MIN_PRIORITY)); +``` +### Complete sample +``` +system().addDispatcher("images", new MailboxesDispatcher(system(), 2, Thread.MIN_PRIORITY)); + +ActorRef log1 = system().actorOf(LogActor.class, "log"); +ActorRef log2 = system().actorOf(LogActor.class, "log"); +ActorRef log3 = system().actorOf(Props.create(LogActor.class).changeDispatcher("images"), "log2"); +ActorRef log4 = system().actorOf(Props.create(LogActor.class).changeDispatcher("images"), "log3"); + +ActorRef[] refs = new ActorRef[]{log1, log2, log3, log4}; +for (int i = 0; i < 100; i++) { + refs[i % refs.length].send("test" + i); +} +``` +Log output will be with messages without ordering across all messages, but ordered for every actor. + +License +=============== +License use [MIT License](LICENSE) diff --git a/temp/droidkit-actors-0.2.48/actors-android/build.gradle b/temp/droidkit-actors-0.2.48/actors-android/build.gradle new file mode 100644 index 0000000..bf408e3 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-android/build.gradle @@ -0,0 +1,130 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:0.9.+' + } +} +apply plugin: 'android-library' +apply plugin: 'maven' +apply plugin: 'signing' + +repositories { + mavenCentral() + maven { url "https://oss.sonatype.org/content/groups/public/" } +} + +sourceCompatibility = 1.6 +group = 'com.droidkit' +version = fullVersion + +android { + compileSdkVersion 19 + buildToolsVersion "20.0.0" + + defaultConfig { + minSdkVersion 9 + targetSdkVersion 19 + } + + buildTypes { + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } + + lintOptions { + abortOnError false + } +} + +// Android JavaDocs +android.libraryVariants.all { variant -> + task("${variant.name}Javadoc", type: Javadoc) { + destinationDir = new File("$project.buildDir/javadoc/$variant.name") + + source = variant.javaCompile.source + exclude '**/BuildConfig.java' + exclude '**/R.java' + + ext.androidJar = "${android.plugin.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" + classpath = files(variant.javaCompile.classpath.files) + files(ext.androidJar) + } + + task("${variant.name}JavadocJar", type: Jar, dependsOn: "${variant.name}Javadoc") { + classifier 'javadoc' + destinationDir = new File("$project.buildDir/libs/") + from "$project.buildDir/javadoc/$variant.name" + } + + task("${variant.name}SourcesJar", type: Jar) { + classifier 'sources' + + destinationDir = new File("$project.buildDir/libs/") + + from variant.javaCompile.source + exclude '**/BuildConfig.java' + exclude '**/R.java' + } +} + +project.afterEvaluate { + artifacts { + archives releaseJavadocJar + archives releaseSourcesJar + } + + if (project.hasProperty("ossrhUsername") && project.hasProperty("ossrhPassword")) { + + signing { + sign configurations.archives + } + + uploadArchives { + repositories { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + + repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + + pom.project { + name 'DroidKit Actors: Android' + packaging 'aar' + description 'DroidKit Actors: Android is extension for DroidKit Actors for creating UIActors' + url 'https://github.com/secretapphd/droidkit-actors' + + scm { + url 'scm:git@github.com:secretapphd/droidkit-actors.git' + connection 'scm:git@github.com:secretapphd/droidkit-actors.git' + developerConnection 'scm:git@github.com:secretapphd/droidkit-actors.git' + } + + licenses { + license { + name 'The MIT License (MIT)' + url 'http://opensource.org/licenses/MIT' + distribution 'repo' + } + } + + developers { + developer { + id 'ex3ndr' + name 'Stepan Korshakov' + } + } + } + } + } + } + } +} + +dependencies { + compile 'com.android.support:appcompat-v7:20.+' + compile 'com.droidkit:actors:0.2.+' +} \ No newline at end of file diff --git a/temp/droidkit-actors-0.2.48/actors-android/proguard-rules.txt b/temp/droidkit-actors-0.2.48/actors-android/proguard-rules.txt new file mode 100644 index 0000000..7efd9b8 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-android/proguard-rules.txt @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/ex3ndr/Documents/android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} \ No newline at end of file diff --git a/temp/droidkit-actors-0.2.48/actors-android/src/main/AndroidManifest.xml b/temp/droidkit-actors-0.2.48/actors-android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fbf285e --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-android/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/temp/droidkit-actors-0.2.48/actors-android/src/main/java/com/droidkit/actors/android/UiActor.java b/temp/droidkit-actors-0.2.48/actors-android/src/main/java/com/droidkit/actors/android/UiActor.java new file mode 100644 index 0000000..9de0061 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-android/src/main/java/com/droidkit/actors/android/UiActor.java @@ -0,0 +1,85 @@ +package com.droidkit.actors.android; + +import com.droidkit.actors.*; +import com.droidkit.actors.messages.PoisonPill; + +import java.util.UUID; + +/** + * Actor-like object that works in UI Thread and backed by real Actor that dispatched on UI thread too. + */ +public class UiActor { + private String path; + private ActorSystem actorSystem; + private ActorRef actorRef; + private boolean isKilled; + + public UiActor() { + this(ActorSystem.system()); + } + + public UiActor(ActorSystem actorSystem) { + this.actorSystem = actorSystem; + this.actorRef = actorSystem.actorOf(BackedUiActor.createProps(this), "ui_" + UUID.randomUUID()); + this.isKilled = false; + } + + /** + * Path of actor + * + * @return Path + */ + public String getPath() { + return path; + } + + /** + * Backed ActorRef + * + * @return ActorRef + */ + public ActorRef getActorRef() { + return actorRef; + } + + /** + * On incoming message + * + * @param message message + */ + public void onReceive(Object message) { + + } + + /** + * Stop receiving messages by this actor + */ + public void kill() { + isKilled = true; + actorRef.send(PoisonPill.INSTANCE); + } + + private static class BackedUiActor extends Actor { + public static Props createProps(final UiActor uiActor) { + return Props.create(BackedUiActor.class, new ActorCreator() { + @Override + public BackedUiActor create() { + return new BackedUiActor(uiActor); + } + }).changeDispatcher("ui"); + } + + private UiActor uiActor; + + private BackedUiActor(UiActor uiActor) { + this.uiActor = uiActor; + } + + @Override + public void onReceive(Object message) { + if (!uiActor.isKilled) { + uiActor.onReceive(message); + } + } + } +} diff --git a/temp/droidkit-actors-0.2.48/actors-android/src/main/java/com/droidkit/actors/android/UiActorDispatcher.java b/temp/droidkit-actors-0.2.48/actors-android/src/main/java/com/droidkit/actors/android/UiActorDispatcher.java new file mode 100644 index 0000000..be98fee --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-android/src/main/java/com/droidkit/actors/android/UiActorDispatcher.java @@ -0,0 +1,24 @@ +package com.droidkit.actors.android; + +import com.droidkit.actors.ActorSystem; +import com.droidkit.actors.dispatch.Dispatch; +import com.droidkit.actors.mailbox.AbsActorDispatcher; +import com.droidkit.actors.mailbox.Envelope; +import com.droidkit.actors.mailbox.MailboxesQueue; + +/** + * Actor Dispatcher for dispatching messages on UI Thread + */ +public class UiActorDispatcher extends AbsActorDispatcher { + + public UiActorDispatcher(ActorSystem actorSystem) { + super(actorSystem); + + initDispatcher(new UiDispatcher(new MailboxesQueue(), new Dispatch() { + @Override + public void dispatchMessage(Envelope message) { + processEnvelope(message); + } + })); + } +} diff --git a/temp/droidkit-actors-0.2.48/actors-android/src/main/java/com/droidkit/actors/android/UiDispatcher.java b/temp/droidkit-actors-0.2.48/actors-android/src/main/java/com/droidkit/actors/android/UiDispatcher.java new file mode 100644 index 0000000..a598ea4 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-android/src/main/java/com/droidkit/actors/android/UiDispatcher.java @@ -0,0 +1,54 @@ +package com.droidkit.actors.android; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import com.droidkit.actors.ActorTime; +import com.droidkit.actors.dispatch.AbstractDispatchQueue; +import com.droidkit.actors.dispatch.AbstractDispatcher; +import com.droidkit.actors.dispatch.Dispatch; + +/** + * Thread Dispatcher that dispatches messages on UI Thread + */ +public class UiDispatcher> extends AbstractDispatcher { + private Handler handler = new Handler(Looper.getMainLooper()) { + @Override + public void dispatchMessage(Message msg) { + doIteration(); + } + }; + + protected UiDispatcher(Q queue, Dispatch dispatch) { + super(queue, dispatch); + } + + protected void invalidate() { + handler.removeMessages(0); + handler.sendEmptyMessage(0); + } + + protected void invalidateDelay(long delay) { + handler.removeMessages(0); + if (delay > 15000) { + delay = 15000; + } + handler.sendEmptyMessageDelayed(0, delay); + } + + @Override + protected void notifyDispatcher() { + invalidate(); + } + + protected void doIteration() { + long time = ActorTime.currentTime(); + T action = getQueue().dispatch(time); + if (action == null) { + long delay = getQueue().waitDelay(time); + invalidateDelay(delay); + } else { + dispatchMessage(action); + } + } +} \ No newline at end of file diff --git a/temp/droidkit-actors-0.2.48/actors-sample/build.gradle b/temp/droidkit-actors-0.2.48/actors-sample/build.gradle new file mode 100644 index 0000000..c26084b --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-sample/build.gradle @@ -0,0 +1,60 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:0.9.+' + } +} +apply plugin: 'android' + +repositories { + mavenCentral() + maven { url "https://oss.sonatype.org/content/groups/public/" } +} + +android { + compileSdkVersion 19 + buildToolsVersion "20.0.0" + + defaultConfig { + minSdkVersion 9 + targetSdkVersion 19 + versionCode apkVersionCode + versionName apkVersionName + } + + signingConfigs { + release { + + } + } + + buildTypes { + release { + signingConfig signingConfigs.release + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } + + if (project.hasProperty('androidStore') && + project.hasProperty('androidStorePass') && + project.hasProperty('androidAliasPass')&& + project.hasProperty('androidAlias')) { + android.signingConfigs.release.storeFile = file(androidStore) + android.signingConfigs.release.storePassword = androidStorePass + android.signingConfigs.release.keyAlias = androidAlias + android.signingConfigs.release.keyPassword = androidAliasPass + } else { + buildTypes.release.signingConfig = null + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:19.+' + // compile project(":actors-android") + compile project(":actors") + compile project(":actors-android") +} diff --git a/temp/droidkit-actors-0.2.48/actors-sample/proguard-rules.txt b/temp/droidkit-actors-0.2.48/actors-sample/proguard-rules.txt new file mode 100644 index 0000000..7efd9b8 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-sample/proguard-rules.txt @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/ex3ndr/Documents/android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} \ No newline at end of file diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/AndroidManifest.xml b/temp/droidkit-actors-0.2.48/actors-sample/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2aaabd4 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-sample/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/CounterActor.java b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/CounterActor.java new file mode 100644 index 0000000..f580259 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/CounterActor.java @@ -0,0 +1,21 @@ +package com.droidkit.actors.sample; + +import com.droidkit.actors.Actor; + +/** + * Created by ex3ndr on 27.08.14. + */ +public class CounterActor extends Actor { + int last = -1; + + @Override + public void onReceive(Object message) { + if (message instanceof Integer) { + int val = (Integer) message; + if (last != val - 1) { + Log.d("Error! Wrong order expected #" + (last + 1) + " got #" + val); + } + last++; + } + } +} diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/DownloadFile.java b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/DownloadFile.java new file mode 100644 index 0000000..8c4e7ea --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/DownloadFile.java @@ -0,0 +1,58 @@ +package com.droidkit.actors.sample; + +import com.droidkit.actors.ReflectedActor; +import com.droidkit.actors.tasks.AskCallback; +import com.droidkit.actors.tasks.AskFuture; + +/** + * Created by ex3ndr on 18.08.14. + */ +public class DownloadFile extends ReflectedActor { + + public void onReceive(String[] url) { + ask(HttpDownloader.download(url[0])); + ask(HttpDownloader.download(url[1])); + AskFuture future = ask(HttpDownloader.download(url[2])); + + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + future.cancel(); + +// combine("downloaded", byte[].class, +// ask(HttpDownloader.download(url[0])), +// ask(HttpDownloader.download(url[1]))); + } + + public void onDownloadedReceive(byte[][] data) { + Log.d("DownloadFile:onDownloadedReceive:" + data); + Log.d("downloaded " + data[0].length + " bytes and " + data[1].length + " bytes"); + } + + public void onDownloadedReceive(Throwable throwable) { + Log.d("DownloadFile:onDownloadedReceiveError:" + throwable); + } + + public void onReceive(String url) { + Log.d("DownloadFile:onReceiveUrl:" + url); + ask(HttpDownloader.download(url), new AskCallback() { + @Override + public void onResult(Object result) { + self().send(result); + } + + @Override + public void onError(Throwable throwable) { + + } + }); + } + + public void onReceive(byte[] data) { + Log.d("DownloadFile:receiveData:" + data); + Log.d("downloaded " + data.length + " bytes"); + } +} \ No newline at end of file diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/HashUtil.java b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/HashUtil.java new file mode 100644 index 0000000..ce5d4e8 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/HashUtil.java @@ -0,0 +1,34 @@ +package com.droidkit.actors.sample; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Created by ex3ndr on 17.08.14. + */ +public class HashUtil { + public static String md5(String s) { + try { + byte[] bytesOfMessage = s.getBytes("UTF-8"); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] thedigest = md.digest(bytesOfMessage); + return hex(thedigest); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + throw new RuntimeException(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + throw new RuntimeException(); + } + } + + public static String hex(byte[] data) { + StringBuilder res = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + res.append(Integer.toHexString(data[i] & 0xFF).toLowerCase()); + } + return res.toString(); + } + +} diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/HttpDownloader.java b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/HttpDownloader.java new file mode 100644 index 0000000..7b4ef14 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/HttpDownloader.java @@ -0,0 +1,73 @@ +package com.droidkit.actors.sample; + +import com.droidkit.actors.*; +import com.droidkit.actors.dispatch.RunnableDispatcher; +import com.droidkit.actors.tasks.TaskActor; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * Created by ex3ndr on 18.08.14. + */ +public class HttpDownloader extends TaskActor { + + private static final RunnableDispatcher dispatcher = new RunnableDispatcher(2); + + public static String path(String url) { + return "/http_" + HashUtil.md5(url); + } + + public static Props prop(final String url) { + return Props.create(HttpDownloader.class, new ActorCreator() { + @Override + public HttpDownloader create() { + return new HttpDownloader(url); + } + }); + } + + public static ActorSelection download(String url) { + return new ActorSelection(prop(url), path(url)); + } + + private String url; + private Runnable runnable; + + public HttpDownloader(final String url) { + this.url = url; + runnable = new Runnable() { + @Override + public void run() { + try { + Log.d("HttpDownloader:startDownload:" + url); + URL urlSpec = new URL(url); + HttpURLConnection urlConnection = (HttpURLConnection) urlSpec.openConnection(); + urlConnection.setConnectTimeout(15000); + urlConnection.setReadTimeout(15000); + InputStream in = urlConnection.getInputStream(); + byte[] data = IOUtils.readAll(in); + complete(data); + Log.d("HttpDownloader:complete:" + url); + } catch (IOException e) { + error(e); + Log.d("HttpDownloader:error:" + url); + } + } + }; + setTimeOut(500); + } + + @Override + public void startTask() { + Log.d("HttpDownloader:startTask:" + url); + dispatcher.postAction(runnable); + } + + @Override + public void onTaskObsolete() { + Log.d("HttpDownloader:onTaskObsolete:" + url); + } +} diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/IOUtils.java b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/IOUtils.java new file mode 100644 index 0000000..de2a5e9 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/IOUtils.java @@ -0,0 +1,29 @@ +package com.droidkit.actors.sample; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Created by ex3ndr on 20.08.14. + */ +public class IOUtils { + public static byte[] readAll(InputStream in) throws IOException { + BufferedInputStream bufferedInputStream = new BufferedInputStream(in); + ByteArrayOutputStream os = new ByteArrayOutputStream(4096); + byte[] buffer = new byte[4 * 1024]; + int len; + int readed = 0; + try { + while ((len = bufferedInputStream.read(buffer)) >= 0) { + Thread.yield(); + os.write(buffer, 0, len); + readed += len; + } + } catch (java.io.IOException e) { + + } + return os.toByteArray(); + } +} diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/Log.java b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/Log.java new file mode 100644 index 0000000..c9d08bd --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/Log.java @@ -0,0 +1,15 @@ +package com.droidkit.actors.sample; + +import com.droidkit.actors.ActorRef; +import com.droidkit.actors.ActorSystem; + +/** + * Created by ex3ndr on 18.08.14. + */ +public class Log { + private static final ActorRef log = ActorSystem.system().actorOf(LogActor.class, "log"); + + public static void d(String s) { + log.send(s); + } +} diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/LogActor.java b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/LogActor.java new file mode 100644 index 0000000..c737cd7 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/LogActor.java @@ -0,0 +1,25 @@ +package com.droidkit.actors.sample; + +import android.util.Log; +import com.droidkit.actors.Actor; + +/** + * Created by ex3ndr on 14.08.14. + */ +public class LogActor extends Actor { + + @Override + public void preStart() { + Log.d("LOGACTOR#" + hashCode(), "preStart"); + } + + @Override + public void onReceive(Object message) { + Log.d("LOGACTOR#" + hashCode(), message + ""); + } + + @Override + public void postStop() { + Log.d("LOGACTOR#" + hashCode(), "postStop"); + } +} diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/MainActivity.java b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/MainActivity.java new file mode 100644 index 0000000..b0d0c05 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-sample/src/main/java/com/droidkit/actors/sample/MainActivity.java @@ -0,0 +1,73 @@ +package com.droidkit.actors.sample; + +import android.support.v7.app.ActionBarActivity; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; +import com.droidkit.actors.ActorRef; +import com.droidkit.actors.android.UiActor; +import com.droidkit.actors.android.UiActorDispatcher; + +import static com.droidkit.actors.ActorSystem.system; + +public class MainActivity extends ActionBarActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + system().addDispatcher("ui", new UiActorDispatcher(system())); + +// ActorRef log = system().actorOf(LogActor.class, "log"); + +// ActorRef downloader = system().actorOf(DownloadFile.class, "dow"); +// downloader.send("http://flirtyfleurs.com/wp-content/uploads/2012/10/pwg-sample-11_photo.jpg"); +// downloader.send("http://flirtyfleurs.com/wp-content/uploads/2012/10/pwg-sample-11_photo.jpg", 600); +// downloader.send("http://flirtyfleurs.com/wp-content/uploads/2012/10/pwg-sample-11_photo.jpg", 3000); + +// ActorRef dow2 = system().actorOf(DownloadFile.class, "dow2"); +// dow2.send(new String[]{ +// "http://flirtyfleurs.com/wp-content/uploads/2012/10/pwg-sample-11_photo.jpg", +// "http://isc.stuorg.iastate.edu/wp-content/uploads/sample.jpg", +// "http://imgsv.imaging.nikon.com/lineup/lens/zoom/normalzoom/af-s_dx_18-300mmf_35-56g_ed_vr/img/sample/sample4_l.jpg"}); + + final TextView view = (TextView) findViewById(R.id.demo); + final UiActor actor = new UiActor() { + @Override + public void onReceive(Object message) { + view.setText(message.toString()); + } + }; + + findViewById(R.id.demoButton).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + for (int i = 0; i < 10; i++) { + actor.getActorRef().send("message_" + i, i * 500); + } + } + }); + + new Thread() { + @Override + public void run() { + ActorRef ref = system().actorOf(CounterActor.class, "counter1"); + Log.d("Start"); + for (int i = 0; i < 1000000; i++) { + ref.send((Integer) i); + if (i % 1000 == 0) { + Log.d("Progress " + i); + try { + Thread.sleep(300); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + Log.d("End"); + } + }.start(); + + } +} \ No newline at end of file diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/drawable-hdpi/ic_launcher.png b/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..96a442e Binary files /dev/null and b/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/drawable-mdpi/ic_launcher.png b/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..359047d Binary files /dev/null and b/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/drawable-xhdpi/ic_launcher.png b/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..71c6d76 Binary files /dev/null and b/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/drawable-xxhdpi/ic_launcher.png b/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..4df1894 Binary files /dev/null and b/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/layout/activity_main.xml b/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..9097779 --- /dev/null +++ b/temp/droidkit-actors-0.2.48/actors-sample/src/main/res/layout/activity_main.xml @@ -0,0 +1,26 @@ + + + + + + +