Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88a52fb92c | ||
|
|
723f1665a7 | ||
|
|
4f92d3d58d | ||
|
|
05d06a4f31 |
@@ -9,6 +9,7 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
5404AEEB24A90717003B2F54 /* PrefsShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E67E4524A8B0FE0025D261 /* PrefsShared.swift */; };
|
||||
5404AEED24A95F3F003B2F54 /* SlideInAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5404AEEC24A95F3F003B2F54 /* SlideInAnimation.swift */; };
|
||||
5404AEEF24ACC089003B2F54 /* VCAnalysisBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5404AEEE24ACC089003B2F54 /* VCAnalysisBar.swift */; };
|
||||
540E6780242D2CF100871BBE /* VCRecordings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540E677F242D2CF100871BBE /* VCRecordings.swift */; };
|
||||
540E67822433483D00871BBE /* VCEditRecording.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540E67812433483D00871BBE /* VCEditRecording.swift */; };
|
||||
540E67842433FAFE00871BBE /* TVCPreviousRecords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540E67832433FAFE00871BBE /* TVCPreviousRecords.swift */; };
|
||||
@@ -176,6 +177,7 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
5404AEEC24A95F3F003B2F54 /* SlideInAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideInAnimation.swift; sourceTree = "<group>"; };
|
||||
5404AEEE24ACC089003B2F54 /* VCAnalysisBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCAnalysisBar.swift; sourceTree = "<group>"; };
|
||||
540E677F242D2CF100871BBE /* VCRecordings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCRecordings.swift; sourceTree = "<group>"; };
|
||||
540E67812433483D00871BBE /* VCEditRecording.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCEditRecording.swift; sourceTree = "<group>"; };
|
||||
540E67832433FAFE00871BBE /* TVCPreviousRecords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVCPreviousRecords.swift; sourceTree = "<group>"; };
|
||||
@@ -347,7 +349,8 @@
|
||||
5412F8ED24571B8100A63D7A /* VCDateFilter.swift */,
|
||||
54953E6023E0D69A0054345C /* TVCHosts.swift */,
|
||||
54953E6E23E44CD00054345C /* TVCHostDetails.swift */,
|
||||
541FC47424A12CE9009154D8 /* Analytics */,
|
||||
544F911F24A67EC5001D4B00 /* TVCOccurrenceContext.swift */,
|
||||
541FC47424A12CE9009154D8 /* Analysis */,
|
||||
);
|
||||
path = Requests;
|
||||
sourceTree = "<group>";
|
||||
@@ -415,13 +418,13 @@
|
||||
path = main;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
541FC47424A12CE9009154D8 /* Analytics */ = {
|
||||
541FC47424A12CE9009154D8 /* Analysis */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5404AEEE24ACC089003B2F54 /* VCAnalysisBar.swift */,
|
||||
541FC47724A1453F009154D8 /* VCCoOccurrence.swift */,
|
||||
544F911F24A67EC5001D4B00 /* TVCOccurrenceContext.swift */,
|
||||
);
|
||||
path = Analytics;
|
||||
path = Analysis;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
542E2A9B24051F79001462DC /* media */ = {
|
||||
@@ -860,6 +863,7 @@
|
||||
54E540F8247DB90F00F7C34A /* RecordingsDB.swift in Sources */,
|
||||
54E67E4F24A8E2910025D261 /* Equatable.swift in Sources */,
|
||||
54E540F4247D3F2600F7C34A /* TestDataSource.swift in Sources */,
|
||||
5404AEEF24ACC089003B2F54 /* VCAnalysisBar.swift in Sources */,
|
||||
545DDDD424466D37003B6544 /* AutoLayout.swift in Sources */,
|
||||
54B345AD241BBB00004C53CC /* DBExtensions.swift in Sources */,
|
||||
54E540F2247C423200F7C34A /* DomainFilter.swift in Sources */,
|
||||
@@ -1157,7 +1161,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 23;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
INFOPLIST_FILE = main/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1176,7 +1180,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 23;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
INFOPLIST_FILE = main/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1195,7 +1199,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 23;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||
@@ -1213,7 +1217,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 23;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||
|
||||
14
README.md
14
README.md
@@ -16,6 +16,8 @@ Your data belongs to you.
|
||||
Therefore, monitoring and analysis take place on your device only.
|
||||
The app does not share any data with us or any other third-party – unless you choose to.
|
||||
|
||||
Join [Testflight beta](https://testflight.apple.com/join/9jjaFeHO)
|
||||
|
||||
|
||||
### How does it work?
|
||||
|
||||
@@ -31,19 +33,23 @@ That means, AppCheck does not have to be active in the foreground all the time.
|
||||
- See history of previous connections
|
||||
- Block unwanted traffic based on domain names
|
||||
- Record app specific activity<sup>1</sup>
|
||||
- Apply logging filters
|
||||
- Apply logging filters (block or ignore) and display filters (specific range or last x minutes)
|
||||
- Sort results by time, name, or occurrence count
|
||||
- Context Analysis
|
||||
- What other domains occur often at the same time?
|
||||
- What happened immediately before or after the action?
|
||||
- Export results for custom analysis
|
||||
|
||||
**… and soon:**
|
||||
|
||||
- Alert Monitor & reminder
|
||||
- Occurrence Context Analysis
|
||||
- Participate in privacy research
|
||||
|
||||
|
||||
<sup>1</sup> Due to technical limitations, recording is not limited to any single application. Remember to force-quit all other applications before starting a recording.
|
||||
<sup>1</sup> Due to technical limitations, recordings can not be restricted to a single application. Remember to force-quit all other applications before starting a recording.
|
||||
|
||||
|
||||
## Research Project
|
||||
|
||||
*information will be added soon*
|
||||
*information will be added soon™*
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 158 KiB |
@@ -344,9 +344,16 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<containerView key="tableHeaderView" opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kh4-PQ-hy6">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="49"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<connections>
|
||||
<segue destination="1ba-SA-8sT" kind="embed" id="vf1-07-AS4"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="HostCell" textLabel="Rnk-SP-UHm" detailTextLabel="ovQ-lJ-hWJ" style="IBUITableViewCellStyleSubtitle" id="uv0-9B-Zbb">
|
||||
<rect key="frame" x="0.0" y="28" width="320" height="57.5"/>
|
||||
<rect key="frame" x="0.0" y="77" width="320" height="57.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="uv0-9B-Zbb" id="6vH-Du-gCg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="293" height="57.5"/>
|
||||
@@ -392,17 +399,13 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<tabBar key="tableHeaderView" contentMode="scaleToFill" fixedFrame="YES" translucent="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1Jy-zg-CXR">
|
||||
<containerView key="tableHeaderView" opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SxM-2c-aJb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="49"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<items>
|
||||
<tabBarItem title="Co-Occurrence" image="intersection" id="KXh-kQ-rAF"/>
|
||||
</items>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="h7Z-Qr-pJ5" id="qNN-nI-Kub"/>
|
||||
<segue destination="1ba-SA-8sT" kind="embed" id="ueN-6L-cP7"/>
|
||||
</connections>
|
||||
</tabBar>
|
||||
</containerView>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="HostDetailCell" textLabel="J2P-mU-Vad" detailTextLabel="eWb-mX-udN" style="IBUITableViewCellStyleValue1" id="ZCA-Dz-i92">
|
||||
<rect key="frame" x="0.0" y="77" width="320" height="43.5"/>
|
||||
@@ -438,25 +441,52 @@
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Occurrences" prompt="com.domain.network.cdn" id="bys-2u-rHs"/>
|
||||
<connections>
|
||||
<outlet property="actionsBar" destination="1Jy-zg-CXR" id="7x3-Vy-i9C"/>
|
||||
<segue destination="W5Q-oz-bFb" kind="modal" identifier="segueAnalysisCoOccurrence" id="ukY-Dy-AIA"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="UxH-PH-KQy" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2100" y="-1250"/>
|
||||
</scene>
|
||||
<!--Analysis Bar-->
|
||||
<scene sceneID="1qq-WD-Lqq">
|
||||
<objects>
|
||||
<viewController id="1ba-SA-8sT" customClass="VCAnalysisBar" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="qp6-er-N6U">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="49"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tabBar contentMode="scaleToFill" translucent="NO" id="1Jy-zg-CXR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="49"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<items>
|
||||
<tabBarItem title="Co-Occurrence" image="intersection" id="KXh-kQ-rAF"/>
|
||||
</items>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="1ba-SA-8sT" id="bRS-kh-dOv"/>
|
||||
</connections>
|
||||
</tabBar>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="dtz-KG-P4C"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="tabBar" destination="1Jy-zg-CXR" id="VTV-xq-Aou"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="XnK-B9-RSJ" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1400" y="-1950"/>
|
||||
</scene>
|
||||
<!--Co Occurrence-->
|
||||
<scene sceneID="Gbm-AP-b72">
|
||||
<objects>
|
||||
<viewController id="W5Q-oz-bFb" customClass="VCCoOccurrence" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController storyboardIdentifier="IBCoOccurrence" id="W5Q-oz-bFb" customClass="VCCoOccurrence" customModule="AppCheck" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="f34-NO-d8f">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="548"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<navigationBar contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rvt-nC-2Zr">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="56"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<items>
|
||||
<navigationItem title="Co-Occurrence" id="csY-x8-Rpe">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="done" id="eg9-p3-Xas">
|
||||
@@ -466,7 +496,7 @@
|
||||
</barButtonItem>
|
||||
<barButtonItem key="rightBarButtonItem" id="bTi-7F-CFS">
|
||||
<button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="infoLight" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" id="kqK-SL-CxZ">
|
||||
<rect key="frame" x="279" y="16" width="25" height="24"/>
|
||||
<rect key="frame" x="279" y="10" width="25" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<connections>
|
||||
<action selector="showInfoScreen" destination="W5Q-oz-bFb" eventType="touchUpInside" id="TuI-R9-PNr"/>
|
||||
@@ -477,7 +507,7 @@
|
||||
</items>
|
||||
</navigationBar>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="PGb-pB-cfO">
|
||||
<rect key="frame" x="0.0" y="56" width="320" height="492"/>
|
||||
<rect key="frame" x="0.0" y="44" width="320" height="524"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<segmentedControl key="tableHeaderView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" id="7ye-tU-pdo">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="32"/>
|
||||
@@ -1376,6 +1406,7 @@ Duration: 60:00</string>
|
||||
</scenes>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="EzT-Xq-wka"/>
|
||||
<segue reference="ueN-6L-cP7"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
<resources>
|
||||
<image name="filter-clear" width="20" height="20"/>
|
||||
|
||||
@@ -33,6 +33,13 @@ class SearchBarManager: NSObject, UISearchResultsUpdating {
|
||||
if #available(iOS 11.0, *) {
|
||||
tvc?.navigationItem.searchController = controller
|
||||
} else {
|
||||
let thv = tvc?.tableView.tableHeaderView
|
||||
guard thv == nil || thv is UISearchBar else {
|
||||
// Don't overwrite actions bar (co-occurrence, etc.)
|
||||
// FIXME: find alternative or iOS 9-10 users can't search in hosts
|
||||
tvc = nil
|
||||
return
|
||||
}
|
||||
controller.loadViewIfNeeded() // Fix: "Attempting to load the view of a view controller while it is deallocating"
|
||||
tvc?.definesPresentationContext = true // make search bar disappear if user changes scene (eg. select cell)
|
||||
//tvc?.tableView.backgroundView = UIView() // iOS 11+ bug: bright white background in dark mode
|
||||
@@ -42,7 +49,7 @@ class SearchBarManager: NSObject, UISearchResultsUpdating {
|
||||
}
|
||||
|
||||
/// Search callback
|
||||
func updateSearchResults(for controller: UISearchController) {
|
||||
internal func updateSearchResults(for controller: UISearchController) {
|
||||
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(performSearch), object: nil)
|
||||
perform(#selector(performSearch), with: nil, afterDelay: 0.2)
|
||||
}
|
||||
|
||||
@@ -172,18 +172,10 @@ private class StickyPresentationController: UIPresentationController {
|
||||
let preferred = presentedViewController.preferredContentSize
|
||||
switch stickTo {
|
||||
case .left, .right:
|
||||
let fitted = target.systemLayoutSizeFitting(
|
||||
CGSize(width: preferred.width, height: full.height),
|
||||
withHorizontalFittingPriority: .fittingSizeLevel,
|
||||
verticalFittingPriority: .required
|
||||
)
|
||||
let fitted = target.fittingSize(fixedHeight: full.height, preferredWidth: preferred.width)
|
||||
return CGSize(width: min(fitted.width, full.width), height: full.height)
|
||||
case .top, .bottom:
|
||||
let fitted = target.systemLayoutSizeFitting(
|
||||
CGSize(width: full.width, height: preferred.height),
|
||||
withHorizontalFittingPriority: .required,
|
||||
verticalFittingPriority: .fittingSizeLevel
|
||||
)
|
||||
let fitted = target.fittingSize(fixedWidth: full.width, preferredHeight: preferred.height)
|
||||
return CGSize(width: full.width, height: min(fitted.height, full.height))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,8 +244,9 @@ extension SQLiteDatabase {
|
||||
}
|
||||
|
||||
/// Get sorted, unique list of `ts` with given `fqdn`.
|
||||
func dnsLogsUniqTs(_ fqdn: String) -> [Timestamp]? {
|
||||
try? run(sql: "SELECT DISTINCT ts FROM heap WHERE fqdn = ? ORDER BY ts;", bind: [BindText(fqdn)]) {
|
||||
func dnsLogsUniqTs(_ domain: String, isFQDN flag: Bool) -> [Timestamp]? {
|
||||
try? run(sql: "SELECT DISTINCT ts FROM heap WHERE \(flag ? "fqdn" : "domain") = ? ORDER BY ts;",
|
||||
bind: [BindText(domain)]) {
|
||||
allRows($0) { col_ts($0, 0) }
|
||||
}
|
||||
}
|
||||
@@ -257,7 +258,7 @@ extension SQLiteDatabase {
|
||||
/// - dt: Search for `ts - dt <= X <= ts + dt`
|
||||
/// - fqdn: Rows matching this domain will be excluded from the result set.
|
||||
/// - Returns: List of tuples ordered by rank (ASC).
|
||||
func contextAnalysis(coOccurrence times: [Timestamp], plusMinus dt: Timestamp, exclude fqdn: String) -> [ContextAnalysisResult]? {
|
||||
func contextAnalysis(coOccurrence times: [Timestamp], plusMinus dt: Timestamp, exclude domain: String, isFQDN flag: Bool) -> [ContextAnalysisResult]? {
|
||||
guard times.count > 0 else { return nil }
|
||||
createFunction("fnDist") {
|
||||
let x = $0.first as! Timestamp
|
||||
@@ -282,10 +283,10 @@ extension SQLiteDatabase {
|
||||
SELECT fqdn, count, avg, (\(fnRank)) rank FROM (
|
||||
SELECT fqdn, COUNT(*) count, AVG(dist) avg FROM (
|
||||
SELECT fqdn, fnDist(ts) dist FROM heap
|
||||
WHERE ts BETWEEN ? AND ? AND fqdn != ? AND dist <= ?
|
||||
WHERE ts BETWEEN ? AND ? AND \(flag ? "fqdn" : "domain") != ? AND dist <= ?
|
||||
) GROUP BY fqdn
|
||||
) ORDER BY rank ASC LIMIT 99;
|
||||
""", bind: [BindInt64(dt), BindInt64(low), BindInt64(high), BindText(fqdn), BindInt64(dt)]) {
|
||||
""", bind: [BindInt64(dt), BindInt64(low), BindInt64(high), BindText(domain), BindInt64(dt)]) {
|
||||
allRows($0) {
|
||||
(col_text($0, 0) ?? "", sqlite3_column_int($0, 1), sqlite3_column_double($0, 2), sqlite3_column_double($0, 3))
|
||||
}
|
||||
|
||||
@@ -43,14 +43,6 @@ extension UITableView {
|
||||
func safeMoveRow(_ from: Int, to: Int) {
|
||||
isFrontmost ? moveRow(at: IndexPath(row: from), to: IndexPath(row: to)) : reloadData()
|
||||
}
|
||||
|
||||
/// Recalculate and apply new `tableHeaderView` height.
|
||||
func sizeHeaderToFit() {
|
||||
if let head = tableHeaderView {
|
||||
head.frame.size.height = head.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
|
||||
tableHeaderView = head
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,18 @@ extension UIView {
|
||||
return UIImage(cgImage: image!.cgImage!)
|
||||
}
|
||||
}
|
||||
|
||||
/// Find size that fits into frame with given `width` as precondition.
|
||||
/// - Parameter preferredHeight:If unset, find smallest possible size.
|
||||
func fittingSize(fixedWidth: CGFloat, preferredHeight: CGFloat = 0) -> CGSize {
|
||||
systemLayoutSizeFitting(CGSize(width: fixedWidth, height: preferredHeight), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
|
||||
}
|
||||
|
||||
/// Find size that fits into frame with given `height` as precondition.
|
||||
/// - Parameter preferredWidth:If unset, find smallest possible size.
|
||||
func fittingSize(fixedHeight: CGFloat, preferredWidth: CGFloat = 0) -> CGSize {
|
||||
systemLayoutSizeFitting(CGSize(width: preferredWidth, height: fixedHeight), withHorizontalFittingPriority: .fittingSizeLevel, verticalFittingPriority: .required)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIEdgeInsets {
|
||||
|
||||
60
main/Requests/Analysis/VCAnalysisBar.swift
Normal file
60
main/Requests/Analysis/VCAnalysisBar.swift
Normal file
@@ -0,0 +1,60 @@
|
||||
import UIKit
|
||||
|
||||
protocol AnalysisBarDelegate {
|
||||
func analysisBarWillOpenCoOccurrence() -> (domain: String, isFQDN: Bool)
|
||||
}
|
||||
|
||||
class VCAnalysisBar: UIViewController, UITabBarDelegate {
|
||||
|
||||
@IBOutlet private var tabBar: UITabBar!
|
||||
|
||||
override func viewDidLoad() {
|
||||
if #available(iOS 10.0, *) {
|
||||
tabBar.unselectedItemTintColor = .sysLink
|
||||
}
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
override func willMove(toParent parent: UIViewController?) {
|
||||
super.willMove(toParent: parent)
|
||||
let enabled = (parent as? AnalysisBarDelegate) != nil
|
||||
for item in tabBar.items! { item.isEnabled = enabled }
|
||||
}
|
||||
|
||||
// MARK: - Tab Bar Appearance
|
||||
|
||||
override func viewWillAppear(_: Bool) {
|
||||
resizeTableViewHeader()
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_: UITraitCollection?) {
|
||||
resizeTableViewHeader()
|
||||
}
|
||||
|
||||
func resizeTableViewHeader() {
|
||||
guard let tableView = (parent as? UITableViewController)?.tableView,
|
||||
let head = tableView.tableHeaderView else { return }
|
||||
// Recalculate and apply new height. Otherwise tabBar won't compress
|
||||
tabBar.sizeToFit()
|
||||
head.frame.size.height = tabBar.frame.height
|
||||
tableView.tableHeaderView = head
|
||||
}
|
||||
|
||||
// MARK: - Tab Bar Delegate
|
||||
|
||||
func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
|
||||
tabBar.selectedItem = nil
|
||||
openCoOccurrence()
|
||||
}
|
||||
|
||||
private func openCoOccurrence() {
|
||||
guard let delegate = parent as? AnalysisBarDelegate,
|
||||
let vc = storyboard?.instantiateViewController(withIdentifier: "IBCoOccurrence") as? VCCoOccurrence else {
|
||||
return
|
||||
}
|
||||
let info = delegate.analysisBarWillOpenCoOccurrence()
|
||||
vc.domainName = info.domain
|
||||
vc.isFQDN = info.isFQDN
|
||||
present(vc, animated: true)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import UIKit
|
||||
|
||||
class VCCoOccurrence: UIViewController, UITableViewDataSource {
|
||||
var fqdn: String!
|
||||
var domainName: String!
|
||||
var isFQDN: Bool!
|
||||
private var dataSource: [ContextAnalysisResult] = []
|
||||
|
||||
@IBOutlet private var tableView: UITableView!
|
||||
@@ -30,14 +31,15 @@ class VCCoOccurrence: UIViewController, UITableViewDataSource {
|
||||
dataSource = [("Loading …", 0, 0, 0)]
|
||||
logMaxCount = 1
|
||||
tableView.reloadData()
|
||||
let domain = fqdn!
|
||||
let domain = domainName!
|
||||
let flag = isFQDN!
|
||||
let time = Timestamp(selectedTime)
|
||||
DispatchQueue.global().async { [weak self] in
|
||||
let temp: [ContextAnalysisResult]
|
||||
let total: Int32
|
||||
if let db = AppDB,
|
||||
let times = db.dnsLogsUniqTs(domain), times.count > 0,
|
||||
let result = db.contextAnalysis(coOccurrence: times, plusMinus: time, exclude: domain),
|
||||
let times = db.dnsLogsUniqTs(domain, isFQDN: flag), times.count > 0,
|
||||
let result = db.contextAnalysis(coOccurrence: times, plusMinus: time, exclude: domain, isFQDN: flag),
|
||||
result.count > 0
|
||||
{
|
||||
temp = result
|
||||
@@ -1,9 +1,7 @@
|
||||
import UIKit
|
||||
|
||||
class TVCHostDetails: UITableViewController, SyncUpdateDelegate, UITabBarDelegate {
|
||||
class TVCHostDetails: UITableViewController, SyncUpdateDelegate, AnalysisBarDelegate {
|
||||
|
||||
@IBOutlet private var actionsBar: UITabBar!
|
||||
|
||||
public var fullDomain: String!
|
||||
private var dataSource: [GroupedTsOccurrence] = []
|
||||
// TODO: respect date reverse sort order
|
||||
@@ -14,9 +12,19 @@ class TVCHostDetails: UITableViewController, SyncUpdateDelegate, UITabBarDelegat
|
||||
sync.addObserver(self) // calls `syncUpdate(reset:)`
|
||||
if #available(iOS 10.0, *) {
|
||||
sync.allowPullToRefresh(onTVC: self, forObserver: self)
|
||||
actionsBar.unselectedItemTintColor = .sysLink
|
||||
}
|
||||
UIDevice.orientationDidChangeNotification.observe(call: #selector(didChangeOrientation), on: self)
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if let index = tableView.indexPathForSelectedRow?.row {
|
||||
let tvc = segue.destination as? TVCOccurrenceContext
|
||||
tvc?.domain = fullDomain
|
||||
tvc?.ts = dataSource[index].ts
|
||||
}
|
||||
}
|
||||
|
||||
func analysisBarWillOpenCoOccurrence() -> (domain: String, isFQDN: Bool) {
|
||||
(fullDomain, true)
|
||||
}
|
||||
|
||||
// MARK: - Table View Data Source
|
||||
@@ -33,34 +41,6 @@ class TVCHostDetails: UITableViewController, SyncUpdateDelegate, UITabBarDelegat
|
||||
}
|
||||
}
|
||||
|
||||
// #########################
|
||||
// #
|
||||
// # MARK: - Tab Bar
|
||||
// #
|
||||
// #########################
|
||||
|
||||
extension TVCHostDetails {
|
||||
|
||||
@objc private func didChangeOrientation(_ sender: Notification) {
|
||||
tableView.sizeHeaderToFit() // otherwise TabBar won't compress
|
||||
}
|
||||
|
||||
func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
|
||||
tabBar.selectedItem = nil
|
||||
performSegue(withIdentifier: "segueAnalysisCoOccurrence", sender: nil)
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if segue.identifier == "segueAnalysisCoOccurrence" {
|
||||
(segue.destination as? VCCoOccurrence)?.fqdn = fullDomain
|
||||
} else if let index = tableView.indexPathForSelectedRow?.row {
|
||||
let tvc = segue.destination as? TVCOccurrenceContext
|
||||
tvc?.domain = fullDomain
|
||||
tvc?.ts = dataSource[index].ts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ################################
|
||||
// #
|
||||
// # MARK: - Partial Update
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import UIKit
|
||||
|
||||
class TVCHosts: UITableViewController, GroupedDomainDataSourceDelegate {
|
||||
class TVCHosts: UITableViewController, GroupedDomainDataSourceDelegate, AnalysisBarDelegate {
|
||||
|
||||
lazy var source = GroupedDomainDataSource(withParent: parentDomain)
|
||||
|
||||
@@ -21,6 +21,10 @@ class TVCHosts: UITableViewController, GroupedDomainDataSourceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func analysisBarWillOpenCoOccurrence() -> (domain: String, isFQDN: Bool) {
|
||||
(parentDomain, false)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Table View Data Source
|
||||
|
||||
|
||||
Reference in New Issue
Block a user