Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions lib/units/websocket/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {TransactionManager} from '../../wire/transmanager.js'
import {ClientDispatcher} from './support/clientDispatcher.js'
import {DeviceOwnership} from './support/deviceOwnership.js'
import {OwnershipCache} from './support/ownershipCache.js'
import {parseLogcatFilters} from './support/logcatFilters.js'
import {
UpdateAccessTokenMessage,
DeleteUserMessage,
Expand Down Expand Up @@ -891,10 +892,20 @@ export default (async (options: Options) => {
}
})

socket.on('logcat.start', (serial: string, rc: string, data: any) =>
runTx(serial, rc, LogcatStartMessage, {filters: data.filters}))
socket.on('logcat.startIos', (serial: string, rc: string, data: any) =>
runTx(serial, rc, LogcatStartMessage, {filters: data.filters}))
const startLogcat = (serial: string, rc: string, data: unknown) => {
const filters = parseLogcatFilters((data as {filters?: unknown} | null)?.filters)
if (!filters) {
socket.emit('tx.done', rc, {
source: serial,
success: false,
data: 'invalid_logcat_filters'
})
return
}
return runTx(serial, rc, LogcatStartMessage, {filters})
}
socket.on('logcat.start', startLogcat)
socket.on('logcat.startIos', startLogcat)
socket.on('logcat.stop', (serial: string, rc: string) =>
runTx(serial, rc, LogcatStopMessage, {}))
socket.on('logcat.stopIos', (serial: string, rc: string) =>
Expand Down
29 changes: 29 additions & 0 deletions lib/units/websocket/support/logcatFilters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export interface LogcatFilter {
tag: string
priority: number
}

const isLogcatFilter = (value: unknown): value is LogcatFilter => {
if (!value || typeof value !== 'object') {
return false
}
const filter = value as Partial<LogcatFilter>
return typeof filter.tag === 'string' &&
typeof filter.priority === 'number' &&
Number.isInteger(filter.priority) &&
filter.priority >= 0 &&
filter.priority <= 0xffffffff
}

// The UI sends one {tag, priority} filter, while the protobuf field is repeated.
// Keep accepting arrays for older clients, and reject malformed payloads before
// protobuf-ts tries to merge them into the repeated field.
export const parseLogcatFilters = (value: unknown): LogcatFilter[] | null => {
if (isLogcatFilter(value)) {
return [value]
}
if (Array.isArray(value) && value.every(isLogcatFilter)) {
return value
}
return null
}
28 changes: 28 additions & 0 deletions test/unit/units/websocket/logcatFilters.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {describe, expect, it} from 'vitest'
import {parseLogcatFilters} from '../../../../lib/units/websocket/support/logcatFilters.ts'

describe('parseLogcatFilters', () => {
it('wraps the single filter sent by the UI in the repeated protobuf field', () => {
expect(parseLogcatFilters({tag: '*', priority: 2})).toEqual([
{tag: '*', priority: 2}
])
})

it('preserves filter arrays sent by older clients', () => {
const filters = [
{tag: 'ActivityManager', priority: 4},
{tag: 'System.err', priority: 6}
]
expect(parseLogcatFilters(filters)).toEqual(filters)
})

it('accepts an empty filter array', () => {
expect(parseLogcatFilters([])).toEqual([])
})

it('rejects malformed filters before protobuf serialization', () => {
expect(parseLogcatFilters({tag: '*', priority: 'verbose'})).toBeNull()
expect(parseLogcatFilters([{tag: '*', priority: 2}, {tag: 1, priority: 3}])).toBeNull()
expect(parseLogcatFilters(undefined)).toBeNull()
})
})
Loading