async def get_binary_details(region: str, model: str, firmware: str): """ Gets the binary details such as filename and decrypt key.\n `firmware` is the firmware code of the device that you got from `/latest` endpoint.\n\n `decrypt_key` is used for decrypting the file after downloading. It presents a hex string. Pass it to `/download` endpoint. """ # Create new session. key = Session.from_response( requests.post(Constants.NONCE_URL, headers=Constants.HEADERS())) # Make the request. req = requests.post(url=Constants.BINARY_INFO_URL, data=Constants.BINARY_INFO(firmware, region, model, key.logic_check(firmware)), headers=Constants.HEADERS(key.encrypted_nonce, key.auth), cookies=Constants.COOKIES(key.session_id)) # Read the request. if req.status_code == 200: data = KiesData(req.text) # Return error when binary couldn't be found. if data.status_code != "200": raise HTTPException(400, "Firmware couldn't be found.") # If file extension ends with .enc4 that means it is using version 4 encryption, otherwise 2 (.enc2). ENCRYPT_VERSION = 4 if str( data.body["BINARY_NAME"]).endswith("4") else 2 # Get binary details return { "display_name": data.body["DEVICE_MODEL_DISPLAYNAME"], "size": int(data.body["BINARY_BYTE_SIZE"]), "filename": data.body["BINARY_NAME"], "path": data.body["MODEL_PATH"], "version": data.body["CURRENT_OS_VERSION"].replace("(", " ("), "encrypt_version": ENCRYPT_VERSION, # Convert bytes to GB, so it will be more readable for an end-user. "size_readable": "{:.2f} GB".format(float(data.body["BINARY_BYTE_SIZE"]) / 1024 / 1024 / 1024), # Generate decrypted key for decrypting the file after downloading. # Decrypt key gives a list of bytes, but as it is not possible to send as query parameter, # we are converting it to a single HEX value. "decrypt_key": \ key.getv2key(firmware, model, region).hex() if ENCRYPT_VERSION == 2 else \ key.getv4key(data.body["LATEST_FW_VERSION"], data.body["LOGIC_VALUE_FACTORY"]).hex(), # A URL of samsungmobile that includes release changelogs. # Not available for every device. "firmware_changelog_url": data.body["DESCRIPTION"], "platform": data.body["DEVICE_PLATFORM"] } # Raise HTTPException when status is not 200. raise HTTPException( 500, "Something went wrong when sending request to Kies servers.")
async def download_binary(filename: str, path: str, decrypt_key: str, request: Request): """ Downloads the firmware and decrypts the file during download automatically.\n **Do not try the endpoint in the interactive API docs, because as it returns a file, it doesn't work in OpenAPI.** """ # Create new session. key = Session.from_response( requests.post(Constants.NONCE_URL, headers=Constants.HEADERS())) # Make the request. req = requests.post(url=Constants.BINARY_FILE_URL, data=Constants.BINARY_FILE( filename, key.logic_check(filename.split(".")[0][-16:])), headers=Constants.HEADERS(key.encrypted_nonce, key.auth), cookies=Constants.COOKIES(key.session_id)) # Refresh session. key.refresh_session(req) # Read the request. if req.status_code == 200: data = KiesData(req.text) # Return error when binary couldn't be found. if data.status_code != "200": raise HTTPException( int(data.status_code), f"The service returned {data.status_code}. Maybe parameters are invalid?" ) # Else, make another request to get the binary. else: # Check and parse the range header. START_RANGE, END_RANGE = ( 0, 0 ) if "Range" not in request.headers else Constants.parse_range_header( request.headers["Range"]) # Check if range is invalid. if START_RANGE == -1 or END_RANGE == -1: raise HTTPException( 416, "Range is invalid. If you didn't meant input a 'Range' header, remove it from request." ) # Create headers. _headers = Constants.HEADERS(key.encrypted_nonce, key.auth) # If incoming request contains a Range header, directly pass it to request. if "Range" in _headers: _headers["Range"] = "bytes=" + Constants.make_range_header( START_RANGE, END_RANGE) # Another request for streaming the firmware. req2 = requests.get(url=Constants.BINARY_DOWNLOAD_URL, params="file=" + path + filename, headers=_headers, cookies=Constants.COOKIES(key.session_id), stream=True) # Check if status code is not 200 or 206. if req2.status_code not in [200, 206]: # Raise HTTPException when status is not success. raise HTTPException( req2.status_code, f"The service returned {req2.status_code}. Maybe parameters are invalid?" ) # Get the total size of binary. CONTENT_LENGTH = int(req2.headers["Content-Length"]) # Decrypt bytes while downloading the file. # So this way, we can directly serve the bytes to the client without downloading to the disk. return StreamingResponse( Decryptor(req2, bytes.fromhex(decrypt_key)), media_type="application/zip", headers={ "Content-Disposition": "attachment;filename=" + filename.replace(".enc4", "").replace(".enc2", ""), "Content-Length": str(CONTENT_LENGTH), "Accept-Ranges": "bytes", "Content-Range": f"bytes {Constants.make_range_header(START_RANGE, END_RANGE)}" }, status_code=200 if not START_RANGE else 206) # Raise HTTPException when status is not 200. raise HTTPException( 500, "Something went wrong when sending request to Kies servers.")