From 783aa5fd798442ed4eab144fefb7554b0fd8c92f Mon Sep 17 00:00:00 2001 From: kaedwen Date: Sun, 16 Jul 2023 09:50:19 +0200 Subject: [PATCH 1/9] add sonos handler to play file --- .gitignore | 3 +- .vscode/launch.json | 4 +- audio/ding-dong.wav | Bin 0 -> 716462 bytes go.mod | 2 + go.sum | 4 + main.go | 13 +- pkg/common/config.go | 9 + pkg/ring/ring.go | 74 + pkg/ring/sonos/sonos.go | 55 + pkg/server/http.go | 14 +- static/disk.go | 12 +- static/emb.go | 8 +- .../github.com/holoplot/go-evdev/.gitignore | 2 + vendor/github.com/holoplot/go-evdev/LICENSE | 23 + vendor/github.com/holoplot/go-evdev/README.md | 48 + vendor/github.com/holoplot/go-evdev/bitmap.go | 33 + vendor/github.com/holoplot/go-evdev/codes.go | 3751 +++++++++++++++++ vendor/github.com/holoplot/go-evdev/device.go | 261 ++ vendor/github.com/holoplot/go-evdev/ioctl.go | 274 ++ vendor/github.com/holoplot/go-evdev/list.go | 39 + vendor/github.com/holoplot/go-evdev/names.go | 43 + vendor/github.com/holoplot/go-evdev/types.go | 86 + vendor/github.com/holoplot/go-evdev/uinput.go | 160 + vendor/github.com/szatmary/sonos/.gitignore | 12 + .../szatmary/sonos/AVTransport/AVTransport.go | 1379 ++++++ .../szatmary/sonos/AlarmClock/AlarmClock.go | 593 +++ .../ConnectionManager/ConnectionManager.go | 174 + .../ContentDirectory/ContentDirectory.go | 539 +++ .../DeviceProperties/DeviceProperties.go | 789 ++++ .../sonos/GroupManagement/GroupManagement.go | 200 + .../GroupRenderingControl.go | 254 ++ vendor/github.com/szatmary/sonos/LICENSE | 21 + .../sonos/MusicServices/MusicServices.go | 169 + .../github.com/szatmary/sonos/QPlay/QPlay.go | 115 + .../github.com/szatmary/sonos/Queue/Queue.go | 435 ++ vendor/github.com/szatmary/sonos/README.md | 0 .../RenderingControl/RenderingControl.go | 940 +++++ .../SystemProperties/SystemProperties.go | 576 +++ .../sonos/VirtualLineIn/VirtualLineIn.go | 306 ++ .../ZoneGroupTopology/ZoneGroupTopology.go | 318 ++ vendor/github.com/szatmary/sonos/sonos.go | 100 + .../github.com/szatmary/sonos/zonegroups.go | 107 + .../github.com/szatmary/sonos/zoneplayer.go | 236 ++ vendor/modules.txt | 20 + 44 files changed, 12187 insertions(+), 14 deletions(-) create mode 100644 audio/ding-dong.wav create mode 100644 pkg/ring/ring.go create mode 100644 pkg/ring/sonos/sonos.go create mode 100644 vendor/github.com/holoplot/go-evdev/.gitignore create mode 100644 vendor/github.com/holoplot/go-evdev/LICENSE create mode 100644 vendor/github.com/holoplot/go-evdev/README.md create mode 100644 vendor/github.com/holoplot/go-evdev/bitmap.go create mode 100644 vendor/github.com/holoplot/go-evdev/codes.go create mode 100644 vendor/github.com/holoplot/go-evdev/device.go create mode 100644 vendor/github.com/holoplot/go-evdev/ioctl.go create mode 100644 vendor/github.com/holoplot/go-evdev/list.go create mode 100644 vendor/github.com/holoplot/go-evdev/names.go create mode 100644 vendor/github.com/holoplot/go-evdev/types.go create mode 100644 vendor/github.com/holoplot/go-evdev/uinput.go create mode 100644 vendor/github.com/szatmary/sonos/.gitignore create mode 100644 vendor/github.com/szatmary/sonos/AVTransport/AVTransport.go create mode 100644 vendor/github.com/szatmary/sonos/AlarmClock/AlarmClock.go create mode 100644 vendor/github.com/szatmary/sonos/ConnectionManager/ConnectionManager.go create mode 100644 vendor/github.com/szatmary/sonos/ContentDirectory/ContentDirectory.go create mode 100644 vendor/github.com/szatmary/sonos/DeviceProperties/DeviceProperties.go create mode 100644 vendor/github.com/szatmary/sonos/GroupManagement/GroupManagement.go create mode 100644 vendor/github.com/szatmary/sonos/GroupRenderingControl/GroupRenderingControl.go create mode 100644 vendor/github.com/szatmary/sonos/LICENSE create mode 100644 vendor/github.com/szatmary/sonos/MusicServices/MusicServices.go create mode 100644 vendor/github.com/szatmary/sonos/QPlay/QPlay.go create mode 100644 vendor/github.com/szatmary/sonos/Queue/Queue.go create mode 100644 vendor/github.com/szatmary/sonos/README.md create mode 100644 vendor/github.com/szatmary/sonos/RenderingControl/RenderingControl.go create mode 100644 vendor/github.com/szatmary/sonos/SystemProperties/SystemProperties.go create mode 100644 vendor/github.com/szatmary/sonos/VirtualLineIn/VirtualLineIn.go create mode 100644 vendor/github.com/szatmary/sonos/ZoneGroupTopology/ZoneGroupTopology.go create mode 100644 vendor/github.com/szatmary/sonos/sonos.go create mode 100644 vendor/github.com/szatmary/sonos/zonegroups.go create mode 100644 vendor/github.com/szatmary/sonos/zoneplayer.go diff --git a/.gitignore b/.gitignore index 062567a..003a4d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -service* \ No newline at end of file +service* +cert \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index ed70a24..125342d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,7 +13,9 @@ "env": { "HTTP_STATIC": "${workspaceFolder}/static/dist", "VIDEO_OUT_DEVICE": "/dev/video4", - "AUDIO_OUT_DEVICE": "plughw:0,1,0" + "AUDIO_OUT_DEVICE": "plughw:0,1,0", + "INPUT_DEVICE": "/dev/input/event5", + "JINGLE_PATH": "${workspaceFolder}/audio" } }, { diff --git a/audio/ding-dong.wav b/audio/ding-dong.wav new file mode 100644 index 0000000000000000000000000000000000000000..4081daa06b65deb6cf19523ad976e47f10e332e7 GIT binary patch literal 716462 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Ùg6)j_`?+8doWv=wW?^#z%u3hJ@ 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

Sn82mutGut}WJF%F}2BUZ0+{ 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 a)qi&W len(bm.bits)*8 { + return false + } + + return bm.bits[bit/8]&(1<<(bit%8)) != 0 +} + +func (bm *bitmap) setBits() []int { + var a []int + + for i, by := range bm.bits { + for bit := 0; bit < 8; bit++ { + if by&byte(1<playback(effect_id = FF_GAIN) is the first effect_id to + // cause a collision with another ff method, in this case ff->set_gain(). + // Therefore the greatest safe value for effect_id is FF_GAIN - 1, + // and thus the total number of effects should never exceed FF_GAIN. + + FF_MAX_EFFECTS = FF_GAIN + + FF_MAX = 0x7f + FF_CNT = (FF_MAX + 1) +) + +// +// Type to String +// + +var INPUTToString = map[EvProp]string{ + INPUT_PROP_POINTER: "INPUT_PROP_POINTER", + INPUT_PROP_DIRECT: "INPUT_PROP_DIRECT", + INPUT_PROP_BUTTONPAD: "INPUT_PROP_BUTTONPAD", + INPUT_PROP_SEMI_MT: "INPUT_PROP_SEMI_MT", + INPUT_PROP_TOPBUTTONPAD: "INPUT_PROP_TOPBUTTONPAD", + INPUT_PROP_POINTING_STICK: "INPUT_PROP_POINTING_STICK", + INPUT_PROP_ACCELEROMETER: "INPUT_PROP_ACCELEROMETER", + + INPUT_PROP_MAX: "INPUT_PROP_MAX", + INPUT_PROP_CNT: "INPUT_PROP_CNT", +} + +var EVToString = map[EvType]string{ + EV_SYN: "EV_SYN", + EV_KEY: "EV_KEY", + EV_REL: "EV_REL", + EV_ABS: "EV_ABS", + EV_MSC: "EV_MSC", + EV_SW: "EV_SW", + EV_LED: "EV_LED", + EV_SND: "EV_SND", + EV_REP: "EV_REP", + EV_FF: "EV_FF", + EV_PWR: "EV_PWR", + EV_FF_STATUS: "EV_FF_STATUS", + EV_MAX: "EV_MAX", + EV_CNT: "EV_CNT", +} + +var SYNToString = map[EvCode]string{ + SYN_REPORT: "SYN_REPORT", + SYN_CONFIG: "SYN_CONFIG", + SYN_MT_REPORT: "SYN_MT_REPORT", + SYN_DROPPED: "SYN_DROPPED", + SYN_MAX: "SYN_MAX", + SYN_CNT: "SYN_CNT", +} + +var KEYToString = map[EvCode]string{ + KEY_RESERVED: "KEY_RESERVED", + KEY_ESC: "KEY_ESC", + KEY_1: "KEY_1", + KEY_2: "KEY_2", + KEY_3: "KEY_3", + KEY_4: "KEY_4", + KEY_5: "KEY_5", + KEY_6: "KEY_6", + KEY_7: "KEY_7", + KEY_8: "KEY_8", + KEY_9: "KEY_9", + KEY_0: "KEY_0", + KEY_MINUS: "KEY_MINUS", + KEY_EQUAL: "KEY_EQUAL", + KEY_BACKSPACE: "KEY_BACKSPACE", + KEY_TAB: "KEY_TAB", + KEY_Q: "KEY_Q", + KEY_W: "KEY_W", + KEY_E: "KEY_E", + KEY_R: "KEY_R", + KEY_T: "KEY_T", + KEY_Y: "KEY_Y", + KEY_U: "KEY_U", + KEY_I: "KEY_I", + KEY_O: "KEY_O", + KEY_P: "KEY_P", + KEY_LEFTBRACE: "KEY_LEFTBRACE", + KEY_RIGHTBRACE: "KEY_RIGHTBRACE", + KEY_ENTER: "KEY_ENTER", + KEY_LEFTCTRL: "KEY_LEFTCTRL", + KEY_A: "KEY_A", + KEY_S: "KEY_S", + KEY_D: "KEY_D", + KEY_F: "KEY_F", + KEY_G: "KEY_G", + KEY_H: "KEY_H", + KEY_J: "KEY_J", + KEY_K: "KEY_K", + KEY_L: "KEY_L", + KEY_SEMICOLON: "KEY_SEMICOLON", + KEY_APOSTROPHE: "KEY_APOSTROPHE", + KEY_GRAVE: "KEY_GRAVE", + KEY_LEFTSHIFT: "KEY_LEFTSHIFT", + KEY_BACKSLASH: "KEY_BACKSLASH", + KEY_Z: "KEY_Z", + KEY_X: "KEY_X", + KEY_C: "KEY_C", + KEY_V: "KEY_V", + KEY_B: "KEY_B", + KEY_N: "KEY_N", + KEY_M: "KEY_M", + KEY_COMMA: "KEY_COMMA", + KEY_DOT: "KEY_DOT", + KEY_SLASH: "KEY_SLASH", + KEY_RIGHTSHIFT: "KEY_RIGHTSHIFT", + KEY_KPASTERISK: "KEY_KPASTERISK", + KEY_LEFTALT: "KEY_LEFTALT", + KEY_SPACE: "KEY_SPACE", + KEY_CAPSLOCK: "KEY_CAPSLOCK", + KEY_F1: "KEY_F1", + KEY_F2: "KEY_F2", + KEY_F3: "KEY_F3", + KEY_F4: "KEY_F4", + KEY_F5: "KEY_F5", + KEY_F6: "KEY_F6", + KEY_F7: "KEY_F7", + KEY_F8: "KEY_F8", + KEY_F9: "KEY_F9", + KEY_F10: "KEY_F10", + KEY_NUMLOCK: "KEY_NUMLOCK", + KEY_SCROLLLOCK: "KEY_SCROLLLOCK", + KEY_KP7: "KEY_KP7", + KEY_KP8: "KEY_KP8", + KEY_KP9: "KEY_KP9", + KEY_KPMINUS: "KEY_KPMINUS", + KEY_KP4: "KEY_KP4", + KEY_KP5: "KEY_KP5", + KEY_KP6: "KEY_KP6", + KEY_KPPLUS: "KEY_KPPLUS", + KEY_KP1: "KEY_KP1", + KEY_KP2: "KEY_KP2", + KEY_KP3: "KEY_KP3", + KEY_KP0: "KEY_KP0", + KEY_KPDOT: "KEY_KPDOT", + + KEY_ZENKAKUHANKAKU: "KEY_ZENKAKUHANKAKU", + KEY_102ND: "KEY_102ND", + KEY_F11: "KEY_F11", + KEY_F12: "KEY_F12", + KEY_RO: "KEY_RO", + KEY_KATAKANA: "KEY_KATAKANA", + KEY_HIRAGANA: "KEY_HIRAGANA", + KEY_HENKAN: "KEY_HENKAN", + KEY_KATAKANAHIRAGANA: "KEY_KATAKANAHIRAGANA", + KEY_MUHENKAN: "KEY_MUHENKAN", + KEY_KPJPCOMMA: "KEY_KPJPCOMMA", + KEY_KPENTER: "KEY_KPENTER", + KEY_RIGHTCTRL: "KEY_RIGHTCTRL", + KEY_KPSLASH: "KEY_KPSLASH", + KEY_SYSRQ: "KEY_SYSRQ", + KEY_RIGHTALT: "KEY_RIGHTALT", + KEY_LINEFEED: "KEY_LINEFEED", + KEY_HOME: "KEY_HOME", + KEY_UP: "KEY_UP", + KEY_PAGEUP: "KEY_PAGEUP", + KEY_LEFT: "KEY_LEFT", + KEY_RIGHT: "KEY_RIGHT", + KEY_END: "KEY_END", + KEY_DOWN: "KEY_DOWN", + KEY_PAGEDOWN: "KEY_PAGEDOWN", + KEY_INSERT: "KEY_INSERT", + KEY_DELETE: "KEY_DELETE", + KEY_MACRO: "KEY_MACRO", + KEY_MUTE: "KEY_MUTE", + KEY_VOLUMEDOWN: "KEY_VOLUMEDOWN", + KEY_VOLUMEUP: "KEY_VOLUMEUP", + KEY_POWER: "KEY_POWER", + KEY_KPEQUAL: "KEY_KPEQUAL", + KEY_KPPLUSMINUS: "KEY_KPPLUSMINUS", + KEY_PAUSE: "KEY_PAUSE", + KEY_SCALE: "KEY_SCALE", + + KEY_KPCOMMA: "KEY_KPCOMMA", + KEY_HANGEUL: "KEY_HANGEUL", + // KEY_HANGUEL: "KEY_HANGUEL", // (KEY_HANGEUL) + KEY_HANJA: "KEY_HANJA", + KEY_YEN: "KEY_YEN", + KEY_LEFTMETA: "KEY_LEFTMETA", + KEY_RIGHTMETA: "KEY_RIGHTMETA", + KEY_COMPOSE: "KEY_COMPOSE", + + KEY_STOP: "KEY_STOP", + KEY_AGAIN: "KEY_AGAIN", + KEY_PROPS: "KEY_PROPS", + KEY_UNDO: "KEY_UNDO", + KEY_FRONT: "KEY_FRONT", + KEY_COPY: "KEY_COPY", + KEY_OPEN: "KEY_OPEN", + KEY_PASTE: "KEY_PASTE", + KEY_FIND: "KEY_FIND", + KEY_CUT: "KEY_CUT", + KEY_HELP: "KEY_HELP", + KEY_MENU: "KEY_MENU", + KEY_CALC: "KEY_CALC", + KEY_SETUP: "KEY_SETUP", + KEY_SLEEP: "KEY_SLEEP", + KEY_WAKEUP: "KEY_WAKEUP", + KEY_FILE: "KEY_FILE", + KEY_SENDFILE: "KEY_SENDFILE", + KEY_DELETEFILE: "KEY_DELETEFILE", + KEY_XFER: "KEY_XFER", + KEY_PROG1: "KEY_PROG1", + KEY_PROG2: "KEY_PROG2", + KEY_WWW: "KEY_WWW", + KEY_MSDOS: "KEY_MSDOS", + KEY_COFFEE: "KEY_COFFEE", + // KEY_SCREENLOCK: "KEY_SCREENLOCK", // (KEY_COFFEE) + KEY_ROTATE_DISPLAY: "KEY_ROTATE_DISPLAY", + // KEY_DIRECTION: "KEY_DIRECTION", // (KEY_ROTATE_DISPLAY) + KEY_CYCLEWINDOWS: "KEY_CYCLEWINDOWS", + KEY_MAIL: "KEY_MAIL", + KEY_BOOKMARKS: "KEY_BOOKMARKS", + KEY_COMPUTER: "KEY_COMPUTER", + KEY_BACK: "KEY_BACK", + KEY_FORWARD: "KEY_FORWARD", + KEY_CLOSECD: "KEY_CLOSECD", + KEY_EJECTCD: "KEY_EJECTCD", + KEY_EJECTCLOSECD: "KEY_EJECTCLOSECD", + KEY_NEXTSONG: "KEY_NEXTSONG", + KEY_PLAYPAUSE: "KEY_PLAYPAUSE", + KEY_PREVIOUSSONG: "KEY_PREVIOUSSONG", + KEY_STOPCD: "KEY_STOPCD", + KEY_RECORD: "KEY_RECORD", + KEY_REWIND: "KEY_REWIND", + KEY_PHONE: "KEY_PHONE", + KEY_ISO: "KEY_ISO", + KEY_CONFIG: "KEY_CONFIG", + KEY_HOMEPAGE: "KEY_HOMEPAGE", + KEY_REFRESH: "KEY_REFRESH", + KEY_EXIT: "KEY_EXIT", + KEY_MOVE: "KEY_MOVE", + KEY_EDIT: "KEY_EDIT", + KEY_SCROLLUP: "KEY_SCROLLUP", + KEY_SCROLLDOWN: "KEY_SCROLLDOWN", + KEY_KPLEFTPAREN: "KEY_KPLEFTPAREN", + KEY_KPRIGHTPAREN: "KEY_KPRIGHTPAREN", + KEY_NEW: "KEY_NEW", + KEY_REDO: "KEY_REDO", + + KEY_F13: "KEY_F13", + KEY_F14: "KEY_F14", + KEY_F15: "KEY_F15", + KEY_F16: "KEY_F16", + KEY_F17: "KEY_F17", + KEY_F18: "KEY_F18", + KEY_F19: "KEY_F19", + KEY_F20: "KEY_F20", + KEY_F21: "KEY_F21", + KEY_F22: "KEY_F22", + KEY_F23: "KEY_F23", + KEY_F24: "KEY_F24", + + KEY_PLAYCD: "KEY_PLAYCD", + KEY_PAUSECD: "KEY_PAUSECD", + KEY_PROG3: "KEY_PROG3", + KEY_PROG4: "KEY_PROG4", + KEY_ALL_APPLICATIONS: "KEY_ALL_APPLICATIONS", + // KEY_DASHBOARD: "KEY_DASHBOARD", // (KEY_ALL_APPLICATIONS) + KEY_SUSPEND: "KEY_SUSPEND", + KEY_CLOSE: "KEY_CLOSE", + KEY_PLAY: "KEY_PLAY", + KEY_FASTFORWARD: "KEY_FASTFORWARD", + KEY_BASSBOOST: "KEY_BASSBOOST", + KEY_PRINT: "KEY_PRINT", + KEY_HP: "KEY_HP", + KEY_CAMERA: "KEY_CAMERA", + KEY_SOUND: "KEY_SOUND", + KEY_QUESTION: "KEY_QUESTION", + KEY_EMAIL: "KEY_EMAIL", + KEY_CHAT: "KEY_CHAT", + KEY_SEARCH: "KEY_SEARCH", + KEY_CONNECT: "KEY_CONNECT", + KEY_FINANCE: "KEY_FINANCE", + KEY_SPORT: "KEY_SPORT", + KEY_SHOP: "KEY_SHOP", + KEY_ALTERASE: "KEY_ALTERASE", + KEY_CANCEL: "KEY_CANCEL", + KEY_BRIGHTNESSDOWN: "KEY_BRIGHTNESSDOWN", + KEY_BRIGHTNESSUP: "KEY_BRIGHTNESSUP", + KEY_MEDIA: "KEY_MEDIA", + + KEY_SWITCHVIDEOMODE: "KEY_SWITCHVIDEOMODE", + KEY_KBDILLUMTOGGLE: "KEY_KBDILLUMTOGGLE", + KEY_KBDILLUMDOWN: "KEY_KBDILLUMDOWN", + KEY_KBDILLUMUP: "KEY_KBDILLUMUP", + + KEY_SEND: "KEY_SEND", + KEY_REPLY: "KEY_REPLY", + KEY_FORWARDMAIL: "KEY_FORWARDMAIL", + KEY_SAVE: "KEY_SAVE", + KEY_DOCUMENTS: "KEY_DOCUMENTS", + + KEY_BATTERY: "KEY_BATTERY", + + KEY_BLUETOOTH: "KEY_BLUETOOTH", + KEY_WLAN: "KEY_WLAN", + KEY_UWB: "KEY_UWB", + + KEY_UNKNOWN: "KEY_UNKNOWN", + + KEY_VIDEO_NEXT: "KEY_VIDEO_NEXT", + KEY_VIDEO_PREV: "KEY_VIDEO_PREV", + KEY_BRIGHTNESS_CYCLE: "KEY_BRIGHTNESS_CYCLE", + KEY_BRIGHTNESS_AUTO: "KEY_BRIGHTNESS_AUTO", + // KEY_BRIGHTNESS_ZERO: "KEY_BRIGHTNESS_ZERO", // (KEY_BRIGHTNESS_AUTO) + KEY_DISPLAY_OFF: "KEY_DISPLAY_OFF", + + KEY_WWAN: "KEY_WWAN", + // KEY_WIMAX: "KEY_WIMAX", // (KEY_WWAN) + KEY_RFKILL: "KEY_RFKILL", + + KEY_MICMUTE: "KEY_MICMUTE", + + BTN_MISC: "BTN_MISC", + // BTN_0: "BTN_0", // (BTN_MISC) + BTN_1: "BTN_1", + BTN_2: "BTN_2", + BTN_3: "BTN_3", + BTN_4: "BTN_4", + BTN_5: "BTN_5", + BTN_6: "BTN_6", + BTN_7: "BTN_7", + BTN_8: "BTN_8", + BTN_9: "BTN_9", + + BTN_MOUSE: "BTN_MOUSE", + // BTN_LEFT: "BTN_LEFT", // (BTN_MOUSE) + BTN_RIGHT: "BTN_RIGHT", + BTN_MIDDLE: "BTN_MIDDLE", + BTN_SIDE: "BTN_SIDE", + BTN_EXTRA: "BTN_EXTRA", + BTN_FORWARD: "BTN_FORWARD", + BTN_BACK: "BTN_BACK", + BTN_TASK: "BTN_TASK", + + BTN_JOYSTICK: "BTN_JOYSTICK", + // BTN_TRIGGER: "BTN_TRIGGER", // (BTN_JOYSTICK) + BTN_THUMB: "BTN_THUMB", + BTN_THUMB2: "BTN_THUMB2", + BTN_TOP: "BTN_TOP", + BTN_TOP2: "BTN_TOP2", + BTN_PINKIE: "BTN_PINKIE", + BTN_BASE: "BTN_BASE", + BTN_BASE2: "BTN_BASE2", + BTN_BASE3: "BTN_BASE3", + BTN_BASE4: "BTN_BASE4", + BTN_BASE5: "BTN_BASE5", + BTN_BASE6: "BTN_BASE6", + BTN_DEAD: "BTN_DEAD", + + BTN_GAMEPAD: "BTN_GAMEPAD", + // BTN_SOUTH: "BTN_SOUTH", // (BTN_GAMEPAD) + // BTN_A: "BTN_A", // (BTN_GAMEPAD) + BTN_EAST: "BTN_EAST", + // BTN_B: "BTN_B", // (BTN_EAST) + BTN_C: "BTN_C", + BTN_NORTH: "BTN_NORTH", + // BTN_X: "BTN_X", // (BTN_NORTH) + BTN_WEST: "BTN_WEST", + // BTN_Y: "BTN_Y", // (BTN_WEST) + BTN_Z: "BTN_Z", + BTN_TL: "BTN_TL", + BTN_TR: "BTN_TR", + BTN_TL2: "BTN_TL2", + BTN_TR2: "BTN_TR2", + BTN_SELECT: "BTN_SELECT", + BTN_START: "BTN_START", + BTN_MODE: "BTN_MODE", + BTN_THUMBL: "BTN_THUMBL", + BTN_THUMBR: "BTN_THUMBR", + + BTN_DIGI: "BTN_DIGI", + // BTN_TOOL_PEN: "BTN_TOOL_PEN", // (BTN_DIGI) + BTN_TOOL_RUBBER: "BTN_TOOL_RUBBER", + BTN_TOOL_BRUSH: "BTN_TOOL_BRUSH", + BTN_TOOL_PENCIL: "BTN_TOOL_PENCIL", + BTN_TOOL_AIRBRUSH: "BTN_TOOL_AIRBRUSH", + BTN_TOOL_FINGER: "BTN_TOOL_FINGER", + BTN_TOOL_MOUSE: "BTN_TOOL_MOUSE", + BTN_TOOL_LENS: "BTN_TOOL_LENS", + BTN_TOOL_QUINTTAP: "BTN_TOOL_QUINTTAP", + BTN_STYLUS3: "BTN_STYLUS3", + BTN_TOUCH: "BTN_TOUCH", + BTN_STYLUS: "BTN_STYLUS", + BTN_STYLUS2: "BTN_STYLUS2", + BTN_TOOL_DOUBLETAP: "BTN_TOOL_DOUBLETAP", + BTN_TOOL_TRIPLETAP: "BTN_TOOL_TRIPLETAP", + BTN_TOOL_QUADTAP: "BTN_TOOL_QUADTAP", + + BTN_WHEEL: "BTN_WHEEL", + // BTN_GEAR_DOWN: "BTN_GEAR_DOWN", // (BTN_WHEEL) + BTN_GEAR_UP: "BTN_GEAR_UP", + + KEY_OK: "KEY_OK", + KEY_SELECT: "KEY_SELECT", + KEY_GOTO: "KEY_GOTO", + KEY_CLEAR: "KEY_CLEAR", + KEY_POWER2: "KEY_POWER2", + KEY_OPTION: "KEY_OPTION", + KEY_INFO: "KEY_INFO", + KEY_TIME: "KEY_TIME", + KEY_VENDOR: "KEY_VENDOR", + KEY_ARCHIVE: "KEY_ARCHIVE", + KEY_PROGRAM: "KEY_PROGRAM", + KEY_CHANNEL: "KEY_CHANNEL", + KEY_FAVORITES: "KEY_FAVORITES", + KEY_EPG: "KEY_EPG", + KEY_PVR: "KEY_PVR", + KEY_MHP: "KEY_MHP", + KEY_LANGUAGE: "KEY_LANGUAGE", + KEY_TITLE: "KEY_TITLE", + KEY_SUBTITLE: "KEY_SUBTITLE", + KEY_ANGLE: "KEY_ANGLE", + KEY_FULL_SCREEN: "KEY_FULL_SCREEN", + // KEY_ZOOM: "KEY_ZOOM", // (KEY_FULL_SCREEN) + KEY_MODE: "KEY_MODE", + KEY_KEYBOARD: "KEY_KEYBOARD", + KEY_ASPECT_RATIO: "KEY_ASPECT_RATIO", + // KEY_SCREEN: "KEY_SCREEN", // (KEY_ASPECT_RATIO) + KEY_PC: "KEY_PC", + KEY_TV: "KEY_TV", + KEY_TV2: "KEY_TV2", + KEY_VCR: "KEY_VCR", + KEY_VCR2: "KEY_VCR2", + KEY_SAT: "KEY_SAT", + KEY_SAT2: "KEY_SAT2", + KEY_CD: "KEY_CD", + KEY_TAPE: "KEY_TAPE", + KEY_RADIO: "KEY_RADIO", + KEY_TUNER: "KEY_TUNER", + KEY_PLAYER: "KEY_PLAYER", + KEY_TEXT: "KEY_TEXT", + KEY_DVD: "KEY_DVD", + KEY_AUX: "KEY_AUX", + KEY_MP3: "KEY_MP3", + KEY_AUDIO: "KEY_AUDIO", + KEY_VIDEO: "KEY_VIDEO", + KEY_DIRECTORY: "KEY_DIRECTORY", + KEY_LIST: "KEY_LIST", + KEY_MEMO: "KEY_MEMO", + KEY_CALENDAR: "KEY_CALENDAR", + KEY_RED: "KEY_RED", + KEY_GREEN: "KEY_GREEN", + KEY_YELLOW: "KEY_YELLOW", + KEY_BLUE: "KEY_BLUE", + KEY_CHANNELUP: "KEY_CHANNELUP", + KEY_CHANNELDOWN: "KEY_CHANNELDOWN", + KEY_FIRST: "KEY_FIRST", + KEY_LAST: "KEY_LAST", + KEY_AB: "KEY_AB", + KEY_NEXT: "KEY_NEXT", + KEY_RESTART: "KEY_RESTART", + KEY_SLOW: "KEY_SLOW", + KEY_SHUFFLE: "KEY_SHUFFLE", + KEY_BREAK: "KEY_BREAK", + KEY_PREVIOUS: "KEY_PREVIOUS", + KEY_DIGITS: "KEY_DIGITS", + KEY_TEEN: "KEY_TEEN", + KEY_TWEN: "KEY_TWEN", + KEY_VIDEOPHONE: "KEY_VIDEOPHONE", + KEY_GAMES: "KEY_GAMES", + KEY_ZOOMIN: "KEY_ZOOMIN", + KEY_ZOOMOUT: "KEY_ZOOMOUT", + KEY_ZOOMRESET: "KEY_ZOOMRESET", + KEY_WORDPROCESSOR: "KEY_WORDPROCESSOR", + KEY_EDITOR: "KEY_EDITOR", + KEY_SPREADSHEET: "KEY_SPREADSHEET", + KEY_GRAPHICSEDITOR: "KEY_GRAPHICSEDITOR", + KEY_PRESENTATION: "KEY_PRESENTATION", + KEY_DATABASE: "KEY_DATABASE", + KEY_NEWS: "KEY_NEWS", + KEY_VOICEMAIL: "KEY_VOICEMAIL", + KEY_ADDRESSBOOK: "KEY_ADDRESSBOOK", + KEY_MESSENGER: "KEY_MESSENGER", + KEY_DISPLAYTOGGLE: "KEY_DISPLAYTOGGLE", + // KEY_BRIGHTNESS_TOGGLE: "KEY_BRIGHTNESS_TOGGLE", // (KEY_DISPLAYTOGGLE) + KEY_SPELLCHECK: "KEY_SPELLCHECK", + KEY_LOGOFF: "KEY_LOGOFF", + + KEY_DOLLAR: "KEY_DOLLAR", + KEY_EURO: "KEY_EURO", + + KEY_FRAMEBACK: "KEY_FRAMEBACK", + KEY_FRAMEFORWARD: "KEY_FRAMEFORWARD", + KEY_CONTEXT_MENU: "KEY_CONTEXT_MENU", + KEY_MEDIA_REPEAT: "KEY_MEDIA_REPEAT", + KEY_10CHANNELSUP: "KEY_10CHANNELSUP", + KEY_10CHANNELSDOWN: "KEY_10CHANNELSDOWN", + KEY_IMAGES: "KEY_IMAGES", + KEY_NOTIFICATION_CENTER: "KEY_NOTIFICATION_CENTER", + KEY_PICKUP_PHONE: "KEY_PICKUP_PHONE", + KEY_HANGUP_PHONE: "KEY_HANGUP_PHONE", + + KEY_DEL_EOL: "KEY_DEL_EOL", + KEY_DEL_EOS: "KEY_DEL_EOS", + KEY_INS_LINE: "KEY_INS_LINE", + KEY_DEL_LINE: "KEY_DEL_LINE", + + KEY_FN: "KEY_FN", + KEY_FN_ESC: "KEY_FN_ESC", + KEY_FN_F1: "KEY_FN_F1", + KEY_FN_F2: "KEY_FN_F2", + KEY_FN_F3: "KEY_FN_F3", + KEY_FN_F4: "KEY_FN_F4", + KEY_FN_F5: "KEY_FN_F5", + KEY_FN_F6: "KEY_FN_F6", + KEY_FN_F7: "KEY_FN_F7", + KEY_FN_F8: "KEY_FN_F8", + KEY_FN_F9: "KEY_FN_F9", + KEY_FN_F10: "KEY_FN_F10", + KEY_FN_F11: "KEY_FN_F11", + KEY_FN_F12: "KEY_FN_F12", + KEY_FN_1: "KEY_FN_1", + KEY_FN_2: "KEY_FN_2", + KEY_FN_D: "KEY_FN_D", + KEY_FN_E: "KEY_FN_E", + KEY_FN_F: "KEY_FN_F", + KEY_FN_S: "KEY_FN_S", + KEY_FN_B: "KEY_FN_B", + KEY_FN_RIGHT_SHIFT: "KEY_FN_RIGHT_SHIFT", + + KEY_BRL_DOT1: "KEY_BRL_DOT1", + KEY_BRL_DOT2: "KEY_BRL_DOT2", + KEY_BRL_DOT3: "KEY_BRL_DOT3", + KEY_BRL_DOT4: "KEY_BRL_DOT4", + KEY_BRL_DOT5: "KEY_BRL_DOT5", + KEY_BRL_DOT6: "KEY_BRL_DOT6", + KEY_BRL_DOT7: "KEY_BRL_DOT7", + KEY_BRL_DOT8: "KEY_BRL_DOT8", + KEY_BRL_DOT9: "KEY_BRL_DOT9", + KEY_BRL_DOT10: "KEY_BRL_DOT10", + + KEY_NUMERIC_0: "KEY_NUMERIC_0", + KEY_NUMERIC_1: "KEY_NUMERIC_1", + KEY_NUMERIC_2: "KEY_NUMERIC_2", + KEY_NUMERIC_3: "KEY_NUMERIC_3", + KEY_NUMERIC_4: "KEY_NUMERIC_4", + KEY_NUMERIC_5: "KEY_NUMERIC_5", + KEY_NUMERIC_6: "KEY_NUMERIC_6", + KEY_NUMERIC_7: "KEY_NUMERIC_7", + KEY_NUMERIC_8: "KEY_NUMERIC_8", + KEY_NUMERIC_9: "KEY_NUMERIC_9", + KEY_NUMERIC_STAR: "KEY_NUMERIC_STAR", + KEY_NUMERIC_POUND: "KEY_NUMERIC_POUND", + KEY_NUMERIC_A: "KEY_NUMERIC_A", + KEY_NUMERIC_B: "KEY_NUMERIC_B", + KEY_NUMERIC_C: "KEY_NUMERIC_C", + KEY_NUMERIC_D: "KEY_NUMERIC_D", + + KEY_CAMERA_FOCUS: "KEY_CAMERA_FOCUS", + KEY_WPS_BUTTON: "KEY_WPS_BUTTON", + + KEY_TOUCHPAD_TOGGLE: "KEY_TOUCHPAD_TOGGLE", + KEY_TOUCHPAD_ON: "KEY_TOUCHPAD_ON", + KEY_TOUCHPAD_OFF: "KEY_TOUCHPAD_OFF", + + KEY_CAMERA_ZOOMIN: "KEY_CAMERA_ZOOMIN", + KEY_CAMERA_ZOOMOUT: "KEY_CAMERA_ZOOMOUT", + KEY_CAMERA_UP: "KEY_CAMERA_UP", + KEY_CAMERA_DOWN: "KEY_CAMERA_DOWN", + KEY_CAMERA_LEFT: "KEY_CAMERA_LEFT", + KEY_CAMERA_RIGHT: "KEY_CAMERA_RIGHT", + + KEY_ATTENDANT_ON: "KEY_ATTENDANT_ON", + KEY_ATTENDANT_OFF: "KEY_ATTENDANT_OFF", + KEY_ATTENDANT_TOGGLE: "KEY_ATTENDANT_TOGGLE", + KEY_LIGHTS_TOGGLE: "KEY_LIGHTS_TOGGLE", + + BTN_DPAD_UP: "BTN_DPAD_UP", + BTN_DPAD_DOWN: "BTN_DPAD_DOWN", + BTN_DPAD_LEFT: "BTN_DPAD_LEFT", + BTN_DPAD_RIGHT: "BTN_DPAD_RIGHT", + + KEY_ALS_TOGGLE: "KEY_ALS_TOGGLE", + KEY_ROTATE_LOCK_TOGGLE: "KEY_ROTATE_LOCK_TOGGLE", + + KEY_BUTTONCONFIG: "KEY_BUTTONCONFIG", + KEY_TASKMANAGER: "KEY_TASKMANAGER", + KEY_JOURNAL: "KEY_JOURNAL", + KEY_CONTROLPANEL: "KEY_CONTROLPANEL", + KEY_APPSELECT: "KEY_APPSELECT", + KEY_SCREENSAVER: "KEY_SCREENSAVER", + KEY_VOICECOMMAND: "KEY_VOICECOMMAND", + KEY_ASSISTANT: "KEY_ASSISTANT", + KEY_KBD_LAYOUT_NEXT: "KEY_KBD_LAYOUT_NEXT", + KEY_EMOJI_PICKER: "KEY_EMOJI_PICKER", + KEY_DICTATE: "KEY_DICTATE", + + KEY_BRIGHTNESS_MIN: "KEY_BRIGHTNESS_MIN", + KEY_BRIGHTNESS_MAX: "KEY_BRIGHTNESS_MAX", + + KEY_KBDINPUTASSIST_PREV: "KEY_KBDINPUTASSIST_PREV", + KEY_KBDINPUTASSIST_NEXT: "KEY_KBDINPUTASSIST_NEXT", + KEY_KBDINPUTASSIST_PREVGROUP: "KEY_KBDINPUTASSIST_PREVGROUP", + KEY_KBDINPUTASSIST_NEXTGROUP: "KEY_KBDINPUTASSIST_NEXTGROUP", + KEY_KBDINPUTASSIST_ACCEPT: "KEY_KBDINPUTASSIST_ACCEPT", + KEY_KBDINPUTASSIST_CANCEL: "KEY_KBDINPUTASSIST_CANCEL", + + KEY_RIGHT_UP: "KEY_RIGHT_UP", + KEY_RIGHT_DOWN: "KEY_RIGHT_DOWN", + KEY_LEFT_UP: "KEY_LEFT_UP", + KEY_LEFT_DOWN: "KEY_LEFT_DOWN", + + KEY_ROOT_MENU: "KEY_ROOT_MENU", + + KEY_MEDIA_TOP_MENU: "KEY_MEDIA_TOP_MENU", + KEY_NUMERIC_11: "KEY_NUMERIC_11", + KEY_NUMERIC_12: "KEY_NUMERIC_12", + + KEY_AUDIO_DESC: "KEY_AUDIO_DESC", + KEY_3D_MODE: "KEY_3D_MODE", + KEY_NEXT_FAVORITE: "KEY_NEXT_FAVORITE", + KEY_STOP_RECORD: "KEY_STOP_RECORD", + KEY_PAUSE_RECORD: "KEY_PAUSE_RECORD", + KEY_VOD: "KEY_VOD", + KEY_UNMUTE: "KEY_UNMUTE", + KEY_FASTREVERSE: "KEY_FASTREVERSE", + KEY_SLOWREVERSE: "KEY_SLOWREVERSE", + + KEY_DATA: "KEY_DATA", + KEY_ONSCREEN_KEYBOARD: "KEY_ONSCREEN_KEYBOARD", + + KEY_PRIVACY_SCREEN_TOGGLE: "KEY_PRIVACY_SCREEN_TOGGLE", + + KEY_SELECTIVE_SCREENSHOT: "KEY_SELECTIVE_SCREENSHOT", + + KEY_MACRO1: "KEY_MACRO1", + KEY_MACRO2: "KEY_MACRO2", + KEY_MACRO3: "KEY_MACRO3", + KEY_MACRO4: "KEY_MACRO4", + KEY_MACRO5: "KEY_MACRO5", + KEY_MACRO6: "KEY_MACRO6", + KEY_MACRO7: "KEY_MACRO7", + KEY_MACRO8: "KEY_MACRO8", + KEY_MACRO9: "KEY_MACRO9", + KEY_MACRO10: "KEY_MACRO10", + KEY_MACRO11: "KEY_MACRO11", + KEY_MACRO12: "KEY_MACRO12", + KEY_MACRO13: "KEY_MACRO13", + KEY_MACRO14: "KEY_MACRO14", + KEY_MACRO15: "KEY_MACRO15", + KEY_MACRO16: "KEY_MACRO16", + KEY_MACRO17: "KEY_MACRO17", + KEY_MACRO18: "KEY_MACRO18", + KEY_MACRO19: "KEY_MACRO19", + KEY_MACRO20: "KEY_MACRO20", + KEY_MACRO21: "KEY_MACRO21", + KEY_MACRO22: "KEY_MACRO22", + KEY_MACRO23: "KEY_MACRO23", + KEY_MACRO24: "KEY_MACRO24", + KEY_MACRO25: "KEY_MACRO25", + KEY_MACRO26: "KEY_MACRO26", + KEY_MACRO27: "KEY_MACRO27", + KEY_MACRO28: "KEY_MACRO28", + KEY_MACRO29: "KEY_MACRO29", + KEY_MACRO30: "KEY_MACRO30", + + KEY_MACRO_RECORD_START: "KEY_MACRO_RECORD_START", + KEY_MACRO_RECORD_STOP: "KEY_MACRO_RECORD_STOP", + KEY_MACRO_PRESET_CYCLE: "KEY_MACRO_PRESET_CYCLE", + KEY_MACRO_PRESET1: "KEY_MACRO_PRESET1", + KEY_MACRO_PRESET2: "KEY_MACRO_PRESET2", + KEY_MACRO_PRESET3: "KEY_MACRO_PRESET3", + + KEY_KBD_LCD_MENU1: "KEY_KBD_LCD_MENU1", + KEY_KBD_LCD_MENU2: "KEY_KBD_LCD_MENU2", + KEY_KBD_LCD_MENU3: "KEY_KBD_LCD_MENU3", + KEY_KBD_LCD_MENU4: "KEY_KBD_LCD_MENU4", + KEY_KBD_LCD_MENU5: "KEY_KBD_LCD_MENU5", + + BTN_TRIGGER_HAPPY: "BTN_TRIGGER_HAPPY", + // BTN_TRIGGER_HAPPY1: "BTN_TRIGGER_HAPPY1", // (BTN_TRIGGER_HAPPY) + BTN_TRIGGER_HAPPY2: "BTN_TRIGGER_HAPPY2", + BTN_TRIGGER_HAPPY3: "BTN_TRIGGER_HAPPY3", + BTN_TRIGGER_HAPPY4: "BTN_TRIGGER_HAPPY4", + BTN_TRIGGER_HAPPY5: "BTN_TRIGGER_HAPPY5", + BTN_TRIGGER_HAPPY6: "BTN_TRIGGER_HAPPY6", + BTN_TRIGGER_HAPPY7: "BTN_TRIGGER_HAPPY7", + BTN_TRIGGER_HAPPY8: "BTN_TRIGGER_HAPPY8", + BTN_TRIGGER_HAPPY9: "BTN_TRIGGER_HAPPY9", + BTN_TRIGGER_HAPPY10: "BTN_TRIGGER_HAPPY10", + BTN_TRIGGER_HAPPY11: "BTN_TRIGGER_HAPPY11", + BTN_TRIGGER_HAPPY12: "BTN_TRIGGER_HAPPY12", + BTN_TRIGGER_HAPPY13: "BTN_TRIGGER_HAPPY13", + BTN_TRIGGER_HAPPY14: "BTN_TRIGGER_HAPPY14", + BTN_TRIGGER_HAPPY15: "BTN_TRIGGER_HAPPY15", + BTN_TRIGGER_HAPPY16: "BTN_TRIGGER_HAPPY16", + BTN_TRIGGER_HAPPY17: "BTN_TRIGGER_HAPPY17", + BTN_TRIGGER_HAPPY18: "BTN_TRIGGER_HAPPY18", + BTN_TRIGGER_HAPPY19: "BTN_TRIGGER_HAPPY19", + BTN_TRIGGER_HAPPY20: "BTN_TRIGGER_HAPPY20", + BTN_TRIGGER_HAPPY21: "BTN_TRIGGER_HAPPY21", + BTN_TRIGGER_HAPPY22: "BTN_TRIGGER_HAPPY22", + BTN_TRIGGER_HAPPY23: "BTN_TRIGGER_HAPPY23", + BTN_TRIGGER_HAPPY24: "BTN_TRIGGER_HAPPY24", + BTN_TRIGGER_HAPPY25: "BTN_TRIGGER_HAPPY25", + BTN_TRIGGER_HAPPY26: "BTN_TRIGGER_HAPPY26", + BTN_TRIGGER_HAPPY27: "BTN_TRIGGER_HAPPY27", + BTN_TRIGGER_HAPPY28: "BTN_TRIGGER_HAPPY28", + BTN_TRIGGER_HAPPY29: "BTN_TRIGGER_HAPPY29", + BTN_TRIGGER_HAPPY30: "BTN_TRIGGER_HAPPY30", + BTN_TRIGGER_HAPPY31: "BTN_TRIGGER_HAPPY31", + BTN_TRIGGER_HAPPY32: "BTN_TRIGGER_HAPPY32", + BTN_TRIGGER_HAPPY33: "BTN_TRIGGER_HAPPY33", + BTN_TRIGGER_HAPPY34: "BTN_TRIGGER_HAPPY34", + BTN_TRIGGER_HAPPY35: "BTN_TRIGGER_HAPPY35", + BTN_TRIGGER_HAPPY36: "BTN_TRIGGER_HAPPY36", + BTN_TRIGGER_HAPPY37: "BTN_TRIGGER_HAPPY37", + BTN_TRIGGER_HAPPY38: "BTN_TRIGGER_HAPPY38", + BTN_TRIGGER_HAPPY39: "BTN_TRIGGER_HAPPY39", + BTN_TRIGGER_HAPPY40: "BTN_TRIGGER_HAPPY40", + + // KEY_MIN_INTERESTING: "KEY_MIN_INTERESTING", // (KEY_MUTE) + KEY_MAX: "KEY_MAX", + KEY_CNT: "KEY_CNT", +} + +var RELToString = map[EvCode]string{ + REL_X: "REL_X", + REL_Y: "REL_Y", + REL_Z: "REL_Z", + REL_RX: "REL_RX", + REL_RY: "REL_RY", + REL_RZ: "REL_RZ", + REL_HWHEEL: "REL_HWHEEL", + REL_DIAL: "REL_DIAL", + REL_WHEEL: "REL_WHEEL", + REL_MISC: "REL_MISC", + + REL_RESERVED: "REL_RESERVED", + REL_WHEEL_HI_RES: "REL_WHEEL_HI_RES", + REL_HWHEEL_HI_RES: "REL_HWHEEL_HI_RES", + REL_MAX: "REL_MAX", + REL_CNT: "REL_CNT", +} + +var ABSToString = map[EvCode]string{ + ABS_X: "ABS_X", + ABS_Y: "ABS_Y", + ABS_Z: "ABS_Z", + ABS_RX: "ABS_RX", + ABS_RY: "ABS_RY", + ABS_RZ: "ABS_RZ", + ABS_THROTTLE: "ABS_THROTTLE", + ABS_RUDDER: "ABS_RUDDER", + ABS_WHEEL: "ABS_WHEEL", + ABS_GAS: "ABS_GAS", + ABS_BRAKE: "ABS_BRAKE", + ABS_HAT0X: "ABS_HAT0X", + ABS_HAT0Y: "ABS_HAT0Y", + ABS_HAT1X: "ABS_HAT1X", + ABS_HAT1Y: "ABS_HAT1Y", + ABS_HAT2X: "ABS_HAT2X", + ABS_HAT2Y: "ABS_HAT2Y", + ABS_HAT3X: "ABS_HAT3X", + ABS_HAT3Y: "ABS_HAT3Y", + ABS_PRESSURE: "ABS_PRESSURE", + ABS_DISTANCE: "ABS_DISTANCE", + ABS_TILT_X: "ABS_TILT_X", + ABS_TILT_Y: "ABS_TILT_Y", + ABS_TOOL_WIDTH: "ABS_TOOL_WIDTH", + + ABS_VOLUME: "ABS_VOLUME", + + ABS_MISC: "ABS_MISC", + + ABS_RESERVED: "ABS_RESERVED", + + ABS_MT_SLOT: "ABS_MT_SLOT", + ABS_MT_TOUCH_MAJOR: "ABS_MT_TOUCH_MAJOR", + ABS_MT_TOUCH_MINOR: "ABS_MT_TOUCH_MINOR", + ABS_MT_WIDTH_MAJOR: "ABS_MT_WIDTH_MAJOR", + ABS_MT_WIDTH_MINOR: "ABS_MT_WIDTH_MINOR", + ABS_MT_ORIENTATION: "ABS_MT_ORIENTATION", + ABS_MT_POSITION_X: "ABS_MT_POSITION_X", + ABS_MT_POSITION_Y: "ABS_MT_POSITION_Y", + ABS_MT_TOOL_TYPE: "ABS_MT_TOOL_TYPE", + ABS_MT_BLOB_ID: "ABS_MT_BLOB_ID", + ABS_MT_TRACKING_ID: "ABS_MT_TRACKING_ID", + ABS_MT_PRESSURE: "ABS_MT_PRESSURE", + ABS_MT_DISTANCE: "ABS_MT_DISTANCE", + ABS_MT_TOOL_X: "ABS_MT_TOOL_X", + ABS_MT_TOOL_Y: "ABS_MT_TOOL_Y", + + ABS_MAX: "ABS_MAX", + ABS_CNT: "ABS_CNT", +} + +var SWToString = map[EvCode]string{ + SW_LID: "SW_LID", + SW_TABLET_MODE: "SW_TABLET_MODE", + SW_HEADPHONE_INSERT: "SW_HEADPHONE_INSERT", + SW_RFKILL_ALL: "SW_RFKILL_ALL", + // SW_RADIO: "SW_RADIO", // (SW_RFKILL_ALL) + SW_MICROPHONE_INSERT: "SW_MICROPHONE_INSERT", + SW_DOCK: "SW_DOCK", + SW_LINEOUT_INSERT: "SW_LINEOUT_INSERT", + SW_JACK_PHYSICAL_INSERT: "SW_JACK_PHYSICAL_INSERT", + SW_VIDEOOUT_INSERT: "SW_VIDEOOUT_INSERT", + SW_CAMERA_LENS_COVER: "SW_CAMERA_LENS_COVER", + SW_KEYPAD_SLIDE: "SW_KEYPAD_SLIDE", + SW_FRONT_PROXIMITY: "SW_FRONT_PROXIMITY", + SW_ROTATE_LOCK: "SW_ROTATE_LOCK", + SW_LINEIN_INSERT: "SW_LINEIN_INSERT", + SW_MUTE_DEVICE: "SW_MUTE_DEVICE", + SW_PEN_INSERTED: "SW_PEN_INSERTED", + SW_MACHINE_COVER: "SW_MACHINE_COVER", + // SW_MAX: "SW_MAX", // (SW_MACHINE_COVER) + SW_CNT: "SW_CNT", +} + +var MSCToString = map[EvCode]string{ + MSC_SERIAL: "MSC_SERIAL", + MSC_PULSELED: "MSC_PULSELED", + MSC_GESTURE: "MSC_GESTURE", + MSC_RAW: "MSC_RAW", + MSC_SCAN: "MSC_SCAN", + MSC_TIMESTAMP: "MSC_TIMESTAMP", + MSC_MAX: "MSC_MAX", + MSC_CNT: "MSC_CNT", +} + +var LEDToString = map[EvCode]string{ + LED_NUML: "LED_NUML", + LED_CAPSL: "LED_CAPSL", + LED_SCROLLL: "LED_SCROLLL", + LED_COMPOSE: "LED_COMPOSE", + LED_KANA: "LED_KANA", + LED_SLEEP: "LED_SLEEP", + LED_SUSPEND: "LED_SUSPEND", + LED_MUTE: "LED_MUTE", + LED_MISC: "LED_MISC", + LED_MAIL: "LED_MAIL", + LED_CHARGING: "LED_CHARGING", + LED_MAX: "LED_MAX", + LED_CNT: "LED_CNT", +} + +var REPToString = map[EvCode]string{ + REP_DELAY: "REP_DELAY", + REP_PERIOD: "REP_PERIOD", + // REP_MAX: "REP_MAX", // (REP_PERIOD) + REP_CNT: "REP_CNT", +} + +var SNDToString = map[EvCode]string{ + SND_CLICK: "SND_CLICK", + SND_BELL: "SND_BELL", + SND_TONE: "SND_TONE", + SND_MAX: "SND_MAX", + SND_CNT: "SND_CNT", +} + +var IDToString = map[EvCode]string{ + ID_BUS: "ID_BUS", + ID_VENDOR: "ID_VENDOR", + ID_PRODUCT: "ID_PRODUCT", + ID_VERSION: "ID_VERSION", +} + +var BUSToString = map[EvCode]string{ + BUS_PCI: "BUS_PCI", + BUS_ISAPNP: "BUS_ISAPNP", + BUS_USB: "BUS_USB", + BUS_HIL: "BUS_HIL", + BUS_BLUETOOTH: "BUS_BLUETOOTH", + BUS_VIRTUAL: "BUS_VIRTUAL", + + BUS_ISA: "BUS_ISA", + BUS_I8042: "BUS_I8042", + BUS_XTKBD: "BUS_XTKBD", + BUS_RS232: "BUS_RS232", + BUS_GAMEPORT: "BUS_GAMEPORT", + BUS_PARPORT: "BUS_PARPORT", + BUS_AMIGA: "BUS_AMIGA", + BUS_ADB: "BUS_ADB", + BUS_I2C: "BUS_I2C", + BUS_HOST: "BUS_HOST", + BUS_GSC: "BUS_GSC", + BUS_ATARI: "BUS_ATARI", + BUS_SPI: "BUS_SPI", + BUS_RMI: "BUS_RMI", + BUS_CEC: "BUS_CEC", + BUS_INTEL_ISHTP: "BUS_INTEL_ISHTP", +} + +var MTToString = map[EvCode]string{ + MT_TOOL_FINGER: "MT_TOOL_FINGER", + MT_TOOL_PEN: "MT_TOOL_PEN", + MT_TOOL_PALM: "MT_TOOL_PALM", + MT_TOOL_DIAL: "MT_TOOL_DIAL", + MT_TOOL_MAX: "MT_TOOL_MAX", +} + +var FFToString = map[EvCode]string{ + FF_STATUS_STOPPED: "FF_STATUS_STOPPED", + FF_STATUS_PLAYING: "FF_STATUS_PLAYING", + // FF_STATUS_MAX: "FF_STATUS_MAX", // (FF_STATUS_PLAYING) + + FF_RUMBLE: "FF_RUMBLE", + FF_PERIODIC: "FF_PERIODIC", + FF_CONSTANT: "FF_CONSTANT", + FF_SPRING: "FF_SPRING", + FF_FRICTION: "FF_FRICTION", + FF_DAMPER: "FF_DAMPER", + FF_INERTIA: "FF_INERTIA", + FF_RAMP: "FF_RAMP", + + // FF_EFFECT_MIN: "FF_EFFECT_MIN", // (FF_RUMBLE) + // FF_EFFECT_MAX: "FF_EFFECT_MAX", // (FF_RAMP) + + FF_SQUARE: "FF_SQUARE", + FF_TRIANGLE: "FF_TRIANGLE", + FF_SINE: "FF_SINE", + FF_SAW_UP: "FF_SAW_UP", + FF_SAW_DOWN: "FF_SAW_DOWN", + FF_CUSTOM: "FF_CUSTOM", + + // FF_WAVEFORM_MIN: "FF_WAVEFORM_MIN", // (FF_SQUARE) + // FF_WAVEFORM_MAX: "FF_WAVEFORM_MAX", // (FF_CUSTOM) + + FF_GAIN: "FF_GAIN", + FF_AUTOCENTER: "FF_AUTOCENTER", + + // FF_MAX_EFFECTS: "FF_MAX_EFFECTS", // (FF_GAIN) + + FF_MAX: "FF_MAX", + FF_CNT: "FF_CNT", +} + +// +// Type from String +// + +var INPUTFromString = map[string]EvProp{ + "INPUT_PROP_POINTER": INPUT_PROP_POINTER, + "INPUT_PROP_DIRECT": INPUT_PROP_DIRECT, + "INPUT_PROP_BUTTONPAD": INPUT_PROP_BUTTONPAD, + "INPUT_PROP_SEMI_MT": INPUT_PROP_SEMI_MT, + "INPUT_PROP_TOPBUTTONPAD": INPUT_PROP_TOPBUTTONPAD, + "INPUT_PROP_POINTING_STICK": INPUT_PROP_POINTING_STICK, + "INPUT_PROP_ACCELEROMETER": INPUT_PROP_ACCELEROMETER, + + "INPUT_PROP_MAX": INPUT_PROP_MAX, + "INPUT_PROP_CNT": INPUT_PROP_CNT, +} + +var EVFromString = map[string]EvType{ + "EV_SYN": EV_SYN, + "EV_KEY": EV_KEY, + "EV_REL": EV_REL, + "EV_ABS": EV_ABS, + "EV_MSC": EV_MSC, + "EV_SW": EV_SW, + "EV_LED": EV_LED, + "EV_SND": EV_SND, + "EV_REP": EV_REP, + "EV_FF": EV_FF, + "EV_PWR": EV_PWR, + "EV_FF_STATUS": EV_FF_STATUS, + "EV_MAX": EV_MAX, + "EV_CNT": EV_CNT, +} + +var SYNFromString = map[string]EvCode{ + "SYN_REPORT": SYN_REPORT, + "SYN_CONFIG": SYN_CONFIG, + "SYN_MT_REPORT": SYN_MT_REPORT, + "SYN_DROPPED": SYN_DROPPED, + "SYN_MAX": SYN_MAX, + "SYN_CNT": SYN_CNT, +} + +var KEYFromString = map[string]EvCode{ + "KEY_RESERVED": KEY_RESERVED, + "KEY_ESC": KEY_ESC, + "KEY_1": KEY_1, + "KEY_2": KEY_2, + "KEY_3": KEY_3, + "KEY_4": KEY_4, + "KEY_5": KEY_5, + "KEY_6": KEY_6, + "KEY_7": KEY_7, + "KEY_8": KEY_8, + "KEY_9": KEY_9, + "KEY_0": KEY_0, + "KEY_MINUS": KEY_MINUS, + "KEY_EQUAL": KEY_EQUAL, + "KEY_BACKSPACE": KEY_BACKSPACE, + "KEY_TAB": KEY_TAB, + "KEY_Q": KEY_Q, + "KEY_W": KEY_W, + "KEY_E": KEY_E, + "KEY_R": KEY_R, + "KEY_T": KEY_T, + "KEY_Y": KEY_Y, + "KEY_U": KEY_U, + "KEY_I": KEY_I, + "KEY_O": KEY_O, + "KEY_P": KEY_P, + "KEY_LEFTBRACE": KEY_LEFTBRACE, + "KEY_RIGHTBRACE": KEY_RIGHTBRACE, + "KEY_ENTER": KEY_ENTER, + "KEY_LEFTCTRL": KEY_LEFTCTRL, + "KEY_A": KEY_A, + "KEY_S": KEY_S, + "KEY_D": KEY_D, + "KEY_F": KEY_F, + "KEY_G": KEY_G, + "KEY_H": KEY_H, + "KEY_J": KEY_J, + "KEY_K": KEY_K, + "KEY_L": KEY_L, + "KEY_SEMICOLON": KEY_SEMICOLON, + "KEY_APOSTROPHE": KEY_APOSTROPHE, + "KEY_GRAVE": KEY_GRAVE, + "KEY_LEFTSHIFT": KEY_LEFTSHIFT, + "KEY_BACKSLASH": KEY_BACKSLASH, + "KEY_Z": KEY_Z, + "KEY_X": KEY_X, + "KEY_C": KEY_C, + "KEY_V": KEY_V, + "KEY_B": KEY_B, + "KEY_N": KEY_N, + "KEY_M": KEY_M, + "KEY_COMMA": KEY_COMMA, + "KEY_DOT": KEY_DOT, + "KEY_SLASH": KEY_SLASH, + "KEY_RIGHTSHIFT": KEY_RIGHTSHIFT, + "KEY_KPASTERISK": KEY_KPASTERISK, + "KEY_LEFTALT": KEY_LEFTALT, + "KEY_SPACE": KEY_SPACE, + "KEY_CAPSLOCK": KEY_CAPSLOCK, + "KEY_F1": KEY_F1, + "KEY_F2": KEY_F2, + "KEY_F3": KEY_F3, + "KEY_F4": KEY_F4, + "KEY_F5": KEY_F5, + "KEY_F6": KEY_F6, + "KEY_F7": KEY_F7, + "KEY_F8": KEY_F8, + "KEY_F9": KEY_F9, + "KEY_F10": KEY_F10, + "KEY_NUMLOCK": KEY_NUMLOCK, + "KEY_SCROLLLOCK": KEY_SCROLLLOCK, + "KEY_KP7": KEY_KP7, + "KEY_KP8": KEY_KP8, + "KEY_KP9": KEY_KP9, + "KEY_KPMINUS": KEY_KPMINUS, + "KEY_KP4": KEY_KP4, + "KEY_KP5": KEY_KP5, + "KEY_KP6": KEY_KP6, + "KEY_KPPLUS": KEY_KPPLUS, + "KEY_KP1": KEY_KP1, + "KEY_KP2": KEY_KP2, + "KEY_KP3": KEY_KP3, + "KEY_KP0": KEY_KP0, + "KEY_KPDOT": KEY_KPDOT, + + "KEY_ZENKAKUHANKAKU": KEY_ZENKAKUHANKAKU, + "KEY_102ND": KEY_102ND, + "KEY_F11": KEY_F11, + "KEY_F12": KEY_F12, + "KEY_RO": KEY_RO, + "KEY_KATAKANA": KEY_KATAKANA, + "KEY_HIRAGANA": KEY_HIRAGANA, + "KEY_HENKAN": KEY_HENKAN, + "KEY_KATAKANAHIRAGANA": KEY_KATAKANAHIRAGANA, + "KEY_MUHENKAN": KEY_MUHENKAN, + "KEY_KPJPCOMMA": KEY_KPJPCOMMA, + "KEY_KPENTER": KEY_KPENTER, + "KEY_RIGHTCTRL": KEY_RIGHTCTRL, + "KEY_KPSLASH": KEY_KPSLASH, + "KEY_SYSRQ": KEY_SYSRQ, + "KEY_RIGHTALT": KEY_RIGHTALT, + "KEY_LINEFEED": KEY_LINEFEED, + "KEY_HOME": KEY_HOME, + "KEY_UP": KEY_UP, + "KEY_PAGEUP": KEY_PAGEUP, + "KEY_LEFT": KEY_LEFT, + "KEY_RIGHT": KEY_RIGHT, + "KEY_END": KEY_END, + "KEY_DOWN": KEY_DOWN, + "KEY_PAGEDOWN": KEY_PAGEDOWN, + "KEY_INSERT": KEY_INSERT, + "KEY_DELETE": KEY_DELETE, + "KEY_MACRO": KEY_MACRO, + "KEY_MUTE": KEY_MUTE, + "KEY_VOLUMEDOWN": KEY_VOLUMEDOWN, + "KEY_VOLUMEUP": KEY_VOLUMEUP, + "KEY_POWER": KEY_POWER, + "KEY_KPEQUAL": KEY_KPEQUAL, + "KEY_KPPLUSMINUS": KEY_KPPLUSMINUS, + "KEY_PAUSE": KEY_PAUSE, + "KEY_SCALE": KEY_SCALE, + + "KEY_KPCOMMA": KEY_KPCOMMA, + "KEY_HANGEUL": KEY_HANGEUL, + "KEY_HANGUEL": KEY_HANGUEL, + "KEY_HANJA": KEY_HANJA, + "KEY_YEN": KEY_YEN, + "KEY_LEFTMETA": KEY_LEFTMETA, + "KEY_RIGHTMETA": KEY_RIGHTMETA, + "KEY_COMPOSE": KEY_COMPOSE, + + "KEY_STOP": KEY_STOP, + "KEY_AGAIN": KEY_AGAIN, + "KEY_PROPS": KEY_PROPS, + "KEY_UNDO": KEY_UNDO, + "KEY_FRONT": KEY_FRONT, + "KEY_COPY": KEY_COPY, + "KEY_OPEN": KEY_OPEN, + "KEY_PASTE": KEY_PASTE, + "KEY_FIND": KEY_FIND, + "KEY_CUT": KEY_CUT, + "KEY_HELP": KEY_HELP, + "KEY_MENU": KEY_MENU, + "KEY_CALC": KEY_CALC, + "KEY_SETUP": KEY_SETUP, + "KEY_SLEEP": KEY_SLEEP, + "KEY_WAKEUP": KEY_WAKEUP, + "KEY_FILE": KEY_FILE, + "KEY_SENDFILE": KEY_SENDFILE, + "KEY_DELETEFILE": KEY_DELETEFILE, + "KEY_XFER": KEY_XFER, + "KEY_PROG1": KEY_PROG1, + "KEY_PROG2": KEY_PROG2, + "KEY_WWW": KEY_WWW, + "KEY_MSDOS": KEY_MSDOS, + "KEY_COFFEE": KEY_COFFEE, + "KEY_SCREENLOCK": KEY_SCREENLOCK, + "KEY_ROTATE_DISPLAY": KEY_ROTATE_DISPLAY, + "KEY_DIRECTION": KEY_DIRECTION, + "KEY_CYCLEWINDOWS": KEY_CYCLEWINDOWS, + "KEY_MAIL": KEY_MAIL, + "KEY_BOOKMARKS": KEY_BOOKMARKS, + "KEY_COMPUTER": KEY_COMPUTER, + "KEY_BACK": KEY_BACK, + "KEY_FORWARD": KEY_FORWARD, + "KEY_CLOSECD": KEY_CLOSECD, + "KEY_EJECTCD": KEY_EJECTCD, + "KEY_EJECTCLOSECD": KEY_EJECTCLOSECD, + "KEY_NEXTSONG": KEY_NEXTSONG, + "KEY_PLAYPAUSE": KEY_PLAYPAUSE, + "KEY_PREVIOUSSONG": KEY_PREVIOUSSONG, + "KEY_STOPCD": KEY_STOPCD, + "KEY_RECORD": KEY_RECORD, + "KEY_REWIND": KEY_REWIND, + "KEY_PHONE": KEY_PHONE, + "KEY_ISO": KEY_ISO, + "KEY_CONFIG": KEY_CONFIG, + "KEY_HOMEPAGE": KEY_HOMEPAGE, + "KEY_REFRESH": KEY_REFRESH, + "KEY_EXIT": KEY_EXIT, + "KEY_MOVE": KEY_MOVE, + "KEY_EDIT": KEY_EDIT, + "KEY_SCROLLUP": KEY_SCROLLUP, + "KEY_SCROLLDOWN": KEY_SCROLLDOWN, + "KEY_KPLEFTPAREN": KEY_KPLEFTPAREN, + "KEY_KPRIGHTPAREN": KEY_KPRIGHTPAREN, + "KEY_NEW": KEY_NEW, + "KEY_REDO": KEY_REDO, + + "KEY_F13": KEY_F13, + "KEY_F14": KEY_F14, + "KEY_F15": KEY_F15, + "KEY_F16": KEY_F16, + "KEY_F17": KEY_F17, + "KEY_F18": KEY_F18, + "KEY_F19": KEY_F19, + "KEY_F20": KEY_F20, + "KEY_F21": KEY_F21, + "KEY_F22": KEY_F22, + "KEY_F23": KEY_F23, + "KEY_F24": KEY_F24, + + "KEY_PLAYCD": KEY_PLAYCD, + "KEY_PAUSECD": KEY_PAUSECD, + "KEY_PROG3": KEY_PROG3, + "KEY_PROG4": KEY_PROG4, + "KEY_ALL_APPLICATIONS": KEY_ALL_APPLICATIONS, + "KEY_DASHBOARD": KEY_DASHBOARD, + "KEY_SUSPEND": KEY_SUSPEND, + "KEY_CLOSE": KEY_CLOSE, + "KEY_PLAY": KEY_PLAY, + "KEY_FASTFORWARD": KEY_FASTFORWARD, + "KEY_BASSBOOST": KEY_BASSBOOST, + "KEY_PRINT": KEY_PRINT, + "KEY_HP": KEY_HP, + "KEY_CAMERA": KEY_CAMERA, + "KEY_SOUND": KEY_SOUND, + "KEY_QUESTION": KEY_QUESTION, + "KEY_EMAIL": KEY_EMAIL, + "KEY_CHAT": KEY_CHAT, + "KEY_SEARCH": KEY_SEARCH, + "KEY_CONNECT": KEY_CONNECT, + "KEY_FINANCE": KEY_FINANCE, + "KEY_SPORT": KEY_SPORT, + "KEY_SHOP": KEY_SHOP, + "KEY_ALTERASE": KEY_ALTERASE, + "KEY_CANCEL": KEY_CANCEL, + "KEY_BRIGHTNESSDOWN": KEY_BRIGHTNESSDOWN, + "KEY_BRIGHTNESSUP": KEY_BRIGHTNESSUP, + "KEY_MEDIA": KEY_MEDIA, + + "KEY_SWITCHVIDEOMODE": KEY_SWITCHVIDEOMODE, + "KEY_KBDILLUMTOGGLE": KEY_KBDILLUMTOGGLE, + "KEY_KBDILLUMDOWN": KEY_KBDILLUMDOWN, + "KEY_KBDILLUMUP": KEY_KBDILLUMUP, + + "KEY_SEND": KEY_SEND, + "KEY_REPLY": KEY_REPLY, + "KEY_FORWARDMAIL": KEY_FORWARDMAIL, + "KEY_SAVE": KEY_SAVE, + "KEY_DOCUMENTS": KEY_DOCUMENTS, + + "KEY_BATTERY": KEY_BATTERY, + + "KEY_BLUETOOTH": KEY_BLUETOOTH, + "KEY_WLAN": KEY_WLAN, + "KEY_UWB": KEY_UWB, + + "KEY_UNKNOWN": KEY_UNKNOWN, + + "KEY_VIDEO_NEXT": KEY_VIDEO_NEXT, + "KEY_VIDEO_PREV": KEY_VIDEO_PREV, + "KEY_BRIGHTNESS_CYCLE": KEY_BRIGHTNESS_CYCLE, + "KEY_BRIGHTNESS_AUTO": KEY_BRIGHTNESS_AUTO, + "KEY_BRIGHTNESS_ZERO": KEY_BRIGHTNESS_ZERO, + "KEY_DISPLAY_OFF": KEY_DISPLAY_OFF, + + "KEY_WWAN": KEY_WWAN, + "KEY_WIMAX": KEY_WIMAX, + "KEY_RFKILL": KEY_RFKILL, + + "KEY_MICMUTE": KEY_MICMUTE, + + "BTN_MISC": BTN_MISC, + "BTN_0": BTN_0, + "BTN_1": BTN_1, + "BTN_2": BTN_2, + "BTN_3": BTN_3, + "BTN_4": BTN_4, + "BTN_5": BTN_5, + "BTN_6": BTN_6, + "BTN_7": BTN_7, + "BTN_8": BTN_8, + "BTN_9": BTN_9, + + "BTN_MOUSE": BTN_MOUSE, + "BTN_LEFT": BTN_LEFT, + "BTN_RIGHT": BTN_RIGHT, + "BTN_MIDDLE": BTN_MIDDLE, + "BTN_SIDE": BTN_SIDE, + "BTN_EXTRA": BTN_EXTRA, + "BTN_FORWARD": BTN_FORWARD, + "BTN_BACK": BTN_BACK, + "BTN_TASK": BTN_TASK, + + "BTN_JOYSTICK": BTN_JOYSTICK, + "BTN_TRIGGER": BTN_TRIGGER, + "BTN_THUMB": BTN_THUMB, + "BTN_THUMB2": BTN_THUMB2, + "BTN_TOP": BTN_TOP, + "BTN_TOP2": BTN_TOP2, + "BTN_PINKIE": BTN_PINKIE, + "BTN_BASE": BTN_BASE, + "BTN_BASE2": BTN_BASE2, + "BTN_BASE3": BTN_BASE3, + "BTN_BASE4": BTN_BASE4, + "BTN_BASE5": BTN_BASE5, + "BTN_BASE6": BTN_BASE6, + "BTN_DEAD": BTN_DEAD, + + "BTN_GAMEPAD": BTN_GAMEPAD, + "BTN_SOUTH": BTN_SOUTH, + "BTN_A": BTN_A, + "BTN_EAST": BTN_EAST, + "BTN_B": BTN_B, + "BTN_C": BTN_C, + "BTN_NORTH": BTN_NORTH, + "BTN_X": BTN_X, + "BTN_WEST": BTN_WEST, + "BTN_Y": BTN_Y, + "BTN_Z": BTN_Z, + "BTN_TL": BTN_TL, + "BTN_TR": BTN_TR, + "BTN_TL2": BTN_TL2, + "BTN_TR2": BTN_TR2, + "BTN_SELECT": BTN_SELECT, + "BTN_START": BTN_START, + "BTN_MODE": BTN_MODE, + "BTN_THUMBL": BTN_THUMBL, + "BTN_THUMBR": BTN_THUMBR, + + "BTN_DIGI": BTN_DIGI, + "BTN_TOOL_PEN": BTN_TOOL_PEN, + "BTN_TOOL_RUBBER": BTN_TOOL_RUBBER, + "BTN_TOOL_BRUSH": BTN_TOOL_BRUSH, + "BTN_TOOL_PENCIL": BTN_TOOL_PENCIL, + "BTN_TOOL_AIRBRUSH": BTN_TOOL_AIRBRUSH, + "BTN_TOOL_FINGER": BTN_TOOL_FINGER, + "BTN_TOOL_MOUSE": BTN_TOOL_MOUSE, + "BTN_TOOL_LENS": BTN_TOOL_LENS, + "BTN_TOOL_QUINTTAP": BTN_TOOL_QUINTTAP, + "BTN_STYLUS3": BTN_STYLUS3, + "BTN_TOUCH": BTN_TOUCH, + "BTN_STYLUS": BTN_STYLUS, + "BTN_STYLUS2": BTN_STYLUS2, + "BTN_TOOL_DOUBLETAP": BTN_TOOL_DOUBLETAP, + "BTN_TOOL_TRIPLETAP": BTN_TOOL_TRIPLETAP, + "BTN_TOOL_QUADTAP": BTN_TOOL_QUADTAP, + + "BTN_WHEEL": BTN_WHEEL, + "BTN_GEAR_DOWN": BTN_GEAR_DOWN, + "BTN_GEAR_UP": BTN_GEAR_UP, + + "KEY_OK": KEY_OK, + "KEY_SELECT": KEY_SELECT, + "KEY_GOTO": KEY_GOTO, + "KEY_CLEAR": KEY_CLEAR, + "KEY_POWER2": KEY_POWER2, + "KEY_OPTION": KEY_OPTION, + "KEY_INFO": KEY_INFO, + "KEY_TIME": KEY_TIME, + "KEY_VENDOR": KEY_VENDOR, + "KEY_ARCHIVE": KEY_ARCHIVE, + "KEY_PROGRAM": KEY_PROGRAM, + "KEY_CHANNEL": KEY_CHANNEL, + "KEY_FAVORITES": KEY_FAVORITES, + "KEY_EPG": KEY_EPG, + "KEY_PVR": KEY_PVR, + "KEY_MHP": KEY_MHP, + "KEY_LANGUAGE": KEY_LANGUAGE, + "KEY_TITLE": KEY_TITLE, + "KEY_SUBTITLE": KEY_SUBTITLE, + "KEY_ANGLE": KEY_ANGLE, + "KEY_FULL_SCREEN": KEY_FULL_SCREEN, + "KEY_ZOOM": KEY_ZOOM, + "KEY_MODE": KEY_MODE, + "KEY_KEYBOARD": KEY_KEYBOARD, + "KEY_ASPECT_RATIO": KEY_ASPECT_RATIO, + "KEY_SCREEN": KEY_SCREEN, + "KEY_PC": KEY_PC, + "KEY_TV": KEY_TV, + "KEY_TV2": KEY_TV2, + "KEY_VCR": KEY_VCR, + "KEY_VCR2": KEY_VCR2, + "KEY_SAT": KEY_SAT, + "KEY_SAT2": KEY_SAT2, + "KEY_CD": KEY_CD, + "KEY_TAPE": KEY_TAPE, + "KEY_RADIO": KEY_RADIO, + "KEY_TUNER": KEY_TUNER, + "KEY_PLAYER": KEY_PLAYER, + "KEY_TEXT": KEY_TEXT, + "KEY_DVD": KEY_DVD, + "KEY_AUX": KEY_AUX, + "KEY_MP3": KEY_MP3, + "KEY_AUDIO": KEY_AUDIO, + "KEY_VIDEO": KEY_VIDEO, + "KEY_DIRECTORY": KEY_DIRECTORY, + "KEY_LIST": KEY_LIST, + "KEY_MEMO": KEY_MEMO, + "KEY_CALENDAR": KEY_CALENDAR, + "KEY_RED": KEY_RED, + "KEY_GREEN": KEY_GREEN, + "KEY_YELLOW": KEY_YELLOW, + "KEY_BLUE": KEY_BLUE, + "KEY_CHANNELUP": KEY_CHANNELUP, + "KEY_CHANNELDOWN": KEY_CHANNELDOWN, + "KEY_FIRST": KEY_FIRST, + "KEY_LAST": KEY_LAST, + "KEY_AB": KEY_AB, + "KEY_NEXT": KEY_NEXT, + "KEY_RESTART": KEY_RESTART, + "KEY_SLOW": KEY_SLOW, + "KEY_SHUFFLE": KEY_SHUFFLE, + "KEY_BREAK": KEY_BREAK, + "KEY_PREVIOUS": KEY_PREVIOUS, + "KEY_DIGITS": KEY_DIGITS, + "KEY_TEEN": KEY_TEEN, + "KEY_TWEN": KEY_TWEN, + "KEY_VIDEOPHONE": KEY_VIDEOPHONE, + "KEY_GAMES": KEY_GAMES, + "KEY_ZOOMIN": KEY_ZOOMIN, + "KEY_ZOOMOUT": KEY_ZOOMOUT, + "KEY_ZOOMRESET": KEY_ZOOMRESET, + "KEY_WORDPROCESSOR": KEY_WORDPROCESSOR, + "KEY_EDITOR": KEY_EDITOR, + "KEY_SPREADSHEET": KEY_SPREADSHEET, + "KEY_GRAPHICSEDITOR": KEY_GRAPHICSEDITOR, + "KEY_PRESENTATION": KEY_PRESENTATION, + "KEY_DATABASE": KEY_DATABASE, + "KEY_NEWS": KEY_NEWS, + "KEY_VOICEMAIL": KEY_VOICEMAIL, + "KEY_ADDRESSBOOK": KEY_ADDRESSBOOK, + "KEY_MESSENGER": KEY_MESSENGER, + "KEY_DISPLAYTOGGLE": KEY_DISPLAYTOGGLE, + "KEY_BRIGHTNESS_TOGGLE": KEY_BRIGHTNESS_TOGGLE, + "KEY_SPELLCHECK": KEY_SPELLCHECK, + "KEY_LOGOFF": KEY_LOGOFF, + + "KEY_DOLLAR": KEY_DOLLAR, + "KEY_EURO": KEY_EURO, + + "KEY_FRAMEBACK": KEY_FRAMEBACK, + "KEY_FRAMEFORWARD": KEY_FRAMEFORWARD, + "KEY_CONTEXT_MENU": KEY_CONTEXT_MENU, + "KEY_MEDIA_REPEAT": KEY_MEDIA_REPEAT, + "KEY_10CHANNELSUP": KEY_10CHANNELSUP, + "KEY_10CHANNELSDOWN": KEY_10CHANNELSDOWN, + "KEY_IMAGES": KEY_IMAGES, + "KEY_NOTIFICATION_CENTER": KEY_NOTIFICATION_CENTER, + "KEY_PICKUP_PHONE": KEY_PICKUP_PHONE, + "KEY_HANGUP_PHONE": KEY_HANGUP_PHONE, + + "KEY_DEL_EOL": KEY_DEL_EOL, + "KEY_DEL_EOS": KEY_DEL_EOS, + "KEY_INS_LINE": KEY_INS_LINE, + "KEY_DEL_LINE": KEY_DEL_LINE, + + "KEY_FN": KEY_FN, + "KEY_FN_ESC": KEY_FN_ESC, + "KEY_FN_F1": KEY_FN_F1, + "KEY_FN_F2": KEY_FN_F2, + "KEY_FN_F3": KEY_FN_F3, + "KEY_FN_F4": KEY_FN_F4, + "KEY_FN_F5": KEY_FN_F5, + "KEY_FN_F6": KEY_FN_F6, + "KEY_FN_F7": KEY_FN_F7, + "KEY_FN_F8": KEY_FN_F8, + "KEY_FN_F9": KEY_FN_F9, + "KEY_FN_F10": KEY_FN_F10, + "KEY_FN_F11": KEY_FN_F11, + "KEY_FN_F12": KEY_FN_F12, + "KEY_FN_1": KEY_FN_1, + "KEY_FN_2": KEY_FN_2, + "KEY_FN_D": KEY_FN_D, + "KEY_FN_E": KEY_FN_E, + "KEY_FN_F": KEY_FN_F, + "KEY_FN_S": KEY_FN_S, + "KEY_FN_B": KEY_FN_B, + "KEY_FN_RIGHT_SHIFT": KEY_FN_RIGHT_SHIFT, + + "KEY_BRL_DOT1": KEY_BRL_DOT1, + "KEY_BRL_DOT2": KEY_BRL_DOT2, + "KEY_BRL_DOT3": KEY_BRL_DOT3, + "KEY_BRL_DOT4": KEY_BRL_DOT4, + "KEY_BRL_DOT5": KEY_BRL_DOT5, + "KEY_BRL_DOT6": KEY_BRL_DOT6, + "KEY_BRL_DOT7": KEY_BRL_DOT7, + "KEY_BRL_DOT8": KEY_BRL_DOT8, + "KEY_BRL_DOT9": KEY_BRL_DOT9, + "KEY_BRL_DOT10": KEY_BRL_DOT10, + + "KEY_NUMERIC_0": KEY_NUMERIC_0, + "KEY_NUMERIC_1": KEY_NUMERIC_1, + "KEY_NUMERIC_2": KEY_NUMERIC_2, + "KEY_NUMERIC_3": KEY_NUMERIC_3, + "KEY_NUMERIC_4": KEY_NUMERIC_4, + "KEY_NUMERIC_5": KEY_NUMERIC_5, + "KEY_NUMERIC_6": KEY_NUMERIC_6, + "KEY_NUMERIC_7": KEY_NUMERIC_7, + "KEY_NUMERIC_8": KEY_NUMERIC_8, + "KEY_NUMERIC_9": KEY_NUMERIC_9, + "KEY_NUMERIC_STAR": KEY_NUMERIC_STAR, + "KEY_NUMERIC_POUND": KEY_NUMERIC_POUND, + "KEY_NUMERIC_A": KEY_NUMERIC_A, + "KEY_NUMERIC_B": KEY_NUMERIC_B, + "KEY_NUMERIC_C": KEY_NUMERIC_C, + "KEY_NUMERIC_D": KEY_NUMERIC_D, + + "KEY_CAMERA_FOCUS": KEY_CAMERA_FOCUS, + "KEY_WPS_BUTTON": KEY_WPS_BUTTON, + + "KEY_TOUCHPAD_TOGGLE": KEY_TOUCHPAD_TOGGLE, + "KEY_TOUCHPAD_ON": KEY_TOUCHPAD_ON, + "KEY_TOUCHPAD_OFF": KEY_TOUCHPAD_OFF, + + "KEY_CAMERA_ZOOMIN": KEY_CAMERA_ZOOMIN, + "KEY_CAMERA_ZOOMOUT": KEY_CAMERA_ZOOMOUT, + "KEY_CAMERA_UP": KEY_CAMERA_UP, + "KEY_CAMERA_DOWN": KEY_CAMERA_DOWN, + "KEY_CAMERA_LEFT": KEY_CAMERA_LEFT, + "KEY_CAMERA_RIGHT": KEY_CAMERA_RIGHT, + + "KEY_ATTENDANT_ON": KEY_ATTENDANT_ON, + "KEY_ATTENDANT_OFF": KEY_ATTENDANT_OFF, + "KEY_ATTENDANT_TOGGLE": KEY_ATTENDANT_TOGGLE, + "KEY_LIGHTS_TOGGLE": KEY_LIGHTS_TOGGLE, + + "BTN_DPAD_UP": BTN_DPAD_UP, + "BTN_DPAD_DOWN": BTN_DPAD_DOWN, + "BTN_DPAD_LEFT": BTN_DPAD_LEFT, + "BTN_DPAD_RIGHT": BTN_DPAD_RIGHT, + + "KEY_ALS_TOGGLE": KEY_ALS_TOGGLE, + "KEY_ROTATE_LOCK_TOGGLE": KEY_ROTATE_LOCK_TOGGLE, + + "KEY_BUTTONCONFIG": KEY_BUTTONCONFIG, + "KEY_TASKMANAGER": KEY_TASKMANAGER, + "KEY_JOURNAL": KEY_JOURNAL, + "KEY_CONTROLPANEL": KEY_CONTROLPANEL, + "KEY_APPSELECT": KEY_APPSELECT, + "KEY_SCREENSAVER": KEY_SCREENSAVER, + "KEY_VOICECOMMAND": KEY_VOICECOMMAND, + "KEY_ASSISTANT": KEY_ASSISTANT, + "KEY_KBD_LAYOUT_NEXT": KEY_KBD_LAYOUT_NEXT, + "KEY_EMOJI_PICKER": KEY_EMOJI_PICKER, + "KEY_DICTATE": KEY_DICTATE, + + "KEY_BRIGHTNESS_MIN": KEY_BRIGHTNESS_MIN, + "KEY_BRIGHTNESS_MAX": KEY_BRIGHTNESS_MAX, + + "KEY_KBDINPUTASSIST_PREV": KEY_KBDINPUTASSIST_PREV, + "KEY_KBDINPUTASSIST_NEXT": KEY_KBDINPUTASSIST_NEXT, + "KEY_KBDINPUTASSIST_PREVGROUP": KEY_KBDINPUTASSIST_PREVGROUP, + "KEY_KBDINPUTASSIST_NEXTGROUP": KEY_KBDINPUTASSIST_NEXTGROUP, + "KEY_KBDINPUTASSIST_ACCEPT": KEY_KBDINPUTASSIST_ACCEPT, + "KEY_KBDINPUTASSIST_CANCEL": KEY_KBDINPUTASSIST_CANCEL, + + "KEY_RIGHT_UP": KEY_RIGHT_UP, + "KEY_RIGHT_DOWN": KEY_RIGHT_DOWN, + "KEY_LEFT_UP": KEY_LEFT_UP, + "KEY_LEFT_DOWN": KEY_LEFT_DOWN, + + "KEY_ROOT_MENU": KEY_ROOT_MENU, + + "KEY_MEDIA_TOP_MENU": KEY_MEDIA_TOP_MENU, + "KEY_NUMERIC_11": KEY_NUMERIC_11, + "KEY_NUMERIC_12": KEY_NUMERIC_12, + + "KEY_AUDIO_DESC": KEY_AUDIO_DESC, + "KEY_3D_MODE": KEY_3D_MODE, + "KEY_NEXT_FAVORITE": KEY_NEXT_FAVORITE, + "KEY_STOP_RECORD": KEY_STOP_RECORD, + "KEY_PAUSE_RECORD": KEY_PAUSE_RECORD, + "KEY_VOD": KEY_VOD, + "KEY_UNMUTE": KEY_UNMUTE, + "KEY_FASTREVERSE": KEY_FASTREVERSE, + "KEY_SLOWREVERSE": KEY_SLOWREVERSE, + + "KEY_DATA": KEY_DATA, + "KEY_ONSCREEN_KEYBOARD": KEY_ONSCREEN_KEYBOARD, + + "KEY_PRIVACY_SCREEN_TOGGLE": KEY_PRIVACY_SCREEN_TOGGLE, + + "KEY_SELECTIVE_SCREENSHOT": KEY_SELECTIVE_SCREENSHOT, + + "KEY_MACRO1": KEY_MACRO1, + "KEY_MACRO2": KEY_MACRO2, + "KEY_MACRO3": KEY_MACRO3, + "KEY_MACRO4": KEY_MACRO4, + "KEY_MACRO5": KEY_MACRO5, + "KEY_MACRO6": KEY_MACRO6, + "KEY_MACRO7": KEY_MACRO7, + "KEY_MACRO8": KEY_MACRO8, + "KEY_MACRO9": KEY_MACRO9, + "KEY_MACRO10": KEY_MACRO10, + "KEY_MACRO11": KEY_MACRO11, + "KEY_MACRO12": KEY_MACRO12, + "KEY_MACRO13": KEY_MACRO13, + "KEY_MACRO14": KEY_MACRO14, + "KEY_MACRO15": KEY_MACRO15, + "KEY_MACRO16": KEY_MACRO16, + "KEY_MACRO17": KEY_MACRO17, + "KEY_MACRO18": KEY_MACRO18, + "KEY_MACRO19": KEY_MACRO19, + "KEY_MACRO20": KEY_MACRO20, + "KEY_MACRO21": KEY_MACRO21, + "KEY_MACRO22": KEY_MACRO22, + "KEY_MACRO23": KEY_MACRO23, + "KEY_MACRO24": KEY_MACRO24, + "KEY_MACRO25": KEY_MACRO25, + "KEY_MACRO26": KEY_MACRO26, + "KEY_MACRO27": KEY_MACRO27, + "KEY_MACRO28": KEY_MACRO28, + "KEY_MACRO29": KEY_MACRO29, + "KEY_MACRO30": KEY_MACRO30, + + "KEY_MACRO_RECORD_START": KEY_MACRO_RECORD_START, + "KEY_MACRO_RECORD_STOP": KEY_MACRO_RECORD_STOP, + "KEY_MACRO_PRESET_CYCLE": KEY_MACRO_PRESET_CYCLE, + "KEY_MACRO_PRESET1": KEY_MACRO_PRESET1, + "KEY_MACRO_PRESET2": KEY_MACRO_PRESET2, + "KEY_MACRO_PRESET3": KEY_MACRO_PRESET3, + + "KEY_KBD_LCD_MENU1": KEY_KBD_LCD_MENU1, + "KEY_KBD_LCD_MENU2": KEY_KBD_LCD_MENU2, + "KEY_KBD_LCD_MENU3": KEY_KBD_LCD_MENU3, + "KEY_KBD_LCD_MENU4": KEY_KBD_LCD_MENU4, + "KEY_KBD_LCD_MENU5": KEY_KBD_LCD_MENU5, + + "BTN_TRIGGER_HAPPY": BTN_TRIGGER_HAPPY, + "BTN_TRIGGER_HAPPY1": BTN_TRIGGER_HAPPY1, + "BTN_TRIGGER_HAPPY2": BTN_TRIGGER_HAPPY2, + "BTN_TRIGGER_HAPPY3": BTN_TRIGGER_HAPPY3, + "BTN_TRIGGER_HAPPY4": BTN_TRIGGER_HAPPY4, + "BTN_TRIGGER_HAPPY5": BTN_TRIGGER_HAPPY5, + "BTN_TRIGGER_HAPPY6": BTN_TRIGGER_HAPPY6, + "BTN_TRIGGER_HAPPY7": BTN_TRIGGER_HAPPY7, + "BTN_TRIGGER_HAPPY8": BTN_TRIGGER_HAPPY8, + "BTN_TRIGGER_HAPPY9": BTN_TRIGGER_HAPPY9, + "BTN_TRIGGER_HAPPY10": BTN_TRIGGER_HAPPY10, + "BTN_TRIGGER_HAPPY11": BTN_TRIGGER_HAPPY11, + "BTN_TRIGGER_HAPPY12": BTN_TRIGGER_HAPPY12, + "BTN_TRIGGER_HAPPY13": BTN_TRIGGER_HAPPY13, + "BTN_TRIGGER_HAPPY14": BTN_TRIGGER_HAPPY14, + "BTN_TRIGGER_HAPPY15": BTN_TRIGGER_HAPPY15, + "BTN_TRIGGER_HAPPY16": BTN_TRIGGER_HAPPY16, + "BTN_TRIGGER_HAPPY17": BTN_TRIGGER_HAPPY17, + "BTN_TRIGGER_HAPPY18": BTN_TRIGGER_HAPPY18, + "BTN_TRIGGER_HAPPY19": BTN_TRIGGER_HAPPY19, + "BTN_TRIGGER_HAPPY20": BTN_TRIGGER_HAPPY20, + "BTN_TRIGGER_HAPPY21": BTN_TRIGGER_HAPPY21, + "BTN_TRIGGER_HAPPY22": BTN_TRIGGER_HAPPY22, + "BTN_TRIGGER_HAPPY23": BTN_TRIGGER_HAPPY23, + "BTN_TRIGGER_HAPPY24": BTN_TRIGGER_HAPPY24, + "BTN_TRIGGER_HAPPY25": BTN_TRIGGER_HAPPY25, + "BTN_TRIGGER_HAPPY26": BTN_TRIGGER_HAPPY26, + "BTN_TRIGGER_HAPPY27": BTN_TRIGGER_HAPPY27, + "BTN_TRIGGER_HAPPY28": BTN_TRIGGER_HAPPY28, + "BTN_TRIGGER_HAPPY29": BTN_TRIGGER_HAPPY29, + "BTN_TRIGGER_HAPPY30": BTN_TRIGGER_HAPPY30, + "BTN_TRIGGER_HAPPY31": BTN_TRIGGER_HAPPY31, + "BTN_TRIGGER_HAPPY32": BTN_TRIGGER_HAPPY32, + "BTN_TRIGGER_HAPPY33": BTN_TRIGGER_HAPPY33, + "BTN_TRIGGER_HAPPY34": BTN_TRIGGER_HAPPY34, + "BTN_TRIGGER_HAPPY35": BTN_TRIGGER_HAPPY35, + "BTN_TRIGGER_HAPPY36": BTN_TRIGGER_HAPPY36, + "BTN_TRIGGER_HAPPY37": BTN_TRIGGER_HAPPY37, + "BTN_TRIGGER_HAPPY38": BTN_TRIGGER_HAPPY38, + "BTN_TRIGGER_HAPPY39": BTN_TRIGGER_HAPPY39, + "BTN_TRIGGER_HAPPY40": BTN_TRIGGER_HAPPY40, + + "KEY_MIN_INTERESTING": KEY_MIN_INTERESTING, + "KEY_MAX": KEY_MAX, + "KEY_CNT": KEY_CNT, +} + +var RELFromString = map[string]EvCode{ + "REL_X": REL_X, + "REL_Y": REL_Y, + "REL_Z": REL_Z, + "REL_RX": REL_RX, + "REL_RY": REL_RY, + "REL_RZ": REL_RZ, + "REL_HWHEEL": REL_HWHEEL, + "REL_DIAL": REL_DIAL, + "REL_WHEEL": REL_WHEEL, + "REL_MISC": REL_MISC, + + "REL_RESERVED": REL_RESERVED, + "REL_WHEEL_HI_RES": REL_WHEEL_HI_RES, + "REL_HWHEEL_HI_RES": REL_HWHEEL_HI_RES, + "REL_MAX": REL_MAX, + "REL_CNT": REL_CNT, +} + +var ABSFromString = map[string]EvCode{ + "ABS_X": ABS_X, + "ABS_Y": ABS_Y, + "ABS_Z": ABS_Z, + "ABS_RX": ABS_RX, + "ABS_RY": ABS_RY, + "ABS_RZ": ABS_RZ, + "ABS_THROTTLE": ABS_THROTTLE, + "ABS_RUDDER": ABS_RUDDER, + "ABS_WHEEL": ABS_WHEEL, + "ABS_GAS": ABS_GAS, + "ABS_BRAKE": ABS_BRAKE, + "ABS_HAT0X": ABS_HAT0X, + "ABS_HAT0Y": ABS_HAT0Y, + "ABS_HAT1X": ABS_HAT1X, + "ABS_HAT1Y": ABS_HAT1Y, + "ABS_HAT2X": ABS_HAT2X, + "ABS_HAT2Y": ABS_HAT2Y, + "ABS_HAT3X": ABS_HAT3X, + "ABS_HAT3Y": ABS_HAT3Y, + "ABS_PRESSURE": ABS_PRESSURE, + "ABS_DISTANCE": ABS_DISTANCE, + "ABS_TILT_X": ABS_TILT_X, + "ABS_TILT_Y": ABS_TILT_Y, + "ABS_TOOL_WIDTH": ABS_TOOL_WIDTH, + + "ABS_VOLUME": ABS_VOLUME, + + "ABS_MISC": ABS_MISC, + + "ABS_RESERVED": ABS_RESERVED, + + "ABS_MT_SLOT": ABS_MT_SLOT, + "ABS_MT_TOUCH_MAJOR": ABS_MT_TOUCH_MAJOR, + "ABS_MT_TOUCH_MINOR": ABS_MT_TOUCH_MINOR, + "ABS_MT_WIDTH_MAJOR": ABS_MT_WIDTH_MAJOR, + "ABS_MT_WIDTH_MINOR": ABS_MT_WIDTH_MINOR, + "ABS_MT_ORIENTATION": ABS_MT_ORIENTATION, + "ABS_MT_POSITION_X": ABS_MT_POSITION_X, + "ABS_MT_POSITION_Y": ABS_MT_POSITION_Y, + "ABS_MT_TOOL_TYPE": ABS_MT_TOOL_TYPE, + "ABS_MT_BLOB_ID": ABS_MT_BLOB_ID, + "ABS_MT_TRACKING_ID": ABS_MT_TRACKING_ID, + "ABS_MT_PRESSURE": ABS_MT_PRESSURE, + "ABS_MT_DISTANCE": ABS_MT_DISTANCE, + "ABS_MT_TOOL_X": ABS_MT_TOOL_X, + "ABS_MT_TOOL_Y": ABS_MT_TOOL_Y, + + "ABS_MAX": ABS_MAX, + "ABS_CNT": ABS_CNT, +} + +var SWFromString = map[string]EvCode{ + "SW_LID": SW_LID, + "SW_TABLET_MODE": SW_TABLET_MODE, + "SW_HEADPHONE_INSERT": SW_HEADPHONE_INSERT, + "SW_RFKILL_ALL": SW_RFKILL_ALL, + "SW_RADIO": SW_RADIO, + "SW_MICROPHONE_INSERT": SW_MICROPHONE_INSERT, + "SW_DOCK": SW_DOCK, + "SW_LINEOUT_INSERT": SW_LINEOUT_INSERT, + "SW_JACK_PHYSICAL_INSERT": SW_JACK_PHYSICAL_INSERT, + "SW_VIDEOOUT_INSERT": SW_VIDEOOUT_INSERT, + "SW_CAMERA_LENS_COVER": SW_CAMERA_LENS_COVER, + "SW_KEYPAD_SLIDE": SW_KEYPAD_SLIDE, + "SW_FRONT_PROXIMITY": SW_FRONT_PROXIMITY, + "SW_ROTATE_LOCK": SW_ROTATE_LOCK, + "SW_LINEIN_INSERT": SW_LINEIN_INSERT, + "SW_MUTE_DEVICE": SW_MUTE_DEVICE, + "SW_PEN_INSERTED": SW_PEN_INSERTED, + "SW_MACHINE_COVER": SW_MACHINE_COVER, + "SW_MAX": SW_MAX, + "SW_CNT": SW_CNT, +} + +var MSCFromString = map[string]EvCode{ + "MSC_SERIAL": MSC_SERIAL, + "MSC_PULSELED": MSC_PULSELED, + "MSC_GESTURE": MSC_GESTURE, + "MSC_RAW": MSC_RAW, + "MSC_SCAN": MSC_SCAN, + "MSC_TIMESTAMP": MSC_TIMESTAMP, + "MSC_MAX": MSC_MAX, + "MSC_CNT": MSC_CNT, +} + +var LEDFromString = map[string]EvCode{ + "LED_NUML": LED_NUML, + "LED_CAPSL": LED_CAPSL, + "LED_SCROLLL": LED_SCROLLL, + "LED_COMPOSE": LED_COMPOSE, + "LED_KANA": LED_KANA, + "LED_SLEEP": LED_SLEEP, + "LED_SUSPEND": LED_SUSPEND, + "LED_MUTE": LED_MUTE, + "LED_MISC": LED_MISC, + "LED_MAIL": LED_MAIL, + "LED_CHARGING": LED_CHARGING, + "LED_MAX": LED_MAX, + "LED_CNT": LED_CNT, +} + +var REPFromString = map[string]EvCode{ + "REP_DELAY": REP_DELAY, + "REP_PERIOD": REP_PERIOD, + "REP_MAX": REP_MAX, + "REP_CNT": REP_CNT, +} + +var SNDFromString = map[string]EvCode{ + "SND_CLICK": SND_CLICK, + "SND_BELL": SND_BELL, + "SND_TONE": SND_TONE, + "SND_MAX": SND_MAX, + "SND_CNT": SND_CNT, +} + +var IDFromString = map[string]EvCode{ + "ID_BUS": ID_BUS, + "ID_VENDOR": ID_VENDOR, + "ID_PRODUCT": ID_PRODUCT, + "ID_VERSION": ID_VERSION, +} + +var BUSFromString = map[string]EvCode{ + "BUS_PCI": BUS_PCI, + "BUS_ISAPNP": BUS_ISAPNP, + "BUS_USB": BUS_USB, + "BUS_HIL": BUS_HIL, + "BUS_BLUETOOTH": BUS_BLUETOOTH, + "BUS_VIRTUAL": BUS_VIRTUAL, + + "BUS_ISA": BUS_ISA, + "BUS_I8042": BUS_I8042, + "BUS_XTKBD": BUS_XTKBD, + "BUS_RS232": BUS_RS232, + "BUS_GAMEPORT": BUS_GAMEPORT, + "BUS_PARPORT": BUS_PARPORT, + "BUS_AMIGA": BUS_AMIGA, + "BUS_ADB": BUS_ADB, + "BUS_I2C": BUS_I2C, + "BUS_HOST": BUS_HOST, + "BUS_GSC": BUS_GSC, + "BUS_ATARI": BUS_ATARI, + "BUS_SPI": BUS_SPI, + "BUS_RMI": BUS_RMI, + "BUS_CEC": BUS_CEC, + "BUS_INTEL_ISHTP": BUS_INTEL_ISHTP, +} + +var MTFromString = map[string]EvCode{ + "MT_TOOL_FINGER": MT_TOOL_FINGER, + "MT_TOOL_PEN": MT_TOOL_PEN, + "MT_TOOL_PALM": MT_TOOL_PALM, + "MT_TOOL_DIAL": MT_TOOL_DIAL, + "MT_TOOL_MAX": MT_TOOL_MAX, +} + +var FFFromString = map[string]EvCode{ + "FF_STATUS_STOPPED": FF_STATUS_STOPPED, + "FF_STATUS_PLAYING": FF_STATUS_PLAYING, + "FF_STATUS_MAX": FF_STATUS_MAX, + + "FF_RUMBLE": FF_RUMBLE, + "FF_PERIODIC": FF_PERIODIC, + "FF_CONSTANT": FF_CONSTANT, + "FF_SPRING": FF_SPRING, + "FF_FRICTION": FF_FRICTION, + "FF_DAMPER": FF_DAMPER, + "FF_INERTIA": FF_INERTIA, + "FF_RAMP": FF_RAMP, + + "FF_EFFECT_MIN": FF_EFFECT_MIN, + "FF_EFFECT_MAX": FF_EFFECT_MAX, + + "FF_SQUARE": FF_SQUARE, + "FF_TRIANGLE": FF_TRIANGLE, + "FF_SINE": FF_SINE, + "FF_SAW_UP": FF_SAW_UP, + "FF_SAW_DOWN": FF_SAW_DOWN, + "FF_CUSTOM": FF_CUSTOM, + + "FF_WAVEFORM_MIN": FF_WAVEFORM_MIN, + "FF_WAVEFORM_MAX": FF_WAVEFORM_MAX, + + "FF_GAIN": FF_GAIN, + "FF_AUTOCENTER": FF_AUTOCENTER, + + "FF_MAX_EFFECTS": FF_MAX_EFFECTS, + + "FF_MAX": FF_MAX, + "FF_CNT": FF_CNT, +} + +// +// Type Names (informative debug use only) +// + +var INPUTNames = map[EvProp]string{ + INPUT_PROP_POINTER: "INPUT_PROP_POINTER", + INPUT_PROP_DIRECT: "INPUT_PROP_DIRECT", + INPUT_PROP_BUTTONPAD: "INPUT_PROP_BUTTONPAD", + INPUT_PROP_SEMI_MT: "INPUT_PROP_SEMI_MT", + INPUT_PROP_TOPBUTTONPAD: "INPUT_PROP_TOPBUTTONPAD", + INPUT_PROP_POINTING_STICK: "INPUT_PROP_POINTING_STICK", + INPUT_PROP_ACCELEROMETER: "INPUT_PROP_ACCELEROMETER", + + INPUT_PROP_MAX: "INPUT_PROP_MAX", + INPUT_PROP_CNT: "INPUT_PROP_CNT", +} + +var EVNames = map[EvType]string{ + EV_SYN: "EV_SYN", + EV_KEY: "EV_KEY", + EV_REL: "EV_REL", + EV_ABS: "EV_ABS", + EV_MSC: "EV_MSC", + EV_SW: "EV_SW", + EV_LED: "EV_LED", + EV_SND: "EV_SND", + EV_REP: "EV_REP", + EV_FF: "EV_FF", + EV_PWR: "EV_PWR", + EV_FF_STATUS: "EV_FF_STATUS", + EV_MAX: "EV_MAX", + EV_CNT: "EV_CNT", +} + +var SYNNames = map[EvCode]string{ + SYN_REPORT: "SYN_REPORT", + SYN_CONFIG: "SYN_CONFIG", + SYN_MT_REPORT: "SYN_MT_REPORT", + SYN_DROPPED: "SYN_DROPPED", + SYN_MAX: "SYN_MAX", + SYN_CNT: "SYN_CNT", +} + +var KEYNames = map[EvCode]string{ + KEY_RESERVED: "KEY_RESERVED", + KEY_ESC: "KEY_ESC", + KEY_1: "KEY_1", + KEY_2: "KEY_2", + KEY_3: "KEY_3", + KEY_4: "KEY_4", + KEY_5: "KEY_5", + KEY_6: "KEY_6", + KEY_7: "KEY_7", + KEY_8: "KEY_8", + KEY_9: "KEY_9", + KEY_0: "KEY_0", + KEY_MINUS: "KEY_MINUS", + KEY_EQUAL: "KEY_EQUAL", + KEY_BACKSPACE: "KEY_BACKSPACE", + KEY_TAB: "KEY_TAB", + KEY_Q: "KEY_Q", + KEY_W: "KEY_W", + KEY_E: "KEY_E", + KEY_R: "KEY_R", + KEY_T: "KEY_T", + KEY_Y: "KEY_Y", + KEY_U: "KEY_U", + KEY_I: "KEY_I", + KEY_O: "KEY_O", + KEY_P: "KEY_P", + KEY_LEFTBRACE: "KEY_LEFTBRACE", + KEY_RIGHTBRACE: "KEY_RIGHTBRACE", + KEY_ENTER: "KEY_ENTER", + KEY_LEFTCTRL: "KEY_LEFTCTRL", + KEY_A: "KEY_A", + KEY_S: "KEY_S", + KEY_D: "KEY_D", + KEY_F: "KEY_F", + KEY_G: "KEY_G", + KEY_H: "KEY_H", + KEY_J: "KEY_J", + KEY_K: "KEY_K", + KEY_L: "KEY_L", + KEY_SEMICOLON: "KEY_SEMICOLON", + KEY_APOSTROPHE: "KEY_APOSTROPHE", + KEY_GRAVE: "KEY_GRAVE", + KEY_LEFTSHIFT: "KEY_LEFTSHIFT", + KEY_BACKSLASH: "KEY_BACKSLASH", + KEY_Z: "KEY_Z", + KEY_X: "KEY_X", + KEY_C: "KEY_C", + KEY_V: "KEY_V", + KEY_B: "KEY_B", + KEY_N: "KEY_N", + KEY_M: "KEY_M", + KEY_COMMA: "KEY_COMMA", + KEY_DOT: "KEY_DOT", + KEY_SLASH: "KEY_SLASH", + KEY_RIGHTSHIFT: "KEY_RIGHTSHIFT", + KEY_KPASTERISK: "KEY_KPASTERISK", + KEY_LEFTALT: "KEY_LEFTALT", + KEY_SPACE: "KEY_SPACE", + KEY_CAPSLOCK: "KEY_CAPSLOCK", + KEY_F1: "KEY_F1", + KEY_F2: "KEY_F2", + KEY_F3: "KEY_F3", + KEY_F4: "KEY_F4", + KEY_F5: "KEY_F5", + KEY_F6: "KEY_F6", + KEY_F7: "KEY_F7", + KEY_F8: "KEY_F8", + KEY_F9: "KEY_F9", + KEY_F10: "KEY_F10", + KEY_NUMLOCK: "KEY_NUMLOCK", + KEY_SCROLLLOCK: "KEY_SCROLLLOCK", + KEY_KP7: "KEY_KP7", + KEY_KP8: "KEY_KP8", + KEY_KP9: "KEY_KP9", + KEY_KPMINUS: "KEY_KPMINUS", + KEY_KP4: "KEY_KP4", + KEY_KP5: "KEY_KP5", + KEY_KP6: "KEY_KP6", + KEY_KPPLUS: "KEY_KPPLUS", + KEY_KP1: "KEY_KP1", + KEY_KP2: "KEY_KP2", + KEY_KP3: "KEY_KP3", + KEY_KP0: "KEY_KP0", + KEY_KPDOT: "KEY_KPDOT", + + KEY_ZENKAKUHANKAKU: "KEY_ZENKAKUHANKAKU", + KEY_102ND: "KEY_102ND", + KEY_F11: "KEY_F11", + KEY_F12: "KEY_F12", + KEY_RO: "KEY_RO", + KEY_KATAKANA: "KEY_KATAKANA", + KEY_HIRAGANA: "KEY_HIRAGANA", + KEY_HENKAN: "KEY_HENKAN", + KEY_KATAKANAHIRAGANA: "KEY_KATAKANAHIRAGANA", + KEY_MUHENKAN: "KEY_MUHENKAN", + KEY_KPJPCOMMA: "KEY_KPJPCOMMA", + KEY_KPENTER: "KEY_KPENTER", + KEY_RIGHTCTRL: "KEY_RIGHTCTRL", + KEY_KPSLASH: "KEY_KPSLASH", + KEY_SYSRQ: "KEY_SYSRQ", + KEY_RIGHTALT: "KEY_RIGHTALT", + KEY_LINEFEED: "KEY_LINEFEED", + KEY_HOME: "KEY_HOME", + KEY_UP: "KEY_UP", + KEY_PAGEUP: "KEY_PAGEUP", + KEY_LEFT: "KEY_LEFT", + KEY_RIGHT: "KEY_RIGHT", + KEY_END: "KEY_END", + KEY_DOWN: "KEY_DOWN", + KEY_PAGEDOWN: "KEY_PAGEDOWN", + KEY_INSERT: "KEY_INSERT", + KEY_DELETE: "KEY_DELETE", + KEY_MACRO: "KEY_MACRO", + KEY_MUTE: "KEY_MUTE/KEY_MIN_INTERESTING", + KEY_VOLUMEDOWN: "KEY_VOLUMEDOWN", + KEY_VOLUMEUP: "KEY_VOLUMEUP", + KEY_POWER: "KEY_POWER", + KEY_KPEQUAL: "KEY_KPEQUAL", + KEY_KPPLUSMINUS: "KEY_KPPLUSMINUS", + KEY_PAUSE: "KEY_PAUSE", + KEY_SCALE: "KEY_SCALE", + + KEY_KPCOMMA: "KEY_KPCOMMA", + KEY_HANGEUL: "KEY_HANGEUL/KEY_HANGUEL", + KEY_HANJA: "KEY_HANJA", + KEY_YEN: "KEY_YEN", + KEY_LEFTMETA: "KEY_LEFTMETA", + KEY_RIGHTMETA: "KEY_RIGHTMETA", + KEY_COMPOSE: "KEY_COMPOSE", + + KEY_STOP: "KEY_STOP", + KEY_AGAIN: "KEY_AGAIN", + KEY_PROPS: "KEY_PROPS", + KEY_UNDO: "KEY_UNDO", + KEY_FRONT: "KEY_FRONT", + KEY_COPY: "KEY_COPY", + KEY_OPEN: "KEY_OPEN", + KEY_PASTE: "KEY_PASTE", + KEY_FIND: "KEY_FIND", + KEY_CUT: "KEY_CUT", + KEY_HELP: "KEY_HELP", + KEY_MENU: "KEY_MENU", + KEY_CALC: "KEY_CALC", + KEY_SETUP: "KEY_SETUP", + KEY_SLEEP: "KEY_SLEEP", + KEY_WAKEUP: "KEY_WAKEUP", + KEY_FILE: "KEY_FILE", + KEY_SENDFILE: "KEY_SENDFILE", + KEY_DELETEFILE: "KEY_DELETEFILE", + KEY_XFER: "KEY_XFER", + KEY_PROG1: "KEY_PROG1", + KEY_PROG2: "KEY_PROG2", + KEY_WWW: "KEY_WWW", + KEY_MSDOS: "KEY_MSDOS", + KEY_COFFEE: "KEY_COFFEE/KEY_SCREENLOCK", + KEY_ROTATE_DISPLAY: "KEY_ROTATE_DISPLAY/KEY_DIRECTION", + KEY_CYCLEWINDOWS: "KEY_CYCLEWINDOWS", + KEY_MAIL: "KEY_MAIL", + KEY_BOOKMARKS: "KEY_BOOKMARKS", + KEY_COMPUTER: "KEY_COMPUTER", + KEY_BACK: "KEY_BACK", + KEY_FORWARD: "KEY_FORWARD", + KEY_CLOSECD: "KEY_CLOSECD", + KEY_EJECTCD: "KEY_EJECTCD", + KEY_EJECTCLOSECD: "KEY_EJECTCLOSECD", + KEY_NEXTSONG: "KEY_NEXTSONG", + KEY_PLAYPAUSE: "KEY_PLAYPAUSE", + KEY_PREVIOUSSONG: "KEY_PREVIOUSSONG", + KEY_STOPCD: "KEY_STOPCD", + KEY_RECORD: "KEY_RECORD", + KEY_REWIND: "KEY_REWIND", + KEY_PHONE: "KEY_PHONE", + KEY_ISO: "KEY_ISO", + KEY_CONFIG: "KEY_CONFIG", + KEY_HOMEPAGE: "KEY_HOMEPAGE", + KEY_REFRESH: "KEY_REFRESH", + KEY_EXIT: "KEY_EXIT", + KEY_MOVE: "KEY_MOVE", + KEY_EDIT: "KEY_EDIT", + KEY_SCROLLUP: "KEY_SCROLLUP", + KEY_SCROLLDOWN: "KEY_SCROLLDOWN", + KEY_KPLEFTPAREN: "KEY_KPLEFTPAREN", + KEY_KPRIGHTPAREN: "KEY_KPRIGHTPAREN", + KEY_NEW: "KEY_NEW", + KEY_REDO: "KEY_REDO", + + KEY_F13: "KEY_F13", + KEY_F14: "KEY_F14", + KEY_F15: "KEY_F15", + KEY_F16: "KEY_F16", + KEY_F17: "KEY_F17", + KEY_F18: "KEY_F18", + KEY_F19: "KEY_F19", + KEY_F20: "KEY_F20", + KEY_F21: "KEY_F21", + KEY_F22: "KEY_F22", + KEY_F23: "KEY_F23", + KEY_F24: "KEY_F24", + + KEY_PLAYCD: "KEY_PLAYCD", + KEY_PAUSECD: "KEY_PAUSECD", + KEY_PROG3: "KEY_PROG3", + KEY_PROG4: "KEY_PROG4", + KEY_ALL_APPLICATIONS: "KEY_ALL_APPLICATIONS/KEY_DASHBOARD", + KEY_SUSPEND: "KEY_SUSPEND", + KEY_CLOSE: "KEY_CLOSE", + KEY_PLAY: "KEY_PLAY", + KEY_FASTFORWARD: "KEY_FASTFORWARD", + KEY_BASSBOOST: "KEY_BASSBOOST", + KEY_PRINT: "KEY_PRINT", + KEY_HP: "KEY_HP", + KEY_CAMERA: "KEY_CAMERA", + KEY_SOUND: "KEY_SOUND", + KEY_QUESTION: "KEY_QUESTION", + KEY_EMAIL: "KEY_EMAIL", + KEY_CHAT: "KEY_CHAT", + KEY_SEARCH: "KEY_SEARCH", + KEY_CONNECT: "KEY_CONNECT", + KEY_FINANCE: "KEY_FINANCE", + KEY_SPORT: "KEY_SPORT", + KEY_SHOP: "KEY_SHOP", + KEY_ALTERASE: "KEY_ALTERASE", + KEY_CANCEL: "KEY_CANCEL", + KEY_BRIGHTNESSDOWN: "KEY_BRIGHTNESSDOWN", + KEY_BRIGHTNESSUP: "KEY_BRIGHTNESSUP", + KEY_MEDIA: "KEY_MEDIA", + + KEY_SWITCHVIDEOMODE: "KEY_SWITCHVIDEOMODE", + KEY_KBDILLUMTOGGLE: "KEY_KBDILLUMTOGGLE", + KEY_KBDILLUMDOWN: "KEY_KBDILLUMDOWN", + KEY_KBDILLUMUP: "KEY_KBDILLUMUP", + + KEY_SEND: "KEY_SEND", + KEY_REPLY: "KEY_REPLY", + KEY_FORWARDMAIL: "KEY_FORWARDMAIL", + KEY_SAVE: "KEY_SAVE", + KEY_DOCUMENTS: "KEY_DOCUMENTS", + + KEY_BATTERY: "KEY_BATTERY", + + KEY_BLUETOOTH: "KEY_BLUETOOTH", + KEY_WLAN: "KEY_WLAN", + KEY_UWB: "KEY_UWB", + + KEY_UNKNOWN: "KEY_UNKNOWN", + + KEY_VIDEO_NEXT: "KEY_VIDEO_NEXT", + KEY_VIDEO_PREV: "KEY_VIDEO_PREV", + KEY_BRIGHTNESS_CYCLE: "KEY_BRIGHTNESS_CYCLE", + KEY_BRIGHTNESS_AUTO: "KEY_BRIGHTNESS_AUTO/KEY_BRIGHTNESS_ZERO", + KEY_DISPLAY_OFF: "KEY_DISPLAY_OFF", + + KEY_WWAN: "KEY_WWAN/KEY_WIMAX", + KEY_RFKILL: "KEY_RFKILL", + + KEY_MICMUTE: "KEY_MICMUTE", + + BTN_MISC: "BTN_MISC/BTN_0", + BTN_1: "BTN_1", + BTN_2: "BTN_2", + BTN_3: "BTN_3", + BTN_4: "BTN_4", + BTN_5: "BTN_5", + BTN_6: "BTN_6", + BTN_7: "BTN_7", + BTN_8: "BTN_8", + BTN_9: "BTN_9", + + BTN_MOUSE: "BTN_MOUSE/BTN_LEFT", + BTN_RIGHT: "BTN_RIGHT", + BTN_MIDDLE: "BTN_MIDDLE", + BTN_SIDE: "BTN_SIDE", + BTN_EXTRA: "BTN_EXTRA", + BTN_FORWARD: "BTN_FORWARD", + BTN_BACK: "BTN_BACK", + BTN_TASK: "BTN_TASK", + + BTN_JOYSTICK: "BTN_JOYSTICK/BTN_TRIGGER", + BTN_THUMB: "BTN_THUMB", + BTN_THUMB2: "BTN_THUMB2", + BTN_TOP: "BTN_TOP", + BTN_TOP2: "BTN_TOP2", + BTN_PINKIE: "BTN_PINKIE", + BTN_BASE: "BTN_BASE", + BTN_BASE2: "BTN_BASE2", + BTN_BASE3: "BTN_BASE3", + BTN_BASE4: "BTN_BASE4", + BTN_BASE5: "BTN_BASE5", + BTN_BASE6: "BTN_BASE6", + BTN_DEAD: "BTN_DEAD", + + BTN_GAMEPAD: "BTN_GAMEPAD/BTN_SOUTH/BTN_A", + BTN_EAST: "BTN_EAST/BTN_B", + BTN_C: "BTN_C", + BTN_NORTH: "BTN_NORTH/BTN_X", + BTN_WEST: "BTN_WEST/BTN_Y", + BTN_Z: "BTN_Z", + BTN_TL: "BTN_TL", + BTN_TR: "BTN_TR", + BTN_TL2: "BTN_TL2", + BTN_TR2: "BTN_TR2", + BTN_SELECT: "BTN_SELECT", + BTN_START: "BTN_START", + BTN_MODE: "BTN_MODE", + BTN_THUMBL: "BTN_THUMBL", + BTN_THUMBR: "BTN_THUMBR", + + BTN_DIGI: "BTN_DIGI/BTN_TOOL_PEN", + BTN_TOOL_RUBBER: "BTN_TOOL_RUBBER", + BTN_TOOL_BRUSH: "BTN_TOOL_BRUSH", + BTN_TOOL_PENCIL: "BTN_TOOL_PENCIL", + BTN_TOOL_AIRBRUSH: "BTN_TOOL_AIRBRUSH", + BTN_TOOL_FINGER: "BTN_TOOL_FINGER", + BTN_TOOL_MOUSE: "BTN_TOOL_MOUSE", + BTN_TOOL_LENS: "BTN_TOOL_LENS", + BTN_TOOL_QUINTTAP: "BTN_TOOL_QUINTTAP", + BTN_STYLUS3: "BTN_STYLUS3", + BTN_TOUCH: "BTN_TOUCH", + BTN_STYLUS: "BTN_STYLUS", + BTN_STYLUS2: "BTN_STYLUS2", + BTN_TOOL_DOUBLETAP: "BTN_TOOL_DOUBLETAP", + BTN_TOOL_TRIPLETAP: "BTN_TOOL_TRIPLETAP", + BTN_TOOL_QUADTAP: "BTN_TOOL_QUADTAP", + + BTN_WHEEL: "BTN_WHEEL/BTN_GEAR_DOWN", + BTN_GEAR_UP: "BTN_GEAR_UP", + + KEY_OK: "KEY_OK", + KEY_SELECT: "KEY_SELECT", + KEY_GOTO: "KEY_GOTO", + KEY_CLEAR: "KEY_CLEAR", + KEY_POWER2: "KEY_POWER2", + KEY_OPTION: "KEY_OPTION", + KEY_INFO: "KEY_INFO", + KEY_TIME: "KEY_TIME", + KEY_VENDOR: "KEY_VENDOR", + KEY_ARCHIVE: "KEY_ARCHIVE", + KEY_PROGRAM: "KEY_PROGRAM", + KEY_CHANNEL: "KEY_CHANNEL", + KEY_FAVORITES: "KEY_FAVORITES", + KEY_EPG: "KEY_EPG", + KEY_PVR: "KEY_PVR", + KEY_MHP: "KEY_MHP", + KEY_LANGUAGE: "KEY_LANGUAGE", + KEY_TITLE: "KEY_TITLE", + KEY_SUBTITLE: "KEY_SUBTITLE", + KEY_ANGLE: "KEY_ANGLE", + KEY_FULL_SCREEN: "KEY_FULL_SCREEN/KEY_ZOOM", + KEY_MODE: "KEY_MODE", + KEY_KEYBOARD: "KEY_KEYBOARD", + KEY_ASPECT_RATIO: "KEY_ASPECT_RATIO/KEY_SCREEN", + KEY_PC: "KEY_PC", + KEY_TV: "KEY_TV", + KEY_TV2: "KEY_TV2", + KEY_VCR: "KEY_VCR", + KEY_VCR2: "KEY_VCR2", + KEY_SAT: "KEY_SAT", + KEY_SAT2: "KEY_SAT2", + KEY_CD: "KEY_CD", + KEY_TAPE: "KEY_TAPE", + KEY_RADIO: "KEY_RADIO", + KEY_TUNER: "KEY_TUNER", + KEY_PLAYER: "KEY_PLAYER", + KEY_TEXT: "KEY_TEXT", + KEY_DVD: "KEY_DVD", + KEY_AUX: "KEY_AUX", + KEY_MP3: "KEY_MP3", + KEY_AUDIO: "KEY_AUDIO", + KEY_VIDEO: "KEY_VIDEO", + KEY_DIRECTORY: "KEY_DIRECTORY", + KEY_LIST: "KEY_LIST", + KEY_MEMO: "KEY_MEMO", + KEY_CALENDAR: "KEY_CALENDAR", + KEY_RED: "KEY_RED", + KEY_GREEN: "KEY_GREEN", + KEY_YELLOW: "KEY_YELLOW", + KEY_BLUE: "KEY_BLUE", + KEY_CHANNELUP: "KEY_CHANNELUP", + KEY_CHANNELDOWN: "KEY_CHANNELDOWN", + KEY_FIRST: "KEY_FIRST", + KEY_LAST: "KEY_LAST", + KEY_AB: "KEY_AB", + KEY_NEXT: "KEY_NEXT", + KEY_RESTART: "KEY_RESTART", + KEY_SLOW: "KEY_SLOW", + KEY_SHUFFLE: "KEY_SHUFFLE", + KEY_BREAK: "KEY_BREAK", + KEY_PREVIOUS: "KEY_PREVIOUS", + KEY_DIGITS: "KEY_DIGITS", + KEY_TEEN: "KEY_TEEN", + KEY_TWEN: "KEY_TWEN", + KEY_VIDEOPHONE: "KEY_VIDEOPHONE", + KEY_GAMES: "KEY_GAMES", + KEY_ZOOMIN: "KEY_ZOOMIN", + KEY_ZOOMOUT: "KEY_ZOOMOUT", + KEY_ZOOMRESET: "KEY_ZOOMRESET", + KEY_WORDPROCESSOR: "KEY_WORDPROCESSOR", + KEY_EDITOR: "KEY_EDITOR", + KEY_SPREADSHEET: "KEY_SPREADSHEET", + KEY_GRAPHICSEDITOR: "KEY_GRAPHICSEDITOR", + KEY_PRESENTATION: "KEY_PRESENTATION", + KEY_DATABASE: "KEY_DATABASE", + KEY_NEWS: "KEY_NEWS", + KEY_VOICEMAIL: "KEY_VOICEMAIL", + KEY_ADDRESSBOOK: "KEY_ADDRESSBOOK", + KEY_MESSENGER: "KEY_MESSENGER", + KEY_DISPLAYTOGGLE: "KEY_DISPLAYTOGGLE/KEY_BRIGHTNESS_TOGGLE", + KEY_SPELLCHECK: "KEY_SPELLCHECK", + KEY_LOGOFF: "KEY_LOGOFF", + + KEY_DOLLAR: "KEY_DOLLAR", + KEY_EURO: "KEY_EURO", + + KEY_FRAMEBACK: "KEY_FRAMEBACK", + KEY_FRAMEFORWARD: "KEY_FRAMEFORWARD", + KEY_CONTEXT_MENU: "KEY_CONTEXT_MENU", + KEY_MEDIA_REPEAT: "KEY_MEDIA_REPEAT", + KEY_10CHANNELSUP: "KEY_10CHANNELSUP", + KEY_10CHANNELSDOWN: "KEY_10CHANNELSDOWN", + KEY_IMAGES: "KEY_IMAGES", + KEY_NOTIFICATION_CENTER: "KEY_NOTIFICATION_CENTER", + KEY_PICKUP_PHONE: "KEY_PICKUP_PHONE", + KEY_HANGUP_PHONE: "KEY_HANGUP_PHONE", + + KEY_DEL_EOL: "KEY_DEL_EOL", + KEY_DEL_EOS: "KEY_DEL_EOS", + KEY_INS_LINE: "KEY_INS_LINE", + KEY_DEL_LINE: "KEY_DEL_LINE", + + KEY_FN: "KEY_FN", + KEY_FN_ESC: "KEY_FN_ESC", + KEY_FN_F1: "KEY_FN_F1", + KEY_FN_F2: "KEY_FN_F2", + KEY_FN_F3: "KEY_FN_F3", + KEY_FN_F4: "KEY_FN_F4", + KEY_FN_F5: "KEY_FN_F5", + KEY_FN_F6: "KEY_FN_F6", + KEY_FN_F7: "KEY_FN_F7", + KEY_FN_F8: "KEY_FN_F8", + KEY_FN_F9: "KEY_FN_F9", + KEY_FN_F10: "KEY_FN_F10", + KEY_FN_F11: "KEY_FN_F11", + KEY_FN_F12: "KEY_FN_F12", + KEY_FN_1: "KEY_FN_1", + KEY_FN_2: "KEY_FN_2", + KEY_FN_D: "KEY_FN_D", + KEY_FN_E: "KEY_FN_E", + KEY_FN_F: "KEY_FN_F", + KEY_FN_S: "KEY_FN_S", + KEY_FN_B: "KEY_FN_B", + KEY_FN_RIGHT_SHIFT: "KEY_FN_RIGHT_SHIFT", + + KEY_BRL_DOT1: "KEY_BRL_DOT1", + KEY_BRL_DOT2: "KEY_BRL_DOT2", + KEY_BRL_DOT3: "KEY_BRL_DOT3", + KEY_BRL_DOT4: "KEY_BRL_DOT4", + KEY_BRL_DOT5: "KEY_BRL_DOT5", + KEY_BRL_DOT6: "KEY_BRL_DOT6", + KEY_BRL_DOT7: "KEY_BRL_DOT7", + KEY_BRL_DOT8: "KEY_BRL_DOT8", + KEY_BRL_DOT9: "KEY_BRL_DOT9", + KEY_BRL_DOT10: "KEY_BRL_DOT10", + + KEY_NUMERIC_0: "KEY_NUMERIC_0", + KEY_NUMERIC_1: "KEY_NUMERIC_1", + KEY_NUMERIC_2: "KEY_NUMERIC_2", + KEY_NUMERIC_3: "KEY_NUMERIC_3", + KEY_NUMERIC_4: "KEY_NUMERIC_4", + KEY_NUMERIC_5: "KEY_NUMERIC_5", + KEY_NUMERIC_6: "KEY_NUMERIC_6", + KEY_NUMERIC_7: "KEY_NUMERIC_7", + KEY_NUMERIC_8: "KEY_NUMERIC_8", + KEY_NUMERIC_9: "KEY_NUMERIC_9", + KEY_NUMERIC_STAR: "KEY_NUMERIC_STAR", + KEY_NUMERIC_POUND: "KEY_NUMERIC_POUND", + KEY_NUMERIC_A: "KEY_NUMERIC_A", + KEY_NUMERIC_B: "KEY_NUMERIC_B", + KEY_NUMERIC_C: "KEY_NUMERIC_C", + KEY_NUMERIC_D: "KEY_NUMERIC_D", + + KEY_CAMERA_FOCUS: "KEY_CAMERA_FOCUS", + KEY_WPS_BUTTON: "KEY_WPS_BUTTON", + + KEY_TOUCHPAD_TOGGLE: "KEY_TOUCHPAD_TOGGLE", + KEY_TOUCHPAD_ON: "KEY_TOUCHPAD_ON", + KEY_TOUCHPAD_OFF: "KEY_TOUCHPAD_OFF", + + KEY_CAMERA_ZOOMIN: "KEY_CAMERA_ZOOMIN", + KEY_CAMERA_ZOOMOUT: "KEY_CAMERA_ZOOMOUT", + KEY_CAMERA_UP: "KEY_CAMERA_UP", + KEY_CAMERA_DOWN: "KEY_CAMERA_DOWN", + KEY_CAMERA_LEFT: "KEY_CAMERA_LEFT", + KEY_CAMERA_RIGHT: "KEY_CAMERA_RIGHT", + + KEY_ATTENDANT_ON: "KEY_ATTENDANT_ON", + KEY_ATTENDANT_OFF: "KEY_ATTENDANT_OFF", + KEY_ATTENDANT_TOGGLE: "KEY_ATTENDANT_TOGGLE", + KEY_LIGHTS_TOGGLE: "KEY_LIGHTS_TOGGLE", + + BTN_DPAD_UP: "BTN_DPAD_UP", + BTN_DPAD_DOWN: "BTN_DPAD_DOWN", + BTN_DPAD_LEFT: "BTN_DPAD_LEFT", + BTN_DPAD_RIGHT: "BTN_DPAD_RIGHT", + + KEY_ALS_TOGGLE: "KEY_ALS_TOGGLE", + KEY_ROTATE_LOCK_TOGGLE: "KEY_ROTATE_LOCK_TOGGLE", + + KEY_BUTTONCONFIG: "KEY_BUTTONCONFIG", + KEY_TASKMANAGER: "KEY_TASKMANAGER", + KEY_JOURNAL: "KEY_JOURNAL", + KEY_CONTROLPANEL: "KEY_CONTROLPANEL", + KEY_APPSELECT: "KEY_APPSELECT", + KEY_SCREENSAVER: "KEY_SCREENSAVER", + KEY_VOICECOMMAND: "KEY_VOICECOMMAND", + KEY_ASSISTANT: "KEY_ASSISTANT", + KEY_KBD_LAYOUT_NEXT: "KEY_KBD_LAYOUT_NEXT", + KEY_EMOJI_PICKER: "KEY_EMOJI_PICKER", + KEY_DICTATE: "KEY_DICTATE", + + KEY_BRIGHTNESS_MIN: "KEY_BRIGHTNESS_MIN", + KEY_BRIGHTNESS_MAX: "KEY_BRIGHTNESS_MAX", + + KEY_KBDINPUTASSIST_PREV: "KEY_KBDINPUTASSIST_PREV", + KEY_KBDINPUTASSIST_NEXT: "KEY_KBDINPUTASSIST_NEXT", + KEY_KBDINPUTASSIST_PREVGROUP: "KEY_KBDINPUTASSIST_PREVGROUP", + KEY_KBDINPUTASSIST_NEXTGROUP: "KEY_KBDINPUTASSIST_NEXTGROUP", + KEY_KBDINPUTASSIST_ACCEPT: "KEY_KBDINPUTASSIST_ACCEPT", + KEY_KBDINPUTASSIST_CANCEL: "KEY_KBDINPUTASSIST_CANCEL", + + KEY_RIGHT_UP: "KEY_RIGHT_UP", + KEY_RIGHT_DOWN: "KEY_RIGHT_DOWN", + KEY_LEFT_UP: "KEY_LEFT_UP", + KEY_LEFT_DOWN: "KEY_LEFT_DOWN", + + KEY_ROOT_MENU: "KEY_ROOT_MENU", + + KEY_MEDIA_TOP_MENU: "KEY_MEDIA_TOP_MENU", + KEY_NUMERIC_11: "KEY_NUMERIC_11", + KEY_NUMERIC_12: "KEY_NUMERIC_12", + + KEY_AUDIO_DESC: "KEY_AUDIO_DESC", + KEY_3D_MODE: "KEY_3D_MODE", + KEY_NEXT_FAVORITE: "KEY_NEXT_FAVORITE", + KEY_STOP_RECORD: "KEY_STOP_RECORD", + KEY_PAUSE_RECORD: "KEY_PAUSE_RECORD", + KEY_VOD: "KEY_VOD", + KEY_UNMUTE: "KEY_UNMUTE", + KEY_FASTREVERSE: "KEY_FASTREVERSE", + KEY_SLOWREVERSE: "KEY_SLOWREVERSE", + + KEY_DATA: "KEY_DATA", + KEY_ONSCREEN_KEYBOARD: "KEY_ONSCREEN_KEYBOARD", + + KEY_PRIVACY_SCREEN_TOGGLE: "KEY_PRIVACY_SCREEN_TOGGLE", + + KEY_SELECTIVE_SCREENSHOT: "KEY_SELECTIVE_SCREENSHOT", + + KEY_MACRO1: "KEY_MACRO1", + KEY_MACRO2: "KEY_MACRO2", + KEY_MACRO3: "KEY_MACRO3", + KEY_MACRO4: "KEY_MACRO4", + KEY_MACRO5: "KEY_MACRO5", + KEY_MACRO6: "KEY_MACRO6", + KEY_MACRO7: "KEY_MACRO7", + KEY_MACRO8: "KEY_MACRO8", + KEY_MACRO9: "KEY_MACRO9", + KEY_MACRO10: "KEY_MACRO10", + KEY_MACRO11: "KEY_MACRO11", + KEY_MACRO12: "KEY_MACRO12", + KEY_MACRO13: "KEY_MACRO13", + KEY_MACRO14: "KEY_MACRO14", + KEY_MACRO15: "KEY_MACRO15", + KEY_MACRO16: "KEY_MACRO16", + KEY_MACRO17: "KEY_MACRO17", + KEY_MACRO18: "KEY_MACRO18", + KEY_MACRO19: "KEY_MACRO19", + KEY_MACRO20: "KEY_MACRO20", + KEY_MACRO21: "KEY_MACRO21", + KEY_MACRO22: "KEY_MACRO22", + KEY_MACRO23: "KEY_MACRO23", + KEY_MACRO24: "KEY_MACRO24", + KEY_MACRO25: "KEY_MACRO25", + KEY_MACRO26: "KEY_MACRO26", + KEY_MACRO27: "KEY_MACRO27", + KEY_MACRO28: "KEY_MACRO28", + KEY_MACRO29: "KEY_MACRO29", + KEY_MACRO30: "KEY_MACRO30", + + KEY_MACRO_RECORD_START: "KEY_MACRO_RECORD_START", + KEY_MACRO_RECORD_STOP: "KEY_MACRO_RECORD_STOP", + KEY_MACRO_PRESET_CYCLE: "KEY_MACRO_PRESET_CYCLE", + KEY_MACRO_PRESET1: "KEY_MACRO_PRESET1", + KEY_MACRO_PRESET2: "KEY_MACRO_PRESET2", + KEY_MACRO_PRESET3: "KEY_MACRO_PRESET3", + + KEY_KBD_LCD_MENU1: "KEY_KBD_LCD_MENU1", + KEY_KBD_LCD_MENU2: "KEY_KBD_LCD_MENU2", + KEY_KBD_LCD_MENU3: "KEY_KBD_LCD_MENU3", + KEY_KBD_LCD_MENU4: "KEY_KBD_LCD_MENU4", + KEY_KBD_LCD_MENU5: "KEY_KBD_LCD_MENU5", + + BTN_TRIGGER_HAPPY: "BTN_TRIGGER_HAPPY/BTN_TRIGGER_HAPPY1", + BTN_TRIGGER_HAPPY2: "BTN_TRIGGER_HAPPY2", + BTN_TRIGGER_HAPPY3: "BTN_TRIGGER_HAPPY3", + BTN_TRIGGER_HAPPY4: "BTN_TRIGGER_HAPPY4", + BTN_TRIGGER_HAPPY5: "BTN_TRIGGER_HAPPY5", + BTN_TRIGGER_HAPPY6: "BTN_TRIGGER_HAPPY6", + BTN_TRIGGER_HAPPY7: "BTN_TRIGGER_HAPPY7", + BTN_TRIGGER_HAPPY8: "BTN_TRIGGER_HAPPY8", + BTN_TRIGGER_HAPPY9: "BTN_TRIGGER_HAPPY9", + BTN_TRIGGER_HAPPY10: "BTN_TRIGGER_HAPPY10", + BTN_TRIGGER_HAPPY11: "BTN_TRIGGER_HAPPY11", + BTN_TRIGGER_HAPPY12: "BTN_TRIGGER_HAPPY12", + BTN_TRIGGER_HAPPY13: "BTN_TRIGGER_HAPPY13", + BTN_TRIGGER_HAPPY14: "BTN_TRIGGER_HAPPY14", + BTN_TRIGGER_HAPPY15: "BTN_TRIGGER_HAPPY15", + BTN_TRIGGER_HAPPY16: "BTN_TRIGGER_HAPPY16", + BTN_TRIGGER_HAPPY17: "BTN_TRIGGER_HAPPY17", + BTN_TRIGGER_HAPPY18: "BTN_TRIGGER_HAPPY18", + BTN_TRIGGER_HAPPY19: "BTN_TRIGGER_HAPPY19", + BTN_TRIGGER_HAPPY20: "BTN_TRIGGER_HAPPY20", + BTN_TRIGGER_HAPPY21: "BTN_TRIGGER_HAPPY21", + BTN_TRIGGER_HAPPY22: "BTN_TRIGGER_HAPPY22", + BTN_TRIGGER_HAPPY23: "BTN_TRIGGER_HAPPY23", + BTN_TRIGGER_HAPPY24: "BTN_TRIGGER_HAPPY24", + BTN_TRIGGER_HAPPY25: "BTN_TRIGGER_HAPPY25", + BTN_TRIGGER_HAPPY26: "BTN_TRIGGER_HAPPY26", + BTN_TRIGGER_HAPPY27: "BTN_TRIGGER_HAPPY27", + BTN_TRIGGER_HAPPY28: "BTN_TRIGGER_HAPPY28", + BTN_TRIGGER_HAPPY29: "BTN_TRIGGER_HAPPY29", + BTN_TRIGGER_HAPPY30: "BTN_TRIGGER_HAPPY30", + BTN_TRIGGER_HAPPY31: "BTN_TRIGGER_HAPPY31", + BTN_TRIGGER_HAPPY32: "BTN_TRIGGER_HAPPY32", + BTN_TRIGGER_HAPPY33: "BTN_TRIGGER_HAPPY33", + BTN_TRIGGER_HAPPY34: "BTN_TRIGGER_HAPPY34", + BTN_TRIGGER_HAPPY35: "BTN_TRIGGER_HAPPY35", + BTN_TRIGGER_HAPPY36: "BTN_TRIGGER_HAPPY36", + BTN_TRIGGER_HAPPY37: "BTN_TRIGGER_HAPPY37", + BTN_TRIGGER_HAPPY38: "BTN_TRIGGER_HAPPY38", + BTN_TRIGGER_HAPPY39: "BTN_TRIGGER_HAPPY39", + BTN_TRIGGER_HAPPY40: "BTN_TRIGGER_HAPPY40", + + KEY_MAX: "KEY_MAX", + KEY_CNT: "KEY_CNT", +} + +var RELNames = map[EvCode]string{ + REL_X: "REL_X", + REL_Y: "REL_Y", + REL_Z: "REL_Z", + REL_RX: "REL_RX", + REL_RY: "REL_RY", + REL_RZ: "REL_RZ", + REL_HWHEEL: "REL_HWHEEL", + REL_DIAL: "REL_DIAL", + REL_WHEEL: "REL_WHEEL", + REL_MISC: "REL_MISC", + + REL_RESERVED: "REL_RESERVED", + REL_WHEEL_HI_RES: "REL_WHEEL_HI_RES", + REL_HWHEEL_HI_RES: "REL_HWHEEL_HI_RES", + REL_MAX: "REL_MAX", + REL_CNT: "REL_CNT", +} + +var ABSNames = map[EvCode]string{ + ABS_X: "ABS_X", + ABS_Y: "ABS_Y", + ABS_Z: "ABS_Z", + ABS_RX: "ABS_RX", + ABS_RY: "ABS_RY", + ABS_RZ: "ABS_RZ", + ABS_THROTTLE: "ABS_THROTTLE", + ABS_RUDDER: "ABS_RUDDER", + ABS_WHEEL: "ABS_WHEEL", + ABS_GAS: "ABS_GAS", + ABS_BRAKE: "ABS_BRAKE", + ABS_HAT0X: "ABS_HAT0X", + ABS_HAT0Y: "ABS_HAT0Y", + ABS_HAT1X: "ABS_HAT1X", + ABS_HAT1Y: "ABS_HAT1Y", + ABS_HAT2X: "ABS_HAT2X", + ABS_HAT2Y: "ABS_HAT2Y", + ABS_HAT3X: "ABS_HAT3X", + ABS_HAT3Y: "ABS_HAT3Y", + ABS_PRESSURE: "ABS_PRESSURE", + ABS_DISTANCE: "ABS_DISTANCE", + ABS_TILT_X: "ABS_TILT_X", + ABS_TILT_Y: "ABS_TILT_Y", + ABS_TOOL_WIDTH: "ABS_TOOL_WIDTH", + + ABS_VOLUME: "ABS_VOLUME", + + ABS_MISC: "ABS_MISC", + + ABS_RESERVED: "ABS_RESERVED", + + ABS_MT_SLOT: "ABS_MT_SLOT", + ABS_MT_TOUCH_MAJOR: "ABS_MT_TOUCH_MAJOR", + ABS_MT_TOUCH_MINOR: "ABS_MT_TOUCH_MINOR", + ABS_MT_WIDTH_MAJOR: "ABS_MT_WIDTH_MAJOR", + ABS_MT_WIDTH_MINOR: "ABS_MT_WIDTH_MINOR", + ABS_MT_ORIENTATION: "ABS_MT_ORIENTATION", + ABS_MT_POSITION_X: "ABS_MT_POSITION_X", + ABS_MT_POSITION_Y: "ABS_MT_POSITION_Y", + ABS_MT_TOOL_TYPE: "ABS_MT_TOOL_TYPE", + ABS_MT_BLOB_ID: "ABS_MT_BLOB_ID", + ABS_MT_TRACKING_ID: "ABS_MT_TRACKING_ID", + ABS_MT_PRESSURE: "ABS_MT_PRESSURE", + ABS_MT_DISTANCE: "ABS_MT_DISTANCE", + ABS_MT_TOOL_X: "ABS_MT_TOOL_X", + ABS_MT_TOOL_Y: "ABS_MT_TOOL_Y", + + ABS_MAX: "ABS_MAX", + ABS_CNT: "ABS_CNT", +} + +var SWNames = map[EvCode]string{ + SW_LID: "SW_LID", + SW_TABLET_MODE: "SW_TABLET_MODE", + SW_HEADPHONE_INSERT: "SW_HEADPHONE_INSERT", + SW_RFKILL_ALL: "SW_RFKILL_ALL/SW_RADIO", + SW_MICROPHONE_INSERT: "SW_MICROPHONE_INSERT", + SW_DOCK: "SW_DOCK", + SW_LINEOUT_INSERT: "SW_LINEOUT_INSERT", + SW_JACK_PHYSICAL_INSERT: "SW_JACK_PHYSICAL_INSERT", + SW_VIDEOOUT_INSERT: "SW_VIDEOOUT_INSERT", + SW_CAMERA_LENS_COVER: "SW_CAMERA_LENS_COVER", + SW_KEYPAD_SLIDE: "SW_KEYPAD_SLIDE", + SW_FRONT_PROXIMITY: "SW_FRONT_PROXIMITY", + SW_ROTATE_LOCK: "SW_ROTATE_LOCK", + SW_LINEIN_INSERT: "SW_LINEIN_INSERT", + SW_MUTE_DEVICE: "SW_MUTE_DEVICE", + SW_PEN_INSERTED: "SW_PEN_INSERTED", + SW_MACHINE_COVER: "SW_MACHINE_COVER/SW_MAX", + SW_CNT: "SW_CNT", +} + +var MSCNames = map[EvCode]string{ + MSC_SERIAL: "MSC_SERIAL", + MSC_PULSELED: "MSC_PULSELED", + MSC_GESTURE: "MSC_GESTURE", + MSC_RAW: "MSC_RAW", + MSC_SCAN: "MSC_SCAN", + MSC_TIMESTAMP: "MSC_TIMESTAMP", + MSC_MAX: "MSC_MAX", + MSC_CNT: "MSC_CNT", +} + +var LEDNames = map[EvCode]string{ + LED_NUML: "LED_NUML", + LED_CAPSL: "LED_CAPSL", + LED_SCROLLL: "LED_SCROLLL", + LED_COMPOSE: "LED_COMPOSE", + LED_KANA: "LED_KANA", + LED_SLEEP: "LED_SLEEP", + LED_SUSPEND: "LED_SUSPEND", + LED_MUTE: "LED_MUTE", + LED_MISC: "LED_MISC", + LED_MAIL: "LED_MAIL", + LED_CHARGING: "LED_CHARGING", + LED_MAX: "LED_MAX", + LED_CNT: "LED_CNT", +} + +var REPNames = map[EvCode]string{ + REP_DELAY: "REP_DELAY", + REP_PERIOD: "REP_PERIOD/REP_MAX", + REP_CNT: "REP_CNT", +} + +var SNDNames = map[EvCode]string{ + SND_CLICK: "SND_CLICK", + SND_BELL: "SND_BELL", + SND_TONE: "SND_TONE", + SND_MAX: "SND_MAX", + SND_CNT: "SND_CNT", +} + +var IDNames = map[EvCode]string{ + ID_BUS: "ID_BUS", + ID_VENDOR: "ID_VENDOR", + ID_PRODUCT: "ID_PRODUCT", + ID_VERSION: "ID_VERSION", +} + +var BUSNames = map[EvCode]string{ + BUS_PCI: "BUS_PCI", + BUS_ISAPNP: "BUS_ISAPNP", + BUS_USB: "BUS_USB", + BUS_HIL: "BUS_HIL", + BUS_BLUETOOTH: "BUS_BLUETOOTH", + BUS_VIRTUAL: "BUS_VIRTUAL", + + BUS_ISA: "BUS_ISA", + BUS_I8042: "BUS_I8042", + BUS_XTKBD: "BUS_XTKBD", + BUS_RS232: "BUS_RS232", + BUS_GAMEPORT: "BUS_GAMEPORT", + BUS_PARPORT: "BUS_PARPORT", + BUS_AMIGA: "BUS_AMIGA", + BUS_ADB: "BUS_ADB", + BUS_I2C: "BUS_I2C", + BUS_HOST: "BUS_HOST", + BUS_GSC: "BUS_GSC", + BUS_ATARI: "BUS_ATARI", + BUS_SPI: "BUS_SPI", + BUS_RMI: "BUS_RMI", + BUS_CEC: "BUS_CEC", + BUS_INTEL_ISHTP: "BUS_INTEL_ISHTP", +} + +var MTNames = map[EvCode]string{ + MT_TOOL_FINGER: "MT_TOOL_FINGER", + MT_TOOL_PEN: "MT_TOOL_PEN", + MT_TOOL_PALM: "MT_TOOL_PALM", + MT_TOOL_DIAL: "MT_TOOL_DIAL", + MT_TOOL_MAX: "MT_TOOL_MAX", +} + +var FFNames = map[EvCode]string{ + FF_STATUS_STOPPED: "FF_STATUS_STOPPED", + FF_STATUS_PLAYING: "FF_STATUS_PLAYING/FF_STATUS_MAX", + + FF_RUMBLE: "FF_RUMBLE/FF_EFFECT_MIN", + FF_PERIODIC: "FF_PERIODIC", + FF_CONSTANT: "FF_CONSTANT", + FF_SPRING: "FF_SPRING", + FF_FRICTION: "FF_FRICTION", + FF_DAMPER: "FF_DAMPER", + FF_INERTIA: "FF_INERTIA", + FF_RAMP: "FF_RAMP/FF_EFFECT_MAX", + + FF_SQUARE: "FF_SQUARE/FF_WAVEFORM_MIN", + FF_TRIANGLE: "FF_TRIANGLE", + FF_SINE: "FF_SINE", + FF_SAW_UP: "FF_SAW_UP", + FF_SAW_DOWN: "FF_SAW_DOWN", + FF_CUSTOM: "FF_CUSTOM/FF_WAVEFORM_MAX", + + FF_GAIN: "FF_GAIN/FF_MAX_EFFECTS", + FF_AUTOCENTER: "FF_AUTOCENTER", + + FF_MAX: "FF_MAX", + FF_CNT: "FF_CNT", +} diff --git a/vendor/github.com/holoplot/go-evdev/device.go b/vendor/github.com/holoplot/go-evdev/device.go new file mode 100644 index 0000000..40e98e4 --- /dev/null +++ b/vendor/github.com/holoplot/go-evdev/device.go @@ -0,0 +1,261 @@ +package evdev + +import ( + "encoding/binary" + "fmt" + "os" + "syscall" +) + +// InputDevice represent a Linux kernel input device in userspace. +// It can be used to query and write device properties, read input events, +// or grab it for exclusive access. +type InputDevice struct { + file *os.File + driverVersion int32 +} + +// Open creates a new InputDevice from the given path. Returns an error if +// the device node could not be opened or its properties failed to read. +func Open(path string) (*InputDevice, error) { + d := &InputDevice{} + + var err error + d.file, err = os.OpenFile(path, os.O_RDWR, 0) + if err != nil { + return nil, err + } + + d.driverVersion, err = ioctlEVIOCGVERSION(d.file.Fd()) + if err != nil { + return nil, fmt.Errorf("cannot get driver version: %v", err) + } + + return d, nil +} + +// OpenByName creates a new InputDevice from the device name as reported by the kernel. +// Returns an error if the name does not exist, or the device node could +// not be opened or its properties failed to read. +func OpenByName(name string) (*InputDevice, error) { + devices, err := ListDevicePaths() + if err != nil { + return nil, err + } + for _, d := range devices { + if d.Name == name { + return Open(d.Path) + } + } + return nil, fmt.Errorf("could not find input device with name %q", name) +} + +// Close releases the resources held by an InputDevice. After calling this +// function, the InputDevice is no longer operational. +func (d *InputDevice) Close() error { + return d.file.Close() +} + +// Path returns the device's node path it was opened under. +func (d *InputDevice) Path() string { + return d.file.Name() +} + +// DriverVersion returns the version of the Linux Evdev driver. +// The three ints returned by this function describe the major, minor and +// micro parts of the version code. +func (d *InputDevice) DriverVersion() (int, int, int) { + return int(d.driverVersion >> 16), + int((d.driverVersion >> 8) & 0xff), + int((d.driverVersion >> 0) & 0xff) +} + +// Name returns the device's name as reported by the kernel. +func (d *InputDevice) Name() (string, error) { + return ioctlEVIOCGNAME(d.file.Fd()) +} + +// PhysicalLocation returns the device's physical location as reported by the kernel. +func (d *InputDevice) PhysicalLocation() (string, error) { + return ioctlEVIOCGPHYS(d.file.Fd()) +} + +// UniqueID returns the device's unique identifier as reported by the kernel. +func (d *InputDevice) UniqueID() (string, error) { + return ioctlEVIOCGUNIQ(d.file.Fd()) +} + +// InputID returns the device's vendor/product/busType/version information as reported by the kernel. +func (d *InputDevice) InputID() (InputID, error) { + return ioctlEVIOCGID(d.file.Fd()) +} + +// CapableTypes returns a slice of EvType that are the device supports +func (d *InputDevice) CapableTypes() []EvType { + var types []EvType + + evBits, err := ioctlEVIOCGBIT(d.file.Fd(), 0) + if err != nil { + return []EvType{} + } + + evBitmap := newBitmap(evBits) + + for _, t := range evBitmap.setBits() { + types = append(types, EvType(t)) + } + + return types +} + +// CapableEvents returns a slice of EvCode that are the device supports for given EvType +func (d *InputDevice) CapableEvents(t EvType) []EvCode { + var codes []EvCode + + evBits, err := ioctlEVIOCGBIT(d.file.Fd(), int(t)) + if err != nil { + return []EvCode{} + } + + evBitmap := newBitmap(evBits) + + for _, t := range evBitmap.setBits() { + codes = append(codes, EvCode(t)) + } + + return codes +} + +// Properties returns a slice of EvProp that are the device supports +func (d *InputDevice) Properties() []EvProp { + var props []EvProp + + propBits, err := ioctlEVIOCGPROP(d.file.Fd()) + if err != nil { + return []EvProp{} + } + + propBitmap := newBitmap(propBits) + + for _, p := range propBitmap.setBits() { + props = append(props, EvProp(p)) + } + + return props +} + +// State return a StateMap for the given type. The map will be empty if the requested type +// is not supported by the device. +func (d *InputDevice) State(t EvType) (StateMap, error) { + fd := d.file.Fd() + + evBits, err := ioctlEVIOCGBIT(fd, 0) + if err != nil { + return nil, fmt.Errorf("cannot get evBits: %v", err) + } + + evBitmap := newBitmap(evBits) + + if !evBitmap.bitIsSet(int(t)) { + return StateMap{}, nil + } + + codeBits, err := ioctlEVIOCGBIT(fd, int(t)) + if err != nil { + return nil, fmt.Errorf("cannot get evBits: %v", err) + } + + codeBitmap := newBitmap(codeBits) + + var stateBits []byte + + switch t { + case EV_KEY: + stateBits, err = ioctlEVIOCGKEY(fd) + case EV_SW: + stateBits, err = ioctlEVIOCGSW(fd) + case EV_LED: + stateBits, err = ioctlEVIOCGLED(fd) + case EV_SND: + stateBits, err = ioctlEVIOCGSND(fd) + default: + err = fmt.Errorf("unsupported evType %d", t) + } + + if err != nil { + return nil, err + } + + stateBitmap := newBitmap(stateBits) + st := StateMap{} + + for _, code := range codeBitmap.setBits() { + st[EvCode(code)] = stateBitmap.bitIsSet(code) + } + + return st, nil +} + +// AbsInfos returns the AbsInfo struct for all axis the device supports. +func (d *InputDevice) AbsInfos() (map[EvCode]AbsInfo, error) { + a := make(map[EvCode]AbsInfo) + + absBits, err := ioctlEVIOCGBIT(d.file.Fd(), EV_ABS) + if err != nil { + return nil, fmt.Errorf("cannot get absBits: %v", err) + } + + absBitmap := newBitmap(absBits) + + for _, abs := range absBitmap.setBits() { + absInfo, err := ioctlEVIOCGABS(d.file.Fd(), abs) + if err == nil { + a[EvCode(abs)] = absInfo + } + } + + return a, nil +} + +// Grab grabs the device for exclusive access. No other process will receive +// input events until the device instance is active. +func (d *InputDevice) Grab() error { + return ioctlEVIOCGRAB(d.file.Fd(), 1) +} + +// Ungrab releases a previously taken exclusive use with Grab(). +func (d *InputDevice) Ungrab() error { + return ioctlEVIOCGRAB(d.file.Fd(), 0) +} + +// Revoke revokes device access +func (d *InputDevice) Revoke() error { + return ioctlEVIOCREVOKE(d.file.Fd()) +} + +// NonBlock sets file descriptor into nonblocking mode. +// This way it is possible to interrupt ReadOne call by closing the device. +// Note: file.Fd() call will set file descriptor back to blocking mode so make sure your program +// is not using any other method than ReadOne after NonBlock call. +func (d *InputDevice) NonBlock() error { + return syscall.SetNonblock(int(d.file.Fd()), true) +} + +// ReadOne reads one InputEvent from the device. It blocks until an event has +// been received or an error has occurred. +func (d *InputDevice) ReadOne() (*InputEvent, error) { + event := InputEvent{} + + err := binary.Read(d.file, binary.LittleEndian, &event) + if err != nil { + return nil, err + } + + return &event, nil +} + +// WriteOne writes one InputEvent to the device. +// Useful for controlling LEDs of the device +func (d *InputDevice) WriteOne(event *InputEvent) error { + return binary.Write(d.file, binary.LittleEndian, event) +} diff --git a/vendor/github.com/holoplot/go-evdev/ioctl.go b/vendor/github.com/holoplot/go-evdev/ioctl.go new file mode 100644 index 0000000..18b6a09 --- /dev/null +++ b/vendor/github.com/holoplot/go-evdev/ioctl.go @@ -0,0 +1,274 @@ +package evdev + +import ( + "errors" + "fmt" + "strings" + "syscall" + "unsafe" +) + +const ( + ioctlDirNone = 0x0 + ioctlDirWrite = 0x1 + ioctlDirRead = 0x2 +) + +func trimNull(s string) string { + return strings.Trim(s, "\x00") +} + +func ioctlMakeCode(dir, typ, nr int, size uintptr) uint32 { + var code uint32 + if dir > ioctlDirWrite|ioctlDirRead { + panic(fmt.Errorf("invalid ioctl dir value: %d", dir)) + } + + if size > 1<<14 { + panic(fmt.Errorf("invalid ioctl size value: %d", size)) + } + + code |= uint32(dir) << 30 + code |= uint32(size) << 16 + code |= uint32(typ) << 8 + code |= uint32(nr) + + return code +} + +func doIoctl(fd uintptr, code uint32, ptr unsafe.Pointer) error { + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(code), uintptr(ptr)) + if errno != 0 { + return errors.New(errno.Error()) + } + + return nil +} + +func ioctlEVIOCGVERSION(fd uintptr) (int32, error) { + version := int32(0) + code := ioctlMakeCode(ioctlDirRead, 'E', 0x01, unsafe.Sizeof(version)) + err := doIoctl(fd, code, unsafe.Pointer(&version)) + return version, err +} + +func ioctlEVIOCGID(fd uintptr) (InputID, error) { + id := InputID{} + code := ioctlMakeCode(ioctlDirRead, 'E', 0x02, unsafe.Sizeof(id)) + err := doIoctl(fd, code, unsafe.Pointer(&id)) + return id, err +} + +func ioctlEVIOCGREP(fd uintptr) ([2]uint32, error) { + rep := [2]uint32{} + code := ioctlMakeCode(ioctlDirRead, 'E', 0x03, unsafe.Sizeof(rep)) + err := doIoctl(fd, code, unsafe.Pointer(&rep)) + return rep, err +} + +func ioctlEVIOCSREP(fd uintptr, rep [2]uint32) error { + code := ioctlMakeCode(ioctlDirWrite, 'E', 0x03, unsafe.Sizeof(rep)) + return doIoctl(fd, code, unsafe.Pointer(&rep)) +} + +func ioctlEVIOCGKEYCODE(fd uintptr) (InputKeymapEntry, error) { + entry := InputKeymapEntry{} + code := ioctlMakeCode(ioctlDirRead, 'E', 0x04, unsafe.Sizeof(entry)) + err := doIoctl(fd, code, unsafe.Pointer(&entry)) + return entry, err +} + +func ioctlEVIOCSKEYCODE(fd uintptr, entry InputKeymapEntry) error { + code := ioctlMakeCode(ioctlDirWrite, 'E', 0x04, unsafe.Sizeof(entry)) + return doIoctl(fd, code, unsafe.Pointer(&entry)) +} + +func ioctlEVIOCGNAME(fd uintptr) (string, error) { + str := [256]byte{} + code := ioctlMakeCode(ioctlDirRead, 'E', 0x06, unsafe.Sizeof(str)) + err := doIoctl(fd, code, unsafe.Pointer(&str)) + return trimNull(string(str[:])), err +} + +func ioctlEVIOCGPHYS(fd uintptr) (string, error) { + str := [256]byte{} + code := ioctlMakeCode(ioctlDirRead, 'E', 0x07, unsafe.Sizeof(str)) + err := doIoctl(fd, code, unsafe.Pointer(&str)) + return trimNull(string(str[:])), err +} + +func ioctlEVIOCGUNIQ(fd uintptr) (string, error) { + str := [256]byte{} + code := ioctlMakeCode(ioctlDirRead, 'E', 0x08, unsafe.Sizeof(str)) + err := doIoctl(fd, code, unsafe.Pointer(&str)) + return trimNull(string(str[:])), err +} + +func ioctlEVIOCGPROP(fd uintptr) ([]byte, error) { + bits := [256]byte{} + code := ioctlMakeCode(ioctlDirRead, 'E', 0x09, unsafe.Sizeof(bits)) + err := doIoctl(fd, code, unsafe.Pointer(&bits)) + return bits[:], err +} + +func ioctlEVIOCGKEY(fd uintptr) ([]byte, error) { + bits := [KEY_MAX]byte{} + code := ioctlMakeCode(ioctlDirRead, 'E', 0x18, unsafe.Sizeof(bits)) + err := doIoctl(fd, code, unsafe.Pointer(&bits)) + return bits[:], err +} + +func ioctlEVIOCGLED(fd uintptr) ([]byte, error) { + bits := [LED_MAX]byte{} + code := ioctlMakeCode(ioctlDirRead, 'E', 0x19, unsafe.Sizeof(bits)) + err := doIoctl(fd, code, unsafe.Pointer(&bits)) + return bits[:], err +} + +func ioctlEVIOCGSND(fd uintptr) ([]byte, error) { + bits := [SND_MAX]byte{} + code := ioctlMakeCode(ioctlDirRead, 'E', 0x1a, unsafe.Sizeof(bits)) + err := doIoctl(fd, code, unsafe.Pointer(&bits)) + return bits[:], err +} + +func ioctlEVIOCGSW(fd uintptr) ([]byte, error) { + bits := [SW_MAX]byte{} + code := ioctlMakeCode(ioctlDirRead, 'E', 0x1b, unsafe.Sizeof(bits)) + err := doIoctl(fd, code, unsafe.Pointer(&bits)) + return bits[:], err +} + +func ioctlEVIOCGBIT(fd uintptr, evtype int) ([]byte, error) { + var cnt int + + switch evtype { + case 0: + // special case, indicating the list of all feature types supported should be returned, + // rather than the list of particular features for that type + cnt = EV_CNT + case EV_KEY: + cnt = KEY_CNT + case EV_REL: + cnt = REL_CNT + case EV_ABS: + cnt = ABS_CNT + case EV_MSC: + cnt = MSC_CNT + case EV_SW: + cnt = SW_CNT + case EV_LED: + cnt = LED_CNT + case EV_SND: + cnt = SND_CNT + case EV_REP: + cnt = REP_CNT + case EV_FF: + cnt = FF_CNT + default: // EV_PWR, EV_FF_STATUS ?? + cnt = KEY_MAX + } + + bytesNumber := (cnt + 7) / 8 + + bits := [KEY_MAX]byte{} + code := ioctlMakeCode(ioctlDirRead, 'E', 0x20+evtype, unsafe.Sizeof(bits)) + err := doIoctl(fd, code, unsafe.Pointer(&bits)) + return bits[:bytesNumber], err +} + +func ioctlEVIOCGABS(fd uintptr, abs int) (AbsInfo, error) { + info := AbsInfo{} + code := ioctlMakeCode(ioctlDirRead, 'E', 0x40+abs, unsafe.Sizeof(info)) + err := doIoctl(fd, code, unsafe.Pointer(&info)) + return info, err +} + +func ioctlEVIOCSABS(fd uintptr, abs int, info AbsInfo) error { + code := ioctlMakeCode(ioctlDirWrite, 'E', 0xc0+abs, unsafe.Sizeof(info)) + return doIoctl(fd, code, unsafe.Pointer(&info)) +} + +func ioctlEVIOCGRAB(fd uintptr, p int32) error { + code := ioctlMakeCode(ioctlDirWrite, 'E', 0x90, unsafe.Sizeof(p)) + if p != 0 { + return doIoctl(fd, code, unsafe.Pointer(&p)) + } + return doIoctl(fd, code, nil) +} + +func ioctlEVIOCREVOKE(fd uintptr) error { + var p int32 + code := ioctlMakeCode(ioctlDirWrite, 'E', 0x91, unsafe.Sizeof(p)) + return doIoctl(fd, code, nil) +} + +func ioctlUISETEVBIT(fd uintptr, ev uintptr) error { + var p int32 + code := ioctlMakeCode(ioctlDirWrite, 'U', 100, unsafe.Sizeof(p)) + return doIoctl(fd, code, unsafe.Pointer(ev)) +} + +func ioctlUISETKEYBIT(fd uintptr, key uintptr) error { + var p int32 + code := ioctlMakeCode(ioctlDirWrite, 'U', 101, unsafe.Sizeof(p)) + return doIoctl(fd, code, unsafe.Pointer(key)) +} + +func ioctlUISETRELBIT(fd uintptr, rel uintptr) error { + var p int32 + code := ioctlMakeCode(ioctlDirWrite, 'U', 102, unsafe.Sizeof(p)) + return doIoctl(fd, code, unsafe.Pointer(rel)) +} + +func ioctlUISETABSBIT(fd uintptr, abs uintptr) error { + var p int32 + code := ioctlMakeCode(ioctlDirWrite, 'U', 103, unsafe.Sizeof(p)) + return doIoctl(fd, code, unsafe.Pointer(abs)) +} + +func ioctlUISETMSCBIT(fd uintptr, msc uintptr) error { + var p int32 + code := ioctlMakeCode(ioctlDirWrite, 'U', 104, unsafe.Sizeof(p)) + return doIoctl(fd, code, unsafe.Pointer(msc)) +} + +func ioctlUISETLEDBIT(fd uintptr, led uintptr) error { + var p int32 + code := ioctlMakeCode(ioctlDirWrite, 'U', 105, unsafe.Sizeof(p)) + return doIoctl(fd, code, unsafe.Pointer(led)) +} + +func ioctlUISETSNDBIT(fd uintptr, snd uintptr) error { + var p int32 + code := ioctlMakeCode(ioctlDirWrite, 'U', 106, unsafe.Sizeof(p)) + return doIoctl(fd, code, unsafe.Pointer(snd)) +} + +func ioctlUISETFFBIT(fd uintptr, fe uintptr) error { + var p int32 + code := ioctlMakeCode(ioctlDirWrite, 'U', 107, unsafe.Sizeof(p)) + return doIoctl(fd, code, unsafe.Pointer(fe)) +} + +func ioctlUISETSWBIT(fd uintptr, sw uintptr) error { + var p int32 + code := ioctlMakeCode(ioctlDirWrite, 'U', 109, unsafe.Sizeof(p)) + return doIoctl(fd, code, unsafe.Pointer(sw)) +} + +func ioctlUISETPROPBIT(fd uintptr, prop uintptr) error { + var p int32 + code := ioctlMakeCode(ioctlDirWrite, 'U', 110, unsafe.Sizeof(p)) + return doIoctl(fd, code, unsafe.Pointer(prop)) +} + +func ioctlUIDEVCREATE(fd uintptr) error { + code := ioctlMakeCode(ioctlDirNone, 'U', 1, 0) + return doIoctl(fd, code, nil) +} + +func ioctlUIDEVDESTROY(fd uintptr) error { + code := ioctlMakeCode(ioctlDirNone, 'U', 2, 0) + return doIoctl(fd, code, nil) +} diff --git a/vendor/github.com/holoplot/go-evdev/list.go b/vendor/github.com/holoplot/go-evdev/list.go new file mode 100644 index 0000000..51a8107 --- /dev/null +++ b/vendor/github.com/holoplot/go-evdev/list.go @@ -0,0 +1,39 @@ +package evdev + +import ( + "fmt" + "io/ioutil" +) + +// InputPath contains information about an InputDevice Name & Path +type InputPath struct { + Name string + Path string +} + +// ListDevicePaths lists all available input devices, returning their +// filename path, and the name as reported by the kernel. +func ListDevicePaths() ([]InputPath, error) { + var list []InputPath + + basePath := "/dev/input" + + files, err := ioutil.ReadDir(basePath) + if err != nil { + return list, err + } + + for _, fileName := range files { + if fileName.IsDir() { + continue + } + + full := fmt.Sprintf("%s/%s", basePath, fileName.Name()) + if d, err := Open(full); err == nil { + name, _ := d.Name() + list = append(list, InputPath{Name: name, Path: d.Path()}) + d.Close() + } + } + return list, nil +} diff --git a/vendor/github.com/holoplot/go-evdev/names.go b/vendor/github.com/holoplot/go-evdev/names.go new file mode 100644 index 0000000..c7a0a24 --- /dev/null +++ b/vendor/github.com/holoplot/go-evdev/names.go @@ -0,0 +1,43 @@ +package evdev + +var EvCodeNameLookup = map[EvType]map[EvCode]string{ + EV_SYN: SYNNames, + EV_KEY: KEYNames, + EV_REL: RELNames, + EV_ABS: ABSNames, + EV_MSC: MSCNames, + EV_SW: SWNames, + EV_LED: LEDNames, + EV_SND: SNDNames, + EV_REP: REPNames, + EV_FF: FFNames, + // EV_PWR: + // EV_FF_STATUS: +} + +// TypeName returns the name of an EvType as string, or "UNKNOWN" if the type is not valid +func TypeName(t EvType) string { + name, ok := EVToString[t] + if ok { + return name + } + return "unknown" +} + +// PropName returns the name of the given EvProp, or "UNKNOWN" if the property is not valid +func PropName(p EvProp) string { + name, ok := INPUTToString[p] + if ok { + return name + } + return "unknown" +} + +// CodeName returns the name of an EvfCode in the given EvType, or "UNKNOWN" of the code is not valid. +func CodeName(t EvType, c EvCode) string { + name, ok := EvCodeNameLookup[t][c] + if !ok { + return "unknown" + } + return name +} diff --git a/vendor/github.com/holoplot/go-evdev/types.go b/vendor/github.com/holoplot/go-evdev/types.go new file mode 100644 index 0000000..c227cc2 --- /dev/null +++ b/vendor/github.com/holoplot/go-evdev/types.go @@ -0,0 +1,86 @@ +package evdev + +import ( + "fmt" + "syscall" +) + +// EvType is EV_KEY, EV_SW, EV_LED, EV_SND, ... +type EvType uint16 + +// EvCode describes codes within a type (eg. KEY_A, KEY_B, ...) +type EvCode uint16 + +// EvProp describes device properties (eg. INPUT_PROP_ACCELEROMETER, INPUT_PROP_BUTTONPAD, ...) +type EvProp uint16 + +// StateMap describes the current state of codes within a type, as booleans. +type StateMap map[EvCode]bool + +// InputEvent describes an event that is generated by an InputDevice +type InputEvent struct { + Time syscall.Timeval // time in seconds since epoch at which event occurred + Type EvType // event type - one of ecodes.EV_* + Code EvCode // event code related to the event type + Value int32 // event value related to the event type +} + +func (e *InputEvent) TypeName() string { + return TypeName(e.Type) +} + +func (e *InputEvent) CodeName() string { + return CodeName(e.Type, e.Code) +} + +func (e *InputEvent) String() string { + return fmt.Sprintf( + "type: 0x%02x [%s], code: 0x%02x [%s], value: %d", + e.Type, e.TypeName(), e.Code, e.CodeName(), e.Value, + ) +} + +// InputID ... +type InputID struct { + BusType uint16 + Vendor uint16 + Product uint16 + Version uint16 +} + +// AbsInfo describes details on ABS input types +type AbsInfo struct { + Value int32 + Minimum int32 + Maximum int32 + Fuzz int32 + Flat int32 + Resolution int32 +} + +// InputKeymapEntry is used to retrieve and modify keymap data +type InputKeymapEntry struct { + Flags uint8 + Len uint8 + Index uint16 + KeyCode uint32 + ScanCode [32]uint8 +} + +// InputMask ... +type InputMask struct { + Type uint32 + CodesSize uint32 + CodesPtr uint64 +} + +// UinputUserDevice is used when creating or cloning a device +type UinputUserDevice struct { + Name [uinputMaxNameSize]byte + ID InputID + EffectsMax uint32 + Absmax [absSize]int32 + Absmin [absSize]int32 + Absfuzz [absSize]int32 + Absflat [absSize]int32 +} diff --git a/vendor/github.com/holoplot/go-evdev/uinput.go b/vendor/github.com/holoplot/go-evdev/uinput.go new file mode 100644 index 0000000..0f2a691 --- /dev/null +++ b/vendor/github.com/holoplot/go-evdev/uinput.go @@ -0,0 +1,160 @@ +package evdev + +import ( + "bytes" + "encoding/binary" + "fmt" + "os" + "syscall" +) + +const ( + uinputMaxNameSize = 80 + absSize = 64 +) + +// CreateDevice creates a device from scratch with the provided capabilities and name +// If set up fails the device will be removed from the system, +// once set up it can be removed by calling dev.Close +func CreateDevice(name string, id InputID, capabilities map[EvType][]EvCode) (*InputDevice, error) { + deviceFile, err := os.OpenFile("/dev/uinput", syscall.O_WRONLY|syscall.O_NONBLOCK, 0660) + if err != nil { + return nil, err + } + + newDev := &InputDevice{ + file: deviceFile, + } + + for ev, codes := range capabilities { + if err := ioctlUISETEVBIT(newDev.file.Fd(), uintptr(ev)); err != nil { + DestroyDevice(newDev) + return nil, fmt.Errorf("failed to set ev bit: %d - %w", ev, err) + } + + if err := setEventCodes(newDev, ev, codes); err != nil { + DestroyDevice(newDev) + return nil, fmt.Errorf("failed to set ev code: %w", err) + } + } + + if _, err = createInputDevice(newDev.file, UinputUserDevice{ + Name: toUinputName([]byte(name)), + ID: id, + }); err != nil { + DestroyDevice(newDev) + return nil, fmt.Errorf("failed to create device: %w", err) + } + + return newDev, nil +} + +// CloneDevice creates a new device from an existing one +// all capabilites will be coppied over to the new virtual device +// If set up fails the device will be removed from the system, +// once set up it can be removed by calling dev.Close +func CloneDevice(name string, dev *InputDevice) (*InputDevice, error) { + deviceFile, err := os.OpenFile("/dev/uinput", syscall.O_WRONLY|syscall.O_NONBLOCK, 0660) + if err != nil { + return nil, err + } + + newDev := &InputDevice{ + file: deviceFile, + driverVersion: dev.driverVersion, + } + + for _, ev := range dev.CapableTypes() { + if err := ioctlUISETEVBIT(newDev.file.Fd(), uintptr(ev)); err != nil { + DestroyDevice(newDev) + return nil, fmt.Errorf("failed to set ev bit: %d - %w", ev, err) + } + + eventCodes := dev.CapableEvents(ev) + if err := setEventCodes(newDev, ev, eventCodes); err != nil { + DestroyDevice(newDev) + return nil, fmt.Errorf("failed to set ev code: %w", err) + } + } + + id, err := dev.InputID() + if err != nil { + DestroyDevice(newDev) + return nil, fmt.Errorf("failed to get original device id: %w", err) + } + + if _, err = createInputDevice(newDev.file, UinputUserDevice{ + Name: toUinputName([]byte(name)), + ID: id, + }); err != nil { + return nil, fmt.Errorf("failed to create device: %w", err) + } + + return newDev, nil +} + +// Destroy destroys an input device, removing it from the system +// This is designed to be called on self created virtual devices and may fail if called +// on real devices attached to the system +func DestroyDevice(dev *InputDevice) error { + return ioctlUIDEVDESTROY(dev.file.Fd()) +} + +func setEventCodes(dev *InputDevice, ev EvType, codes []EvCode) error { + for _, code := range codes { + var err error + + switch ev { + case EV_ABS: + err = ioctlUISETABSBIT(dev.file.Fd(), uintptr(code)) + case EV_FF: + err = ioctlUISETFFBIT(dev.file.Fd(), uintptr(code)) + case EV_KEY: + err = ioctlUISETKEYBIT(dev.file.Fd(), uintptr(code)) + case EV_LED: + err = ioctlUISETLEDBIT(dev.file.Fd(), uintptr(code)) + case EV_MSC: + err = ioctlUISETMSCBIT(dev.file.Fd(), uintptr(code)) + case EV_REL: + err = ioctlUISETRELBIT(dev.file.Fd(), uintptr(code)) + case EV_SND: + err = ioctlUISETSNDBIT(dev.file.Fd(), uintptr(code)) + case EV_SW: + err = ioctlUISETSWBIT(dev.file.Fd(), uintptr(code)) + } + + if err != nil { + return err + } + } + + return nil +} + +func toUinputName(name []byte) (uinputName [uinputMaxNameSize]byte) { + var fixedSizeName [uinputMaxNameSize]byte + copy(fixedSizeName[:], name) + + return fixedSizeName +} + +func createInputDevice(file *os.File, dev UinputUserDevice) (fd *os.File, err error) { + buf := new(bytes.Buffer) + + if err = binary.Write(buf, binary.LittleEndian, dev); err != nil { + file.Close() + return nil, fmt.Errorf("failed to write user device buffer: %w", err) + } + + if _, err = file.Write(buf.Bytes()); err != nil { + file.Close() + return nil, fmt.Errorf("failed to write uidev struct to device file: %w", err) + } + + if err = ioctlUIDEVCREATE(file.Fd()); err != nil { + file.Close() + return nil, fmt.Errorf("failed to create device: %w", err) + } + + return file, nil +} diff --git a/vendor/github.com/szatmary/sonos/.gitignore b/vendor/github.com/szatmary/sonos/.gitignore new file mode 100644 index 0000000..f1c181e --- /dev/null +++ b/vendor/github.com/szatmary/sonos/.gitignore @@ -0,0 +1,12 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/vendor/github.com/szatmary/sonos/AVTransport/AVTransport.go b/vendor/github.com/szatmary/sonos/AVTransport/AVTransport.go new file mode 100644 index 0000000..3e69390 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/AVTransport/AVTransport.go @@ -0,0 +1,1379 @@ +package avtransport + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:AVTransport:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/MediaRenderer/AVTransport/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/MediaRenderer/AVTransport/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + SetAVTransportURI *SetAVTransportURIArgs `xml:"u:SetAVTransportURI,omitempty"` + SetNextAVTransportURI *SetNextAVTransportURIArgs `xml:"u:SetNextAVTransportURI,omitempty"` + AddURIToQueue *AddURIToQueueArgs `xml:"u:AddURIToQueue,omitempty"` + AddMultipleURIsToQueue *AddMultipleURIsToQueueArgs `xml:"u:AddMultipleURIsToQueue,omitempty"` + ReorderTracksInQueue *ReorderTracksInQueueArgs `xml:"u:ReorderTracksInQueue,omitempty"` + RemoveTrackFromQueue *RemoveTrackFromQueueArgs `xml:"u:RemoveTrackFromQueue,omitempty"` + RemoveTrackRangeFromQueue *RemoveTrackRangeFromQueueArgs `xml:"u:RemoveTrackRangeFromQueue,omitempty"` + RemoveAllTracksFromQueue *RemoveAllTracksFromQueueArgs `xml:"u:RemoveAllTracksFromQueue,omitempty"` + SaveQueue *SaveQueueArgs `xml:"u:SaveQueue,omitempty"` + BackupQueue *BackupQueueArgs `xml:"u:BackupQueue,omitempty"` + CreateSavedQueue *CreateSavedQueueArgs `xml:"u:CreateSavedQueue,omitempty"` + AddURIToSavedQueue *AddURIToSavedQueueArgs `xml:"u:AddURIToSavedQueue,omitempty"` + ReorderTracksInSavedQueue *ReorderTracksInSavedQueueArgs `xml:"u:ReorderTracksInSavedQueue,omitempty"` + GetMediaInfo *GetMediaInfoArgs `xml:"u:GetMediaInfo,omitempty"` + GetTransportInfo *GetTransportInfoArgs `xml:"u:GetTransportInfo,omitempty"` + GetPositionInfo *GetPositionInfoArgs `xml:"u:GetPositionInfo,omitempty"` + GetDeviceCapabilities *GetDeviceCapabilitiesArgs `xml:"u:GetDeviceCapabilities,omitempty"` + GetTransportSettings *GetTransportSettingsArgs `xml:"u:GetTransportSettings,omitempty"` + GetCrossfadeMode *GetCrossfadeModeArgs `xml:"u:GetCrossfadeMode,omitempty"` + Stop *StopArgs `xml:"u:Stop,omitempty"` + Play *PlayArgs `xml:"u:Play,omitempty"` + Pause *PauseArgs `xml:"u:Pause,omitempty"` + Seek *SeekArgs `xml:"u:Seek,omitempty"` + Next *NextArgs `xml:"u:Next,omitempty"` + Previous *PreviousArgs `xml:"u:Previous,omitempty"` + SetPlayMode *SetPlayModeArgs `xml:"u:SetPlayMode,omitempty"` + SetCrossfadeMode *SetCrossfadeModeArgs `xml:"u:SetCrossfadeMode,omitempty"` + NotifyDeletedURI *NotifyDeletedURIArgs `xml:"u:NotifyDeletedURI,omitempty"` + GetCurrentTransportActions *GetCurrentTransportActionsArgs `xml:"u:GetCurrentTransportActions,omitempty"` + BecomeCoordinatorOfStandaloneGroup *BecomeCoordinatorOfStandaloneGroupArgs `xml:"u:BecomeCoordinatorOfStandaloneGroup,omitempty"` + DelegateGroupCoordinationTo *DelegateGroupCoordinationToArgs `xml:"u:DelegateGroupCoordinationTo,omitempty"` + BecomeGroupCoordinator *BecomeGroupCoordinatorArgs `xml:"u:BecomeGroupCoordinator,omitempty"` + BecomeGroupCoordinatorAndSource *BecomeGroupCoordinatorAndSourceArgs `xml:"u:BecomeGroupCoordinatorAndSource,omitempty"` + ChangeCoordinator *ChangeCoordinatorArgs `xml:"u:ChangeCoordinator,omitempty"` + ChangeTransportSettings *ChangeTransportSettingsArgs `xml:"u:ChangeTransportSettings,omitempty"` + ConfigureSleepTimer *ConfigureSleepTimerArgs `xml:"u:ConfigureSleepTimer,omitempty"` + GetRemainingSleepTimerDuration *GetRemainingSleepTimerDurationArgs `xml:"u:GetRemainingSleepTimerDuration,omitempty"` + RunAlarm *RunAlarmArgs `xml:"u:RunAlarm,omitempty"` + StartAutoplay *StartAutoplayArgs `xml:"u:StartAutoplay,omitempty"` + GetRunningAlarmProperties *GetRunningAlarmPropertiesArgs `xml:"u:GetRunningAlarmProperties,omitempty"` + SnoozeAlarm *SnoozeAlarmArgs `xml:"u:SnoozeAlarm,omitempty"` + EndDirectControlSession *EndDirectControlSessionArgs `xml:"u:EndDirectControlSession,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + SetAVTransportURI *SetAVTransportURIResponse `xml:"SetAVTransportURIResponse,omitempty"` + SetNextAVTransportURI *SetNextAVTransportURIResponse `xml:"SetNextAVTransportURIResponse,omitempty"` + AddURIToQueue *AddURIToQueueResponse `xml:"AddURIToQueueResponse,omitempty"` + AddMultipleURIsToQueue *AddMultipleURIsToQueueResponse `xml:"AddMultipleURIsToQueueResponse,omitempty"` + ReorderTracksInQueue *ReorderTracksInQueueResponse `xml:"ReorderTracksInQueueResponse,omitempty"` + RemoveTrackFromQueue *RemoveTrackFromQueueResponse `xml:"RemoveTrackFromQueueResponse,omitempty"` + RemoveTrackRangeFromQueue *RemoveTrackRangeFromQueueResponse `xml:"RemoveTrackRangeFromQueueResponse,omitempty"` + RemoveAllTracksFromQueue *RemoveAllTracksFromQueueResponse `xml:"RemoveAllTracksFromQueueResponse,omitempty"` + SaveQueue *SaveQueueResponse `xml:"SaveQueueResponse,omitempty"` + BackupQueue *BackupQueueResponse `xml:"BackupQueueResponse,omitempty"` + CreateSavedQueue *CreateSavedQueueResponse `xml:"CreateSavedQueueResponse,omitempty"` + AddURIToSavedQueue *AddURIToSavedQueueResponse `xml:"AddURIToSavedQueueResponse,omitempty"` + ReorderTracksInSavedQueue *ReorderTracksInSavedQueueResponse `xml:"ReorderTracksInSavedQueueResponse,omitempty"` + GetMediaInfo *GetMediaInfoResponse `xml:"GetMediaInfoResponse,omitempty"` + GetTransportInfo *GetTransportInfoResponse `xml:"GetTransportInfoResponse,omitempty"` + GetPositionInfo *GetPositionInfoResponse `xml:"GetPositionInfoResponse,omitempty"` + GetDeviceCapabilities *GetDeviceCapabilitiesResponse `xml:"GetDeviceCapabilitiesResponse,omitempty"` + GetTransportSettings *GetTransportSettingsResponse `xml:"GetTransportSettingsResponse,omitempty"` + GetCrossfadeMode *GetCrossfadeModeResponse `xml:"GetCrossfadeModeResponse,omitempty"` + Stop *StopResponse `xml:"StopResponse,omitempty"` + Play *PlayResponse `xml:"PlayResponse,omitempty"` + Pause *PauseResponse `xml:"PauseResponse,omitempty"` + Seek *SeekResponse `xml:"SeekResponse,omitempty"` + Next *NextResponse `xml:"NextResponse,omitempty"` + Previous *PreviousResponse `xml:"PreviousResponse,omitempty"` + SetPlayMode *SetPlayModeResponse `xml:"SetPlayModeResponse,omitempty"` + SetCrossfadeMode *SetCrossfadeModeResponse `xml:"SetCrossfadeModeResponse,omitempty"` + NotifyDeletedURI *NotifyDeletedURIResponse `xml:"NotifyDeletedURIResponse,omitempty"` + GetCurrentTransportActions *GetCurrentTransportActionsResponse `xml:"GetCurrentTransportActionsResponse,omitempty"` + BecomeCoordinatorOfStandaloneGroup *BecomeCoordinatorOfStandaloneGroupResponse `xml:"BecomeCoordinatorOfStandaloneGroupResponse,omitempty"` + DelegateGroupCoordinationTo *DelegateGroupCoordinationToResponse `xml:"DelegateGroupCoordinationToResponse,omitempty"` + BecomeGroupCoordinator *BecomeGroupCoordinatorResponse `xml:"BecomeGroupCoordinatorResponse,omitempty"` + BecomeGroupCoordinatorAndSource *BecomeGroupCoordinatorAndSourceResponse `xml:"BecomeGroupCoordinatorAndSourceResponse,omitempty"` + ChangeCoordinator *ChangeCoordinatorResponse `xml:"ChangeCoordinatorResponse,omitempty"` + ChangeTransportSettings *ChangeTransportSettingsResponse `xml:"ChangeTransportSettingsResponse,omitempty"` + ConfigureSleepTimer *ConfigureSleepTimerResponse `xml:"ConfigureSleepTimerResponse,omitempty"` + GetRemainingSleepTimerDuration *GetRemainingSleepTimerDurationResponse `xml:"GetRemainingSleepTimerDurationResponse,omitempty"` + RunAlarm *RunAlarmResponse `xml:"RunAlarmResponse,omitempty"` + StartAutoplay *StartAutoplayResponse `xml:"StartAutoplayResponse,omitempty"` + GetRunningAlarmProperties *GetRunningAlarmPropertiesResponse `xml:"GetRunningAlarmPropertiesResponse,omitempty"` + SnoozeAlarm *SnoozeAlarmResponse `xml:"SnoozeAlarmResponse,omitempty"` + EndDirectControlSession *EndDirectControlSessionResponse `xml:"EndDirectControlSessionResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type SetAVTransportURIArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + CurrentURI string `xml:"CurrentURI"` + CurrentURIMetaData string `xml:"CurrentURIMetaData"` +} +type SetAVTransportURIResponse struct { +} + +func (s *Service) SetAVTransportURI(httpClient *http.Client, args *SetAVTransportURIArgs) (*SetAVTransportURIResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetAVTransportURI`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetAVTransportURI: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetAVTransportURI == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.SetAVTransportURI()`) + } + + return r.Body.SetAVTransportURI, nil +} + +type SetNextAVTransportURIArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + NextURI string `xml:"NextURI"` + NextURIMetaData string `xml:"NextURIMetaData"` +} +type SetNextAVTransportURIResponse struct { +} + +func (s *Service) SetNextAVTransportURI(httpClient *http.Client, args *SetNextAVTransportURIArgs) (*SetNextAVTransportURIResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetNextAVTransportURI`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetNextAVTransportURI: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetNextAVTransportURI == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.SetNextAVTransportURI()`) + } + + return r.Body.SetNextAVTransportURI, nil +} + +type AddURIToQueueArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + EnqueuedURI string `xml:"EnqueuedURI"` + EnqueuedURIMetaData string `xml:"EnqueuedURIMetaData"` + DesiredFirstTrackNumberEnqueued uint32 `xml:"DesiredFirstTrackNumberEnqueued"` + EnqueueAsNext bool `xml:"EnqueueAsNext"` +} +type AddURIToQueueResponse struct { + FirstTrackNumberEnqueued uint32 `xml:"FirstTrackNumberEnqueued"` + NumTracksAdded uint32 `xml:"NumTracksAdded"` + NewQueueLength uint32 `xml:"NewQueueLength"` +} + +func (s *Service) AddURIToQueue(httpClient *http.Client, args *AddURIToQueueArgs) (*AddURIToQueueResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`AddURIToQueue`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{AddURIToQueue: args}, + }) + if err != nil { + return nil, err + } + if r.Body.AddURIToQueue == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.AddURIToQueue()`) + } + + return r.Body.AddURIToQueue, nil +} + +type AddMultipleURIsToQueueArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + UpdateID uint32 `xml:"UpdateID"` + NumberOfURIs uint32 `xml:"NumberOfURIs"` + EnqueuedURIs string `xml:"EnqueuedURIs"` + EnqueuedURIsMetaData string `xml:"EnqueuedURIsMetaData"` + ContainerURI string `xml:"ContainerURI"` + ContainerMetaData string `xml:"ContainerMetaData"` + DesiredFirstTrackNumberEnqueued uint32 `xml:"DesiredFirstTrackNumberEnqueued"` + EnqueueAsNext bool `xml:"EnqueueAsNext"` +} +type AddMultipleURIsToQueueResponse struct { + FirstTrackNumberEnqueued uint32 `xml:"FirstTrackNumberEnqueued"` + NumTracksAdded uint32 `xml:"NumTracksAdded"` + NewQueueLength uint32 `xml:"NewQueueLength"` + NewUpdateID uint32 `xml:"NewUpdateID"` +} + +func (s *Service) AddMultipleURIsToQueue(httpClient *http.Client, args *AddMultipleURIsToQueueArgs) (*AddMultipleURIsToQueueResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`AddMultipleURIsToQueue`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{AddMultipleURIsToQueue: args}, + }) + if err != nil { + return nil, err + } + if r.Body.AddMultipleURIsToQueue == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.AddMultipleURIsToQueue()`) + } + + return r.Body.AddMultipleURIsToQueue, nil +} + +type ReorderTracksInQueueArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + StartingIndex uint32 `xml:"StartingIndex"` + NumberOfTracks uint32 `xml:"NumberOfTracks"` + InsertBefore uint32 `xml:"InsertBefore"` + UpdateID uint32 `xml:"UpdateID"` +} +type ReorderTracksInQueueResponse struct { +} + +func (s *Service) ReorderTracksInQueue(httpClient *http.Client, args *ReorderTracksInQueueArgs) (*ReorderTracksInQueueResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ReorderTracksInQueue`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ReorderTracksInQueue: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ReorderTracksInQueue == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.ReorderTracksInQueue()`) + } + + return r.Body.ReorderTracksInQueue, nil +} + +type RemoveTrackFromQueueArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + ObjectID string `xml:"ObjectID"` + UpdateID uint32 `xml:"UpdateID"` +} +type RemoveTrackFromQueueResponse struct { +} + +func (s *Service) RemoveTrackFromQueue(httpClient *http.Client, args *RemoveTrackFromQueueArgs) (*RemoveTrackFromQueueResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RemoveTrackFromQueue`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RemoveTrackFromQueue: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RemoveTrackFromQueue == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.RemoveTrackFromQueue()`) + } + + return r.Body.RemoveTrackFromQueue, nil +} + +type RemoveTrackRangeFromQueueArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + UpdateID uint32 `xml:"UpdateID"` + StartingIndex uint32 `xml:"StartingIndex"` + NumberOfTracks uint32 `xml:"NumberOfTracks"` +} +type RemoveTrackRangeFromQueueResponse struct { + NewUpdateID uint32 `xml:"NewUpdateID"` +} + +func (s *Service) RemoveTrackRangeFromQueue(httpClient *http.Client, args *RemoveTrackRangeFromQueueArgs) (*RemoveTrackRangeFromQueueResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RemoveTrackRangeFromQueue`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RemoveTrackRangeFromQueue: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RemoveTrackRangeFromQueue == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.RemoveTrackRangeFromQueue()`) + } + + return r.Body.RemoveTrackRangeFromQueue, nil +} + +type RemoveAllTracksFromQueueArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type RemoveAllTracksFromQueueResponse struct { +} + +func (s *Service) RemoveAllTracksFromQueue(httpClient *http.Client, args *RemoveAllTracksFromQueueArgs) (*RemoveAllTracksFromQueueResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RemoveAllTracksFromQueue`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RemoveAllTracksFromQueue: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RemoveAllTracksFromQueue == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.RemoveAllTracksFromQueue()`) + } + + return r.Body.RemoveAllTracksFromQueue, nil +} + +type SaveQueueArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + Title string `xml:"Title"` + ObjectID string `xml:"ObjectID"` +} +type SaveQueueResponse struct { + AssignedObjectID string `xml:"AssignedObjectID"` +} + +func (s *Service) SaveQueue(httpClient *http.Client, args *SaveQueueArgs) (*SaveQueueResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SaveQueue`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SaveQueue: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SaveQueue == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.SaveQueue()`) + } + + return r.Body.SaveQueue, nil +} + +type BackupQueueArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type BackupQueueResponse struct { +} + +func (s *Service) BackupQueue(httpClient *http.Client, args *BackupQueueArgs) (*BackupQueueResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`BackupQueue`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{BackupQueue: args}, + }) + if err != nil { + return nil, err + } + if r.Body.BackupQueue == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.BackupQueue()`) + } + + return r.Body.BackupQueue, nil +} + +type CreateSavedQueueArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + Title string `xml:"Title"` + EnqueuedURI string `xml:"EnqueuedURI"` + EnqueuedURIMetaData string `xml:"EnqueuedURIMetaData"` +} +type CreateSavedQueueResponse struct { + NumTracksAdded uint32 `xml:"NumTracksAdded"` + NewQueueLength uint32 `xml:"NewQueueLength"` + AssignedObjectID string `xml:"AssignedObjectID"` + NewUpdateID uint32 `xml:"NewUpdateID"` +} + +func (s *Service) CreateSavedQueue(httpClient *http.Client, args *CreateSavedQueueArgs) (*CreateSavedQueueResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`CreateSavedQueue`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{CreateSavedQueue: args}, + }) + if err != nil { + return nil, err + } + if r.Body.CreateSavedQueue == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.CreateSavedQueue()`) + } + + return r.Body.CreateSavedQueue, nil +} + +type AddURIToSavedQueueArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + ObjectID string `xml:"ObjectID"` + UpdateID uint32 `xml:"UpdateID"` + EnqueuedURI string `xml:"EnqueuedURI"` + EnqueuedURIMetaData string `xml:"EnqueuedURIMetaData"` + AddAtIndex uint32 `xml:"AddAtIndex"` +} +type AddURIToSavedQueueResponse struct { + NumTracksAdded uint32 `xml:"NumTracksAdded"` + NewQueueLength uint32 `xml:"NewQueueLength"` + NewUpdateID uint32 `xml:"NewUpdateID"` +} + +func (s *Service) AddURIToSavedQueue(httpClient *http.Client, args *AddURIToSavedQueueArgs) (*AddURIToSavedQueueResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`AddURIToSavedQueue`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{AddURIToSavedQueue: args}, + }) + if err != nil { + return nil, err + } + if r.Body.AddURIToSavedQueue == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.AddURIToSavedQueue()`) + } + + return r.Body.AddURIToSavedQueue, nil +} + +type ReorderTracksInSavedQueueArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + ObjectID string `xml:"ObjectID"` + UpdateID uint32 `xml:"UpdateID"` + TrackList string `xml:"TrackList"` + NewPositionList string `xml:"NewPositionList"` +} +type ReorderTracksInSavedQueueResponse struct { + QueueLengthChange int32 `xml:"QueueLengthChange"` + NewQueueLength uint32 `xml:"NewQueueLength"` + NewUpdateID uint32 `xml:"NewUpdateID"` +} + +func (s *Service) ReorderTracksInSavedQueue(httpClient *http.Client, args *ReorderTracksInSavedQueueArgs) (*ReorderTracksInSavedQueueResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ReorderTracksInSavedQueue`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ReorderTracksInSavedQueue: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ReorderTracksInSavedQueue == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.ReorderTracksInSavedQueue()`) + } + + return r.Body.ReorderTracksInSavedQueue, nil +} + +type GetMediaInfoArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetMediaInfoResponse struct { + NrTracks uint32 `xml:"NrTracks"` + MediaDuration string `xml:"MediaDuration"` + CurrentURI string `xml:"CurrentURI"` + CurrentURIMetaData string `xml:"CurrentURIMetaData"` + NextURI string `xml:"NextURI"` + NextURIMetaData string `xml:"NextURIMetaData"` + PlayMedium string `xml:"PlayMedium"` + RecordMedium string `xml:"RecordMedium"` + WriteStatus string `xml:"WriteStatus"` +} + +func (s *Service) GetMediaInfo(httpClient *http.Client, args *GetMediaInfoArgs) (*GetMediaInfoResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetMediaInfo`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetMediaInfo: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetMediaInfo == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.GetMediaInfo()`) + } + + return r.Body.GetMediaInfo, nil +} + +type GetTransportInfoArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetTransportInfoResponse struct { + CurrentTransportState string `xml:"CurrentTransportState"` + CurrentTransportStatus string `xml:"CurrentTransportStatus"` + CurrentSpeed string `xml:"CurrentSpeed"` +} + +func (s *Service) GetTransportInfo(httpClient *http.Client, args *GetTransportInfoArgs) (*GetTransportInfoResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetTransportInfo`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetTransportInfo: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetTransportInfo == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.GetTransportInfo()`) + } + + return r.Body.GetTransportInfo, nil +} + +type GetPositionInfoArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetPositionInfoResponse struct { + Track uint32 `xml:"Track"` + TrackDuration string `xml:"TrackDuration"` + TrackMetaData string `xml:"TrackMetaData"` + TrackURI string `xml:"TrackURI"` + RelTime string `xml:"RelTime"` + AbsTime string `xml:"AbsTime"` + RelCount int32 `xml:"RelCount"` + AbsCount int32 `xml:"AbsCount"` +} + +func (s *Service) GetPositionInfo(httpClient *http.Client, args *GetPositionInfoArgs) (*GetPositionInfoResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetPositionInfo`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetPositionInfo: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetPositionInfo == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.GetPositionInfo()`) + } + + return r.Body.GetPositionInfo, nil +} + +type GetDeviceCapabilitiesArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetDeviceCapabilitiesResponse struct { + PlayMedia string `xml:"PlayMedia"` + RecMedia string `xml:"RecMedia"` + RecQualityModes string `xml:"RecQualityModes"` +} + +func (s *Service) GetDeviceCapabilities(httpClient *http.Client, args *GetDeviceCapabilitiesArgs) (*GetDeviceCapabilitiesResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetDeviceCapabilities`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetDeviceCapabilities: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetDeviceCapabilities == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.GetDeviceCapabilities()`) + } + + return r.Body.GetDeviceCapabilities, nil +} + +type GetTransportSettingsArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetTransportSettingsResponse struct { + PlayMode string `xml:"PlayMode"` + RecQualityMode string `xml:"RecQualityMode"` +} + +func (s *Service) GetTransportSettings(httpClient *http.Client, args *GetTransportSettingsArgs) (*GetTransportSettingsResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetTransportSettings`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetTransportSettings: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetTransportSettings == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.GetTransportSettings()`) + } + + return r.Body.GetTransportSettings, nil +} + +type GetCrossfadeModeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetCrossfadeModeResponse struct { + CrossfadeMode bool `xml:"CrossfadeMode"` +} + +func (s *Service) GetCrossfadeMode(httpClient *http.Client, args *GetCrossfadeModeArgs) (*GetCrossfadeModeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetCrossfadeMode`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetCrossfadeMode: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetCrossfadeMode == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.GetCrossfadeMode()`) + } + + return r.Body.GetCrossfadeMode, nil +} + +type StopArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type StopResponse struct { +} + +func (s *Service) Stop(httpClient *http.Client, args *StopArgs) (*StopResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Stop`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Stop: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Stop == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.Stop()`) + } + + return r.Body.Stop, nil +} + +type PlayArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: 1 + Speed string `xml:"Speed"` +} +type PlayResponse struct { +} + +func (s *Service) Play(httpClient *http.Client, args *PlayArgs) (*PlayResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Play`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Play: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Play == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.Play()`) + } + + return r.Body.Play, nil +} + +type PauseArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type PauseResponse struct { +} + +func (s *Service) Pause(httpClient *http.Client, args *PauseArgs) (*PauseResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Pause`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Pause: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Pause == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.Pause()`) + } + + return r.Body.Pause, nil +} + +type SeekArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: TRACK_NR + // Allowed Value: REL_TIME + // Allowed Value: TIME_DELTA + Unit string `xml:"Unit"` + Target string `xml:"Target"` +} +type SeekResponse struct { +} + +func (s *Service) Seek(httpClient *http.Client, args *SeekArgs) (*SeekResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Seek`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Seek: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Seek == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.Seek()`) + } + + return r.Body.Seek, nil +} + +type NextArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type NextResponse struct { +} + +func (s *Service) Next(httpClient *http.Client, args *NextArgs) (*NextResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Next`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Next: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Next == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.Next()`) + } + + return r.Body.Next, nil +} + +type PreviousArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type PreviousResponse struct { +} + +func (s *Service) Previous(httpClient *http.Client, args *PreviousArgs) (*PreviousResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Previous`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Previous: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Previous == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.Previous()`) + } + + return r.Body.Previous, nil +} + +type SetPlayModeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: NORMAL + // Allowed Value: REPEAT_ALL + // Allowed Value: REPEAT_ONE + // Allowed Value: SHUFFLE_NOREPEAT + // Allowed Value: SHUFFLE + // Allowed Value: SHUFFLE_REPEAT_ONE + NewPlayMode string `xml:"NewPlayMode"` +} +type SetPlayModeResponse struct { +} + +func (s *Service) SetPlayMode(httpClient *http.Client, args *SetPlayModeArgs) (*SetPlayModeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetPlayMode`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetPlayMode: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetPlayMode == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.SetPlayMode()`) + } + + return r.Body.SetPlayMode, nil +} + +type SetCrossfadeModeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + CrossfadeMode bool `xml:"CrossfadeMode"` +} +type SetCrossfadeModeResponse struct { +} + +func (s *Service) SetCrossfadeMode(httpClient *http.Client, args *SetCrossfadeModeArgs) (*SetCrossfadeModeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetCrossfadeMode`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetCrossfadeMode: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetCrossfadeMode == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.SetCrossfadeMode()`) + } + + return r.Body.SetCrossfadeMode, nil +} + +type NotifyDeletedURIArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + DeletedURI string `xml:"DeletedURI"` +} +type NotifyDeletedURIResponse struct { +} + +func (s *Service) NotifyDeletedURI(httpClient *http.Client, args *NotifyDeletedURIArgs) (*NotifyDeletedURIResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`NotifyDeletedURI`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{NotifyDeletedURI: args}, + }) + if err != nil { + return nil, err + } + if r.Body.NotifyDeletedURI == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.NotifyDeletedURI()`) + } + + return r.Body.NotifyDeletedURI, nil +} + +type GetCurrentTransportActionsArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetCurrentTransportActionsResponse struct { + Actions string `xml:"Actions"` +} + +func (s *Service) GetCurrentTransportActions(httpClient *http.Client, args *GetCurrentTransportActionsArgs) (*GetCurrentTransportActionsResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetCurrentTransportActions`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetCurrentTransportActions: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetCurrentTransportActions == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.GetCurrentTransportActions()`) + } + + return r.Body.GetCurrentTransportActions, nil +} + +type BecomeCoordinatorOfStandaloneGroupArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type BecomeCoordinatorOfStandaloneGroupResponse struct { + DelegatedGroupCoordinatorID string `xml:"DelegatedGroupCoordinatorID"` + NewGroupID string `xml:"NewGroupID"` +} + +func (s *Service) BecomeCoordinatorOfStandaloneGroup(httpClient *http.Client, args *BecomeCoordinatorOfStandaloneGroupArgs) (*BecomeCoordinatorOfStandaloneGroupResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`BecomeCoordinatorOfStandaloneGroup`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{BecomeCoordinatorOfStandaloneGroup: args}, + }) + if err != nil { + return nil, err + } + if r.Body.BecomeCoordinatorOfStandaloneGroup == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.BecomeCoordinatorOfStandaloneGroup()`) + } + + return r.Body.BecomeCoordinatorOfStandaloneGroup, nil +} + +type DelegateGroupCoordinationToArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + NewCoordinator string `xml:"NewCoordinator"` + RejoinGroup bool `xml:"RejoinGroup"` +} +type DelegateGroupCoordinationToResponse struct { +} + +func (s *Service) DelegateGroupCoordinationTo(httpClient *http.Client, args *DelegateGroupCoordinationToArgs) (*DelegateGroupCoordinationToResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`DelegateGroupCoordinationTo`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{DelegateGroupCoordinationTo: args}, + }) + if err != nil { + return nil, err + } + if r.Body.DelegateGroupCoordinationTo == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.DelegateGroupCoordinationTo()`) + } + + return r.Body.DelegateGroupCoordinationTo, nil +} + +type BecomeGroupCoordinatorArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + CurrentCoordinator string `xml:"CurrentCoordinator"` + CurrentGroupID string `xml:"CurrentGroupID"` + OtherMembers string `xml:"OtherMembers"` + TransportSettings string `xml:"TransportSettings"` + CurrentURI string `xml:"CurrentURI"` + CurrentURIMetaData string `xml:"CurrentURIMetaData"` + SleepTimerState string `xml:"SleepTimerState"` + AlarmState string `xml:"AlarmState"` + StreamRestartState string `xml:"StreamRestartState"` + CurrentQueueTrackList string `xml:"CurrentQueueTrackList"` + CurrentVLIState string `xml:"CurrentVLIState"` +} +type BecomeGroupCoordinatorResponse struct { +} + +func (s *Service) BecomeGroupCoordinator(httpClient *http.Client, args *BecomeGroupCoordinatorArgs) (*BecomeGroupCoordinatorResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`BecomeGroupCoordinator`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{BecomeGroupCoordinator: args}, + }) + if err != nil { + return nil, err + } + if r.Body.BecomeGroupCoordinator == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.BecomeGroupCoordinator()`) + } + + return r.Body.BecomeGroupCoordinator, nil +} + +type BecomeGroupCoordinatorAndSourceArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + CurrentCoordinator string `xml:"CurrentCoordinator"` + CurrentGroupID string `xml:"CurrentGroupID"` + OtherMembers string `xml:"OtherMembers"` + CurrentURI string `xml:"CurrentURI"` + CurrentURIMetaData string `xml:"CurrentURIMetaData"` + SleepTimerState string `xml:"SleepTimerState"` + AlarmState string `xml:"AlarmState"` + StreamRestartState string `xml:"StreamRestartState"` + CurrentAVTTrackList string `xml:"CurrentAVTTrackList"` + CurrentQueueTrackList string `xml:"CurrentQueueTrackList"` + CurrentSourceState string `xml:"CurrentSourceState"` + ResumePlayback bool `xml:"ResumePlayback"` +} +type BecomeGroupCoordinatorAndSourceResponse struct { +} + +func (s *Service) BecomeGroupCoordinatorAndSource(httpClient *http.Client, args *BecomeGroupCoordinatorAndSourceArgs) (*BecomeGroupCoordinatorAndSourceResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`BecomeGroupCoordinatorAndSource`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{BecomeGroupCoordinatorAndSource: args}, + }) + if err != nil { + return nil, err + } + if r.Body.BecomeGroupCoordinatorAndSource == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.BecomeGroupCoordinatorAndSource()`) + } + + return r.Body.BecomeGroupCoordinatorAndSource, nil +} + +type ChangeCoordinatorArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + CurrentCoordinator string `xml:"CurrentCoordinator"` + NewCoordinator string `xml:"NewCoordinator"` + NewTransportSettings string `xml:"NewTransportSettings"` + CurrentAVTransportURI string `xml:"CurrentAVTransportURI"` +} +type ChangeCoordinatorResponse struct { +} + +func (s *Service) ChangeCoordinator(httpClient *http.Client, args *ChangeCoordinatorArgs) (*ChangeCoordinatorResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ChangeCoordinator`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ChangeCoordinator: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ChangeCoordinator == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.ChangeCoordinator()`) + } + + return r.Body.ChangeCoordinator, nil +} + +type ChangeTransportSettingsArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + NewTransportSettings string `xml:"NewTransportSettings"` + CurrentAVTransportURI string `xml:"CurrentAVTransportURI"` +} +type ChangeTransportSettingsResponse struct { +} + +func (s *Service) ChangeTransportSettings(httpClient *http.Client, args *ChangeTransportSettingsArgs) (*ChangeTransportSettingsResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ChangeTransportSettings`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ChangeTransportSettings: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ChangeTransportSettings == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.ChangeTransportSettings()`) + } + + return r.Body.ChangeTransportSettings, nil +} + +type ConfigureSleepTimerArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + NewSleepTimerDuration string `xml:"NewSleepTimerDuration"` +} +type ConfigureSleepTimerResponse struct { +} + +func (s *Service) ConfigureSleepTimer(httpClient *http.Client, args *ConfigureSleepTimerArgs) (*ConfigureSleepTimerResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ConfigureSleepTimer`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ConfigureSleepTimer: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ConfigureSleepTimer == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.ConfigureSleepTimer()`) + } + + return r.Body.ConfigureSleepTimer, nil +} + +type GetRemainingSleepTimerDurationArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetRemainingSleepTimerDurationResponse struct { + RemainingSleepTimerDuration string `xml:"RemainingSleepTimerDuration"` + CurrentSleepTimerGeneration uint32 `xml:"CurrentSleepTimerGeneration"` +} + +func (s *Service) GetRemainingSleepTimerDuration(httpClient *http.Client, args *GetRemainingSleepTimerDurationArgs) (*GetRemainingSleepTimerDurationResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetRemainingSleepTimerDuration`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetRemainingSleepTimerDuration: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetRemainingSleepTimerDuration == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.GetRemainingSleepTimerDuration()`) + } + + return r.Body.GetRemainingSleepTimerDuration, nil +} + +type RunAlarmArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + AlarmID uint32 `xml:"AlarmID"` + LoggedStartTime string `xml:"LoggedStartTime"` + Duration string `xml:"Duration"` + ProgramURI string `xml:"ProgramURI"` + ProgramMetaData string `xml:"ProgramMetaData"` + // Allowed Value: NORMAL + // Allowed Value: REPEAT_ALL + // Allowed Value: REPEAT_ONE + // Allowed Value: SHUFFLE_NOREPEAT + // Allowed Value: SHUFFLE + // Allowed Value: SHUFFLE_REPEAT_ONE + PlayMode string `xml:"PlayMode"` + Volume uint16 `xml:"Volume"` + IncludeLinkedZones bool `xml:"IncludeLinkedZones"` +} +type RunAlarmResponse struct { +} + +func (s *Service) RunAlarm(httpClient *http.Client, args *RunAlarmArgs) (*RunAlarmResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RunAlarm`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RunAlarm: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RunAlarm == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.RunAlarm()`) + } + + return r.Body.RunAlarm, nil +} + +type StartAutoplayArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + ProgramURI string `xml:"ProgramURI"` + ProgramMetaData string `xml:"ProgramMetaData"` + Volume uint16 `xml:"Volume"` + IncludeLinkedZones bool `xml:"IncludeLinkedZones"` + ResetVolumeAfter bool `xml:"ResetVolumeAfter"` +} +type StartAutoplayResponse struct { +} + +func (s *Service) StartAutoplay(httpClient *http.Client, args *StartAutoplayArgs) (*StartAutoplayResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`StartAutoplay`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{StartAutoplay: args}, + }) + if err != nil { + return nil, err + } + if r.Body.StartAutoplay == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.StartAutoplay()`) + } + + return r.Body.StartAutoplay, nil +} + +type GetRunningAlarmPropertiesArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetRunningAlarmPropertiesResponse struct { + AlarmID uint32 `xml:"AlarmID"` + GroupID string `xml:"GroupID"` + LoggedStartTime string `xml:"LoggedStartTime"` +} + +func (s *Service) GetRunningAlarmProperties(httpClient *http.Client, args *GetRunningAlarmPropertiesArgs) (*GetRunningAlarmPropertiesResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetRunningAlarmProperties`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetRunningAlarmProperties: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetRunningAlarmProperties == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.GetRunningAlarmProperties()`) + } + + return r.Body.GetRunningAlarmProperties, nil +} + +type SnoozeAlarmArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + Duration string `xml:"Duration"` +} +type SnoozeAlarmResponse struct { +} + +func (s *Service) SnoozeAlarm(httpClient *http.Client, args *SnoozeAlarmArgs) (*SnoozeAlarmResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SnoozeAlarm`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SnoozeAlarm: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SnoozeAlarm == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.SnoozeAlarm()`) + } + + return r.Body.SnoozeAlarm, nil +} + +type EndDirectControlSessionArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type EndDirectControlSessionResponse struct { +} + +func (s *Service) EndDirectControlSession(httpClient *http.Client, args *EndDirectControlSessionArgs) (*EndDirectControlSessionResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`EndDirectControlSession`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{EndDirectControlSession: args}, + }) + if err != nil { + return nil, err + } + if r.Body.EndDirectControlSession == nil { + return nil, errors.New(`unexpected respose from service calling avtransport.EndDirectControlSession()`) + } + + return r.Body.EndDirectControlSession, nil +} diff --git a/vendor/github.com/szatmary/sonos/AlarmClock/AlarmClock.go b/vendor/github.com/szatmary/sonos/AlarmClock/AlarmClock.go new file mode 100644 index 0000000..9131a55 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/AlarmClock/AlarmClock.go @@ -0,0 +1,593 @@ +package alarmclock + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:AlarmClock:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/AlarmClock/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/AlarmClock/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + SetFormat *SetFormatArgs `xml:"u:SetFormat,omitempty"` + GetFormat *GetFormatArgs `xml:"u:GetFormat,omitempty"` + SetTimeZone *SetTimeZoneArgs `xml:"u:SetTimeZone,omitempty"` + GetTimeZone *GetTimeZoneArgs `xml:"u:GetTimeZone,omitempty"` + GetTimeZoneAndRule *GetTimeZoneAndRuleArgs `xml:"u:GetTimeZoneAndRule,omitempty"` + GetTimeZoneRule *GetTimeZoneRuleArgs `xml:"u:GetTimeZoneRule,omitempty"` + SetTimeServer *SetTimeServerArgs `xml:"u:SetTimeServer,omitempty"` + GetTimeServer *GetTimeServerArgs `xml:"u:GetTimeServer,omitempty"` + SetTimeNow *SetTimeNowArgs `xml:"u:SetTimeNow,omitempty"` + GetHouseholdTimeAtStamp *GetHouseholdTimeAtStampArgs `xml:"u:GetHouseholdTimeAtStamp,omitempty"` + GetTimeNow *GetTimeNowArgs `xml:"u:GetTimeNow,omitempty"` + CreateAlarm *CreateAlarmArgs `xml:"u:CreateAlarm,omitempty"` + UpdateAlarm *UpdateAlarmArgs `xml:"u:UpdateAlarm,omitempty"` + DestroyAlarm *DestroyAlarmArgs `xml:"u:DestroyAlarm,omitempty"` + ListAlarms *ListAlarmsArgs `xml:"u:ListAlarms,omitempty"` + SetDailyIndexRefreshTime *SetDailyIndexRefreshTimeArgs `xml:"u:SetDailyIndexRefreshTime,omitempty"` + GetDailyIndexRefreshTime *GetDailyIndexRefreshTimeArgs `xml:"u:GetDailyIndexRefreshTime,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + SetFormat *SetFormatResponse `xml:"SetFormatResponse,omitempty"` + GetFormat *GetFormatResponse `xml:"GetFormatResponse,omitempty"` + SetTimeZone *SetTimeZoneResponse `xml:"SetTimeZoneResponse,omitempty"` + GetTimeZone *GetTimeZoneResponse `xml:"GetTimeZoneResponse,omitempty"` + GetTimeZoneAndRule *GetTimeZoneAndRuleResponse `xml:"GetTimeZoneAndRuleResponse,omitempty"` + GetTimeZoneRule *GetTimeZoneRuleResponse `xml:"GetTimeZoneRuleResponse,omitempty"` + SetTimeServer *SetTimeServerResponse `xml:"SetTimeServerResponse,omitempty"` + GetTimeServer *GetTimeServerResponse `xml:"GetTimeServerResponse,omitempty"` + SetTimeNow *SetTimeNowResponse `xml:"SetTimeNowResponse,omitempty"` + GetHouseholdTimeAtStamp *GetHouseholdTimeAtStampResponse `xml:"GetHouseholdTimeAtStampResponse,omitempty"` + GetTimeNow *GetTimeNowResponse `xml:"GetTimeNowResponse,omitempty"` + CreateAlarm *CreateAlarmResponse `xml:"CreateAlarmResponse,omitempty"` + UpdateAlarm *UpdateAlarmResponse `xml:"UpdateAlarmResponse,omitempty"` + DestroyAlarm *DestroyAlarmResponse `xml:"DestroyAlarmResponse,omitempty"` + ListAlarms *ListAlarmsResponse `xml:"ListAlarmsResponse,omitempty"` + SetDailyIndexRefreshTime *SetDailyIndexRefreshTimeResponse `xml:"SetDailyIndexRefreshTimeResponse,omitempty"` + GetDailyIndexRefreshTime *GetDailyIndexRefreshTimeResponse `xml:"GetDailyIndexRefreshTimeResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type SetFormatArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + DesiredTimeFormat string `xml:"DesiredTimeFormat"` + DesiredDateFormat string `xml:"DesiredDateFormat"` +} +type SetFormatResponse struct { +} + +func (s *Service) SetFormat(httpClient *http.Client, args *SetFormatArgs) (*SetFormatResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetFormat`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetFormat: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetFormat == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.SetFormat()`) + } + + return r.Body.SetFormat, nil +} + +type GetFormatArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetFormatResponse struct { + CurrentTimeFormat string `xml:"CurrentTimeFormat"` + CurrentDateFormat string `xml:"CurrentDateFormat"` +} + +func (s *Service) GetFormat(httpClient *http.Client, args *GetFormatArgs) (*GetFormatResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetFormat`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetFormat: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetFormat == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.GetFormat()`) + } + + return r.Body.GetFormat, nil +} + +type SetTimeZoneArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + Index int32 `xml:"Index"` + AutoAdjustDst bool `xml:"AutoAdjustDst"` +} +type SetTimeZoneResponse struct { +} + +func (s *Service) SetTimeZone(httpClient *http.Client, args *SetTimeZoneArgs) (*SetTimeZoneResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetTimeZone`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetTimeZone: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetTimeZone == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.SetTimeZone()`) + } + + return r.Body.SetTimeZone, nil +} + +type GetTimeZoneArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetTimeZoneResponse struct { + Index int32 `xml:"Index"` + AutoAdjustDst bool `xml:"AutoAdjustDst"` +} + +func (s *Service) GetTimeZone(httpClient *http.Client, args *GetTimeZoneArgs) (*GetTimeZoneResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetTimeZone`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetTimeZone: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetTimeZone == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.GetTimeZone()`) + } + + return r.Body.GetTimeZone, nil +} + +type GetTimeZoneAndRuleArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetTimeZoneAndRuleResponse struct { + Index int32 `xml:"Index"` + AutoAdjustDst bool `xml:"AutoAdjustDst"` + CurrentTimeZone string `xml:"CurrentTimeZone"` +} + +func (s *Service) GetTimeZoneAndRule(httpClient *http.Client, args *GetTimeZoneAndRuleArgs) (*GetTimeZoneAndRuleResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetTimeZoneAndRule`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetTimeZoneAndRule: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetTimeZoneAndRule == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.GetTimeZoneAndRule()`) + } + + return r.Body.GetTimeZoneAndRule, nil +} + +type GetTimeZoneRuleArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + Index int32 `xml:"Index"` +} +type GetTimeZoneRuleResponse struct { + TimeZone string `xml:"TimeZone"` +} + +func (s *Service) GetTimeZoneRule(httpClient *http.Client, args *GetTimeZoneRuleArgs) (*GetTimeZoneRuleResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetTimeZoneRule`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetTimeZoneRule: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetTimeZoneRule == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.GetTimeZoneRule()`) + } + + return r.Body.GetTimeZoneRule, nil +} + +type SetTimeServerArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + DesiredTimeServer string `xml:"DesiredTimeServer"` +} +type SetTimeServerResponse struct { +} + +func (s *Service) SetTimeServer(httpClient *http.Client, args *SetTimeServerArgs) (*SetTimeServerResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetTimeServer`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetTimeServer: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetTimeServer == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.SetTimeServer()`) + } + + return r.Body.SetTimeServer, nil +} + +type GetTimeServerArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetTimeServerResponse struct { + CurrentTimeServer string `xml:"CurrentTimeServer"` +} + +func (s *Service) GetTimeServer(httpClient *http.Client, args *GetTimeServerArgs) (*GetTimeServerResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetTimeServer`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetTimeServer: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetTimeServer == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.GetTimeServer()`) + } + + return r.Body.GetTimeServer, nil +} + +type SetTimeNowArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + DesiredTime string `xml:"DesiredTime"` + TimeZoneForDesiredTime string `xml:"TimeZoneForDesiredTime"` +} +type SetTimeNowResponse struct { +} + +func (s *Service) SetTimeNow(httpClient *http.Client, args *SetTimeNowArgs) (*SetTimeNowResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetTimeNow`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetTimeNow: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetTimeNow == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.SetTimeNow()`) + } + + return r.Body.SetTimeNow, nil +} + +type GetHouseholdTimeAtStampArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + TimeStamp string `xml:"TimeStamp"` +} +type GetHouseholdTimeAtStampResponse struct { + HouseholdUTCTime string `xml:"HouseholdUTCTime"` +} + +func (s *Service) GetHouseholdTimeAtStamp(httpClient *http.Client, args *GetHouseholdTimeAtStampArgs) (*GetHouseholdTimeAtStampResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetHouseholdTimeAtStamp`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetHouseholdTimeAtStamp: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetHouseholdTimeAtStamp == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.GetHouseholdTimeAtStamp()`) + } + + return r.Body.GetHouseholdTimeAtStamp, nil +} + +type GetTimeNowArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetTimeNowResponse struct { + CurrentUTCTime string `xml:"CurrentUTCTime"` + CurrentLocalTime string `xml:"CurrentLocalTime"` + CurrentTimeZone string `xml:"CurrentTimeZone"` + CurrentTimeGeneration uint32 `xml:"CurrentTimeGeneration"` +} + +func (s *Service) GetTimeNow(httpClient *http.Client, args *GetTimeNowArgs) (*GetTimeNowResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetTimeNow`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetTimeNow: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetTimeNow == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.GetTimeNow()`) + } + + return r.Body.GetTimeNow, nil +} + +type CreateAlarmArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + StartLocalTime string `xml:"StartLocalTime"` + Duration string `xml:"Duration"` + // Allowed Value: ONCE + // Allowed Value: WEEKDAYS + // Allowed Value: WEEKENDS + // Allowed Value: DAILY + Recurrence string `xml:"Recurrence"` + Enabled bool `xml:"Enabled"` + RoomUUID string `xml:"RoomUUID"` + ProgramURI string `xml:"ProgramURI"` + ProgramMetaData string `xml:"ProgramMetaData"` + // Allowed Value: NORMAL + // Allowed Value: REPEAT_ALL + // Allowed Value: SHUFFLE_NOREPEAT + // Allowed Value: SHUFFLE + PlayMode string `xml:"PlayMode"` + Volume uint16 `xml:"Volume"` + IncludeLinkedZones bool `xml:"IncludeLinkedZones"` +} +type CreateAlarmResponse struct { + AssignedID uint32 `xml:"AssignedID"` +} + +func (s *Service) CreateAlarm(httpClient *http.Client, args *CreateAlarmArgs) (*CreateAlarmResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`CreateAlarm`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{CreateAlarm: args}, + }) + if err != nil { + return nil, err + } + if r.Body.CreateAlarm == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.CreateAlarm()`) + } + + return r.Body.CreateAlarm, nil +} + +type UpdateAlarmArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ID uint32 `xml:"ID"` + StartLocalTime string `xml:"StartLocalTime"` + Duration string `xml:"Duration"` + // Allowed Value: ONCE + // Allowed Value: WEEKDAYS + // Allowed Value: WEEKENDS + // Allowed Value: DAILY + Recurrence string `xml:"Recurrence"` + Enabled bool `xml:"Enabled"` + RoomUUID string `xml:"RoomUUID"` + ProgramURI string `xml:"ProgramURI"` + ProgramMetaData string `xml:"ProgramMetaData"` + // Allowed Value: NORMAL + // Allowed Value: REPEAT_ALL + // Allowed Value: SHUFFLE_NOREPEAT + // Allowed Value: SHUFFLE + PlayMode string `xml:"PlayMode"` + Volume uint16 `xml:"Volume"` + IncludeLinkedZones bool `xml:"IncludeLinkedZones"` +} +type UpdateAlarmResponse struct { +} + +func (s *Service) UpdateAlarm(httpClient *http.Client, args *UpdateAlarmArgs) (*UpdateAlarmResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`UpdateAlarm`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{UpdateAlarm: args}, + }) + if err != nil { + return nil, err + } + if r.Body.UpdateAlarm == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.UpdateAlarm()`) + } + + return r.Body.UpdateAlarm, nil +} + +type DestroyAlarmArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ID uint32 `xml:"ID"` +} +type DestroyAlarmResponse struct { +} + +func (s *Service) DestroyAlarm(httpClient *http.Client, args *DestroyAlarmArgs) (*DestroyAlarmResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`DestroyAlarm`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{DestroyAlarm: args}, + }) + if err != nil { + return nil, err + } + if r.Body.DestroyAlarm == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.DestroyAlarm()`) + } + + return r.Body.DestroyAlarm, nil +} + +type ListAlarmsArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type ListAlarmsResponse struct { + CurrentAlarmList string `xml:"CurrentAlarmList"` + CurrentAlarmListVersion string `xml:"CurrentAlarmListVersion"` +} + +func (s *Service) ListAlarms(httpClient *http.Client, args *ListAlarmsArgs) (*ListAlarmsResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ListAlarms`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ListAlarms: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ListAlarms == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.ListAlarms()`) + } + + return r.Body.ListAlarms, nil +} + +type SetDailyIndexRefreshTimeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + DesiredDailyIndexRefreshTime string `xml:"DesiredDailyIndexRefreshTime"` +} +type SetDailyIndexRefreshTimeResponse struct { +} + +func (s *Service) SetDailyIndexRefreshTime(httpClient *http.Client, args *SetDailyIndexRefreshTimeArgs) (*SetDailyIndexRefreshTimeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetDailyIndexRefreshTime`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetDailyIndexRefreshTime: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetDailyIndexRefreshTime == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.SetDailyIndexRefreshTime()`) + } + + return r.Body.SetDailyIndexRefreshTime, nil +} + +type GetDailyIndexRefreshTimeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetDailyIndexRefreshTimeResponse struct { + CurrentDailyIndexRefreshTime string `xml:"CurrentDailyIndexRefreshTime"` +} + +func (s *Service) GetDailyIndexRefreshTime(httpClient *http.Client, args *GetDailyIndexRefreshTimeArgs) (*GetDailyIndexRefreshTimeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetDailyIndexRefreshTime`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetDailyIndexRefreshTime: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetDailyIndexRefreshTime == nil { + return nil, errors.New(`unexpected respose from service calling alarmclock.GetDailyIndexRefreshTime()`) + } + + return r.Body.GetDailyIndexRefreshTime, nil +} diff --git a/vendor/github.com/szatmary/sonos/ConnectionManager/ConnectionManager.go b/vendor/github.com/szatmary/sonos/ConnectionManager/ConnectionManager.go new file mode 100644 index 0000000..986ae93 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/ConnectionManager/ConnectionManager.go @@ -0,0 +1,174 @@ +package connectionmanager + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:ConnectionManager:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/MediaServer/ConnectionManager/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/MediaServer/ConnectionManager/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + GetProtocolInfo *GetProtocolInfoArgs `xml:"u:GetProtocolInfo,omitempty"` + GetCurrentConnectionIDs *GetCurrentConnectionIDsArgs `xml:"u:GetCurrentConnectionIDs,omitempty"` + GetCurrentConnectionInfo *GetCurrentConnectionInfoArgs `xml:"u:GetCurrentConnectionInfo,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + GetProtocolInfo *GetProtocolInfoResponse `xml:"GetProtocolInfoResponse,omitempty"` + GetCurrentConnectionIDs *GetCurrentConnectionIDsResponse `xml:"GetCurrentConnectionIDsResponse,omitempty"` + GetCurrentConnectionInfo *GetCurrentConnectionInfoResponse `xml:"GetCurrentConnectionInfoResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type GetProtocolInfoArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetProtocolInfoResponse struct { + Source string `xml:"Source"` + Sink string `xml:"Sink"` +} + +func (s *Service) GetProtocolInfo(httpClient *http.Client, args *GetProtocolInfoArgs) (*GetProtocolInfoResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetProtocolInfo`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetProtocolInfo: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetProtocolInfo == nil { + return nil, errors.New(`unexpected respose from service calling connectionmanager.GetProtocolInfo()`) + } + + return r.Body.GetProtocolInfo, nil +} + +type GetCurrentConnectionIDsArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetCurrentConnectionIDsResponse struct { + ConnectionIDs string `xml:"ConnectionIDs"` +} + +func (s *Service) GetCurrentConnectionIDs(httpClient *http.Client, args *GetCurrentConnectionIDsArgs) (*GetCurrentConnectionIDsResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetCurrentConnectionIDs`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetCurrentConnectionIDs: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetCurrentConnectionIDs == nil { + return nil, errors.New(`unexpected respose from service calling connectionmanager.GetCurrentConnectionIDs()`) + } + + return r.Body.GetCurrentConnectionIDs, nil +} + +type GetCurrentConnectionInfoArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ConnectionID int32 `xml:"ConnectionID"` +} +type GetCurrentConnectionInfoResponse struct { + RcsID int32 `xml:"RcsID"` + AVTransportID int32 `xml:"AVTransportID"` + ProtocolInfo string `xml:"ProtocolInfo"` + PeerConnectionManager string `xml:"PeerConnectionManager"` + PeerConnectionID int32 `xml:"PeerConnectionID"` + Direction string `xml:"Direction"` + Status string `xml:"Status"` +} + +func (s *Service) GetCurrentConnectionInfo(httpClient *http.Client, args *GetCurrentConnectionInfoArgs) (*GetCurrentConnectionInfoResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetCurrentConnectionInfo`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetCurrentConnectionInfo: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetCurrentConnectionInfo == nil { + return nil, errors.New(`unexpected respose from service calling connectionmanager.GetCurrentConnectionInfo()`) + } + + return r.Body.GetCurrentConnectionInfo, nil +} diff --git a/vendor/github.com/szatmary/sonos/ContentDirectory/ContentDirectory.go b/vendor/github.com/szatmary/sonos/ContentDirectory/ContentDirectory.go new file mode 100644 index 0000000..87e05c0 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/ContentDirectory/ContentDirectory.go @@ -0,0 +1,539 @@ +package contentdirectory + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:ContentDirectory:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/MediaServer/ContentDirectory/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/MediaServer/ContentDirectory/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + GetSearchCapabilities *GetSearchCapabilitiesArgs `xml:"u:GetSearchCapabilities,omitempty"` + GetSortCapabilities *GetSortCapabilitiesArgs `xml:"u:GetSortCapabilities,omitempty"` + GetSystemUpdateID *GetSystemUpdateIDArgs `xml:"u:GetSystemUpdateID,omitempty"` + GetAlbumArtistDisplayOption *GetAlbumArtistDisplayOptionArgs `xml:"u:GetAlbumArtistDisplayOption,omitempty"` + GetLastIndexChange *GetLastIndexChangeArgs `xml:"u:GetLastIndexChange,omitempty"` + Browse *BrowseArgs `xml:"u:Browse,omitempty"` + FindPrefix *FindPrefixArgs `xml:"u:FindPrefix,omitempty"` + GetAllPrefixLocations *GetAllPrefixLocationsArgs `xml:"u:GetAllPrefixLocations,omitempty"` + CreateObject *CreateObjectArgs `xml:"u:CreateObject,omitempty"` + UpdateObject *UpdateObjectArgs `xml:"u:UpdateObject,omitempty"` + DestroyObject *DestroyObjectArgs `xml:"u:DestroyObject,omitempty"` + RefreshShareIndex *RefreshShareIndexArgs `xml:"u:RefreshShareIndex,omitempty"` + RequestResort *RequestResortArgs `xml:"u:RequestResort,omitempty"` + GetShareIndexInProgress *GetShareIndexInProgressArgs `xml:"u:GetShareIndexInProgress,omitempty"` + GetBrowseable *GetBrowseableArgs `xml:"u:GetBrowseable,omitempty"` + SetBrowseable *SetBrowseableArgs `xml:"u:SetBrowseable,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + GetSearchCapabilities *GetSearchCapabilitiesResponse `xml:"GetSearchCapabilitiesResponse,omitempty"` + GetSortCapabilities *GetSortCapabilitiesResponse `xml:"GetSortCapabilitiesResponse,omitempty"` + GetSystemUpdateID *GetSystemUpdateIDResponse `xml:"GetSystemUpdateIDResponse,omitempty"` + GetAlbumArtistDisplayOption *GetAlbumArtistDisplayOptionResponse `xml:"GetAlbumArtistDisplayOptionResponse,omitempty"` + GetLastIndexChange *GetLastIndexChangeResponse `xml:"GetLastIndexChangeResponse,omitempty"` + Browse *BrowseResponse `xml:"BrowseResponse,omitempty"` + FindPrefix *FindPrefixResponse `xml:"FindPrefixResponse,omitempty"` + GetAllPrefixLocations *GetAllPrefixLocationsResponse `xml:"GetAllPrefixLocationsResponse,omitempty"` + CreateObject *CreateObjectResponse `xml:"CreateObjectResponse,omitempty"` + UpdateObject *UpdateObjectResponse `xml:"UpdateObjectResponse,omitempty"` + DestroyObject *DestroyObjectResponse `xml:"DestroyObjectResponse,omitempty"` + RefreshShareIndex *RefreshShareIndexResponse `xml:"RefreshShareIndexResponse,omitempty"` + RequestResort *RequestResortResponse `xml:"RequestResortResponse,omitempty"` + GetShareIndexInProgress *GetShareIndexInProgressResponse `xml:"GetShareIndexInProgressResponse,omitempty"` + GetBrowseable *GetBrowseableResponse `xml:"GetBrowseableResponse,omitempty"` + SetBrowseable *SetBrowseableResponse `xml:"SetBrowseableResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type GetSearchCapabilitiesArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetSearchCapabilitiesResponse struct { + SearchCaps string `xml:"SearchCaps"` +} + +func (s *Service) GetSearchCapabilities(httpClient *http.Client, args *GetSearchCapabilitiesArgs) (*GetSearchCapabilitiesResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetSearchCapabilities`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetSearchCapabilities: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetSearchCapabilities == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.GetSearchCapabilities()`) + } + + return r.Body.GetSearchCapabilities, nil +} + +type GetSortCapabilitiesArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetSortCapabilitiesResponse struct { + SortCaps string `xml:"SortCaps"` +} + +func (s *Service) GetSortCapabilities(httpClient *http.Client, args *GetSortCapabilitiesArgs) (*GetSortCapabilitiesResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetSortCapabilities`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetSortCapabilities: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetSortCapabilities == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.GetSortCapabilities()`) + } + + return r.Body.GetSortCapabilities, nil +} + +type GetSystemUpdateIDArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetSystemUpdateIDResponse struct { + Id uint32 `xml:"Id"` +} + +func (s *Service) GetSystemUpdateID(httpClient *http.Client, args *GetSystemUpdateIDArgs) (*GetSystemUpdateIDResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetSystemUpdateID`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetSystemUpdateID: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetSystemUpdateID == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.GetSystemUpdateID()`) + } + + return r.Body.GetSystemUpdateID, nil +} + +type GetAlbumArtistDisplayOptionArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetAlbumArtistDisplayOptionResponse struct { + AlbumArtistDisplayOption string `xml:"AlbumArtistDisplayOption"` +} + +func (s *Service) GetAlbumArtistDisplayOption(httpClient *http.Client, args *GetAlbumArtistDisplayOptionArgs) (*GetAlbumArtistDisplayOptionResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetAlbumArtistDisplayOption`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetAlbumArtistDisplayOption: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetAlbumArtistDisplayOption == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.GetAlbumArtistDisplayOption()`) + } + + return r.Body.GetAlbumArtistDisplayOption, nil +} + +type GetLastIndexChangeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetLastIndexChangeResponse struct { + LastIndexChange string `xml:"LastIndexChange"` +} + +func (s *Service) GetLastIndexChange(httpClient *http.Client, args *GetLastIndexChangeArgs) (*GetLastIndexChangeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetLastIndexChange`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetLastIndexChange: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetLastIndexChange == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.GetLastIndexChange()`) + } + + return r.Body.GetLastIndexChange, nil +} + +type BrowseArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ObjectID string `xml:"ObjectID"` + // Allowed Value: BrowseMetadata + // Allowed Value: BrowseDirectChildren + BrowseFlag string `xml:"BrowseFlag"` + Filter string `xml:"Filter"` + StartingIndex uint32 `xml:"StartingIndex"` + RequestedCount uint32 `xml:"RequestedCount"` + SortCriteria string `xml:"SortCriteria"` +} +type BrowseResponse struct { + Result string `xml:"Result"` + NumberReturned uint32 `xml:"NumberReturned"` + TotalMatches uint32 `xml:"TotalMatches"` + UpdateID uint32 `xml:"UpdateID"` +} + +func (s *Service) Browse(httpClient *http.Client, args *BrowseArgs) (*BrowseResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Browse`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Browse: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Browse == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.Browse()`) + } + + return r.Body.Browse, nil +} + +type FindPrefixArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ObjectID string `xml:"ObjectID"` + Prefix string `xml:"Prefix"` +} +type FindPrefixResponse struct { + StartingIndex uint32 `xml:"StartingIndex"` + UpdateID uint32 `xml:"UpdateID"` +} + +func (s *Service) FindPrefix(httpClient *http.Client, args *FindPrefixArgs) (*FindPrefixResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`FindPrefix`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{FindPrefix: args}, + }) + if err != nil { + return nil, err + } + if r.Body.FindPrefix == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.FindPrefix()`) + } + + return r.Body.FindPrefix, nil +} + +type GetAllPrefixLocationsArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ObjectID string `xml:"ObjectID"` +} +type GetAllPrefixLocationsResponse struct { + TotalPrefixes uint32 `xml:"TotalPrefixes"` + PrefixAndIndexCSV string `xml:"PrefixAndIndexCSV"` + UpdateID uint32 `xml:"UpdateID"` +} + +func (s *Service) GetAllPrefixLocations(httpClient *http.Client, args *GetAllPrefixLocationsArgs) (*GetAllPrefixLocationsResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetAllPrefixLocations`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetAllPrefixLocations: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetAllPrefixLocations == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.GetAllPrefixLocations()`) + } + + return r.Body.GetAllPrefixLocations, nil +} + +type CreateObjectArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ContainerID string `xml:"ContainerID"` + Elements string `xml:"Elements"` +} +type CreateObjectResponse struct { + ObjectID string `xml:"ObjectID"` + Result string `xml:"Result"` +} + +func (s *Service) CreateObject(httpClient *http.Client, args *CreateObjectArgs) (*CreateObjectResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`CreateObject`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{CreateObject: args}, + }) + if err != nil { + return nil, err + } + if r.Body.CreateObject == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.CreateObject()`) + } + + return r.Body.CreateObject, nil +} + +type UpdateObjectArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ObjectID string `xml:"ObjectID"` + CurrentTagValue string `xml:"CurrentTagValue"` + NewTagValue string `xml:"NewTagValue"` +} +type UpdateObjectResponse struct { +} + +func (s *Service) UpdateObject(httpClient *http.Client, args *UpdateObjectArgs) (*UpdateObjectResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`UpdateObject`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{UpdateObject: args}, + }) + if err != nil { + return nil, err + } + if r.Body.UpdateObject == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.UpdateObject()`) + } + + return r.Body.UpdateObject, nil +} + +type DestroyObjectArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ObjectID string `xml:"ObjectID"` +} +type DestroyObjectResponse struct { +} + +func (s *Service) DestroyObject(httpClient *http.Client, args *DestroyObjectArgs) (*DestroyObjectResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`DestroyObject`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{DestroyObject: args}, + }) + if err != nil { + return nil, err + } + if r.Body.DestroyObject == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.DestroyObject()`) + } + + return r.Body.DestroyObject, nil +} + +type RefreshShareIndexArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + AlbumArtistDisplayOption string `xml:"AlbumArtistDisplayOption"` +} +type RefreshShareIndexResponse struct { +} + +func (s *Service) RefreshShareIndex(httpClient *http.Client, args *RefreshShareIndexArgs) (*RefreshShareIndexResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RefreshShareIndex`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RefreshShareIndex: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RefreshShareIndex == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.RefreshShareIndex()`) + } + + return r.Body.RefreshShareIndex, nil +} + +type RequestResortArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + SortOrder string `xml:"SortOrder"` +} +type RequestResortResponse struct { +} + +func (s *Service) RequestResort(httpClient *http.Client, args *RequestResortArgs) (*RequestResortResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RequestResort`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RequestResort: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RequestResort == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.RequestResort()`) + } + + return r.Body.RequestResort, nil +} + +type GetShareIndexInProgressArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetShareIndexInProgressResponse struct { + IsIndexing bool `xml:"IsIndexing"` +} + +func (s *Service) GetShareIndexInProgress(httpClient *http.Client, args *GetShareIndexInProgressArgs) (*GetShareIndexInProgressResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetShareIndexInProgress`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetShareIndexInProgress: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetShareIndexInProgress == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.GetShareIndexInProgress()`) + } + + return r.Body.GetShareIndexInProgress, nil +} + +type GetBrowseableArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetBrowseableResponse struct { + IsBrowseable bool `xml:"IsBrowseable"` +} + +func (s *Service) GetBrowseable(httpClient *http.Client, args *GetBrowseableArgs) (*GetBrowseableResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetBrowseable`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetBrowseable: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetBrowseable == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.GetBrowseable()`) + } + + return r.Body.GetBrowseable, nil +} + +type SetBrowseableArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + Browseable bool `xml:"Browseable"` +} +type SetBrowseableResponse struct { +} + +func (s *Service) SetBrowseable(httpClient *http.Client, args *SetBrowseableArgs) (*SetBrowseableResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetBrowseable`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetBrowseable: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetBrowseable == nil { + return nil, errors.New(`unexpected respose from service calling contentdirectory.SetBrowseable()`) + } + + return r.Body.SetBrowseable, nil +} diff --git a/vendor/github.com/szatmary/sonos/DeviceProperties/DeviceProperties.go b/vendor/github.com/szatmary/sonos/DeviceProperties/DeviceProperties.go new file mode 100644 index 0000000..76638e5 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/DeviceProperties/DeviceProperties.go @@ -0,0 +1,789 @@ +package deviceproperties + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:DeviceProperties:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/DeviceProperties/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/DeviceProperties/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + SetLEDState *SetLEDStateArgs `xml:"u:SetLEDState,omitempty"` + GetLEDState *GetLEDStateArgs `xml:"u:GetLEDState,omitempty"` + AddBondedZones *AddBondedZonesArgs `xml:"u:AddBondedZones,omitempty"` + RemoveBondedZones *RemoveBondedZonesArgs `xml:"u:RemoveBondedZones,omitempty"` + CreateStereoPair *CreateStereoPairArgs `xml:"u:CreateStereoPair,omitempty"` + SeparateStereoPair *SeparateStereoPairArgs `xml:"u:SeparateStereoPair,omitempty"` + SetZoneAttributes *SetZoneAttributesArgs `xml:"u:SetZoneAttributes,omitempty"` + GetZoneAttributes *GetZoneAttributesArgs `xml:"u:GetZoneAttributes,omitempty"` + GetHouseholdID *GetHouseholdIDArgs `xml:"u:GetHouseholdID,omitempty"` + GetZoneInfo *GetZoneInfoArgs `xml:"u:GetZoneInfo,omitempty"` + SetAutoplayLinkedZones *SetAutoplayLinkedZonesArgs `xml:"u:SetAutoplayLinkedZones,omitempty"` + GetAutoplayLinkedZones *GetAutoplayLinkedZonesArgs `xml:"u:GetAutoplayLinkedZones,omitempty"` + SetAutoplayRoomUUID *SetAutoplayRoomUUIDArgs `xml:"u:SetAutoplayRoomUUID,omitempty"` + GetAutoplayRoomUUID *GetAutoplayRoomUUIDArgs `xml:"u:GetAutoplayRoomUUID,omitempty"` + SetAutoplayVolume *SetAutoplayVolumeArgs `xml:"u:SetAutoplayVolume,omitempty"` + GetAutoplayVolume *GetAutoplayVolumeArgs `xml:"u:GetAutoplayVolume,omitempty"` + SetUseAutoplayVolume *SetUseAutoplayVolumeArgs `xml:"u:SetUseAutoplayVolume,omitempty"` + GetUseAutoplayVolume *GetUseAutoplayVolumeArgs `xml:"u:GetUseAutoplayVolume,omitempty"` + AddHTSatellite *AddHTSatelliteArgs `xml:"u:AddHTSatellite,omitempty"` + RemoveHTSatellite *RemoveHTSatelliteArgs `xml:"u:RemoveHTSatellite,omitempty"` + EnterConfigMode *EnterConfigModeArgs `xml:"u:EnterConfigMode,omitempty"` + ExitConfigMode *ExitConfigModeArgs `xml:"u:ExitConfigMode,omitempty"` + GetButtonState *GetButtonStateArgs `xml:"u:GetButtonState,omitempty"` + SetButtonLockState *SetButtonLockStateArgs `xml:"u:SetButtonLockState,omitempty"` + GetButtonLockState *GetButtonLockStateArgs `xml:"u:GetButtonLockState,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + SetLEDState *SetLEDStateResponse `xml:"SetLEDStateResponse,omitempty"` + GetLEDState *GetLEDStateResponse `xml:"GetLEDStateResponse,omitempty"` + AddBondedZones *AddBondedZonesResponse `xml:"AddBondedZonesResponse,omitempty"` + RemoveBondedZones *RemoveBondedZonesResponse `xml:"RemoveBondedZonesResponse,omitempty"` + CreateStereoPair *CreateStereoPairResponse `xml:"CreateStereoPairResponse,omitempty"` + SeparateStereoPair *SeparateStereoPairResponse `xml:"SeparateStereoPairResponse,omitempty"` + SetZoneAttributes *SetZoneAttributesResponse `xml:"SetZoneAttributesResponse,omitempty"` + GetZoneAttributes *GetZoneAttributesResponse `xml:"GetZoneAttributesResponse,omitempty"` + GetHouseholdID *GetHouseholdIDResponse `xml:"GetHouseholdIDResponse,omitempty"` + GetZoneInfo *GetZoneInfoResponse `xml:"GetZoneInfoResponse,omitempty"` + SetAutoplayLinkedZones *SetAutoplayLinkedZonesResponse `xml:"SetAutoplayLinkedZonesResponse,omitempty"` + GetAutoplayLinkedZones *GetAutoplayLinkedZonesResponse `xml:"GetAutoplayLinkedZonesResponse,omitempty"` + SetAutoplayRoomUUID *SetAutoplayRoomUUIDResponse `xml:"SetAutoplayRoomUUIDResponse,omitempty"` + GetAutoplayRoomUUID *GetAutoplayRoomUUIDResponse `xml:"GetAutoplayRoomUUIDResponse,omitempty"` + SetAutoplayVolume *SetAutoplayVolumeResponse `xml:"SetAutoplayVolumeResponse,omitempty"` + GetAutoplayVolume *GetAutoplayVolumeResponse `xml:"GetAutoplayVolumeResponse,omitempty"` + SetUseAutoplayVolume *SetUseAutoplayVolumeResponse `xml:"SetUseAutoplayVolumeResponse,omitempty"` + GetUseAutoplayVolume *GetUseAutoplayVolumeResponse `xml:"GetUseAutoplayVolumeResponse,omitempty"` + AddHTSatellite *AddHTSatelliteResponse `xml:"AddHTSatelliteResponse,omitempty"` + RemoveHTSatellite *RemoveHTSatelliteResponse `xml:"RemoveHTSatelliteResponse,omitempty"` + EnterConfigMode *EnterConfigModeResponse `xml:"EnterConfigModeResponse,omitempty"` + ExitConfigMode *ExitConfigModeResponse `xml:"ExitConfigModeResponse,omitempty"` + GetButtonState *GetButtonStateResponse `xml:"GetButtonStateResponse,omitempty"` + SetButtonLockState *SetButtonLockStateResponse `xml:"SetButtonLockStateResponse,omitempty"` + GetButtonLockState *GetButtonLockStateResponse `xml:"GetButtonLockStateResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type SetLEDStateArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + // Allowed Value: On + // Allowed Value: Off + DesiredLEDState string `xml:"DesiredLEDState"` +} +type SetLEDStateResponse struct { +} + +func (s *Service) SetLEDState(httpClient *http.Client, args *SetLEDStateArgs) (*SetLEDStateResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetLEDState`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetLEDState: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetLEDState == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.SetLEDState()`) + } + + return r.Body.SetLEDState, nil +} + +type GetLEDStateArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetLEDStateResponse struct { + CurrentLEDState string `xml:"CurrentLEDState"` +} + +func (s *Service) GetLEDState(httpClient *http.Client, args *GetLEDStateArgs) (*GetLEDStateResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetLEDState`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetLEDState: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetLEDState == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.GetLEDState()`) + } + + return r.Body.GetLEDState, nil +} + +type AddBondedZonesArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ChannelMapSet string `xml:"ChannelMapSet"` +} +type AddBondedZonesResponse struct { +} + +func (s *Service) AddBondedZones(httpClient *http.Client, args *AddBondedZonesArgs) (*AddBondedZonesResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`AddBondedZones`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{AddBondedZones: args}, + }) + if err != nil { + return nil, err + } + if r.Body.AddBondedZones == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.AddBondedZones()`) + } + + return r.Body.AddBondedZones, nil +} + +type RemoveBondedZonesArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ChannelMapSet string `xml:"ChannelMapSet"` + KeepGrouped bool `xml:"KeepGrouped"` +} +type RemoveBondedZonesResponse struct { +} + +func (s *Service) RemoveBondedZones(httpClient *http.Client, args *RemoveBondedZonesArgs) (*RemoveBondedZonesResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RemoveBondedZones`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RemoveBondedZones: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RemoveBondedZones == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.RemoveBondedZones()`) + } + + return r.Body.RemoveBondedZones, nil +} + +type CreateStereoPairArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ChannelMapSet string `xml:"ChannelMapSet"` +} +type CreateStereoPairResponse struct { +} + +func (s *Service) CreateStereoPair(httpClient *http.Client, args *CreateStereoPairArgs) (*CreateStereoPairResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`CreateStereoPair`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{CreateStereoPair: args}, + }) + if err != nil { + return nil, err + } + if r.Body.CreateStereoPair == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.CreateStereoPair()`) + } + + return r.Body.CreateStereoPair, nil +} + +type SeparateStereoPairArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ChannelMapSet string `xml:"ChannelMapSet"` +} +type SeparateStereoPairResponse struct { +} + +func (s *Service) SeparateStereoPair(httpClient *http.Client, args *SeparateStereoPairArgs) (*SeparateStereoPairResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SeparateStereoPair`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SeparateStereoPair: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SeparateStereoPair == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.SeparateStereoPair()`) + } + + return r.Body.SeparateStereoPair, nil +} + +type SetZoneAttributesArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + DesiredZoneName string `xml:"DesiredZoneName"` + DesiredIcon string `xml:"DesiredIcon"` + DesiredConfiguration string `xml:"DesiredConfiguration"` +} +type SetZoneAttributesResponse struct { +} + +func (s *Service) SetZoneAttributes(httpClient *http.Client, args *SetZoneAttributesArgs) (*SetZoneAttributesResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetZoneAttributes`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetZoneAttributes: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetZoneAttributes == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.SetZoneAttributes()`) + } + + return r.Body.SetZoneAttributes, nil +} + +type GetZoneAttributesArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetZoneAttributesResponse struct { + CurrentZoneName string `xml:"CurrentZoneName"` + CurrentIcon string `xml:"CurrentIcon"` + CurrentConfiguration string `xml:"CurrentConfiguration"` +} + +func (s *Service) GetZoneAttributes(httpClient *http.Client, args *GetZoneAttributesArgs) (*GetZoneAttributesResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetZoneAttributes`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetZoneAttributes: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetZoneAttributes == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.GetZoneAttributes()`) + } + + return r.Body.GetZoneAttributes, nil +} + +type GetHouseholdIDArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetHouseholdIDResponse struct { + CurrentHouseholdID string `xml:"CurrentHouseholdID"` +} + +func (s *Service) GetHouseholdID(httpClient *http.Client, args *GetHouseholdIDArgs) (*GetHouseholdIDResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetHouseholdID`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetHouseholdID: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetHouseholdID == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.GetHouseholdID()`) + } + + return r.Body.GetHouseholdID, nil +} + +type GetZoneInfoArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetZoneInfoResponse struct { + SerialNumber string `xml:"SerialNumber"` + SoftwareVersion string `xml:"SoftwareVersion"` + DisplaySoftwareVersion string `xml:"DisplaySoftwareVersion"` + HardwareVersion string `xml:"HardwareVersion"` + IPAddress string `xml:"IPAddress"` + MACAddress string `xml:"MACAddress"` + CopyrightInfo string `xml:"CopyrightInfo"` + ExtraInfo string `xml:"ExtraInfo"` + HTAudioIn uint32 `xml:"HTAudioIn"` + Flags uint32 `xml:"Flags"` +} + +func (s *Service) GetZoneInfo(httpClient *http.Client, args *GetZoneInfoArgs) (*GetZoneInfoResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetZoneInfo`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetZoneInfo: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetZoneInfo == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.GetZoneInfo()`) + } + + return r.Body.GetZoneInfo, nil +} + +type SetAutoplayLinkedZonesArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + IncludeLinkedZones bool `xml:"IncludeLinkedZones"` + Source string `xml:"Source"` +} +type SetAutoplayLinkedZonesResponse struct { +} + +func (s *Service) SetAutoplayLinkedZones(httpClient *http.Client, args *SetAutoplayLinkedZonesArgs) (*SetAutoplayLinkedZonesResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetAutoplayLinkedZones`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetAutoplayLinkedZones: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetAutoplayLinkedZones == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.SetAutoplayLinkedZones()`) + } + + return r.Body.SetAutoplayLinkedZones, nil +} + +type GetAutoplayLinkedZonesArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + Source string `xml:"Source"` +} +type GetAutoplayLinkedZonesResponse struct { + IncludeLinkedZones bool `xml:"IncludeLinkedZones"` +} + +func (s *Service) GetAutoplayLinkedZones(httpClient *http.Client, args *GetAutoplayLinkedZonesArgs) (*GetAutoplayLinkedZonesResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetAutoplayLinkedZones`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetAutoplayLinkedZones: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetAutoplayLinkedZones == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.GetAutoplayLinkedZones()`) + } + + return r.Body.GetAutoplayLinkedZones, nil +} + +type SetAutoplayRoomUUIDArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + RoomUUID string `xml:"RoomUUID"` + Source string `xml:"Source"` +} +type SetAutoplayRoomUUIDResponse struct { +} + +func (s *Service) SetAutoplayRoomUUID(httpClient *http.Client, args *SetAutoplayRoomUUIDArgs) (*SetAutoplayRoomUUIDResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetAutoplayRoomUUID`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetAutoplayRoomUUID: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetAutoplayRoomUUID == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.SetAutoplayRoomUUID()`) + } + + return r.Body.SetAutoplayRoomUUID, nil +} + +type GetAutoplayRoomUUIDArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + Source string `xml:"Source"` +} +type GetAutoplayRoomUUIDResponse struct { + RoomUUID string `xml:"RoomUUID"` +} + +func (s *Service) GetAutoplayRoomUUID(httpClient *http.Client, args *GetAutoplayRoomUUIDArgs) (*GetAutoplayRoomUUIDResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetAutoplayRoomUUID`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetAutoplayRoomUUID: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetAutoplayRoomUUID == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.GetAutoplayRoomUUID()`) + } + + return r.Body.GetAutoplayRoomUUID, nil +} + +type SetAutoplayVolumeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + // Allowed Range: 0 -> 100 step: 1 + Volume uint16 `xml:"Volume"` + Source string `xml:"Source"` +} +type SetAutoplayVolumeResponse struct { +} + +func (s *Service) SetAutoplayVolume(httpClient *http.Client, args *SetAutoplayVolumeArgs) (*SetAutoplayVolumeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetAutoplayVolume`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetAutoplayVolume: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetAutoplayVolume == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.SetAutoplayVolume()`) + } + + return r.Body.SetAutoplayVolume, nil +} + +type GetAutoplayVolumeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + Source string `xml:"Source"` +} +type GetAutoplayVolumeResponse struct { + CurrentVolume uint16 `xml:"CurrentVolume"` +} + +func (s *Service) GetAutoplayVolume(httpClient *http.Client, args *GetAutoplayVolumeArgs) (*GetAutoplayVolumeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetAutoplayVolume`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetAutoplayVolume: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetAutoplayVolume == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.GetAutoplayVolume()`) + } + + return r.Body.GetAutoplayVolume, nil +} + +type SetUseAutoplayVolumeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + UseVolume bool `xml:"UseVolume"` + Source string `xml:"Source"` +} +type SetUseAutoplayVolumeResponse struct { +} + +func (s *Service) SetUseAutoplayVolume(httpClient *http.Client, args *SetUseAutoplayVolumeArgs) (*SetUseAutoplayVolumeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetUseAutoplayVolume`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetUseAutoplayVolume: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetUseAutoplayVolume == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.SetUseAutoplayVolume()`) + } + + return r.Body.SetUseAutoplayVolume, nil +} + +type GetUseAutoplayVolumeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + Source string `xml:"Source"` +} +type GetUseAutoplayVolumeResponse struct { + UseVolume bool `xml:"UseVolume"` +} + +func (s *Service) GetUseAutoplayVolume(httpClient *http.Client, args *GetUseAutoplayVolumeArgs) (*GetUseAutoplayVolumeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetUseAutoplayVolume`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetUseAutoplayVolume: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetUseAutoplayVolume == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.GetUseAutoplayVolume()`) + } + + return r.Body.GetUseAutoplayVolume, nil +} + +type AddHTSatelliteArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + HTSatChanMapSet string `xml:"HTSatChanMapSet"` +} +type AddHTSatelliteResponse struct { +} + +func (s *Service) AddHTSatellite(httpClient *http.Client, args *AddHTSatelliteArgs) (*AddHTSatelliteResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`AddHTSatellite`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{AddHTSatellite: args}, + }) + if err != nil { + return nil, err + } + if r.Body.AddHTSatellite == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.AddHTSatellite()`) + } + + return r.Body.AddHTSatellite, nil +} + +type RemoveHTSatelliteArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + SatRoomUUID string `xml:"SatRoomUUID"` +} +type RemoveHTSatelliteResponse struct { +} + +func (s *Service) RemoveHTSatellite(httpClient *http.Client, args *RemoveHTSatelliteArgs) (*RemoveHTSatelliteResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RemoveHTSatellite`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RemoveHTSatellite: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RemoveHTSatellite == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.RemoveHTSatellite()`) + } + + return r.Body.RemoveHTSatellite, nil +} + +type EnterConfigModeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + Mode string `xml:"Mode"` + Options string `xml:"Options"` +} +type EnterConfigModeResponse struct { + State string `xml:"State"` +} + +func (s *Service) EnterConfigMode(httpClient *http.Client, args *EnterConfigModeArgs) (*EnterConfigModeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`EnterConfigMode`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{EnterConfigMode: args}, + }) + if err != nil { + return nil, err + } + if r.Body.EnterConfigMode == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.EnterConfigMode()`) + } + + return r.Body.EnterConfigMode, nil +} + +type ExitConfigModeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + Options string `xml:"Options"` +} +type ExitConfigModeResponse struct { +} + +func (s *Service) ExitConfigMode(httpClient *http.Client, args *ExitConfigModeArgs) (*ExitConfigModeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ExitConfigMode`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ExitConfigMode: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ExitConfigMode == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.ExitConfigMode()`) + } + + return r.Body.ExitConfigMode, nil +} + +type GetButtonStateArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetButtonStateResponse struct { + State string `xml:"State"` +} + +func (s *Service) GetButtonState(httpClient *http.Client, args *GetButtonStateArgs) (*GetButtonStateResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetButtonState`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetButtonState: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetButtonState == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.GetButtonState()`) + } + + return r.Body.GetButtonState, nil +} + +type SetButtonLockStateArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + // Allowed Value: On + // Allowed Value: Off + DesiredButtonLockState string `xml:"DesiredButtonLockState"` +} +type SetButtonLockStateResponse struct { +} + +func (s *Service) SetButtonLockState(httpClient *http.Client, args *SetButtonLockStateArgs) (*SetButtonLockStateResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetButtonLockState`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetButtonLockState: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetButtonLockState == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.SetButtonLockState()`) + } + + return r.Body.SetButtonLockState, nil +} + +type GetButtonLockStateArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetButtonLockStateResponse struct { + CurrentButtonLockState string `xml:"CurrentButtonLockState"` +} + +func (s *Service) GetButtonLockState(httpClient *http.Client, args *GetButtonLockStateArgs) (*GetButtonLockStateResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetButtonLockState`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetButtonLockState: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetButtonLockState == nil { + return nil, errors.New(`unexpected respose from service calling deviceproperties.GetButtonLockState()`) + } + + return r.Body.GetButtonLockState, nil +} diff --git a/vendor/github.com/szatmary/sonos/GroupManagement/GroupManagement.go b/vendor/github.com/szatmary/sonos/GroupManagement/GroupManagement.go new file mode 100644 index 0000000..3720577 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/GroupManagement/GroupManagement.go @@ -0,0 +1,200 @@ +package groupmanagement + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:GroupManagement:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/GroupManagement/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/GroupManagement/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + AddMember *AddMemberArgs `xml:"u:AddMember,omitempty"` + RemoveMember *RemoveMemberArgs `xml:"u:RemoveMember,omitempty"` + ReportTrackBufferingResult *ReportTrackBufferingResultArgs `xml:"u:ReportTrackBufferingResult,omitempty"` + SetSourceAreaIds *SetSourceAreaIdsArgs `xml:"u:SetSourceAreaIds,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + AddMember *AddMemberResponse `xml:"AddMemberResponse,omitempty"` + RemoveMember *RemoveMemberResponse `xml:"RemoveMemberResponse,omitempty"` + ReportTrackBufferingResult *ReportTrackBufferingResultResponse `xml:"ReportTrackBufferingResultResponse,omitempty"` + SetSourceAreaIds *SetSourceAreaIdsResponse `xml:"SetSourceAreaIdsResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type AddMemberArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + MemberID string `xml:"MemberID"` + BootSeq uint32 `xml:"BootSeq"` +} +type AddMemberResponse struct { + CurrentTransportSettings string `xml:"CurrentTransportSettings"` + CurrentURI string `xml:"CurrentURI"` + GroupUUIDJoined string `xml:"GroupUUIDJoined"` + ResetVolumeAfter bool `xml:"ResetVolumeAfter"` + VolumeAVTransportURI string `xml:"VolumeAVTransportURI"` +} + +func (s *Service) AddMember(httpClient *http.Client, args *AddMemberArgs) (*AddMemberResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`AddMember`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{AddMember: args}, + }) + if err != nil { + return nil, err + } + if r.Body.AddMember == nil { + return nil, errors.New(`unexpected respose from service calling groupmanagement.AddMember()`) + } + + return r.Body.AddMember, nil +} + +type RemoveMemberArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + MemberID string `xml:"MemberID"` +} +type RemoveMemberResponse struct { +} + +func (s *Service) RemoveMember(httpClient *http.Client, args *RemoveMemberArgs) (*RemoveMemberResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RemoveMember`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RemoveMember: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RemoveMember == nil { + return nil, errors.New(`unexpected respose from service calling groupmanagement.RemoveMember()`) + } + + return r.Body.RemoveMember, nil +} + +type ReportTrackBufferingResultArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + MemberID string `xml:"MemberID"` + ResultCode int32 `xml:"ResultCode"` +} +type ReportTrackBufferingResultResponse struct { +} + +func (s *Service) ReportTrackBufferingResult(httpClient *http.Client, args *ReportTrackBufferingResultArgs) (*ReportTrackBufferingResultResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ReportTrackBufferingResult`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ReportTrackBufferingResult: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ReportTrackBufferingResult == nil { + return nil, errors.New(`unexpected respose from service calling groupmanagement.ReportTrackBufferingResult()`) + } + + return r.Body.ReportTrackBufferingResult, nil +} + +type SetSourceAreaIdsArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + DesiredSourceAreaIds string `xml:"DesiredSourceAreaIds"` +} +type SetSourceAreaIdsResponse struct { +} + +func (s *Service) SetSourceAreaIds(httpClient *http.Client, args *SetSourceAreaIdsArgs) (*SetSourceAreaIdsResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetSourceAreaIds`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetSourceAreaIds: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetSourceAreaIds == nil { + return nil, errors.New(`unexpected respose from service calling groupmanagement.SetSourceAreaIds()`) + } + + return r.Body.SetSourceAreaIds, nil +} diff --git a/vendor/github.com/szatmary/sonos/GroupRenderingControl/GroupRenderingControl.go b/vendor/github.com/szatmary/sonos/GroupRenderingControl/GroupRenderingControl.go new file mode 100644 index 0000000..bd823b8 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/GroupRenderingControl/GroupRenderingControl.go @@ -0,0 +1,254 @@ +package grouprenderingcontrol + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:GroupRenderingControl:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/MediaRenderer/GroupRenderingControl/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/MediaRenderer/GroupRenderingControl/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + GetGroupMute *GetGroupMuteArgs `xml:"u:GetGroupMute,omitempty"` + SetGroupMute *SetGroupMuteArgs `xml:"u:SetGroupMute,omitempty"` + GetGroupVolume *GetGroupVolumeArgs `xml:"u:GetGroupVolume,omitempty"` + SetGroupVolume *SetGroupVolumeArgs `xml:"u:SetGroupVolume,omitempty"` + SetRelativeGroupVolume *SetRelativeGroupVolumeArgs `xml:"u:SetRelativeGroupVolume,omitempty"` + SnapshotGroupVolume *SnapshotGroupVolumeArgs `xml:"u:SnapshotGroupVolume,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + GetGroupMute *GetGroupMuteResponse `xml:"GetGroupMuteResponse,omitempty"` + SetGroupMute *SetGroupMuteResponse `xml:"SetGroupMuteResponse,omitempty"` + GetGroupVolume *GetGroupVolumeResponse `xml:"GetGroupVolumeResponse,omitempty"` + SetGroupVolume *SetGroupVolumeResponse `xml:"SetGroupVolumeResponse,omitempty"` + SetRelativeGroupVolume *SetRelativeGroupVolumeResponse `xml:"SetRelativeGroupVolumeResponse,omitempty"` + SnapshotGroupVolume *SnapshotGroupVolumeResponse `xml:"SnapshotGroupVolumeResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type GetGroupMuteArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetGroupMuteResponse struct { + CurrentMute bool `xml:"CurrentMute"` +} + +func (s *Service) GetGroupMute(httpClient *http.Client, args *GetGroupMuteArgs) (*GetGroupMuteResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetGroupMute`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetGroupMute: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetGroupMute == nil { + return nil, errors.New(`unexpected respose from service calling grouprenderingcontrol.GetGroupMute()`) + } + + return r.Body.GetGroupMute, nil +} + +type SetGroupMuteArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + DesiredMute bool `xml:"DesiredMute"` +} +type SetGroupMuteResponse struct { +} + +func (s *Service) SetGroupMute(httpClient *http.Client, args *SetGroupMuteArgs) (*SetGroupMuteResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetGroupMute`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetGroupMute: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetGroupMute == nil { + return nil, errors.New(`unexpected respose from service calling grouprenderingcontrol.SetGroupMute()`) + } + + return r.Body.SetGroupMute, nil +} + +type GetGroupVolumeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetGroupVolumeResponse struct { + CurrentVolume uint16 `xml:"CurrentVolume"` +} + +func (s *Service) GetGroupVolume(httpClient *http.Client, args *GetGroupVolumeArgs) (*GetGroupVolumeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetGroupVolume`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetGroupVolume: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetGroupVolume == nil { + return nil, errors.New(`unexpected respose from service calling grouprenderingcontrol.GetGroupVolume()`) + } + + return r.Body.GetGroupVolume, nil +} + +type SetGroupVolumeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Range: 0 -> 100 step: 1 + DesiredVolume uint16 `xml:"DesiredVolume"` +} +type SetGroupVolumeResponse struct { +} + +func (s *Service) SetGroupVolume(httpClient *http.Client, args *SetGroupVolumeArgs) (*SetGroupVolumeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetGroupVolume`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetGroupVolume: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetGroupVolume == nil { + return nil, errors.New(`unexpected respose from service calling grouprenderingcontrol.SetGroupVolume()`) + } + + return r.Body.SetGroupVolume, nil +} + +type SetRelativeGroupVolumeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + Adjustment int32 `xml:"Adjustment"` +} +type SetRelativeGroupVolumeResponse struct { + NewVolume uint16 `xml:"NewVolume"` +} + +func (s *Service) SetRelativeGroupVolume(httpClient *http.Client, args *SetRelativeGroupVolumeArgs) (*SetRelativeGroupVolumeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetRelativeGroupVolume`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetRelativeGroupVolume: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetRelativeGroupVolume == nil { + return nil, errors.New(`unexpected respose from service calling grouprenderingcontrol.SetRelativeGroupVolume()`) + } + + return r.Body.SetRelativeGroupVolume, nil +} + +type SnapshotGroupVolumeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type SnapshotGroupVolumeResponse struct { +} + +func (s *Service) SnapshotGroupVolume(httpClient *http.Client, args *SnapshotGroupVolumeArgs) (*SnapshotGroupVolumeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SnapshotGroupVolume`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SnapshotGroupVolume: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SnapshotGroupVolume == nil { + return nil, errors.New(`unexpected respose from service calling grouprenderingcontrol.SnapshotGroupVolume()`) + } + + return r.Body.SnapshotGroupVolume, nil +} diff --git a/vendor/github.com/szatmary/sonos/LICENSE b/vendor/github.com/szatmary/sonos/LICENSE new file mode 100644 index 0000000..1f49b97 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Matthew Szatmary + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/szatmary/sonos/MusicServices/MusicServices.go b/vendor/github.com/szatmary/sonos/MusicServices/MusicServices.go new file mode 100644 index 0000000..48d0926 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/MusicServices/MusicServices.go @@ -0,0 +1,169 @@ +package musicservices + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:MusicServices:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/MusicServices/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/MusicServices/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + GetSessionId *GetSessionIdArgs `xml:"u:GetSessionId,omitempty"` + ListAvailableServices *ListAvailableServicesArgs `xml:"u:ListAvailableServices,omitempty"` + UpdateAvailableServices *UpdateAvailableServicesArgs `xml:"u:UpdateAvailableServices,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + GetSessionId *GetSessionIdResponse `xml:"GetSessionIdResponse,omitempty"` + ListAvailableServices *ListAvailableServicesResponse `xml:"ListAvailableServicesResponse,omitempty"` + UpdateAvailableServices *UpdateAvailableServicesResponse `xml:"UpdateAvailableServicesResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type GetSessionIdArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + ServiceId uint32 `xml:"ServiceId"` + Username string `xml:"Username"` +} +type GetSessionIdResponse struct { + SessionId string `xml:"SessionId"` +} + +func (s *Service) GetSessionId(httpClient *http.Client, args *GetSessionIdArgs) (*GetSessionIdResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetSessionId`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetSessionId: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetSessionId == nil { + return nil, errors.New(`unexpected respose from service calling musicservices.GetSessionId()`) + } + + return r.Body.GetSessionId, nil +} + +type ListAvailableServicesArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type ListAvailableServicesResponse struct { + AvailableServiceDescriptorList string `xml:"AvailableServiceDescriptorList"` + AvailableServiceTypeList string `xml:"AvailableServiceTypeList"` + AvailableServiceListVersion string `xml:"AvailableServiceListVersion"` +} + +func (s *Service) ListAvailableServices(httpClient *http.Client, args *ListAvailableServicesArgs) (*ListAvailableServicesResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ListAvailableServices`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ListAvailableServices: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ListAvailableServices == nil { + return nil, errors.New(`unexpected respose from service calling musicservices.ListAvailableServices()`) + } + + return r.Body.ListAvailableServices, nil +} + +type UpdateAvailableServicesArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type UpdateAvailableServicesResponse struct { +} + +func (s *Service) UpdateAvailableServices(httpClient *http.Client, args *UpdateAvailableServicesArgs) (*UpdateAvailableServicesResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`UpdateAvailableServices`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{UpdateAvailableServices: args}, + }) + if err != nil { + return nil, err + } + if r.Body.UpdateAvailableServices == nil { + return nil, errors.New(`unexpected respose from service calling musicservices.UpdateAvailableServices()`) + } + + return r.Body.UpdateAvailableServices, nil +} diff --git a/vendor/github.com/szatmary/sonos/QPlay/QPlay.go b/vendor/github.com/szatmary/sonos/QPlay/QPlay.go new file mode 100644 index 0000000..7f4af92 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/QPlay/QPlay.go @@ -0,0 +1,115 @@ +package qplay + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:QPlay:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/QPlay/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/QPlay/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + QPlayAuth *QPlayAuthArgs `xml:"u:QPlayAuth,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + QPlayAuth *QPlayAuthResponse `xml:"QPlayAuthResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type QPlayAuthArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + Seed string `xml:"Seed"` +} +type QPlayAuthResponse struct { + Code string `xml:"Code"` + MID string `xml:"MID"` + DID string `xml:"DID"` +} + +func (s *Service) QPlayAuth(httpClient *http.Client, args *QPlayAuthArgs) (*QPlayAuthResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`QPlayAuth`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{QPlayAuth: args}, + }) + if err != nil { + return nil, err + } + if r.Body.QPlayAuth == nil { + return nil, errors.New(`unexpected respose from service calling qplay.QPlayAuth()`) + } + + return r.Body.QPlayAuth, nil +} diff --git a/vendor/github.com/szatmary/sonos/Queue/Queue.go b/vendor/github.com/szatmary/sonos/Queue/Queue.go new file mode 100644 index 0000000..4c6f026 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/Queue/Queue.go @@ -0,0 +1,435 @@ +package queue + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:Queue:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/MediaRenderer/Queue/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/MediaRenderer/Queue/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + AddURI *AddURIArgs `xml:"u:AddURI,omitempty"` + AddMultipleURIs *AddMultipleURIsArgs `xml:"u:AddMultipleURIs,omitempty"` + AttachQueue *AttachQueueArgs `xml:"u:AttachQueue,omitempty"` + Backup *BackupArgs `xml:"u:Backup,omitempty"` + Browse *BrowseArgs `xml:"u:Browse,omitempty"` + CreateQueue *CreateQueueArgs `xml:"u:CreateQueue,omitempty"` + RemoveAllTracks *RemoveAllTracksArgs `xml:"u:RemoveAllTracks,omitempty"` + RemoveTrackRange *RemoveTrackRangeArgs `xml:"u:RemoveTrackRange,omitempty"` + ReorderTracks *ReorderTracksArgs `xml:"u:ReorderTracks,omitempty"` + ReplaceAllTracks *ReplaceAllTracksArgs `xml:"u:ReplaceAllTracks,omitempty"` + SaveAsSonosPlaylist *SaveAsSonosPlaylistArgs `xml:"u:SaveAsSonosPlaylist,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + AddURI *AddURIResponse `xml:"AddURIResponse,omitempty"` + AddMultipleURIs *AddMultipleURIsResponse `xml:"AddMultipleURIsResponse,omitempty"` + AttachQueue *AttachQueueResponse `xml:"AttachQueueResponse,omitempty"` + Backup *BackupResponse `xml:"BackupResponse,omitempty"` + Browse *BrowseResponse `xml:"BrowseResponse,omitempty"` + CreateQueue *CreateQueueResponse `xml:"CreateQueueResponse,omitempty"` + RemoveAllTracks *RemoveAllTracksResponse `xml:"RemoveAllTracksResponse,omitempty"` + RemoveTrackRange *RemoveTrackRangeResponse `xml:"RemoveTrackRangeResponse,omitempty"` + ReorderTracks *ReorderTracksResponse `xml:"ReorderTracksResponse,omitempty"` + ReplaceAllTracks *ReplaceAllTracksResponse `xml:"ReplaceAllTracksResponse,omitempty"` + SaveAsSonosPlaylist *SaveAsSonosPlaylistResponse `xml:"SaveAsSonosPlaylistResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type AddURIArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + QueueID uint32 `xml:"QueueID"` + UpdateID uint32 `xml:"UpdateID"` + EnqueuedURI string `xml:"EnqueuedURI"` + EnqueuedURIMetaData string `xml:"EnqueuedURIMetaData"` + DesiredFirstTrackNumberEnqueued uint32 `xml:"DesiredFirstTrackNumberEnqueued"` + EnqueueAsNext bool `xml:"EnqueueAsNext"` +} +type AddURIResponse struct { + FirstTrackNumberEnqueued uint32 `xml:"FirstTrackNumberEnqueued"` + NumTracksAdded uint32 `xml:"NumTracksAdded"` + NewQueueLength uint32 `xml:"NewQueueLength"` + NewUpdateID uint32 `xml:"NewUpdateID"` +} + +func (s *Service) AddURI(httpClient *http.Client, args *AddURIArgs) (*AddURIResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`AddURI`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{AddURI: args}, + }) + if err != nil { + return nil, err + } + if r.Body.AddURI == nil { + return nil, errors.New(`unexpected respose from service calling queue.AddURI()`) + } + + return r.Body.AddURI, nil +} + +type AddMultipleURIsArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + QueueID uint32 `xml:"QueueID"` + UpdateID uint32 `xml:"UpdateID"` + ContainerURI string `xml:"ContainerURI"` + ContainerMetaData string `xml:"ContainerMetaData"` + DesiredFirstTrackNumberEnqueued uint32 `xml:"DesiredFirstTrackNumberEnqueued"` + EnqueueAsNext bool `xml:"EnqueueAsNext"` + NumberOfURIs uint32 `xml:"NumberOfURIs"` + EnqueuedURIsAndMetaData string `xml:"EnqueuedURIsAndMetaData"` +} +type AddMultipleURIsResponse struct { + FirstTrackNumberEnqueued uint32 `xml:"FirstTrackNumberEnqueued"` + NumTracksAdded uint32 `xml:"NumTracksAdded"` + NewQueueLength uint32 `xml:"NewQueueLength"` + NewUpdateID uint32 `xml:"NewUpdateID"` +} + +func (s *Service) AddMultipleURIs(httpClient *http.Client, args *AddMultipleURIsArgs) (*AddMultipleURIsResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`AddMultipleURIs`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{AddMultipleURIs: args}, + }) + if err != nil { + return nil, err + } + if r.Body.AddMultipleURIs == nil { + return nil, errors.New(`unexpected respose from service calling queue.AddMultipleURIs()`) + } + + return r.Body.AddMultipleURIs, nil +} + +type AttachQueueArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + QueueOwnerID string `xml:"QueueOwnerID"` +} +type AttachQueueResponse struct { + QueueID uint32 `xml:"QueueID"` + QueueOwnerContext string `xml:"QueueOwnerContext"` +} + +func (s *Service) AttachQueue(httpClient *http.Client, args *AttachQueueArgs) (*AttachQueueResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`AttachQueue`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{AttachQueue: args}, + }) + if err != nil { + return nil, err + } + if r.Body.AttachQueue == nil { + return nil, errors.New(`unexpected respose from service calling queue.AttachQueue()`) + } + + return r.Body.AttachQueue, nil +} + +type BackupArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type BackupResponse struct { +} + +func (s *Service) Backup(httpClient *http.Client, args *BackupArgs) (*BackupResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Backup`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Backup: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Backup == nil { + return nil, errors.New(`unexpected respose from service calling queue.Backup()`) + } + + return r.Body.Backup, nil +} + +type BrowseArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + QueueID uint32 `xml:"QueueID"` + StartingIndex uint32 `xml:"StartingIndex"` + RequestedCount uint32 `xml:"RequestedCount"` +} +type BrowseResponse struct { + Result string `xml:"Result"` + NumberReturned uint32 `xml:"NumberReturned"` + TotalMatches uint32 `xml:"TotalMatches"` + UpdateID uint32 `xml:"UpdateID"` +} + +func (s *Service) Browse(httpClient *http.Client, args *BrowseArgs) (*BrowseResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Browse`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Browse: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Browse == nil { + return nil, errors.New(`unexpected respose from service calling queue.Browse()`) + } + + return r.Body.Browse, nil +} + +type CreateQueueArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + QueueOwnerID string `xml:"QueueOwnerID"` + QueueOwnerContext string `xml:"QueueOwnerContext"` + QueuePolicy string `xml:"QueuePolicy"` +} +type CreateQueueResponse struct { + QueueID uint32 `xml:"QueueID"` +} + +func (s *Service) CreateQueue(httpClient *http.Client, args *CreateQueueArgs) (*CreateQueueResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`CreateQueue`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{CreateQueue: args}, + }) + if err != nil { + return nil, err + } + if r.Body.CreateQueue == nil { + return nil, errors.New(`unexpected respose from service calling queue.CreateQueue()`) + } + + return r.Body.CreateQueue, nil +} + +type RemoveAllTracksArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + QueueID uint32 `xml:"QueueID"` + UpdateID uint32 `xml:"UpdateID"` +} +type RemoveAllTracksResponse struct { + NewUpdateID uint32 `xml:"NewUpdateID"` +} + +func (s *Service) RemoveAllTracks(httpClient *http.Client, args *RemoveAllTracksArgs) (*RemoveAllTracksResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RemoveAllTracks`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RemoveAllTracks: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RemoveAllTracks == nil { + return nil, errors.New(`unexpected respose from service calling queue.RemoveAllTracks()`) + } + + return r.Body.RemoveAllTracks, nil +} + +type RemoveTrackRangeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + QueueID uint32 `xml:"QueueID"` + UpdateID uint32 `xml:"UpdateID"` + StartingIndex uint32 `xml:"StartingIndex"` + NumberOfTracks uint32 `xml:"NumberOfTracks"` +} +type RemoveTrackRangeResponse struct { + NewUpdateID uint32 `xml:"NewUpdateID"` +} + +func (s *Service) RemoveTrackRange(httpClient *http.Client, args *RemoveTrackRangeArgs) (*RemoveTrackRangeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RemoveTrackRange`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RemoveTrackRange: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RemoveTrackRange == nil { + return nil, errors.New(`unexpected respose from service calling queue.RemoveTrackRange()`) + } + + return r.Body.RemoveTrackRange, nil +} + +type ReorderTracksArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + QueueID uint32 `xml:"QueueID"` + StartingIndex uint32 `xml:"StartingIndex"` + NumberOfTracks uint32 `xml:"NumberOfTracks"` + InsertBefore uint32 `xml:"InsertBefore"` + UpdateID uint32 `xml:"UpdateID"` +} +type ReorderTracksResponse struct { + NewUpdateID uint32 `xml:"NewUpdateID"` +} + +func (s *Service) ReorderTracks(httpClient *http.Client, args *ReorderTracksArgs) (*ReorderTracksResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ReorderTracks`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ReorderTracks: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ReorderTracks == nil { + return nil, errors.New(`unexpected respose from service calling queue.ReorderTracks()`) + } + + return r.Body.ReorderTracks, nil +} + +type ReplaceAllTracksArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + QueueID uint32 `xml:"QueueID"` + UpdateID uint32 `xml:"UpdateID"` + ContainerURI string `xml:"ContainerURI"` + ContainerMetaData string `xml:"ContainerMetaData"` + CurrentTrackIndex uint32 `xml:"CurrentTrackIndex"` + NewCurrentTrackIndices string `xml:"NewCurrentTrackIndices"` + NumberOfURIs uint32 `xml:"NumberOfURIs"` + EnqueuedURIsAndMetaData string `xml:"EnqueuedURIsAndMetaData"` +} +type ReplaceAllTracksResponse struct { + NewQueueLength uint32 `xml:"NewQueueLength"` + NewUpdateID uint32 `xml:"NewUpdateID"` +} + +func (s *Service) ReplaceAllTracks(httpClient *http.Client, args *ReplaceAllTracksArgs) (*ReplaceAllTracksResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ReplaceAllTracks`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ReplaceAllTracks: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ReplaceAllTracks == nil { + return nil, errors.New(`unexpected respose from service calling queue.ReplaceAllTracks()`) + } + + return r.Body.ReplaceAllTracks, nil +} + +type SaveAsSonosPlaylistArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + QueueID uint32 `xml:"QueueID"` + Title string `xml:"Title"` + ObjectID string `xml:"ObjectID"` +} +type SaveAsSonosPlaylistResponse struct { + AssignedObjectID string `xml:"AssignedObjectID"` +} + +func (s *Service) SaveAsSonosPlaylist(httpClient *http.Client, args *SaveAsSonosPlaylistArgs) (*SaveAsSonosPlaylistResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SaveAsSonosPlaylist`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SaveAsSonosPlaylist: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SaveAsSonosPlaylist == nil { + return nil, errors.New(`unexpected respose from service calling queue.SaveAsSonosPlaylist()`) + } + + return r.Body.SaveAsSonosPlaylist, nil +} diff --git a/vendor/github.com/szatmary/sonos/README.md b/vendor/github.com/szatmary/sonos/README.md new file mode 100644 index 0000000..e69de29 diff --git a/vendor/github.com/szatmary/sonos/RenderingControl/RenderingControl.go b/vendor/github.com/szatmary/sonos/RenderingControl/RenderingControl.go new file mode 100644 index 0000000..8dd78de --- /dev/null +++ b/vendor/github.com/szatmary/sonos/RenderingControl/RenderingControl.go @@ -0,0 +1,940 @@ +package renderingcontrol + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:RenderingControl:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/MediaRenderer/RenderingControl/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/MediaRenderer/RenderingControl/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + GetMute *GetMuteArgs `xml:"u:GetMute,omitempty"` + SetMute *SetMuteArgs `xml:"u:SetMute,omitempty"` + ResetBasicEQ *ResetBasicEQArgs `xml:"u:ResetBasicEQ,omitempty"` + ResetExtEQ *ResetExtEQArgs `xml:"u:ResetExtEQ,omitempty"` + GetVolume *GetVolumeArgs `xml:"u:GetVolume,omitempty"` + SetVolume *SetVolumeArgs `xml:"u:SetVolume,omitempty"` + SetRelativeVolume *SetRelativeVolumeArgs `xml:"u:SetRelativeVolume,omitempty"` + GetVolumeDB *GetVolumeDBArgs `xml:"u:GetVolumeDB,omitempty"` + SetVolumeDB *SetVolumeDBArgs `xml:"u:SetVolumeDB,omitempty"` + GetVolumeDBRange *GetVolumeDBRangeArgs `xml:"u:GetVolumeDBRange,omitempty"` + GetBass *GetBassArgs `xml:"u:GetBass,omitempty"` + SetBass *SetBassArgs `xml:"u:SetBass,omitempty"` + GetTreble *GetTrebleArgs `xml:"u:GetTreble,omitempty"` + SetTreble *SetTrebleArgs `xml:"u:SetTreble,omitempty"` + GetEQ *GetEQArgs `xml:"u:GetEQ,omitempty"` + SetEQ *SetEQArgs `xml:"u:SetEQ,omitempty"` + GetLoudness *GetLoudnessArgs `xml:"u:GetLoudness,omitempty"` + SetLoudness *SetLoudnessArgs `xml:"u:SetLoudness,omitempty"` + GetSupportsOutputFixed *GetSupportsOutputFixedArgs `xml:"u:GetSupportsOutputFixed,omitempty"` + GetOutputFixed *GetOutputFixedArgs `xml:"u:GetOutputFixed,omitempty"` + SetOutputFixed *SetOutputFixedArgs `xml:"u:SetOutputFixed,omitempty"` + GetHeadphoneConnected *GetHeadphoneConnectedArgs `xml:"u:GetHeadphoneConnected,omitempty"` + RampToVolume *RampToVolumeArgs `xml:"u:RampToVolume,omitempty"` + RestoreVolumePriorToRamp *RestoreVolumePriorToRampArgs `xml:"u:RestoreVolumePriorToRamp,omitempty"` + SetChannelMap *SetChannelMapArgs `xml:"u:SetChannelMap,omitempty"` + SetRoomCalibrationX *SetRoomCalibrationXArgs `xml:"u:SetRoomCalibrationX,omitempty"` + GetRoomCalibrationStatus *GetRoomCalibrationStatusArgs `xml:"u:GetRoomCalibrationStatus,omitempty"` + SetRoomCalibrationStatus *SetRoomCalibrationStatusArgs `xml:"u:SetRoomCalibrationStatus,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + GetMute *GetMuteResponse `xml:"GetMuteResponse,omitempty"` + SetMute *SetMuteResponse `xml:"SetMuteResponse,omitempty"` + ResetBasicEQ *ResetBasicEQResponse `xml:"ResetBasicEQResponse,omitempty"` + ResetExtEQ *ResetExtEQResponse `xml:"ResetExtEQResponse,omitempty"` + GetVolume *GetVolumeResponse `xml:"GetVolumeResponse,omitempty"` + SetVolume *SetVolumeResponse `xml:"SetVolumeResponse,omitempty"` + SetRelativeVolume *SetRelativeVolumeResponse `xml:"SetRelativeVolumeResponse,omitempty"` + GetVolumeDB *GetVolumeDBResponse `xml:"GetVolumeDBResponse,omitempty"` + SetVolumeDB *SetVolumeDBResponse `xml:"SetVolumeDBResponse,omitempty"` + GetVolumeDBRange *GetVolumeDBRangeResponse `xml:"GetVolumeDBRangeResponse,omitempty"` + GetBass *GetBassResponse `xml:"GetBassResponse,omitempty"` + SetBass *SetBassResponse `xml:"SetBassResponse,omitempty"` + GetTreble *GetTrebleResponse `xml:"GetTrebleResponse,omitempty"` + SetTreble *SetTrebleResponse `xml:"SetTrebleResponse,omitempty"` + GetEQ *GetEQResponse `xml:"GetEQResponse,omitempty"` + SetEQ *SetEQResponse `xml:"SetEQResponse,omitempty"` + GetLoudness *GetLoudnessResponse `xml:"GetLoudnessResponse,omitempty"` + SetLoudness *SetLoudnessResponse `xml:"SetLoudnessResponse,omitempty"` + GetSupportsOutputFixed *GetSupportsOutputFixedResponse `xml:"GetSupportsOutputFixedResponse,omitempty"` + GetOutputFixed *GetOutputFixedResponse `xml:"GetOutputFixedResponse,omitempty"` + SetOutputFixed *SetOutputFixedResponse `xml:"SetOutputFixedResponse,omitempty"` + GetHeadphoneConnected *GetHeadphoneConnectedResponse `xml:"GetHeadphoneConnectedResponse,omitempty"` + RampToVolume *RampToVolumeResponse `xml:"RampToVolumeResponse,omitempty"` + RestoreVolumePriorToRamp *RestoreVolumePriorToRampResponse `xml:"RestoreVolumePriorToRampResponse,omitempty"` + SetChannelMap *SetChannelMapResponse `xml:"SetChannelMapResponse,omitempty"` + SetRoomCalibrationX *SetRoomCalibrationXResponse `xml:"SetRoomCalibrationXResponse,omitempty"` + GetRoomCalibrationStatus *GetRoomCalibrationStatusResponse `xml:"GetRoomCalibrationStatusResponse,omitempty"` + SetRoomCalibrationStatus *SetRoomCalibrationStatusResponse `xml:"SetRoomCalibrationStatusResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type GetMuteArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: Master + // Allowed Value: LF + // Allowed Value: RF + // Allowed Value: SpeakerOnly + Channel string `xml:"Channel"` +} +type GetMuteResponse struct { + CurrentMute bool `xml:"CurrentMute"` +} + +func (s *Service) GetMute(httpClient *http.Client, args *GetMuteArgs) (*GetMuteResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetMute`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetMute: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetMute == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetMute()`) + } + + return r.Body.GetMute, nil +} + +type SetMuteArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: Master + // Allowed Value: LF + // Allowed Value: RF + // Allowed Value: SpeakerOnly + Channel string `xml:"Channel"` + DesiredMute bool `xml:"DesiredMute"` +} +type SetMuteResponse struct { +} + +func (s *Service) SetMute(httpClient *http.Client, args *SetMuteArgs) (*SetMuteResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetMute`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetMute: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetMute == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetMute()`) + } + + return r.Body.SetMute, nil +} + +type ResetBasicEQArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type ResetBasicEQResponse struct { + Bass int16 `xml:"Bass"` + Treble int16 `xml:"Treble"` + Loudness bool `xml:"Loudness"` + LeftVolume uint16 `xml:"LeftVolume"` + RightVolume uint16 `xml:"RightVolume"` +} + +func (s *Service) ResetBasicEQ(httpClient *http.Client, args *ResetBasicEQArgs) (*ResetBasicEQResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ResetBasicEQ`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ResetBasicEQ: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ResetBasicEQ == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.ResetBasicEQ()`) + } + + return r.Body.ResetBasicEQ, nil +} + +type ResetExtEQArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + EQType string `xml:"EQType"` +} +type ResetExtEQResponse struct { +} + +func (s *Service) ResetExtEQ(httpClient *http.Client, args *ResetExtEQArgs) (*ResetExtEQResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ResetExtEQ`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ResetExtEQ: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ResetExtEQ == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.ResetExtEQ()`) + } + + return r.Body.ResetExtEQ, nil +} + +type GetVolumeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: Master + // Allowed Value: LF + // Allowed Value: RF + Channel string `xml:"Channel"` +} +type GetVolumeResponse struct { + CurrentVolume uint16 `xml:"CurrentVolume"` +} + +func (s *Service) GetVolume(httpClient *http.Client, args *GetVolumeArgs) (*GetVolumeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetVolume`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetVolume: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetVolume == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetVolume()`) + } + + return r.Body.GetVolume, nil +} + +type SetVolumeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: Master + // Allowed Value: LF + // Allowed Value: RF + Channel string `xml:"Channel"` + // Allowed Range: 0 -> 100 step: 1 + DesiredVolume uint16 `xml:"DesiredVolume"` +} +type SetVolumeResponse struct { +} + +func (s *Service) SetVolume(httpClient *http.Client, args *SetVolumeArgs) (*SetVolumeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetVolume`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetVolume: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetVolume == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetVolume()`) + } + + return r.Body.SetVolume, nil +} + +type SetRelativeVolumeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: Master + // Allowed Value: LF + // Allowed Value: RF + Channel string `xml:"Channel"` + Adjustment int32 `xml:"Adjustment"` +} +type SetRelativeVolumeResponse struct { + NewVolume uint16 `xml:"NewVolume"` +} + +func (s *Service) SetRelativeVolume(httpClient *http.Client, args *SetRelativeVolumeArgs) (*SetRelativeVolumeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetRelativeVolume`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetRelativeVolume: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetRelativeVolume == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetRelativeVolume()`) + } + + return r.Body.SetRelativeVolume, nil +} + +type GetVolumeDBArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: Master + // Allowed Value: LF + // Allowed Value: RF + Channel string `xml:"Channel"` +} +type GetVolumeDBResponse struct { + CurrentVolume int16 `xml:"CurrentVolume"` +} + +func (s *Service) GetVolumeDB(httpClient *http.Client, args *GetVolumeDBArgs) (*GetVolumeDBResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetVolumeDB`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetVolumeDB: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetVolumeDB == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetVolumeDB()`) + } + + return r.Body.GetVolumeDB, nil +} + +type SetVolumeDBArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: Master + // Allowed Value: LF + // Allowed Value: RF + Channel string `xml:"Channel"` + DesiredVolume int16 `xml:"DesiredVolume"` +} +type SetVolumeDBResponse struct { +} + +func (s *Service) SetVolumeDB(httpClient *http.Client, args *SetVolumeDBArgs) (*SetVolumeDBResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetVolumeDB`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetVolumeDB: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetVolumeDB == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetVolumeDB()`) + } + + return r.Body.SetVolumeDB, nil +} + +type GetVolumeDBRangeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: Master + // Allowed Value: LF + // Allowed Value: RF + Channel string `xml:"Channel"` +} +type GetVolumeDBRangeResponse struct { + MinValue int16 `xml:"MinValue"` + MaxValue int16 `xml:"MaxValue"` +} + +func (s *Service) GetVolumeDBRange(httpClient *http.Client, args *GetVolumeDBRangeArgs) (*GetVolumeDBRangeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetVolumeDBRange`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetVolumeDBRange: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetVolumeDBRange == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetVolumeDBRange()`) + } + + return r.Body.GetVolumeDBRange, nil +} + +type GetBassArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetBassResponse struct { + CurrentBass int16 `xml:"CurrentBass"` +} + +func (s *Service) GetBass(httpClient *http.Client, args *GetBassArgs) (*GetBassResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetBass`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetBass: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetBass == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetBass()`) + } + + return r.Body.GetBass, nil +} + +type SetBassArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Range: -10 -> 10 step: 1 + DesiredBass int16 `xml:"DesiredBass"` +} +type SetBassResponse struct { +} + +func (s *Service) SetBass(httpClient *http.Client, args *SetBassArgs) (*SetBassResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetBass`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetBass: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetBass == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetBass()`) + } + + return r.Body.SetBass, nil +} + +type GetTrebleArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetTrebleResponse struct { + CurrentTreble int16 `xml:"CurrentTreble"` +} + +func (s *Service) GetTreble(httpClient *http.Client, args *GetTrebleArgs) (*GetTrebleResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetTreble`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetTreble: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetTreble == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetTreble()`) + } + + return r.Body.GetTreble, nil +} + +type SetTrebleArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Range: -10 -> 10 step: 1 + DesiredTreble int16 `xml:"DesiredTreble"` +} +type SetTrebleResponse struct { +} + +func (s *Service) SetTreble(httpClient *http.Client, args *SetTrebleArgs) (*SetTrebleResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetTreble`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetTreble: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetTreble == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetTreble()`) + } + + return r.Body.SetTreble, nil +} + +type GetEQArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + EQType string `xml:"EQType"` +} +type GetEQResponse struct { + CurrentValue int16 `xml:"CurrentValue"` +} + +func (s *Service) GetEQ(httpClient *http.Client, args *GetEQArgs) (*GetEQResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetEQ`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetEQ: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetEQ == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetEQ()`) + } + + return r.Body.GetEQ, nil +} + +type SetEQArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + EQType string `xml:"EQType"` + DesiredValue int16 `xml:"DesiredValue"` +} +type SetEQResponse struct { +} + +func (s *Service) SetEQ(httpClient *http.Client, args *SetEQArgs) (*SetEQResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetEQ`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetEQ: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetEQ == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetEQ()`) + } + + return r.Body.SetEQ, nil +} + +type GetLoudnessArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: Master + // Allowed Value: LF + // Allowed Value: RF + Channel string `xml:"Channel"` +} +type GetLoudnessResponse struct { + CurrentLoudness bool `xml:"CurrentLoudness"` +} + +func (s *Service) GetLoudness(httpClient *http.Client, args *GetLoudnessArgs) (*GetLoudnessResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetLoudness`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetLoudness: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetLoudness == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetLoudness()`) + } + + return r.Body.GetLoudness, nil +} + +type SetLoudnessArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: Master + // Allowed Value: LF + // Allowed Value: RF + Channel string `xml:"Channel"` + DesiredLoudness bool `xml:"DesiredLoudness"` +} +type SetLoudnessResponse struct { +} + +func (s *Service) SetLoudness(httpClient *http.Client, args *SetLoudnessArgs) (*SetLoudnessResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetLoudness`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetLoudness: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetLoudness == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetLoudness()`) + } + + return r.Body.SetLoudness, nil +} + +type GetSupportsOutputFixedArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetSupportsOutputFixedResponse struct { + CurrentSupportsFixed bool `xml:"CurrentSupportsFixed"` +} + +func (s *Service) GetSupportsOutputFixed(httpClient *http.Client, args *GetSupportsOutputFixedArgs) (*GetSupportsOutputFixedResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetSupportsOutputFixed`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetSupportsOutputFixed: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetSupportsOutputFixed == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetSupportsOutputFixed()`) + } + + return r.Body.GetSupportsOutputFixed, nil +} + +type GetOutputFixedArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetOutputFixedResponse struct { + CurrentFixed bool `xml:"CurrentFixed"` +} + +func (s *Service) GetOutputFixed(httpClient *http.Client, args *GetOutputFixedArgs) (*GetOutputFixedResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetOutputFixed`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetOutputFixed: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetOutputFixed == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetOutputFixed()`) + } + + return r.Body.GetOutputFixed, nil +} + +type SetOutputFixedArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + DesiredFixed bool `xml:"DesiredFixed"` +} +type SetOutputFixedResponse struct { +} + +func (s *Service) SetOutputFixed(httpClient *http.Client, args *SetOutputFixedArgs) (*SetOutputFixedResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetOutputFixed`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetOutputFixed: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetOutputFixed == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetOutputFixed()`) + } + + return r.Body.SetOutputFixed, nil +} + +type GetHeadphoneConnectedArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetHeadphoneConnectedResponse struct { + CurrentHeadphoneConnected bool `xml:"CurrentHeadphoneConnected"` +} + +func (s *Service) GetHeadphoneConnected(httpClient *http.Client, args *GetHeadphoneConnectedArgs) (*GetHeadphoneConnectedResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetHeadphoneConnected`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetHeadphoneConnected: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetHeadphoneConnected == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetHeadphoneConnected()`) + } + + return r.Body.GetHeadphoneConnected, nil +} + +type RampToVolumeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: Master + // Allowed Value: LF + // Allowed Value: RF + Channel string `xml:"Channel"` + // Allowed Value: SLEEP_TIMER_RAMP_TYPE + // Allowed Value: ALARM_RAMP_TYPE + // Allowed Value: AUTOPLAY_RAMP_TYPE + RampType string `xml:"RampType"` + // Allowed Range: 0 -> 100 step: 1 + DesiredVolume uint16 `xml:"DesiredVolume"` + ResetVolumeAfter bool `xml:"ResetVolumeAfter"` + ProgramURI string `xml:"ProgramURI"` +} +type RampToVolumeResponse struct { + RampTime uint32 `xml:"RampTime"` +} + +func (s *Service) RampToVolume(httpClient *http.Client, args *RampToVolumeArgs) (*RampToVolumeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RampToVolume`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RampToVolume: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RampToVolume == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.RampToVolume()`) + } + + return r.Body.RampToVolume, nil +} + +type RestoreVolumePriorToRampArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + // Allowed Value: Master + // Allowed Value: LF + // Allowed Value: RF + Channel string `xml:"Channel"` +} +type RestoreVolumePriorToRampResponse struct { +} + +func (s *Service) RestoreVolumePriorToRamp(httpClient *http.Client, args *RestoreVolumePriorToRampArgs) (*RestoreVolumePriorToRampResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RestoreVolumePriorToRamp`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RestoreVolumePriorToRamp: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RestoreVolumePriorToRamp == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.RestoreVolumePriorToRamp()`) + } + + return r.Body.RestoreVolumePriorToRamp, nil +} + +type SetChannelMapArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + ChannelMap string `xml:"ChannelMap"` +} +type SetChannelMapResponse struct { +} + +func (s *Service) SetChannelMap(httpClient *http.Client, args *SetChannelMapArgs) (*SetChannelMapResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetChannelMap`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetChannelMap: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetChannelMap == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetChannelMap()`) + } + + return r.Body.SetChannelMap, nil +} + +type SetRoomCalibrationXArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + CalibrationID string `xml:"CalibrationID"` + Coefficients string `xml:"Coefficients"` + CalibrationMode string `xml:"CalibrationMode"` +} +type SetRoomCalibrationXResponse struct { +} + +func (s *Service) SetRoomCalibrationX(httpClient *http.Client, args *SetRoomCalibrationXArgs) (*SetRoomCalibrationXResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetRoomCalibrationX`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetRoomCalibrationX: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetRoomCalibrationX == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetRoomCalibrationX()`) + } + + return r.Body.SetRoomCalibrationX, nil +} + +type GetRoomCalibrationStatusArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type GetRoomCalibrationStatusResponse struct { + RoomCalibrationEnabled bool `xml:"RoomCalibrationEnabled"` + RoomCalibrationAvailable bool `xml:"RoomCalibrationAvailable"` +} + +func (s *Service) GetRoomCalibrationStatus(httpClient *http.Client, args *GetRoomCalibrationStatusArgs) (*GetRoomCalibrationStatusResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetRoomCalibrationStatus`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetRoomCalibrationStatus: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetRoomCalibrationStatus == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetRoomCalibrationStatus()`) + } + + return r.Body.GetRoomCalibrationStatus, nil +} + +type SetRoomCalibrationStatusArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + RoomCalibrationEnabled bool `xml:"RoomCalibrationEnabled"` +} +type SetRoomCalibrationStatusResponse struct { +} + +func (s *Service) SetRoomCalibrationStatus(httpClient *http.Client, args *SetRoomCalibrationStatusArgs) (*SetRoomCalibrationStatusResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetRoomCalibrationStatus`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetRoomCalibrationStatus: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetRoomCalibrationStatus == nil { + return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetRoomCalibrationStatus()`) + } + + return r.Body.SetRoomCalibrationStatus, nil +} diff --git a/vendor/github.com/szatmary/sonos/SystemProperties/SystemProperties.go b/vendor/github.com/szatmary/sonos/SystemProperties/SystemProperties.go new file mode 100644 index 0000000..c099216 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/SystemProperties/SystemProperties.go @@ -0,0 +1,576 @@ +package systemproperties + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:SystemProperties:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/SystemProperties/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/SystemProperties/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + SetString *SetStringArgs `xml:"u:SetString,omitempty"` + GetString *GetStringArgs `xml:"u:GetString,omitempty"` + Remove *RemoveArgs `xml:"u:Remove,omitempty"` + GetWebCode *GetWebCodeArgs `xml:"u:GetWebCode,omitempty"` + ProvisionCredentialedTrialAccountX *ProvisionCredentialedTrialAccountXArgs `xml:"u:ProvisionCredentialedTrialAccountX,omitempty"` + AddAccountX *AddAccountXArgs `xml:"u:AddAccountX,omitempty"` + AddOAuthAccountX *AddOAuthAccountXArgs `xml:"u:AddOAuthAccountX,omitempty"` + RemoveAccount *RemoveAccountArgs `xml:"u:RemoveAccount,omitempty"` + EditAccountPasswordX *EditAccountPasswordXArgs `xml:"u:EditAccountPasswordX,omitempty"` + SetAccountNicknameX *SetAccountNicknameXArgs `xml:"u:SetAccountNicknameX,omitempty"` + RefreshAccountCredentialsX *RefreshAccountCredentialsXArgs `xml:"u:RefreshAccountCredentialsX,omitempty"` + EditAccountMd *EditAccountMdArgs `xml:"u:EditAccountMd,omitempty"` + DoPostUpdateTasks *DoPostUpdateTasksArgs `xml:"u:DoPostUpdateTasks,omitempty"` + ResetThirdPartyCredentials *ResetThirdPartyCredentialsArgs `xml:"u:ResetThirdPartyCredentials,omitempty"` + EnableRDM *EnableRDMArgs `xml:"u:EnableRDM,omitempty"` + GetRDM *GetRDMArgs `xml:"u:GetRDM,omitempty"` + ReplaceAccountX *ReplaceAccountXArgs `xml:"u:ReplaceAccountX,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + SetString *SetStringResponse `xml:"SetStringResponse,omitempty"` + GetString *GetStringResponse `xml:"GetStringResponse,omitempty"` + Remove *RemoveResponse `xml:"RemoveResponse,omitempty"` + GetWebCode *GetWebCodeResponse `xml:"GetWebCodeResponse,omitempty"` + ProvisionCredentialedTrialAccountX *ProvisionCredentialedTrialAccountXResponse `xml:"ProvisionCredentialedTrialAccountXResponse,omitempty"` + AddAccountX *AddAccountXResponse `xml:"AddAccountXResponse,omitempty"` + AddOAuthAccountX *AddOAuthAccountXResponse `xml:"AddOAuthAccountXResponse,omitempty"` + RemoveAccount *RemoveAccountResponse `xml:"RemoveAccountResponse,omitempty"` + EditAccountPasswordX *EditAccountPasswordXResponse `xml:"EditAccountPasswordXResponse,omitempty"` + SetAccountNicknameX *SetAccountNicknameXResponse `xml:"SetAccountNicknameXResponse,omitempty"` + RefreshAccountCredentialsX *RefreshAccountCredentialsXResponse `xml:"RefreshAccountCredentialsXResponse,omitempty"` + EditAccountMd *EditAccountMdResponse `xml:"EditAccountMdResponse,omitempty"` + DoPostUpdateTasks *DoPostUpdateTasksResponse `xml:"DoPostUpdateTasksResponse,omitempty"` + ResetThirdPartyCredentials *ResetThirdPartyCredentialsResponse `xml:"ResetThirdPartyCredentialsResponse,omitempty"` + EnableRDM *EnableRDMResponse `xml:"EnableRDMResponse,omitempty"` + GetRDM *GetRDMResponse `xml:"GetRDMResponse,omitempty"` + ReplaceAccountX *ReplaceAccountXResponse `xml:"ReplaceAccountXResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type SetStringArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + VariableName string `xml:"VariableName"` + StringValue string `xml:"StringValue"` +} +type SetStringResponse struct { +} + +func (s *Service) SetString(httpClient *http.Client, args *SetStringArgs) (*SetStringResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetString`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetString: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetString == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.SetString()`) + } + + return r.Body.SetString, nil +} + +type GetStringArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + VariableName string `xml:"VariableName"` +} +type GetStringResponse struct { + StringValue string `xml:"StringValue"` +} + +func (s *Service) GetString(httpClient *http.Client, args *GetStringArgs) (*GetStringResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetString`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetString: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetString == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.GetString()`) + } + + return r.Body.GetString, nil +} + +type RemoveArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + VariableName string `xml:"VariableName"` +} +type RemoveResponse struct { +} + +func (s *Service) Remove(httpClient *http.Client, args *RemoveArgs) (*RemoveResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Remove`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Remove: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Remove == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.Remove()`) + } + + return r.Body.Remove, nil +} + +type GetWebCodeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + AccountType uint32 `xml:"AccountType"` +} +type GetWebCodeResponse struct { + WebCode string `xml:"WebCode"` +} + +func (s *Service) GetWebCode(httpClient *http.Client, args *GetWebCodeArgs) (*GetWebCodeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetWebCode`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetWebCode: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetWebCode == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.GetWebCode()`) + } + + return r.Body.GetWebCode, nil +} + +type ProvisionCredentialedTrialAccountXArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + AccountType uint32 `xml:"AccountType"` + AccountID string `xml:"AccountID"` + AccountPassword string `xml:"AccountPassword"` +} +type ProvisionCredentialedTrialAccountXResponse struct { + IsExpired bool `xml:"IsExpired"` + AccountUDN string `xml:"AccountUDN"` +} + +func (s *Service) ProvisionCredentialedTrialAccountX(httpClient *http.Client, args *ProvisionCredentialedTrialAccountXArgs) (*ProvisionCredentialedTrialAccountXResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ProvisionCredentialedTrialAccountX`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ProvisionCredentialedTrialAccountX: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ProvisionCredentialedTrialAccountX == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.ProvisionCredentialedTrialAccountX()`) + } + + return r.Body.ProvisionCredentialedTrialAccountX, nil +} + +type AddAccountXArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + AccountType uint32 `xml:"AccountType"` + AccountID string `xml:"AccountID"` + AccountPassword string `xml:"AccountPassword"` +} +type AddAccountXResponse struct { + AccountUDN string `xml:"AccountUDN"` +} + +func (s *Service) AddAccountX(httpClient *http.Client, args *AddAccountXArgs) (*AddAccountXResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`AddAccountX`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{AddAccountX: args}, + }) + if err != nil { + return nil, err + } + if r.Body.AddAccountX == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.AddAccountX()`) + } + + return r.Body.AddAccountX, nil +} + +type AddOAuthAccountXArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + AccountType uint32 `xml:"AccountType"` + AccountToken string `xml:"AccountToken"` + AccountKey string `xml:"AccountKey"` + OAuthDeviceID string `xml:"OAuthDeviceID"` + AuthorizationCode string `xml:"AuthorizationCode"` + RedirectURI string `xml:"RedirectURI"` + UserIdHashCode string `xml:"UserIdHashCode"` + AccountTier uint32 `xml:"AccountTier"` +} +type AddOAuthAccountXResponse struct { + AccountUDN string `xml:"AccountUDN"` + AccountNickname string `xml:"AccountNickname"` +} + +func (s *Service) AddOAuthAccountX(httpClient *http.Client, args *AddOAuthAccountXArgs) (*AddOAuthAccountXResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`AddOAuthAccountX`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{AddOAuthAccountX: args}, + }) + if err != nil { + return nil, err + } + if r.Body.AddOAuthAccountX == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.AddOAuthAccountX()`) + } + + return r.Body.AddOAuthAccountX, nil +} + +type RemoveAccountArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + AccountType uint32 `xml:"AccountType"` + AccountID string `xml:"AccountID"` +} +type RemoveAccountResponse struct { +} + +func (s *Service) RemoveAccount(httpClient *http.Client, args *RemoveAccountArgs) (*RemoveAccountResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RemoveAccount`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RemoveAccount: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RemoveAccount == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.RemoveAccount()`) + } + + return r.Body.RemoveAccount, nil +} + +type EditAccountPasswordXArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + AccountType uint32 `xml:"AccountType"` + AccountID string `xml:"AccountID"` + NewAccountPassword string `xml:"NewAccountPassword"` +} +type EditAccountPasswordXResponse struct { +} + +func (s *Service) EditAccountPasswordX(httpClient *http.Client, args *EditAccountPasswordXArgs) (*EditAccountPasswordXResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`EditAccountPasswordX`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{EditAccountPasswordX: args}, + }) + if err != nil { + return nil, err + } + if r.Body.EditAccountPasswordX == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.EditAccountPasswordX()`) + } + + return r.Body.EditAccountPasswordX, nil +} + +type SetAccountNicknameXArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + AccountUDN string `xml:"AccountUDN"` + AccountNickname string `xml:"AccountNickname"` +} +type SetAccountNicknameXResponse struct { +} + +func (s *Service) SetAccountNicknameX(httpClient *http.Client, args *SetAccountNicknameXArgs) (*SetAccountNicknameXResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetAccountNicknameX`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetAccountNicknameX: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetAccountNicknameX == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.SetAccountNicknameX()`) + } + + return r.Body.SetAccountNicknameX, nil +} + +type RefreshAccountCredentialsXArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + AccountType uint32 `xml:"AccountType"` + AccountUID uint32 `xml:"AccountUID"` + AccountToken string `xml:"AccountToken"` + AccountKey string `xml:"AccountKey"` +} +type RefreshAccountCredentialsXResponse struct { +} + +func (s *Service) RefreshAccountCredentialsX(httpClient *http.Client, args *RefreshAccountCredentialsXArgs) (*RefreshAccountCredentialsXResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RefreshAccountCredentialsX`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RefreshAccountCredentialsX: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RefreshAccountCredentialsX == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.RefreshAccountCredentialsX()`) + } + + return r.Body.RefreshAccountCredentialsX, nil +} + +type EditAccountMdArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + AccountType uint32 `xml:"AccountType"` + AccountID string `xml:"AccountID"` + NewAccountMd string `xml:"NewAccountMd"` +} +type EditAccountMdResponse struct { +} + +func (s *Service) EditAccountMd(httpClient *http.Client, args *EditAccountMdArgs) (*EditAccountMdResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`EditAccountMd`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{EditAccountMd: args}, + }) + if err != nil { + return nil, err + } + if r.Body.EditAccountMd == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.EditAccountMd()`) + } + + return r.Body.EditAccountMd, nil +} + +type DoPostUpdateTasksArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type DoPostUpdateTasksResponse struct { +} + +func (s *Service) DoPostUpdateTasks(httpClient *http.Client, args *DoPostUpdateTasksArgs) (*DoPostUpdateTasksResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`DoPostUpdateTasks`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{DoPostUpdateTasks: args}, + }) + if err != nil { + return nil, err + } + if r.Body.DoPostUpdateTasks == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.DoPostUpdateTasks()`) + } + + return r.Body.DoPostUpdateTasks, nil +} + +type ResetThirdPartyCredentialsArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type ResetThirdPartyCredentialsResponse struct { +} + +func (s *Service) ResetThirdPartyCredentials(httpClient *http.Client, args *ResetThirdPartyCredentialsArgs) (*ResetThirdPartyCredentialsResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ResetThirdPartyCredentials`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ResetThirdPartyCredentials: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ResetThirdPartyCredentials == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.ResetThirdPartyCredentials()`) + } + + return r.Body.ResetThirdPartyCredentials, nil +} + +type EnableRDMArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + RDMValue bool `xml:"RDMValue"` +} +type EnableRDMResponse struct { +} + +func (s *Service) EnableRDM(httpClient *http.Client, args *EnableRDMArgs) (*EnableRDMResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`EnableRDM`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{EnableRDM: args}, + }) + if err != nil { + return nil, err + } + if r.Body.EnableRDM == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.EnableRDM()`) + } + + return r.Body.EnableRDM, nil +} + +type GetRDMArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetRDMResponse struct { + RDMValue bool `xml:"RDMValue"` +} + +func (s *Service) GetRDM(httpClient *http.Client, args *GetRDMArgs) (*GetRDMResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetRDM`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetRDM: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetRDM == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.GetRDM()`) + } + + return r.Body.GetRDM, nil +} + +type ReplaceAccountXArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + AccountUDN string `xml:"AccountUDN"` + NewAccountID string `xml:"NewAccountID"` + NewAccountPassword string `xml:"NewAccountPassword"` + AccountToken string `xml:"AccountToken"` + AccountKey string `xml:"AccountKey"` + OAuthDeviceID string `xml:"OAuthDeviceID"` +} +type ReplaceAccountXResponse struct { + NewAccountUDN string `xml:"NewAccountUDN"` +} + +func (s *Service) ReplaceAccountX(httpClient *http.Client, args *ReplaceAccountXArgs) (*ReplaceAccountXResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ReplaceAccountX`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ReplaceAccountX: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ReplaceAccountX == nil { + return nil, errors.New(`unexpected respose from service calling systemproperties.ReplaceAccountX()`) + } + + return r.Body.ReplaceAccountX, nil +} diff --git a/vendor/github.com/szatmary/sonos/VirtualLineIn/VirtualLineIn.go b/vendor/github.com/szatmary/sonos/VirtualLineIn/VirtualLineIn.go new file mode 100644 index 0000000..508579d --- /dev/null +++ b/vendor/github.com/szatmary/sonos/VirtualLineIn/VirtualLineIn.go @@ -0,0 +1,306 @@ +package virtuallinein + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:VirtualLineIn:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/MediaRenderer/VirtualLineIn/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/MediaRenderer/VirtualLineIn/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + StartTransmission *StartTransmissionArgs `xml:"u:StartTransmission,omitempty"` + StopTransmission *StopTransmissionArgs `xml:"u:StopTransmission,omitempty"` + Play *PlayArgs `xml:"u:Play,omitempty"` + Pause *PauseArgs `xml:"u:Pause,omitempty"` + Next *NextArgs `xml:"u:Next,omitempty"` + Previous *PreviousArgs `xml:"u:Previous,omitempty"` + Stop *StopArgs `xml:"u:Stop,omitempty"` + SetVolume *SetVolumeArgs `xml:"u:SetVolume,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + StartTransmission *StartTransmissionResponse `xml:"StartTransmissionResponse,omitempty"` + StopTransmission *StopTransmissionResponse `xml:"StopTransmissionResponse,omitempty"` + Play *PlayResponse `xml:"PlayResponse,omitempty"` + Pause *PauseResponse `xml:"PauseResponse,omitempty"` + Next *NextResponse `xml:"NextResponse,omitempty"` + Previous *PreviousResponse `xml:"PreviousResponse,omitempty"` + Stop *StopResponse `xml:"StopResponse,omitempty"` + SetVolume *SetVolumeResponse `xml:"SetVolumeResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type StartTransmissionArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + CoordinatorID string `xml:"CoordinatorID"` +} +type StartTransmissionResponse struct { + CurrentTransportSettings string `xml:"CurrentTransportSettings"` +} + +func (s *Service) StartTransmission(httpClient *http.Client, args *StartTransmissionArgs) (*StartTransmissionResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`StartTransmission`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{StartTransmission: args}, + }) + if err != nil { + return nil, err + } + if r.Body.StartTransmission == nil { + return nil, errors.New(`unexpected respose from service calling virtuallinein.StartTransmission()`) + } + + return r.Body.StartTransmission, nil +} + +type StopTransmissionArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + CoordinatorID string `xml:"CoordinatorID"` +} +type StopTransmissionResponse struct { +} + +func (s *Service) StopTransmission(httpClient *http.Client, args *StopTransmissionArgs) (*StopTransmissionResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`StopTransmission`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{StopTransmission: args}, + }) + if err != nil { + return nil, err + } + if r.Body.StopTransmission == nil { + return nil, errors.New(`unexpected respose from service calling virtuallinein.StopTransmission()`) + } + + return r.Body.StopTransmission, nil +} + +type PlayArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + Speed string `xml:"Speed"` +} +type PlayResponse struct { +} + +func (s *Service) Play(httpClient *http.Client, args *PlayArgs) (*PlayResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Play`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Play: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Play == nil { + return nil, errors.New(`unexpected respose from service calling virtuallinein.Play()`) + } + + return r.Body.Play, nil +} + +type PauseArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type PauseResponse struct { +} + +func (s *Service) Pause(httpClient *http.Client, args *PauseArgs) (*PauseResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Pause`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Pause: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Pause == nil { + return nil, errors.New(`unexpected respose from service calling virtuallinein.Pause()`) + } + + return r.Body.Pause, nil +} + +type NextArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type NextResponse struct { +} + +func (s *Service) Next(httpClient *http.Client, args *NextArgs) (*NextResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Next`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Next: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Next == nil { + return nil, errors.New(`unexpected respose from service calling virtuallinein.Next()`) + } + + return r.Body.Next, nil +} + +type PreviousArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type PreviousResponse struct { +} + +func (s *Service) Previous(httpClient *http.Client, args *PreviousArgs) (*PreviousResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Previous`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Previous: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Previous == nil { + return nil, errors.New(`unexpected respose from service calling virtuallinein.Previous()`) + } + + return r.Body.Previous, nil +} + +type StopArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` +} +type StopResponse struct { +} + +func (s *Service) Stop(httpClient *http.Client, args *StopArgs) (*StopResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`Stop`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{Stop: args}, + }) + if err != nil { + return nil, err + } + if r.Body.Stop == nil { + return nil, errors.New(`unexpected respose from service calling virtuallinein.Stop()`) + } + + return r.Body.Stop, nil +} + +type SetVolumeArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + InstanceID uint32 `xml:"InstanceID"` + DesiredVolume uint16 `xml:"DesiredVolume"` +} +type SetVolumeResponse struct { +} + +func (s *Service) SetVolume(httpClient *http.Client, args *SetVolumeArgs) (*SetVolumeResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SetVolume`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SetVolume: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SetVolume == nil { + return nil, errors.New(`unexpected respose from service calling virtuallinein.SetVolume()`) + } + + return r.Body.SetVolume, nil +} diff --git a/vendor/github.com/szatmary/sonos/ZoneGroupTopology/ZoneGroupTopology.go b/vendor/github.com/szatmary/sonos/ZoneGroupTopology/ZoneGroupTopology.go new file mode 100644 index 0000000..2bc1887 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/ZoneGroupTopology/ZoneGroupTopology.go @@ -0,0 +1,318 @@ +package zonegrouptopology + +import ( + "bytes" + "encoding/xml" + "errors" + "io/ioutil" + "net/http" + "net/url" +) + +const ( + _ServiceURN = "urn:schemas-upnp-org:service:ZoneGroupTopology:1" + _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" + _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" +) + +type Service struct { + ControlEndpoint *url.URL + EventEndpoint *url.URL +} + +func NewService(deviceUrl *url.URL) *Service { + c, err := url.Parse(`/ZoneGroupTopology/Control`) + if nil != err { + panic(err) + } + e, err := url.Parse(`/ZoneGroupTopology/Event`) + if nil != err { + panic(err) + } + return &Service{ + ControlEndpoint: deviceUrl.ResolveReference(c), + EventEndpoint: deviceUrl.ResolveReference(e), + } +} + +type Envelope struct { + XMLName xml.Name `xml:"s:Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"s:encodingStyle,attr"` + Body Body `xml:"s:Body"` +} +type Body struct { + XMLName xml.Name `xml:"s:Body"` + CheckForUpdate *CheckForUpdateArgs `xml:"u:CheckForUpdate,omitempty"` + BeginSoftwareUpdate *BeginSoftwareUpdateArgs `xml:"u:BeginSoftwareUpdate,omitempty"` + ReportUnresponsiveDevice *ReportUnresponsiveDeviceArgs `xml:"u:ReportUnresponsiveDevice,omitempty"` + ReportAlarmStartedRunning *ReportAlarmStartedRunningArgs `xml:"u:ReportAlarmStartedRunning,omitempty"` + SubmitDiagnostics *SubmitDiagnosticsArgs `xml:"u:SubmitDiagnostics,omitempty"` + RegisterMobileDevice *RegisterMobileDeviceArgs `xml:"u:RegisterMobileDevice,omitempty"` + GetZoneGroupAttributes *GetZoneGroupAttributesArgs `xml:"u:GetZoneGroupAttributes,omitempty"` + GetZoneGroupState *GetZoneGroupStateArgs `xml:"u:GetZoneGroupState,omitempty"` +} +type EnvelopeResponse struct { + XMLName xml.Name `xml:"Envelope"` + Xmlns string `xml:"xmlns:s,attr"` + EncodingStyle string `xml:"encodingStyle,attr"` + Body BodyResponse `xml:"Body"` +} +type BodyResponse struct { + XMLName xml.Name `xml:"Body"` + CheckForUpdate *CheckForUpdateResponse `xml:"CheckForUpdateResponse,omitempty"` + BeginSoftwareUpdate *BeginSoftwareUpdateResponse `xml:"BeginSoftwareUpdateResponse,omitempty"` + ReportUnresponsiveDevice *ReportUnresponsiveDeviceResponse `xml:"ReportUnresponsiveDeviceResponse,omitempty"` + ReportAlarmStartedRunning *ReportAlarmStartedRunningResponse `xml:"ReportAlarmStartedRunningResponse,omitempty"` + SubmitDiagnostics *SubmitDiagnosticsResponse `xml:"SubmitDiagnosticsResponse,omitempty"` + RegisterMobileDevice *RegisterMobileDeviceResponse `xml:"RegisterMobileDeviceResponse,omitempty"` + GetZoneGroupAttributes *GetZoneGroupAttributesResponse `xml:"GetZoneGroupAttributesResponse,omitempty"` + GetZoneGroupState *GetZoneGroupStateResponse `xml:"GetZoneGroupStateResponse,omitempty"` +} + +func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { + marshaled, err := xml.Marshal(envelope) + if err != nil { + return nil, err + } + postBody := []byte(``) + postBody = append(postBody, marshaled...) + req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) + if err != nil { + return nil, err + } + req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) + req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) + res, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + var envelopeResponse EnvelopeResponse + err = xml.Unmarshal(responseBody, &envelopeResponse) + if err != nil { + return nil, err + } + return &envelopeResponse, nil +} + +type CheckForUpdateArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + // Allowed Value: All + // Allowed Value: Software + UpdateType string `xml:"UpdateType"` + CachedOnly bool `xml:"CachedOnly"` + Version string `xml:"Version"` +} +type CheckForUpdateResponse struct { + UpdateItem string `xml:"UpdateItem"` +} + +func (s *Service) CheckForUpdate(httpClient *http.Client, args *CheckForUpdateArgs) (*CheckForUpdateResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`CheckForUpdate`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{CheckForUpdate: args}, + }) + if err != nil { + return nil, err + } + if r.Body.CheckForUpdate == nil { + return nil, errors.New(`unexpected respose from service calling zonegrouptopology.CheckForUpdate()`) + } + + return r.Body.CheckForUpdate, nil +} + +type BeginSoftwareUpdateArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + UpdateURL string `xml:"UpdateURL"` + Flags uint32 `xml:"Flags"` + ExtraOptions string `xml:"ExtraOptions"` +} +type BeginSoftwareUpdateResponse struct { +} + +func (s *Service) BeginSoftwareUpdate(httpClient *http.Client, args *BeginSoftwareUpdateArgs) (*BeginSoftwareUpdateResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`BeginSoftwareUpdate`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{BeginSoftwareUpdate: args}, + }) + if err != nil { + return nil, err + } + if r.Body.BeginSoftwareUpdate == nil { + return nil, errors.New(`unexpected respose from service calling zonegrouptopology.BeginSoftwareUpdate()`) + } + + return r.Body.BeginSoftwareUpdate, nil +} + +type ReportUnresponsiveDeviceArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + DeviceUUID string `xml:"DeviceUUID"` + // Allowed Value: Remove + // Allowed Value: TopologyMonitorProbe + // Allowed Value: VerifyThenRemoveSystemwide + DesiredAction string `xml:"DesiredAction"` +} +type ReportUnresponsiveDeviceResponse struct { +} + +func (s *Service) ReportUnresponsiveDevice(httpClient *http.Client, args *ReportUnresponsiveDeviceArgs) (*ReportUnresponsiveDeviceResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ReportUnresponsiveDevice`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ReportUnresponsiveDevice: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ReportUnresponsiveDevice == nil { + return nil, errors.New(`unexpected respose from service calling zonegrouptopology.ReportUnresponsiveDevice()`) + } + + return r.Body.ReportUnresponsiveDevice, nil +} + +type ReportAlarmStartedRunningArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type ReportAlarmStartedRunningResponse struct { +} + +func (s *Service) ReportAlarmStartedRunning(httpClient *http.Client, args *ReportAlarmStartedRunningArgs) (*ReportAlarmStartedRunningResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`ReportAlarmStartedRunning`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{ReportAlarmStartedRunning: args}, + }) + if err != nil { + return nil, err + } + if r.Body.ReportAlarmStartedRunning == nil { + return nil, errors.New(`unexpected respose from service calling zonegrouptopology.ReportAlarmStartedRunning()`) + } + + return r.Body.ReportAlarmStartedRunning, nil +} + +type SubmitDiagnosticsArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + IncludeControllers bool `xml:"IncludeControllers"` + Type string `xml:"Type"` +} +type SubmitDiagnosticsResponse struct { + DiagnosticID uint32 `xml:"DiagnosticID"` +} + +func (s *Service) SubmitDiagnostics(httpClient *http.Client, args *SubmitDiagnosticsArgs) (*SubmitDiagnosticsResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`SubmitDiagnostics`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{SubmitDiagnostics: args}, + }) + if err != nil { + return nil, err + } + if r.Body.SubmitDiagnostics == nil { + return nil, errors.New(`unexpected respose from service calling zonegrouptopology.SubmitDiagnostics()`) + } + + return r.Body.SubmitDiagnostics, nil +} + +type RegisterMobileDeviceArgs struct { + Xmlns string `xml:"xmlns:u,attr"` + MobileDeviceName string `xml:"MobileDeviceName"` + MobileDeviceUDN string `xml:"MobileDeviceUDN"` + MobileIPAndPort string `xml:"MobileIPAndPort"` +} +type RegisterMobileDeviceResponse struct { +} + +func (s *Service) RegisterMobileDevice(httpClient *http.Client, args *RegisterMobileDeviceArgs) (*RegisterMobileDeviceResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`RegisterMobileDevice`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{RegisterMobileDevice: args}, + }) + if err != nil { + return nil, err + } + if r.Body.RegisterMobileDevice == nil { + return nil, errors.New(`unexpected respose from service calling zonegrouptopology.RegisterMobileDevice()`) + } + + return r.Body.RegisterMobileDevice, nil +} + +type GetZoneGroupAttributesArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetZoneGroupAttributesResponse struct { + CurrentZoneGroupName string `xml:"CurrentZoneGroupName"` + CurrentZoneGroupID string `xml:"CurrentZoneGroupID"` + CurrentZonePlayerUUIDsInGroup string `xml:"CurrentZonePlayerUUIDsInGroup"` + CurrentMuseHouseholdId string `xml:"CurrentMuseHouseholdId"` +} + +func (s *Service) GetZoneGroupAttributes(httpClient *http.Client, args *GetZoneGroupAttributesArgs) (*GetZoneGroupAttributesResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetZoneGroupAttributes`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetZoneGroupAttributes: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetZoneGroupAttributes == nil { + return nil, errors.New(`unexpected respose from service calling zonegrouptopology.GetZoneGroupAttributes()`) + } + + return r.Body.GetZoneGroupAttributes, nil +} + +type GetZoneGroupStateArgs struct { + Xmlns string `xml:"xmlns:u,attr"` +} +type GetZoneGroupStateResponse struct { + ZoneGroupState string `xml:"ZoneGroupState"` +} + +func (s *Service) GetZoneGroupState(httpClient *http.Client, args *GetZoneGroupStateArgs) (*GetZoneGroupStateResponse, error) { + args.Xmlns = _ServiceURN + r, err := s.exec(`GetZoneGroupState`, httpClient, + &Envelope{ + EncodingStyle: _EncodingSchema, + Xmlns: _EnvelopeSchema, + Body: Body{GetZoneGroupState: args}, + }) + if err != nil { + return nil, err + } + if r.Body.GetZoneGroupState == nil { + return nil, errors.New(`unexpected respose from service calling zonegrouptopology.GetZoneGroupState()`) + } + + return r.Body.GetZoneGroupState, nil +} diff --git a/vendor/github.com/szatmary/sonos/sonos.go b/vendor/github.com/szatmary/sonos/sonos.go new file mode 100644 index 0000000..524bcd2 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/sonos.go @@ -0,0 +1,100 @@ +package sonos + +import ( + "bufio" + "errors" + "fmt" + "net" + "net/http" + "net/url" + "time" +) + +const ( + mx = 5 + st = "urn:schemas-upnp-org:device:ZonePlayer:1" + bcastaddr = "239.255.255.250:1900" +) + +type Sonos struct { + // Context Context + listenSocket *net.UDPConn + udpReader *bufio.Reader + found chan *ZonePlayer +} + +func NewSonos() (*Sonos, error) { + conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: []byte{0, 0, 0, 0}, Port: 0, Zone: ""}) + if err != nil { + return nil, err + } + + s := Sonos{ + listenSocket: conn, + udpReader: bufio.NewReader(conn), + found: make(chan *ZonePlayer), + } + + return &s, nil +} + +func (s *Sonos) Close() { + s.listenSocket.Close() +} + +func (s *Sonos) Search() (chan *ZonePlayer, error) { + go func() { + for { + response, err := http.ReadResponse(s.udpReader, nil) + if err != nil { + continue + } + + location, err := url.Parse(response.Header.Get("Location")) + if err != nil { + continue + } + zp, err := NewZonePlayer(location) + if err != nil { + continue + } + if zp.IsCoordinator() { + s.found <- zp + } + } + }() + + // MX should be set to use timeout value in integer seconds + pkt := []byte(fmt.Sprintf("M-SEARCH * HTTP/1.1\r\nHOST: %s\r\nMAN: \"ssdp:discover\"\r\nMX: %d\r\nST: %s\r\n\r\n", bcastaddr, mx, st)) + bcast, err := net.ResolveUDPAddr("udp", bcastaddr) + if err != nil { + return nil, err + } + _, err = s.listenSocket.WriteTo(pkt, bcast) + if err != nil { + return nil, err + } + + return s.found, nil +} + +func FindRoom(room string, timeout time.Duration) (*ZonePlayer, error) { + son, err := NewSonos() + if err != nil { + return nil, err + } + defer son.Close() + + found, _ := son.Search() + to := time.After(timeout) + for { + select { + case <-to: + return nil, errors.New("timeout") + case zp := <-found: + if zp.RoomName() == room { + return zp, nil + } + } + } +} diff --git a/vendor/github.com/szatmary/sonos/zonegroups.go b/vendor/github.com/szatmary/sonos/zonegroups.go new file mode 100644 index 0000000..3a80aaa --- /dev/null +++ b/vendor/github.com/szatmary/sonos/zonegroups.go @@ -0,0 +1,107 @@ +package sonos + +import "encoding/xml" + +type VanishedDevice struct { + XMLName xml.Name `xml:"VanishedDevice"` + UUID string `xml:"UUID"` + Location string `xml:"Location"` + ZoneName string `xml:"ZoneName"` + Icon string `xml:"Icon"` + Configuration string `xml:"Configuration"` + SoftwareVersion string `xml:"SoftwareVersion"` + SWGen string `xml:"SWGen"` + MinCompatibleVersion string `xml:"MinCompatibleVersion"` + LegacyCompatibleVersion string `xml:"LegacyCompatibleVersion"` + BootSeq string `xml:"BootSeq"` + TVConfigurationError string `xml:"TVConfigurationError"` + HdmiCecAvailable string `xml:"HdmiCecAvailable"` + WirelessMode string `xml:"WirelessMode"` + WirelessLeafOnly string `xml:"WirelessLeafOnly"` + HasConfiguredSSID string `xml:"HasConfiguredSSID"` + ChannelFreq string `xml:"ChannelFreq"` + BehindWifiExtender string `xml:"BehindWifiExtender"` + WifiEnabled string `xml:"WifiEnabled"` + Orientation string `xml:"Orientation"` + RoomCalibrationState string `xml:"RoomCalibrationState"` + SecureRegState string `xml:"SecureRegState"` + VoiceConfigState string `xml:"VoiceConfigState"` + MicEnabled string `xml:"MicEnabled"` + AirPlayEnabled string `xml:"AirPlayEnabled"` + IdleState string `xml:"IdleState"` + MoreInfo string `xml:"MoreInfo"` +} + +type Satellite struct { + XMLName xml.Name `xml:"Satellite"` + UUID string `xml:"UUID"` + Location string `xml:"Location"` + ZoneName string `xml:"ZoneName"` + Icon string `xml:"Icon"` + Configuration string `xml:"Configuration"` + SoftwareVersion string `xml:"SoftwareVersion"` + SWGen string `xml:"SWGen"` + MinCompatibleVersion string `xml:"MinCompatibleVersion"` + LegacyCompatibleVersion string `xml:"LegacyCompatibleVersion"` + BootSeq string `xml:"BootSeq"` + TVConfigurationError string `xml:"TVConfigurationError"` + HdmiCecAvailable string `xml:"HdmiCecAvailable"` + WirelessMode string `xml:"WirelessMode"` + WirelessLeafOnly string `xml:"WirelessLeafOnly"` + HasConfiguredSSID string `xml:"HasConfiguredSSID"` + ChannelFreq string `xml:"ChannelFreq"` + BehindWifiExtender string `xml:"BehindWifiExtender"` + WifiEnabled string `xml:"WifiEnabled"` + Orientation string `xml:"Orientation"` + RoomCalibrationState string `xml:"RoomCalibrationState"` + SecureRegState string `xml:"SecureRegState"` + VoiceConfigState string `xml:"VoiceConfigState"` + MicEnabled string `xml:"MicEnabled"` + AirPlayEnabled string `xml:"AirPlayEnabled"` + IdleState string `xml:"IdleState"` + MoreInfo string `xml:"MoreInfo"` +} + +type ZoneGroupMember struct { + XMLName xml.Name `xml:"ZoneGroupMember"` + UUID string `xml:"UUID"` + Location string `xml:"Location"` + ZoneName string `xml:"ZoneName"` + Icon string `xml:"Icon"` + Configuration string `xml:"Configuration"` + SoftwareVersion string `xml:"SoftwareVersion"` + SWGen string `xml:"SWGen"` + MinCompatibleVersion string `xml:"MinCompatibleVersion"` + LegacyCompatibleVersion string `xml:"LegacyCompatibleVersion"` + BootSeq string `xml:"BootSeq"` + TVConfigurationError string `xml:"TVConfigurationError"` + HdmiCecAvailable string `xml:"HdmiCecAvailable"` + WirelessMode string `xml:"WirelessMode"` + WirelessLeafOnly string `xml:"WirelessLeafOnly"` + HasConfiguredSSID string `xml:"HasConfiguredSSID"` + ChannelFreq string `xml:"ChannelFreq"` + BehindWifiExtender string `xml:"BehindWifiExtender"` + WifiEnabled string `xml:"WifiEnabled"` + Orientation string `xml:"Orientation"` + RoomCalibrationState string `xml:"RoomCalibrationState"` + SecureRegState string `xml:"SecureRegState"` + VoiceConfigState string `xml:"VoiceConfigState"` + MicEnabled string `xml:"MicEnabled"` + AirPlayEnabled string `xml:"AirPlayEnabled"` + IdleState string `xml:"IdleState"` + MoreInfo string `xml:"MoreInfo"` + Satellite []Satellite `xml:"Satellite>Satellite"` + VanishedDevice []VanishedDevice `xml:"VanishedDevices>VanishedDevice"` +} + +type ZoneGroup struct { + XMLName xml.Name `xml:"ZoneGroup"` + Coordinator string `xml:"Coordinator,attr"` + ID string `xml:"ID,attr"` + ZoneGroupMember []ZoneGroupMember `xml:"ZoneGroupMember"` +} + +type ZoneGroupState struct { + XMLName xml.Name `xml:"ZoneGroupState"` + ZoneGroups []ZoneGroup `xml:"ZoneGroups>ZoneGroup"` +} diff --git a/vendor/github.com/szatmary/sonos/zoneplayer.go b/vendor/github.com/szatmary/sonos/zoneplayer.go new file mode 100644 index 0000000..1b87f60 --- /dev/null +++ b/vendor/github.com/szatmary/sonos/zoneplayer.go @@ -0,0 +1,236 @@ +package sonos + +import ( + "encoding/xml" + "io/ioutil" + "net/http" + "net/url" + + avt "github.com/szatmary/sonos/AVTransport" + clk "github.com/szatmary/sonos/AlarmClock" + con "github.com/szatmary/sonos/ConnectionManager" + dir "github.com/szatmary/sonos/ContentDirectory" + dev "github.com/szatmary/sonos/DeviceProperties" + gmn "github.com/szatmary/sonos/GroupManagement" + rcg "github.com/szatmary/sonos/GroupRenderingControl" + mus "github.com/szatmary/sonos/MusicServices" + ply "github.com/szatmary/sonos/QPlay" + que "github.com/szatmary/sonos/Queue" + ren "github.com/szatmary/sonos/RenderingControl" + sys "github.com/szatmary/sonos/SystemProperties" + vli "github.com/szatmary/sonos/VirtualLineIn" + zgt "github.com/szatmary/sonos/ZoneGroupTopology" +) + +type SpecVersion struct { + XMLName xml.Name `xml:"specVersion"` + Major int `xml:"major"` + Minor int `xml:"minor"` +} + +type Service struct { + XMLName xml.Name `xml:"service"` + ServiceType string `xml:"serviceType"` + ServiceId string `xml:"serviceId"` + ControlURL string `xml:"controlURL"` + EventSubURL string `xml:"eventSubURL"` + SCPDURL string `xml:"SCPDURL"` +} + +type Icon struct { + XMLName xml.Name `xml:"icon"` + Id string `xml:"id"` + Mimetype string `xml:"mimetype"` + Width int `xml:"width"` + Height int `xml:"height"` + Depth int `xml:"depth"` + Url url.URL `xml:"url"` +} + +type Device struct { + XMLName xml.Name `xml:"device"` + DeviceType string `xml:"deviceType"` + FriendlyName string `xml:"friendlyName"` + Manufacturer string `xml:"manufacturer"` + ManufacturerURL string `xml:"manufacturerURL"` + ModelNumber string `xml:"modelNumber"` + ModelDescription string `xml:"modelDescription"` + ModelName string `xml:"modelName"` + ModelURL string `xml:"modelURL"` + SoftwareVersion string `xml:"softwareVersion"` + SwGen string `xml:"swGen"` + HardwareVersion string `xml:"hardwareVersion"` + SerialNum string `xml:"serialNum"` + MACAddress string `xml:"MACAddress"` + UDN string `xml:"UDN"` + Icons []Icon `xml:"iconList>icon"` + MinCompatibleVersion string `xml:"minCompatibleVersion"` + LegacyCompatibleVersion string `xml:"legacyCompatibleVersion"` + ApiVersion string `xml:"apiVersion"` + MinApiVersion string `xml:"minApiVersion"` + DisplayVersion string `xml:"displayVersion"` + ExtraVersion string `xml:"extraVersion"` + RoomName string `xml:"roomName"` + DisplayName string `xml:"displayName"` + ZoneType int `xml:"zoneType"` + Feature1 string `xml:"feature1"` + Feature2 string `xml:"feature2"` + Feature3 string `xml:"feature3"` + Seriesid string `xml:"seriesid"` + Variant int `xml:"variant"` + InternalSpeakerSize float32 `xml:"internalSpeakerSize"` + BassExtension float32 `xml:"bassExtension"` + SatGainOffset float32 `xml:"satGainOffset"` + Memory int `xml:"memory"` + Flash int `xml:"flash"` + FlashRepartitioned int `xml:"flashRepartitioned"` + AmpOnTime int `xml:"ampOnTime"` + RetailMode int `xml:"retailMode"` + Services []Service `xml:"serviceList>service"` + Devices []Device `xml:"deviceList>device"` +} + +type Root struct { + XMLName xml.Name `xml:"root"` + Xmlns string `xml:"xmlns,attr"` + SpecVersion SpecVersion `xml:"specVersion"` + Device Device `xml:"device"` +} + +type ZonePlayer struct { + Root *Root + HttpClient *http.Client + DeviceDescriptionURL *url.URL + // services + AlarmClock *clk.Service + AVTransport *avt.Service + ConnectionManager *con.Service + ContentDirectory *dir.Service + DeviceProperties *dev.Service + GroupManagement *gmn.Service + GroupRenderingControl *rcg.Service + MusicServices *mus.Service + QPlay *ply.Service + Queue *que.Service + RenderingControl *ren.Service + SystemProperties *sys.Service + VirtualLineIn *vli.Service + ZoneGroupTopology *zgt.Service +} + +func NewZonePlayer(deviceDescriptionURL *url.URL) (*ZonePlayer, error) { + zp := ZonePlayer{ + Root: &Root{}, + HttpClient: &http.Client{}, + DeviceDescriptionURL: deviceDescriptionURL, + AlarmClock: clk.NewService(deviceDescriptionURL), + AVTransport: avt.NewService(deviceDescriptionURL), + ConnectionManager: con.NewService(deviceDescriptionURL), + ContentDirectory: dir.NewService(deviceDescriptionURL), + DeviceProperties: dev.NewService(deviceDescriptionURL), + GroupManagement: gmn.NewService(deviceDescriptionURL), + GroupRenderingControl: rcg.NewService(deviceDescriptionURL), + MusicServices: mus.NewService(deviceDescriptionURL), + QPlay: ply.NewService(deviceDescriptionURL), + Queue: que.NewService(deviceDescriptionURL), + RenderingControl: ren.NewService(deviceDescriptionURL), + SystemProperties: sys.NewService(deviceDescriptionURL), + VirtualLineIn: vli.NewService(deviceDescriptionURL), + ZoneGroupTopology: zgt.NewService(deviceDescriptionURL), + } + + resp, err := zp.HttpClient.Get(zp.DeviceDescriptionURL.String()) + if err != nil { + return nil, err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + err = xml.Unmarshal(body, zp.Root) + if err != nil { + return nil, err + } + + return &zp, nil +} + +func (z *ZonePlayer) RoomName() string { + return z.Root.Device.RoomName +} + +func (z *ZonePlayer) ModelName() string { + return z.Root.Device.ModelName +} + +func (z *ZonePlayer) HardwareVersion() string { + return z.Root.Device.HardwareVersion +} + +func (z *ZonePlayer) SerialNum() string { + return z.Root.Device.SerialNum +} + +func (z *ZonePlayer) IsCoordinator() bool { + zoneGroupState, err := z.GetZoneGroupState() + if err != nil { + return false + } + for _, group := range zoneGroupState.ZoneGroups { + if "uuid:"+group.Coordinator == z.Root.Device.UDN { + return true + } + } + + return false +} + +// Convience functions + +func (z *ZonePlayer) GetZoneGroupState() (*ZoneGroupState, error) { + zoneGroupStateResponse, err := z.ZoneGroupTopology.GetZoneGroupState(z.HttpClient, &zgt.GetZoneGroupStateArgs{}) + if err != nil { + return nil, err + } + var zoneGroupState ZoneGroupState + err = xml.Unmarshal([]byte(zoneGroupStateResponse.ZoneGroupState), &zoneGroupState) + if err != nil { + return nil, err + } + + return &zoneGroupState, nil +} + +func (z *ZonePlayer) GetVolume() (int, error) { + res, err := z.RenderingControl.GetVolume(z.HttpClient, &ren.GetVolumeArgs{Channel: "Master"}) + if err != nil { + return 0, err + } + + return int(res.CurrentVolume), err +} + +func (z *ZonePlayer) SetVolume(desiredVolume int) error { + _, err := z.RenderingControl.SetVolume(z.HttpClient, &ren.SetVolumeArgs{ + Channel: "Master", + DesiredVolume: uint16(desiredVolume), + }) + return err +} + +func (z *ZonePlayer) Play() error { + _, err := z.AVTransport.Play(z.HttpClient, &avt.PlayArgs{ + Speed: "1", + }) + return err +} + +func (z *ZonePlayer) SetAVTransportURI(url string) error { + _, err := z.AVTransport.SetAVTransportURI(z.HttpClient, &avt.SetAVTransportURIArgs{ + CurrentURI: url, + }) + return err +} diff --git a/vendor/modules.txt b/vendor/modules.txt index e29eff9..49c8ce7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -73,6 +73,9 @@ github.com/goccy/go-json/internal/runtime # github.com/google/uuid v1.3.0 ## explicit github.com/google/uuid +# github.com/holoplot/go-evdev v0.0.0-20230626094006-70c9462cc0f2 +## explicit; go 1.18 +github.com/holoplot/go-evdev # github.com/json-iterator/go v1.1.12 ## explicit; go 1.12 github.com/json-iterator/go @@ -205,6 +208,23 @@ github.com/pmezard/go-difflib/difflib ## explicit; go 1.20 github.com/stretchr/testify/assert github.com/stretchr/testify/require +# github.com/szatmary/sonos v0.0.0-20191204204454-000c9219ff0e +## explicit +github.com/szatmary/sonos +github.com/szatmary/sonos/AVTransport +github.com/szatmary/sonos/AlarmClock +github.com/szatmary/sonos/ConnectionManager +github.com/szatmary/sonos/ContentDirectory +github.com/szatmary/sonos/DeviceProperties +github.com/szatmary/sonos/GroupManagement +github.com/szatmary/sonos/GroupRenderingControl +github.com/szatmary/sonos/MusicServices +github.com/szatmary/sonos/QPlay +github.com/szatmary/sonos/Queue +github.com/szatmary/sonos/RenderingControl +github.com/szatmary/sonos/SystemProperties +github.com/szatmary/sonos/VirtualLineIn +github.com/szatmary/sonos/ZoneGroupTopology # github.com/tinyzimmer/go-glib v0.0.25 ## explicit; go 1.16 github.com/tinyzimmer/go-glib/glib From d584b50b04cc48f772e245423ada08348b8cc77d Mon Sep 17 00:00:00 2001 From: kaedwen Date: Sun, 16 Jul 2023 10:23:46 +0200 Subject: [PATCH 2/9] ring all --- pkg/common/config.go | 2 +- pkg/ring/ring.go | 9 +++++++-- pkg/ring/sonos/sonos.go | 19 ++++++++++--------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/pkg/common/config.go b/pkg/common/config.go index c21a70e..961c792 100644 --- a/pkg/common/config.go +++ b/pkg/common/config.go @@ -31,7 +31,7 @@ type ConfigRing struct { Key string `arg:"--ring-key" default:"KEY_F1"` JingleName string `arg:"--jingle-name,env:JINGLE_NAME" default:"ding-dong.wav"` JinglePath *string `arg:"--jingle-path,env:JINGLE_PATH"` - SonosTarget string `arg:"--sonos-target,env:SONOS_TARGET" default:"Living Room"` + SonosTarget string `arg:"--sonos-target,env:SONOS_TARGET" default:"-"` } type ConfigHTTP struct { diff --git a/pkg/ring/ring.go b/pkg/ring/ring.go index 9dcc311..3c5bd5b 100644 --- a/pkg/ring/ring.go +++ b/pkg/ring/ring.go @@ -22,12 +22,17 @@ type RingHandler struct { } func NewRingHandler(lg *zap.Logger, cfg *common.ConfigRing) (*RingHandler, error) { - sp, err := sonos.NewSonosPlayer(lg.With(zap.String("context", "sonos")), cfg.SonosTarget) + spl, err := sonos.NewSonosPlayers(lg.With(zap.String("context", "sonos")), cfg.SonosTarget) if err != nil { return nil, err } - return &RingHandler{lg, cfg, []Player{sp}}, nil + pl := make([]Player, 0, len(spl)) + for _, sp := range spl { + pl = append(pl, sp) + } + + return &RingHandler{lg, cfg, pl}, nil } func (h *RingHandler) Watch(ctx context.Context) error { diff --git a/pkg/ring/sonos/sonos.go b/pkg/ring/sonos/sonos.go index 38e8d40..3ea667b 100644 --- a/pkg/ring/sonos/sonos.go +++ b/pkg/ring/sonos/sonos.go @@ -1,7 +1,6 @@ package sonos import ( - "errors" "net/url" "time" @@ -14,13 +13,13 @@ type SonosPlayer struct { zp *so.ZonePlayer } -func NewSonosPlayer(lg *zap.Logger, target string) (*SonosPlayer, error) { - zp, err := SearchTarget(lg, target) +func NewSonosPlayers(lg *zap.Logger, target string) ([]*SonosPlayer, error) { + spl, err := SearchTargets(lg, target) if err != nil { return nil, err } - return &SonosPlayer{lg, zp}, nil + return spl, nil } func (p *SonosPlayer) Play(uri *url.URL) error { @@ -32,7 +31,9 @@ func (p *SonosPlayer) Play(uri *url.URL) error { return p.zp.Play() } -func SearchTarget(lg *zap.Logger, target string) (*so.ZonePlayer, error) { +func SearchTargets(lg *zap.Logger, target string) ([]*SonosPlayer, error) { + players := make([]*SonosPlayer, 0) + son, err := so.NewSonos() if err != nil { return nil, err @@ -40,15 +41,15 @@ func SearchTarget(lg *zap.Logger, target string) (*so.ZonePlayer, error) { defer son.Close() found, _ := son.Search() - to := time.After(10 * time.Second) + to := time.After(5 * time.Second) for { select { case <-to: - return nil, errors.New("timeout") + return players, nil case zp := <-found: lg.Info("found player", zap.String("name", zp.RoomName())) - if zp.RoomName() == target { - return zp, nil + if target == "-" || zp.RoomName() == target { + players = append(players, &SonosPlayer{lg, zp}) } } } From f619107517a2b8890244f2ca98c5923af3cac17a Mon Sep 17 00:00:00 2001 From: kaedwen Date: Sun, 16 Jul 2023 13:42:08 +0200 Subject: [PATCH 3/9] add systemd service --- .vscode/launch.json | 3 ++- assets/README.md | 11 +++++++++++ assets/gpio.dts | 42 ++++++++++++++++++++++++++++++++++++++++++ assets/ring.service | 9 +++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 assets/README.md create mode 100644 assets/gpio.dts create mode 100644 assets/ring.service diff --git a/.vscode/launch.json b/.vscode/launch.json index 125342d..068ae0b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,8 @@ "VIDEO_OUT_DEVICE": "/dev/video4", "AUDIO_OUT_DEVICE": "plughw:0,1,0", "INPUT_DEVICE": "/dev/input/event5", - "JINGLE_PATH": "${workspaceFolder}/audio" + "JINGLE_PATH": "${workspaceFolder}/audio", + "SONOS_TARGET": "Living Room" } }, { diff --git a/assets/README.md b/assets/README.md new file mode 100644 index 0000000..7cbb1d7 --- /dev/null +++ b/assets/README.md @@ -0,0 +1,11 @@ +# GPIO setup + +## Using Linux Device-Tree +For ring trigger it is necessary to configure a Key Stroke. This can be achived with configuring an gpio. + +Copy the `gpio.dts` to `/boot/overlay-user/gpio.dts` and if you are using armbian execute +``` +armbian-add-overlay /boot/overlay-user/gpio.dts +``` + +This will compile the `dts` file to a `dtbo` compiled one and will append the overlay to your `armbianEnv.txt \ No newline at end of file diff --git a/assets/gpio.dts b/assets/gpio.dts new file mode 100644 index 0000000..c883ce8 --- /dev/null +++ b/assets/gpio.dts @@ -0,0 +1,42 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "allwinner,sun4i-a10", "allwinner,sun7i-a20", "allwinner,sun8i-h3", "allwinner,sun50i-a64", "allwinner,sun50i-h5"; + + /* + * This fragment is needed only for the internal pull-up activation, + * external pull-up resistor is highly recommended if using long wires + */ + fragment@0 { + target = <&pio>; + __overlay__ { + gpio_button_0: gpio_button_0 { + pins = "PA7"; + function = "gpio_in"; + bias-pull-up; + }; + }; + }; + + fragment@1 { + target-path = "/"; + __overlay__ { + gpio-keys-user { + /* + * Use "gpio-keys" for EINT capable pins, "gpio-keys-polled" for other pins + * add "poll-interval" property if using "gpio-keys-polled" + */ + compatible = "gpio-keys"; + pinctrl-names = "default"; + pinctrl-0 = <&gpio_button_0>; + + f1_button { + label = "GPIO Key F1"; + linux,code = <59>; /* KEY_F1, see include/uapi/linux/input-event-codes.h */ + gpios = <&pio 0 7 1>; /* PA07 GPIO_ACTIVE_LOW */ + }; + }; + }; + }; +}; \ No newline at end of file diff --git a/assets/ring.service b/assets/ring.service new file mode 100644 index 0000000..6383564 --- /dev/null +++ b/assets/ring.service @@ -0,0 +1,9 @@ +[Unit] +Description=WebRTC Ring Service + +[Service] +ExecStart=/usr/local/bin/ring +EnvironmentFile=-/etc/ring/ring.conf + +[Install] +WantedBy=multi-user.target \ No newline at end of file From bec6edbb8c3ae9e6dc6aa5c7775ab623427ee3a0 Mon Sep 17 00:00:00 2001 From: kaedwen Date: Sun, 16 Jul 2023 15:14:37 +0200 Subject: [PATCH 4/9] self lookup mdns and audioClip --- go.mod | 3 + go.sum | 11 + pkg/ring/ring.go | 28 +- pkg/ring/sonos/sonos.go | 184 +- vendor/github.com/hashicorp/mdns/.gitignore | 23 + vendor/github.com/hashicorp/mdns/LICENSE | 20 + vendor/github.com/hashicorp/mdns/README.md | 37 + vendor/github.com/hashicorp/mdns/client.go | 407 +++ vendor/github.com/hashicorp/mdns/server.go | 288 ++ vendor/github.com/hashicorp/mdns/zone.go | 307 ++ vendor/github.com/ianr0bkny/go-sonos/LICENSE | 28 + .../ianr0bkny/go-sonos/ssdp/ssdp.go | 942 ++++++ vendor/github.com/miekg/dns/.codecov.yml | 8 + vendor/github.com/miekg/dns/.gitignore | 4 + vendor/github.com/miekg/dns/AUTHORS | 1 + vendor/github.com/miekg/dns/CODEOWNERS | 1 + vendor/github.com/miekg/dns/CONTRIBUTORS | 10 + vendor/github.com/miekg/dns/COPYRIGHT | 9 + vendor/github.com/miekg/dns/LICENSE | 30 + vendor/github.com/miekg/dns/Makefile.fuzz | 33 + vendor/github.com/miekg/dns/Makefile.release | 52 + vendor/github.com/miekg/dns/README.md | 181 ++ vendor/github.com/miekg/dns/acceptfunc.go | 61 + vendor/github.com/miekg/dns/client.go | 449 +++ vendor/github.com/miekg/dns/clientconfig.go | 135 + vendor/github.com/miekg/dns/dane.go | 43 + vendor/github.com/miekg/dns/defaults.go | 381 +++ vendor/github.com/miekg/dns/dns.go | 158 + vendor/github.com/miekg/dns/dnssec.go | 757 +++++ vendor/github.com/miekg/dns/dnssec_keygen.go | 139 + vendor/github.com/miekg/dns/dnssec_keyscan.go | 309 ++ vendor/github.com/miekg/dns/dnssec_privkey.go | 77 + vendor/github.com/miekg/dns/doc.go | 292 ++ vendor/github.com/miekg/dns/duplicate.go | 37 + vendor/github.com/miekg/dns/edns.go | 675 ++++ vendor/github.com/miekg/dns/format.go | 93 + vendor/github.com/miekg/dns/fuzz.go | 32 + vendor/github.com/miekg/dns/generate.go | 247 ++ vendor/github.com/miekg/dns/labels.go | 212 ++ vendor/github.com/miekg/dns/listen_go111.go | 44 + .../github.com/miekg/dns/listen_go_not111.go | 23 + vendor/github.com/miekg/dns/msg.go | 1197 +++++++ vendor/github.com/miekg/dns/msg_helpers.go | 833 +++++ vendor/github.com/miekg/dns/msg_truncate.go | 117 + vendor/github.com/miekg/dns/nsecx.go | 95 + vendor/github.com/miekg/dns/privaterr.go | 113 + vendor/github.com/miekg/dns/reverse.go | 52 + vendor/github.com/miekg/dns/sanitize.go | 86 + vendor/github.com/miekg/dns/scan.go | 1365 ++++++++ vendor/github.com/miekg/dns/scan_rr.go | 1774 ++++++++++ vendor/github.com/miekg/dns/serve_mux.go | 122 + vendor/github.com/miekg/dns/server.go | 828 +++++ vendor/github.com/miekg/dns/sig0.go | 197 ++ vendor/github.com/miekg/dns/singleinflight.go | 61 + vendor/github.com/miekg/dns/smimea.go | 44 + vendor/github.com/miekg/dns/svcb.go | 744 +++++ vendor/github.com/miekg/dns/tlsa.go | 44 + vendor/github.com/miekg/dns/tsig.go | 429 +++ vendor/github.com/miekg/dns/types.go | 1562 +++++++++ vendor/github.com/miekg/dns/udp.go | 102 + vendor/github.com/miekg/dns/udp_windows.go | 35 + vendor/github.com/miekg/dns/update.go | 110 + vendor/github.com/miekg/dns/version.go | 15 + vendor/github.com/miekg/dns/xfr.go | 266 ++ vendor/github.com/miekg/dns/zduplicate.go | 1340 ++++++++ vendor/github.com/miekg/dns/zmsg.go | 2875 +++++++++++++++++ vendor/github.com/miekg/dns/ztypes.go | 952 ++++++ .../github.com/szatmary/sonos/zoneplayer.go | 2 +- vendor/golang.org/x/net/ipv6/batch.go | 116 + vendor/golang.org/x/net/ipv6/control.go | 187 ++ .../x/net/ipv6/control_rfc2292_unix.go | 51 + .../x/net/ipv6/control_rfc3542_unix.go | 97 + vendor/golang.org/x/net/ipv6/control_stub.go | 14 + vendor/golang.org/x/net/ipv6/control_unix.go | 56 + .../golang.org/x/net/ipv6/control_windows.go | 12 + vendor/golang.org/x/net/ipv6/dgramopt.go | 301 ++ vendor/golang.org/x/net/ipv6/doc.go | 239 ++ vendor/golang.org/x/net/ipv6/endpoint.go | 127 + vendor/golang.org/x/net/ipv6/genericopt.go | 56 + vendor/golang.org/x/net/ipv6/header.go | 55 + vendor/golang.org/x/net/ipv6/helper.go | 58 + vendor/golang.org/x/net/ipv6/iana.go | 86 + vendor/golang.org/x/net/ipv6/icmp.go | 60 + vendor/golang.org/x/net/ipv6/icmp_bsd.go | 30 + vendor/golang.org/x/net/ipv6/icmp_linux.go | 27 + vendor/golang.org/x/net/ipv6/icmp_solaris.go | 27 + vendor/golang.org/x/net/ipv6/icmp_stub.go | 24 + vendor/golang.org/x/net/ipv6/icmp_windows.go | 22 + vendor/golang.org/x/net/ipv6/icmp_zos.go | 29 + vendor/golang.org/x/net/ipv6/payload.go | 23 + vendor/golang.org/x/net/ipv6/payload_cmsg.go | 71 + .../golang.org/x/net/ipv6/payload_nocmsg.go | 39 + vendor/golang.org/x/net/ipv6/sockopt.go | 43 + vendor/golang.org/x/net/ipv6/sockopt_posix.go | 90 + vendor/golang.org/x/net/ipv6/sockopt_stub.go | 47 + vendor/golang.org/x/net/ipv6/sys_aix.go | 80 + vendor/golang.org/x/net/ipv6/sys_asmreq.go | 25 + .../golang.org/x/net/ipv6/sys_asmreq_stub.go | 18 + vendor/golang.org/x/net/ipv6/sys_bpf.go | 25 + vendor/golang.org/x/net/ipv6/sys_bpf_stub.go | 17 + vendor/golang.org/x/net/ipv6/sys_bsd.go | 60 + vendor/golang.org/x/net/ipv6/sys_darwin.go | 80 + vendor/golang.org/x/net/ipv6/sys_freebsd.go | 94 + vendor/golang.org/x/net/ipv6/sys_linux.go | 76 + vendor/golang.org/x/net/ipv6/sys_solaris.go | 76 + vendor/golang.org/x/net/ipv6/sys_ssmreq.go | 55 + .../golang.org/x/net/ipv6/sys_ssmreq_stub.go | 22 + vendor/golang.org/x/net/ipv6/sys_stub.go | 14 + vendor/golang.org/x/net/ipv6/sys_windows.go | 68 + vendor/golang.org/x/net/ipv6/sys_zos.go | 72 + .../golang.org/x/net/ipv6/zsys_aix_ppc64.go | 69 + vendor/golang.org/x/net/ipv6/zsys_darwin.go | 64 + .../golang.org/x/net/ipv6/zsys_dragonfly.go | 42 + .../golang.org/x/net/ipv6/zsys_freebsd_386.go | 64 + .../x/net/ipv6/zsys_freebsd_amd64.go | 66 + .../golang.org/x/net/ipv6/zsys_freebsd_arm.go | 66 + .../x/net/ipv6/zsys_freebsd_arm64.go | 64 + .../x/net/ipv6/zsys_freebsd_riscv64.go | 64 + .../golang.org/x/net/ipv6/zsys_linux_386.go | 72 + .../golang.org/x/net/ipv6/zsys_linux_amd64.go | 74 + .../golang.org/x/net/ipv6/zsys_linux_arm.go | 72 + .../golang.org/x/net/ipv6/zsys_linux_arm64.go | 74 + .../x/net/ipv6/zsys_linux_loong64.go | 77 + .../golang.org/x/net/ipv6/zsys_linux_mips.go | 72 + .../x/net/ipv6/zsys_linux_mips64.go | 74 + .../x/net/ipv6/zsys_linux_mips64le.go | 74 + .../x/net/ipv6/zsys_linux_mipsle.go | 72 + .../golang.org/x/net/ipv6/zsys_linux_ppc.go | 72 + .../golang.org/x/net/ipv6/zsys_linux_ppc64.go | 74 + .../x/net/ipv6/zsys_linux_ppc64le.go | 74 + .../x/net/ipv6/zsys_linux_riscv64.go | 77 + .../golang.org/x/net/ipv6/zsys_linux_s390x.go | 74 + vendor/golang.org/x/net/ipv6/zsys_netbsd.go | 42 + vendor/golang.org/x/net/ipv6/zsys_openbsd.go | 42 + vendor/golang.org/x/net/ipv6/zsys_solaris.go | 63 + .../golang.org/x/net/ipv6/zsys_zos_s390x.go | 62 + vendor/modules.txt | 10 + 137 files changed, 26608 insertions(+), 42 deletions(-) create mode 100644 vendor/github.com/hashicorp/mdns/.gitignore create mode 100644 vendor/github.com/hashicorp/mdns/LICENSE create mode 100644 vendor/github.com/hashicorp/mdns/README.md create mode 100644 vendor/github.com/hashicorp/mdns/client.go create mode 100644 vendor/github.com/hashicorp/mdns/server.go create mode 100644 vendor/github.com/hashicorp/mdns/zone.go create mode 100644 vendor/github.com/ianr0bkny/go-sonos/LICENSE create mode 100644 vendor/github.com/ianr0bkny/go-sonos/ssdp/ssdp.go create mode 100644 vendor/github.com/miekg/dns/.codecov.yml create mode 100644 vendor/github.com/miekg/dns/.gitignore create mode 100644 vendor/github.com/miekg/dns/AUTHORS create mode 100644 vendor/github.com/miekg/dns/CODEOWNERS create mode 100644 vendor/github.com/miekg/dns/CONTRIBUTORS create mode 100644 vendor/github.com/miekg/dns/COPYRIGHT create mode 100644 vendor/github.com/miekg/dns/LICENSE create mode 100644 vendor/github.com/miekg/dns/Makefile.fuzz create mode 100644 vendor/github.com/miekg/dns/Makefile.release create mode 100644 vendor/github.com/miekg/dns/README.md create mode 100644 vendor/github.com/miekg/dns/acceptfunc.go create mode 100644 vendor/github.com/miekg/dns/client.go create mode 100644 vendor/github.com/miekg/dns/clientconfig.go create mode 100644 vendor/github.com/miekg/dns/dane.go create mode 100644 vendor/github.com/miekg/dns/defaults.go create mode 100644 vendor/github.com/miekg/dns/dns.go create mode 100644 vendor/github.com/miekg/dns/dnssec.go create mode 100644 vendor/github.com/miekg/dns/dnssec_keygen.go create mode 100644 vendor/github.com/miekg/dns/dnssec_keyscan.go create mode 100644 vendor/github.com/miekg/dns/dnssec_privkey.go create mode 100644 vendor/github.com/miekg/dns/doc.go create mode 100644 vendor/github.com/miekg/dns/duplicate.go create mode 100644 vendor/github.com/miekg/dns/edns.go create mode 100644 vendor/github.com/miekg/dns/format.go create mode 100644 vendor/github.com/miekg/dns/fuzz.go create mode 100644 vendor/github.com/miekg/dns/generate.go create mode 100644 vendor/github.com/miekg/dns/labels.go create mode 100644 vendor/github.com/miekg/dns/listen_go111.go create mode 100644 vendor/github.com/miekg/dns/listen_go_not111.go create mode 100644 vendor/github.com/miekg/dns/msg.go create mode 100644 vendor/github.com/miekg/dns/msg_helpers.go create mode 100644 vendor/github.com/miekg/dns/msg_truncate.go create mode 100644 vendor/github.com/miekg/dns/nsecx.go create mode 100644 vendor/github.com/miekg/dns/privaterr.go create mode 100644 vendor/github.com/miekg/dns/reverse.go create mode 100644 vendor/github.com/miekg/dns/sanitize.go create mode 100644 vendor/github.com/miekg/dns/scan.go create mode 100644 vendor/github.com/miekg/dns/scan_rr.go create mode 100644 vendor/github.com/miekg/dns/serve_mux.go create mode 100644 vendor/github.com/miekg/dns/server.go create mode 100644 vendor/github.com/miekg/dns/sig0.go create mode 100644 vendor/github.com/miekg/dns/singleinflight.go create mode 100644 vendor/github.com/miekg/dns/smimea.go create mode 100644 vendor/github.com/miekg/dns/svcb.go create mode 100644 vendor/github.com/miekg/dns/tlsa.go create mode 100644 vendor/github.com/miekg/dns/tsig.go create mode 100644 vendor/github.com/miekg/dns/types.go create mode 100644 vendor/github.com/miekg/dns/udp.go create mode 100644 vendor/github.com/miekg/dns/udp_windows.go create mode 100644 vendor/github.com/miekg/dns/update.go create mode 100644 vendor/github.com/miekg/dns/version.go create mode 100644 vendor/github.com/miekg/dns/xfr.go create mode 100644 vendor/github.com/miekg/dns/zduplicate.go create mode 100644 vendor/github.com/miekg/dns/zmsg.go create mode 100644 vendor/github.com/miekg/dns/ztypes.go create mode 100644 vendor/golang.org/x/net/ipv6/batch.go create mode 100644 vendor/golang.org/x/net/ipv6/control.go create mode 100644 vendor/golang.org/x/net/ipv6/control_rfc2292_unix.go create mode 100644 vendor/golang.org/x/net/ipv6/control_rfc3542_unix.go create mode 100644 vendor/golang.org/x/net/ipv6/control_stub.go create mode 100644 vendor/golang.org/x/net/ipv6/control_unix.go create mode 100644 vendor/golang.org/x/net/ipv6/control_windows.go create mode 100644 vendor/golang.org/x/net/ipv6/dgramopt.go create mode 100644 vendor/golang.org/x/net/ipv6/doc.go create mode 100644 vendor/golang.org/x/net/ipv6/endpoint.go create mode 100644 vendor/golang.org/x/net/ipv6/genericopt.go create mode 100644 vendor/golang.org/x/net/ipv6/header.go create mode 100644 vendor/golang.org/x/net/ipv6/helper.go create mode 100644 vendor/golang.org/x/net/ipv6/iana.go create mode 100644 vendor/golang.org/x/net/ipv6/icmp.go create mode 100644 vendor/golang.org/x/net/ipv6/icmp_bsd.go create mode 100644 vendor/golang.org/x/net/ipv6/icmp_linux.go create mode 100644 vendor/golang.org/x/net/ipv6/icmp_solaris.go create mode 100644 vendor/golang.org/x/net/ipv6/icmp_stub.go create mode 100644 vendor/golang.org/x/net/ipv6/icmp_windows.go create mode 100644 vendor/golang.org/x/net/ipv6/icmp_zos.go create mode 100644 vendor/golang.org/x/net/ipv6/payload.go create mode 100644 vendor/golang.org/x/net/ipv6/payload_cmsg.go create mode 100644 vendor/golang.org/x/net/ipv6/payload_nocmsg.go create mode 100644 vendor/golang.org/x/net/ipv6/sockopt.go create mode 100644 vendor/golang.org/x/net/ipv6/sockopt_posix.go create mode 100644 vendor/golang.org/x/net/ipv6/sockopt_stub.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_aix.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_asmreq.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_asmreq_stub.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_bpf.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_bpf_stub.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_bsd.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_darwin.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_freebsd.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_linux.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_solaris.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_ssmreq.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_ssmreq_stub.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_stub.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_windows.go create mode 100644 vendor/golang.org/x/net/ipv6/sys_zos.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_aix_ppc64.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_darwin.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_dragonfly.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_freebsd_386.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_freebsd_amd64.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_freebsd_arm.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_freebsd_arm64.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_386.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_amd64.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_arm.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_arm64.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_loong64.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_mips.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_mips64.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_mips64le.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_mipsle.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_ppc.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_ppc64.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_ppc64le.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_riscv64.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_linux_s390x.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_netbsd.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_openbsd.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_solaris.go create mode 100644 vendor/golang.org/x/net/ipv6/zsys_zos_s390x.go diff --git a/go.mod b/go.mod index 5f8f151..04b8ece 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,9 @@ go 1.20 require ( github.com/alexflint/go-arg v1.4.3 github.com/gin-gonic/gin v1.9.1 + github.com/hashicorp/mdns v1.0.5 github.com/holoplot/go-evdev v0.0.0-20230626094006-70c9462cc0f2 + github.com/ianr0bkny/go-sonos v0.0.0-20171025003233-056585059953 github.com/pion/rtcp v1.2.10 github.com/pion/webrtc/v3 v3.2.10 github.com/szatmary/sonos v0.0.0-20191204204454-000c9219ff0e @@ -33,6 +35,7 @@ require ( github.com/leodido/go-urn v1.2.4 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-pointer v0.0.1 // indirect + github.com/miekg/dns v1.1.41 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect diff --git a/go.sum b/go.sum index ea00514..582b08b 100644 --- a/go.sum +++ b/go.sum @@ -63,9 +63,13 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/mdns v1.0.5 h1:1M5hW1cunYeoXOqHwEb/GBDDHAFo0Yqb/uz/beC6LbE= +github.com/hashicorp/mdns v1.0.5/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/holoplot/go-evdev v0.0.0-20230626094006-70c9462cc0f2 h1:bxudnRpvfq2PMYEYN7VGj03+MXG4EobVUmONigCx1yA= github.com/holoplot/go-evdev v0.0.0-20230626094006-70c9462cc0f2/go.mod h1:iHAf8OIncO2gcQ8XOjS7CMJ2aPbX2Bs0wl5pZyanEqk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianr0bkny/go-sonos v0.0.0-20171025003233-056585059953 h1:n9vfPPfv66b56toxNZk/w7VPSZ5eK/NxVrtRs1uptzo= +github.com/ianr0bkny/go-sonos v0.0.0-20171025003233-056585059953/go.mod h1:uvzy1yuMP2e76h67o5rpaXPDPq4e/cUGU4RYkm9a0Js= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -87,6 +91,8 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= +github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -203,6 +209,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= @@ -216,7 +223,9 @@ golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -229,6 +238,8 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/ring/ring.go b/pkg/ring/ring.go index 3c5bd5b..eb544f1 100644 --- a/pkg/ring/ring.go +++ b/pkg/ring/ring.go @@ -11,31 +11,33 @@ import ( "go.uber.org/zap" ) -type Player interface { - Play(uri *url.URL) error +type PlayHandler interface { + Play(*url.URL) error + Watch(context.Context) error } type RingHandler struct { - lg *zap.Logger - cfg *common.ConfigRing - players []Player + lg *zap.Logger + cfg *common.ConfigRing + playHandlers []PlayHandler } func NewRingHandler(lg *zap.Logger, cfg *common.ConfigRing) (*RingHandler, error) { - spl, err := sonos.NewSonosPlayers(lg.With(zap.String("context", "sonos")), cfg.SonosTarget) + spl, err := sonos.NewSonosHandler(lg.With(zap.String("context", "sonos")), cfg.SonosTarget) if err != nil { return nil, err } - pl := make([]Player, 0, len(spl)) - for _, sp := range spl { - pl = append(pl, sp) - } - - return &RingHandler{lg, cfg, pl}, nil + return &RingHandler{lg, cfg, []PlayHandler{spl}}, nil } func (h *RingHandler) Watch(ctx context.Context) error { + for _, p := range h.playHandlers { + if err := p.Watch(ctx); err != nil { + return err + } + } + d, err := evdev.Open(h.cfg.Device) if err != nil { return err @@ -61,7 +63,7 @@ func (h *RingHandler) Watch(ctx context.Context) error { e, err := d.ReadOne() if err == nil && e.Code == key && e.Value == 0 { tu := bu.JoinPath(h.cfg.JingleName) - for _, p := range h.players { + for _, p := range h.playHandlers { if err := p.Play(tu); err != nil { h.lg.Error("failed to play", zap.Error(err)) } diff --git a/pkg/ring/sonos/sonos.go b/pkg/ring/sonos/sonos.go index 3ea667b..d74bd7b 100644 --- a/pkg/ring/sonos/sonos.go +++ b/pkg/ring/sonos/sonos.go @@ -1,56 +1,184 @@ package sonos import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net" + "net/http" "net/url" - "time" - so "github.com/szatmary/sonos" + "github.com/google/uuid" + "github.com/hashicorp/mdns" "go.uber.org/zap" ) +type sonosInfo struct { + Device struct { + Id string `json:"id"` + SerialNumber string `json:"serialNumber"` + Model string `json:"model"` + ModelDisplayName string `json:"modelDisplayName"` + Color string `json:"color"` + Capabilities []string `json:"capabilities"` + ApiVersion string `json:"apiVersion"` + MinApiVersion string `json:"minApiVersion"` + Name string `json:"name"` + WebsocketUrl string `json:"websocketUrl"` + SoftwareVersion string `json:"softwareVersion"` + HwVersion string `json:"hwVersion"` + SwGen int `json:"swGen"` + } `json:"device"` + HouseholdId string `json:"householdId"` + PlayerId string `json:"playerId"` + GroupId string `json:"groupId"` + WebsocketUrl string `json:"websocketUrl"` + RestUrl string `json:"restUrl"` +} + +type sonosAudioClip struct { + Name string `json:"name"` + AppId string `json:"appId"` + StreamUrl string `json:"streamUrl"` + Volume int `json:"volume"` +} + +type SonosHandler struct { + lg *zap.Logger + target string + players map[string]*SonosPlayer +} + type SonosPlayer struct { - lg *zap.Logger - zp *so.ZonePlayer + client *http.Client + address *url.URL + info sonosInfo } -func NewSonosPlayers(lg *zap.Logger, target string) ([]*SonosPlayer, error) { - spl, err := SearchTargets(lg, target) +func NewSonosPlayer(address *url.URL) (*SonosPlayer, error) { + client := &http.Client{Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }} + + return &SonosPlayer{client, address, sonosInfo{}}, nil +} + +func NewSonosHandler(lg *zap.Logger, target string) (*SonosHandler, error) { + return &SonosHandler{lg, target, make(map[string]*SonosPlayer)}, nil +} + +func (p *SonosPlayer) init() error { + req, err := http.NewRequest(http.MethodGet, p.address.JoinPath("api/v1/players/local/info").String(), nil) + if err != nil { + return err + } + + // for some reason there must be a api key given + // does not matter what + req.Header.Add("X-Sonos-Api-Key", uuid.NewString()) + + res, err := p.client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + if res.StatusCode != 200 { + return fmt.Errorf("received status %d", res.StatusCode) + } + + b, err := io.ReadAll(res.Body) + if err != nil { + return err + } + + err = json.Unmarshal(b, &p.info) if err != nil { - return nil, err + return err } - return spl, nil + return nil } func (p *SonosPlayer) Play(uri *url.URL) error { - err := p.zp.SetAVTransportURI(uri.String()) + sab := sonosAudioClip{ + Name: "Pull Bell", + AppId: "com.acme.app", + StreamUrl: uri.String(), + Volume: 5, + } + + b, err := json.Marshal(sab) if err != nil { return err } - return p.zp.Play() -} + req, err := http.NewRequest(http.MethodPost, p.address.JoinPath(fmt.Sprintf("api/v1/players/%s/audioClip", p.info.PlayerId)).String(), bytes.NewBuffer(b)) + if err != nil { + return err + } -func SearchTargets(lg *zap.Logger, target string) ([]*SonosPlayer, error) { - players := make([]*SonosPlayer, 0) + // for some reason there must be a api key given + // does not matter what + req.Header.Add("X-Sonos-Api-Key", uuid.NewString()) + req.Header.Set("Content-Type", "application/json; charset=UTF-8") - son, err := so.NewSonos() + res, err := p.client.Do(req) if err != nil { - return nil, err - } - defer son.Close() - - found, _ := son.Search() - to := time.After(5 * time.Second) - for { - select { - case <-to: - return players, nil - case zp := <-found: - lg.Info("found player", zap.String("name", zp.RoomName())) - if target == "-" || zp.RoomName() == target { - players = append(players, &SonosPlayer{lg, zp}) + return err + } + defer res.Body.Close() + + if res.StatusCode != 200 { + return fmt.Errorf("received status %d", res.StatusCode) + } + + return nil +} + +func (h *SonosHandler) Watch(ctx context.Context) error { + // Make a channel for results and start listening + entriesCh := make(chan *mdns.ServiceEntry, 4) + go func() { + for { + select { + case <-ctx.Done(): + return + case e := <-entriesCh: + h.lg.Info("found player", zap.String("name", e.Name), zap.String("address", e.AddrV4.String())) + if _, has := h.players[e.Name]; !has { + p, err := NewSonosPlayer(&url.URL{ + Scheme: "https", + Host: net.JoinHostPort(e.AddrV4.String(), "1443"), + }) + if err != nil { + h.lg.Error("failed to create player", zap.Error(err)) + continue + } + if err := p.init(); err != nil { + h.lg.Error("failed to init player", zap.Error(err)) + continue + } + + h.players[e.Name] = p + } } } + }() + + // Start the lookup + return mdns.Lookup("_sonos._tcp", entriesCh) +} + +func (h *SonosHandler) Play(uri *url.URL) error { + for _, p := range h.players { + if err := p.Play(uri); err != nil { + h.lg.Error("failed to play", zap.String("address", p.address.String()), zap.Error(err)) + } } + + return nil } diff --git a/vendor/github.com/hashicorp/mdns/.gitignore b/vendor/github.com/hashicorp/mdns/.gitignore new file mode 100644 index 0000000..8365624 --- /dev/null +++ b/vendor/github.com/hashicorp/mdns/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test diff --git a/vendor/github.com/hashicorp/mdns/LICENSE b/vendor/github.com/hashicorp/mdns/LICENSE new file mode 100644 index 0000000..a5df10e --- /dev/null +++ b/vendor/github.com/hashicorp/mdns/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Armon Dadgar + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/hashicorp/mdns/README.md b/vendor/github.com/hashicorp/mdns/README.md new file mode 100644 index 0000000..38eee81 --- /dev/null +++ b/vendor/github.com/hashicorp/mdns/README.md @@ -0,0 +1,37 @@ +mdns +==== + +Simple mDNS client/server library in Golang. mDNS or Multicast DNS can be +used to discover services on the local network without the use of an authoritative +DNS server. This enables peer-to-peer discovery. It is important to note that many +networks restrict the use of multicasting, which prevents mDNS from functioning. +Notably, multicast cannot be used in any sort of cloud, or shared infrastructure +environment. However it works well in most office, home, or private infrastructure +environments. + +Using the library is very simple, here is an example of publishing a service entry: + + // Setup our service export + host, _ := os.Hostname() + info := []string{"My awesome service"} + service, _ := mdns.NewMDNSService(host, "_foobar._tcp", "", "", 8000, nil, info) + + // Create the mDNS server, defer shutdown + server, _ := mdns.NewServer(&mdns.Config{Zone: service}) + defer server.Shutdown() + + +Doing a lookup for service providers is also very simple: + + // Make a channel for results and start listening + entriesCh := make(chan *mdns.ServiceEntry, 4) + go func() { + for entry := range entriesCh { + fmt.Printf("Got new entry: %v\n", entry) + } + }() + + // Start the lookup + mdns.Lookup("_foobar._tcp", entriesCh) + close(entriesCh) + diff --git a/vendor/github.com/hashicorp/mdns/client.go b/vendor/github.com/hashicorp/mdns/client.go new file mode 100644 index 0000000..7d530e8 --- /dev/null +++ b/vendor/github.com/hashicorp/mdns/client.go @@ -0,0 +1,407 @@ +package mdns + +import ( + "fmt" + "log" + "net" + "strings" + "sync/atomic" + "time" + + "github.com/miekg/dns" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// ServiceEntry is returned after we query for a service +type ServiceEntry struct { + Name string + Host string + AddrV4 net.IP + AddrV6 net.IP + Port int + Info string + InfoFields []string + + Addr net.IP // @Deprecated + + hasTXT bool + sent bool +} + +// complete is used to check if we have all the info we need +func (s *ServiceEntry) complete() bool { + return (s.AddrV4 != nil || s.AddrV6 != nil || s.Addr != nil) && s.Port != 0 && s.hasTXT +} + +// QueryParam is used to customize how a Lookup is performed +type QueryParam struct { + Service string // Service to lookup + Domain string // Lookup domain, default "local" + Timeout time.Duration // Lookup timeout, default 1 second + Interface *net.Interface // Multicast interface to use + Entries chan<- *ServiceEntry // Entries Channel + WantUnicastResponse bool // Unicast response desired, as per 5.4 in RFC + DisableIPv4 bool // Whether to disable usage of IPv4 for MDNS operations. Does not affect discovered addresses. + DisableIPv6 bool // Whether to disable usage of IPv6 for MDNS operations. Does not affect discovered addresses. +} + +// DefaultParams is used to return a default set of QueryParam's +func DefaultParams(service string) *QueryParam { + return &QueryParam{ + Service: service, + Domain: "local", + Timeout: time.Second, + Entries: make(chan *ServiceEntry), + WantUnicastResponse: false, // TODO(reddaly): Change this default. + DisableIPv4: false, + DisableIPv6: false, + } +} + +// Query looks up a given service, in a domain, waiting at most +// for a timeout before finishing the query. The results are streamed +// to a channel. Sends will not block, so clients should make sure to +// either read or buffer. +func Query(params *QueryParam) error { + // Create a new client + client, err := newClient(!params.DisableIPv4, !params.DisableIPv6) + if err != nil { + return err + } + defer client.Close() + + // Set the multicast interface + if params.Interface != nil { + if err := client.setInterface(params.Interface); err != nil { + return err + } + } + + // Ensure defaults are set + if params.Domain == "" { + params.Domain = "local" + } + if params.Timeout == 0 { + params.Timeout = time.Second + } + + // Run the query + return client.query(params) +} + +// Lookup is the same as Query, however it uses all the default parameters +func Lookup(service string, entries chan<- *ServiceEntry) error { + params := DefaultParams(service) + params.Entries = entries + return Query(params) +} + +// Client provides a query interface that can be used to +// search for service providers using mDNS +type client struct { + use_ipv4 bool + use_ipv6 bool + + ipv4UnicastConn *net.UDPConn + ipv6UnicastConn *net.UDPConn + + ipv4MulticastConn *net.UDPConn + ipv6MulticastConn *net.UDPConn + + closed int32 + closedCh chan struct{} // TODO(reddaly): This doesn't appear to be used. +} + +// NewClient creates a new mdns Client that can be used to query +// for records +func newClient(v4 bool, v6 bool) (*client, error) { + if !v4 && !v6 { + return nil, fmt.Errorf("Must enable at least one of IPv4 and IPv6 querying") + } + + // TODO(reddaly): At least attempt to bind to the port required in the spec. + // Create a IPv4 listener + var uconn4 *net.UDPConn + var uconn6 *net.UDPConn + var mconn4 *net.UDPConn + var mconn6 *net.UDPConn + var err error + + if v4 { + uconn4, err = net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) + if err != nil { + log.Printf("[ERR] mdns: Failed to bind to udp4 port: %v", err) + } + } + + if v6 { + uconn6, err = net.ListenUDP("udp6", &net.UDPAddr{IP: net.IPv6zero, Port: 0}) + if err != nil { + log.Printf("[ERR] mdns: Failed to bind to udp6 port: %v", err) + } + } + + if uconn4 == nil && uconn6 == nil { + return nil, fmt.Errorf("failed to bind to any unicast udp port") + } + + if v4 { + mconn4, err = net.ListenMulticastUDP("udp4", nil, ipv4Addr) + if err != nil { + log.Printf("[ERR] mdns: Failed to bind to udp4 port: %v", err) + } + } + if v6 { + mconn6, err = net.ListenMulticastUDP("udp6", nil, ipv6Addr) + if err != nil { + log.Printf("[ERR] mdns: Failed to bind to udp6 port: %v", err) + } + } + + if mconn4 == nil && mconn6 == nil { + return nil, fmt.Errorf("failed to bind to any multicast udp port") + } + + c := &client{ + use_ipv4: v4, + use_ipv6: v6, + ipv4MulticastConn: mconn4, + ipv6MulticastConn: mconn6, + ipv4UnicastConn: uconn4, + ipv6UnicastConn: uconn6, + closedCh: make(chan struct{}), + } + return c, nil +} + +// Close is used to cleanup the client +func (c *client) Close() error { + if !atomic.CompareAndSwapInt32(&c.closed, 0, 1) { + // something else already closed it + return nil + } + + log.Printf("[INFO] mdns: Closing client %v", *c) + close(c.closedCh) + + if c.ipv4UnicastConn != nil { + c.ipv4UnicastConn.Close() + } + if c.ipv6UnicastConn != nil { + c.ipv6UnicastConn.Close() + } + if c.ipv4MulticastConn != nil { + c.ipv4MulticastConn.Close() + } + if c.ipv6MulticastConn != nil { + c.ipv6MulticastConn.Close() + } + + return nil +} + +// setInterface is used to set the query interface, uses system +// default if not provided +func (c *client) setInterface(iface *net.Interface) error { + if c.use_ipv4 { + p := ipv4.NewPacketConn(c.ipv4UnicastConn) + if err := p.SetMulticastInterface(iface); err != nil { + return err + } + p = ipv4.NewPacketConn(c.ipv4MulticastConn) + if err := p.SetMulticastInterface(iface); err != nil { + return err + } + } + if c.use_ipv6 { + p2 := ipv6.NewPacketConn(c.ipv6UnicastConn) + if err := p2.SetMulticastInterface(iface); err != nil { + return err + } + p2 = ipv6.NewPacketConn(c.ipv6MulticastConn) + if err := p2.SetMulticastInterface(iface); err != nil { + return err + } + } + return nil +} + +// query is used to perform a lookup and stream results +func (c *client) query(params *QueryParam) error { + // Create the service name + serviceAddr := fmt.Sprintf("%s.%s.", trimDot(params.Service), trimDot(params.Domain)) + + // Start listening for response packets + msgCh := make(chan *dns.Msg, 32) + if c.use_ipv4 { + go c.recv(c.ipv4UnicastConn, msgCh) + go c.recv(c.ipv4MulticastConn, msgCh) + } + if c.use_ipv6 { + go c.recv(c.ipv6UnicastConn, msgCh) + go c.recv(c.ipv6MulticastConn, msgCh) + } + + // Send the query + m := new(dns.Msg) + m.SetQuestion(serviceAddr, dns.TypePTR) + // RFC 6762, section 18.12. Repurposing of Top Bit of qclass in Question + // Section + // + // In the Question Section of a Multicast DNS query, the top bit of the qclass + // field is used to indicate that unicast responses are preferred for this + // particular question. (See Section 5.4.) + if params.WantUnicastResponse { + m.Question[0].Qclass |= 1 << 15 + } + m.RecursionDesired = false + if err := c.sendQuery(m); err != nil { + return err + } + + // Map the in-progress responses + inprogress := make(map[string]*ServiceEntry) + + // Listen until we reach the timeout + finish := time.After(params.Timeout) + for { + select { + case resp := <-msgCh: + var inp *ServiceEntry + for _, answer := range append(resp.Answer, resp.Extra...) { + // TODO(reddaly): Check that response corresponds to serviceAddr? + switch rr := answer.(type) { + case *dns.PTR: + // Create new entry for this + inp = ensureName(inprogress, rr.Ptr) + + case *dns.SRV: + // Check for a target mismatch + if rr.Target != rr.Hdr.Name { + alias(inprogress, rr.Hdr.Name, rr.Target) + } + + // Get the port + inp = ensureName(inprogress, rr.Hdr.Name) + inp.Host = rr.Target + inp.Port = int(rr.Port) + + case *dns.TXT: + // Pull out the txt + inp = ensureName(inprogress, rr.Hdr.Name) + inp.Info = strings.Join(rr.Txt, "|") + inp.InfoFields = rr.Txt + inp.hasTXT = true + + case *dns.A: + // Pull out the IP + inp = ensureName(inprogress, rr.Hdr.Name) + inp.Addr = rr.A // @Deprecated + inp.AddrV4 = rr.A + + case *dns.AAAA: + // Pull out the IP + inp = ensureName(inprogress, rr.Hdr.Name) + inp.Addr = rr.AAAA // @Deprecated + inp.AddrV6 = rr.AAAA + } + } + + if inp == nil { + continue + } + + // Check if this entry is complete + if inp.complete() { + if inp.sent { + continue + } + inp.sent = true + select { + case params.Entries <- inp: + default: + } + } else { + // Fire off a node specific query + m := new(dns.Msg) + m.SetQuestion(inp.Name, dns.TypePTR) + m.RecursionDesired = false + if err := c.sendQuery(m); err != nil { + log.Printf("[ERR] mdns: Failed to query instance %s: %v", inp.Name, err) + } + } + case <-finish: + return nil + } + } +} + +// sendQuery is used to multicast a query out +func (c *client) sendQuery(q *dns.Msg) error { + buf, err := q.Pack() + if err != nil { + return err + } + if c.ipv4UnicastConn != nil { + _, err = c.ipv4UnicastConn.WriteToUDP(buf, ipv4Addr) + if err != nil { + return err + } + } + if c.ipv6UnicastConn != nil { + _, err = c.ipv6UnicastConn.WriteToUDP(buf, ipv6Addr) + if err != nil { + return err + } + } + return nil +} + +// recv is used to receive until we get a shutdown +func (c *client) recv(l *net.UDPConn, msgCh chan *dns.Msg) { + if l == nil { + return + } + buf := make([]byte, 65536) + for atomic.LoadInt32(&c.closed) == 0 { + n, err := l.Read(buf) + + if atomic.LoadInt32(&c.closed) == 1 { + return + } + + if err != nil { + log.Printf("[ERR] mdns: Failed to read packet: %v", err) + continue + } + msg := new(dns.Msg) + if err := msg.Unpack(buf[:n]); err != nil { + log.Printf("[ERR] mdns: Failed to unpack packet: %v", err) + continue + } + select { + case msgCh <- msg: + case <-c.closedCh: + return + } + } +} + +// ensureName is used to ensure the named node is in progress +func ensureName(inprogress map[string]*ServiceEntry, name string) *ServiceEntry { + if inp, ok := inprogress[name]; ok { + return inp + } + inp := &ServiceEntry{ + Name: name, + } + inprogress[name] = inp + return inp +} + +// alias is used to setup an alias between two entries +func alias(inprogress map[string]*ServiceEntry, src, dst string) { + srcEntry := ensureName(inprogress, src) + inprogress[dst] = srcEntry +} diff --git a/vendor/github.com/hashicorp/mdns/server.go b/vendor/github.com/hashicorp/mdns/server.go new file mode 100644 index 0000000..a33668d --- /dev/null +++ b/vendor/github.com/hashicorp/mdns/server.go @@ -0,0 +1,288 @@ +package mdns + +import ( + "fmt" + "log" + "net" + "strings" + "sync/atomic" + + "github.com/miekg/dns" +) + +const ( + ipv4mdns = "224.0.0.251" + ipv6mdns = "ff02::fb" + mdnsPort = 5353 + forceUnicastResponses = false +) + +var ( + ipv4Addr = &net.UDPAddr{ + IP: net.ParseIP(ipv4mdns), + Port: mdnsPort, + } + ipv6Addr = &net.UDPAddr{ + IP: net.ParseIP(ipv6mdns), + Port: mdnsPort, + } +) + +// Config is used to configure the mDNS server +type Config struct { + // Zone must be provided to support responding to queries + Zone Zone + + // Iface if provided binds the multicast listener to the given + // interface. If not provided, the system default multicase interface + // is used. + Iface *net.Interface + + // LogEmptyResponses indicates the server should print an informative message + // when there is an mDNS query for which the server has no response. + LogEmptyResponses bool +} + +// mDNS server is used to listen for mDNS queries and respond if we +// have a matching local record +type Server struct { + config *Config + + ipv4List *net.UDPConn + ipv6List *net.UDPConn + + shutdown int32 + shutdownCh chan struct{} +} + +// NewServer is used to create a new mDNS server from a config +func NewServer(config *Config) (*Server, error) { + // Create the listeners + ipv4List, _ := net.ListenMulticastUDP("udp4", config.Iface, ipv4Addr) + ipv6List, _ := net.ListenMulticastUDP("udp6", config.Iface, ipv6Addr) + + // Check if we have any listener + if ipv4List == nil && ipv6List == nil { + return nil, fmt.Errorf("No multicast listeners could be started") + } + + s := &Server{ + config: config, + ipv4List: ipv4List, + ipv6List: ipv6List, + shutdownCh: make(chan struct{}), + } + + if ipv4List != nil { + go s.recv(s.ipv4List) + } + + if ipv6List != nil { + go s.recv(s.ipv6List) + } + + return s, nil +} + +// Shutdown is used to shutdown the listener +func (s *Server) Shutdown() error { + if !atomic.CompareAndSwapInt32(&s.shutdown, 0, 1) { + // something else already closed us + return nil + } + + close(s.shutdownCh) + + if s.ipv4List != nil { + s.ipv4List.Close() + } + if s.ipv6List != nil { + s.ipv6List.Close() + } + return nil +} + +// recv is a long running routine to receive packets from an interface +func (s *Server) recv(c *net.UDPConn) { + if c == nil { + return + } + buf := make([]byte, 65536) + for atomic.LoadInt32(&s.shutdown) == 0 { + n, from, err := c.ReadFrom(buf) + + if err != nil { + continue + } + if err := s.parsePacket(buf[:n], from); err != nil { + log.Printf("[ERR] mdns: Failed to handle query: %v", err) + } + } +} + +// parsePacket is used to parse an incoming packet +func (s *Server) parsePacket(packet []byte, from net.Addr) error { + var msg dns.Msg + if err := msg.Unpack(packet); err != nil { + log.Printf("[ERR] mdns: Failed to unpack packet: %v", err) + return err + } + return s.handleQuery(&msg, from) +} + +// handleQuery is used to handle an incoming query +func (s *Server) handleQuery(query *dns.Msg, from net.Addr) error { + if query.Opcode != dns.OpcodeQuery { + // "In both multicast query and multicast response messages, the OPCODE MUST + // be zero on transmission (only standard queries are currently supported + // over multicast). Multicast DNS messages received with an OPCODE other + // than zero MUST be silently ignored." Note: OpcodeQuery == 0 + return fmt.Errorf("mdns: received query with non-zero Opcode %v: %v", query.Opcode, *query) + } + if query.Rcode != 0 { + // "In both multicast query and multicast response messages, the Response + // Code MUST be zero on transmission. Multicast DNS messages received with + // non-zero Response Codes MUST be silently ignored." + return fmt.Errorf("mdns: received query with non-zero Rcode %v: %v", query.Rcode, *query) + } + + // TODO(reddaly): Handle "TC (Truncated) Bit": + // In query messages, if the TC bit is set, it means that additional + // Known-Answer records may be following shortly. A responder SHOULD + // record this fact, and wait for those additional Known-Answer records, + // before deciding whether to respond. If the TC bit is clear, it means + // that the querying host has no additional Known Answers. + if query.Truncated { + return fmt.Errorf("[ERR] mdns: support for DNS requests with high truncated bit not implemented: %v", *query) + } + + var unicastAnswer, multicastAnswer []dns.RR + + // Handle each question + for _, q := range query.Question { + mrecs, urecs := s.handleQuestion(q) + multicastAnswer = append(multicastAnswer, mrecs...) + unicastAnswer = append(unicastAnswer, urecs...) + } + + // See section 18 of RFC 6762 for rules about DNS headers. + resp := func(unicast bool) *dns.Msg { + // 18.1: ID (Query Identifier) + // 0 for multicast response, query.Id for unicast response + id := uint16(0) + if unicast { + id = query.Id + } + + var answer []dns.RR + if unicast { + answer = unicastAnswer + } else { + answer = multicastAnswer + } + if len(answer) == 0 { + return nil + } + + return &dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: id, + + // 18.2: QR (Query/Response) Bit - must be set to 1 in response. + Response: true, + + // 18.3: OPCODE - must be zero in response (OpcodeQuery == 0) + Opcode: dns.OpcodeQuery, + + // 18.4: AA (Authoritative Answer) Bit - must be set to 1 + Authoritative: true, + + // The following fields must all be set to 0: + // 18.5: TC (TRUNCATED) Bit + // 18.6: RD (Recursion Desired) Bit + // 18.7: RA (Recursion Available) Bit + // 18.8: Z (Zero) Bit + // 18.9: AD (Authentic Data) Bit + // 18.10: CD (Checking Disabled) Bit + // 18.11: RCODE (Response Code) + }, + // 18.12 pertains to questions (handled by handleQuestion) + // 18.13 pertains to resource records (handled by handleQuestion) + + // 18.14: Name Compression - responses should be compressed (though see + // caveats in the RFC), so set the Compress bit (part of the dns library + // API, not part of the DNS packet) to true. + Compress: true, + + Answer: answer, + } + } + + if s.config.LogEmptyResponses && len(multicastAnswer) == 0 && len(unicastAnswer) == 0 { + questions := make([]string, len(query.Question)) + for i, q := range query.Question { + questions[i] = q.Name + } + log.Printf("no responses for query with questions: %s", strings.Join(questions, ", ")) + } + + if mresp := resp(false); mresp != nil { + if err := s.sendResponse(mresp, from, false); err != nil { + return fmt.Errorf("mdns: error sending multicast response: %v", err) + } + } + if uresp := resp(true); uresp != nil { + if err := s.sendResponse(uresp, from, true); err != nil { + return fmt.Errorf("mdns: error sending unicast response: %v", err) + } + } + return nil +} + +// handleQuestion is used to handle an incoming question +// +// The response to a question may be transmitted over multicast, unicast, or +// both. The return values are DNS records for each transmission type. +func (s *Server) handleQuestion(q dns.Question) (multicastRecs, unicastRecs []dns.RR) { + records := s.config.Zone.Records(q) + + if len(records) == 0 { + return nil, nil + } + + // Handle unicast and multicast responses. + // TODO(reddaly): The decision about sending over unicast vs. multicast is not + // yet fully compliant with RFC 6762. For example, the unicast bit should be + // ignored if the records in question are close to TTL expiration. For now, + // we just use the unicast bit to make the decision, as per the spec: + // RFC 6762, section 18.12. Repurposing of Top Bit of qclass in Question + // Section + // + // In the Question Section of a Multicast DNS query, the top bit of the + // qclass field is used to indicate that unicast responses are preferred + // for this particular question. (See Section 5.4.) + if q.Qclass&(1<<15) != 0 || forceUnicastResponses { + return nil, records + } + return records, nil +} + +// sendResponse is used to send a response packet +func (s *Server) sendResponse(resp *dns.Msg, from net.Addr, unicast bool) error { + // TODO(reddaly): Respect the unicast argument, and allow sending responses + // over multicast. + buf, err := resp.Pack() + if err != nil { + return err + } + + // Determine the socket to send from + addr := from.(*net.UDPAddr) + if addr.IP.To4() != nil { + _, err = s.ipv4List.WriteToUDP(buf, addr) + return err + } else { + _, err = s.ipv6List.WriteToUDP(buf, addr) + return err + } +} diff --git a/vendor/github.com/hashicorp/mdns/zone.go b/vendor/github.com/hashicorp/mdns/zone.go new file mode 100644 index 0000000..6f442c7 --- /dev/null +++ b/vendor/github.com/hashicorp/mdns/zone.go @@ -0,0 +1,307 @@ +package mdns + +import ( + "fmt" + "net" + "os" + "strings" + + "github.com/miekg/dns" +) + +const ( + // defaultTTL is the default TTL value in returned DNS records in seconds. + defaultTTL = 120 +) + +// Zone is the interface used to integrate with the server and +// to serve records dynamically +type Zone interface { + // Records returns DNS records in response to a DNS question. + Records(q dns.Question) []dns.RR +} + +// MDNSService is used to export a named service by implementing a Zone +type MDNSService struct { + Instance string // Instance name (e.g. "hostService name") + Service string // Service name (e.g. "_http._tcp.") + Domain string // If blank, assumes "local" + HostName string // Host machine DNS name (e.g. "mymachine.net.") + Port int // Service Port + IPs []net.IP // IP addresses for the service's host + TXT []string // Service TXT records + + serviceAddr string // Fully qualified service address + instanceAddr string // Fully qualified instance address + enumAddr string // _services._dns-sd._udp. +} + +// validateFQDN returns an error if the passed string is not a fully qualified +// hdomain name (more specifically, a hostname). +func validateFQDN(s string) error { + if len(s) == 0 { + return fmt.Errorf("FQDN must not be blank") + } + if s[len(s)-1] != '.' { + return fmt.Errorf("FQDN must end in period: %s", s) + } + // TODO(reddaly): Perform full validation. + + return nil +} + +// NewMDNSService returns a new instance of MDNSService. +// +// If domain, hostName, or ips is set to the zero value, then a default value +// will be inferred from the operating system. +// +// TODO(reddaly): This interface may need to change to account for "unique +// record" conflict rules of the mDNS protocol. Upon startup, the server should +// check to ensure that the instance name does not conflict with other instance +// names, and, if required, select a new name. There may also be conflicting +// hostName A/AAAA records. +func NewMDNSService(instance, service, domain, hostName string, port int, ips []net.IP, txt []string) (*MDNSService, error) { + // Sanity check inputs + if instance == "" { + return nil, fmt.Errorf("missing service instance name") + } + if service == "" { + return nil, fmt.Errorf("missing service name") + } + if port == 0 { + return nil, fmt.Errorf("missing service port") + } + + // Set default domain + if domain == "" { + domain = "local." + } + if err := validateFQDN(domain); err != nil { + return nil, fmt.Errorf("domain %q is not a fully-qualified domain name: %v", domain, err) + } + + // Get host information if no host is specified. + if hostName == "" { + var err error + hostName, err = os.Hostname() + if err != nil { + return nil, fmt.Errorf("could not determine host: %v", err) + } + hostName = fmt.Sprintf("%s.", hostName) + } + if err := validateFQDN(hostName); err != nil { + return nil, fmt.Errorf("hostName %q is not a fully-qualified domain name: %v", hostName, err) + } + + if len(ips) == 0 { + var err error + ips, err = net.LookupIP(hostName) + if err != nil { + // Try appending the host domain suffix and lookup again + // (required for Linux-based hosts) + tmpHostName := fmt.Sprintf("%s%s", hostName, domain) + + ips, err = net.LookupIP(tmpHostName) + + if err != nil { + return nil, fmt.Errorf("could not determine host IP addresses for %s", hostName) + } + } + } + for _, ip := range ips { + if ip.To4() == nil && ip.To16() == nil { + return nil, fmt.Errorf("invalid IP address in IPs list: %v", ip) + } + } + + return &MDNSService{ + Instance: instance, + Service: service, + Domain: domain, + HostName: hostName, + Port: port, + IPs: ips, + TXT: txt, + serviceAddr: fmt.Sprintf("%s.%s.", trimDot(service), trimDot(domain)), + instanceAddr: fmt.Sprintf("%s.%s.%s.", instance, trimDot(service), trimDot(domain)), + enumAddr: fmt.Sprintf("_services._dns-sd._udp.%s.", trimDot(domain)), + }, nil +} + +// trimDot is used to trim the dots from the start or end of a string +func trimDot(s string) string { + return strings.Trim(s, ".") +} + +// Records returns DNS records in response to a DNS question. +func (m *MDNSService) Records(q dns.Question) []dns.RR { + switch q.Name { + case m.enumAddr: + return m.serviceEnum(q) + case m.serviceAddr: + return m.serviceRecords(q) + case m.instanceAddr: + return m.instanceRecords(q) + case m.HostName: + if q.Qtype == dns.TypeA || q.Qtype == dns.TypeAAAA { + return m.instanceRecords(q) + } + fallthrough + default: + return nil + } +} + +func (m *MDNSService) serviceEnum(q dns.Question) []dns.RR { + switch q.Qtype { + case dns.TypeANY: + fallthrough + case dns.TypePTR: + rr := &dns.PTR{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypePTR, + Class: dns.ClassINET, + Ttl: defaultTTL, + }, + Ptr: m.serviceAddr, + } + return []dns.RR{rr} + default: + return nil + } +} + +// serviceRecords is called when the query matches the service name +func (m *MDNSService) serviceRecords(q dns.Question) []dns.RR { + switch q.Qtype { + case dns.TypeANY: + fallthrough + case dns.TypePTR: + // Build a PTR response for the service + rr := &dns.PTR{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypePTR, + Class: dns.ClassINET, + Ttl: defaultTTL, + }, + Ptr: m.instanceAddr, + } + servRec := []dns.RR{rr} + + // Get the instance records + instRecs := m.instanceRecords(dns.Question{ + Name: m.instanceAddr, + Qtype: dns.TypeANY, + }) + + // Return the service record with the instance records + return append(servRec, instRecs...) + default: + return nil + } +} + +// serviceRecords is called when the query matches the instance name +func (m *MDNSService) instanceRecords(q dns.Question) []dns.RR { + switch q.Qtype { + case dns.TypeANY: + // Get the SRV, which includes A and AAAA + recs := m.instanceRecords(dns.Question{ + Name: m.instanceAddr, + Qtype: dns.TypeSRV, + }) + + // Add the TXT record + recs = append(recs, m.instanceRecords(dns.Question{ + Name: m.instanceAddr, + Qtype: dns.TypeTXT, + })...) + return recs + + case dns.TypeA: + var rr []dns.RR + for _, ip := range m.IPs { + if ip4 := ip.To4(); ip4 != nil { + rr = append(rr, &dns.A{ + Hdr: dns.RR_Header{ + Name: m.HostName, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: defaultTTL, + }, + A: ip4, + }) + } + } + return rr + + case dns.TypeAAAA: + var rr []dns.RR + for _, ip := range m.IPs { + if ip.To4() != nil { + // TODO(reddaly): IPv4 addresses could be encoded in IPv6 format and + // putinto AAAA records, but the current logic puts ipv4-encodable + // addresses into the A records exclusively. Perhaps this should be + // configurable? + continue + } + + if ip16 := ip.To16(); ip16 != nil { + rr = append(rr, &dns.AAAA{ + Hdr: dns.RR_Header{ + Name: m.HostName, + Rrtype: dns.TypeAAAA, + Class: dns.ClassINET, + Ttl: defaultTTL, + }, + AAAA: ip16, + }) + } + } + return rr + + case dns.TypeSRV: + // Create the SRV Record + srv := &dns.SRV{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeSRV, + Class: dns.ClassINET, + Ttl: defaultTTL, + }, + Priority: 10, + Weight: 1, + Port: uint16(m.Port), + Target: m.HostName, + } + recs := []dns.RR{srv} + + // Add the A record + recs = append(recs, m.instanceRecords(dns.Question{ + Name: m.instanceAddr, + Qtype: dns.TypeA, + })...) + + // Add the AAAA record + recs = append(recs, m.instanceRecords(dns.Question{ + Name: m.instanceAddr, + Qtype: dns.TypeAAAA, + })...) + return recs + + case dns.TypeTXT: + txt := &dns.TXT{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeTXT, + Class: dns.ClassINET, + Ttl: defaultTTL, + }, + Txt: m.TXT, + } + return []dns.RR{txt} + } + return nil +} diff --git a/vendor/github.com/ianr0bkny/go-sonos/LICENSE b/vendor/github.com/ianr0bkny/go-sonos/LICENSE new file mode 100644 index 0000000..65b32a3 --- /dev/null +++ b/vendor/github.com/ianr0bkny/go-sonos/LICENSE @@ -0,0 +1,28 @@ +go-sonos +======== + +Copyright (c) 2012, Ian T. Richards +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/github.com/ianr0bkny/go-sonos/ssdp/ssdp.go b/vendor/github.com/ianr0bkny/go-sonos/ssdp/ssdp.go new file mode 100644 index 0000000..0f3dc9d --- /dev/null +++ b/vendor/github.com/ianr0bkny/go-sonos/ssdp/ssdp.go @@ -0,0 +1,942 @@ +// +// go-sonos +// ======== +// +// Copyright (c) 2012, Ian T. Richards +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +// +// A client implementation of the SSDP protocol. +// +// mgr := ssdp.MakeManager() +// mgr.Discover("eth0", "13104", false) +// qry := ssdp.ServiceQueryTerms{ +// ssdp.ServiceKey("schemas-upnp-org-MusicServices"): -1, +// } +// result := mgr.QueryServices(qry) +// if device_list, has := result["schemas-upnp-org-MusicServices"]; has { +// for _, device := range device_list { +// ... +// } +// } +// mgr.Close() +// +package ssdp + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "log" + "net" + "net/textproto" + "regexp" + "strconv" + "strings" + "time" +) + +var ssdpHNAPRegex *regexp.Regexp +var ssdpOtherDeviceRegex *regexp.Regexp +var ssdpOtherDeviceUUIDRegex *regexp.Regexp +var ssdpOtherServiceRegex *regexp.Regexp +var ssdpOtherServiceUUIDRegex *regexp.Regexp +var ssdpResponseStartLineRegexp *regexp.Regexp +var ssdpServerStringRegexp *regexp.Regexp +var ssdpRootDeviceUUIDRegex *regexp.Regexp +var ssdpUPnPBareUUIDRegex *regexp.Regexp +var ssdpUPnPDeviceRegex *regexp.Regexp +var ssdpUPnPDeviceUUIDRegex *regexp.Regexp +var ssdpUPnPServiceRegex *regexp.Regexp +var ssdpUPnPServiceUUIDRegex *regexp.Regexp +var ssdpUPnPUIDRegex *regexp.Regexp + +func init() { + ssdpHNAPRegex = regexp.MustCompile("^hnap:(.+)$") + ssdpOtherDeviceRegex = regexp.MustCompile("^urn:([^:]+):device:([^:]+)(:(.+))?$") + ssdpOtherDeviceUUIDRegex = regexp.MustCompile("^uuid:([^:]+)::urn:([^:]+):device:([^:]+)(:(.+))?$") + ssdpOtherServiceRegex = regexp.MustCompile("^urn:([^:]+):service:([^:]+)(:(.+))?$") + ssdpOtherServiceUUIDRegex = regexp.MustCompile("^uuid:([^:]+)::urn:([^:]+):service:([^:]+)(:(.+))?$") + ssdpResponseStartLineRegexp = regexp.MustCompile("^HTTP/([0-9\\.]+)$") + ssdpServerStringRegexp = regexp.MustCompile("^([^ /,]+)(/([^ ,]+))?,?\\s+[Uu][Pp][Nn][Pp](/([^ ,]+))?,?\\s+([^/]+)(/(.+))?$") + ssdpRootDeviceUUIDRegex = regexp.MustCompile("^uuid:([^:]+)::upnp:rootdevice$") + ssdpUPnPBareUUIDRegex = regexp.MustCompile("^uuid:([^:]+)$") + ssdpUPnPDeviceRegex = regexp.MustCompile("^urn:schemas-upnp-org:device:([^:]+)(:(.+))?$") + ssdpUPnPDeviceUUIDRegex = regexp.MustCompile("uuid:([^:]+)::urn:schemas-upnp-org:device:([^:]+)(:(.+))?$") + ssdpUPnPServiceRegex = regexp.MustCompile("^urn:schemas-upnp-org:service:([^:]+)(:(.+))?$") + ssdpUPnPServiceUUIDRegex = regexp.MustCompile("uuid:([^:]+)::urn:schemas-upnp-org:service:([^:]+)(:(.+))?$") + ssdpUPnPUIDRegex = regexp.MustCompile("^uuid:(.+)$") +} + +const ( + ssdpBroadcastGroup = "239.255.255.250:1900" + ssdpBroadcastVersion = "udp" +) + +// Type protection for a device URI +type Location string + +// Type protection for a SSDP service key +type ServiceKey string + +// Type protection for a Universally Unique ID +type UUID string + +type ssdpServerDescription struct { + os string + os_version string + upnp_version string + product string + productVersion string +} + +type ssdpResourceBase struct { + ssdpServerDescription + location Location + uuid UUID +} + +// An abstraction of an SSDP service +type Service interface { + // The version of the service + Version() int64 +} + +type ssdpService struct { + ssdpResourceBase + name string + version int64 + uri string +} + +func (this *ssdpService) Version() int64 { + return this.version +} + +type ssdpServiceSet map[ServiceKey]Service + +// An abstraction of an SSDP leaf device +type Device interface { + // The product name (e.g. "Sonos") + Product() string + // The product version (e.g. "28.1-83040 (BR100)") + ProductVersion() string + // The device name (e.h. "ZonePlayer") + Name() string + // A URI that can be queried for device capabilities + Location() Location + // The device's Universally Unique ID + UUID() UUID + // Search for a service in the device's capabilities; same + // return semantics as querying a map + Service(key ServiceKey) (service Service, has bool) + // Return a list of services implemented by this device + Services() []ServiceKey +} + +type ssdpDevice struct { + ssdpResourceBase + name string + version int64 + uri string + services ssdpServiceSet +} + +func (this *ssdpDevice) Product() string { + return this.product +} + +func (this *ssdpDevice) ProductVersion() string { + return this.productVersion +} + +func (this *ssdpDevice) Name() string { + return this.name +} + +func (this *ssdpDevice) Location() Location { + return this.location +} + +func (this *ssdpDevice) UUID() UUID { + return this.uuid +} + +func (this *ssdpDevice) Service(key ServiceKey) (service Service, has bool) { + service, has = this.services[key] + return +} + +func (this *ssdpDevice) Services() []ServiceKey { + i := 0 + keys := make([]ServiceKey, len(this.services)) + for key, _ := range this.services { + keys[i] = key + i += 1 + } + return keys +} + +// Indexes leaf devices by UUID +type DeviceMap map[UUID]Device + +type ssdpRootDevice struct { + ssdpResourceBase + Devices DeviceMap +} + +type ssdpRootDeviceMap map[Location]*ssdpRootDevice + +// Discovered devices sorted by supported service +type ServiceMap map[ServiceKey]DeviceMap + +type ssdpResourceType int + +const ( + ssdpTypessdpRootDevice ssdpResourceType = iota + ssdpTypeService + ssdpTypeDevice + ssdpTypeHNAP + ssdpTypeUUID + ssdpTypeUnknown +) + +type ssdpResource struct { + ssdpServerDescription + ssdptype ssdpResourceType + uuid UUID + name string + version int64 + uri string + location Location + children []*ssdpResource +} + +type ssdpMessageType int + +const ( + ssdpSearch ssdpMessageType = iota + ssdpNotify + ssdpResponse + ssdpInvalid +) + +type ssdpRawMessage struct { + msgtype ssdpMessageType + httpver string + header map[string]string +} + +type ssdpNotifyMessage struct { + _01_nls string + cache_control string + host string + location Location + nts string + nt string + opt string + server string + usn string + x_rincon_bootseq string + x_rincon_household string + x_user_agent string +} + +type ssdpNotifyQueue chan *ssdpNotifyMessage + +type ssdpResponseMessage struct { + ssdpServerDescription + _01_nls string + al string + cache_control string + date string + ext string + location Location + opt string + st string + usn string + x_rincon_bootseq string + x_rincon_household string + x_rincon_variant string + x_rincon_wifimode string + x_user_agent string + content_length string + bootid_upnp_org string + configid_upnp_org string + household_smartspeaker_audio string + x_av_server_info string +} + +type ssdpResponseQueue chan *ssdpResponseMessage + +type ssdpConnection struct { + conn *net.UDPConn + addr *net.UDPAddr +} + +// A map of service key to minimum required version +type ServiceQueryTerms map[ServiceKey]int64 + +// Encapsulates SSDP discovery, handles updates, and stores results +type Manager interface { + // Initiates SSDP discovery, where ifiname names a network device + // to query, port gives a free port on that network device to listen + // for responses, and the subscribe flag (currrently unimplemented) + // determines whether to listen to asynchronous updates after the + // initial query is complete. + Discover(ifiname, port string, subscribe bool) error + // After discovery is complete searches for devices implementing + // the services specified in query. + QueryServices(query ServiceQueryTerms) ServiceMap + // Return the list of devices that were found during discovery + Devices() DeviceMap + // Shuts down asynchronous subscriptions to device state + Close() error +} + +type ssdpDefaultManager struct { + responseQueue ssdpResponseQueue + notifyQueue ssdpNotifyQueue + unicast ssdpConnection + multicast ssdpConnection + rootDeviceMap ssdpRootDeviceMap + deviceMap DeviceMap + serviceMap ServiceMap + readyChan chan int + closeChan chan int +} + +// Returns an empty manager ready for SSDP discovery +func MakeManager() Manager { + mgr := &ssdpDefaultManager{} + mgr.responseQueue = make(ssdpResponseQueue) + mgr.notifyQueue = make(ssdpNotifyQueue) + mgr.unicast = ssdpConnection{} + mgr.multicast = ssdpConnection{} + mgr.rootDeviceMap = make(ssdpRootDeviceMap) + mgr.deviceMap = make(DeviceMap) + mgr.serviceMap = make(ServiceMap) + mgr.readyChan = make(chan int) + mgr.closeChan = make(chan int) + return mgr +} + +func (this *ssdpDefaultManager) Discover(ifiname, port string, subscribe bool) (err error) { + this.ssdpDiscoverImpl(ifiname, port, subscribe) + return +} + +func (this *ssdpDefaultManager) QueryServices(query ServiceQueryTerms) (results ServiceMap) { + results = make(ServiceMap) + for name, minver := range query { + results[name] = make(DeviceMap) + if dlist, has := this.serviceMap[name]; has { + for uuid, _ := range dlist { + de := this.deviceMap[uuid] + if svc, has := de.Service(name); has { + if minver <= svc.Version() { + results[name][de.UUID()] = de + } + } + } + } + } + return +} + +func (this *ssdpDefaultManager) Devices() DeviceMap { + return this.deviceMap +} + +func (this *ssdpDefaultManager) Close() (err error) { + if nil != this.unicast.conn { + this.unicast.conn.Close() + <-this.closeChan + } + if nil != this.multicast.conn { + this.multicast.conn.Close() + <-this.closeChan + } + return +} + +func ssdpNewDevice(res *ssdpResource) (de *ssdpDevice) { + de = new(ssdpDevice) + de.ssdpServerDescription = res.ssdpServerDescription + de.uuid = res.uuid + de.location = res.location + de.name = res.name + de.version = res.version + de.uri = res.uri + de.services = make(ssdpServiceSet) + return +} + +func ssdpNewssdpRootDevice(res *ssdpResource) (rd *ssdpRootDevice) { + rd = new(ssdpRootDevice) + rd.ssdpServerDescription = res.ssdpServerDescription + rd.uuid = res.uuid + rd.location = res.location + rd.Devices = make(DeviceMap) + return +} + +func (rd *ssdpRootDevice) ssdpRequireEmbeddedDevice(de *ssdpDevice) { + if _, has := rd.Devices[de.UUID()]; !has { + rd.Devices[de.UUID()] = de + } +} + +func ssdpNewRawMessage() *ssdpRawMessage { + return &ssdpRawMessage{ssdpInvalid, "1.1", make(map[string]string)} +} + +func ssdpNewResource(uuid UUID) (res *ssdpResource) { + res = new(ssdpResource) + res.ssdptype = ssdpTypeUnknown + res.uuid = uuid + return +} + +func ssdpNewService(res *ssdpResource) (sv *ssdpService) { + sv = new(ssdpService) + sv.ssdpServerDescription = res.ssdpServerDescription + sv.uuid = res.uuid + sv.location = res.location + sv.name = res.name + sv.version = res.version + sv.uri = res.uri + return +} + +func (this *ssdpDefaultManager) ssdpParseStartLineFields(raw *ssdpRawMessage, fields []string) { + if "M-SEARCH" == fields[0] { + raw.msgtype = ssdpSearch + } else if "NOTIFY" == fields[0] { + raw.msgtype = ssdpNotify + } else if m := ssdpResponseStartLineRegexp.FindStringSubmatch(fields[0]); 0 < len(m) { + raw.msgtype = ssdpResponse + raw.httpver = m[1] + } else { + panic(fmt.Sprintf("Invalid start line `%s'", fields[0])) + } +} + +func (this *ssdpDefaultManager) ssdpParseStartLine(raw *ssdpRawMessage, line []byte) { + fields := strings.Fields(string(line)) + if 3 != len(fields) { + panic("Invalid start line") + } else { + this.ssdpParseStartLineFields(raw, fields) + } +} + +func (this *ssdpDefaultManager) ssdpParseHeaderLine(raw *ssdpRawMessage, line []byte) { + i := strings.Index(string(line), ":") + if -1 == i { + panic("Invalid header") + } + field := textproto.CanonicalMIMEHeaderKey(strings.TrimSpace(string(line[0:i]))) + value := strings.TrimSpace(string(line[i+1:])) + if _, has := raw.header[field]; has { + panic("Header field redefined") + } else { + raw.header[field] = value + } +} + +func (this *ssdpDefaultManager) ssdpParseInputLine(raw *ssdpRawMessage, line []byte, lineno int) { + if 1 < lineno { + this.ssdpParseHeaderLine(raw, line) + } else { + this.ssdpParseStartLine(raw, line) + } +} + +func (this *ssdpDefaultManager) ssdpParseInput(msg []byte) (raw *ssdpRawMessage) { + raw = ssdpNewRawMessage() + bin := bufio.NewReader(bytes.NewReader(msg)) + var line []byte + lineno := 0 + for { + if fragment, is_prefix, err := bin.ReadLine(); nil == err { + line = append(line, fragment...) + if !is_prefix { + lineno += 1 + if 1 < lineno && 0 == len(line) { + break + } + this.ssdpParseInputLine(raw, line, lineno) + line = nil + } + } else if io.EOF == err { + panic("Premature end of header") + } else { + panic(err) + } + } + return +} + +func (this *ssdpDefaultManager) ssdpHandleNotify(raw *ssdpRawMessage) *ssdpNotifyMessage { + msg := new(ssdpNotifyMessage) /*asynchronous reply from multicast*/ + for key, value := range raw.header { + switch key { + case "Location": + msg.location = Location(value) + case "Server": + msg.server = value + case "Host": + msg.host = value + case "Usn": + msg.usn = value + case "Cache-Control": + msg.cache_control = value + case "X-User-Agent": + msg.x_user_agent = value + case "X-Rincon-Bootseq": + msg.x_rincon_bootseq = value + case "X-Rincon-Household": + msg.x_rincon_household = value + case "Nts": + msg.nts = value + case "Nt": + msg.nt = value + case "Opt": + msg.opt = value + case "01-Nls": + msg._01_nls = value + default: + log.Printf("No support for field `%s' (value `%s')", key, value) + } + } + return msg +} + +func (this *ssdpDefaultManager) ssdpHandleResponse(raw *ssdpRawMessage) *ssdpResponseMessage { + msg := new(ssdpResponseMessage) /*synchronous reply from unicast*/ + for key, value := range raw.header { + switch key { + case "Location": + msg.location = Location(value) + case "St": + msg.st = value + case "Server": + m := ssdpServerStringRegexp.FindStringSubmatch(value) + if 0 < len(m) { + msg.os = m[1] + msg.os_version = m[3] + msg.upnp_version = m[5] + msg.product = m[6] + msg.productVersion = m[8] + } else { + log.Printf("Invalid server description `%s'", value) + } + case "Opt": + msg.opt = value + case "Usn": + msg.usn = value + case "Ext": + msg.ext = value + case "Date": + msg.date = value + case "Cache-Control": + msg.cache_control = value + case "X-User-Agent": + msg.x_user_agent = value + case "X-Rincon-Bootseq": + msg.x_rincon_bootseq = value + case "X-Rincon-Household": + msg.x_rincon_household = value + case "X-Rincon-Variant": + msg.x_rincon_variant = value + case "X-Rincon-Wifimode": + msg.x_rincon_wifimode = value + case "Al": + msg.al = value + case "01-Nls": + msg._01_nls = value + case "Content-Length": + msg.content_length = value + case "Bootid.upnp.org": + msg.bootid_upnp_org = value + case "Configid.upnp.org": + msg.configid_upnp_org = value + case "Household.smartspeaker.audio": + msg.household_smartspeaker_audio = value + case "X-Av-Server-Info": + msg.x_av_server_info = value + default: + log.Printf("No support for field `%s' (value `%s')", key, value) + log.Printf("%v", raw) + } + } + return msg +} + +func (this *ssdpDefaultManager) ssdpHandleMessage(raw *ssdpRawMessage) { + switch raw.msgtype { + case ssdpSearch: /*ignore*/ + case ssdpResponse: + this.responseQueue <- this.ssdpHandleResponse(raw) + case ssdpNotify: + this.notifyQueue <- this.ssdpHandleNotify(raw) + } +} + +func (this *ssdpDefaultManager) ssdpDiscoverLoop(conn net.Conn) { + this.readyChan <- 1 + msg := make([]byte, 65536) /*max size of a single UDP packet*/ + defer func() { + recover() + this.closeChan <- 1 + }() + for { + if n, err := conn.Read(msg); nil != err { + panic(err) + } else if raw := this.ssdpParseInput(msg[:n]); nil != raw { + this.ssdpHandleMessage(raw) + } + } +} + +func (this *ssdpDefaultManager) ssdpUnicastDiscoverImpl(ifi *net.Interface, port string) (err error) { + addrs, err := ifi.Addrs() + if nil != err { + return + } else if 0 == len(addrs) { + err = errors.New(fmt.Sprintf("No addresses found for interface %s", ifi.Name)) + return + } + var lip net.IP + for _, addr := range addrs { + if nil != addr.(*net.IPNet).IP.DefaultMask() { + lip = addr.(*net.IPNet).IP + break; + } + } + laddr, err := net.ResolveUDPAddr(ssdpBroadcastVersion, net.JoinHostPort(lip.String(), port)) + if nil != err { + return + } + uc, err := net.ListenUDP(ssdpBroadcastVersion, laddr) + if nil != err { + return + } + this.unicast.addr = laddr + this.unicast.conn = uc + go this.ssdpDiscoverLoop(uc) + <-this.readyChan + return +} + +func (this *ssdpDefaultManager) ssdpMulticastDiscoverImpl(ifi *net.Interface, subscribe bool) (err error) { + maddr, err := net.ResolveUDPAddr(ssdpBroadcastVersion, ssdpBroadcastGroup) + if nil != err { + return + } + this.multicast.addr = maddr + var mc *net.UDPConn + if subscribe { + mc, err = net.ListenMulticastUDP(ssdpBroadcastVersion, ifi, maddr) + if nil != err { + return + } + this.multicast.conn = mc + go this.ssdpDiscoverLoop(mc) + <-this.readyChan + } + return +} + +func (this *ssdpDefaultManager) ssdpQueryMessage(timeout int) (msg *bytes.Buffer) { + msg = new(bytes.Buffer) + msg.WriteString("M-SEARCH * HTTP/1.1\r\n") + msg.WriteString("HosT: 239.255.255.250:1900\r\n") + msg.WriteString("MAN: \"ssdp:discover\"\r\n") + msg.WriteString(fmt.Sprintf("MX: %d\r\n", timeout)) + msg.WriteString("ST: ssdp:all\r\n") + msg.WriteString("USER-AGENT: unix/5.1 UPnP/1.1 crash/1.0\r\n") + msg.WriteString("\r\n") + return +} + +func (this *ssdpDefaultManager) ssdpRequiressdpRootDevice(res *ssdpResource) (rd *ssdpRootDevice) { + var has bool + if rd, has = this.rootDeviceMap[res.location]; !has { + rd = ssdpNewssdpRootDevice(res) + this.rootDeviceMap[rd.location] = rd + } + return +} + +func (this *ssdpDefaultManager) ssdpRequireDevice(res *ssdpResource) (de *ssdpDevice) { + if raw, has := this.deviceMap[res.uuid]; !has { + de = ssdpNewDevice(res) + this.deviceMap[res.uuid] = de + } else { + de = raw.(*ssdpDevice) + } + return +} + +func (this *ssdpDefaultManager) ssdpNotifyssdpRootDevice(res *ssdpResource) { + this.ssdpRequiressdpRootDevice(res) + this.ssdpRequireDevice(res) +} + +func (this *ssdpDefaultManager) ssdpNotifyDevice(res *ssdpResource) { + rd := this.ssdpRequiressdpRootDevice(res) + de := this.ssdpRequireDevice(res) + rd.ssdpRequireEmbeddedDevice(de) +} + +func (this *ssdpDefaultManager) ssdpGetServiceKey(sv *ssdpService) (key ServiceKey) { + uri := sv.uri + if 0 >= len(uri) { + uri = "schemas-upnp-org" + } + key = ServiceKey(fmt.Sprintf("%s-%s", uri, sv.name)) + return +} + +func (this *ssdpDefaultManager) ssdpRequireService(de *ssdpDevice, sv *ssdpService) { + key := this.ssdpGetServiceKey(sv) + if _, has := de.services[key]; !has { + de.services[key] = sv + if _, has := this.serviceMap[key]; !has { + this.serviceMap[key] = make(DeviceMap) + } + this.serviceMap[key][de.UUID()] = de + } +} + +func (this *ssdpDefaultManager) ssdpNotifyService(res *ssdpResource) { + de := this.ssdpRequireDevice(res) + sv := ssdpNewService(res) + this.ssdpRequireService(de, sv) +} + +func (this *ssdpDefaultManager) ssdpNotifyResource(res *ssdpResource) { + switch res.ssdptype { + case ssdpTypessdpRootDevice: + this.ssdpNotifyssdpRootDevice(res) + case ssdpTypeDevice: + this.ssdpNotifyDevice(res) + case ssdpTypeService: + this.ssdpNotifyService(res) + case ssdpTypeHNAP: + /*TODO*/ + case ssdpTypeUUID: + /*TODO ... not sure what to do*/ + default: + log.Fatalf("Unhandled ssdptype %d", res.ssdptype) + } +} + +func (this *ssdpDefaultManager) ssdpIncludessdpRootDevice(ssdpsm *ssdpResponseMessage) { + if n := ssdpRootDeviceUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { + uuid := UUID(n[1]) + res := ssdpNewResource(uuid) + res.ssdpServerDescription = ssdpsm.ssdpServerDescription + res.uuid = uuid + res.location = ssdpsm.location + res.ssdptype = ssdpTypessdpRootDevice + this.ssdpNotifyResource(res) + } else { + log.Printf("Invalid Unique Service Name for upnp:rootdevice: `%s'", ssdpsm.usn) + } +} + +func (this *ssdpDefaultManager) ssdpIncludeService(ssdpsm *ssdpResponseMessage) { + if n := ssdpUPnPServiceUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { + uuid := UUID(n[1]) + res := ssdpNewResource(uuid) + res.ssdpServerDescription = ssdpsm.ssdpServerDescription + res.location = ssdpsm.location + res.ssdptype = ssdpTypeService + res.name = n[2] + var err error + if res.version, err = strconv.ParseInt(n[4], 10, 4); nil != err { + log.Printf("Error in parsing service version `%s'", n[4]) + } + this.ssdpNotifyResource(res) + } else { + log.Printf("Invalid Unique Service Name for UPnP service: `%s'", ssdpsm.usn) + } +} + +func (this *ssdpDefaultManager) ssdpIncludeDevice(ssdpsm *ssdpResponseMessage) { + if n := ssdpUPnPDeviceUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { + uuid := UUID(n[1]) + res := ssdpNewResource(uuid) + res.ssdpServerDescription = ssdpsm.ssdpServerDescription + res.location = ssdpsm.location + res.ssdptype = ssdpTypeDevice + res.name = n[2] + var err error + if res.version, err = strconv.ParseInt(n[4], 10, 4); nil != err { + log.Printf("Error in parsing device version `%s'", n[4]) + } + this.ssdpNotifyResource(res) + } else { + log.Printf("Invalid Unique Service Name for UPnP device: `%s'", ssdpsm.usn) + } +} + +func (this *ssdpDefaultManager) ssdpIncludeUUID(ssdpsm *ssdpResponseMessage) { + if n := ssdpUPnPBareUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { + uuid := UUID(n[1]) + res := ssdpNewResource(uuid) + res.ssdpServerDescription = ssdpsm.ssdpServerDescription + res.location = ssdpsm.location + res.ssdptype = ssdpTypeUUID + this.ssdpNotifyResource(res) + } else { + log.Printf("Invalid Unique Service Name: `%s'", ssdpsm.usn) + } +} + +func (this *ssdpDefaultManager) ssdpIncludeHNAP(ssdpsm *ssdpResponseMessage, name string) { + if n := ssdpUPnPBareUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { + uuid := UUID(n[1]) + res := ssdpNewResource(uuid) + res.ssdpServerDescription = ssdpsm.ssdpServerDescription + res.location = ssdpsm.location + res.ssdptype = ssdpTypeHNAP + res.name = name + this.ssdpNotifyResource(res) + } else { + log.Printf("Invalid Unique Service Name for HNAP: `%s'", ssdpsm.usn) + } +} + +func (this *ssdpDefaultManager) ssdpIncludeOtherService(ssdpsm *ssdpResponseMessage) { + if n := ssdpOtherServiceUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { + uuid := UUID(n[1]) + res := ssdpNewResource(uuid) + res.ssdpServerDescription = ssdpsm.ssdpServerDescription + res.location = ssdpsm.location + res.ssdptype = ssdpTypeService + res.uri = n[2] + res.name = n[3] + var err error + if res.version, err = strconv.ParseInt(n[5], 10, 4); nil != err { + log.Printf("Error in parsing service version `%s'", n[4]) + } + this.ssdpNotifyResource(res) + } else { + log.Printf("Invalid Unique Service Name for third-party service: `%s'", ssdpsm.usn) + } +} + +func (this *ssdpDefaultManager) ssdpIncludeOtherDevice(ssdpsm *ssdpResponseMessage) { + if n := ssdpOtherDeviceUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { + uuid := UUID(n[1]) + res := ssdpNewResource(uuid) + res.ssdpServerDescription = ssdpsm.ssdpServerDescription + res.location = ssdpsm.location + res.ssdptype = ssdpTypeDevice + res.uri = n[2] + res.name = n[3] + var err error + if res.version, err = strconv.ParseInt(n[5], 10, 4); nil != err { + log.Printf("Error in parsing device version `%s'", n[4]) + } + this.ssdpNotifyResource(res) + } else { + log.Printf("Invalid Unique Service Name for third-party device: `%s'", ssdpsm.usn) + } +} + +func (this *ssdpDefaultManager) ssdpIncludeResponse(msg *ssdpResponseMessage) { + if "upnp:rootdevice" == msg.st { + this.ssdpIncludessdpRootDevice(msg) + } else if m := ssdpUPnPServiceRegex.FindStringSubmatch(msg.st); 0 < len(m) { + this.ssdpIncludeService(msg) + } else if m := ssdpUPnPDeviceRegex.FindStringSubmatch(msg.st); 0 < len(m) { + this.ssdpIncludeDevice(msg) + } else if m := ssdpUPnPUIDRegex.FindStringSubmatch(msg.st); 0 < len(m) { + this.ssdpIncludeUUID(msg) + } else if m := ssdpHNAPRegex.FindStringSubmatch(msg.st); 0 < len(m) { + this.ssdpIncludeHNAP(msg, m[1]) + } else if m := ssdpOtherServiceRegex.FindStringSubmatch(msg.st); 0 < len(m) { + this.ssdpIncludeOtherService(msg) + } else if m := ssdpOtherDeviceRegex.FindStringSubmatch(msg.st); 0 < len(m) { + this.ssdpIncludeOtherDevice(msg) + } else { + log.Printf("Unsupported search term [ST] `%s'", msg.st) + } +} + +func (this *ssdpDefaultManager) ssdpIncludeNotification(msg *ssdpNotifyMessage) { + /*TODO*/ +} + +func (this *ssdpDefaultManager) ssdpSendQuery(timeout int) (err error) { + msg := this.ssdpQueryMessage(timeout) + if _, err = this.unicast.conn.WriteTo(msg.Bytes(), this.multicast.addr); nil != err { + return + } else { + timeout := time.NewTimer(time.Duration(timeout) * time.Second) + done := false + for !done { + select { + case m := <-this.responseQueue: + this.ssdpIncludeResponse(m) + case raw := <-this.notifyQueue: + this.ssdpIncludeNotification(raw) + case <-timeout.C: + done = true + } + } + } + return +} + +func (this *ssdpDefaultManager) ssdpQueryLoop() (err error) { + for i := 0; i < 2; i++ { + if err = this.ssdpSendQuery(3 /*timeout seconds*/); nil != err { + return + } + } + return +} + +func (this *ssdpDefaultManager) ssdpDiscoverImpl(ifiname, port string, subscribe bool) { + ifi, err := net.InterfaceByName(ifiname) + if nil != err { + panic(err) + } else if err = this.ssdpUnicastDiscoverImpl(ifi, port); nil != err { + panic(err) + } else if err = this.ssdpMulticastDiscoverImpl(ifi, subscribe); nil != err { + panic(err) + } else if err = this.ssdpQueryLoop(); nil != err { + panic(err) + } +} diff --git a/vendor/github.com/miekg/dns/.codecov.yml b/vendor/github.com/miekg/dns/.codecov.yml new file mode 100644 index 0000000..f91e5c1 --- /dev/null +++ b/vendor/github.com/miekg/dns/.codecov.yml @@ -0,0 +1,8 @@ +coverage: + status: + project: + default: + target: 40% + threshold: null + patch: false + changes: false diff --git a/vendor/github.com/miekg/dns/.gitignore b/vendor/github.com/miekg/dns/.gitignore new file mode 100644 index 0000000..776cd95 --- /dev/null +++ b/vendor/github.com/miekg/dns/.gitignore @@ -0,0 +1,4 @@ +*.6 +tags +test.out +a.out diff --git a/vendor/github.com/miekg/dns/AUTHORS b/vendor/github.com/miekg/dns/AUTHORS new file mode 100644 index 0000000..1965683 --- /dev/null +++ b/vendor/github.com/miekg/dns/AUTHORS @@ -0,0 +1 @@ +Miek Gieben diff --git a/vendor/github.com/miekg/dns/CODEOWNERS b/vendor/github.com/miekg/dns/CODEOWNERS new file mode 100644 index 0000000..e091703 --- /dev/null +++ b/vendor/github.com/miekg/dns/CODEOWNERS @@ -0,0 +1 @@ +* @miekg @tmthrgd diff --git a/vendor/github.com/miekg/dns/CONTRIBUTORS b/vendor/github.com/miekg/dns/CONTRIBUTORS new file mode 100644 index 0000000..5903779 --- /dev/null +++ b/vendor/github.com/miekg/dns/CONTRIBUTORS @@ -0,0 +1,10 @@ +Alex A. Skinner +Andrew Tunnell-Jones +Ask Bjørn Hansen +Dave Cheney +Dusty Wilson +Marek Majkowski +Peter van Dijk +Omri Bahumi +Alex Sergeyev +James Hartig diff --git a/vendor/github.com/miekg/dns/COPYRIGHT b/vendor/github.com/miekg/dns/COPYRIGHT new file mode 100644 index 0000000..35702b1 --- /dev/null +++ b/vendor/github.com/miekg/dns/COPYRIGHT @@ -0,0 +1,9 @@ +Copyright 2009 The Go Authors. All rights reserved. Use of this source code +is governed by a BSD-style license that can be found in the LICENSE file. +Extensions of the original work are copyright (c) 2011 Miek Gieben + +Copyright 2011 Miek Gieben. All rights reserved. Use of this source code is +governed by a BSD-style license that can be found in the LICENSE file. + +Copyright 2014 CloudFlare. All rights reserved. Use of this source code is +governed by a BSD-style license that can be found in the LICENSE file. diff --git a/vendor/github.com/miekg/dns/LICENSE b/vendor/github.com/miekg/dns/LICENSE new file mode 100644 index 0000000..55f12ab --- /dev/null +++ b/vendor/github.com/miekg/dns/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +As this is fork of the official Go code the same license applies. +Extensions of the original work are copyright (c) 2011 Miek Gieben diff --git a/vendor/github.com/miekg/dns/Makefile.fuzz b/vendor/github.com/miekg/dns/Makefile.fuzz new file mode 100644 index 0000000..dc158c4 --- /dev/null +++ b/vendor/github.com/miekg/dns/Makefile.fuzz @@ -0,0 +1,33 @@ +# Makefile for fuzzing +# +# Use go-fuzz and needs the tools installed. +# See https://blog.cloudflare.com/dns-parser-meet-go-fuzzer/ +# +# Installing go-fuzz: +# $ make -f Makefile.fuzz get +# Installs: +# * github.com/dvyukov/go-fuzz/go-fuzz +# * get github.com/dvyukov/go-fuzz/go-fuzz-build + +all: build + +.PHONY: build +build: + go-fuzz-build -tags fuzz github.com/miekg/dns + +.PHONY: build-newrr +build-newrr: + go-fuzz-build -func FuzzNewRR -tags fuzz github.com/miekg/dns + +.PHONY: fuzz +fuzz: + go-fuzz -bin=dns-fuzz.zip -workdir=fuzz + +.PHONY: get +get: + go get github.com/dvyukov/go-fuzz/go-fuzz + go get github.com/dvyukov/go-fuzz/go-fuzz-build + +.PHONY: clean +clean: + rm *-fuzz.zip diff --git a/vendor/github.com/miekg/dns/Makefile.release b/vendor/github.com/miekg/dns/Makefile.release new file mode 100644 index 0000000..a0ce9b7 --- /dev/null +++ b/vendor/github.com/miekg/dns/Makefile.release @@ -0,0 +1,52 @@ +# Makefile for releasing. +# +# The release is controlled from version.go. The version found there is +# used to tag the git repo, we're not building any artifacts so there is nothing +# to upload to github. +# +# * Up the version in version.go +# * Run: make -f Makefile.release release +# * will *commit* your change with 'Release $VERSION' +# * push to github +# + +define GO +//+build ignore + +package main + +import ( + "fmt" + + "github.com/miekg/dns" +) + +func main() { + fmt.Println(dns.Version.String()) +} +endef + +$(file > version_release.go,$(GO)) +VERSION:=$(shell go run version_release.go) +TAG="v$(VERSION)" + +all: + @echo Use the \'release\' target to start a release $(VERSION) + rm -f version_release.go + +.PHONY: release +release: commit push + @echo Released $(VERSION) + rm -f version_release.go + +.PHONY: commit +commit: + @echo Committing release $(VERSION) + git commit -am"Release $(VERSION)" + git tag $(TAG) + +.PHONY: push +push: + @echo Pushing release $(VERSION) to master + git push --tags + git push diff --git a/vendor/github.com/miekg/dns/README.md b/vendor/github.com/miekg/dns/README.md new file mode 100644 index 0000000..3594492 --- /dev/null +++ b/vendor/github.com/miekg/dns/README.md @@ -0,0 +1,181 @@ +[![Build Status](https://travis-ci.org/miekg/dns.svg?branch=master)](https://travis-ci.org/miekg/dns) +[![Code Coverage](https://img.shields.io/codecov/c/github/miekg/dns/master.svg)](https://codecov.io/github/miekg/dns?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/miekg/dns)](https://goreportcard.com/report/miekg/dns) +[![](https://godoc.org/github.com/miekg/dns?status.svg)](https://godoc.org/github.com/miekg/dns) + +# Alternative (more granular) approach to a DNS library + +> Less is more. + +Complete and usable DNS library. All Resource Records are supported, including the DNSSEC types. +It follows a lean and mean philosophy. If there is stuff you should know as a DNS programmer there +isn't a convenience function for it. Server side and client side programming is supported, i.e. you +can build servers and resolvers with it. + +We try to keep the "master" branch as sane as possible and at the bleeding edge of standards, +avoiding breaking changes wherever reasonable. We support the last two versions of Go. + +# Goals + +* KISS; +* Fast; +* Small API. If it's easy to code in Go, don't make a function for it. + +# Users + +A not-so-up-to-date-list-that-may-be-actually-current: + +* https://github.com/coredns/coredns +* https://github.com/abh/geodns +* https://github.com/baidu/bfe +* http://www.statdns.com/ +* http://www.dnsinspect.com/ +* https://github.com/chuangbo/jianbing-dictionary-dns +* http://www.dns-lg.com/ +* https://github.com/fcambus/rrda +* https://github.com/kenshinx/godns +* https://github.com/skynetservices/skydns +* https://github.com/hashicorp/consul +* https://github.com/DevelopersPL/godnsagent +* https://github.com/duedil-ltd/discodns +* https://github.com/StalkR/dns-reverse-proxy +* https://github.com/tianon/rawdns +* https://mesosphere.github.io/mesos-dns/ +* https://github.com/fcambus/statzone +* https://github.com/benschw/dns-clb-go +* https://github.com/corny/dnscheck for +* https://github.com/miekg/unbound +* https://github.com/miekg/exdns +* https://dnslookup.org +* https://github.com/looterz/grimd +* https://github.com/phamhongviet/serf-dns +* https://github.com/mehrdadrad/mylg +* https://github.com/bamarni/dockness +* https://github.com/fffaraz/microdns +* https://github.com/ipdcode/hades +* https://github.com/StackExchange/dnscontrol/ +* https://www.dnsperf.com/ +* https://dnssectest.net/ +* https://github.com/oif/apex +* https://github.com/jedisct1/dnscrypt-proxy +* https://github.com/jedisct1/rpdns +* https://github.com/xor-gate/sshfp +* https://github.com/rs/dnstrace +* https://blitiri.com.ar/p/dnss ([github mirror](https://github.com/albertito/dnss)) +* https://render.com +* https://github.com/peterzen/goresolver +* https://github.com/folbricht/routedns +* https://domainr.com/ +* https://zonedb.org/ +* https://router7.org/ +* https://github.com/fortio/dnsping +* https://github.com/Luzilla/dnsbl_exporter +* https://github.com/bodgit/tsig +* https://github.com/v2fly/v2ray-core (test only) +* https://kuma.io/ + + +Send pull request if you want to be listed here. + +# Features + +* UDP/TCP queries, IPv4 and IPv6 +* RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE (for all record types) are supported +* Fast +* Server side programming (mimicking the net/http package) +* Client side programming +* DNSSEC: signing, validating and key generation for DSA, RSA, ECDSA and Ed25519 +* EDNS0, NSID, Cookies +* AXFR/IXFR +* TSIG, SIG(0) +* DNS over TLS (DoT): encrypted connection between client and server over TCP +* DNS name compression + +Have fun! + +Miek Gieben - 2010-2012 - +DNS Authors 2012- + +# Building + +This library uses Go modules and uses semantic versioning. Building is done with the `go` tool, so +the following should work: + + go get github.com/miekg/dns + go build github.com/miekg/dns + +## Examples + +A short "how to use the API" is at the beginning of doc.go (this also will show when you call `godoc +github.com/miekg/dns`). + +Example programs can be found in the `github.com/miekg/exdns` repository. + +## Supported RFCs + +*all of them* + +* 103{4,5} - DNS standard +* 1348 - NSAP record (removed the record) +* 1982 - Serial Arithmetic +* 1876 - LOC record +* 1995 - IXFR +* 1996 - DNS notify +* 2136 - DNS Update (dynamic updates) +* 2181 - RRset definition - there is no RRset type though, just []RR +* 2537 - RSAMD5 DNS keys +* 2065 - DNSSEC (updated in later RFCs) +* 2671 - EDNS record +* 2782 - SRV record +* 2845 - TSIG record +* 2915 - NAPTR record +* 2929 - DNS IANA Considerations +* 3110 - RSASHA1 DNS keys +* 3123 - APL record +* 3225 - DO bit (DNSSEC OK) +* 340{1,2,3} - NAPTR record +* 3445 - Limiting the scope of (DNS)KEY +* 3597 - Unknown RRs +* 403{3,4,5} - DNSSEC + validation functions +* 4255 - SSHFP record +* 4343 - Case insensitivity +* 4408 - SPF record +* 4509 - SHA256 Hash in DS +* 4592 - Wildcards in the DNS +* 4635 - HMAC SHA TSIG +* 4701 - DHCID +* 4892 - id.server +* 5001 - NSID +* 5155 - NSEC3 record +* 5205 - HIP record +* 5702 - SHA2 in the DNS +* 5936 - AXFR +* 5966 - TCP implementation recommendations +* 6605 - ECDSA +* 6725 - IANA Registry Update +* 6742 - ILNP DNS +* 6840 - Clarifications and Implementation Notes for DNS Security +* 6844 - CAA record +* 6891 - EDNS0 update +* 6895 - DNS IANA considerations +* 6944 - DNSSEC DNSKEY Algorithm Status +* 6975 - Algorithm Understanding in DNSSEC +* 7043 - EUI48/EUI64 records +* 7314 - DNS (EDNS) EXPIRE Option +* 7477 - CSYNC RR +* 7828 - edns-tcp-keepalive EDNS0 Option +* 7553 - URI record +* 7858 - DNS over TLS: Initiation and Performance Considerations +* 7871 - EDNS0 Client Subnet +* 7873 - Domain Name System (DNS) Cookies +* 8080 - EdDSA for DNSSEC +* 8499 - DNS Terminology +* 8659 - DNS Certification Authority Authorization (CAA) Resource Record +* 8976 - Message Digest for DNS Zones (ZONEMD RR) + +## Loosely Based Upon + +* ldns - +* NSD - +* Net::DNS - +* GRONG - diff --git a/vendor/github.com/miekg/dns/acceptfunc.go b/vendor/github.com/miekg/dns/acceptfunc.go new file mode 100644 index 0000000..825617f --- /dev/null +++ b/vendor/github.com/miekg/dns/acceptfunc.go @@ -0,0 +1,61 @@ +package dns + +// MsgAcceptFunc is used early in the server code to accept or reject a message with RcodeFormatError. +// It returns a MsgAcceptAction to indicate what should happen with the message. +type MsgAcceptFunc func(dh Header) MsgAcceptAction + +// DefaultMsgAcceptFunc checks the request and will reject if: +// +// * isn't a request (don't respond in that case) +// +// * opcode isn't OpcodeQuery or OpcodeNotify +// +// * Zero bit isn't zero +// +// * has more than 1 question in the question section +// +// * has more than 1 RR in the Answer section +// +// * has more than 0 RRs in the Authority section +// +// * has more than 2 RRs in the Additional section +// +var DefaultMsgAcceptFunc MsgAcceptFunc = defaultMsgAcceptFunc + +// MsgAcceptAction represents the action to be taken. +type MsgAcceptAction int + +const ( + MsgAccept MsgAcceptAction = iota // Accept the message + MsgReject // Reject the message with a RcodeFormatError + MsgIgnore // Ignore the error and send nothing back. + MsgRejectNotImplemented // Reject the message with a RcodeNotImplemented +) + +func defaultMsgAcceptFunc(dh Header) MsgAcceptAction { + if isResponse := dh.Bits&_QR != 0; isResponse { + return MsgIgnore + } + + // Don't allow dynamic updates, because then the sections can contain a whole bunch of RRs. + opcode := int(dh.Bits>>11) & 0xF + if opcode != OpcodeQuery && opcode != OpcodeNotify { + return MsgRejectNotImplemented + } + + if dh.Qdcount != 1 { + return MsgReject + } + // NOTIFY requests can have a SOA in the ANSWER section. See RFC 1996 Section 3.7 and 3.11. + if dh.Ancount > 1 { + return MsgReject + } + // IXFR request could have one SOA RR in the NS section. See RFC 1995, section 3. + if dh.Nscount > 1 { + return MsgReject + } + if dh.Arcount > 2 { + return MsgReject + } + return MsgAccept +} diff --git a/vendor/github.com/miekg/dns/client.go b/vendor/github.com/miekg/dns/client.go new file mode 100644 index 0000000..f907698 --- /dev/null +++ b/vendor/github.com/miekg/dns/client.go @@ -0,0 +1,449 @@ +package dns + +// A client implementation. + +import ( + "context" + "crypto/tls" + "encoding/binary" + "fmt" + "io" + "net" + "strings" + "time" +) + +const ( + dnsTimeout time.Duration = 2 * time.Second + tcpIdleTimeout time.Duration = 8 * time.Second +) + +// A Conn represents a connection to a DNS server. +type Conn struct { + net.Conn // a net.Conn holding the connection + UDPSize uint16 // minimum receive buffer for UDP messages + TsigSecret map[string]string // secret(s) for Tsig map[], zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2) + TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations. + tsigRequestMAC string +} + +// A Client defines parameters for a DNS client. +type Client struct { + Net string // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP) + UDPSize uint16 // minimum receive buffer for UDP messages + TLSConfig *tls.Config // TLS connection configuration + Dialer *net.Dialer // a net.Dialer used to set local address, timeouts and more + // Timeout is a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout, + // WriteTimeout when non-zero. Can be overridden with net.Dialer.Timeout (see Client.ExchangeWithDialer and + // Client.Dialer) or context.Context.Deadline (see ExchangeContext) + Timeout time.Duration + DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds, or net.Dialer.Timeout if expiring earlier - overridden by Timeout when that value is non-zero + ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero + WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero + TsigSecret map[string]string // secret(s) for Tsig map[], zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2) + TsigProvider TsigProvider // An implementation of the TsigProvider interface. If defined it replaces TsigSecret and is used for all TSIG operations. + SingleInflight bool // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass + group singleflight +} + +// Exchange performs a synchronous UDP query. It sends the message m to the address +// contained in a and waits for a reply. Exchange does not retry a failed query, nor +// will it fall back to TCP in case of truncation. +// See client.Exchange for more information on setting larger buffer sizes. +func Exchange(m *Msg, a string) (r *Msg, err error) { + client := Client{Net: "udp"} + r, _, err = client.Exchange(m, a) + return r, err +} + +func (c *Client) dialTimeout() time.Duration { + if c.Timeout != 0 { + return c.Timeout + } + if c.DialTimeout != 0 { + return c.DialTimeout + } + return dnsTimeout +} + +func (c *Client) readTimeout() time.Duration { + if c.ReadTimeout != 0 { + return c.ReadTimeout + } + return dnsTimeout +} + +func (c *Client) writeTimeout() time.Duration { + if c.WriteTimeout != 0 { + return c.WriteTimeout + } + return dnsTimeout +} + +// Dial connects to the address on the named network. +func (c *Client) Dial(address string) (conn *Conn, err error) { + // create a new dialer with the appropriate timeout + var d net.Dialer + if c.Dialer == nil { + d = net.Dialer{Timeout: c.getTimeoutForRequest(c.dialTimeout())} + } else { + d = *c.Dialer + } + + network := c.Net + if network == "" { + network = "udp" + } + + useTLS := strings.HasPrefix(network, "tcp") && strings.HasSuffix(network, "-tls") + + conn = new(Conn) + if useTLS { + network = strings.TrimSuffix(network, "-tls") + + conn.Conn, err = tls.DialWithDialer(&d, network, address, c.TLSConfig) + } else { + conn.Conn, err = d.Dial(network, address) + } + if err != nil { + return nil, err + } + conn.UDPSize = c.UDPSize + return conn, nil +} + +// Exchange performs a synchronous query. It sends the message m to the address +// contained in a and waits for a reply. Basic use pattern with a *dns.Client: +// +// c := new(dns.Client) +// in, rtt, err := c.Exchange(message, "127.0.0.1:53") +// +// Exchange does not retry a failed query, nor will it fall back to TCP in +// case of truncation. +// It is up to the caller to create a message that allows for larger responses to be +// returned. Specifically this means adding an EDNS0 OPT RR that will advertise a larger +// buffer, see SetEdns0. Messages without an OPT RR will fallback to the historic limit +// of 512 bytes +// To specify a local address or a timeout, the caller has to set the `Client.Dialer` +// attribute appropriately +func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, err error) { + co, err := c.Dial(address) + + if err != nil { + return nil, 0, err + } + defer co.Close() + return c.ExchangeWithConn(m, co) +} + +// ExchangeWithConn has the same behavior as Exchange, just with a predetermined connection +// that will be used instead of creating a new one. +// Usage pattern with a *dns.Client: +// c := new(dns.Client) +// // connection management logic goes here +// +// conn := c.Dial(address) +// in, rtt, err := c.ExchangeWithConn(message, conn) +// +// This allows users of the library to implement their own connection management, +// as opposed to Exchange, which will always use new connections and incur the added overhead +// that entails when using "tcp" and especially "tcp-tls" clients. +func (c *Client) ExchangeWithConn(m *Msg, conn *Conn) (r *Msg, rtt time.Duration, err error) { + if !c.SingleInflight { + return c.exchange(m, conn) + } + + q := m.Question[0] + key := fmt.Sprintf("%s:%d:%d", q.Name, q.Qtype, q.Qclass) + r, rtt, err, shared := c.group.Do(key, func() (*Msg, time.Duration, error) { + return c.exchange(m, conn) + }) + if r != nil && shared { + r = r.Copy() + } + + return r, rtt, err +} + +func (c *Client) exchange(m *Msg, co *Conn) (r *Msg, rtt time.Duration, err error) { + + opt := m.IsEdns0() + // If EDNS0 is used use that for size. + if opt != nil && opt.UDPSize() >= MinMsgSize { + co.UDPSize = opt.UDPSize() + } + // Otherwise use the client's configured UDP size. + if opt == nil && c.UDPSize >= MinMsgSize { + co.UDPSize = c.UDPSize + } + + co.TsigSecret, co.TsigProvider = c.TsigSecret, c.TsigProvider + t := time.Now() + // write with the appropriate write timeout + co.SetWriteDeadline(t.Add(c.getTimeoutForRequest(c.writeTimeout()))) + if err = co.WriteMsg(m); err != nil { + return nil, 0, err + } + + co.SetReadDeadline(time.Now().Add(c.getTimeoutForRequest(c.readTimeout()))) + if _, ok := co.Conn.(net.PacketConn); ok { + for { + r, err = co.ReadMsg() + // Ignore replies with mismatched IDs because they might be + // responses to earlier queries that timed out. + if err != nil || r.Id == m.Id { + break + } + } + } else { + r, err = co.ReadMsg() + if err == nil && r.Id != m.Id { + err = ErrId + } + } + rtt = time.Since(t) + return r, rtt, err +} + +// ReadMsg reads a message from the connection co. +// If the received message contains a TSIG record the transaction signature +// is verified. This method always tries to return the message, however if an +// error is returned there are no guarantees that the returned message is a +// valid representation of the packet read. +func (co *Conn) ReadMsg() (*Msg, error) { + p, err := co.ReadMsgHeader(nil) + if err != nil { + return nil, err + } + + m := new(Msg) + if err := m.Unpack(p); err != nil { + // If an error was returned, we still want to allow the user to use + // the message, but naively they can just check err if they don't want + // to use an erroneous message + return m, err + } + if t := m.IsTsig(); t != nil { + if co.TsigProvider != nil { + err = tsigVerifyProvider(p, co.TsigProvider, co.tsigRequestMAC, false) + } else { + if _, ok := co.TsigSecret[t.Hdr.Name]; !ok { + return m, ErrSecret + } + // Need to work on the original message p, as that was used to calculate the tsig. + err = TsigVerify(p, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false) + } + } + return m, err +} + +// ReadMsgHeader reads a DNS message, parses and populates hdr (when hdr is not nil). +// Returns message as a byte slice to be parsed with Msg.Unpack later on. +// Note that error handling on the message body is not possible as only the header is parsed. +func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) { + var ( + p []byte + n int + err error + ) + + if _, ok := co.Conn.(net.PacketConn); ok { + if co.UDPSize > MinMsgSize { + p = make([]byte, co.UDPSize) + } else { + p = make([]byte, MinMsgSize) + } + n, err = co.Read(p) + } else { + var length uint16 + if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil { + return nil, err + } + + p = make([]byte, length) + n, err = io.ReadFull(co.Conn, p) + } + + if err != nil { + return nil, err + } else if n < headerSize { + return nil, ErrShortRead + } + + p = p[:n] + if hdr != nil { + dh, _, err := unpackMsgHdr(p, 0) + if err != nil { + return nil, err + } + *hdr = dh + } + return p, err +} + +// Read implements the net.Conn read method. +func (co *Conn) Read(p []byte) (n int, err error) { + if co.Conn == nil { + return 0, ErrConnEmpty + } + + if _, ok := co.Conn.(net.PacketConn); ok { + // UDP connection + return co.Conn.Read(p) + } + + var length uint16 + if err := binary.Read(co.Conn, binary.BigEndian, &length); err != nil { + return 0, err + } + if int(length) > len(p) { + return 0, io.ErrShortBuffer + } + + return io.ReadFull(co.Conn, p[:length]) +} + +// WriteMsg sends a message through the connection co. +// If the message m contains a TSIG record the transaction +// signature is calculated. +func (co *Conn) WriteMsg(m *Msg) (err error) { + var out []byte + if t := m.IsTsig(); t != nil { + mac := "" + if co.TsigProvider != nil { + out, mac, err = tsigGenerateProvider(m, co.TsigProvider, co.tsigRequestMAC, false) + } else { + if _, ok := co.TsigSecret[t.Hdr.Name]; !ok { + return ErrSecret + } + out, mac, err = TsigGenerate(m, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false) + } + // Set for the next read, although only used in zone transfers + co.tsigRequestMAC = mac + } else { + out, err = m.Pack() + } + if err != nil { + return err + } + _, err = co.Write(out) + return err +} + +// Write implements the net.Conn Write method. +func (co *Conn) Write(p []byte) (int, error) { + if len(p) > MaxMsgSize { + return 0, &Error{err: "message too large"} + } + + if _, ok := co.Conn.(net.PacketConn); ok { + return co.Conn.Write(p) + } + + msg := make([]byte, 2+len(p)) + binary.BigEndian.PutUint16(msg, uint16(len(p))) + copy(msg[2:], p) + return co.Conn.Write(msg) +} + +// Return the appropriate timeout for a specific request +func (c *Client) getTimeoutForRequest(timeout time.Duration) time.Duration { + var requestTimeout time.Duration + if c.Timeout != 0 { + requestTimeout = c.Timeout + } else { + requestTimeout = timeout + } + // net.Dialer.Timeout has priority if smaller than the timeouts computed so + // far + if c.Dialer != nil && c.Dialer.Timeout != 0 { + if c.Dialer.Timeout < requestTimeout { + requestTimeout = c.Dialer.Timeout + } + } + return requestTimeout +} + +// Dial connects to the address on the named network. +func Dial(network, address string) (conn *Conn, err error) { + conn = new(Conn) + conn.Conn, err = net.Dial(network, address) + if err != nil { + return nil, err + } + return conn, nil +} + +// ExchangeContext performs a synchronous UDP query, like Exchange. It +// additionally obeys deadlines from the passed Context. +func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) { + client := Client{Net: "udp"} + r, _, err = client.ExchangeContext(ctx, m, a) + // ignoring rtt to leave the original ExchangeContext API unchanged, but + // this function will go away + return r, err +} + +// ExchangeConn performs a synchronous query. It sends the message m via the connection +// c and waits for a reply. The connection c is not closed by ExchangeConn. +// Deprecated: This function is going away, but can easily be mimicked: +// +// co := &dns.Conn{Conn: c} // c is your net.Conn +// co.WriteMsg(m) +// in, _ := co.ReadMsg() +// co.Close() +// +func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) { + println("dns: ExchangeConn: this function is deprecated") + co := new(Conn) + co.Conn = c + if err = co.WriteMsg(m); err != nil { + return nil, err + } + r, err = co.ReadMsg() + if err == nil && r.Id != m.Id { + err = ErrId + } + return r, err +} + +// DialTimeout acts like Dial but takes a timeout. +func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) { + client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}} + return client.Dial(address) +} + +// DialWithTLS connects to the address on the named network with TLS. +func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, err error) { + if !strings.HasSuffix(network, "-tls") { + network += "-tls" + } + client := Client{Net: network, TLSConfig: tlsConfig} + return client.Dial(address) +} + +// DialTimeoutWithTLS acts like DialWithTLS but takes a timeout. +func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout time.Duration) (conn *Conn, err error) { + if !strings.HasSuffix(network, "-tls") { + network += "-tls" + } + client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}, TLSConfig: tlsConfig} + return client.Dial(address) +} + +// ExchangeContext acts like Exchange, but honors the deadline on the provided +// context, if present. If there is both a context deadline and a configured +// timeout on the client, the earliest of the two takes effect. +func (c *Client) ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, rtt time.Duration, err error) { + var timeout time.Duration + if deadline, ok := ctx.Deadline(); !ok { + timeout = 0 + } else { + timeout = time.Until(deadline) + } + // not passing the context to the underlying calls, as the API does not support + // context. For timeouts you should set up Client.Dialer and call Client.Exchange. + // TODO(tmthrgd,miekg): this is a race condition. + c.Dialer = &net.Dialer{Timeout: timeout} + return c.Exchange(m, a) +} diff --git a/vendor/github.com/miekg/dns/clientconfig.go b/vendor/github.com/miekg/dns/clientconfig.go new file mode 100644 index 0000000..e11b630 --- /dev/null +++ b/vendor/github.com/miekg/dns/clientconfig.go @@ -0,0 +1,135 @@ +package dns + +import ( + "bufio" + "io" + "os" + "strconv" + "strings" +) + +// ClientConfig wraps the contents of the /etc/resolv.conf file. +type ClientConfig struct { + Servers []string // servers to use + Search []string // suffixes to append to local name + Port string // what port to use + Ndots int // number of dots in name to trigger absolute lookup + Timeout int // seconds before giving up on packet + Attempts int // lost packets before giving up on server, not used in the package dns +} + +// ClientConfigFromFile parses a resolv.conf(5) like file and returns +// a *ClientConfig. +func ClientConfigFromFile(resolvconf string) (*ClientConfig, error) { + file, err := os.Open(resolvconf) + if err != nil { + return nil, err + } + defer file.Close() + return ClientConfigFromReader(file) +} + +// ClientConfigFromReader works like ClientConfigFromFile but takes an io.Reader as argument +func ClientConfigFromReader(resolvconf io.Reader) (*ClientConfig, error) { + c := new(ClientConfig) + scanner := bufio.NewScanner(resolvconf) + c.Servers = make([]string, 0) + c.Search = make([]string, 0) + c.Port = "53" + c.Ndots = 1 + c.Timeout = 5 + c.Attempts = 2 + + for scanner.Scan() { + if err := scanner.Err(); err != nil { + return nil, err + } + line := scanner.Text() + f := strings.Fields(line) + if len(f) < 1 { + continue + } + switch f[0] { + case "nameserver": // add one name server + if len(f) > 1 { + // One more check: make sure server name is + // just an IP address. Otherwise we need DNS + // to look it up. + name := f[1] + c.Servers = append(c.Servers, name) + } + + case "domain": // set search path to just this domain + if len(f) > 1 { + c.Search = make([]string, 1) + c.Search[0] = f[1] + } else { + c.Search = make([]string, 0) + } + + case "search": // set search path to given servers + c.Search = append([]string(nil), f[1:]...) + + case "options": // magic options + for _, s := range f[1:] { + switch { + case len(s) >= 6 && s[:6] == "ndots:": + n, _ := strconv.Atoi(s[6:]) + if n < 0 { + n = 0 + } else if n > 15 { + n = 15 + } + c.Ndots = n + case len(s) >= 8 && s[:8] == "timeout:": + n, _ := strconv.Atoi(s[8:]) + if n < 1 { + n = 1 + } + c.Timeout = n + case len(s) >= 9 && s[:9] == "attempts:": + n, _ := strconv.Atoi(s[9:]) + if n < 1 { + n = 1 + } + c.Attempts = n + case s == "rotate": + /* not imp */ + } + } + } + } + return c, nil +} + +// NameList returns all of the names that should be queried based on the +// config. It is based off of go's net/dns name building, but it does not +// check the length of the resulting names. +func (c *ClientConfig) NameList(name string) []string { + // if this domain is already fully qualified, no append needed. + if IsFqdn(name) { + return []string{name} + } + + // Check to see if the name has more labels than Ndots. Do this before making + // the domain fully qualified. + hasNdots := CountLabel(name) > c.Ndots + // Make the domain fully qualified. + name = Fqdn(name) + + // Make a list of names based off search. + names := []string{} + + // If name has enough dots, try that first. + if hasNdots { + names = append(names, name) + } + for _, s := range c.Search { + names = append(names, Fqdn(name+s)) + } + // If we didn't have enough dots, try after suffixes. + if !hasNdots { + names = append(names, name) + } + return names +} diff --git a/vendor/github.com/miekg/dns/dane.go b/vendor/github.com/miekg/dns/dane.go new file mode 100644 index 0000000..8c4a14e --- /dev/null +++ b/vendor/github.com/miekg/dns/dane.go @@ -0,0 +1,43 @@ +package dns + +import ( + "crypto/sha256" + "crypto/sha512" + "crypto/x509" + "encoding/hex" + "errors" +) + +// CertificateToDANE converts a certificate to a hex string as used in the TLSA or SMIMEA records. +func CertificateToDANE(selector, matchingType uint8, cert *x509.Certificate) (string, error) { + switch matchingType { + case 0: + switch selector { + case 0: + return hex.EncodeToString(cert.Raw), nil + case 1: + return hex.EncodeToString(cert.RawSubjectPublicKeyInfo), nil + } + case 1: + h := sha256.New() + switch selector { + case 0: + h.Write(cert.Raw) + return hex.EncodeToString(h.Sum(nil)), nil + case 1: + h.Write(cert.RawSubjectPublicKeyInfo) + return hex.EncodeToString(h.Sum(nil)), nil + } + case 2: + h := sha512.New() + switch selector { + case 0: + h.Write(cert.Raw) + return hex.EncodeToString(h.Sum(nil)), nil + case 1: + h.Write(cert.RawSubjectPublicKeyInfo) + return hex.EncodeToString(h.Sum(nil)), nil + } + } + return "", errors.New("dns: bad MatchingType or Selector") +} diff --git a/vendor/github.com/miekg/dns/defaults.go b/vendor/github.com/miekg/dns/defaults.go new file mode 100644 index 0000000..d47b0b1 --- /dev/null +++ b/vendor/github.com/miekg/dns/defaults.go @@ -0,0 +1,381 @@ +package dns + +import ( + "errors" + "net" + "strconv" + "strings" +) + +const hexDigit = "0123456789abcdef" + +// Everything is assumed in ClassINET. + +// SetReply creates a reply message from a request message. +func (dns *Msg) SetReply(request *Msg) *Msg { + dns.Id = request.Id + dns.Response = true + dns.Opcode = request.Opcode + if dns.Opcode == OpcodeQuery { + dns.RecursionDesired = request.RecursionDesired // Copy rd bit + dns.CheckingDisabled = request.CheckingDisabled // Copy cd bit + } + dns.Rcode = RcodeSuccess + if len(request.Question) > 0 { + dns.Question = make([]Question, 1) + dns.Question[0] = request.Question[0] + } + return dns +} + +// SetQuestion creates a question message, it sets the Question +// section, generates an Id and sets the RecursionDesired (RD) +// bit to true. +func (dns *Msg) SetQuestion(z string, t uint16) *Msg { + dns.Id = Id() + dns.RecursionDesired = true + dns.Question = make([]Question, 1) + dns.Question[0] = Question{z, t, ClassINET} + return dns +} + +// SetNotify creates a notify message, it sets the Question +// section, generates an Id and sets the Authoritative (AA) +// bit to true. +func (dns *Msg) SetNotify(z string) *Msg { + dns.Opcode = OpcodeNotify + dns.Authoritative = true + dns.Id = Id() + dns.Question = make([]Question, 1) + dns.Question[0] = Question{z, TypeSOA, ClassINET} + return dns +} + +// SetRcode creates an error message suitable for the request. +func (dns *Msg) SetRcode(request *Msg, rcode int) *Msg { + dns.SetReply(request) + dns.Rcode = rcode + return dns +} + +// SetRcodeFormatError creates a message with FormError set. +func (dns *Msg) SetRcodeFormatError(request *Msg) *Msg { + dns.Rcode = RcodeFormatError + dns.Opcode = OpcodeQuery + dns.Response = true + dns.Authoritative = false + dns.Id = request.Id + return dns +} + +// SetUpdate makes the message a dynamic update message. It +// sets the ZONE section to: z, TypeSOA, ClassINET. +func (dns *Msg) SetUpdate(z string) *Msg { + dns.Id = Id() + dns.Response = false + dns.Opcode = OpcodeUpdate + dns.Compress = false // BIND9 cannot handle compression + dns.Question = make([]Question, 1) + dns.Question[0] = Question{z, TypeSOA, ClassINET} + return dns +} + +// SetIxfr creates message for requesting an IXFR. +func (dns *Msg) SetIxfr(z string, serial uint32, ns, mbox string) *Msg { + dns.Id = Id() + dns.Question = make([]Question, 1) + dns.Ns = make([]RR, 1) + s := new(SOA) + s.Hdr = RR_Header{z, TypeSOA, ClassINET, defaultTtl, 0} + s.Serial = serial + s.Ns = ns + s.Mbox = mbox + dns.Question[0] = Question{z, TypeIXFR, ClassINET} + dns.Ns[0] = s + return dns +} + +// SetAxfr creates message for requesting an AXFR. +func (dns *Msg) SetAxfr(z string) *Msg { + dns.Id = Id() + dns.Question = make([]Question, 1) + dns.Question[0] = Question{z, TypeAXFR, ClassINET} + return dns +} + +// SetTsig appends a TSIG RR to the message. +// This is only a skeleton TSIG RR that is added as the last RR in the +// additional section. The TSIG is calculated when the message is being send. +func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg { + t := new(TSIG) + t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0} + t.Algorithm = algo + t.Fudge = fudge + t.TimeSigned = uint64(timesigned) + t.OrigId = dns.Id + dns.Extra = append(dns.Extra, t) + return dns +} + +// SetEdns0 appends a EDNS0 OPT RR to the message. +// TSIG should always the last RR in a message. +func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg { + e := new(OPT) + e.Hdr.Name = "." + e.Hdr.Rrtype = TypeOPT + e.SetUDPSize(udpsize) + if do { + e.SetDo() + } + dns.Extra = append(dns.Extra, e) + return dns +} + +// IsTsig checks if the message has a TSIG record as the last record +// in the additional section. It returns the TSIG record found or nil. +func (dns *Msg) IsTsig() *TSIG { + if len(dns.Extra) > 0 { + if dns.Extra[len(dns.Extra)-1].Header().Rrtype == TypeTSIG { + return dns.Extra[len(dns.Extra)-1].(*TSIG) + } + } + return nil +} + +// IsEdns0 checks if the message has a EDNS0 (OPT) record, any EDNS0 +// record in the additional section will do. It returns the OPT record +// found or nil. +func (dns *Msg) IsEdns0() *OPT { + // RFC 6891, Section 6.1.1 allows the OPT record to appear + // anywhere in the additional record section, but it's usually at + // the end so start there. + for i := len(dns.Extra) - 1; i >= 0; i-- { + if dns.Extra[i].Header().Rrtype == TypeOPT { + return dns.Extra[i].(*OPT) + } + } + return nil +} + +// popEdns0 is like IsEdns0, but it removes the record from the message. +func (dns *Msg) popEdns0() *OPT { + // RFC 6891, Section 6.1.1 allows the OPT record to appear + // anywhere in the additional record section, but it's usually at + // the end so start there. + for i := len(dns.Extra) - 1; i >= 0; i-- { + if dns.Extra[i].Header().Rrtype == TypeOPT { + opt := dns.Extra[i].(*OPT) + dns.Extra = append(dns.Extra[:i], dns.Extra[i+1:]...) + return opt + } + } + return nil +} + +// IsDomainName checks if s is a valid domain name, it returns the number of +// labels and true, when a domain name is valid. Note that non fully qualified +// domain name is considered valid, in this case the last label is counted in +// the number of labels. When false is returned the number of labels is not +// defined. Also note that this function is extremely liberal; almost any +// string is a valid domain name as the DNS is 8 bit protocol. It checks if each +// label fits in 63 characters and that the entire name will fit into the 255 +// octet wire format limit. +func IsDomainName(s string) (labels int, ok bool) { + // XXX: The logic in this function was copied from packDomainName and + // should be kept in sync with that function. + + const lenmsg = 256 + + if len(s) == 0 { // Ok, for instance when dealing with update RR without any rdata. + return 0, false + } + + s = Fqdn(s) + + // Each dot ends a segment of the name. Except for escaped dots (\.), which + // are normal dots. + + var ( + off int + begin int + wasDot bool + ) + for i := 0; i < len(s); i++ { + switch s[i] { + case '\\': + if off+1 > lenmsg { + return labels, false + } + + // check for \DDD + if i+3 < len(s) && isDigit(s[i+1]) && isDigit(s[i+2]) && isDigit(s[i+3]) { + i += 3 + begin += 3 + } else { + i++ + begin++ + } + + wasDot = false + case '.': + if wasDot { + // two dots back to back is not legal + return labels, false + } + wasDot = true + + labelLen := i - begin + if labelLen >= 1<<6 { // top two bits of length must be clear + return labels, false + } + + // off can already (we're in a loop) be bigger than lenmsg + // this happens when a name isn't fully qualified + off += 1 + labelLen + if off > lenmsg { + return labels, false + } + + labels++ + begin = i + 1 + default: + wasDot = false + } + } + + return labels, true +} + +// IsSubDomain checks if child is indeed a child of the parent. If child and parent +// are the same domain true is returned as well. +func IsSubDomain(parent, child string) bool { + // Entire child is contained in parent + return CompareDomainName(parent, child) == CountLabel(parent) +} + +// IsMsg sanity checks buf and returns an error if it isn't a valid DNS packet. +// The checking is performed on the binary payload. +func IsMsg(buf []byte) error { + // Header + if len(buf) < headerSize { + return errors.New("dns: bad message header") + } + // Header: Opcode + // TODO(miek): more checks here, e.g. check all header bits. + return nil +} + +// IsFqdn checks if a domain name is fully qualified. +func IsFqdn(s string) bool { + s2 := strings.TrimSuffix(s, ".") + if s == s2 { + return false + } + + i := strings.LastIndexFunc(s2, func(r rune) bool { + return r != '\\' + }) + + // Test whether we have an even number of escape sequences before + // the dot or none. + return (len(s2)-i)%2 != 0 +} + +// IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181. +// This means the RRs need to have the same type, name, and class. Returns true +// if the RR set is valid, otherwise false. +func IsRRset(rrset []RR) bool { + if len(rrset) == 0 { + return false + } + if len(rrset) == 1 { + return true + } + rrHeader := rrset[0].Header() + rrType := rrHeader.Rrtype + rrClass := rrHeader.Class + rrName := rrHeader.Name + + for _, rr := range rrset[1:] { + curRRHeader := rr.Header() + if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName { + // Mismatch between the records, so this is not a valid rrset for + //signing/verifying + return false + } + } + + return true +} + +// Fqdn return the fully qualified domain name from s. +// If s is already fully qualified, it behaves as the identity function. +func Fqdn(s string) string { + if IsFqdn(s) { + return s + } + return s + "." +} + +// CanonicalName returns the domain name in canonical form. A name in canonical +// form is lowercase and fully qualified. See Section 6.2 in RFC 4034. +func CanonicalName(s string) string { + return strings.ToLower(Fqdn(s)) +} + +// Copied from the official Go code. + +// ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP +// address suitable for reverse DNS (PTR) record lookups or an error if it fails +// to parse the IP address. +func ReverseAddr(addr string) (arpa string, err error) { + ip := net.ParseIP(addr) + if ip == nil { + return "", &Error{err: "unrecognized address: " + addr} + } + if v4 := ip.To4(); v4 != nil { + buf := make([]byte, 0, net.IPv4len*4+len("in-addr.arpa.")) + // Add it, in reverse, to the buffer + for i := len(v4) - 1; i >= 0; i-- { + buf = strconv.AppendInt(buf, int64(v4[i]), 10) + buf = append(buf, '.') + } + // Append "in-addr.arpa." and return (buf already has the final .) + buf = append(buf, "in-addr.arpa."...) + return string(buf), nil + } + // Must be IPv6 + buf := make([]byte, 0, net.IPv6len*4+len("ip6.arpa.")) + // Add it, in reverse, to the buffer + for i := len(ip) - 1; i >= 0; i-- { + v := ip[i] + buf = append(buf, hexDigit[v&0xF], '.', hexDigit[v>>4], '.') + } + // Append "ip6.arpa." and return (buf already has the final .) + buf = append(buf, "ip6.arpa."...) + return string(buf), nil +} + +// String returns the string representation for the type t. +func (t Type) String() string { + if t1, ok := TypeToString[uint16(t)]; ok { + return t1 + } + return "TYPE" + strconv.Itoa(int(t)) +} + +// String returns the string representation for the class c. +func (c Class) String() string { + if s, ok := ClassToString[uint16(c)]; ok { + // Only emit mnemonics when they are unambiguous, specially ANY is in both. + if _, ok := StringToType[s]; !ok { + return s + } + } + return "CLASS" + strconv.Itoa(int(c)) +} + +// String returns the string representation for the name n. +func (n Name) String() string { + return sprintName(string(n)) +} diff --git a/vendor/github.com/miekg/dns/dns.go b/vendor/github.com/miekg/dns/dns.go new file mode 100644 index 0000000..a88484b --- /dev/null +++ b/vendor/github.com/miekg/dns/dns.go @@ -0,0 +1,158 @@ +package dns + +import ( + "encoding/hex" + "strconv" +) + +const ( + year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits. + defaultTtl = 3600 // Default internal TTL. + + // DefaultMsgSize is the standard default for messages larger than 512 bytes. + DefaultMsgSize = 4096 + // MinMsgSize is the minimal size of a DNS packet. + MinMsgSize = 512 + // MaxMsgSize is the largest possible DNS packet. + MaxMsgSize = 65535 +) + +// Error represents a DNS error. +type Error struct{ err string } + +func (e *Error) Error() string { + if e == nil { + return "dns: " + } + return "dns: " + e.err +} + +// An RR represents a resource record. +type RR interface { + // Header returns the header of an resource record. The header contains + // everything up to the rdata. + Header() *RR_Header + // String returns the text representation of the resource record. + String() string + + // copy returns a copy of the RR + copy() RR + + // len returns the length (in octets) of the compressed or uncompressed RR in wire format. + // + // If compression is nil, the uncompressed size will be returned, otherwise the compressed + // size will be returned and domain names will be added to the map for future compression. + len(off int, compression map[string]struct{}) int + + // pack packs the records RDATA into wire format. The header will + // already have been packed into msg. + pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) + + // unpack unpacks an RR from wire format. + // + // This will only be called on a new and empty RR type with only the header populated. It + // will only be called if the record's RDATA is non-empty. + unpack(msg []byte, off int) (off1 int, err error) + + // parse parses an RR from zone file format. + // + // This will only be called on a new and empty RR type with only the header populated. + parse(c *zlexer, origin string) *ParseError + + // isDuplicate returns whether the two RRs are duplicates. + isDuplicate(r2 RR) bool +} + +// RR_Header is the header all DNS resource records share. +type RR_Header struct { + Name string `dns:"cdomain-name"` + Rrtype uint16 + Class uint16 + Ttl uint32 + Rdlength uint16 // Length of data after header. +} + +// Header returns itself. This is here to make RR_Header implements the RR interface. +func (h *RR_Header) Header() *RR_Header { return h } + +// Just to implement the RR interface. +func (h *RR_Header) copy() RR { return nil } + +func (h *RR_Header) String() string { + var s string + + if h.Rrtype == TypeOPT { + s = ";" + // and maybe other things + } + + s += sprintName(h.Name) + "\t" + s += strconv.FormatInt(int64(h.Ttl), 10) + "\t" + s += Class(h.Class).String() + "\t" + s += Type(h.Rrtype).String() + "\t" + return s +} + +func (h *RR_Header) len(off int, compression map[string]struct{}) int { + l := domainNameLen(h.Name, off, compression, true) + l += 10 // rrtype(2) + class(2) + ttl(4) + rdlength(2) + return l +} + +func (h *RR_Header) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + // RR_Header has no RDATA to pack. + return off, nil +} + +func (h *RR_Header) unpack(msg []byte, off int) (int, error) { + panic("dns: internal error: unpack should never be called on RR_Header") +} + +func (h *RR_Header) parse(c *zlexer, origin string) *ParseError { + panic("dns: internal error: parse should never be called on RR_Header") +} + +// ToRFC3597 converts a known RR to the unknown RR representation from RFC 3597. +func (rr *RFC3597) ToRFC3597(r RR) error { + buf := make([]byte, Len(r)) + headerEnd, off, err := packRR(r, buf, 0, compressionMap{}, false) + if err != nil { + return err + } + buf = buf[:off] + + *rr = RFC3597{Hdr: *r.Header()} + rr.Hdr.Rdlength = uint16(off - headerEnd) + + if noRdata(rr.Hdr) { + return nil + } + + _, err = rr.unpack(buf, headerEnd) + return err +} + +// fromRFC3597 converts an unknown RR representation from RFC 3597 to the known RR type. +func (rr *RFC3597) fromRFC3597(r RR) error { + hdr := r.Header() + *hdr = rr.Hdr + + // Can't overflow uint16 as the length of Rdata is validated in (*RFC3597).parse. + // We can only get here when rr was constructed with that method. + hdr.Rdlength = uint16(hex.DecodedLen(len(rr.Rdata))) + + if noRdata(*hdr) { + // Dynamic update. + return nil + } + + // rr.pack requires an extra allocation and a copy so we just decode Rdata + // manually, it's simpler anyway. + msg, err := hex.DecodeString(rr.Rdata) + if err != nil { + return err + } + + _, err = r.unpack(msg, 0) + return err +} diff --git a/vendor/github.com/miekg/dns/dnssec.go b/vendor/github.com/miekg/dns/dnssec.go new file mode 100644 index 0000000..80d2be5 --- /dev/null +++ b/vendor/github.com/miekg/dns/dnssec.go @@ -0,0 +1,757 @@ +package dns + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" + "encoding/asn1" + "encoding/binary" + "encoding/hex" + "math/big" + "sort" + "strings" + "time" +) + +// DNSSEC encryption algorithm codes. +const ( + _ uint8 = iota + RSAMD5 + DH + DSA + _ // Skip 4, RFC 6725, section 2.1 + RSASHA1 + DSANSEC3SHA1 + RSASHA1NSEC3SHA1 + RSASHA256 + _ // Skip 9, RFC 6725, section 2.1 + RSASHA512 + _ // Skip 11, RFC 6725, section 2.1 + ECCGOST + ECDSAP256SHA256 + ECDSAP384SHA384 + ED25519 + ED448 + INDIRECT uint8 = 252 + PRIVATEDNS uint8 = 253 // Private (experimental keys) + PRIVATEOID uint8 = 254 +) + +// AlgorithmToString is a map of algorithm IDs to algorithm names. +var AlgorithmToString = map[uint8]string{ + RSAMD5: "RSAMD5", + DH: "DH", + DSA: "DSA", + RSASHA1: "RSASHA1", + DSANSEC3SHA1: "DSA-NSEC3-SHA1", + RSASHA1NSEC3SHA1: "RSASHA1-NSEC3-SHA1", + RSASHA256: "RSASHA256", + RSASHA512: "RSASHA512", + ECCGOST: "ECC-GOST", + ECDSAP256SHA256: "ECDSAP256SHA256", + ECDSAP384SHA384: "ECDSAP384SHA384", + ED25519: "ED25519", + ED448: "ED448", + INDIRECT: "INDIRECT", + PRIVATEDNS: "PRIVATEDNS", + PRIVATEOID: "PRIVATEOID", +} + +// AlgorithmToHash is a map of algorithm crypto hash IDs to crypto.Hash's. +var AlgorithmToHash = map[uint8]crypto.Hash{ + RSAMD5: crypto.MD5, // Deprecated in RFC 6725 + DSA: crypto.SHA1, + RSASHA1: crypto.SHA1, + RSASHA1NSEC3SHA1: crypto.SHA1, + RSASHA256: crypto.SHA256, + ECDSAP256SHA256: crypto.SHA256, + ECDSAP384SHA384: crypto.SHA384, + RSASHA512: crypto.SHA512, + ED25519: crypto.Hash(0), +} + +// DNSSEC hashing algorithm codes. +const ( + _ uint8 = iota + SHA1 // RFC 4034 + SHA256 // RFC 4509 + GOST94 // RFC 5933 + SHA384 // Experimental + SHA512 // Experimental +) + +// HashToString is a map of hash IDs to names. +var HashToString = map[uint8]string{ + SHA1: "SHA1", + SHA256: "SHA256", + GOST94: "GOST94", + SHA384: "SHA384", + SHA512: "SHA512", +} + +// DNSKEY flag values. +const ( + SEP = 1 + REVOKE = 1 << 7 + ZONE = 1 << 8 +) + +// The RRSIG needs to be converted to wireformat with some of the rdata (the signature) missing. +type rrsigWireFmt struct { + TypeCovered uint16 + Algorithm uint8 + Labels uint8 + OrigTtl uint32 + Expiration uint32 + Inception uint32 + KeyTag uint16 + SignerName string `dns:"domain-name"` + /* No Signature */ +} + +// Used for converting DNSKEY's rdata to wirefmt. +type dnskeyWireFmt struct { + Flags uint16 + Protocol uint8 + Algorithm uint8 + PublicKey string `dns:"base64"` + /* Nothing is left out */ +} + +func divRoundUp(a, b int) int { + return (a + b - 1) / b +} + +// KeyTag calculates the keytag (or key-id) of the DNSKEY. +func (k *DNSKEY) KeyTag() uint16 { + if k == nil { + return 0 + } + var keytag int + switch k.Algorithm { + case RSAMD5: + // Look at the bottom two bytes of the modules, which the last + // item in the pubkey. + // This algorithm has been deprecated, but keep this key-tag calculation. + modulus, _ := fromBase64([]byte(k.PublicKey)) + if len(modulus) > 1 { + x := binary.BigEndian.Uint16(modulus[len(modulus)-2:]) + keytag = int(x) + } + default: + keywire := new(dnskeyWireFmt) + keywire.Flags = k.Flags + keywire.Protocol = k.Protocol + keywire.Algorithm = k.Algorithm + keywire.PublicKey = k.PublicKey + wire := make([]byte, DefaultMsgSize) + n, err := packKeyWire(keywire, wire) + if err != nil { + return 0 + } + wire = wire[:n] + for i, v := range wire { + if i&1 != 0 { + keytag += int(v) // must be larger than uint32 + } else { + keytag += int(v) << 8 + } + } + keytag += keytag >> 16 & 0xFFFF + keytag &= 0xFFFF + } + return uint16(keytag) +} + +// ToDS converts a DNSKEY record to a DS record. +func (k *DNSKEY) ToDS(h uint8) *DS { + if k == nil { + return nil + } + ds := new(DS) + ds.Hdr.Name = k.Hdr.Name + ds.Hdr.Class = k.Hdr.Class + ds.Hdr.Rrtype = TypeDS + ds.Hdr.Ttl = k.Hdr.Ttl + ds.Algorithm = k.Algorithm + ds.DigestType = h + ds.KeyTag = k.KeyTag() + + keywire := new(dnskeyWireFmt) + keywire.Flags = k.Flags + keywire.Protocol = k.Protocol + keywire.Algorithm = k.Algorithm + keywire.PublicKey = k.PublicKey + wire := make([]byte, DefaultMsgSize) + n, err := packKeyWire(keywire, wire) + if err != nil { + return nil + } + wire = wire[:n] + + owner := make([]byte, 255) + off, err1 := PackDomainName(CanonicalName(k.Hdr.Name), owner, 0, nil, false) + if err1 != nil { + return nil + } + owner = owner[:off] + // RFC4034: + // digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA); + // "|" denotes concatenation + // DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key. + + var hash crypto.Hash + switch h { + case SHA1: + hash = crypto.SHA1 + case SHA256: + hash = crypto.SHA256 + case SHA384: + hash = crypto.SHA384 + case SHA512: + hash = crypto.SHA512 + default: + return nil + } + + s := hash.New() + s.Write(owner) + s.Write(wire) + ds.Digest = hex.EncodeToString(s.Sum(nil)) + return ds +} + +// ToCDNSKEY converts a DNSKEY record to a CDNSKEY record. +func (k *DNSKEY) ToCDNSKEY() *CDNSKEY { + c := &CDNSKEY{DNSKEY: *k} + c.Hdr = k.Hdr + c.Hdr.Rrtype = TypeCDNSKEY + return c +} + +// ToCDS converts a DS record to a CDS record. +func (d *DS) ToCDS() *CDS { + c := &CDS{DS: *d} + c.Hdr = d.Hdr + c.Hdr.Rrtype = TypeCDS + return c +} + +// Sign signs an RRSet. The signature needs to be filled in with the values: +// Inception, Expiration, KeyTag, SignerName and Algorithm. The rest is copied +// from the RRset. Sign returns a non-nill error when the signing went OK. +// There is no check if RRSet is a proper (RFC 2181) RRSet. If OrigTTL is non +// zero, it is used as-is, otherwise the TTL of the RRset is used as the +// OrigTTL. +func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error { + if k == nil { + return ErrPrivKey + } + // s.Inception and s.Expiration may be 0 (rollover etc.), the rest must be set + if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { + return ErrKey + } + + h0 := rrset[0].Header() + rr.Hdr.Rrtype = TypeRRSIG + rr.Hdr.Name = h0.Name + rr.Hdr.Class = h0.Class + if rr.OrigTtl == 0 { // If set don't override + rr.OrigTtl = h0.Ttl + } + rr.TypeCovered = h0.Rrtype + rr.Labels = uint8(CountLabel(h0.Name)) + + if strings.HasPrefix(h0.Name, "*") { + rr.Labels-- // wildcard, remove from label count + } + + sigwire := new(rrsigWireFmt) + sigwire.TypeCovered = rr.TypeCovered + sigwire.Algorithm = rr.Algorithm + sigwire.Labels = rr.Labels + sigwire.OrigTtl = rr.OrigTtl + sigwire.Expiration = rr.Expiration + sigwire.Inception = rr.Inception + sigwire.KeyTag = rr.KeyTag + // For signing, lowercase this name + sigwire.SignerName = CanonicalName(rr.SignerName) + + // Create the desired binary blob + signdata := make([]byte, DefaultMsgSize) + n, err := packSigWire(sigwire, signdata) + if err != nil { + return err + } + signdata = signdata[:n] + wire, err := rawSignatureData(rrset, rr) + if err != nil { + return err + } + + hash, ok := AlgorithmToHash[rr.Algorithm] + if !ok { + return ErrAlg + } + + switch rr.Algorithm { + case ED25519: + // ed25519 signs the raw message and performs hashing internally. + // All other supported signature schemes operate over the pre-hashed + // message, and thus ed25519 must be handled separately here. + // + // The raw message is passed directly into sign and crypto.Hash(0) is + // used to signal to the crypto.Signer that the data has not been hashed. + signature, err := sign(k, append(signdata, wire...), crypto.Hash(0), rr.Algorithm) + if err != nil { + return err + } + + rr.Signature = toBase64(signature) + return nil + case RSAMD5, DSA, DSANSEC3SHA1: + // See RFC 6944. + return ErrAlg + default: + h := hash.New() + h.Write(signdata) + h.Write(wire) + + signature, err := sign(k, h.Sum(nil), hash, rr.Algorithm) + if err != nil { + return err + } + + rr.Signature = toBase64(signature) + return nil + } +} + +func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte, error) { + signature, err := k.Sign(rand.Reader, hashed, hash) + if err != nil { + return nil, err + } + + switch alg { + case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512: + return signature, nil + case ECDSAP256SHA256, ECDSAP384SHA384: + ecdsaSignature := &struct { + R, S *big.Int + }{} + if _, err := asn1.Unmarshal(signature, ecdsaSignature); err != nil { + return nil, err + } + + var intlen int + switch alg { + case ECDSAP256SHA256: + intlen = 32 + case ECDSAP384SHA384: + intlen = 48 + } + + signature := intToBytes(ecdsaSignature.R, intlen) + signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...) + return signature, nil + case ED25519: + return signature, nil + default: + return nil, ErrAlg + } +} + +// Verify validates an RRSet with the signature and key. This is only the +// cryptographic test, the signature validity period must be checked separately. +// This function copies the rdata of some RRs (to lowercase domain names) for the validation to work. +func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error { + // First the easy checks + if !IsRRset(rrset) { + return ErrRRset + } + if rr.KeyTag != k.KeyTag() { + return ErrKey + } + if rr.Hdr.Class != k.Hdr.Class { + return ErrKey + } + if rr.Algorithm != k.Algorithm { + return ErrKey + } + if !strings.EqualFold(rr.SignerName, k.Hdr.Name) { + return ErrKey + } + if k.Protocol != 3 { + return ErrKey + } + + // IsRRset checked that we have at least one RR and that the RRs in + // the set have consistent type, class, and name. Also check that type and + // class matches the RRSIG record. + if h0 := rrset[0].Header(); h0.Class != rr.Hdr.Class || h0.Rrtype != rr.TypeCovered { + return ErrRRset + } + + // RFC 4035 5.3.2. Reconstructing the Signed Data + // Copy the sig, except the rrsig data + sigwire := new(rrsigWireFmt) + sigwire.TypeCovered = rr.TypeCovered + sigwire.Algorithm = rr.Algorithm + sigwire.Labels = rr.Labels + sigwire.OrigTtl = rr.OrigTtl + sigwire.Expiration = rr.Expiration + sigwire.Inception = rr.Inception + sigwire.KeyTag = rr.KeyTag + sigwire.SignerName = CanonicalName(rr.SignerName) + // Create the desired binary blob + signeddata := make([]byte, DefaultMsgSize) + n, err := packSigWire(sigwire, signeddata) + if err != nil { + return err + } + signeddata = signeddata[:n] + wire, err := rawSignatureData(rrset, rr) + if err != nil { + return err + } + + sigbuf := rr.sigBuf() // Get the binary signature data + if rr.Algorithm == PRIVATEDNS { // PRIVATEOID + // TODO(miek) + // remove the domain name and assume its ours? + } + + hash, ok := AlgorithmToHash[rr.Algorithm] + if !ok { + return ErrAlg + } + + switch rr.Algorithm { + case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512: + // TODO(mg): this can be done quicker, ie. cache the pubkey data somewhere?? + pubkey := k.publicKeyRSA() // Get the key + if pubkey == nil { + return ErrKey + } + + h := hash.New() + h.Write(signeddata) + h.Write(wire) + return rsa.VerifyPKCS1v15(pubkey, hash, h.Sum(nil), sigbuf) + + case ECDSAP256SHA256, ECDSAP384SHA384: + pubkey := k.publicKeyECDSA() + if pubkey == nil { + return ErrKey + } + + // Split sigbuf into the r and s coordinates + r := new(big.Int).SetBytes(sigbuf[:len(sigbuf)/2]) + s := new(big.Int).SetBytes(sigbuf[len(sigbuf)/2:]) + + h := hash.New() + h.Write(signeddata) + h.Write(wire) + if ecdsa.Verify(pubkey, h.Sum(nil), r, s) { + return nil + } + return ErrSig + + case ED25519: + pubkey := k.publicKeyED25519() + if pubkey == nil { + return ErrKey + } + + if ed25519.Verify(pubkey, append(signeddata, wire...), sigbuf) { + return nil + } + return ErrSig + + default: + return ErrAlg + } +} + +// ValidityPeriod uses RFC1982 serial arithmetic to calculate +// if a signature period is valid. If t is the zero time, the +// current time is taken other t is. Returns true if the signature +// is valid at the given time, otherwise returns false. +func (rr *RRSIG) ValidityPeriod(t time.Time) bool { + var utc int64 + if t.IsZero() { + utc = time.Now().UTC().Unix() + } else { + utc = t.UTC().Unix() + } + modi := (int64(rr.Inception) - utc) / year68 + mode := (int64(rr.Expiration) - utc) / year68 + ti := int64(rr.Inception) + modi*year68 + te := int64(rr.Expiration) + mode*year68 + return ti <= utc && utc <= te +} + +// Return the signatures base64 encoding sigdata as a byte slice. +func (rr *RRSIG) sigBuf() []byte { + sigbuf, err := fromBase64([]byte(rr.Signature)) + if err != nil { + return nil + } + return sigbuf +} + +// publicKeyRSA returns the RSA public key from a DNSKEY record. +func (k *DNSKEY) publicKeyRSA() *rsa.PublicKey { + keybuf, err := fromBase64([]byte(k.PublicKey)) + if err != nil { + return nil + } + + if len(keybuf) < 1+1+64 { + // Exponent must be at least 1 byte and modulus at least 64 + return nil + } + + // RFC 2537/3110, section 2. RSA Public KEY Resource Records + // Length is in the 0th byte, unless its zero, then it + // it in bytes 1 and 2 and its a 16 bit number + explen := uint16(keybuf[0]) + keyoff := 1 + if explen == 0 { + explen = uint16(keybuf[1])<<8 | uint16(keybuf[2]) + keyoff = 3 + } + + if explen > 4 || explen == 0 || keybuf[keyoff] == 0 { + // Exponent larger than supported by the crypto package, + // empty, or contains prohibited leading zero. + return nil + } + + modoff := keyoff + int(explen) + modlen := len(keybuf) - modoff + if modlen < 64 || modlen > 512 || keybuf[modoff] == 0 { + // Modulus is too small, large, or contains prohibited leading zero. + return nil + } + + pubkey := new(rsa.PublicKey) + + var expo uint64 + // The exponent of length explen is between keyoff and modoff. + for _, v := range keybuf[keyoff:modoff] { + expo <<= 8 + expo |= uint64(v) + } + if expo > 1<<31-1 { + // Larger exponent than supported by the crypto package. + return nil + } + + pubkey.E = int(expo) + pubkey.N = new(big.Int).SetBytes(keybuf[modoff:]) + return pubkey +} + +// publicKeyECDSA returns the Curve public key from the DNSKEY record. +func (k *DNSKEY) publicKeyECDSA() *ecdsa.PublicKey { + keybuf, err := fromBase64([]byte(k.PublicKey)) + if err != nil { + return nil + } + pubkey := new(ecdsa.PublicKey) + switch k.Algorithm { + case ECDSAP256SHA256: + pubkey.Curve = elliptic.P256() + if len(keybuf) != 64 { + // wrongly encoded key + return nil + } + case ECDSAP384SHA384: + pubkey.Curve = elliptic.P384() + if len(keybuf) != 96 { + // Wrongly encoded key + return nil + } + } + pubkey.X = new(big.Int).SetBytes(keybuf[:len(keybuf)/2]) + pubkey.Y = new(big.Int).SetBytes(keybuf[len(keybuf)/2:]) + return pubkey +} + +func (k *DNSKEY) publicKeyED25519() ed25519.PublicKey { + keybuf, err := fromBase64([]byte(k.PublicKey)) + if err != nil { + return nil + } + if len(keybuf) != ed25519.PublicKeySize { + return nil + } + return keybuf +} + +type wireSlice [][]byte + +func (p wireSlice) Len() int { return len(p) } +func (p wireSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p wireSlice) Less(i, j int) bool { + _, ioff, _ := UnpackDomainName(p[i], 0) + _, joff, _ := UnpackDomainName(p[j], 0) + return bytes.Compare(p[i][ioff+10:], p[j][joff+10:]) < 0 +} + +// Return the raw signature data. +func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) { + wires := make(wireSlice, len(rrset)) + for i, r := range rrset { + r1 := r.copy() + h := r1.Header() + h.Ttl = s.OrigTtl + labels := SplitDomainName(h.Name) + // 6.2. Canonical RR Form. (4) - wildcards + if len(labels) > int(s.Labels) { + // Wildcard + h.Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "." + } + // RFC 4034: 6.2. Canonical RR Form. (2) - domain name to lowercase + h.Name = CanonicalName(h.Name) + // 6.2. Canonical RR Form. (3) - domain rdata to lowercase. + // NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR, + // HINFO, MINFO, MX, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX, + // SRV, DNAME, A6 + // + // RFC 6840 - Clarifications and Implementation Notes for DNS Security (DNSSEC): + // Section 6.2 of [RFC4034] also erroneously lists HINFO as a record + // that needs conversion to lowercase, and twice at that. Since HINFO + // records contain no domain names, they are not subject to case + // conversion. + switch x := r1.(type) { + case *NS: + x.Ns = CanonicalName(x.Ns) + case *MD: + x.Md = CanonicalName(x.Md) + case *MF: + x.Mf = CanonicalName(x.Mf) + case *CNAME: + x.Target = CanonicalName(x.Target) + case *SOA: + x.Ns = CanonicalName(x.Ns) + x.Mbox = CanonicalName(x.Mbox) + case *MB: + x.Mb = CanonicalName(x.Mb) + case *MG: + x.Mg = CanonicalName(x.Mg) + case *MR: + x.Mr = CanonicalName(x.Mr) + case *PTR: + x.Ptr = CanonicalName(x.Ptr) + case *MINFO: + x.Rmail = CanonicalName(x.Rmail) + x.Email = CanonicalName(x.Email) + case *MX: + x.Mx = CanonicalName(x.Mx) + case *RP: + x.Mbox = CanonicalName(x.Mbox) + x.Txt = CanonicalName(x.Txt) + case *AFSDB: + x.Hostname = CanonicalName(x.Hostname) + case *RT: + x.Host = CanonicalName(x.Host) + case *SIG: + x.SignerName = CanonicalName(x.SignerName) + case *PX: + x.Map822 = CanonicalName(x.Map822) + x.Mapx400 = CanonicalName(x.Mapx400) + case *NAPTR: + x.Replacement = CanonicalName(x.Replacement) + case *KX: + x.Exchanger = CanonicalName(x.Exchanger) + case *SRV: + x.Target = CanonicalName(x.Target) + case *DNAME: + x.Target = CanonicalName(x.Target) + } + // 6.2. Canonical RR Form. (5) - origTTL + wire := make([]byte, Len(r1)+1) // +1 to be safe(r) + off, err1 := PackRR(r1, wire, 0, nil, false) + if err1 != nil { + return nil, err1 + } + wire = wire[:off] + wires[i] = wire + } + sort.Sort(wires) + for i, wire := range wires { + if i > 0 && bytes.Equal(wire, wires[i-1]) { + continue + } + buf = append(buf, wire...) + } + return buf, nil +} + +func packSigWire(sw *rrsigWireFmt, msg []byte) (int, error) { + // copied from zmsg.go RRSIG packing + off, err := packUint16(sw.TypeCovered, msg, 0) + if err != nil { + return off, err + } + off, err = packUint8(sw.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(sw.Labels, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(sw.OrigTtl, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(sw.Expiration, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(sw.Inception, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(sw.KeyTag, msg, off) + if err != nil { + return off, err + } + off, err = PackDomainName(sw.SignerName, msg, off, nil, false) + if err != nil { + return off, err + } + return off, nil +} + +func packKeyWire(dw *dnskeyWireFmt, msg []byte) (int, error) { + // copied from zmsg.go DNSKEY packing + off, err := packUint16(dw.Flags, msg, 0) + if err != nil { + return off, err + } + off, err = packUint8(dw.Protocol, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(dw.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packStringBase64(dw.PublicKey, msg, off) + if err != nil { + return off, err + } + return off, nil +} diff --git a/vendor/github.com/miekg/dns/dnssec_keygen.go b/vendor/github.com/miekg/dns/dnssec_keygen.go new file mode 100644 index 0000000..b8124b5 --- /dev/null +++ b/vendor/github.com/miekg/dns/dnssec_keygen.go @@ -0,0 +1,139 @@ +package dns + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "math/big" +) + +// Generate generates a DNSKEY of the given bit size. +// The public part is put inside the DNSKEY record. +// The Algorithm in the key must be set as this will define +// what kind of DNSKEY will be generated. +// The ECDSA algorithms imply a fixed keysize, in that case +// bits should be set to the size of the algorithm. +func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) { + switch k.Algorithm { + case RSASHA1, RSASHA256, RSASHA1NSEC3SHA1: + if bits < 512 || bits > 4096 { + return nil, ErrKeySize + } + case RSASHA512: + if bits < 1024 || bits > 4096 { + return nil, ErrKeySize + } + case ECDSAP256SHA256: + if bits != 256 { + return nil, ErrKeySize + } + case ECDSAP384SHA384: + if bits != 384 { + return nil, ErrKeySize + } + case ED25519: + if bits != 256 { + return nil, ErrKeySize + } + default: + return nil, ErrAlg + } + + switch k.Algorithm { + case RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1: + priv, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return nil, err + } + k.setPublicKeyRSA(priv.PublicKey.E, priv.PublicKey.N) + return priv, nil + case ECDSAP256SHA256, ECDSAP384SHA384: + var c elliptic.Curve + switch k.Algorithm { + case ECDSAP256SHA256: + c = elliptic.P256() + case ECDSAP384SHA384: + c = elliptic.P384() + } + priv, err := ecdsa.GenerateKey(c, rand.Reader) + if err != nil { + return nil, err + } + k.setPublicKeyECDSA(priv.PublicKey.X, priv.PublicKey.Y) + return priv, nil + case ED25519: + pub, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + k.setPublicKeyED25519(pub) + return priv, nil + default: + return nil, ErrAlg + } +} + +// Set the public key (the value E and N) +func (k *DNSKEY) setPublicKeyRSA(_E int, _N *big.Int) bool { + if _E == 0 || _N == nil { + return false + } + buf := exponentToBuf(_E) + buf = append(buf, _N.Bytes()...) + k.PublicKey = toBase64(buf) + return true +} + +// Set the public key for Elliptic Curves +func (k *DNSKEY) setPublicKeyECDSA(_X, _Y *big.Int) bool { + if _X == nil || _Y == nil { + return false + } + var intlen int + switch k.Algorithm { + case ECDSAP256SHA256: + intlen = 32 + case ECDSAP384SHA384: + intlen = 48 + } + k.PublicKey = toBase64(curveToBuf(_X, _Y, intlen)) + return true +} + +// Set the public key for Ed25519 +func (k *DNSKEY) setPublicKeyED25519(_K ed25519.PublicKey) bool { + if _K == nil { + return false + } + k.PublicKey = toBase64(_K) + return true +} + +// Set the public key (the values E and N) for RSA +// RFC 3110: Section 2. RSA Public KEY Resource Records +func exponentToBuf(_E int) []byte { + var buf []byte + i := big.NewInt(int64(_E)).Bytes() + if len(i) < 256 { + buf = make([]byte, 1, 1+len(i)) + buf[0] = uint8(len(i)) + } else { + buf = make([]byte, 3, 3+len(i)) + buf[0] = 0 + buf[1] = uint8(len(i) >> 8) + buf[2] = uint8(len(i)) + } + buf = append(buf, i...) + return buf +} + +// Set the public key for X and Y for Curve. The two +// values are just concatenated. +func curveToBuf(_X, _Y *big.Int, intlen int) []byte { + buf := intToBytes(_X, intlen) + buf = append(buf, intToBytes(_Y, intlen)...) + return buf +} diff --git a/vendor/github.com/miekg/dns/dnssec_keyscan.go b/vendor/github.com/miekg/dns/dnssec_keyscan.go new file mode 100644 index 0000000..f796581 --- /dev/null +++ b/vendor/github.com/miekg/dns/dnssec_keyscan.go @@ -0,0 +1,309 @@ +package dns + +import ( + "bufio" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "io" + "math/big" + "strconv" + "strings" +) + +// NewPrivateKey returns a PrivateKey by parsing the string s. +// s should be in the same form of the BIND private key files. +func (k *DNSKEY) NewPrivateKey(s string) (crypto.PrivateKey, error) { + if s == "" || s[len(s)-1] != '\n' { // We need a closing newline + return k.ReadPrivateKey(strings.NewReader(s+"\n"), "") + } + return k.ReadPrivateKey(strings.NewReader(s), "") +} + +// ReadPrivateKey reads a private key from the io.Reader q. The string file is +// only used in error reporting. +// The public key must be known, because some cryptographic algorithms embed +// the public inside the privatekey. +func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, error) { + m, err := parseKey(q, file) + if m == nil { + return nil, err + } + if _, ok := m["private-key-format"]; !ok { + return nil, ErrPrivKey + } + if m["private-key-format"] != "v1.2" && m["private-key-format"] != "v1.3" { + return nil, ErrPrivKey + } + // TODO(mg): check if the pubkey matches the private key + algo, err := strconv.ParseUint(strings.SplitN(m["algorithm"], " ", 2)[0], 10, 8) + if err != nil { + return nil, ErrPrivKey + } + switch uint8(algo) { + case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512: + priv, err := readPrivateKeyRSA(m) + if err != nil { + return nil, err + } + pub := k.publicKeyRSA() + if pub == nil { + return nil, ErrKey + } + priv.PublicKey = *pub + return priv, nil + case ECDSAP256SHA256, ECDSAP384SHA384: + priv, err := readPrivateKeyECDSA(m) + if err != nil { + return nil, err + } + pub := k.publicKeyECDSA() + if pub == nil { + return nil, ErrKey + } + priv.PublicKey = *pub + return priv, nil + case ED25519: + return readPrivateKeyED25519(m) + default: + return nil, ErrAlg + } +} + +// Read a private key (file) string and create a public key. Return the private key. +func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) { + p := new(rsa.PrivateKey) + p.Primes = []*big.Int{nil, nil} + for k, v := range m { + switch k { + case "modulus", "publicexponent", "privateexponent", "prime1", "prime2": + v1, err := fromBase64([]byte(v)) + if err != nil { + return nil, err + } + switch k { + case "modulus": + p.PublicKey.N = new(big.Int).SetBytes(v1) + case "publicexponent": + i := new(big.Int).SetBytes(v1) + p.PublicKey.E = int(i.Int64()) // int64 should be large enough + case "privateexponent": + p.D = new(big.Int).SetBytes(v1) + case "prime1": + p.Primes[0] = new(big.Int).SetBytes(v1) + case "prime2": + p.Primes[1] = new(big.Int).SetBytes(v1) + } + case "exponent1", "exponent2", "coefficient": + // not used in Go (yet) + case "created", "publish", "activate": + // not used in Go (yet) + } + } + return p, nil +} + +func readPrivateKeyECDSA(m map[string]string) (*ecdsa.PrivateKey, error) { + p := new(ecdsa.PrivateKey) + p.D = new(big.Int) + // TODO: validate that the required flags are present + for k, v := range m { + switch k { + case "privatekey": + v1, err := fromBase64([]byte(v)) + if err != nil { + return nil, err + } + p.D.SetBytes(v1) + case "created", "publish", "activate": + /* not used in Go (yet) */ + } + } + return p, nil +} + +func readPrivateKeyED25519(m map[string]string) (ed25519.PrivateKey, error) { + var p ed25519.PrivateKey + // TODO: validate that the required flags are present + for k, v := range m { + switch k { + case "privatekey": + p1, err := fromBase64([]byte(v)) + if err != nil { + return nil, err + } + if len(p1) != ed25519.SeedSize { + return nil, ErrPrivKey + } + p = ed25519.NewKeyFromSeed(p1) + case "created", "publish", "activate": + /* not used in Go (yet) */ + } + } + return p, nil +} + +// parseKey reads a private key from r. It returns a map[string]string, +// with the key-value pairs, or an error when the file is not correct. +func parseKey(r io.Reader, file string) (map[string]string, error) { + m := make(map[string]string) + var k string + + c := newKLexer(r) + + for l, ok := c.Next(); ok; l, ok = c.Next() { + // It should alternate + switch l.value { + case zKey: + k = l.token + case zValue: + if k == "" { + return nil, &ParseError{file, "no private key seen", l} + } + + m[strings.ToLower(k)] = l.token + k = "" + } + } + + // Surface any read errors from r. + if err := c.Err(); err != nil { + return nil, &ParseError{file: file, err: err.Error()} + } + + return m, nil +} + +type klexer struct { + br io.ByteReader + + readErr error + + line int + column int + + key bool + + eol bool // end-of-line +} + +func newKLexer(r io.Reader) *klexer { + br, ok := r.(io.ByteReader) + if !ok { + br = bufio.NewReaderSize(r, 1024) + } + + return &klexer{ + br: br, + + line: 1, + + key: true, + } +} + +func (kl *klexer) Err() error { + if kl.readErr == io.EOF { + return nil + } + + return kl.readErr +} + +// readByte returns the next byte from the input +func (kl *klexer) readByte() (byte, bool) { + if kl.readErr != nil { + return 0, false + } + + c, err := kl.br.ReadByte() + if err != nil { + kl.readErr = err + return 0, false + } + + // delay the newline handling until the next token is delivered, + // fixes off-by-one errors when reporting a parse error. + if kl.eol { + kl.line++ + kl.column = 0 + kl.eol = false + } + + if c == '\n' { + kl.eol = true + } else { + kl.column++ + } + + return c, true +} + +func (kl *klexer) Next() (lex, bool) { + var ( + l lex + + str strings.Builder + + commt bool + ) + + for x, ok := kl.readByte(); ok; x, ok = kl.readByte() { + l.line, l.column = kl.line, kl.column + + switch x { + case ':': + if commt || !kl.key { + break + } + + kl.key = false + + // Next token is a space, eat it + kl.readByte() + + l.value = zKey + l.token = str.String() + return l, true + case ';': + commt = true + case '\n': + if commt { + // Reset a comment + commt = false + } + + if kl.key && str.Len() == 0 { + // ignore empty lines + break + } + + kl.key = true + + l.value = zValue + l.token = str.String() + return l, true + default: + if commt { + break + } + + str.WriteByte(x) + } + } + + if kl.readErr != nil && kl.readErr != io.EOF { + // Don't return any tokens after a read error occurs. + return lex{value: zEOF}, false + } + + if str.Len() > 0 { + // Send remainder + l.value = zValue + l.token = str.String() + return l, true + } + + return lex{value: zEOF}, false +} diff --git a/vendor/github.com/miekg/dns/dnssec_privkey.go b/vendor/github.com/miekg/dns/dnssec_privkey.go new file mode 100644 index 0000000..f160772 --- /dev/null +++ b/vendor/github.com/miekg/dns/dnssec_privkey.go @@ -0,0 +1,77 @@ +package dns + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "math/big" + "strconv" +) + +const format = "Private-key-format: v1.3\n" + +var bigIntOne = big.NewInt(1) + +// PrivateKeyString converts a PrivateKey to a string. This string has the same +// format as the private-key-file of BIND9 (Private-key-format: v1.3). +// It needs some info from the key (the algorithm), so its a method of the DNSKEY. +// It supports *rsa.PrivateKey, *ecdsa.PrivateKey and ed25519.PrivateKey. +func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string { + algorithm := strconv.Itoa(int(r.Algorithm)) + algorithm += " (" + AlgorithmToString[r.Algorithm] + ")" + + switch p := p.(type) { + case *rsa.PrivateKey: + modulus := toBase64(p.PublicKey.N.Bytes()) + e := big.NewInt(int64(p.PublicKey.E)) + publicExponent := toBase64(e.Bytes()) + privateExponent := toBase64(p.D.Bytes()) + prime1 := toBase64(p.Primes[0].Bytes()) + prime2 := toBase64(p.Primes[1].Bytes()) + // Calculate Exponent1/2 and Coefficient as per: http://en.wikipedia.org/wiki/RSA#Using_the_Chinese_remainder_algorithm + // and from: http://code.google.com/p/go/issues/detail?id=987 + p1 := new(big.Int).Sub(p.Primes[0], bigIntOne) + q1 := new(big.Int).Sub(p.Primes[1], bigIntOne) + exp1 := new(big.Int).Mod(p.D, p1) + exp2 := new(big.Int).Mod(p.D, q1) + coeff := new(big.Int).ModInverse(p.Primes[1], p.Primes[0]) + + exponent1 := toBase64(exp1.Bytes()) + exponent2 := toBase64(exp2.Bytes()) + coefficient := toBase64(coeff.Bytes()) + + return format + + "Algorithm: " + algorithm + "\n" + + "Modulus: " + modulus + "\n" + + "PublicExponent: " + publicExponent + "\n" + + "PrivateExponent: " + privateExponent + "\n" + + "Prime1: " + prime1 + "\n" + + "Prime2: " + prime2 + "\n" + + "Exponent1: " + exponent1 + "\n" + + "Exponent2: " + exponent2 + "\n" + + "Coefficient: " + coefficient + "\n" + + case *ecdsa.PrivateKey: + var intlen int + switch r.Algorithm { + case ECDSAP256SHA256: + intlen = 32 + case ECDSAP384SHA384: + intlen = 48 + } + private := toBase64(intToBytes(p.D, intlen)) + return format + + "Algorithm: " + algorithm + "\n" + + "PrivateKey: " + private + "\n" + + case ed25519.PrivateKey: + private := toBase64(p.Seed()) + return format + + "Algorithm: " + algorithm + "\n" + + "PrivateKey: " + private + "\n" + + default: + return "" + } +} diff --git a/vendor/github.com/miekg/dns/doc.go b/vendor/github.com/miekg/dns/doc.go new file mode 100644 index 0000000..f7629ec --- /dev/null +++ b/vendor/github.com/miekg/dns/doc.go @@ -0,0 +1,292 @@ +/* +Package dns implements a full featured interface to the Domain Name System. +Both server- and client-side programming is supported. The package allows +complete control over what is sent out to the DNS. The API follows the +less-is-more principle, by presenting a small, clean interface. + +It supports (asynchronous) querying/replying, incoming/outgoing zone transfers, +TSIG, EDNS0, dynamic updates, notifies and DNSSEC validation/signing. + +Note that domain names MUST be fully qualified before sending them, unqualified +names in a message will result in a packing failure. + +Resource records are native types. They are not stored in wire format. Basic +usage pattern for creating a new resource record: + + r := new(dns.MX) + r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 3600} + r.Preference = 10 + r.Mx = "mx.miek.nl." + +Or directly from a string: + + mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.") + +Or when the default origin (.) and TTL (3600) and class (IN) suit you: + + mx, err := dns.NewRR("miek.nl MX 10 mx.miek.nl") + +Or even: + + mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek") + +In the DNS messages are exchanged, these messages contain resource records +(sets). Use pattern for creating a message: + + m := new(dns.Msg) + m.SetQuestion("miek.nl.", dns.TypeMX) + +Or when not certain if the domain name is fully qualified: + + m.SetQuestion(dns.Fqdn("miek.nl"), dns.TypeMX) + +The message m is now a message with the question section set to ask the MX +records for the miek.nl. zone. + +The following is slightly more verbose, but more flexible: + + m1 := new(dns.Msg) + m1.Id = dns.Id() + m1.RecursionDesired = true + m1.Question = make([]dns.Question, 1) + m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET} + +After creating a message it can be sent. Basic use pattern for synchronous +querying the DNS at a server configured on 127.0.0.1 and port 53: + + c := new(dns.Client) + in, rtt, err := c.Exchange(m1, "127.0.0.1:53") + +Suppressing multiple outstanding queries (with the same question, type and +class) is as easy as setting: + + c.SingleInflight = true + +More advanced options are available using a net.Dialer and the corresponding API. +For example it is possible to set a timeout, or to specify a source IP address +and port to use for the connection: + + c := new(dns.Client) + laddr := net.UDPAddr{ + IP: net.ParseIP("[::1]"), + Port: 12345, + Zone: "", + } + c.Dialer := &net.Dialer{ + Timeout: 200 * time.Millisecond, + LocalAddr: &laddr, + } + in, rtt, err := c.Exchange(m1, "8.8.8.8:53") + +If these "advanced" features are not needed, a simple UDP query can be sent, +with: + + in, err := dns.Exchange(m1, "127.0.0.1:53") + +When this functions returns you will get DNS message. A DNS message consists +out of four sections. +The question section: in.Question, the answer section: in.Answer, +the authority section: in.Ns and the additional section: in.Extra. + +Each of these sections (except the Question section) contain a []RR. Basic +use pattern for accessing the rdata of a TXT RR as the first RR in +the Answer section: + + if t, ok := in.Answer[0].(*dns.TXT); ok { + // do something with t.Txt + } + +Domain Name and TXT Character String Representations + +Both domain names and TXT character strings are converted to presentation form +both when unpacked and when converted to strings. + +For TXT character strings, tabs, carriage returns and line feeds will be +converted to \t, \r and \n respectively. Back slashes and quotations marks will +be escaped. Bytes below 32 and above 127 will be converted to \DDD form. + +For domain names, in addition to the above rules brackets, periods, spaces, +semicolons and the at symbol are escaped. + +DNSSEC + +DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It uses +public key cryptography to sign resource records. The public keys are stored in +DNSKEY records and the signatures in RRSIG records. + +Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK) +bit to a request. + + m := new(dns.Msg) + m.SetEdns0(4096, true) + +Signature generation, signature verification and key generation are all supported. + +DYNAMIC UPDATES + +Dynamic updates reuses the DNS message format, but renames three of the +sections. Question is Zone, Answer is Prerequisite, Authority is Update, only +the Additional is not renamed. See RFC 2136 for the gory details. + +You can set a rather complex set of rules for the existence of absence of +certain resource records or names in a zone to specify if resource records +should be added or removed. The table from RFC 2136 supplemented with the Go +DNS function shows which functions exist to specify the prerequisites. + + 3.2.4 - Table Of Metavalues Used In Prerequisite Section + + CLASS TYPE RDATA Meaning Function + -------------------------------------------------------------- + ANY ANY empty Name is in use dns.NameUsed + ANY rrset empty RRset exists (value indep) dns.RRsetUsed + NONE ANY empty Name is not in use dns.NameNotUsed + NONE rrset empty RRset does not exist dns.RRsetNotUsed + zone rrset rr RRset exists (value dep) dns.Used + +The prerequisite section can also be left empty. If you have decided on the +prerequisites you can tell what RRs should be added or deleted. The next table +shows the options you have and what functions to call. + + 3.4.2.6 - Table Of Metavalues Used In Update Section + + CLASS TYPE RDATA Meaning Function + --------------------------------------------------------------- + ANY ANY empty Delete all RRsets from name dns.RemoveName + ANY rrset empty Delete an RRset dns.RemoveRRset + NONE rrset rr Delete an RR from RRset dns.Remove + zone rrset rr Add to an RRset dns.Insert + +TRANSACTION SIGNATURE + +An TSIG or transaction signature adds a HMAC TSIG record to each message sent. +The supported algorithms include: HmacMD5, HmacSHA1, HmacSHA256 and HmacSHA512. + +Basic use pattern when querying with a TSIG name "axfr." (note that these key names +must be fully qualified - as they are domain names) and the base64 secret +"so6ZGir4GPAqINNh9U5c3A==": + +If an incoming message contains a TSIG record it MUST be the last record in +the additional section (RFC2845 3.2). This means that you should make the +call to SetTsig last, right before executing the query. If you make any +changes to the RRset after calling SetTsig() the signature will be incorrect. + + c := new(dns.Client) + c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} + m := new(dns.Msg) + m.SetQuestion("miek.nl.", dns.TypeMX) + m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) + ... + // When sending the TSIG RR is calculated and filled in before sending + +When requesting an zone transfer (almost all TSIG usage is when requesting zone +transfers), with TSIG, this is the basic use pattern. In this example we +request an AXFR for miek.nl. with TSIG key named "axfr." and secret +"so6ZGir4GPAqINNh9U5c3A==" and using the server 176.58.119.54: + + t := new(dns.Transfer) + m := new(dns.Msg) + t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} + m.SetAxfr("miek.nl.") + m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) + c, err := t.In(m, "176.58.119.54:53") + for r := range c { ... } + +You can now read the records from the transfer as they come in. Each envelope +is checked with TSIG. If something is not correct an error is returned. + +A custom TSIG implementation can be used. This requires additional code to +perform any session establishment and signature generation/verification. The +client must be configured with an implementation of the TsigProvider interface: + + type Provider struct{} + + func (*Provider) Generate(msg []byte, tsig *dns.TSIG) ([]byte, error) { + // Use tsig.Hdr.Name and tsig.Algorithm in your code to + // generate the MAC using msg as the payload. + } + + func (*Provider) Verify(msg []byte, tsig *dns.TSIG) error { + // Use tsig.Hdr.Name and tsig.Algorithm in your code to verify + // that msg matches the value in tsig.MAC. + } + + c := new(dns.Client) + c.TsigProvider = new(Provider) + m := new(dns.Msg) + m.SetQuestion("miek.nl.", dns.TypeMX) + m.SetTsig(keyname, dns.HmacSHA1, 300, time.Now().Unix()) + ... + // TSIG RR is calculated by calling your Generate method + +Basic use pattern validating and replying to a message that has TSIG set. + + server := &dns.Server{Addr: ":53", Net: "udp"} + server.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} + go server.ListenAndServe() + dns.HandleFunc(".", handleRequest) + + func handleRequest(w dns.ResponseWriter, r *dns.Msg) { + m := new(dns.Msg) + m.SetReply(r) + if r.IsTsig() != nil { + if w.TsigStatus() == nil { + // *Msg r has an TSIG record and it was validated + m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix()) + } else { + // *Msg r has an TSIG records and it was not validated + } + } + w.WriteMsg(m) + } + +PRIVATE RRS + +RFC 6895 sets aside a range of type codes for private use. This range is 65,280 +- 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these +can be used, before requesting an official type code from IANA. + +See https://miek.nl/2014/september/21/idn-and-private-rr-in-go-dns/ for more +information. + +EDNS0 + +EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated by +RFC 6891. It defines an new RR type, the OPT RR, which is then completely +abused. + +Basic use pattern for creating an (empty) OPT RR: + + o := new(dns.OPT) + o.Hdr.Name = "." // MUST be the root zone, per definition. + o.Hdr.Rrtype = dns.TypeOPT + +The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891) interfaces. +Currently only a few have been standardized: EDNS0_NSID (RFC 5001) and +EDNS0_SUBNET (RFC 7871). Note that these options may be combined in an OPT RR. +Basic use pattern for a server to check if (and which) options are set: + + // o is a dns.OPT + for _, s := range o.Option { + switch e := s.(type) { + case *dns.EDNS0_NSID: + // do stuff with e.Nsid + case *dns.EDNS0_SUBNET: + // access e.Family, e.Address, etc. + } + } + +SIG(0) + +From RFC 2931: + + SIG(0) provides protection for DNS transactions and requests .... + ... protection for glue records, DNS requests, protection for message headers + on requests and responses, and protection of the overall integrity of a response. + +It works like TSIG, except that SIG(0) uses public key cryptography, instead of +the shared secret approach in TSIG. Supported algorithms: ECDSAP256SHA256, +ECDSAP384SHA384, RSASHA1, RSASHA256 and RSASHA512. + +Signing subsequent messages in multi-message sessions is not implemented. +*/ +package dns diff --git a/vendor/github.com/miekg/dns/duplicate.go b/vendor/github.com/miekg/dns/duplicate.go new file mode 100644 index 0000000..d21ae1c --- /dev/null +++ b/vendor/github.com/miekg/dns/duplicate.go @@ -0,0 +1,37 @@ +package dns + +//go:generate go run duplicate_generate.go + +// IsDuplicate checks of r1 and r2 are duplicates of each other, excluding the TTL. +// So this means the header data is equal *and* the RDATA is the same. Returns true +// if so, otherwise false. It's a protocol violation to have identical RRs in a message. +func IsDuplicate(r1, r2 RR) bool { + // Check whether the record header is identical. + if !r1.Header().isDuplicate(r2.Header()) { + return false + } + + // Check whether the RDATA is identical. + return r1.isDuplicate(r2) +} + +func (r1 *RR_Header) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*RR_Header) + if !ok { + return false + } + if r1.Class != r2.Class { + return false + } + if r1.Rrtype != r2.Rrtype { + return false + } + if !isDuplicateName(r1.Name, r2.Name) { + return false + } + // ignore TTL + return true +} + +// isDuplicateName checks if the domain names s1 and s2 are equal. +func isDuplicateName(s1, s2 string) bool { return equal(s1, s2) } diff --git a/vendor/github.com/miekg/dns/edns.go b/vendor/github.com/miekg/dns/edns.go new file mode 100644 index 0000000..1a87f4c --- /dev/null +++ b/vendor/github.com/miekg/dns/edns.go @@ -0,0 +1,675 @@ +package dns + +import ( + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "net" + "strconv" +) + +// EDNS0 Option codes. +const ( + EDNS0LLQ = 0x1 // long lived queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01 + EDNS0UL = 0x2 // update lease draft: http://files.dns-sd.org/draft-sekar-dns-ul.txt + EDNS0NSID = 0x3 // nsid (See RFC 5001) + EDNS0DAU = 0x5 // DNSSEC Algorithm Understood + EDNS0DHU = 0x6 // DS Hash Understood + EDNS0N3U = 0x7 // NSEC3 Hash Understood + EDNS0SUBNET = 0x8 // client-subnet (See RFC 7871) + EDNS0EXPIRE = 0x9 // EDNS0 expire + EDNS0COOKIE = 0xa // EDNS0 Cookie + EDNS0TCPKEEPALIVE = 0xb // EDNS0 tcp keep alive (See RFC 7828) + EDNS0PADDING = 0xc // EDNS0 padding (See RFC 7830) + EDNS0LOCALSTART = 0xFDE9 // Beginning of range reserved for local/experimental use (See RFC 6891) + EDNS0LOCALEND = 0xFFFE // End of range reserved for local/experimental use (See RFC 6891) + _DO = 1 << 15 // DNSSEC OK +) + +// OPT is the EDNS0 RR appended to messages to convey extra (meta) information. +// See RFC 6891. +type OPT struct { + Hdr RR_Header + Option []EDNS0 `dns:"opt"` +} + +func (rr *OPT) String() string { + s := "\n;; OPT PSEUDOSECTION:\n; EDNS: version " + strconv.Itoa(int(rr.Version())) + "; " + if rr.Do() { + s += "flags: do; " + } else { + s += "flags: ; " + } + s += "udp: " + strconv.Itoa(int(rr.UDPSize())) + + for _, o := range rr.Option { + switch o.(type) { + case *EDNS0_NSID: + s += "\n; NSID: " + o.String() + h, e := o.pack() + var r string + if e == nil { + for _, c := range h { + r += "(" + string(c) + ")" + } + s += " " + r + } + case *EDNS0_SUBNET: + s += "\n; SUBNET: " + o.String() + case *EDNS0_COOKIE: + s += "\n; COOKIE: " + o.String() + case *EDNS0_UL: + s += "\n; UPDATE LEASE: " + o.String() + case *EDNS0_LLQ: + s += "\n; LONG LIVED QUERIES: " + o.String() + case *EDNS0_DAU: + s += "\n; DNSSEC ALGORITHM UNDERSTOOD: " + o.String() + case *EDNS0_DHU: + s += "\n; DS HASH UNDERSTOOD: " + o.String() + case *EDNS0_N3U: + s += "\n; NSEC3 HASH UNDERSTOOD: " + o.String() + case *EDNS0_LOCAL: + s += "\n; LOCAL OPT: " + o.String() + case *EDNS0_PADDING: + s += "\n; PADDING: " + o.String() + } + } + return s +} + +func (rr *OPT) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + for _, o := range rr.Option { + l += 4 // Account for 2-byte option code and 2-byte option length. + lo, _ := o.pack() + l += len(lo) + } + return l +} + +func (*OPT) parse(c *zlexer, origin string) *ParseError { + return &ParseError{err: "OPT records do not have a presentation format"} +} + +func (r1 *OPT) isDuplicate(r2 RR) bool { return false } + +// return the old value -> delete SetVersion? + +// Version returns the EDNS version used. Only zero is defined. +func (rr *OPT) Version() uint8 { + return uint8(rr.Hdr.Ttl & 0x00FF0000 >> 16) +} + +// SetVersion sets the version of EDNS. This is usually zero. +func (rr *OPT) SetVersion(v uint8) { + rr.Hdr.Ttl = rr.Hdr.Ttl&0xFF00FFFF | uint32(v)<<16 +} + +// ExtendedRcode returns the EDNS extended RCODE field (the upper 8 bits of the TTL). +func (rr *OPT) ExtendedRcode() int { + return int(rr.Hdr.Ttl&0xFF000000>>24) << 4 +} + +// SetExtendedRcode sets the EDNS extended RCODE field. +// +// If the RCODE is not an extended RCODE, will reset the extended RCODE field to 0. +func (rr *OPT) SetExtendedRcode(v uint16) { + rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | uint32(v>>4)<<24 +} + +// UDPSize returns the UDP buffer size. +func (rr *OPT) UDPSize() uint16 { + return rr.Hdr.Class +} + +// SetUDPSize sets the UDP buffer size. +func (rr *OPT) SetUDPSize(size uint16) { + rr.Hdr.Class = size +} + +// Do returns the value of the DO (DNSSEC OK) bit. +func (rr *OPT) Do() bool { + return rr.Hdr.Ttl&_DO == _DO +} + +// SetDo sets the DO (DNSSEC OK) bit. +// If we pass an argument, set the DO bit to that value. +// It is possible to pass 2 or more arguments. Any arguments after the 1st is silently ignored. +func (rr *OPT) SetDo(do ...bool) { + if len(do) == 1 { + if do[0] { + rr.Hdr.Ttl |= _DO + } else { + rr.Hdr.Ttl &^= _DO + } + } else { + rr.Hdr.Ttl |= _DO + } +} + +// EDNS0 defines an EDNS0 Option. An OPT RR can have multiple options appended to it. +type EDNS0 interface { + // Option returns the option code for the option. + Option() uint16 + // pack returns the bytes of the option data. + pack() ([]byte, error) + // unpack sets the data as found in the buffer. Is also sets + // the length of the slice as the length of the option data. + unpack([]byte) error + // String returns the string representation of the option. + String() string + // copy returns a deep-copy of the option. + copy() EDNS0 +} + +// EDNS0_NSID option is used to retrieve a nameserver +// identifier. When sending a request Nsid must be set to the empty string +// The identifier is an opaque string encoded as hex. +// Basic use pattern for creating an nsid option: +// +// o := new(dns.OPT) +// o.Hdr.Name = "." +// o.Hdr.Rrtype = dns.TypeOPT +// e := new(dns.EDNS0_NSID) +// e.Code = dns.EDNS0NSID +// e.Nsid = "AA" +// o.Option = append(o.Option, e) +type EDNS0_NSID struct { + Code uint16 // Always EDNS0NSID + Nsid string // This string needs to be hex encoded +} + +func (e *EDNS0_NSID) pack() ([]byte, error) { + h, err := hex.DecodeString(e.Nsid) + if err != nil { + return nil, err + } + return h, nil +} + +// Option implements the EDNS0 interface. +func (e *EDNS0_NSID) Option() uint16 { return EDNS0NSID } // Option returns the option code. +func (e *EDNS0_NSID) unpack(b []byte) error { e.Nsid = hex.EncodeToString(b); return nil } +func (e *EDNS0_NSID) String() string { return e.Nsid } +func (e *EDNS0_NSID) copy() EDNS0 { return &EDNS0_NSID{e.Code, e.Nsid} } + +// EDNS0_SUBNET is the subnet option that is used to give the remote nameserver +// an idea of where the client lives. See RFC 7871. It can then give back a different +// answer depending on the location or network topology. +// Basic use pattern for creating an subnet option: +// +// o := new(dns.OPT) +// o.Hdr.Name = "." +// o.Hdr.Rrtype = dns.TypeOPT +// e := new(dns.EDNS0_SUBNET) +// e.Code = dns.EDNS0SUBNET +// e.Family = 1 // 1 for IPv4 source address, 2 for IPv6 +// e.SourceNetmask = 32 // 32 for IPV4, 128 for IPv6 +// e.SourceScope = 0 +// e.Address = net.ParseIP("127.0.0.1").To4() // for IPv4 +// // e.Address = net.ParseIP("2001:7b8:32a::2") // for IPV6 +// o.Option = append(o.Option, e) +// +// This code will parse all the available bits when unpacking (up to optlen). +// When packing it will apply SourceNetmask. If you need more advanced logic, +// patches welcome and good luck. +type EDNS0_SUBNET struct { + Code uint16 // Always EDNS0SUBNET + Family uint16 // 1 for IP, 2 for IP6 + SourceNetmask uint8 + SourceScope uint8 + Address net.IP +} + +// Option implements the EDNS0 interface. +func (e *EDNS0_SUBNET) Option() uint16 { return EDNS0SUBNET } + +func (e *EDNS0_SUBNET) pack() ([]byte, error) { + b := make([]byte, 4) + binary.BigEndian.PutUint16(b[0:], e.Family) + b[2] = e.SourceNetmask + b[3] = e.SourceScope + switch e.Family { + case 0: + // "dig" sets AddressFamily to 0 if SourceNetmask is also 0 + // We might don't need to complain either + if e.SourceNetmask != 0 { + return nil, errors.New("dns: bad address family") + } + case 1: + if e.SourceNetmask > net.IPv4len*8 { + return nil, errors.New("dns: bad netmask") + } + if len(e.Address.To4()) != net.IPv4len { + return nil, errors.New("dns: bad address") + } + ip := e.Address.To4().Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv4len*8)) + needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up + b = append(b, ip[:needLength]...) + case 2: + if e.SourceNetmask > net.IPv6len*8 { + return nil, errors.New("dns: bad netmask") + } + if len(e.Address) != net.IPv6len { + return nil, errors.New("dns: bad address") + } + ip := e.Address.Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv6len*8)) + needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up + b = append(b, ip[:needLength]...) + default: + return nil, errors.New("dns: bad address family") + } + return b, nil +} + +func (e *EDNS0_SUBNET) unpack(b []byte) error { + if len(b) < 4 { + return ErrBuf + } + e.Family = binary.BigEndian.Uint16(b) + e.SourceNetmask = b[2] + e.SourceScope = b[3] + switch e.Family { + case 0: + // "dig" sets AddressFamily to 0 if SourceNetmask is also 0 + // It's okay to accept such a packet + if e.SourceNetmask != 0 { + return errors.New("dns: bad address family") + } + e.Address = net.IPv4(0, 0, 0, 0) + case 1: + if e.SourceNetmask > net.IPv4len*8 || e.SourceScope > net.IPv4len*8 { + return errors.New("dns: bad netmask") + } + addr := make(net.IP, net.IPv4len) + copy(addr, b[4:]) + e.Address = addr.To16() + case 2: + if e.SourceNetmask > net.IPv6len*8 || e.SourceScope > net.IPv6len*8 { + return errors.New("dns: bad netmask") + } + addr := make(net.IP, net.IPv6len) + copy(addr, b[4:]) + e.Address = addr + default: + return errors.New("dns: bad address family") + } + return nil +} + +func (e *EDNS0_SUBNET) String() (s string) { + if e.Address == nil { + s = "" + } else if e.Address.To4() != nil { + s = e.Address.String() + } else { + s = "[" + e.Address.String() + "]" + } + s += "/" + strconv.Itoa(int(e.SourceNetmask)) + "/" + strconv.Itoa(int(e.SourceScope)) + return +} + +func (e *EDNS0_SUBNET) copy() EDNS0 { + return &EDNS0_SUBNET{ + e.Code, + e.Family, + e.SourceNetmask, + e.SourceScope, + e.Address, + } +} + +// The EDNS0_COOKIE option is used to add a DNS Cookie to a message. +// +// o := new(dns.OPT) +// o.Hdr.Name = "." +// o.Hdr.Rrtype = dns.TypeOPT +// e := new(dns.EDNS0_COOKIE) +// e.Code = dns.EDNS0COOKIE +// e.Cookie = "24a5ac.." +// o.Option = append(o.Option, e) +// +// The Cookie field consists out of a client cookie (RFC 7873 Section 4), that is +// always 8 bytes. It may then optionally be followed by the server cookie. The server +// cookie is of variable length, 8 to a maximum of 32 bytes. In other words: +// +// cCookie := o.Cookie[:16] +// sCookie := o.Cookie[16:] +// +// There is no guarantee that the Cookie string has a specific length. +type EDNS0_COOKIE struct { + Code uint16 // Always EDNS0COOKIE + Cookie string // Hex-encoded cookie data +} + +func (e *EDNS0_COOKIE) pack() ([]byte, error) { + h, err := hex.DecodeString(e.Cookie) + if err != nil { + return nil, err + } + return h, nil +} + +// Option implements the EDNS0 interface. +func (e *EDNS0_COOKIE) Option() uint16 { return EDNS0COOKIE } +func (e *EDNS0_COOKIE) unpack(b []byte) error { e.Cookie = hex.EncodeToString(b); return nil } +func (e *EDNS0_COOKIE) String() string { return e.Cookie } +func (e *EDNS0_COOKIE) copy() EDNS0 { return &EDNS0_COOKIE{e.Code, e.Cookie} } + +// The EDNS0_UL (Update Lease) (draft RFC) option is used to tell the server to set +// an expiration on an update RR. This is helpful for clients that cannot clean +// up after themselves. This is a draft RFC and more information can be found at +// https://tools.ietf.org/html/draft-sekar-dns-ul-02 +// +// o := new(dns.OPT) +// o.Hdr.Name = "." +// o.Hdr.Rrtype = dns.TypeOPT +// e := new(dns.EDNS0_UL) +// e.Code = dns.EDNS0UL +// e.Lease = 120 // in seconds +// o.Option = append(o.Option, e) +type EDNS0_UL struct { + Code uint16 // Always EDNS0UL + Lease uint32 + KeyLease uint32 +} + +// Option implements the EDNS0 interface. +func (e *EDNS0_UL) Option() uint16 { return EDNS0UL } +func (e *EDNS0_UL) String() string { return fmt.Sprintf("%d %d", e.Lease, e.KeyLease) } +func (e *EDNS0_UL) copy() EDNS0 { return &EDNS0_UL{e.Code, e.Lease, e.KeyLease} } + +// Copied: http://golang.org/src/pkg/net/dnsmsg.go +func (e *EDNS0_UL) pack() ([]byte, error) { + var b []byte + if e.KeyLease == 0 { + b = make([]byte, 4) + } else { + b = make([]byte, 8) + binary.BigEndian.PutUint32(b[4:], e.KeyLease) + } + binary.BigEndian.PutUint32(b, e.Lease) + return b, nil +} + +func (e *EDNS0_UL) unpack(b []byte) error { + switch len(b) { + case 4: + e.KeyLease = 0 + case 8: + e.KeyLease = binary.BigEndian.Uint32(b[4:]) + default: + return ErrBuf + } + e.Lease = binary.BigEndian.Uint32(b) + return nil +} + +// EDNS0_LLQ stands for Long Lived Queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01 +// Implemented for completeness, as the EDNS0 type code is assigned. +type EDNS0_LLQ struct { + Code uint16 // Always EDNS0LLQ + Version uint16 + Opcode uint16 + Error uint16 + Id uint64 + LeaseLife uint32 +} + +// Option implements the EDNS0 interface. +func (e *EDNS0_LLQ) Option() uint16 { return EDNS0LLQ } + +func (e *EDNS0_LLQ) pack() ([]byte, error) { + b := make([]byte, 18) + binary.BigEndian.PutUint16(b[0:], e.Version) + binary.BigEndian.PutUint16(b[2:], e.Opcode) + binary.BigEndian.PutUint16(b[4:], e.Error) + binary.BigEndian.PutUint64(b[6:], e.Id) + binary.BigEndian.PutUint32(b[14:], e.LeaseLife) + return b, nil +} + +func (e *EDNS0_LLQ) unpack(b []byte) error { + if len(b) < 18 { + return ErrBuf + } + e.Version = binary.BigEndian.Uint16(b[0:]) + e.Opcode = binary.BigEndian.Uint16(b[2:]) + e.Error = binary.BigEndian.Uint16(b[4:]) + e.Id = binary.BigEndian.Uint64(b[6:]) + e.LeaseLife = binary.BigEndian.Uint32(b[14:]) + return nil +} + +func (e *EDNS0_LLQ) String() string { + s := strconv.FormatUint(uint64(e.Version), 10) + " " + strconv.FormatUint(uint64(e.Opcode), 10) + + " " + strconv.FormatUint(uint64(e.Error), 10) + " " + strconv.FormatUint(e.Id, 10) + + " " + strconv.FormatUint(uint64(e.LeaseLife), 10) + return s +} +func (e *EDNS0_LLQ) copy() EDNS0 { + return &EDNS0_LLQ{e.Code, e.Version, e.Opcode, e.Error, e.Id, e.LeaseLife} +} + +// EDNS0_DUA implements the EDNS0 "DNSSEC Algorithm Understood" option. See RFC 6975. +type EDNS0_DAU struct { + Code uint16 // Always EDNS0DAU + AlgCode []uint8 +} + +// Option implements the EDNS0 interface. +func (e *EDNS0_DAU) Option() uint16 { return EDNS0DAU } +func (e *EDNS0_DAU) pack() ([]byte, error) { return e.AlgCode, nil } +func (e *EDNS0_DAU) unpack(b []byte) error { e.AlgCode = b; return nil } + +func (e *EDNS0_DAU) String() string { + s := "" + for _, alg := range e.AlgCode { + if a, ok := AlgorithmToString[alg]; ok { + s += " " + a + } else { + s += " " + strconv.Itoa(int(alg)) + } + } + return s +} +func (e *EDNS0_DAU) copy() EDNS0 { return &EDNS0_DAU{e.Code, e.AlgCode} } + +// EDNS0_DHU implements the EDNS0 "DS Hash Understood" option. See RFC 6975. +type EDNS0_DHU struct { + Code uint16 // Always EDNS0DHU + AlgCode []uint8 +} + +// Option implements the EDNS0 interface. +func (e *EDNS0_DHU) Option() uint16 { return EDNS0DHU } +func (e *EDNS0_DHU) pack() ([]byte, error) { return e.AlgCode, nil } +func (e *EDNS0_DHU) unpack(b []byte) error { e.AlgCode = b; return nil } + +func (e *EDNS0_DHU) String() string { + s := "" + for _, alg := range e.AlgCode { + if a, ok := HashToString[alg]; ok { + s += " " + a + } else { + s += " " + strconv.Itoa(int(alg)) + } + } + return s +} +func (e *EDNS0_DHU) copy() EDNS0 { return &EDNS0_DHU{e.Code, e.AlgCode} } + +// EDNS0_N3U implements the EDNS0 "NSEC3 Hash Understood" option. See RFC 6975. +type EDNS0_N3U struct { + Code uint16 // Always EDNS0N3U + AlgCode []uint8 +} + +// Option implements the EDNS0 interface. +func (e *EDNS0_N3U) Option() uint16 { return EDNS0N3U } +func (e *EDNS0_N3U) pack() ([]byte, error) { return e.AlgCode, nil } +func (e *EDNS0_N3U) unpack(b []byte) error { e.AlgCode = b; return nil } + +func (e *EDNS0_N3U) String() string { + // Re-use the hash map + s := "" + for _, alg := range e.AlgCode { + if a, ok := HashToString[alg]; ok { + s += " " + a + } else { + s += " " + strconv.Itoa(int(alg)) + } + } + return s +} +func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} } + +// EDNS0_EXPIRE implements the EDNS0 option as described in RFC 7314. +type EDNS0_EXPIRE struct { + Code uint16 // Always EDNS0EXPIRE + Expire uint32 +} + +// Option implements the EDNS0 interface. +func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE } +func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) } +func (e *EDNS0_EXPIRE) copy() EDNS0 { return &EDNS0_EXPIRE{e.Code, e.Expire} } + +func (e *EDNS0_EXPIRE) pack() ([]byte, error) { + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, e.Expire) + return b, nil +} + +func (e *EDNS0_EXPIRE) unpack(b []byte) error { + if len(b) == 0 { + // zero-length EXPIRE query, see RFC 7314 Section 2 + return nil + } + if len(b) < 4 { + return ErrBuf + } + e.Expire = binary.BigEndian.Uint32(b) + return nil +} + +// The EDNS0_LOCAL option is used for local/experimental purposes. The option +// code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND] +// (RFC6891), although any unassigned code can actually be used. The content of +// the option is made available in Data, unaltered. +// Basic use pattern for creating a local option: +// +// o := new(dns.OPT) +// o.Hdr.Name = "." +// o.Hdr.Rrtype = dns.TypeOPT +// e := new(dns.EDNS0_LOCAL) +// e.Code = dns.EDNS0LOCALSTART +// e.Data = []byte{72, 82, 74} +// o.Option = append(o.Option, e) +type EDNS0_LOCAL struct { + Code uint16 + Data []byte +} + +// Option implements the EDNS0 interface. +func (e *EDNS0_LOCAL) Option() uint16 { return e.Code } +func (e *EDNS0_LOCAL) String() string { + return strconv.FormatInt(int64(e.Code), 10) + ":0x" + hex.EncodeToString(e.Data) +} +func (e *EDNS0_LOCAL) copy() EDNS0 { + b := make([]byte, len(e.Data)) + copy(b, e.Data) + return &EDNS0_LOCAL{e.Code, b} +} + +func (e *EDNS0_LOCAL) pack() ([]byte, error) { + b := make([]byte, len(e.Data)) + copied := copy(b, e.Data) + if copied != len(e.Data) { + return nil, ErrBuf + } + return b, nil +} + +func (e *EDNS0_LOCAL) unpack(b []byte) error { + e.Data = make([]byte, len(b)) + copied := copy(e.Data, b) + if copied != len(b) { + return ErrBuf + } + return nil +} + +// EDNS0_TCP_KEEPALIVE is an EDNS0 option that instructs the server to keep +// the TCP connection alive. See RFC 7828. +type EDNS0_TCP_KEEPALIVE struct { + Code uint16 // Always EDNSTCPKEEPALIVE + Length uint16 // the value 0 if the TIMEOUT is omitted, the value 2 if it is present; + Timeout uint16 // an idle timeout value for the TCP connection, specified in units of 100 milliseconds, encoded in network byte order. +} + +// Option implements the EDNS0 interface. +func (e *EDNS0_TCP_KEEPALIVE) Option() uint16 { return EDNS0TCPKEEPALIVE } + +func (e *EDNS0_TCP_KEEPALIVE) pack() ([]byte, error) { + if e.Timeout != 0 && e.Length != 2 { + return nil, errors.New("dns: timeout specified but length is not 2") + } + if e.Timeout == 0 && e.Length != 0 { + return nil, errors.New("dns: timeout not specified but length is not 0") + } + b := make([]byte, 4+e.Length) + binary.BigEndian.PutUint16(b[0:], e.Code) + binary.BigEndian.PutUint16(b[2:], e.Length) + if e.Length == 2 { + binary.BigEndian.PutUint16(b[4:], e.Timeout) + } + return b, nil +} + +func (e *EDNS0_TCP_KEEPALIVE) unpack(b []byte) error { + if len(b) < 4 { + return ErrBuf + } + e.Length = binary.BigEndian.Uint16(b[2:4]) + if e.Length != 0 && e.Length != 2 { + return errors.New("dns: length mismatch, want 0/2 but got " + strconv.FormatUint(uint64(e.Length), 10)) + } + if e.Length == 2 { + if len(b) < 6 { + return ErrBuf + } + e.Timeout = binary.BigEndian.Uint16(b[4:6]) + } + return nil +} + +func (e *EDNS0_TCP_KEEPALIVE) String() (s string) { + s = "use tcp keep-alive" + if e.Length == 0 { + s += ", timeout omitted" + } else { + s += fmt.Sprintf(", timeout %dms", e.Timeout*100) + } + return +} +func (e *EDNS0_TCP_KEEPALIVE) copy() EDNS0 { return &EDNS0_TCP_KEEPALIVE{e.Code, e.Length, e.Timeout} } + +// EDNS0_PADDING option is used to add padding to a request/response. The default +// value of padding SHOULD be 0x0 but other values MAY be used, for instance if +// compression is applied before encryption which may break signatures. +type EDNS0_PADDING struct { + Padding []byte +} + +// Option implements the EDNS0 interface. +func (e *EDNS0_PADDING) Option() uint16 { return EDNS0PADDING } +func (e *EDNS0_PADDING) pack() ([]byte, error) { return e.Padding, nil } +func (e *EDNS0_PADDING) unpack(b []byte) error { e.Padding = b; return nil } +func (e *EDNS0_PADDING) String() string { return fmt.Sprintf("%0X", e.Padding) } +func (e *EDNS0_PADDING) copy() EDNS0 { + b := make([]byte, len(e.Padding)) + copy(b, e.Padding) + return &EDNS0_PADDING{b} +} diff --git a/vendor/github.com/miekg/dns/format.go b/vendor/github.com/miekg/dns/format.go new file mode 100644 index 0000000..0ec79f2 --- /dev/null +++ b/vendor/github.com/miekg/dns/format.go @@ -0,0 +1,93 @@ +package dns + +import ( + "net" + "reflect" + "strconv" +) + +// NumField returns the number of rdata fields r has. +func NumField(r RR) int { + return reflect.ValueOf(r).Elem().NumField() - 1 // Remove RR_Header +} + +// Field returns the rdata field i as a string. Fields are indexed starting from 1. +// RR types that holds slice data, for instance the NSEC type bitmap will return a single +// string where the types are concatenated using a space. +// Accessing non existing fields will cause a panic. +func Field(r RR, i int) string { + if i == 0 { + return "" + } + d := reflect.ValueOf(r).Elem().Field(i) + switch d.Kind() { + case reflect.String: + return d.String() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(d.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.FormatUint(d.Uint(), 10) + case reflect.Slice: + switch reflect.ValueOf(r).Elem().Type().Field(i).Tag { + case `dns:"a"`: + // TODO(miek): Hmm store this as 16 bytes + if d.Len() < net.IPv4len { + return "" + } + if d.Len() < net.IPv6len { + return net.IPv4(byte(d.Index(0).Uint()), + byte(d.Index(1).Uint()), + byte(d.Index(2).Uint()), + byte(d.Index(3).Uint())).String() + } + return net.IPv4(byte(d.Index(12).Uint()), + byte(d.Index(13).Uint()), + byte(d.Index(14).Uint()), + byte(d.Index(15).Uint())).String() + case `dns:"aaaa"`: + if d.Len() < net.IPv6len { + return "" + } + return net.IP{ + byte(d.Index(0).Uint()), + byte(d.Index(1).Uint()), + byte(d.Index(2).Uint()), + byte(d.Index(3).Uint()), + byte(d.Index(4).Uint()), + byte(d.Index(5).Uint()), + byte(d.Index(6).Uint()), + byte(d.Index(7).Uint()), + byte(d.Index(8).Uint()), + byte(d.Index(9).Uint()), + byte(d.Index(10).Uint()), + byte(d.Index(11).Uint()), + byte(d.Index(12).Uint()), + byte(d.Index(13).Uint()), + byte(d.Index(14).Uint()), + byte(d.Index(15).Uint()), + }.String() + case `dns:"nsec"`: + if d.Len() == 0 { + return "" + } + s := Type(d.Index(0).Uint()).String() + for i := 1; i < d.Len(); i++ { + s += " " + Type(d.Index(i).Uint()).String() + } + return s + default: + // if it does not have a tag its a string slice + fallthrough + case `dns:"txt"`: + if d.Len() == 0 { + return "" + } + s := d.Index(0).String() + for i := 1; i < d.Len(); i++ { + s += " " + d.Index(i).String() + } + return s + } + } + return "" +} diff --git a/vendor/github.com/miekg/dns/fuzz.go b/vendor/github.com/miekg/dns/fuzz.go new file mode 100644 index 0000000..57410ac --- /dev/null +++ b/vendor/github.com/miekg/dns/fuzz.go @@ -0,0 +1,32 @@ +// +build fuzz + +package dns + +import "strings" + +func Fuzz(data []byte) int { + msg := new(Msg) + + if err := msg.Unpack(data); err != nil { + return 0 + } + if _, err := msg.Pack(); err != nil { + return 0 + } + + return 1 +} + +func FuzzNewRR(data []byte) int { + str := string(data) + // Do not fuzz lines that include the $INCLUDE keyword and hint the fuzzer + // at avoiding them. + // See GH#1025 for context. + if strings.Contains(strings.ToUpper(str), "$INCLUDE") { + return -1 + } + if _, err := NewRR(str); err != nil { + return 0 + } + return 1 +} diff --git a/vendor/github.com/miekg/dns/generate.go b/vendor/github.com/miekg/dns/generate.go new file mode 100644 index 0000000..ac8df34 --- /dev/null +++ b/vendor/github.com/miekg/dns/generate.go @@ -0,0 +1,247 @@ +package dns + +import ( + "bytes" + "fmt" + "io" + "strconv" + "strings" +) + +// Parse the $GENERATE statement as used in BIND9 zones. +// See http://www.zytrax.com/books/dns/ch8/generate.html for instance. +// We are called after '$GENERATE '. After which we expect: +// * the range (12-24/2) +// * lhs (ownername) +// * [[ttl][class]] +// * type +// * rhs (rdata) +// But we are lazy here, only the range is parsed *all* occurrences +// of $ after that are interpreted. +func (zp *ZoneParser) generate(l lex) (RR, bool) { + token := l.token + step := int64(1) + if i := strings.IndexByte(token, '/'); i >= 0 { + if i+1 == len(token) { + return zp.setParseError("bad step in $GENERATE range", l) + } + + s, err := strconv.ParseInt(token[i+1:], 10, 64) + if err != nil || s <= 0 { + return zp.setParseError("bad step in $GENERATE range", l) + } + + step = s + token = token[:i] + } + + sx := strings.SplitN(token, "-", 2) + if len(sx) != 2 { + return zp.setParseError("bad start-stop in $GENERATE range", l) + } + + start, err := strconv.ParseInt(sx[0], 10, 64) + if err != nil { + return zp.setParseError("bad start in $GENERATE range", l) + } + + end, err := strconv.ParseInt(sx[1], 10, 64) + if err != nil { + return zp.setParseError("bad stop in $GENERATE range", l) + } + if end < 0 || start < 0 || end < start || (end-start)/step > 65535 { + return zp.setParseError("bad range in $GENERATE range", l) + } + + // _BLANK + l, ok := zp.c.Next() + if !ok || l.value != zBlank { + return zp.setParseError("garbage after $GENERATE range", l) + } + + // Create a complete new string, which we then parse again. + var s string + for l, ok := zp.c.Next(); ok; l, ok = zp.c.Next() { + if l.err { + return zp.setParseError("bad data in $GENERATE directive", l) + } + if l.value == zNewline { + break + } + + s += l.token + } + + r := &generateReader{ + s: s, + + cur: start, + start: start, + end: end, + step: step, + + file: zp.file, + lex: &l, + } + zp.sub = NewZoneParser(r, zp.origin, zp.file) + zp.sub.includeDepth, zp.sub.includeAllowed = zp.includeDepth, zp.includeAllowed + zp.sub.generateDisallowed = true + zp.sub.SetDefaultTTL(defaultTtl) + return zp.subNext() +} + +type generateReader struct { + s string + si int + + cur int64 + start int64 + end int64 + step int64 + + mod bytes.Buffer + + escape bool + + eof bool + + file string + lex *lex +} + +func (r *generateReader) parseError(msg string, end int) *ParseError { + r.eof = true // Make errors sticky. + + l := *r.lex + l.token = r.s[r.si-1 : end] + l.column += r.si // l.column starts one zBLANK before r.s + + return &ParseError{r.file, msg, l} +} + +func (r *generateReader) Read(p []byte) (int, error) { + // NewZLexer, through NewZoneParser, should use ReadByte and + // not end up here. + + panic("not implemented") +} + +func (r *generateReader) ReadByte() (byte, error) { + if r.eof { + return 0, io.EOF + } + if r.mod.Len() > 0 { + return r.mod.ReadByte() + } + + if r.si >= len(r.s) { + r.si = 0 + r.cur += r.step + + r.eof = r.cur > r.end || r.cur < 0 + return '\n', nil + } + + si := r.si + r.si++ + + switch r.s[si] { + case '\\': + if r.escape { + r.escape = false + return '\\', nil + } + + r.escape = true + return r.ReadByte() + case '$': + if r.escape { + r.escape = false + return '$', nil + } + + mod := "%d" + + if si >= len(r.s)-1 { + // End of the string + fmt.Fprintf(&r.mod, mod, r.cur) + return r.mod.ReadByte() + } + + if r.s[si+1] == '$' { + r.si++ + return '$', nil + } + + var offset int64 + + // Search for { and } + if r.s[si+1] == '{' { + // Modifier block + sep := strings.Index(r.s[si+2:], "}") + if sep < 0 { + return 0, r.parseError("bad modifier in $GENERATE", len(r.s)) + } + + var errMsg string + mod, offset, errMsg = modToPrintf(r.s[si+2 : si+2+sep]) + if errMsg != "" { + return 0, r.parseError(errMsg, si+3+sep) + } + if r.start+offset < 0 || r.end+offset > 1<<31-1 { + return 0, r.parseError("bad offset in $GENERATE", si+3+sep) + } + + r.si += 2 + sep // Jump to it + } + + fmt.Fprintf(&r.mod, mod, r.cur+offset) + return r.mod.ReadByte() + default: + if r.escape { // Pretty useless here + r.escape = false + return r.ReadByte() + } + + return r.s[si], nil + } +} + +// Convert a $GENERATE modifier 0,0,d to something Printf can deal with. +func modToPrintf(s string) (string, int64, string) { + // Modifier is { offset [ ,width [ ,base ] ] } - provide default + // values for optional width and type, if necessary. + var offStr, widthStr, base string + switch xs := strings.Split(s, ","); len(xs) { + case 1: + offStr, widthStr, base = xs[0], "0", "d" + case 2: + offStr, widthStr, base = xs[0], xs[1], "d" + case 3: + offStr, widthStr, base = xs[0], xs[1], xs[2] + default: + return "", 0, "bad modifier in $GENERATE" + } + + switch base { + case "o", "d", "x", "X": + default: + return "", 0, "bad base in $GENERATE" + } + + offset, err := strconv.ParseInt(offStr, 10, 64) + if err != nil { + return "", 0, "bad offset in $GENERATE" + } + + width, err := strconv.ParseInt(widthStr, 10, 64) + if err != nil || width < 0 || width > 255 { + return "", 0, "bad width in $GENERATE" + } + + if width == 0 { + return "%" + base, offset, "" + } + + return "%0" + widthStr + base, offset, "" +} diff --git a/vendor/github.com/miekg/dns/labels.go b/vendor/github.com/miekg/dns/labels.go new file mode 100644 index 0000000..f9faacf --- /dev/null +++ b/vendor/github.com/miekg/dns/labels.go @@ -0,0 +1,212 @@ +package dns + +// Holds a bunch of helper functions for dealing with labels. + +// SplitDomainName splits a name string into it's labels. +// www.miek.nl. returns []string{"www", "miek", "nl"} +// .www.miek.nl. returns []string{"", "www", "miek", "nl"}, +// The root label (.) returns nil. Note that using +// strings.Split(s) will work in most cases, but does not handle +// escaped dots (\.) for instance. +// s must be a syntactically valid domain name, see IsDomainName. +func SplitDomainName(s string) (labels []string) { + if s == "" { + return nil + } + fqdnEnd := 0 // offset of the final '.' or the length of the name + idx := Split(s) + begin := 0 + if IsFqdn(s) { + fqdnEnd = len(s) - 1 + } else { + fqdnEnd = len(s) + } + + switch len(idx) { + case 0: + return nil + case 1: + // no-op + default: + for _, end := range idx[1:] { + labels = append(labels, s[begin:end-1]) + begin = end + } + } + + return append(labels, s[begin:fqdnEnd]) +} + +// CompareDomainName compares the names s1 and s2 and +// returns how many labels they have in common starting from the *right*. +// The comparison stops at the first inequality. The names are downcased +// before the comparison. +// +// www.miek.nl. and miek.nl. have two labels in common: miek and nl +// www.miek.nl. and www.bla.nl. have one label in common: nl +// +// s1 and s2 must be syntactically valid domain names. +func CompareDomainName(s1, s2 string) (n int) { + // the first check: root label + if s1 == "." || s2 == "." { + return 0 + } + + l1 := Split(s1) + l2 := Split(s2) + + j1 := len(l1) - 1 // end + i1 := len(l1) - 2 // start + j2 := len(l2) - 1 + i2 := len(l2) - 2 + // the second check can be done here: last/only label + // before we fall through into the for-loop below + if equal(s1[l1[j1]:], s2[l2[j2]:]) { + n++ + } else { + return + } + for { + if i1 < 0 || i2 < 0 { + break + } + if equal(s1[l1[i1]:l1[j1]], s2[l2[i2]:l2[j2]]) { + n++ + } else { + break + } + j1-- + i1-- + j2-- + i2-- + } + return +} + +// CountLabel counts the number of labels in the string s. +// s must be a syntactically valid domain name. +func CountLabel(s string) (labels int) { + if s == "." { + return + } + off := 0 + end := false + for { + off, end = NextLabel(s, off) + labels++ + if end { + return + } + } +} + +// Split splits a name s into its label indexes. +// www.miek.nl. returns []int{0, 4, 9}, www.miek.nl also returns []int{0, 4, 9}. +// The root name (.) returns nil. Also see SplitDomainName. +// s must be a syntactically valid domain name. +func Split(s string) []int { + if s == "." { + return nil + } + idx := make([]int, 1, 3) + off := 0 + end := false + + for { + off, end = NextLabel(s, off) + if end { + return idx + } + idx = append(idx, off) + } +} + +// NextLabel returns the index of the start of the next label in the +// string s starting at offset. +// The bool end is true when the end of the string has been reached. +// Also see PrevLabel. +func NextLabel(s string, offset int) (i int, end bool) { + if s == "" { + return 0, true + } + for i = offset; i < len(s)-1; i++ { + if s[i] != '.' { + continue + } + j := i - 1 + for j >= 0 && s[j] == '\\' { + j-- + } + + if (j-i)%2 == 0 { + continue + } + + return i + 1, false + } + return i + 1, true +} + +// PrevLabel returns the index of the label when starting from the right and +// jumping n labels to the left. +// The bool start is true when the start of the string has been overshot. +// Also see NextLabel. +func PrevLabel(s string, n int) (i int, start bool) { + if s == "" { + return 0, true + } + if n == 0 { + return len(s), false + } + + l := len(s) - 1 + if s[l] == '.' { + l-- + } + + for ; l >= 0 && n > 0; l-- { + if s[l] != '.' { + continue + } + j := l - 1 + for j >= 0 && s[j] == '\\' { + j-- + } + + if (j-l)%2 == 0 { + continue + } + + n-- + if n == 0 { + return l + 1, false + } + } + + return 0, n > 1 +} + +// equal compares a and b while ignoring case. It returns true when equal otherwise false. +func equal(a, b string) bool { + // might be lifted into API function. + la := len(a) + lb := len(b) + if la != lb { + return false + } + + for i := la - 1; i >= 0; i-- { + ai := a[i] + bi := b[i] + if ai >= 'A' && ai <= 'Z' { + ai |= 'a' - 'A' + } + if bi >= 'A' && bi <= 'Z' { + bi |= 'a' - 'A' + } + if ai != bi { + return false + } + } + return true +} diff --git a/vendor/github.com/miekg/dns/listen_go111.go b/vendor/github.com/miekg/dns/listen_go111.go new file mode 100644 index 0000000..fad195c --- /dev/null +++ b/vendor/github.com/miekg/dns/listen_go111.go @@ -0,0 +1,44 @@ +// +build go1.11 +// +build aix darwin dragonfly freebsd linux netbsd openbsd + +package dns + +import ( + "context" + "net" + "syscall" + + "golang.org/x/sys/unix" +) + +const supportsReusePort = true + +func reuseportControl(network, address string, c syscall.RawConn) error { + var opErr error + err := c.Control(func(fd uintptr) { + opErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + }) + if err != nil { + return err + } + + return opErr +} + +func listenTCP(network, addr string, reuseport bool) (net.Listener, error) { + var lc net.ListenConfig + if reuseport { + lc.Control = reuseportControl + } + + return lc.Listen(context.Background(), network, addr) +} + +func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) { + var lc net.ListenConfig + if reuseport { + lc.Control = reuseportControl + } + + return lc.ListenPacket(context.Background(), network, addr) +} diff --git a/vendor/github.com/miekg/dns/listen_go_not111.go b/vendor/github.com/miekg/dns/listen_go_not111.go new file mode 100644 index 0000000..b920141 --- /dev/null +++ b/vendor/github.com/miekg/dns/listen_go_not111.go @@ -0,0 +1,23 @@ +// +build !go1.11 !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd + +package dns + +import "net" + +const supportsReusePort = false + +func listenTCP(network, addr string, reuseport bool) (net.Listener, error) { + if reuseport { + // TODO(tmthrgd): return an error? + } + + return net.Listen(network, addr) +} + +func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) { + if reuseport { + // TODO(tmthrgd): return an error? + } + + return net.ListenPacket(network, addr) +} diff --git a/vendor/github.com/miekg/dns/msg.go b/vendor/github.com/miekg/dns/msg.go new file mode 100644 index 0000000..ead4b69 --- /dev/null +++ b/vendor/github.com/miekg/dns/msg.go @@ -0,0 +1,1197 @@ +// DNS packet assembly, see RFC 1035. Converting from - Unpack() - +// and to - Pack() - wire format. +// All the packers and unpackers take a (msg []byte, off int) +// and return (off1 int, ok bool). If they return ok==false, they +// also return off1==len(msg), so that the next unpacker will +// also fail. This lets us avoid checks of ok until the end of a +// packing sequence. + +package dns + +//go:generate go run msg_generate.go + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + "math/big" + "strconv" + "strings" +) + +const ( + maxCompressionOffset = 2 << 13 // We have 14 bits for the compression pointer + maxDomainNameWireOctets = 255 // See RFC 1035 section 2.3.4 + + // This is the maximum number of compression pointers that should occur in a + // semantically valid message. Each label in a domain name must be at least one + // octet and is separated by a period. The root label won't be represented by a + // compression pointer to a compression pointer, hence the -2 to exclude the + // smallest valid root label. + // + // It is possible to construct a valid message that has more compression pointers + // than this, and still doesn't loop, by pointing to a previous pointer. This is + // not something a well written implementation should ever do, so we leave them + // to trip the maximum compression pointer check. + maxCompressionPointers = (maxDomainNameWireOctets+1)/2 - 2 + + // This is the maximum length of a domain name in presentation format. The + // maximum wire length of a domain name is 255 octets (see above), with the + // maximum label length being 63. The wire format requires one extra byte over + // the presentation format, reducing the number of octets by 1. Each label in + // the name will be separated by a single period, with each octet in the label + // expanding to at most 4 bytes (\DDD). If all other labels are of the maximum + // length, then the final label can only be 61 octets long to not exceed the + // maximum allowed wire length. + maxDomainNamePresentationLength = 61*4 + 1 + 63*4 + 1 + 63*4 + 1 + 63*4 + 1 +) + +// Errors defined in this package. +var ( + ErrAlg error = &Error{err: "bad algorithm"} // ErrAlg indicates an error with the (DNSSEC) algorithm. + ErrAuth error = &Error{err: "bad authentication"} // ErrAuth indicates an error in the TSIG authentication. + ErrBuf error = &Error{err: "buffer size too small"} // ErrBuf indicates that the buffer used is too small for the message. + ErrConnEmpty error = &Error{err: "conn has no connection"} // ErrConnEmpty indicates a connection is being used before it is initialized. + ErrExtendedRcode error = &Error{err: "bad extended rcode"} // ErrExtendedRcode ... + ErrFqdn error = &Error{err: "domain must be fully qualified"} // ErrFqdn indicates that a domain name does not have a closing dot. + ErrId error = &Error{err: "id mismatch"} // ErrId indicates there is a mismatch with the message's ID. + ErrKeyAlg error = &Error{err: "bad key algorithm"} // ErrKeyAlg indicates that the algorithm in the key is not valid. + ErrKey error = &Error{err: "bad key"} + ErrKeySize error = &Error{err: "bad key size"} + ErrLongDomain error = &Error{err: fmt.Sprintf("domain name exceeded %d wire-format octets", maxDomainNameWireOctets)} + ErrNoSig error = &Error{err: "no signature found"} + ErrPrivKey error = &Error{err: "bad private key"} + ErrRcode error = &Error{err: "bad rcode"} + ErrRdata error = &Error{err: "bad rdata"} + ErrRRset error = &Error{err: "bad rrset"} + ErrSecret error = &Error{err: "no secrets defined"} + ErrShortRead error = &Error{err: "short read"} + ErrSig error = &Error{err: "bad signature"} // ErrSig indicates that a signature can not be cryptographically validated. + ErrSoa error = &Error{err: "no SOA"} // ErrSOA indicates that no SOA RR was seen when doing zone transfers. + ErrTime error = &Error{err: "bad time"} // ErrTime indicates a timing error in TSIG authentication. +) + +// Id by default returns a 16-bit random number to be used as a message id. The +// number is drawn from a cryptographically secure random number generator. +// This being a variable the function can be reassigned to a custom function. +// For instance, to make it return a static value for testing: +// +// dns.Id = func() uint16 { return 3 } +var Id = id + +// id returns a 16 bits random number to be used as a +// message id. The random provided should be good enough. +func id() uint16 { + var output uint16 + err := binary.Read(rand.Reader, binary.BigEndian, &output) + if err != nil { + panic("dns: reading random id failed: " + err.Error()) + } + return output +} + +// MsgHdr is a a manually-unpacked version of (id, bits). +type MsgHdr struct { + Id uint16 + Response bool + Opcode int + Authoritative bool + Truncated bool + RecursionDesired bool + RecursionAvailable bool + Zero bool + AuthenticatedData bool + CheckingDisabled bool + Rcode int +} + +// Msg contains the layout of a DNS message. +type Msg struct { + MsgHdr + Compress bool `json:"-"` // If true, the message will be compressed when converted to wire format. + Question []Question // Holds the RR(s) of the question section. + Answer []RR // Holds the RR(s) of the answer section. + Ns []RR // Holds the RR(s) of the authority section. + Extra []RR // Holds the RR(s) of the additional section. +} + +// ClassToString is a maps Classes to strings for each CLASS wire type. +var ClassToString = map[uint16]string{ + ClassINET: "IN", + ClassCSNET: "CS", + ClassCHAOS: "CH", + ClassHESIOD: "HS", + ClassNONE: "NONE", + ClassANY: "ANY", +} + +// OpcodeToString maps Opcodes to strings. +var OpcodeToString = map[int]string{ + OpcodeQuery: "QUERY", + OpcodeIQuery: "IQUERY", + OpcodeStatus: "STATUS", + OpcodeNotify: "NOTIFY", + OpcodeUpdate: "UPDATE", +} + +// RcodeToString maps Rcodes to strings. +var RcodeToString = map[int]string{ + RcodeSuccess: "NOERROR", + RcodeFormatError: "FORMERR", + RcodeServerFailure: "SERVFAIL", + RcodeNameError: "NXDOMAIN", + RcodeNotImplemented: "NOTIMP", + RcodeRefused: "REFUSED", + RcodeYXDomain: "YXDOMAIN", // See RFC 2136 + RcodeYXRrset: "YXRRSET", + RcodeNXRrset: "NXRRSET", + RcodeNotAuth: "NOTAUTH", + RcodeNotZone: "NOTZONE", + RcodeBadSig: "BADSIG", // Also known as RcodeBadVers, see RFC 6891 + // RcodeBadVers: "BADVERS", + RcodeBadKey: "BADKEY", + RcodeBadTime: "BADTIME", + RcodeBadMode: "BADMODE", + RcodeBadName: "BADNAME", + RcodeBadAlg: "BADALG", + RcodeBadTrunc: "BADTRUNC", + RcodeBadCookie: "BADCOOKIE", +} + +// compressionMap is used to allow a more efficient compression map +// to be used for internal packDomainName calls without changing the +// signature or functionality of public API. +// +// In particular, map[string]uint16 uses 25% less per-entry memory +// than does map[string]int. +type compressionMap struct { + ext map[string]int // external callers + int map[string]uint16 // internal callers +} + +func (m compressionMap) valid() bool { + return m.int != nil || m.ext != nil +} + +func (m compressionMap) insert(s string, pos int) { + if m.ext != nil { + m.ext[s] = pos + } else { + m.int[s] = uint16(pos) + } +} + +func (m compressionMap) find(s string) (int, bool) { + if m.ext != nil { + pos, ok := m.ext[s] + return pos, ok + } + + pos, ok := m.int[s] + return int(pos), ok +} + +// Domain names are a sequence of counted strings +// split at the dots. They end with a zero-length string. + +// PackDomainName packs a domain name s into msg[off:]. +// If compression is wanted compress must be true and the compression +// map needs to hold a mapping between domain names and offsets +// pointing into msg. +func PackDomainName(s string, msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) { + return packDomainName(s, msg, off, compressionMap{ext: compression}, compress) +} + +func packDomainName(s string, msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + // XXX: A logical copy of this function exists in IsDomainName and + // should be kept in sync with this function. + + ls := len(s) + if ls == 0 { // Ok, for instance when dealing with update RR without any rdata. + return off, nil + } + + // If not fully qualified, error out. + if !IsFqdn(s) { + return len(msg), ErrFqdn + } + + // Each dot ends a segment of the name. + // We trade each dot byte for a length byte. + // Except for escaped dots (\.), which are normal dots. + // There is also a trailing zero. + + // Compression + pointer := -1 + + // Emit sequence of counted strings, chopping at dots. + var ( + begin int + compBegin int + compOff int + bs []byte + wasDot bool + ) +loop: + for i := 0; i < ls; i++ { + var c byte + if bs == nil { + c = s[i] + } else { + c = bs[i] + } + + switch c { + case '\\': + if off+1 > len(msg) { + return len(msg), ErrBuf + } + + if bs == nil { + bs = []byte(s) + } + + // check for \DDD + if i+3 < ls && isDigit(bs[i+1]) && isDigit(bs[i+2]) && isDigit(bs[i+3]) { + bs[i] = dddToByte(bs[i+1:]) + copy(bs[i+1:ls-3], bs[i+4:]) + ls -= 3 + compOff += 3 + } else { + copy(bs[i:ls-1], bs[i+1:]) + ls-- + compOff++ + } + + wasDot = false + case '.': + if wasDot { + // two dots back to back is not legal + return len(msg), ErrRdata + } + wasDot = true + + labelLen := i - begin + if labelLen >= 1<<6 { // top two bits of length must be clear + return len(msg), ErrRdata + } + + // off can already (we're in a loop) be bigger than len(msg) + // this happens when a name isn't fully qualified + if off+1+labelLen > len(msg) { + return len(msg), ErrBuf + } + + // Don't try to compress '.' + // We should only compress when compress is true, but we should also still pick + // up names that can be used for *future* compression(s). + if compression.valid() && !isRootLabel(s, bs, begin, ls) { + if p, ok := compression.find(s[compBegin:]); ok { + // The first hit is the longest matching dname + // keep the pointer offset we get back and store + // the offset of the current name, because that's + // where we need to insert the pointer later + + // If compress is true, we're allowed to compress this dname + if compress { + pointer = p // Where to point to + break loop + } + } else if off < maxCompressionOffset { + // Only offsets smaller than maxCompressionOffset can be used. + compression.insert(s[compBegin:], off) + } + } + + // The following is covered by the length check above. + msg[off] = byte(labelLen) + + if bs == nil { + copy(msg[off+1:], s[begin:i]) + } else { + copy(msg[off+1:], bs[begin:i]) + } + off += 1 + labelLen + + begin = i + 1 + compBegin = begin + compOff + default: + wasDot = false + } + } + + // Root label is special + if isRootLabel(s, bs, 0, ls) { + return off, nil + } + + // If we did compression and we find something add the pointer here + if pointer != -1 { + // We have two bytes (14 bits) to put the pointer in + binary.BigEndian.PutUint16(msg[off:], uint16(pointer^0xC000)) + return off + 2, nil + } + + if off < len(msg) { + msg[off] = 0 + } + + return off + 1, nil +} + +// isRootLabel returns whether s or bs, from off to end, is the root +// label ".". +// +// If bs is nil, s will be checked, otherwise bs will be checked. +func isRootLabel(s string, bs []byte, off, end int) bool { + if bs == nil { + return s[off:end] == "." + } + + return end-off == 1 && bs[off] == '.' +} + +// Unpack a domain name. +// In addition to the simple sequences of counted strings above, +// domain names are allowed to refer to strings elsewhere in the +// packet, to avoid repeating common suffixes when returning +// many entries in a single domain. The pointers are marked +// by a length byte with the top two bits set. Ignoring those +// two bits, that byte and the next give a 14 bit offset from msg[0] +// where we should pick up the trail. +// Note that if we jump elsewhere in the packet, +// we return off1 == the offset after the first pointer we found, +// which is where the next record will start. +// In theory, the pointers are only allowed to jump backward. +// We let them jump anywhere and stop jumping after a while. + +// UnpackDomainName unpacks a domain name into a string. It returns +// the name, the new offset into msg and any error that occurred. +// +// When an error is encountered, the unpacked name will be discarded +// and len(msg) will be returned as the offset. +func UnpackDomainName(msg []byte, off int) (string, int, error) { + s := make([]byte, 0, maxDomainNamePresentationLength) + off1 := 0 + lenmsg := len(msg) + budget := maxDomainNameWireOctets + ptr := 0 // number of pointers followed +Loop: + for { + if off >= lenmsg { + return "", lenmsg, ErrBuf + } + c := int(msg[off]) + off++ + switch c & 0xC0 { + case 0x00: + if c == 0x00 { + // end of name + break Loop + } + // literal string + if off+c > lenmsg { + return "", lenmsg, ErrBuf + } + budget -= c + 1 // +1 for the label separator + if budget <= 0 { + return "", lenmsg, ErrLongDomain + } + for _, b := range msg[off : off+c] { + if isDomainNameLabelSpecial(b) { + s = append(s, '\\', b) + } else if b < ' ' || b > '~' { + s = append(s, escapeByte(b)...) + } else { + s = append(s, b) + } + } + s = append(s, '.') + off += c + case 0xC0: + // pointer to somewhere else in msg. + // remember location after first ptr, + // since that's how many bytes we consumed. + // also, don't follow too many pointers -- + // maybe there's a loop. + if off >= lenmsg { + return "", lenmsg, ErrBuf + } + c1 := msg[off] + off++ + if ptr == 0 { + off1 = off + } + if ptr++; ptr > maxCompressionPointers { + return "", lenmsg, &Error{err: "too many compression pointers"} + } + // pointer should guarantee that it advances and points forwards at least + // but the condition on previous three lines guarantees that it's + // at least loop-free + off = (c^0xC0)<<8 | int(c1) + default: + // 0x80 and 0x40 are reserved + return "", lenmsg, ErrRdata + } + } + if ptr == 0 { + off1 = off + } + if len(s) == 0 { + return ".", off1, nil + } + return string(s), off1, nil +} + +func packTxt(txt []string, msg []byte, offset int, tmp []byte) (int, error) { + if len(txt) == 0 { + if offset >= len(msg) { + return offset, ErrBuf + } + msg[offset] = 0 + return offset, nil + } + var err error + for _, s := range txt { + if len(s) > len(tmp) { + return offset, ErrBuf + } + offset, err = packTxtString(s, msg, offset, tmp) + if err != nil { + return offset, err + } + } + return offset, nil +} + +func packTxtString(s string, msg []byte, offset int, tmp []byte) (int, error) { + lenByteOffset := offset + if offset >= len(msg) || len(s) > len(tmp) { + return offset, ErrBuf + } + offset++ + bs := tmp[:len(s)] + copy(bs, s) + for i := 0; i < len(bs); i++ { + if len(msg) <= offset { + return offset, ErrBuf + } + if bs[i] == '\\' { + i++ + if i == len(bs) { + break + } + // check for \DDD + if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) { + msg[offset] = dddToByte(bs[i:]) + i += 2 + } else { + msg[offset] = bs[i] + } + } else { + msg[offset] = bs[i] + } + offset++ + } + l := offset - lenByteOffset - 1 + if l > 255 { + return offset, &Error{err: "string exceeded 255 bytes in txt"} + } + msg[lenByteOffset] = byte(l) + return offset, nil +} + +func packOctetString(s string, msg []byte, offset int, tmp []byte) (int, error) { + if offset >= len(msg) || len(s) > len(tmp) { + return offset, ErrBuf + } + bs := tmp[:len(s)] + copy(bs, s) + for i := 0; i < len(bs); i++ { + if len(msg) <= offset { + return offset, ErrBuf + } + if bs[i] == '\\' { + i++ + if i == len(bs) { + break + } + // check for \DDD + if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) { + msg[offset] = dddToByte(bs[i:]) + i += 2 + } else { + msg[offset] = bs[i] + } + } else { + msg[offset] = bs[i] + } + offset++ + } + return offset, nil +} + +func unpackTxt(msg []byte, off0 int) (ss []string, off int, err error) { + off = off0 + var s string + for off < len(msg) && err == nil { + s, off, err = unpackString(msg, off) + if err == nil { + ss = append(ss, s) + } + } + return +} + +// Helpers for dealing with escaped bytes +func isDigit(b byte) bool { return b >= '0' && b <= '9' } + +func dddToByte(s []byte) byte { + _ = s[2] // bounds check hint to compiler; see golang.org/issue/14808 + return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0')) +} + +func dddStringToByte(s string) byte { + _ = s[2] // bounds check hint to compiler; see golang.org/issue/14808 + return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0')) +} + +// Helper function for packing and unpacking +func intToBytes(i *big.Int, length int) []byte { + buf := i.Bytes() + if len(buf) < length { + b := make([]byte, length) + copy(b[length-len(buf):], buf) + return b + } + return buf +} + +// PackRR packs a resource record rr into msg[off:]. +// See PackDomainName for documentation about the compression. +func PackRR(rr RR, msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) { + headerEnd, off1, err := packRR(rr, msg, off, compressionMap{ext: compression}, compress) + if err == nil { + // packRR no longer sets the Rdlength field on the rr, but + // callers might be expecting it so we set it here. + rr.Header().Rdlength = uint16(off1 - headerEnd) + } + return off1, err +} + +func packRR(rr RR, msg []byte, off int, compression compressionMap, compress bool) (headerEnd int, off1 int, err error) { + if rr == nil { + return len(msg), len(msg), &Error{err: "nil rr"} + } + + headerEnd, err = rr.Header().packHeader(msg, off, compression, compress) + if err != nil { + return headerEnd, len(msg), err + } + + off1, err = rr.pack(msg, headerEnd, compression, compress) + if err != nil { + return headerEnd, len(msg), err + } + + rdlength := off1 - headerEnd + if int(uint16(rdlength)) != rdlength { // overflow + return headerEnd, len(msg), ErrRdata + } + + // The RDLENGTH field is the last field in the header and we set it here. + binary.BigEndian.PutUint16(msg[headerEnd-2:], uint16(rdlength)) + return headerEnd, off1, nil +} + +// UnpackRR unpacks msg[off:] into an RR. +func UnpackRR(msg []byte, off int) (rr RR, off1 int, err error) { + h, off, msg, err := unpackHeader(msg, off) + if err != nil { + return nil, len(msg), err + } + + return UnpackRRWithHeader(h, msg, off) +} + +// UnpackRRWithHeader unpacks the record type specific payload given an existing +// RR_Header. +func UnpackRRWithHeader(h RR_Header, msg []byte, off int) (rr RR, off1 int, err error) { + if newFn, ok := TypeToRR[h.Rrtype]; ok { + rr = newFn() + *rr.Header() = h + } else { + rr = &RFC3597{Hdr: h} + } + + if off < 0 || off > len(msg) { + return &h, off, &Error{err: "bad off"} + } + + end := off + int(h.Rdlength) + if end < off || end > len(msg) { + return &h, end, &Error{err: "bad rdlength"} + } + + if noRdata(h) { + return rr, off, nil + } + + off, err = rr.unpack(msg, off) + if err != nil { + return nil, end, err + } + if off != end { + return &h, end, &Error{err: "bad rdlength"} + } + + return rr, off, nil +} + +// unpackRRslice unpacks msg[off:] into an []RR. +// If we cannot unpack the whole array, then it will return nil +func unpackRRslice(l int, msg []byte, off int) (dst1 []RR, off1 int, err error) { + var r RR + // Don't pre-allocate, l may be under attacker control + var dst []RR + for i := 0; i < l; i++ { + off1 := off + r, off, err = UnpackRR(msg, off) + if err != nil { + off = len(msg) + break + } + // If offset does not increase anymore, l is a lie + if off1 == off { + break + } + dst = append(dst, r) + } + if err != nil && off == len(msg) { + dst = nil + } + return dst, off, err +} + +// Convert a MsgHdr to a string, with dig-like headers: +// +//;; opcode: QUERY, status: NOERROR, id: 48404 +// +//;; flags: qr aa rd ra; +func (h *MsgHdr) String() string { + if h == nil { + return " MsgHdr" + } + + s := ";; opcode: " + OpcodeToString[h.Opcode] + s += ", status: " + RcodeToString[h.Rcode] + s += ", id: " + strconv.Itoa(int(h.Id)) + "\n" + + s += ";; flags:" + if h.Response { + s += " qr" + } + if h.Authoritative { + s += " aa" + } + if h.Truncated { + s += " tc" + } + if h.RecursionDesired { + s += " rd" + } + if h.RecursionAvailable { + s += " ra" + } + if h.Zero { // Hmm + s += " z" + } + if h.AuthenticatedData { + s += " ad" + } + if h.CheckingDisabled { + s += " cd" + } + + s += ";" + return s +} + +// Pack packs a Msg: it is converted to to wire format. +// If the dns.Compress is true the message will be in compressed wire format. +func (dns *Msg) Pack() (msg []byte, err error) { + return dns.PackBuffer(nil) +} + +// PackBuffer packs a Msg, using the given buffer buf. If buf is too small a new buffer is allocated. +func (dns *Msg) PackBuffer(buf []byte) (msg []byte, err error) { + // If this message can't be compressed, avoid filling the + // compression map and creating garbage. + if dns.Compress && dns.isCompressible() { + compression := make(map[string]uint16) // Compression pointer mappings. + return dns.packBufferWithCompressionMap(buf, compressionMap{int: compression}, true) + } + + return dns.packBufferWithCompressionMap(buf, compressionMap{}, false) +} + +// packBufferWithCompressionMap packs a Msg, using the given buffer buf. +func (dns *Msg) packBufferWithCompressionMap(buf []byte, compression compressionMap, compress bool) (msg []byte, err error) { + if dns.Rcode < 0 || dns.Rcode > 0xFFF { + return nil, ErrRcode + } + + // Set extended rcode unconditionally if we have an opt, this will allow + // resetting the extended rcode bits if they need to. + if opt := dns.IsEdns0(); opt != nil { + opt.SetExtendedRcode(uint16(dns.Rcode)) + } else if dns.Rcode > 0xF { + // If Rcode is an extended one and opt is nil, error out. + return nil, ErrExtendedRcode + } + + // Convert convenient Msg into wire-like Header. + var dh Header + dh.Id = dns.Id + dh.Bits = uint16(dns.Opcode)<<11 | uint16(dns.Rcode&0xF) + if dns.Response { + dh.Bits |= _QR + } + if dns.Authoritative { + dh.Bits |= _AA + } + if dns.Truncated { + dh.Bits |= _TC + } + if dns.RecursionDesired { + dh.Bits |= _RD + } + if dns.RecursionAvailable { + dh.Bits |= _RA + } + if dns.Zero { + dh.Bits |= _Z + } + if dns.AuthenticatedData { + dh.Bits |= _AD + } + if dns.CheckingDisabled { + dh.Bits |= _CD + } + + dh.Qdcount = uint16(len(dns.Question)) + dh.Ancount = uint16(len(dns.Answer)) + dh.Nscount = uint16(len(dns.Ns)) + dh.Arcount = uint16(len(dns.Extra)) + + // We need the uncompressed length here, because we first pack it and then compress it. + msg = buf + uncompressedLen := msgLenWithCompressionMap(dns, nil) + if packLen := uncompressedLen + 1; len(msg) < packLen { + msg = make([]byte, packLen) + } + + // Pack it in: header and then the pieces. + off := 0 + off, err = dh.pack(msg, off, compression, compress) + if err != nil { + return nil, err + } + for _, r := range dns.Question { + off, err = r.pack(msg, off, compression, compress) + if err != nil { + return nil, err + } + } + for _, r := range dns.Answer { + _, off, err = packRR(r, msg, off, compression, compress) + if err != nil { + return nil, err + } + } + for _, r := range dns.Ns { + _, off, err = packRR(r, msg, off, compression, compress) + if err != nil { + return nil, err + } + } + for _, r := range dns.Extra { + _, off, err = packRR(r, msg, off, compression, compress) + if err != nil { + return nil, err + } + } + return msg[:off], nil +} + +func (dns *Msg) unpack(dh Header, msg []byte, off int) (err error) { + // If we are at the end of the message we should return *just* the + // header. This can still be useful to the caller. 9.9.9.9 sends these + // when responding with REFUSED for instance. + if off == len(msg) { + // reset sections before returning + dns.Question, dns.Answer, dns.Ns, dns.Extra = nil, nil, nil, nil + return nil + } + + // Qdcount, Ancount, Nscount, Arcount can't be trusted, as they are + // attacker controlled. This means we can't use them to pre-allocate + // slices. + dns.Question = nil + for i := 0; i < int(dh.Qdcount); i++ { + off1 := off + var q Question + q, off, err = unpackQuestion(msg, off) + if err != nil { + return err + } + if off1 == off { // Offset does not increase anymore, dh.Qdcount is a lie! + dh.Qdcount = uint16(i) + break + } + dns.Question = append(dns.Question, q) + } + + dns.Answer, off, err = unpackRRslice(int(dh.Ancount), msg, off) + // The header counts might have been wrong so we need to update it + dh.Ancount = uint16(len(dns.Answer)) + if err == nil { + dns.Ns, off, err = unpackRRslice(int(dh.Nscount), msg, off) + } + // The header counts might have been wrong so we need to update it + dh.Nscount = uint16(len(dns.Ns)) + if err == nil { + dns.Extra, off, err = unpackRRslice(int(dh.Arcount), msg, off) + } + // The header counts might have been wrong so we need to update it + dh.Arcount = uint16(len(dns.Extra)) + + // Set extended Rcode + if opt := dns.IsEdns0(); opt != nil { + dns.Rcode |= opt.ExtendedRcode() + } + + if off != len(msg) { + // TODO(miek) make this an error? + // use PackOpt to let people tell how detailed the error reporting should be? + // println("dns: extra bytes in dns packet", off, "<", len(msg)) + } + return err + +} + +// Unpack unpacks a binary message to a Msg structure. +func (dns *Msg) Unpack(msg []byte) (err error) { + dh, off, err := unpackMsgHdr(msg, 0) + if err != nil { + return err + } + + dns.setHdr(dh) + return dns.unpack(dh, msg, off) +} + +// Convert a complete message to a string with dig-like output. +func (dns *Msg) String() string { + if dns == nil { + return " MsgHdr" + } + s := dns.MsgHdr.String() + " " + s += "QUERY: " + strconv.Itoa(len(dns.Question)) + ", " + s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", " + s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", " + s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n" + if len(dns.Question) > 0 { + s += "\n;; QUESTION SECTION:\n" + for _, r := range dns.Question { + s += r.String() + "\n" + } + } + if len(dns.Answer) > 0 { + s += "\n;; ANSWER SECTION:\n" + for _, r := range dns.Answer { + if r != nil { + s += r.String() + "\n" + } + } + } + if len(dns.Ns) > 0 { + s += "\n;; AUTHORITY SECTION:\n" + for _, r := range dns.Ns { + if r != nil { + s += r.String() + "\n" + } + } + } + if len(dns.Extra) > 0 { + s += "\n;; ADDITIONAL SECTION:\n" + for _, r := range dns.Extra { + if r != nil { + s += r.String() + "\n" + } + } + } + return s +} + +// isCompressible returns whether the msg may be compressible. +func (dns *Msg) isCompressible() bool { + // If we only have one question, there is nothing we can ever compress. + return len(dns.Question) > 1 || len(dns.Answer) > 0 || + len(dns.Ns) > 0 || len(dns.Extra) > 0 +} + +// Len returns the message length when in (un)compressed wire format. +// If dns.Compress is true compression it is taken into account. Len() +// is provided to be a faster way to get the size of the resulting packet, +// than packing it, measuring the size and discarding the buffer. +func (dns *Msg) Len() int { + // If this message can't be compressed, avoid filling the + // compression map and creating garbage. + if dns.Compress && dns.isCompressible() { + compression := make(map[string]struct{}) + return msgLenWithCompressionMap(dns, compression) + } + + return msgLenWithCompressionMap(dns, nil) +} + +func msgLenWithCompressionMap(dns *Msg, compression map[string]struct{}) int { + l := headerSize + + for _, r := range dns.Question { + l += r.len(l, compression) + } + for _, r := range dns.Answer { + if r != nil { + l += r.len(l, compression) + } + } + for _, r := range dns.Ns { + if r != nil { + l += r.len(l, compression) + } + } + for _, r := range dns.Extra { + if r != nil { + l += r.len(l, compression) + } + } + + return l +} + +func domainNameLen(s string, off int, compression map[string]struct{}, compress bool) int { + if s == "" || s == "." { + return 1 + } + + escaped := strings.Contains(s, "\\") + + if compression != nil && (compress || off < maxCompressionOffset) { + // compressionLenSearch will insert the entry into the compression + // map if it doesn't contain it. + if l, ok := compressionLenSearch(compression, s, off); ok && compress { + if escaped { + return escapedNameLen(s[:l]) + 2 + } + + return l + 2 + } + } + + if escaped { + return escapedNameLen(s) + 1 + } + + return len(s) + 1 +} + +func escapedNameLen(s string) int { + nameLen := len(s) + for i := 0; i < len(s); i++ { + if s[i] != '\\' { + continue + } + + if i+3 < len(s) && isDigit(s[i+1]) && isDigit(s[i+2]) && isDigit(s[i+3]) { + nameLen -= 3 + i += 3 + } else { + nameLen-- + i++ + } + } + + return nameLen +} + +func compressionLenSearch(c map[string]struct{}, s string, msgOff int) (int, bool) { + for off, end := 0, false; !end; off, end = NextLabel(s, off) { + if _, ok := c[s[off:]]; ok { + return off, true + } + + if msgOff+off < maxCompressionOffset { + c[s[off:]] = struct{}{} + } + } + + return 0, false +} + +// Copy returns a new RR which is a deep-copy of r. +func Copy(r RR) RR { return r.copy() } + +// Len returns the length (in octets) of the uncompressed RR in wire format. +func Len(r RR) int { return r.len(0, nil) } + +// Copy returns a new *Msg which is a deep-copy of dns. +func (dns *Msg) Copy() *Msg { return dns.CopyTo(new(Msg)) } + +// CopyTo copies the contents to the provided message using a deep-copy and returns the copy. +func (dns *Msg) CopyTo(r1 *Msg) *Msg { + r1.MsgHdr = dns.MsgHdr + r1.Compress = dns.Compress + + if len(dns.Question) > 0 { + r1.Question = make([]Question, len(dns.Question)) + copy(r1.Question, dns.Question) // TODO(miek): Question is an immutable value, ok to do a shallow-copy + } + + rrArr := make([]RR, len(dns.Answer)+len(dns.Ns)+len(dns.Extra)) + r1.Answer, rrArr = rrArr[:0:len(dns.Answer)], rrArr[len(dns.Answer):] + r1.Ns, rrArr = rrArr[:0:len(dns.Ns)], rrArr[len(dns.Ns):] + r1.Extra = rrArr[:0:len(dns.Extra)] + + for _, r := range dns.Answer { + r1.Answer = append(r1.Answer, r.copy()) + } + + for _, r := range dns.Ns { + r1.Ns = append(r1.Ns, r.copy()) + } + + for _, r := range dns.Extra { + r1.Extra = append(r1.Extra, r.copy()) + } + + return r1 +} + +func (q *Question) pack(msg []byte, off int, compression compressionMap, compress bool) (int, error) { + off, err := packDomainName(q.Name, msg, off, compression, compress) + if err != nil { + return off, err + } + off, err = packUint16(q.Qtype, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(q.Qclass, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func unpackQuestion(msg []byte, off int) (Question, int, error) { + var ( + q Question + err error + ) + q.Name, off, err = UnpackDomainName(msg, off) + if err != nil { + return q, off, err + } + if off == len(msg) { + return q, off, nil + } + q.Qtype, off, err = unpackUint16(msg, off) + if err != nil { + return q, off, err + } + if off == len(msg) { + return q, off, nil + } + q.Qclass, off, err = unpackUint16(msg, off) + if off == len(msg) { + return q, off, nil + } + return q, off, err +} + +func (dh *Header) pack(msg []byte, off int, compression compressionMap, compress bool) (int, error) { + off, err := packUint16(dh.Id, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(dh.Bits, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(dh.Qdcount, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(dh.Ancount, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(dh.Nscount, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(dh.Arcount, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func unpackMsgHdr(msg []byte, off int) (Header, int, error) { + var ( + dh Header + err error + ) + dh.Id, off, err = unpackUint16(msg, off) + if err != nil { + return dh, off, err + } + dh.Bits, off, err = unpackUint16(msg, off) + if err != nil { + return dh, off, err + } + dh.Qdcount, off, err = unpackUint16(msg, off) + if err != nil { + return dh, off, err + } + dh.Ancount, off, err = unpackUint16(msg, off) + if err != nil { + return dh, off, err + } + dh.Nscount, off, err = unpackUint16(msg, off) + if err != nil { + return dh, off, err + } + dh.Arcount, off, err = unpackUint16(msg, off) + if err != nil { + return dh, off, err + } + return dh, off, nil +} + +// setHdr set the header in the dns using the binary data in dh. +func (dns *Msg) setHdr(dh Header) { + dns.Id = dh.Id + dns.Response = dh.Bits&_QR != 0 + dns.Opcode = int(dh.Bits>>11) & 0xF + dns.Authoritative = dh.Bits&_AA != 0 + dns.Truncated = dh.Bits&_TC != 0 + dns.RecursionDesired = dh.Bits&_RD != 0 + dns.RecursionAvailable = dh.Bits&_RA != 0 + dns.Zero = dh.Bits&_Z != 0 // _Z covers the zero bit, which should be zero; not sure why we set it to the opposite. + dns.AuthenticatedData = dh.Bits&_AD != 0 + dns.CheckingDisabled = dh.Bits&_CD != 0 + dns.Rcode = int(dh.Bits & 0xF) +} diff --git a/vendor/github.com/miekg/dns/msg_helpers.go b/vendor/github.com/miekg/dns/msg_helpers.go new file mode 100644 index 0000000..47625ed --- /dev/null +++ b/vendor/github.com/miekg/dns/msg_helpers.go @@ -0,0 +1,833 @@ +package dns + +import ( + "encoding/base32" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "net" + "sort" + "strings" +) + +// helper functions called from the generated zmsg.go + +// These function are named after the tag to help pack/unpack, if there is no tag it is the name +// of the type they pack/unpack (string, int, etc). We prefix all with unpackData or packData, so packDataA or +// packDataDomainName. + +func unpackDataA(msg []byte, off int) (net.IP, int, error) { + if off+net.IPv4len > len(msg) { + return nil, len(msg), &Error{err: "overflow unpacking a"} + } + a := append(make(net.IP, 0, net.IPv4len), msg[off:off+net.IPv4len]...) + off += net.IPv4len + return a, off, nil +} + +func packDataA(a net.IP, msg []byte, off int) (int, error) { + switch len(a) { + case net.IPv4len, net.IPv6len: + // It must be a slice of 4, even if it is 16, we encode only the first 4 + if off+net.IPv4len > len(msg) { + return len(msg), &Error{err: "overflow packing a"} + } + + copy(msg[off:], a.To4()) + off += net.IPv4len + case 0: + // Allowed, for dynamic updates. + default: + return len(msg), &Error{err: "overflow packing a"} + } + return off, nil +} + +func unpackDataAAAA(msg []byte, off int) (net.IP, int, error) { + if off+net.IPv6len > len(msg) { + return nil, len(msg), &Error{err: "overflow unpacking aaaa"} + } + aaaa := append(make(net.IP, 0, net.IPv6len), msg[off:off+net.IPv6len]...) + off += net.IPv6len + return aaaa, off, nil +} + +func packDataAAAA(aaaa net.IP, msg []byte, off int) (int, error) { + switch len(aaaa) { + case net.IPv6len: + if off+net.IPv6len > len(msg) { + return len(msg), &Error{err: "overflow packing aaaa"} + } + + copy(msg[off:], aaaa) + off += net.IPv6len + case 0: + // Allowed, dynamic updates. + default: + return len(msg), &Error{err: "overflow packing aaaa"} + } + return off, nil +} + +// unpackHeader unpacks an RR header, returning the offset to the end of the header and a +// re-sliced msg according to the expected length of the RR. +func unpackHeader(msg []byte, off int) (rr RR_Header, off1 int, truncmsg []byte, err error) { + hdr := RR_Header{} + if off == len(msg) { + return hdr, off, msg, nil + } + + hdr.Name, off, err = UnpackDomainName(msg, off) + if err != nil { + return hdr, len(msg), msg, err + } + hdr.Rrtype, off, err = unpackUint16(msg, off) + if err != nil { + return hdr, len(msg), msg, err + } + hdr.Class, off, err = unpackUint16(msg, off) + if err != nil { + return hdr, len(msg), msg, err + } + hdr.Ttl, off, err = unpackUint32(msg, off) + if err != nil { + return hdr, len(msg), msg, err + } + hdr.Rdlength, off, err = unpackUint16(msg, off) + if err != nil { + return hdr, len(msg), msg, err + } + msg, err = truncateMsgFromRdlength(msg, off, hdr.Rdlength) + return hdr, off, msg, err +} + +// packHeader packs an RR header, returning the offset to the end of the header. +// See PackDomainName for documentation about the compression. +func (hdr RR_Header) packHeader(msg []byte, off int, compression compressionMap, compress bool) (int, error) { + if off == len(msg) { + return off, nil + } + + off, err := packDomainName(hdr.Name, msg, off, compression, compress) + if err != nil { + return len(msg), err + } + off, err = packUint16(hdr.Rrtype, msg, off) + if err != nil { + return len(msg), err + } + off, err = packUint16(hdr.Class, msg, off) + if err != nil { + return len(msg), err + } + off, err = packUint32(hdr.Ttl, msg, off) + if err != nil { + return len(msg), err + } + off, err = packUint16(0, msg, off) // The RDLENGTH field will be set later in packRR. + if err != nil { + return len(msg), err + } + return off, nil +} + +// helper helper functions. + +// truncateMsgFromRdLength truncates msg to match the expected length of the RR. +// Returns an error if msg is smaller than the expected size. +func truncateMsgFromRdlength(msg []byte, off int, rdlength uint16) (truncmsg []byte, err error) { + lenrd := off + int(rdlength) + if lenrd > len(msg) { + return msg, &Error{err: "overflowing header size"} + } + return msg[:lenrd], nil +} + +var base32HexNoPadEncoding = base32.HexEncoding.WithPadding(base32.NoPadding) + +func fromBase32(s []byte) (buf []byte, err error) { + for i, b := range s { + if b >= 'a' && b <= 'z' { + s[i] = b - 32 + } + } + buflen := base32HexNoPadEncoding.DecodedLen(len(s)) + buf = make([]byte, buflen) + n, err := base32HexNoPadEncoding.Decode(buf, s) + buf = buf[:n] + return +} + +func toBase32(b []byte) string { + return base32HexNoPadEncoding.EncodeToString(b) +} + +func fromBase64(s []byte) (buf []byte, err error) { + buflen := base64.StdEncoding.DecodedLen(len(s)) + buf = make([]byte, buflen) + n, err := base64.StdEncoding.Decode(buf, s) + buf = buf[:n] + return +} + +func toBase64(b []byte) string { return base64.StdEncoding.EncodeToString(b) } + +// dynamicUpdate returns true if the Rdlength is zero. +func noRdata(h RR_Header) bool { return h.Rdlength == 0 } + +func unpackUint8(msg []byte, off int) (i uint8, off1 int, err error) { + if off+1 > len(msg) { + return 0, len(msg), &Error{err: "overflow unpacking uint8"} + } + return msg[off], off + 1, nil +} + +func packUint8(i uint8, msg []byte, off int) (off1 int, err error) { + if off+1 > len(msg) { + return len(msg), &Error{err: "overflow packing uint8"} + } + msg[off] = i + return off + 1, nil +} + +func unpackUint16(msg []byte, off int) (i uint16, off1 int, err error) { + if off+2 > len(msg) { + return 0, len(msg), &Error{err: "overflow unpacking uint16"} + } + return binary.BigEndian.Uint16(msg[off:]), off + 2, nil +} + +func packUint16(i uint16, msg []byte, off int) (off1 int, err error) { + if off+2 > len(msg) { + return len(msg), &Error{err: "overflow packing uint16"} + } + binary.BigEndian.PutUint16(msg[off:], i) + return off + 2, nil +} + +func unpackUint32(msg []byte, off int) (i uint32, off1 int, err error) { + if off+4 > len(msg) { + return 0, len(msg), &Error{err: "overflow unpacking uint32"} + } + return binary.BigEndian.Uint32(msg[off:]), off + 4, nil +} + +func packUint32(i uint32, msg []byte, off int) (off1 int, err error) { + if off+4 > len(msg) { + return len(msg), &Error{err: "overflow packing uint32"} + } + binary.BigEndian.PutUint32(msg[off:], i) + return off + 4, nil +} + +func unpackUint48(msg []byte, off int) (i uint64, off1 int, err error) { + if off+6 > len(msg) { + return 0, len(msg), &Error{err: "overflow unpacking uint64 as uint48"} + } + // Used in TSIG where the last 48 bits are occupied, so for now, assume a uint48 (6 bytes) + i = uint64(msg[off])<<40 | uint64(msg[off+1])<<32 | uint64(msg[off+2])<<24 | uint64(msg[off+3])<<16 | + uint64(msg[off+4])<<8 | uint64(msg[off+5]) + off += 6 + return i, off, nil +} + +func packUint48(i uint64, msg []byte, off int) (off1 int, err error) { + if off+6 > len(msg) { + return len(msg), &Error{err: "overflow packing uint64 as uint48"} + } + msg[off] = byte(i >> 40) + msg[off+1] = byte(i >> 32) + msg[off+2] = byte(i >> 24) + msg[off+3] = byte(i >> 16) + msg[off+4] = byte(i >> 8) + msg[off+5] = byte(i) + off += 6 + return off, nil +} + +func unpackUint64(msg []byte, off int) (i uint64, off1 int, err error) { + if off+8 > len(msg) { + return 0, len(msg), &Error{err: "overflow unpacking uint64"} + } + return binary.BigEndian.Uint64(msg[off:]), off + 8, nil +} + +func packUint64(i uint64, msg []byte, off int) (off1 int, err error) { + if off+8 > len(msg) { + return len(msg), &Error{err: "overflow packing uint64"} + } + binary.BigEndian.PutUint64(msg[off:], i) + off += 8 + return off, nil +} + +func unpackString(msg []byte, off int) (string, int, error) { + if off+1 > len(msg) { + return "", off, &Error{err: "overflow unpacking txt"} + } + l := int(msg[off]) + off++ + if off+l > len(msg) { + return "", off, &Error{err: "overflow unpacking txt"} + } + var s strings.Builder + consumed := 0 + for i, b := range msg[off : off+l] { + switch { + case b == '"' || b == '\\': + if consumed == 0 { + s.Grow(l * 2) + } + s.Write(msg[off+consumed : off+i]) + s.WriteByte('\\') + s.WriteByte(b) + consumed = i + 1 + case b < ' ' || b > '~': // unprintable + if consumed == 0 { + s.Grow(l * 2) + } + s.Write(msg[off+consumed : off+i]) + s.WriteString(escapeByte(b)) + consumed = i + 1 + } + } + if consumed == 0 { // no escaping needed + return string(msg[off : off+l]), off + l, nil + } + s.Write(msg[off+consumed : off+l]) + return s.String(), off + l, nil +} + +func packString(s string, msg []byte, off int) (int, error) { + txtTmp := make([]byte, 256*4+1) + off, err := packTxtString(s, msg, off, txtTmp) + if err != nil { + return len(msg), err + } + return off, nil +} + +func unpackStringBase32(msg []byte, off, end int) (string, int, error) { + if end > len(msg) { + return "", len(msg), &Error{err: "overflow unpacking base32"} + } + s := toBase32(msg[off:end]) + return s, end, nil +} + +func packStringBase32(s string, msg []byte, off int) (int, error) { + b32, err := fromBase32([]byte(s)) + if err != nil { + return len(msg), err + } + if off+len(b32) > len(msg) { + return len(msg), &Error{err: "overflow packing base32"} + } + copy(msg[off:off+len(b32)], b32) + off += len(b32) + return off, nil +} + +func unpackStringBase64(msg []byte, off, end int) (string, int, error) { + // Rest of the RR is base64 encoded value, so we don't need an explicit length + // to be set. Thus far all RR's that have base64 encoded fields have those as their + // last one. What we do need is the end of the RR! + if end > len(msg) { + return "", len(msg), &Error{err: "overflow unpacking base64"} + } + s := toBase64(msg[off:end]) + return s, end, nil +} + +func packStringBase64(s string, msg []byte, off int) (int, error) { + b64, err := fromBase64([]byte(s)) + if err != nil { + return len(msg), err + } + if off+len(b64) > len(msg) { + return len(msg), &Error{err: "overflow packing base64"} + } + copy(msg[off:off+len(b64)], b64) + off += len(b64) + return off, nil +} + +func unpackStringHex(msg []byte, off, end int) (string, int, error) { + // Rest of the RR is hex encoded value, so we don't need an explicit length + // to be set. NSEC and TSIG have hex fields with a length field. + // What we do need is the end of the RR! + if end > len(msg) { + return "", len(msg), &Error{err: "overflow unpacking hex"} + } + + s := hex.EncodeToString(msg[off:end]) + return s, end, nil +} + +func packStringHex(s string, msg []byte, off int) (int, error) { + h, err := hex.DecodeString(s) + if err != nil { + return len(msg), err + } + if off+len(h) > len(msg) { + return len(msg), &Error{err: "overflow packing hex"} + } + copy(msg[off:off+len(h)], h) + off += len(h) + return off, nil +} + +func unpackStringAny(msg []byte, off, end int) (string, int, error) { + if end > len(msg) { + return "", len(msg), &Error{err: "overflow unpacking anything"} + } + return string(msg[off:end]), end, nil +} + +func packStringAny(s string, msg []byte, off int) (int, error) { + if off+len(s) > len(msg) { + return len(msg), &Error{err: "overflow packing anything"} + } + copy(msg[off:off+len(s)], s) + off += len(s) + return off, nil +} + +func unpackStringTxt(msg []byte, off int) ([]string, int, error) { + txt, off, err := unpackTxt(msg, off) + if err != nil { + return nil, len(msg), err + } + return txt, off, nil +} + +func packStringTxt(s []string, msg []byte, off int) (int, error) { + txtTmp := make([]byte, 256*4+1) // If the whole string consists out of \DDD we need this many. + off, err := packTxt(s, msg, off, txtTmp) + if err != nil { + return len(msg), err + } + return off, nil +} + +func unpackDataOpt(msg []byte, off int) ([]EDNS0, int, error) { + var edns []EDNS0 +Option: + var code uint16 + if off+4 > len(msg) { + return nil, len(msg), &Error{err: "overflow unpacking opt"} + } + code = binary.BigEndian.Uint16(msg[off:]) + off += 2 + optlen := binary.BigEndian.Uint16(msg[off:]) + off += 2 + if off+int(optlen) > len(msg) { + return nil, len(msg), &Error{err: "overflow unpacking opt"} + } + e := makeDataOpt(code) + if err := e.unpack(msg[off : off+int(optlen)]); err != nil { + return nil, len(msg), err + } + edns = append(edns, e) + off += int(optlen) + + if off < len(msg) { + goto Option + } + + return edns, off, nil +} + +func makeDataOpt(code uint16) EDNS0 { + switch code { + case EDNS0NSID: + return new(EDNS0_NSID) + case EDNS0SUBNET: + return new(EDNS0_SUBNET) + case EDNS0COOKIE: + return new(EDNS0_COOKIE) + case EDNS0EXPIRE: + return new(EDNS0_EXPIRE) + case EDNS0UL: + return new(EDNS0_UL) + case EDNS0LLQ: + return new(EDNS0_LLQ) + case EDNS0DAU: + return new(EDNS0_DAU) + case EDNS0DHU: + return new(EDNS0_DHU) + case EDNS0N3U: + return new(EDNS0_N3U) + case EDNS0PADDING: + return new(EDNS0_PADDING) + default: + e := new(EDNS0_LOCAL) + e.Code = code + return e + } +} + +func packDataOpt(options []EDNS0, msg []byte, off int) (int, error) { + for _, el := range options { + b, err := el.pack() + if err != nil || off+4 > len(msg) { + return len(msg), &Error{err: "overflow packing opt"} + } + binary.BigEndian.PutUint16(msg[off:], el.Option()) // Option code + binary.BigEndian.PutUint16(msg[off+2:], uint16(len(b))) // Length + off += 4 + if off+len(b) > len(msg) { + return len(msg), &Error{err: "overflow packing opt"} + } + // Actual data + copy(msg[off:off+len(b)], b) + off += len(b) + } + return off, nil +} + +func unpackStringOctet(msg []byte, off int) (string, int, error) { + s := string(msg[off:]) + return s, len(msg), nil +} + +func packStringOctet(s string, msg []byte, off int) (int, error) { + txtTmp := make([]byte, 256*4+1) + off, err := packOctetString(s, msg, off, txtTmp) + if err != nil { + return len(msg), err + } + return off, nil +} + +func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) { + var nsec []uint16 + length, window, lastwindow := 0, 0, -1 + for off < len(msg) { + if off+2 > len(msg) { + return nsec, len(msg), &Error{err: "overflow unpacking nsecx"} + } + window = int(msg[off]) + length = int(msg[off+1]) + off += 2 + if window <= lastwindow { + // RFC 4034: Blocks are present in the NSEC RR RDATA in + // increasing numerical order. + return nsec, len(msg), &Error{err: "out of order NSEC block"} + } + if length == 0 { + // RFC 4034: Blocks with no types present MUST NOT be included. + return nsec, len(msg), &Error{err: "empty NSEC block"} + } + if length > 32 { + return nsec, len(msg), &Error{err: "NSEC block too long"} + } + if off+length > len(msg) { + return nsec, len(msg), &Error{err: "overflowing NSEC block"} + } + + // Walk the bytes in the window and extract the type bits + for j, b := range msg[off : off+length] { + // Check the bits one by one, and set the type + if b&0x80 == 0x80 { + nsec = append(nsec, uint16(window*256+j*8+0)) + } + if b&0x40 == 0x40 { + nsec = append(nsec, uint16(window*256+j*8+1)) + } + if b&0x20 == 0x20 { + nsec = append(nsec, uint16(window*256+j*8+2)) + } + if b&0x10 == 0x10 { + nsec = append(nsec, uint16(window*256+j*8+3)) + } + if b&0x8 == 0x8 { + nsec = append(nsec, uint16(window*256+j*8+4)) + } + if b&0x4 == 0x4 { + nsec = append(nsec, uint16(window*256+j*8+5)) + } + if b&0x2 == 0x2 { + nsec = append(nsec, uint16(window*256+j*8+6)) + } + if b&0x1 == 0x1 { + nsec = append(nsec, uint16(window*256+j*8+7)) + } + } + off += length + lastwindow = window + } + return nsec, off, nil +} + +// typeBitMapLen is a helper function which computes the "maximum" length of +// a the NSEC Type BitMap field. +func typeBitMapLen(bitmap []uint16) int { + var l int + var lastwindow, lastlength uint16 + for _, t := range bitmap { + window := t / 256 + length := (t-window*256)/8 + 1 + if window > lastwindow && lastlength != 0 { // New window, jump to the new offset + l += int(lastlength) + 2 + lastlength = 0 + } + if window < lastwindow || length < lastlength { + // packDataNsec would return Error{err: "nsec bits out of order"} here, but + // when computing the length, we want do be liberal. + continue + } + lastwindow, lastlength = window, length + } + l += int(lastlength) + 2 + return l +} + +func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) { + if len(bitmap) == 0 { + return off, nil + } + var lastwindow, lastlength uint16 + for _, t := range bitmap { + window := t / 256 + length := (t-window*256)/8 + 1 + if window > lastwindow && lastlength != 0 { // New window, jump to the new offset + off += int(lastlength) + 2 + lastlength = 0 + } + if window < lastwindow || length < lastlength { + return len(msg), &Error{err: "nsec bits out of order"} + } + if off+2+int(length) > len(msg) { + return len(msg), &Error{err: "overflow packing nsec"} + } + // Setting the window # + msg[off] = byte(window) + // Setting the octets length + msg[off+1] = byte(length) + // Setting the bit value for the type in the right octet + msg[off+1+int(length)] |= byte(1 << (7 - t%8)) + lastwindow, lastlength = window, length + } + off += int(lastlength) + 2 + return off, nil +} + +func unpackDataSVCB(msg []byte, off int) ([]SVCBKeyValue, int, error) { + var xs []SVCBKeyValue + var code uint16 + var length uint16 + var err error + for off < len(msg) { + code, off, err = unpackUint16(msg, off) + if err != nil { + return nil, len(msg), &Error{err: "overflow unpacking SVCB"} + } + length, off, err = unpackUint16(msg, off) + if err != nil || off+int(length) > len(msg) { + return nil, len(msg), &Error{err: "overflow unpacking SVCB"} + } + e := makeSVCBKeyValue(SVCBKey(code)) + if e == nil { + return nil, len(msg), &Error{err: "bad SVCB key"} + } + if err := e.unpack(msg[off : off+int(length)]); err != nil { + return nil, len(msg), err + } + if len(xs) > 0 && e.Key() <= xs[len(xs)-1].Key() { + return nil, len(msg), &Error{err: "SVCB keys not in strictly increasing order"} + } + xs = append(xs, e) + off += int(length) + } + return xs, off, nil +} + +func packDataSVCB(pairs []SVCBKeyValue, msg []byte, off int) (int, error) { + pairs = append([]SVCBKeyValue(nil), pairs...) + sort.Slice(pairs, func(i, j int) bool { + return pairs[i].Key() < pairs[j].Key() + }) + prev := svcb_RESERVED + for _, el := range pairs { + if el.Key() == prev { + return len(msg), &Error{err: "repeated SVCB keys are not allowed"} + } + prev = el.Key() + packed, err := el.pack() + if err != nil { + return len(msg), err + } + off, err = packUint16(uint16(el.Key()), msg, off) + if err != nil { + return len(msg), &Error{err: "overflow packing SVCB"} + } + off, err = packUint16(uint16(len(packed)), msg, off) + if err != nil || off+len(packed) > len(msg) { + return len(msg), &Error{err: "overflow packing SVCB"} + } + copy(msg[off:off+len(packed)], packed) + off += len(packed) + } + return off, nil +} + +func unpackDataDomainNames(msg []byte, off, end int) ([]string, int, error) { + var ( + servers []string + s string + err error + ) + if end > len(msg) { + return nil, len(msg), &Error{err: "overflow unpacking domain names"} + } + for off < end { + s, off, err = UnpackDomainName(msg, off) + if err != nil { + return servers, len(msg), err + } + servers = append(servers, s) + } + return servers, off, nil +} + +func packDataDomainNames(names []string, msg []byte, off int, compression compressionMap, compress bool) (int, error) { + var err error + for _, name := range names { + off, err = packDomainName(name, msg, off, compression, compress) + if err != nil { + return len(msg), err + } + } + return off, nil +} + +func packDataApl(data []APLPrefix, msg []byte, off int) (int, error) { + var err error + for i := range data { + off, err = packDataAplPrefix(&data[i], msg, off) + if err != nil { + return len(msg), err + } + } + return off, nil +} + +func packDataAplPrefix(p *APLPrefix, msg []byte, off int) (int, error) { + if len(p.Network.IP) != len(p.Network.Mask) { + return len(msg), &Error{err: "address and mask lengths don't match"} + } + + var err error + prefix, _ := p.Network.Mask.Size() + addr := p.Network.IP.Mask(p.Network.Mask)[:(prefix+7)/8] + + switch len(p.Network.IP) { + case net.IPv4len: + off, err = packUint16(1, msg, off) + case net.IPv6len: + off, err = packUint16(2, msg, off) + default: + err = &Error{err: "unrecognized address family"} + } + if err != nil { + return len(msg), err + } + + off, err = packUint8(uint8(prefix), msg, off) + if err != nil { + return len(msg), err + } + + var n uint8 + if p.Negation { + n = 0x80 + } + + // trim trailing zero bytes as specified in RFC3123 Sections 4.1 and 4.2. + i := len(addr) - 1 + for ; i >= 0 && addr[i] == 0; i-- { + } + addr = addr[:i+1] + + adflen := uint8(len(addr)) & 0x7f + off, err = packUint8(n|adflen, msg, off) + if err != nil { + return len(msg), err + } + + if off+len(addr) > len(msg) { + return len(msg), &Error{err: "overflow packing APL prefix"} + } + off += copy(msg[off:], addr) + + return off, nil +} + +func unpackDataApl(msg []byte, off int) ([]APLPrefix, int, error) { + var result []APLPrefix + for off < len(msg) { + prefix, end, err := unpackDataAplPrefix(msg, off) + if err != nil { + return nil, len(msg), err + } + off = end + result = append(result, prefix) + } + return result, off, nil +} + +func unpackDataAplPrefix(msg []byte, off int) (APLPrefix, int, error) { + family, off, err := unpackUint16(msg, off) + if err != nil { + return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"} + } + prefix, off, err := unpackUint8(msg, off) + if err != nil { + return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"} + } + nlen, off, err := unpackUint8(msg, off) + if err != nil { + return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL prefix"} + } + + var ip []byte + switch family { + case 1: + ip = make([]byte, net.IPv4len) + case 2: + ip = make([]byte, net.IPv6len) + default: + return APLPrefix{}, len(msg), &Error{err: "unrecognized APL address family"} + } + if int(prefix) > 8*len(ip) { + return APLPrefix{}, len(msg), &Error{err: "APL prefix too long"} + } + afdlen := int(nlen & 0x7f) + if afdlen > len(ip) { + return APLPrefix{}, len(msg), &Error{err: "APL length too long"} + } + if off+afdlen > len(msg) { + return APLPrefix{}, len(msg), &Error{err: "overflow unpacking APL address"} + } + off += copy(ip, msg[off:off+afdlen]) + if afdlen > 0 { + last := ip[afdlen-1] + if last == 0 { + return APLPrefix{}, len(msg), &Error{err: "extra APL address bits"} + } + } + ipnet := net.IPNet{ + IP: ip, + Mask: net.CIDRMask(int(prefix), 8*len(ip)), + } + network := ipnet.IP.Mask(ipnet.Mask) + if !network.Equal(ipnet.IP) { + return APLPrefix{}, len(msg), &Error{err: "invalid APL address length"} + } + + return APLPrefix{ + Negation: (nlen & 0x80) != 0, + Network: ipnet, + }, off, nil +} diff --git a/vendor/github.com/miekg/dns/msg_truncate.go b/vendor/github.com/miekg/dns/msg_truncate.go new file mode 100644 index 0000000..2ddc9a7 --- /dev/null +++ b/vendor/github.com/miekg/dns/msg_truncate.go @@ -0,0 +1,117 @@ +package dns + +// Truncate ensures the reply message will fit into the requested buffer +// size by removing records that exceed the requested size. +// +// It will first check if the reply fits without compression and then with +// compression. If it won't fit with compression, Truncate then walks the +// record adding as many records as possible without exceeding the +// requested buffer size. +// +// If the message fits within the requested size without compression, +// Truncate will set the message's Compress attribute to false. It is +// the caller's responsibility to set it back to true if they wish to +// compress the payload regardless of size. +// +// The TC bit will be set if any records were excluded from the message. +// If the TC bit is already set on the message it will be retained. +// TC indicates that the client should retry over TCP. +// +// According to RFC 2181, the TC bit should only be set if not all of the +// "required" RRs can be included in the response. Unfortunately, we have +// no way of knowing which RRs are required so we set the TC bit if any RR +// had to be omitted from the response. +// +// The appropriate buffer size can be retrieved from the requests OPT +// record, if present, and is transport specific otherwise. dns.MinMsgSize +// should be used for UDP requests without an OPT record, and +// dns.MaxMsgSize for TCP requests without an OPT record. +func (dns *Msg) Truncate(size int) { + if dns.IsTsig() != nil { + // To simplify this implementation, we don't perform + // truncation on responses with a TSIG record. + return + } + + // RFC 6891 mandates that the payload size in an OPT record + // less than 512 (MinMsgSize) bytes must be treated as equal to 512 bytes. + // + // For ease of use, we impose that restriction here. + if size < MinMsgSize { + size = MinMsgSize + } + + l := msgLenWithCompressionMap(dns, nil) // uncompressed length + if l <= size { + // Don't waste effort compressing this message. + dns.Compress = false + return + } + + dns.Compress = true + + edns0 := dns.popEdns0() + if edns0 != nil { + // Account for the OPT record that gets added at the end, + // by subtracting that length from our budget. + // + // The EDNS(0) OPT record must have the root domain and + // it's length is thus unaffected by compression. + size -= Len(edns0) + } + + compression := make(map[string]struct{}) + + l = headerSize + for _, r := range dns.Question { + l += r.len(l, compression) + } + + var numAnswer int + if l < size { + l, numAnswer = truncateLoop(dns.Answer, size, l, compression) + } + + var numNS int + if l < size { + l, numNS = truncateLoop(dns.Ns, size, l, compression) + } + + var numExtra int + if l < size { + _, numExtra = truncateLoop(dns.Extra, size, l, compression) + } + + // See the function documentation for when we set this. + dns.Truncated = dns.Truncated || len(dns.Answer) > numAnswer || + len(dns.Ns) > numNS || len(dns.Extra) > numExtra + + dns.Answer = dns.Answer[:numAnswer] + dns.Ns = dns.Ns[:numNS] + dns.Extra = dns.Extra[:numExtra] + + if edns0 != nil { + // Add the OPT record back onto the additional section. + dns.Extra = append(dns.Extra, edns0) + } +} + +func truncateLoop(rrs []RR, size, l int, compression map[string]struct{}) (int, int) { + for i, r := range rrs { + if r == nil { + continue + } + + l += r.len(l, compression) + if l > size { + // Return size, rather than l prior to this record, + // to prevent any further records being added. + return size, i + } + if l == size { + return l, i + 1 + } + } + + return l, len(rrs) +} diff --git a/vendor/github.com/miekg/dns/nsecx.go b/vendor/github.com/miekg/dns/nsecx.go new file mode 100644 index 0000000..f882681 --- /dev/null +++ b/vendor/github.com/miekg/dns/nsecx.go @@ -0,0 +1,95 @@ +package dns + +import ( + "crypto/sha1" + "encoding/hex" + "strings" +) + +// HashName hashes a string (label) according to RFC 5155. It returns the hashed string in uppercase. +func HashName(label string, ha uint8, iter uint16, salt string) string { + if ha != SHA1 { + return "" + } + + wireSalt := make([]byte, hex.DecodedLen(len(salt))) + n, err := packStringHex(salt, wireSalt, 0) + if err != nil { + return "" + } + wireSalt = wireSalt[:n] + + name := make([]byte, 255) + off, err := PackDomainName(strings.ToLower(label), name, 0, nil, false) + if err != nil { + return "" + } + name = name[:off] + + s := sha1.New() + // k = 0 + s.Write(name) + s.Write(wireSalt) + nsec3 := s.Sum(nil) + + // k > 0 + for k := uint16(0); k < iter; k++ { + s.Reset() + s.Write(nsec3) + s.Write(wireSalt) + nsec3 = s.Sum(nsec3[:0]) + } + + return toBase32(nsec3) +} + +// Cover returns true if a name is covered by the NSEC3 record. +func (rr *NSEC3) Cover(name string) bool { + nameHash := HashName(name, rr.Hash, rr.Iterations, rr.Salt) + owner := strings.ToUpper(rr.Hdr.Name) + labelIndices := Split(owner) + if len(labelIndices) < 2 { + return false + } + ownerHash := owner[:labelIndices[1]-1] + ownerZone := owner[labelIndices[1]:] + if !IsSubDomain(ownerZone, strings.ToUpper(name)) { // name is outside owner zone + return false + } + + nextHash := rr.NextDomain + + // if empty interval found, try cover wildcard hashes so nameHash shouldn't match with ownerHash + if ownerHash == nextHash && nameHash != ownerHash { // empty interval + return true + } + if ownerHash > nextHash { // end of zone + if nameHash > ownerHash { // covered since there is nothing after ownerHash + return true + } + return nameHash < nextHash // if nameHash is before beginning of zone it is covered + } + if nameHash < ownerHash { // nameHash is before ownerHash, not covered + return false + } + return nameHash < nextHash // if nameHash is before nextHash is it covered (between ownerHash and nextHash) +} + +// Match returns true if a name matches the NSEC3 record +func (rr *NSEC3) Match(name string) bool { + nameHash := HashName(name, rr.Hash, rr.Iterations, rr.Salt) + owner := strings.ToUpper(rr.Hdr.Name) + labelIndices := Split(owner) + if len(labelIndices) < 2 { + return false + } + ownerHash := owner[:labelIndices[1]-1] + ownerZone := owner[labelIndices[1]:] + if !IsSubDomain(ownerZone, strings.ToUpper(name)) { // name is outside owner zone + return false + } + if ownerHash == nameHash { + return true + } + return false +} diff --git a/vendor/github.com/miekg/dns/privaterr.go b/vendor/github.com/miekg/dns/privaterr.go new file mode 100644 index 0000000..45c7f26 --- /dev/null +++ b/vendor/github.com/miekg/dns/privaterr.go @@ -0,0 +1,113 @@ +package dns + +import "strings" + +// PrivateRdata is an interface used for implementing "Private Use" RR types, see +// RFC 6895. This allows one to experiment with new RR types, without requesting an +// official type code. Also see dns.PrivateHandle and dns.PrivateHandleRemove. +type PrivateRdata interface { + // String returns the text presentation of the Rdata of the Private RR. + String() string + // Parse parses the Rdata of the private RR. + Parse([]string) error + // Pack is used when packing a private RR into a buffer. + Pack([]byte) (int, error) + // Unpack is used when unpacking a private RR from a buffer. + Unpack([]byte) (int, error) + // Copy copies the Rdata into the PrivateRdata argument. + Copy(PrivateRdata) error + // Len returns the length in octets of the Rdata. + Len() int +} + +// PrivateRR represents an RR that uses a PrivateRdata user-defined type. +// It mocks normal RRs and implements dns.RR interface. +type PrivateRR struct { + Hdr RR_Header + Data PrivateRdata + + generator func() PrivateRdata // for copy +} + +// Header return the RR header of r. +func (r *PrivateRR) Header() *RR_Header { return &r.Hdr } + +func (r *PrivateRR) String() string { return r.Hdr.String() + r.Data.String() } + +// Private len and copy parts to satisfy RR interface. +func (r *PrivateRR) len(off int, compression map[string]struct{}) int { + l := r.Hdr.len(off, compression) + l += r.Data.Len() + return l +} + +func (r *PrivateRR) copy() RR { + // make new RR like this: + rr := &PrivateRR{r.Hdr, r.generator(), r.generator} + + if err := r.Data.Copy(rr.Data); err != nil { + panic("dns: got value that could not be used to copy Private rdata: " + err.Error()) + } + + return rr +} + +func (r *PrivateRR) pack(msg []byte, off int, compression compressionMap, compress bool) (int, error) { + n, err := r.Data.Pack(msg[off:]) + if err != nil { + return len(msg), err + } + off += n + return off, nil +} + +func (r *PrivateRR) unpack(msg []byte, off int) (int, error) { + off1, err := r.Data.Unpack(msg[off:]) + off += off1 + return off, err +} + +func (r *PrivateRR) parse(c *zlexer, origin string) *ParseError { + var l lex + text := make([]string, 0, 2) // could be 0..N elements, median is probably 1 +Fetch: + for { + // TODO(miek): we could also be returning _QUOTE, this might or might not + // be an issue (basically parsing TXT becomes hard) + switch l, _ = c.Next(); l.value { + case zNewline, zEOF: + break Fetch + case zString: + text = append(text, l.token) + } + } + + err := r.Data.Parse(text) + if err != nil { + return &ParseError{"", err.Error(), l} + } + + return nil +} + +func (r1 *PrivateRR) isDuplicate(r2 RR) bool { return false } + +// PrivateHandle registers a private resource record type. It requires +// string and numeric representation of private RR type and generator function as argument. +func PrivateHandle(rtypestr string, rtype uint16, generator func() PrivateRdata) { + rtypestr = strings.ToUpper(rtypestr) + + TypeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator(), generator} } + TypeToString[rtype] = rtypestr + StringToType[rtypestr] = rtype +} + +// PrivateHandleRemove removes definitions required to support private RR type. +func PrivateHandleRemove(rtype uint16) { + rtypestr, ok := TypeToString[rtype] + if ok { + delete(TypeToRR, rtype) + delete(TypeToString, rtype) + delete(StringToType, rtypestr) + } +} diff --git a/vendor/github.com/miekg/dns/reverse.go b/vendor/github.com/miekg/dns/reverse.go new file mode 100644 index 0000000..28151af --- /dev/null +++ b/vendor/github.com/miekg/dns/reverse.go @@ -0,0 +1,52 @@ +package dns + +// StringToType is the reverse of TypeToString, needed for string parsing. +var StringToType = reverseInt16(TypeToString) + +// StringToClass is the reverse of ClassToString, needed for string parsing. +var StringToClass = reverseInt16(ClassToString) + +// StringToOpcode is a map of opcodes to strings. +var StringToOpcode = reverseInt(OpcodeToString) + +// StringToRcode is a map of rcodes to strings. +var StringToRcode = reverseInt(RcodeToString) + +func init() { + // Preserve previous NOTIMP typo, see github.com/miekg/dns/issues/733. + StringToRcode["NOTIMPL"] = RcodeNotImplemented +} + +// StringToAlgorithm is the reverse of AlgorithmToString. +var StringToAlgorithm = reverseInt8(AlgorithmToString) + +// StringToHash is a map of names to hash IDs. +var StringToHash = reverseInt8(HashToString) + +// StringToCertType is the reverseof CertTypeToString. +var StringToCertType = reverseInt16(CertTypeToString) + +// Reverse a map +func reverseInt8(m map[uint8]string) map[string]uint8 { + n := make(map[string]uint8, len(m)) + for u, s := range m { + n[s] = u + } + return n +} + +func reverseInt16(m map[uint16]string) map[string]uint16 { + n := make(map[string]uint16, len(m)) + for u, s := range m { + n[s] = u + } + return n +} + +func reverseInt(m map[int]string) map[string]int { + n := make(map[string]int, len(m)) + for u, s := range m { + n[s] = u + } + return n +} diff --git a/vendor/github.com/miekg/dns/sanitize.go b/vendor/github.com/miekg/dns/sanitize.go new file mode 100644 index 0000000..a638e86 --- /dev/null +++ b/vendor/github.com/miekg/dns/sanitize.go @@ -0,0 +1,86 @@ +package dns + +// Dedup removes identical RRs from rrs. It preserves the original ordering. +// The lowest TTL of any duplicates is used in the remaining one. Dedup modifies +// rrs. +// m is used to store the RRs temporary. If it is nil a new map will be allocated. +func Dedup(rrs []RR, m map[string]RR) []RR { + + if m == nil { + m = make(map[string]RR) + } + // Save the keys, so we don't have to call normalizedString twice. + keys := make([]*string, 0, len(rrs)) + + for _, r := range rrs { + key := normalizedString(r) + keys = append(keys, &key) + if mr, ok := m[key]; ok { + // Shortest TTL wins. + rh, mrh := r.Header(), mr.Header() + if mrh.Ttl > rh.Ttl { + mrh.Ttl = rh.Ttl + } + continue + } + + m[key] = r + } + // If the length of the result map equals the amount of RRs we got, + // it means they were all different. We can then just return the original rrset. + if len(m) == len(rrs) { + return rrs + } + + j := 0 + for i, r := range rrs { + // If keys[i] lives in the map, we should copy and remove it. + if _, ok := m[*keys[i]]; ok { + delete(m, *keys[i]) + rrs[j] = r + j++ + } + + if len(m) == 0 { + break + } + } + + return rrs[:j] +} + +// normalizedString returns a normalized string from r. The TTL +// is removed and the domain name is lowercased. We go from this: +// DomainNameTTLCLASSTYPERDATA to: +// lowercasenameCLASSTYPE... +func normalizedString(r RR) string { + // A string Go DNS makes has: domainnameTTL... + b := []byte(r.String()) + + // find the first non-escaped tab, then another, so we capture where the TTL lives. + esc := false + ttlStart, ttlEnd := 0, 0 + for i := 0; i < len(b) && ttlEnd == 0; i++ { + switch { + case b[i] == '\\': + esc = !esc + case b[i] == '\t' && !esc: + if ttlStart == 0 { + ttlStart = i + continue + } + if ttlEnd == 0 { + ttlEnd = i + } + case b[i] >= 'A' && b[i] <= 'Z' && !esc: + b[i] += 32 + default: + esc = false + } + } + + // remove TTL. + copy(b[ttlStart:], b[ttlEnd:]) + cut := ttlEnd - ttlStart + return string(b[:len(b)-cut]) +} diff --git a/vendor/github.com/miekg/dns/scan.go b/vendor/github.com/miekg/dns/scan.go new file mode 100644 index 0000000..39055bd --- /dev/null +++ b/vendor/github.com/miekg/dns/scan.go @@ -0,0 +1,1365 @@ +package dns + +import ( + "bufio" + "fmt" + "io" + "os" + "path/filepath" + "strconv" + "strings" +) + +const maxTok = 2048 // Largest token we can return. + +// The maximum depth of $INCLUDE directives supported by the +// ZoneParser API. +const maxIncludeDepth = 7 + +// Tokinize a RFC 1035 zone file. The tokenizer will normalize it: +// * Add ownernames if they are left blank; +// * Suppress sequences of spaces; +// * Make each RR fit on one line (_NEWLINE is send as last) +// * Handle comments: ; +// * Handle braces - anywhere. +const ( + // Zonefile + zEOF = iota + zString + zBlank + zQuote + zNewline + zRrtpe + zOwner + zClass + zDirOrigin // $ORIGIN + zDirTTL // $TTL + zDirInclude // $INCLUDE + zDirGenerate // $GENERATE + + // Privatekey file + zValue + zKey + + zExpectOwnerDir // Ownername + zExpectOwnerBl // Whitespace after the ownername + zExpectAny // Expect rrtype, ttl or class + zExpectAnyNoClass // Expect rrtype or ttl + zExpectAnyNoClassBl // The whitespace after _EXPECT_ANY_NOCLASS + zExpectAnyNoTTL // Expect rrtype or class + zExpectAnyNoTTLBl // Whitespace after _EXPECT_ANY_NOTTL + zExpectRrtype // Expect rrtype + zExpectRrtypeBl // Whitespace BEFORE rrtype + zExpectRdata // The first element of the rdata + zExpectDirTTLBl // Space after directive $TTL + zExpectDirTTL // Directive $TTL + zExpectDirOriginBl // Space after directive $ORIGIN + zExpectDirOrigin // Directive $ORIGIN + zExpectDirIncludeBl // Space after directive $INCLUDE + zExpectDirInclude // Directive $INCLUDE + zExpectDirGenerate // Directive $GENERATE + zExpectDirGenerateBl // Space after directive $GENERATE +) + +// ParseError is a parsing error. It contains the parse error and the location in the io.Reader +// where the error occurred. +type ParseError struct { + file string + err string + lex lex +} + +func (e *ParseError) Error() (s string) { + if e.file != "" { + s = e.file + ": " + } + s += "dns: " + e.err + ": " + strconv.QuoteToASCII(e.lex.token) + " at line: " + + strconv.Itoa(e.lex.line) + ":" + strconv.Itoa(e.lex.column) + return +} + +type lex struct { + token string // text of the token + err bool // when true, token text has lexer error + value uint8 // value: zString, _BLANK, etc. + torc uint16 // type or class as parsed in the lexer, we only need to look this up in the grammar + line int // line in the file + column int // column in the file +} + +// ttlState describes the state necessary to fill in an omitted RR TTL +type ttlState struct { + ttl uint32 // ttl is the current default TTL + isByDirective bool // isByDirective indicates whether ttl was set by a $TTL directive +} + +// NewRR reads the RR contained in the string s. Only the first RR is returned. +// If s contains no records, NewRR will return nil with no error. +// +// The class defaults to IN and TTL defaults to 3600. The full zone file syntax +// like $TTL, $ORIGIN, etc. is supported. All fields of the returned RR are +// set, except RR.Header().Rdlength which is set to 0. +func NewRR(s string) (RR, error) { + if len(s) > 0 && s[len(s)-1] != '\n' { // We need a closing newline + return ReadRR(strings.NewReader(s+"\n"), "") + } + return ReadRR(strings.NewReader(s), "") +} + +// ReadRR reads the RR contained in r. +// +// The string file is used in error reporting and to resolve relative +// $INCLUDE directives. +// +// See NewRR for more documentation. +func ReadRR(r io.Reader, file string) (RR, error) { + zp := NewZoneParser(r, ".", file) + zp.SetDefaultTTL(defaultTtl) + zp.SetIncludeAllowed(true) + rr, _ := zp.Next() + return rr, zp.Err() +} + +// ZoneParser is a parser for an RFC 1035 style zonefile. +// +// Each parsed RR in the zone is returned sequentially from Next. An +// optional comment can be retrieved with Comment. +// +// The directives $INCLUDE, $ORIGIN, $TTL and $GENERATE are all +// supported. Although $INCLUDE is disabled by default. +// Note that $GENERATE's range support up to a maximum of 65535 steps. +// +// Basic usage pattern when reading from a string (z) containing the +// zone data: +// +// zp := NewZoneParser(strings.NewReader(z), "", "") +// +// for rr, ok := zp.Next(); ok; rr, ok = zp.Next() { +// // Do something with rr +// } +// +// if err := zp.Err(); err != nil { +// // log.Println(err) +// } +// +// Comments specified after an RR (and on the same line!) are +// returned too: +// +// foo. IN A 10.0.0.1 ; this is a comment +// +// The text "; this is comment" is returned from Comment. Comments inside +// the RR are returned concatenated along with the RR. Comments on a line +// by themselves are discarded. +type ZoneParser struct { + c *zlexer + + parseErr *ParseError + + origin string + file string + + defttl *ttlState + + h RR_Header + + // sub is used to parse $INCLUDE files and $GENERATE directives. + // Next, by calling subNext, forwards the resulting RRs from this + // sub parser to the calling code. + sub *ZoneParser + osFile *os.File + + includeDepth uint8 + + includeAllowed bool + generateDisallowed bool +} + +// NewZoneParser returns an RFC 1035 style zonefile parser that reads +// from r. +// +// The string file is used in error reporting and to resolve relative +// $INCLUDE directives. The string origin is used as the initial +// origin, as if the file would start with an $ORIGIN directive. +func NewZoneParser(r io.Reader, origin, file string) *ZoneParser { + var pe *ParseError + if origin != "" { + origin = Fqdn(origin) + if _, ok := IsDomainName(origin); !ok { + pe = &ParseError{file, "bad initial origin name", lex{}} + } + } + + return &ZoneParser{ + c: newZLexer(r), + + parseErr: pe, + + origin: origin, + file: file, + } +} + +// SetDefaultTTL sets the parsers default TTL to ttl. +func (zp *ZoneParser) SetDefaultTTL(ttl uint32) { + zp.defttl = &ttlState{ttl, false} +} + +// SetIncludeAllowed controls whether $INCLUDE directives are +// allowed. $INCLUDE directives are not supported by default. +// +// The $INCLUDE directive will open and read from a user controlled +// file on the system. Even if the file is not a valid zonefile, the +// contents of the file may be revealed in error messages, such as: +// +// /etc/passwd: dns: not a TTL: "root:x:0:0:root:/root:/bin/bash" at line: 1:31 +// /etc/shadow: dns: not a TTL: "root:$6$::0:99999:7:::" at line: 1:125 +func (zp *ZoneParser) SetIncludeAllowed(v bool) { + zp.includeAllowed = v +} + +// Err returns the first non-EOF error that was encountered by the +// ZoneParser. +func (zp *ZoneParser) Err() error { + if zp.parseErr != nil { + return zp.parseErr + } + + if zp.sub != nil { + if err := zp.sub.Err(); err != nil { + return err + } + } + + return zp.c.Err() +} + +func (zp *ZoneParser) setParseError(err string, l lex) (RR, bool) { + zp.parseErr = &ParseError{zp.file, err, l} + return nil, false +} + +// Comment returns an optional text comment that occurred alongside +// the RR. +func (zp *ZoneParser) Comment() string { + if zp.parseErr != nil { + return "" + } + + if zp.sub != nil { + return zp.sub.Comment() + } + + return zp.c.Comment() +} + +func (zp *ZoneParser) subNext() (RR, bool) { + if rr, ok := zp.sub.Next(); ok { + return rr, true + } + + if zp.sub.osFile != nil { + zp.sub.osFile.Close() + zp.sub.osFile = nil + } + + if zp.sub.Err() != nil { + // We have errors to surface. + return nil, false + } + + zp.sub = nil + return zp.Next() +} + +// Next advances the parser to the next RR in the zonefile and +// returns the (RR, true). It will return (nil, false) when the +// parsing stops, either by reaching the end of the input or an +// error. After Next returns (nil, false), the Err method will return +// any error that occurred during parsing. +func (zp *ZoneParser) Next() (RR, bool) { + if zp.parseErr != nil { + return nil, false + } + if zp.sub != nil { + return zp.subNext() + } + + // 6 possible beginnings of a line (_ is a space): + // + // 0. zRRTYPE -> all omitted until the rrtype + // 1. zOwner _ zRrtype -> class/ttl omitted + // 2. zOwner _ zString _ zRrtype -> class omitted + // 3. zOwner _ zString _ zClass _ zRrtype -> ttl/class + // 4. zOwner _ zClass _ zRrtype -> ttl omitted + // 5. zOwner _ zClass _ zString _ zRrtype -> class/ttl (reversed) + // + // After detecting these, we know the zRrtype so we can jump to functions + // handling the rdata for each of these types. + + st := zExpectOwnerDir // initial state + h := &zp.h + + for l, ok := zp.c.Next(); ok; l, ok = zp.c.Next() { + // zlexer spotted an error already + if l.err { + return zp.setParseError(l.token, l) + } + + switch st { + case zExpectOwnerDir: + // We can also expect a directive, like $TTL or $ORIGIN + if zp.defttl != nil { + h.Ttl = zp.defttl.ttl + } + + h.Class = ClassINET + + switch l.value { + case zNewline: + st = zExpectOwnerDir + case zOwner: + name, ok := toAbsoluteName(l.token, zp.origin) + if !ok { + return zp.setParseError("bad owner name", l) + } + + h.Name = name + + st = zExpectOwnerBl + case zDirTTL: + st = zExpectDirTTLBl + case zDirOrigin: + st = zExpectDirOriginBl + case zDirInclude: + st = zExpectDirIncludeBl + case zDirGenerate: + st = zExpectDirGenerateBl + case zRrtpe: + h.Rrtype = l.torc + + st = zExpectRdata + case zClass: + h.Class = l.torc + + st = zExpectAnyNoClassBl + case zBlank: + // Discard, can happen when there is nothing on the + // line except the RR type + case zString: + ttl, ok := stringToTTL(l.token) + if !ok { + return zp.setParseError("not a TTL", l) + } + + h.Ttl = ttl + + if zp.defttl == nil || !zp.defttl.isByDirective { + zp.defttl = &ttlState{ttl, false} + } + + st = zExpectAnyNoTTLBl + default: + return zp.setParseError("syntax error at beginning", l) + } + case zExpectDirIncludeBl: + if l.value != zBlank { + return zp.setParseError("no blank after $INCLUDE-directive", l) + } + + st = zExpectDirInclude + case zExpectDirInclude: + if l.value != zString { + return zp.setParseError("expecting $INCLUDE value, not this...", l) + } + + neworigin := zp.origin // There may be optionally a new origin set after the filename, if not use current one + switch l, _ := zp.c.Next(); l.value { + case zBlank: + l, _ := zp.c.Next() + if l.value == zString { + name, ok := toAbsoluteName(l.token, zp.origin) + if !ok { + return zp.setParseError("bad origin name", l) + } + + neworigin = name + } + case zNewline, zEOF: + // Ok + default: + return zp.setParseError("garbage after $INCLUDE", l) + } + + if !zp.includeAllowed { + return zp.setParseError("$INCLUDE directive not allowed", l) + } + if zp.includeDepth >= maxIncludeDepth { + return zp.setParseError("too deeply nested $INCLUDE", l) + } + + // Start with the new file + includePath := l.token + if !filepath.IsAbs(includePath) { + includePath = filepath.Join(filepath.Dir(zp.file), includePath) + } + + r1, e1 := os.Open(includePath) + if e1 != nil { + var as string + if !filepath.IsAbs(l.token) { + as = fmt.Sprintf(" as `%s'", includePath) + } + + msg := fmt.Sprintf("failed to open `%s'%s: %v", l.token, as, e1) + return zp.setParseError(msg, l) + } + + zp.sub = NewZoneParser(r1, neworigin, includePath) + zp.sub.defttl, zp.sub.includeDepth, zp.sub.osFile = zp.defttl, zp.includeDepth+1, r1 + zp.sub.SetIncludeAllowed(true) + return zp.subNext() + case zExpectDirTTLBl: + if l.value != zBlank { + return zp.setParseError("no blank after $TTL-directive", l) + } + + st = zExpectDirTTL + case zExpectDirTTL: + if l.value != zString { + return zp.setParseError("expecting $TTL value, not this...", l) + } + + if err := slurpRemainder(zp.c); err != nil { + return zp.setParseError(err.err, err.lex) + } + + ttl, ok := stringToTTL(l.token) + if !ok { + return zp.setParseError("expecting $TTL value, not this...", l) + } + + zp.defttl = &ttlState{ttl, true} + + st = zExpectOwnerDir + case zExpectDirOriginBl: + if l.value != zBlank { + return zp.setParseError("no blank after $ORIGIN-directive", l) + } + + st = zExpectDirOrigin + case zExpectDirOrigin: + if l.value != zString { + return zp.setParseError("expecting $ORIGIN value, not this...", l) + } + + if err := slurpRemainder(zp.c); err != nil { + return zp.setParseError(err.err, err.lex) + } + + name, ok := toAbsoluteName(l.token, zp.origin) + if !ok { + return zp.setParseError("bad origin name", l) + } + + zp.origin = name + + st = zExpectOwnerDir + case zExpectDirGenerateBl: + if l.value != zBlank { + return zp.setParseError("no blank after $GENERATE-directive", l) + } + + st = zExpectDirGenerate + case zExpectDirGenerate: + if zp.generateDisallowed { + return zp.setParseError("nested $GENERATE directive not allowed", l) + } + if l.value != zString { + return zp.setParseError("expecting $GENERATE value, not this...", l) + } + + return zp.generate(l) + case zExpectOwnerBl: + if l.value != zBlank { + return zp.setParseError("no blank after owner", l) + } + + st = zExpectAny + case zExpectAny: + switch l.value { + case zRrtpe: + if zp.defttl == nil { + return zp.setParseError("missing TTL with no previous value", l) + } + + h.Rrtype = l.torc + + st = zExpectRdata + case zClass: + h.Class = l.torc + + st = zExpectAnyNoClassBl + case zString: + ttl, ok := stringToTTL(l.token) + if !ok { + return zp.setParseError("not a TTL", l) + } + + h.Ttl = ttl + + if zp.defttl == nil || !zp.defttl.isByDirective { + zp.defttl = &ttlState{ttl, false} + } + + st = zExpectAnyNoTTLBl + default: + return zp.setParseError("expecting RR type, TTL or class, not this...", l) + } + case zExpectAnyNoClassBl: + if l.value != zBlank { + return zp.setParseError("no blank before class", l) + } + + st = zExpectAnyNoClass + case zExpectAnyNoTTLBl: + if l.value != zBlank { + return zp.setParseError("no blank before TTL", l) + } + + st = zExpectAnyNoTTL + case zExpectAnyNoTTL: + switch l.value { + case zClass: + h.Class = l.torc + + st = zExpectRrtypeBl + case zRrtpe: + h.Rrtype = l.torc + + st = zExpectRdata + default: + return zp.setParseError("expecting RR type or class, not this...", l) + } + case zExpectAnyNoClass: + switch l.value { + case zString: + ttl, ok := stringToTTL(l.token) + if !ok { + return zp.setParseError("not a TTL", l) + } + + h.Ttl = ttl + + if zp.defttl == nil || !zp.defttl.isByDirective { + zp.defttl = &ttlState{ttl, false} + } + + st = zExpectRrtypeBl + case zRrtpe: + h.Rrtype = l.torc + + st = zExpectRdata + default: + return zp.setParseError("expecting RR type or TTL, not this...", l) + } + case zExpectRrtypeBl: + if l.value != zBlank { + return zp.setParseError("no blank before RR type", l) + } + + st = zExpectRrtype + case zExpectRrtype: + if l.value != zRrtpe { + return zp.setParseError("unknown RR type", l) + } + + h.Rrtype = l.torc + + st = zExpectRdata + case zExpectRdata: + var ( + rr RR + parseAsRFC3597 bool + ) + if newFn, ok := TypeToRR[h.Rrtype]; ok { + rr = newFn() + *rr.Header() = *h + + // We may be parsing a known RR type using the RFC3597 format. + // If so, we handle that here in a generic way. + // + // This is also true for PrivateRR types which will have the + // RFC3597 parsing done for them and the Unpack method called + // to populate the RR instead of simply deferring to Parse. + if zp.c.Peek().token == "\\#" { + parseAsRFC3597 = true + } + } else { + rr = &RFC3597{Hdr: *h} + } + + _, isPrivate := rr.(*PrivateRR) + if !isPrivate && zp.c.Peek().token == "" { + // This is a dynamic update rr. + + // TODO(tmthrgd): Previously slurpRemainder was only called + // for certain RR types, which may have been important. + if err := slurpRemainder(zp.c); err != nil { + return zp.setParseError(err.err, err.lex) + } + + return rr, true + } else if l.value == zNewline { + return zp.setParseError("unexpected newline", l) + } + + parseAsRR := rr + if parseAsRFC3597 { + parseAsRR = &RFC3597{Hdr: *h} + } + + if err := parseAsRR.parse(zp.c, zp.origin); err != nil { + // err is a concrete *ParseError without the file field set. + // The setParseError call below will construct a new + // *ParseError with file set to zp.file. + + // err.lex may be nil in which case we substitute our current + // lex token. + if err.lex == (lex{}) { + return zp.setParseError(err.err, l) + } + + return zp.setParseError(err.err, err.lex) + } + + if parseAsRFC3597 { + err := parseAsRR.(*RFC3597).fromRFC3597(rr) + if err != nil { + return zp.setParseError(err.Error(), l) + } + } + + return rr, true + } + } + + // If we get here, we and the h.Rrtype is still zero, we haven't parsed anything, this + // is not an error, because an empty zone file is still a zone file. + return nil, false +} + +type zlexer struct { + br io.ByteReader + + readErr error + + line int + column int + + comBuf string + comment string + + l lex + cachedL *lex + + brace int + quote bool + space bool + commt bool + rrtype bool + owner bool + + nextL bool + + eol bool // end-of-line +} + +func newZLexer(r io.Reader) *zlexer { + br, ok := r.(io.ByteReader) + if !ok { + br = bufio.NewReaderSize(r, 1024) + } + + return &zlexer{ + br: br, + + line: 1, + + owner: true, + } +} + +func (zl *zlexer) Err() error { + if zl.readErr == io.EOF { + return nil + } + + return zl.readErr +} + +// readByte returns the next byte from the input +func (zl *zlexer) readByte() (byte, bool) { + if zl.readErr != nil { + return 0, false + } + + c, err := zl.br.ReadByte() + if err != nil { + zl.readErr = err + return 0, false + } + + // delay the newline handling until the next token is delivered, + // fixes off-by-one errors when reporting a parse error. + if zl.eol { + zl.line++ + zl.column = 0 + zl.eol = false + } + + if c == '\n' { + zl.eol = true + } else { + zl.column++ + } + + return c, true +} + +func (zl *zlexer) Peek() lex { + if zl.nextL { + return zl.l + } + + l, ok := zl.Next() + if !ok { + return l + } + + if zl.nextL { + // Cache l. Next returns zl.cachedL then zl.l. + zl.cachedL = &l + } else { + // In this case l == zl.l, so we just tell Next to return zl.l. + zl.nextL = true + } + + return l +} + +func (zl *zlexer) Next() (lex, bool) { + l := &zl.l + switch { + case zl.cachedL != nil: + l, zl.cachedL = zl.cachedL, nil + return *l, true + case zl.nextL: + zl.nextL = false + return *l, true + case l.err: + // Parsing errors should be sticky. + return lex{value: zEOF}, false + } + + var ( + str [maxTok]byte // Hold string text + com [maxTok]byte // Hold comment text + + stri int // Offset in str (0 means empty) + comi int // Offset in com (0 means empty) + + escape bool + ) + + if zl.comBuf != "" { + comi = copy(com[:], zl.comBuf) + zl.comBuf = "" + } + + zl.comment = "" + + for x, ok := zl.readByte(); ok; x, ok = zl.readByte() { + l.line, l.column = zl.line, zl.column + + if stri >= len(str) { + l.token = "token length insufficient for parsing" + l.err = true + return *l, true + } + if comi >= len(com) { + l.token = "comment length insufficient for parsing" + l.err = true + return *l, true + } + + switch x { + case ' ', '\t': + if escape || zl.quote { + // Inside quotes or escaped this is legal. + str[stri] = x + stri++ + + escape = false + break + } + + if zl.commt { + com[comi] = x + comi++ + break + } + + var retL lex + if stri == 0 { + // Space directly in the beginning, handled in the grammar + } else if zl.owner { + // If we have a string and its the first, make it an owner + l.value = zOwner + l.token = string(str[:stri]) + + // escape $... start with a \ not a $, so this will work + switch strings.ToUpper(l.token) { + case "$TTL": + l.value = zDirTTL + case "$ORIGIN": + l.value = zDirOrigin + case "$INCLUDE": + l.value = zDirInclude + case "$GENERATE": + l.value = zDirGenerate + } + + retL = *l + } else { + l.value = zString + l.token = string(str[:stri]) + + if !zl.rrtype { + tokenUpper := strings.ToUpper(l.token) + if t, ok := StringToType[tokenUpper]; ok { + l.value = zRrtpe + l.torc = t + + zl.rrtype = true + } else if strings.HasPrefix(tokenUpper, "TYPE") { + t, ok := typeToInt(l.token) + if !ok { + l.token = "unknown RR type" + l.err = true + return *l, true + } + + l.value = zRrtpe + l.torc = t + + zl.rrtype = true + } + + if t, ok := StringToClass[tokenUpper]; ok { + l.value = zClass + l.torc = t + } else if strings.HasPrefix(tokenUpper, "CLASS") { + t, ok := classToInt(l.token) + if !ok { + l.token = "unknown class" + l.err = true + return *l, true + } + + l.value = zClass + l.torc = t + } + } + + retL = *l + } + + zl.owner = false + + if !zl.space { + zl.space = true + + l.value = zBlank + l.token = " " + + if retL == (lex{}) { + return *l, true + } + + zl.nextL = true + } + + if retL != (lex{}) { + return retL, true + } + case ';': + if escape || zl.quote { + // Inside quotes or escaped this is legal. + str[stri] = x + stri++ + + escape = false + break + } + + zl.commt = true + zl.comBuf = "" + + if comi > 1 { + // A newline was previously seen inside a comment that + // was inside braces and we delayed adding it until now. + com[comi] = ' ' // convert newline to space + comi++ + if comi >= len(com) { + l.token = "comment length insufficient for parsing" + l.err = true + return *l, true + } + } + + com[comi] = ';' + comi++ + + if stri > 0 { + zl.comBuf = string(com[:comi]) + + l.value = zString + l.token = string(str[:stri]) + return *l, true + } + case '\r': + escape = false + + if zl.quote { + str[stri] = x + stri++ + } + + // discard if outside of quotes + case '\n': + escape = false + + // Escaped newline + if zl.quote { + str[stri] = x + stri++ + break + } + + if zl.commt { + // Reset a comment + zl.commt = false + zl.rrtype = false + + // If not in a brace this ends the comment AND the RR + if zl.brace == 0 { + zl.owner = true + + l.value = zNewline + l.token = "\n" + zl.comment = string(com[:comi]) + return *l, true + } + + zl.comBuf = string(com[:comi]) + break + } + + if zl.brace == 0 { + // If there is previous text, we should output it here + var retL lex + if stri != 0 { + l.value = zString + l.token = string(str[:stri]) + + if !zl.rrtype { + tokenUpper := strings.ToUpper(l.token) + if t, ok := StringToType[tokenUpper]; ok { + zl.rrtype = true + + l.value = zRrtpe + l.torc = t + } + } + + retL = *l + } + + l.value = zNewline + l.token = "\n" + + zl.comment = zl.comBuf + zl.comBuf = "" + zl.rrtype = false + zl.owner = true + + if retL != (lex{}) { + zl.nextL = true + return retL, true + } + + return *l, true + } + case '\\': + // comments do not get escaped chars, everything is copied + if zl.commt { + com[comi] = x + comi++ + break + } + + // something already escaped must be in string + if escape { + str[stri] = x + stri++ + + escape = false + break + } + + // something escaped outside of string gets added to string + str[stri] = x + stri++ + + escape = true + case '"': + if zl.commt { + com[comi] = x + comi++ + break + } + + if escape { + str[stri] = x + stri++ + + escape = false + break + } + + zl.space = false + + // send previous gathered text and the quote + var retL lex + if stri != 0 { + l.value = zString + l.token = string(str[:stri]) + + retL = *l + } + + // send quote itself as separate token + l.value = zQuote + l.token = "\"" + + zl.quote = !zl.quote + + if retL != (lex{}) { + zl.nextL = true + return retL, true + } + + return *l, true + case '(', ')': + if zl.commt { + com[comi] = x + comi++ + break + } + + if escape || zl.quote { + // Inside quotes or escaped this is legal. + str[stri] = x + stri++ + + escape = false + break + } + + switch x { + case ')': + zl.brace-- + + if zl.brace < 0 { + l.token = "extra closing brace" + l.err = true + return *l, true + } + case '(': + zl.brace++ + } + default: + escape = false + + if zl.commt { + com[comi] = x + comi++ + break + } + + str[stri] = x + stri++ + + zl.space = false + } + } + + if zl.readErr != nil && zl.readErr != io.EOF { + // Don't return any tokens after a read error occurs. + return lex{value: zEOF}, false + } + + var retL lex + if stri > 0 { + // Send remainder of str + l.value = zString + l.token = string(str[:stri]) + retL = *l + + if comi <= 0 { + return retL, true + } + } + + if comi > 0 { + // Send remainder of com + l.value = zNewline + l.token = "\n" + zl.comment = string(com[:comi]) + + if retL != (lex{}) { + zl.nextL = true + return retL, true + } + + return *l, true + } + + if zl.brace != 0 { + l.token = "unbalanced brace" + l.err = true + return *l, true + } + + return lex{value: zEOF}, false +} + +func (zl *zlexer) Comment() string { + if zl.l.err { + return "" + } + + return zl.comment +} + +// Extract the class number from CLASSxx +func classToInt(token string) (uint16, bool) { + offset := 5 + if len(token) < offset+1 { + return 0, false + } + class, err := strconv.ParseUint(token[offset:], 10, 16) + if err != nil { + return 0, false + } + return uint16(class), true +} + +// Extract the rr number from TYPExxx +func typeToInt(token string) (uint16, bool) { + offset := 4 + if len(token) < offset+1 { + return 0, false + } + typ, err := strconv.ParseUint(token[offset:], 10, 16) + if err != nil { + return 0, false + } + return uint16(typ), true +} + +// stringToTTL parses things like 2w, 2m, etc, and returns the time in seconds. +func stringToTTL(token string) (uint32, bool) { + var s, i uint32 + for _, c := range token { + switch c { + case 's', 'S': + s += i + i = 0 + case 'm', 'M': + s += i * 60 + i = 0 + case 'h', 'H': + s += i * 60 * 60 + i = 0 + case 'd', 'D': + s += i * 60 * 60 * 24 + i = 0 + case 'w', 'W': + s += i * 60 * 60 * 24 * 7 + i = 0 + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + i *= 10 + i += uint32(c) - '0' + default: + return 0, false + } + } + return s + i, true +} + +// Parse LOC records' [.][mM] into a +// mantissa exponent format. Token should contain the entire +// string (i.e. no spaces allowed) +func stringToCm(token string) (e, m uint8, ok bool) { + if token[len(token)-1] == 'M' || token[len(token)-1] == 'm' { + token = token[0 : len(token)-1] + } + s := strings.SplitN(token, ".", 2) + var meters, cmeters, val int + var err error + switch len(s) { + case 2: + if cmeters, err = strconv.Atoi(s[1]); err != nil { + return + } + // There's no point in having more than 2 digits in this part, and would rather make the implementation complicated ('123' should be treated as '12'). + // So we simply reject it. + // We also make sure the first character is a digit to reject '+-' signs. + if len(s[1]) > 2 || s[1][0] < '0' || s[1][0] > '9' { + return + } + if len(s[1]) == 1 { + // 'nn.1' must be treated as 'nn-meters and 10cm, not 1cm. + cmeters *= 10 + } + if s[0] == "" { + // This will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm). + break + } + fallthrough + case 1: + if meters, err = strconv.Atoi(s[0]); err != nil { + return + } + // RFC1876 states the max value is 90000000.00. The latter two conditions enforce it. + if s[0][0] < '0' || s[0][0] > '9' || meters > 90000000 || (meters == 90000000 && cmeters != 0) { + return + } + case 0: + // huh? + return 0, 0, false + } + ok = true + if meters > 0 { + e = 2 + val = meters + } else { + e = 0 + val = cmeters + } + for val >= 10 { + e++ + val /= 10 + } + m = uint8(val) + return +} + +func toAbsoluteName(name, origin string) (absolute string, ok bool) { + // check for an explicit origin reference + if name == "@" { + // require a nonempty origin + if origin == "" { + return "", false + } + return origin, true + } + + // require a valid domain name + _, ok = IsDomainName(name) + if !ok || name == "" { + return "", false + } + + // check if name is already absolute + if IsFqdn(name) { + return name, true + } + + // require a nonempty origin + if origin == "" { + return "", false + } + return appendOrigin(name, origin), true +} + +func appendOrigin(name, origin string) string { + if origin == "." { + return name + origin + } + return name + "." + origin +} + +// LOC record helper function +func locCheckNorth(token string, latitude uint32) (uint32, bool) { + if latitude > 90*1000*60*60 { + return latitude, false + } + switch token { + case "n", "N": + return LOC_EQUATOR + latitude, true + case "s", "S": + return LOC_EQUATOR - latitude, true + } + return latitude, false +} + +// LOC record helper function +func locCheckEast(token string, longitude uint32) (uint32, bool) { + if longitude > 180*1000*60*60 { + return longitude, false + } + switch token { + case "e", "E": + return LOC_EQUATOR + longitude, true + case "w", "W": + return LOC_EQUATOR - longitude, true + } + return longitude, false +} + +// "Eat" the rest of the "line" +func slurpRemainder(c *zlexer) *ParseError { + l, _ := c.Next() + switch l.value { + case zBlank: + l, _ = c.Next() + if l.value != zNewline && l.value != zEOF { + return &ParseError{"", "garbage after rdata", l} + } + case zNewline: + case zEOF: + default: + return &ParseError{"", "garbage after rdata", l} + } + return nil +} + +// Parse a 64 bit-like ipv6 address: "0014:4fff:ff20:ee64" +// Used for NID and L64 record. +func stringToNodeID(l lex) (uint64, *ParseError) { + if len(l.token) < 19 { + return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} + } + // There must be three colons at fixes positions, if not its a parse error + if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' { + return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} + } + s := l.token[0:4] + l.token[5:9] + l.token[10:14] + l.token[15:19] + u, err := strconv.ParseUint(s, 16, 64) + if err != nil { + return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l} + } + return u, nil +} diff --git a/vendor/github.com/miekg/dns/scan_rr.go b/vendor/github.com/miekg/dns/scan_rr.go new file mode 100644 index 0000000..05765ae --- /dev/null +++ b/vendor/github.com/miekg/dns/scan_rr.go @@ -0,0 +1,1774 @@ +package dns + +import ( + "bytes" + "encoding/base64" + "net" + "strconv" + "strings" +) + +// A remainder of the rdata with embedded spaces, return the parsed string (sans the spaces) +// or an error +func endingToString(c *zlexer, errstr string) (string, *ParseError) { + var buffer bytes.Buffer + l, _ := c.Next() // zString + for l.value != zNewline && l.value != zEOF { + if l.err { + return buffer.String(), &ParseError{"", errstr, l} + } + switch l.value { + case zString: + buffer.WriteString(l.token) + case zBlank: // Ok + default: + return "", &ParseError{"", errstr, l} + } + l, _ = c.Next() + } + + return buffer.String(), nil +} + +// A remainder of the rdata with embedded spaces, split on unquoted whitespace +// and return the parsed string slice or an error +func endingToTxtSlice(c *zlexer, errstr string) ([]string, *ParseError) { + // Get the remaining data until we see a zNewline + l, _ := c.Next() + if l.err { + return nil, &ParseError{"", errstr, l} + } + + // Build the slice + s := make([]string, 0) + quote := false + empty := false + for l.value != zNewline && l.value != zEOF { + if l.err { + return nil, &ParseError{"", errstr, l} + } + switch l.value { + case zString: + empty = false + if len(l.token) > 255 { + // split up tokens that are larger than 255 into 255-chunks + sx := []string{} + p, i := 0, 255 + for { + if i <= len(l.token) { + sx = append(sx, l.token[p:i]) + } else { + sx = append(sx, l.token[p:]) + break + + } + p, i = p+255, i+255 + } + s = append(s, sx...) + break + } + + s = append(s, l.token) + case zBlank: + if quote { + // zBlank can only be seen in between txt parts. + return nil, &ParseError{"", errstr, l} + } + case zQuote: + if empty && quote { + s = append(s, "") + } + quote = !quote + empty = true + default: + return nil, &ParseError{"", errstr, l} + } + l, _ = c.Next() + } + + if quote { + return nil, &ParseError{"", errstr, l} + } + + return s, nil +} + +func (rr *A) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + rr.A = net.ParseIP(l.token) + // IPv4 addresses cannot include ":". + // We do this rather than use net.IP's To4() because + // To4() treats IPv4-mapped IPv6 addresses as being + // IPv4. + isIPv4 := !strings.Contains(l.token, ":") + if rr.A == nil || !isIPv4 || l.err { + return &ParseError{"", "bad A A", l} + } + return slurpRemainder(c) +} + +func (rr *AAAA) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + rr.AAAA = net.ParseIP(l.token) + // IPv6 addresses must include ":", and IPv4 + // addresses cannot include ":". + isIPv6 := strings.Contains(l.token, ":") + if rr.AAAA == nil || !isIPv6 || l.err { + return &ParseError{"", "bad AAAA AAAA", l} + } + return slurpRemainder(c) +} + +func (rr *NS) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad NS Ns", l} + } + rr.Ns = name + return slurpRemainder(c) +} + +func (rr *PTR) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad PTR Ptr", l} + } + rr.Ptr = name + return slurpRemainder(c) +} + +func (rr *NSAPPTR) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad NSAP-PTR Ptr", l} + } + rr.Ptr = name + return slurpRemainder(c) +} + +func (rr *RP) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + mbox, mboxOk := toAbsoluteName(l.token, o) + if l.err || !mboxOk { + return &ParseError{"", "bad RP Mbox", l} + } + rr.Mbox = mbox + + c.Next() // zBlank + l, _ = c.Next() + rr.Txt = l.token + + txt, txtOk := toAbsoluteName(l.token, o) + if l.err || !txtOk { + return &ParseError{"", "bad RP Txt", l} + } + rr.Txt = txt + + return slurpRemainder(c) +} + +func (rr *MR) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad MR Mr", l} + } + rr.Mr = name + return slurpRemainder(c) +} + +func (rr *MB) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad MB Mb", l} + } + rr.Mb = name + return slurpRemainder(c) +} + +func (rr *MG) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad MG Mg", l} + } + rr.Mg = name + return slurpRemainder(c) +} + +func (rr *HINFO) parse(c *zlexer, o string) *ParseError { + chunks, e := endingToTxtSlice(c, "bad HINFO Fields") + if e != nil { + return e + } + + if ln := len(chunks); ln == 0 { + return nil + } else if ln == 1 { + // Can we split it? + if out := strings.Fields(chunks[0]); len(out) > 1 { + chunks = out + } else { + chunks = append(chunks, "") + } + } + + rr.Cpu = chunks[0] + rr.Os = strings.Join(chunks[1:], " ") + + return nil +} + +func (rr *MINFO) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + rmail, rmailOk := toAbsoluteName(l.token, o) + if l.err || !rmailOk { + return &ParseError{"", "bad MINFO Rmail", l} + } + rr.Rmail = rmail + + c.Next() // zBlank + l, _ = c.Next() + rr.Email = l.token + + email, emailOk := toAbsoluteName(l.token, o) + if l.err || !emailOk { + return &ParseError{"", "bad MINFO Email", l} + } + rr.Email = email + + return slurpRemainder(c) +} + +func (rr *MF) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad MF Mf", l} + } + rr.Mf = name + return slurpRemainder(c) +} + +func (rr *MD) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad MD Md", l} + } + rr.Md = name + return slurpRemainder(c) +} + +func (rr *MX) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad MX Pref", l} + } + rr.Preference = uint16(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + rr.Mx = l.token + + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad MX Mx", l} + } + rr.Mx = name + + return slurpRemainder(c) +} + +func (rr *RT) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil { + return &ParseError{"", "bad RT Preference", l} + } + rr.Preference = uint16(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + rr.Host = l.token + + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad RT Host", l} + } + rr.Host = name + + return slurpRemainder(c) +} + +func (rr *AFSDB) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad AFSDB Subtype", l} + } + rr.Subtype = uint16(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + rr.Hostname = l.token + + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad AFSDB Hostname", l} + } + rr.Hostname = name + return slurpRemainder(c) +} + +func (rr *X25) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + if l.err { + return &ParseError{"", "bad X25 PSDNAddress", l} + } + rr.PSDNAddress = l.token + return slurpRemainder(c) +} + +func (rr *KX) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad KX Pref", l} + } + rr.Preference = uint16(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + rr.Exchanger = l.token + + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad KX Exchanger", l} + } + rr.Exchanger = name + return slurpRemainder(c) +} + +func (rr *CNAME) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad CNAME Target", l} + } + rr.Target = name + return slurpRemainder(c) +} + +func (rr *DNAME) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad DNAME Target", l} + } + rr.Target = name + return slurpRemainder(c) +} + +func (rr *SOA) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + ns, nsOk := toAbsoluteName(l.token, o) + if l.err || !nsOk { + return &ParseError{"", "bad SOA Ns", l} + } + rr.Ns = ns + + c.Next() // zBlank + l, _ = c.Next() + rr.Mbox = l.token + + mbox, mboxOk := toAbsoluteName(l.token, o) + if l.err || !mboxOk { + return &ParseError{"", "bad SOA Mbox", l} + } + rr.Mbox = mbox + + c.Next() // zBlank + + var ( + v uint32 + ok bool + ) + for i := 0; i < 5; i++ { + l, _ = c.Next() + if l.err { + return &ParseError{"", "bad SOA zone parameter", l} + } + if j, err := strconv.ParseUint(l.token, 10, 32); err != nil { + if i == 0 { + // Serial must be a number + return &ParseError{"", "bad SOA zone parameter", l} + } + // We allow other fields to be unitful duration strings + if v, ok = stringToTTL(l.token); !ok { + return &ParseError{"", "bad SOA zone parameter", l} + + } + } else { + v = uint32(j) + } + switch i { + case 0: + rr.Serial = v + c.Next() // zBlank + case 1: + rr.Refresh = v + c.Next() // zBlank + case 2: + rr.Retry = v + c.Next() // zBlank + case 3: + rr.Expire = v + c.Next() // zBlank + case 4: + rr.Minttl = v + } + } + return slurpRemainder(c) +} + +func (rr *SRV) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad SRV Priority", l} + } + rr.Priority = uint16(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + i, e1 := strconv.ParseUint(l.token, 10, 16) + if e1 != nil || l.err { + return &ParseError{"", "bad SRV Weight", l} + } + rr.Weight = uint16(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + i, e2 := strconv.ParseUint(l.token, 10, 16) + if e2 != nil || l.err { + return &ParseError{"", "bad SRV Port", l} + } + rr.Port = uint16(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + rr.Target = l.token + + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad SRV Target", l} + } + rr.Target = name + return slurpRemainder(c) +} + +func (rr *NAPTR) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad NAPTR Order", l} + } + rr.Order = uint16(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + i, e1 := strconv.ParseUint(l.token, 10, 16) + if e1 != nil || l.err { + return &ParseError{"", "bad NAPTR Preference", l} + } + rr.Preference = uint16(i) + + // Flags + c.Next() // zBlank + l, _ = c.Next() // _QUOTE + if l.value != zQuote { + return &ParseError{"", "bad NAPTR Flags", l} + } + l, _ = c.Next() // Either String or Quote + if l.value == zString { + rr.Flags = l.token + l, _ = c.Next() // _QUOTE + if l.value != zQuote { + return &ParseError{"", "bad NAPTR Flags", l} + } + } else if l.value == zQuote { + rr.Flags = "" + } else { + return &ParseError{"", "bad NAPTR Flags", l} + } + + // Service + c.Next() // zBlank + l, _ = c.Next() // _QUOTE + if l.value != zQuote { + return &ParseError{"", "bad NAPTR Service", l} + } + l, _ = c.Next() // Either String or Quote + if l.value == zString { + rr.Service = l.token + l, _ = c.Next() // _QUOTE + if l.value != zQuote { + return &ParseError{"", "bad NAPTR Service", l} + } + } else if l.value == zQuote { + rr.Service = "" + } else { + return &ParseError{"", "bad NAPTR Service", l} + } + + // Regexp + c.Next() // zBlank + l, _ = c.Next() // _QUOTE + if l.value != zQuote { + return &ParseError{"", "bad NAPTR Regexp", l} + } + l, _ = c.Next() // Either String or Quote + if l.value == zString { + rr.Regexp = l.token + l, _ = c.Next() // _QUOTE + if l.value != zQuote { + return &ParseError{"", "bad NAPTR Regexp", l} + } + } else if l.value == zQuote { + rr.Regexp = "" + } else { + return &ParseError{"", "bad NAPTR Regexp", l} + } + + // After quote no space?? + c.Next() // zBlank + l, _ = c.Next() // zString + rr.Replacement = l.token + + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad NAPTR Replacement", l} + } + rr.Replacement = name + return slurpRemainder(c) +} + +func (rr *TALINK) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + previousName, previousNameOk := toAbsoluteName(l.token, o) + if l.err || !previousNameOk { + return &ParseError{"", "bad TALINK PreviousName", l} + } + rr.PreviousName = previousName + + c.Next() // zBlank + l, _ = c.Next() + rr.NextName = l.token + + nextName, nextNameOk := toAbsoluteName(l.token, o) + if l.err || !nextNameOk { + return &ParseError{"", "bad TALINK NextName", l} + } + rr.NextName = nextName + + return slurpRemainder(c) +} + +func (rr *LOC) parse(c *zlexer, o string) *ParseError { + // Non zero defaults for LOC record, see RFC 1876, Section 3. + rr.Size = 0x12 // 1e2 cm (1m) + rr.HorizPre = 0x16 // 1e6 cm (10000m) + rr.VertPre = 0x13 // 1e3 cm (10m) + ok := false + + // North + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 32) + if e != nil || l.err || i > 90 { + return &ParseError{"", "bad LOC Latitude", l} + } + rr.Latitude = 1000 * 60 * 60 * uint32(i) + + c.Next() // zBlank + // Either number, 'N' or 'S' + l, _ = c.Next() + if rr.Latitude, ok = locCheckNorth(l.token, rr.Latitude); ok { + goto East + } + if i, err := strconv.ParseUint(l.token, 10, 32); err != nil || l.err || i > 59 { + return &ParseError{"", "bad LOC Latitude minutes", l} + } else { + rr.Latitude += 1000 * 60 * uint32(i) + } + + c.Next() // zBlank + l, _ = c.Next() + if i, err := strconv.ParseFloat(l.token, 64); err != nil || l.err || i < 0 || i >= 60 { + return &ParseError{"", "bad LOC Latitude seconds", l} + } else { + rr.Latitude += uint32(1000 * i) + } + c.Next() // zBlank + // Either number, 'N' or 'S' + l, _ = c.Next() + if rr.Latitude, ok = locCheckNorth(l.token, rr.Latitude); ok { + goto East + } + // If still alive, flag an error + return &ParseError{"", "bad LOC Latitude North/South", l} + +East: + // East + c.Next() // zBlank + l, _ = c.Next() + if i, err := strconv.ParseUint(l.token, 10, 32); err != nil || l.err || i > 180 { + return &ParseError{"", "bad LOC Longitude", l} + } else { + rr.Longitude = 1000 * 60 * 60 * uint32(i) + } + c.Next() // zBlank + // Either number, 'E' or 'W' + l, _ = c.Next() + if rr.Longitude, ok = locCheckEast(l.token, rr.Longitude); ok { + goto Altitude + } + if i, err := strconv.ParseUint(l.token, 10, 32); err != nil || l.err || i > 59 { + return &ParseError{"", "bad LOC Longitude minutes", l} + } else { + rr.Longitude += 1000 * 60 * uint32(i) + } + c.Next() // zBlank + l, _ = c.Next() + if i, err := strconv.ParseFloat(l.token, 64); err != nil || l.err || i < 0 || i >= 60 { + return &ParseError{"", "bad LOC Longitude seconds", l} + } else { + rr.Longitude += uint32(1000 * i) + } + c.Next() // zBlank + // Either number, 'E' or 'W' + l, _ = c.Next() + if rr.Longitude, ok = locCheckEast(l.token, rr.Longitude); ok { + goto Altitude + } + // If still alive, flag an error + return &ParseError{"", "bad LOC Longitude East/West", l} + +Altitude: + c.Next() // zBlank + l, _ = c.Next() + if l.token == "" || l.err { + return &ParseError{"", "bad LOC Altitude", l} + } + if l.token[len(l.token)-1] == 'M' || l.token[len(l.token)-1] == 'm' { + l.token = l.token[0 : len(l.token)-1] + } + if i, err := strconv.ParseFloat(l.token, 64); err != nil { + return &ParseError{"", "bad LOC Altitude", l} + } else { + rr.Altitude = uint32(i*100.0 + 10000000.0 + 0.5) + } + + // And now optionally the other values + l, _ = c.Next() + count := 0 + for l.value != zNewline && l.value != zEOF { + switch l.value { + case zString: + switch count { + case 0: // Size + exp, m, ok := stringToCm(l.token) + if !ok { + return &ParseError{"", "bad LOC Size", l} + } + rr.Size = exp&0x0f | m<<4&0xf0 + case 1: // HorizPre + exp, m, ok := stringToCm(l.token) + if !ok { + return &ParseError{"", "bad LOC HorizPre", l} + } + rr.HorizPre = exp&0x0f | m<<4&0xf0 + case 2: // VertPre + exp, m, ok := stringToCm(l.token) + if !ok { + return &ParseError{"", "bad LOC VertPre", l} + } + rr.VertPre = exp&0x0f | m<<4&0xf0 + } + count++ + case zBlank: + // Ok + default: + return &ParseError{"", "bad LOC Size, HorizPre or VertPre", l} + } + l, _ = c.Next() + } + return nil +} + +func (rr *HIP) parse(c *zlexer, o string) *ParseError { + // HitLength is not represented + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 8) + if e != nil || l.err { + return &ParseError{"", "bad HIP PublicKeyAlgorithm", l} + } + rr.PublicKeyAlgorithm = uint8(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + if l.token == "" || l.err { + return &ParseError{"", "bad HIP Hit", l} + } + rr.Hit = l.token // This can not contain spaces, see RFC 5205 Section 6. + rr.HitLength = uint8(len(rr.Hit)) / 2 + + c.Next() // zBlank + l, _ = c.Next() // zString + if l.token == "" || l.err { + return &ParseError{"", "bad HIP PublicKey", l} + } + rr.PublicKey = l.token // This cannot contain spaces + rr.PublicKeyLength = uint16(base64.StdEncoding.DecodedLen(len(rr.PublicKey))) + + // RendezvousServers (if any) + l, _ = c.Next() + var xs []string + for l.value != zNewline && l.value != zEOF { + switch l.value { + case zString: + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad HIP RendezvousServers", l} + } + xs = append(xs, name) + case zBlank: + // Ok + default: + return &ParseError{"", "bad HIP RendezvousServers", l} + } + l, _ = c.Next() + } + + rr.RendezvousServers = xs + return nil +} + +func (rr *CERT) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + if v, ok := StringToCertType[l.token]; ok { + rr.Type = v + } else if i, err := strconv.ParseUint(l.token, 10, 16); err != nil { + return &ParseError{"", "bad CERT Type", l} + } else { + rr.Type = uint16(i) + } + c.Next() // zBlank + l, _ = c.Next() // zString + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad CERT KeyTag", l} + } + rr.KeyTag = uint16(i) + c.Next() // zBlank + l, _ = c.Next() // zString + if v, ok := StringToAlgorithm[l.token]; ok { + rr.Algorithm = v + } else if i, err := strconv.ParseUint(l.token, 10, 8); err != nil { + return &ParseError{"", "bad CERT Algorithm", l} + } else { + rr.Algorithm = uint8(i) + } + s, e1 := endingToString(c, "bad CERT Certificate") + if e1 != nil { + return e1 + } + rr.Certificate = s + return nil +} + +func (rr *OPENPGPKEY) parse(c *zlexer, o string) *ParseError { + s, e := endingToString(c, "bad OPENPGPKEY PublicKey") + if e != nil { + return e + } + rr.PublicKey = s + return nil +} + +func (rr *CSYNC) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + j, e := strconv.ParseUint(l.token, 10, 32) + if e != nil { + // Serial must be a number + return &ParseError{"", "bad CSYNC serial", l} + } + rr.Serial = uint32(j) + + c.Next() // zBlank + + l, _ = c.Next() + j, e1 := strconv.ParseUint(l.token, 10, 16) + if e1 != nil { + // Serial must be a number + return &ParseError{"", "bad CSYNC flags", l} + } + rr.Flags = uint16(j) + + rr.TypeBitMap = make([]uint16, 0) + var ( + k uint16 + ok bool + ) + l, _ = c.Next() + for l.value != zNewline && l.value != zEOF { + switch l.value { + case zBlank: + // Ok + case zString: + tokenUpper := strings.ToUpper(l.token) + if k, ok = StringToType[tokenUpper]; !ok { + if k, ok = typeToInt(l.token); !ok { + return &ParseError{"", "bad CSYNC TypeBitMap", l} + } + } + rr.TypeBitMap = append(rr.TypeBitMap, k) + default: + return &ParseError{"", "bad CSYNC TypeBitMap", l} + } + l, _ = c.Next() + } + return nil +} + +func (rr *ZONEMD) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 32) + if e != nil || l.err { + return &ParseError{"", "bad ZONEMD Serial", l} + } + rr.Serial = uint32(i) + + c.Next() // zBlank + l, _ = c.Next() + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { + return &ParseError{"", "bad ZONEMD Scheme", l} + } + rr.Scheme = uint8(i) + + c.Next() // zBlank + l, _ = c.Next() + i, err := strconv.ParseUint(l.token, 10, 8) + if err != nil || l.err { + return &ParseError{"", "bad ZONEMD Hash Algorithm", l} + } + rr.Hash = uint8(i) + + s, e2 := endingToString(c, "bad ZONEMD Digest") + if e2 != nil { + return e2 + } + rr.Digest = s + return nil +} + +func (rr *SIG) parse(c *zlexer, o string) *ParseError { return rr.RRSIG.parse(c, o) } + +func (rr *RRSIG) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + tokenUpper := strings.ToUpper(l.token) + if t, ok := StringToType[tokenUpper]; !ok { + if strings.HasPrefix(tokenUpper, "TYPE") { + t, ok = typeToInt(l.token) + if !ok { + return &ParseError{"", "bad RRSIG Typecovered", l} + } + rr.TypeCovered = t + } else { + return &ParseError{"", "bad RRSIG Typecovered", l} + } + } else { + rr.TypeCovered = t + } + + c.Next() // zBlank + l, _ = c.Next() + i, e := strconv.ParseUint(l.token, 10, 8) + if e != nil || l.err { + return &ParseError{"", "bad RRSIG Algorithm", l} + } + rr.Algorithm = uint8(i) + + c.Next() // zBlank + l, _ = c.Next() + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { + return &ParseError{"", "bad RRSIG Labels", l} + } + rr.Labels = uint8(i) + + c.Next() // zBlank + l, _ = c.Next() + i, e2 := strconv.ParseUint(l.token, 10, 32) + if e2 != nil || l.err { + return &ParseError{"", "bad RRSIG OrigTtl", l} + } + rr.OrigTtl = uint32(i) + + c.Next() // zBlank + l, _ = c.Next() + if i, err := StringToTime(l.token); err != nil { + // Try to see if all numeric and use it as epoch + if i, err := strconv.ParseUint(l.token, 10, 32); err == nil { + rr.Expiration = uint32(i) + } else { + return &ParseError{"", "bad RRSIG Expiration", l} + } + } else { + rr.Expiration = i + } + + c.Next() // zBlank + l, _ = c.Next() + if i, err := StringToTime(l.token); err != nil { + if i, err := strconv.ParseUint(l.token, 10, 32); err == nil { + rr.Inception = uint32(i) + } else { + return &ParseError{"", "bad RRSIG Inception", l} + } + } else { + rr.Inception = i + } + + c.Next() // zBlank + l, _ = c.Next() + i, e3 := strconv.ParseUint(l.token, 10, 16) + if e3 != nil || l.err { + return &ParseError{"", "bad RRSIG KeyTag", l} + } + rr.KeyTag = uint16(i) + + c.Next() // zBlank + l, _ = c.Next() + rr.SignerName = l.token + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad RRSIG SignerName", l} + } + rr.SignerName = name + + s, e4 := endingToString(c, "bad RRSIG Signature") + if e4 != nil { + return e4 + } + rr.Signature = s + + return nil +} + +func (rr *NSEC) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad NSEC NextDomain", l} + } + rr.NextDomain = name + + rr.TypeBitMap = make([]uint16, 0) + var ( + k uint16 + ok bool + ) + l, _ = c.Next() + for l.value != zNewline && l.value != zEOF { + switch l.value { + case zBlank: + // Ok + case zString: + tokenUpper := strings.ToUpper(l.token) + if k, ok = StringToType[tokenUpper]; !ok { + if k, ok = typeToInt(l.token); !ok { + return &ParseError{"", "bad NSEC TypeBitMap", l} + } + } + rr.TypeBitMap = append(rr.TypeBitMap, k) + default: + return &ParseError{"", "bad NSEC TypeBitMap", l} + } + l, _ = c.Next() + } + return nil +} + +func (rr *NSEC3) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 8) + if e != nil || l.err { + return &ParseError{"", "bad NSEC3 Hash", l} + } + rr.Hash = uint8(i) + c.Next() // zBlank + l, _ = c.Next() + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { + return &ParseError{"", "bad NSEC3 Flags", l} + } + rr.Flags = uint8(i) + c.Next() // zBlank + l, _ = c.Next() + i, e2 := strconv.ParseUint(l.token, 10, 16) + if e2 != nil || l.err { + return &ParseError{"", "bad NSEC3 Iterations", l} + } + rr.Iterations = uint16(i) + c.Next() + l, _ = c.Next() + if l.token == "" || l.err { + return &ParseError{"", "bad NSEC3 Salt", l} + } + if l.token != "-" { + rr.SaltLength = uint8(len(l.token)) / 2 + rr.Salt = l.token + } + + c.Next() + l, _ = c.Next() + if l.token == "" || l.err { + return &ParseError{"", "bad NSEC3 NextDomain", l} + } + rr.HashLength = 20 // Fix for NSEC3 (sha1 160 bits) + rr.NextDomain = l.token + + rr.TypeBitMap = make([]uint16, 0) + var ( + k uint16 + ok bool + ) + l, _ = c.Next() + for l.value != zNewline && l.value != zEOF { + switch l.value { + case zBlank: + // Ok + case zString: + tokenUpper := strings.ToUpper(l.token) + if k, ok = StringToType[tokenUpper]; !ok { + if k, ok = typeToInt(l.token); !ok { + return &ParseError{"", "bad NSEC3 TypeBitMap", l} + } + } + rr.TypeBitMap = append(rr.TypeBitMap, k) + default: + return &ParseError{"", "bad NSEC3 TypeBitMap", l} + } + l, _ = c.Next() + } + return nil +} + +func (rr *NSEC3PARAM) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 8) + if e != nil || l.err { + return &ParseError{"", "bad NSEC3PARAM Hash", l} + } + rr.Hash = uint8(i) + c.Next() // zBlank + l, _ = c.Next() + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { + return &ParseError{"", "bad NSEC3PARAM Flags", l} + } + rr.Flags = uint8(i) + c.Next() // zBlank + l, _ = c.Next() + i, e2 := strconv.ParseUint(l.token, 10, 16) + if e2 != nil || l.err { + return &ParseError{"", "bad NSEC3PARAM Iterations", l} + } + rr.Iterations = uint16(i) + c.Next() + l, _ = c.Next() + if l.token != "-" { + rr.SaltLength = uint8(len(l.token) / 2) + rr.Salt = l.token + } + return slurpRemainder(c) +} + +func (rr *EUI48) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + if len(l.token) != 17 || l.err { + return &ParseError{"", "bad EUI48 Address", l} + } + addr := make([]byte, 12) + dash := 0 + for i := 0; i < 10; i += 2 { + addr[i] = l.token[i+dash] + addr[i+1] = l.token[i+1+dash] + dash++ + if l.token[i+1+dash] != '-' { + return &ParseError{"", "bad EUI48 Address", l} + } + } + addr[10] = l.token[15] + addr[11] = l.token[16] + + i, e := strconv.ParseUint(string(addr), 16, 48) + if e != nil { + return &ParseError{"", "bad EUI48 Address", l} + } + rr.Address = i + return slurpRemainder(c) +} + +func (rr *EUI64) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + if len(l.token) != 23 || l.err { + return &ParseError{"", "bad EUI64 Address", l} + } + addr := make([]byte, 16) + dash := 0 + for i := 0; i < 14; i += 2 { + addr[i] = l.token[i+dash] + addr[i+1] = l.token[i+1+dash] + dash++ + if l.token[i+1+dash] != '-' { + return &ParseError{"", "bad EUI64 Address", l} + } + } + addr[14] = l.token[21] + addr[15] = l.token[22] + + i, e := strconv.ParseUint(string(addr), 16, 64) + if e != nil { + return &ParseError{"", "bad EUI68 Address", l} + } + rr.Address = i + return slurpRemainder(c) +} + +func (rr *SSHFP) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 8) + if e != nil || l.err { + return &ParseError{"", "bad SSHFP Algorithm", l} + } + rr.Algorithm = uint8(i) + c.Next() // zBlank + l, _ = c.Next() + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { + return &ParseError{"", "bad SSHFP Type", l} + } + rr.Type = uint8(i) + c.Next() // zBlank + s, e2 := endingToString(c, "bad SSHFP Fingerprint") + if e2 != nil { + return e2 + } + rr.FingerPrint = s + return nil +} + +func (rr *DNSKEY) parseDNSKEY(c *zlexer, o, typ string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad " + typ + " Flags", l} + } + rr.Flags = uint16(i) + c.Next() // zBlank + l, _ = c.Next() // zString + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { + return &ParseError{"", "bad " + typ + " Protocol", l} + } + rr.Protocol = uint8(i) + c.Next() // zBlank + l, _ = c.Next() // zString + i, e2 := strconv.ParseUint(l.token, 10, 8) + if e2 != nil || l.err { + return &ParseError{"", "bad " + typ + " Algorithm", l} + } + rr.Algorithm = uint8(i) + s, e3 := endingToString(c, "bad "+typ+" PublicKey") + if e3 != nil { + return e3 + } + rr.PublicKey = s + return nil +} + +func (rr *DNSKEY) parse(c *zlexer, o string) *ParseError { return rr.parseDNSKEY(c, o, "DNSKEY") } +func (rr *KEY) parse(c *zlexer, o string) *ParseError { return rr.parseDNSKEY(c, o, "KEY") } +func (rr *CDNSKEY) parse(c *zlexer, o string) *ParseError { return rr.parseDNSKEY(c, o, "CDNSKEY") } +func (rr *DS) parse(c *zlexer, o string) *ParseError { return rr.parseDS(c, o, "DS") } +func (rr *DLV) parse(c *zlexer, o string) *ParseError { return rr.parseDS(c, o, "DLV") } +func (rr *CDS) parse(c *zlexer, o string) *ParseError { return rr.parseDS(c, o, "CDS") } + +func (rr *RKEY) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad RKEY Flags", l} + } + rr.Flags = uint16(i) + c.Next() // zBlank + l, _ = c.Next() // zString + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { + return &ParseError{"", "bad RKEY Protocol", l} + } + rr.Protocol = uint8(i) + c.Next() // zBlank + l, _ = c.Next() // zString + i, e2 := strconv.ParseUint(l.token, 10, 8) + if e2 != nil || l.err { + return &ParseError{"", "bad RKEY Algorithm", l} + } + rr.Algorithm = uint8(i) + s, e3 := endingToString(c, "bad RKEY PublicKey") + if e3 != nil { + return e3 + } + rr.PublicKey = s + return nil +} + +func (rr *EID) parse(c *zlexer, o string) *ParseError { + s, e := endingToString(c, "bad EID Endpoint") + if e != nil { + return e + } + rr.Endpoint = s + return nil +} + +func (rr *NIMLOC) parse(c *zlexer, o string) *ParseError { + s, e := endingToString(c, "bad NIMLOC Locator") + if e != nil { + return e + } + rr.Locator = s + return nil +} + +func (rr *GPOS) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + _, e := strconv.ParseFloat(l.token, 64) + if e != nil || l.err { + return &ParseError{"", "bad GPOS Longitude", l} + } + rr.Longitude = l.token + c.Next() // zBlank + l, _ = c.Next() + _, e1 := strconv.ParseFloat(l.token, 64) + if e1 != nil || l.err { + return &ParseError{"", "bad GPOS Latitude", l} + } + rr.Latitude = l.token + c.Next() // zBlank + l, _ = c.Next() + _, e2 := strconv.ParseFloat(l.token, 64) + if e2 != nil || l.err { + return &ParseError{"", "bad GPOS Altitude", l} + } + rr.Altitude = l.token + return slurpRemainder(c) +} + +func (rr *DS) parseDS(c *zlexer, o, typ string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad " + typ + " KeyTag", l} + } + rr.KeyTag = uint16(i) + c.Next() // zBlank + l, _ = c.Next() + if i, err := strconv.ParseUint(l.token, 10, 8); err != nil { + tokenUpper := strings.ToUpper(l.token) + i, ok := StringToAlgorithm[tokenUpper] + if !ok || l.err { + return &ParseError{"", "bad " + typ + " Algorithm", l} + } + rr.Algorithm = i + } else { + rr.Algorithm = uint8(i) + } + c.Next() // zBlank + l, _ = c.Next() + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { + return &ParseError{"", "bad " + typ + " DigestType", l} + } + rr.DigestType = uint8(i) + s, e2 := endingToString(c, "bad "+typ+" Digest") + if e2 != nil { + return e2 + } + rr.Digest = s + return nil +} + +func (rr *TA) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad TA KeyTag", l} + } + rr.KeyTag = uint16(i) + c.Next() // zBlank + l, _ = c.Next() + if i, err := strconv.ParseUint(l.token, 10, 8); err != nil { + tokenUpper := strings.ToUpper(l.token) + i, ok := StringToAlgorithm[tokenUpper] + if !ok || l.err { + return &ParseError{"", "bad TA Algorithm", l} + } + rr.Algorithm = i + } else { + rr.Algorithm = uint8(i) + } + c.Next() // zBlank + l, _ = c.Next() + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { + return &ParseError{"", "bad TA DigestType", l} + } + rr.DigestType = uint8(i) + s, e2 := endingToString(c, "bad TA Digest") + if e2 != nil { + return e2 + } + rr.Digest = s + return nil +} + +func (rr *TLSA) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 8) + if e != nil || l.err { + return &ParseError{"", "bad TLSA Usage", l} + } + rr.Usage = uint8(i) + c.Next() // zBlank + l, _ = c.Next() + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { + return &ParseError{"", "bad TLSA Selector", l} + } + rr.Selector = uint8(i) + c.Next() // zBlank + l, _ = c.Next() + i, e2 := strconv.ParseUint(l.token, 10, 8) + if e2 != nil || l.err { + return &ParseError{"", "bad TLSA MatchingType", l} + } + rr.MatchingType = uint8(i) + // So this needs be e2 (i.e. different than e), because...??t + s, e3 := endingToString(c, "bad TLSA Certificate") + if e3 != nil { + return e3 + } + rr.Certificate = s + return nil +} + +func (rr *SMIMEA) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 8) + if e != nil || l.err { + return &ParseError{"", "bad SMIMEA Usage", l} + } + rr.Usage = uint8(i) + c.Next() // zBlank + l, _ = c.Next() + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { + return &ParseError{"", "bad SMIMEA Selector", l} + } + rr.Selector = uint8(i) + c.Next() // zBlank + l, _ = c.Next() + i, e2 := strconv.ParseUint(l.token, 10, 8) + if e2 != nil || l.err { + return &ParseError{"", "bad SMIMEA MatchingType", l} + } + rr.MatchingType = uint8(i) + // So this needs be e2 (i.e. different than e), because...??t + s, e3 := endingToString(c, "bad SMIMEA Certificate") + if e3 != nil { + return e3 + } + rr.Certificate = s + return nil +} + +func (rr *RFC3597) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + if l.token != "\\#" { + return &ParseError{"", "bad RFC3597 Rdata", l} + } + + c.Next() // zBlank + l, _ = c.Next() + rdlength, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad RFC3597 Rdata ", l} + } + + s, e1 := endingToString(c, "bad RFC3597 Rdata") + if e1 != nil { + return e1 + } + if int(rdlength)*2 != len(s) { + return &ParseError{"", "bad RFC3597 Rdata", l} + } + rr.Rdata = s + return nil +} + +func (rr *SPF) parse(c *zlexer, o string) *ParseError { + s, e := endingToTxtSlice(c, "bad SPF Txt") + if e != nil { + return e + } + rr.Txt = s + return nil +} + +func (rr *AVC) parse(c *zlexer, o string) *ParseError { + s, e := endingToTxtSlice(c, "bad AVC Txt") + if e != nil { + return e + } + rr.Txt = s + return nil +} + +func (rr *TXT) parse(c *zlexer, o string) *ParseError { + // no zBlank reading here, because all this rdata is TXT + s, e := endingToTxtSlice(c, "bad TXT Txt") + if e != nil { + return e + } + rr.Txt = s + return nil +} + +// identical to setTXT +func (rr *NINFO) parse(c *zlexer, o string) *ParseError { + s, e := endingToTxtSlice(c, "bad NINFO ZSData") + if e != nil { + return e + } + rr.ZSData = s + return nil +} + +func (rr *URI) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad URI Priority", l} + } + rr.Priority = uint16(i) + c.Next() // zBlank + l, _ = c.Next() + i, e1 := strconv.ParseUint(l.token, 10, 16) + if e1 != nil || l.err { + return &ParseError{"", "bad URI Weight", l} + } + rr.Weight = uint16(i) + + c.Next() // zBlank + s, e2 := endingToTxtSlice(c, "bad URI Target") + if e2 != nil { + return e2 + } + if len(s) != 1 { + return &ParseError{"", "bad URI Target", l} + } + rr.Target = s[0] + return nil +} + +func (rr *DHCID) parse(c *zlexer, o string) *ParseError { + // awesome record to parse! + s, e := endingToString(c, "bad DHCID Digest") + if e != nil { + return e + } + rr.Digest = s + return nil +} + +func (rr *NID) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad NID Preference", l} + } + rr.Preference = uint16(i) + c.Next() // zBlank + l, _ = c.Next() // zString + u, e1 := stringToNodeID(l) + if e1 != nil || l.err { + return e1 + } + rr.NodeID = u + return slurpRemainder(c) +} + +func (rr *L32) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad L32 Preference", l} + } + rr.Preference = uint16(i) + c.Next() // zBlank + l, _ = c.Next() // zString + rr.Locator32 = net.ParseIP(l.token) + if rr.Locator32 == nil || l.err { + return &ParseError{"", "bad L32 Locator", l} + } + return slurpRemainder(c) +} + +func (rr *LP) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad LP Preference", l} + } + rr.Preference = uint16(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + rr.Fqdn = l.token + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{"", "bad LP Fqdn", l} + } + rr.Fqdn = name + return slurpRemainder(c) +} + +func (rr *L64) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad L64 Preference", l} + } + rr.Preference = uint16(i) + c.Next() // zBlank + l, _ = c.Next() // zString + u, e1 := stringToNodeID(l) + if e1 != nil || l.err { + return e1 + } + rr.Locator64 = u + return slurpRemainder(c) +} + +func (rr *UID) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 32) + if e != nil || l.err { + return &ParseError{"", "bad UID Uid", l} + } + rr.Uid = uint32(i) + return slurpRemainder(c) +} + +func (rr *GID) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 32) + if e != nil || l.err { + return &ParseError{"", "bad GID Gid", l} + } + rr.Gid = uint32(i) + return slurpRemainder(c) +} + +func (rr *UINFO) parse(c *zlexer, o string) *ParseError { + s, e := endingToTxtSlice(c, "bad UINFO Uinfo") + if e != nil { + return e + } + if ln := len(s); ln == 0 { + return nil + } + rr.Uinfo = s[0] // silently discard anything after the first character-string + return nil +} + +func (rr *PX) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{"", "bad PX Preference", l} + } + rr.Preference = uint16(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + rr.Map822 = l.token + map822, map822Ok := toAbsoluteName(l.token, o) + if l.err || !map822Ok { + return &ParseError{"", "bad PX Map822", l} + } + rr.Map822 = map822 + + c.Next() // zBlank + l, _ = c.Next() // zString + rr.Mapx400 = l.token + mapx400, mapx400Ok := toAbsoluteName(l.token, o) + if l.err || !mapx400Ok { + return &ParseError{"", "bad PX Mapx400", l} + } + rr.Mapx400 = mapx400 + return slurpRemainder(c) +} + +func (rr *CAA) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 8) + if e != nil || l.err { + return &ParseError{"", "bad CAA Flag", l} + } + rr.Flag = uint8(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + if l.value != zString { + return &ParseError{"", "bad CAA Tag", l} + } + rr.Tag = l.token + + c.Next() // zBlank + s, e1 := endingToTxtSlice(c, "bad CAA Value") + if e1 != nil { + return e1 + } + if len(s) != 1 { + return &ParseError{"", "bad CAA Value", l} + } + rr.Value = s[0] + return nil +} + +func (rr *TKEY) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + + // Algorithm + if l.value != zString { + return &ParseError{"", "bad TKEY algorithm", l} + } + rr.Algorithm = l.token + c.Next() // zBlank + + // Get the key length and key values + l, _ = c.Next() + i, e := strconv.ParseUint(l.token, 10, 8) + if e != nil || l.err { + return &ParseError{"", "bad TKEY key length", l} + } + rr.KeySize = uint16(i) + c.Next() // zBlank + l, _ = c.Next() + if l.value != zString { + return &ParseError{"", "bad TKEY key", l} + } + rr.Key = l.token + c.Next() // zBlank + + // Get the otherdata length and string data + l, _ = c.Next() + i, e1 := strconv.ParseUint(l.token, 10, 8) + if e1 != nil || l.err { + return &ParseError{"", "bad TKEY otherdata length", l} + } + rr.OtherLen = uint16(i) + c.Next() // zBlank + l, _ = c.Next() + if l.value != zString { + return &ParseError{"", "bad TKEY otherday", l} + } + rr.OtherData = l.token + return nil +} + +func (rr *APL) parse(c *zlexer, o string) *ParseError { + var prefixes []APLPrefix + + for { + l, _ := c.Next() + if l.value == zNewline || l.value == zEOF { + break + } + if l.value == zBlank && prefixes != nil { + continue + } + if l.value != zString { + return &ParseError{"", "unexpected APL field", l} + } + + // Expected format: [!]afi:address/prefix + + colon := strings.IndexByte(l.token, ':') + if colon == -1 { + return &ParseError{"", "missing colon in APL field", l} + } + + family, cidr := l.token[:colon], l.token[colon+1:] + + var negation bool + if family != "" && family[0] == '!' { + negation = true + family = family[1:] + } + + afi, e := strconv.ParseUint(family, 10, 16) + if e != nil { + return &ParseError{"", "failed to parse APL family: " + e.Error(), l} + } + var addrLen int + switch afi { + case 1: + addrLen = net.IPv4len + case 2: + addrLen = net.IPv6len + default: + return &ParseError{"", "unrecognized APL family", l} + } + + ip, subnet, e1 := net.ParseCIDR(cidr) + if e1 != nil { + return &ParseError{"", "failed to parse APL address: " + e1.Error(), l} + } + if !ip.Equal(subnet.IP) { + return &ParseError{"", "extra bits in APL address", l} + } + + if len(subnet.IP) != addrLen { + return &ParseError{"", "address mismatch with the APL family", l} + } + + prefixes = append(prefixes, APLPrefix{ + Negation: negation, + Network: *subnet, + }) + } + + rr.Prefixes = prefixes + return nil +} diff --git a/vendor/github.com/miekg/dns/serve_mux.go b/vendor/github.com/miekg/dns/serve_mux.go new file mode 100644 index 0000000..e7f36e2 --- /dev/null +++ b/vendor/github.com/miekg/dns/serve_mux.go @@ -0,0 +1,122 @@ +package dns + +import ( + "sync" +) + +// ServeMux is an DNS request multiplexer. It matches the zone name of +// each incoming request against a list of registered patterns add calls +// the handler for the pattern that most closely matches the zone name. +// +// ServeMux is DNSSEC aware, meaning that queries for the DS record are +// redirected to the parent zone (if that is also registered), otherwise +// the child gets the query. +// +// ServeMux is also safe for concurrent access from multiple goroutines. +// +// The zero ServeMux is empty and ready for use. +type ServeMux struct { + z map[string]Handler + m sync.RWMutex +} + +// NewServeMux allocates and returns a new ServeMux. +func NewServeMux() *ServeMux { + return new(ServeMux) +} + +// DefaultServeMux is the default ServeMux used by Serve. +var DefaultServeMux = NewServeMux() + +func (mux *ServeMux) match(q string, t uint16) Handler { + mux.m.RLock() + defer mux.m.RUnlock() + if mux.z == nil { + return nil + } + + q = CanonicalName(q) + + var handler Handler + for off, end := 0, false; !end; off, end = NextLabel(q, off) { + if h, ok := mux.z[q[off:]]; ok { + if t != TypeDS { + return h + } + // Continue for DS to see if we have a parent too, if so delegate to the parent + handler = h + } + } + + // Wildcard match, if we have found nothing try the root zone as a last resort. + if h, ok := mux.z["."]; ok { + return h + } + + return handler +} + +// Handle adds a handler to the ServeMux for pattern. +func (mux *ServeMux) Handle(pattern string, handler Handler) { + if pattern == "" { + panic("dns: invalid pattern " + pattern) + } + mux.m.Lock() + if mux.z == nil { + mux.z = make(map[string]Handler) + } + mux.z[CanonicalName(pattern)] = handler + mux.m.Unlock() +} + +// HandleFunc adds a handler function to the ServeMux for pattern. +func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) { + mux.Handle(pattern, HandlerFunc(handler)) +} + +// HandleRemove deregisters the handler specific for pattern from the ServeMux. +func (mux *ServeMux) HandleRemove(pattern string) { + if pattern == "" { + panic("dns: invalid pattern " + pattern) + } + mux.m.Lock() + delete(mux.z, CanonicalName(pattern)) + mux.m.Unlock() +} + +// ServeDNS dispatches the request to the handler whose pattern most +// closely matches the request message. +// +// ServeDNS is DNSSEC aware, meaning that queries for the DS record +// are redirected to the parent zone (if that is also registered), +// otherwise the child gets the query. +// +// If no handler is found, or there is no question, a standard REFUSED +// message is returned +func (mux *ServeMux) ServeDNS(w ResponseWriter, req *Msg) { + var h Handler + if len(req.Question) >= 1 { // allow more than one question + h = mux.match(req.Question[0].Name, req.Question[0].Qtype) + } + + if h != nil { + h.ServeDNS(w, req) + } else { + handleRefused(w, req) + } +} + +// Handle registers the handler with the given pattern +// in the DefaultServeMux. The documentation for +// ServeMux explains how patterns are matched. +func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) } + +// HandleRemove deregisters the handle with the given pattern +// in the DefaultServeMux. +func HandleRemove(pattern string) { DefaultServeMux.HandleRemove(pattern) } + +// HandleFunc registers the handler function with the given pattern +// in the DefaultServeMux. +func HandleFunc(pattern string, handler func(ResponseWriter, *Msg)) { + DefaultServeMux.HandleFunc(pattern, handler) +} diff --git a/vendor/github.com/miekg/dns/server.go b/vendor/github.com/miekg/dns/server.go new file mode 100644 index 0000000..b2a63bd --- /dev/null +++ b/vendor/github.com/miekg/dns/server.go @@ -0,0 +1,828 @@ +// DNS server implementation. + +package dns + +import ( + "context" + "crypto/tls" + "encoding/binary" + "errors" + "io" + "net" + "strings" + "sync" + "time" +) + +// Default maximum number of TCP queries before we close the socket. +const maxTCPQueries = 128 + +// aLongTimeAgo is a non-zero time, far in the past, used for +// immediate cancelation of network operations. +var aLongTimeAgo = time.Unix(1, 0) + +// Handler is implemented by any value that implements ServeDNS. +type Handler interface { + ServeDNS(w ResponseWriter, r *Msg) +} + +// The HandlerFunc type is an adapter to allow the use of +// ordinary functions as DNS handlers. If f is a function +// with the appropriate signature, HandlerFunc(f) is a +// Handler object that calls f. +type HandlerFunc func(ResponseWriter, *Msg) + +// ServeDNS calls f(w, r). +func (f HandlerFunc) ServeDNS(w ResponseWriter, r *Msg) { + f(w, r) +} + +// A ResponseWriter interface is used by an DNS handler to +// construct an DNS response. +type ResponseWriter interface { + // LocalAddr returns the net.Addr of the server + LocalAddr() net.Addr + // RemoteAddr returns the net.Addr of the client that sent the current request. + RemoteAddr() net.Addr + // WriteMsg writes a reply back to the client. + WriteMsg(*Msg) error + // Write writes a raw buffer back to the client. + Write([]byte) (int, error) + // Close closes the connection. + Close() error + // TsigStatus returns the status of the Tsig. + TsigStatus() error + // TsigTimersOnly sets the tsig timers only boolean. + TsigTimersOnly(bool) + // Hijack lets the caller take over the connection. + // After a call to Hijack(), the DNS package will not do anything with the connection. + Hijack() +} + +// A ConnectionStater interface is used by a DNS Handler to access TLS connection state +// when available. +type ConnectionStater interface { + ConnectionState() *tls.ConnectionState +} + +type response struct { + closed bool // connection has been closed + hijacked bool // connection has been hijacked by handler + tsigTimersOnly bool + tsigStatus error + tsigRequestMAC string + tsigSecret map[string]string // the tsig secrets + udp net.PacketConn // i/o connection if UDP was used + tcp net.Conn // i/o connection if TCP was used + udpSession *SessionUDP // oob data to get egress interface right + pcSession net.Addr // address to use when writing to a generic net.PacketConn + writer Writer // writer to output the raw DNS bits +} + +// handleRefused returns a HandlerFunc that returns REFUSED for every request it gets. +func handleRefused(w ResponseWriter, r *Msg) { + m := new(Msg) + m.SetRcode(r, RcodeRefused) + w.WriteMsg(m) +} + +// HandleFailed returns a HandlerFunc that returns SERVFAIL for every request it gets. +// Deprecated: This function is going away. +func HandleFailed(w ResponseWriter, r *Msg) { + m := new(Msg) + m.SetRcode(r, RcodeServerFailure) + // does not matter if this write fails + w.WriteMsg(m) +} + +// ListenAndServe Starts a server on address and network specified Invoke handler +// for incoming queries. +func ListenAndServe(addr string, network string, handler Handler) error { + server := &Server{Addr: addr, Net: network, Handler: handler} + return server.ListenAndServe() +} + +// ListenAndServeTLS acts like http.ListenAndServeTLS, more information in +// http://golang.org/pkg/net/http/#ListenAndServeTLS +func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } + + config := tls.Config{ + Certificates: []tls.Certificate{cert}, + } + + server := &Server{ + Addr: addr, + Net: "tcp-tls", + TLSConfig: &config, + Handler: handler, + } + + return server.ListenAndServe() +} + +// ActivateAndServe activates a server with a listener from systemd, +// l and p should not both be non-nil. +// If both l and p are not nil only p will be used. +// Invoke handler for incoming queries. +func ActivateAndServe(l net.Listener, p net.PacketConn, handler Handler) error { + server := &Server{Listener: l, PacketConn: p, Handler: handler} + return server.ActivateAndServe() +} + +// Writer writes raw DNS messages; each call to Write should send an entire message. +type Writer interface { + io.Writer +} + +// Reader reads raw DNS messages; each call to ReadTCP or ReadUDP should return an entire message. +type Reader interface { + // ReadTCP reads a raw message from a TCP connection. Implementations may alter + // connection properties, for example the read-deadline. + ReadTCP(conn net.Conn, timeout time.Duration) ([]byte, error) + // ReadUDP reads a raw message from a UDP connection. Implementations may alter + // connection properties, for example the read-deadline. + ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) +} + +// PacketConnReader is an optional interface that Readers can implement to support using generic net.PacketConns. +type PacketConnReader interface { + Reader + + // ReadPacketConn reads a raw message from a generic net.PacketConn UDP connection. Implementations may + // alter connection properties, for example the read-deadline. + ReadPacketConn(conn net.PacketConn, timeout time.Duration) ([]byte, net.Addr, error) +} + +// defaultReader is an adapter for the Server struct that implements the Reader and +// PacketConnReader interfaces using the readTCP, readUDP and readPacketConn funcs +// of the embedded Server. +type defaultReader struct { + *Server +} + +var _ PacketConnReader = defaultReader{} + +func (dr defaultReader) ReadTCP(conn net.Conn, timeout time.Duration) ([]byte, error) { + return dr.readTCP(conn, timeout) +} + +func (dr defaultReader) ReadUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) { + return dr.readUDP(conn, timeout) +} + +func (dr defaultReader) ReadPacketConn(conn net.PacketConn, timeout time.Duration) ([]byte, net.Addr, error) { + return dr.readPacketConn(conn, timeout) +} + +// DecorateReader is a decorator hook for extending or supplanting the functionality of a Reader. +// Implementations should never return a nil Reader. +// Readers should also implement the optional PacketConnReader interface. +// PacketConnReader is required to use a generic net.PacketConn. +type DecorateReader func(Reader) Reader + +// DecorateWriter is a decorator hook for extending or supplanting the functionality of a Writer. +// Implementations should never return a nil Writer. +type DecorateWriter func(Writer) Writer + +// A Server defines parameters for running an DNS server. +type Server struct { + // Address to listen on, ":dns" if empty. + Addr string + // if "tcp" or "tcp-tls" (DNS over TLS) it will invoke a TCP listener, otherwise an UDP one + Net string + // TCP Listener to use, this is to aid in systemd's socket activation. + Listener net.Listener + // TLS connection configuration + TLSConfig *tls.Config + // UDP "Listener" to use, this is to aid in systemd's socket activation. + PacketConn net.PacketConn + // Handler to invoke, dns.DefaultServeMux if nil. + Handler Handler + // Default buffer size to use to read incoming UDP messages. If not set + // it defaults to MinMsgSize (512 B). + UDPSize int + // The net.Conn.SetReadTimeout value for new connections, defaults to 2 * time.Second. + ReadTimeout time.Duration + // The net.Conn.SetWriteTimeout value for new connections, defaults to 2 * time.Second. + WriteTimeout time.Duration + // TCP idle timeout for multiple queries, if nil, defaults to 8 * time.Second (RFC 5966). + IdleTimeout func() time.Duration + // Secret(s) for Tsig map[]. The zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2). + TsigSecret map[string]string + // If NotifyStartedFunc is set it is called once the server has started listening. + NotifyStartedFunc func() + // DecorateReader is optional, allows customization of the process that reads raw DNS messages. + DecorateReader DecorateReader + // DecorateWriter is optional, allows customization of the process that writes raw DNS messages. + DecorateWriter DecorateWriter + // Maximum number of TCP queries before we close the socket. Default is maxTCPQueries (unlimited if -1). + MaxTCPQueries int + // Whether to set the SO_REUSEPORT socket option, allowing multiple listeners to be bound to a single address. + // It is only supported on go1.11+ and when using ListenAndServe. + ReusePort bool + // AcceptMsgFunc will check the incoming message and will reject it early in the process. + // By default DefaultMsgAcceptFunc will be used. + MsgAcceptFunc MsgAcceptFunc + + // Shutdown handling + lock sync.RWMutex + started bool + shutdown chan struct{} + conns map[net.Conn]struct{} + + // A pool for UDP message buffers. + udpPool sync.Pool +} + +func (srv *Server) isStarted() bool { + srv.lock.RLock() + started := srv.started + srv.lock.RUnlock() + return started +} + +func makeUDPBuffer(size int) func() interface{} { + return func() interface{} { + return make([]byte, size) + } +} + +func (srv *Server) init() { + srv.shutdown = make(chan struct{}) + srv.conns = make(map[net.Conn]struct{}) + + if srv.UDPSize == 0 { + srv.UDPSize = MinMsgSize + } + if srv.MsgAcceptFunc == nil { + srv.MsgAcceptFunc = DefaultMsgAcceptFunc + } + if srv.Handler == nil { + srv.Handler = DefaultServeMux + } + + srv.udpPool.New = makeUDPBuffer(srv.UDPSize) +} + +func unlockOnce(l sync.Locker) func() { + var once sync.Once + return func() { once.Do(l.Unlock) } +} + +// ListenAndServe starts a nameserver on the configured address in *Server. +func (srv *Server) ListenAndServe() error { + unlock := unlockOnce(&srv.lock) + srv.lock.Lock() + defer unlock() + + if srv.started { + return &Error{err: "server already started"} + } + + addr := srv.Addr + if addr == "" { + addr = ":domain" + } + + srv.init() + + switch srv.Net { + case "tcp", "tcp4", "tcp6": + l, err := listenTCP(srv.Net, addr, srv.ReusePort) + if err != nil { + return err + } + srv.Listener = l + srv.started = true + unlock() + return srv.serveTCP(l) + case "tcp-tls", "tcp4-tls", "tcp6-tls": + if srv.TLSConfig == nil || (len(srv.TLSConfig.Certificates) == 0 && srv.TLSConfig.GetCertificate == nil) { + return errors.New("dns: neither Certificates nor GetCertificate set in Config") + } + network := strings.TrimSuffix(srv.Net, "-tls") + l, err := listenTCP(network, addr, srv.ReusePort) + if err != nil { + return err + } + l = tls.NewListener(l, srv.TLSConfig) + srv.Listener = l + srv.started = true + unlock() + return srv.serveTCP(l) + case "udp", "udp4", "udp6": + l, err := listenUDP(srv.Net, addr, srv.ReusePort) + if err != nil { + return err + } + u := l.(*net.UDPConn) + if e := setUDPSocketOptions(u); e != nil { + u.Close() + return e + } + srv.PacketConn = l + srv.started = true + unlock() + return srv.serveUDP(u) + } + return &Error{err: "bad network"} +} + +// ActivateAndServe starts a nameserver with the PacketConn or Listener +// configured in *Server. Its main use is to start a server from systemd. +func (srv *Server) ActivateAndServe() error { + unlock := unlockOnce(&srv.lock) + srv.lock.Lock() + defer unlock() + + if srv.started { + return &Error{err: "server already started"} + } + + srv.init() + + if srv.PacketConn != nil { + // Check PacketConn interface's type is valid and value + // is not nil + if t, ok := srv.PacketConn.(*net.UDPConn); ok && t != nil { + if e := setUDPSocketOptions(t); e != nil { + return e + } + } + srv.started = true + unlock() + return srv.serveUDP(srv.PacketConn) + } + if srv.Listener != nil { + srv.started = true + unlock() + return srv.serveTCP(srv.Listener) + } + return &Error{err: "bad listeners"} +} + +// Shutdown shuts down a server. After a call to Shutdown, ListenAndServe and +// ActivateAndServe will return. +func (srv *Server) Shutdown() error { + return srv.ShutdownContext(context.Background()) +} + +// ShutdownContext shuts down a server. After a call to ShutdownContext, +// ListenAndServe and ActivateAndServe will return. +// +// A context.Context may be passed to limit how long to wait for connections +// to terminate. +func (srv *Server) ShutdownContext(ctx context.Context) error { + srv.lock.Lock() + if !srv.started { + srv.lock.Unlock() + return &Error{err: "server not started"} + } + + srv.started = false + + if srv.PacketConn != nil { + srv.PacketConn.SetReadDeadline(aLongTimeAgo) // Unblock reads + } + + if srv.Listener != nil { + srv.Listener.Close() + } + + for rw := range srv.conns { + rw.SetReadDeadline(aLongTimeAgo) // Unblock reads + } + + srv.lock.Unlock() + + if testShutdownNotify != nil { + testShutdownNotify.Broadcast() + } + + var ctxErr error + select { + case <-srv.shutdown: + case <-ctx.Done(): + ctxErr = ctx.Err() + } + + if srv.PacketConn != nil { + srv.PacketConn.Close() + } + + return ctxErr +} + +var testShutdownNotify *sync.Cond + +// getReadTimeout is a helper func to use system timeout if server did not intend to change it. +func (srv *Server) getReadTimeout() time.Duration { + if srv.ReadTimeout != 0 { + return srv.ReadTimeout + } + return dnsTimeout +} + +// serveTCP starts a TCP listener for the server. +func (srv *Server) serveTCP(l net.Listener) error { + defer l.Close() + + if srv.NotifyStartedFunc != nil { + srv.NotifyStartedFunc() + } + + var wg sync.WaitGroup + defer func() { + wg.Wait() + close(srv.shutdown) + }() + + for srv.isStarted() { + rw, err := l.Accept() + if err != nil { + if !srv.isStarted() { + return nil + } + if neterr, ok := err.(net.Error); ok && neterr.Temporary() { + continue + } + return err + } + srv.lock.Lock() + // Track the connection to allow unblocking reads on shutdown. + srv.conns[rw] = struct{}{} + srv.lock.Unlock() + wg.Add(1) + go srv.serveTCPConn(&wg, rw) + } + + return nil +} + +// serveUDP starts a UDP listener for the server. +func (srv *Server) serveUDP(l net.PacketConn) error { + defer l.Close() + + reader := Reader(defaultReader{srv}) + if srv.DecorateReader != nil { + reader = srv.DecorateReader(reader) + } + + lUDP, isUDP := l.(*net.UDPConn) + readerPC, canPacketConn := reader.(PacketConnReader) + if !isUDP && !canPacketConn { + return &Error{err: "PacketConnReader was not implemented on Reader returned from DecorateReader but is required for net.PacketConn"} + } + + if srv.NotifyStartedFunc != nil { + srv.NotifyStartedFunc() + } + + var wg sync.WaitGroup + defer func() { + wg.Wait() + close(srv.shutdown) + }() + + rtimeout := srv.getReadTimeout() + // deadline is not used here + for srv.isStarted() { + var ( + m []byte + sPC net.Addr + sUDP *SessionUDP + err error + ) + if isUDP { + m, sUDP, err = reader.ReadUDP(lUDP, rtimeout) + } else { + m, sPC, err = readerPC.ReadPacketConn(l, rtimeout) + } + if err != nil { + if !srv.isStarted() { + return nil + } + if netErr, ok := err.(net.Error); ok && netErr.Temporary() { + continue + } + return err + } + if len(m) < headerSize { + if cap(m) == srv.UDPSize { + srv.udpPool.Put(m[:srv.UDPSize]) + } + continue + } + wg.Add(1) + go srv.serveUDPPacket(&wg, m, l, sUDP, sPC) + } + + return nil +} + +// Serve a new TCP connection. +func (srv *Server) serveTCPConn(wg *sync.WaitGroup, rw net.Conn) { + w := &response{tsigSecret: srv.TsigSecret, tcp: rw} + if srv.DecorateWriter != nil { + w.writer = srv.DecorateWriter(w) + } else { + w.writer = w + } + + reader := Reader(defaultReader{srv}) + if srv.DecorateReader != nil { + reader = srv.DecorateReader(reader) + } + + idleTimeout := tcpIdleTimeout + if srv.IdleTimeout != nil { + idleTimeout = srv.IdleTimeout() + } + + timeout := srv.getReadTimeout() + + limit := srv.MaxTCPQueries + if limit == 0 { + limit = maxTCPQueries + } + + for q := 0; (q < limit || limit == -1) && srv.isStarted(); q++ { + m, err := reader.ReadTCP(w.tcp, timeout) + if err != nil { + // TODO(tmthrgd): handle error + break + } + srv.serveDNS(m, w) + if w.closed { + break // Close() was called + } + if w.hijacked { + break // client will call Close() themselves + } + // The first read uses the read timeout, the rest use the + // idle timeout. + timeout = idleTimeout + } + + if !w.hijacked { + w.Close() + } + + srv.lock.Lock() + delete(srv.conns, w.tcp) + srv.lock.Unlock() + + wg.Done() +} + +// Serve a new UDP request. +func (srv *Server) serveUDPPacket(wg *sync.WaitGroup, m []byte, u net.PacketConn, udpSession *SessionUDP, pcSession net.Addr) { + w := &response{tsigSecret: srv.TsigSecret, udp: u, udpSession: udpSession, pcSession: pcSession} + if srv.DecorateWriter != nil { + w.writer = srv.DecorateWriter(w) + } else { + w.writer = w + } + + srv.serveDNS(m, w) + wg.Done() +} + +func (srv *Server) serveDNS(m []byte, w *response) { + dh, off, err := unpackMsgHdr(m, 0) + if err != nil { + // Let client hang, they are sending crap; any reply can be used to amplify. + return + } + + req := new(Msg) + req.setHdr(dh) + + switch action := srv.MsgAcceptFunc(dh); action { + case MsgAccept: + if req.unpack(dh, m, off) == nil { + break + } + + fallthrough + case MsgReject, MsgRejectNotImplemented: + opcode := req.Opcode + req.SetRcodeFormatError(req) + req.Zero = false + if action == MsgRejectNotImplemented { + req.Opcode = opcode + req.Rcode = RcodeNotImplemented + } + + // Are we allowed to delete any OPT records here? + req.Ns, req.Answer, req.Extra = nil, nil, nil + + w.WriteMsg(req) + fallthrough + case MsgIgnore: + if w.udp != nil && cap(m) == srv.UDPSize { + srv.udpPool.Put(m[:srv.UDPSize]) + } + + return + } + + w.tsigStatus = nil + if w.tsigSecret != nil { + if t := req.IsTsig(); t != nil { + if secret, ok := w.tsigSecret[t.Hdr.Name]; ok { + w.tsigStatus = TsigVerify(m, secret, "", false) + } else { + w.tsigStatus = ErrSecret + } + w.tsigTimersOnly = false + w.tsigRequestMAC = req.Extra[len(req.Extra)-1].(*TSIG).MAC + } + } + + if w.udp != nil && cap(m) == srv.UDPSize { + srv.udpPool.Put(m[:srv.UDPSize]) + } + + srv.Handler.ServeDNS(w, req) // Writes back to the client +} + +func (srv *Server) readTCP(conn net.Conn, timeout time.Duration) ([]byte, error) { + // If we race with ShutdownContext, the read deadline may + // have been set in the distant past to unblock the read + // below. We must not override it, otherwise we may block + // ShutdownContext. + srv.lock.RLock() + if srv.started { + conn.SetReadDeadline(time.Now().Add(timeout)) + } + srv.lock.RUnlock() + + var length uint16 + if err := binary.Read(conn, binary.BigEndian, &length); err != nil { + return nil, err + } + + m := make([]byte, length) + if _, err := io.ReadFull(conn, m); err != nil { + return nil, err + } + + return m, nil +} + +func (srv *Server) readUDP(conn *net.UDPConn, timeout time.Duration) ([]byte, *SessionUDP, error) { + srv.lock.RLock() + if srv.started { + // See the comment in readTCP above. + conn.SetReadDeadline(time.Now().Add(timeout)) + } + srv.lock.RUnlock() + + m := srv.udpPool.Get().([]byte) + n, s, err := ReadFromSessionUDP(conn, m) + if err != nil { + srv.udpPool.Put(m) + return nil, nil, err + } + m = m[:n] + return m, s, nil +} + +func (srv *Server) readPacketConn(conn net.PacketConn, timeout time.Duration) ([]byte, net.Addr, error) { + srv.lock.RLock() + if srv.started { + // See the comment in readTCP above. + conn.SetReadDeadline(time.Now().Add(timeout)) + } + srv.lock.RUnlock() + + m := srv.udpPool.Get().([]byte) + n, addr, err := conn.ReadFrom(m) + if err != nil { + srv.udpPool.Put(m) + return nil, nil, err + } + m = m[:n] + return m, addr, nil +} + +// WriteMsg implements the ResponseWriter.WriteMsg method. +func (w *response) WriteMsg(m *Msg) (err error) { + if w.closed { + return &Error{err: "WriteMsg called after Close"} + } + + var data []byte + if w.tsigSecret != nil { // if no secrets, dont check for the tsig (which is a longer check) + if t := m.IsTsig(); t != nil { + data, w.tsigRequestMAC, err = TsigGenerate(m, w.tsigSecret[t.Hdr.Name], w.tsigRequestMAC, w.tsigTimersOnly) + if err != nil { + return err + } + _, err = w.writer.Write(data) + return err + } + } + data, err = m.Pack() + if err != nil { + return err + } + _, err = w.writer.Write(data) + return err +} + +// Write implements the ResponseWriter.Write method. +func (w *response) Write(m []byte) (int, error) { + if w.closed { + return 0, &Error{err: "Write called after Close"} + } + + switch { + case w.udp != nil: + if u, ok := w.udp.(*net.UDPConn); ok { + return WriteToSessionUDP(u, m, w.udpSession) + } + return w.udp.WriteTo(m, w.pcSession) + case w.tcp != nil: + if len(m) > MaxMsgSize { + return 0, &Error{err: "message too large"} + } + + msg := make([]byte, 2+len(m)) + binary.BigEndian.PutUint16(msg, uint16(len(m))) + copy(msg[2:], m) + return w.tcp.Write(msg) + default: + panic("dns: internal error: udp and tcp both nil") + } +} + +// LocalAddr implements the ResponseWriter.LocalAddr method. +func (w *response) LocalAddr() net.Addr { + switch { + case w.udp != nil: + return w.udp.LocalAddr() + case w.tcp != nil: + return w.tcp.LocalAddr() + default: + panic("dns: internal error: udp and tcp both nil") + } +} + +// RemoteAddr implements the ResponseWriter.RemoteAddr method. +func (w *response) RemoteAddr() net.Addr { + switch { + case w.udpSession != nil: + return w.udpSession.RemoteAddr() + case w.pcSession != nil: + return w.pcSession + case w.tcp != nil: + return w.tcp.RemoteAddr() + default: + panic("dns: internal error: udpSession, pcSession and tcp are all nil") + } +} + +// TsigStatus implements the ResponseWriter.TsigStatus method. +func (w *response) TsigStatus() error { return w.tsigStatus } + +// TsigTimersOnly implements the ResponseWriter.TsigTimersOnly method. +func (w *response) TsigTimersOnly(b bool) { w.tsigTimersOnly = b } + +// Hijack implements the ResponseWriter.Hijack method. +func (w *response) Hijack() { w.hijacked = true } + +// Close implements the ResponseWriter.Close method +func (w *response) Close() error { + if w.closed { + return &Error{err: "connection already closed"} + } + w.closed = true + + switch { + case w.udp != nil: + // Can't close the udp conn, as that is actually the listener. + return nil + case w.tcp != nil: + return w.tcp.Close() + default: + panic("dns: internal error: udp and tcp both nil") + } +} + +// ConnectionState() implements the ConnectionStater.ConnectionState() interface. +func (w *response) ConnectionState() *tls.ConnectionState { + type tlsConnectionStater interface { + ConnectionState() tls.ConnectionState + } + if v, ok := w.tcp.(tlsConnectionStater); ok { + t := v.ConnectionState() + return &t + } + return nil +} diff --git a/vendor/github.com/miekg/dns/sig0.go b/vendor/github.com/miekg/dns/sig0.go new file mode 100644 index 0000000..e781c9b --- /dev/null +++ b/vendor/github.com/miekg/dns/sig0.go @@ -0,0 +1,197 @@ +package dns + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "encoding/binary" + "math/big" + "strings" + "time" +) + +// Sign signs a dns.Msg. It fills the signature with the appropriate data. +// The SIG record should have the SignerName, KeyTag, Algorithm, Inception +// and Expiration set. +func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) { + if k == nil { + return nil, ErrPrivKey + } + if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 { + return nil, ErrKey + } + + rr.Hdr = RR_Header{Name: ".", Rrtype: TypeSIG, Class: ClassANY, Ttl: 0} + rr.OrigTtl, rr.TypeCovered, rr.Labels = 0, 0, 0 + + buf := make([]byte, m.Len()+Len(rr)) + mbuf, err := m.PackBuffer(buf) + if err != nil { + return nil, err + } + if &buf[0] != &mbuf[0] { + return nil, ErrBuf + } + off, err := PackRR(rr, buf, len(mbuf), nil, false) + if err != nil { + return nil, err + } + buf = buf[:off:cap(buf)] + + hash, ok := AlgorithmToHash[rr.Algorithm] + if !ok { + return nil, ErrAlg + } + + hasher := hash.New() + // Write SIG rdata + hasher.Write(buf[len(mbuf)+1+2+2+4+2:]) + // Write message + hasher.Write(buf[:len(mbuf)]) + + signature, err := sign(k, hasher.Sum(nil), hash, rr.Algorithm) + if err != nil { + return nil, err + } + + rr.Signature = toBase64(signature) + + buf = append(buf, signature...) + if len(buf) > int(^uint16(0)) { + return nil, ErrBuf + } + // Adjust sig data length + rdoff := len(mbuf) + 1 + 2 + 2 + 4 + rdlen := binary.BigEndian.Uint16(buf[rdoff:]) + rdlen += uint16(len(signature)) + binary.BigEndian.PutUint16(buf[rdoff:], rdlen) + // Adjust additional count + adc := binary.BigEndian.Uint16(buf[10:]) + adc++ + binary.BigEndian.PutUint16(buf[10:], adc) + return buf, nil +} + +// Verify validates the message buf using the key k. +// It's assumed that buf is a valid message from which rr was unpacked. +func (rr *SIG) Verify(k *KEY, buf []byte) error { + if k == nil { + return ErrKey + } + if rr.KeyTag == 0 || rr.SignerName == "" || rr.Algorithm == 0 { + return ErrKey + } + + var hash crypto.Hash + switch rr.Algorithm { + case RSASHA1: + hash = crypto.SHA1 + case RSASHA256, ECDSAP256SHA256: + hash = crypto.SHA256 + case ECDSAP384SHA384: + hash = crypto.SHA384 + case RSASHA512: + hash = crypto.SHA512 + default: + return ErrAlg + } + hasher := hash.New() + + buflen := len(buf) + qdc := binary.BigEndian.Uint16(buf[4:]) + anc := binary.BigEndian.Uint16(buf[6:]) + auc := binary.BigEndian.Uint16(buf[8:]) + adc := binary.BigEndian.Uint16(buf[10:]) + offset := headerSize + var err error + for i := uint16(0); i < qdc && offset < buflen; i++ { + _, offset, err = UnpackDomainName(buf, offset) + if err != nil { + return err + } + // Skip past Type and Class + offset += 2 + 2 + } + for i := uint16(1); i < anc+auc+adc && offset < buflen; i++ { + _, offset, err = UnpackDomainName(buf, offset) + if err != nil { + return err + } + // Skip past Type, Class and TTL + offset += 2 + 2 + 4 + if offset+1 >= buflen { + continue + } + rdlen := binary.BigEndian.Uint16(buf[offset:]) + offset += 2 + offset += int(rdlen) + } + if offset >= buflen { + return &Error{err: "overflowing unpacking signed message"} + } + + // offset should be just prior to SIG + bodyend := offset + // owner name SHOULD be root + _, offset, err = UnpackDomainName(buf, offset) + if err != nil { + return err + } + // Skip Type, Class, TTL, RDLen + offset += 2 + 2 + 4 + 2 + sigstart := offset + // Skip Type Covered, Algorithm, Labels, Original TTL + offset += 2 + 1 + 1 + 4 + if offset+4+4 >= buflen { + return &Error{err: "overflow unpacking signed message"} + } + expire := binary.BigEndian.Uint32(buf[offset:]) + offset += 4 + incept := binary.BigEndian.Uint32(buf[offset:]) + offset += 4 + now := uint32(time.Now().Unix()) + if now < incept || now > expire { + return ErrTime + } + // Skip key tag + offset += 2 + var signername string + signername, offset, err = UnpackDomainName(buf, offset) + if err != nil { + return err + } + // If key has come from the DNS name compression might + // have mangled the case of the name + if !strings.EqualFold(signername, k.Header().Name) { + return &Error{err: "signer name doesn't match key name"} + } + sigend := offset + hasher.Write(buf[sigstart:sigend]) + hasher.Write(buf[:10]) + hasher.Write([]byte{ + byte((adc - 1) << 8), + byte(adc - 1), + }) + hasher.Write(buf[12:bodyend]) + + hashed := hasher.Sum(nil) + sig := buf[sigend:] + switch k.Algorithm { + case RSASHA1, RSASHA256, RSASHA512: + pk := k.publicKeyRSA() + if pk != nil { + return rsa.VerifyPKCS1v15(pk, hash, hashed, sig) + } + case ECDSAP256SHA256, ECDSAP384SHA384: + pk := k.publicKeyECDSA() + r := new(big.Int).SetBytes(sig[:len(sig)/2]) + s := new(big.Int).SetBytes(sig[len(sig)/2:]) + if pk != nil { + if ecdsa.Verify(pk, hashed, r, s) { + return nil + } + return ErrSig + } + } + return ErrKeyAlg +} diff --git a/vendor/github.com/miekg/dns/singleinflight.go b/vendor/github.com/miekg/dns/singleinflight.go new file mode 100644 index 0000000..febcc30 --- /dev/null +++ b/vendor/github.com/miekg/dns/singleinflight.go @@ -0,0 +1,61 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Adapted for dns package usage by Miek Gieben. + +package dns + +import "sync" +import "time" + +// call is an in-flight or completed singleflight.Do call +type call struct { + wg sync.WaitGroup + val *Msg + rtt time.Duration + err error + dups int +} + +// singleflight represents a class of work and forms a namespace in +// which units of work can be executed with duplicate suppression. +type singleflight struct { + sync.Mutex // protects m + m map[string]*call // lazily initialized + + dontDeleteForTesting bool // this is only to be used by TestConcurrentExchanges +} + +// Do executes and returns the results of the given function, making +// sure that only one execution is in-flight for a given key at a +// time. If a duplicate comes in, the duplicate caller waits for the +// original to complete and receives the same results. +// The return value shared indicates whether v was given to multiple callers. +func (g *singleflight) Do(key string, fn func() (*Msg, time.Duration, error)) (v *Msg, rtt time.Duration, err error, shared bool) { + g.Lock() + if g.m == nil { + g.m = make(map[string]*call) + } + if c, ok := g.m[key]; ok { + c.dups++ + g.Unlock() + c.wg.Wait() + return c.val, c.rtt, c.err, true + } + c := new(call) + c.wg.Add(1) + g.m[key] = c + g.Unlock() + + c.val, c.rtt, c.err = fn() + c.wg.Done() + + if !g.dontDeleteForTesting { + g.Lock() + delete(g.m, key) + g.Unlock() + } + + return c.val, c.rtt, c.err, c.dups > 0 +} diff --git a/vendor/github.com/miekg/dns/smimea.go b/vendor/github.com/miekg/dns/smimea.go new file mode 100644 index 0000000..89f09f0 --- /dev/null +++ b/vendor/github.com/miekg/dns/smimea.go @@ -0,0 +1,44 @@ +package dns + +import ( + "crypto/sha256" + "crypto/x509" + "encoding/hex" +) + +// Sign creates a SMIMEA record from an SSL certificate. +func (r *SMIMEA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (err error) { + r.Hdr.Rrtype = TypeSMIMEA + r.Usage = uint8(usage) + r.Selector = uint8(selector) + r.MatchingType = uint8(matchingType) + + r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert) + return err +} + +// Verify verifies a SMIMEA record against an SSL certificate. If it is OK +// a nil error is returned. +func (r *SMIMEA) Verify(cert *x509.Certificate) error { + c, err := CertificateToDANE(r.Selector, r.MatchingType, cert) + if err != nil { + return err // Not also ErrSig? + } + if r.Certificate == c { + return nil + } + return ErrSig // ErrSig, really? +} + +// SMIMEAName returns the ownername of a SMIMEA resource record as per the +// format specified in RFC 'draft-ietf-dane-smime-12' Section 2 and 3 +func SMIMEAName(email, domain string) (string, error) { + hasher := sha256.New() + hasher.Write([]byte(email)) + + // RFC Section 3: "The local-part is hashed using the SHA2-256 + // algorithm with the hash truncated to 28 octets and + // represented in its hexadecimal representation to become the + // left-most label in the prepared domain name" + return hex.EncodeToString(hasher.Sum(nil)[:28]) + "." + "_smimecert." + domain, nil +} diff --git a/vendor/github.com/miekg/dns/svcb.go b/vendor/github.com/miekg/dns/svcb.go new file mode 100644 index 0000000..ec0a76f --- /dev/null +++ b/vendor/github.com/miekg/dns/svcb.go @@ -0,0 +1,744 @@ +package dns + +import ( + "bytes" + "encoding/binary" + "errors" + "net" + "sort" + "strconv" + "strings" +) + +type SVCBKey uint16 + +// Keys defined in draft-ietf-dnsop-svcb-https-01 Section 12.3.2. +const ( + SVCB_MANDATORY SVCBKey = 0 + SVCB_ALPN SVCBKey = 1 + SVCB_NO_DEFAULT_ALPN SVCBKey = 2 + SVCB_PORT SVCBKey = 3 + SVCB_IPV4HINT SVCBKey = 4 + SVCB_ECHCONFIG SVCBKey = 5 + SVCB_IPV6HINT SVCBKey = 6 + svcb_RESERVED SVCBKey = 65535 +) + +var svcbKeyToStringMap = map[SVCBKey]string{ + SVCB_MANDATORY: "mandatory", + SVCB_ALPN: "alpn", + SVCB_NO_DEFAULT_ALPN: "no-default-alpn", + SVCB_PORT: "port", + SVCB_IPV4HINT: "ipv4hint", + SVCB_ECHCONFIG: "echconfig", + SVCB_IPV6HINT: "ipv6hint", +} + +var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap) + +func reverseSVCBKeyMap(m map[SVCBKey]string) map[string]SVCBKey { + n := make(map[string]SVCBKey, len(m)) + for u, s := range m { + n[s] = u + } + return n +} + +// String takes the numerical code of an SVCB key and returns its name. +// Returns an empty string for reserved keys. +// Accepts unassigned keys as well as experimental/private keys. +func (key SVCBKey) String() string { + if x := svcbKeyToStringMap[key]; x != "" { + return x + } + if key == svcb_RESERVED { + return "" + } + return "key" + strconv.FormatUint(uint64(key), 10) +} + +// svcbStringToKey returns the numerical code of an SVCB key. +// Returns svcb_RESERVED for reserved/invalid keys. +// Accepts unassigned keys as well as experimental/private keys. +func svcbStringToKey(s string) SVCBKey { + if strings.HasPrefix(s, "key") { + a, err := strconv.ParseUint(s[3:], 10, 16) + // no leading zeros + // key shouldn't be registered + if err != nil || a == 65535 || s[3] == '0' || svcbKeyToStringMap[SVCBKey(a)] != "" { + return svcb_RESERVED + } + return SVCBKey(a) + } + if key, ok := svcbStringToKeyMap[s]; ok { + return key + } + return svcb_RESERVED +} + +func (rr *SVCB) parse(c *zlexer, o string) *ParseError { + l, _ := c.Next() + i, e := strconv.ParseUint(l.token, 10, 16) + if e != nil || l.err { + return &ParseError{l.token, "bad SVCB priority", l} + } + rr.Priority = uint16(i) + + c.Next() // zBlank + l, _ = c.Next() // zString + rr.Target = l.token + + name, nameOk := toAbsoluteName(l.token, o) + if l.err || !nameOk { + return &ParseError{l.token, "bad SVCB Target", l} + } + rr.Target = name + + // Values (if any) + l, _ = c.Next() + var xs []SVCBKeyValue + // Helps require whitespace between pairs. + // Prevents key1000="a"key1001=... + canHaveNextKey := true + for l.value != zNewline && l.value != zEOF { + switch l.value { + case zString: + if !canHaveNextKey { + // The key we can now read was probably meant to be + // a part of the last value. + return &ParseError{l.token, "bad SVCB value quotation", l} + } + + // In key=value pairs, value does not have to be quoted unless value + // contains whitespace. And keys don't need to have values. + // Similarly, keys with an equality signs after them don't need values. + // l.token includes at least up to the first equality sign. + idx := strings.IndexByte(l.token, '=') + var key, value string + if idx < 0 { + // Key with no value and no equality sign + key = l.token + } else if idx == 0 { + return &ParseError{l.token, "bad SVCB key", l} + } else { + key, value = l.token[:idx], l.token[idx+1:] + + if value == "" { + // We have a key and an equality sign. Maybe we have nothing + // after "=" or we have a double quote. + l, _ = c.Next() + if l.value == zQuote { + // Only needed when value ends with double quotes. + // Any value starting with zQuote ends with it. + canHaveNextKey = false + + l, _ = c.Next() + switch l.value { + case zString: + // We have a value in double quotes. + value = l.token + l, _ = c.Next() + if l.value != zQuote { + return &ParseError{l.token, "SVCB unterminated value", l} + } + case zQuote: + // There's nothing in double quotes. + default: + return &ParseError{l.token, "bad SVCB value", l} + } + } + } + } + kv := makeSVCBKeyValue(svcbStringToKey(key)) + if kv == nil { + return &ParseError{l.token, "bad SVCB key", l} + } + if err := kv.parse(value); err != nil { + return &ParseError{l.token, err.Error(), l} + } + xs = append(xs, kv) + case zQuote: + return &ParseError{l.token, "SVCB key can't contain double quotes", l} + case zBlank: + canHaveNextKey = true + default: + return &ParseError{l.token, "bad SVCB values", l} + } + l, _ = c.Next() + } + rr.Value = xs + if rr.Priority == 0 && len(xs) > 0 { + return &ParseError{l.token, "SVCB aliasform can't have values", l} + } + return nil +} + +// makeSVCBKeyValue returns an SVCBKeyValue struct with the key or nil for reserved keys. +func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue { + switch key { + case SVCB_MANDATORY: + return new(SVCBMandatory) + case SVCB_ALPN: + return new(SVCBAlpn) + case SVCB_NO_DEFAULT_ALPN: + return new(SVCBNoDefaultAlpn) + case SVCB_PORT: + return new(SVCBPort) + case SVCB_IPV4HINT: + return new(SVCBIPv4Hint) + case SVCB_ECHCONFIG: + return new(SVCBECHConfig) + case SVCB_IPV6HINT: + return new(SVCBIPv6Hint) + case svcb_RESERVED: + return nil + default: + e := new(SVCBLocal) + e.KeyCode = key + return e + } +} + +// SVCB RR. See RFC xxxx (https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-01). +type SVCB struct { + Hdr RR_Header + Priority uint16 + Target string `dns:"domain-name"` + Value []SVCBKeyValue `dns:"pairs"` // Value must be empty if Priority is zero. +} + +// HTTPS RR. Everything valid for SVCB applies to HTTPS as well. +// Except that the HTTPS record is intended for use with the HTTP and HTTPS protocols. +type HTTPS struct { + SVCB +} + +func (rr *HTTPS) String() string { + return rr.SVCB.String() +} + +func (rr *HTTPS) parse(c *zlexer, o string) *ParseError { + return rr.SVCB.parse(c, o) +} + +// SVCBKeyValue defines a key=value pair for the SVCB RR type. +// An SVCB RR can have multiple SVCBKeyValues appended to it. +type SVCBKeyValue interface { + Key() SVCBKey // Key returns the numerical key code. + pack() ([]byte, error) // pack returns the encoded value. + unpack([]byte) error // unpack sets the value. + String() string // String returns the string representation of the value. + parse(string) error // parse sets the value to the given string representation of the value. + copy() SVCBKeyValue // copy returns a deep-copy of the pair. + len() int // len returns the length of value in the wire format. +} + +// SVCBMandatory pair adds to required keys that must be interpreted for the RR +// to be functional. +// Basic use pattern for creating a mandatory option: +// +// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}} +// e := new(dns.SVCBMandatory) +// e.Code = []uint16{65403} +// s.Value = append(s.Value, e) +type SVCBMandatory struct { + Code []SVCBKey // Must not include mandatory +} + +func (*SVCBMandatory) Key() SVCBKey { return SVCB_MANDATORY } + +func (s *SVCBMandatory) String() string { + str := make([]string, len(s.Code)) + for i, e := range s.Code { + str[i] = e.String() + } + return strings.Join(str, ",") +} + +func (s *SVCBMandatory) pack() ([]byte, error) { + codes := append([]SVCBKey(nil), s.Code...) + sort.Slice(codes, func(i, j int) bool { + return codes[i] < codes[j] + }) + b := make([]byte, 2*len(codes)) + for i, e := range codes { + binary.BigEndian.PutUint16(b[2*i:], uint16(e)) + } + return b, nil +} + +func (s *SVCBMandatory) unpack(b []byte) error { + if len(b)%2 != 0 { + return errors.New("dns: svcbmandatory: value length is not a multiple of 2") + } + codes := make([]SVCBKey, 0, len(b)/2) + for i := 0; i < len(b); i += 2 { + // We assume strictly increasing order. + codes = append(codes, SVCBKey(binary.BigEndian.Uint16(b[i:]))) + } + s.Code = codes + return nil +} + +func (s *SVCBMandatory) parse(b string) error { + str := strings.Split(b, ",") + codes := make([]SVCBKey, 0, len(str)) + for _, e := range str { + codes = append(codes, svcbStringToKey(e)) + } + s.Code = codes + return nil +} + +func (s *SVCBMandatory) len() int { + return 2 * len(s.Code) +} + +func (s *SVCBMandatory) copy() SVCBKeyValue { + return &SVCBMandatory{ + append([]SVCBKey(nil), s.Code...), + } +} + +// SVCBAlpn pair is used to list supported connection protocols. +// Protocol ids can be found at: +// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids +// Basic use pattern for creating an alpn option: +// +// h := new(dns.HTTPS) +// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET} +// e := new(dns.SVCBAlpn) +// e.Alpn = []string{"h2", "http/1.1"} +// h.Value = append(o.Value, e) +type SVCBAlpn struct { + Alpn []string +} + +func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN } +func (s *SVCBAlpn) String() string { return strings.Join(s.Alpn, ",") } + +func (s *SVCBAlpn) pack() ([]byte, error) { + // Liberally estimate the size of an alpn as 10 octets + b := make([]byte, 0, 10*len(s.Alpn)) + for _, e := range s.Alpn { + if e == "" { + return nil, errors.New("dns: svcbalpn: empty alpn-id") + } + if len(e) > 255 { + return nil, errors.New("dns: svcbalpn: alpn-id too long") + } + b = append(b, byte(len(e))) + b = append(b, e...) + } + return b, nil +} + +func (s *SVCBAlpn) unpack(b []byte) error { + // Estimate the size of the smallest alpn as 4 bytes + alpn := make([]string, 0, len(b)/4) + for i := 0; i < len(b); { + length := int(b[i]) + i++ + if i+length > len(b) { + return errors.New("dns: svcbalpn: alpn array overflowing") + } + alpn = append(alpn, string(b[i:i+length])) + i += length + } + s.Alpn = alpn + return nil +} + +func (s *SVCBAlpn) parse(b string) error { + s.Alpn = strings.Split(b, ",") + return nil +} + +func (s *SVCBAlpn) len() int { + var l int + for _, e := range s.Alpn { + l += 1 + len(e) + } + return l +} + +func (s *SVCBAlpn) copy() SVCBKeyValue { + return &SVCBAlpn{ + append([]string(nil), s.Alpn...), + } +} + +// SVCBNoDefaultAlpn pair signifies no support for default connection protocols. +// Basic use pattern for creating a no-default-alpn option: +// +// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}} +// e := new(dns.SVCBNoDefaultAlpn) +// s.Value = append(s.Value, e) +type SVCBNoDefaultAlpn struct{} + +func (*SVCBNoDefaultAlpn) Key() SVCBKey { return SVCB_NO_DEFAULT_ALPN } +func (*SVCBNoDefaultAlpn) copy() SVCBKeyValue { return &SVCBNoDefaultAlpn{} } +func (*SVCBNoDefaultAlpn) pack() ([]byte, error) { return []byte{}, nil } +func (*SVCBNoDefaultAlpn) String() string { return "" } +func (*SVCBNoDefaultAlpn) len() int { return 0 } + +func (*SVCBNoDefaultAlpn) unpack(b []byte) error { + if len(b) != 0 { + return errors.New("dns: svcbnodefaultalpn: no_default_alpn must have no value") + } + return nil +} + +func (*SVCBNoDefaultAlpn) parse(b string) error { + if b != "" { + return errors.New("dns: svcbnodefaultalpn: no_default_alpn must have no value") + } + return nil +} + +// SVCBPort pair defines the port for connection. +// Basic use pattern for creating a port option: +// +// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}} +// e := new(dns.SVCBPort) +// e.Port = 80 +// s.Value = append(s.Value, e) +type SVCBPort struct { + Port uint16 +} + +func (*SVCBPort) Key() SVCBKey { return SVCB_PORT } +func (*SVCBPort) len() int { return 2 } +func (s *SVCBPort) String() string { return strconv.FormatUint(uint64(s.Port), 10) } +func (s *SVCBPort) copy() SVCBKeyValue { return &SVCBPort{s.Port} } + +func (s *SVCBPort) unpack(b []byte) error { + if len(b) != 2 { + return errors.New("dns: svcbport: port length is not exactly 2 octets") + } + s.Port = binary.BigEndian.Uint16(b) + return nil +} + +func (s *SVCBPort) pack() ([]byte, error) { + b := make([]byte, 2) + binary.BigEndian.PutUint16(b, s.Port) + return b, nil +} + +func (s *SVCBPort) parse(b string) error { + port, err := strconv.ParseUint(b, 10, 16) + if err != nil { + return errors.New("dns: svcbport: port out of range") + } + s.Port = uint16(port) + return nil +} + +// SVCBIPv4Hint pair suggests an IPv4 address which may be used to open connections +// if A and AAAA record responses for SVCB's Target domain haven't been received. +// In that case, optionally, A and AAAA requests can be made, after which the connection +// to the hinted IP address may be terminated and a new connection may be opened. +// Basic use pattern for creating an ipv4hint option: +// +// h := new(dns.HTTPS) +// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET} +// e := new(dns.SVCBIPv4Hint) +// e.Hint = []net.IP{net.IPv4(1,1,1,1).To4()} +// +// Or +// +// e.Hint = []net.IP{net.ParseIP("1.1.1.1").To4()} +// h.Value = append(h.Value, e) +type SVCBIPv4Hint struct { + Hint []net.IP +} + +func (*SVCBIPv4Hint) Key() SVCBKey { return SVCB_IPV4HINT } +func (s *SVCBIPv4Hint) len() int { return 4 * len(s.Hint) } + +func (s *SVCBIPv4Hint) pack() ([]byte, error) { + b := make([]byte, 0, 4*len(s.Hint)) + for _, e := range s.Hint { + x := e.To4() + if x == nil { + return nil, errors.New("dns: svcbipv4hint: expected ipv4, hint is ipv6") + } + b = append(b, x...) + } + return b, nil +} + +func (s *SVCBIPv4Hint) unpack(b []byte) error { + if len(b) == 0 || len(b)%4 != 0 { + return errors.New("dns: svcbipv4hint: ipv4 address byte array length is not a multiple of 4") + } + x := make([]net.IP, 0, len(b)/4) + for i := 0; i < len(b); i += 4 { + x = append(x, net.IP(b[i:i+4])) + } + s.Hint = x + return nil +} + +func (s *SVCBIPv4Hint) String() string { + str := make([]string, len(s.Hint)) + for i, e := range s.Hint { + x := e.To4() + if x == nil { + return "" + } + str[i] = x.String() + } + return strings.Join(str, ",") +} + +func (s *SVCBIPv4Hint) parse(b string) error { + if strings.Contains(b, ":") { + return errors.New("dns: svcbipv4hint: expected ipv4, got ipv6") + } + str := strings.Split(b, ",") + dst := make([]net.IP, len(str)) + for i, e := range str { + ip := net.ParseIP(e).To4() + if ip == nil { + return errors.New("dns: svcbipv4hint: bad ip") + } + dst[i] = ip + } + s.Hint = dst + return nil +} + +func (s *SVCBIPv4Hint) copy() SVCBKeyValue { + return &SVCBIPv4Hint{ + append([]net.IP(nil), s.Hint...), + } +} + +// SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx]. +// Basic use pattern for creating an echconfig option: +// +// h := new(dns.HTTPS) +// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET} +// e := new(dns.SVCBECHConfig) +// e.ECH = []byte{0xfe, 0x08, ...} +// h.Value = append(h.Value, e) +type SVCBECHConfig struct { + ECH []byte +} + +func (*SVCBECHConfig) Key() SVCBKey { return SVCB_ECHCONFIG } +func (s *SVCBECHConfig) String() string { return toBase64(s.ECH) } +func (s *SVCBECHConfig) len() int { return len(s.ECH) } + +func (s *SVCBECHConfig) pack() ([]byte, error) { + return append([]byte(nil), s.ECH...), nil +} + +func (s *SVCBECHConfig) copy() SVCBKeyValue { + return &SVCBECHConfig{ + append([]byte(nil), s.ECH...), + } +} + +func (s *SVCBECHConfig) unpack(b []byte) error { + s.ECH = append([]byte(nil), b...) + return nil +} +func (s *SVCBECHConfig) parse(b string) error { + x, err := fromBase64([]byte(b)) + if err != nil { + return errors.New("dns: svcbechconfig: bad base64 echconfig") + } + s.ECH = x + return nil +} + +// SVCBIPv6Hint pair suggests an IPv6 address which may be used to open connections +// if A and AAAA record responses for SVCB's Target domain haven't been received. +// In that case, optionally, A and AAAA requests can be made, after which the +// connection to the hinted IP address may be terminated and a new connection may be opened. +// Basic use pattern for creating an ipv6hint option: +// +// h := new(dns.HTTPS) +// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET} +// e := new(dns.SVCBIPv6Hint) +// e.Hint = []net.IP{net.ParseIP("2001:db8::1")} +// h.Value = append(h.Value, e) +type SVCBIPv6Hint struct { + Hint []net.IP +} + +func (*SVCBIPv6Hint) Key() SVCBKey { return SVCB_IPV6HINT } +func (s *SVCBIPv6Hint) len() int { return 16 * len(s.Hint) } + +func (s *SVCBIPv6Hint) pack() ([]byte, error) { + b := make([]byte, 0, 16*len(s.Hint)) + for _, e := range s.Hint { + if len(e) != net.IPv6len || e.To4() != nil { + return nil, errors.New("dns: svcbipv6hint: expected ipv6, hint is ipv4") + } + b = append(b, e...) + } + return b, nil +} + +func (s *SVCBIPv6Hint) unpack(b []byte) error { + if len(b) == 0 || len(b)%16 != 0 { + return errors.New("dns: svcbipv6hint: ipv6 address byte array length not a multiple of 16") + } + x := make([]net.IP, 0, len(b)/16) + for i := 0; i < len(b); i += 16 { + ip := net.IP(b[i : i+16]) + if ip.To4() != nil { + return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4") + } + x = append(x, ip) + } + s.Hint = x + return nil +} + +func (s *SVCBIPv6Hint) String() string { + str := make([]string, len(s.Hint)) + for i, e := range s.Hint { + if x := e.To4(); x != nil { + return "" + } + str[i] = e.String() + } + return strings.Join(str, ",") +} + +func (s *SVCBIPv6Hint) parse(b string) error { + if strings.Contains(b, ".") { + return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4") + } + str := strings.Split(b, ",") + dst := make([]net.IP, len(str)) + for i, e := range str { + ip := net.ParseIP(e) + if ip == nil { + return errors.New("dns: svcbipv6hint: bad ip") + } + dst[i] = ip + } + s.Hint = dst + return nil +} + +func (s *SVCBIPv6Hint) copy() SVCBKeyValue { + return &SVCBIPv6Hint{ + append([]net.IP(nil), s.Hint...), + } +} + +// SVCBLocal pair is intended for experimental/private use. The key is recommended +// to be in the range [SVCB_PRIVATE_LOWER, SVCB_PRIVATE_UPPER]. +// Basic use pattern for creating a keyNNNNN option: +// +// h := new(dns.HTTPS) +// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET} +// e := new(dns.SVCBLocal) +// e.KeyCode = 65400 +// e.Data = []byte("abc") +// h.Value = append(h.Value, e) +type SVCBLocal struct { + KeyCode SVCBKey // Never 65535 or any assigned keys. + Data []byte // All byte sequences are allowed. +} + +func (s *SVCBLocal) Key() SVCBKey { return s.KeyCode } +func (s *SVCBLocal) pack() ([]byte, error) { return append([]byte(nil), s.Data...), nil } +func (s *SVCBLocal) len() int { return len(s.Data) } + +func (s *SVCBLocal) unpack(b []byte) error { + s.Data = append([]byte(nil), b...) + return nil +} + +func (s *SVCBLocal) String() string { + var str strings.Builder + str.Grow(4 * len(s.Data)) + for _, e := range s.Data { + if ' ' <= e && e <= '~' { + switch e { + case '"', ';', ' ', '\\': + str.WriteByte('\\') + str.WriteByte(e) + default: + str.WriteByte(e) + } + } else { + str.WriteString(escapeByte(e)) + } + } + return str.String() +} + +func (s *SVCBLocal) parse(b string) error { + data := make([]byte, 0, len(b)) + for i := 0; i < len(b); { + if b[i] != '\\' { + data = append(data, b[i]) + i++ + continue + } + if i+1 == len(b) { + return errors.New("dns: svcblocal: svcb private/experimental key escape unterminated") + } + if isDigit(b[i+1]) { + if i+3 < len(b) && isDigit(b[i+2]) && isDigit(b[i+3]) { + a, err := strconv.ParseUint(b[i+1:i+4], 10, 8) + if err == nil { + i += 4 + data = append(data, byte(a)) + continue + } + } + return errors.New("dns: svcblocal: svcb private/experimental key bad escaped octet") + } else { + data = append(data, b[i+1]) + i += 2 + } + } + s.Data = data + return nil +} + +func (s *SVCBLocal) copy() SVCBKeyValue { + return &SVCBLocal{s.KeyCode, + append([]byte(nil), s.Data...), + } +} + +func (rr *SVCB) String() string { + s := rr.Hdr.String() + + strconv.Itoa(int(rr.Priority)) + " " + + sprintName(rr.Target) + for _, e := range rr.Value { + s += " " + e.Key().String() + "=\"" + e.String() + "\"" + } + return s +} + +// areSVCBPairArraysEqual checks if SVCBKeyValue arrays are equal after sorting their +// copies. arrA and arrB have equal lengths, otherwise zduplicate.go wouldn't call this function. +func areSVCBPairArraysEqual(a []SVCBKeyValue, b []SVCBKeyValue) bool { + a = append([]SVCBKeyValue(nil), a...) + b = append([]SVCBKeyValue(nil), b...) + sort.Slice(a, func(i, j int) bool { return a[i].Key() < a[j].Key() }) + sort.Slice(b, func(i, j int) bool { return b[i].Key() < b[j].Key() }) + for i, e := range a { + if e.Key() != b[i].Key() { + return false + } + b1, err1 := e.pack() + b2, err2 := b[i].pack() + if err1 != nil || err2 != nil || !bytes.Equal(b1, b2) { + return false + } + } + return true +} diff --git a/vendor/github.com/miekg/dns/tlsa.go b/vendor/github.com/miekg/dns/tlsa.go new file mode 100644 index 0000000..4e07983 --- /dev/null +++ b/vendor/github.com/miekg/dns/tlsa.go @@ -0,0 +1,44 @@ +package dns + +import ( + "crypto/x509" + "net" + "strconv" +) + +// Sign creates a TLSA record from an SSL certificate. +func (r *TLSA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (err error) { + r.Hdr.Rrtype = TypeTLSA + r.Usage = uint8(usage) + r.Selector = uint8(selector) + r.MatchingType = uint8(matchingType) + + r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert) + return err +} + +// Verify verifies a TLSA record against an SSL certificate. If it is OK +// a nil error is returned. +func (r *TLSA) Verify(cert *x509.Certificate) error { + c, err := CertificateToDANE(r.Selector, r.MatchingType, cert) + if err != nil { + return err // Not also ErrSig? + } + if r.Certificate == c { + return nil + } + return ErrSig // ErrSig, really? +} + +// TLSAName returns the ownername of a TLSA resource record as per the +// rules specified in RFC 6698, Section 3. +func TLSAName(name, service, network string) (string, error) { + if !IsFqdn(name) { + return "", ErrFqdn + } + p, err := net.LookupPort(network, service) + if err != nil { + return "", err + } + return "_" + strconv.Itoa(p) + "._" + network + "." + name, nil +} diff --git a/vendor/github.com/miekg/dns/tsig.go b/vendor/github.com/miekg/dns/tsig.go new file mode 100644 index 0000000..b49562d --- /dev/null +++ b/vendor/github.com/miekg/dns/tsig.go @@ -0,0 +1,429 @@ +package dns + +import ( + "crypto/hmac" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/binary" + "encoding/hex" + "hash" + "strconv" + "strings" + "time" +) + +// HMAC hashing codes. These are transmitted as domain names. +const ( + HmacSHA1 = "hmac-sha1." + HmacSHA224 = "hmac-sha224." + HmacSHA256 = "hmac-sha256." + HmacSHA384 = "hmac-sha384." + HmacSHA512 = "hmac-sha512." + + HmacMD5 = "hmac-md5.sig-alg.reg.int." // Deprecated: HmacMD5 is no longer supported. +) + +// TsigProvider provides the API to plug-in a custom TSIG implementation. +type TsigProvider interface { + // Generate is passed the DNS message to be signed and the partial TSIG RR. It returns the signature and nil, otherwise an error. + Generate(msg []byte, t *TSIG) ([]byte, error) + // Verify is passed the DNS message to be verified and the TSIG RR. If the signature is valid it will return nil, otherwise an error. + Verify(msg []byte, t *TSIG) error +} + +type tsigHMACProvider string + +func (key tsigHMACProvider) Generate(msg []byte, t *TSIG) ([]byte, error) { + // If we barf here, the caller is to blame + rawsecret, err := fromBase64([]byte(key)) + if err != nil { + return nil, err + } + var h hash.Hash + switch CanonicalName(t.Algorithm) { + case HmacSHA1: + h = hmac.New(sha1.New, rawsecret) + case HmacSHA224: + h = hmac.New(sha256.New224, rawsecret) + case HmacSHA256: + h = hmac.New(sha256.New, rawsecret) + case HmacSHA384: + h = hmac.New(sha512.New384, rawsecret) + case HmacSHA512: + h = hmac.New(sha512.New, rawsecret) + default: + return nil, ErrKeyAlg + } + h.Write(msg) + return h.Sum(nil), nil +} + +func (key tsigHMACProvider) Verify(msg []byte, t *TSIG) error { + b, err := key.Generate(msg, t) + if err != nil { + return err + } + mac, err := hex.DecodeString(t.MAC) + if err != nil { + return err + } + if !hmac.Equal(b, mac) { + return ErrSig + } + return nil +} + +// TSIG is the RR the holds the transaction signature of a message. +// See RFC 2845 and RFC 4635. +type TSIG struct { + Hdr RR_Header + Algorithm string `dns:"domain-name"` + TimeSigned uint64 `dns:"uint48"` + Fudge uint16 + MACSize uint16 + MAC string `dns:"size-hex:MACSize"` + OrigId uint16 + Error uint16 + OtherLen uint16 + OtherData string `dns:"size-hex:OtherLen"` +} + +// TSIG has no official presentation format, but this will suffice. + +func (rr *TSIG) String() string { + s := "\n;; TSIG PSEUDOSECTION:\n; " // add another semi-colon to signify TSIG does not have a presentation format + s += rr.Hdr.String() + + " " + rr.Algorithm + + " " + tsigTimeToString(rr.TimeSigned) + + " " + strconv.Itoa(int(rr.Fudge)) + + " " + strconv.Itoa(int(rr.MACSize)) + + " " + strings.ToUpper(rr.MAC) + + " " + strconv.Itoa(int(rr.OrigId)) + + " " + strconv.Itoa(int(rr.Error)) + // BIND prints NOERROR + " " + strconv.Itoa(int(rr.OtherLen)) + + " " + rr.OtherData + return s +} + +func (*TSIG) parse(c *zlexer, origin string) *ParseError { + return &ParseError{err: "TSIG records do not have a presentation format"} +} + +// The following values must be put in wireformat, so that the MAC can be calculated. +// RFC 2845, section 3.4.2. TSIG Variables. +type tsigWireFmt struct { + // From RR_Header + Name string `dns:"domain-name"` + Class uint16 + Ttl uint32 + // Rdata of the TSIG + Algorithm string `dns:"domain-name"` + TimeSigned uint64 `dns:"uint48"` + Fudge uint16 + // MACSize, MAC and OrigId excluded + Error uint16 + OtherLen uint16 + OtherData string `dns:"size-hex:OtherLen"` +} + +// If we have the MAC use this type to convert it to wiredata. Section 3.4.3. Request MAC +type macWireFmt struct { + MACSize uint16 + MAC string `dns:"size-hex:MACSize"` +} + +// 3.3. Time values used in TSIG calculations +type timerWireFmt struct { + TimeSigned uint64 `dns:"uint48"` + Fudge uint16 +} + +// TsigGenerate fills out the TSIG record attached to the message. +// The message should contain +// a "stub" TSIG RR with the algorithm, key name (owner name of the RR), +// time fudge (defaults to 300 seconds) and the current time +// The TSIG MAC is saved in that Tsig RR. +// When TsigGenerate is called for the first time requestMAC is set to the empty string and +// timersOnly is false. +// If something goes wrong an error is returned, otherwise it is nil. +func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) { + return tsigGenerateProvider(m, tsigHMACProvider(secret), requestMAC, timersOnly) +} + +func tsigGenerateProvider(m *Msg, provider TsigProvider, requestMAC string, timersOnly bool) ([]byte, string, error) { + if m.IsTsig() == nil { + panic("dns: TSIG not last RR in additional") + } + + rr := m.Extra[len(m.Extra)-1].(*TSIG) + m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg + mbuf, err := m.Pack() + if err != nil { + return nil, "", err + } + buf, err := tsigBuffer(mbuf, rr, requestMAC, timersOnly) + if err != nil { + return nil, "", err + } + + t := new(TSIG) + // Copy all TSIG fields except MAC and its size, which are filled using the computed digest. + *t = *rr + mac, err := provider.Generate(buf, rr) + if err != nil { + return nil, "", err + } + t.MAC = hex.EncodeToString(mac) + t.MACSize = uint16(len(t.MAC) / 2) // Size is half! + + tbuf := make([]byte, Len(t)) + off, err := PackRR(t, tbuf, 0, nil, false) + if err != nil { + return nil, "", err + } + mbuf = append(mbuf, tbuf[:off]...) + // Update the ArCount directly in the buffer. + binary.BigEndian.PutUint16(mbuf[10:], uint16(len(m.Extra)+1)) + + return mbuf, t.MAC, nil +} + +// TsigVerify verifies the TSIG on a message. +// If the signature does not validate err contains the +// error, otherwise it is nil. +func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error { + return tsigVerify(msg, tsigHMACProvider(secret), requestMAC, timersOnly, uint64(time.Now().Unix())) +} + +func tsigVerifyProvider(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool) error { + return tsigVerify(msg, provider, requestMAC, timersOnly, uint64(time.Now().Unix())) +} + +// actual implementation of TsigVerify, taking the current time ('now') as a parameter for the convenience of tests. +func tsigVerify(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool, now uint64) error { + // Strip the TSIG from the incoming msg + stripped, tsig, err := stripTsig(msg) + if err != nil { + return err + } + + buf, err := tsigBuffer(stripped, tsig, requestMAC, timersOnly) + if err != nil { + return err + } + + if err := provider.Verify(buf, tsig); err != nil { + return err + } + + // Fudge factor works both ways. A message can arrive before it was signed because + // of clock skew. + // We check this after verifying the signature, following draft-ietf-dnsop-rfc2845bis + // instead of RFC2845, in order to prevent a security vulnerability as reported in CVE-2017-3142/3143. + ti := now - tsig.TimeSigned + if now < tsig.TimeSigned { + ti = tsig.TimeSigned - now + } + if uint64(tsig.Fudge) < ti { + return ErrTime + } + + return nil +} + +// Create a wiredata buffer for the MAC calculation. +func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) ([]byte, error) { + var buf []byte + if rr.TimeSigned == 0 { + rr.TimeSigned = uint64(time.Now().Unix()) + } + if rr.Fudge == 0 { + rr.Fudge = 300 // Standard (RFC) default. + } + + // Replace message ID in header with original ID from TSIG + binary.BigEndian.PutUint16(msgbuf[0:2], rr.OrigId) + + if requestMAC != "" { + m := new(macWireFmt) + m.MACSize = uint16(len(requestMAC) / 2) + m.MAC = requestMAC + buf = make([]byte, len(requestMAC)) // long enough + n, err := packMacWire(m, buf) + if err != nil { + return nil, err + } + buf = buf[:n] + } + + tsigvar := make([]byte, DefaultMsgSize) + if timersOnly { + tsig := new(timerWireFmt) + tsig.TimeSigned = rr.TimeSigned + tsig.Fudge = rr.Fudge + n, err := packTimerWire(tsig, tsigvar) + if err != nil { + return nil, err + } + tsigvar = tsigvar[:n] + } else { + tsig := new(tsigWireFmt) + tsig.Name = CanonicalName(rr.Hdr.Name) + tsig.Class = ClassANY + tsig.Ttl = rr.Hdr.Ttl + tsig.Algorithm = CanonicalName(rr.Algorithm) + tsig.TimeSigned = rr.TimeSigned + tsig.Fudge = rr.Fudge + tsig.Error = rr.Error + tsig.OtherLen = rr.OtherLen + tsig.OtherData = rr.OtherData + n, err := packTsigWire(tsig, tsigvar) + if err != nil { + return nil, err + } + tsigvar = tsigvar[:n] + } + + if requestMAC != "" { + x := append(buf, msgbuf...) + buf = append(x, tsigvar...) + } else { + buf = append(msgbuf, tsigvar...) + } + return buf, nil +} + +// Strip the TSIG from the raw message. +func stripTsig(msg []byte) ([]byte, *TSIG, error) { + // Copied from msg.go's Unpack() Header, but modified. + var ( + dh Header + err error + ) + off, tsigoff := 0, 0 + + if dh, off, err = unpackMsgHdr(msg, off); err != nil { + return nil, nil, err + } + if dh.Arcount == 0 { + return nil, nil, ErrNoSig + } + + // Rcode, see msg.go Unpack() + if int(dh.Bits&0xF) == RcodeNotAuth { + return nil, nil, ErrAuth + } + + for i := 0; i < int(dh.Qdcount); i++ { + _, off, err = unpackQuestion(msg, off) + if err != nil { + return nil, nil, err + } + } + + _, off, err = unpackRRslice(int(dh.Ancount), msg, off) + if err != nil { + return nil, nil, err + } + _, off, err = unpackRRslice(int(dh.Nscount), msg, off) + if err != nil { + return nil, nil, err + } + + rr := new(TSIG) + var extra RR + for i := 0; i < int(dh.Arcount); i++ { + tsigoff = off + extra, off, err = UnpackRR(msg, off) + if err != nil { + return nil, nil, err + } + if extra.Header().Rrtype == TypeTSIG { + rr = extra.(*TSIG) + // Adjust Arcount. + arcount := binary.BigEndian.Uint16(msg[10:]) + binary.BigEndian.PutUint16(msg[10:], arcount-1) + break + } + } + if rr == nil { + return nil, nil, ErrNoSig + } + return msg[:tsigoff], rr, nil +} + +// Translate the TSIG time signed into a date. There is no +// need for RFC1982 calculations as this date is 48 bits. +func tsigTimeToString(t uint64) string { + ti := time.Unix(int64(t), 0).UTC() + return ti.Format("20060102150405") +} + +func packTsigWire(tw *tsigWireFmt, msg []byte) (int, error) { + // copied from zmsg.go TSIG packing + // RR_Header + off, err := PackDomainName(tw.Name, msg, 0, nil, false) + if err != nil { + return off, err + } + off, err = packUint16(tw.Class, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(tw.Ttl, msg, off) + if err != nil { + return off, err + } + + off, err = PackDomainName(tw.Algorithm, msg, off, nil, false) + if err != nil { + return off, err + } + off, err = packUint48(tw.TimeSigned, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(tw.Fudge, msg, off) + if err != nil { + return off, err + } + + off, err = packUint16(tw.Error, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(tw.OtherLen, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(tw.OtherData, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func packMacWire(mw *macWireFmt, msg []byte) (int, error) { + off, err := packUint16(mw.MACSize, msg, 0) + if err != nil { + return off, err + } + off, err = packStringHex(mw.MAC, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func packTimerWire(tw *timerWireFmt, msg []byte) (int, error) { + off, err := packUint48(tw.TimeSigned, msg, 0) + if err != nil { + return off, err + } + off, err = packUint16(tw.Fudge, msg, off) + if err != nil { + return off, err + } + return off, nil +} diff --git a/vendor/github.com/miekg/dns/types.go b/vendor/github.com/miekg/dns/types.go new file mode 100644 index 0000000..99dd315 --- /dev/null +++ b/vendor/github.com/miekg/dns/types.go @@ -0,0 +1,1562 @@ +package dns + +import ( + "bytes" + "fmt" + "net" + "strconv" + "strings" + "time" +) + +type ( + // Type is a DNS type. + Type uint16 + // Class is a DNS class. + Class uint16 + // Name is a DNS domain name. + Name string +) + +// Packet formats + +// Wire constants and supported types. +const ( + // valid RR_Header.Rrtype and Question.qtype + + TypeNone uint16 = 0 + TypeA uint16 = 1 + TypeNS uint16 = 2 + TypeMD uint16 = 3 + TypeMF uint16 = 4 + TypeCNAME uint16 = 5 + TypeSOA uint16 = 6 + TypeMB uint16 = 7 + TypeMG uint16 = 8 + TypeMR uint16 = 9 + TypeNULL uint16 = 10 + TypePTR uint16 = 12 + TypeHINFO uint16 = 13 + TypeMINFO uint16 = 14 + TypeMX uint16 = 15 + TypeTXT uint16 = 16 + TypeRP uint16 = 17 + TypeAFSDB uint16 = 18 + TypeX25 uint16 = 19 + TypeISDN uint16 = 20 + TypeRT uint16 = 21 + TypeNSAPPTR uint16 = 23 + TypeSIG uint16 = 24 + TypeKEY uint16 = 25 + TypePX uint16 = 26 + TypeGPOS uint16 = 27 + TypeAAAA uint16 = 28 + TypeLOC uint16 = 29 + TypeNXT uint16 = 30 + TypeEID uint16 = 31 + TypeNIMLOC uint16 = 32 + TypeSRV uint16 = 33 + TypeATMA uint16 = 34 + TypeNAPTR uint16 = 35 + TypeKX uint16 = 36 + TypeCERT uint16 = 37 + TypeDNAME uint16 = 39 + TypeOPT uint16 = 41 // EDNS + TypeAPL uint16 = 42 + TypeDS uint16 = 43 + TypeSSHFP uint16 = 44 + TypeRRSIG uint16 = 46 + TypeNSEC uint16 = 47 + TypeDNSKEY uint16 = 48 + TypeDHCID uint16 = 49 + TypeNSEC3 uint16 = 50 + TypeNSEC3PARAM uint16 = 51 + TypeTLSA uint16 = 52 + TypeSMIMEA uint16 = 53 + TypeHIP uint16 = 55 + TypeNINFO uint16 = 56 + TypeRKEY uint16 = 57 + TypeTALINK uint16 = 58 + TypeCDS uint16 = 59 + TypeCDNSKEY uint16 = 60 + TypeOPENPGPKEY uint16 = 61 + TypeCSYNC uint16 = 62 + TypeZONEMD uint16 = 63 + TypeSVCB uint16 = 64 + TypeHTTPS uint16 = 65 + TypeSPF uint16 = 99 + TypeUINFO uint16 = 100 + TypeUID uint16 = 101 + TypeGID uint16 = 102 + TypeUNSPEC uint16 = 103 + TypeNID uint16 = 104 + TypeL32 uint16 = 105 + TypeL64 uint16 = 106 + TypeLP uint16 = 107 + TypeEUI48 uint16 = 108 + TypeEUI64 uint16 = 109 + TypeURI uint16 = 256 + TypeCAA uint16 = 257 + TypeAVC uint16 = 258 + + TypeTKEY uint16 = 249 + TypeTSIG uint16 = 250 + + // valid Question.Qtype only + TypeIXFR uint16 = 251 + TypeAXFR uint16 = 252 + TypeMAILB uint16 = 253 + TypeMAILA uint16 = 254 + TypeANY uint16 = 255 + + TypeTA uint16 = 32768 + TypeDLV uint16 = 32769 + TypeReserved uint16 = 65535 + + // valid Question.Qclass + ClassINET = 1 + ClassCSNET = 2 + ClassCHAOS = 3 + ClassHESIOD = 4 + ClassNONE = 254 + ClassANY = 255 + + // Message Response Codes, see https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml + RcodeSuccess = 0 // NoError - No Error [DNS] + RcodeFormatError = 1 // FormErr - Format Error [DNS] + RcodeServerFailure = 2 // ServFail - Server Failure [DNS] + RcodeNameError = 3 // NXDomain - Non-Existent Domain [DNS] + RcodeNotImplemented = 4 // NotImp - Not Implemented [DNS] + RcodeRefused = 5 // Refused - Query Refused [DNS] + RcodeYXDomain = 6 // YXDomain - Name Exists when it should not [DNS Update] + RcodeYXRrset = 7 // YXRRSet - RR Set Exists when it should not [DNS Update] + RcodeNXRrset = 8 // NXRRSet - RR Set that should exist does not [DNS Update] + RcodeNotAuth = 9 // NotAuth - Server Not Authoritative for zone [DNS Update] + RcodeNotZone = 10 // NotZone - Name not contained in zone [DNS Update/TSIG] + RcodeBadSig = 16 // BADSIG - TSIG Signature Failure [TSIG] + RcodeBadVers = 16 // BADVERS - Bad OPT Version [EDNS0] + RcodeBadKey = 17 // BADKEY - Key not recognized [TSIG] + RcodeBadTime = 18 // BADTIME - Signature out of time window [TSIG] + RcodeBadMode = 19 // BADMODE - Bad TKEY Mode [TKEY] + RcodeBadName = 20 // BADNAME - Duplicate key name [TKEY] + RcodeBadAlg = 21 // BADALG - Algorithm not supported [TKEY] + RcodeBadTrunc = 22 // BADTRUNC - Bad Truncation [TSIG] + RcodeBadCookie = 23 // BADCOOKIE - Bad/missing Server Cookie [DNS Cookies] + + // Message Opcodes. There is no 3. + OpcodeQuery = 0 + OpcodeIQuery = 1 + OpcodeStatus = 2 + OpcodeNotify = 4 + OpcodeUpdate = 5 +) + +// Used in ZONEMD https://tools.ietf.org/html/rfc8976 + +const ( + // ZoneMD Accepted Schemes + ZoneMDSchemeSimple = 1 + + // ZoneMD Hash Algorithms + ZoneMDHashAlgSHA384 = 1 + ZoneMDHashAlgSHA512 = 2 +) + +// Header is the wire format for the DNS packet header. +type Header struct { + Id uint16 + Bits uint16 + Qdcount, Ancount, Nscount, Arcount uint16 +} + +const ( + headerSize = 12 + + // Header.Bits + _QR = 1 << 15 // query/response (response=1) + _AA = 1 << 10 // authoritative + _TC = 1 << 9 // truncated + _RD = 1 << 8 // recursion desired + _RA = 1 << 7 // recursion available + _Z = 1 << 6 // Z + _AD = 1 << 5 // authenticated data + _CD = 1 << 4 // checking disabled +) + +// Various constants used in the LOC RR. See RFC 1887. +const ( + LOC_EQUATOR = 1 << 31 // RFC 1876, Section 2. + LOC_PRIMEMERIDIAN = 1 << 31 // RFC 1876, Section 2. + LOC_HOURS = 60 * 1000 + LOC_DEGREES = 60 * LOC_HOURS + LOC_ALTITUDEBASE = 100000 +) + +// Different Certificate Types, see RFC 4398, Section 2.1 +const ( + CertPKIX = 1 + iota + CertSPKI + CertPGP + CertIPIX + CertISPKI + CertIPGP + CertACPKIX + CertIACPKIX + CertURI = 253 + CertOID = 254 +) + +// CertTypeToString converts the Cert Type to its string representation. +// See RFC 4398 and RFC 6944. +var CertTypeToString = map[uint16]string{ + CertPKIX: "PKIX", + CertSPKI: "SPKI", + CertPGP: "PGP", + CertIPIX: "IPIX", + CertISPKI: "ISPKI", + CertIPGP: "IPGP", + CertACPKIX: "ACPKIX", + CertIACPKIX: "IACPKIX", + CertURI: "URI", + CertOID: "OID", +} + +//go:generate go run types_generate.go + +// Question holds a DNS question. Usually there is just one. While the +// original DNS RFCs allow multiple questions in the question section of a +// message, in practice it never works. Because most DNS servers see multiple +// questions as an error, it is recommended to only have one question per +// message. +type Question struct { + Name string `dns:"cdomain-name"` // "cdomain-name" specifies encoding (and may be compressed) + Qtype uint16 + Qclass uint16 +} + +func (q *Question) len(off int, compression map[string]struct{}) int { + l := domainNameLen(q.Name, off, compression, true) + l += 2 + 2 + return l +} + +func (q *Question) String() (s string) { + // prefix with ; (as in dig) + s = ";" + sprintName(q.Name) + "\t" + s += Class(q.Qclass).String() + "\t" + s += " " + Type(q.Qtype).String() + return s +} + +// ANY is a wild card record. See RFC 1035, Section 3.2.3. ANY +// is named "*" there. +type ANY struct { + Hdr RR_Header + // Does not have any rdata +} + +func (rr *ANY) String() string { return rr.Hdr.String() } + +func (*ANY) parse(c *zlexer, origin string) *ParseError { + return &ParseError{err: "ANY records do not have a presentation format"} +} + +// NULL RR. See RFC 1035. +type NULL struct { + Hdr RR_Header + Data string `dns:"any"` +} + +func (rr *NULL) String() string { + // There is no presentation format; prefix string with a comment. + return ";" + rr.Hdr.String() + rr.Data +} + +func (*NULL) parse(c *zlexer, origin string) *ParseError { + return &ParseError{err: "NULL records do not have a presentation format"} +} + +// CNAME RR. See RFC 1034. +type CNAME struct { + Hdr RR_Header + Target string `dns:"cdomain-name"` +} + +func (rr *CNAME) String() string { return rr.Hdr.String() + sprintName(rr.Target) } + +// HINFO RR. See RFC 1034. +type HINFO struct { + Hdr RR_Header + Cpu string + Os string +} + +func (rr *HINFO) String() string { + return rr.Hdr.String() + sprintTxt([]string{rr.Cpu, rr.Os}) +} + +// MB RR. See RFC 1035. +type MB struct { + Hdr RR_Header + Mb string `dns:"cdomain-name"` +} + +func (rr *MB) String() string { return rr.Hdr.String() + sprintName(rr.Mb) } + +// MG RR. See RFC 1035. +type MG struct { + Hdr RR_Header + Mg string `dns:"cdomain-name"` +} + +func (rr *MG) String() string { return rr.Hdr.String() + sprintName(rr.Mg) } + +// MINFO RR. See RFC 1035. +type MINFO struct { + Hdr RR_Header + Rmail string `dns:"cdomain-name"` + Email string `dns:"cdomain-name"` +} + +func (rr *MINFO) String() string { + return rr.Hdr.String() + sprintName(rr.Rmail) + " " + sprintName(rr.Email) +} + +// MR RR. See RFC 1035. +type MR struct { + Hdr RR_Header + Mr string `dns:"cdomain-name"` +} + +func (rr *MR) String() string { + return rr.Hdr.String() + sprintName(rr.Mr) +} + +// MF RR. See RFC 1035. +type MF struct { + Hdr RR_Header + Mf string `dns:"cdomain-name"` +} + +func (rr *MF) String() string { + return rr.Hdr.String() + sprintName(rr.Mf) +} + +// MD RR. See RFC 1035. +type MD struct { + Hdr RR_Header + Md string `dns:"cdomain-name"` +} + +func (rr *MD) String() string { + return rr.Hdr.String() + sprintName(rr.Md) +} + +// MX RR. See RFC 1035. +type MX struct { + Hdr RR_Header + Preference uint16 + Mx string `dns:"cdomain-name"` +} + +func (rr *MX) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + " " + sprintName(rr.Mx) +} + +// AFSDB RR. See RFC 1183. +type AFSDB struct { + Hdr RR_Header + Subtype uint16 + Hostname string `dns:"domain-name"` +} + +func (rr *AFSDB) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Subtype)) + " " + sprintName(rr.Hostname) +} + +// X25 RR. See RFC 1183, Section 3.1. +type X25 struct { + Hdr RR_Header + PSDNAddress string +} + +func (rr *X25) String() string { + return rr.Hdr.String() + rr.PSDNAddress +} + +// RT RR. See RFC 1183, Section 3.3. +type RT struct { + Hdr RR_Header + Preference uint16 + Host string `dns:"domain-name"` // RFC 3597 prohibits compressing records not defined in RFC 1035. +} + +func (rr *RT) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + " " + sprintName(rr.Host) +} + +// NS RR. See RFC 1035. +type NS struct { + Hdr RR_Header + Ns string `dns:"cdomain-name"` +} + +func (rr *NS) String() string { + return rr.Hdr.String() + sprintName(rr.Ns) +} + +// PTR RR. See RFC 1035. +type PTR struct { + Hdr RR_Header + Ptr string `dns:"cdomain-name"` +} + +func (rr *PTR) String() string { + return rr.Hdr.String() + sprintName(rr.Ptr) +} + +// RP RR. See RFC 1138, Section 2.2. +type RP struct { + Hdr RR_Header + Mbox string `dns:"domain-name"` + Txt string `dns:"domain-name"` +} + +func (rr *RP) String() string { + return rr.Hdr.String() + sprintName(rr.Mbox) + " " + sprintName(rr.Txt) +} + +// SOA RR. See RFC 1035. +type SOA struct { + Hdr RR_Header + Ns string `dns:"cdomain-name"` + Mbox string `dns:"cdomain-name"` + Serial uint32 + Refresh uint32 + Retry uint32 + Expire uint32 + Minttl uint32 +} + +func (rr *SOA) String() string { + return rr.Hdr.String() + sprintName(rr.Ns) + " " + sprintName(rr.Mbox) + + " " + strconv.FormatInt(int64(rr.Serial), 10) + + " " + strconv.FormatInt(int64(rr.Refresh), 10) + + " " + strconv.FormatInt(int64(rr.Retry), 10) + + " " + strconv.FormatInt(int64(rr.Expire), 10) + + " " + strconv.FormatInt(int64(rr.Minttl), 10) +} + +// TXT RR. See RFC 1035. +type TXT struct { + Hdr RR_Header + Txt []string `dns:"txt"` +} + +func (rr *TXT) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) } + +func sprintName(s string) string { + var dst strings.Builder + + for i := 0; i < len(s); { + if s[i] == '.' { + if dst.Len() != 0 { + dst.WriteByte('.') + } + i++ + continue + } + + b, n := nextByte(s, i) + if n == 0 { + // Drop "dangling" incomplete escapes. + if dst.Len() == 0 { + return s[:i] + } + break + } + if isDomainNameLabelSpecial(b) { + if dst.Len() == 0 { + dst.Grow(len(s) * 2) + dst.WriteString(s[:i]) + } + dst.WriteByte('\\') + dst.WriteByte(b) + } else if b < ' ' || b > '~' { // unprintable, use \DDD + if dst.Len() == 0 { + dst.Grow(len(s) * 2) + dst.WriteString(s[:i]) + } + dst.WriteString(escapeByte(b)) + } else { + if dst.Len() != 0 { + dst.WriteByte(b) + } + } + i += n + } + if dst.Len() == 0 { + return s + } + return dst.String() +} + +func sprintTxtOctet(s string) string { + var dst strings.Builder + dst.Grow(2 + len(s)) + dst.WriteByte('"') + for i := 0; i < len(s); { + if i+1 < len(s) && s[i] == '\\' && s[i+1] == '.' { + dst.WriteString(s[i : i+2]) + i += 2 + continue + } + + b, n := nextByte(s, i) + if n == 0 { + i++ // dangling back slash + } else { + writeTXTStringByte(&dst, b) + } + i += n + } + dst.WriteByte('"') + return dst.String() +} + +func sprintTxt(txt []string) string { + var out strings.Builder + for i, s := range txt { + out.Grow(3 + len(s)) + if i > 0 { + out.WriteString(` "`) + } else { + out.WriteByte('"') + } + for j := 0; j < len(s); { + b, n := nextByte(s, j) + if n == 0 { + break + } + writeTXTStringByte(&out, b) + j += n + } + out.WriteByte('"') + } + return out.String() +} + +func writeTXTStringByte(s *strings.Builder, b byte) { + switch { + case b == '"' || b == '\\': + s.WriteByte('\\') + s.WriteByte(b) + case b < ' ' || b > '~': + s.WriteString(escapeByte(b)) + default: + s.WriteByte(b) + } +} + +const ( + escapedByteSmall = "" + + `\000\001\002\003\004\005\006\007\008\009` + + `\010\011\012\013\014\015\016\017\018\019` + + `\020\021\022\023\024\025\026\027\028\029` + + `\030\031` + escapedByteLarge = `\127\128\129` + + `\130\131\132\133\134\135\136\137\138\139` + + `\140\141\142\143\144\145\146\147\148\149` + + `\150\151\152\153\154\155\156\157\158\159` + + `\160\161\162\163\164\165\166\167\168\169` + + `\170\171\172\173\174\175\176\177\178\179` + + `\180\181\182\183\184\185\186\187\188\189` + + `\190\191\192\193\194\195\196\197\198\199` + + `\200\201\202\203\204\205\206\207\208\209` + + `\210\211\212\213\214\215\216\217\218\219` + + `\220\221\222\223\224\225\226\227\228\229` + + `\230\231\232\233\234\235\236\237\238\239` + + `\240\241\242\243\244\245\246\247\248\249` + + `\250\251\252\253\254\255` +) + +// escapeByte returns the \DDD escaping of b which must +// satisfy b < ' ' || b > '~'. +func escapeByte(b byte) string { + if b < ' ' { + return escapedByteSmall[b*4 : b*4+4] + } + + b -= '~' + 1 + // The cast here is needed as b*4 may overflow byte. + return escapedByteLarge[int(b)*4 : int(b)*4+4] +} + +// isDomainNameLabelSpecial returns true if +// a domain name label byte should be prefixed +// with an escaping backslash. +func isDomainNameLabelSpecial(b byte) bool { + switch b { + case '.', ' ', '\'', '@', ';', '(', ')', '"', '\\': + return true + } + return false +} + +func nextByte(s string, offset int) (byte, int) { + if offset >= len(s) { + return 0, 0 + } + if s[offset] != '\\' { + // not an escape sequence + return s[offset], 1 + } + switch len(s) - offset { + case 1: // dangling escape + return 0, 0 + case 2, 3: // too short to be \ddd + default: // maybe \ddd + if isDigit(s[offset+1]) && isDigit(s[offset+2]) && isDigit(s[offset+3]) { + return dddStringToByte(s[offset+1:]), 4 + } + } + // not \ddd, just an RFC 1035 "quoted" character + return s[offset+1], 2 +} + +// SPF RR. See RFC 4408, Section 3.1.1. +type SPF struct { + Hdr RR_Header + Txt []string `dns:"txt"` +} + +func (rr *SPF) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) } + +// AVC RR. See https://www.iana.org/assignments/dns-parameters/AVC/avc-completed-template. +type AVC struct { + Hdr RR_Header + Txt []string `dns:"txt"` +} + +func (rr *AVC) String() string { return rr.Hdr.String() + sprintTxt(rr.Txt) } + +// SRV RR. See RFC 2782. +type SRV struct { + Hdr RR_Header + Priority uint16 + Weight uint16 + Port uint16 + Target string `dns:"domain-name"` +} + +func (rr *SRV) String() string { + return rr.Hdr.String() + + strconv.Itoa(int(rr.Priority)) + " " + + strconv.Itoa(int(rr.Weight)) + " " + + strconv.Itoa(int(rr.Port)) + " " + sprintName(rr.Target) +} + +// NAPTR RR. See RFC 2915. +type NAPTR struct { + Hdr RR_Header + Order uint16 + Preference uint16 + Flags string + Service string + Regexp string + Replacement string `dns:"domain-name"` +} + +func (rr *NAPTR) String() string { + return rr.Hdr.String() + + strconv.Itoa(int(rr.Order)) + " " + + strconv.Itoa(int(rr.Preference)) + " " + + "\"" + rr.Flags + "\" " + + "\"" + rr.Service + "\" " + + "\"" + rr.Regexp + "\" " + + rr.Replacement +} + +// CERT RR. See RFC 4398. +type CERT struct { + Hdr RR_Header + Type uint16 + KeyTag uint16 + Algorithm uint8 + Certificate string `dns:"base64"` +} + +func (rr *CERT) String() string { + var ( + ok bool + certtype, algorithm string + ) + if certtype, ok = CertTypeToString[rr.Type]; !ok { + certtype = strconv.Itoa(int(rr.Type)) + } + if algorithm, ok = AlgorithmToString[rr.Algorithm]; !ok { + algorithm = strconv.Itoa(int(rr.Algorithm)) + } + return rr.Hdr.String() + certtype + + " " + strconv.Itoa(int(rr.KeyTag)) + + " " + algorithm + + " " + rr.Certificate +} + +// DNAME RR. See RFC 2672. +type DNAME struct { + Hdr RR_Header + Target string `dns:"domain-name"` +} + +func (rr *DNAME) String() string { + return rr.Hdr.String() + sprintName(rr.Target) +} + +// A RR. See RFC 1035. +type A struct { + Hdr RR_Header + A net.IP `dns:"a"` +} + +func (rr *A) String() string { + if rr.A == nil { + return rr.Hdr.String() + } + return rr.Hdr.String() + rr.A.String() +} + +// AAAA RR. See RFC 3596. +type AAAA struct { + Hdr RR_Header + AAAA net.IP `dns:"aaaa"` +} + +func (rr *AAAA) String() string { + if rr.AAAA == nil { + return rr.Hdr.String() + } + return rr.Hdr.String() + rr.AAAA.String() +} + +// PX RR. See RFC 2163. +type PX struct { + Hdr RR_Header + Preference uint16 + Map822 string `dns:"domain-name"` + Mapx400 string `dns:"domain-name"` +} + +func (rr *PX) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + " " + sprintName(rr.Map822) + " " + sprintName(rr.Mapx400) +} + +// GPOS RR. See RFC 1712. +type GPOS struct { + Hdr RR_Header + Longitude string + Latitude string + Altitude string +} + +func (rr *GPOS) String() string { + return rr.Hdr.String() + rr.Longitude + " " + rr.Latitude + " " + rr.Altitude +} + +// LOC RR. See RFC RFC 1876. +type LOC struct { + Hdr RR_Header + Version uint8 + Size uint8 + HorizPre uint8 + VertPre uint8 + Latitude uint32 + Longitude uint32 + Altitude uint32 +} + +// cmToM takes a cm value expressed in RFC 1876 SIZE mantissa/exponent +// format and returns a string in m (two decimals for the cm). +func cmToM(m, e uint8) string { + if e < 2 { + if e == 1 { + m *= 10 + } + + return fmt.Sprintf("0.%02d", m) + } + + s := fmt.Sprintf("%d", m) + for e > 2 { + s += "0" + e-- + } + return s +} + +func (rr *LOC) String() string { + s := rr.Hdr.String() + + lat := rr.Latitude + ns := "N" + if lat > LOC_EQUATOR { + lat = lat - LOC_EQUATOR + } else { + ns = "S" + lat = LOC_EQUATOR - lat + } + h := lat / LOC_DEGREES + lat = lat % LOC_DEGREES + m := lat / LOC_HOURS + lat = lat % LOC_HOURS + s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, float64(lat)/1000, ns) + + lon := rr.Longitude + ew := "E" + if lon > LOC_PRIMEMERIDIAN { + lon = lon - LOC_PRIMEMERIDIAN + } else { + ew = "W" + lon = LOC_PRIMEMERIDIAN - lon + } + h = lon / LOC_DEGREES + lon = lon % LOC_DEGREES + m = lon / LOC_HOURS + lon = lon % LOC_HOURS + s += fmt.Sprintf("%02d %02d %0.3f %s ", h, m, float64(lon)/1000, ew) + + var alt = float64(rr.Altitude) / 100 + alt -= LOC_ALTITUDEBASE + if rr.Altitude%100 != 0 { + s += fmt.Sprintf("%.2fm ", alt) + } else { + s += fmt.Sprintf("%.0fm ", alt) + } + + s += cmToM(rr.Size&0xf0>>4, rr.Size&0x0f) + "m " + s += cmToM(rr.HorizPre&0xf0>>4, rr.HorizPre&0x0f) + "m " + s += cmToM(rr.VertPre&0xf0>>4, rr.VertPre&0x0f) + "m" + + return s +} + +// SIG RR. See RFC 2535. The SIG RR is identical to RRSIG and nowadays only used for SIG(0), See RFC 2931. +type SIG struct { + RRSIG +} + +// RRSIG RR. See RFC 4034 and RFC 3755. +type RRSIG struct { + Hdr RR_Header + TypeCovered uint16 + Algorithm uint8 + Labels uint8 + OrigTtl uint32 + Expiration uint32 + Inception uint32 + KeyTag uint16 + SignerName string `dns:"domain-name"` + Signature string `dns:"base64"` +} + +func (rr *RRSIG) String() string { + s := rr.Hdr.String() + s += Type(rr.TypeCovered).String() + s += " " + strconv.Itoa(int(rr.Algorithm)) + + " " + strconv.Itoa(int(rr.Labels)) + + " " + strconv.FormatInt(int64(rr.OrigTtl), 10) + + " " + TimeToString(rr.Expiration) + + " " + TimeToString(rr.Inception) + + " " + strconv.Itoa(int(rr.KeyTag)) + + " " + sprintName(rr.SignerName) + + " " + rr.Signature + return s +} + +// NSEC RR. See RFC 4034 and RFC 3755. +type NSEC struct { + Hdr RR_Header + NextDomain string `dns:"domain-name"` + TypeBitMap []uint16 `dns:"nsec"` +} + +func (rr *NSEC) String() string { + s := rr.Hdr.String() + sprintName(rr.NextDomain) + for _, t := range rr.TypeBitMap { + s += " " + Type(t).String() + } + return s +} + +func (rr *NSEC) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.NextDomain, off+l, compression, false) + l += typeBitMapLen(rr.TypeBitMap) + return l +} + +// DLV RR. See RFC 4431. +type DLV struct{ DS } + +// CDS RR. See RFC 7344. +type CDS struct{ DS } + +// DS RR. See RFC 4034 and RFC 3658. +type DS struct { + Hdr RR_Header + KeyTag uint16 + Algorithm uint8 + DigestType uint8 + Digest string `dns:"hex"` +} + +func (rr *DS) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.KeyTag)) + + " " + strconv.Itoa(int(rr.Algorithm)) + + " " + strconv.Itoa(int(rr.DigestType)) + + " " + strings.ToUpper(rr.Digest) +} + +// KX RR. See RFC 2230. +type KX struct { + Hdr RR_Header + Preference uint16 + Exchanger string `dns:"domain-name"` +} + +func (rr *KX) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + + " " + sprintName(rr.Exchanger) +} + +// TA RR. See http://www.watson.org/~weiler/INI1999-19.pdf. +type TA struct { + Hdr RR_Header + KeyTag uint16 + Algorithm uint8 + DigestType uint8 + Digest string `dns:"hex"` +} + +func (rr *TA) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.KeyTag)) + + " " + strconv.Itoa(int(rr.Algorithm)) + + " " + strconv.Itoa(int(rr.DigestType)) + + " " + strings.ToUpper(rr.Digest) +} + +// TALINK RR. See https://www.iana.org/assignments/dns-parameters/TALINK/talink-completed-template. +type TALINK struct { + Hdr RR_Header + PreviousName string `dns:"domain-name"` + NextName string `dns:"domain-name"` +} + +func (rr *TALINK) String() string { + return rr.Hdr.String() + + sprintName(rr.PreviousName) + " " + sprintName(rr.NextName) +} + +// SSHFP RR. See RFC RFC 4255. +type SSHFP struct { + Hdr RR_Header + Algorithm uint8 + Type uint8 + FingerPrint string `dns:"hex"` +} + +func (rr *SSHFP) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Algorithm)) + + " " + strconv.Itoa(int(rr.Type)) + + " " + strings.ToUpper(rr.FingerPrint) +} + +// KEY RR. See RFC RFC 2535. +type KEY struct { + DNSKEY +} + +// CDNSKEY RR. See RFC 7344. +type CDNSKEY struct { + DNSKEY +} + +// DNSKEY RR. See RFC 4034 and RFC 3755. +type DNSKEY struct { + Hdr RR_Header + Flags uint16 + Protocol uint8 + Algorithm uint8 + PublicKey string `dns:"base64"` +} + +func (rr *DNSKEY) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Flags)) + + " " + strconv.Itoa(int(rr.Protocol)) + + " " + strconv.Itoa(int(rr.Algorithm)) + + " " + rr.PublicKey +} + +// RKEY RR. See https://www.iana.org/assignments/dns-parameters/RKEY/rkey-completed-template. +type RKEY struct { + Hdr RR_Header + Flags uint16 + Protocol uint8 + Algorithm uint8 + PublicKey string `dns:"base64"` +} + +func (rr *RKEY) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Flags)) + + " " + strconv.Itoa(int(rr.Protocol)) + + " " + strconv.Itoa(int(rr.Algorithm)) + + " " + rr.PublicKey +} + +// NSAPPTR RR. See RFC 1348. +type NSAPPTR struct { + Hdr RR_Header + Ptr string `dns:"domain-name"` +} + +func (rr *NSAPPTR) String() string { return rr.Hdr.String() + sprintName(rr.Ptr) } + +// NSEC3 RR. See RFC 5155. +type NSEC3 struct { + Hdr RR_Header + Hash uint8 + Flags uint8 + Iterations uint16 + SaltLength uint8 + Salt string `dns:"size-hex:SaltLength"` + HashLength uint8 + NextDomain string `dns:"size-base32:HashLength"` + TypeBitMap []uint16 `dns:"nsec"` +} + +func (rr *NSEC3) String() string { + s := rr.Hdr.String() + s += strconv.Itoa(int(rr.Hash)) + + " " + strconv.Itoa(int(rr.Flags)) + + " " + strconv.Itoa(int(rr.Iterations)) + + " " + saltToString(rr.Salt) + + " " + rr.NextDomain + for _, t := range rr.TypeBitMap { + s += " " + Type(t).String() + } + return s +} + +func (rr *NSEC3) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 6 + len(rr.Salt)/2 + 1 + len(rr.NextDomain) + 1 + l += typeBitMapLen(rr.TypeBitMap) + return l +} + +// NSEC3PARAM RR. See RFC 5155. +type NSEC3PARAM struct { + Hdr RR_Header + Hash uint8 + Flags uint8 + Iterations uint16 + SaltLength uint8 + Salt string `dns:"size-hex:SaltLength"` +} + +func (rr *NSEC3PARAM) String() string { + s := rr.Hdr.String() + s += strconv.Itoa(int(rr.Hash)) + + " " + strconv.Itoa(int(rr.Flags)) + + " " + strconv.Itoa(int(rr.Iterations)) + + " " + saltToString(rr.Salt) + return s +} + +// TKEY RR. See RFC 2930. +type TKEY struct { + Hdr RR_Header + Algorithm string `dns:"domain-name"` + Inception uint32 + Expiration uint32 + Mode uint16 + Error uint16 + KeySize uint16 + Key string `dns:"size-hex:KeySize"` + OtherLen uint16 + OtherData string `dns:"size-hex:OtherLen"` +} + +// TKEY has no official presentation format, but this will suffice. +func (rr *TKEY) String() string { + s := ";" + rr.Hdr.String() + + " " + rr.Algorithm + + " " + TimeToString(rr.Inception) + + " " + TimeToString(rr.Expiration) + + " " + strconv.Itoa(int(rr.Mode)) + + " " + strconv.Itoa(int(rr.Error)) + + " " + strconv.Itoa(int(rr.KeySize)) + + " " + rr.Key + + " " + strconv.Itoa(int(rr.OtherLen)) + + " " + rr.OtherData + return s +} + +// RFC3597 represents an unknown/generic RR. See RFC 3597. +type RFC3597 struct { + Hdr RR_Header + Rdata string `dns:"hex"` +} + +func (rr *RFC3597) String() string { + // Let's call it a hack + s := rfc3597Header(rr.Hdr) + + s += "\\# " + strconv.Itoa(len(rr.Rdata)/2) + " " + rr.Rdata + return s +} + +func rfc3597Header(h RR_Header) string { + var s string + + s += sprintName(h.Name) + "\t" + s += strconv.FormatInt(int64(h.Ttl), 10) + "\t" + s += "CLASS" + strconv.Itoa(int(h.Class)) + "\t" + s += "TYPE" + strconv.Itoa(int(h.Rrtype)) + "\t" + return s +} + +// URI RR. See RFC 7553. +type URI struct { + Hdr RR_Header + Priority uint16 + Weight uint16 + Target string `dns:"octet"` +} + +// rr.Target to be parsed as a sequence of character encoded octets according to RFC 3986 +func (rr *URI) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Priority)) + + " " + strconv.Itoa(int(rr.Weight)) + " " + sprintTxtOctet(rr.Target) +} + +// DHCID RR. See RFC 4701. +type DHCID struct { + Hdr RR_Header + Digest string `dns:"base64"` +} + +func (rr *DHCID) String() string { return rr.Hdr.String() + rr.Digest } + +// TLSA RR. See RFC 6698. +type TLSA struct { + Hdr RR_Header + Usage uint8 + Selector uint8 + MatchingType uint8 + Certificate string `dns:"hex"` +} + +func (rr *TLSA) String() string { + return rr.Hdr.String() + + strconv.Itoa(int(rr.Usage)) + + " " + strconv.Itoa(int(rr.Selector)) + + " " + strconv.Itoa(int(rr.MatchingType)) + + " " + rr.Certificate +} + +// SMIMEA RR. See RFC 8162. +type SMIMEA struct { + Hdr RR_Header + Usage uint8 + Selector uint8 + MatchingType uint8 + Certificate string `dns:"hex"` +} + +func (rr *SMIMEA) String() string { + s := rr.Hdr.String() + + strconv.Itoa(int(rr.Usage)) + + " " + strconv.Itoa(int(rr.Selector)) + + " " + strconv.Itoa(int(rr.MatchingType)) + + // Every Nth char needs a space on this output. If we output + // this as one giant line, we can't read it can in because in some cases + // the cert length overflows scan.maxTok (2048). + sx := splitN(rr.Certificate, 1024) // conservative value here + s += " " + strings.Join(sx, " ") + return s +} + +// HIP RR. See RFC 8005. +type HIP struct { + Hdr RR_Header + HitLength uint8 + PublicKeyAlgorithm uint8 + PublicKeyLength uint16 + Hit string `dns:"size-hex:HitLength"` + PublicKey string `dns:"size-base64:PublicKeyLength"` + RendezvousServers []string `dns:"domain-name"` +} + +func (rr *HIP) String() string { + s := rr.Hdr.String() + + strconv.Itoa(int(rr.PublicKeyAlgorithm)) + + " " + rr.Hit + + " " + rr.PublicKey + for _, d := range rr.RendezvousServers { + s += " " + sprintName(d) + } + return s +} + +// NINFO RR. See https://www.iana.org/assignments/dns-parameters/NINFO/ninfo-completed-template. +type NINFO struct { + Hdr RR_Header + ZSData []string `dns:"txt"` +} + +func (rr *NINFO) String() string { return rr.Hdr.String() + sprintTxt(rr.ZSData) } + +// NID RR. See RFC RFC 6742. +type NID struct { + Hdr RR_Header + Preference uint16 + NodeID uint64 +} + +func (rr *NID) String() string { + s := rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + node := fmt.Sprintf("%0.16x", rr.NodeID) + s += " " + node[0:4] + ":" + node[4:8] + ":" + node[8:12] + ":" + node[12:16] + return s +} + +// L32 RR, See RFC 6742. +type L32 struct { + Hdr RR_Header + Preference uint16 + Locator32 net.IP `dns:"a"` +} + +func (rr *L32) String() string { + if rr.Locator32 == nil { + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + } + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + + " " + rr.Locator32.String() +} + +// L64 RR, See RFC 6742. +type L64 struct { + Hdr RR_Header + Preference uint16 + Locator64 uint64 +} + +func (rr *L64) String() string { + s := rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + node := fmt.Sprintf("%0.16X", rr.Locator64) + s += " " + node[0:4] + ":" + node[4:8] + ":" + node[8:12] + ":" + node[12:16] + return s +} + +// LP RR. See RFC 6742. +type LP struct { + Hdr RR_Header + Preference uint16 + Fqdn string `dns:"domain-name"` +} + +func (rr *LP) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Preference)) + " " + sprintName(rr.Fqdn) +} + +// EUI48 RR. See RFC 7043. +type EUI48 struct { + Hdr RR_Header + Address uint64 `dns:"uint48"` +} + +func (rr *EUI48) String() string { return rr.Hdr.String() + euiToString(rr.Address, 48) } + +// EUI64 RR. See RFC 7043. +type EUI64 struct { + Hdr RR_Header + Address uint64 +} + +func (rr *EUI64) String() string { return rr.Hdr.String() + euiToString(rr.Address, 64) } + +// CAA RR. See RFC 6844. +type CAA struct { + Hdr RR_Header + Flag uint8 + Tag string + Value string `dns:"octet"` +} + +// rr.Value Is the character-string encoding of the value field as specified in RFC 1035, Section 5.1. +func (rr *CAA) String() string { + return rr.Hdr.String() + strconv.Itoa(int(rr.Flag)) + " " + rr.Tag + " " + sprintTxtOctet(rr.Value) +} + +// UID RR. Deprecated, IANA-Reserved. +type UID struct { + Hdr RR_Header + Uid uint32 +} + +func (rr *UID) String() string { return rr.Hdr.String() + strconv.FormatInt(int64(rr.Uid), 10) } + +// GID RR. Deprecated, IANA-Reserved. +type GID struct { + Hdr RR_Header + Gid uint32 +} + +func (rr *GID) String() string { return rr.Hdr.String() + strconv.FormatInt(int64(rr.Gid), 10) } + +// UINFO RR. Deprecated, IANA-Reserved. +type UINFO struct { + Hdr RR_Header + Uinfo string +} + +func (rr *UINFO) String() string { return rr.Hdr.String() + sprintTxt([]string{rr.Uinfo}) } + +// EID RR. See http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt. +type EID struct { + Hdr RR_Header + Endpoint string `dns:"hex"` +} + +func (rr *EID) String() string { return rr.Hdr.String() + strings.ToUpper(rr.Endpoint) } + +// NIMLOC RR. See http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt. +type NIMLOC struct { + Hdr RR_Header + Locator string `dns:"hex"` +} + +func (rr *NIMLOC) String() string { return rr.Hdr.String() + strings.ToUpper(rr.Locator) } + +// OPENPGPKEY RR. See RFC 7929. +type OPENPGPKEY struct { + Hdr RR_Header + PublicKey string `dns:"base64"` +} + +func (rr *OPENPGPKEY) String() string { return rr.Hdr.String() + rr.PublicKey } + +// CSYNC RR. See RFC 7477. +type CSYNC struct { + Hdr RR_Header + Serial uint32 + Flags uint16 + TypeBitMap []uint16 `dns:"nsec"` +} + +func (rr *CSYNC) String() string { + s := rr.Hdr.String() + strconv.FormatInt(int64(rr.Serial), 10) + " " + strconv.Itoa(int(rr.Flags)) + + for _, t := range rr.TypeBitMap { + s += " " + Type(t).String() + } + return s +} + +func (rr *CSYNC) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 4 + 2 + l += typeBitMapLen(rr.TypeBitMap) + return l +} + +// ZONEMD RR, from draft-ietf-dnsop-dns-zone-digest +type ZONEMD struct { + Hdr RR_Header + Serial uint32 + Scheme uint8 + Hash uint8 + Digest string `dns:"hex"` +} + +func (rr *ZONEMD) String() string { + return rr.Hdr.String() + + strconv.Itoa(int(rr.Serial)) + + " " + strconv.Itoa(int(rr.Scheme)) + + " " + strconv.Itoa(int(rr.Hash)) + + " " + rr.Digest +} + +// APL RR. See RFC 3123. +type APL struct { + Hdr RR_Header + Prefixes []APLPrefix `dns:"apl"` +} + +// APLPrefix is an address prefix hold by an APL record. +type APLPrefix struct { + Negation bool + Network net.IPNet +} + +// String returns presentation form of the APL record. +func (rr *APL) String() string { + var sb strings.Builder + sb.WriteString(rr.Hdr.String()) + for i, p := range rr.Prefixes { + if i > 0 { + sb.WriteByte(' ') + } + sb.WriteString(p.str()) + } + return sb.String() +} + +// str returns presentation form of the APL prefix. +func (p *APLPrefix) str() string { + var sb strings.Builder + if p.Negation { + sb.WriteByte('!') + } + + switch len(p.Network.IP) { + case net.IPv4len: + sb.WriteByte('1') + case net.IPv6len: + sb.WriteByte('2') + } + + sb.WriteByte(':') + + switch len(p.Network.IP) { + case net.IPv4len: + sb.WriteString(p.Network.IP.String()) + case net.IPv6len: + // add prefix for IPv4-mapped IPv6 + if v4 := p.Network.IP.To4(); v4 != nil { + sb.WriteString("::ffff:") + } + sb.WriteString(p.Network.IP.String()) + } + + sb.WriteByte('/') + + prefix, _ := p.Network.Mask.Size() + sb.WriteString(strconv.Itoa(prefix)) + + return sb.String() +} + +// equals reports whether two APL prefixes are identical. +func (a *APLPrefix) equals(b *APLPrefix) bool { + return a.Negation == b.Negation && + bytes.Equal(a.Network.IP, b.Network.IP) && + bytes.Equal(a.Network.Mask, b.Network.Mask) +} + +// copy returns a copy of the APL prefix. +func (p *APLPrefix) copy() APLPrefix { + return APLPrefix{ + Negation: p.Negation, + Network: copyNet(p.Network), + } +} + +// len returns size of the prefix in wire format. +func (p *APLPrefix) len() int { + // 4-byte header and the network address prefix (see Section 4 of RFC 3123) + prefix, _ := p.Network.Mask.Size() + return 4 + (prefix+7)/8 +} + +// TimeToString translates the RRSIG's incep. and expir. times to the +// string representation used when printing the record. +// It takes serial arithmetic (RFC 1982) into account. +func TimeToString(t uint32) string { + mod := (int64(t)-time.Now().Unix())/year68 - 1 + if mod < 0 { + mod = 0 + } + ti := time.Unix(int64(t)-mod*year68, 0).UTC() + return ti.Format("20060102150405") +} + +// StringToTime translates the RRSIG's incep. and expir. times from +// string values like "20110403154150" to an 32 bit integer. +// It takes serial arithmetic (RFC 1982) into account. +func StringToTime(s string) (uint32, error) { + t, err := time.Parse("20060102150405", s) + if err != nil { + return 0, err + } + mod := t.Unix()/year68 - 1 + if mod < 0 { + mod = 0 + } + return uint32(t.Unix() - mod*year68), nil +} + +// saltToString converts a NSECX salt to uppercase and returns "-" when it is empty. +func saltToString(s string) string { + if s == "" { + return "-" + } + return strings.ToUpper(s) +} + +func euiToString(eui uint64, bits int) (hex string) { + switch bits { + case 64: + hex = fmt.Sprintf("%16.16x", eui) + hex = hex[0:2] + "-" + hex[2:4] + "-" + hex[4:6] + "-" + hex[6:8] + + "-" + hex[8:10] + "-" + hex[10:12] + "-" + hex[12:14] + "-" + hex[14:16] + case 48: + hex = fmt.Sprintf("%12.12x", eui) + hex = hex[0:2] + "-" + hex[2:4] + "-" + hex[4:6] + "-" + hex[6:8] + + "-" + hex[8:10] + "-" + hex[10:12] + } + return +} + +// copyIP returns a copy of ip. +func copyIP(ip net.IP) net.IP { + p := make(net.IP, len(ip)) + copy(p, ip) + return p +} + +// copyNet returns a copy of a subnet. +func copyNet(n net.IPNet) net.IPNet { + m := make(net.IPMask, len(n.Mask)) + copy(m, n.Mask) + + return net.IPNet{ + IP: copyIP(n.IP), + Mask: m, + } +} + +// SplitN splits a string into N sized string chunks. +// This might become an exported function once. +func splitN(s string, n int) []string { + if len(s) < n { + return []string{s} + } + sx := []string{} + p, i := 0, n + for { + if i <= len(s) { + sx = append(sx, s[p:i]) + } else { + sx = append(sx, s[p:]) + break + + } + p, i = p+n, i+n + } + + return sx +} diff --git a/vendor/github.com/miekg/dns/udp.go b/vendor/github.com/miekg/dns/udp.go new file mode 100644 index 0000000..a4826ee --- /dev/null +++ b/vendor/github.com/miekg/dns/udp.go @@ -0,0 +1,102 @@ +// +build !windows + +package dns + +import ( + "net" + + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// This is the required size of the OOB buffer to pass to ReadMsgUDP. +var udpOOBSize = func() int { + // We can't know whether we'll get an IPv4 control message or an + // IPv6 control message ahead of time. To get around this, we size + // the buffer equal to the largest of the two. + + oob4 := ipv4.NewControlMessage(ipv4.FlagDst | ipv4.FlagInterface) + oob6 := ipv6.NewControlMessage(ipv6.FlagDst | ipv6.FlagInterface) + + if len(oob4) > len(oob6) { + return len(oob4) + } + + return len(oob6) +}() + +// SessionUDP holds the remote address and the associated +// out-of-band data. +type SessionUDP struct { + raddr *net.UDPAddr + context []byte +} + +// RemoteAddr returns the remote network address. +func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr } + +// ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a +// net.UDPAddr. +func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) { + oob := make([]byte, udpOOBSize) + n, oobn, _, raddr, err := conn.ReadMsgUDP(b, oob) + if err != nil { + return n, nil, err + } + return n, &SessionUDP{raddr, oob[:oobn]}, err +} + +// WriteToSessionUDP acts just like net.UDPConn.WriteTo(), but uses a *SessionUDP instead of a net.Addr. +func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) { + oob := correctSource(session.context) + n, _, err := conn.WriteMsgUDP(b, oob, session.raddr) + return n, err +} + +func setUDPSocketOptions(conn *net.UDPConn) error { + // Try setting the flags for both families and ignore the errors unless they + // both error. + err6 := ipv6.NewPacketConn(conn).SetControlMessage(ipv6.FlagDst|ipv6.FlagInterface, true) + err4 := ipv4.NewPacketConn(conn).SetControlMessage(ipv4.FlagDst|ipv4.FlagInterface, true) + if err6 != nil && err4 != nil { + return err4 + } + return nil +} + +// parseDstFromOOB takes oob data and returns the destination IP. +func parseDstFromOOB(oob []byte) net.IP { + // Start with IPv6 and then fallback to IPv4 + // TODO(fastest963): Figure out a way to prefer one or the other. Looking at + // the lvl of the header for a 0 or 41 isn't cross-platform. + cm6 := new(ipv6.ControlMessage) + if cm6.Parse(oob) == nil && cm6.Dst != nil { + return cm6.Dst + } + cm4 := new(ipv4.ControlMessage) + if cm4.Parse(oob) == nil && cm4.Dst != nil { + return cm4.Dst + } + return nil +} + +// correctSource takes oob data and returns new oob data with the Src equal to the Dst +func correctSource(oob []byte) []byte { + dst := parseDstFromOOB(oob) + if dst == nil { + return nil + } + // If the dst is definitely an IPv6, then use ipv6's ControlMessage to + // respond otherwise use ipv4's because ipv6's marshal ignores ipv4 + // addresses. + if dst.To4() == nil { + cm := new(ipv6.ControlMessage) + cm.Src = dst + oob = cm.Marshal() + } else { + cm := new(ipv4.ControlMessage) + cm.Src = dst + oob = cm.Marshal() + } + return oob +} diff --git a/vendor/github.com/miekg/dns/udp_windows.go b/vendor/github.com/miekg/dns/udp_windows.go new file mode 100644 index 0000000..e7dd8ca --- /dev/null +++ b/vendor/github.com/miekg/dns/udp_windows.go @@ -0,0 +1,35 @@ +// +build windows + +package dns + +import "net" + +// SessionUDP holds the remote address +type SessionUDP struct { + raddr *net.UDPAddr +} + +// RemoteAddr returns the remote network address. +func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr } + +// ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a +// net.UDPAddr. +// TODO(fastest963): Once go1.10 is released, use ReadMsgUDP. +func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) { + n, raddr, err := conn.ReadFrom(b) + if err != nil { + return n, nil, err + } + return n, &SessionUDP{raddr.(*net.UDPAddr)}, err +} + +// WriteToSessionUDP acts just like net.UDPConn.WriteTo(), but uses a *SessionUDP instead of a net.Addr. +// TODO(fastest963): Once go1.10 is released, use WriteMsgUDP. +func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) { + return conn.WriteTo(b, session.raddr) +} + +// TODO(fastest963): Once go1.10 is released and we can use *MsgUDP methods +// use the standard method in udp.go for these. +func setUDPSocketOptions(*net.UDPConn) error { return nil } +func parseDstFromOOB([]byte, net.IP) net.IP { return nil } diff --git a/vendor/github.com/miekg/dns/update.go b/vendor/github.com/miekg/dns/update.go new file mode 100644 index 0000000..69dd386 --- /dev/null +++ b/vendor/github.com/miekg/dns/update.go @@ -0,0 +1,110 @@ +package dns + +// NameUsed sets the RRs in the prereq section to +// "Name is in use" RRs. RFC 2136 section 2.4.4. +func (u *Msg) NameUsed(rr []RR) { + if u.Answer == nil { + u.Answer = make([]RR, 0, len(rr)) + } + for _, r := range rr { + u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}}) + } +} + +// NameNotUsed sets the RRs in the prereq section to +// "Name is in not use" RRs. RFC 2136 section 2.4.5. +func (u *Msg) NameNotUsed(rr []RR) { + if u.Answer == nil { + u.Answer = make([]RR, 0, len(rr)) + } + for _, r := range rr { + u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassNONE}}) + } +} + +// Used sets the RRs in the prereq section to +// "RRset exists (value dependent -- with rdata)" RRs. RFC 2136 section 2.4.2. +func (u *Msg) Used(rr []RR) { + if len(u.Question) == 0 { + panic("dns: empty question section") + } + if u.Answer == nil { + u.Answer = make([]RR, 0, len(rr)) + } + for _, r := range rr { + r.Header().Class = u.Question[0].Qclass + u.Answer = append(u.Answer, r) + } +} + +// RRsetUsed sets the RRs in the prereq section to +// "RRset exists (value independent -- no rdata)" RRs. RFC 2136 section 2.4.1. +func (u *Msg) RRsetUsed(rr []RR) { + if u.Answer == nil { + u.Answer = make([]RR, 0, len(rr)) + } + for _, r := range rr { + h := r.Header() + u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: h.Name, Ttl: 0, Rrtype: h.Rrtype, Class: ClassANY}}) + } +} + +// RRsetNotUsed sets the RRs in the prereq section to +// "RRset does not exist" RRs. RFC 2136 section 2.4.3. +func (u *Msg) RRsetNotUsed(rr []RR) { + if u.Answer == nil { + u.Answer = make([]RR, 0, len(rr)) + } + for _, r := range rr { + h := r.Header() + u.Answer = append(u.Answer, &ANY{Hdr: RR_Header{Name: h.Name, Ttl: 0, Rrtype: h.Rrtype, Class: ClassNONE}}) + } +} + +// Insert creates a dynamic update packet that adds an complete RRset, see RFC 2136 section 2.5.1. +func (u *Msg) Insert(rr []RR) { + if len(u.Question) == 0 { + panic("dns: empty question section") + } + if u.Ns == nil { + u.Ns = make([]RR, 0, len(rr)) + } + for _, r := range rr { + r.Header().Class = u.Question[0].Qclass + u.Ns = append(u.Ns, r) + } +} + +// RemoveRRset creates a dynamic update packet that deletes an RRset, see RFC 2136 section 2.5.2. +func (u *Msg) RemoveRRset(rr []RR) { + if u.Ns == nil { + u.Ns = make([]RR, 0, len(rr)) + } + for _, r := range rr { + h := r.Header() + u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: h.Name, Ttl: 0, Rrtype: h.Rrtype, Class: ClassANY}}) + } +} + +// RemoveName creates a dynamic update packet that deletes all RRsets of a name, see RFC 2136 section 2.5.3 +func (u *Msg) RemoveName(rr []RR) { + if u.Ns == nil { + u.Ns = make([]RR, 0, len(rr)) + } + for _, r := range rr { + u.Ns = append(u.Ns, &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}}) + } +} + +// Remove creates a dynamic update packet deletes RR from a RRSset, see RFC 2136 section 2.5.4 +func (u *Msg) Remove(rr []RR) { + if u.Ns == nil { + u.Ns = make([]RR, 0, len(rr)) + } + for _, r := range rr { + h := r.Header() + h.Class = ClassNONE + h.Ttl = 0 + u.Ns = append(u.Ns, r) + } +} diff --git a/vendor/github.com/miekg/dns/version.go b/vendor/github.com/miekg/dns/version.go new file mode 100644 index 0000000..5a358ac --- /dev/null +++ b/vendor/github.com/miekg/dns/version.go @@ -0,0 +1,15 @@ +package dns + +import "fmt" + +// Version is current version of this library. +var Version = v{1, 1, 41} + +// v holds the version of this library. +type v struct { + Major, Minor, Patch int +} + +func (v v) String() string { + return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) +} diff --git a/vendor/github.com/miekg/dns/xfr.go b/vendor/github.com/miekg/dns/xfr.go new file mode 100644 index 0000000..43970e6 --- /dev/null +++ b/vendor/github.com/miekg/dns/xfr.go @@ -0,0 +1,266 @@ +package dns + +import ( + "fmt" + "time" +) + +// Envelope is used when doing a zone transfer with a remote server. +type Envelope struct { + RR []RR // The set of RRs in the answer section of the xfr reply message. + Error error // If something went wrong, this contains the error. +} + +// A Transfer defines parameters that are used during a zone transfer. +type Transfer struct { + *Conn + DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds + ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds + WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds + TsigSecret map[string]string // Secret(s) for Tsig map[], zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2) + tsigTimersOnly bool +} + +// Think we need to away to stop the transfer + +// In performs an incoming transfer with the server in a. +// If you would like to set the source IP, or some other attribute +// of a Dialer for a Transfer, you can do so by specifying the attributes +// in the Transfer.Conn: +// +// d := net.Dialer{LocalAddr: transfer_source} +// con, err := d.Dial("tcp", master) +// dnscon := &dns.Conn{Conn:con} +// transfer = &dns.Transfer{Conn: dnscon} +// channel, err := transfer.In(message, master) +// +func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) { + switch q.Question[0].Qtype { + case TypeAXFR, TypeIXFR: + default: + return nil, &Error{"unsupported question type"} + } + + timeout := dnsTimeout + if t.DialTimeout != 0 { + timeout = t.DialTimeout + } + + if t.Conn == nil { + t.Conn, err = DialTimeout("tcp", a, timeout) + if err != nil { + return nil, err + } + } + + if err := t.WriteMsg(q); err != nil { + return nil, err + } + + env = make(chan *Envelope) + switch q.Question[0].Qtype { + case TypeAXFR: + go t.inAxfr(q, env) + case TypeIXFR: + go t.inIxfr(q, env) + } + + return env, nil +} + +func (t *Transfer) inAxfr(q *Msg, c chan *Envelope) { + first := true + defer t.Close() + defer close(c) + timeout := dnsTimeout + if t.ReadTimeout != 0 { + timeout = t.ReadTimeout + } + for { + t.Conn.SetReadDeadline(time.Now().Add(timeout)) + in, err := t.ReadMsg() + if err != nil { + c <- &Envelope{nil, err} + return + } + if q.Id != in.Id { + c <- &Envelope{in.Answer, ErrId} + return + } + if first { + if in.Rcode != RcodeSuccess { + c <- &Envelope{in.Answer, &Error{err: fmt.Sprintf(errXFR, in.Rcode)}} + return + } + if !isSOAFirst(in) { + c <- &Envelope{in.Answer, ErrSoa} + return + } + first = !first + // only one answer that is SOA, receive more + if len(in.Answer) == 1 { + t.tsigTimersOnly = true + c <- &Envelope{in.Answer, nil} + continue + } + } + + if !first { + t.tsigTimersOnly = true // Subsequent envelopes use this. + if isSOALast(in) { + c <- &Envelope{in.Answer, nil} + return + } + c <- &Envelope{in.Answer, nil} + } + } +} + +func (t *Transfer) inIxfr(q *Msg, c chan *Envelope) { + var serial uint32 // The first serial seen is the current server serial + axfr := true + n := 0 + qser := q.Ns[0].(*SOA).Serial + defer t.Close() + defer close(c) + timeout := dnsTimeout + if t.ReadTimeout != 0 { + timeout = t.ReadTimeout + } + for { + t.SetReadDeadline(time.Now().Add(timeout)) + in, err := t.ReadMsg() + if err != nil { + c <- &Envelope{nil, err} + return + } + if q.Id != in.Id { + c <- &Envelope{in.Answer, ErrId} + return + } + if in.Rcode != RcodeSuccess { + c <- &Envelope{in.Answer, &Error{err: fmt.Sprintf(errXFR, in.Rcode)}} + return + } + if n == 0 { + // Check if the returned answer is ok + if !isSOAFirst(in) { + c <- &Envelope{in.Answer, ErrSoa} + return + } + // This serial is important + serial = in.Answer[0].(*SOA).Serial + // Check if there are no changes in zone + if qser >= serial { + c <- &Envelope{in.Answer, nil} + return + } + } + // Now we need to check each message for SOA records, to see what we need to do + t.tsigTimersOnly = true + for _, rr := range in.Answer { + if v, ok := rr.(*SOA); ok { + if v.Serial == serial { + n++ + // quit if it's a full axfr or the the servers' SOA is repeated the third time + if axfr && n == 2 || n == 3 { + c <- &Envelope{in.Answer, nil} + return + } + } else if axfr { + // it's an ixfr + axfr = false + } + } + } + c <- &Envelope{in.Answer, nil} + } +} + +// Out performs an outgoing transfer with the client connecting in w. +// Basic use pattern: +// +// ch := make(chan *dns.Envelope) +// tr := new(dns.Transfer) +// var wg sync.WaitGroup +// go func() { +// tr.Out(w, r, ch) +// wg.Done() +// }() +// ch <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}} +// close(ch) +// wg.Wait() // wait until everything is written out +// w.Close() // close connection +// +// The server is responsible for sending the correct sequence of RRs through the channel ch. +func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error { + for x := range ch { + r := new(Msg) + // Compress? + r.SetReply(q) + r.Authoritative = true + // assume it fits TODO(miek): fix + r.Answer = append(r.Answer, x.RR...) + if tsig := q.IsTsig(); tsig != nil && w.TsigStatus() == nil { + r.SetTsig(tsig.Hdr.Name, tsig.Algorithm, tsig.Fudge, time.Now().Unix()) + } + if err := w.WriteMsg(r); err != nil { + return err + } + w.TsigTimersOnly(true) + } + return nil +} + +// ReadMsg reads a message from the transfer connection t. +func (t *Transfer) ReadMsg() (*Msg, error) { + m := new(Msg) + p := make([]byte, MaxMsgSize) + n, err := t.Read(p) + if err != nil && n == 0 { + return nil, err + } + p = p[:n] + if err := m.Unpack(p); err != nil { + return nil, err + } + if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { + if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { + return m, ErrSecret + } + // Need to work on the original message p, as that was used to calculate the tsig. + err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) + t.tsigRequestMAC = ts.MAC + } + return m, err +} + +// WriteMsg writes a message through the transfer connection t. +func (t *Transfer) WriteMsg(m *Msg) (err error) { + var out []byte + if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { + if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { + return ErrSecret + } + out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) + } else { + out, err = m.Pack() + } + if err != nil { + return err + } + _, err = t.Write(out) + return err +} + +func isSOAFirst(in *Msg) bool { + return len(in.Answer) > 0 && + in.Answer[0].Header().Rrtype == TypeSOA +} + +func isSOALast(in *Msg) bool { + return len(in.Answer) > 0 && + in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA +} + +const errXFR = "bad xfr rcode: %d" diff --git a/vendor/github.com/miekg/dns/zduplicate.go b/vendor/github.com/miekg/dns/zduplicate.go new file mode 100644 index 0000000..9eb1dac --- /dev/null +++ b/vendor/github.com/miekg/dns/zduplicate.go @@ -0,0 +1,1340 @@ +// Code generated by "go run duplicate_generate.go"; DO NOT EDIT. + +package dns + +// isDuplicate() functions + +func (r1 *A) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*A) + if !ok { + return false + } + _ = r2 + if !r1.A.Equal(r2.A) { + return false + } + return true +} + +func (r1 *AAAA) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*AAAA) + if !ok { + return false + } + _ = r2 + if !r1.AAAA.Equal(r2.AAAA) { + return false + } + return true +} + +func (r1 *AFSDB) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*AFSDB) + if !ok { + return false + } + _ = r2 + if r1.Subtype != r2.Subtype { + return false + } + if !isDuplicateName(r1.Hostname, r2.Hostname) { + return false + } + return true +} + +func (r1 *ANY) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*ANY) + if !ok { + return false + } + _ = r2 + return true +} + +func (r1 *APL) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*APL) + if !ok { + return false + } + _ = r2 + if len(r1.Prefixes) != len(r2.Prefixes) { + return false + } + for i := 0; i < len(r1.Prefixes); i++ { + if !r1.Prefixes[i].equals(&r2.Prefixes[i]) { + return false + } + } + return true +} + +func (r1 *AVC) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*AVC) + if !ok { + return false + } + _ = r2 + if len(r1.Txt) != len(r2.Txt) { + return false + } + for i := 0; i < len(r1.Txt); i++ { + if r1.Txt[i] != r2.Txt[i] { + return false + } + } + return true +} + +func (r1 *CAA) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*CAA) + if !ok { + return false + } + _ = r2 + if r1.Flag != r2.Flag { + return false + } + if r1.Tag != r2.Tag { + return false + } + if r1.Value != r2.Value { + return false + } + return true +} + +func (r1 *CDNSKEY) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*CDNSKEY) + if !ok { + return false + } + _ = r2 + if r1.Flags != r2.Flags { + return false + } + if r1.Protocol != r2.Protocol { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.PublicKey != r2.PublicKey { + return false + } + return true +} + +func (r1 *CDS) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*CDS) + if !ok { + return false + } + _ = r2 + if r1.KeyTag != r2.KeyTag { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.DigestType != r2.DigestType { + return false + } + if r1.Digest != r2.Digest { + return false + } + return true +} + +func (r1 *CERT) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*CERT) + if !ok { + return false + } + _ = r2 + if r1.Type != r2.Type { + return false + } + if r1.KeyTag != r2.KeyTag { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.Certificate != r2.Certificate { + return false + } + return true +} + +func (r1 *CNAME) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*CNAME) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Target, r2.Target) { + return false + } + return true +} + +func (r1 *CSYNC) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*CSYNC) + if !ok { + return false + } + _ = r2 + if r1.Serial != r2.Serial { + return false + } + if r1.Flags != r2.Flags { + return false + } + if len(r1.TypeBitMap) != len(r2.TypeBitMap) { + return false + } + for i := 0; i < len(r1.TypeBitMap); i++ { + if r1.TypeBitMap[i] != r2.TypeBitMap[i] { + return false + } + } + return true +} + +func (r1 *DHCID) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*DHCID) + if !ok { + return false + } + _ = r2 + if r1.Digest != r2.Digest { + return false + } + return true +} + +func (r1 *DLV) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*DLV) + if !ok { + return false + } + _ = r2 + if r1.KeyTag != r2.KeyTag { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.DigestType != r2.DigestType { + return false + } + if r1.Digest != r2.Digest { + return false + } + return true +} + +func (r1 *DNAME) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*DNAME) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Target, r2.Target) { + return false + } + return true +} + +func (r1 *DNSKEY) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*DNSKEY) + if !ok { + return false + } + _ = r2 + if r1.Flags != r2.Flags { + return false + } + if r1.Protocol != r2.Protocol { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.PublicKey != r2.PublicKey { + return false + } + return true +} + +func (r1 *DS) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*DS) + if !ok { + return false + } + _ = r2 + if r1.KeyTag != r2.KeyTag { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.DigestType != r2.DigestType { + return false + } + if r1.Digest != r2.Digest { + return false + } + return true +} + +func (r1 *EID) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*EID) + if !ok { + return false + } + _ = r2 + if r1.Endpoint != r2.Endpoint { + return false + } + return true +} + +func (r1 *EUI48) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*EUI48) + if !ok { + return false + } + _ = r2 + if r1.Address != r2.Address { + return false + } + return true +} + +func (r1 *EUI64) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*EUI64) + if !ok { + return false + } + _ = r2 + if r1.Address != r2.Address { + return false + } + return true +} + +func (r1 *GID) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*GID) + if !ok { + return false + } + _ = r2 + if r1.Gid != r2.Gid { + return false + } + return true +} + +func (r1 *GPOS) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*GPOS) + if !ok { + return false + } + _ = r2 + if r1.Longitude != r2.Longitude { + return false + } + if r1.Latitude != r2.Latitude { + return false + } + if r1.Altitude != r2.Altitude { + return false + } + return true +} + +func (r1 *HINFO) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*HINFO) + if !ok { + return false + } + _ = r2 + if r1.Cpu != r2.Cpu { + return false + } + if r1.Os != r2.Os { + return false + } + return true +} + +func (r1 *HIP) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*HIP) + if !ok { + return false + } + _ = r2 + if r1.HitLength != r2.HitLength { + return false + } + if r1.PublicKeyAlgorithm != r2.PublicKeyAlgorithm { + return false + } + if r1.PublicKeyLength != r2.PublicKeyLength { + return false + } + if r1.Hit != r2.Hit { + return false + } + if r1.PublicKey != r2.PublicKey { + return false + } + if len(r1.RendezvousServers) != len(r2.RendezvousServers) { + return false + } + for i := 0; i < len(r1.RendezvousServers); i++ { + if !isDuplicateName(r1.RendezvousServers[i], r2.RendezvousServers[i]) { + return false + } + } + return true +} + +func (r1 *HTTPS) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*HTTPS) + if !ok { + return false + } + _ = r2 + if r1.Priority != r2.Priority { + return false + } + if !isDuplicateName(r1.Target, r2.Target) { + return false + } + if len(r1.Value) != len(r2.Value) { + return false + } + if !areSVCBPairArraysEqual(r1.Value, r2.Value) { + return false + } + return true +} + +func (r1 *KEY) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*KEY) + if !ok { + return false + } + _ = r2 + if r1.Flags != r2.Flags { + return false + } + if r1.Protocol != r2.Protocol { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.PublicKey != r2.PublicKey { + return false + } + return true +} + +func (r1 *KX) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*KX) + if !ok { + return false + } + _ = r2 + if r1.Preference != r2.Preference { + return false + } + if !isDuplicateName(r1.Exchanger, r2.Exchanger) { + return false + } + return true +} + +func (r1 *L32) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*L32) + if !ok { + return false + } + _ = r2 + if r1.Preference != r2.Preference { + return false + } + if !r1.Locator32.Equal(r2.Locator32) { + return false + } + return true +} + +func (r1 *L64) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*L64) + if !ok { + return false + } + _ = r2 + if r1.Preference != r2.Preference { + return false + } + if r1.Locator64 != r2.Locator64 { + return false + } + return true +} + +func (r1 *LOC) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*LOC) + if !ok { + return false + } + _ = r2 + if r1.Version != r2.Version { + return false + } + if r1.Size != r2.Size { + return false + } + if r1.HorizPre != r2.HorizPre { + return false + } + if r1.VertPre != r2.VertPre { + return false + } + if r1.Latitude != r2.Latitude { + return false + } + if r1.Longitude != r2.Longitude { + return false + } + if r1.Altitude != r2.Altitude { + return false + } + return true +} + +func (r1 *LP) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*LP) + if !ok { + return false + } + _ = r2 + if r1.Preference != r2.Preference { + return false + } + if !isDuplicateName(r1.Fqdn, r2.Fqdn) { + return false + } + return true +} + +func (r1 *MB) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*MB) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Mb, r2.Mb) { + return false + } + return true +} + +func (r1 *MD) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*MD) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Md, r2.Md) { + return false + } + return true +} + +func (r1 *MF) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*MF) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Mf, r2.Mf) { + return false + } + return true +} + +func (r1 *MG) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*MG) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Mg, r2.Mg) { + return false + } + return true +} + +func (r1 *MINFO) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*MINFO) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Rmail, r2.Rmail) { + return false + } + if !isDuplicateName(r1.Email, r2.Email) { + return false + } + return true +} + +func (r1 *MR) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*MR) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Mr, r2.Mr) { + return false + } + return true +} + +func (r1 *MX) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*MX) + if !ok { + return false + } + _ = r2 + if r1.Preference != r2.Preference { + return false + } + if !isDuplicateName(r1.Mx, r2.Mx) { + return false + } + return true +} + +func (r1 *NAPTR) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*NAPTR) + if !ok { + return false + } + _ = r2 + if r1.Order != r2.Order { + return false + } + if r1.Preference != r2.Preference { + return false + } + if r1.Flags != r2.Flags { + return false + } + if r1.Service != r2.Service { + return false + } + if r1.Regexp != r2.Regexp { + return false + } + if !isDuplicateName(r1.Replacement, r2.Replacement) { + return false + } + return true +} + +func (r1 *NID) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*NID) + if !ok { + return false + } + _ = r2 + if r1.Preference != r2.Preference { + return false + } + if r1.NodeID != r2.NodeID { + return false + } + return true +} + +func (r1 *NIMLOC) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*NIMLOC) + if !ok { + return false + } + _ = r2 + if r1.Locator != r2.Locator { + return false + } + return true +} + +func (r1 *NINFO) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*NINFO) + if !ok { + return false + } + _ = r2 + if len(r1.ZSData) != len(r2.ZSData) { + return false + } + for i := 0; i < len(r1.ZSData); i++ { + if r1.ZSData[i] != r2.ZSData[i] { + return false + } + } + return true +} + +func (r1 *NS) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*NS) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Ns, r2.Ns) { + return false + } + return true +} + +func (r1 *NSAPPTR) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*NSAPPTR) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Ptr, r2.Ptr) { + return false + } + return true +} + +func (r1 *NSEC) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*NSEC) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.NextDomain, r2.NextDomain) { + return false + } + if len(r1.TypeBitMap) != len(r2.TypeBitMap) { + return false + } + for i := 0; i < len(r1.TypeBitMap); i++ { + if r1.TypeBitMap[i] != r2.TypeBitMap[i] { + return false + } + } + return true +} + +func (r1 *NSEC3) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*NSEC3) + if !ok { + return false + } + _ = r2 + if r1.Hash != r2.Hash { + return false + } + if r1.Flags != r2.Flags { + return false + } + if r1.Iterations != r2.Iterations { + return false + } + if r1.SaltLength != r2.SaltLength { + return false + } + if r1.Salt != r2.Salt { + return false + } + if r1.HashLength != r2.HashLength { + return false + } + if r1.NextDomain != r2.NextDomain { + return false + } + if len(r1.TypeBitMap) != len(r2.TypeBitMap) { + return false + } + for i := 0; i < len(r1.TypeBitMap); i++ { + if r1.TypeBitMap[i] != r2.TypeBitMap[i] { + return false + } + } + return true +} + +func (r1 *NSEC3PARAM) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*NSEC3PARAM) + if !ok { + return false + } + _ = r2 + if r1.Hash != r2.Hash { + return false + } + if r1.Flags != r2.Flags { + return false + } + if r1.Iterations != r2.Iterations { + return false + } + if r1.SaltLength != r2.SaltLength { + return false + } + if r1.Salt != r2.Salt { + return false + } + return true +} + +func (r1 *NULL) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*NULL) + if !ok { + return false + } + _ = r2 + if r1.Data != r2.Data { + return false + } + return true +} + +func (r1 *OPENPGPKEY) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*OPENPGPKEY) + if !ok { + return false + } + _ = r2 + if r1.PublicKey != r2.PublicKey { + return false + } + return true +} + +func (r1 *PTR) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*PTR) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Ptr, r2.Ptr) { + return false + } + return true +} + +func (r1 *PX) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*PX) + if !ok { + return false + } + _ = r2 + if r1.Preference != r2.Preference { + return false + } + if !isDuplicateName(r1.Map822, r2.Map822) { + return false + } + if !isDuplicateName(r1.Mapx400, r2.Mapx400) { + return false + } + return true +} + +func (r1 *RFC3597) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*RFC3597) + if !ok { + return false + } + _ = r2 + if r1.Rdata != r2.Rdata { + return false + } + return true +} + +func (r1 *RKEY) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*RKEY) + if !ok { + return false + } + _ = r2 + if r1.Flags != r2.Flags { + return false + } + if r1.Protocol != r2.Protocol { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.PublicKey != r2.PublicKey { + return false + } + return true +} + +func (r1 *RP) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*RP) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Mbox, r2.Mbox) { + return false + } + if !isDuplicateName(r1.Txt, r2.Txt) { + return false + } + return true +} + +func (r1 *RRSIG) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*RRSIG) + if !ok { + return false + } + _ = r2 + if r1.TypeCovered != r2.TypeCovered { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.Labels != r2.Labels { + return false + } + if r1.OrigTtl != r2.OrigTtl { + return false + } + if r1.Expiration != r2.Expiration { + return false + } + if r1.Inception != r2.Inception { + return false + } + if r1.KeyTag != r2.KeyTag { + return false + } + if !isDuplicateName(r1.SignerName, r2.SignerName) { + return false + } + if r1.Signature != r2.Signature { + return false + } + return true +} + +func (r1 *RT) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*RT) + if !ok { + return false + } + _ = r2 + if r1.Preference != r2.Preference { + return false + } + if !isDuplicateName(r1.Host, r2.Host) { + return false + } + return true +} + +func (r1 *SIG) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*SIG) + if !ok { + return false + } + _ = r2 + if r1.TypeCovered != r2.TypeCovered { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.Labels != r2.Labels { + return false + } + if r1.OrigTtl != r2.OrigTtl { + return false + } + if r1.Expiration != r2.Expiration { + return false + } + if r1.Inception != r2.Inception { + return false + } + if r1.KeyTag != r2.KeyTag { + return false + } + if !isDuplicateName(r1.SignerName, r2.SignerName) { + return false + } + if r1.Signature != r2.Signature { + return false + } + return true +} + +func (r1 *SMIMEA) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*SMIMEA) + if !ok { + return false + } + _ = r2 + if r1.Usage != r2.Usage { + return false + } + if r1.Selector != r2.Selector { + return false + } + if r1.MatchingType != r2.MatchingType { + return false + } + if r1.Certificate != r2.Certificate { + return false + } + return true +} + +func (r1 *SOA) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*SOA) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Ns, r2.Ns) { + return false + } + if !isDuplicateName(r1.Mbox, r2.Mbox) { + return false + } + if r1.Serial != r2.Serial { + return false + } + if r1.Refresh != r2.Refresh { + return false + } + if r1.Retry != r2.Retry { + return false + } + if r1.Expire != r2.Expire { + return false + } + if r1.Minttl != r2.Minttl { + return false + } + return true +} + +func (r1 *SPF) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*SPF) + if !ok { + return false + } + _ = r2 + if len(r1.Txt) != len(r2.Txt) { + return false + } + for i := 0; i < len(r1.Txt); i++ { + if r1.Txt[i] != r2.Txt[i] { + return false + } + } + return true +} + +func (r1 *SRV) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*SRV) + if !ok { + return false + } + _ = r2 + if r1.Priority != r2.Priority { + return false + } + if r1.Weight != r2.Weight { + return false + } + if r1.Port != r2.Port { + return false + } + if !isDuplicateName(r1.Target, r2.Target) { + return false + } + return true +} + +func (r1 *SSHFP) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*SSHFP) + if !ok { + return false + } + _ = r2 + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.Type != r2.Type { + return false + } + if r1.FingerPrint != r2.FingerPrint { + return false + } + return true +} + +func (r1 *SVCB) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*SVCB) + if !ok { + return false + } + _ = r2 + if r1.Priority != r2.Priority { + return false + } + if !isDuplicateName(r1.Target, r2.Target) { + return false + } + if len(r1.Value) != len(r2.Value) { + return false + } + if !areSVCBPairArraysEqual(r1.Value, r2.Value) { + return false + } + return true +} + +func (r1 *TA) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*TA) + if !ok { + return false + } + _ = r2 + if r1.KeyTag != r2.KeyTag { + return false + } + if r1.Algorithm != r2.Algorithm { + return false + } + if r1.DigestType != r2.DigestType { + return false + } + if r1.Digest != r2.Digest { + return false + } + return true +} + +func (r1 *TALINK) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*TALINK) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.PreviousName, r2.PreviousName) { + return false + } + if !isDuplicateName(r1.NextName, r2.NextName) { + return false + } + return true +} + +func (r1 *TKEY) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*TKEY) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Algorithm, r2.Algorithm) { + return false + } + if r1.Inception != r2.Inception { + return false + } + if r1.Expiration != r2.Expiration { + return false + } + if r1.Mode != r2.Mode { + return false + } + if r1.Error != r2.Error { + return false + } + if r1.KeySize != r2.KeySize { + return false + } + if r1.Key != r2.Key { + return false + } + if r1.OtherLen != r2.OtherLen { + return false + } + if r1.OtherData != r2.OtherData { + return false + } + return true +} + +func (r1 *TLSA) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*TLSA) + if !ok { + return false + } + _ = r2 + if r1.Usage != r2.Usage { + return false + } + if r1.Selector != r2.Selector { + return false + } + if r1.MatchingType != r2.MatchingType { + return false + } + if r1.Certificate != r2.Certificate { + return false + } + return true +} + +func (r1 *TSIG) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*TSIG) + if !ok { + return false + } + _ = r2 + if !isDuplicateName(r1.Algorithm, r2.Algorithm) { + return false + } + if r1.TimeSigned != r2.TimeSigned { + return false + } + if r1.Fudge != r2.Fudge { + return false + } + if r1.MACSize != r2.MACSize { + return false + } + if r1.MAC != r2.MAC { + return false + } + if r1.OrigId != r2.OrigId { + return false + } + if r1.Error != r2.Error { + return false + } + if r1.OtherLen != r2.OtherLen { + return false + } + if r1.OtherData != r2.OtherData { + return false + } + return true +} + +func (r1 *TXT) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*TXT) + if !ok { + return false + } + _ = r2 + if len(r1.Txt) != len(r2.Txt) { + return false + } + for i := 0; i < len(r1.Txt); i++ { + if r1.Txt[i] != r2.Txt[i] { + return false + } + } + return true +} + +func (r1 *UID) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*UID) + if !ok { + return false + } + _ = r2 + if r1.Uid != r2.Uid { + return false + } + return true +} + +func (r1 *UINFO) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*UINFO) + if !ok { + return false + } + _ = r2 + if r1.Uinfo != r2.Uinfo { + return false + } + return true +} + +func (r1 *URI) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*URI) + if !ok { + return false + } + _ = r2 + if r1.Priority != r2.Priority { + return false + } + if r1.Weight != r2.Weight { + return false + } + if r1.Target != r2.Target { + return false + } + return true +} + +func (r1 *X25) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*X25) + if !ok { + return false + } + _ = r2 + if r1.PSDNAddress != r2.PSDNAddress { + return false + } + return true +} + +func (r1 *ZONEMD) isDuplicate(_r2 RR) bool { + r2, ok := _r2.(*ZONEMD) + if !ok { + return false + } + _ = r2 + if r1.Serial != r2.Serial { + return false + } + if r1.Scheme != r2.Scheme { + return false + } + if r1.Hash != r2.Hash { + return false + } + if r1.Digest != r2.Digest { + return false + } + return true +} diff --git a/vendor/github.com/miekg/dns/zmsg.go b/vendor/github.com/miekg/dns/zmsg.go new file mode 100644 index 0000000..fc0822f --- /dev/null +++ b/vendor/github.com/miekg/dns/zmsg.go @@ -0,0 +1,2875 @@ +// Code generated by "go run msg_generate.go"; DO NOT EDIT. + +package dns + +// pack*() functions + +func (rr *A) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDataA(rr.A, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *AAAA) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDataAAAA(rr.AAAA, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *AFSDB) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Subtype, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Hostname, msg, off, compression, false) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *ANY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + return off, nil +} + +func (rr *APL) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDataApl(rr.Prefixes, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *AVC) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packStringTxt(rr.Txt, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *CAA) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint8(rr.Flag, msg, off) + if err != nil { + return off, err + } + off, err = packString(rr.Tag, msg, off) + if err != nil { + return off, err + } + off, err = packStringOctet(rr.Value, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *CDNSKEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Flags, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Protocol, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packStringBase64(rr.PublicKey, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *CDS) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.KeyTag, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.DigestType, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.Digest, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *CERT) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Type, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.KeyTag, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packStringBase64(rr.Certificate, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *CNAME) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Target, msg, off, compression, compress) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *CSYNC) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint32(rr.Serial, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.Flags, msg, off) + if err != nil { + return off, err + } + off, err = packDataNsec(rr.TypeBitMap, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *DHCID) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packStringBase64(rr.Digest, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *DLV) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.KeyTag, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.DigestType, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.Digest, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *DNAME) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Target, msg, off, compression, false) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *DNSKEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Flags, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Protocol, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packStringBase64(rr.PublicKey, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *DS) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.KeyTag, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.DigestType, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.Digest, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *EID) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packStringHex(rr.Endpoint, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *EUI48) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint48(rr.Address, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *EUI64) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint64(rr.Address, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *GID) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint32(rr.Gid, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *GPOS) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packString(rr.Longitude, msg, off) + if err != nil { + return off, err + } + off, err = packString(rr.Latitude, msg, off) + if err != nil { + return off, err + } + off, err = packString(rr.Altitude, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *HINFO) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packString(rr.Cpu, msg, off) + if err != nil { + return off, err + } + off, err = packString(rr.Os, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *HIP) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint8(rr.HitLength, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.PublicKeyAlgorithm, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.PublicKeyLength, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.Hit, msg, off) + if err != nil { + return off, err + } + off, err = packStringBase64(rr.PublicKey, msg, off) + if err != nil { + return off, err + } + off, err = packDataDomainNames(rr.RendezvousServers, msg, off, compression, false) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *HTTPS) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Priority, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Target, msg, off, compression, false) + if err != nil { + return off, err + } + off, err = packDataSVCB(rr.Value, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *KEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Flags, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Protocol, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packStringBase64(rr.PublicKey, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *KX) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Preference, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Exchanger, msg, off, compression, false) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *L32) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Preference, msg, off) + if err != nil { + return off, err + } + off, err = packDataA(rr.Locator32, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *L64) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Preference, msg, off) + if err != nil { + return off, err + } + off, err = packUint64(rr.Locator64, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *LOC) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint8(rr.Version, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Size, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.HorizPre, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.VertPre, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.Latitude, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.Longitude, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.Altitude, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *LP) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Preference, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Fqdn, msg, off, compression, false) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MB) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Mb, msg, off, compression, compress) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MD) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Md, msg, off, compression, compress) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MF) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Mf, msg, off, compression, compress) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MG) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Mg, msg, off, compression, compress) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MINFO) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Rmail, msg, off, compression, compress) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Email, msg, off, compression, compress) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MR) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Mr, msg, off, compression, compress) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MX) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Preference, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Mx, msg, off, compression, compress) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NAPTR) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Order, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.Preference, msg, off) + if err != nil { + return off, err + } + off, err = packString(rr.Flags, msg, off) + if err != nil { + return off, err + } + off, err = packString(rr.Service, msg, off) + if err != nil { + return off, err + } + off, err = packString(rr.Regexp, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Replacement, msg, off, compression, false) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NID) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Preference, msg, off) + if err != nil { + return off, err + } + off, err = packUint64(rr.NodeID, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NIMLOC) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packStringHex(rr.Locator, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NINFO) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packStringTxt(rr.ZSData, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NS) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Ns, msg, off, compression, compress) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NSAPPTR) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Ptr, msg, off, compression, false) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NSEC) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.NextDomain, msg, off, compression, false) + if err != nil { + return off, err + } + off, err = packDataNsec(rr.TypeBitMap, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NSEC3) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint8(rr.Hash, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Flags, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.Iterations, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.SaltLength, msg, off) + if err != nil { + return off, err + } + // Only pack salt if value is not "-", i.e. empty + if rr.Salt != "-" { + off, err = packStringHex(rr.Salt, msg, off) + if err != nil { + return off, err + } + } + off, err = packUint8(rr.HashLength, msg, off) + if err != nil { + return off, err + } + off, err = packStringBase32(rr.NextDomain, msg, off) + if err != nil { + return off, err + } + off, err = packDataNsec(rr.TypeBitMap, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NSEC3PARAM) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint8(rr.Hash, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Flags, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.Iterations, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.SaltLength, msg, off) + if err != nil { + return off, err + } + // Only pack salt if value is not "-", i.e. empty + if rr.Salt != "-" { + off, err = packStringHex(rr.Salt, msg, off) + if err != nil { + return off, err + } + } + return off, nil +} + +func (rr *NULL) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packStringAny(rr.Data, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *OPENPGPKEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packStringBase64(rr.PublicKey, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *OPT) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDataOpt(rr.Option, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *PTR) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Ptr, msg, off, compression, compress) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *PX) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Preference, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Map822, msg, off, compression, false) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Mapx400, msg, off, compression, false) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *RFC3597) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packStringHex(rr.Rdata, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *RKEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Flags, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Protocol, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packStringBase64(rr.PublicKey, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *RP) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Mbox, msg, off, compression, false) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Txt, msg, off, compression, false) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *RRSIG) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.TypeCovered, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Labels, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.OrigTtl, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.Expiration, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.Inception, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.KeyTag, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.SignerName, msg, off, compression, false) + if err != nil { + return off, err + } + off, err = packStringBase64(rr.Signature, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *RT) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Preference, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Host, msg, off, compression, false) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SIG) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.TypeCovered, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Labels, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.OrigTtl, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.Expiration, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.Inception, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.KeyTag, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.SignerName, msg, off, compression, false) + if err != nil { + return off, err + } + off, err = packStringBase64(rr.Signature, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SMIMEA) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint8(rr.Usage, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Selector, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.MatchingType, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.Certificate, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SOA) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Ns, msg, off, compression, compress) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Mbox, msg, off, compression, compress) + if err != nil { + return off, err + } + off, err = packUint32(rr.Serial, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.Refresh, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.Retry, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.Expire, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.Minttl, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SPF) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packStringTxt(rr.Txt, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SRV) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Priority, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.Weight, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.Port, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Target, msg, off, compression, false) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SSHFP) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint8(rr.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Type, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.FingerPrint, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SVCB) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Priority, msg, off) + if err != nil { + return off, err + } + off, err = packDomainName(rr.Target, msg, off, compression, false) + if err != nil { + return off, err + } + off, err = packDataSVCB(rr.Value, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *TA) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.KeyTag, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Algorithm, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.DigestType, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.Digest, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *TALINK) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.PreviousName, msg, off, compression, false) + if err != nil { + return off, err + } + off, err = packDomainName(rr.NextName, msg, off, compression, false) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *TKEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Algorithm, msg, off, compression, false) + if err != nil { + return off, err + } + off, err = packUint32(rr.Inception, msg, off) + if err != nil { + return off, err + } + off, err = packUint32(rr.Expiration, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.Mode, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.Error, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.KeySize, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.Key, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.OtherLen, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.OtherData, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *TLSA) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint8(rr.Usage, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Selector, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.MatchingType, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.Certificate, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *TSIG) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packDomainName(rr.Algorithm, msg, off, compression, false) + if err != nil { + return off, err + } + off, err = packUint48(rr.TimeSigned, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.Fudge, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.MACSize, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.MAC, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.OrigId, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.Error, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.OtherLen, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.OtherData, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *TXT) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packStringTxt(rr.Txt, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *UID) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint32(rr.Uid, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *UINFO) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packString(rr.Uinfo, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *URI) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint16(rr.Priority, msg, off) + if err != nil { + return off, err + } + off, err = packUint16(rr.Weight, msg, off) + if err != nil { + return off, err + } + off, err = packStringOctet(rr.Target, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *X25) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packString(rr.PSDNAddress, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *ZONEMD) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) { + off, err = packUint32(rr.Serial, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Scheme, msg, off) + if err != nil { + return off, err + } + off, err = packUint8(rr.Hash, msg, off) + if err != nil { + return off, err + } + off, err = packStringHex(rr.Digest, msg, off) + if err != nil { + return off, err + } + return off, nil +} + +// unpack*() functions + +func (rr *A) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.A, off, err = unpackDataA(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *AAAA) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.AAAA, off, err = unpackDataAAAA(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *AFSDB) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Subtype, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Hostname, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *ANY) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + return off, nil +} + +func (rr *APL) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Prefixes, off, err = unpackDataApl(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *AVC) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Txt, off, err = unpackStringTxt(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *CAA) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Flag, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Tag, off, err = unpackString(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Value, off, err = unpackStringOctet(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *CDNSKEY) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Flags, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Protocol, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Algorithm, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.PublicKey, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *CDS) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.KeyTag, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Algorithm, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.DigestType, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Digest, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *CERT) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Type, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.KeyTag, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Algorithm, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Certificate, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *CNAME) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Target, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *CSYNC) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Serial, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Flags, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.TypeBitMap, off, err = unpackDataNsec(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *DHCID) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Digest, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *DLV) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.KeyTag, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Algorithm, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.DigestType, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Digest, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *DNAME) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Target, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *DNSKEY) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Flags, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Protocol, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Algorithm, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.PublicKey, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *DS) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.KeyTag, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Algorithm, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.DigestType, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Digest, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *EID) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Endpoint, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *EUI48) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Address, off, err = unpackUint48(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *EUI64) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Address, off, err = unpackUint64(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *GID) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Gid, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *GPOS) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Longitude, off, err = unpackString(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Latitude, off, err = unpackString(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Altitude, off, err = unpackString(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *HINFO) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Cpu, off, err = unpackString(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Os, off, err = unpackString(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *HIP) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.HitLength, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.PublicKeyAlgorithm, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.PublicKeyLength, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Hit, off, err = unpackStringHex(msg, off, off+int(rr.HitLength)) + if err != nil { + return off, err + } + rr.PublicKey, off, err = unpackStringBase64(msg, off, off+int(rr.PublicKeyLength)) + if err != nil { + return off, err + } + rr.RendezvousServers, off, err = unpackDataDomainNames(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *HTTPS) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Priority, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Target, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Value, off, err = unpackDataSVCB(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *KEY) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Flags, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Protocol, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Algorithm, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.PublicKey, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *KX) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Preference, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Exchanger, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *L32) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Preference, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Locator32, off, err = unpackDataA(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *L64) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Preference, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Locator64, off, err = unpackUint64(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *LOC) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Version, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Size, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.HorizPre, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.VertPre, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Latitude, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Longitude, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Altitude, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *LP) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Preference, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Fqdn, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MB) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Mb, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MD) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Md, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MF) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Mf, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MG) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Mg, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MINFO) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Rmail, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Email, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MR) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Mr, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *MX) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Preference, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Mx, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NAPTR) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Order, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Preference, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Flags, off, err = unpackString(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Service, off, err = unpackString(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Regexp, off, err = unpackString(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Replacement, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NID) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Preference, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.NodeID, off, err = unpackUint64(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NIMLOC) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Locator, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NINFO) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.ZSData, off, err = unpackStringTxt(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NS) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Ns, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NSAPPTR) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Ptr, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NSEC) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.NextDomain, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.TypeBitMap, off, err = unpackDataNsec(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NSEC3) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Hash, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Flags, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Iterations, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.SaltLength, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Salt, off, err = unpackStringHex(msg, off, off+int(rr.SaltLength)) + if err != nil { + return off, err + } + rr.HashLength, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.NextDomain, off, err = unpackStringBase32(msg, off, off+int(rr.HashLength)) + if err != nil { + return off, err + } + rr.TypeBitMap, off, err = unpackDataNsec(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NSEC3PARAM) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Hash, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Flags, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Iterations, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.SaltLength, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Salt, off, err = unpackStringHex(msg, off, off+int(rr.SaltLength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *NULL) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Data, off, err = unpackStringAny(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *OPENPGPKEY) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.PublicKey, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *OPT) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Option, off, err = unpackDataOpt(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *PTR) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Ptr, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *PX) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Preference, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Map822, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Mapx400, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *RFC3597) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Rdata, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *RKEY) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Flags, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Protocol, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Algorithm, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.PublicKey, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *RP) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Mbox, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Txt, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *RRSIG) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.TypeCovered, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Algorithm, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Labels, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.OrigTtl, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Expiration, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Inception, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.KeyTag, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.SignerName, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Signature, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *RT) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Preference, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Host, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SIG) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.TypeCovered, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Algorithm, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Labels, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.OrigTtl, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Expiration, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Inception, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.KeyTag, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.SignerName, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Signature, off, err = unpackStringBase64(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SMIMEA) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Usage, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Selector, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.MatchingType, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Certificate, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SOA) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Ns, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Mbox, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Serial, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Refresh, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Retry, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Expire, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Minttl, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SPF) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Txt, off, err = unpackStringTxt(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SRV) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Priority, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Weight, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Port, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Target, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SSHFP) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Algorithm, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Type, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.FingerPrint, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *SVCB) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Priority, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Target, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Value, off, err = unpackDataSVCB(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *TA) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.KeyTag, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Algorithm, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.DigestType, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Digest, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *TALINK) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.PreviousName, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.NextName, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *TKEY) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Algorithm, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Inception, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Expiration, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Mode, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Error, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.KeySize, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Key, off, err = unpackStringHex(msg, off, off+int(rr.KeySize)) + if err != nil { + return off, err + } + rr.OtherLen, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.OtherData, off, err = unpackStringHex(msg, off, off+int(rr.OtherLen)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *TLSA) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Usage, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Selector, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.MatchingType, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Certificate, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *TSIG) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Algorithm, off, err = UnpackDomainName(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.TimeSigned, off, err = unpackUint48(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Fudge, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.MACSize, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.MAC, off, err = unpackStringHex(msg, off, off+int(rr.MACSize)) + if err != nil { + return off, err + } + rr.OrigId, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Error, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.OtherLen, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.OtherData, off, err = unpackStringHex(msg, off, off+int(rr.OtherLen)) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *TXT) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Txt, off, err = unpackStringTxt(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *UID) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Uid, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *UINFO) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Uinfo, off, err = unpackString(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *URI) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Priority, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Weight, off, err = unpackUint16(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Target, off, err = unpackStringOctet(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *X25) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.PSDNAddress, off, err = unpackString(msg, off) + if err != nil { + return off, err + } + return off, nil +} + +func (rr *ZONEMD) unpack(msg []byte, off int) (off1 int, err error) { + rdStart := off + _ = rdStart + + rr.Serial, off, err = unpackUint32(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Scheme, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Hash, off, err = unpackUint8(msg, off) + if err != nil { + return off, err + } + if off == len(msg) { + return off, nil + } + rr.Digest, off, err = unpackStringHex(msg, off, rdStart+int(rr.Hdr.Rdlength)) + if err != nil { + return off, err + } + return off, nil +} diff --git a/vendor/github.com/miekg/dns/ztypes.go b/vendor/github.com/miekg/dns/ztypes.go new file mode 100644 index 0000000..5d060cf --- /dev/null +++ b/vendor/github.com/miekg/dns/ztypes.go @@ -0,0 +1,952 @@ +// Code generated by "go run types_generate.go"; DO NOT EDIT. + +package dns + +import ( + "encoding/base64" + "net" +) + +// TypeToRR is a map of constructors for each RR type. +var TypeToRR = map[uint16]func() RR{ + TypeA: func() RR { return new(A) }, + TypeAAAA: func() RR { return new(AAAA) }, + TypeAFSDB: func() RR { return new(AFSDB) }, + TypeANY: func() RR { return new(ANY) }, + TypeAPL: func() RR { return new(APL) }, + TypeAVC: func() RR { return new(AVC) }, + TypeCAA: func() RR { return new(CAA) }, + TypeCDNSKEY: func() RR { return new(CDNSKEY) }, + TypeCDS: func() RR { return new(CDS) }, + TypeCERT: func() RR { return new(CERT) }, + TypeCNAME: func() RR { return new(CNAME) }, + TypeCSYNC: func() RR { return new(CSYNC) }, + TypeDHCID: func() RR { return new(DHCID) }, + TypeDLV: func() RR { return new(DLV) }, + TypeDNAME: func() RR { return new(DNAME) }, + TypeDNSKEY: func() RR { return new(DNSKEY) }, + TypeDS: func() RR { return new(DS) }, + TypeEID: func() RR { return new(EID) }, + TypeEUI48: func() RR { return new(EUI48) }, + TypeEUI64: func() RR { return new(EUI64) }, + TypeGID: func() RR { return new(GID) }, + TypeGPOS: func() RR { return new(GPOS) }, + TypeHINFO: func() RR { return new(HINFO) }, + TypeHIP: func() RR { return new(HIP) }, + TypeHTTPS: func() RR { return new(HTTPS) }, + TypeKEY: func() RR { return new(KEY) }, + TypeKX: func() RR { return new(KX) }, + TypeL32: func() RR { return new(L32) }, + TypeL64: func() RR { return new(L64) }, + TypeLOC: func() RR { return new(LOC) }, + TypeLP: func() RR { return new(LP) }, + TypeMB: func() RR { return new(MB) }, + TypeMD: func() RR { return new(MD) }, + TypeMF: func() RR { return new(MF) }, + TypeMG: func() RR { return new(MG) }, + TypeMINFO: func() RR { return new(MINFO) }, + TypeMR: func() RR { return new(MR) }, + TypeMX: func() RR { return new(MX) }, + TypeNAPTR: func() RR { return new(NAPTR) }, + TypeNID: func() RR { return new(NID) }, + TypeNIMLOC: func() RR { return new(NIMLOC) }, + TypeNINFO: func() RR { return new(NINFO) }, + TypeNS: func() RR { return new(NS) }, + TypeNSAPPTR: func() RR { return new(NSAPPTR) }, + TypeNSEC: func() RR { return new(NSEC) }, + TypeNSEC3: func() RR { return new(NSEC3) }, + TypeNSEC3PARAM: func() RR { return new(NSEC3PARAM) }, + TypeNULL: func() RR { return new(NULL) }, + TypeOPENPGPKEY: func() RR { return new(OPENPGPKEY) }, + TypeOPT: func() RR { return new(OPT) }, + TypePTR: func() RR { return new(PTR) }, + TypePX: func() RR { return new(PX) }, + TypeRKEY: func() RR { return new(RKEY) }, + TypeRP: func() RR { return new(RP) }, + TypeRRSIG: func() RR { return new(RRSIG) }, + TypeRT: func() RR { return new(RT) }, + TypeSIG: func() RR { return new(SIG) }, + TypeSMIMEA: func() RR { return new(SMIMEA) }, + TypeSOA: func() RR { return new(SOA) }, + TypeSPF: func() RR { return new(SPF) }, + TypeSRV: func() RR { return new(SRV) }, + TypeSSHFP: func() RR { return new(SSHFP) }, + TypeSVCB: func() RR { return new(SVCB) }, + TypeTA: func() RR { return new(TA) }, + TypeTALINK: func() RR { return new(TALINK) }, + TypeTKEY: func() RR { return new(TKEY) }, + TypeTLSA: func() RR { return new(TLSA) }, + TypeTSIG: func() RR { return new(TSIG) }, + TypeTXT: func() RR { return new(TXT) }, + TypeUID: func() RR { return new(UID) }, + TypeUINFO: func() RR { return new(UINFO) }, + TypeURI: func() RR { return new(URI) }, + TypeX25: func() RR { return new(X25) }, + TypeZONEMD: func() RR { return new(ZONEMD) }, +} + +// TypeToString is a map of strings for each RR type. +var TypeToString = map[uint16]string{ + TypeA: "A", + TypeAAAA: "AAAA", + TypeAFSDB: "AFSDB", + TypeANY: "ANY", + TypeAPL: "APL", + TypeATMA: "ATMA", + TypeAVC: "AVC", + TypeAXFR: "AXFR", + TypeCAA: "CAA", + TypeCDNSKEY: "CDNSKEY", + TypeCDS: "CDS", + TypeCERT: "CERT", + TypeCNAME: "CNAME", + TypeCSYNC: "CSYNC", + TypeDHCID: "DHCID", + TypeDLV: "DLV", + TypeDNAME: "DNAME", + TypeDNSKEY: "DNSKEY", + TypeDS: "DS", + TypeEID: "EID", + TypeEUI48: "EUI48", + TypeEUI64: "EUI64", + TypeGID: "GID", + TypeGPOS: "GPOS", + TypeHINFO: "HINFO", + TypeHIP: "HIP", + TypeHTTPS: "HTTPS", + TypeISDN: "ISDN", + TypeIXFR: "IXFR", + TypeKEY: "KEY", + TypeKX: "KX", + TypeL32: "L32", + TypeL64: "L64", + TypeLOC: "LOC", + TypeLP: "LP", + TypeMAILA: "MAILA", + TypeMAILB: "MAILB", + TypeMB: "MB", + TypeMD: "MD", + TypeMF: "MF", + TypeMG: "MG", + TypeMINFO: "MINFO", + TypeMR: "MR", + TypeMX: "MX", + TypeNAPTR: "NAPTR", + TypeNID: "NID", + TypeNIMLOC: "NIMLOC", + TypeNINFO: "NINFO", + TypeNS: "NS", + TypeNSEC: "NSEC", + TypeNSEC3: "NSEC3", + TypeNSEC3PARAM: "NSEC3PARAM", + TypeNULL: "NULL", + TypeNXT: "NXT", + TypeNone: "None", + TypeOPENPGPKEY: "OPENPGPKEY", + TypeOPT: "OPT", + TypePTR: "PTR", + TypePX: "PX", + TypeRKEY: "RKEY", + TypeRP: "RP", + TypeRRSIG: "RRSIG", + TypeRT: "RT", + TypeReserved: "Reserved", + TypeSIG: "SIG", + TypeSMIMEA: "SMIMEA", + TypeSOA: "SOA", + TypeSPF: "SPF", + TypeSRV: "SRV", + TypeSSHFP: "SSHFP", + TypeSVCB: "SVCB", + TypeTA: "TA", + TypeTALINK: "TALINK", + TypeTKEY: "TKEY", + TypeTLSA: "TLSA", + TypeTSIG: "TSIG", + TypeTXT: "TXT", + TypeUID: "UID", + TypeUINFO: "UINFO", + TypeUNSPEC: "UNSPEC", + TypeURI: "URI", + TypeX25: "X25", + TypeZONEMD: "ZONEMD", + TypeNSAPPTR: "NSAP-PTR", +} + +func (rr *A) Header() *RR_Header { return &rr.Hdr } +func (rr *AAAA) Header() *RR_Header { return &rr.Hdr } +func (rr *AFSDB) Header() *RR_Header { return &rr.Hdr } +func (rr *ANY) Header() *RR_Header { return &rr.Hdr } +func (rr *APL) Header() *RR_Header { return &rr.Hdr } +func (rr *AVC) Header() *RR_Header { return &rr.Hdr } +func (rr *CAA) Header() *RR_Header { return &rr.Hdr } +func (rr *CDNSKEY) Header() *RR_Header { return &rr.Hdr } +func (rr *CDS) Header() *RR_Header { return &rr.Hdr } +func (rr *CERT) Header() *RR_Header { return &rr.Hdr } +func (rr *CNAME) Header() *RR_Header { return &rr.Hdr } +func (rr *CSYNC) Header() *RR_Header { return &rr.Hdr } +func (rr *DHCID) Header() *RR_Header { return &rr.Hdr } +func (rr *DLV) Header() *RR_Header { return &rr.Hdr } +func (rr *DNAME) Header() *RR_Header { return &rr.Hdr } +func (rr *DNSKEY) Header() *RR_Header { return &rr.Hdr } +func (rr *DS) Header() *RR_Header { return &rr.Hdr } +func (rr *EID) Header() *RR_Header { return &rr.Hdr } +func (rr *EUI48) Header() *RR_Header { return &rr.Hdr } +func (rr *EUI64) Header() *RR_Header { return &rr.Hdr } +func (rr *GID) Header() *RR_Header { return &rr.Hdr } +func (rr *GPOS) Header() *RR_Header { return &rr.Hdr } +func (rr *HINFO) Header() *RR_Header { return &rr.Hdr } +func (rr *HIP) Header() *RR_Header { return &rr.Hdr } +func (rr *HTTPS) Header() *RR_Header { return &rr.Hdr } +func (rr *KEY) Header() *RR_Header { return &rr.Hdr } +func (rr *KX) Header() *RR_Header { return &rr.Hdr } +func (rr *L32) Header() *RR_Header { return &rr.Hdr } +func (rr *L64) Header() *RR_Header { return &rr.Hdr } +func (rr *LOC) Header() *RR_Header { return &rr.Hdr } +func (rr *LP) Header() *RR_Header { return &rr.Hdr } +func (rr *MB) Header() *RR_Header { return &rr.Hdr } +func (rr *MD) Header() *RR_Header { return &rr.Hdr } +func (rr *MF) Header() *RR_Header { return &rr.Hdr } +func (rr *MG) Header() *RR_Header { return &rr.Hdr } +func (rr *MINFO) Header() *RR_Header { return &rr.Hdr } +func (rr *MR) Header() *RR_Header { return &rr.Hdr } +func (rr *MX) Header() *RR_Header { return &rr.Hdr } +func (rr *NAPTR) Header() *RR_Header { return &rr.Hdr } +func (rr *NID) Header() *RR_Header { return &rr.Hdr } +func (rr *NIMLOC) Header() *RR_Header { return &rr.Hdr } +func (rr *NINFO) Header() *RR_Header { return &rr.Hdr } +func (rr *NS) Header() *RR_Header { return &rr.Hdr } +func (rr *NSAPPTR) Header() *RR_Header { return &rr.Hdr } +func (rr *NSEC) Header() *RR_Header { return &rr.Hdr } +func (rr *NSEC3) Header() *RR_Header { return &rr.Hdr } +func (rr *NSEC3PARAM) Header() *RR_Header { return &rr.Hdr } +func (rr *NULL) Header() *RR_Header { return &rr.Hdr } +func (rr *OPENPGPKEY) Header() *RR_Header { return &rr.Hdr } +func (rr *OPT) Header() *RR_Header { return &rr.Hdr } +func (rr *PTR) Header() *RR_Header { return &rr.Hdr } +func (rr *PX) Header() *RR_Header { return &rr.Hdr } +func (rr *RFC3597) Header() *RR_Header { return &rr.Hdr } +func (rr *RKEY) Header() *RR_Header { return &rr.Hdr } +func (rr *RP) Header() *RR_Header { return &rr.Hdr } +func (rr *RRSIG) Header() *RR_Header { return &rr.Hdr } +func (rr *RT) Header() *RR_Header { return &rr.Hdr } +func (rr *SIG) Header() *RR_Header { return &rr.Hdr } +func (rr *SMIMEA) Header() *RR_Header { return &rr.Hdr } +func (rr *SOA) Header() *RR_Header { return &rr.Hdr } +func (rr *SPF) Header() *RR_Header { return &rr.Hdr } +func (rr *SRV) Header() *RR_Header { return &rr.Hdr } +func (rr *SSHFP) Header() *RR_Header { return &rr.Hdr } +func (rr *SVCB) Header() *RR_Header { return &rr.Hdr } +func (rr *TA) Header() *RR_Header { return &rr.Hdr } +func (rr *TALINK) Header() *RR_Header { return &rr.Hdr } +func (rr *TKEY) Header() *RR_Header { return &rr.Hdr } +func (rr *TLSA) Header() *RR_Header { return &rr.Hdr } +func (rr *TSIG) Header() *RR_Header { return &rr.Hdr } +func (rr *TXT) Header() *RR_Header { return &rr.Hdr } +func (rr *UID) Header() *RR_Header { return &rr.Hdr } +func (rr *UINFO) Header() *RR_Header { return &rr.Hdr } +func (rr *URI) Header() *RR_Header { return &rr.Hdr } +func (rr *X25) Header() *RR_Header { return &rr.Hdr } +func (rr *ZONEMD) Header() *RR_Header { return &rr.Hdr } + +// len() functions +func (rr *A) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + if len(rr.A) != 0 { + l += net.IPv4len + } + return l +} +func (rr *AAAA) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + if len(rr.AAAA) != 0 { + l += net.IPv6len + } + return l +} +func (rr *AFSDB) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Subtype + l += domainNameLen(rr.Hostname, off+l, compression, false) + return l +} +func (rr *ANY) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + return l +} +func (rr *APL) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + for _, x := range rr.Prefixes { + l += x.len() + } + return l +} +func (rr *AVC) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + for _, x := range rr.Txt { + l += len(x) + 1 + } + return l +} +func (rr *CAA) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l++ // Flag + l += len(rr.Tag) + 1 + l += len(rr.Value) + return l +} +func (rr *CERT) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Type + l += 2 // KeyTag + l++ // Algorithm + l += base64.StdEncoding.DecodedLen(len(rr.Certificate)) + return l +} +func (rr *CNAME) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Target, off+l, compression, true) + return l +} +func (rr *DHCID) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += base64.StdEncoding.DecodedLen(len(rr.Digest)) + return l +} +func (rr *DNAME) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Target, off+l, compression, false) + return l +} +func (rr *DNSKEY) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Flags + l++ // Protocol + l++ // Algorithm + l += base64.StdEncoding.DecodedLen(len(rr.PublicKey)) + return l +} +func (rr *DS) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // KeyTag + l++ // Algorithm + l++ // DigestType + l += len(rr.Digest) / 2 + return l +} +func (rr *EID) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += len(rr.Endpoint) / 2 + return l +} +func (rr *EUI48) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 6 // Address + return l +} +func (rr *EUI64) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 8 // Address + return l +} +func (rr *GID) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 4 // Gid + return l +} +func (rr *GPOS) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += len(rr.Longitude) + 1 + l += len(rr.Latitude) + 1 + l += len(rr.Altitude) + 1 + return l +} +func (rr *HINFO) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += len(rr.Cpu) + 1 + l += len(rr.Os) + 1 + return l +} +func (rr *HIP) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l++ // HitLength + l++ // PublicKeyAlgorithm + l += 2 // PublicKeyLength + l += len(rr.Hit) / 2 + l += base64.StdEncoding.DecodedLen(len(rr.PublicKey)) + for _, x := range rr.RendezvousServers { + l += domainNameLen(x, off+l, compression, false) + } + return l +} +func (rr *KX) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Preference + l += domainNameLen(rr.Exchanger, off+l, compression, false) + return l +} +func (rr *L32) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Preference + if len(rr.Locator32) != 0 { + l += net.IPv4len + } + return l +} +func (rr *L64) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Preference + l += 8 // Locator64 + return l +} +func (rr *LOC) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l++ // Version + l++ // Size + l++ // HorizPre + l++ // VertPre + l += 4 // Latitude + l += 4 // Longitude + l += 4 // Altitude + return l +} +func (rr *LP) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Preference + l += domainNameLen(rr.Fqdn, off+l, compression, false) + return l +} +func (rr *MB) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Mb, off+l, compression, true) + return l +} +func (rr *MD) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Md, off+l, compression, true) + return l +} +func (rr *MF) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Mf, off+l, compression, true) + return l +} +func (rr *MG) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Mg, off+l, compression, true) + return l +} +func (rr *MINFO) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Rmail, off+l, compression, true) + l += domainNameLen(rr.Email, off+l, compression, true) + return l +} +func (rr *MR) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Mr, off+l, compression, true) + return l +} +func (rr *MX) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Preference + l += domainNameLen(rr.Mx, off+l, compression, true) + return l +} +func (rr *NAPTR) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Order + l += 2 // Preference + l += len(rr.Flags) + 1 + l += len(rr.Service) + 1 + l += len(rr.Regexp) + 1 + l += domainNameLen(rr.Replacement, off+l, compression, false) + return l +} +func (rr *NID) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Preference + l += 8 // NodeID + return l +} +func (rr *NIMLOC) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += len(rr.Locator) / 2 + return l +} +func (rr *NINFO) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + for _, x := range rr.ZSData { + l += len(x) + 1 + } + return l +} +func (rr *NS) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Ns, off+l, compression, true) + return l +} +func (rr *NSAPPTR) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Ptr, off+l, compression, false) + return l +} +func (rr *NSEC3PARAM) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l++ // Hash + l++ // Flags + l += 2 // Iterations + l++ // SaltLength + l += len(rr.Salt) / 2 + return l +} +func (rr *NULL) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += len(rr.Data) + return l +} +func (rr *OPENPGPKEY) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += base64.StdEncoding.DecodedLen(len(rr.PublicKey)) + return l +} +func (rr *PTR) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Ptr, off+l, compression, true) + return l +} +func (rr *PX) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Preference + l += domainNameLen(rr.Map822, off+l, compression, false) + l += domainNameLen(rr.Mapx400, off+l, compression, false) + return l +} +func (rr *RFC3597) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += len(rr.Rdata) / 2 + return l +} +func (rr *RKEY) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Flags + l++ // Protocol + l++ // Algorithm + l += base64.StdEncoding.DecodedLen(len(rr.PublicKey)) + return l +} +func (rr *RP) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Mbox, off+l, compression, false) + l += domainNameLen(rr.Txt, off+l, compression, false) + return l +} +func (rr *RRSIG) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // TypeCovered + l++ // Algorithm + l++ // Labels + l += 4 // OrigTtl + l += 4 // Expiration + l += 4 // Inception + l += 2 // KeyTag + l += domainNameLen(rr.SignerName, off+l, compression, false) + l += base64.StdEncoding.DecodedLen(len(rr.Signature)) + return l +} +func (rr *RT) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Preference + l += domainNameLen(rr.Host, off+l, compression, false) + return l +} +func (rr *SMIMEA) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l++ // Usage + l++ // Selector + l++ // MatchingType + l += len(rr.Certificate) / 2 + return l +} +func (rr *SOA) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Ns, off+l, compression, true) + l += domainNameLen(rr.Mbox, off+l, compression, true) + l += 4 // Serial + l += 4 // Refresh + l += 4 // Retry + l += 4 // Expire + l += 4 // Minttl + return l +} +func (rr *SPF) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + for _, x := range rr.Txt { + l += len(x) + 1 + } + return l +} +func (rr *SRV) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Priority + l += 2 // Weight + l += 2 // Port + l += domainNameLen(rr.Target, off+l, compression, false) + return l +} +func (rr *SSHFP) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l++ // Algorithm + l++ // Type + l += len(rr.FingerPrint) / 2 + return l +} +func (rr *SVCB) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Priority + l += domainNameLen(rr.Target, off+l, compression, false) + for _, x := range rr.Value { + l += 4 + int(x.len()) + } + return l +} +func (rr *TA) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // KeyTag + l++ // Algorithm + l++ // DigestType + l += len(rr.Digest) / 2 + return l +} +func (rr *TALINK) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.PreviousName, off+l, compression, false) + l += domainNameLen(rr.NextName, off+l, compression, false) + return l +} +func (rr *TKEY) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Algorithm, off+l, compression, false) + l += 4 // Inception + l += 4 // Expiration + l += 2 // Mode + l += 2 // Error + l += 2 // KeySize + l += len(rr.Key) / 2 + l += 2 // OtherLen + l += len(rr.OtherData) / 2 + return l +} +func (rr *TLSA) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l++ // Usage + l++ // Selector + l++ // MatchingType + l += len(rr.Certificate) / 2 + return l +} +func (rr *TSIG) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += domainNameLen(rr.Algorithm, off+l, compression, false) + l += 6 // TimeSigned + l += 2 // Fudge + l += 2 // MACSize + l += len(rr.MAC) / 2 + l += 2 // OrigId + l += 2 // Error + l += 2 // OtherLen + l += len(rr.OtherData) / 2 + return l +} +func (rr *TXT) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + for _, x := range rr.Txt { + l += len(x) + 1 + } + return l +} +func (rr *UID) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 4 // Uid + return l +} +func (rr *UINFO) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += len(rr.Uinfo) + 1 + return l +} +func (rr *URI) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 2 // Priority + l += 2 // Weight + l += len(rr.Target) + return l +} +func (rr *X25) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += len(rr.PSDNAddress) + 1 + return l +} +func (rr *ZONEMD) len(off int, compression map[string]struct{}) int { + l := rr.Hdr.len(off, compression) + l += 4 // Serial + l++ // Scheme + l++ // Hash + l += len(rr.Digest) / 2 + return l +} + +// copy() functions +func (rr *A) copy() RR { + return &A{rr.Hdr, copyIP(rr.A)} +} +func (rr *AAAA) copy() RR { + return &AAAA{rr.Hdr, copyIP(rr.AAAA)} +} +func (rr *AFSDB) copy() RR { + return &AFSDB{rr.Hdr, rr.Subtype, rr.Hostname} +} +func (rr *ANY) copy() RR { + return &ANY{rr.Hdr} +} +func (rr *APL) copy() RR { + Prefixes := make([]APLPrefix, len(rr.Prefixes)) + for i, e := range rr.Prefixes { + Prefixes[i] = e.copy() + } + return &APL{rr.Hdr, Prefixes} +} +func (rr *AVC) copy() RR { + Txt := make([]string, len(rr.Txt)) + copy(Txt, rr.Txt) + return &AVC{rr.Hdr, Txt} +} +func (rr *CAA) copy() RR { + return &CAA{rr.Hdr, rr.Flag, rr.Tag, rr.Value} +} +func (rr *CDNSKEY) copy() RR { + return &CDNSKEY{*rr.DNSKEY.copy().(*DNSKEY)} +} +func (rr *CDS) copy() RR { + return &CDS{*rr.DS.copy().(*DS)} +} +func (rr *CERT) copy() RR { + return &CERT{rr.Hdr, rr.Type, rr.KeyTag, rr.Algorithm, rr.Certificate} +} +func (rr *CNAME) copy() RR { + return &CNAME{rr.Hdr, rr.Target} +} +func (rr *CSYNC) copy() RR { + TypeBitMap := make([]uint16, len(rr.TypeBitMap)) + copy(TypeBitMap, rr.TypeBitMap) + return &CSYNC{rr.Hdr, rr.Serial, rr.Flags, TypeBitMap} +} +func (rr *DHCID) copy() RR { + return &DHCID{rr.Hdr, rr.Digest} +} +func (rr *DLV) copy() RR { + return &DLV{*rr.DS.copy().(*DS)} +} +func (rr *DNAME) copy() RR { + return &DNAME{rr.Hdr, rr.Target} +} +func (rr *DNSKEY) copy() RR { + return &DNSKEY{rr.Hdr, rr.Flags, rr.Protocol, rr.Algorithm, rr.PublicKey} +} +func (rr *DS) copy() RR { + return &DS{rr.Hdr, rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest} +} +func (rr *EID) copy() RR { + return &EID{rr.Hdr, rr.Endpoint} +} +func (rr *EUI48) copy() RR { + return &EUI48{rr.Hdr, rr.Address} +} +func (rr *EUI64) copy() RR { + return &EUI64{rr.Hdr, rr.Address} +} +func (rr *GID) copy() RR { + return &GID{rr.Hdr, rr.Gid} +} +func (rr *GPOS) copy() RR { + return &GPOS{rr.Hdr, rr.Longitude, rr.Latitude, rr.Altitude} +} +func (rr *HINFO) copy() RR { + return &HINFO{rr.Hdr, rr.Cpu, rr.Os} +} +func (rr *HIP) copy() RR { + RendezvousServers := make([]string, len(rr.RendezvousServers)) + copy(RendezvousServers, rr.RendezvousServers) + return &HIP{rr.Hdr, rr.HitLength, rr.PublicKeyAlgorithm, rr.PublicKeyLength, rr.Hit, rr.PublicKey, RendezvousServers} +} +func (rr *HTTPS) copy() RR { + return &HTTPS{*rr.SVCB.copy().(*SVCB)} +} +func (rr *KEY) copy() RR { + return &KEY{*rr.DNSKEY.copy().(*DNSKEY)} +} +func (rr *KX) copy() RR { + return &KX{rr.Hdr, rr.Preference, rr.Exchanger} +} +func (rr *L32) copy() RR { + return &L32{rr.Hdr, rr.Preference, copyIP(rr.Locator32)} +} +func (rr *L64) copy() RR { + return &L64{rr.Hdr, rr.Preference, rr.Locator64} +} +func (rr *LOC) copy() RR { + return &LOC{rr.Hdr, rr.Version, rr.Size, rr.HorizPre, rr.VertPre, rr.Latitude, rr.Longitude, rr.Altitude} +} +func (rr *LP) copy() RR { + return &LP{rr.Hdr, rr.Preference, rr.Fqdn} +} +func (rr *MB) copy() RR { + return &MB{rr.Hdr, rr.Mb} +} +func (rr *MD) copy() RR { + return &MD{rr.Hdr, rr.Md} +} +func (rr *MF) copy() RR { + return &MF{rr.Hdr, rr.Mf} +} +func (rr *MG) copy() RR { + return &MG{rr.Hdr, rr.Mg} +} +func (rr *MINFO) copy() RR { + return &MINFO{rr.Hdr, rr.Rmail, rr.Email} +} +func (rr *MR) copy() RR { + return &MR{rr.Hdr, rr.Mr} +} +func (rr *MX) copy() RR { + return &MX{rr.Hdr, rr.Preference, rr.Mx} +} +func (rr *NAPTR) copy() RR { + return &NAPTR{rr.Hdr, rr.Order, rr.Preference, rr.Flags, rr.Service, rr.Regexp, rr.Replacement} +} +func (rr *NID) copy() RR { + return &NID{rr.Hdr, rr.Preference, rr.NodeID} +} +func (rr *NIMLOC) copy() RR { + return &NIMLOC{rr.Hdr, rr.Locator} +} +func (rr *NINFO) copy() RR { + ZSData := make([]string, len(rr.ZSData)) + copy(ZSData, rr.ZSData) + return &NINFO{rr.Hdr, ZSData} +} +func (rr *NS) copy() RR { + return &NS{rr.Hdr, rr.Ns} +} +func (rr *NSAPPTR) copy() RR { + return &NSAPPTR{rr.Hdr, rr.Ptr} +} +func (rr *NSEC) copy() RR { + TypeBitMap := make([]uint16, len(rr.TypeBitMap)) + copy(TypeBitMap, rr.TypeBitMap) + return &NSEC{rr.Hdr, rr.NextDomain, TypeBitMap} +} +func (rr *NSEC3) copy() RR { + TypeBitMap := make([]uint16, len(rr.TypeBitMap)) + copy(TypeBitMap, rr.TypeBitMap) + return &NSEC3{rr.Hdr, rr.Hash, rr.Flags, rr.Iterations, rr.SaltLength, rr.Salt, rr.HashLength, rr.NextDomain, TypeBitMap} +} +func (rr *NSEC3PARAM) copy() RR { + return &NSEC3PARAM{rr.Hdr, rr.Hash, rr.Flags, rr.Iterations, rr.SaltLength, rr.Salt} +} +func (rr *NULL) copy() RR { + return &NULL{rr.Hdr, rr.Data} +} +func (rr *OPENPGPKEY) copy() RR { + return &OPENPGPKEY{rr.Hdr, rr.PublicKey} +} +func (rr *OPT) copy() RR { + Option := make([]EDNS0, len(rr.Option)) + for i, e := range rr.Option { + Option[i] = e.copy() + } + return &OPT{rr.Hdr, Option} +} +func (rr *PTR) copy() RR { + return &PTR{rr.Hdr, rr.Ptr} +} +func (rr *PX) copy() RR { + return &PX{rr.Hdr, rr.Preference, rr.Map822, rr.Mapx400} +} +func (rr *RFC3597) copy() RR { + return &RFC3597{rr.Hdr, rr.Rdata} +} +func (rr *RKEY) copy() RR { + return &RKEY{rr.Hdr, rr.Flags, rr.Protocol, rr.Algorithm, rr.PublicKey} +} +func (rr *RP) copy() RR { + return &RP{rr.Hdr, rr.Mbox, rr.Txt} +} +func (rr *RRSIG) copy() RR { + return &RRSIG{rr.Hdr, rr.TypeCovered, rr.Algorithm, rr.Labels, rr.OrigTtl, rr.Expiration, rr.Inception, rr.KeyTag, rr.SignerName, rr.Signature} +} +func (rr *RT) copy() RR { + return &RT{rr.Hdr, rr.Preference, rr.Host} +} +func (rr *SIG) copy() RR { + return &SIG{*rr.RRSIG.copy().(*RRSIG)} +} +func (rr *SMIMEA) copy() RR { + return &SMIMEA{rr.Hdr, rr.Usage, rr.Selector, rr.MatchingType, rr.Certificate} +} +func (rr *SOA) copy() RR { + return &SOA{rr.Hdr, rr.Ns, rr.Mbox, rr.Serial, rr.Refresh, rr.Retry, rr.Expire, rr.Minttl} +} +func (rr *SPF) copy() RR { + Txt := make([]string, len(rr.Txt)) + copy(Txt, rr.Txt) + return &SPF{rr.Hdr, Txt} +} +func (rr *SRV) copy() RR { + return &SRV{rr.Hdr, rr.Priority, rr.Weight, rr.Port, rr.Target} +} +func (rr *SSHFP) copy() RR { + return &SSHFP{rr.Hdr, rr.Algorithm, rr.Type, rr.FingerPrint} +} +func (rr *SVCB) copy() RR { + Value := make([]SVCBKeyValue, len(rr.Value)) + for i, e := range rr.Value { + Value[i] = e.copy() + } + return &SVCB{rr.Hdr, rr.Priority, rr.Target, Value} +} +func (rr *TA) copy() RR { + return &TA{rr.Hdr, rr.KeyTag, rr.Algorithm, rr.DigestType, rr.Digest} +} +func (rr *TALINK) copy() RR { + return &TALINK{rr.Hdr, rr.PreviousName, rr.NextName} +} +func (rr *TKEY) copy() RR { + return &TKEY{rr.Hdr, rr.Algorithm, rr.Inception, rr.Expiration, rr.Mode, rr.Error, rr.KeySize, rr.Key, rr.OtherLen, rr.OtherData} +} +func (rr *TLSA) copy() RR { + return &TLSA{rr.Hdr, rr.Usage, rr.Selector, rr.MatchingType, rr.Certificate} +} +func (rr *TSIG) copy() RR { + return &TSIG{rr.Hdr, rr.Algorithm, rr.TimeSigned, rr.Fudge, rr.MACSize, rr.MAC, rr.OrigId, rr.Error, rr.OtherLen, rr.OtherData} +} +func (rr *TXT) copy() RR { + Txt := make([]string, len(rr.Txt)) + copy(Txt, rr.Txt) + return &TXT{rr.Hdr, Txt} +} +func (rr *UID) copy() RR { + return &UID{rr.Hdr, rr.Uid} +} +func (rr *UINFO) copy() RR { + return &UINFO{rr.Hdr, rr.Uinfo} +} +func (rr *URI) copy() RR { + return &URI{rr.Hdr, rr.Priority, rr.Weight, rr.Target} +} +func (rr *X25) copy() RR { + return &X25{rr.Hdr, rr.PSDNAddress} +} +func (rr *ZONEMD) copy() RR { + return &ZONEMD{rr.Hdr, rr.Serial, rr.Scheme, rr.Hash, rr.Digest} +} diff --git a/vendor/github.com/szatmary/sonos/zoneplayer.go b/vendor/github.com/szatmary/sonos/zoneplayer.go index 1b87f60..21c1777 100644 --- a/vendor/github.com/szatmary/sonos/zoneplayer.go +++ b/vendor/github.com/szatmary/sonos/zoneplayer.go @@ -223,7 +223,7 @@ func (z *ZonePlayer) SetVolume(desiredVolume int) error { func (z *ZonePlayer) Play() error { _, err := z.AVTransport.Play(z.HttpClient, &avt.PlayArgs{ - Speed: "1", + Speed: "1.0", }) return err } diff --git a/vendor/golang.org/x/net/ipv6/batch.go b/vendor/golang.org/x/net/ipv6/batch.go new file mode 100644 index 0000000..2ccb984 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/batch.go @@ -0,0 +1,116 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import ( + "net" + "runtime" + + "golang.org/x/net/internal/socket" +) + +// BUG(mikio): On Windows, the ReadBatch and WriteBatch methods of +// PacketConn are not implemented. + +// A Message represents an IO message. +// +// type Message struct { +// Buffers [][]byte +// OOB []byte +// Addr net.Addr +// N int +// NN int +// Flags int +// } +// +// The Buffers fields represents a list of contiguous buffers, which +// can be used for vectored IO, for example, putting a header and a +// payload in each slice. +// When writing, the Buffers field must contain at least one byte to +// write. +// When reading, the Buffers field will always contain a byte to read. +// +// The OOB field contains protocol-specific control or miscellaneous +// ancillary data known as out-of-band data. +// It can be nil when not required. +// +// The Addr field specifies a destination address when writing. +// It can be nil when the underlying protocol of the endpoint uses +// connection-oriented communication. +// After a successful read, it may contain the source address on the +// received packet. +// +// The N field indicates the number of bytes read or written from/to +// Buffers. +// +// The NN field indicates the number of bytes read or written from/to +// OOB. +// +// The Flags field contains protocol-specific information on the +// received message. +type Message = socket.Message + +// ReadBatch reads a batch of messages. +// +// The provided flags is a set of platform-dependent flags, such as +// syscall.MSG_PEEK. +// +// On a successful read it returns the number of messages received, up +// to len(ms). +// +// On Linux, a batch read will be optimized. +// On other platforms, this method will read only a single message. +func (c *payloadHandler) ReadBatch(ms []Message, flags int) (int, error) { + if !c.ok() { + return 0, errInvalidConn + } + switch runtime.GOOS { + case "linux": + n, err := c.RecvMsgs([]socket.Message(ms), flags) + if err != nil { + err = &net.OpError{Op: "read", Net: c.PacketConn.LocalAddr().Network(), Source: c.PacketConn.LocalAddr(), Err: err} + } + return n, err + default: + n := 1 + err := c.RecvMsg(&ms[0], flags) + if err != nil { + n = 0 + err = &net.OpError{Op: "read", Net: c.PacketConn.LocalAddr().Network(), Source: c.PacketConn.LocalAddr(), Err: err} + } + return n, err + } +} + +// WriteBatch writes a batch of messages. +// +// The provided flags is a set of platform-dependent flags, such as +// syscall.MSG_DONTROUTE. +// +// It returns the number of messages written on a successful write. +// +// On Linux, a batch write will be optimized. +// On other platforms, this method will write only a single message. +func (c *payloadHandler) WriteBatch(ms []Message, flags int) (int, error) { + if !c.ok() { + return 0, errInvalidConn + } + switch runtime.GOOS { + case "linux": + n, err := c.SendMsgs([]socket.Message(ms), flags) + if err != nil { + err = &net.OpError{Op: "write", Net: c.PacketConn.LocalAddr().Network(), Source: c.PacketConn.LocalAddr(), Err: err} + } + return n, err + default: + n := 1 + err := c.SendMsg(&ms[0], flags) + if err != nil { + n = 0 + err = &net.OpError{Op: "write", Net: c.PacketConn.LocalAddr().Network(), Source: c.PacketConn.LocalAddr(), Err: err} + } + return n, err + } +} diff --git a/vendor/golang.org/x/net/ipv6/control.go b/vendor/golang.org/x/net/ipv6/control.go new file mode 100644 index 0000000..2da6444 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/control.go @@ -0,0 +1,187 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import ( + "fmt" + "net" + "sync" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/internal/socket" +) + +// Note that RFC 3542 obsoletes RFC 2292 but OS X Snow Leopard and the +// former still support RFC 2292 only. Please be aware that almost +// all protocol implementations prohibit using a combination of RFC +// 2292 and RFC 3542 for some practical reasons. + +type rawOpt struct { + sync.RWMutex + cflags ControlFlags +} + +func (c *rawOpt) set(f ControlFlags) { c.cflags |= f } +func (c *rawOpt) clear(f ControlFlags) { c.cflags &^= f } +func (c *rawOpt) isset(f ControlFlags) bool { return c.cflags&f != 0 } + +// A ControlFlags represents per packet basis IP-level socket option +// control flags. +type ControlFlags uint + +const ( + FlagTrafficClass ControlFlags = 1 << iota // pass the traffic class on the received packet + FlagHopLimit // pass the hop limit on the received packet + FlagSrc // pass the source address on the received packet + FlagDst // pass the destination address on the received packet + FlagInterface // pass the interface index on the received packet + FlagPathMTU // pass the path MTU on the received packet path +) + +const flagPacketInfo = FlagDst | FlagInterface + +// A ControlMessage represents per packet basis IP-level socket +// options. +type ControlMessage struct { + // Receiving socket options: SetControlMessage allows to + // receive the options from the protocol stack using ReadFrom + // method of PacketConn. + // + // Specifying socket options: ControlMessage for WriteTo + // method of PacketConn allows to send the options to the + // protocol stack. + // + TrafficClass int // traffic class, must be 1 <= value <= 255 when specifying + HopLimit int // hop limit, must be 1 <= value <= 255 when specifying + Src net.IP // source address, specifying only + Dst net.IP // destination address, receiving only + IfIndex int // interface index, must be 1 <= value when specifying + NextHop net.IP // next hop address, specifying only + MTU int // path MTU, receiving only +} + +func (cm *ControlMessage) String() string { + if cm == nil { + return "" + } + return fmt.Sprintf("tclass=%#x hoplim=%d src=%v dst=%v ifindex=%d nexthop=%v mtu=%d", cm.TrafficClass, cm.HopLimit, cm.Src, cm.Dst, cm.IfIndex, cm.NextHop, cm.MTU) +} + +// Marshal returns the binary encoding of cm. +func (cm *ControlMessage) Marshal() []byte { + if cm == nil { + return nil + } + var l int + tclass := false + if ctlOpts[ctlTrafficClass].name > 0 && cm.TrafficClass > 0 { + tclass = true + l += socket.ControlMessageSpace(ctlOpts[ctlTrafficClass].length) + } + hoplimit := false + if ctlOpts[ctlHopLimit].name > 0 && cm.HopLimit > 0 { + hoplimit = true + l += socket.ControlMessageSpace(ctlOpts[ctlHopLimit].length) + } + pktinfo := false + if ctlOpts[ctlPacketInfo].name > 0 && (cm.Src.To16() != nil && cm.Src.To4() == nil || cm.IfIndex > 0) { + pktinfo = true + l += socket.ControlMessageSpace(ctlOpts[ctlPacketInfo].length) + } + nexthop := false + if ctlOpts[ctlNextHop].name > 0 && cm.NextHop.To16() != nil && cm.NextHop.To4() == nil { + nexthop = true + l += socket.ControlMessageSpace(ctlOpts[ctlNextHop].length) + } + var b []byte + if l > 0 { + b = make([]byte, l) + bb := b + if tclass { + bb = ctlOpts[ctlTrafficClass].marshal(bb, cm) + } + if hoplimit { + bb = ctlOpts[ctlHopLimit].marshal(bb, cm) + } + if pktinfo { + bb = ctlOpts[ctlPacketInfo].marshal(bb, cm) + } + if nexthop { + bb = ctlOpts[ctlNextHop].marshal(bb, cm) + } + } + return b +} + +// Parse parses b as a control message and stores the result in cm. +func (cm *ControlMessage) Parse(b []byte) error { + ms, err := socket.ControlMessage(b).Parse() + if err != nil { + return err + } + for _, m := range ms { + lvl, typ, l, err := m.ParseHeader() + if err != nil { + return err + } + if lvl != iana.ProtocolIPv6 { + continue + } + switch { + case typ == ctlOpts[ctlTrafficClass].name && l >= ctlOpts[ctlTrafficClass].length: + ctlOpts[ctlTrafficClass].parse(cm, m.Data(l)) + case typ == ctlOpts[ctlHopLimit].name && l >= ctlOpts[ctlHopLimit].length: + ctlOpts[ctlHopLimit].parse(cm, m.Data(l)) + case typ == ctlOpts[ctlPacketInfo].name && l >= ctlOpts[ctlPacketInfo].length: + ctlOpts[ctlPacketInfo].parse(cm, m.Data(l)) + case typ == ctlOpts[ctlPathMTU].name && l >= ctlOpts[ctlPathMTU].length: + ctlOpts[ctlPathMTU].parse(cm, m.Data(l)) + } + } + return nil +} + +// NewControlMessage returns a new control message. +// +// The returned message is large enough for options specified by cf. +func NewControlMessage(cf ControlFlags) []byte { + opt := rawOpt{cflags: cf} + var l int + if opt.isset(FlagTrafficClass) && ctlOpts[ctlTrafficClass].name > 0 { + l += socket.ControlMessageSpace(ctlOpts[ctlTrafficClass].length) + } + if opt.isset(FlagHopLimit) && ctlOpts[ctlHopLimit].name > 0 { + l += socket.ControlMessageSpace(ctlOpts[ctlHopLimit].length) + } + if opt.isset(flagPacketInfo) && ctlOpts[ctlPacketInfo].name > 0 { + l += socket.ControlMessageSpace(ctlOpts[ctlPacketInfo].length) + } + if opt.isset(FlagPathMTU) && ctlOpts[ctlPathMTU].name > 0 { + l += socket.ControlMessageSpace(ctlOpts[ctlPathMTU].length) + } + var b []byte + if l > 0 { + b = make([]byte, l) + } + return b +} + +// Ancillary data socket options +const ( + ctlTrafficClass = iota // header field + ctlHopLimit // header field + ctlPacketInfo // inbound or outbound packet path + ctlNextHop // nexthop + ctlPathMTU // path mtu + ctlMax +) + +// A ctlOpt represents a binding for ancillary data socket option. +type ctlOpt struct { + name int // option name, must be equal or greater than 1 + length int // option length + marshal func([]byte, *ControlMessage) []byte + parse func(*ControlMessage, []byte) +} diff --git a/vendor/golang.org/x/net/ipv6/control_rfc2292_unix.go b/vendor/golang.org/x/net/ipv6/control_rfc2292_unix.go new file mode 100644 index 0000000..2733ddb --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/control_rfc2292_unix.go @@ -0,0 +1,51 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin +// +build darwin + +package ipv6 + +import ( + "unsafe" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/internal/socket" + + "golang.org/x/sys/unix" +) + +func marshal2292HopLimit(b []byte, cm *ControlMessage) []byte { + m := socket.ControlMessage(b) + m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_2292HOPLIMIT, 4) + if cm != nil { + socket.NativeEndian.PutUint32(m.Data(4), uint32(cm.HopLimit)) + } + return m.Next(4) +} + +func marshal2292PacketInfo(b []byte, cm *ControlMessage) []byte { + m := socket.ControlMessage(b) + m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_2292PKTINFO, sizeofInet6Pktinfo) + if cm != nil { + pi := (*inet6Pktinfo)(unsafe.Pointer(&m.Data(sizeofInet6Pktinfo)[0])) + if ip := cm.Src.To16(); ip != nil && ip.To4() == nil { + copy(pi.Addr[:], ip) + } + if cm.IfIndex > 0 { + pi.setIfindex(cm.IfIndex) + } + } + return m.Next(sizeofInet6Pktinfo) +} + +func marshal2292NextHop(b []byte, cm *ControlMessage) []byte { + m := socket.ControlMessage(b) + m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_2292NEXTHOP, sizeofSockaddrInet6) + if cm != nil { + sa := (*sockaddrInet6)(unsafe.Pointer(&m.Data(sizeofSockaddrInet6)[0])) + sa.setSockaddr(cm.NextHop, cm.IfIndex) + } + return m.Next(sizeofSockaddrInet6) +} diff --git a/vendor/golang.org/x/net/ipv6/control_rfc3542_unix.go b/vendor/golang.org/x/net/ipv6/control_rfc3542_unix.go new file mode 100644 index 0000000..9c90844 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/control_rfc3542_unix.go @@ -0,0 +1,97 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos + +package ipv6 + +import ( + "net" + "unsafe" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/internal/socket" + + "golang.org/x/sys/unix" +) + +func marshalTrafficClass(b []byte, cm *ControlMessage) []byte { + m := socket.ControlMessage(b) + m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_TCLASS, 4) + if cm != nil { + socket.NativeEndian.PutUint32(m.Data(4), uint32(cm.TrafficClass)) + } + return m.Next(4) +} + +func parseTrafficClass(cm *ControlMessage, b []byte) { + cm.TrafficClass = int(socket.NativeEndian.Uint32(b[:4])) +} + +func marshalHopLimit(b []byte, cm *ControlMessage) []byte { + m := socket.ControlMessage(b) + m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_HOPLIMIT, 4) + if cm != nil { + socket.NativeEndian.PutUint32(m.Data(4), uint32(cm.HopLimit)) + } + return m.Next(4) +} + +func parseHopLimit(cm *ControlMessage, b []byte) { + cm.HopLimit = int(socket.NativeEndian.Uint32(b[:4])) +} + +func marshalPacketInfo(b []byte, cm *ControlMessage) []byte { + m := socket.ControlMessage(b) + m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_PKTINFO, sizeofInet6Pktinfo) + if cm != nil { + pi := (*inet6Pktinfo)(unsafe.Pointer(&m.Data(sizeofInet6Pktinfo)[0])) + if ip := cm.Src.To16(); ip != nil && ip.To4() == nil { + copy(pi.Addr[:], ip) + } + if cm.IfIndex > 0 { + pi.setIfindex(cm.IfIndex) + } + } + return m.Next(sizeofInet6Pktinfo) +} + +func parsePacketInfo(cm *ControlMessage, b []byte) { + pi := (*inet6Pktinfo)(unsafe.Pointer(&b[0])) + if len(cm.Dst) < net.IPv6len { + cm.Dst = make(net.IP, net.IPv6len) + } + copy(cm.Dst, pi.Addr[:]) + cm.IfIndex = int(pi.Ifindex) +} + +func marshalNextHop(b []byte, cm *ControlMessage) []byte { + m := socket.ControlMessage(b) + m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_NEXTHOP, sizeofSockaddrInet6) + if cm != nil { + sa := (*sockaddrInet6)(unsafe.Pointer(&m.Data(sizeofSockaddrInet6)[0])) + sa.setSockaddr(cm.NextHop, cm.IfIndex) + } + return m.Next(sizeofSockaddrInet6) +} + +func parseNextHop(cm *ControlMessage, b []byte) { +} + +func marshalPathMTU(b []byte, cm *ControlMessage) []byte { + m := socket.ControlMessage(b) + m.MarshalHeader(iana.ProtocolIPv6, unix.IPV6_PATHMTU, sizeofIPv6Mtuinfo) + return m.Next(sizeofIPv6Mtuinfo) +} + +func parsePathMTU(cm *ControlMessage, b []byte) { + mi := (*ipv6Mtuinfo)(unsafe.Pointer(&b[0])) + if len(cm.Dst) < net.IPv6len { + cm.Dst = make(net.IP, net.IPv6len) + } + copy(cm.Dst, mi.Addr.Addr[:]) + cm.IfIndex = int(mi.Addr.Scope_id) + cm.MTU = int(mi.Mtu) +} diff --git a/vendor/golang.org/x/net/ipv6/control_stub.go b/vendor/golang.org/x/net/ipv6/control_stub.go new file mode 100644 index 0000000..b7e8643 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/control_stub.go @@ -0,0 +1,14 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows && !zos +// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows,!zos + +package ipv6 + +import "golang.org/x/net/internal/socket" + +func setControlMessage(c *socket.Conn, opt *rawOpt, cf ControlFlags, on bool) error { + return errNotImplemented +} diff --git a/vendor/golang.org/x/net/ipv6/control_unix.go b/vendor/golang.org/x/net/ipv6/control_unix.go new file mode 100644 index 0000000..63e475d --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/control_unix.go @@ -0,0 +1,56 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos + +package ipv6 + +import "golang.org/x/net/internal/socket" + +func setControlMessage(c *socket.Conn, opt *rawOpt, cf ControlFlags, on bool) error { + opt.Lock() + defer opt.Unlock() + if so, ok := sockOpts[ssoReceiveTrafficClass]; ok && cf&FlagTrafficClass != 0 { + if err := so.SetInt(c, boolint(on)); err != nil { + return err + } + if on { + opt.set(FlagTrafficClass) + } else { + opt.clear(FlagTrafficClass) + } + } + if so, ok := sockOpts[ssoReceiveHopLimit]; ok && cf&FlagHopLimit != 0 { + if err := so.SetInt(c, boolint(on)); err != nil { + return err + } + if on { + opt.set(FlagHopLimit) + } else { + opt.clear(FlagHopLimit) + } + } + if so, ok := sockOpts[ssoReceivePacketInfo]; ok && cf&flagPacketInfo != 0 { + if err := so.SetInt(c, boolint(on)); err != nil { + return err + } + if on { + opt.set(cf & flagPacketInfo) + } else { + opt.clear(cf & flagPacketInfo) + } + } + if so, ok := sockOpts[ssoReceivePathMTU]; ok && cf&FlagPathMTU != 0 { + if err := so.SetInt(c, boolint(on)); err != nil { + return err + } + if on { + opt.set(FlagPathMTU) + } else { + opt.clear(FlagPathMTU) + } + } + return nil +} diff --git a/vendor/golang.org/x/net/ipv6/control_windows.go b/vendor/golang.org/x/net/ipv6/control_windows.go new file mode 100644 index 0000000..8882d81 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/control_windows.go @@ -0,0 +1,12 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import "golang.org/x/net/internal/socket" + +func setControlMessage(c *socket.Conn, opt *rawOpt, cf ControlFlags, on bool) error { + // TODO(mikio): implement this + return errNotImplemented +} diff --git a/vendor/golang.org/x/net/ipv6/dgramopt.go b/vendor/golang.org/x/net/ipv6/dgramopt.go new file mode 100644 index 0000000..846f0e1 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/dgramopt.go @@ -0,0 +1,301 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import ( + "net" + + "golang.org/x/net/bpf" +) + +// MulticastHopLimit returns the hop limit field value for outgoing +// multicast packets. +func (c *dgramOpt) MulticastHopLimit() (int, error) { + if !c.ok() { + return 0, errInvalidConn + } + so, ok := sockOpts[ssoMulticastHopLimit] + if !ok { + return 0, errNotImplemented + } + return so.GetInt(c.Conn) +} + +// SetMulticastHopLimit sets the hop limit field value for future +// outgoing multicast packets. +func (c *dgramOpt) SetMulticastHopLimit(hoplim int) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoMulticastHopLimit] + if !ok { + return errNotImplemented + } + return so.SetInt(c.Conn, hoplim) +} + +// MulticastInterface returns the default interface for multicast +// packet transmissions. +func (c *dgramOpt) MulticastInterface() (*net.Interface, error) { + if !c.ok() { + return nil, errInvalidConn + } + so, ok := sockOpts[ssoMulticastInterface] + if !ok { + return nil, errNotImplemented + } + return so.getMulticastInterface(c.Conn) +} + +// SetMulticastInterface sets the default interface for future +// multicast packet transmissions. +func (c *dgramOpt) SetMulticastInterface(ifi *net.Interface) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoMulticastInterface] + if !ok { + return errNotImplemented + } + return so.setMulticastInterface(c.Conn, ifi) +} + +// MulticastLoopback reports whether transmitted multicast packets +// should be copied and send back to the originator. +func (c *dgramOpt) MulticastLoopback() (bool, error) { + if !c.ok() { + return false, errInvalidConn + } + so, ok := sockOpts[ssoMulticastLoopback] + if !ok { + return false, errNotImplemented + } + on, err := so.GetInt(c.Conn) + if err != nil { + return false, err + } + return on == 1, nil +} + +// SetMulticastLoopback sets whether transmitted multicast packets +// should be copied and send back to the originator. +func (c *dgramOpt) SetMulticastLoopback(on bool) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoMulticastLoopback] + if !ok { + return errNotImplemented + } + return so.SetInt(c.Conn, boolint(on)) +} + +// JoinGroup joins the group address group on the interface ifi. +// By default all sources that can cast data to group are accepted. +// It's possible to mute and unmute data transmission from a specific +// source by using ExcludeSourceSpecificGroup and +// IncludeSourceSpecificGroup. +// JoinGroup uses the system assigned multicast interface when ifi is +// nil, although this is not recommended because the assignment +// depends on platforms and sometimes it might require routing +// configuration. +func (c *dgramOpt) JoinGroup(ifi *net.Interface, group net.Addr) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoJoinGroup] + if !ok { + return errNotImplemented + } + grp := netAddrToIP16(group) + if grp == nil { + return errMissingAddress + } + return so.setGroup(c.Conn, ifi, grp) +} + +// LeaveGroup leaves the group address group on the interface ifi +// regardless of whether the group is any-source group or +// source-specific group. +func (c *dgramOpt) LeaveGroup(ifi *net.Interface, group net.Addr) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoLeaveGroup] + if !ok { + return errNotImplemented + } + grp := netAddrToIP16(group) + if grp == nil { + return errMissingAddress + } + return so.setGroup(c.Conn, ifi, grp) +} + +// JoinSourceSpecificGroup joins the source-specific group comprising +// group and source on the interface ifi. +// JoinSourceSpecificGroup uses the system assigned multicast +// interface when ifi is nil, although this is not recommended because +// the assignment depends on platforms and sometimes it might require +// routing configuration. +func (c *dgramOpt) JoinSourceSpecificGroup(ifi *net.Interface, group, source net.Addr) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoJoinSourceGroup] + if !ok { + return errNotImplemented + } + grp := netAddrToIP16(group) + if grp == nil { + return errMissingAddress + } + src := netAddrToIP16(source) + if src == nil { + return errMissingAddress + } + return so.setSourceGroup(c.Conn, ifi, grp, src) +} + +// LeaveSourceSpecificGroup leaves the source-specific group on the +// interface ifi. +func (c *dgramOpt) LeaveSourceSpecificGroup(ifi *net.Interface, group, source net.Addr) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoLeaveSourceGroup] + if !ok { + return errNotImplemented + } + grp := netAddrToIP16(group) + if grp == nil { + return errMissingAddress + } + src := netAddrToIP16(source) + if src == nil { + return errMissingAddress + } + return so.setSourceGroup(c.Conn, ifi, grp, src) +} + +// ExcludeSourceSpecificGroup excludes the source-specific group from +// the already joined any-source groups by JoinGroup on the interface +// ifi. +func (c *dgramOpt) ExcludeSourceSpecificGroup(ifi *net.Interface, group, source net.Addr) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoBlockSourceGroup] + if !ok { + return errNotImplemented + } + grp := netAddrToIP16(group) + if grp == nil { + return errMissingAddress + } + src := netAddrToIP16(source) + if src == nil { + return errMissingAddress + } + return so.setSourceGroup(c.Conn, ifi, grp, src) +} + +// IncludeSourceSpecificGroup includes the excluded source-specific +// group by ExcludeSourceSpecificGroup again on the interface ifi. +func (c *dgramOpt) IncludeSourceSpecificGroup(ifi *net.Interface, group, source net.Addr) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoUnblockSourceGroup] + if !ok { + return errNotImplemented + } + grp := netAddrToIP16(group) + if grp == nil { + return errMissingAddress + } + src := netAddrToIP16(source) + if src == nil { + return errMissingAddress + } + return so.setSourceGroup(c.Conn, ifi, grp, src) +} + +// Checksum reports whether the kernel will compute, store or verify a +// checksum for both incoming and outgoing packets. If on is true, it +// returns an offset in bytes into the data of where the checksum +// field is located. +func (c *dgramOpt) Checksum() (on bool, offset int, err error) { + if !c.ok() { + return false, 0, errInvalidConn + } + so, ok := sockOpts[ssoChecksum] + if !ok { + return false, 0, errNotImplemented + } + offset, err = so.GetInt(c.Conn) + if err != nil { + return false, 0, err + } + if offset < 0 { + return false, 0, nil + } + return true, offset, nil +} + +// SetChecksum enables the kernel checksum processing. If on is true, +// the offset should be an offset in bytes into the data of where the +// checksum field is located. +func (c *dgramOpt) SetChecksum(on bool, offset int) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoChecksum] + if !ok { + return errNotImplemented + } + if !on { + offset = -1 + } + return so.SetInt(c.Conn, offset) +} + +// ICMPFilter returns an ICMP filter. +func (c *dgramOpt) ICMPFilter() (*ICMPFilter, error) { + if !c.ok() { + return nil, errInvalidConn + } + so, ok := sockOpts[ssoICMPFilter] + if !ok { + return nil, errNotImplemented + } + return so.getICMPFilter(c.Conn) +} + +// SetICMPFilter deploys the ICMP filter. +func (c *dgramOpt) SetICMPFilter(f *ICMPFilter) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoICMPFilter] + if !ok { + return errNotImplemented + } + return so.setICMPFilter(c.Conn, f) +} + +// SetBPF attaches a BPF program to the connection. +// +// Only supported on Linux. +func (c *dgramOpt) SetBPF(filter []bpf.RawInstruction) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoAttachFilter] + if !ok { + return errNotImplemented + } + return so.setBPF(c.Conn, filter) +} diff --git a/vendor/golang.org/x/net/ipv6/doc.go b/vendor/golang.org/x/net/ipv6/doc.go new file mode 100644 index 0000000..2148b81 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/doc.go @@ -0,0 +1,239 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ipv6 implements IP-level socket options for the Internet +// Protocol version 6. +// +// The package provides IP-level socket options that allow +// manipulation of IPv6 facilities. +// +// The IPv6 protocol is defined in RFC 8200. +// Socket interface extensions are defined in RFC 3493, RFC 3542 and +// RFC 3678. +// MLDv1 and MLDv2 are defined in RFC 2710 and RFC 3810. +// Source-specific multicast is defined in RFC 4607. +// +// On Darwin, this package requires OS X Mavericks version 10.9 or +// above, or equivalent. +// +// # Unicasting +// +// The options for unicasting are available for net.TCPConn, +// net.UDPConn and net.IPConn which are created as network connections +// that use the IPv6 transport. When a single TCP connection carrying +// a data flow of multiple packets needs to indicate the flow is +// important, Conn is used to set the traffic class field on the IPv6 +// header for each packet. +// +// ln, err := net.Listen("tcp6", "[::]:1024") +// if err != nil { +// // error handling +// } +// defer ln.Close() +// for { +// c, err := ln.Accept() +// if err != nil { +// // error handling +// } +// go func(c net.Conn) { +// defer c.Close() +// +// The outgoing packets will be labeled DiffServ assured forwarding +// class 1 low drop precedence, known as AF11 packets. +// +// if err := ipv6.NewConn(c).SetTrafficClass(0x28); err != nil { +// // error handling +// } +// if _, err := c.Write(data); err != nil { +// // error handling +// } +// }(c) +// } +// +// # Multicasting +// +// The options for multicasting are available for net.UDPConn and +// net.IPConn which are created as network connections that use the +// IPv6 transport. A few network facilities must be prepared before +// you begin multicasting, at a minimum joining network interfaces and +// multicast groups. +// +// en0, err := net.InterfaceByName("en0") +// if err != nil { +// // error handling +// } +// en1, err := net.InterfaceByIndex(911) +// if err != nil { +// // error handling +// } +// group := net.ParseIP("ff02::114") +// +// First, an application listens to an appropriate address with an +// appropriate service port. +// +// c, err := net.ListenPacket("udp6", "[::]:1024") +// if err != nil { +// // error handling +// } +// defer c.Close() +// +// Second, the application joins multicast groups, starts listening to +// the groups on the specified network interfaces. Note that the +// service port for transport layer protocol does not matter with this +// operation as joining groups affects only network and link layer +// protocols, such as IPv6 and Ethernet. +// +// p := ipv6.NewPacketConn(c) +// if err := p.JoinGroup(en0, &net.UDPAddr{IP: group}); err != nil { +// // error handling +// } +// if err := p.JoinGroup(en1, &net.UDPAddr{IP: group}); err != nil { +// // error handling +// } +// +// The application might set per packet control message transmissions +// between the protocol stack within the kernel. When the application +// needs a destination address on an incoming packet, +// SetControlMessage of PacketConn is used to enable control message +// transmissions. +// +// if err := p.SetControlMessage(ipv6.FlagDst, true); err != nil { +// // error handling +// } +// +// The application could identify whether the received packets are +// of interest by using the control message that contains the +// destination address of the received packet. +// +// b := make([]byte, 1500) +// for { +// n, rcm, src, err := p.ReadFrom(b) +// if err != nil { +// // error handling +// } +// if rcm.Dst.IsMulticast() { +// if rcm.Dst.Equal(group) { +// // joined group, do something +// } else { +// // unknown group, discard +// continue +// } +// } +// +// The application can also send both unicast and multicast packets. +// +// p.SetTrafficClass(0x0) +// p.SetHopLimit(16) +// if _, err := p.WriteTo(data[:n], nil, src); err != nil { +// // error handling +// } +// dst := &net.UDPAddr{IP: group, Port: 1024} +// wcm := ipv6.ControlMessage{TrafficClass: 0xe0, HopLimit: 1} +// for _, ifi := range []*net.Interface{en0, en1} { +// wcm.IfIndex = ifi.Index +// if _, err := p.WriteTo(data[:n], &wcm, dst); err != nil { +// // error handling +// } +// } +// } +// +// # More multicasting +// +// An application that uses PacketConn may join multiple multicast +// groups. For example, a UDP listener with port 1024 might join two +// different groups across over two different network interfaces by +// using: +// +// c, err := net.ListenPacket("udp6", "[::]:1024") +// if err != nil { +// // error handling +// } +// defer c.Close() +// p := ipv6.NewPacketConn(c) +// if err := p.JoinGroup(en0, &net.UDPAddr{IP: net.ParseIP("ff02::1:114")}); err != nil { +// // error handling +// } +// if err := p.JoinGroup(en0, &net.UDPAddr{IP: net.ParseIP("ff02::2:114")}); err != nil { +// // error handling +// } +// if err := p.JoinGroup(en1, &net.UDPAddr{IP: net.ParseIP("ff02::2:114")}); err != nil { +// // error handling +// } +// +// It is possible for multiple UDP listeners that listen on the same +// UDP port to join the same multicast group. The net package will +// provide a socket that listens to a wildcard address with reusable +// UDP port when an appropriate multicast address prefix is passed to +// the net.ListenPacket or net.ListenUDP. +// +// c1, err := net.ListenPacket("udp6", "[ff02::]:1024") +// if err != nil { +// // error handling +// } +// defer c1.Close() +// c2, err := net.ListenPacket("udp6", "[ff02::]:1024") +// if err != nil { +// // error handling +// } +// defer c2.Close() +// p1 := ipv6.NewPacketConn(c1) +// if err := p1.JoinGroup(en0, &net.UDPAddr{IP: net.ParseIP("ff02::114")}); err != nil { +// // error handling +// } +// p2 := ipv6.NewPacketConn(c2) +// if err := p2.JoinGroup(en0, &net.UDPAddr{IP: net.ParseIP("ff02::114")}); err != nil { +// // error handling +// } +// +// Also it is possible for the application to leave or rejoin a +// multicast group on the network interface. +// +// if err := p.LeaveGroup(en0, &net.UDPAddr{IP: net.ParseIP("ff02::114")}); err != nil { +// // error handling +// } +// if err := p.JoinGroup(en0, &net.UDPAddr{IP: net.ParseIP("ff01::114")}); err != nil { +// // error handling +// } +// +// # Source-specific multicasting +// +// An application that uses PacketConn on MLDv2 supported platform is +// able to join source-specific multicast groups. +// The application may use JoinSourceSpecificGroup and +// LeaveSourceSpecificGroup for the operation known as "include" mode, +// +// ssmgroup := net.UDPAddr{IP: net.ParseIP("ff32::8000:9")} +// ssmsource := net.UDPAddr{IP: net.ParseIP("fe80::cafe")} +// if err := p.JoinSourceSpecificGroup(en0, &ssmgroup, &ssmsource); err != nil { +// // error handling +// } +// if err := p.LeaveSourceSpecificGroup(en0, &ssmgroup, &ssmsource); err != nil { +// // error handling +// } +// +// or JoinGroup, ExcludeSourceSpecificGroup, +// IncludeSourceSpecificGroup and LeaveGroup for the operation known +// as "exclude" mode. +// +// exclsource := net.UDPAddr{IP: net.ParseIP("fe80::dead")} +// if err := p.JoinGroup(en0, &ssmgroup); err != nil { +// // error handling +// } +// if err := p.ExcludeSourceSpecificGroup(en0, &ssmgroup, &exclsource); err != nil { +// // error handling +// } +// if err := p.LeaveGroup(en0, &ssmgroup); err != nil { +// // error handling +// } +// +// Note that it depends on each platform implementation what happens +// when an application which runs on MLDv2 unsupported platform uses +// JoinSourceSpecificGroup and LeaveSourceSpecificGroup. +// In general the platform tries to fall back to conversations using +// MLDv1 and starts to listen to multicast traffic. +// In the fallback case, ExcludeSourceSpecificGroup and +// IncludeSourceSpecificGroup may return an error. +package ipv6 // import "golang.org/x/net/ipv6" + +// BUG(mikio): This package is not implemented on JS, NaCl and Plan 9. diff --git a/vendor/golang.org/x/net/ipv6/endpoint.go b/vendor/golang.org/x/net/ipv6/endpoint.go new file mode 100644 index 0000000..f534a0b --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/endpoint.go @@ -0,0 +1,127 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import ( + "net" + "time" + + "golang.org/x/net/internal/socket" +) + +// BUG(mikio): On Windows, the JoinSourceSpecificGroup, +// LeaveSourceSpecificGroup, ExcludeSourceSpecificGroup and +// IncludeSourceSpecificGroup methods of PacketConn are not +// implemented. + +// A Conn represents a network endpoint that uses IPv6 transport. +// It allows to set basic IP-level socket options such as traffic +// class and hop limit. +type Conn struct { + genericOpt +} + +type genericOpt struct { + *socket.Conn +} + +func (c *genericOpt) ok() bool { return c != nil && c.Conn != nil } + +// PathMTU returns a path MTU value for the destination associated +// with the endpoint. +func (c *Conn) PathMTU() (int, error) { + if !c.ok() { + return 0, errInvalidConn + } + so, ok := sockOpts[ssoPathMTU] + if !ok { + return 0, errNotImplemented + } + _, mtu, err := so.getMTUInfo(c.Conn) + if err != nil { + return 0, err + } + return mtu, nil +} + +// NewConn returns a new Conn. +func NewConn(c net.Conn) *Conn { + cc, _ := socket.NewConn(c) + return &Conn{ + genericOpt: genericOpt{Conn: cc}, + } +} + +// A PacketConn represents a packet network endpoint that uses IPv6 +// transport. It is used to control several IP-level socket options +// including IPv6 header manipulation. It also provides datagram +// based network I/O methods specific to the IPv6 and higher layer +// protocols such as OSPF, GRE, and UDP. +type PacketConn struct { + genericOpt + dgramOpt + payloadHandler +} + +type dgramOpt struct { + *socket.Conn +} + +func (c *dgramOpt) ok() bool { return c != nil && c.Conn != nil } + +// SetControlMessage allows to receive the per packet basis IP-level +// socket options. +func (c *PacketConn) SetControlMessage(cf ControlFlags, on bool) error { + if !c.payloadHandler.ok() { + return errInvalidConn + } + return setControlMessage(c.dgramOpt.Conn, &c.payloadHandler.rawOpt, cf, on) +} + +// SetDeadline sets the read and write deadlines associated with the +// endpoint. +func (c *PacketConn) SetDeadline(t time.Time) error { + if !c.payloadHandler.ok() { + return errInvalidConn + } + return c.payloadHandler.SetDeadline(t) +} + +// SetReadDeadline sets the read deadline associated with the +// endpoint. +func (c *PacketConn) SetReadDeadline(t time.Time) error { + if !c.payloadHandler.ok() { + return errInvalidConn + } + return c.payloadHandler.SetReadDeadline(t) +} + +// SetWriteDeadline sets the write deadline associated with the +// endpoint. +func (c *PacketConn) SetWriteDeadline(t time.Time) error { + if !c.payloadHandler.ok() { + return errInvalidConn + } + return c.payloadHandler.SetWriteDeadline(t) +} + +// Close closes the endpoint. +func (c *PacketConn) Close() error { + if !c.payloadHandler.ok() { + return errInvalidConn + } + return c.payloadHandler.Close() +} + +// NewPacketConn returns a new PacketConn using c as its underlying +// transport. +func NewPacketConn(c net.PacketConn) *PacketConn { + cc, _ := socket.NewConn(c.(net.Conn)) + return &PacketConn{ + genericOpt: genericOpt{Conn: cc}, + dgramOpt: dgramOpt{Conn: cc}, + payloadHandler: payloadHandler{PacketConn: c, Conn: cc}, + } +} diff --git a/vendor/golang.org/x/net/ipv6/genericopt.go b/vendor/golang.org/x/net/ipv6/genericopt.go new file mode 100644 index 0000000..0326aed --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/genericopt.go @@ -0,0 +1,56 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +// TrafficClass returns the traffic class field value for outgoing +// packets. +func (c *genericOpt) TrafficClass() (int, error) { + if !c.ok() { + return 0, errInvalidConn + } + so, ok := sockOpts[ssoTrafficClass] + if !ok { + return 0, errNotImplemented + } + return so.GetInt(c.Conn) +} + +// SetTrafficClass sets the traffic class field value for future +// outgoing packets. +func (c *genericOpt) SetTrafficClass(tclass int) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoTrafficClass] + if !ok { + return errNotImplemented + } + return so.SetInt(c.Conn, tclass) +} + +// HopLimit returns the hop limit field value for outgoing packets. +func (c *genericOpt) HopLimit() (int, error) { + if !c.ok() { + return 0, errInvalidConn + } + so, ok := sockOpts[ssoHopLimit] + if !ok { + return 0, errNotImplemented + } + return so.GetInt(c.Conn) +} + +// SetHopLimit sets the hop limit field value for future outgoing +// packets. +func (c *genericOpt) SetHopLimit(hoplim int) error { + if !c.ok() { + return errInvalidConn + } + so, ok := sockOpts[ssoHopLimit] + if !ok { + return errNotImplemented + } + return so.SetInt(c.Conn, hoplim) +} diff --git a/vendor/golang.org/x/net/ipv6/header.go b/vendor/golang.org/x/net/ipv6/header.go new file mode 100644 index 0000000..e05cb08 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/header.go @@ -0,0 +1,55 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import ( + "encoding/binary" + "fmt" + "net" +) + +const ( + Version = 6 // protocol version + HeaderLen = 40 // header length +) + +// A Header represents an IPv6 base header. +type Header struct { + Version int // protocol version + TrafficClass int // traffic class + FlowLabel int // flow label + PayloadLen int // payload length + NextHeader int // next header + HopLimit int // hop limit + Src net.IP // source address + Dst net.IP // destination address +} + +func (h *Header) String() string { + if h == nil { + return "" + } + return fmt.Sprintf("ver=%d tclass=%#x flowlbl=%#x payloadlen=%d nxthdr=%d hoplim=%d src=%v dst=%v", h.Version, h.TrafficClass, h.FlowLabel, h.PayloadLen, h.NextHeader, h.HopLimit, h.Src, h.Dst) +} + +// ParseHeader parses b as an IPv6 base header. +func ParseHeader(b []byte) (*Header, error) { + if len(b) < HeaderLen { + return nil, errHeaderTooShort + } + h := &Header{ + Version: int(b[0]) >> 4, + TrafficClass: int(b[0]&0x0f)<<4 | int(b[1])>>4, + FlowLabel: int(b[1]&0x0f)<<16 | int(b[2])<<8 | int(b[3]), + PayloadLen: int(binary.BigEndian.Uint16(b[4:6])), + NextHeader: int(b[6]), + HopLimit: int(b[7]), + } + h.Src = make(net.IP, net.IPv6len) + copy(h.Src, b[8:24]) + h.Dst = make(net.IP, net.IPv6len) + copy(h.Dst, b[24:40]) + return h, nil +} diff --git a/vendor/golang.org/x/net/ipv6/helper.go b/vendor/golang.org/x/net/ipv6/helper.go new file mode 100644 index 0000000..c2d508f --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/helper.go @@ -0,0 +1,58 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import ( + "errors" + "net" + "runtime" +) + +var ( + errInvalidConn = errors.New("invalid connection") + errMissingAddress = errors.New("missing address") + errHeaderTooShort = errors.New("header too short") + errInvalidConnType = errors.New("invalid conn type") + errNotImplemented = errors.New("not implemented on " + runtime.GOOS + "/" + runtime.GOARCH) +) + +func boolint(b bool) int { + if b { + return 1 + } + return 0 +} + +func netAddrToIP16(a net.Addr) net.IP { + switch v := a.(type) { + case *net.UDPAddr: + if ip := v.IP.To16(); ip != nil && ip.To4() == nil { + return ip + } + case *net.IPAddr: + if ip := v.IP.To16(); ip != nil && ip.To4() == nil { + return ip + } + } + return nil +} + +func opAddr(a net.Addr) net.Addr { + switch a.(type) { + case *net.TCPAddr: + if a == nil { + return nil + } + case *net.UDPAddr: + if a == nil { + return nil + } + case *net.IPAddr: + if a == nil { + return nil + } + } + return a +} diff --git a/vendor/golang.org/x/net/ipv6/iana.go b/vendor/golang.org/x/net/ipv6/iana.go new file mode 100644 index 0000000..32db1aa --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/iana.go @@ -0,0 +1,86 @@ +// go generate gen.go +// Code generated by the command above; DO NOT EDIT. + +package ipv6 + +// Internet Control Message Protocol version 6 (ICMPv6) Parameters, Updated: 2018-03-09 +const ( + ICMPTypeDestinationUnreachable ICMPType = 1 // Destination Unreachable + ICMPTypePacketTooBig ICMPType = 2 // Packet Too Big + ICMPTypeTimeExceeded ICMPType = 3 // Time Exceeded + ICMPTypeParameterProblem ICMPType = 4 // Parameter Problem + ICMPTypeEchoRequest ICMPType = 128 // Echo Request + ICMPTypeEchoReply ICMPType = 129 // Echo Reply + ICMPTypeMulticastListenerQuery ICMPType = 130 // Multicast Listener Query + ICMPTypeMulticastListenerReport ICMPType = 131 // Multicast Listener Report + ICMPTypeMulticastListenerDone ICMPType = 132 // Multicast Listener Done + ICMPTypeRouterSolicitation ICMPType = 133 // Router Solicitation + ICMPTypeRouterAdvertisement ICMPType = 134 // Router Advertisement + ICMPTypeNeighborSolicitation ICMPType = 135 // Neighbor Solicitation + ICMPTypeNeighborAdvertisement ICMPType = 136 // Neighbor Advertisement + ICMPTypeRedirect ICMPType = 137 // Redirect Message + ICMPTypeRouterRenumbering ICMPType = 138 // Router Renumbering + ICMPTypeNodeInformationQuery ICMPType = 139 // ICMP Node Information Query + ICMPTypeNodeInformationResponse ICMPType = 140 // ICMP Node Information Response + ICMPTypeInverseNeighborDiscoverySolicitation ICMPType = 141 // Inverse Neighbor Discovery Solicitation Message + ICMPTypeInverseNeighborDiscoveryAdvertisement ICMPType = 142 // Inverse Neighbor Discovery Advertisement Message + ICMPTypeVersion2MulticastListenerReport ICMPType = 143 // Version 2 Multicast Listener Report + ICMPTypeHomeAgentAddressDiscoveryRequest ICMPType = 144 // Home Agent Address Discovery Request Message + ICMPTypeHomeAgentAddressDiscoveryReply ICMPType = 145 // Home Agent Address Discovery Reply Message + ICMPTypeMobilePrefixSolicitation ICMPType = 146 // Mobile Prefix Solicitation + ICMPTypeMobilePrefixAdvertisement ICMPType = 147 // Mobile Prefix Advertisement + ICMPTypeCertificationPathSolicitation ICMPType = 148 // Certification Path Solicitation Message + ICMPTypeCertificationPathAdvertisement ICMPType = 149 // Certification Path Advertisement Message + ICMPTypeMulticastRouterAdvertisement ICMPType = 151 // Multicast Router Advertisement + ICMPTypeMulticastRouterSolicitation ICMPType = 152 // Multicast Router Solicitation + ICMPTypeMulticastRouterTermination ICMPType = 153 // Multicast Router Termination + ICMPTypeFMIPv6 ICMPType = 154 // FMIPv6 Messages + ICMPTypeRPLControl ICMPType = 155 // RPL Control Message + ICMPTypeILNPv6LocatorUpdate ICMPType = 156 // ILNPv6 Locator Update Message + ICMPTypeDuplicateAddressRequest ICMPType = 157 // Duplicate Address Request + ICMPTypeDuplicateAddressConfirmation ICMPType = 158 // Duplicate Address Confirmation + ICMPTypeMPLControl ICMPType = 159 // MPL Control Message + ICMPTypeExtendedEchoRequest ICMPType = 160 // Extended Echo Request + ICMPTypeExtendedEchoReply ICMPType = 161 // Extended Echo Reply +) + +// Internet Control Message Protocol version 6 (ICMPv6) Parameters, Updated: 2018-03-09 +var icmpTypes = map[ICMPType]string{ + 1: "destination unreachable", + 2: "packet too big", + 3: "time exceeded", + 4: "parameter problem", + 128: "echo request", + 129: "echo reply", + 130: "multicast listener query", + 131: "multicast listener report", + 132: "multicast listener done", + 133: "router solicitation", + 134: "router advertisement", + 135: "neighbor solicitation", + 136: "neighbor advertisement", + 137: "redirect message", + 138: "router renumbering", + 139: "icmp node information query", + 140: "icmp node information response", + 141: "inverse neighbor discovery solicitation message", + 142: "inverse neighbor discovery advertisement message", + 143: "version 2 multicast listener report", + 144: "home agent address discovery request message", + 145: "home agent address discovery reply message", + 146: "mobile prefix solicitation", + 147: "mobile prefix advertisement", + 148: "certification path solicitation message", + 149: "certification path advertisement message", + 151: "multicast router advertisement", + 152: "multicast router solicitation", + 153: "multicast router termination", + 154: "fmipv6 messages", + 155: "rpl control message", + 156: "ilnpv6 locator update message", + 157: "duplicate address request", + 158: "duplicate address confirmation", + 159: "mpl control message", + 160: "extended echo request", + 161: "extended echo reply", +} diff --git a/vendor/golang.org/x/net/ipv6/icmp.go b/vendor/golang.org/x/net/ipv6/icmp.go new file mode 100644 index 0000000..b7f48e2 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/icmp.go @@ -0,0 +1,60 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import "golang.org/x/net/internal/iana" + +// BUG(mikio): On Windows, methods related to ICMPFilter are not +// implemented. + +// An ICMPType represents a type of ICMP message. +type ICMPType int + +func (typ ICMPType) String() string { + s, ok := icmpTypes[typ] + if !ok { + return "" + } + return s +} + +// Protocol returns the ICMPv6 protocol number. +func (typ ICMPType) Protocol() int { + return iana.ProtocolIPv6ICMP +} + +// An ICMPFilter represents an ICMP message filter for incoming +// packets. The filter belongs to a packet delivery path on a host and +// it cannot interact with forwarding packets or tunnel-outer packets. +// +// Note: RFC 8200 defines a reasonable role model. A node means a +// device that implements IP. A router means a node that forwards IP +// packets not explicitly addressed to itself, and a host means a node +// that is not a router. +type ICMPFilter struct { + icmpv6Filter +} + +// Accept accepts incoming ICMP packets including the type field value +// typ. +func (f *ICMPFilter) Accept(typ ICMPType) { + f.accept(typ) +} + +// Block blocks incoming ICMP packets including the type field value +// typ. +func (f *ICMPFilter) Block(typ ICMPType) { + f.block(typ) +} + +// SetAll sets the filter action to the filter. +func (f *ICMPFilter) SetAll(block bool) { + f.setAll(block) +} + +// WillBlock reports whether the ICMP type will be blocked. +func (f *ICMPFilter) WillBlock(typ ICMPType) bool { + return f.willBlock(typ) +} diff --git a/vendor/golang.org/x/net/ipv6/icmp_bsd.go b/vendor/golang.org/x/net/ipv6/icmp_bsd.go new file mode 100644 index 0000000..120bf87 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/icmp_bsd.go @@ -0,0 +1,30 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || dragonfly || freebsd || netbsd || openbsd +// +build aix darwin dragonfly freebsd netbsd openbsd + +package ipv6 + +func (f *icmpv6Filter) accept(typ ICMPType) { + f.Filt[typ>>5] |= 1 << (uint32(typ) & 31) +} + +func (f *icmpv6Filter) block(typ ICMPType) { + f.Filt[typ>>5] &^= 1 << (uint32(typ) & 31) +} + +func (f *icmpv6Filter) setAll(block bool) { + for i := range f.Filt { + if block { + f.Filt[i] = 0 + } else { + f.Filt[i] = 1<<32 - 1 + } + } +} + +func (f *icmpv6Filter) willBlock(typ ICMPType) bool { + return f.Filt[typ>>5]&(1<<(uint32(typ)&31)) == 0 +} diff --git a/vendor/golang.org/x/net/ipv6/icmp_linux.go b/vendor/golang.org/x/net/ipv6/icmp_linux.go new file mode 100644 index 0000000..647f6b4 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/icmp_linux.go @@ -0,0 +1,27 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +func (f *icmpv6Filter) accept(typ ICMPType) { + f.Data[typ>>5] &^= 1 << (uint32(typ) & 31) +} + +func (f *icmpv6Filter) block(typ ICMPType) { + f.Data[typ>>5] |= 1 << (uint32(typ) & 31) +} + +func (f *icmpv6Filter) setAll(block bool) { + for i := range f.Data { + if block { + f.Data[i] = 1<<32 - 1 + } else { + f.Data[i] = 0 + } + } +} + +func (f *icmpv6Filter) willBlock(typ ICMPType) bool { + return f.Data[typ>>5]&(1<<(uint32(typ)&31)) != 0 +} diff --git a/vendor/golang.org/x/net/ipv6/icmp_solaris.go b/vendor/golang.org/x/net/ipv6/icmp_solaris.go new file mode 100644 index 0000000..7c23bb1 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/icmp_solaris.go @@ -0,0 +1,27 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +func (f *icmpv6Filter) accept(typ ICMPType) { + f.X__icmp6_filt[typ>>5] |= 1 << (uint32(typ) & 31) +} + +func (f *icmpv6Filter) block(typ ICMPType) { + f.X__icmp6_filt[typ>>5] &^= 1 << (uint32(typ) & 31) +} + +func (f *icmpv6Filter) setAll(block bool) { + for i := range f.X__icmp6_filt { + if block { + f.X__icmp6_filt[i] = 0 + } else { + f.X__icmp6_filt[i] = 1<<32 - 1 + } + } +} + +func (f *icmpv6Filter) willBlock(typ ICMPType) bool { + return f.X__icmp6_filt[typ>>5]&(1<<(uint32(typ)&31)) == 0 +} diff --git a/vendor/golang.org/x/net/ipv6/icmp_stub.go b/vendor/golang.org/x/net/ipv6/icmp_stub.go new file mode 100644 index 0000000..d60136a --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/icmp_stub.go @@ -0,0 +1,24 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows && !zos +// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows,!zos + +package ipv6 + +type icmpv6Filter struct { +} + +func (f *icmpv6Filter) accept(typ ICMPType) { +} + +func (f *icmpv6Filter) block(typ ICMPType) { +} + +func (f *icmpv6Filter) setAll(block bool) { +} + +func (f *icmpv6Filter) willBlock(typ ICMPType) bool { + return false +} diff --git a/vendor/golang.org/x/net/ipv6/icmp_windows.go b/vendor/golang.org/x/net/ipv6/icmp_windows.go new file mode 100644 index 0000000..443cd07 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/icmp_windows.go @@ -0,0 +1,22 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +func (f *icmpv6Filter) accept(typ ICMPType) { + // TODO(mikio): implement this +} + +func (f *icmpv6Filter) block(typ ICMPType) { + // TODO(mikio): implement this +} + +func (f *icmpv6Filter) setAll(block bool) { + // TODO(mikio): implement this +} + +func (f *icmpv6Filter) willBlock(typ ICMPType) bool { + // TODO(mikio): implement this + return false +} diff --git a/vendor/golang.org/x/net/ipv6/icmp_zos.go b/vendor/golang.org/x/net/ipv6/icmp_zos.go new file mode 100644 index 0000000..ddf8f09 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/icmp_zos.go @@ -0,0 +1,29 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +func (f *icmpv6Filter) accept(typ ICMPType) { + f.Filt[typ>>5] |= 1 << (uint32(typ) & 31) + +} + +func (f *icmpv6Filter) block(typ ICMPType) { + f.Filt[typ>>5] &^= 1 << (uint32(typ) & 31) + +} + +func (f *icmpv6Filter) setAll(block bool) { + for i := range f.Filt { + if block { + f.Filt[i] = 0 + } else { + f.Filt[i] = 1<<32 - 1 + } + } +} + +func (f *icmpv6Filter) willBlock(typ ICMPType) bool { + return f.Filt[typ>>5]&(1<<(uint32(typ)&31)) == 0 +} diff --git a/vendor/golang.org/x/net/ipv6/payload.go b/vendor/golang.org/x/net/ipv6/payload.go new file mode 100644 index 0000000..a8197f1 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/payload.go @@ -0,0 +1,23 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import ( + "net" + + "golang.org/x/net/internal/socket" +) + +// BUG(mikio): On Windows, the ControlMessage for ReadFrom and WriteTo +// methods of PacketConn is not implemented. + +// A payloadHandler represents the IPv6 datagram payload handler. +type payloadHandler struct { + net.PacketConn + *socket.Conn + rawOpt +} + +func (c *payloadHandler) ok() bool { return c != nil && c.PacketConn != nil && c.Conn != nil } diff --git a/vendor/golang.org/x/net/ipv6/payload_cmsg.go b/vendor/golang.org/x/net/ipv6/payload_cmsg.go new file mode 100644 index 0000000..b0692e4 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/payload_cmsg.go @@ -0,0 +1,71 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos + +package ipv6 + +import ( + "net" + + "golang.org/x/net/internal/socket" +) + +// ReadFrom reads a payload of the received IPv6 datagram, from the +// endpoint c, copying the payload into b. It returns the number of +// bytes copied into b, the control message cm and the source address +// src of the received datagram. +func (c *payloadHandler) ReadFrom(b []byte) (n int, cm *ControlMessage, src net.Addr, err error) { + if !c.ok() { + return 0, nil, nil, errInvalidConn + } + c.rawOpt.RLock() + m := socket.Message{ + Buffers: [][]byte{b}, + OOB: NewControlMessage(c.rawOpt.cflags), + } + c.rawOpt.RUnlock() + switch c.PacketConn.(type) { + case *net.UDPConn: + if err := c.RecvMsg(&m, 0); err != nil { + return 0, nil, nil, &net.OpError{Op: "read", Net: c.PacketConn.LocalAddr().Network(), Source: c.PacketConn.LocalAddr(), Err: err} + } + case *net.IPConn: + if err := c.RecvMsg(&m, 0); err != nil { + return 0, nil, nil, &net.OpError{Op: "read", Net: c.PacketConn.LocalAddr().Network(), Source: c.PacketConn.LocalAddr(), Err: err} + } + default: + return 0, nil, nil, &net.OpError{Op: "read", Net: c.PacketConn.LocalAddr().Network(), Source: c.PacketConn.LocalAddr(), Err: errInvalidConnType} + } + if m.NN > 0 { + cm = new(ControlMessage) + if err := cm.Parse(m.OOB[:m.NN]); err != nil { + return 0, nil, nil, &net.OpError{Op: "read", Net: c.PacketConn.LocalAddr().Network(), Source: c.PacketConn.LocalAddr(), Err: err} + } + cm.Src = netAddrToIP16(m.Addr) + } + return m.N, cm, m.Addr, nil +} + +// WriteTo writes a payload of the IPv6 datagram, to the destination +// address dst through the endpoint c, copying the payload from b. It +// returns the number of bytes written. The control message cm allows +// the IPv6 header fields and the datagram path to be specified. The +// cm may be nil if control of the outgoing datagram is not required. +func (c *payloadHandler) WriteTo(b []byte, cm *ControlMessage, dst net.Addr) (n int, err error) { + if !c.ok() { + return 0, errInvalidConn + } + m := socket.Message{ + Buffers: [][]byte{b}, + OOB: cm.Marshal(), + Addr: dst, + } + err = c.SendMsg(&m, 0) + if err != nil { + err = &net.OpError{Op: "write", Net: c.PacketConn.LocalAddr().Network(), Source: c.PacketConn.LocalAddr(), Addr: opAddr(dst), Err: err} + } + return m.N, err +} diff --git a/vendor/golang.org/x/net/ipv6/payload_nocmsg.go b/vendor/golang.org/x/net/ipv6/payload_nocmsg.go new file mode 100644 index 0000000..cd0ff50 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/payload_nocmsg.go @@ -0,0 +1,39 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !zos +// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!zos + +package ipv6 + +import "net" + +// ReadFrom reads a payload of the received IPv6 datagram, from the +// endpoint c, copying the payload into b. It returns the number of +// bytes copied into b, the control message cm and the source address +// src of the received datagram. +func (c *payloadHandler) ReadFrom(b []byte) (n int, cm *ControlMessage, src net.Addr, err error) { + if !c.ok() { + return 0, nil, nil, errInvalidConn + } + if n, src, err = c.PacketConn.ReadFrom(b); err != nil { + return 0, nil, nil, err + } + return +} + +// WriteTo writes a payload of the IPv6 datagram, to the destination +// address dst through the endpoint c, copying the payload from b. It +// returns the number of bytes written. The control message cm allows +// the IPv6 header fields and the datagram path to be specified. The +// cm may be nil if control of the outgoing datagram is not required. +func (c *payloadHandler) WriteTo(b []byte, cm *ControlMessage, dst net.Addr) (n int, err error) { + if !c.ok() { + return 0, errInvalidConn + } + if dst == nil { + return 0, errMissingAddress + } + return c.PacketConn.WriteTo(b, dst) +} diff --git a/vendor/golang.org/x/net/ipv6/sockopt.go b/vendor/golang.org/x/net/ipv6/sockopt.go new file mode 100644 index 0000000..cc3907d --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sockopt.go @@ -0,0 +1,43 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import "golang.org/x/net/internal/socket" + +// Sticky socket options +const ( + ssoTrafficClass = iota // header field for unicast packet, RFC 3542 + ssoHopLimit // header field for unicast packet, RFC 3493 + ssoMulticastInterface // outbound interface for multicast packet, RFC 3493 + ssoMulticastHopLimit // header field for multicast packet, RFC 3493 + ssoMulticastLoopback // loopback for multicast packet, RFC 3493 + ssoReceiveTrafficClass // header field on received packet, RFC 3542 + ssoReceiveHopLimit // header field on received packet, RFC 2292 or 3542 + ssoReceivePacketInfo // incbound or outbound packet path, RFC 2292 or 3542 + ssoReceivePathMTU // path mtu, RFC 3542 + ssoPathMTU // path mtu, RFC 3542 + ssoChecksum // packet checksum, RFC 2292 or 3542 + ssoICMPFilter // icmp filter, RFC 2292 or 3542 + ssoJoinGroup // any-source multicast, RFC 3493 + ssoLeaveGroup // any-source multicast, RFC 3493 + ssoJoinSourceGroup // source-specific multicast + ssoLeaveSourceGroup // source-specific multicast + ssoBlockSourceGroup // any-source or source-specific multicast + ssoUnblockSourceGroup // any-source or source-specific multicast + ssoAttachFilter // attach BPF for filtering inbound traffic +) + +// Sticky socket option value types +const ( + ssoTypeIPMreq = iota + 1 + ssoTypeGroupReq + ssoTypeGroupSourceReq +) + +// A sockOpt represents a binding for sticky socket option. +type sockOpt struct { + socket.Option + typ int // hint for option value type; optional +} diff --git a/vendor/golang.org/x/net/ipv6/sockopt_posix.go b/vendor/golang.org/x/net/ipv6/sockopt_posix.go new file mode 100644 index 0000000..37c6287 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sockopt_posix.go @@ -0,0 +1,90 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows || zos +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris windows zos + +package ipv6 + +import ( + "net" + "runtime" + "unsafe" + + "golang.org/x/net/bpf" + "golang.org/x/net/internal/socket" +) + +func (so *sockOpt) getMulticastInterface(c *socket.Conn) (*net.Interface, error) { + n, err := so.GetInt(c) + if err != nil { + return nil, err + } + return net.InterfaceByIndex(n) +} + +func (so *sockOpt) setMulticastInterface(c *socket.Conn, ifi *net.Interface) error { + var n int + if ifi != nil { + n = ifi.Index + } + return so.SetInt(c, n) +} + +func (so *sockOpt) getICMPFilter(c *socket.Conn) (*ICMPFilter, error) { + b := make([]byte, so.Len) + n, err := so.Get(c, b) + if err != nil { + return nil, err + } + if n != sizeofICMPv6Filter { + return nil, errNotImplemented + } + return (*ICMPFilter)(unsafe.Pointer(&b[0])), nil +} + +func (so *sockOpt) setICMPFilter(c *socket.Conn, f *ICMPFilter) error { + b := (*[sizeofICMPv6Filter]byte)(unsafe.Pointer(f))[:sizeofICMPv6Filter] + return so.Set(c, b) +} + +func (so *sockOpt) getMTUInfo(c *socket.Conn) (*net.Interface, int, error) { + b := make([]byte, so.Len) + n, err := so.Get(c, b) + if err != nil { + return nil, 0, err + } + if n != sizeofIPv6Mtuinfo { + return nil, 0, errNotImplemented + } + mi := (*ipv6Mtuinfo)(unsafe.Pointer(&b[0])) + if mi.Addr.Scope_id == 0 || runtime.GOOS == "aix" { + // AIX kernel might return a wrong address. + return nil, int(mi.Mtu), nil + } + ifi, err := net.InterfaceByIndex(int(mi.Addr.Scope_id)) + if err != nil { + return nil, 0, err + } + return ifi, int(mi.Mtu), nil +} + +func (so *sockOpt) setGroup(c *socket.Conn, ifi *net.Interface, grp net.IP) error { + switch so.typ { + case ssoTypeIPMreq: + return so.setIPMreq(c, ifi, grp) + case ssoTypeGroupReq: + return so.setGroupReq(c, ifi, grp) + default: + return errNotImplemented + } +} + +func (so *sockOpt) setSourceGroup(c *socket.Conn, ifi *net.Interface, grp, src net.IP) error { + return so.setGroupSourceReq(c, ifi, grp, src) +} + +func (so *sockOpt) setBPF(c *socket.Conn, f []bpf.RawInstruction) error { + return so.setAttachFilter(c, f) +} diff --git a/vendor/golang.org/x/net/ipv6/sockopt_stub.go b/vendor/golang.org/x/net/ipv6/sockopt_stub.go new file mode 100644 index 0000000..32fd866 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sockopt_stub.go @@ -0,0 +1,47 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows && !zos +// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows,!zos + +package ipv6 + +import ( + "net" + + "golang.org/x/net/bpf" + "golang.org/x/net/internal/socket" +) + +func (so *sockOpt) getMulticastInterface(c *socket.Conn) (*net.Interface, error) { + return nil, errNotImplemented +} + +func (so *sockOpt) setMulticastInterface(c *socket.Conn, ifi *net.Interface) error { + return errNotImplemented +} + +func (so *sockOpt) getICMPFilter(c *socket.Conn) (*ICMPFilter, error) { + return nil, errNotImplemented +} + +func (so *sockOpt) setICMPFilter(c *socket.Conn, f *ICMPFilter) error { + return errNotImplemented +} + +func (so *sockOpt) getMTUInfo(c *socket.Conn) (*net.Interface, int, error) { + return nil, 0, errNotImplemented +} + +func (so *sockOpt) setGroup(c *socket.Conn, ifi *net.Interface, grp net.IP) error { + return errNotImplemented +} + +func (so *sockOpt) setSourceGroup(c *socket.Conn, ifi *net.Interface, grp, src net.IP) error { + return errNotImplemented +} + +func (so *sockOpt) setBPF(c *socket.Conn, f []bpf.RawInstruction) error { + return errNotImplemented +} diff --git a/vendor/golang.org/x/net/ipv6/sys_aix.go b/vendor/golang.org/x/net/ipv6/sys_aix.go new file mode 100644 index 0000000..a47182a --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_aix.go @@ -0,0 +1,80 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Added for go1.11 compatibility +//go:build aix +// +build aix + +package ipv6 + +import ( + "net" + "syscall" + "unsafe" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/internal/socket" + + "golang.org/x/sys/unix" +) + +var ( + ctlOpts = [ctlMax]ctlOpt{ + ctlTrafficClass: {unix.IPV6_TCLASS, 4, marshalTrafficClass, parseTrafficClass}, + ctlHopLimit: {unix.IPV6_HOPLIMIT, 4, marshalHopLimit, parseHopLimit}, + ctlPacketInfo: {unix.IPV6_PKTINFO, sizeofInet6Pktinfo, marshalPacketInfo, parsePacketInfo}, + ctlNextHop: {unix.IPV6_NEXTHOP, sizeofSockaddrInet6, marshalNextHop, parseNextHop}, + ctlPathMTU: {unix.IPV6_PATHMTU, sizeofIPv6Mtuinfo, marshalPathMTU, parsePathMTU}, + } + + sockOpts = map[int]*sockOpt{ + ssoTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_TCLASS, Len: 4}}, + ssoHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_UNICAST_HOPS, Len: 4}}, + ssoMulticastInterface: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_IF, Len: 4}}, + ssoMulticastHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_HOPS, Len: 4}}, + ssoMulticastLoopback: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_LOOP, Len: 4}}, + ssoReceiveTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVTCLASS, Len: 4}}, + ssoReceiveHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVHOPLIMIT, Len: 4}}, + ssoReceivePacketInfo: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPKTINFO, Len: 4}}, + ssoReceivePathMTU: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPATHMTU, Len: 4}}, + ssoPathMTU: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_PATHMTU, Len: sizeofIPv6Mtuinfo}}, + ssoChecksum: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_CHECKSUM, Len: 4}}, + ssoICMPFilter: {Option: socket.Option{Level: iana.ProtocolIPv6ICMP, Name: unix.ICMP6_FILTER, Len: sizeofICMPv6Filter}}, + ssoJoinGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_JOIN_GROUP, Len: sizeofIPv6Mreq}, typ: ssoTypeIPMreq}, + ssoLeaveGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_LEAVE_GROUP, Len: sizeofIPv6Mreq}, typ: ssoTypeIPMreq}, + } +) + +func (sa *sockaddrInet6) setSockaddr(ip net.IP, i int) { + sa.Len = sizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], ip) + sa.Scope_id = uint32(i) +} + +func (pi *inet6Pktinfo) setIfindex(i int) { + pi.Ifindex = int32(i) +} + +func (mreq *ipv6Mreq) setIfindex(i int) { + mreq.Interface = uint32(i) +} + +func (gr *groupReq) setGroup(grp net.IP) { + sa := (*sockaddrInet6)(unsafe.Pointer(uintptr(unsafe.Pointer(gr)) + 4)) + sa.Len = sizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], grp) +} + +func (gsr *groupSourceReq) setSourceGroup(grp, src net.IP) { + sa := (*sockaddrInet6)(unsafe.Pointer(uintptr(unsafe.Pointer(gsr)) + 4)) + sa.Len = sizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], grp) + sa = (*sockaddrInet6)(unsafe.Pointer(uintptr(unsafe.Pointer(gsr)) + 132)) + sa.Len = sizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], src) +} diff --git a/vendor/golang.org/x/net/ipv6/sys_asmreq.go b/vendor/golang.org/x/net/ipv6/sys_asmreq.go new file mode 100644 index 0000000..6ff9950 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_asmreq.go @@ -0,0 +1,25 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris windows + +package ipv6 + +import ( + "net" + "unsafe" + + "golang.org/x/net/internal/socket" +) + +func (so *sockOpt) setIPMreq(c *socket.Conn, ifi *net.Interface, grp net.IP) error { + var mreq ipv6Mreq + copy(mreq.Multiaddr[:], grp) + if ifi != nil { + mreq.setIfindex(ifi.Index) + } + b := (*[sizeofIPv6Mreq]byte)(unsafe.Pointer(&mreq))[:sizeofIPv6Mreq] + return so.Set(c, b) +} diff --git a/vendor/golang.org/x/net/ipv6/sys_asmreq_stub.go b/vendor/golang.org/x/net/ipv6/sys_asmreq_stub.go new file mode 100644 index 0000000..485290c --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_asmreq_stub.go @@ -0,0 +1,18 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows +// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows + +package ipv6 + +import ( + "net" + + "golang.org/x/net/internal/socket" +) + +func (so *sockOpt) setIPMreq(c *socket.Conn, ifi *net.Interface, grp net.IP) error { + return errNotImplemented +} diff --git a/vendor/golang.org/x/net/ipv6/sys_bpf.go b/vendor/golang.org/x/net/ipv6/sys_bpf.go new file mode 100644 index 0000000..b5661fb --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_bpf.go @@ -0,0 +1,25 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux +// +build linux + +package ipv6 + +import ( + "unsafe" + + "golang.org/x/net/bpf" + "golang.org/x/net/internal/socket" + "golang.org/x/sys/unix" +) + +func (so *sockOpt) setAttachFilter(c *socket.Conn, f []bpf.RawInstruction) error { + prog := unix.SockFprog{ + Len: uint16(len(f)), + Filter: (*unix.SockFilter)(unsafe.Pointer(&f[0])), + } + b := (*[unix.SizeofSockFprog]byte)(unsafe.Pointer(&prog))[:unix.SizeofSockFprog] + return so.Set(c, b) +} diff --git a/vendor/golang.org/x/net/ipv6/sys_bpf_stub.go b/vendor/golang.org/x/net/ipv6/sys_bpf_stub.go new file mode 100644 index 0000000..cb00661 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_bpf_stub.go @@ -0,0 +1,17 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !linux +// +build !linux + +package ipv6 + +import ( + "golang.org/x/net/bpf" + "golang.org/x/net/internal/socket" +) + +func (so *sockOpt) setAttachFilter(c *socket.Conn, f []bpf.RawInstruction) error { + return errNotImplemented +} diff --git a/vendor/golang.org/x/net/ipv6/sys_bsd.go b/vendor/golang.org/x/net/ipv6/sys_bsd.go new file mode 100644 index 0000000..bde41a6 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_bsd.go @@ -0,0 +1,60 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build dragonfly || netbsd || openbsd +// +build dragonfly netbsd openbsd + +package ipv6 + +import ( + "net" + "syscall" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/internal/socket" + + "golang.org/x/sys/unix" +) + +var ( + ctlOpts = [ctlMax]ctlOpt{ + ctlTrafficClass: {unix.IPV6_TCLASS, 4, marshalTrafficClass, parseTrafficClass}, + ctlHopLimit: {unix.IPV6_HOPLIMIT, 4, marshalHopLimit, parseHopLimit}, + ctlPacketInfo: {unix.IPV6_PKTINFO, sizeofInet6Pktinfo, marshalPacketInfo, parsePacketInfo}, + ctlNextHop: {unix.IPV6_NEXTHOP, sizeofSockaddrInet6, marshalNextHop, parseNextHop}, + ctlPathMTU: {unix.IPV6_PATHMTU, sizeofIPv6Mtuinfo, marshalPathMTU, parsePathMTU}, + } + + sockOpts = map[int]*sockOpt{ + ssoTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_TCLASS, Len: 4}}, + ssoHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_UNICAST_HOPS, Len: 4}}, + ssoMulticastInterface: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_IF, Len: 4}}, + ssoMulticastHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_HOPS, Len: 4}}, + ssoMulticastLoopback: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_LOOP, Len: 4}}, + ssoReceiveTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVTCLASS, Len: 4}}, + ssoReceiveHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVHOPLIMIT, Len: 4}}, + ssoReceivePacketInfo: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPKTINFO, Len: 4}}, + ssoReceivePathMTU: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPATHMTU, Len: 4}}, + ssoPathMTU: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_PATHMTU, Len: sizeofIPv6Mtuinfo}}, + ssoChecksum: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_CHECKSUM, Len: 4}}, + ssoICMPFilter: {Option: socket.Option{Level: iana.ProtocolIPv6ICMP, Name: unix.ICMP6_FILTER, Len: sizeofICMPv6Filter}}, + ssoJoinGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_JOIN_GROUP, Len: sizeofIPv6Mreq}, typ: ssoTypeIPMreq}, + ssoLeaveGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_LEAVE_GROUP, Len: sizeofIPv6Mreq}, typ: ssoTypeIPMreq}, + } +) + +func (sa *sockaddrInet6) setSockaddr(ip net.IP, i int) { + sa.Len = sizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], ip) + sa.Scope_id = uint32(i) +} + +func (pi *inet6Pktinfo) setIfindex(i int) { + pi.Ifindex = uint32(i) +} + +func (mreq *ipv6Mreq) setIfindex(i int) { + mreq.Interface = uint32(i) +} diff --git a/vendor/golang.org/x/net/ipv6/sys_darwin.go b/vendor/golang.org/x/net/ipv6/sys_darwin.go new file mode 100644 index 0000000..b80ec80 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_darwin.go @@ -0,0 +1,80 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import ( + "net" + "syscall" + "unsafe" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/internal/socket" + + "golang.org/x/sys/unix" +) + +var ( + ctlOpts = [ctlMax]ctlOpt{ + ctlTrafficClass: {unix.IPV6_TCLASS, 4, marshalTrafficClass, parseTrafficClass}, + ctlHopLimit: {unix.IPV6_HOPLIMIT, 4, marshalHopLimit, parseHopLimit}, + ctlPacketInfo: {unix.IPV6_PKTINFO, sizeofInet6Pktinfo, marshalPacketInfo, parsePacketInfo}, + ctlNextHop: {unix.IPV6_NEXTHOP, sizeofSockaddrInet6, marshalNextHop, parseNextHop}, + ctlPathMTU: {unix.IPV6_PATHMTU, sizeofIPv6Mtuinfo, marshalPathMTU, parsePathMTU}, + } + + sockOpts = map[int]*sockOpt{ + ssoHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_UNICAST_HOPS, Len: 4}}, + ssoMulticastInterface: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_IF, Len: 4}}, + ssoMulticastHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_HOPS, Len: 4}}, + ssoMulticastLoopback: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_LOOP, Len: 4}}, + ssoTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_TCLASS, Len: 4}}, + ssoReceiveTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVTCLASS, Len: 4}}, + ssoReceiveHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVHOPLIMIT, Len: 4}}, + ssoReceivePacketInfo: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPKTINFO, Len: 4}}, + ssoReceivePathMTU: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPATHMTU, Len: 4}}, + ssoPathMTU: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_PATHMTU, Len: sizeofIPv6Mtuinfo}}, + ssoChecksum: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_CHECKSUM, Len: 4}}, + ssoICMPFilter: {Option: socket.Option{Level: iana.ProtocolIPv6ICMP, Name: unix.ICMP6_FILTER, Len: sizeofICMPv6Filter}}, + ssoJoinGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_JOIN_GROUP, Len: sizeofGroupReq}, typ: ssoTypeGroupReq}, + ssoLeaveGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_LEAVE_GROUP, Len: sizeofGroupReq}, typ: ssoTypeGroupReq}, + ssoJoinSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_JOIN_SOURCE_GROUP, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoLeaveSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_LEAVE_SOURCE_GROUP, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoBlockSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_BLOCK_SOURCE, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoUnblockSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_UNBLOCK_SOURCE, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + } +) + +func (sa *sockaddrInet6) setSockaddr(ip net.IP, i int) { + sa.Len = sizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], ip) + sa.Scope_id = uint32(i) +} + +func (pi *inet6Pktinfo) setIfindex(i int) { + pi.Ifindex = uint32(i) +} + +func (mreq *ipv6Mreq) setIfindex(i int) { + mreq.Interface = uint32(i) +} + +func (gr *groupReq) setGroup(grp net.IP) { + sa := (*sockaddrInet6)(unsafe.Pointer(uintptr(unsafe.Pointer(gr)) + 4)) + sa.Len = sizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], grp) +} + +func (gsr *groupSourceReq) setSourceGroup(grp, src net.IP) { + sa := (*sockaddrInet6)(unsafe.Pointer(uintptr(unsafe.Pointer(gsr)) + 4)) + sa.Len = sizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], grp) + sa = (*sockaddrInet6)(unsafe.Pointer(uintptr(unsafe.Pointer(gsr)) + 132)) + sa.Len = sizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], src) +} diff --git a/vendor/golang.org/x/net/ipv6/sys_freebsd.go b/vendor/golang.org/x/net/ipv6/sys_freebsd.go new file mode 100644 index 0000000..6282cf9 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_freebsd.go @@ -0,0 +1,94 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import ( + "net" + "runtime" + "strings" + "syscall" + "unsafe" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/internal/socket" + + "golang.org/x/sys/unix" +) + +var ( + ctlOpts = [ctlMax]ctlOpt{ + ctlTrafficClass: {unix.IPV6_TCLASS, 4, marshalTrafficClass, parseTrafficClass}, + ctlHopLimit: {unix.IPV6_HOPLIMIT, 4, marshalHopLimit, parseHopLimit}, + ctlPacketInfo: {unix.IPV6_PKTINFO, sizeofInet6Pktinfo, marshalPacketInfo, parsePacketInfo}, + ctlNextHop: {unix.IPV6_NEXTHOP, sizeofSockaddrInet6, marshalNextHop, parseNextHop}, + ctlPathMTU: {unix.IPV6_PATHMTU, sizeofIPv6Mtuinfo, marshalPathMTU, parsePathMTU}, + } + + sockOpts = map[int]sockOpt{ + ssoTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_TCLASS, Len: 4}}, + ssoHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_UNICAST_HOPS, Len: 4}}, + ssoMulticastInterface: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_IF, Len: 4}}, + ssoMulticastHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_HOPS, Len: 4}}, + ssoMulticastLoopback: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_LOOP, Len: 4}}, + ssoReceiveTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVTCLASS, Len: 4}}, + ssoReceiveHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVHOPLIMIT, Len: 4}}, + ssoReceivePacketInfo: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPKTINFO, Len: 4}}, + ssoReceivePathMTU: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPATHMTU, Len: 4}}, + ssoPathMTU: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_PATHMTU, Len: sizeofIPv6Mtuinfo}}, + ssoChecksum: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_CHECKSUM, Len: 4}}, + ssoICMPFilter: {Option: socket.Option{Level: iana.ProtocolIPv6ICMP, Name: unix.ICMP6_FILTER, Len: sizeofICMPv6Filter}}, + ssoJoinGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_JOIN_GROUP, Len: sizeofGroupReq}, typ: ssoTypeGroupReq}, + ssoLeaveGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_LEAVE_GROUP, Len: sizeofGroupReq}, typ: ssoTypeGroupReq}, + ssoJoinSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_JOIN_SOURCE_GROUP, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoLeaveSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_LEAVE_SOURCE_GROUP, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoBlockSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_BLOCK_SOURCE, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoUnblockSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_UNBLOCK_SOURCE, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + } +) + +func init() { + if runtime.GOOS == "freebsd" && runtime.GOARCH == "386" { + archs, _ := syscall.Sysctl("kern.supported_archs") + for _, s := range strings.Fields(archs) { + if s == "amd64" { + compatFreeBSD32 = true + break + } + } + } +} + +func (sa *sockaddrInet6) setSockaddr(ip net.IP, i int) { + sa.Len = sizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], ip) + sa.Scope_id = uint32(i) +} + +func (pi *inet6Pktinfo) setIfindex(i int) { + pi.Ifindex = uint32(i) +} + +func (mreq *ipv6Mreq) setIfindex(i int) { + mreq.Interface = uint32(i) +} + +func (gr *groupReq) setGroup(grp net.IP) { + sa := (*sockaddrInet6)(unsafe.Pointer(&gr.Group)) + sa.Len = sizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], grp) +} + +func (gsr *groupSourceReq) setSourceGroup(grp, src net.IP) { + sa := (*sockaddrInet6)(unsafe.Pointer(&gsr.Group)) + sa.Len = sizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], grp) + sa = (*sockaddrInet6)(unsafe.Pointer(&gsr.Source)) + sa.Len = sizeofSockaddrInet6 + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], src) +} diff --git a/vendor/golang.org/x/net/ipv6/sys_linux.go b/vendor/golang.org/x/net/ipv6/sys_linux.go new file mode 100644 index 0000000..82e2121 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_linux.go @@ -0,0 +1,76 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import ( + "net" + "syscall" + "unsafe" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/internal/socket" + + "golang.org/x/sys/unix" +) + +var ( + ctlOpts = [ctlMax]ctlOpt{ + ctlTrafficClass: {unix.IPV6_TCLASS, 4, marshalTrafficClass, parseTrafficClass}, + ctlHopLimit: {unix.IPV6_HOPLIMIT, 4, marshalHopLimit, parseHopLimit}, + ctlPacketInfo: {unix.IPV6_PKTINFO, sizeofInet6Pktinfo, marshalPacketInfo, parsePacketInfo}, + ctlPathMTU: {unix.IPV6_PATHMTU, sizeofIPv6Mtuinfo, marshalPathMTU, parsePathMTU}, + } + + sockOpts = map[int]*sockOpt{ + ssoTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_TCLASS, Len: 4}}, + ssoHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_UNICAST_HOPS, Len: 4}}, + ssoMulticastInterface: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_IF, Len: 4}}, + ssoMulticastHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_HOPS, Len: 4}}, + ssoMulticastLoopback: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_LOOP, Len: 4}}, + ssoReceiveTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVTCLASS, Len: 4}}, + ssoReceiveHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVHOPLIMIT, Len: 4}}, + ssoReceivePacketInfo: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPKTINFO, Len: 4}}, + ssoReceivePathMTU: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPATHMTU, Len: 4}}, + ssoPathMTU: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_PATHMTU, Len: sizeofIPv6Mtuinfo}}, + ssoChecksum: {Option: socket.Option{Level: iana.ProtocolReserved, Name: unix.IPV6_CHECKSUM, Len: 4}}, + ssoICMPFilter: {Option: socket.Option{Level: iana.ProtocolIPv6ICMP, Name: unix.ICMPV6_FILTER, Len: sizeofICMPv6Filter}}, + ssoJoinGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_JOIN_GROUP, Len: sizeofGroupReq}, typ: ssoTypeGroupReq}, + ssoLeaveGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_LEAVE_GROUP, Len: sizeofGroupReq}, typ: ssoTypeGroupReq}, + ssoJoinSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_JOIN_SOURCE_GROUP, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoLeaveSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_LEAVE_SOURCE_GROUP, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoBlockSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_BLOCK_SOURCE, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoUnblockSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_UNBLOCK_SOURCE, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoAttachFilter: {Option: socket.Option{Level: unix.SOL_SOCKET, Name: unix.SO_ATTACH_FILTER, Len: unix.SizeofSockFprog}}, + } +) + +func (sa *sockaddrInet6) setSockaddr(ip net.IP, i int) { + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], ip) + sa.Scope_id = uint32(i) +} + +func (pi *inet6Pktinfo) setIfindex(i int) { + pi.Ifindex = int32(i) +} + +func (mreq *ipv6Mreq) setIfindex(i int) { + mreq.Ifindex = int32(i) +} + +func (gr *groupReq) setGroup(grp net.IP) { + sa := (*sockaddrInet6)(unsafe.Pointer(&gr.Group)) + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], grp) +} + +func (gsr *groupSourceReq) setSourceGroup(grp, src net.IP) { + sa := (*sockaddrInet6)(unsafe.Pointer(&gsr.Group)) + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], grp) + sa = (*sockaddrInet6)(unsafe.Pointer(&gsr.Source)) + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], src) +} diff --git a/vendor/golang.org/x/net/ipv6/sys_solaris.go b/vendor/golang.org/x/net/ipv6/sys_solaris.go new file mode 100644 index 0000000..1fc30ad --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_solaris.go @@ -0,0 +1,76 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import ( + "net" + "syscall" + "unsafe" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/internal/socket" + + "golang.org/x/sys/unix" +) + +var ( + ctlOpts = [ctlMax]ctlOpt{ + ctlTrafficClass: {unix.IPV6_TCLASS, 4, marshalTrafficClass, parseTrafficClass}, + ctlHopLimit: {unix.IPV6_HOPLIMIT, 4, marshalHopLimit, parseHopLimit}, + ctlPacketInfo: {unix.IPV6_PKTINFO, sizeofInet6Pktinfo, marshalPacketInfo, parsePacketInfo}, + ctlNextHop: {unix.IPV6_NEXTHOP, sizeofSockaddrInet6, marshalNextHop, parseNextHop}, + ctlPathMTU: {unix.IPV6_PATHMTU, sizeofIPv6Mtuinfo, marshalPathMTU, parsePathMTU}, + } + + sockOpts = map[int]*sockOpt{ + ssoTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_TCLASS, Len: 4}}, + ssoHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_UNICAST_HOPS, Len: 4}}, + ssoMulticastInterface: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_IF, Len: 4}}, + ssoMulticastHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_HOPS, Len: 4}}, + ssoMulticastLoopback: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_LOOP, Len: 4}}, + ssoReceiveTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVTCLASS, Len: 4}}, + ssoReceiveHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVHOPLIMIT, Len: 4}}, + ssoReceivePacketInfo: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPKTINFO, Len: 4}}, + ssoReceivePathMTU: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPATHMTU, Len: 4}}, + ssoPathMTU: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_PATHMTU, Len: sizeofIPv6Mtuinfo}}, + ssoChecksum: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_CHECKSUM, Len: 4}}, + ssoICMPFilter: {Option: socket.Option{Level: iana.ProtocolIPv6ICMP, Name: unix.ICMP6_FILTER, Len: sizeofICMPv6Filter}}, + ssoJoinGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_JOIN_GROUP, Len: sizeofGroupReq}, typ: ssoTypeGroupReq}, + ssoLeaveGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_LEAVE_GROUP, Len: sizeofGroupReq}, typ: ssoTypeGroupReq}, + ssoJoinSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_JOIN_SOURCE_GROUP, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoLeaveSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_LEAVE_SOURCE_GROUP, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoBlockSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_BLOCK_SOURCE, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoUnblockSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_UNBLOCK_SOURCE, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + } +) + +func (sa *sockaddrInet6) setSockaddr(ip net.IP, i int) { + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], ip) + sa.Scope_id = uint32(i) +} + +func (pi *inet6Pktinfo) setIfindex(i int) { + pi.Ifindex = uint32(i) +} + +func (mreq *ipv6Mreq) setIfindex(i int) { + mreq.Interface = uint32(i) +} + +func (gr *groupReq) setGroup(grp net.IP) { + sa := (*sockaddrInet6)(unsafe.Pointer(uintptr(unsafe.Pointer(gr)) + 4)) + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], grp) +} + +func (gsr *groupSourceReq) setSourceGroup(grp, src net.IP) { + sa := (*sockaddrInet6)(unsafe.Pointer(uintptr(unsafe.Pointer(gsr)) + 4)) + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], grp) + sa = (*sockaddrInet6)(unsafe.Pointer(uintptr(unsafe.Pointer(gsr)) + 260)) + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], src) +} diff --git a/vendor/golang.org/x/net/ipv6/sys_ssmreq.go b/vendor/golang.org/x/net/ipv6/sys_ssmreq.go new file mode 100644 index 0000000..023488a --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_ssmreq.go @@ -0,0 +1,55 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || freebsd || linux || solaris || zos +// +build aix darwin freebsd linux solaris zos + +package ipv6 + +import ( + "net" + "unsafe" + + "golang.org/x/net/internal/socket" +) + +var compatFreeBSD32 bool // 386 emulation on amd64 + +func (so *sockOpt) setGroupReq(c *socket.Conn, ifi *net.Interface, grp net.IP) error { + var gr groupReq + if ifi != nil { + gr.Interface = uint32(ifi.Index) + } + gr.setGroup(grp) + var b []byte + if compatFreeBSD32 { + var d [sizeofGroupReq + 4]byte + s := (*[sizeofGroupReq]byte)(unsafe.Pointer(&gr)) + copy(d[:4], s[:4]) + copy(d[8:], s[4:]) + b = d[:] + } else { + b = (*[sizeofGroupReq]byte)(unsafe.Pointer(&gr))[:sizeofGroupReq] + } + return so.Set(c, b) +} + +func (so *sockOpt) setGroupSourceReq(c *socket.Conn, ifi *net.Interface, grp, src net.IP) error { + var gsr groupSourceReq + if ifi != nil { + gsr.Interface = uint32(ifi.Index) + } + gsr.setSourceGroup(grp, src) + var b []byte + if compatFreeBSD32 { + var d [sizeofGroupSourceReq + 4]byte + s := (*[sizeofGroupSourceReq]byte)(unsafe.Pointer(&gsr)) + copy(d[:4], s[:4]) + copy(d[8:], s[4:]) + b = d[:] + } else { + b = (*[sizeofGroupSourceReq]byte)(unsafe.Pointer(&gsr))[:sizeofGroupSourceReq] + } + return so.Set(c, b) +} diff --git a/vendor/golang.org/x/net/ipv6/sys_ssmreq_stub.go b/vendor/golang.org/x/net/ipv6/sys_ssmreq_stub.go new file mode 100644 index 0000000..acdf2e5 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_ssmreq_stub.go @@ -0,0 +1,22 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !aix && !darwin && !freebsd && !linux && !solaris && !zos +// +build !aix,!darwin,!freebsd,!linux,!solaris,!zos + +package ipv6 + +import ( + "net" + + "golang.org/x/net/internal/socket" +) + +func (so *sockOpt) setGroupReq(c *socket.Conn, ifi *net.Interface, grp net.IP) error { + return errNotImplemented +} + +func (so *sockOpt) setGroupSourceReq(c *socket.Conn, ifi *net.Interface, grp, src net.IP) error { + return errNotImplemented +} diff --git a/vendor/golang.org/x/net/ipv6/sys_stub.go b/vendor/golang.org/x/net/ipv6/sys_stub.go new file mode 100644 index 0000000..5807bba --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_stub.go @@ -0,0 +1,14 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows && !zos +// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows,!zos + +package ipv6 + +var ( + ctlOpts = [ctlMax]ctlOpt{} + + sockOpts = map[int]*sockOpt{} +) diff --git a/vendor/golang.org/x/net/ipv6/sys_windows.go b/vendor/golang.org/x/net/ipv6/sys_windows.go new file mode 100644 index 0000000..fda8a29 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_windows.go @@ -0,0 +1,68 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import ( + "net" + "syscall" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/internal/socket" + + "golang.org/x/sys/windows" +) + +const ( + sizeofSockaddrInet6 = 0x1c + + sizeofIPv6Mreq = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofICMPv6Filter = 0 +) + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Interface uint32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type icmpv6Filter struct { + // TODO(mikio): implement this +} + +var ( + ctlOpts = [ctlMax]ctlOpt{} + + sockOpts = map[int]*sockOpt{ + ssoHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: windows.IPV6_UNICAST_HOPS, Len: 4}}, + ssoMulticastInterface: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: windows.IPV6_MULTICAST_IF, Len: 4}}, + ssoMulticastHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: windows.IPV6_MULTICAST_HOPS, Len: 4}}, + ssoMulticastLoopback: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: windows.IPV6_MULTICAST_LOOP, Len: 4}}, + ssoJoinGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: windows.IPV6_JOIN_GROUP, Len: sizeofIPv6Mreq}, typ: ssoTypeIPMreq}, + ssoLeaveGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: windows.IPV6_LEAVE_GROUP, Len: sizeofIPv6Mreq}, typ: ssoTypeIPMreq}, + } +) + +func (sa *sockaddrInet6) setSockaddr(ip net.IP, i int) { + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], ip) + sa.Scope_id = uint32(i) +} + +func (mreq *ipv6Mreq) setIfindex(i int) { + mreq.Interface = uint32(i) +} diff --git a/vendor/golang.org/x/net/ipv6/sys_zos.go b/vendor/golang.org/x/net/ipv6/sys_zos.go new file mode 100644 index 0000000..31adc86 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/sys_zos.go @@ -0,0 +1,72 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ipv6 + +import ( + "net" + "syscall" + "unsafe" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/internal/socket" + + "golang.org/x/sys/unix" +) + +var ( + ctlOpts = [ctlMax]ctlOpt{ + ctlHopLimit: {unix.IPV6_HOPLIMIT, 4, marshalHopLimit, parseHopLimit}, + ctlPacketInfo: {unix.IPV6_PKTINFO, sizeofInet6Pktinfo, marshalPacketInfo, parsePacketInfo}, + ctlPathMTU: {unix.IPV6_PATHMTU, sizeofIPv6Mtuinfo, marshalPathMTU, parsePathMTU}, + } + + sockOpts = map[int]*sockOpt{ + ssoTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_TCLASS, Len: 4}}, + ssoHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_UNICAST_HOPS, Len: 4}}, + ssoMulticastInterface: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_IF, Len: 4}}, + ssoMulticastHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_HOPS, Len: 4}}, + ssoMulticastLoopback: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_MULTICAST_LOOP, Len: 4}}, + ssoReceiveTrafficClass: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVTCLASS, Len: 4}}, + ssoReceiveHopLimit: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVHOPLIMIT, Len: 4}}, + ssoReceivePacketInfo: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPKTINFO, Len: 4}}, + ssoReceivePathMTU: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_RECVPATHMTU, Len: 4}}, + ssoChecksum: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.IPV6_CHECKSUM, Len: 4}}, + ssoICMPFilter: {Option: socket.Option{Level: iana.ProtocolIPv6ICMP, Name: unix.ICMP6_FILTER, Len: sizeofICMPv6Filter}}, + ssoJoinGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_JOIN_GROUP, Len: sizeofGroupReq}, typ: ssoTypeGroupReq}, + ssoLeaveGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_LEAVE_GROUP, Len: sizeofGroupReq}, typ: ssoTypeGroupReq}, + ssoJoinSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_JOIN_SOURCE_GROUP, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoLeaveSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_LEAVE_SOURCE_GROUP, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoBlockSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_BLOCK_SOURCE, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + ssoUnblockSourceGroup: {Option: socket.Option{Level: iana.ProtocolIPv6, Name: unix.MCAST_UNBLOCK_SOURCE, Len: sizeofGroupSourceReq}, typ: ssoTypeGroupSourceReq}, + } +) + +func (sa *sockaddrInet6) setSockaddr(ip net.IP, i int) { + sa.Family = syscall.AF_INET6 + copy(sa.Addr[:], ip) + sa.Scope_id = uint32(i) +} + +func (pi *inet6Pktinfo) setIfindex(i int) { + pi.Ifindex = uint32(i) +} + +func (gr *groupReq) setGroup(grp net.IP) { + sa := (*sockaddrInet6)(unsafe.Pointer(&gr.Group)) + sa.Family = syscall.AF_INET6 + sa.Len = sizeofSockaddrInet6 + copy(sa.Addr[:], grp) +} + +func (gsr *groupSourceReq) setSourceGroup(grp, src net.IP) { + sa := (*sockaddrInet6)(unsafe.Pointer(&gsr.Group)) + sa.Family = syscall.AF_INET6 + sa.Len = sizeofSockaddrInet6 + copy(sa.Addr[:], grp) + sa = (*sockaddrInet6)(unsafe.Pointer(&gsr.Source)) + sa.Family = syscall.AF_INET6 + sa.Len = sizeofSockaddrInet6 + copy(sa.Addr[:], src) +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_aix_ppc64.go b/vendor/golang.org/x/net/ipv6/zsys_aix_ppc64.go new file mode 100644 index 0000000..f604b0f --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_aix_ppc64.go @@ -0,0 +1,69 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_aix.go + +// Added for go1.11 compatibility +//go:build aix +// +build aix + +package ipv6 + +const ( + sizeofSockaddrStorage = 0x508 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x510 + sizeofGroupSourceReq = 0xa18 + + sizeofICMPv6Filter = 0x20 +) + +type sockaddrStorage struct { + X__ss_len uint8 + Family uint8 + X__ss_pad1 [6]uint8 + X__ss_align int64 + X__ss_pad2 [1265]uint8 + Pad_cgo_0 [7]byte +} + +type sockaddrInet6 struct { + Len uint8 + Family uint8 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Interface uint32 +} + +type icmpv6Filter struct { + Filt [8]uint32 +} + +type groupReq struct { + Interface uint32 + Group sockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Group sockaddrStorage + Source sockaddrStorage +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_darwin.go b/vendor/golang.org/x/net/ipv6/zsys_darwin.go new file mode 100644 index 0000000..dd6f7b2 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_darwin.go @@ -0,0 +1,64 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_darwin.go + +package ipv6 + +const ( + sizeofSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x84 + sizeofGroupSourceReq = 0x104 + + sizeofICMPv6Filter = 0x20 +) + +type sockaddrStorage struct { + Len uint8 + Family uint8 + X__ss_pad1 [6]int8 + X__ss_align int64 + X__ss_pad2 [112]int8 +} + +type sockaddrInet6 struct { + Len uint8 + Family uint8 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex uint32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Interface uint32 +} + +type icmpv6Filter struct { + Filt [8]uint32 +} + +type groupReq struct { + Interface uint32 + Pad_cgo_0 [128]byte +} + +type groupSourceReq struct { + Interface uint32 + Pad_cgo_0 [128]byte + Pad_cgo_1 [128]byte +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_dragonfly.go b/vendor/golang.org/x/net/ipv6/zsys_dragonfly.go new file mode 100644 index 0000000..6b45a94 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_dragonfly.go @@ -0,0 +1,42 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_dragonfly.go + +package ipv6 + +const ( + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + + sizeofIPv6Mreq = 0x14 + + sizeofICMPv6Filter = 0x20 +) + +type sockaddrInet6 struct { + Len uint8 + Family uint8 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex uint32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Interface uint32 +} + +type icmpv6Filter struct { + Filt [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_freebsd_386.go b/vendor/golang.org/x/net/ipv6/zsys_freebsd_386.go new file mode 100644 index 0000000..8da5592 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_freebsd_386.go @@ -0,0 +1,64 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_freebsd.go + +package ipv6 + +const ( + sizeofSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x84 + sizeofGroupSourceReq = 0x104 + + sizeofICMPv6Filter = 0x20 +) + +type sockaddrStorage struct { + Len uint8 + Family uint8 + X__ss_pad1 [6]int8 + X__ss_align int64 + X__ss_pad2 [112]int8 +} + +type sockaddrInet6 struct { + Len uint8 + Family uint8 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex uint32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Interface uint32 +} + +type groupReq struct { + Interface uint32 + Group sockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Group sockaddrStorage + Source sockaddrStorage +} + +type icmpv6Filter struct { + Filt [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_freebsd_amd64.go b/vendor/golang.org/x/net/ipv6/zsys_freebsd_amd64.go new file mode 100644 index 0000000..72a1a65 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_freebsd_amd64.go @@ -0,0 +1,66 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_freebsd.go + +package ipv6 + +const ( + sizeofSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x88 + sizeofGroupSourceReq = 0x108 + + sizeofICMPv6Filter = 0x20 +) + +type sockaddrStorage struct { + Len uint8 + Family uint8 + X__ss_pad1 [6]int8 + X__ss_align int64 + X__ss_pad2 [112]int8 +} + +type sockaddrInet6 struct { + Len uint8 + Family uint8 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex uint32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Interface uint32 +} + +type groupReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group sockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group sockaddrStorage + Source sockaddrStorage +} + +type icmpv6Filter struct { + Filt [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_freebsd_arm.go b/vendor/golang.org/x/net/ipv6/zsys_freebsd_arm.go new file mode 100644 index 0000000..72a1a65 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_freebsd_arm.go @@ -0,0 +1,66 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_freebsd.go + +package ipv6 + +const ( + sizeofSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x88 + sizeofGroupSourceReq = 0x108 + + sizeofICMPv6Filter = 0x20 +) + +type sockaddrStorage struct { + Len uint8 + Family uint8 + X__ss_pad1 [6]int8 + X__ss_align int64 + X__ss_pad2 [112]int8 +} + +type sockaddrInet6 struct { + Len uint8 + Family uint8 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex uint32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Interface uint32 +} + +type groupReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group sockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group sockaddrStorage + Source sockaddrStorage +} + +type icmpv6Filter struct { + Filt [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_freebsd_arm64.go b/vendor/golang.org/x/net/ipv6/zsys_freebsd_arm64.go new file mode 100644 index 0000000..5b39eb8 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_freebsd_arm64.go @@ -0,0 +1,64 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_freebsd.go + +package ipv6 + +const ( + sizeofSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x88 + sizeofGroupSourceReq = 0x108 + + sizeofICMPv6Filter = 0x20 +) + +type sockaddrStorage struct { + Len uint8 + Family uint8 + X__ss_pad1 [6]uint8 + X__ss_align int64 + X__ss_pad2 [112]uint8 +} + +type sockaddrInet6 struct { + Len uint8 + Family uint8 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex uint32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Interface uint32 +} + +type groupReq struct { + Interface uint32 + Group sockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Group sockaddrStorage + Source sockaddrStorage +} + +type icmpv6Filter struct { + Filt [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_freebsd_riscv64.go b/vendor/golang.org/x/net/ipv6/zsys_freebsd_riscv64.go new file mode 100644 index 0000000..5b39eb8 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_freebsd_riscv64.go @@ -0,0 +1,64 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_freebsd.go + +package ipv6 + +const ( + sizeofSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x88 + sizeofGroupSourceReq = 0x108 + + sizeofICMPv6Filter = 0x20 +) + +type sockaddrStorage struct { + Len uint8 + Family uint8 + X__ss_pad1 [6]uint8 + X__ss_align int64 + X__ss_pad2 [112]uint8 +} + +type sockaddrInet6 struct { + Len uint8 + Family uint8 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex uint32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Interface uint32 +} + +type groupReq struct { + Interface uint32 + Group sockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Group sockaddrStorage + Source sockaddrStorage +} + +type icmpv6Filter struct { + Filt [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_386.go b/vendor/golang.org/x/net/ipv6/zsys_linux_386.go new file mode 100644 index 0000000..ad71871 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_386.go @@ -0,0 +1,72 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x84 + sizeofGroupSourceReq = 0x104 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_amd64.go b/vendor/golang.org/x/net/ipv6/zsys_linux_amd64.go new file mode 100644 index 0000000..2514ab9 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_amd64.go @@ -0,0 +1,74 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x88 + sizeofGroupSourceReq = 0x108 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_arm.go b/vendor/golang.org/x/net/ipv6/zsys_linux_arm.go new file mode 100644 index 0000000..ad71871 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_arm.go @@ -0,0 +1,72 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x84 + sizeofGroupSourceReq = 0x104 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_arm64.go b/vendor/golang.org/x/net/ipv6/zsys_linux_arm64.go new file mode 100644 index 0000000..2514ab9 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_arm64.go @@ -0,0 +1,74 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x88 + sizeofGroupSourceReq = 0x108 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_loong64.go b/vendor/golang.org/x/net/ipv6/zsys_linux_loong64.go new file mode 100644 index 0000000..598fbfa --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_loong64.go @@ -0,0 +1,77 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +//go:build loong64 +// +build loong64 + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x88 + sizeofGroupSourceReq = 0x108 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_mips.go b/vendor/golang.org/x/net/ipv6/zsys_linux_mips.go new file mode 100644 index 0000000..ad71871 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_mips.go @@ -0,0 +1,72 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x84 + sizeofGroupSourceReq = 0x104 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_mips64.go b/vendor/golang.org/x/net/ipv6/zsys_linux_mips64.go new file mode 100644 index 0000000..2514ab9 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_mips64.go @@ -0,0 +1,74 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x88 + sizeofGroupSourceReq = 0x108 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_mips64le.go b/vendor/golang.org/x/net/ipv6/zsys_linux_mips64le.go new file mode 100644 index 0000000..2514ab9 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_mips64le.go @@ -0,0 +1,74 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x88 + sizeofGroupSourceReq = 0x108 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_mipsle.go b/vendor/golang.org/x/net/ipv6/zsys_linux_mipsle.go new file mode 100644 index 0000000..ad71871 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_mipsle.go @@ -0,0 +1,72 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x84 + sizeofGroupSourceReq = 0x104 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_ppc.go b/vendor/golang.org/x/net/ipv6/zsys_linux_ppc.go new file mode 100644 index 0000000..d06c2ad --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_ppc.go @@ -0,0 +1,72 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x84 + sizeofGroupSourceReq = 0x104 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]uint8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_ppc64.go b/vendor/golang.org/x/net/ipv6/zsys_linux_ppc64.go new file mode 100644 index 0000000..2514ab9 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_ppc64.go @@ -0,0 +1,74 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x88 + sizeofGroupSourceReq = 0x108 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_ppc64le.go b/vendor/golang.org/x/net/ipv6/zsys_linux_ppc64le.go new file mode 100644 index 0000000..2514ab9 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_ppc64le.go @@ -0,0 +1,74 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x88 + sizeofGroupSourceReq = 0x108 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_riscv64.go b/vendor/golang.org/x/net/ipv6/zsys_linux_riscv64.go new file mode 100644 index 0000000..d4f78e4 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_riscv64.go @@ -0,0 +1,77 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +//go:build riscv64 +// +build riscv64 + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x88 + sizeofGroupSourceReq = 0x108 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_linux_s390x.go b/vendor/golang.org/x/net/ipv6/zsys_linux_s390x.go new file mode 100644 index 0000000..2514ab9 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_linux_s390x.go @@ -0,0 +1,74 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_linux.go + +package ipv6 + +const ( + sizeofKernelSockaddrStorage = 0x80 + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + sizeofIPv6FlowlabelReq = 0x20 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x88 + sizeofGroupSourceReq = 0x108 + + sizeofICMPv6Filter = 0x20 +) + +type kernelSockaddrStorage struct { + Family uint16 + X__data [126]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex int32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6FlowlabelReq struct { + Dst [16]byte /* in6_addr */ + Label uint32 + Action uint8 + Share uint8 + Flags uint16 + Expires uint16 + Linger uint16 + X__flr_pad uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Ifindex int32 +} + +type groupReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + Pad_cgo_0 [4]byte + Group kernelSockaddrStorage + Source kernelSockaddrStorage +} + +type icmpv6Filter struct { + Data [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_netbsd.go b/vendor/golang.org/x/net/ipv6/zsys_netbsd.go new file mode 100644 index 0000000..f7335d5 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_netbsd.go @@ -0,0 +1,42 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_netbsd.go + +package ipv6 + +const ( + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + + sizeofIPv6Mreq = 0x14 + + sizeofICMPv6Filter = 0x20 +) + +type sockaddrInet6 struct { + Len uint8 + Family uint8 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex uint32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Interface uint32 +} + +type icmpv6Filter struct { + Filt [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_openbsd.go b/vendor/golang.org/x/net/ipv6/zsys_openbsd.go new file mode 100644 index 0000000..6d15928 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_openbsd.go @@ -0,0 +1,42 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_openbsd.go + +package ipv6 + +const ( + sizeofSockaddrInet6 = 0x1c + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x20 + + sizeofIPv6Mreq = 0x14 + + sizeofICMPv6Filter = 0x20 +) + +type sockaddrInet6 struct { + Len uint8 + Family uint8 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex uint32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Interface uint32 +} + +type icmpv6Filter struct { + Filt [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_solaris.go b/vendor/golang.org/x/net/ipv6/zsys_solaris.go new file mode 100644 index 0000000..1716197 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_solaris.go @@ -0,0 +1,63 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs defs_solaris.go + +package ipv6 + +const ( + sizeofSockaddrStorage = 0x100 + sizeofSockaddrInet6 = 0x20 + sizeofInet6Pktinfo = 0x14 + sizeofIPv6Mtuinfo = 0x24 + + sizeofIPv6Mreq = 0x14 + sizeofGroupReq = 0x104 + sizeofGroupSourceReq = 0x204 + + sizeofICMPv6Filter = 0x20 +) + +type sockaddrStorage struct { + Family uint16 + X_ss_pad1 [6]int8 + X_ss_align float64 + X_ss_pad2 [240]int8 +} + +type sockaddrInet6 struct { + Family uint16 + Port uint16 + Flowinfo uint32 + Addr [16]byte /* in6_addr */ + Scope_id uint32 + X__sin6_src_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte /* in6_addr */ + Ifindex uint32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type ipv6Mreq struct { + Multiaddr [16]byte /* in6_addr */ + Interface uint32 +} + +type groupReq struct { + Interface uint32 + Pad_cgo_0 [256]byte +} + +type groupSourceReq struct { + Interface uint32 + Pad_cgo_0 [256]byte + Pad_cgo_1 [256]byte +} + +type icmpv6Filter struct { + X__icmp6_filt [8]uint32 +} diff --git a/vendor/golang.org/x/net/ipv6/zsys_zos_s390x.go b/vendor/golang.org/x/net/ipv6/zsys_zos_s390x.go new file mode 100644 index 0000000..7c75645 --- /dev/null +++ b/vendor/golang.org/x/net/ipv6/zsys_zos_s390x.go @@ -0,0 +1,62 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Hand edited based on zerrors_zos_s390x.go +// TODO(Bill O'Farrell): auto-generate. + +package ipv6 + +const ( + sizeofSockaddrStorage = 128 + sizeofICMPv6Filter = 32 + sizeofInet6Pktinfo = 20 + sizeofIPv6Mtuinfo = 32 + sizeofSockaddrInet6 = 28 + sizeofGroupReq = 136 + sizeofGroupSourceReq = 264 +) + +type sockaddrStorage struct { + Len uint8 + Family byte + ss_pad1 [6]byte + ss_align int64 + ss_pad2 [112]byte +} + +type sockaddrInet6 struct { + Len uint8 + Family uint8 + Port uint16 + Flowinfo uint32 + Addr [16]byte + Scope_id uint32 +} + +type inet6Pktinfo struct { + Addr [16]byte + Ifindex uint32 +} + +type ipv6Mtuinfo struct { + Addr sockaddrInet6 + Mtu uint32 +} + +type groupReq struct { + Interface uint32 + reserved uint32 + Group sockaddrStorage +} + +type groupSourceReq struct { + Interface uint32 + reserved uint32 + Group sockaddrStorage + Source sockaddrStorage +} + +type icmpv6Filter struct { + Filt [8]uint32 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 49c8ce7..8f355ca 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -73,9 +73,15 @@ github.com/goccy/go-json/internal/runtime # github.com/google/uuid v1.3.0 ## explicit github.com/google/uuid +# github.com/hashicorp/mdns v1.0.5 +## explicit; go 1.13 +github.com/hashicorp/mdns # github.com/holoplot/go-evdev v0.0.0-20230626094006-70c9462cc0f2 ## explicit; go 1.18 github.com/holoplot/go-evdev +# github.com/ianr0bkny/go-sonos v0.0.0-20171025003233-056585059953 +## explicit +github.com/ianr0bkny/go-sonos/ssdp # github.com/json-iterator/go v1.1.12 ## explicit; go 1.12 github.com/json-iterator/go @@ -94,6 +100,9 @@ github.com/mattn/go-isatty # github.com/mattn/go-pointer v0.0.1 ## explicit github.com/mattn/go-pointer +# github.com/miekg/dns v1.1.41 +## explicit; go 1.13 +github.com/miekg/dns # github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd ## explicit github.com/modern-go/concurrent @@ -295,6 +304,7 @@ golang.org/x/net/internal/iana golang.org/x/net/internal/socket golang.org/x/net/internal/socks golang.org/x/net/ipv4 +golang.org/x/net/ipv6 golang.org/x/net/proxy # golang.org/x/sys v0.9.0 ## explicit; go 1.17 From d1eced609c3ce54d7bce983d1d4f11e722fb1ce3 Mon Sep 17 00:00:00 2001 From: kaedwen Date: Sun, 16 Jul 2023 15:33:05 +0200 Subject: [PATCH 5/9] add sonons volume and skip when not all --- pkg/common/config.go | 1 + pkg/ring/sonos/sonos.go | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/pkg/common/config.go b/pkg/common/config.go index 961c792..31b1329 100644 --- a/pkg/common/config.go +++ b/pkg/common/config.go @@ -32,6 +32,7 @@ type ConfigRing struct { JingleName string `arg:"--jingle-name,env:JINGLE_NAME" default:"ding-dong.wav"` JinglePath *string `arg:"--jingle-path,env:JINGLE_PATH"` SonosTarget string `arg:"--sonos-target,env:SONOS_TARGET" default:"-"` + SonosVolume int `arg:"--sonos-volume,env:SONOS_VOLUME" default:"50"` } type ConfigHTTP struct { diff --git a/pkg/ring/sonos/sonos.go b/pkg/ring/sonos/sonos.go index d74bd7b..e736fc8 100644 --- a/pkg/ring/sonos/sonos.go +++ b/pkg/ring/sonos/sonos.go @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" "github.com/hashicorp/mdns" + "github.com/kaedwen/webrtc/pkg/common" "go.uber.org/zap" ) @@ -48,7 +49,7 @@ type sonosAudioClip struct { type SonosHandler struct { lg *zap.Logger - target string + cfg *common.ConfigRing players map[string]*SonosPlayer } @@ -66,8 +67,8 @@ func NewSonosPlayer(address *url.URL) (*SonosPlayer, error) { return &SonosPlayer{client, address, sonosInfo{}}, nil } -func NewSonosHandler(lg *zap.Logger, target string) (*SonosHandler, error) { - return &SonosHandler{lg, target, make(map[string]*SonosPlayer)}, nil +func NewSonosHandler(lg *zap.Logger, cfg *common.ConfigRing) (*SonosHandler, error) { + return &SonosHandler{lg, cfg, make(map[string]*SonosPlayer)}, nil } func (p *SonosPlayer) init() error { @@ -103,12 +104,12 @@ func (p *SonosPlayer) init() error { return nil } -func (p *SonosPlayer) Play(uri *url.URL) error { +func (p *SonosPlayer) Play(uri *url.URL, volume int) error { sab := sonosAudioClip{ Name: "Pull Bell", AppId: "com.acme.app", StreamUrl: uri.String(), - Volume: 5, + Volume: volume, } b, err := json.Marshal(sab) @@ -163,6 +164,11 @@ func (h *SonosHandler) Watch(ctx context.Context) error { continue } + if h.cfg.SonosTarget != "-" && p.info.Device.Name != h.cfg.SonosTarget { + h.lg.Info("skip player", zap.String("name", p.info.Device.Name)) + continue + } + h.players[e.Name] = p } } @@ -175,7 +181,7 @@ func (h *SonosHandler) Watch(ctx context.Context) error { func (h *SonosHandler) Play(uri *url.URL) error { for _, p := range h.players { - if err := p.Play(uri); err != nil { + if err := p.Play(uri, h.cfg.SonosVolume); err != nil { h.lg.Error("failed to play", zap.String("address", p.address.String()), zap.Error(err)) } } From 3a6fae5bf2b6cafc1ee5651dcb0933f8560395f2 Mon Sep 17 00:00:00 2001 From: kaedwen Date: Sun, 16 Jul 2023 15:33:43 +0200 Subject: [PATCH 6/9] fix handler setup --- pkg/ring/ring.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/ring/ring.go b/pkg/ring/ring.go index eb544f1..7422cf2 100644 --- a/pkg/ring/ring.go +++ b/pkg/ring/ring.go @@ -23,7 +23,7 @@ type RingHandler struct { } func NewRingHandler(lg *zap.Logger, cfg *common.ConfigRing) (*RingHandler, error) { - spl, err := sonos.NewSonosHandler(lg.With(zap.String("context", "sonos")), cfg.SonosTarget) + spl, err := sonos.NewSonosHandler(lg.With(zap.String("context", "sonos")), cfg) if err != nil { return nil, err } From 18a99b371575b233216c724bf0972405950dbbcd Mon Sep 17 00:00:00 2001 From: kaedwen Date: Tue, 18 Jul 2023 19:43:19 +0200 Subject: [PATCH 7/9] cert renew --- .gitignore | 2 +- .vscode/launch.json | 2 +- Makefile | 1 + assets/cert/renew.env | 5 + assets/cert/renew.service | 10 + assets/cert/renew.sh | 13 + assets/cert/renew.timer | 10 + go.mod | 4 +- go.sum | 4 - pkg/common/config.go | 24 +- pkg/ring/ring.go | 20 +- pkg/ring/sonos/sonos.go | 3 + pkg/streamer/gst-sink.go | 18 + pkg/streamer/gst.go | 1 + pkg/webrtc/webrtc.go | 36 +- static/disk.go | 6 - static/emb.go | 6 - vendor/github.com/ianr0bkny/go-sonos/LICENSE | 28 - .../ianr0bkny/go-sonos/ssdp/ssdp.go | 942 ----------- vendor/github.com/szatmary/sonos/.gitignore | 12 - .../szatmary/sonos/AVTransport/AVTransport.go | 1379 ----------------- .../szatmary/sonos/AlarmClock/AlarmClock.go | 593 ------- .../ConnectionManager/ConnectionManager.go | 174 --- .../ContentDirectory/ContentDirectory.go | 539 ------- .../DeviceProperties/DeviceProperties.go | 789 ---------- .../sonos/GroupManagement/GroupManagement.go | 200 --- .../GroupRenderingControl.go | 254 --- vendor/github.com/szatmary/sonos/LICENSE | 21 - .../sonos/MusicServices/MusicServices.go | 169 -- .../github.com/szatmary/sonos/QPlay/QPlay.go | 115 -- .../github.com/szatmary/sonos/Queue/Queue.go | 435 ------ vendor/github.com/szatmary/sonos/README.md | 0 .../RenderingControl/RenderingControl.go | 940 ----------- .../SystemProperties/SystemProperties.go | 576 ------- .../sonos/VirtualLineIn/VirtualLineIn.go | 306 ---- .../ZoneGroupTopology/ZoneGroupTopology.go | 318 ---- vendor/github.com/szatmary/sonos/sonos.go | 100 -- .../github.com/szatmary/sonos/zonegroups.go | 107 -- .../github.com/szatmary/sonos/zoneplayer.go | 236 --- vendor/modules.txt | 20 - 40 files changed, 122 insertions(+), 8296 deletions(-) create mode 100644 assets/cert/renew.env create mode 100644 assets/cert/renew.service create mode 100644 assets/cert/renew.sh create mode 100644 assets/cert/renew.timer delete mode 100644 vendor/github.com/ianr0bkny/go-sonos/LICENSE delete mode 100644 vendor/github.com/ianr0bkny/go-sonos/ssdp/ssdp.go delete mode 100644 vendor/github.com/szatmary/sonos/.gitignore delete mode 100644 vendor/github.com/szatmary/sonos/AVTransport/AVTransport.go delete mode 100644 vendor/github.com/szatmary/sonos/AlarmClock/AlarmClock.go delete mode 100644 vendor/github.com/szatmary/sonos/ConnectionManager/ConnectionManager.go delete mode 100644 vendor/github.com/szatmary/sonos/ContentDirectory/ContentDirectory.go delete mode 100644 vendor/github.com/szatmary/sonos/DeviceProperties/DeviceProperties.go delete mode 100644 vendor/github.com/szatmary/sonos/GroupManagement/GroupManagement.go delete mode 100644 vendor/github.com/szatmary/sonos/GroupRenderingControl/GroupRenderingControl.go delete mode 100644 vendor/github.com/szatmary/sonos/LICENSE delete mode 100644 vendor/github.com/szatmary/sonos/MusicServices/MusicServices.go delete mode 100644 vendor/github.com/szatmary/sonos/QPlay/QPlay.go delete mode 100644 vendor/github.com/szatmary/sonos/Queue/Queue.go delete mode 100644 vendor/github.com/szatmary/sonos/README.md delete mode 100644 vendor/github.com/szatmary/sonos/RenderingControl/RenderingControl.go delete mode 100644 vendor/github.com/szatmary/sonos/SystemProperties/SystemProperties.go delete mode 100644 vendor/github.com/szatmary/sonos/VirtualLineIn/VirtualLineIn.go delete mode 100644 vendor/github.com/szatmary/sonos/ZoneGroupTopology/ZoneGroupTopology.go delete mode 100644 vendor/github.com/szatmary/sonos/sonos.go delete mode 100644 vendor/github.com/szatmary/sonos/zonegroups.go delete mode 100644 vendor/github.com/szatmary/sonos/zoneplayer.go diff --git a/.gitignore b/.gitignore index 003a4d0..0aed642 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ service* -cert \ No newline at end of file +renew.local.env \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 068ae0b..97966bc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,7 @@ "program": "${workspaceFolder}/main.go", "env": { "HTTP_STATIC": "${workspaceFolder}/static/dist", - "VIDEO_OUT_DEVICE": "/dev/video4", + //"VIDEO_OUT_DEVICE": "/dev/video4", "AUDIO_OUT_DEVICE": "plughw:0,1,0", "INPUT_DEVICE": "/dev/input/event5", "JINGLE_PATH": "${workspaceFolder}/audio", diff --git a/Makefile b/Makefile index fee7ff9..bfa262f 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ build: build-static: npm --prefix static ci && npm --prefix static run build + cp -r audio static/dist/audio build-armhf: GOARCH=arm \ diff --git a/assets/cert/renew.env b/assets/cert/renew.env new file mode 100644 index 0000000..780d18b --- /dev/null +++ b/assets/cert/renew.env @@ -0,0 +1,5 @@ +HTTPREQ_ENDPOINT= +ACME_KID= +ACME_HMAC= +ACME_DOMAINS= +ACME_EMAIL= \ No newline at end of file diff --git a/assets/cert/renew.service b/assets/cert/renew.service new file mode 100644 index 0000000..0aed877 --- /dev/null +++ b/assets/cert/renew.service @@ -0,0 +1,10 @@ +[Unit] +Description=Renew certificate + +[Service] +Type=simple +EnvironmentFile=/etc/ring/renew.env +ExecStart=/usr/local/bin/renew.sh + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/assets/cert/renew.sh b/assets/cert/renew.sh new file mode 100644 index 0000000..08c3b4c --- /dev/null +++ b/assets/cert/renew.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +set +x + +lego --path "/etc/ldap/cert" --email "${ACME_EMAIL}" --server "https://acme.zerossl.com/v2/DV90" --accept-tos --eab \ +--kid "${ACME_KID}" \ +--hmac "${ACME_HMAC}" \ +--key-type "rsa4096" \ +--dns "httpreq" \ +--domains "${ACME_DOMAINS}" \ +--pem \ +renew \ +--renew-hook="systemctl restart ring" \ No newline at end of file diff --git a/assets/cert/renew.timer b/assets/cert/renew.timer new file mode 100644 index 0000000..7199602 --- /dev/null +++ b/assets/cert/renew.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Renew certificate + +[Timer] +Persistent=true +OnCalendar=*-*-* 3:35 +RandomizedDelaySec=1h + +[Install] +WantedBy=timers.target \ No newline at end of file diff --git a/go.mod b/go.mod index 04b8ece..bddd8a8 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,11 @@ go 1.20 require ( github.com/alexflint/go-arg v1.4.3 github.com/gin-gonic/gin v1.9.1 + github.com/google/uuid v1.3.0 github.com/hashicorp/mdns v1.0.5 github.com/holoplot/go-evdev v0.0.0-20230626094006-70c9462cc0f2 - github.com/ianr0bkny/go-sonos v0.0.0-20171025003233-056585059953 github.com/pion/rtcp v1.2.10 github.com/pion/webrtc/v3 v3.2.10 - github.com/szatmary/sonos v0.0.0-20191204204454-000c9219ff0e github.com/tinyzimmer/go-glib v0.0.25 github.com/tinyzimmer/go-gst v0.2.33 go.uber.org/zap v1.24.0 @@ -28,7 +27,6 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.1 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.10.3 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect diff --git a/go.sum b/go.sum index 582b08b..0114c63 100644 --- a/go.sum +++ b/go.sum @@ -68,8 +68,6 @@ github.com/hashicorp/mdns v1.0.5/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/ github.com/holoplot/go-evdev v0.0.0-20230626094006-70c9462cc0f2 h1:bxudnRpvfq2PMYEYN7VGj03+MXG4EobVUmONigCx1yA= github.com/holoplot/go-evdev v0.0.0-20230626094006-70c9462cc0f2/go.mod h1:iHAf8OIncO2gcQ8XOjS7CMJ2aPbX2Bs0wl5pZyanEqk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianr0bkny/go-sonos v0.0.0-20171025003233-056585059953 h1:n9vfPPfv66b56toxNZk/w7VPSZ5eK/NxVrtRs1uptzo= -github.com/ianr0bkny/go-sonos v0.0.0-20171025003233-056585059953/go.mod h1:uvzy1yuMP2e76h67o5rpaXPDPq4e/cUGU4RYkm9a0Js= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -168,8 +166,6 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/szatmary/sonos v0.0.0-20191204204454-000c9219ff0e h1:beo21nzzdqCVGe6EdG3Lo3UbU7meQN4JjAAeyZccH94= -github.com/szatmary/sonos v0.0.0-20191204204454-000c9219ff0e/go.mod h1:F5m1XEgf251EShYE9r+2Rl45NPLGovXttfuAjBb93bY= github.com/tinyzimmer/go-glib v0.0.25 h1:2GpumtkxA0wpXhCXP6D3ksb5pGMfo9WbhgLvEw8njK4= github.com/tinyzimmer/go-glib v0.0.25/go.mod h1:ltV0gO6xNFzZhsIRbFXv8RTq9NGoNT2dmAER4YmZfaM= github.com/tinyzimmer/go-gst v0.2.33 h1:wdwUYoN7dkWGUTrZIgB9Mp5LMRr/Sld5PVGRsE7/O9s= diff --git a/pkg/common/config.go b/pkg/common/config.go index 31b1329..55ea433 100644 --- a/pkg/common/config.go +++ b/pkg/common/config.go @@ -27,12 +27,12 @@ type ConfigLogging struct { } type ConfigRing struct { - Device string `arg:"--input-device,env:INPUT_DEVICE" default:"/dev/input/by-path/platform-gpio-keys-user-event"` - Key string `arg:"--ring-key" default:"KEY_F1"` - JingleName string `arg:"--jingle-name,env:JINGLE_NAME" default:"ding-dong.wav"` - JinglePath *string `arg:"--jingle-path,env:JINGLE_PATH"` - SonosTarget string `arg:"--sonos-target,env:SONOS_TARGET" default:"-"` - SonosVolume int `arg:"--sonos-volume,env:SONOS_VOLUME" default:"50"` + Device *string `arg:"--input-device,env:INPUT_DEVICE"` + Key string `arg:"--ring-key" default:"KEY_F1"` + JingleBaseUri *string `arg:"--jingle-base-uri,env:JINGLE_BASE_URI"` + JinglePath string `arg:"--jingle-path,env:JINGLE_PATH" default:"audio/ding-dong.wav"` + SonosTarget string `arg:"--sonos-target,env:SONOS_TARGET" default:"-"` + SonosVolume int `arg:"--sonos-volume,env:SONOS_VOLUME" default:"50"` } type ConfigHTTP struct { @@ -47,11 +47,12 @@ type ConfigHTTP struct { } type ConfigVideoOutputStream struct { - Source string `arg:"--video-out-src,env:VIDEO_OUT_SRC" default:"v4l2src"` - Device string `arg:"--video-out-device,env:VIDEO_OUT_DEVICE" default:"/dev/video0"` - Codec string `arg:"--video-out-codec,env:VIDEO_OUT_CODEC" default:"vp8"` - Height uint `arg:"--video-out-height,env:VIDEO_OUT_HEIGHT" default:"480"` - Width uint `arg:"--video-out-width,env:VIDEO_OUT_WIDTH" default:"640"` + Source string `arg:"--video-out-src,env:VIDEO_OUT_SRC" default:"v4l2src"` + Device string `arg:"--video-out-device,env:VIDEO_OUT_DEVICE" default:"/dev/video0"` + Codec string `arg:"--video-out-codec,env:VIDEO_OUT_CODEC" default:"vp8"` + Height uint `arg:"--video-out-height,env:VIDEO_OUT_HEIGHT" default:"480"` + Width uint `arg:"--video-out-width,env:VIDEO_OUT_WIDTH" default:"640"` + USE_QUEUE bool `arg:"--video-out-queue,env:VIDEO_OUT_QUEUE" default:"false"` } type ConfigAudioOutputStream struct { @@ -60,6 +61,7 @@ type ConfigAudioOutputStream struct { Device *string `arg:"--audio-out-device,env:AUDIO_OUT_DEVICE"` Codec string `arg:"--audio-out-codec,env:AUDIO_OUT_CODEC" default:"opus"` Channels uint `arg:"--audio-out-channels,env:AUDIO_OUT_CHANNELS" default:"1"` + USE_QUEUE bool `arg:"--audio-out-queue,env:AUDIO_OUT_QUEUE" default:"false"` } type ConfigAudioInputStream struct { diff --git a/pkg/ring/ring.go b/pkg/ring/ring.go index 7422cf2..af9e2de 100644 --- a/pkg/ring/ring.go +++ b/pkg/ring/ring.go @@ -32,13 +32,23 @@ func NewRingHandler(lg *zap.Logger, cfg *common.ConfigRing) (*RingHandler, error } func (h *RingHandler) Watch(ctx context.Context) error { + if h.cfg.Device == nil { + h.lg.Warn("nothing to watch for key press") + return nil + } + + if h.cfg.JingleBaseUri == nil { + h.lg.Warn("missing jingle base uri, nothing to play") + return nil + } + for _, p := range h.playHandlers { if err := p.Watch(ctx); err != nil { return err } } - d, err := evdev.Open(h.cfg.Device) + d, err := evdev.Open(*h.cfg.Device) if err != nil { return err } @@ -48,9 +58,9 @@ func (h *RingHandler) Watch(ctx context.Context) error { key := evdev.KEYFromString[h.cfg.Key] - bu := url.URL{ - Scheme: "http", - Host: "192.168.60.141:9099", + bu, err := url.Parse(*h.cfg.JingleBaseUri) + if err != nil { + return err } go func() { @@ -62,7 +72,7 @@ func (h *RingHandler) Watch(ctx context.Context) error { for { e, err := d.ReadOne() if err == nil && e.Code == key && e.Value == 0 { - tu := bu.JoinPath(h.cfg.JingleName) + tu := bu.JoinPath(h.cfg.JinglePath) for _, p := range h.playHandlers { if err := p.Play(tu); err != nil { h.lg.Error("failed to play", zap.Error(err)) diff --git a/pkg/ring/sonos/sonos.go b/pkg/ring/sonos/sonos.go index e736fc8..8ec64e7 100644 --- a/pkg/ring/sonos/sonos.go +++ b/pkg/ring/sonos/sonos.go @@ -43,6 +43,7 @@ type sonosInfo struct { type sonosAudioClip struct { Name string `json:"name"` AppId string `json:"appId"` + ClipType string `json:"clipType,omitempty"` StreamUrl string `json:"streamUrl"` Volume int `json:"volume"` } @@ -108,6 +109,7 @@ func (p *SonosPlayer) Play(uri *url.URL, volume int) error { sab := sonosAudioClip{ Name: "Pull Bell", AppId: "com.acme.app", + ClipType: "CHIME", StreamUrl: uri.String(), Volume: volume, } @@ -181,6 +183,7 @@ func (h *SonosHandler) Watch(ctx context.Context) error { func (h *SonosHandler) Play(uri *url.URL) error { for _, p := range h.players { + h.lg.Info("playing clip", zap.String("target", p.address.String()), zap.String("clip", uri.String())) if err := p.Play(uri, h.cfg.SonosVolume); err != nil { h.lg.Error("failed to play", zap.String("address", p.address.String()), zap.Error(err)) } diff --git a/pkg/streamer/gst-sink.go b/pkg/streamer/gst-sink.go index a71ff1d..e210ced 100644 --- a/pkg/streamer/gst-sink.go +++ b/pkg/streamer/gst-sink.go @@ -71,6 +71,15 @@ func CreateVideoPipelineSink(s StreamElement) (*gst.Pipeline, <-chan media.Sampl } elems = append(elems, conv) + if s.Queue { + // add a queue + queue, err := gst.NewElement("queue") + if err != nil { + return nil, nil, err + } + elems = append(elems, queue) + } + // Create the enc enc, err := gst.NewElement("vp8enc") if err != nil { @@ -140,6 +149,15 @@ func CreateAudioPipelineSink(s StreamElement) (*gst.Pipeline, <-chan media.Sampl } elems = append(elems, conv) + if s.Queue { + // add a queue + queue, err := gst.NewElement("queue") + if err != nil { + return nil, nil, err + } + elems = append(elems, queue) + } + // Create the enc enc, err := gst.NewElement("opusenc") if err != nil { diff --git a/pkg/streamer/gst.go b/pkg/streamer/gst.go index 25f2bc1..c14fb5f 100644 --- a/pkg/streamer/gst.go +++ b/pkg/streamer/gst.go @@ -30,6 +30,7 @@ type StreamElement struct { Kind string Properties map[string]interface{} Caps *StreamElementCaps + Queue bool } func (se StreamElement) ToGstCaps() *gst.Caps { diff --git a/pkg/webrtc/webrtc.go b/pkg/webrtc/webrtc.go index 02b2704..e0ef051 100644 --- a/pkg/webrtc/webrtc.go +++ b/pkg/webrtc/webrtc.go @@ -2,6 +2,8 @@ package webrtc import ( "context" + "errors" + "io" "sync" "time" @@ -121,6 +123,7 @@ func (wh *WebrtcHandler) handleAudioSamples(ctx context.Context, cfg *common.Con Channels: cfg.Channels, Rate: 48000, }, + Queue: cfg.USE_QUEUE, } var err error @@ -164,6 +167,7 @@ func (wh *WebrtcHandler) handleVideoSamples(ctx context.Context, cfg *common.Con Width: cfg.Width, Height: cfg.Height, }, + Queue: cfg.USE_QUEUE, } var err error @@ -195,7 +199,7 @@ func (wh *WebrtcHandler) handleVideoSamples(ctx context.Context, cfg *common.Con return nil } -func (wh *WebrtcHandler) createPeerHandle(ctx context.Context, sh *server.SignalingHandle) error { +func (wh *WebrtcHandler) createPeerHandle(rctx context.Context, sh *server.SignalingHandle) error { wh.mu.Lock() defer wh.mu.Unlock() @@ -210,6 +214,9 @@ func (wh *WebrtcHandler) createPeerHandle(ctx context.Context, sh *server.Signal return err } + // create a context for this handle + hctx, hcancel := context.WithCancel(rctx) + // Set a handler for when a new remote track starts, this handler creates a gstreamer pipeline // for the given codec peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { @@ -227,9 +234,13 @@ func (wh *WebrtcHandler) createPeerHandle(ctx context.Context, sh *server.Signal select { case <-ticker.C: if err := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); err != nil { + if errors.Is(err, io.ErrClosedPipe) { + return + } + wh.lg.Error("failed to send PLI", zap.Error(err)) } - case <-ctx.Done(): + case <-hctx.Done(): return } } @@ -240,18 +251,24 @@ func (wh *WebrtcHandler) createPeerHandle(ctx context.Context, sh *server.Signal }) if err != nil { wh.lg.Error("failed to create src pipeline", zap.Error(err)) + return } pipeline.Start() buf := make([]byte, 1400) for { - i, _, readErr := track.Read(buf) + n, _, readErr := track.Read(buf) if readErr != nil { + if errors.Is(readErr, io.EOF) { + return + } + wh.lg.Error("read failed", zap.Error(err)) + continue } - if i > 0 { - pipeline.Push(buf[:i]) + if n > 0 { + err := pipeline.Push(buf[:n]) if err != nil { wh.lg.Error("push failed", zap.Error(err)) } @@ -266,10 +283,17 @@ func (wh *WebrtcHandler) createPeerHandle(ctx context.Context, sh *server.Signal if connectionState == webrtc.ICEConnectionStateDisconnected { peerConnection.Close() + hcancel() // remove this handle wh.mu.Lock() + delete(wh.peerHandles, sh.Id) + + if len(wh.peerHandles) == 0 { + wh.stopPipelines() + } + wh.mu.Unlock() } }) @@ -354,7 +378,7 @@ func (wh *WebrtcHandler) createPeerHandle(ctx context.Context, sh *server.Signal if err != nil { wh.lg.Error("failed to handle offer", zap.Error(err)) } - case <-ctx.Done(): + case <-hctx.Done(): return } } diff --git a/static/disk.go b/static/disk.go index 44bfb9f..cd86ccb 100644 --- a/static/disk.go +++ b/static/disk.go @@ -39,12 +39,6 @@ func (s staticDiskSource) Open(p string) (common.StaticSourceFile, error) { } func SetupHandler(e *gin.Engine, cfg *common.Config) { - if cfg.Ring.JinglePath != nil { - e.GET(cfg.Ring.JingleName, func(c *gin.Context) { - c.File(path.Join(*cfg.Ring.JinglePath, cfg.Ring.JingleName)) - }) - } - if cfg.Http.StaticPath != nil { handler := common.NewStaticHandler(staticDiskSource{*cfg.Http.StaticPath}, "index.html") e.NoRoute(func(c *gin.Context) { diff --git a/static/emb.go b/static/emb.go index 625bf7a..9439eba 100644 --- a/static/emb.go +++ b/static/emb.go @@ -42,12 +42,6 @@ func (s staticSource) Open(p string) (common.StaticSourceFile, error) { } func SetupHandler(e *gin.Engine, cfg *common.Config) { - if cfg.Ring.JinglePath != nil { - e.GET(cfg.Ring.JingleName, func(c *gin.Context) { - c.File(path.Join(*cfg.Ring.JinglePath, cfg.Ring.JingleName)) - }) - } - handler := common.NewStaticHandler(staticSource{"dist"}, "index.html") e.NoRoute(func(c *gin.Context) { encoding := c.Request.Header.Get("Accept-Encoding") diff --git a/vendor/github.com/ianr0bkny/go-sonos/LICENSE b/vendor/github.com/ianr0bkny/go-sonos/LICENSE deleted file mode 100644 index 65b32a3..0000000 --- a/vendor/github.com/ianr0bkny/go-sonos/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -go-sonos -======== - -Copyright (c) 2012, Ian T. Richards -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/vendor/github.com/ianr0bkny/go-sonos/ssdp/ssdp.go b/vendor/github.com/ianr0bkny/go-sonos/ssdp/ssdp.go deleted file mode 100644 index 0f3dc9d..0000000 --- a/vendor/github.com/ianr0bkny/go-sonos/ssdp/ssdp.go +++ /dev/null @@ -1,942 +0,0 @@ -// -// go-sonos -// ======== -// -// Copyright (c) 2012, Ian T. Richards -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// - -// -// A client implementation of the SSDP protocol. -// -// mgr := ssdp.MakeManager() -// mgr.Discover("eth0", "13104", false) -// qry := ssdp.ServiceQueryTerms{ -// ssdp.ServiceKey("schemas-upnp-org-MusicServices"): -1, -// } -// result := mgr.QueryServices(qry) -// if device_list, has := result["schemas-upnp-org-MusicServices"]; has { -// for _, device := range device_list { -// ... -// } -// } -// mgr.Close() -// -package ssdp - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "log" - "net" - "net/textproto" - "regexp" - "strconv" - "strings" - "time" -) - -var ssdpHNAPRegex *regexp.Regexp -var ssdpOtherDeviceRegex *regexp.Regexp -var ssdpOtherDeviceUUIDRegex *regexp.Regexp -var ssdpOtherServiceRegex *regexp.Regexp -var ssdpOtherServiceUUIDRegex *regexp.Regexp -var ssdpResponseStartLineRegexp *regexp.Regexp -var ssdpServerStringRegexp *regexp.Regexp -var ssdpRootDeviceUUIDRegex *regexp.Regexp -var ssdpUPnPBareUUIDRegex *regexp.Regexp -var ssdpUPnPDeviceRegex *regexp.Regexp -var ssdpUPnPDeviceUUIDRegex *regexp.Regexp -var ssdpUPnPServiceRegex *regexp.Regexp -var ssdpUPnPServiceUUIDRegex *regexp.Regexp -var ssdpUPnPUIDRegex *regexp.Regexp - -func init() { - ssdpHNAPRegex = regexp.MustCompile("^hnap:(.+)$") - ssdpOtherDeviceRegex = regexp.MustCompile("^urn:([^:]+):device:([^:]+)(:(.+))?$") - ssdpOtherDeviceUUIDRegex = regexp.MustCompile("^uuid:([^:]+)::urn:([^:]+):device:([^:]+)(:(.+))?$") - ssdpOtherServiceRegex = regexp.MustCompile("^urn:([^:]+):service:([^:]+)(:(.+))?$") - ssdpOtherServiceUUIDRegex = regexp.MustCompile("^uuid:([^:]+)::urn:([^:]+):service:([^:]+)(:(.+))?$") - ssdpResponseStartLineRegexp = regexp.MustCompile("^HTTP/([0-9\\.]+)$") - ssdpServerStringRegexp = regexp.MustCompile("^([^ /,]+)(/([^ ,]+))?,?\\s+[Uu][Pp][Nn][Pp](/([^ ,]+))?,?\\s+([^/]+)(/(.+))?$") - ssdpRootDeviceUUIDRegex = regexp.MustCompile("^uuid:([^:]+)::upnp:rootdevice$") - ssdpUPnPBareUUIDRegex = regexp.MustCompile("^uuid:([^:]+)$") - ssdpUPnPDeviceRegex = regexp.MustCompile("^urn:schemas-upnp-org:device:([^:]+)(:(.+))?$") - ssdpUPnPDeviceUUIDRegex = regexp.MustCompile("uuid:([^:]+)::urn:schemas-upnp-org:device:([^:]+)(:(.+))?$") - ssdpUPnPServiceRegex = regexp.MustCompile("^urn:schemas-upnp-org:service:([^:]+)(:(.+))?$") - ssdpUPnPServiceUUIDRegex = regexp.MustCompile("uuid:([^:]+)::urn:schemas-upnp-org:service:([^:]+)(:(.+))?$") - ssdpUPnPUIDRegex = regexp.MustCompile("^uuid:(.+)$") -} - -const ( - ssdpBroadcastGroup = "239.255.255.250:1900" - ssdpBroadcastVersion = "udp" -) - -// Type protection for a device URI -type Location string - -// Type protection for a SSDP service key -type ServiceKey string - -// Type protection for a Universally Unique ID -type UUID string - -type ssdpServerDescription struct { - os string - os_version string - upnp_version string - product string - productVersion string -} - -type ssdpResourceBase struct { - ssdpServerDescription - location Location - uuid UUID -} - -// An abstraction of an SSDP service -type Service interface { - // The version of the service - Version() int64 -} - -type ssdpService struct { - ssdpResourceBase - name string - version int64 - uri string -} - -func (this *ssdpService) Version() int64 { - return this.version -} - -type ssdpServiceSet map[ServiceKey]Service - -// An abstraction of an SSDP leaf device -type Device interface { - // The product name (e.g. "Sonos") - Product() string - // The product version (e.g. "28.1-83040 (BR100)") - ProductVersion() string - // The device name (e.h. "ZonePlayer") - Name() string - // A URI that can be queried for device capabilities - Location() Location - // The device's Universally Unique ID - UUID() UUID - // Search for a service in the device's capabilities; same - // return semantics as querying a map - Service(key ServiceKey) (service Service, has bool) - // Return a list of services implemented by this device - Services() []ServiceKey -} - -type ssdpDevice struct { - ssdpResourceBase - name string - version int64 - uri string - services ssdpServiceSet -} - -func (this *ssdpDevice) Product() string { - return this.product -} - -func (this *ssdpDevice) ProductVersion() string { - return this.productVersion -} - -func (this *ssdpDevice) Name() string { - return this.name -} - -func (this *ssdpDevice) Location() Location { - return this.location -} - -func (this *ssdpDevice) UUID() UUID { - return this.uuid -} - -func (this *ssdpDevice) Service(key ServiceKey) (service Service, has bool) { - service, has = this.services[key] - return -} - -func (this *ssdpDevice) Services() []ServiceKey { - i := 0 - keys := make([]ServiceKey, len(this.services)) - for key, _ := range this.services { - keys[i] = key - i += 1 - } - return keys -} - -// Indexes leaf devices by UUID -type DeviceMap map[UUID]Device - -type ssdpRootDevice struct { - ssdpResourceBase - Devices DeviceMap -} - -type ssdpRootDeviceMap map[Location]*ssdpRootDevice - -// Discovered devices sorted by supported service -type ServiceMap map[ServiceKey]DeviceMap - -type ssdpResourceType int - -const ( - ssdpTypessdpRootDevice ssdpResourceType = iota - ssdpTypeService - ssdpTypeDevice - ssdpTypeHNAP - ssdpTypeUUID - ssdpTypeUnknown -) - -type ssdpResource struct { - ssdpServerDescription - ssdptype ssdpResourceType - uuid UUID - name string - version int64 - uri string - location Location - children []*ssdpResource -} - -type ssdpMessageType int - -const ( - ssdpSearch ssdpMessageType = iota - ssdpNotify - ssdpResponse - ssdpInvalid -) - -type ssdpRawMessage struct { - msgtype ssdpMessageType - httpver string - header map[string]string -} - -type ssdpNotifyMessage struct { - _01_nls string - cache_control string - host string - location Location - nts string - nt string - opt string - server string - usn string - x_rincon_bootseq string - x_rincon_household string - x_user_agent string -} - -type ssdpNotifyQueue chan *ssdpNotifyMessage - -type ssdpResponseMessage struct { - ssdpServerDescription - _01_nls string - al string - cache_control string - date string - ext string - location Location - opt string - st string - usn string - x_rincon_bootseq string - x_rincon_household string - x_rincon_variant string - x_rincon_wifimode string - x_user_agent string - content_length string - bootid_upnp_org string - configid_upnp_org string - household_smartspeaker_audio string - x_av_server_info string -} - -type ssdpResponseQueue chan *ssdpResponseMessage - -type ssdpConnection struct { - conn *net.UDPConn - addr *net.UDPAddr -} - -// A map of service key to minimum required version -type ServiceQueryTerms map[ServiceKey]int64 - -// Encapsulates SSDP discovery, handles updates, and stores results -type Manager interface { - // Initiates SSDP discovery, where ifiname names a network device - // to query, port gives a free port on that network device to listen - // for responses, and the subscribe flag (currrently unimplemented) - // determines whether to listen to asynchronous updates after the - // initial query is complete. - Discover(ifiname, port string, subscribe bool) error - // After discovery is complete searches for devices implementing - // the services specified in query. - QueryServices(query ServiceQueryTerms) ServiceMap - // Return the list of devices that were found during discovery - Devices() DeviceMap - // Shuts down asynchronous subscriptions to device state - Close() error -} - -type ssdpDefaultManager struct { - responseQueue ssdpResponseQueue - notifyQueue ssdpNotifyQueue - unicast ssdpConnection - multicast ssdpConnection - rootDeviceMap ssdpRootDeviceMap - deviceMap DeviceMap - serviceMap ServiceMap - readyChan chan int - closeChan chan int -} - -// Returns an empty manager ready for SSDP discovery -func MakeManager() Manager { - mgr := &ssdpDefaultManager{} - mgr.responseQueue = make(ssdpResponseQueue) - mgr.notifyQueue = make(ssdpNotifyQueue) - mgr.unicast = ssdpConnection{} - mgr.multicast = ssdpConnection{} - mgr.rootDeviceMap = make(ssdpRootDeviceMap) - mgr.deviceMap = make(DeviceMap) - mgr.serviceMap = make(ServiceMap) - mgr.readyChan = make(chan int) - mgr.closeChan = make(chan int) - return mgr -} - -func (this *ssdpDefaultManager) Discover(ifiname, port string, subscribe bool) (err error) { - this.ssdpDiscoverImpl(ifiname, port, subscribe) - return -} - -func (this *ssdpDefaultManager) QueryServices(query ServiceQueryTerms) (results ServiceMap) { - results = make(ServiceMap) - for name, minver := range query { - results[name] = make(DeviceMap) - if dlist, has := this.serviceMap[name]; has { - for uuid, _ := range dlist { - de := this.deviceMap[uuid] - if svc, has := de.Service(name); has { - if minver <= svc.Version() { - results[name][de.UUID()] = de - } - } - } - } - } - return -} - -func (this *ssdpDefaultManager) Devices() DeviceMap { - return this.deviceMap -} - -func (this *ssdpDefaultManager) Close() (err error) { - if nil != this.unicast.conn { - this.unicast.conn.Close() - <-this.closeChan - } - if nil != this.multicast.conn { - this.multicast.conn.Close() - <-this.closeChan - } - return -} - -func ssdpNewDevice(res *ssdpResource) (de *ssdpDevice) { - de = new(ssdpDevice) - de.ssdpServerDescription = res.ssdpServerDescription - de.uuid = res.uuid - de.location = res.location - de.name = res.name - de.version = res.version - de.uri = res.uri - de.services = make(ssdpServiceSet) - return -} - -func ssdpNewssdpRootDevice(res *ssdpResource) (rd *ssdpRootDevice) { - rd = new(ssdpRootDevice) - rd.ssdpServerDescription = res.ssdpServerDescription - rd.uuid = res.uuid - rd.location = res.location - rd.Devices = make(DeviceMap) - return -} - -func (rd *ssdpRootDevice) ssdpRequireEmbeddedDevice(de *ssdpDevice) { - if _, has := rd.Devices[de.UUID()]; !has { - rd.Devices[de.UUID()] = de - } -} - -func ssdpNewRawMessage() *ssdpRawMessage { - return &ssdpRawMessage{ssdpInvalid, "1.1", make(map[string]string)} -} - -func ssdpNewResource(uuid UUID) (res *ssdpResource) { - res = new(ssdpResource) - res.ssdptype = ssdpTypeUnknown - res.uuid = uuid - return -} - -func ssdpNewService(res *ssdpResource) (sv *ssdpService) { - sv = new(ssdpService) - sv.ssdpServerDescription = res.ssdpServerDescription - sv.uuid = res.uuid - sv.location = res.location - sv.name = res.name - sv.version = res.version - sv.uri = res.uri - return -} - -func (this *ssdpDefaultManager) ssdpParseStartLineFields(raw *ssdpRawMessage, fields []string) { - if "M-SEARCH" == fields[0] { - raw.msgtype = ssdpSearch - } else if "NOTIFY" == fields[0] { - raw.msgtype = ssdpNotify - } else if m := ssdpResponseStartLineRegexp.FindStringSubmatch(fields[0]); 0 < len(m) { - raw.msgtype = ssdpResponse - raw.httpver = m[1] - } else { - panic(fmt.Sprintf("Invalid start line `%s'", fields[0])) - } -} - -func (this *ssdpDefaultManager) ssdpParseStartLine(raw *ssdpRawMessage, line []byte) { - fields := strings.Fields(string(line)) - if 3 != len(fields) { - panic("Invalid start line") - } else { - this.ssdpParseStartLineFields(raw, fields) - } -} - -func (this *ssdpDefaultManager) ssdpParseHeaderLine(raw *ssdpRawMessage, line []byte) { - i := strings.Index(string(line), ":") - if -1 == i { - panic("Invalid header") - } - field := textproto.CanonicalMIMEHeaderKey(strings.TrimSpace(string(line[0:i]))) - value := strings.TrimSpace(string(line[i+1:])) - if _, has := raw.header[field]; has { - panic("Header field redefined") - } else { - raw.header[field] = value - } -} - -func (this *ssdpDefaultManager) ssdpParseInputLine(raw *ssdpRawMessage, line []byte, lineno int) { - if 1 < lineno { - this.ssdpParseHeaderLine(raw, line) - } else { - this.ssdpParseStartLine(raw, line) - } -} - -func (this *ssdpDefaultManager) ssdpParseInput(msg []byte) (raw *ssdpRawMessage) { - raw = ssdpNewRawMessage() - bin := bufio.NewReader(bytes.NewReader(msg)) - var line []byte - lineno := 0 - for { - if fragment, is_prefix, err := bin.ReadLine(); nil == err { - line = append(line, fragment...) - if !is_prefix { - lineno += 1 - if 1 < lineno && 0 == len(line) { - break - } - this.ssdpParseInputLine(raw, line, lineno) - line = nil - } - } else if io.EOF == err { - panic("Premature end of header") - } else { - panic(err) - } - } - return -} - -func (this *ssdpDefaultManager) ssdpHandleNotify(raw *ssdpRawMessage) *ssdpNotifyMessage { - msg := new(ssdpNotifyMessage) /*asynchronous reply from multicast*/ - for key, value := range raw.header { - switch key { - case "Location": - msg.location = Location(value) - case "Server": - msg.server = value - case "Host": - msg.host = value - case "Usn": - msg.usn = value - case "Cache-Control": - msg.cache_control = value - case "X-User-Agent": - msg.x_user_agent = value - case "X-Rincon-Bootseq": - msg.x_rincon_bootseq = value - case "X-Rincon-Household": - msg.x_rincon_household = value - case "Nts": - msg.nts = value - case "Nt": - msg.nt = value - case "Opt": - msg.opt = value - case "01-Nls": - msg._01_nls = value - default: - log.Printf("No support for field `%s' (value `%s')", key, value) - } - } - return msg -} - -func (this *ssdpDefaultManager) ssdpHandleResponse(raw *ssdpRawMessage) *ssdpResponseMessage { - msg := new(ssdpResponseMessage) /*synchronous reply from unicast*/ - for key, value := range raw.header { - switch key { - case "Location": - msg.location = Location(value) - case "St": - msg.st = value - case "Server": - m := ssdpServerStringRegexp.FindStringSubmatch(value) - if 0 < len(m) { - msg.os = m[1] - msg.os_version = m[3] - msg.upnp_version = m[5] - msg.product = m[6] - msg.productVersion = m[8] - } else { - log.Printf("Invalid server description `%s'", value) - } - case "Opt": - msg.opt = value - case "Usn": - msg.usn = value - case "Ext": - msg.ext = value - case "Date": - msg.date = value - case "Cache-Control": - msg.cache_control = value - case "X-User-Agent": - msg.x_user_agent = value - case "X-Rincon-Bootseq": - msg.x_rincon_bootseq = value - case "X-Rincon-Household": - msg.x_rincon_household = value - case "X-Rincon-Variant": - msg.x_rincon_variant = value - case "X-Rincon-Wifimode": - msg.x_rincon_wifimode = value - case "Al": - msg.al = value - case "01-Nls": - msg._01_nls = value - case "Content-Length": - msg.content_length = value - case "Bootid.upnp.org": - msg.bootid_upnp_org = value - case "Configid.upnp.org": - msg.configid_upnp_org = value - case "Household.smartspeaker.audio": - msg.household_smartspeaker_audio = value - case "X-Av-Server-Info": - msg.x_av_server_info = value - default: - log.Printf("No support for field `%s' (value `%s')", key, value) - log.Printf("%v", raw) - } - } - return msg -} - -func (this *ssdpDefaultManager) ssdpHandleMessage(raw *ssdpRawMessage) { - switch raw.msgtype { - case ssdpSearch: /*ignore*/ - case ssdpResponse: - this.responseQueue <- this.ssdpHandleResponse(raw) - case ssdpNotify: - this.notifyQueue <- this.ssdpHandleNotify(raw) - } -} - -func (this *ssdpDefaultManager) ssdpDiscoverLoop(conn net.Conn) { - this.readyChan <- 1 - msg := make([]byte, 65536) /*max size of a single UDP packet*/ - defer func() { - recover() - this.closeChan <- 1 - }() - for { - if n, err := conn.Read(msg); nil != err { - panic(err) - } else if raw := this.ssdpParseInput(msg[:n]); nil != raw { - this.ssdpHandleMessage(raw) - } - } -} - -func (this *ssdpDefaultManager) ssdpUnicastDiscoverImpl(ifi *net.Interface, port string) (err error) { - addrs, err := ifi.Addrs() - if nil != err { - return - } else if 0 == len(addrs) { - err = errors.New(fmt.Sprintf("No addresses found for interface %s", ifi.Name)) - return - } - var lip net.IP - for _, addr := range addrs { - if nil != addr.(*net.IPNet).IP.DefaultMask() { - lip = addr.(*net.IPNet).IP - break; - } - } - laddr, err := net.ResolveUDPAddr(ssdpBroadcastVersion, net.JoinHostPort(lip.String(), port)) - if nil != err { - return - } - uc, err := net.ListenUDP(ssdpBroadcastVersion, laddr) - if nil != err { - return - } - this.unicast.addr = laddr - this.unicast.conn = uc - go this.ssdpDiscoverLoop(uc) - <-this.readyChan - return -} - -func (this *ssdpDefaultManager) ssdpMulticastDiscoverImpl(ifi *net.Interface, subscribe bool) (err error) { - maddr, err := net.ResolveUDPAddr(ssdpBroadcastVersion, ssdpBroadcastGroup) - if nil != err { - return - } - this.multicast.addr = maddr - var mc *net.UDPConn - if subscribe { - mc, err = net.ListenMulticastUDP(ssdpBroadcastVersion, ifi, maddr) - if nil != err { - return - } - this.multicast.conn = mc - go this.ssdpDiscoverLoop(mc) - <-this.readyChan - } - return -} - -func (this *ssdpDefaultManager) ssdpQueryMessage(timeout int) (msg *bytes.Buffer) { - msg = new(bytes.Buffer) - msg.WriteString("M-SEARCH * HTTP/1.1\r\n") - msg.WriteString("HosT: 239.255.255.250:1900\r\n") - msg.WriteString("MAN: \"ssdp:discover\"\r\n") - msg.WriteString(fmt.Sprintf("MX: %d\r\n", timeout)) - msg.WriteString("ST: ssdp:all\r\n") - msg.WriteString("USER-AGENT: unix/5.1 UPnP/1.1 crash/1.0\r\n") - msg.WriteString("\r\n") - return -} - -func (this *ssdpDefaultManager) ssdpRequiressdpRootDevice(res *ssdpResource) (rd *ssdpRootDevice) { - var has bool - if rd, has = this.rootDeviceMap[res.location]; !has { - rd = ssdpNewssdpRootDevice(res) - this.rootDeviceMap[rd.location] = rd - } - return -} - -func (this *ssdpDefaultManager) ssdpRequireDevice(res *ssdpResource) (de *ssdpDevice) { - if raw, has := this.deviceMap[res.uuid]; !has { - de = ssdpNewDevice(res) - this.deviceMap[res.uuid] = de - } else { - de = raw.(*ssdpDevice) - } - return -} - -func (this *ssdpDefaultManager) ssdpNotifyssdpRootDevice(res *ssdpResource) { - this.ssdpRequiressdpRootDevice(res) - this.ssdpRequireDevice(res) -} - -func (this *ssdpDefaultManager) ssdpNotifyDevice(res *ssdpResource) { - rd := this.ssdpRequiressdpRootDevice(res) - de := this.ssdpRequireDevice(res) - rd.ssdpRequireEmbeddedDevice(de) -} - -func (this *ssdpDefaultManager) ssdpGetServiceKey(sv *ssdpService) (key ServiceKey) { - uri := sv.uri - if 0 >= len(uri) { - uri = "schemas-upnp-org" - } - key = ServiceKey(fmt.Sprintf("%s-%s", uri, sv.name)) - return -} - -func (this *ssdpDefaultManager) ssdpRequireService(de *ssdpDevice, sv *ssdpService) { - key := this.ssdpGetServiceKey(sv) - if _, has := de.services[key]; !has { - de.services[key] = sv - if _, has := this.serviceMap[key]; !has { - this.serviceMap[key] = make(DeviceMap) - } - this.serviceMap[key][de.UUID()] = de - } -} - -func (this *ssdpDefaultManager) ssdpNotifyService(res *ssdpResource) { - de := this.ssdpRequireDevice(res) - sv := ssdpNewService(res) - this.ssdpRequireService(de, sv) -} - -func (this *ssdpDefaultManager) ssdpNotifyResource(res *ssdpResource) { - switch res.ssdptype { - case ssdpTypessdpRootDevice: - this.ssdpNotifyssdpRootDevice(res) - case ssdpTypeDevice: - this.ssdpNotifyDevice(res) - case ssdpTypeService: - this.ssdpNotifyService(res) - case ssdpTypeHNAP: - /*TODO*/ - case ssdpTypeUUID: - /*TODO ... not sure what to do*/ - default: - log.Fatalf("Unhandled ssdptype %d", res.ssdptype) - } -} - -func (this *ssdpDefaultManager) ssdpIncludessdpRootDevice(ssdpsm *ssdpResponseMessage) { - if n := ssdpRootDeviceUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { - uuid := UUID(n[1]) - res := ssdpNewResource(uuid) - res.ssdpServerDescription = ssdpsm.ssdpServerDescription - res.uuid = uuid - res.location = ssdpsm.location - res.ssdptype = ssdpTypessdpRootDevice - this.ssdpNotifyResource(res) - } else { - log.Printf("Invalid Unique Service Name for upnp:rootdevice: `%s'", ssdpsm.usn) - } -} - -func (this *ssdpDefaultManager) ssdpIncludeService(ssdpsm *ssdpResponseMessage) { - if n := ssdpUPnPServiceUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { - uuid := UUID(n[1]) - res := ssdpNewResource(uuid) - res.ssdpServerDescription = ssdpsm.ssdpServerDescription - res.location = ssdpsm.location - res.ssdptype = ssdpTypeService - res.name = n[2] - var err error - if res.version, err = strconv.ParseInt(n[4], 10, 4); nil != err { - log.Printf("Error in parsing service version `%s'", n[4]) - } - this.ssdpNotifyResource(res) - } else { - log.Printf("Invalid Unique Service Name for UPnP service: `%s'", ssdpsm.usn) - } -} - -func (this *ssdpDefaultManager) ssdpIncludeDevice(ssdpsm *ssdpResponseMessage) { - if n := ssdpUPnPDeviceUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { - uuid := UUID(n[1]) - res := ssdpNewResource(uuid) - res.ssdpServerDescription = ssdpsm.ssdpServerDescription - res.location = ssdpsm.location - res.ssdptype = ssdpTypeDevice - res.name = n[2] - var err error - if res.version, err = strconv.ParseInt(n[4], 10, 4); nil != err { - log.Printf("Error in parsing device version `%s'", n[4]) - } - this.ssdpNotifyResource(res) - } else { - log.Printf("Invalid Unique Service Name for UPnP device: `%s'", ssdpsm.usn) - } -} - -func (this *ssdpDefaultManager) ssdpIncludeUUID(ssdpsm *ssdpResponseMessage) { - if n := ssdpUPnPBareUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { - uuid := UUID(n[1]) - res := ssdpNewResource(uuid) - res.ssdpServerDescription = ssdpsm.ssdpServerDescription - res.location = ssdpsm.location - res.ssdptype = ssdpTypeUUID - this.ssdpNotifyResource(res) - } else { - log.Printf("Invalid Unique Service Name: `%s'", ssdpsm.usn) - } -} - -func (this *ssdpDefaultManager) ssdpIncludeHNAP(ssdpsm *ssdpResponseMessage, name string) { - if n := ssdpUPnPBareUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { - uuid := UUID(n[1]) - res := ssdpNewResource(uuid) - res.ssdpServerDescription = ssdpsm.ssdpServerDescription - res.location = ssdpsm.location - res.ssdptype = ssdpTypeHNAP - res.name = name - this.ssdpNotifyResource(res) - } else { - log.Printf("Invalid Unique Service Name for HNAP: `%s'", ssdpsm.usn) - } -} - -func (this *ssdpDefaultManager) ssdpIncludeOtherService(ssdpsm *ssdpResponseMessage) { - if n := ssdpOtherServiceUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { - uuid := UUID(n[1]) - res := ssdpNewResource(uuid) - res.ssdpServerDescription = ssdpsm.ssdpServerDescription - res.location = ssdpsm.location - res.ssdptype = ssdpTypeService - res.uri = n[2] - res.name = n[3] - var err error - if res.version, err = strconv.ParseInt(n[5], 10, 4); nil != err { - log.Printf("Error in parsing service version `%s'", n[4]) - } - this.ssdpNotifyResource(res) - } else { - log.Printf("Invalid Unique Service Name for third-party service: `%s'", ssdpsm.usn) - } -} - -func (this *ssdpDefaultManager) ssdpIncludeOtherDevice(ssdpsm *ssdpResponseMessage) { - if n := ssdpOtherDeviceUUIDRegex.FindStringSubmatch(ssdpsm.usn); 0 < len(n) { - uuid := UUID(n[1]) - res := ssdpNewResource(uuid) - res.ssdpServerDescription = ssdpsm.ssdpServerDescription - res.location = ssdpsm.location - res.ssdptype = ssdpTypeDevice - res.uri = n[2] - res.name = n[3] - var err error - if res.version, err = strconv.ParseInt(n[5], 10, 4); nil != err { - log.Printf("Error in parsing device version `%s'", n[4]) - } - this.ssdpNotifyResource(res) - } else { - log.Printf("Invalid Unique Service Name for third-party device: `%s'", ssdpsm.usn) - } -} - -func (this *ssdpDefaultManager) ssdpIncludeResponse(msg *ssdpResponseMessage) { - if "upnp:rootdevice" == msg.st { - this.ssdpIncludessdpRootDevice(msg) - } else if m := ssdpUPnPServiceRegex.FindStringSubmatch(msg.st); 0 < len(m) { - this.ssdpIncludeService(msg) - } else if m := ssdpUPnPDeviceRegex.FindStringSubmatch(msg.st); 0 < len(m) { - this.ssdpIncludeDevice(msg) - } else if m := ssdpUPnPUIDRegex.FindStringSubmatch(msg.st); 0 < len(m) { - this.ssdpIncludeUUID(msg) - } else if m := ssdpHNAPRegex.FindStringSubmatch(msg.st); 0 < len(m) { - this.ssdpIncludeHNAP(msg, m[1]) - } else if m := ssdpOtherServiceRegex.FindStringSubmatch(msg.st); 0 < len(m) { - this.ssdpIncludeOtherService(msg) - } else if m := ssdpOtherDeviceRegex.FindStringSubmatch(msg.st); 0 < len(m) { - this.ssdpIncludeOtherDevice(msg) - } else { - log.Printf("Unsupported search term [ST] `%s'", msg.st) - } -} - -func (this *ssdpDefaultManager) ssdpIncludeNotification(msg *ssdpNotifyMessage) { - /*TODO*/ -} - -func (this *ssdpDefaultManager) ssdpSendQuery(timeout int) (err error) { - msg := this.ssdpQueryMessage(timeout) - if _, err = this.unicast.conn.WriteTo(msg.Bytes(), this.multicast.addr); nil != err { - return - } else { - timeout := time.NewTimer(time.Duration(timeout) * time.Second) - done := false - for !done { - select { - case m := <-this.responseQueue: - this.ssdpIncludeResponse(m) - case raw := <-this.notifyQueue: - this.ssdpIncludeNotification(raw) - case <-timeout.C: - done = true - } - } - } - return -} - -func (this *ssdpDefaultManager) ssdpQueryLoop() (err error) { - for i := 0; i < 2; i++ { - if err = this.ssdpSendQuery(3 /*timeout seconds*/); nil != err { - return - } - } - return -} - -func (this *ssdpDefaultManager) ssdpDiscoverImpl(ifiname, port string, subscribe bool) { - ifi, err := net.InterfaceByName(ifiname) - if nil != err { - panic(err) - } else if err = this.ssdpUnicastDiscoverImpl(ifi, port); nil != err { - panic(err) - } else if err = this.ssdpMulticastDiscoverImpl(ifi, subscribe); nil != err { - panic(err) - } else if err = this.ssdpQueryLoop(); nil != err { - panic(err) - } -} diff --git a/vendor/github.com/szatmary/sonos/.gitignore b/vendor/github.com/szatmary/sonos/.gitignore deleted file mode 100644 index f1c181e..0000000 --- a/vendor/github.com/szatmary/sonos/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out diff --git a/vendor/github.com/szatmary/sonos/AVTransport/AVTransport.go b/vendor/github.com/szatmary/sonos/AVTransport/AVTransport.go deleted file mode 100644 index 3e69390..0000000 --- a/vendor/github.com/szatmary/sonos/AVTransport/AVTransport.go +++ /dev/null @@ -1,1379 +0,0 @@ -package avtransport - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:AVTransport:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/MediaRenderer/AVTransport/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/MediaRenderer/AVTransport/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - SetAVTransportURI *SetAVTransportURIArgs `xml:"u:SetAVTransportURI,omitempty"` - SetNextAVTransportURI *SetNextAVTransportURIArgs `xml:"u:SetNextAVTransportURI,omitempty"` - AddURIToQueue *AddURIToQueueArgs `xml:"u:AddURIToQueue,omitempty"` - AddMultipleURIsToQueue *AddMultipleURIsToQueueArgs `xml:"u:AddMultipleURIsToQueue,omitempty"` - ReorderTracksInQueue *ReorderTracksInQueueArgs `xml:"u:ReorderTracksInQueue,omitempty"` - RemoveTrackFromQueue *RemoveTrackFromQueueArgs `xml:"u:RemoveTrackFromQueue,omitempty"` - RemoveTrackRangeFromQueue *RemoveTrackRangeFromQueueArgs `xml:"u:RemoveTrackRangeFromQueue,omitempty"` - RemoveAllTracksFromQueue *RemoveAllTracksFromQueueArgs `xml:"u:RemoveAllTracksFromQueue,omitempty"` - SaveQueue *SaveQueueArgs `xml:"u:SaveQueue,omitempty"` - BackupQueue *BackupQueueArgs `xml:"u:BackupQueue,omitempty"` - CreateSavedQueue *CreateSavedQueueArgs `xml:"u:CreateSavedQueue,omitempty"` - AddURIToSavedQueue *AddURIToSavedQueueArgs `xml:"u:AddURIToSavedQueue,omitempty"` - ReorderTracksInSavedQueue *ReorderTracksInSavedQueueArgs `xml:"u:ReorderTracksInSavedQueue,omitempty"` - GetMediaInfo *GetMediaInfoArgs `xml:"u:GetMediaInfo,omitempty"` - GetTransportInfo *GetTransportInfoArgs `xml:"u:GetTransportInfo,omitempty"` - GetPositionInfo *GetPositionInfoArgs `xml:"u:GetPositionInfo,omitempty"` - GetDeviceCapabilities *GetDeviceCapabilitiesArgs `xml:"u:GetDeviceCapabilities,omitempty"` - GetTransportSettings *GetTransportSettingsArgs `xml:"u:GetTransportSettings,omitempty"` - GetCrossfadeMode *GetCrossfadeModeArgs `xml:"u:GetCrossfadeMode,omitempty"` - Stop *StopArgs `xml:"u:Stop,omitempty"` - Play *PlayArgs `xml:"u:Play,omitempty"` - Pause *PauseArgs `xml:"u:Pause,omitempty"` - Seek *SeekArgs `xml:"u:Seek,omitempty"` - Next *NextArgs `xml:"u:Next,omitempty"` - Previous *PreviousArgs `xml:"u:Previous,omitempty"` - SetPlayMode *SetPlayModeArgs `xml:"u:SetPlayMode,omitempty"` - SetCrossfadeMode *SetCrossfadeModeArgs `xml:"u:SetCrossfadeMode,omitempty"` - NotifyDeletedURI *NotifyDeletedURIArgs `xml:"u:NotifyDeletedURI,omitempty"` - GetCurrentTransportActions *GetCurrentTransportActionsArgs `xml:"u:GetCurrentTransportActions,omitempty"` - BecomeCoordinatorOfStandaloneGroup *BecomeCoordinatorOfStandaloneGroupArgs `xml:"u:BecomeCoordinatorOfStandaloneGroup,omitempty"` - DelegateGroupCoordinationTo *DelegateGroupCoordinationToArgs `xml:"u:DelegateGroupCoordinationTo,omitempty"` - BecomeGroupCoordinator *BecomeGroupCoordinatorArgs `xml:"u:BecomeGroupCoordinator,omitempty"` - BecomeGroupCoordinatorAndSource *BecomeGroupCoordinatorAndSourceArgs `xml:"u:BecomeGroupCoordinatorAndSource,omitempty"` - ChangeCoordinator *ChangeCoordinatorArgs `xml:"u:ChangeCoordinator,omitempty"` - ChangeTransportSettings *ChangeTransportSettingsArgs `xml:"u:ChangeTransportSettings,omitempty"` - ConfigureSleepTimer *ConfigureSleepTimerArgs `xml:"u:ConfigureSleepTimer,omitempty"` - GetRemainingSleepTimerDuration *GetRemainingSleepTimerDurationArgs `xml:"u:GetRemainingSleepTimerDuration,omitempty"` - RunAlarm *RunAlarmArgs `xml:"u:RunAlarm,omitempty"` - StartAutoplay *StartAutoplayArgs `xml:"u:StartAutoplay,omitempty"` - GetRunningAlarmProperties *GetRunningAlarmPropertiesArgs `xml:"u:GetRunningAlarmProperties,omitempty"` - SnoozeAlarm *SnoozeAlarmArgs `xml:"u:SnoozeAlarm,omitempty"` - EndDirectControlSession *EndDirectControlSessionArgs `xml:"u:EndDirectControlSession,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - SetAVTransportURI *SetAVTransportURIResponse `xml:"SetAVTransportURIResponse,omitempty"` - SetNextAVTransportURI *SetNextAVTransportURIResponse `xml:"SetNextAVTransportURIResponse,omitempty"` - AddURIToQueue *AddURIToQueueResponse `xml:"AddURIToQueueResponse,omitempty"` - AddMultipleURIsToQueue *AddMultipleURIsToQueueResponse `xml:"AddMultipleURIsToQueueResponse,omitempty"` - ReorderTracksInQueue *ReorderTracksInQueueResponse `xml:"ReorderTracksInQueueResponse,omitempty"` - RemoveTrackFromQueue *RemoveTrackFromQueueResponse `xml:"RemoveTrackFromQueueResponse,omitempty"` - RemoveTrackRangeFromQueue *RemoveTrackRangeFromQueueResponse `xml:"RemoveTrackRangeFromQueueResponse,omitempty"` - RemoveAllTracksFromQueue *RemoveAllTracksFromQueueResponse `xml:"RemoveAllTracksFromQueueResponse,omitempty"` - SaveQueue *SaveQueueResponse `xml:"SaveQueueResponse,omitempty"` - BackupQueue *BackupQueueResponse `xml:"BackupQueueResponse,omitempty"` - CreateSavedQueue *CreateSavedQueueResponse `xml:"CreateSavedQueueResponse,omitempty"` - AddURIToSavedQueue *AddURIToSavedQueueResponse `xml:"AddURIToSavedQueueResponse,omitempty"` - ReorderTracksInSavedQueue *ReorderTracksInSavedQueueResponse `xml:"ReorderTracksInSavedQueueResponse,omitempty"` - GetMediaInfo *GetMediaInfoResponse `xml:"GetMediaInfoResponse,omitempty"` - GetTransportInfo *GetTransportInfoResponse `xml:"GetTransportInfoResponse,omitempty"` - GetPositionInfo *GetPositionInfoResponse `xml:"GetPositionInfoResponse,omitempty"` - GetDeviceCapabilities *GetDeviceCapabilitiesResponse `xml:"GetDeviceCapabilitiesResponse,omitempty"` - GetTransportSettings *GetTransportSettingsResponse `xml:"GetTransportSettingsResponse,omitempty"` - GetCrossfadeMode *GetCrossfadeModeResponse `xml:"GetCrossfadeModeResponse,omitempty"` - Stop *StopResponse `xml:"StopResponse,omitempty"` - Play *PlayResponse `xml:"PlayResponse,omitempty"` - Pause *PauseResponse `xml:"PauseResponse,omitempty"` - Seek *SeekResponse `xml:"SeekResponse,omitempty"` - Next *NextResponse `xml:"NextResponse,omitempty"` - Previous *PreviousResponse `xml:"PreviousResponse,omitempty"` - SetPlayMode *SetPlayModeResponse `xml:"SetPlayModeResponse,omitempty"` - SetCrossfadeMode *SetCrossfadeModeResponse `xml:"SetCrossfadeModeResponse,omitempty"` - NotifyDeletedURI *NotifyDeletedURIResponse `xml:"NotifyDeletedURIResponse,omitempty"` - GetCurrentTransportActions *GetCurrentTransportActionsResponse `xml:"GetCurrentTransportActionsResponse,omitempty"` - BecomeCoordinatorOfStandaloneGroup *BecomeCoordinatorOfStandaloneGroupResponse `xml:"BecomeCoordinatorOfStandaloneGroupResponse,omitempty"` - DelegateGroupCoordinationTo *DelegateGroupCoordinationToResponse `xml:"DelegateGroupCoordinationToResponse,omitempty"` - BecomeGroupCoordinator *BecomeGroupCoordinatorResponse `xml:"BecomeGroupCoordinatorResponse,omitempty"` - BecomeGroupCoordinatorAndSource *BecomeGroupCoordinatorAndSourceResponse `xml:"BecomeGroupCoordinatorAndSourceResponse,omitempty"` - ChangeCoordinator *ChangeCoordinatorResponse `xml:"ChangeCoordinatorResponse,omitempty"` - ChangeTransportSettings *ChangeTransportSettingsResponse `xml:"ChangeTransportSettingsResponse,omitempty"` - ConfigureSleepTimer *ConfigureSleepTimerResponse `xml:"ConfigureSleepTimerResponse,omitempty"` - GetRemainingSleepTimerDuration *GetRemainingSleepTimerDurationResponse `xml:"GetRemainingSleepTimerDurationResponse,omitempty"` - RunAlarm *RunAlarmResponse `xml:"RunAlarmResponse,omitempty"` - StartAutoplay *StartAutoplayResponse `xml:"StartAutoplayResponse,omitempty"` - GetRunningAlarmProperties *GetRunningAlarmPropertiesResponse `xml:"GetRunningAlarmPropertiesResponse,omitempty"` - SnoozeAlarm *SnoozeAlarmResponse `xml:"SnoozeAlarmResponse,omitempty"` - EndDirectControlSession *EndDirectControlSessionResponse `xml:"EndDirectControlSessionResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type SetAVTransportURIArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - CurrentURI string `xml:"CurrentURI"` - CurrentURIMetaData string `xml:"CurrentURIMetaData"` -} -type SetAVTransportURIResponse struct { -} - -func (s *Service) SetAVTransportURI(httpClient *http.Client, args *SetAVTransportURIArgs) (*SetAVTransportURIResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetAVTransportURI`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetAVTransportURI: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetAVTransportURI == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.SetAVTransportURI()`) - } - - return r.Body.SetAVTransportURI, nil -} - -type SetNextAVTransportURIArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - NextURI string `xml:"NextURI"` - NextURIMetaData string `xml:"NextURIMetaData"` -} -type SetNextAVTransportURIResponse struct { -} - -func (s *Service) SetNextAVTransportURI(httpClient *http.Client, args *SetNextAVTransportURIArgs) (*SetNextAVTransportURIResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetNextAVTransportURI`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetNextAVTransportURI: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetNextAVTransportURI == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.SetNextAVTransportURI()`) - } - - return r.Body.SetNextAVTransportURI, nil -} - -type AddURIToQueueArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - EnqueuedURI string `xml:"EnqueuedURI"` - EnqueuedURIMetaData string `xml:"EnqueuedURIMetaData"` - DesiredFirstTrackNumberEnqueued uint32 `xml:"DesiredFirstTrackNumberEnqueued"` - EnqueueAsNext bool `xml:"EnqueueAsNext"` -} -type AddURIToQueueResponse struct { - FirstTrackNumberEnqueued uint32 `xml:"FirstTrackNumberEnqueued"` - NumTracksAdded uint32 `xml:"NumTracksAdded"` - NewQueueLength uint32 `xml:"NewQueueLength"` -} - -func (s *Service) AddURIToQueue(httpClient *http.Client, args *AddURIToQueueArgs) (*AddURIToQueueResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`AddURIToQueue`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{AddURIToQueue: args}, - }) - if err != nil { - return nil, err - } - if r.Body.AddURIToQueue == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.AddURIToQueue()`) - } - - return r.Body.AddURIToQueue, nil -} - -type AddMultipleURIsToQueueArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - UpdateID uint32 `xml:"UpdateID"` - NumberOfURIs uint32 `xml:"NumberOfURIs"` - EnqueuedURIs string `xml:"EnqueuedURIs"` - EnqueuedURIsMetaData string `xml:"EnqueuedURIsMetaData"` - ContainerURI string `xml:"ContainerURI"` - ContainerMetaData string `xml:"ContainerMetaData"` - DesiredFirstTrackNumberEnqueued uint32 `xml:"DesiredFirstTrackNumberEnqueued"` - EnqueueAsNext bool `xml:"EnqueueAsNext"` -} -type AddMultipleURIsToQueueResponse struct { - FirstTrackNumberEnqueued uint32 `xml:"FirstTrackNumberEnqueued"` - NumTracksAdded uint32 `xml:"NumTracksAdded"` - NewQueueLength uint32 `xml:"NewQueueLength"` - NewUpdateID uint32 `xml:"NewUpdateID"` -} - -func (s *Service) AddMultipleURIsToQueue(httpClient *http.Client, args *AddMultipleURIsToQueueArgs) (*AddMultipleURIsToQueueResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`AddMultipleURIsToQueue`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{AddMultipleURIsToQueue: args}, - }) - if err != nil { - return nil, err - } - if r.Body.AddMultipleURIsToQueue == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.AddMultipleURIsToQueue()`) - } - - return r.Body.AddMultipleURIsToQueue, nil -} - -type ReorderTracksInQueueArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - StartingIndex uint32 `xml:"StartingIndex"` - NumberOfTracks uint32 `xml:"NumberOfTracks"` - InsertBefore uint32 `xml:"InsertBefore"` - UpdateID uint32 `xml:"UpdateID"` -} -type ReorderTracksInQueueResponse struct { -} - -func (s *Service) ReorderTracksInQueue(httpClient *http.Client, args *ReorderTracksInQueueArgs) (*ReorderTracksInQueueResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ReorderTracksInQueue`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ReorderTracksInQueue: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ReorderTracksInQueue == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.ReorderTracksInQueue()`) - } - - return r.Body.ReorderTracksInQueue, nil -} - -type RemoveTrackFromQueueArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - ObjectID string `xml:"ObjectID"` - UpdateID uint32 `xml:"UpdateID"` -} -type RemoveTrackFromQueueResponse struct { -} - -func (s *Service) RemoveTrackFromQueue(httpClient *http.Client, args *RemoveTrackFromQueueArgs) (*RemoveTrackFromQueueResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RemoveTrackFromQueue`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RemoveTrackFromQueue: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RemoveTrackFromQueue == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.RemoveTrackFromQueue()`) - } - - return r.Body.RemoveTrackFromQueue, nil -} - -type RemoveTrackRangeFromQueueArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - UpdateID uint32 `xml:"UpdateID"` - StartingIndex uint32 `xml:"StartingIndex"` - NumberOfTracks uint32 `xml:"NumberOfTracks"` -} -type RemoveTrackRangeFromQueueResponse struct { - NewUpdateID uint32 `xml:"NewUpdateID"` -} - -func (s *Service) RemoveTrackRangeFromQueue(httpClient *http.Client, args *RemoveTrackRangeFromQueueArgs) (*RemoveTrackRangeFromQueueResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RemoveTrackRangeFromQueue`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RemoveTrackRangeFromQueue: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RemoveTrackRangeFromQueue == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.RemoveTrackRangeFromQueue()`) - } - - return r.Body.RemoveTrackRangeFromQueue, nil -} - -type RemoveAllTracksFromQueueArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type RemoveAllTracksFromQueueResponse struct { -} - -func (s *Service) RemoveAllTracksFromQueue(httpClient *http.Client, args *RemoveAllTracksFromQueueArgs) (*RemoveAllTracksFromQueueResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RemoveAllTracksFromQueue`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RemoveAllTracksFromQueue: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RemoveAllTracksFromQueue == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.RemoveAllTracksFromQueue()`) - } - - return r.Body.RemoveAllTracksFromQueue, nil -} - -type SaveQueueArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - Title string `xml:"Title"` - ObjectID string `xml:"ObjectID"` -} -type SaveQueueResponse struct { - AssignedObjectID string `xml:"AssignedObjectID"` -} - -func (s *Service) SaveQueue(httpClient *http.Client, args *SaveQueueArgs) (*SaveQueueResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SaveQueue`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SaveQueue: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SaveQueue == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.SaveQueue()`) - } - - return r.Body.SaveQueue, nil -} - -type BackupQueueArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type BackupQueueResponse struct { -} - -func (s *Service) BackupQueue(httpClient *http.Client, args *BackupQueueArgs) (*BackupQueueResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`BackupQueue`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{BackupQueue: args}, - }) - if err != nil { - return nil, err - } - if r.Body.BackupQueue == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.BackupQueue()`) - } - - return r.Body.BackupQueue, nil -} - -type CreateSavedQueueArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - Title string `xml:"Title"` - EnqueuedURI string `xml:"EnqueuedURI"` - EnqueuedURIMetaData string `xml:"EnqueuedURIMetaData"` -} -type CreateSavedQueueResponse struct { - NumTracksAdded uint32 `xml:"NumTracksAdded"` - NewQueueLength uint32 `xml:"NewQueueLength"` - AssignedObjectID string `xml:"AssignedObjectID"` - NewUpdateID uint32 `xml:"NewUpdateID"` -} - -func (s *Service) CreateSavedQueue(httpClient *http.Client, args *CreateSavedQueueArgs) (*CreateSavedQueueResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`CreateSavedQueue`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{CreateSavedQueue: args}, - }) - if err != nil { - return nil, err - } - if r.Body.CreateSavedQueue == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.CreateSavedQueue()`) - } - - return r.Body.CreateSavedQueue, nil -} - -type AddURIToSavedQueueArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - ObjectID string `xml:"ObjectID"` - UpdateID uint32 `xml:"UpdateID"` - EnqueuedURI string `xml:"EnqueuedURI"` - EnqueuedURIMetaData string `xml:"EnqueuedURIMetaData"` - AddAtIndex uint32 `xml:"AddAtIndex"` -} -type AddURIToSavedQueueResponse struct { - NumTracksAdded uint32 `xml:"NumTracksAdded"` - NewQueueLength uint32 `xml:"NewQueueLength"` - NewUpdateID uint32 `xml:"NewUpdateID"` -} - -func (s *Service) AddURIToSavedQueue(httpClient *http.Client, args *AddURIToSavedQueueArgs) (*AddURIToSavedQueueResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`AddURIToSavedQueue`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{AddURIToSavedQueue: args}, - }) - if err != nil { - return nil, err - } - if r.Body.AddURIToSavedQueue == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.AddURIToSavedQueue()`) - } - - return r.Body.AddURIToSavedQueue, nil -} - -type ReorderTracksInSavedQueueArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - ObjectID string `xml:"ObjectID"` - UpdateID uint32 `xml:"UpdateID"` - TrackList string `xml:"TrackList"` - NewPositionList string `xml:"NewPositionList"` -} -type ReorderTracksInSavedQueueResponse struct { - QueueLengthChange int32 `xml:"QueueLengthChange"` - NewQueueLength uint32 `xml:"NewQueueLength"` - NewUpdateID uint32 `xml:"NewUpdateID"` -} - -func (s *Service) ReorderTracksInSavedQueue(httpClient *http.Client, args *ReorderTracksInSavedQueueArgs) (*ReorderTracksInSavedQueueResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ReorderTracksInSavedQueue`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ReorderTracksInSavedQueue: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ReorderTracksInSavedQueue == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.ReorderTracksInSavedQueue()`) - } - - return r.Body.ReorderTracksInSavedQueue, nil -} - -type GetMediaInfoArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetMediaInfoResponse struct { - NrTracks uint32 `xml:"NrTracks"` - MediaDuration string `xml:"MediaDuration"` - CurrentURI string `xml:"CurrentURI"` - CurrentURIMetaData string `xml:"CurrentURIMetaData"` - NextURI string `xml:"NextURI"` - NextURIMetaData string `xml:"NextURIMetaData"` - PlayMedium string `xml:"PlayMedium"` - RecordMedium string `xml:"RecordMedium"` - WriteStatus string `xml:"WriteStatus"` -} - -func (s *Service) GetMediaInfo(httpClient *http.Client, args *GetMediaInfoArgs) (*GetMediaInfoResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetMediaInfo`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetMediaInfo: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetMediaInfo == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.GetMediaInfo()`) - } - - return r.Body.GetMediaInfo, nil -} - -type GetTransportInfoArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetTransportInfoResponse struct { - CurrentTransportState string `xml:"CurrentTransportState"` - CurrentTransportStatus string `xml:"CurrentTransportStatus"` - CurrentSpeed string `xml:"CurrentSpeed"` -} - -func (s *Service) GetTransportInfo(httpClient *http.Client, args *GetTransportInfoArgs) (*GetTransportInfoResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetTransportInfo`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetTransportInfo: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetTransportInfo == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.GetTransportInfo()`) - } - - return r.Body.GetTransportInfo, nil -} - -type GetPositionInfoArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetPositionInfoResponse struct { - Track uint32 `xml:"Track"` - TrackDuration string `xml:"TrackDuration"` - TrackMetaData string `xml:"TrackMetaData"` - TrackURI string `xml:"TrackURI"` - RelTime string `xml:"RelTime"` - AbsTime string `xml:"AbsTime"` - RelCount int32 `xml:"RelCount"` - AbsCount int32 `xml:"AbsCount"` -} - -func (s *Service) GetPositionInfo(httpClient *http.Client, args *GetPositionInfoArgs) (*GetPositionInfoResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetPositionInfo`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetPositionInfo: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetPositionInfo == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.GetPositionInfo()`) - } - - return r.Body.GetPositionInfo, nil -} - -type GetDeviceCapabilitiesArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetDeviceCapabilitiesResponse struct { - PlayMedia string `xml:"PlayMedia"` - RecMedia string `xml:"RecMedia"` - RecQualityModes string `xml:"RecQualityModes"` -} - -func (s *Service) GetDeviceCapabilities(httpClient *http.Client, args *GetDeviceCapabilitiesArgs) (*GetDeviceCapabilitiesResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetDeviceCapabilities`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetDeviceCapabilities: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetDeviceCapabilities == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.GetDeviceCapabilities()`) - } - - return r.Body.GetDeviceCapabilities, nil -} - -type GetTransportSettingsArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetTransportSettingsResponse struct { - PlayMode string `xml:"PlayMode"` - RecQualityMode string `xml:"RecQualityMode"` -} - -func (s *Service) GetTransportSettings(httpClient *http.Client, args *GetTransportSettingsArgs) (*GetTransportSettingsResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetTransportSettings`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetTransportSettings: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetTransportSettings == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.GetTransportSettings()`) - } - - return r.Body.GetTransportSettings, nil -} - -type GetCrossfadeModeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetCrossfadeModeResponse struct { - CrossfadeMode bool `xml:"CrossfadeMode"` -} - -func (s *Service) GetCrossfadeMode(httpClient *http.Client, args *GetCrossfadeModeArgs) (*GetCrossfadeModeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetCrossfadeMode`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetCrossfadeMode: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetCrossfadeMode == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.GetCrossfadeMode()`) - } - - return r.Body.GetCrossfadeMode, nil -} - -type StopArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type StopResponse struct { -} - -func (s *Service) Stop(httpClient *http.Client, args *StopArgs) (*StopResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Stop`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Stop: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Stop == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.Stop()`) - } - - return r.Body.Stop, nil -} - -type PlayArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: 1 - Speed string `xml:"Speed"` -} -type PlayResponse struct { -} - -func (s *Service) Play(httpClient *http.Client, args *PlayArgs) (*PlayResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Play`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Play: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Play == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.Play()`) - } - - return r.Body.Play, nil -} - -type PauseArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type PauseResponse struct { -} - -func (s *Service) Pause(httpClient *http.Client, args *PauseArgs) (*PauseResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Pause`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Pause: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Pause == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.Pause()`) - } - - return r.Body.Pause, nil -} - -type SeekArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: TRACK_NR - // Allowed Value: REL_TIME - // Allowed Value: TIME_DELTA - Unit string `xml:"Unit"` - Target string `xml:"Target"` -} -type SeekResponse struct { -} - -func (s *Service) Seek(httpClient *http.Client, args *SeekArgs) (*SeekResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Seek`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Seek: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Seek == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.Seek()`) - } - - return r.Body.Seek, nil -} - -type NextArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type NextResponse struct { -} - -func (s *Service) Next(httpClient *http.Client, args *NextArgs) (*NextResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Next`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Next: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Next == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.Next()`) - } - - return r.Body.Next, nil -} - -type PreviousArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type PreviousResponse struct { -} - -func (s *Service) Previous(httpClient *http.Client, args *PreviousArgs) (*PreviousResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Previous`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Previous: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Previous == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.Previous()`) - } - - return r.Body.Previous, nil -} - -type SetPlayModeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: NORMAL - // Allowed Value: REPEAT_ALL - // Allowed Value: REPEAT_ONE - // Allowed Value: SHUFFLE_NOREPEAT - // Allowed Value: SHUFFLE - // Allowed Value: SHUFFLE_REPEAT_ONE - NewPlayMode string `xml:"NewPlayMode"` -} -type SetPlayModeResponse struct { -} - -func (s *Service) SetPlayMode(httpClient *http.Client, args *SetPlayModeArgs) (*SetPlayModeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetPlayMode`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetPlayMode: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetPlayMode == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.SetPlayMode()`) - } - - return r.Body.SetPlayMode, nil -} - -type SetCrossfadeModeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - CrossfadeMode bool `xml:"CrossfadeMode"` -} -type SetCrossfadeModeResponse struct { -} - -func (s *Service) SetCrossfadeMode(httpClient *http.Client, args *SetCrossfadeModeArgs) (*SetCrossfadeModeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetCrossfadeMode`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetCrossfadeMode: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetCrossfadeMode == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.SetCrossfadeMode()`) - } - - return r.Body.SetCrossfadeMode, nil -} - -type NotifyDeletedURIArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - DeletedURI string `xml:"DeletedURI"` -} -type NotifyDeletedURIResponse struct { -} - -func (s *Service) NotifyDeletedURI(httpClient *http.Client, args *NotifyDeletedURIArgs) (*NotifyDeletedURIResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`NotifyDeletedURI`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{NotifyDeletedURI: args}, - }) - if err != nil { - return nil, err - } - if r.Body.NotifyDeletedURI == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.NotifyDeletedURI()`) - } - - return r.Body.NotifyDeletedURI, nil -} - -type GetCurrentTransportActionsArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetCurrentTransportActionsResponse struct { - Actions string `xml:"Actions"` -} - -func (s *Service) GetCurrentTransportActions(httpClient *http.Client, args *GetCurrentTransportActionsArgs) (*GetCurrentTransportActionsResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetCurrentTransportActions`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetCurrentTransportActions: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetCurrentTransportActions == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.GetCurrentTransportActions()`) - } - - return r.Body.GetCurrentTransportActions, nil -} - -type BecomeCoordinatorOfStandaloneGroupArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type BecomeCoordinatorOfStandaloneGroupResponse struct { - DelegatedGroupCoordinatorID string `xml:"DelegatedGroupCoordinatorID"` - NewGroupID string `xml:"NewGroupID"` -} - -func (s *Service) BecomeCoordinatorOfStandaloneGroup(httpClient *http.Client, args *BecomeCoordinatorOfStandaloneGroupArgs) (*BecomeCoordinatorOfStandaloneGroupResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`BecomeCoordinatorOfStandaloneGroup`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{BecomeCoordinatorOfStandaloneGroup: args}, - }) - if err != nil { - return nil, err - } - if r.Body.BecomeCoordinatorOfStandaloneGroup == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.BecomeCoordinatorOfStandaloneGroup()`) - } - - return r.Body.BecomeCoordinatorOfStandaloneGroup, nil -} - -type DelegateGroupCoordinationToArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - NewCoordinator string `xml:"NewCoordinator"` - RejoinGroup bool `xml:"RejoinGroup"` -} -type DelegateGroupCoordinationToResponse struct { -} - -func (s *Service) DelegateGroupCoordinationTo(httpClient *http.Client, args *DelegateGroupCoordinationToArgs) (*DelegateGroupCoordinationToResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`DelegateGroupCoordinationTo`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{DelegateGroupCoordinationTo: args}, - }) - if err != nil { - return nil, err - } - if r.Body.DelegateGroupCoordinationTo == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.DelegateGroupCoordinationTo()`) - } - - return r.Body.DelegateGroupCoordinationTo, nil -} - -type BecomeGroupCoordinatorArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - CurrentCoordinator string `xml:"CurrentCoordinator"` - CurrentGroupID string `xml:"CurrentGroupID"` - OtherMembers string `xml:"OtherMembers"` - TransportSettings string `xml:"TransportSettings"` - CurrentURI string `xml:"CurrentURI"` - CurrentURIMetaData string `xml:"CurrentURIMetaData"` - SleepTimerState string `xml:"SleepTimerState"` - AlarmState string `xml:"AlarmState"` - StreamRestartState string `xml:"StreamRestartState"` - CurrentQueueTrackList string `xml:"CurrentQueueTrackList"` - CurrentVLIState string `xml:"CurrentVLIState"` -} -type BecomeGroupCoordinatorResponse struct { -} - -func (s *Service) BecomeGroupCoordinator(httpClient *http.Client, args *BecomeGroupCoordinatorArgs) (*BecomeGroupCoordinatorResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`BecomeGroupCoordinator`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{BecomeGroupCoordinator: args}, - }) - if err != nil { - return nil, err - } - if r.Body.BecomeGroupCoordinator == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.BecomeGroupCoordinator()`) - } - - return r.Body.BecomeGroupCoordinator, nil -} - -type BecomeGroupCoordinatorAndSourceArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - CurrentCoordinator string `xml:"CurrentCoordinator"` - CurrentGroupID string `xml:"CurrentGroupID"` - OtherMembers string `xml:"OtherMembers"` - CurrentURI string `xml:"CurrentURI"` - CurrentURIMetaData string `xml:"CurrentURIMetaData"` - SleepTimerState string `xml:"SleepTimerState"` - AlarmState string `xml:"AlarmState"` - StreamRestartState string `xml:"StreamRestartState"` - CurrentAVTTrackList string `xml:"CurrentAVTTrackList"` - CurrentQueueTrackList string `xml:"CurrentQueueTrackList"` - CurrentSourceState string `xml:"CurrentSourceState"` - ResumePlayback bool `xml:"ResumePlayback"` -} -type BecomeGroupCoordinatorAndSourceResponse struct { -} - -func (s *Service) BecomeGroupCoordinatorAndSource(httpClient *http.Client, args *BecomeGroupCoordinatorAndSourceArgs) (*BecomeGroupCoordinatorAndSourceResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`BecomeGroupCoordinatorAndSource`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{BecomeGroupCoordinatorAndSource: args}, - }) - if err != nil { - return nil, err - } - if r.Body.BecomeGroupCoordinatorAndSource == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.BecomeGroupCoordinatorAndSource()`) - } - - return r.Body.BecomeGroupCoordinatorAndSource, nil -} - -type ChangeCoordinatorArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - CurrentCoordinator string `xml:"CurrentCoordinator"` - NewCoordinator string `xml:"NewCoordinator"` - NewTransportSettings string `xml:"NewTransportSettings"` - CurrentAVTransportURI string `xml:"CurrentAVTransportURI"` -} -type ChangeCoordinatorResponse struct { -} - -func (s *Service) ChangeCoordinator(httpClient *http.Client, args *ChangeCoordinatorArgs) (*ChangeCoordinatorResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ChangeCoordinator`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ChangeCoordinator: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ChangeCoordinator == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.ChangeCoordinator()`) - } - - return r.Body.ChangeCoordinator, nil -} - -type ChangeTransportSettingsArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - NewTransportSettings string `xml:"NewTransportSettings"` - CurrentAVTransportURI string `xml:"CurrentAVTransportURI"` -} -type ChangeTransportSettingsResponse struct { -} - -func (s *Service) ChangeTransportSettings(httpClient *http.Client, args *ChangeTransportSettingsArgs) (*ChangeTransportSettingsResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ChangeTransportSettings`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ChangeTransportSettings: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ChangeTransportSettings == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.ChangeTransportSettings()`) - } - - return r.Body.ChangeTransportSettings, nil -} - -type ConfigureSleepTimerArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - NewSleepTimerDuration string `xml:"NewSleepTimerDuration"` -} -type ConfigureSleepTimerResponse struct { -} - -func (s *Service) ConfigureSleepTimer(httpClient *http.Client, args *ConfigureSleepTimerArgs) (*ConfigureSleepTimerResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ConfigureSleepTimer`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ConfigureSleepTimer: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ConfigureSleepTimer == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.ConfigureSleepTimer()`) - } - - return r.Body.ConfigureSleepTimer, nil -} - -type GetRemainingSleepTimerDurationArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetRemainingSleepTimerDurationResponse struct { - RemainingSleepTimerDuration string `xml:"RemainingSleepTimerDuration"` - CurrentSleepTimerGeneration uint32 `xml:"CurrentSleepTimerGeneration"` -} - -func (s *Service) GetRemainingSleepTimerDuration(httpClient *http.Client, args *GetRemainingSleepTimerDurationArgs) (*GetRemainingSleepTimerDurationResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetRemainingSleepTimerDuration`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetRemainingSleepTimerDuration: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetRemainingSleepTimerDuration == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.GetRemainingSleepTimerDuration()`) - } - - return r.Body.GetRemainingSleepTimerDuration, nil -} - -type RunAlarmArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - AlarmID uint32 `xml:"AlarmID"` - LoggedStartTime string `xml:"LoggedStartTime"` - Duration string `xml:"Duration"` - ProgramURI string `xml:"ProgramURI"` - ProgramMetaData string `xml:"ProgramMetaData"` - // Allowed Value: NORMAL - // Allowed Value: REPEAT_ALL - // Allowed Value: REPEAT_ONE - // Allowed Value: SHUFFLE_NOREPEAT - // Allowed Value: SHUFFLE - // Allowed Value: SHUFFLE_REPEAT_ONE - PlayMode string `xml:"PlayMode"` - Volume uint16 `xml:"Volume"` - IncludeLinkedZones bool `xml:"IncludeLinkedZones"` -} -type RunAlarmResponse struct { -} - -func (s *Service) RunAlarm(httpClient *http.Client, args *RunAlarmArgs) (*RunAlarmResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RunAlarm`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RunAlarm: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RunAlarm == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.RunAlarm()`) - } - - return r.Body.RunAlarm, nil -} - -type StartAutoplayArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - ProgramURI string `xml:"ProgramURI"` - ProgramMetaData string `xml:"ProgramMetaData"` - Volume uint16 `xml:"Volume"` - IncludeLinkedZones bool `xml:"IncludeLinkedZones"` - ResetVolumeAfter bool `xml:"ResetVolumeAfter"` -} -type StartAutoplayResponse struct { -} - -func (s *Service) StartAutoplay(httpClient *http.Client, args *StartAutoplayArgs) (*StartAutoplayResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`StartAutoplay`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{StartAutoplay: args}, - }) - if err != nil { - return nil, err - } - if r.Body.StartAutoplay == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.StartAutoplay()`) - } - - return r.Body.StartAutoplay, nil -} - -type GetRunningAlarmPropertiesArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetRunningAlarmPropertiesResponse struct { - AlarmID uint32 `xml:"AlarmID"` - GroupID string `xml:"GroupID"` - LoggedStartTime string `xml:"LoggedStartTime"` -} - -func (s *Service) GetRunningAlarmProperties(httpClient *http.Client, args *GetRunningAlarmPropertiesArgs) (*GetRunningAlarmPropertiesResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetRunningAlarmProperties`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetRunningAlarmProperties: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetRunningAlarmProperties == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.GetRunningAlarmProperties()`) - } - - return r.Body.GetRunningAlarmProperties, nil -} - -type SnoozeAlarmArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - Duration string `xml:"Duration"` -} -type SnoozeAlarmResponse struct { -} - -func (s *Service) SnoozeAlarm(httpClient *http.Client, args *SnoozeAlarmArgs) (*SnoozeAlarmResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SnoozeAlarm`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SnoozeAlarm: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SnoozeAlarm == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.SnoozeAlarm()`) - } - - return r.Body.SnoozeAlarm, nil -} - -type EndDirectControlSessionArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type EndDirectControlSessionResponse struct { -} - -func (s *Service) EndDirectControlSession(httpClient *http.Client, args *EndDirectControlSessionArgs) (*EndDirectControlSessionResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`EndDirectControlSession`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{EndDirectControlSession: args}, - }) - if err != nil { - return nil, err - } - if r.Body.EndDirectControlSession == nil { - return nil, errors.New(`unexpected respose from service calling avtransport.EndDirectControlSession()`) - } - - return r.Body.EndDirectControlSession, nil -} diff --git a/vendor/github.com/szatmary/sonos/AlarmClock/AlarmClock.go b/vendor/github.com/szatmary/sonos/AlarmClock/AlarmClock.go deleted file mode 100644 index 9131a55..0000000 --- a/vendor/github.com/szatmary/sonos/AlarmClock/AlarmClock.go +++ /dev/null @@ -1,593 +0,0 @@ -package alarmclock - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:AlarmClock:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/AlarmClock/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/AlarmClock/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - SetFormat *SetFormatArgs `xml:"u:SetFormat,omitempty"` - GetFormat *GetFormatArgs `xml:"u:GetFormat,omitempty"` - SetTimeZone *SetTimeZoneArgs `xml:"u:SetTimeZone,omitempty"` - GetTimeZone *GetTimeZoneArgs `xml:"u:GetTimeZone,omitempty"` - GetTimeZoneAndRule *GetTimeZoneAndRuleArgs `xml:"u:GetTimeZoneAndRule,omitempty"` - GetTimeZoneRule *GetTimeZoneRuleArgs `xml:"u:GetTimeZoneRule,omitempty"` - SetTimeServer *SetTimeServerArgs `xml:"u:SetTimeServer,omitempty"` - GetTimeServer *GetTimeServerArgs `xml:"u:GetTimeServer,omitempty"` - SetTimeNow *SetTimeNowArgs `xml:"u:SetTimeNow,omitempty"` - GetHouseholdTimeAtStamp *GetHouseholdTimeAtStampArgs `xml:"u:GetHouseholdTimeAtStamp,omitempty"` - GetTimeNow *GetTimeNowArgs `xml:"u:GetTimeNow,omitempty"` - CreateAlarm *CreateAlarmArgs `xml:"u:CreateAlarm,omitempty"` - UpdateAlarm *UpdateAlarmArgs `xml:"u:UpdateAlarm,omitempty"` - DestroyAlarm *DestroyAlarmArgs `xml:"u:DestroyAlarm,omitempty"` - ListAlarms *ListAlarmsArgs `xml:"u:ListAlarms,omitempty"` - SetDailyIndexRefreshTime *SetDailyIndexRefreshTimeArgs `xml:"u:SetDailyIndexRefreshTime,omitempty"` - GetDailyIndexRefreshTime *GetDailyIndexRefreshTimeArgs `xml:"u:GetDailyIndexRefreshTime,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - SetFormat *SetFormatResponse `xml:"SetFormatResponse,omitempty"` - GetFormat *GetFormatResponse `xml:"GetFormatResponse,omitempty"` - SetTimeZone *SetTimeZoneResponse `xml:"SetTimeZoneResponse,omitempty"` - GetTimeZone *GetTimeZoneResponse `xml:"GetTimeZoneResponse,omitempty"` - GetTimeZoneAndRule *GetTimeZoneAndRuleResponse `xml:"GetTimeZoneAndRuleResponse,omitempty"` - GetTimeZoneRule *GetTimeZoneRuleResponse `xml:"GetTimeZoneRuleResponse,omitempty"` - SetTimeServer *SetTimeServerResponse `xml:"SetTimeServerResponse,omitempty"` - GetTimeServer *GetTimeServerResponse `xml:"GetTimeServerResponse,omitempty"` - SetTimeNow *SetTimeNowResponse `xml:"SetTimeNowResponse,omitempty"` - GetHouseholdTimeAtStamp *GetHouseholdTimeAtStampResponse `xml:"GetHouseholdTimeAtStampResponse,omitempty"` - GetTimeNow *GetTimeNowResponse `xml:"GetTimeNowResponse,omitempty"` - CreateAlarm *CreateAlarmResponse `xml:"CreateAlarmResponse,omitempty"` - UpdateAlarm *UpdateAlarmResponse `xml:"UpdateAlarmResponse,omitempty"` - DestroyAlarm *DestroyAlarmResponse `xml:"DestroyAlarmResponse,omitempty"` - ListAlarms *ListAlarmsResponse `xml:"ListAlarmsResponse,omitempty"` - SetDailyIndexRefreshTime *SetDailyIndexRefreshTimeResponse `xml:"SetDailyIndexRefreshTimeResponse,omitempty"` - GetDailyIndexRefreshTime *GetDailyIndexRefreshTimeResponse `xml:"GetDailyIndexRefreshTimeResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type SetFormatArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - DesiredTimeFormat string `xml:"DesiredTimeFormat"` - DesiredDateFormat string `xml:"DesiredDateFormat"` -} -type SetFormatResponse struct { -} - -func (s *Service) SetFormat(httpClient *http.Client, args *SetFormatArgs) (*SetFormatResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetFormat`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetFormat: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetFormat == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.SetFormat()`) - } - - return r.Body.SetFormat, nil -} - -type GetFormatArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetFormatResponse struct { - CurrentTimeFormat string `xml:"CurrentTimeFormat"` - CurrentDateFormat string `xml:"CurrentDateFormat"` -} - -func (s *Service) GetFormat(httpClient *http.Client, args *GetFormatArgs) (*GetFormatResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetFormat`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetFormat: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetFormat == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.GetFormat()`) - } - - return r.Body.GetFormat, nil -} - -type SetTimeZoneArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - Index int32 `xml:"Index"` - AutoAdjustDst bool `xml:"AutoAdjustDst"` -} -type SetTimeZoneResponse struct { -} - -func (s *Service) SetTimeZone(httpClient *http.Client, args *SetTimeZoneArgs) (*SetTimeZoneResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetTimeZone`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetTimeZone: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetTimeZone == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.SetTimeZone()`) - } - - return r.Body.SetTimeZone, nil -} - -type GetTimeZoneArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetTimeZoneResponse struct { - Index int32 `xml:"Index"` - AutoAdjustDst bool `xml:"AutoAdjustDst"` -} - -func (s *Service) GetTimeZone(httpClient *http.Client, args *GetTimeZoneArgs) (*GetTimeZoneResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetTimeZone`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetTimeZone: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetTimeZone == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.GetTimeZone()`) - } - - return r.Body.GetTimeZone, nil -} - -type GetTimeZoneAndRuleArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetTimeZoneAndRuleResponse struct { - Index int32 `xml:"Index"` - AutoAdjustDst bool `xml:"AutoAdjustDst"` - CurrentTimeZone string `xml:"CurrentTimeZone"` -} - -func (s *Service) GetTimeZoneAndRule(httpClient *http.Client, args *GetTimeZoneAndRuleArgs) (*GetTimeZoneAndRuleResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetTimeZoneAndRule`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetTimeZoneAndRule: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetTimeZoneAndRule == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.GetTimeZoneAndRule()`) - } - - return r.Body.GetTimeZoneAndRule, nil -} - -type GetTimeZoneRuleArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - Index int32 `xml:"Index"` -} -type GetTimeZoneRuleResponse struct { - TimeZone string `xml:"TimeZone"` -} - -func (s *Service) GetTimeZoneRule(httpClient *http.Client, args *GetTimeZoneRuleArgs) (*GetTimeZoneRuleResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetTimeZoneRule`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetTimeZoneRule: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetTimeZoneRule == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.GetTimeZoneRule()`) - } - - return r.Body.GetTimeZoneRule, nil -} - -type SetTimeServerArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - DesiredTimeServer string `xml:"DesiredTimeServer"` -} -type SetTimeServerResponse struct { -} - -func (s *Service) SetTimeServer(httpClient *http.Client, args *SetTimeServerArgs) (*SetTimeServerResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetTimeServer`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetTimeServer: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetTimeServer == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.SetTimeServer()`) - } - - return r.Body.SetTimeServer, nil -} - -type GetTimeServerArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetTimeServerResponse struct { - CurrentTimeServer string `xml:"CurrentTimeServer"` -} - -func (s *Service) GetTimeServer(httpClient *http.Client, args *GetTimeServerArgs) (*GetTimeServerResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetTimeServer`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetTimeServer: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetTimeServer == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.GetTimeServer()`) - } - - return r.Body.GetTimeServer, nil -} - -type SetTimeNowArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - DesiredTime string `xml:"DesiredTime"` - TimeZoneForDesiredTime string `xml:"TimeZoneForDesiredTime"` -} -type SetTimeNowResponse struct { -} - -func (s *Service) SetTimeNow(httpClient *http.Client, args *SetTimeNowArgs) (*SetTimeNowResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetTimeNow`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetTimeNow: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetTimeNow == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.SetTimeNow()`) - } - - return r.Body.SetTimeNow, nil -} - -type GetHouseholdTimeAtStampArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - TimeStamp string `xml:"TimeStamp"` -} -type GetHouseholdTimeAtStampResponse struct { - HouseholdUTCTime string `xml:"HouseholdUTCTime"` -} - -func (s *Service) GetHouseholdTimeAtStamp(httpClient *http.Client, args *GetHouseholdTimeAtStampArgs) (*GetHouseholdTimeAtStampResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetHouseholdTimeAtStamp`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetHouseholdTimeAtStamp: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetHouseholdTimeAtStamp == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.GetHouseholdTimeAtStamp()`) - } - - return r.Body.GetHouseholdTimeAtStamp, nil -} - -type GetTimeNowArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetTimeNowResponse struct { - CurrentUTCTime string `xml:"CurrentUTCTime"` - CurrentLocalTime string `xml:"CurrentLocalTime"` - CurrentTimeZone string `xml:"CurrentTimeZone"` - CurrentTimeGeneration uint32 `xml:"CurrentTimeGeneration"` -} - -func (s *Service) GetTimeNow(httpClient *http.Client, args *GetTimeNowArgs) (*GetTimeNowResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetTimeNow`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetTimeNow: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetTimeNow == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.GetTimeNow()`) - } - - return r.Body.GetTimeNow, nil -} - -type CreateAlarmArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - StartLocalTime string `xml:"StartLocalTime"` - Duration string `xml:"Duration"` - // Allowed Value: ONCE - // Allowed Value: WEEKDAYS - // Allowed Value: WEEKENDS - // Allowed Value: DAILY - Recurrence string `xml:"Recurrence"` - Enabled bool `xml:"Enabled"` - RoomUUID string `xml:"RoomUUID"` - ProgramURI string `xml:"ProgramURI"` - ProgramMetaData string `xml:"ProgramMetaData"` - // Allowed Value: NORMAL - // Allowed Value: REPEAT_ALL - // Allowed Value: SHUFFLE_NOREPEAT - // Allowed Value: SHUFFLE - PlayMode string `xml:"PlayMode"` - Volume uint16 `xml:"Volume"` - IncludeLinkedZones bool `xml:"IncludeLinkedZones"` -} -type CreateAlarmResponse struct { - AssignedID uint32 `xml:"AssignedID"` -} - -func (s *Service) CreateAlarm(httpClient *http.Client, args *CreateAlarmArgs) (*CreateAlarmResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`CreateAlarm`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{CreateAlarm: args}, - }) - if err != nil { - return nil, err - } - if r.Body.CreateAlarm == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.CreateAlarm()`) - } - - return r.Body.CreateAlarm, nil -} - -type UpdateAlarmArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ID uint32 `xml:"ID"` - StartLocalTime string `xml:"StartLocalTime"` - Duration string `xml:"Duration"` - // Allowed Value: ONCE - // Allowed Value: WEEKDAYS - // Allowed Value: WEEKENDS - // Allowed Value: DAILY - Recurrence string `xml:"Recurrence"` - Enabled bool `xml:"Enabled"` - RoomUUID string `xml:"RoomUUID"` - ProgramURI string `xml:"ProgramURI"` - ProgramMetaData string `xml:"ProgramMetaData"` - // Allowed Value: NORMAL - // Allowed Value: REPEAT_ALL - // Allowed Value: SHUFFLE_NOREPEAT - // Allowed Value: SHUFFLE - PlayMode string `xml:"PlayMode"` - Volume uint16 `xml:"Volume"` - IncludeLinkedZones bool `xml:"IncludeLinkedZones"` -} -type UpdateAlarmResponse struct { -} - -func (s *Service) UpdateAlarm(httpClient *http.Client, args *UpdateAlarmArgs) (*UpdateAlarmResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`UpdateAlarm`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{UpdateAlarm: args}, - }) - if err != nil { - return nil, err - } - if r.Body.UpdateAlarm == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.UpdateAlarm()`) - } - - return r.Body.UpdateAlarm, nil -} - -type DestroyAlarmArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ID uint32 `xml:"ID"` -} -type DestroyAlarmResponse struct { -} - -func (s *Service) DestroyAlarm(httpClient *http.Client, args *DestroyAlarmArgs) (*DestroyAlarmResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`DestroyAlarm`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{DestroyAlarm: args}, - }) - if err != nil { - return nil, err - } - if r.Body.DestroyAlarm == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.DestroyAlarm()`) - } - - return r.Body.DestroyAlarm, nil -} - -type ListAlarmsArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type ListAlarmsResponse struct { - CurrentAlarmList string `xml:"CurrentAlarmList"` - CurrentAlarmListVersion string `xml:"CurrentAlarmListVersion"` -} - -func (s *Service) ListAlarms(httpClient *http.Client, args *ListAlarmsArgs) (*ListAlarmsResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ListAlarms`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ListAlarms: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ListAlarms == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.ListAlarms()`) - } - - return r.Body.ListAlarms, nil -} - -type SetDailyIndexRefreshTimeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - DesiredDailyIndexRefreshTime string `xml:"DesiredDailyIndexRefreshTime"` -} -type SetDailyIndexRefreshTimeResponse struct { -} - -func (s *Service) SetDailyIndexRefreshTime(httpClient *http.Client, args *SetDailyIndexRefreshTimeArgs) (*SetDailyIndexRefreshTimeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetDailyIndexRefreshTime`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetDailyIndexRefreshTime: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetDailyIndexRefreshTime == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.SetDailyIndexRefreshTime()`) - } - - return r.Body.SetDailyIndexRefreshTime, nil -} - -type GetDailyIndexRefreshTimeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetDailyIndexRefreshTimeResponse struct { - CurrentDailyIndexRefreshTime string `xml:"CurrentDailyIndexRefreshTime"` -} - -func (s *Service) GetDailyIndexRefreshTime(httpClient *http.Client, args *GetDailyIndexRefreshTimeArgs) (*GetDailyIndexRefreshTimeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetDailyIndexRefreshTime`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetDailyIndexRefreshTime: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetDailyIndexRefreshTime == nil { - return nil, errors.New(`unexpected respose from service calling alarmclock.GetDailyIndexRefreshTime()`) - } - - return r.Body.GetDailyIndexRefreshTime, nil -} diff --git a/vendor/github.com/szatmary/sonos/ConnectionManager/ConnectionManager.go b/vendor/github.com/szatmary/sonos/ConnectionManager/ConnectionManager.go deleted file mode 100644 index 986ae93..0000000 --- a/vendor/github.com/szatmary/sonos/ConnectionManager/ConnectionManager.go +++ /dev/null @@ -1,174 +0,0 @@ -package connectionmanager - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:ConnectionManager:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/MediaServer/ConnectionManager/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/MediaServer/ConnectionManager/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - GetProtocolInfo *GetProtocolInfoArgs `xml:"u:GetProtocolInfo,omitempty"` - GetCurrentConnectionIDs *GetCurrentConnectionIDsArgs `xml:"u:GetCurrentConnectionIDs,omitempty"` - GetCurrentConnectionInfo *GetCurrentConnectionInfoArgs `xml:"u:GetCurrentConnectionInfo,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - GetProtocolInfo *GetProtocolInfoResponse `xml:"GetProtocolInfoResponse,omitempty"` - GetCurrentConnectionIDs *GetCurrentConnectionIDsResponse `xml:"GetCurrentConnectionIDsResponse,omitempty"` - GetCurrentConnectionInfo *GetCurrentConnectionInfoResponse `xml:"GetCurrentConnectionInfoResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type GetProtocolInfoArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetProtocolInfoResponse struct { - Source string `xml:"Source"` - Sink string `xml:"Sink"` -} - -func (s *Service) GetProtocolInfo(httpClient *http.Client, args *GetProtocolInfoArgs) (*GetProtocolInfoResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetProtocolInfo`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetProtocolInfo: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetProtocolInfo == nil { - return nil, errors.New(`unexpected respose from service calling connectionmanager.GetProtocolInfo()`) - } - - return r.Body.GetProtocolInfo, nil -} - -type GetCurrentConnectionIDsArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetCurrentConnectionIDsResponse struct { - ConnectionIDs string `xml:"ConnectionIDs"` -} - -func (s *Service) GetCurrentConnectionIDs(httpClient *http.Client, args *GetCurrentConnectionIDsArgs) (*GetCurrentConnectionIDsResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetCurrentConnectionIDs`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetCurrentConnectionIDs: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetCurrentConnectionIDs == nil { - return nil, errors.New(`unexpected respose from service calling connectionmanager.GetCurrentConnectionIDs()`) - } - - return r.Body.GetCurrentConnectionIDs, nil -} - -type GetCurrentConnectionInfoArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ConnectionID int32 `xml:"ConnectionID"` -} -type GetCurrentConnectionInfoResponse struct { - RcsID int32 `xml:"RcsID"` - AVTransportID int32 `xml:"AVTransportID"` - ProtocolInfo string `xml:"ProtocolInfo"` - PeerConnectionManager string `xml:"PeerConnectionManager"` - PeerConnectionID int32 `xml:"PeerConnectionID"` - Direction string `xml:"Direction"` - Status string `xml:"Status"` -} - -func (s *Service) GetCurrentConnectionInfo(httpClient *http.Client, args *GetCurrentConnectionInfoArgs) (*GetCurrentConnectionInfoResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetCurrentConnectionInfo`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetCurrentConnectionInfo: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetCurrentConnectionInfo == nil { - return nil, errors.New(`unexpected respose from service calling connectionmanager.GetCurrentConnectionInfo()`) - } - - return r.Body.GetCurrentConnectionInfo, nil -} diff --git a/vendor/github.com/szatmary/sonos/ContentDirectory/ContentDirectory.go b/vendor/github.com/szatmary/sonos/ContentDirectory/ContentDirectory.go deleted file mode 100644 index 87e05c0..0000000 --- a/vendor/github.com/szatmary/sonos/ContentDirectory/ContentDirectory.go +++ /dev/null @@ -1,539 +0,0 @@ -package contentdirectory - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:ContentDirectory:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/MediaServer/ContentDirectory/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/MediaServer/ContentDirectory/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - GetSearchCapabilities *GetSearchCapabilitiesArgs `xml:"u:GetSearchCapabilities,omitempty"` - GetSortCapabilities *GetSortCapabilitiesArgs `xml:"u:GetSortCapabilities,omitempty"` - GetSystemUpdateID *GetSystemUpdateIDArgs `xml:"u:GetSystemUpdateID,omitempty"` - GetAlbumArtistDisplayOption *GetAlbumArtistDisplayOptionArgs `xml:"u:GetAlbumArtistDisplayOption,omitempty"` - GetLastIndexChange *GetLastIndexChangeArgs `xml:"u:GetLastIndexChange,omitempty"` - Browse *BrowseArgs `xml:"u:Browse,omitempty"` - FindPrefix *FindPrefixArgs `xml:"u:FindPrefix,omitempty"` - GetAllPrefixLocations *GetAllPrefixLocationsArgs `xml:"u:GetAllPrefixLocations,omitempty"` - CreateObject *CreateObjectArgs `xml:"u:CreateObject,omitempty"` - UpdateObject *UpdateObjectArgs `xml:"u:UpdateObject,omitempty"` - DestroyObject *DestroyObjectArgs `xml:"u:DestroyObject,omitempty"` - RefreshShareIndex *RefreshShareIndexArgs `xml:"u:RefreshShareIndex,omitempty"` - RequestResort *RequestResortArgs `xml:"u:RequestResort,omitempty"` - GetShareIndexInProgress *GetShareIndexInProgressArgs `xml:"u:GetShareIndexInProgress,omitempty"` - GetBrowseable *GetBrowseableArgs `xml:"u:GetBrowseable,omitempty"` - SetBrowseable *SetBrowseableArgs `xml:"u:SetBrowseable,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - GetSearchCapabilities *GetSearchCapabilitiesResponse `xml:"GetSearchCapabilitiesResponse,omitempty"` - GetSortCapabilities *GetSortCapabilitiesResponse `xml:"GetSortCapabilitiesResponse,omitempty"` - GetSystemUpdateID *GetSystemUpdateIDResponse `xml:"GetSystemUpdateIDResponse,omitempty"` - GetAlbumArtistDisplayOption *GetAlbumArtistDisplayOptionResponse `xml:"GetAlbumArtistDisplayOptionResponse,omitempty"` - GetLastIndexChange *GetLastIndexChangeResponse `xml:"GetLastIndexChangeResponse,omitempty"` - Browse *BrowseResponse `xml:"BrowseResponse,omitempty"` - FindPrefix *FindPrefixResponse `xml:"FindPrefixResponse,omitempty"` - GetAllPrefixLocations *GetAllPrefixLocationsResponse `xml:"GetAllPrefixLocationsResponse,omitempty"` - CreateObject *CreateObjectResponse `xml:"CreateObjectResponse,omitempty"` - UpdateObject *UpdateObjectResponse `xml:"UpdateObjectResponse,omitempty"` - DestroyObject *DestroyObjectResponse `xml:"DestroyObjectResponse,omitempty"` - RefreshShareIndex *RefreshShareIndexResponse `xml:"RefreshShareIndexResponse,omitempty"` - RequestResort *RequestResortResponse `xml:"RequestResortResponse,omitempty"` - GetShareIndexInProgress *GetShareIndexInProgressResponse `xml:"GetShareIndexInProgressResponse,omitempty"` - GetBrowseable *GetBrowseableResponse `xml:"GetBrowseableResponse,omitempty"` - SetBrowseable *SetBrowseableResponse `xml:"SetBrowseableResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type GetSearchCapabilitiesArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetSearchCapabilitiesResponse struct { - SearchCaps string `xml:"SearchCaps"` -} - -func (s *Service) GetSearchCapabilities(httpClient *http.Client, args *GetSearchCapabilitiesArgs) (*GetSearchCapabilitiesResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetSearchCapabilities`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetSearchCapabilities: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetSearchCapabilities == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.GetSearchCapabilities()`) - } - - return r.Body.GetSearchCapabilities, nil -} - -type GetSortCapabilitiesArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetSortCapabilitiesResponse struct { - SortCaps string `xml:"SortCaps"` -} - -func (s *Service) GetSortCapabilities(httpClient *http.Client, args *GetSortCapabilitiesArgs) (*GetSortCapabilitiesResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetSortCapabilities`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetSortCapabilities: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetSortCapabilities == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.GetSortCapabilities()`) - } - - return r.Body.GetSortCapabilities, nil -} - -type GetSystemUpdateIDArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetSystemUpdateIDResponse struct { - Id uint32 `xml:"Id"` -} - -func (s *Service) GetSystemUpdateID(httpClient *http.Client, args *GetSystemUpdateIDArgs) (*GetSystemUpdateIDResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetSystemUpdateID`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetSystemUpdateID: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetSystemUpdateID == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.GetSystemUpdateID()`) - } - - return r.Body.GetSystemUpdateID, nil -} - -type GetAlbumArtistDisplayOptionArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetAlbumArtistDisplayOptionResponse struct { - AlbumArtistDisplayOption string `xml:"AlbumArtistDisplayOption"` -} - -func (s *Service) GetAlbumArtistDisplayOption(httpClient *http.Client, args *GetAlbumArtistDisplayOptionArgs) (*GetAlbumArtistDisplayOptionResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetAlbumArtistDisplayOption`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetAlbumArtistDisplayOption: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetAlbumArtistDisplayOption == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.GetAlbumArtistDisplayOption()`) - } - - return r.Body.GetAlbumArtistDisplayOption, nil -} - -type GetLastIndexChangeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetLastIndexChangeResponse struct { - LastIndexChange string `xml:"LastIndexChange"` -} - -func (s *Service) GetLastIndexChange(httpClient *http.Client, args *GetLastIndexChangeArgs) (*GetLastIndexChangeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetLastIndexChange`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetLastIndexChange: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetLastIndexChange == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.GetLastIndexChange()`) - } - - return r.Body.GetLastIndexChange, nil -} - -type BrowseArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ObjectID string `xml:"ObjectID"` - // Allowed Value: BrowseMetadata - // Allowed Value: BrowseDirectChildren - BrowseFlag string `xml:"BrowseFlag"` - Filter string `xml:"Filter"` - StartingIndex uint32 `xml:"StartingIndex"` - RequestedCount uint32 `xml:"RequestedCount"` - SortCriteria string `xml:"SortCriteria"` -} -type BrowseResponse struct { - Result string `xml:"Result"` - NumberReturned uint32 `xml:"NumberReturned"` - TotalMatches uint32 `xml:"TotalMatches"` - UpdateID uint32 `xml:"UpdateID"` -} - -func (s *Service) Browse(httpClient *http.Client, args *BrowseArgs) (*BrowseResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Browse`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Browse: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Browse == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.Browse()`) - } - - return r.Body.Browse, nil -} - -type FindPrefixArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ObjectID string `xml:"ObjectID"` - Prefix string `xml:"Prefix"` -} -type FindPrefixResponse struct { - StartingIndex uint32 `xml:"StartingIndex"` - UpdateID uint32 `xml:"UpdateID"` -} - -func (s *Service) FindPrefix(httpClient *http.Client, args *FindPrefixArgs) (*FindPrefixResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`FindPrefix`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{FindPrefix: args}, - }) - if err != nil { - return nil, err - } - if r.Body.FindPrefix == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.FindPrefix()`) - } - - return r.Body.FindPrefix, nil -} - -type GetAllPrefixLocationsArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ObjectID string `xml:"ObjectID"` -} -type GetAllPrefixLocationsResponse struct { - TotalPrefixes uint32 `xml:"TotalPrefixes"` - PrefixAndIndexCSV string `xml:"PrefixAndIndexCSV"` - UpdateID uint32 `xml:"UpdateID"` -} - -func (s *Service) GetAllPrefixLocations(httpClient *http.Client, args *GetAllPrefixLocationsArgs) (*GetAllPrefixLocationsResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetAllPrefixLocations`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetAllPrefixLocations: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetAllPrefixLocations == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.GetAllPrefixLocations()`) - } - - return r.Body.GetAllPrefixLocations, nil -} - -type CreateObjectArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ContainerID string `xml:"ContainerID"` - Elements string `xml:"Elements"` -} -type CreateObjectResponse struct { - ObjectID string `xml:"ObjectID"` - Result string `xml:"Result"` -} - -func (s *Service) CreateObject(httpClient *http.Client, args *CreateObjectArgs) (*CreateObjectResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`CreateObject`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{CreateObject: args}, - }) - if err != nil { - return nil, err - } - if r.Body.CreateObject == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.CreateObject()`) - } - - return r.Body.CreateObject, nil -} - -type UpdateObjectArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ObjectID string `xml:"ObjectID"` - CurrentTagValue string `xml:"CurrentTagValue"` - NewTagValue string `xml:"NewTagValue"` -} -type UpdateObjectResponse struct { -} - -func (s *Service) UpdateObject(httpClient *http.Client, args *UpdateObjectArgs) (*UpdateObjectResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`UpdateObject`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{UpdateObject: args}, - }) - if err != nil { - return nil, err - } - if r.Body.UpdateObject == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.UpdateObject()`) - } - - return r.Body.UpdateObject, nil -} - -type DestroyObjectArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ObjectID string `xml:"ObjectID"` -} -type DestroyObjectResponse struct { -} - -func (s *Service) DestroyObject(httpClient *http.Client, args *DestroyObjectArgs) (*DestroyObjectResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`DestroyObject`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{DestroyObject: args}, - }) - if err != nil { - return nil, err - } - if r.Body.DestroyObject == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.DestroyObject()`) - } - - return r.Body.DestroyObject, nil -} - -type RefreshShareIndexArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - AlbumArtistDisplayOption string `xml:"AlbumArtistDisplayOption"` -} -type RefreshShareIndexResponse struct { -} - -func (s *Service) RefreshShareIndex(httpClient *http.Client, args *RefreshShareIndexArgs) (*RefreshShareIndexResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RefreshShareIndex`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RefreshShareIndex: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RefreshShareIndex == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.RefreshShareIndex()`) - } - - return r.Body.RefreshShareIndex, nil -} - -type RequestResortArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - SortOrder string `xml:"SortOrder"` -} -type RequestResortResponse struct { -} - -func (s *Service) RequestResort(httpClient *http.Client, args *RequestResortArgs) (*RequestResortResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RequestResort`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RequestResort: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RequestResort == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.RequestResort()`) - } - - return r.Body.RequestResort, nil -} - -type GetShareIndexInProgressArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetShareIndexInProgressResponse struct { - IsIndexing bool `xml:"IsIndexing"` -} - -func (s *Service) GetShareIndexInProgress(httpClient *http.Client, args *GetShareIndexInProgressArgs) (*GetShareIndexInProgressResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetShareIndexInProgress`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetShareIndexInProgress: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetShareIndexInProgress == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.GetShareIndexInProgress()`) - } - - return r.Body.GetShareIndexInProgress, nil -} - -type GetBrowseableArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetBrowseableResponse struct { - IsBrowseable bool `xml:"IsBrowseable"` -} - -func (s *Service) GetBrowseable(httpClient *http.Client, args *GetBrowseableArgs) (*GetBrowseableResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetBrowseable`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetBrowseable: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetBrowseable == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.GetBrowseable()`) - } - - return r.Body.GetBrowseable, nil -} - -type SetBrowseableArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - Browseable bool `xml:"Browseable"` -} -type SetBrowseableResponse struct { -} - -func (s *Service) SetBrowseable(httpClient *http.Client, args *SetBrowseableArgs) (*SetBrowseableResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetBrowseable`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetBrowseable: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetBrowseable == nil { - return nil, errors.New(`unexpected respose from service calling contentdirectory.SetBrowseable()`) - } - - return r.Body.SetBrowseable, nil -} diff --git a/vendor/github.com/szatmary/sonos/DeviceProperties/DeviceProperties.go b/vendor/github.com/szatmary/sonos/DeviceProperties/DeviceProperties.go deleted file mode 100644 index 76638e5..0000000 --- a/vendor/github.com/szatmary/sonos/DeviceProperties/DeviceProperties.go +++ /dev/null @@ -1,789 +0,0 @@ -package deviceproperties - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:DeviceProperties:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/DeviceProperties/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/DeviceProperties/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - SetLEDState *SetLEDStateArgs `xml:"u:SetLEDState,omitempty"` - GetLEDState *GetLEDStateArgs `xml:"u:GetLEDState,omitempty"` - AddBondedZones *AddBondedZonesArgs `xml:"u:AddBondedZones,omitempty"` - RemoveBondedZones *RemoveBondedZonesArgs `xml:"u:RemoveBondedZones,omitempty"` - CreateStereoPair *CreateStereoPairArgs `xml:"u:CreateStereoPair,omitempty"` - SeparateStereoPair *SeparateStereoPairArgs `xml:"u:SeparateStereoPair,omitempty"` - SetZoneAttributes *SetZoneAttributesArgs `xml:"u:SetZoneAttributes,omitempty"` - GetZoneAttributes *GetZoneAttributesArgs `xml:"u:GetZoneAttributes,omitempty"` - GetHouseholdID *GetHouseholdIDArgs `xml:"u:GetHouseholdID,omitempty"` - GetZoneInfo *GetZoneInfoArgs `xml:"u:GetZoneInfo,omitempty"` - SetAutoplayLinkedZones *SetAutoplayLinkedZonesArgs `xml:"u:SetAutoplayLinkedZones,omitempty"` - GetAutoplayLinkedZones *GetAutoplayLinkedZonesArgs `xml:"u:GetAutoplayLinkedZones,omitempty"` - SetAutoplayRoomUUID *SetAutoplayRoomUUIDArgs `xml:"u:SetAutoplayRoomUUID,omitempty"` - GetAutoplayRoomUUID *GetAutoplayRoomUUIDArgs `xml:"u:GetAutoplayRoomUUID,omitempty"` - SetAutoplayVolume *SetAutoplayVolumeArgs `xml:"u:SetAutoplayVolume,omitempty"` - GetAutoplayVolume *GetAutoplayVolumeArgs `xml:"u:GetAutoplayVolume,omitempty"` - SetUseAutoplayVolume *SetUseAutoplayVolumeArgs `xml:"u:SetUseAutoplayVolume,omitempty"` - GetUseAutoplayVolume *GetUseAutoplayVolumeArgs `xml:"u:GetUseAutoplayVolume,omitempty"` - AddHTSatellite *AddHTSatelliteArgs `xml:"u:AddHTSatellite,omitempty"` - RemoveHTSatellite *RemoveHTSatelliteArgs `xml:"u:RemoveHTSatellite,omitempty"` - EnterConfigMode *EnterConfigModeArgs `xml:"u:EnterConfigMode,omitempty"` - ExitConfigMode *ExitConfigModeArgs `xml:"u:ExitConfigMode,omitempty"` - GetButtonState *GetButtonStateArgs `xml:"u:GetButtonState,omitempty"` - SetButtonLockState *SetButtonLockStateArgs `xml:"u:SetButtonLockState,omitempty"` - GetButtonLockState *GetButtonLockStateArgs `xml:"u:GetButtonLockState,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - SetLEDState *SetLEDStateResponse `xml:"SetLEDStateResponse,omitempty"` - GetLEDState *GetLEDStateResponse `xml:"GetLEDStateResponse,omitempty"` - AddBondedZones *AddBondedZonesResponse `xml:"AddBondedZonesResponse,omitempty"` - RemoveBondedZones *RemoveBondedZonesResponse `xml:"RemoveBondedZonesResponse,omitempty"` - CreateStereoPair *CreateStereoPairResponse `xml:"CreateStereoPairResponse,omitempty"` - SeparateStereoPair *SeparateStereoPairResponse `xml:"SeparateStereoPairResponse,omitempty"` - SetZoneAttributes *SetZoneAttributesResponse `xml:"SetZoneAttributesResponse,omitempty"` - GetZoneAttributes *GetZoneAttributesResponse `xml:"GetZoneAttributesResponse,omitempty"` - GetHouseholdID *GetHouseholdIDResponse `xml:"GetHouseholdIDResponse,omitempty"` - GetZoneInfo *GetZoneInfoResponse `xml:"GetZoneInfoResponse,omitempty"` - SetAutoplayLinkedZones *SetAutoplayLinkedZonesResponse `xml:"SetAutoplayLinkedZonesResponse,omitempty"` - GetAutoplayLinkedZones *GetAutoplayLinkedZonesResponse `xml:"GetAutoplayLinkedZonesResponse,omitempty"` - SetAutoplayRoomUUID *SetAutoplayRoomUUIDResponse `xml:"SetAutoplayRoomUUIDResponse,omitempty"` - GetAutoplayRoomUUID *GetAutoplayRoomUUIDResponse `xml:"GetAutoplayRoomUUIDResponse,omitempty"` - SetAutoplayVolume *SetAutoplayVolumeResponse `xml:"SetAutoplayVolumeResponse,omitempty"` - GetAutoplayVolume *GetAutoplayVolumeResponse `xml:"GetAutoplayVolumeResponse,omitempty"` - SetUseAutoplayVolume *SetUseAutoplayVolumeResponse `xml:"SetUseAutoplayVolumeResponse,omitempty"` - GetUseAutoplayVolume *GetUseAutoplayVolumeResponse `xml:"GetUseAutoplayVolumeResponse,omitempty"` - AddHTSatellite *AddHTSatelliteResponse `xml:"AddHTSatelliteResponse,omitempty"` - RemoveHTSatellite *RemoveHTSatelliteResponse `xml:"RemoveHTSatelliteResponse,omitempty"` - EnterConfigMode *EnterConfigModeResponse `xml:"EnterConfigModeResponse,omitempty"` - ExitConfigMode *ExitConfigModeResponse `xml:"ExitConfigModeResponse,omitempty"` - GetButtonState *GetButtonStateResponse `xml:"GetButtonStateResponse,omitempty"` - SetButtonLockState *SetButtonLockStateResponse `xml:"SetButtonLockStateResponse,omitempty"` - GetButtonLockState *GetButtonLockStateResponse `xml:"GetButtonLockStateResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type SetLEDStateArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - // Allowed Value: On - // Allowed Value: Off - DesiredLEDState string `xml:"DesiredLEDState"` -} -type SetLEDStateResponse struct { -} - -func (s *Service) SetLEDState(httpClient *http.Client, args *SetLEDStateArgs) (*SetLEDStateResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetLEDState`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetLEDState: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetLEDState == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.SetLEDState()`) - } - - return r.Body.SetLEDState, nil -} - -type GetLEDStateArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetLEDStateResponse struct { - CurrentLEDState string `xml:"CurrentLEDState"` -} - -func (s *Service) GetLEDState(httpClient *http.Client, args *GetLEDStateArgs) (*GetLEDStateResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetLEDState`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetLEDState: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetLEDState == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.GetLEDState()`) - } - - return r.Body.GetLEDState, nil -} - -type AddBondedZonesArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ChannelMapSet string `xml:"ChannelMapSet"` -} -type AddBondedZonesResponse struct { -} - -func (s *Service) AddBondedZones(httpClient *http.Client, args *AddBondedZonesArgs) (*AddBondedZonesResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`AddBondedZones`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{AddBondedZones: args}, - }) - if err != nil { - return nil, err - } - if r.Body.AddBondedZones == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.AddBondedZones()`) - } - - return r.Body.AddBondedZones, nil -} - -type RemoveBondedZonesArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ChannelMapSet string `xml:"ChannelMapSet"` - KeepGrouped bool `xml:"KeepGrouped"` -} -type RemoveBondedZonesResponse struct { -} - -func (s *Service) RemoveBondedZones(httpClient *http.Client, args *RemoveBondedZonesArgs) (*RemoveBondedZonesResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RemoveBondedZones`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RemoveBondedZones: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RemoveBondedZones == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.RemoveBondedZones()`) - } - - return r.Body.RemoveBondedZones, nil -} - -type CreateStereoPairArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ChannelMapSet string `xml:"ChannelMapSet"` -} -type CreateStereoPairResponse struct { -} - -func (s *Service) CreateStereoPair(httpClient *http.Client, args *CreateStereoPairArgs) (*CreateStereoPairResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`CreateStereoPair`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{CreateStereoPair: args}, - }) - if err != nil { - return nil, err - } - if r.Body.CreateStereoPair == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.CreateStereoPair()`) - } - - return r.Body.CreateStereoPair, nil -} - -type SeparateStereoPairArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ChannelMapSet string `xml:"ChannelMapSet"` -} -type SeparateStereoPairResponse struct { -} - -func (s *Service) SeparateStereoPair(httpClient *http.Client, args *SeparateStereoPairArgs) (*SeparateStereoPairResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SeparateStereoPair`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SeparateStereoPair: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SeparateStereoPair == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.SeparateStereoPair()`) - } - - return r.Body.SeparateStereoPair, nil -} - -type SetZoneAttributesArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - DesiredZoneName string `xml:"DesiredZoneName"` - DesiredIcon string `xml:"DesiredIcon"` - DesiredConfiguration string `xml:"DesiredConfiguration"` -} -type SetZoneAttributesResponse struct { -} - -func (s *Service) SetZoneAttributes(httpClient *http.Client, args *SetZoneAttributesArgs) (*SetZoneAttributesResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetZoneAttributes`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetZoneAttributes: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetZoneAttributes == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.SetZoneAttributes()`) - } - - return r.Body.SetZoneAttributes, nil -} - -type GetZoneAttributesArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetZoneAttributesResponse struct { - CurrentZoneName string `xml:"CurrentZoneName"` - CurrentIcon string `xml:"CurrentIcon"` - CurrentConfiguration string `xml:"CurrentConfiguration"` -} - -func (s *Service) GetZoneAttributes(httpClient *http.Client, args *GetZoneAttributesArgs) (*GetZoneAttributesResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetZoneAttributes`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetZoneAttributes: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetZoneAttributes == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.GetZoneAttributes()`) - } - - return r.Body.GetZoneAttributes, nil -} - -type GetHouseholdIDArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetHouseholdIDResponse struct { - CurrentHouseholdID string `xml:"CurrentHouseholdID"` -} - -func (s *Service) GetHouseholdID(httpClient *http.Client, args *GetHouseholdIDArgs) (*GetHouseholdIDResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetHouseholdID`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetHouseholdID: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetHouseholdID == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.GetHouseholdID()`) - } - - return r.Body.GetHouseholdID, nil -} - -type GetZoneInfoArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetZoneInfoResponse struct { - SerialNumber string `xml:"SerialNumber"` - SoftwareVersion string `xml:"SoftwareVersion"` - DisplaySoftwareVersion string `xml:"DisplaySoftwareVersion"` - HardwareVersion string `xml:"HardwareVersion"` - IPAddress string `xml:"IPAddress"` - MACAddress string `xml:"MACAddress"` - CopyrightInfo string `xml:"CopyrightInfo"` - ExtraInfo string `xml:"ExtraInfo"` - HTAudioIn uint32 `xml:"HTAudioIn"` - Flags uint32 `xml:"Flags"` -} - -func (s *Service) GetZoneInfo(httpClient *http.Client, args *GetZoneInfoArgs) (*GetZoneInfoResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetZoneInfo`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetZoneInfo: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetZoneInfo == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.GetZoneInfo()`) - } - - return r.Body.GetZoneInfo, nil -} - -type SetAutoplayLinkedZonesArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - IncludeLinkedZones bool `xml:"IncludeLinkedZones"` - Source string `xml:"Source"` -} -type SetAutoplayLinkedZonesResponse struct { -} - -func (s *Service) SetAutoplayLinkedZones(httpClient *http.Client, args *SetAutoplayLinkedZonesArgs) (*SetAutoplayLinkedZonesResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetAutoplayLinkedZones`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetAutoplayLinkedZones: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetAutoplayLinkedZones == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.SetAutoplayLinkedZones()`) - } - - return r.Body.SetAutoplayLinkedZones, nil -} - -type GetAutoplayLinkedZonesArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - Source string `xml:"Source"` -} -type GetAutoplayLinkedZonesResponse struct { - IncludeLinkedZones bool `xml:"IncludeLinkedZones"` -} - -func (s *Service) GetAutoplayLinkedZones(httpClient *http.Client, args *GetAutoplayLinkedZonesArgs) (*GetAutoplayLinkedZonesResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetAutoplayLinkedZones`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetAutoplayLinkedZones: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetAutoplayLinkedZones == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.GetAutoplayLinkedZones()`) - } - - return r.Body.GetAutoplayLinkedZones, nil -} - -type SetAutoplayRoomUUIDArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - RoomUUID string `xml:"RoomUUID"` - Source string `xml:"Source"` -} -type SetAutoplayRoomUUIDResponse struct { -} - -func (s *Service) SetAutoplayRoomUUID(httpClient *http.Client, args *SetAutoplayRoomUUIDArgs) (*SetAutoplayRoomUUIDResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetAutoplayRoomUUID`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetAutoplayRoomUUID: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetAutoplayRoomUUID == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.SetAutoplayRoomUUID()`) - } - - return r.Body.SetAutoplayRoomUUID, nil -} - -type GetAutoplayRoomUUIDArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - Source string `xml:"Source"` -} -type GetAutoplayRoomUUIDResponse struct { - RoomUUID string `xml:"RoomUUID"` -} - -func (s *Service) GetAutoplayRoomUUID(httpClient *http.Client, args *GetAutoplayRoomUUIDArgs) (*GetAutoplayRoomUUIDResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetAutoplayRoomUUID`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetAutoplayRoomUUID: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetAutoplayRoomUUID == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.GetAutoplayRoomUUID()`) - } - - return r.Body.GetAutoplayRoomUUID, nil -} - -type SetAutoplayVolumeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - // Allowed Range: 0 -> 100 step: 1 - Volume uint16 `xml:"Volume"` - Source string `xml:"Source"` -} -type SetAutoplayVolumeResponse struct { -} - -func (s *Service) SetAutoplayVolume(httpClient *http.Client, args *SetAutoplayVolumeArgs) (*SetAutoplayVolumeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetAutoplayVolume`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetAutoplayVolume: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetAutoplayVolume == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.SetAutoplayVolume()`) - } - - return r.Body.SetAutoplayVolume, nil -} - -type GetAutoplayVolumeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - Source string `xml:"Source"` -} -type GetAutoplayVolumeResponse struct { - CurrentVolume uint16 `xml:"CurrentVolume"` -} - -func (s *Service) GetAutoplayVolume(httpClient *http.Client, args *GetAutoplayVolumeArgs) (*GetAutoplayVolumeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetAutoplayVolume`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetAutoplayVolume: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetAutoplayVolume == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.GetAutoplayVolume()`) - } - - return r.Body.GetAutoplayVolume, nil -} - -type SetUseAutoplayVolumeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - UseVolume bool `xml:"UseVolume"` - Source string `xml:"Source"` -} -type SetUseAutoplayVolumeResponse struct { -} - -func (s *Service) SetUseAutoplayVolume(httpClient *http.Client, args *SetUseAutoplayVolumeArgs) (*SetUseAutoplayVolumeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetUseAutoplayVolume`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetUseAutoplayVolume: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetUseAutoplayVolume == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.SetUseAutoplayVolume()`) - } - - return r.Body.SetUseAutoplayVolume, nil -} - -type GetUseAutoplayVolumeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - Source string `xml:"Source"` -} -type GetUseAutoplayVolumeResponse struct { - UseVolume bool `xml:"UseVolume"` -} - -func (s *Service) GetUseAutoplayVolume(httpClient *http.Client, args *GetUseAutoplayVolumeArgs) (*GetUseAutoplayVolumeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetUseAutoplayVolume`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetUseAutoplayVolume: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetUseAutoplayVolume == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.GetUseAutoplayVolume()`) - } - - return r.Body.GetUseAutoplayVolume, nil -} - -type AddHTSatelliteArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - HTSatChanMapSet string `xml:"HTSatChanMapSet"` -} -type AddHTSatelliteResponse struct { -} - -func (s *Service) AddHTSatellite(httpClient *http.Client, args *AddHTSatelliteArgs) (*AddHTSatelliteResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`AddHTSatellite`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{AddHTSatellite: args}, - }) - if err != nil { - return nil, err - } - if r.Body.AddHTSatellite == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.AddHTSatellite()`) - } - - return r.Body.AddHTSatellite, nil -} - -type RemoveHTSatelliteArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - SatRoomUUID string `xml:"SatRoomUUID"` -} -type RemoveHTSatelliteResponse struct { -} - -func (s *Service) RemoveHTSatellite(httpClient *http.Client, args *RemoveHTSatelliteArgs) (*RemoveHTSatelliteResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RemoveHTSatellite`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RemoveHTSatellite: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RemoveHTSatellite == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.RemoveHTSatellite()`) - } - - return r.Body.RemoveHTSatellite, nil -} - -type EnterConfigModeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - Mode string `xml:"Mode"` - Options string `xml:"Options"` -} -type EnterConfigModeResponse struct { - State string `xml:"State"` -} - -func (s *Service) EnterConfigMode(httpClient *http.Client, args *EnterConfigModeArgs) (*EnterConfigModeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`EnterConfigMode`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{EnterConfigMode: args}, - }) - if err != nil { - return nil, err - } - if r.Body.EnterConfigMode == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.EnterConfigMode()`) - } - - return r.Body.EnterConfigMode, nil -} - -type ExitConfigModeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - Options string `xml:"Options"` -} -type ExitConfigModeResponse struct { -} - -func (s *Service) ExitConfigMode(httpClient *http.Client, args *ExitConfigModeArgs) (*ExitConfigModeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ExitConfigMode`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ExitConfigMode: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ExitConfigMode == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.ExitConfigMode()`) - } - - return r.Body.ExitConfigMode, nil -} - -type GetButtonStateArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetButtonStateResponse struct { - State string `xml:"State"` -} - -func (s *Service) GetButtonState(httpClient *http.Client, args *GetButtonStateArgs) (*GetButtonStateResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetButtonState`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetButtonState: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetButtonState == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.GetButtonState()`) - } - - return r.Body.GetButtonState, nil -} - -type SetButtonLockStateArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - // Allowed Value: On - // Allowed Value: Off - DesiredButtonLockState string `xml:"DesiredButtonLockState"` -} -type SetButtonLockStateResponse struct { -} - -func (s *Service) SetButtonLockState(httpClient *http.Client, args *SetButtonLockStateArgs) (*SetButtonLockStateResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetButtonLockState`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetButtonLockState: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetButtonLockState == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.SetButtonLockState()`) - } - - return r.Body.SetButtonLockState, nil -} - -type GetButtonLockStateArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetButtonLockStateResponse struct { - CurrentButtonLockState string `xml:"CurrentButtonLockState"` -} - -func (s *Service) GetButtonLockState(httpClient *http.Client, args *GetButtonLockStateArgs) (*GetButtonLockStateResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetButtonLockState`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetButtonLockState: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetButtonLockState == nil { - return nil, errors.New(`unexpected respose from service calling deviceproperties.GetButtonLockState()`) - } - - return r.Body.GetButtonLockState, nil -} diff --git a/vendor/github.com/szatmary/sonos/GroupManagement/GroupManagement.go b/vendor/github.com/szatmary/sonos/GroupManagement/GroupManagement.go deleted file mode 100644 index 3720577..0000000 --- a/vendor/github.com/szatmary/sonos/GroupManagement/GroupManagement.go +++ /dev/null @@ -1,200 +0,0 @@ -package groupmanagement - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:GroupManagement:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/GroupManagement/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/GroupManagement/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - AddMember *AddMemberArgs `xml:"u:AddMember,omitempty"` - RemoveMember *RemoveMemberArgs `xml:"u:RemoveMember,omitempty"` - ReportTrackBufferingResult *ReportTrackBufferingResultArgs `xml:"u:ReportTrackBufferingResult,omitempty"` - SetSourceAreaIds *SetSourceAreaIdsArgs `xml:"u:SetSourceAreaIds,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - AddMember *AddMemberResponse `xml:"AddMemberResponse,omitempty"` - RemoveMember *RemoveMemberResponse `xml:"RemoveMemberResponse,omitempty"` - ReportTrackBufferingResult *ReportTrackBufferingResultResponse `xml:"ReportTrackBufferingResultResponse,omitempty"` - SetSourceAreaIds *SetSourceAreaIdsResponse `xml:"SetSourceAreaIdsResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type AddMemberArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - MemberID string `xml:"MemberID"` - BootSeq uint32 `xml:"BootSeq"` -} -type AddMemberResponse struct { - CurrentTransportSettings string `xml:"CurrentTransportSettings"` - CurrentURI string `xml:"CurrentURI"` - GroupUUIDJoined string `xml:"GroupUUIDJoined"` - ResetVolumeAfter bool `xml:"ResetVolumeAfter"` - VolumeAVTransportURI string `xml:"VolumeAVTransportURI"` -} - -func (s *Service) AddMember(httpClient *http.Client, args *AddMemberArgs) (*AddMemberResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`AddMember`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{AddMember: args}, - }) - if err != nil { - return nil, err - } - if r.Body.AddMember == nil { - return nil, errors.New(`unexpected respose from service calling groupmanagement.AddMember()`) - } - - return r.Body.AddMember, nil -} - -type RemoveMemberArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - MemberID string `xml:"MemberID"` -} -type RemoveMemberResponse struct { -} - -func (s *Service) RemoveMember(httpClient *http.Client, args *RemoveMemberArgs) (*RemoveMemberResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RemoveMember`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RemoveMember: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RemoveMember == nil { - return nil, errors.New(`unexpected respose from service calling groupmanagement.RemoveMember()`) - } - - return r.Body.RemoveMember, nil -} - -type ReportTrackBufferingResultArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - MemberID string `xml:"MemberID"` - ResultCode int32 `xml:"ResultCode"` -} -type ReportTrackBufferingResultResponse struct { -} - -func (s *Service) ReportTrackBufferingResult(httpClient *http.Client, args *ReportTrackBufferingResultArgs) (*ReportTrackBufferingResultResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ReportTrackBufferingResult`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ReportTrackBufferingResult: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ReportTrackBufferingResult == nil { - return nil, errors.New(`unexpected respose from service calling groupmanagement.ReportTrackBufferingResult()`) - } - - return r.Body.ReportTrackBufferingResult, nil -} - -type SetSourceAreaIdsArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - DesiredSourceAreaIds string `xml:"DesiredSourceAreaIds"` -} -type SetSourceAreaIdsResponse struct { -} - -func (s *Service) SetSourceAreaIds(httpClient *http.Client, args *SetSourceAreaIdsArgs) (*SetSourceAreaIdsResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetSourceAreaIds`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetSourceAreaIds: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetSourceAreaIds == nil { - return nil, errors.New(`unexpected respose from service calling groupmanagement.SetSourceAreaIds()`) - } - - return r.Body.SetSourceAreaIds, nil -} diff --git a/vendor/github.com/szatmary/sonos/GroupRenderingControl/GroupRenderingControl.go b/vendor/github.com/szatmary/sonos/GroupRenderingControl/GroupRenderingControl.go deleted file mode 100644 index bd823b8..0000000 --- a/vendor/github.com/szatmary/sonos/GroupRenderingControl/GroupRenderingControl.go +++ /dev/null @@ -1,254 +0,0 @@ -package grouprenderingcontrol - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:GroupRenderingControl:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/MediaRenderer/GroupRenderingControl/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/MediaRenderer/GroupRenderingControl/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - GetGroupMute *GetGroupMuteArgs `xml:"u:GetGroupMute,omitempty"` - SetGroupMute *SetGroupMuteArgs `xml:"u:SetGroupMute,omitempty"` - GetGroupVolume *GetGroupVolumeArgs `xml:"u:GetGroupVolume,omitempty"` - SetGroupVolume *SetGroupVolumeArgs `xml:"u:SetGroupVolume,omitempty"` - SetRelativeGroupVolume *SetRelativeGroupVolumeArgs `xml:"u:SetRelativeGroupVolume,omitempty"` - SnapshotGroupVolume *SnapshotGroupVolumeArgs `xml:"u:SnapshotGroupVolume,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - GetGroupMute *GetGroupMuteResponse `xml:"GetGroupMuteResponse,omitempty"` - SetGroupMute *SetGroupMuteResponse `xml:"SetGroupMuteResponse,omitempty"` - GetGroupVolume *GetGroupVolumeResponse `xml:"GetGroupVolumeResponse,omitempty"` - SetGroupVolume *SetGroupVolumeResponse `xml:"SetGroupVolumeResponse,omitempty"` - SetRelativeGroupVolume *SetRelativeGroupVolumeResponse `xml:"SetRelativeGroupVolumeResponse,omitempty"` - SnapshotGroupVolume *SnapshotGroupVolumeResponse `xml:"SnapshotGroupVolumeResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type GetGroupMuteArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetGroupMuteResponse struct { - CurrentMute bool `xml:"CurrentMute"` -} - -func (s *Service) GetGroupMute(httpClient *http.Client, args *GetGroupMuteArgs) (*GetGroupMuteResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetGroupMute`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetGroupMute: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetGroupMute == nil { - return nil, errors.New(`unexpected respose from service calling grouprenderingcontrol.GetGroupMute()`) - } - - return r.Body.GetGroupMute, nil -} - -type SetGroupMuteArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - DesiredMute bool `xml:"DesiredMute"` -} -type SetGroupMuteResponse struct { -} - -func (s *Service) SetGroupMute(httpClient *http.Client, args *SetGroupMuteArgs) (*SetGroupMuteResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetGroupMute`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetGroupMute: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetGroupMute == nil { - return nil, errors.New(`unexpected respose from service calling grouprenderingcontrol.SetGroupMute()`) - } - - return r.Body.SetGroupMute, nil -} - -type GetGroupVolumeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetGroupVolumeResponse struct { - CurrentVolume uint16 `xml:"CurrentVolume"` -} - -func (s *Service) GetGroupVolume(httpClient *http.Client, args *GetGroupVolumeArgs) (*GetGroupVolumeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetGroupVolume`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetGroupVolume: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetGroupVolume == nil { - return nil, errors.New(`unexpected respose from service calling grouprenderingcontrol.GetGroupVolume()`) - } - - return r.Body.GetGroupVolume, nil -} - -type SetGroupVolumeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Range: 0 -> 100 step: 1 - DesiredVolume uint16 `xml:"DesiredVolume"` -} -type SetGroupVolumeResponse struct { -} - -func (s *Service) SetGroupVolume(httpClient *http.Client, args *SetGroupVolumeArgs) (*SetGroupVolumeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetGroupVolume`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetGroupVolume: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetGroupVolume == nil { - return nil, errors.New(`unexpected respose from service calling grouprenderingcontrol.SetGroupVolume()`) - } - - return r.Body.SetGroupVolume, nil -} - -type SetRelativeGroupVolumeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - Adjustment int32 `xml:"Adjustment"` -} -type SetRelativeGroupVolumeResponse struct { - NewVolume uint16 `xml:"NewVolume"` -} - -func (s *Service) SetRelativeGroupVolume(httpClient *http.Client, args *SetRelativeGroupVolumeArgs) (*SetRelativeGroupVolumeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetRelativeGroupVolume`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetRelativeGroupVolume: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetRelativeGroupVolume == nil { - return nil, errors.New(`unexpected respose from service calling grouprenderingcontrol.SetRelativeGroupVolume()`) - } - - return r.Body.SetRelativeGroupVolume, nil -} - -type SnapshotGroupVolumeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type SnapshotGroupVolumeResponse struct { -} - -func (s *Service) SnapshotGroupVolume(httpClient *http.Client, args *SnapshotGroupVolumeArgs) (*SnapshotGroupVolumeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SnapshotGroupVolume`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SnapshotGroupVolume: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SnapshotGroupVolume == nil { - return nil, errors.New(`unexpected respose from service calling grouprenderingcontrol.SnapshotGroupVolume()`) - } - - return r.Body.SnapshotGroupVolume, nil -} diff --git a/vendor/github.com/szatmary/sonos/LICENSE b/vendor/github.com/szatmary/sonos/LICENSE deleted file mode 100644 index 1f49b97..0000000 --- a/vendor/github.com/szatmary/sonos/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Matthew Szatmary - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/szatmary/sonos/MusicServices/MusicServices.go b/vendor/github.com/szatmary/sonos/MusicServices/MusicServices.go deleted file mode 100644 index 48d0926..0000000 --- a/vendor/github.com/szatmary/sonos/MusicServices/MusicServices.go +++ /dev/null @@ -1,169 +0,0 @@ -package musicservices - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:MusicServices:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/MusicServices/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/MusicServices/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - GetSessionId *GetSessionIdArgs `xml:"u:GetSessionId,omitempty"` - ListAvailableServices *ListAvailableServicesArgs `xml:"u:ListAvailableServices,omitempty"` - UpdateAvailableServices *UpdateAvailableServicesArgs `xml:"u:UpdateAvailableServices,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - GetSessionId *GetSessionIdResponse `xml:"GetSessionIdResponse,omitempty"` - ListAvailableServices *ListAvailableServicesResponse `xml:"ListAvailableServicesResponse,omitempty"` - UpdateAvailableServices *UpdateAvailableServicesResponse `xml:"UpdateAvailableServicesResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type GetSessionIdArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - ServiceId uint32 `xml:"ServiceId"` - Username string `xml:"Username"` -} -type GetSessionIdResponse struct { - SessionId string `xml:"SessionId"` -} - -func (s *Service) GetSessionId(httpClient *http.Client, args *GetSessionIdArgs) (*GetSessionIdResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetSessionId`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetSessionId: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetSessionId == nil { - return nil, errors.New(`unexpected respose from service calling musicservices.GetSessionId()`) - } - - return r.Body.GetSessionId, nil -} - -type ListAvailableServicesArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type ListAvailableServicesResponse struct { - AvailableServiceDescriptorList string `xml:"AvailableServiceDescriptorList"` - AvailableServiceTypeList string `xml:"AvailableServiceTypeList"` - AvailableServiceListVersion string `xml:"AvailableServiceListVersion"` -} - -func (s *Service) ListAvailableServices(httpClient *http.Client, args *ListAvailableServicesArgs) (*ListAvailableServicesResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ListAvailableServices`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ListAvailableServices: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ListAvailableServices == nil { - return nil, errors.New(`unexpected respose from service calling musicservices.ListAvailableServices()`) - } - - return r.Body.ListAvailableServices, nil -} - -type UpdateAvailableServicesArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type UpdateAvailableServicesResponse struct { -} - -func (s *Service) UpdateAvailableServices(httpClient *http.Client, args *UpdateAvailableServicesArgs) (*UpdateAvailableServicesResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`UpdateAvailableServices`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{UpdateAvailableServices: args}, - }) - if err != nil { - return nil, err - } - if r.Body.UpdateAvailableServices == nil { - return nil, errors.New(`unexpected respose from service calling musicservices.UpdateAvailableServices()`) - } - - return r.Body.UpdateAvailableServices, nil -} diff --git a/vendor/github.com/szatmary/sonos/QPlay/QPlay.go b/vendor/github.com/szatmary/sonos/QPlay/QPlay.go deleted file mode 100644 index 7f4af92..0000000 --- a/vendor/github.com/szatmary/sonos/QPlay/QPlay.go +++ /dev/null @@ -1,115 +0,0 @@ -package qplay - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:QPlay:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/QPlay/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/QPlay/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - QPlayAuth *QPlayAuthArgs `xml:"u:QPlayAuth,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - QPlayAuth *QPlayAuthResponse `xml:"QPlayAuthResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type QPlayAuthArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - Seed string `xml:"Seed"` -} -type QPlayAuthResponse struct { - Code string `xml:"Code"` - MID string `xml:"MID"` - DID string `xml:"DID"` -} - -func (s *Service) QPlayAuth(httpClient *http.Client, args *QPlayAuthArgs) (*QPlayAuthResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`QPlayAuth`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{QPlayAuth: args}, - }) - if err != nil { - return nil, err - } - if r.Body.QPlayAuth == nil { - return nil, errors.New(`unexpected respose from service calling qplay.QPlayAuth()`) - } - - return r.Body.QPlayAuth, nil -} diff --git a/vendor/github.com/szatmary/sonos/Queue/Queue.go b/vendor/github.com/szatmary/sonos/Queue/Queue.go deleted file mode 100644 index 4c6f026..0000000 --- a/vendor/github.com/szatmary/sonos/Queue/Queue.go +++ /dev/null @@ -1,435 +0,0 @@ -package queue - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:Queue:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/MediaRenderer/Queue/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/MediaRenderer/Queue/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - AddURI *AddURIArgs `xml:"u:AddURI,omitempty"` - AddMultipleURIs *AddMultipleURIsArgs `xml:"u:AddMultipleURIs,omitempty"` - AttachQueue *AttachQueueArgs `xml:"u:AttachQueue,omitempty"` - Backup *BackupArgs `xml:"u:Backup,omitempty"` - Browse *BrowseArgs `xml:"u:Browse,omitempty"` - CreateQueue *CreateQueueArgs `xml:"u:CreateQueue,omitempty"` - RemoveAllTracks *RemoveAllTracksArgs `xml:"u:RemoveAllTracks,omitempty"` - RemoveTrackRange *RemoveTrackRangeArgs `xml:"u:RemoveTrackRange,omitempty"` - ReorderTracks *ReorderTracksArgs `xml:"u:ReorderTracks,omitempty"` - ReplaceAllTracks *ReplaceAllTracksArgs `xml:"u:ReplaceAllTracks,omitempty"` - SaveAsSonosPlaylist *SaveAsSonosPlaylistArgs `xml:"u:SaveAsSonosPlaylist,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - AddURI *AddURIResponse `xml:"AddURIResponse,omitempty"` - AddMultipleURIs *AddMultipleURIsResponse `xml:"AddMultipleURIsResponse,omitempty"` - AttachQueue *AttachQueueResponse `xml:"AttachQueueResponse,omitempty"` - Backup *BackupResponse `xml:"BackupResponse,omitempty"` - Browse *BrowseResponse `xml:"BrowseResponse,omitempty"` - CreateQueue *CreateQueueResponse `xml:"CreateQueueResponse,omitempty"` - RemoveAllTracks *RemoveAllTracksResponse `xml:"RemoveAllTracksResponse,omitempty"` - RemoveTrackRange *RemoveTrackRangeResponse `xml:"RemoveTrackRangeResponse,omitempty"` - ReorderTracks *ReorderTracksResponse `xml:"ReorderTracksResponse,omitempty"` - ReplaceAllTracks *ReplaceAllTracksResponse `xml:"ReplaceAllTracksResponse,omitempty"` - SaveAsSonosPlaylist *SaveAsSonosPlaylistResponse `xml:"SaveAsSonosPlaylistResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type AddURIArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - QueueID uint32 `xml:"QueueID"` - UpdateID uint32 `xml:"UpdateID"` - EnqueuedURI string `xml:"EnqueuedURI"` - EnqueuedURIMetaData string `xml:"EnqueuedURIMetaData"` - DesiredFirstTrackNumberEnqueued uint32 `xml:"DesiredFirstTrackNumberEnqueued"` - EnqueueAsNext bool `xml:"EnqueueAsNext"` -} -type AddURIResponse struct { - FirstTrackNumberEnqueued uint32 `xml:"FirstTrackNumberEnqueued"` - NumTracksAdded uint32 `xml:"NumTracksAdded"` - NewQueueLength uint32 `xml:"NewQueueLength"` - NewUpdateID uint32 `xml:"NewUpdateID"` -} - -func (s *Service) AddURI(httpClient *http.Client, args *AddURIArgs) (*AddURIResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`AddURI`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{AddURI: args}, - }) - if err != nil { - return nil, err - } - if r.Body.AddURI == nil { - return nil, errors.New(`unexpected respose from service calling queue.AddURI()`) - } - - return r.Body.AddURI, nil -} - -type AddMultipleURIsArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - QueueID uint32 `xml:"QueueID"` - UpdateID uint32 `xml:"UpdateID"` - ContainerURI string `xml:"ContainerURI"` - ContainerMetaData string `xml:"ContainerMetaData"` - DesiredFirstTrackNumberEnqueued uint32 `xml:"DesiredFirstTrackNumberEnqueued"` - EnqueueAsNext bool `xml:"EnqueueAsNext"` - NumberOfURIs uint32 `xml:"NumberOfURIs"` - EnqueuedURIsAndMetaData string `xml:"EnqueuedURIsAndMetaData"` -} -type AddMultipleURIsResponse struct { - FirstTrackNumberEnqueued uint32 `xml:"FirstTrackNumberEnqueued"` - NumTracksAdded uint32 `xml:"NumTracksAdded"` - NewQueueLength uint32 `xml:"NewQueueLength"` - NewUpdateID uint32 `xml:"NewUpdateID"` -} - -func (s *Service) AddMultipleURIs(httpClient *http.Client, args *AddMultipleURIsArgs) (*AddMultipleURIsResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`AddMultipleURIs`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{AddMultipleURIs: args}, - }) - if err != nil { - return nil, err - } - if r.Body.AddMultipleURIs == nil { - return nil, errors.New(`unexpected respose from service calling queue.AddMultipleURIs()`) - } - - return r.Body.AddMultipleURIs, nil -} - -type AttachQueueArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - QueueOwnerID string `xml:"QueueOwnerID"` -} -type AttachQueueResponse struct { - QueueID uint32 `xml:"QueueID"` - QueueOwnerContext string `xml:"QueueOwnerContext"` -} - -func (s *Service) AttachQueue(httpClient *http.Client, args *AttachQueueArgs) (*AttachQueueResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`AttachQueue`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{AttachQueue: args}, - }) - if err != nil { - return nil, err - } - if r.Body.AttachQueue == nil { - return nil, errors.New(`unexpected respose from service calling queue.AttachQueue()`) - } - - return r.Body.AttachQueue, nil -} - -type BackupArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type BackupResponse struct { -} - -func (s *Service) Backup(httpClient *http.Client, args *BackupArgs) (*BackupResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Backup`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Backup: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Backup == nil { - return nil, errors.New(`unexpected respose from service calling queue.Backup()`) - } - - return r.Body.Backup, nil -} - -type BrowseArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - QueueID uint32 `xml:"QueueID"` - StartingIndex uint32 `xml:"StartingIndex"` - RequestedCount uint32 `xml:"RequestedCount"` -} -type BrowseResponse struct { - Result string `xml:"Result"` - NumberReturned uint32 `xml:"NumberReturned"` - TotalMatches uint32 `xml:"TotalMatches"` - UpdateID uint32 `xml:"UpdateID"` -} - -func (s *Service) Browse(httpClient *http.Client, args *BrowseArgs) (*BrowseResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Browse`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Browse: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Browse == nil { - return nil, errors.New(`unexpected respose from service calling queue.Browse()`) - } - - return r.Body.Browse, nil -} - -type CreateQueueArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - QueueOwnerID string `xml:"QueueOwnerID"` - QueueOwnerContext string `xml:"QueueOwnerContext"` - QueuePolicy string `xml:"QueuePolicy"` -} -type CreateQueueResponse struct { - QueueID uint32 `xml:"QueueID"` -} - -func (s *Service) CreateQueue(httpClient *http.Client, args *CreateQueueArgs) (*CreateQueueResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`CreateQueue`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{CreateQueue: args}, - }) - if err != nil { - return nil, err - } - if r.Body.CreateQueue == nil { - return nil, errors.New(`unexpected respose from service calling queue.CreateQueue()`) - } - - return r.Body.CreateQueue, nil -} - -type RemoveAllTracksArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - QueueID uint32 `xml:"QueueID"` - UpdateID uint32 `xml:"UpdateID"` -} -type RemoveAllTracksResponse struct { - NewUpdateID uint32 `xml:"NewUpdateID"` -} - -func (s *Service) RemoveAllTracks(httpClient *http.Client, args *RemoveAllTracksArgs) (*RemoveAllTracksResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RemoveAllTracks`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RemoveAllTracks: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RemoveAllTracks == nil { - return nil, errors.New(`unexpected respose from service calling queue.RemoveAllTracks()`) - } - - return r.Body.RemoveAllTracks, nil -} - -type RemoveTrackRangeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - QueueID uint32 `xml:"QueueID"` - UpdateID uint32 `xml:"UpdateID"` - StartingIndex uint32 `xml:"StartingIndex"` - NumberOfTracks uint32 `xml:"NumberOfTracks"` -} -type RemoveTrackRangeResponse struct { - NewUpdateID uint32 `xml:"NewUpdateID"` -} - -func (s *Service) RemoveTrackRange(httpClient *http.Client, args *RemoveTrackRangeArgs) (*RemoveTrackRangeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RemoveTrackRange`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RemoveTrackRange: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RemoveTrackRange == nil { - return nil, errors.New(`unexpected respose from service calling queue.RemoveTrackRange()`) - } - - return r.Body.RemoveTrackRange, nil -} - -type ReorderTracksArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - QueueID uint32 `xml:"QueueID"` - StartingIndex uint32 `xml:"StartingIndex"` - NumberOfTracks uint32 `xml:"NumberOfTracks"` - InsertBefore uint32 `xml:"InsertBefore"` - UpdateID uint32 `xml:"UpdateID"` -} -type ReorderTracksResponse struct { - NewUpdateID uint32 `xml:"NewUpdateID"` -} - -func (s *Service) ReorderTracks(httpClient *http.Client, args *ReorderTracksArgs) (*ReorderTracksResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ReorderTracks`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ReorderTracks: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ReorderTracks == nil { - return nil, errors.New(`unexpected respose from service calling queue.ReorderTracks()`) - } - - return r.Body.ReorderTracks, nil -} - -type ReplaceAllTracksArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - QueueID uint32 `xml:"QueueID"` - UpdateID uint32 `xml:"UpdateID"` - ContainerURI string `xml:"ContainerURI"` - ContainerMetaData string `xml:"ContainerMetaData"` - CurrentTrackIndex uint32 `xml:"CurrentTrackIndex"` - NewCurrentTrackIndices string `xml:"NewCurrentTrackIndices"` - NumberOfURIs uint32 `xml:"NumberOfURIs"` - EnqueuedURIsAndMetaData string `xml:"EnqueuedURIsAndMetaData"` -} -type ReplaceAllTracksResponse struct { - NewQueueLength uint32 `xml:"NewQueueLength"` - NewUpdateID uint32 `xml:"NewUpdateID"` -} - -func (s *Service) ReplaceAllTracks(httpClient *http.Client, args *ReplaceAllTracksArgs) (*ReplaceAllTracksResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ReplaceAllTracks`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ReplaceAllTracks: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ReplaceAllTracks == nil { - return nil, errors.New(`unexpected respose from service calling queue.ReplaceAllTracks()`) - } - - return r.Body.ReplaceAllTracks, nil -} - -type SaveAsSonosPlaylistArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - QueueID uint32 `xml:"QueueID"` - Title string `xml:"Title"` - ObjectID string `xml:"ObjectID"` -} -type SaveAsSonosPlaylistResponse struct { - AssignedObjectID string `xml:"AssignedObjectID"` -} - -func (s *Service) SaveAsSonosPlaylist(httpClient *http.Client, args *SaveAsSonosPlaylistArgs) (*SaveAsSonosPlaylistResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SaveAsSonosPlaylist`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SaveAsSonosPlaylist: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SaveAsSonosPlaylist == nil { - return nil, errors.New(`unexpected respose from service calling queue.SaveAsSonosPlaylist()`) - } - - return r.Body.SaveAsSonosPlaylist, nil -} diff --git a/vendor/github.com/szatmary/sonos/README.md b/vendor/github.com/szatmary/sonos/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/vendor/github.com/szatmary/sonos/RenderingControl/RenderingControl.go b/vendor/github.com/szatmary/sonos/RenderingControl/RenderingControl.go deleted file mode 100644 index 8dd78de..0000000 --- a/vendor/github.com/szatmary/sonos/RenderingControl/RenderingControl.go +++ /dev/null @@ -1,940 +0,0 @@ -package renderingcontrol - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:RenderingControl:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/MediaRenderer/RenderingControl/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/MediaRenderer/RenderingControl/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - GetMute *GetMuteArgs `xml:"u:GetMute,omitempty"` - SetMute *SetMuteArgs `xml:"u:SetMute,omitempty"` - ResetBasicEQ *ResetBasicEQArgs `xml:"u:ResetBasicEQ,omitempty"` - ResetExtEQ *ResetExtEQArgs `xml:"u:ResetExtEQ,omitempty"` - GetVolume *GetVolumeArgs `xml:"u:GetVolume,omitempty"` - SetVolume *SetVolumeArgs `xml:"u:SetVolume,omitempty"` - SetRelativeVolume *SetRelativeVolumeArgs `xml:"u:SetRelativeVolume,omitempty"` - GetVolumeDB *GetVolumeDBArgs `xml:"u:GetVolumeDB,omitempty"` - SetVolumeDB *SetVolumeDBArgs `xml:"u:SetVolumeDB,omitempty"` - GetVolumeDBRange *GetVolumeDBRangeArgs `xml:"u:GetVolumeDBRange,omitempty"` - GetBass *GetBassArgs `xml:"u:GetBass,omitempty"` - SetBass *SetBassArgs `xml:"u:SetBass,omitempty"` - GetTreble *GetTrebleArgs `xml:"u:GetTreble,omitempty"` - SetTreble *SetTrebleArgs `xml:"u:SetTreble,omitempty"` - GetEQ *GetEQArgs `xml:"u:GetEQ,omitempty"` - SetEQ *SetEQArgs `xml:"u:SetEQ,omitempty"` - GetLoudness *GetLoudnessArgs `xml:"u:GetLoudness,omitempty"` - SetLoudness *SetLoudnessArgs `xml:"u:SetLoudness,omitempty"` - GetSupportsOutputFixed *GetSupportsOutputFixedArgs `xml:"u:GetSupportsOutputFixed,omitempty"` - GetOutputFixed *GetOutputFixedArgs `xml:"u:GetOutputFixed,omitempty"` - SetOutputFixed *SetOutputFixedArgs `xml:"u:SetOutputFixed,omitempty"` - GetHeadphoneConnected *GetHeadphoneConnectedArgs `xml:"u:GetHeadphoneConnected,omitempty"` - RampToVolume *RampToVolumeArgs `xml:"u:RampToVolume,omitempty"` - RestoreVolumePriorToRamp *RestoreVolumePriorToRampArgs `xml:"u:RestoreVolumePriorToRamp,omitempty"` - SetChannelMap *SetChannelMapArgs `xml:"u:SetChannelMap,omitempty"` - SetRoomCalibrationX *SetRoomCalibrationXArgs `xml:"u:SetRoomCalibrationX,omitempty"` - GetRoomCalibrationStatus *GetRoomCalibrationStatusArgs `xml:"u:GetRoomCalibrationStatus,omitempty"` - SetRoomCalibrationStatus *SetRoomCalibrationStatusArgs `xml:"u:SetRoomCalibrationStatus,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - GetMute *GetMuteResponse `xml:"GetMuteResponse,omitempty"` - SetMute *SetMuteResponse `xml:"SetMuteResponse,omitempty"` - ResetBasicEQ *ResetBasicEQResponse `xml:"ResetBasicEQResponse,omitempty"` - ResetExtEQ *ResetExtEQResponse `xml:"ResetExtEQResponse,omitempty"` - GetVolume *GetVolumeResponse `xml:"GetVolumeResponse,omitempty"` - SetVolume *SetVolumeResponse `xml:"SetVolumeResponse,omitempty"` - SetRelativeVolume *SetRelativeVolumeResponse `xml:"SetRelativeVolumeResponse,omitempty"` - GetVolumeDB *GetVolumeDBResponse `xml:"GetVolumeDBResponse,omitempty"` - SetVolumeDB *SetVolumeDBResponse `xml:"SetVolumeDBResponse,omitempty"` - GetVolumeDBRange *GetVolumeDBRangeResponse `xml:"GetVolumeDBRangeResponse,omitempty"` - GetBass *GetBassResponse `xml:"GetBassResponse,omitempty"` - SetBass *SetBassResponse `xml:"SetBassResponse,omitempty"` - GetTreble *GetTrebleResponse `xml:"GetTrebleResponse,omitempty"` - SetTreble *SetTrebleResponse `xml:"SetTrebleResponse,omitempty"` - GetEQ *GetEQResponse `xml:"GetEQResponse,omitempty"` - SetEQ *SetEQResponse `xml:"SetEQResponse,omitempty"` - GetLoudness *GetLoudnessResponse `xml:"GetLoudnessResponse,omitempty"` - SetLoudness *SetLoudnessResponse `xml:"SetLoudnessResponse,omitempty"` - GetSupportsOutputFixed *GetSupportsOutputFixedResponse `xml:"GetSupportsOutputFixedResponse,omitempty"` - GetOutputFixed *GetOutputFixedResponse `xml:"GetOutputFixedResponse,omitempty"` - SetOutputFixed *SetOutputFixedResponse `xml:"SetOutputFixedResponse,omitempty"` - GetHeadphoneConnected *GetHeadphoneConnectedResponse `xml:"GetHeadphoneConnectedResponse,omitempty"` - RampToVolume *RampToVolumeResponse `xml:"RampToVolumeResponse,omitempty"` - RestoreVolumePriorToRamp *RestoreVolumePriorToRampResponse `xml:"RestoreVolumePriorToRampResponse,omitempty"` - SetChannelMap *SetChannelMapResponse `xml:"SetChannelMapResponse,omitempty"` - SetRoomCalibrationX *SetRoomCalibrationXResponse `xml:"SetRoomCalibrationXResponse,omitempty"` - GetRoomCalibrationStatus *GetRoomCalibrationStatusResponse `xml:"GetRoomCalibrationStatusResponse,omitempty"` - SetRoomCalibrationStatus *SetRoomCalibrationStatusResponse `xml:"SetRoomCalibrationStatusResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type GetMuteArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: Master - // Allowed Value: LF - // Allowed Value: RF - // Allowed Value: SpeakerOnly - Channel string `xml:"Channel"` -} -type GetMuteResponse struct { - CurrentMute bool `xml:"CurrentMute"` -} - -func (s *Service) GetMute(httpClient *http.Client, args *GetMuteArgs) (*GetMuteResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetMute`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetMute: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetMute == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetMute()`) - } - - return r.Body.GetMute, nil -} - -type SetMuteArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: Master - // Allowed Value: LF - // Allowed Value: RF - // Allowed Value: SpeakerOnly - Channel string `xml:"Channel"` - DesiredMute bool `xml:"DesiredMute"` -} -type SetMuteResponse struct { -} - -func (s *Service) SetMute(httpClient *http.Client, args *SetMuteArgs) (*SetMuteResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetMute`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetMute: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetMute == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetMute()`) - } - - return r.Body.SetMute, nil -} - -type ResetBasicEQArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type ResetBasicEQResponse struct { - Bass int16 `xml:"Bass"` - Treble int16 `xml:"Treble"` - Loudness bool `xml:"Loudness"` - LeftVolume uint16 `xml:"LeftVolume"` - RightVolume uint16 `xml:"RightVolume"` -} - -func (s *Service) ResetBasicEQ(httpClient *http.Client, args *ResetBasicEQArgs) (*ResetBasicEQResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ResetBasicEQ`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ResetBasicEQ: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ResetBasicEQ == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.ResetBasicEQ()`) - } - - return r.Body.ResetBasicEQ, nil -} - -type ResetExtEQArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - EQType string `xml:"EQType"` -} -type ResetExtEQResponse struct { -} - -func (s *Service) ResetExtEQ(httpClient *http.Client, args *ResetExtEQArgs) (*ResetExtEQResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ResetExtEQ`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ResetExtEQ: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ResetExtEQ == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.ResetExtEQ()`) - } - - return r.Body.ResetExtEQ, nil -} - -type GetVolumeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: Master - // Allowed Value: LF - // Allowed Value: RF - Channel string `xml:"Channel"` -} -type GetVolumeResponse struct { - CurrentVolume uint16 `xml:"CurrentVolume"` -} - -func (s *Service) GetVolume(httpClient *http.Client, args *GetVolumeArgs) (*GetVolumeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetVolume`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetVolume: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetVolume == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetVolume()`) - } - - return r.Body.GetVolume, nil -} - -type SetVolumeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: Master - // Allowed Value: LF - // Allowed Value: RF - Channel string `xml:"Channel"` - // Allowed Range: 0 -> 100 step: 1 - DesiredVolume uint16 `xml:"DesiredVolume"` -} -type SetVolumeResponse struct { -} - -func (s *Service) SetVolume(httpClient *http.Client, args *SetVolumeArgs) (*SetVolumeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetVolume`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetVolume: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetVolume == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetVolume()`) - } - - return r.Body.SetVolume, nil -} - -type SetRelativeVolumeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: Master - // Allowed Value: LF - // Allowed Value: RF - Channel string `xml:"Channel"` - Adjustment int32 `xml:"Adjustment"` -} -type SetRelativeVolumeResponse struct { - NewVolume uint16 `xml:"NewVolume"` -} - -func (s *Service) SetRelativeVolume(httpClient *http.Client, args *SetRelativeVolumeArgs) (*SetRelativeVolumeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetRelativeVolume`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetRelativeVolume: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetRelativeVolume == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetRelativeVolume()`) - } - - return r.Body.SetRelativeVolume, nil -} - -type GetVolumeDBArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: Master - // Allowed Value: LF - // Allowed Value: RF - Channel string `xml:"Channel"` -} -type GetVolumeDBResponse struct { - CurrentVolume int16 `xml:"CurrentVolume"` -} - -func (s *Service) GetVolumeDB(httpClient *http.Client, args *GetVolumeDBArgs) (*GetVolumeDBResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetVolumeDB`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetVolumeDB: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetVolumeDB == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetVolumeDB()`) - } - - return r.Body.GetVolumeDB, nil -} - -type SetVolumeDBArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: Master - // Allowed Value: LF - // Allowed Value: RF - Channel string `xml:"Channel"` - DesiredVolume int16 `xml:"DesiredVolume"` -} -type SetVolumeDBResponse struct { -} - -func (s *Service) SetVolumeDB(httpClient *http.Client, args *SetVolumeDBArgs) (*SetVolumeDBResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetVolumeDB`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetVolumeDB: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetVolumeDB == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetVolumeDB()`) - } - - return r.Body.SetVolumeDB, nil -} - -type GetVolumeDBRangeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: Master - // Allowed Value: LF - // Allowed Value: RF - Channel string `xml:"Channel"` -} -type GetVolumeDBRangeResponse struct { - MinValue int16 `xml:"MinValue"` - MaxValue int16 `xml:"MaxValue"` -} - -func (s *Service) GetVolumeDBRange(httpClient *http.Client, args *GetVolumeDBRangeArgs) (*GetVolumeDBRangeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetVolumeDBRange`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetVolumeDBRange: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetVolumeDBRange == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetVolumeDBRange()`) - } - - return r.Body.GetVolumeDBRange, nil -} - -type GetBassArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetBassResponse struct { - CurrentBass int16 `xml:"CurrentBass"` -} - -func (s *Service) GetBass(httpClient *http.Client, args *GetBassArgs) (*GetBassResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetBass`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetBass: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetBass == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetBass()`) - } - - return r.Body.GetBass, nil -} - -type SetBassArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Range: -10 -> 10 step: 1 - DesiredBass int16 `xml:"DesiredBass"` -} -type SetBassResponse struct { -} - -func (s *Service) SetBass(httpClient *http.Client, args *SetBassArgs) (*SetBassResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetBass`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetBass: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetBass == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetBass()`) - } - - return r.Body.SetBass, nil -} - -type GetTrebleArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetTrebleResponse struct { - CurrentTreble int16 `xml:"CurrentTreble"` -} - -func (s *Service) GetTreble(httpClient *http.Client, args *GetTrebleArgs) (*GetTrebleResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetTreble`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetTreble: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetTreble == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetTreble()`) - } - - return r.Body.GetTreble, nil -} - -type SetTrebleArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Range: -10 -> 10 step: 1 - DesiredTreble int16 `xml:"DesiredTreble"` -} -type SetTrebleResponse struct { -} - -func (s *Service) SetTreble(httpClient *http.Client, args *SetTrebleArgs) (*SetTrebleResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetTreble`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetTreble: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetTreble == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetTreble()`) - } - - return r.Body.SetTreble, nil -} - -type GetEQArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - EQType string `xml:"EQType"` -} -type GetEQResponse struct { - CurrentValue int16 `xml:"CurrentValue"` -} - -func (s *Service) GetEQ(httpClient *http.Client, args *GetEQArgs) (*GetEQResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetEQ`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetEQ: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetEQ == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetEQ()`) - } - - return r.Body.GetEQ, nil -} - -type SetEQArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - EQType string `xml:"EQType"` - DesiredValue int16 `xml:"DesiredValue"` -} -type SetEQResponse struct { -} - -func (s *Service) SetEQ(httpClient *http.Client, args *SetEQArgs) (*SetEQResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetEQ`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetEQ: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetEQ == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetEQ()`) - } - - return r.Body.SetEQ, nil -} - -type GetLoudnessArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: Master - // Allowed Value: LF - // Allowed Value: RF - Channel string `xml:"Channel"` -} -type GetLoudnessResponse struct { - CurrentLoudness bool `xml:"CurrentLoudness"` -} - -func (s *Service) GetLoudness(httpClient *http.Client, args *GetLoudnessArgs) (*GetLoudnessResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetLoudness`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetLoudness: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetLoudness == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetLoudness()`) - } - - return r.Body.GetLoudness, nil -} - -type SetLoudnessArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: Master - // Allowed Value: LF - // Allowed Value: RF - Channel string `xml:"Channel"` - DesiredLoudness bool `xml:"DesiredLoudness"` -} -type SetLoudnessResponse struct { -} - -func (s *Service) SetLoudness(httpClient *http.Client, args *SetLoudnessArgs) (*SetLoudnessResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetLoudness`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetLoudness: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetLoudness == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetLoudness()`) - } - - return r.Body.SetLoudness, nil -} - -type GetSupportsOutputFixedArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetSupportsOutputFixedResponse struct { - CurrentSupportsFixed bool `xml:"CurrentSupportsFixed"` -} - -func (s *Service) GetSupportsOutputFixed(httpClient *http.Client, args *GetSupportsOutputFixedArgs) (*GetSupportsOutputFixedResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetSupportsOutputFixed`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetSupportsOutputFixed: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetSupportsOutputFixed == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetSupportsOutputFixed()`) - } - - return r.Body.GetSupportsOutputFixed, nil -} - -type GetOutputFixedArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetOutputFixedResponse struct { - CurrentFixed bool `xml:"CurrentFixed"` -} - -func (s *Service) GetOutputFixed(httpClient *http.Client, args *GetOutputFixedArgs) (*GetOutputFixedResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetOutputFixed`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetOutputFixed: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetOutputFixed == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetOutputFixed()`) - } - - return r.Body.GetOutputFixed, nil -} - -type SetOutputFixedArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - DesiredFixed bool `xml:"DesiredFixed"` -} -type SetOutputFixedResponse struct { -} - -func (s *Service) SetOutputFixed(httpClient *http.Client, args *SetOutputFixedArgs) (*SetOutputFixedResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetOutputFixed`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetOutputFixed: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetOutputFixed == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetOutputFixed()`) - } - - return r.Body.SetOutputFixed, nil -} - -type GetHeadphoneConnectedArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetHeadphoneConnectedResponse struct { - CurrentHeadphoneConnected bool `xml:"CurrentHeadphoneConnected"` -} - -func (s *Service) GetHeadphoneConnected(httpClient *http.Client, args *GetHeadphoneConnectedArgs) (*GetHeadphoneConnectedResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetHeadphoneConnected`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetHeadphoneConnected: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetHeadphoneConnected == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetHeadphoneConnected()`) - } - - return r.Body.GetHeadphoneConnected, nil -} - -type RampToVolumeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: Master - // Allowed Value: LF - // Allowed Value: RF - Channel string `xml:"Channel"` - // Allowed Value: SLEEP_TIMER_RAMP_TYPE - // Allowed Value: ALARM_RAMP_TYPE - // Allowed Value: AUTOPLAY_RAMP_TYPE - RampType string `xml:"RampType"` - // Allowed Range: 0 -> 100 step: 1 - DesiredVolume uint16 `xml:"DesiredVolume"` - ResetVolumeAfter bool `xml:"ResetVolumeAfter"` - ProgramURI string `xml:"ProgramURI"` -} -type RampToVolumeResponse struct { - RampTime uint32 `xml:"RampTime"` -} - -func (s *Service) RampToVolume(httpClient *http.Client, args *RampToVolumeArgs) (*RampToVolumeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RampToVolume`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RampToVolume: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RampToVolume == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.RampToVolume()`) - } - - return r.Body.RampToVolume, nil -} - -type RestoreVolumePriorToRampArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - // Allowed Value: Master - // Allowed Value: LF - // Allowed Value: RF - Channel string `xml:"Channel"` -} -type RestoreVolumePriorToRampResponse struct { -} - -func (s *Service) RestoreVolumePriorToRamp(httpClient *http.Client, args *RestoreVolumePriorToRampArgs) (*RestoreVolumePriorToRampResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RestoreVolumePriorToRamp`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RestoreVolumePriorToRamp: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RestoreVolumePriorToRamp == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.RestoreVolumePriorToRamp()`) - } - - return r.Body.RestoreVolumePriorToRamp, nil -} - -type SetChannelMapArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - ChannelMap string `xml:"ChannelMap"` -} -type SetChannelMapResponse struct { -} - -func (s *Service) SetChannelMap(httpClient *http.Client, args *SetChannelMapArgs) (*SetChannelMapResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetChannelMap`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetChannelMap: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetChannelMap == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetChannelMap()`) - } - - return r.Body.SetChannelMap, nil -} - -type SetRoomCalibrationXArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - CalibrationID string `xml:"CalibrationID"` - Coefficients string `xml:"Coefficients"` - CalibrationMode string `xml:"CalibrationMode"` -} -type SetRoomCalibrationXResponse struct { -} - -func (s *Service) SetRoomCalibrationX(httpClient *http.Client, args *SetRoomCalibrationXArgs) (*SetRoomCalibrationXResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetRoomCalibrationX`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetRoomCalibrationX: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetRoomCalibrationX == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetRoomCalibrationX()`) - } - - return r.Body.SetRoomCalibrationX, nil -} - -type GetRoomCalibrationStatusArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type GetRoomCalibrationStatusResponse struct { - RoomCalibrationEnabled bool `xml:"RoomCalibrationEnabled"` - RoomCalibrationAvailable bool `xml:"RoomCalibrationAvailable"` -} - -func (s *Service) GetRoomCalibrationStatus(httpClient *http.Client, args *GetRoomCalibrationStatusArgs) (*GetRoomCalibrationStatusResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetRoomCalibrationStatus`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetRoomCalibrationStatus: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetRoomCalibrationStatus == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.GetRoomCalibrationStatus()`) - } - - return r.Body.GetRoomCalibrationStatus, nil -} - -type SetRoomCalibrationStatusArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - RoomCalibrationEnabled bool `xml:"RoomCalibrationEnabled"` -} -type SetRoomCalibrationStatusResponse struct { -} - -func (s *Service) SetRoomCalibrationStatus(httpClient *http.Client, args *SetRoomCalibrationStatusArgs) (*SetRoomCalibrationStatusResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetRoomCalibrationStatus`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetRoomCalibrationStatus: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetRoomCalibrationStatus == nil { - return nil, errors.New(`unexpected respose from service calling renderingcontrol.SetRoomCalibrationStatus()`) - } - - return r.Body.SetRoomCalibrationStatus, nil -} diff --git a/vendor/github.com/szatmary/sonos/SystemProperties/SystemProperties.go b/vendor/github.com/szatmary/sonos/SystemProperties/SystemProperties.go deleted file mode 100644 index c099216..0000000 --- a/vendor/github.com/szatmary/sonos/SystemProperties/SystemProperties.go +++ /dev/null @@ -1,576 +0,0 @@ -package systemproperties - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:SystemProperties:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/SystemProperties/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/SystemProperties/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - SetString *SetStringArgs `xml:"u:SetString,omitempty"` - GetString *GetStringArgs `xml:"u:GetString,omitempty"` - Remove *RemoveArgs `xml:"u:Remove,omitempty"` - GetWebCode *GetWebCodeArgs `xml:"u:GetWebCode,omitempty"` - ProvisionCredentialedTrialAccountX *ProvisionCredentialedTrialAccountXArgs `xml:"u:ProvisionCredentialedTrialAccountX,omitempty"` - AddAccountX *AddAccountXArgs `xml:"u:AddAccountX,omitempty"` - AddOAuthAccountX *AddOAuthAccountXArgs `xml:"u:AddOAuthAccountX,omitempty"` - RemoveAccount *RemoveAccountArgs `xml:"u:RemoveAccount,omitempty"` - EditAccountPasswordX *EditAccountPasswordXArgs `xml:"u:EditAccountPasswordX,omitempty"` - SetAccountNicknameX *SetAccountNicknameXArgs `xml:"u:SetAccountNicknameX,omitempty"` - RefreshAccountCredentialsX *RefreshAccountCredentialsXArgs `xml:"u:RefreshAccountCredentialsX,omitempty"` - EditAccountMd *EditAccountMdArgs `xml:"u:EditAccountMd,omitempty"` - DoPostUpdateTasks *DoPostUpdateTasksArgs `xml:"u:DoPostUpdateTasks,omitempty"` - ResetThirdPartyCredentials *ResetThirdPartyCredentialsArgs `xml:"u:ResetThirdPartyCredentials,omitempty"` - EnableRDM *EnableRDMArgs `xml:"u:EnableRDM,omitempty"` - GetRDM *GetRDMArgs `xml:"u:GetRDM,omitempty"` - ReplaceAccountX *ReplaceAccountXArgs `xml:"u:ReplaceAccountX,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - SetString *SetStringResponse `xml:"SetStringResponse,omitempty"` - GetString *GetStringResponse `xml:"GetStringResponse,omitempty"` - Remove *RemoveResponse `xml:"RemoveResponse,omitempty"` - GetWebCode *GetWebCodeResponse `xml:"GetWebCodeResponse,omitempty"` - ProvisionCredentialedTrialAccountX *ProvisionCredentialedTrialAccountXResponse `xml:"ProvisionCredentialedTrialAccountXResponse,omitempty"` - AddAccountX *AddAccountXResponse `xml:"AddAccountXResponse,omitempty"` - AddOAuthAccountX *AddOAuthAccountXResponse `xml:"AddOAuthAccountXResponse,omitempty"` - RemoveAccount *RemoveAccountResponse `xml:"RemoveAccountResponse,omitempty"` - EditAccountPasswordX *EditAccountPasswordXResponse `xml:"EditAccountPasswordXResponse,omitempty"` - SetAccountNicknameX *SetAccountNicknameXResponse `xml:"SetAccountNicknameXResponse,omitempty"` - RefreshAccountCredentialsX *RefreshAccountCredentialsXResponse `xml:"RefreshAccountCredentialsXResponse,omitempty"` - EditAccountMd *EditAccountMdResponse `xml:"EditAccountMdResponse,omitempty"` - DoPostUpdateTasks *DoPostUpdateTasksResponse `xml:"DoPostUpdateTasksResponse,omitempty"` - ResetThirdPartyCredentials *ResetThirdPartyCredentialsResponse `xml:"ResetThirdPartyCredentialsResponse,omitempty"` - EnableRDM *EnableRDMResponse `xml:"EnableRDMResponse,omitempty"` - GetRDM *GetRDMResponse `xml:"GetRDMResponse,omitempty"` - ReplaceAccountX *ReplaceAccountXResponse `xml:"ReplaceAccountXResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type SetStringArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - VariableName string `xml:"VariableName"` - StringValue string `xml:"StringValue"` -} -type SetStringResponse struct { -} - -func (s *Service) SetString(httpClient *http.Client, args *SetStringArgs) (*SetStringResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetString`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetString: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetString == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.SetString()`) - } - - return r.Body.SetString, nil -} - -type GetStringArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - VariableName string `xml:"VariableName"` -} -type GetStringResponse struct { - StringValue string `xml:"StringValue"` -} - -func (s *Service) GetString(httpClient *http.Client, args *GetStringArgs) (*GetStringResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetString`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetString: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetString == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.GetString()`) - } - - return r.Body.GetString, nil -} - -type RemoveArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - VariableName string `xml:"VariableName"` -} -type RemoveResponse struct { -} - -func (s *Service) Remove(httpClient *http.Client, args *RemoveArgs) (*RemoveResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Remove`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Remove: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Remove == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.Remove()`) - } - - return r.Body.Remove, nil -} - -type GetWebCodeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - AccountType uint32 `xml:"AccountType"` -} -type GetWebCodeResponse struct { - WebCode string `xml:"WebCode"` -} - -func (s *Service) GetWebCode(httpClient *http.Client, args *GetWebCodeArgs) (*GetWebCodeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetWebCode`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetWebCode: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetWebCode == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.GetWebCode()`) - } - - return r.Body.GetWebCode, nil -} - -type ProvisionCredentialedTrialAccountXArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - AccountType uint32 `xml:"AccountType"` - AccountID string `xml:"AccountID"` - AccountPassword string `xml:"AccountPassword"` -} -type ProvisionCredentialedTrialAccountXResponse struct { - IsExpired bool `xml:"IsExpired"` - AccountUDN string `xml:"AccountUDN"` -} - -func (s *Service) ProvisionCredentialedTrialAccountX(httpClient *http.Client, args *ProvisionCredentialedTrialAccountXArgs) (*ProvisionCredentialedTrialAccountXResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ProvisionCredentialedTrialAccountX`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ProvisionCredentialedTrialAccountX: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ProvisionCredentialedTrialAccountX == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.ProvisionCredentialedTrialAccountX()`) - } - - return r.Body.ProvisionCredentialedTrialAccountX, nil -} - -type AddAccountXArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - AccountType uint32 `xml:"AccountType"` - AccountID string `xml:"AccountID"` - AccountPassword string `xml:"AccountPassword"` -} -type AddAccountXResponse struct { - AccountUDN string `xml:"AccountUDN"` -} - -func (s *Service) AddAccountX(httpClient *http.Client, args *AddAccountXArgs) (*AddAccountXResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`AddAccountX`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{AddAccountX: args}, - }) - if err != nil { - return nil, err - } - if r.Body.AddAccountX == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.AddAccountX()`) - } - - return r.Body.AddAccountX, nil -} - -type AddOAuthAccountXArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - AccountType uint32 `xml:"AccountType"` - AccountToken string `xml:"AccountToken"` - AccountKey string `xml:"AccountKey"` - OAuthDeviceID string `xml:"OAuthDeviceID"` - AuthorizationCode string `xml:"AuthorizationCode"` - RedirectURI string `xml:"RedirectURI"` - UserIdHashCode string `xml:"UserIdHashCode"` - AccountTier uint32 `xml:"AccountTier"` -} -type AddOAuthAccountXResponse struct { - AccountUDN string `xml:"AccountUDN"` - AccountNickname string `xml:"AccountNickname"` -} - -func (s *Service) AddOAuthAccountX(httpClient *http.Client, args *AddOAuthAccountXArgs) (*AddOAuthAccountXResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`AddOAuthAccountX`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{AddOAuthAccountX: args}, - }) - if err != nil { - return nil, err - } - if r.Body.AddOAuthAccountX == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.AddOAuthAccountX()`) - } - - return r.Body.AddOAuthAccountX, nil -} - -type RemoveAccountArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - AccountType uint32 `xml:"AccountType"` - AccountID string `xml:"AccountID"` -} -type RemoveAccountResponse struct { -} - -func (s *Service) RemoveAccount(httpClient *http.Client, args *RemoveAccountArgs) (*RemoveAccountResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RemoveAccount`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RemoveAccount: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RemoveAccount == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.RemoveAccount()`) - } - - return r.Body.RemoveAccount, nil -} - -type EditAccountPasswordXArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - AccountType uint32 `xml:"AccountType"` - AccountID string `xml:"AccountID"` - NewAccountPassword string `xml:"NewAccountPassword"` -} -type EditAccountPasswordXResponse struct { -} - -func (s *Service) EditAccountPasswordX(httpClient *http.Client, args *EditAccountPasswordXArgs) (*EditAccountPasswordXResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`EditAccountPasswordX`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{EditAccountPasswordX: args}, - }) - if err != nil { - return nil, err - } - if r.Body.EditAccountPasswordX == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.EditAccountPasswordX()`) - } - - return r.Body.EditAccountPasswordX, nil -} - -type SetAccountNicknameXArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - AccountUDN string `xml:"AccountUDN"` - AccountNickname string `xml:"AccountNickname"` -} -type SetAccountNicknameXResponse struct { -} - -func (s *Service) SetAccountNicknameX(httpClient *http.Client, args *SetAccountNicknameXArgs) (*SetAccountNicknameXResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetAccountNicknameX`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetAccountNicknameX: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetAccountNicknameX == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.SetAccountNicknameX()`) - } - - return r.Body.SetAccountNicknameX, nil -} - -type RefreshAccountCredentialsXArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - AccountType uint32 `xml:"AccountType"` - AccountUID uint32 `xml:"AccountUID"` - AccountToken string `xml:"AccountToken"` - AccountKey string `xml:"AccountKey"` -} -type RefreshAccountCredentialsXResponse struct { -} - -func (s *Service) RefreshAccountCredentialsX(httpClient *http.Client, args *RefreshAccountCredentialsXArgs) (*RefreshAccountCredentialsXResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RefreshAccountCredentialsX`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RefreshAccountCredentialsX: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RefreshAccountCredentialsX == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.RefreshAccountCredentialsX()`) - } - - return r.Body.RefreshAccountCredentialsX, nil -} - -type EditAccountMdArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - AccountType uint32 `xml:"AccountType"` - AccountID string `xml:"AccountID"` - NewAccountMd string `xml:"NewAccountMd"` -} -type EditAccountMdResponse struct { -} - -func (s *Service) EditAccountMd(httpClient *http.Client, args *EditAccountMdArgs) (*EditAccountMdResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`EditAccountMd`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{EditAccountMd: args}, - }) - if err != nil { - return nil, err - } - if r.Body.EditAccountMd == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.EditAccountMd()`) - } - - return r.Body.EditAccountMd, nil -} - -type DoPostUpdateTasksArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type DoPostUpdateTasksResponse struct { -} - -func (s *Service) DoPostUpdateTasks(httpClient *http.Client, args *DoPostUpdateTasksArgs) (*DoPostUpdateTasksResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`DoPostUpdateTasks`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{DoPostUpdateTasks: args}, - }) - if err != nil { - return nil, err - } - if r.Body.DoPostUpdateTasks == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.DoPostUpdateTasks()`) - } - - return r.Body.DoPostUpdateTasks, nil -} - -type ResetThirdPartyCredentialsArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type ResetThirdPartyCredentialsResponse struct { -} - -func (s *Service) ResetThirdPartyCredentials(httpClient *http.Client, args *ResetThirdPartyCredentialsArgs) (*ResetThirdPartyCredentialsResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ResetThirdPartyCredentials`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ResetThirdPartyCredentials: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ResetThirdPartyCredentials == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.ResetThirdPartyCredentials()`) - } - - return r.Body.ResetThirdPartyCredentials, nil -} - -type EnableRDMArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - RDMValue bool `xml:"RDMValue"` -} -type EnableRDMResponse struct { -} - -func (s *Service) EnableRDM(httpClient *http.Client, args *EnableRDMArgs) (*EnableRDMResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`EnableRDM`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{EnableRDM: args}, - }) - if err != nil { - return nil, err - } - if r.Body.EnableRDM == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.EnableRDM()`) - } - - return r.Body.EnableRDM, nil -} - -type GetRDMArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetRDMResponse struct { - RDMValue bool `xml:"RDMValue"` -} - -func (s *Service) GetRDM(httpClient *http.Client, args *GetRDMArgs) (*GetRDMResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetRDM`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetRDM: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetRDM == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.GetRDM()`) - } - - return r.Body.GetRDM, nil -} - -type ReplaceAccountXArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - AccountUDN string `xml:"AccountUDN"` - NewAccountID string `xml:"NewAccountID"` - NewAccountPassword string `xml:"NewAccountPassword"` - AccountToken string `xml:"AccountToken"` - AccountKey string `xml:"AccountKey"` - OAuthDeviceID string `xml:"OAuthDeviceID"` -} -type ReplaceAccountXResponse struct { - NewAccountUDN string `xml:"NewAccountUDN"` -} - -func (s *Service) ReplaceAccountX(httpClient *http.Client, args *ReplaceAccountXArgs) (*ReplaceAccountXResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ReplaceAccountX`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ReplaceAccountX: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ReplaceAccountX == nil { - return nil, errors.New(`unexpected respose from service calling systemproperties.ReplaceAccountX()`) - } - - return r.Body.ReplaceAccountX, nil -} diff --git a/vendor/github.com/szatmary/sonos/VirtualLineIn/VirtualLineIn.go b/vendor/github.com/szatmary/sonos/VirtualLineIn/VirtualLineIn.go deleted file mode 100644 index 508579d..0000000 --- a/vendor/github.com/szatmary/sonos/VirtualLineIn/VirtualLineIn.go +++ /dev/null @@ -1,306 +0,0 @@ -package virtuallinein - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:VirtualLineIn:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/MediaRenderer/VirtualLineIn/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/MediaRenderer/VirtualLineIn/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - StartTransmission *StartTransmissionArgs `xml:"u:StartTransmission,omitempty"` - StopTransmission *StopTransmissionArgs `xml:"u:StopTransmission,omitempty"` - Play *PlayArgs `xml:"u:Play,omitempty"` - Pause *PauseArgs `xml:"u:Pause,omitempty"` - Next *NextArgs `xml:"u:Next,omitempty"` - Previous *PreviousArgs `xml:"u:Previous,omitempty"` - Stop *StopArgs `xml:"u:Stop,omitempty"` - SetVolume *SetVolumeArgs `xml:"u:SetVolume,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - StartTransmission *StartTransmissionResponse `xml:"StartTransmissionResponse,omitempty"` - StopTransmission *StopTransmissionResponse `xml:"StopTransmissionResponse,omitempty"` - Play *PlayResponse `xml:"PlayResponse,omitempty"` - Pause *PauseResponse `xml:"PauseResponse,omitempty"` - Next *NextResponse `xml:"NextResponse,omitempty"` - Previous *PreviousResponse `xml:"PreviousResponse,omitempty"` - Stop *StopResponse `xml:"StopResponse,omitempty"` - SetVolume *SetVolumeResponse `xml:"SetVolumeResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type StartTransmissionArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - CoordinatorID string `xml:"CoordinatorID"` -} -type StartTransmissionResponse struct { - CurrentTransportSettings string `xml:"CurrentTransportSettings"` -} - -func (s *Service) StartTransmission(httpClient *http.Client, args *StartTransmissionArgs) (*StartTransmissionResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`StartTransmission`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{StartTransmission: args}, - }) - if err != nil { - return nil, err - } - if r.Body.StartTransmission == nil { - return nil, errors.New(`unexpected respose from service calling virtuallinein.StartTransmission()`) - } - - return r.Body.StartTransmission, nil -} - -type StopTransmissionArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - CoordinatorID string `xml:"CoordinatorID"` -} -type StopTransmissionResponse struct { -} - -func (s *Service) StopTransmission(httpClient *http.Client, args *StopTransmissionArgs) (*StopTransmissionResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`StopTransmission`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{StopTransmission: args}, - }) - if err != nil { - return nil, err - } - if r.Body.StopTransmission == nil { - return nil, errors.New(`unexpected respose from service calling virtuallinein.StopTransmission()`) - } - - return r.Body.StopTransmission, nil -} - -type PlayArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - Speed string `xml:"Speed"` -} -type PlayResponse struct { -} - -func (s *Service) Play(httpClient *http.Client, args *PlayArgs) (*PlayResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Play`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Play: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Play == nil { - return nil, errors.New(`unexpected respose from service calling virtuallinein.Play()`) - } - - return r.Body.Play, nil -} - -type PauseArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type PauseResponse struct { -} - -func (s *Service) Pause(httpClient *http.Client, args *PauseArgs) (*PauseResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Pause`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Pause: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Pause == nil { - return nil, errors.New(`unexpected respose from service calling virtuallinein.Pause()`) - } - - return r.Body.Pause, nil -} - -type NextArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type NextResponse struct { -} - -func (s *Service) Next(httpClient *http.Client, args *NextArgs) (*NextResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Next`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Next: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Next == nil { - return nil, errors.New(`unexpected respose from service calling virtuallinein.Next()`) - } - - return r.Body.Next, nil -} - -type PreviousArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type PreviousResponse struct { -} - -func (s *Service) Previous(httpClient *http.Client, args *PreviousArgs) (*PreviousResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Previous`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Previous: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Previous == nil { - return nil, errors.New(`unexpected respose from service calling virtuallinein.Previous()`) - } - - return r.Body.Previous, nil -} - -type StopArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` -} -type StopResponse struct { -} - -func (s *Service) Stop(httpClient *http.Client, args *StopArgs) (*StopResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`Stop`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{Stop: args}, - }) - if err != nil { - return nil, err - } - if r.Body.Stop == nil { - return nil, errors.New(`unexpected respose from service calling virtuallinein.Stop()`) - } - - return r.Body.Stop, nil -} - -type SetVolumeArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - InstanceID uint32 `xml:"InstanceID"` - DesiredVolume uint16 `xml:"DesiredVolume"` -} -type SetVolumeResponse struct { -} - -func (s *Service) SetVolume(httpClient *http.Client, args *SetVolumeArgs) (*SetVolumeResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SetVolume`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SetVolume: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SetVolume == nil { - return nil, errors.New(`unexpected respose from service calling virtuallinein.SetVolume()`) - } - - return r.Body.SetVolume, nil -} diff --git a/vendor/github.com/szatmary/sonos/ZoneGroupTopology/ZoneGroupTopology.go b/vendor/github.com/szatmary/sonos/ZoneGroupTopology/ZoneGroupTopology.go deleted file mode 100644 index 2bc1887..0000000 --- a/vendor/github.com/szatmary/sonos/ZoneGroupTopology/ZoneGroupTopology.go +++ /dev/null @@ -1,318 +0,0 @@ -package zonegrouptopology - -import ( - "bytes" - "encoding/xml" - "errors" - "io/ioutil" - "net/http" - "net/url" -) - -const ( - _ServiceURN = "urn:schemas-upnp-org:service:ZoneGroupTopology:1" - _EncodingSchema = "http://schemas.xmlsoap.org/soap/encoding/" - _EnvelopeSchema = "http://schemas.xmlsoap.org/soap/envelope/" -) - -type Service struct { - ControlEndpoint *url.URL - EventEndpoint *url.URL -} - -func NewService(deviceUrl *url.URL) *Service { - c, err := url.Parse(`/ZoneGroupTopology/Control`) - if nil != err { - panic(err) - } - e, err := url.Parse(`/ZoneGroupTopology/Event`) - if nil != err { - panic(err) - } - return &Service{ - ControlEndpoint: deviceUrl.ResolveReference(c), - EventEndpoint: deviceUrl.ResolveReference(e), - } -} - -type Envelope struct { - XMLName xml.Name `xml:"s:Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"s:encodingStyle,attr"` - Body Body `xml:"s:Body"` -} -type Body struct { - XMLName xml.Name `xml:"s:Body"` - CheckForUpdate *CheckForUpdateArgs `xml:"u:CheckForUpdate,omitempty"` - BeginSoftwareUpdate *BeginSoftwareUpdateArgs `xml:"u:BeginSoftwareUpdate,omitempty"` - ReportUnresponsiveDevice *ReportUnresponsiveDeviceArgs `xml:"u:ReportUnresponsiveDevice,omitempty"` - ReportAlarmStartedRunning *ReportAlarmStartedRunningArgs `xml:"u:ReportAlarmStartedRunning,omitempty"` - SubmitDiagnostics *SubmitDiagnosticsArgs `xml:"u:SubmitDiagnostics,omitempty"` - RegisterMobileDevice *RegisterMobileDeviceArgs `xml:"u:RegisterMobileDevice,omitempty"` - GetZoneGroupAttributes *GetZoneGroupAttributesArgs `xml:"u:GetZoneGroupAttributes,omitempty"` - GetZoneGroupState *GetZoneGroupStateArgs `xml:"u:GetZoneGroupState,omitempty"` -} -type EnvelopeResponse struct { - XMLName xml.Name `xml:"Envelope"` - Xmlns string `xml:"xmlns:s,attr"` - EncodingStyle string `xml:"encodingStyle,attr"` - Body BodyResponse `xml:"Body"` -} -type BodyResponse struct { - XMLName xml.Name `xml:"Body"` - CheckForUpdate *CheckForUpdateResponse `xml:"CheckForUpdateResponse,omitempty"` - BeginSoftwareUpdate *BeginSoftwareUpdateResponse `xml:"BeginSoftwareUpdateResponse,omitempty"` - ReportUnresponsiveDevice *ReportUnresponsiveDeviceResponse `xml:"ReportUnresponsiveDeviceResponse,omitempty"` - ReportAlarmStartedRunning *ReportAlarmStartedRunningResponse `xml:"ReportAlarmStartedRunningResponse,omitempty"` - SubmitDiagnostics *SubmitDiagnosticsResponse `xml:"SubmitDiagnosticsResponse,omitempty"` - RegisterMobileDevice *RegisterMobileDeviceResponse `xml:"RegisterMobileDeviceResponse,omitempty"` - GetZoneGroupAttributes *GetZoneGroupAttributesResponse `xml:"GetZoneGroupAttributesResponse,omitempty"` - GetZoneGroupState *GetZoneGroupStateResponse `xml:"GetZoneGroupStateResponse,omitempty"` -} - -func (s *Service) exec(actionName string, httpClient *http.Client, envelope *Envelope) (*EnvelopeResponse, error) { - marshaled, err := xml.Marshal(envelope) - if err != nil { - return nil, err - } - postBody := []byte(``) - postBody = append(postBody, marshaled...) - req, err := http.NewRequest(`POST`, s.ControlEndpoint.String(), bytes.NewBuffer(postBody)) - if err != nil { - return nil, err - } - req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`) - req.Header.Set(`SOAPAction`, _ServiceURN+`#`+actionName) - res, err := httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - var envelopeResponse EnvelopeResponse - err = xml.Unmarshal(responseBody, &envelopeResponse) - if err != nil { - return nil, err - } - return &envelopeResponse, nil -} - -type CheckForUpdateArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - // Allowed Value: All - // Allowed Value: Software - UpdateType string `xml:"UpdateType"` - CachedOnly bool `xml:"CachedOnly"` - Version string `xml:"Version"` -} -type CheckForUpdateResponse struct { - UpdateItem string `xml:"UpdateItem"` -} - -func (s *Service) CheckForUpdate(httpClient *http.Client, args *CheckForUpdateArgs) (*CheckForUpdateResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`CheckForUpdate`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{CheckForUpdate: args}, - }) - if err != nil { - return nil, err - } - if r.Body.CheckForUpdate == nil { - return nil, errors.New(`unexpected respose from service calling zonegrouptopology.CheckForUpdate()`) - } - - return r.Body.CheckForUpdate, nil -} - -type BeginSoftwareUpdateArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - UpdateURL string `xml:"UpdateURL"` - Flags uint32 `xml:"Flags"` - ExtraOptions string `xml:"ExtraOptions"` -} -type BeginSoftwareUpdateResponse struct { -} - -func (s *Service) BeginSoftwareUpdate(httpClient *http.Client, args *BeginSoftwareUpdateArgs) (*BeginSoftwareUpdateResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`BeginSoftwareUpdate`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{BeginSoftwareUpdate: args}, - }) - if err != nil { - return nil, err - } - if r.Body.BeginSoftwareUpdate == nil { - return nil, errors.New(`unexpected respose from service calling zonegrouptopology.BeginSoftwareUpdate()`) - } - - return r.Body.BeginSoftwareUpdate, nil -} - -type ReportUnresponsiveDeviceArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - DeviceUUID string `xml:"DeviceUUID"` - // Allowed Value: Remove - // Allowed Value: TopologyMonitorProbe - // Allowed Value: VerifyThenRemoveSystemwide - DesiredAction string `xml:"DesiredAction"` -} -type ReportUnresponsiveDeviceResponse struct { -} - -func (s *Service) ReportUnresponsiveDevice(httpClient *http.Client, args *ReportUnresponsiveDeviceArgs) (*ReportUnresponsiveDeviceResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ReportUnresponsiveDevice`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ReportUnresponsiveDevice: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ReportUnresponsiveDevice == nil { - return nil, errors.New(`unexpected respose from service calling zonegrouptopology.ReportUnresponsiveDevice()`) - } - - return r.Body.ReportUnresponsiveDevice, nil -} - -type ReportAlarmStartedRunningArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type ReportAlarmStartedRunningResponse struct { -} - -func (s *Service) ReportAlarmStartedRunning(httpClient *http.Client, args *ReportAlarmStartedRunningArgs) (*ReportAlarmStartedRunningResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`ReportAlarmStartedRunning`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{ReportAlarmStartedRunning: args}, - }) - if err != nil { - return nil, err - } - if r.Body.ReportAlarmStartedRunning == nil { - return nil, errors.New(`unexpected respose from service calling zonegrouptopology.ReportAlarmStartedRunning()`) - } - - return r.Body.ReportAlarmStartedRunning, nil -} - -type SubmitDiagnosticsArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - IncludeControllers bool `xml:"IncludeControllers"` - Type string `xml:"Type"` -} -type SubmitDiagnosticsResponse struct { - DiagnosticID uint32 `xml:"DiagnosticID"` -} - -func (s *Service) SubmitDiagnostics(httpClient *http.Client, args *SubmitDiagnosticsArgs) (*SubmitDiagnosticsResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`SubmitDiagnostics`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{SubmitDiagnostics: args}, - }) - if err != nil { - return nil, err - } - if r.Body.SubmitDiagnostics == nil { - return nil, errors.New(`unexpected respose from service calling zonegrouptopology.SubmitDiagnostics()`) - } - - return r.Body.SubmitDiagnostics, nil -} - -type RegisterMobileDeviceArgs struct { - Xmlns string `xml:"xmlns:u,attr"` - MobileDeviceName string `xml:"MobileDeviceName"` - MobileDeviceUDN string `xml:"MobileDeviceUDN"` - MobileIPAndPort string `xml:"MobileIPAndPort"` -} -type RegisterMobileDeviceResponse struct { -} - -func (s *Service) RegisterMobileDevice(httpClient *http.Client, args *RegisterMobileDeviceArgs) (*RegisterMobileDeviceResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`RegisterMobileDevice`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{RegisterMobileDevice: args}, - }) - if err != nil { - return nil, err - } - if r.Body.RegisterMobileDevice == nil { - return nil, errors.New(`unexpected respose from service calling zonegrouptopology.RegisterMobileDevice()`) - } - - return r.Body.RegisterMobileDevice, nil -} - -type GetZoneGroupAttributesArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetZoneGroupAttributesResponse struct { - CurrentZoneGroupName string `xml:"CurrentZoneGroupName"` - CurrentZoneGroupID string `xml:"CurrentZoneGroupID"` - CurrentZonePlayerUUIDsInGroup string `xml:"CurrentZonePlayerUUIDsInGroup"` - CurrentMuseHouseholdId string `xml:"CurrentMuseHouseholdId"` -} - -func (s *Service) GetZoneGroupAttributes(httpClient *http.Client, args *GetZoneGroupAttributesArgs) (*GetZoneGroupAttributesResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetZoneGroupAttributes`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetZoneGroupAttributes: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetZoneGroupAttributes == nil { - return nil, errors.New(`unexpected respose from service calling zonegrouptopology.GetZoneGroupAttributes()`) - } - - return r.Body.GetZoneGroupAttributes, nil -} - -type GetZoneGroupStateArgs struct { - Xmlns string `xml:"xmlns:u,attr"` -} -type GetZoneGroupStateResponse struct { - ZoneGroupState string `xml:"ZoneGroupState"` -} - -func (s *Service) GetZoneGroupState(httpClient *http.Client, args *GetZoneGroupStateArgs) (*GetZoneGroupStateResponse, error) { - args.Xmlns = _ServiceURN - r, err := s.exec(`GetZoneGroupState`, httpClient, - &Envelope{ - EncodingStyle: _EncodingSchema, - Xmlns: _EnvelopeSchema, - Body: Body{GetZoneGroupState: args}, - }) - if err != nil { - return nil, err - } - if r.Body.GetZoneGroupState == nil { - return nil, errors.New(`unexpected respose from service calling zonegrouptopology.GetZoneGroupState()`) - } - - return r.Body.GetZoneGroupState, nil -} diff --git a/vendor/github.com/szatmary/sonos/sonos.go b/vendor/github.com/szatmary/sonos/sonos.go deleted file mode 100644 index 524bcd2..0000000 --- a/vendor/github.com/szatmary/sonos/sonos.go +++ /dev/null @@ -1,100 +0,0 @@ -package sonos - -import ( - "bufio" - "errors" - "fmt" - "net" - "net/http" - "net/url" - "time" -) - -const ( - mx = 5 - st = "urn:schemas-upnp-org:device:ZonePlayer:1" - bcastaddr = "239.255.255.250:1900" -) - -type Sonos struct { - // Context Context - listenSocket *net.UDPConn - udpReader *bufio.Reader - found chan *ZonePlayer -} - -func NewSonos() (*Sonos, error) { - conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: []byte{0, 0, 0, 0}, Port: 0, Zone: ""}) - if err != nil { - return nil, err - } - - s := Sonos{ - listenSocket: conn, - udpReader: bufio.NewReader(conn), - found: make(chan *ZonePlayer), - } - - return &s, nil -} - -func (s *Sonos) Close() { - s.listenSocket.Close() -} - -func (s *Sonos) Search() (chan *ZonePlayer, error) { - go func() { - for { - response, err := http.ReadResponse(s.udpReader, nil) - if err != nil { - continue - } - - location, err := url.Parse(response.Header.Get("Location")) - if err != nil { - continue - } - zp, err := NewZonePlayer(location) - if err != nil { - continue - } - if zp.IsCoordinator() { - s.found <- zp - } - } - }() - - // MX should be set to use timeout value in integer seconds - pkt := []byte(fmt.Sprintf("M-SEARCH * HTTP/1.1\r\nHOST: %s\r\nMAN: \"ssdp:discover\"\r\nMX: %d\r\nST: %s\r\n\r\n", bcastaddr, mx, st)) - bcast, err := net.ResolveUDPAddr("udp", bcastaddr) - if err != nil { - return nil, err - } - _, err = s.listenSocket.WriteTo(pkt, bcast) - if err != nil { - return nil, err - } - - return s.found, nil -} - -func FindRoom(room string, timeout time.Duration) (*ZonePlayer, error) { - son, err := NewSonos() - if err != nil { - return nil, err - } - defer son.Close() - - found, _ := son.Search() - to := time.After(timeout) - for { - select { - case <-to: - return nil, errors.New("timeout") - case zp := <-found: - if zp.RoomName() == room { - return zp, nil - } - } - } -} diff --git a/vendor/github.com/szatmary/sonos/zonegroups.go b/vendor/github.com/szatmary/sonos/zonegroups.go deleted file mode 100644 index 3a80aaa..0000000 --- a/vendor/github.com/szatmary/sonos/zonegroups.go +++ /dev/null @@ -1,107 +0,0 @@ -package sonos - -import "encoding/xml" - -type VanishedDevice struct { - XMLName xml.Name `xml:"VanishedDevice"` - UUID string `xml:"UUID"` - Location string `xml:"Location"` - ZoneName string `xml:"ZoneName"` - Icon string `xml:"Icon"` - Configuration string `xml:"Configuration"` - SoftwareVersion string `xml:"SoftwareVersion"` - SWGen string `xml:"SWGen"` - MinCompatibleVersion string `xml:"MinCompatibleVersion"` - LegacyCompatibleVersion string `xml:"LegacyCompatibleVersion"` - BootSeq string `xml:"BootSeq"` - TVConfigurationError string `xml:"TVConfigurationError"` - HdmiCecAvailable string `xml:"HdmiCecAvailable"` - WirelessMode string `xml:"WirelessMode"` - WirelessLeafOnly string `xml:"WirelessLeafOnly"` - HasConfiguredSSID string `xml:"HasConfiguredSSID"` - ChannelFreq string `xml:"ChannelFreq"` - BehindWifiExtender string `xml:"BehindWifiExtender"` - WifiEnabled string `xml:"WifiEnabled"` - Orientation string `xml:"Orientation"` - RoomCalibrationState string `xml:"RoomCalibrationState"` - SecureRegState string `xml:"SecureRegState"` - VoiceConfigState string `xml:"VoiceConfigState"` - MicEnabled string `xml:"MicEnabled"` - AirPlayEnabled string `xml:"AirPlayEnabled"` - IdleState string `xml:"IdleState"` - MoreInfo string `xml:"MoreInfo"` -} - -type Satellite struct { - XMLName xml.Name `xml:"Satellite"` - UUID string `xml:"UUID"` - Location string `xml:"Location"` - ZoneName string `xml:"ZoneName"` - Icon string `xml:"Icon"` - Configuration string `xml:"Configuration"` - SoftwareVersion string `xml:"SoftwareVersion"` - SWGen string `xml:"SWGen"` - MinCompatibleVersion string `xml:"MinCompatibleVersion"` - LegacyCompatibleVersion string `xml:"LegacyCompatibleVersion"` - BootSeq string `xml:"BootSeq"` - TVConfigurationError string `xml:"TVConfigurationError"` - HdmiCecAvailable string `xml:"HdmiCecAvailable"` - WirelessMode string `xml:"WirelessMode"` - WirelessLeafOnly string `xml:"WirelessLeafOnly"` - HasConfiguredSSID string `xml:"HasConfiguredSSID"` - ChannelFreq string `xml:"ChannelFreq"` - BehindWifiExtender string `xml:"BehindWifiExtender"` - WifiEnabled string `xml:"WifiEnabled"` - Orientation string `xml:"Orientation"` - RoomCalibrationState string `xml:"RoomCalibrationState"` - SecureRegState string `xml:"SecureRegState"` - VoiceConfigState string `xml:"VoiceConfigState"` - MicEnabled string `xml:"MicEnabled"` - AirPlayEnabled string `xml:"AirPlayEnabled"` - IdleState string `xml:"IdleState"` - MoreInfo string `xml:"MoreInfo"` -} - -type ZoneGroupMember struct { - XMLName xml.Name `xml:"ZoneGroupMember"` - UUID string `xml:"UUID"` - Location string `xml:"Location"` - ZoneName string `xml:"ZoneName"` - Icon string `xml:"Icon"` - Configuration string `xml:"Configuration"` - SoftwareVersion string `xml:"SoftwareVersion"` - SWGen string `xml:"SWGen"` - MinCompatibleVersion string `xml:"MinCompatibleVersion"` - LegacyCompatibleVersion string `xml:"LegacyCompatibleVersion"` - BootSeq string `xml:"BootSeq"` - TVConfigurationError string `xml:"TVConfigurationError"` - HdmiCecAvailable string `xml:"HdmiCecAvailable"` - WirelessMode string `xml:"WirelessMode"` - WirelessLeafOnly string `xml:"WirelessLeafOnly"` - HasConfiguredSSID string `xml:"HasConfiguredSSID"` - ChannelFreq string `xml:"ChannelFreq"` - BehindWifiExtender string `xml:"BehindWifiExtender"` - WifiEnabled string `xml:"WifiEnabled"` - Orientation string `xml:"Orientation"` - RoomCalibrationState string `xml:"RoomCalibrationState"` - SecureRegState string `xml:"SecureRegState"` - VoiceConfigState string `xml:"VoiceConfigState"` - MicEnabled string `xml:"MicEnabled"` - AirPlayEnabled string `xml:"AirPlayEnabled"` - IdleState string `xml:"IdleState"` - MoreInfo string `xml:"MoreInfo"` - Satellite []Satellite `xml:"Satellite>Satellite"` - VanishedDevice []VanishedDevice `xml:"VanishedDevices>VanishedDevice"` -} - -type ZoneGroup struct { - XMLName xml.Name `xml:"ZoneGroup"` - Coordinator string `xml:"Coordinator,attr"` - ID string `xml:"ID,attr"` - ZoneGroupMember []ZoneGroupMember `xml:"ZoneGroupMember"` -} - -type ZoneGroupState struct { - XMLName xml.Name `xml:"ZoneGroupState"` - ZoneGroups []ZoneGroup `xml:"ZoneGroups>ZoneGroup"` -} diff --git a/vendor/github.com/szatmary/sonos/zoneplayer.go b/vendor/github.com/szatmary/sonos/zoneplayer.go deleted file mode 100644 index 21c1777..0000000 --- a/vendor/github.com/szatmary/sonos/zoneplayer.go +++ /dev/null @@ -1,236 +0,0 @@ -package sonos - -import ( - "encoding/xml" - "io/ioutil" - "net/http" - "net/url" - - avt "github.com/szatmary/sonos/AVTransport" - clk "github.com/szatmary/sonos/AlarmClock" - con "github.com/szatmary/sonos/ConnectionManager" - dir "github.com/szatmary/sonos/ContentDirectory" - dev "github.com/szatmary/sonos/DeviceProperties" - gmn "github.com/szatmary/sonos/GroupManagement" - rcg "github.com/szatmary/sonos/GroupRenderingControl" - mus "github.com/szatmary/sonos/MusicServices" - ply "github.com/szatmary/sonos/QPlay" - que "github.com/szatmary/sonos/Queue" - ren "github.com/szatmary/sonos/RenderingControl" - sys "github.com/szatmary/sonos/SystemProperties" - vli "github.com/szatmary/sonos/VirtualLineIn" - zgt "github.com/szatmary/sonos/ZoneGroupTopology" -) - -type SpecVersion struct { - XMLName xml.Name `xml:"specVersion"` - Major int `xml:"major"` - Minor int `xml:"minor"` -} - -type Service struct { - XMLName xml.Name `xml:"service"` - ServiceType string `xml:"serviceType"` - ServiceId string `xml:"serviceId"` - ControlURL string `xml:"controlURL"` - EventSubURL string `xml:"eventSubURL"` - SCPDURL string `xml:"SCPDURL"` -} - -type Icon struct { - XMLName xml.Name `xml:"icon"` - Id string `xml:"id"` - Mimetype string `xml:"mimetype"` - Width int `xml:"width"` - Height int `xml:"height"` - Depth int `xml:"depth"` - Url url.URL `xml:"url"` -} - -type Device struct { - XMLName xml.Name `xml:"device"` - DeviceType string `xml:"deviceType"` - FriendlyName string `xml:"friendlyName"` - Manufacturer string `xml:"manufacturer"` - ManufacturerURL string `xml:"manufacturerURL"` - ModelNumber string `xml:"modelNumber"` - ModelDescription string `xml:"modelDescription"` - ModelName string `xml:"modelName"` - ModelURL string `xml:"modelURL"` - SoftwareVersion string `xml:"softwareVersion"` - SwGen string `xml:"swGen"` - HardwareVersion string `xml:"hardwareVersion"` - SerialNum string `xml:"serialNum"` - MACAddress string `xml:"MACAddress"` - UDN string `xml:"UDN"` - Icons []Icon `xml:"iconList>icon"` - MinCompatibleVersion string `xml:"minCompatibleVersion"` - LegacyCompatibleVersion string `xml:"legacyCompatibleVersion"` - ApiVersion string `xml:"apiVersion"` - MinApiVersion string `xml:"minApiVersion"` - DisplayVersion string `xml:"displayVersion"` - ExtraVersion string `xml:"extraVersion"` - RoomName string `xml:"roomName"` - DisplayName string `xml:"displayName"` - ZoneType int `xml:"zoneType"` - Feature1 string `xml:"feature1"` - Feature2 string `xml:"feature2"` - Feature3 string `xml:"feature3"` - Seriesid string `xml:"seriesid"` - Variant int `xml:"variant"` - InternalSpeakerSize float32 `xml:"internalSpeakerSize"` - BassExtension float32 `xml:"bassExtension"` - SatGainOffset float32 `xml:"satGainOffset"` - Memory int `xml:"memory"` - Flash int `xml:"flash"` - FlashRepartitioned int `xml:"flashRepartitioned"` - AmpOnTime int `xml:"ampOnTime"` - RetailMode int `xml:"retailMode"` - Services []Service `xml:"serviceList>service"` - Devices []Device `xml:"deviceList>device"` -} - -type Root struct { - XMLName xml.Name `xml:"root"` - Xmlns string `xml:"xmlns,attr"` - SpecVersion SpecVersion `xml:"specVersion"` - Device Device `xml:"device"` -} - -type ZonePlayer struct { - Root *Root - HttpClient *http.Client - DeviceDescriptionURL *url.URL - // services - AlarmClock *clk.Service - AVTransport *avt.Service - ConnectionManager *con.Service - ContentDirectory *dir.Service - DeviceProperties *dev.Service - GroupManagement *gmn.Service - GroupRenderingControl *rcg.Service - MusicServices *mus.Service - QPlay *ply.Service - Queue *que.Service - RenderingControl *ren.Service - SystemProperties *sys.Service - VirtualLineIn *vli.Service - ZoneGroupTopology *zgt.Service -} - -func NewZonePlayer(deviceDescriptionURL *url.URL) (*ZonePlayer, error) { - zp := ZonePlayer{ - Root: &Root{}, - HttpClient: &http.Client{}, - DeviceDescriptionURL: deviceDescriptionURL, - AlarmClock: clk.NewService(deviceDescriptionURL), - AVTransport: avt.NewService(deviceDescriptionURL), - ConnectionManager: con.NewService(deviceDescriptionURL), - ContentDirectory: dir.NewService(deviceDescriptionURL), - DeviceProperties: dev.NewService(deviceDescriptionURL), - GroupManagement: gmn.NewService(deviceDescriptionURL), - GroupRenderingControl: rcg.NewService(deviceDescriptionURL), - MusicServices: mus.NewService(deviceDescriptionURL), - QPlay: ply.NewService(deviceDescriptionURL), - Queue: que.NewService(deviceDescriptionURL), - RenderingControl: ren.NewService(deviceDescriptionURL), - SystemProperties: sys.NewService(deviceDescriptionURL), - VirtualLineIn: vli.NewService(deviceDescriptionURL), - ZoneGroupTopology: zgt.NewService(deviceDescriptionURL), - } - - resp, err := zp.HttpClient.Get(zp.DeviceDescriptionURL.String()) - if err != nil { - return nil, err - } - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - err = xml.Unmarshal(body, zp.Root) - if err != nil { - return nil, err - } - - return &zp, nil -} - -func (z *ZonePlayer) RoomName() string { - return z.Root.Device.RoomName -} - -func (z *ZonePlayer) ModelName() string { - return z.Root.Device.ModelName -} - -func (z *ZonePlayer) HardwareVersion() string { - return z.Root.Device.HardwareVersion -} - -func (z *ZonePlayer) SerialNum() string { - return z.Root.Device.SerialNum -} - -func (z *ZonePlayer) IsCoordinator() bool { - zoneGroupState, err := z.GetZoneGroupState() - if err != nil { - return false - } - for _, group := range zoneGroupState.ZoneGroups { - if "uuid:"+group.Coordinator == z.Root.Device.UDN { - return true - } - } - - return false -} - -// Convience functions - -func (z *ZonePlayer) GetZoneGroupState() (*ZoneGroupState, error) { - zoneGroupStateResponse, err := z.ZoneGroupTopology.GetZoneGroupState(z.HttpClient, &zgt.GetZoneGroupStateArgs{}) - if err != nil { - return nil, err - } - var zoneGroupState ZoneGroupState - err = xml.Unmarshal([]byte(zoneGroupStateResponse.ZoneGroupState), &zoneGroupState) - if err != nil { - return nil, err - } - - return &zoneGroupState, nil -} - -func (z *ZonePlayer) GetVolume() (int, error) { - res, err := z.RenderingControl.GetVolume(z.HttpClient, &ren.GetVolumeArgs{Channel: "Master"}) - if err != nil { - return 0, err - } - - return int(res.CurrentVolume), err -} - -func (z *ZonePlayer) SetVolume(desiredVolume int) error { - _, err := z.RenderingControl.SetVolume(z.HttpClient, &ren.SetVolumeArgs{ - Channel: "Master", - DesiredVolume: uint16(desiredVolume), - }) - return err -} - -func (z *ZonePlayer) Play() error { - _, err := z.AVTransport.Play(z.HttpClient, &avt.PlayArgs{ - Speed: "1.0", - }) - return err -} - -func (z *ZonePlayer) SetAVTransportURI(url string) error { - _, err := z.AVTransport.SetAVTransportURI(z.HttpClient, &avt.SetAVTransportURIArgs{ - CurrentURI: url, - }) - return err -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8f355ca..5d0fd6b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -79,9 +79,6 @@ github.com/hashicorp/mdns # github.com/holoplot/go-evdev v0.0.0-20230626094006-70c9462cc0f2 ## explicit; go 1.18 github.com/holoplot/go-evdev -# github.com/ianr0bkny/go-sonos v0.0.0-20171025003233-056585059953 -## explicit -github.com/ianr0bkny/go-sonos/ssdp # github.com/json-iterator/go v1.1.12 ## explicit; go 1.12 github.com/json-iterator/go @@ -217,23 +214,6 @@ github.com/pmezard/go-difflib/difflib ## explicit; go 1.20 github.com/stretchr/testify/assert github.com/stretchr/testify/require -# github.com/szatmary/sonos v0.0.0-20191204204454-000c9219ff0e -## explicit -github.com/szatmary/sonos -github.com/szatmary/sonos/AVTransport -github.com/szatmary/sonos/AlarmClock -github.com/szatmary/sonos/ConnectionManager -github.com/szatmary/sonos/ContentDirectory -github.com/szatmary/sonos/DeviceProperties -github.com/szatmary/sonos/GroupManagement -github.com/szatmary/sonos/GroupRenderingControl -github.com/szatmary/sonos/MusicServices -github.com/szatmary/sonos/QPlay -github.com/szatmary/sonos/Queue -github.com/szatmary/sonos/RenderingControl -github.com/szatmary/sonos/SystemProperties -github.com/szatmary/sonos/VirtualLineIn -github.com/szatmary/sonos/ZoneGroupTopology # github.com/tinyzimmer/go-glib v0.0.25 ## explicit; go 1.16 github.com/tinyzimmer/go-glib/glib From e738657c585194c7d4226b69b7e6690699540244 Mon Sep 17 00:00:00 2001 From: kaedwen Date: Fri, 21 Jul 2023 19:50:20 +0200 Subject: [PATCH 8/9] set pipeline to null to completely release the cam --- pkg/webrtc/webrtc.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/webrtc/webrtc.go b/pkg/webrtc/webrtc.go index e0ef051..d11aa10 100644 --- a/pkg/webrtc/webrtc.go +++ b/pkg/webrtc/webrtc.go @@ -93,12 +93,12 @@ func (wh *WebrtcHandler) startPipelines() { func (wh *WebrtcHandler) stopPipelines() { var err error - err = wh.audioPipeline.SetState(gst.StatePaused) + err = wh.audioPipeline.SetState(gst.StateNull) if err != nil { wh.lg.Fatal("failed to pause audio pipeline", zap.Error(err)) } - err = wh.videoPipeline.SetState(gst.StatePaused) + err = wh.videoPipeline.SetState(gst.StateNull) if err != nil { wh.lg.Fatal("failed to pause video pipeline", zap.Error(err)) } From 1dd37c1727cae7371bbfd631cd2e87e09c0b6036 Mon Sep 17 00:00:00 2001 From: kaedwen Date: Fri, 21 Jul 2023 21:08:36 +0200 Subject: [PATCH 9/9] add codec handling --- .vscode/launch.json | 3 ++- pkg/common/codec.go | 52 ++++++++++++++++++++++++++++++++++++ pkg/common/config.go | 24 ++++++++--------- pkg/common/utils.go | 4 +++ pkg/streamer/gst-sink.go | 57 +++++++++++++++++++++++++++++----------- pkg/streamer/gst.go | 2 ++ pkg/webrtc/webrtc.go | 8 ++++-- 7 files changed, 119 insertions(+), 31 deletions(-) create mode 100644 pkg/common/codec.go diff --git a/.vscode/launch.json b/.vscode/launch.json index 97966bc..37eaf21 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,7 +16,8 @@ "AUDIO_OUT_DEVICE": "plughw:0,1,0", "INPUT_DEVICE": "/dev/input/event5", "JINGLE_PATH": "${workspaceFolder}/audio", - "SONOS_TARGET": "Living Room" + "SONOS_TARGET": "Living Room", + "VIDEO_OUT_CODEC": "h264", } }, { diff --git a/pkg/common/codec.go b/pkg/common/codec.go new file mode 100644 index 0000000..8b1b3a0 --- /dev/null +++ b/pkg/common/codec.go @@ -0,0 +1,52 @@ +package common + +import ( + "fmt" + "strings" + + "github.com/pion/webrtc/v3" +) + +type StreamCodec string + +const ( + // video codecs + H264 StreamCodec = "H264" + VP8 StreamCodec = "VP8" + VP9 StreamCodec = "VP9" + + // audio codecs + OPUS StreamCodec = "OPUS" +) + +func (c *StreamCodec) UnmarshalText(text []byte) error { + switch strings.ToUpper(string(text)) { + case string(H264): + *c = H264 + case string(VP8): + *c = VP8 + case string(VP9): + *c = VP9 + case string(OPUS): + *c = OPUS + default: + return fmt.Errorf("unsupported codec - %s", text) + } + + return nil +} + +func (c StreamCodec) Mime() string { + switch c { + case H264: + return webrtc.MimeTypeH264 + case VP8: + return webrtc.MimeTypeVP8 + case VP9: + return webrtc.MimeTypeVP9 + case OPUS: + return webrtc.MimeTypeOpus + default: + return "UNKNOWN" + } +} diff --git a/pkg/common/config.go b/pkg/common/config.go index 55ea433..c0d0f88 100644 --- a/pkg/common/config.go +++ b/pkg/common/config.go @@ -47,21 +47,21 @@ type ConfigHTTP struct { } type ConfigVideoOutputStream struct { - Source string `arg:"--video-out-src,env:VIDEO_OUT_SRC" default:"v4l2src"` - Device string `arg:"--video-out-device,env:VIDEO_OUT_DEVICE" default:"/dev/video0"` - Codec string `arg:"--video-out-codec,env:VIDEO_OUT_CODEC" default:"vp8"` - Height uint `arg:"--video-out-height,env:VIDEO_OUT_HEIGHT" default:"480"` - Width uint `arg:"--video-out-width,env:VIDEO_OUT_WIDTH" default:"640"` - USE_QUEUE bool `arg:"--video-out-queue,env:VIDEO_OUT_QUEUE" default:"false"` + Source string `arg:"--video-out-src,env:VIDEO_OUT_SRC" default:"v4l2src"` + Device string `arg:"--video-out-device,env:VIDEO_OUT_DEVICE" default:"/dev/video0"` + Codec StreamCodec `arg:"--video-out-codec,env:VIDEO_OUT_CODEC" default:"vp8"` + Height uint `arg:"--video-out-height,env:VIDEO_OUT_HEIGHT" default:"480"` + Width uint `arg:"--video-out-width,env:VIDEO_OUT_WIDTH" default:"640"` + USE_QUEUE bool `arg:"--video-out-queue,env:VIDEO_OUT_QUEUE" default:"false"` } type ConfigAudioOutputStream struct { - Source string `arg:"--audio-out-src,env:AUDIO_OUT_SRC" default:"alsasrc"` - DeviceName string `arg:"--audio-out-device-name,env:AUDIO_OUT_DEVICE" default:"default"` - Device *string `arg:"--audio-out-device,env:AUDIO_OUT_DEVICE"` - Codec string `arg:"--audio-out-codec,env:AUDIO_OUT_CODEC" default:"opus"` - Channels uint `arg:"--audio-out-channels,env:AUDIO_OUT_CHANNELS" default:"1"` - USE_QUEUE bool `arg:"--audio-out-queue,env:AUDIO_OUT_QUEUE" default:"false"` + Source string `arg:"--audio-out-src,env:AUDIO_OUT_SRC" default:"alsasrc"` + DeviceName string `arg:"--audio-out-device-name,env:AUDIO_OUT_DEVICE" default:"default"` + Device *string `arg:"--audio-out-device,env:AUDIO_OUT_DEVICE"` + Codec StreamCodec `arg:"--audio-out-codec,env:AUDIO_OUT_CODEC" default:"opus"` + Channels uint `arg:"--audio-out-channels,env:AUDIO_OUT_CHANNELS" default:"1"` + USE_QUEUE bool `arg:"--audio-out-queue,env:AUDIO_OUT_QUEUE" default:"false"` } type ConfigAudioInputStream struct { diff --git a/pkg/common/utils.go b/pkg/common/utils.go index b30b21c..3e3229d 100644 --- a/pkg/common/utils.go +++ b/pkg/common/utils.go @@ -7,3 +7,7 @@ func Time[T any](runnable func() (T, error)) (time.Duration, T, error) { res, err := runnable() return time.Since(t), res, err } + +func Ptr[T any](v T) *T { + return &v +} diff --git a/pkg/streamer/gst-sink.go b/pkg/streamer/gst-sink.go index e210ced..b139f9c 100644 --- a/pkg/streamer/gst-sink.go +++ b/pkg/streamer/gst-sink.go @@ -1,6 +1,9 @@ package streamer import ( + "fmt" + + "github.com/kaedwen/webrtc/pkg/common" "github.com/pion/webrtc/v3/pkg/media" "github.com/tinyzimmer/go-gst/gst" "github.com/tinyzimmer/go-gst/gst/app" @@ -80,18 +83,35 @@ func CreateVideoPipelineSink(s StreamElement) (*gst.Pipeline, <-chan media.Sampl elems = append(elems, queue) } - // Create the enc - enc, err := gst.NewElement("vp8enc") - if err != nil { - return nil, nil, err - } - elems = append(elems, enc) + switch s.Codec { + case common.VP8: + // Create the enc + enc, err := gst.NewElement("vp8enc") + if err != nil { + return nil, nil, err + } + elems = append(elems, enc) + + enc.SetProperty("error-resilient", "partitions") + enc.SetProperty("keyframe-max-dist", 10) + enc.SetProperty("auto-alt-ref", true) + enc.SetProperty("cpu-used", 5) + enc.SetProperty("deadline", 1) + case common.H264: + // Create the enc + enc, err := gst.NewElement("x264enc") + if err != nil { + return nil, nil, err + } + elems = append(elems, enc) - enc.SetProperty("error-resilient", "partitions") - enc.SetProperty("keyframe-max-dist", 10) - enc.SetProperty("auto-alt-ref", true) - enc.SetProperty("cpu-used", 5) - enc.SetProperty("deadline", 1) + enc.SetProperty("speed-preset", "ultrafast") + enc.SetProperty("tune", "zerolatency") + enc.SetProperty("key-int-max", 2) + enc.SetProperty("bitrate", 300) + default: + return nil, nil, fmt.Errorf("unsupported video codec given - %s", s.Codec) + } // Create the sink appsink, err := app.NewAppSink() @@ -158,12 +178,17 @@ func CreateAudioPipelineSink(s StreamElement) (*gst.Pipeline, <-chan media.Sampl elems = append(elems, queue) } - // Create the enc - enc, err := gst.NewElement("opusenc") - if err != nil { - return nil, nil, err + switch s.Codec { + case common.OPUS: + // Create the enc + enc, err := gst.NewElement("opusenc") + if err != nil { + return nil, nil, err + } + elems = append(elems, enc) + default: + return nil, nil, fmt.Errorf("unsupported audio codec given - %s", s.Codec) } - elems = append(elems, enc) // Create the sink appsink, err := app.NewAppSink() diff --git a/pkg/streamer/gst.go b/pkg/streamer/gst.go index c14fb5f..d4fb58b 100644 --- a/pkg/streamer/gst.go +++ b/pkg/streamer/gst.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/kaedwen/webrtc/pkg/common" "github.com/tinyzimmer/go-gst/gst" "github.com/tinyzimmer/go-gst/gst/app" "go.uber.org/zap" @@ -28,6 +29,7 @@ type StreamElementCaps struct { type StreamElement struct { Kind string + Codec common.StreamCodec Properties map[string]interface{} Caps *StreamElementCaps Queue bool diff --git a/pkg/webrtc/webrtc.go b/pkg/webrtc/webrtc.go index d11aa10..41ba5f4 100644 --- a/pkg/webrtc/webrtc.go +++ b/pkg/webrtc/webrtc.go @@ -22,6 +22,7 @@ const STUN_SERVER = "stun:stun.l.google.com:19302" type WebrtcHandler struct { lg *zap.Logger mu *sync.Mutex + cfg *common.ConfigStream audioPipeline *gst.Pipeline videoPipeline *gst.Pipeline peerHandles map[string]*PeerHandle @@ -35,6 +36,7 @@ type PeerHandle struct { func NewWebrtcHandler(ctx context.Context, lg *zap.Logger, cfg *common.ConfigStream, ch <-chan *server.SignalingHandle) error { wh := WebrtcHandler{ lg: lg, + cfg: cfg, mu: &sync.Mutex{}, peerHandles: make(map[string]*PeerHandle, 0), } @@ -124,6 +126,7 @@ func (wh *WebrtcHandler) handleAudioSamples(ctx context.Context, cfg *common.Con Rate: 48000, }, Queue: cfg.USE_QUEUE, + Codec: cfg.Codec, } var err error @@ -168,6 +171,7 @@ func (wh *WebrtcHandler) handleVideoSamples(ctx context.Context, cfg *common.Con Height: cfg.Height, }, Queue: cfg.USE_QUEUE, + Codec: cfg.Codec, } var err error @@ -299,7 +303,7 @@ func (wh *WebrtcHandler) createPeerHandle(rctx context.Context, sh *server.Signa }) // Create a audio track - audioTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "audio/opus"}, "audio", "pion1") + audioTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: wh.cfg.AudioOut.Codec.Mime()}, "audio", "pion1") if err != nil { return err } @@ -309,7 +313,7 @@ func (wh *WebrtcHandler) createPeerHandle(rctx context.Context, sh *server.Signa } // Create a video track - videoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, "video", "pion2") + videoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: wh.cfg.VideoOut.Codec.Mime()}, "video", "pion2") if err != nil { return err }

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�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