Files
VoCat/internal/device/logging.go
T
2026-08-16 13:49:42 +08:00

85 lines
2.3 KiB
Go

package device
import (
"regexp"
"strings"
"unicode"
"vocat/internal/modem"
)
const maxHardwareErrorDetail = 1024
var longHexPayload = regexp.MustCompile(`(?i)\b[0-9a-f]{48,}\b`)
// HardwareErrorDetail returns a diagnostic error suitable for persistent and
// browser-visible logs. AT payloads can contain APDU authentication material,
// SMS data, or APN credentials, so CommandError values retain only the command
// name and modem final result. Very long hexadecimal payloads from wrapped
// protocol errors are removed as a second line of defence.
func HardwareErrorDetail(err error) string {
if err == nil {
return ""
}
detail := redactCommandErrors(err.Error(), err)
detail = longHexPayload.ReplaceAllString(detail, "[redacted hex payload]")
detail = strings.Map(func(character rune) rune {
if unicode.IsControl(character) && character != '\t' && character != '\n' {
return ' '
}
return character
}, strings.TrimSpace(detail))
runes := []rune(detail)
if len(runes) > maxHardwareErrorDetail {
detail = string(runes[:maxHardwareErrorDetail]) + "..."
}
return detail
}
func redactCommandErrors(detail string, err error) string {
if commandErr, ok := err.(*modem.CommandError); ok {
detail = strings.ReplaceAll(detail, commandErr.Error(), safeCommandError(commandErr))
}
switch wrapped := err.(type) {
case interface{ Unwrap() []error }:
for _, child := range wrapped.Unwrap() {
detail = redactCommandErrors(detail, child)
}
case interface{ Unwrap() error }:
if child := wrapped.Unwrap(); child != nil {
detail = redactCommandErrors(detail, child)
}
}
return detail
}
func safeCommandError(err *modem.CommandError) string {
command := safeATCommandName(err.Command)
final := strings.TrimSpace(err.Final)
if final == "" {
final = "unknown modem error"
}
return command + " failed: " + final
}
func safeATCommandName(command string) string {
command = strings.ToUpper(strings.TrimSpace(command))
if command == "" {
return "AT command"
}
if strings.HasPrefix(command, "ATD") {
return "ATD"
}
for index, character := range command {
if character == '=' || character == '?' || character == ',' ||
character == '"' || unicode.IsSpace(character) {
command = command[:index]
break
}
}
if !strings.HasPrefix(command, "AT") || len(command) > 32 {
return "AT command"
}
return command
}