#include <3ds.h> #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "memorystore.h" #include "olm/base64.hh" #include "util.h" #include #define SOC_ALIGN 0x1000 #define SOC_BUFFERSIZE 0x100000 #define SYNC_TIMEOUT 10000 #if DEBUG #define D #else #define D for(;0;) #endif #if DEBUG PrintConsole* topScreenDebugConsole = NULL; #endif #if DEBUG #define printf_top(f_, ...) do {consoleSelect(topScreenDebugConsole);printf((f_), ##__VA_ARGS__);} while(0) #else #define printf_top(f_, ...) do {} while(0) #endif namespace Matrix { #define POST_BUFFERSIZE 0x100000 static u32 *SOC_buffer = NULL; bool HTTPC_inited = false; bool haveHttpcSupport = false; Client::Client(std::string homeserverUrl, std::string matrixToken, Store* clientStore) { hsUrl = homeserverUrl; token = matrixToken; if (!clientStore) { clientStore = new MemoryStore; } store = clientStore; #if DEBUG if (!topScreenDebugConsole) { topScreenDebugConsole = new PrintConsole; consoleInit(GFX_TOP, topScreenDebugConsole); } #endif } std::string Client::getToken() const { return token; } bool Client::login(std::string username, std::string password, std::string device_id) { json_t* request = json_object(); json_object_set_new(request, "type", json_string("m.login.password")); json_t* identifier = json_object(); json_object_set_new(identifier, "type", json_string("m.id.user")); json_object_set_new(identifier, "user", json_string(username.c_str())); json_object_set_new(request, "identifier", identifier); json_object_set_new(request, "password", json_string(password.c_str())); json_object_set_new(request, "initial_device_display_name", json_string("Nintendo 3DS")); if(!device_id.empty()) { json_object_set_new(request, "device_id", json_string(device_id.c_str())); } json_t* ret = doRequest("POST", "/_matrix/client/v3/login", request); json_decref(request); printf_top("Result : %s\n" ,json_dumps(ret, JSON_ENSURE_ASCII | JSON_ESCAPE_SLASH)); const char* tokenCStr = json_object_get_string_value(ret, "access_token"); if (!tokenCStr) { if (ret) json_decref(ret); return false; } token = tokenCStr; json_decref(ret); return true; } void Client::logout() { json_t* ret = doRequest("POST", "/_matrix/client/v3/logout"); if (ret) { json_decref(ret); } } std::string Client::getUserId() { if (!userIdCache.empty()) { return userIdCache; } json_t* ret = doRequest("GET", "/_matrix/client/v3/account/whoami"); const char* userIdCStr = json_object_get_string_value(ret, "user_id"); if (!userIdCStr) { if (ret) json_decref(ret); return ""; } const char* deviceIdCStr = json_object_get_string_value(ret, "device_id"); std::string deviceIdStr = deviceIdCStr; std::string userIdStr = userIdCStr; json_decref(ret); userIdCache = std::string(userIdStr); deviceIdCache = std::string(deviceIdStr); return userIdCache; } std::string Client::getDeviceId() { if(!deviceIdCache.empty()){ return deviceIdCache; } json_t* ret = doRequest("GET", "/_matrix/client/v3/account/whoami"); const char* userIdCStr = json_object_get_string_value(ret, "user_id"); if (!userIdCStr) { if (ret) json_decref(ret); return ""; } const char* deviceIdCStr = json_object_get_string_value(ret, "device_id"); std::string deviceIdStr = deviceIdCStr; std::string userIdStr = userIdCStr; json_decref(ret); userIdCache = std::string(userIdStr); deviceIdCache = std::string(deviceIdStr); return deviceIdCache; } std::string Client::resolveRoom(std::string alias) { if (alias[0] == '!') { return alias; // this is already a room ID, nothing to do } json_t* ret = doRequest("GET", "/_matrix/client/v3/directory/room/" + urlencode(alias)); const char* roomIdCStr = json_object_get_string_value(ret, "room_id"); if (!roomIdCStr) { if (ret) json_decref(ret); return ""; } std::string roomIdStr = roomIdCStr; printf_top("Room ID: %s\n", roomIdStr.c_str()); json_decref(ret); return roomIdStr; } std::vector Client::getJoinedRooms() { std::vector rooms; json_t* ret = doRequest("GET", "/_matrix/client/v3/joined_rooms"); json_t* roomsArr = json_object_get(ret, "joined_rooms"); if (!roomsArr) { json_decref(ret); return rooms; } size_t index; json_t* value; json_array_foreach(roomsArr, index, value) { const char* val = json_string_value(value); if (val) { rooms.push_back(val); } } json_decref(ret); return rooms; } RoomInfo Client::getRoomInfo(std::string roomId) { // if we resolve the roomId here it only resolves once roomId = resolveRoom(roomId); RoomInfo info = { getRoomName(roomId), getRoomTopic(roomId), getRoomAvatar(roomId), }; return info; } ExtraRoomInfo Client::getExtraRoomInfo(std::string roomId) { roomId = resolveRoom(roomId); ExtraRoomInfo info; info.canonicalAlias = getCanonicalAlias(roomId); // next fetch the members std::string path = "/_matrix/client/v3/rooms/" + urlencode(roomId) + "/joined_members"; json_t* ret = doRequest("GET", path); if (!ret) { return info; } json_t* joined = json_object_get(ret, "joined"); if (!joined || json_typeof(joined) != JSON_OBJECT) { json_decref(ret); return info; } const char* mxid; json_t* member; json_object_foreach(joined, mxid, member) { const char* displayname = json_object_get_string_value(member, "display_name"); const char* avatarUrl = json_object_get_string_value(member, "avatar_url"); MemberInfo memInfo; if (displayname) { memInfo.displayname = displayname; } if (avatarUrl) { memInfo.avatarUrl = avatarUrl; } info.members[mxid] = memInfo; } json_decref(ret); return info; } MemberInfo Client::getMemberInfo(std::string userId, std::string roomId) { std::string displayname = ""; std::string avatarUrl = ""; if (roomId != "") { // first try fetching fro the room json_t* ret = getStateEvent(roomId, "m.room.member", userId); if (ret) { char* valCStr; valCStr = json_object_get_string_value(ret, "displayname"); if (valCStr) { displayname = valCStr; } valCStr = json_object_get_string_value(ret, "avatar_url"); if (valCStr) { avatarUrl = valCStr; } json_decref(ret); } } if (displayname == "") { // then attempt the account std::string path = "/_matrix/client/v3/profile/" + urlencode(userId); json_t* ret = doRequest("GET", path); if (ret) { char* valCStr; valCStr = json_object_get_string_value(ret, "displayname"); if (valCStr) { displayname = valCStr; } valCStr = json_object_get_string_value(ret, "avatar_url"); if (valCStr) { avatarUrl = valCStr; } json_decref(ret); } } MemberInfo info = { displayname, avatarUrl, }; return info; } std::string Client::getRoomName(std::string roomId) { json_t* ret = getStateEvent(roomId, "m.room.name", ""); const char* nameCStr = json_object_get_string_value(ret, "name"); if (!nameCStr) { if (ret) json_decref(ret); return ""; } std::string nameStr = nameCStr; json_decref(ret); return nameStr; } std::string Client::getRoomTopic(std::string roomId) { json_t* ret = getStateEvent(roomId, "m.room.topic", ""); const char* topicCStr = json_object_get_string_value(ret, "topic"); if (!topicCStr) { if (ret) json_decref(ret); return ""; } std::string topicStr = topicCStr; json_decref(ret); return topicStr; } std::string Client::getRoomAvatar(std::string roomId) { json_t* ret = getStateEvent(roomId, "m.room.avatar", ""); const char* urlCStr = json_object_get_string_value(ret, "url"); if (!urlCStr) { if (ret) json_decref(ret); return ""; } std::string urlStr = urlCStr; json_decref(ret); return urlStr; } std::string Client::getCanonicalAlias(std::string roomId) { json_t* ret = getStateEvent(roomId, "m.room.canonical_alias", ""); const char* aliasCStr = json_object_get_string_value(ret, "alias"); if (!aliasCStr) { if (ret) json_decref(ret); return ""; } std::string aliasStr = aliasCStr; json_decref(ret); return aliasStr; } void Client::sendReadReceipt(std::string roomId, std::string eventId) { roomId = resolveRoom(roomId); std::string path = "/_matrix/client/v3/rooms/" + urlencode(roomId) + "/receipt/m.read/" + urlencode(eventId); json_t* ret = doRequest("POST", path); if (ret) { json_decref(ret); } } void Client::uploadKeys(json_t* body) { std::string path = "/_matrix/client/v3/keys/upload"; /*char* jj = json_dumps(body, 0); printf_top("%s\n", jj); free(jj);*/ json_t* ret = doRequest("POST", path, body); if (ret) { /*char* j = json_dumps(ret, 0); printf_top("%s\n",j); free(j);*/ json_decref(ret); } } void Client::setTyping(std::string roomId, bool typing, u32 timeout) { roomId = resolveRoom(roomId); std::string userId = getUserId(); std::string path = "/_matrix/client/v3/rooms/" + urlencode(roomId) + "/typing/" + urlencode(userId); json_t* request = json_object(); json_object_set_new(request, "typing", json_boolean(typing)); json_object_set_new(request, "timeout", json_integer(timeout)); json_t* ret = doRequest("PUT", path, request); json_decref(request); json_decref(ret); } std::string Client::sendEmote(std::string roomId, std::string text) { json_t* request = json_object(); json_object_set_new(request, "msgtype", json_string("m.emote")); json_object_set_new(request, "body", json_string(text.c_str())); std::string eventId = sendMessage(roomId, request); json_decref(request); return eventId; } std::string Client::sendNotice(std::string roomId, std::string text) { json_t* request = json_object(); json_object_set_new(request, "msgtype", json_string("m.notice")); json_object_set_new(request, "body", json_string(text.c_str())); std::string eventId = sendMessage(roomId, request); json_decref(request); return eventId; } std::string Client::sendText(std::string roomId, std::string text) { json_t* request = json_object(); json_object_set_new(request, "msgtype", json_string("m.text")); json_object_set_new(request, "body", json_string(text.c_str())); std::string eventId = sendMessage(roomId, request); json_decref(request); return eventId; } std::string Client::sendMessage(std::string roomId, json_t* content) { return sendEvent(roomId, "m.room.message", content); } void Client::sendEventToDevice(const std::string& eventType, json_t* devices, json_t* content){ std::string txid = std::to_string(time(nullptr)) + "_REQ_" + std::to_string(requestId); std::string path = "/_matrix/client/v3/sendToDevice/" + urlencode(eventType) + "/" + urlencode(txid); json_t* ret = doRequest("PUT", path, content); if (ret) json_decref(ret); } std::string Client::sendEvent(std::string roomId, std::string eventType, json_t* content) { roomId = resolveRoom(roomId); std::string txid = std::to_string(time(NULL)) + "_REQ_" + std::to_string(requestId); std::string path = "/_matrix/client/v3/rooms/" + urlencode(roomId) + "/send/" + urlencode(eventType) + "/" + urlencode(txid); json_t* ret = doRequest("PUT", path, content); const char* eventIdCStr = json_object_get_string_value(ret, "event_id"); if (!eventIdCStr) { if (ret) json_decref(ret); return ""; } std::string eventIdStr = eventIdCStr; json_decref(ret); return eventIdStr; } json_t* Client::getStateEvent(std::string roomId, std::string type, std::string stateKey) { roomId = resolveRoom(roomId); std::string path = "/_matrix/client/v3/rooms/" + urlencode(roomId) + "/state/" + urlencode(type) + "/" + urlencode(stateKey); return doRequest("GET", path); } std::string Client::sendStateEvent(std::string roomId, std::string type, std::string stateKey, json_t* content) { roomId = resolveRoom(roomId); std::string path = "/_matrix/client/v3/rooms/" + urlencode(roomId) + "/state/" + urlencode(type) + "/" + urlencode(stateKey); json_t* ret = doRequest("PUT", path, content); const char* eventIdCStr = json_object_get_string_value(ret, "event_id"); if (!eventIdCStr) { if (ret) json_decref(ret); return ""; } std::string eventIdStr = eventIdCStr; json_decref(ret); return eventIdStr; } std::string Client::redactEvent(std::string roomId, std::string eventId, std::string reason) { roomId = resolveRoom(roomId); std::string txid = std::to_string(time(NULL)) + "_REQ_" + std::to_string(requestId); json_t* content = json_object(); if (reason != "") { json_object_set_new(content, "reason", json_string(reason.c_str())); } std::string path = "/_matrix/client/v3/rooms/" + urlencode(roomId) + "/redact/" + urlencode(eventId) + "/" + txid; json_t* ret = doRequest("PUT", path, content); json_decref(content); const char* eventIdCStr = json_object_get_string_value(ret, "event_id"); if (!eventIdCStr) { if (ret) json_decref(ret); return ""; } std::string eventIdStr = eventIdCStr; json_decref(ret); return eventIdStr; } void startSyncLoopWithoutClass(void* arg) { ((Client*)arg)->syncLoop(); } void Client::startSyncLoop() { stopSyncLoop(); // first we stop an already running sync loop isSyncing = true; stopSyncing = false; s32 prio = 0; svcGetThreadPriority(&prio, CUR_THREAD_HANDLE); syncThread = threadCreate(startSyncLoopWithoutClass, this, 8*1024, prio-1, -2, true); } void Client::stopSyncLoop() { stopSyncing = true; if (isSyncing) { threadJoin(syncThread, U64_MAX); threadFree(syncThread); } isSyncing = false; } void Client::setEventCallback(eventCallback cb) { callbacks.event = cb; } void Client::setLeaveRoomCallback(eventCallback cb) { callbacks.leaveRoom = cb; } void Client::setInviteRoomCallback(eventCallback cb) { callbacks.inviteRoom = cb; } void Client::setRoomInfoCallback(roomInfoCallback cb) { callbacks.roomInfo = cb; } void Client::setRoomLimitedCallback(roomLimitedCallback cb) { callbacks.roomLimited = cb; } void Client::processSync(json_t* sync) { json_t* rooms = json_object_get(sync, "rooms"); if (!rooms) { return; // nothing to do } json_t* leftRooms = json_object_get(rooms, "leave"); json_t* invitedRooms = json_object_get(rooms, "invite"); json_t* joinedRooms = json_object_get(rooms, "join"); const char* roomId; json_t* room; size_t index; json_t* event; if (leftRooms && callbacks.leaveRoom) { json_object_foreach(leftRooms, roomId, room) { // rooms that we left json_t* timeline = json_object_get(room, "timeline"); if (!timeline) { continue; } json_t* events = json_object_get(timeline, "events"); if (!events) { continue; } json_t* leaveEvent = NULL; json_array_foreach(events, index, event) { // check if the event type is m.room.member char* val; val = json_object_get_string_value(event, "type"); if (!val) { continue; } if (strcmp(val, "m.room.member") != 0) { continue; } // check if it is actually us val = json_object_get_string_value(event, "state_key"); if (!val) { continue; } if (strcmp(val, getUserId().c_str()) != 0) { continue; } // we do *not* check for event age as we don't have unsigned stuffs in our timeline due to our filter // so we just assume that the events arrive in the correct order (probably true) leaveEvent = event; } if (!leaveEvent) { printf_top("Left room %s without an event\n", roomId); continue; } callbacks.leaveRoom(roomId, leaveEvent); } } if (invitedRooms && callbacks.inviteRoom) { json_object_foreach(invitedRooms, roomId, room) { // rooms that we were invited to json_t* invite_state = json_object_get(room, "invite_state"); if (!invite_state) { continue; } json_t* events = json_object_get(invite_state, "events"); if (!events) { continue; } json_t* inviteEvent = NULL; json_array_foreach(events, index, event) { // check if the event type is m.room.member char* val; val = json_object_get_string_value(event, "type"); if (!val) { continue; } if (strcmp(val, "m.room.member") != 0) { continue; } // check if it is actually us val = json_object_get_string_value(event, "state_key"); if (!val) { continue; } if (strcmp(val, getUserId().c_str()) != 0) { continue; } // check for if it was an invite event json_t* content = json_object_get(event, "content"); if (!content) { continue; } val = json_object_get_string_value(content, "membership"); if (!val) { continue; } if (strcmp(val, "invite") != 0) { continue; } // we do *not* check for event age as we don't have unsigned stuffs in our timeline due to our filter // so we just assume that the events arrive in the correct order (probably true) inviteEvent = event; } if (!inviteEvent) { printf_top("Invite to room %s without an event\n", roomId); continue; } callbacks.inviteRoom(roomId, inviteEvent); } } if (joinedRooms) { json_object_foreach(joinedRooms, roomId, room) { // rooms that we are joined json_t* state = json_object_get(room, "state"); if (callbacks.roomInfo && state) { json_t* events = json_object_get(state, "events"); if (events) { RoomInfo info; bool addedInfo = false; json_array_foreach(events, index, event) { const char* typeCStr = json_object_get_string_value(event, "type"); if (!typeCStr) { continue; } json_t* content = json_object_get(event, "content"); if (!content) { continue; } if (strcmp(typeCStr, "m.room.name") == 0) { const char* nameCStr = json_object_get_string_value(content, "name"); if (nameCStr) { info.name = nameCStr; addedInfo = true; } } else if (strcmp(typeCStr, "m.room.topic") == 0) { const char* topicCStr = json_object_get_string_value(content, "topic"); if (topicCStr) { info.topic = topicCStr; addedInfo = true; } } else if (strcmp(typeCStr, "m.room.avatar") == 0) { const char* urlCStr = json_object_get_string_value(content, "url"); if (urlCStr) { info.avatarUrl = urlCStr; addedInfo = true; } } } if (addedInfo) { callbacks.roomInfo(roomId, info); } } } json_t* timeline = json_object_get(room, "timeline"); if (callbacks.roomLimited && timeline) { json_t* limited = json_object_get(timeline, "limited"); const char* prevBatch = json_object_get_string_value(timeline, "prev_batch"); if (limited && prevBatch && json_typeof(limited) == JSON_TRUE) { callbacks.roomLimited(roomId, prevBatch); } } if (callbacks.event && timeline) { json_t* events = json_object_get(timeline, "events"); if (events) { json_array_foreach(events, index, event) { callbacks.event(roomId, event); } } } } } } void Client::registerFilter() { static const char *json = "{" " \"account_data\": {" " \"types\": [" " \"m.direct\"" " ]" " }," " \"presence\": {" " \"limit\": 0," " \"types\": [\"none\"]" " }," " \"room\": {" " \"account_data\": {" " \"limit\": 0," " \"types\": [\"none\"]" " }," " \"ephemeral\": {" " \"limit\": 0," " \"types\": []" " }," " \"state\": {" " \"limit\": 3," " \"types\": [" " \"m.room.name\"," " \"m.room.topic\"," " \"m.room.avatar\"" " ]" " }," " \"timeline\": {" " \"limit\": 10," " \"lazy_load_members\": true" " }" " }," " \"event_format\": \"client\"," " \"event_fields\": [" " \"type\"," " \"content\"," " \"sender\"," " \"state_key\"," " \"event_id\"," " \"origin_server_ts\"," " \"redacts\"" " ]" "}"; json_error_t error; json_t* filter = json_loads(json, 0, &error); if (!filter) { printf_top("PANIC!!!!! INVALID FILTER JSON!!!!\n"); printf_top("%s\n", error.text); printf_top("At %d:%d (%d)\n", error.line, error.column, error.position); return; } std::string userId = getUserId(); json_t* ret = doRequest("POST", "/_matrix/client/v3/user/" + urlencode(userId) + "/filter", filter); json_decref(filter); const char* filterIdCStr = json_object_get_string_value(ret, "filter_id"); if (!filterIdCStr) { if (ret) json_decref(ret); return; } std::string filterIdStr = filterIdCStr; json_decref(ret); store->setFilterId(filterIdStr); } void Client::syncLoop() { u32 timeout = 60; while (true) { if (stopSyncing) { return; } std::string token = store->getSyncToken(); std::string filterId = store->getFilterId(); if (filterId == "") { registerFilter(); filterId = store->getFilterId(); } CURLcode res; json_t* ret = doSync(token, filterId, timeout, &res); if (ret) { timeout = 60; // set the token for the next batch const char* tokenCStr = json_object_get_string_value(ret, "next_batch"); if (tokenCStr) { store->setSyncToken(tokenCStr); } else { store->setSyncToken(""); } processSync(ret); json_decref(ret); } else { if (res == CURLE_OPERATION_TIMEDOUT) { timeout += 10*60; printf_top("Timeout reached, increasing it to %lu\n", timeout); } } svcSleepThread((u64)1000000ULL * (u64)200); } } json_t* Client::doSync(std::string token, std::string filter, u32 timeout, CURLcode* res) { // printf_top("Doing sync with token %s\n", token.c_str()); std::string query = "?full_state=false&timeout=" + std::to_string(SYNC_TIMEOUT) + "&filter=" + urlencode(filter); if (token != "") { query += "&since=" + token; } return doRequest("GET", "/_matrix/client/v3/sync" + query, NULL, timeout, res); } size_t DoRequestWriteCallback(char *contents, size_t size, size_t nmemb, void *userp) { // printf_top("----\n%s\n", ((std::string*)userp)->c_str()); ((std::string*)userp)->append((char*)contents, size * nmemb); return size * nmemb; } bool doingCurlRequest = false; bool doingHttpcRequest = false; json_t* Client::doRequest(const char* method, std::string path, json_t* body, u32 timeout, CURLcode* retRes) { std::string url = hsUrl + path; requestId++; return doRequestCurl(method, url, body, timeout, retRes); } CURLM* curl_multi_handle; std::map curl_handles_done; Thread curl_multi_loop_thread; void curl_multi_loop(void* p) { int openHandles = 0; while(true) { CURLMcode mc = curl_multi_perform(curl_multi_handle, &openHandles); if (mc != CURLM_OK) { printf_top("curl multi fail: %u\n", mc); } // curl_multi_wait(curl_multi_handle, NULL, 0, 1000, &openHandles); CURLMsg* msg; int msgsLeft; while ((msg = curl_multi_info_read(curl_multi_handle, &msgsLeft))) { if (msg->msg == CURLMSG_DONE) { curl_handles_done[msg->easy_handle] = msg->data.result; } } if (!openHandles) { svcSleepThread((u64)1000000ULL * 100); } } } json_t* Client::doRequestCurl(const char* method, std::string url, json_t* body, u32 timeout, CURLcode* retRes) { // printf_top("Opening Request %d with CURL\n%s\n", requestId, url.c_str()); if (!SOC_buffer) { SOC_buffer = (u32*)memalign(0x1000, POST_BUFFERSIZE); if (!SOC_buffer) { return NULL; } if (socInit(SOC_buffer, POST_BUFFERSIZE) != 0) { return NULL; } curl_multi_handle = curl_multi_init(); s32 prio = 0; svcGetThreadPriority(&prio, CUR_THREAD_HANDLE); curl_multi_loop_thread = threadCreate(curl_multi_loop, NULL, 8*1024, prio-1, -2, true); } CURL* curl = curl_easy_init(); if (!curl) { printf_top("curl init failed\n"); return NULL; } std::string readBuffer; struct curl_slist* headers = NULL; if (token != "") { headers = curl_slist_append(headers, ("Authorization: Bearer " + token).c_str()); } char* bodyStr = NULL; if (body) { headers = curl_slist_append(headers, "Content-Type: application/json"); bodyStr = json_dumps(body, JSON_ENSURE_ASCII | JSON_ESCAPE_SLASH); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, bodyStr); } curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); curl_easy_setopt(curl, CURLOPT_USERAGENT, "3ds"); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DoRequestWriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout); curl_multi_add_handle(curl_multi_handle, curl); while (curl_handles_done.count(curl) == 0) { svcSleepThread((u64)1000000ULL * 1); } CURLcode res = curl_handles_done[curl]; curl_handles_done.erase(curl); curl_multi_remove_handle(curl_multi_handle, curl); // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); // curl_easy_setopt(curl, CURLOPT_STDERR, stdout); curl_easy_cleanup(curl); if (bodyStr) free(bodyStr); if (retRes) *retRes = res; if (res != CURLE_OK) { printf_top("curl res not ok %d\n", res); return NULL; } // printf_top("++++\n%s\n", readBuffer.c_str()); // printf_top("Body size: %d\n", readBuffer.length()); json_error_t error; json_t* content = json_loads(readBuffer.c_str(), 0, &error); if (!content) { printf_top("Failed to parse json\n"); return NULL; } return content; } void Client::print_json(json_t *json){ char* data = json_dumps(json, 0); puts(data); free(data); } void Client::generate_otk(size_t otkcount) { size_t len = acc.generate_one_time_keys_random_length(otkcount); std::unique_ptr otkrandom = std::make_unique(len); PS_GenerateRandomBytes(otkrandom.get(), len); acc.generate_one_time_keys(otkcount, otkrandom.get(), len); } void Client::generate_otk() { size_t otkcount = acc.max_number_of_one_time_keys() / 2; generate_otk(otkcount); } void Client::generate_device_key() { size_t len = acc.new_account_random_length(); std::unique_ptr random = std::make_unique(len); PS_GenerateRandomBytes(random.get(), len); acc.new_account(random.get(), len); } void Client::generate_fallback_key() { size_t len = acc.generate_fallback_key_random_length(); std::unique_ptr random = std::make_unique(len); PS_GenerateRandomBytes(random.get(), len); acc.generate_fallback_key(random.get(), len); } // Make sure to remove any unsigned and signatures block before doing this ! void Client::sign_json(json_t* json) { puts("Signing json"); char* jsonStr = json_dumps(json, JSON_COMPACT | JSON_SORT_KEYS); size_t signlen = acc.signature_length(); size_t ptrlen = olm::encode_base64_length(signlen); std::unique_ptr signature = std::make_unique(ptrlen); acc.sign(reinterpret_cast(jsonStr), strlen(jsonStr), signature.get() + ptrlen - signlen, signlen); free(jsonStr); olm::encode_base64(signature.get() + ptrlen - signlen, signlen, signature.get()); json_t* signitem = json_object(); std::cout << std::string(reinterpret_cast(signature.get()), ptrlen) << std::endl; json_object_set_new(signitem, ("ed25519:" + getDeviceId()).c_str(), json_stringn(reinterpret_cast(signature.get()), ptrlen)); json_t* signobj = json_object(); json_object_set_new(signobj, getUserId().c_str(), signitem); json_object_set_new(json, "signatures", signobj); } json_t* Client::get_device_keys() { json_t* device_keys = json_object(); json_t* algorithms = json_array(); json_array_append_new(algorithms, json_string("m.olm.v1.curve25519-aes-sha2")); json_array_append_new(algorithms, json_string("m.megolm.v1.aes-sha2")); json_object_set_new(device_keys, "algorithms", algorithms); json_object_set_new(device_keys, "device_id", json_string(getDeviceId().c_str())); json_object_set_new(device_keys, "user_id", json_string(getUserId().c_str())); // Extract keys size_t acclen = acc.get_identity_json_length(); std::unique_ptr acckeys = std::make_unique(acclen); size_t bytes = acc.get_identity_json(acckeys.get(), acclen); std::cout << "Parsing bytes" << std::endl; json_error_t error; sleep(5); std::cout << "Heading into it..." << std::endl; json_t* keys = json_loadb(reinterpret_cast(acckeys.get()), bytes, 0, &error); if (keys == nullptr) { printf("error: on line %d at char %d: %s\n", error.line, error.column, error.text); sleep(10); } std::cout << "Reloaded data" << std::endl; std::cout << "Adding bits" << std::endl; // Merge keys json_object_set(device_keys, ("ed25519:" + getDeviceId()).c_str(), json_object_get(keys, "ed25519")); json_object_set(device_keys, ("curve25519:" + getDeviceId()).c_str(), json_object_get(keys, "curve25519")); json_decref(keys); std::cout << "Finalizing" << std::endl; return device_keys; } json_t* Client::get_fallback_keys() { json_t* fallback_keys = json_object(); // Extract keys size_t acclen = acc.get_unpublished_fallback_key_json_length(); auto acckeys = std::make_unique(acclen); size_t bytes = acc.get_unpublished_fallback_key_json(acckeys.get(), acclen); json_t* keys = json_loadb(reinterpret_cast(acckeys.get()), bytes, 0, nullptr); json_t* keyobj = json_object_get(keys, "curve25519"); void* iter = json_object_iter(keyobj); while (iter != nullptr) { const char* keyid = json_object_iter_key(iter); json_t* keyval = json_object_iter_value(iter); json_t* tosign = json_object(); json_object_set(tosign, "key", keyval); // object is fallback key json_object_set_new(tosign, "fallback", json_true()); sign_json(tosign); json_object_set(fallback_keys, (std::string("signed_curve25519:") + keyid).c_str(), tosign); iter = json_object_iter_next(keyobj, iter); } json_decref(keys); return fallback_keys; } json_t* Client::get_unpublished_otk() { json_t* otk_keys = json_object(); // Extract keys size_t acclen = acc.get_one_time_keys_json_length(); auto acckeys = std::make_unique(acclen); size_t bytes = acc.get_one_time_keys_json(acckeys.get(), acclen); json_t* keys = json_loadb(reinterpret_cast(acckeys.get()), bytes, 0, nullptr); json_t* keyobj = json_object_get(keys, "curve25519"); void* iter = json_object_iter(keyobj); while (iter != nullptr) { const char* keyid = json_object_iter_key(iter); json_t* keyval = json_object_iter_value(iter); json_t* tosign = json_object(); json_object_set(tosign, "key", keyval); sign_json(tosign); json_object_set(otk_keys, (std::string("signed_curve25519:") + keyid).c_str(), tosign); iter = json_object_iter_next(keyobj, iter); } json_decref(keys); return otk_keys; } void Client::save_keys() { size_t pacclen = olm::pickle_length(acc); auto m = std::make_unique(pacclen); olm::pickle(m.get(), acc); FILE* file = fopen("secret-keys", "w"); fwrite(m.get(), pacclen, 1, file); fclose(file); } void Client::upload_keys() { // Upload the keys --- // Build device key json json_t* upload_keys = json_object(); json_t* device_keys = get_device_keys(); std::cout << "Gathered device keys" << std::endl; // Sign keys block sign_json(device_keys); std::cout << "Signed device keys" << std::endl; json_object_set_new(upload_keys, "device_keys", device_keys); json_t* fallback_keys = get_fallback_keys(); std::cout << "Signed fallback keys" << std::endl; // fallback keys are already signed json_object_set_new(upload_keys, "fallback_keys", fallback_keys); // OTK keys json_t* otk = get_unpublished_otk(); std::cout << "Signed OTK keys" << std::endl; // OTK keys are already signed json_object_set_new(upload_keys, "one_time_keys", otk); uploadKeys(upload_keys); std::cout << "Keys uploaded !" << std::endl; print_json(upload_keys); json_decref(upload_keys); acc.mark_keys_as_published(); // Whether a key or not is published is saved, let's save again save_keys(); } void Client::start_encryption() { // std::FILE* file = std::fopen("secret-keys", "r"); std::ifstream ifs("secret-keys", std::ios::binary | std::ios::ate); if (!ifs.is_open()) { std::cout << "No secret keys found, generating " << std::endl; // Generate and save to file // Device keys generate_device_key(); std::cout << "Generated device key" << std::endl; // One time keys generate_otk(); std::cout << "Generated OTK key" << std::endl; // Fallback keys generate_fallback_key(); std::cout << "Generated fallback key" << std::endl; upload_keys(); } else { // Load from file std::ifstream::pos_type fsize = ifs.tellg(); if (fsize == -1) { std::cout << "What." << std::endl; return; } auto data = std::make_unique(fsize); ifs.seekg(0, std::ios::beg); ifs.read(reinterpret_cast(data.get()), static_cast(fsize)); olm::unpickle(data.get(), data.get() + fsize, acc); } } }; // namespace Matrix