use serde::Serialize;
use sha2::{Digest, Sha256};
use crate::buildinfo::BuildInfo;
use crate::gobin::GoBinary;
use crate::itabs::Itab;
use crate::moduledata::ModuleData;
use crate::pclntab::{Function, Pclntab};
use crate::types::Type;
use crate::Result;
#[derive(Debug, Clone, Serialize)]
pub struct Fingerprint {
pub sha256: String,
pub function_count: usize,
pub type_count: usize,
pub itab_count: usize,
pub dep_count: usize,
pub components: Components,
}
#[derive(Debug, Clone, Serialize)]
pub struct Components {
pub main_module_path: Option<String>,
pub user_function_count: usize,
pub user_type_count: usize,
pub deps: Vec<String>,
}
pub fn compute(bin: &GoBinary, pcln: &Pclntab<'_>) -> Result<Fingerprint> {
let functions = pcln.functions().unwrap_or_default();
let buildinfo = BuildInfo::parse(bin).ok();
let md = ModuleData::locate(bin).ok();
let types = md
.as_ref()
.and_then(|m| crate::types::recover_all(bin, m).ok())
.unwrap_or_default();
let itabs = md
.as_ref()
.and_then(|m| crate::itabs::recover_all(bin, m).ok())
.unwrap_or_default();
Ok(compute_from(&functions, &types, &itabs, buildinfo.as_ref()))
}
pub fn compute_behavioral(bin: &GoBinary) -> Result<BehavioralFingerprint> {
let md = ModuleData::locate(bin).ok();
let itabs = md
.as_ref()
.and_then(|m| crate::itabs::recover_all(bin, m).ok())
.unwrap_or_default();
Ok(compute_behavioral_from(&itabs))
}
pub fn compute_behavioral_from(itabs: &[Itab]) -> BehavioralFingerprint {
let counts = crate::stdlib::classify_itabs(itabs);
let mut hasher = Sha256::new();
hasher.update(b"behavioral-v1:\n");
for (name, count) in &counts {
hasher.update(name.as_bytes());
hasher.update(b"=");
hasher.update(count.to_string().as_bytes());
hasher.update(b"\n");
}
let digest = hasher.finalize();
let sha256 = digest
.iter()
.map(|b| format!("{b:02x}"))
.collect::<String>();
BehavioralFingerprint {
sha256,
interface_count: counts.len(),
total_implementations: counts.iter().map(|(_, c)| c).sum(),
counts: counts
.into_iter()
.map(|(n, c)| (n.to_string(), c))
.collect(),
}
}
#[derive(Debug, Clone, Serialize)]
pub struct BehavioralFingerprint {
pub sha256: String,
pub interface_count: usize,
pub total_implementations: usize,
pub counts: Vec<(String, usize)>,
}
pub fn compute_from(
functions: &[Function],
types: &[Type],
itabs: &[Itab],
buildinfo: Option<&BuildInfo>,
) -> Fingerprint {
let mut hasher = Sha256::new();
let main_module = buildinfo.and_then(|b| b.main.as_ref().map(|m| m.path.clone()));
if let Some(path) = &main_module {
hasher.update(b"main:");
hasher.update(path.as_bytes());
hasher.update(b"\n");
}
let mut deps: Vec<String> = buildinfo
.map(|b| {
b.deps
.iter()
.map(|m| format!("{}@{}", m.path, m.version))
.collect()
})
.unwrap_or_default();
deps.sort();
hasher.update(b"deps:\n");
for d in &deps {
hasher.update(d.as_bytes());
hasher.update(b"\n");
}
let mut user_fns: Vec<&str> = functions
.iter()
.map(|f| f.name.as_str())
.filter(|n| is_user_symbol(n))
.collect();
user_fns.sort_unstable();
user_fns.dedup();
hasher.update(b"functions:\n");
for n in &user_fns {
hasher.update(n.as_bytes());
hasher.update(b"\n");
}
let mut user_types: Vec<&str> = types
.iter()
.map(|t| t.name.as_str())
.filter(|n| is_user_symbol(n))
.collect();
user_types.sort_unstable();
user_types.dedup();
hasher.update(b"types:\n");
for n in &user_types {
hasher.update(n.as_bytes());
hasher.update(b"\n");
}
let mut bindings: Vec<String> = itabs
.iter()
.filter(|i| is_user_symbol(&i.concrete_name) || is_user_symbol(&i.interface_name))
.map(|i| format!("{} => {}", i.interface_name, i.concrete_name))
.collect();
bindings.sort();
bindings.dedup();
hasher.update(b"itabs:\n");
for b in &bindings {
hasher.update(b.as_bytes());
hasher.update(b"\n");
}
let digest = hasher.finalize();
let sha256 = digest
.iter()
.map(|b| format!("{b:02x}"))
.collect::<String>();
Fingerprint {
sha256,
function_count: functions.len(),
type_count: types.len(),
itab_count: itabs.len(),
dep_count: deps.len(),
components: Components {
main_module_path: main_module,
user_function_count: user_fns.len(),
user_type_count: user_types.len(),
deps,
},
}
}
fn is_user_symbol(name: &str) -> bool {
const STDLIB_PREFIXES: &[&str] = &[
"runtime.",
"runtime/",
"internal/",
"sync.",
"sync/",
"syscall.",
"syscall/",
"reflect.",
"reflect/",
"fmt.",
"fmt/",
"io.",
"io/",
"os.",
"os/",
"errors.",
"strconv.",
"strings.",
"strings/",
"bytes.",
"bytes/",
"encoding/",
"crypto/",
"math.",
"math/",
"sort.",
"sort/",
"time.",
"time/",
"context.",
"context/",
"bufio.",
"bufio/",
"regexp.",
"regexp/",
"path.",
"path/",
"net.",
"net/",
"hash.",
"hash/",
"html.",
"html/",
"log.",
"log/",
"unicode.",
"unicode/",
"compress/",
"container/",
"database/",
"debug/",
"image.",
"image/",
"mime.",
"mime/",
"plugin.",
"runtime_",
"text.",
"text/",
"vendor/",
"cmd/",
"type:",
"type.",
"go:",
"go.",
"_cgo_",
"x_cgo_",
];
!STDLIB_PREFIXES.iter().any(|p| name.starts_with(p))
}