summaryrefslogtreecommitdiff
path: root/rust/tests/collaboration.rs
blob: 48da859af2068b830f6a91022ac837597e2e0aa6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
use binaryninja::binary_view::BinaryViewExt;
use binaryninja::collaboration::{NoNameChangeset, Remote, RemoteFileType, RemoteProject};
use binaryninja::headless::Session;
use binaryninja::symbol::{SymbolBuilder, SymbolType};
use rstest::*;
use std::path::PathBuf;

#[fixture]
#[once]
fn session() -> Session {
    Session::new().expect("Failed to initialize session")
}

// TODO: This cannot run in CI, as headless does not have collaboration, we should gate this.

fn temp_project_scope<T: Fn(&RemoteProject)>(remote: &Remote, cb: T) {
    if !remote.is_connected() {
        remote.connect().expect("Failed to connect to remote");
    }
    let project = remote
        .create_project("Test Project", "Test project for test purposes")
        .expect("Failed to create project");
    project.open().expect("Failed to open project");
    assert!(project.is_open(), "Project was not opened");
    // Clear out all the possible entries. This is to insure a clean slate.
    let files = project.files().expect("Failed to list files in project");
    for file in &files {
        project.delete_file(&file).expect("Failed to delete file");
    }
    let folders = project
        .folders()
        .expect("Failed to list folders in project");
    for folder in &folders {
        project
            .delete_folder(&folder)
            .expect("Failed to delete folder");
    }
    // Run task
    cb(&project);
    // Cleanup.
    project.close();
    assert!(!project.is_open(), "Project was not closed");
    remote
        .delete_project(&project)
        .expect("Failed to delete project");
}

#[rstest]
fn test_connection(_session: &Session) {
    let remotes = binaryninja::collaboration::known_remotes();
    if remotes.is_empty() {
        eprintln!("No known remotes, skipping test...");
        return;
    }
    let remote = remotes.iter().next().expect("No known remotes!");
    assert!(remote.connect().is_ok(), "Failed to connect to remote");
    remote
        .disconnect()
        .expect("Failed to disconnect from remote");
    assert!(!remote.is_connected(), "Connection was not disconnected");
}

#[rstest]
fn test_project_creation(_session: &Session) {
    let remotes = binaryninja::collaboration::known_remotes();
    if remotes.is_empty() {
        eprintln!("No known remotes, skipping test...");
        return;
    }
    let remote = remotes.iter().next().expect("No known remotes!");
    temp_project_scope(&remote, |project| {
        // Create the file than verify it by opening and checking contents.
        let created_file = project
            .create_file(
                "test_file",
                b"this is my file",
                "test_file",
                "",
                None,
                RemoteFileType::UnknownFileType,
            )
            .expect("Failed to create file in project");
        let created_file_id = created_file.id();
        assert_eq!(created_file.created_by(), remote.username());
        project
            .delete_file(&created_file)
            .expect("Failed to delete file");
        assert!(
            !project
                .get_file_by_id(created_file_id)
                .is_ok_and(|f| f.is_some()),
            "File was not deleted"
        );

        // Create a folder and verify it was created.
        let created_folder = project
            .create_folder("test_folder", "test_folder_desc", None)
            .unwrap();
        let created_folder_id = created_folder.id();
        assert_eq!(created_folder.name().as_str(), "test_folder");
        assert_eq!(created_folder.description().as_str(), "test_folder_desc");

        // Create a file in said folder and verify it exists in it.
        let created_folder_file = project
            .create_file(
                "test_folder_file",
                b"this is my file",
                "test_folder_file",
                "",
                Some(&created_folder),
                RemoteFileType::UnknownFileType,
            )
            .expect("Failed to create file in project folder");
        let created_folder_file_id = created_folder_file.id();
        // Verify the file exists in the folder.
        let check_folder_file = project
            .get_file_by_id(created_folder_file_id)
            .expect("Failed to get folder file by id")
            .unwrap();
        assert_eq!(check_folder_file.name().as_str(), "test_folder_file");
        assert_eq!(
            check_folder_file.folder().unwrap().unwrap().id(),
            created_folder_id,
            "Folder id does not match"
        );
        project
            .delete_file(&created_folder_file)
            .expect("Failed to delete file");

        // Verify the folder can be deleted.
        project
            .delete_folder(&created_folder)
            .expect("Failed to delete folder");
        assert!(
            !project
                .get_folder_by_id(created_folder_id)
                .is_ok_and(|f| f.is_some()),
            "Folder was not deleted"
        );
    })
}

#[rstest]
fn test_project_sync(_session: &Session) {
    let remotes = binaryninja::collaboration::known_remotes();
    if remotes.is_empty() {
        eprintln!("No known remotes, skipping test...");
        return;
    }
    let remote = remotes.iter().next().expect("No known remotes!");
    temp_project_scope(&remote, |project| {
        // Open a view so that we can upload it.
        let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
        let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
        let view_type = view.view_type();
        // Save the view to local database so that we can upload it
        assert!(
            view.file().create_database(out_dir.join("atox.obj.bndb")),
            "Failed to create local database"
        );
        // We should have a single snapshot.
        assert_eq!(view.file().database().unwrap().snapshots().len(), 1);
        // Update the entry function name.
        let entry_function = view
            .entry_point_function()
            .expect("Failed to get entry point function");
        let new_entry_func_symbol =
            SymbolBuilder::new(SymbolType::Function, "test", entry_function.start()).create();
        view.define_user_symbol(&new_entry_func_symbol);
        // Verify that we modified the binary
        assert_eq!(entry_function.symbol().raw_name().as_str(), "test");
        // Make new snapshot.
        assert!(view.file().save_auto_snapshot());
        // We should have two snapshots.
        assert_eq!(view.file().database().unwrap().snapshots().len(), 2);
        // Upload database and verify its remote file stuff
        let remote_file = project
            .upload_database(&view.file(), None, NoNameChangeset)
            .expect("Failed to upload database");
        assert_eq!(remote_file.name().as_str(), "atox.obj");
        assert_eq!(remote_file.created_by(), remote.username());
        assert_eq!(
            remote_file.file_type(),
            RemoteFileType::BinaryViewAnalysisFileType
        );
        // Delete local database and download remote one to verify changes.
        view.file().close();
        drop(view);
        // Verify that the remote file exists.
        project
            .get_file_by_id(remote_file.id())
            .expect("Failed to get remote file by id");
        // Download the remote database with our changes.
        let downloaded_file = remote_file
            .download_database(out_dir.join("downloaded_atox.obj.bndb"))
            .expect("Failed to download database");
        let downloaded_view = downloaded_file
            .view_of_type(view_type)
            .expect("Failed to open downloaded view");
        // Verify the changes in the entry function.
        let entry_function = downloaded_view
            .entry_point_function()
            .expect("Failed to get entry point function");
        assert_eq!(entry_function.symbol().raw_name().as_str(), "test");
        project
            .delete_file(&remote_file)
            .expect("Failed to delete file");
    });
}