Skip to content

Commit 5ab92a7

Browse files
negasorafuzyll
authored andcommitted
Add auto downloading of project file dependencies, clean up download APIs
1 parent ae1dbe7 commit 5ab92a7

File tree

8 files changed

+98
-63
lines changed

8 files changed

+98
-63
lines changed

binaryninjaapi.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22249,13 +22249,21 @@ namespace BinaryNinja::Collaboration
2224922249
*/
2225022250
void DeleteSnapshot(const Ref<CollabSnapshot> snapshot);
2225122251

22252+
/*!
22253+
Download a remote file and possibly dependencies to its project
22254+
Dependency download behavior depends on the value of the collaboration.autoDownloadFileDependencies setting
22255+
\param progress Function to call on progress updates
22256+
\throws RemoteException If there is an error in any request or if the remote is not connected
22257+
*/
22258+
void Download(ProgressFunction progress = DefaultProgressFunction);
22259+
2225222260
/*!
2225322261
Download the contents of a remote file
2225422262
\param progress Function to call on progress updates
2225522263
\return Contents of the file
2225622264
\throws RemoteException If there is an error in any request or if the remote is not connected
2225722265
*/
22258-
std::vector<uint8_t> Download(ProgressFunction progress = {});
22266+
std::vector<uint8_t> DownloadContents(ProgressFunction progress = {});
2225922267

2226022268
/*!
2226122269
Get the current user positions for this file

binaryninjacore.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@
3737
// Current ABI version for linking to the core. This is incremented any time
3838
// there are changes to the API that affect linking, including new functions,
3939
// new types, or modifications to existing functions or types.
40-
#define BN_CURRENT_CORE_ABI_VERSION 146
40+
#define BN_CURRENT_CORE_ABI_VERSION 147
4141

4242
// Minimum ABI version that is supported for loading of plugins. Plugins that
4343
// are linked to an ABI version less than this will not be able to load and
4444
// will require rebuilding. The minimum version is increased when there are
4545
// incompatible changes that break binary compatibility, such as changes to
4646
// existing types or functions.
47-
#define BN_MINIMUM_CORE_ABI_VERSION 146
47+
#define BN_MINIMUM_CORE_ABI_VERSION 147
4848

4949
#ifdef __GNUC__
5050
#ifdef BINARYNINJACORE_LIBRARY
@@ -8449,7 +8449,8 @@ extern "C"
84498449
BINARYNINJACOREAPI bool BNRemoteFilePullSnapshots(BNRemoteFile* file, BNProgressFunction progress, void* progressContext);
84508450
BINARYNINJACOREAPI BNCollaborationSnapshot* BNRemoteFileCreateSnapshot(BNRemoteFile* file, const char* name, uint8_t* contents, size_t contentsSize, uint8_t* analysisCacheContents, size_t analysisCacheContentsSize, uint8_t* fileContents, size_t fileContentsSize, const char** parentIds, size_t parentIdCount, BNProgressFunction progress, void* progressContext);
84518451
BINARYNINJACOREAPI bool BNRemoteFileDeleteSnapshot(BNRemoteFile* file, BNCollaborationSnapshot* snapshot);
8452-
BINARYNINJACOREAPI bool BNRemoteFileDownload(BNRemoteFile* file, BNProgressFunction progress, void* progressCtxt, uint8_t** data, size_t* size);
8452+
BINARYNINJACOREAPI bool BNRemoteFileDownload(BNRemoteFile* file, BNProgressFunction progress, void* progressCtxt);
8453+
BINARYNINJACOREAPI bool BNRemoteFileDownloadContents(BNRemoteFile* file, BNProgressFunction progress, void* progressCtxt, uint8_t** data, size_t* size);
84538454
BINARYNINJACOREAPI char* BNRemoteFileRequestUserPositions(BNRemoteFile* file);
84548455
BINARYNINJACOREAPI char* BNRemoteFileRequestChatLog(BNRemoteFile* file);
84558456

collaboration.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1853,14 +1853,23 @@ void RemoteFile::DeleteSnapshot(const Ref<CollabSnapshot> snapshot)
18531853
}
18541854

18551855

1856-
std::vector<uint8_t> RemoteFile::Download(ProgressFunction progress)
1856+
void RemoteFile::Download(ProgressFunction progress)
1857+
{
1858+
ProgressContext pctxt;
1859+
pctxt.callback = progress;
1860+
if (!BNRemoteFileDownload(m_object, ProgressCallback, &pctxt))
1861+
throw RemoteException("Failed to download file");
1862+
}
1863+
1864+
1865+
std::vector<uint8_t> RemoteFile::DownloadContents(ProgressFunction progress)
18571866
{
18581867
ProgressContext pctxt;
18591868
pctxt.callback = progress;
18601869
size_t size = 0;
18611870
uint8_t* data;
1862-
if (!BNRemoteFileDownload(m_object, ProgressCallback, &pctxt, &data, &size))
1863-
throw SyncException("Failed to download file");
1871+
if (!BNRemoteFileDownloadContents(m_object, ProgressCallback, &pctxt, &data, &size))
1872+
throw SyncException("Failed to download file contents");
18641873

18651874
std::vector<uint8_t> out;
18661875
out.insert(out.end(), &data[0], &data[size]);

python/collaboration/examples/download_everything.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
2020
# IN THE SOFTWARE.
2121

22-
from pathlib import Path
2322
import sys
2423

2524
import binaryninja
@@ -40,18 +39,13 @@ def main():
4039
# Pull every file from every project
4140
for project in remote.projects:
4241
for file in project.files:
43-
bndb_path = file.default_path
44-
print(f"{project.name}/{file.name} BNDB at {bndb_path}")
42+
print(f"{project.name}/{file.name} BNDB at {file.default_path}")
4543

4644
try:
47-
metadata: binaryninja.FileMetadata = file.download_to_bndb(bndb_path)
48-
49-
for v in metadata.existing_views:
50-
if v == 'Raw':
51-
continue
52-
bv = metadata.get_view_of_type(v)
45+
file.download()
46+
with binaryninja.load(file.core_file) as bv:
5347
# Show the entry point to demonstrate we have pulled the analyzed file
54-
print(f"{project.name}/{file.name} {v} Entrypoint @ 0x{bv.entry_point:08x}")
48+
print(f"{project.name}/{file.name} {bv.view_type} Entrypoint @ 0x{bv.entry_point:08x}")
5549
except InterruptedError as e:
5650
# In case of ^C
5751
raise e

python/collaboration/examples/multitool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ def main():
237237
print(f"Unknown File: {args.name}")
238238
sys.exit(1)
239239
with TqdmProgress(desc="", leave=False) as t:
240-
file.download(lambda cur, max: t.progress(cur, max))
240+
file.download_contents(lambda cur, max: t.progress(cur, max))
241241
print_file_info(file)
242242
elif args.file_command == "delete":
243243
file = project.get_file_by_name(args.name)

python/collaboration/examples/sync_test.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,27 +56,27 @@ def main():
5656
project_dir = Path(file.default_path).parent
5757

5858
print(f'Snapshots: {[snapshot.id for snapshot in file.snapshots]}')
59-
bv = binaryninja.load(file.default_path)
60-
view_type = bv.view_type
61-
assert collaboration.RemoteFile.get_for_bv(bv) == file
59+
with binaryninja.load(file.default_path) as bv:
60+
assert collaboration.RemoteFile.get_for_bv(bv) == file
61+
assert bv.entry_function is not None, "Failed to get binary entry function"
6262

63-
print(f'Setting entry function at 0x{bv.entry_function.start:08x} name to \'entry_function\'')
64-
bv.entry_function.name = 'entry_function'
65-
bv.file.save_auto_snapshot()
63+
print(f'Setting entry function at 0x{bv.entry_function.start:08x} name to \'entry_function\'')
64+
bv.entry_function.name = 'entry_function'
65+
bv.file.save_auto_snapshot()
6666

67-
file.sync(bv, lambda conflicts: False)
68-
print(f'Snapshots: {[snapshot.id for snapshot in file.snapshots]}')
67+
file.sync(bv, lambda conflicts: False)
68+
print(f'Snapshots: {[snapshot.id for snapshot in file.snapshots]}')
6969

70-
# Try deleting the bndb, redownload and see if the function name is preserved
71-
bv.file.close()
70+
# Delete the bndb, redownload and see if the function name is preserved
7271
Path(file.default_path).unlink()
7372

7473
print(f'Redownloading {project.name}/{file.name}...')
75-
metadata = file.download_to_bndb()
76-
bv = metadata.get_view_of_type(view_type)
77-
print(f'Entry function name: {bv.entry_function.name}')
78-
assert bv.entry_function.name == 'entry_function'
79-
bv.file.close()
74+
file.download()
75+
with binaryninja.load(file.core_file) as bv:
76+
assert bv.entry_function is not None, "Failed to get binary entry function after redownload"
77+
78+
print(f'Entry function name: {bv.entry_function.name}')
79+
assert bv.entry_function.name == 'entry_function'
8080

8181
finally:
8282
# Clean up

python/collaboration/file.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,19 @@ def delete_snapshot(self, snapshot: 'snapshot.CollabSnapshot'):
374374
if not core.BNRemoteFileDeleteSnapshot(self._handle, snapshot._handle):
375375
raise RuntimeError(util._last_error())
376376

377-
def download(self, progress: 'util.ProgressFuncType' = util.nop) -> bytes:
377+
def download(self, progress: 'util.ProgressFuncType' = util.nop):
378+
"""
379+
Download a remote file and possibly dependencies to its project
380+
Dependency download behavior depends on the value of the collaboration.autoDownloadFileDependencies setting
381+
382+
:param progress: Function to call on progress updates
383+
:raises: RuntimeError if there was an error
384+
"""
385+
value = core.BNRemoteFileDownload(self._handle, util.wrap_progress(progress), None)
386+
if not value:
387+
raise RuntimeError(util._last_error())
388+
389+
def download_contents(self, progress: 'util.ProgressFuncType' = util.nop) -> bytes:
378390
"""
379391
Download the contents of a remote file
380392
@@ -384,7 +396,7 @@ def download(self, progress: 'util.ProgressFuncType' = util.nop) -> bytes:
384396
"""
385397
data = (ctypes.POINTER(ctypes.c_ubyte))()
386398
size = ctypes.c_size_t()
387-
value = core.BNRemoteFileDownload(self._handle, util.wrap_progress(progress), None, data, size)
399+
value = core.BNRemoteFileDownloadContents(self._handle, util.wrap_progress(progress), None, data, size)
388400
if not value:
389401
raise RuntimeError(util._last_error())
390402
return bytes(ctypes.cast(data, ctypes.POINTER(ctypes.c_uint8 * size.value)).contents)
@@ -399,6 +411,7 @@ def download_to_bndb(self, path: Optional[str] = None, progress: 'util.ProgressF
399411
:return: Constructed FileMetadata object
400412
:raises: RuntimeError if there was an error
401413
"""
414+
# TODO: deprecated, use RemoteFile.download() and ProjectFile.export()
402415
if path is None:
403416
path = self.default_path
404417
file = databasesync.download_file(self, path, util.split_progress(progress, 0, [0.5, 0.5]))

rust/src/collaboration/file.rs

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -394,50 +394,60 @@ impl RemoteFile {
394394
unsafe { BnString::into_string(result) }
395395
}
396396

397-
// TODO: AsRef<Path>
398-
/// Download a file from its remote, saving all snapshots to a database in the
399-
/// specified location. Returns a FileContext for opening the file later.
397+
/// Download a remote file and possibly dependencies to its project
398+
/// Dependency download behavior depends on the value of the collaboration.autoDownloadFileDependencies setting
400399
///
401-
/// * `db_path` - File path for saved database
402-
/// * `progress_function` - Function to call for progress updates
403-
pub fn download(&self, db_path: &Path) -> Result<Ref<FileMetadata>, ()> {
404-
sync::download_file(self, db_path)
400+
/// * `progress` - Function to call on progress updates
401+
pub fn download(&self) -> Result<(), ()> {
402+
self.download_with_progress(NoProgressCallback)
405403
}
406404

407-
// TODO: AsRef<Path>
408-
/// Download a file from its remote, saving all snapshots to a database in the
409-
/// specified location. Returns a FileContext for opening the file later.
405+
/// Download a remote file and possibly dependencies to its project
406+
/// Dependency download behavior depends on the value of the collaboration.autoDownloadFileDependencies setting
410407
///
411-
/// * `db_path` - File path for saved database
412-
/// * `progress_function` - Function to call for progress updates
413-
pub fn download_with_progress<F>(
414-
&self,
415-
db_path: &Path,
416-
progress_function: F,
417-
) -> Result<Ref<FileMetadata>, ()>
408+
/// * `progress` - Function to call on progress updates
409+
pub fn download_with_progress<P>(&self, mut progress: P) -> Result<(), ()>
418410
where
419-
F: ProgressCallback,
411+
P: ProgressCallback,
420412
{
421-
sync::download_file_with_progress(self, db_path, progress_function)
413+
let success = unsafe {
414+
BNRemoteFileDownload(
415+
self.handle.as_ptr(),
416+
Some(P::cb_progress_callback),
417+
&mut progress as *mut P as *mut c_void,
418+
)
419+
};
420+
success.then_some(()).ok_or(())
422421
}
423422

424423
/// Download a remote file and save it to a BNDB at the given `path`, returning the associated [`FileMetadata`].
425-
pub fn download_database(&self, path: &Path) -> Result<Ref<FileMetadata>, ()> {
426-
let file = self.download(path)?;
427-
let database = file.database().ok_or(())?;
428-
self.sync(&database, DatabaseConflictHandlerFail, NoNameChangeset)?;
429-
Ok(file)
424+
/// Download a file from its remote, saving all snapshots to a database in the
425+
/// specified location. Returns a FileContext for opening the file later.
426+
///
427+
/// * `path` - File path for saved database
428+
pub fn download_database(&self, path: impl AsRef<Path>) -> Result<Ref<FileMetadata>, ()> {
429+
//TODO: deprecated, use RemoteFile.download() and ProjectFile.export()
430+
self.download_database_with_progress(path, NoProgressCallback)
430431
}
431432

432-
// TODO: This might be a bad helper... maybe remove...
433-
/// Download a remote file and save it to a BNDB at the given `path`.
433+
/// Download a remote file and save it to a BNDB at the given `path`, returning the associated [`FileMetadata`].
434+
/// Download a file from its remote, saving all snapshots to a database in the
435+
/// specified location. Returns a FileContext for opening the file later.
436+
///
437+
/// * `path` - File path for saved database
438+
/// * `progress_function` - Function to call for progress updates
434439
pub fn download_database_with_progress(
435440
&self,
436-
path: &Path,
441+
path: impl AsRef<Path>,
437442
progress: impl ProgressCallback,
438443
) -> Result<Ref<FileMetadata>, ()> {
444+
//TODO: deprecated, use RemoteFile.download_with_progress() and ProjectFile.export()
439445
let mut progress = progress.split(&[50, 50]);
440-
let file = self.download_with_progress(path, progress.next_subpart().unwrap())?;
446+
let file = sync::download_file_with_progress(
447+
self,
448+
path.as_ref(),
449+
progress.next_subpart().unwrap(),
450+
)?;
441451
let database = file.database().ok_or(())?;
442452
self.sync_with_progress(
443453
&database,

0 commit comments

Comments
 (0)