1010// SPDX-License-Identifier: Apache-2.0
1111//
1212
13- import Foundation
13+ import ANSITerminal
1414import ArgumentParser
15- import LCLPing
16- import LCLAuth
1715import Crypto
16+ import Foundation
17+ import LCLAuth
18+ import LCLPing
1819import LCLSpeedtest
1920
2021extension LCLCLI {
@@ -23,21 +24,43 @@ extension LCLCLI {
2324 @Option ( name: . long, help: " Specify the device name to which the data will be sent. " )
2425 var deviceName : String ?
2526
26- @Option ( name: . shortAndLong, help: " Show datapoint on SCN's public visualization. Your contribution will help others better understand our coverage. " )
27+ @Option (
28+ name: . shortAndLong,
29+ help:
30+ " Show datapoint on SCN's public visualization. Your contribution will help others better understand our coverage. "
31+ )
2732 var showData : Bool = false
2833
34+ @Option (
35+ name: . shortAndLong,
36+ help:
37+ " The path to the configuration file that will be used to configure the measurement process "
38+ )
39+ var configurationFile : String ?
40+
2941 static let configuration = CommandConfiguration (
3042 commandName: " measure " ,
3143 abstract: " Run SCN test suite and optionally report the measurement result to SCN. "
3244 )
3345
3446 func run( ) async throws {
35- let encoder : JSONEncoder = JSONEncoder ( )
36- encoder. outputFormatting = . sortedKeys
47+ var configuration : Configuration ? = nil
48+ if let configurationFile = configurationFile {
49+ let configURL = URL ( fileURLWithPath: configurationFile)
50+ guard let configObject = try ? Data ( contentsOf: configURL) else {
51+ throw CLIError . invalidConfiguration ( " Corrupted config file. " )
52+ }
53+ let decoder = JSONDecoder ( )
54+ guard let config = try ? decoder. decode ( Configuration . self, from: configObject) else {
55+ throw CLIError . invalidConfiguration ( " Invalid config file format. " )
56+ }
57+ configuration = config
58+ }
3759
38- // TODO: prompted picker if the location option is not set
3960 var sites : [ CellularSite ]
40- let result : Result < [ CellularSite ] ? , CLIError > = await NetworkingAPI . get ( from: NetworkingAPI . Endpoint. site. url)
61+ let result : Result < [ CellularSite ] ? , CLIError > = await NetworkingAPI . get (
62+ from: NetworkingAPI . Endpoint. site. url
63+ )
4164 switch result {
4265 case . failure( let error) :
4366
@@ -46,11 +69,14 @@ extension LCLCLI {
4669 if let s = cs {
4770 sites = s
4871 } else {
49- throw CLIError . failedToLoadContent ( " No cellular site is available. Please check your internet connection or talk to the SCN administrator. " )
72+ throw CLIError . failedToLoadContent (
73+ " No cellular site is available. Please check your internet connection or talk to the SCN administrator. "
74+ )
5075 }
51-
5276 }
53- var picker = Picker < CellularSite > ( title: " Choose the cellular site you are currently at. " , options: sites)
77+
78+ let encoder : JSONEncoder = JSONEncoder ( )
79+ encoder. outputFormatting = . sortedKeys
5480
5581 let homeURL = FileIO . default. home. appendingPathComponent ( Constants . cliDirectory)
5682 let skURL = homeURL. appendingPathComponent ( " sk " )
@@ -62,15 +88,26 @@ extension LCLCLI {
6288 let sigData = try loadData ( sigURL)
6389 let rData = try loadData ( rURL)
6490 let hpkrData = try loadData ( hpkrURL)
65- let validationResultJSON = try encoder. encode ( ValidationResult ( R: rData, skT: skData, hPKR: hpkrData) )
91+ let validationResultJSON = try encoder. encode (
92+ ValidationResult ( R: rData, skT: skData, hPKR: hpkrData)
93+ )
6694
6795 let ecPrivateKey = try ECDSA . deserializePrivateKey ( raw: skData)
6896
69- guard try ECDSA . verify ( message: validationResultJSON, signature: sigData, publicKey: ecPrivateKey. publicKey) else {
97+ guard
98+ try ECDSA . verify (
99+ message: validationResultJSON,
100+ signature: sigData,
101+ publicKey: ecPrivateKey. publicKey
102+ )
103+ else {
70104 throw CLIError . contentCorrupted
71105 }
72106
73- let pingConfig = try ICMPPingClient . Configuration ( endpoint: . ipv4( " google.com " , 0 ) , deviceName: deviceName)
107+ let pingConfig = try ICMPPingClient . Configuration (
108+ endpoint: . ipv4( " google.com " , 0 ) ,
109+ deviceName: deviceName
110+ )
74111 let outputFormats : Set < OutputFormat > = [ . default]
75112
76113 let client = ICMPPingClient ( configuration: pingConfig)
@@ -80,30 +117,77 @@ extension LCLCLI {
80117 let stopSignal = DispatchSource . makeSignalSource ( signal: SIGINT, queue: . main)
81118 stopSignal. setEventHandler {
82119 print ( " Exit from SCN Measurement Test " )
120+ cursorOn ( )
83121 client. cancel ( )
122+ print ( " Ping test cancelled " )
84123 speedTest. stop ( )
85- return
124+ print ( " Speed test cancelled " )
125+ LCLCLI . MeasureCommand. exit ( )
86126 }
87127
88128 stopSignal. resume ( )
89129
90- guard let selectedSite = picker. pick ( ) else {
91- throw CLIError . noCellularSiteSelected
130+ var selectedSite : CellularSite
131+
132+ if configuration == nil {
133+ var picker = Picker < CellularSite > (
134+ title: " Choose the cellular site you are currently at. " ,
135+ options: sites
136+ )
137+ guard let ss = picker. pick ( ) else {
138+ throw CLIError . noCellularSiteSelected
139+ }
140+ selectedSite = ss
141+ } else {
142+ print ( " Using configuration \( configurationFile ?? " " ) " )
143+ #if DEBUG
144+ print ( configuration ?? " Empty configuration " )
145+ #endif
146+ let siteMap = Dictionary ( uniqueKeysWithValues: sites. map { ( $0. name, $0) } )
147+ guard let cellSiteName = configuration? . cellSiteName else {
148+ throw CLIError . invalidConfiguration ( " Missing cellular site name. " )
149+ }
150+
151+ guard let ss = siteMap [ cellSiteName] else {
152+ throw CLIError . invalidConfiguration ( " Invalid cellular site name. " )
153+ }
154+ selectedSite = ss
92155 }
93156
94157 let deviceId = UUID ( ) . uuidString
95158
96159 let summary = try await client. start ( ) . get ( )
97160
98161 let speedTestResults = try await speedTest. run ( deviceName: deviceName)
99- let downloadMeasurement = computeLatencyAndRetransmission ( speedTestResults. downloadTCPMeasurement, for: . download)
100- let uploadMeasurement = computeLatencyAndRetransmission ( speedTestResults. uploadTCPMeasurement, for: . upload)
101-
102- let downloadSummary = prepareSpeedTestSummary ( data: speedTestResults. downloadSpeed, tcpInfos: speedTestResults. downloadTCPMeasurement, for: . download, unit: . Mbps)
103- let uploadSummary = prepareSpeedTestSummary ( data: speedTestResults. uploadSpeed, tcpInfos: speedTestResults. uploadTCPMeasurement, for: . upload, unit: . Mbps)
162+ let downloadMeasurement = computeLatencyAndRetransmission (
163+ speedTestResults. downloadTCPMeasurement,
164+ for: . download
165+ )
166+ let uploadMeasurement = computeLatencyAndRetransmission (
167+ speedTestResults. uploadTCPMeasurement,
168+ for: . upload
169+ )
170+
171+ let downloadSummary = prepareSpeedTestSummary (
172+ data: speedTestResults. downloadSpeed,
173+ tcpInfos: speedTestResults. downloadTCPMeasurement,
174+ for: . download,
175+ unit: . Mbps
176+ )
177+ let uploadSummary = prepareSpeedTestSummary (
178+ data: speedTestResults. uploadSpeed,
179+ tcpInfos: speedTestResults. uploadTCPMeasurement,
180+ for: . upload,
181+ unit: . Mbps
182+ )
104183
105184 generatePingSummary ( summary, for: . icmp, formats: outputFormats)
106- generateSpeedTestSummary ( downloadSummary, kind: . download, formats: outputFormats, unit: . Mbps)
185+ generateSpeedTestSummary (
186+ downloadSummary,
187+ kind: . download,
188+ formats: outputFormats,
189+ unit: . Mbps
190+ )
107191 generateSpeedTestSummary ( uploadSummary, kind: . upload, formats: outputFormats, unit: . Mbps)
108192
109193 // MARK: Upload test results to the server
@@ -122,11 +206,28 @@ extension LCLCLI {
122206 jitter: ( downloadMeasurement. variance + uploadMeasurement. variance) / 2
123207 )
124208 let serialized = try encoder. encode ( report)
125- let sig_m = try ECDSA . sign ( message: serialized, privateKey: ECDSA . deserializePrivateKey ( raw: skData) )
126- let measurementReport = MeasurementReportModel ( sigmaM: sig_m. hex, hPKR: hpkrData. hex, M: serialized. hex, showData: showData)
209+ let sig_m = try ECDSA . sign (
210+ message: serialized,
211+ privateKey: ECDSA . deserializePrivateKey ( raw: skData)
212+ )
213+ let measurementReport = MeasurementReportModel (
214+ sigmaM: sig_m. hex,
215+ hPKR: hpkrData. hex,
216+ M: serialized. hex,
217+ showData: showData
218+ )
127219
128220 let reportToSent = try encoder. encode ( measurementReport)
129- let result = await NetworkingAPI . send ( to: NetworkingAPI . Endpoint. report. url, using: reportToSent)
221+
222+ #if DEBUG
223+ print ( measurementReport)
224+ print ( String ( data: reportToSent, encoding: . utf8) ?? " Unable to convert data to string " )
225+ print ( " DONE " )
226+ #endif
227+ let result = await NetworkingAPI . send (
228+ to: NetworkingAPI . Endpoint. report. url,
229+ using: reportToSent
230+ )
130231 switch result {
131232 case . success:
132233 print ( " Data reported successfully. " )
0 commit comments