1#!/usr/bin/env python3
2# Copyright(c) 2022 Intel Corporation. All rights reserved.
3# SPDX-License-Identifier: Apache-2.0
4import os
5import sys
6import logging
7import time
8import argparse
9import socket
10import struct
11import hashlib
12from urllib.parse import urlparse
13
14RET = 0
15HOST = None
16PORT = 0
17PORT_LOG = 9999
18PORT_REQ = PORT_LOG + 1
19BUF_SIZE = 4096
20
21# Define the command and its
22# possible max size
23CMD_LOG_START = "start_log"
24CMD_DOWNLOAD = "download"
25MAX_CMD_SZ = 16
26
27# Define the header format and size for
28# transmiting the firmware
29PACKET_HEADER_FORMAT_FW = 'I 42s 32s'
30
31logging.basicConfig()
32log = logging.getLogger("cavs-client")
33log.setLevel(logging.INFO)
34
35class cavstool_client():
36    def __init__(self, host, port, args):
37        self.host = host
38        self.port = port
39        self.args = args
40        self.sock = None
41        self.cmd = None
42
43    def send_cmd(self, cmd):
44        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
45            self.sock = sock
46            self.cmd = cmd
47            self.sock.connect((self.host, self.port))
48            self.sock.sendall(cmd.encode("utf-8"))
49            log.info(f"Sent:     {cmd}")
50            ack = str(self.sock.recv(MAX_CMD_SZ), "utf-8")
51            log.info(f"Receive: {ack}")
52
53            if ack == CMD_LOG_START:
54                self.monitor_log()
55            elif ack == CMD_DOWNLOAD:
56                self.run()
57            else:
58                log.error(f"Receive incorrect msg:{ack} expect:{cmd}")
59
60    def uploading(self, filename):
61        # Send the FW to server
62        fname = os.path.basename(filename)
63        fsize = os.path.getsize(filename)
64
65        md5_tx = hashlib.md5(open(filename,'rb').read(),
66                             usedforsecurity=False).hexdigest()
67
68        # Pack the header and the expecting packed size is 78 bytes.
69        # The header by convention includes:
70        # size(4), filename(42), MD5(32)
71        values = (fsize, fname.encode('utf-8'), md5_tx.encode('utf-8'))
72        log.info(f'filename:{fname}, size:{fsize}, md5:{md5_tx}')
73
74        s = struct.Struct(PACKET_HEADER_FORMAT_FW)
75        header_data = s.pack(*values)
76        header_size = s.size
77        log.info(f'header size: {header_size}')
78
79        with open(filename,'rb') as f:
80            log.info(f'Sending...')
81
82            total = self.sock.send(header_data)
83            total += self.sock.sendfile(f)
84
85            log.info(f"Done Sending ({total}).")
86
87            rck = self.sock.recv(MAX_CMD_SZ).decode("utf-8")
88            log.info(f"RCK ({rck}).")
89            if not rck == "success":
90                global RET
91                RET = -1
92                log.error(f"Firmware uploading failed")
93
94    def run(self):
95        filename = str(self.args.fw_file)
96        self.uploading(filename)
97
98    def monitor_log(self):
99        log.info(f"Start to monitor log output...")
100        while True:
101            # Receive data from the server and print out
102            receive_log = str(self.sock.recv(BUF_SIZE), "utf-8").replace('\x00','')
103            if receive_log:
104                sys.stdout.write(f"{receive_log}")
105                sys.stdout.flush()
106                time.sleep(0.1)
107
108    def __del__(self):
109        self.sock.close()
110
111
112def main():
113    if args.log_only:
114        log.info("Monitor process")
115
116        try:
117            client = cavstool_client(HOST, PORT, args)
118            client.send_cmd(CMD_LOG_START)
119        except KeyboardInterrupt:
120            pass
121
122    else:
123        log.info("Uploading process")
124        client = cavstool_client(HOST, PORT, args)
125        client.send_cmd(CMD_DOWNLOAD)
126
127ap = argparse.ArgumentParser(description="DSP loader/logger client tool", allow_abbrev=False)
128ap.add_argument("-q", "--quiet", action="store_true",
129                help="No loader output, just DSP logging")
130ap.add_argument("-l", "--log-only", action="store_true",
131                help="Don't load firmware, just show log output")
132ap.add_argument("-s", "--server-addr", default="localhost",
133                help="Specify the adsp server address")
134ap.add_argument("-p", "--log-port", type=int,
135                help="Specify the PORT that connected to log server")
136ap.add_argument("-r", "--req-port", type=int,
137                help="Specify the PORT that connected to request server")
138ap.add_argument("fw_file", nargs="?", help="Firmware file")
139args = ap.parse_args()
140
141if args.quiet:
142    log.setLevel(logging.WARN)
143
144if args.log_port:
145    PORT_LOG = args.log_port
146
147if args.req_port:
148    PORT_REQ = args.req_port
149
150if args.server_addr:
151    url = urlparse("//" + args.server_addr)
152
153    if url.hostname:
154        HOST = url.hostname
155
156    if url.port:
157        PORT = int(url.port)
158    else:
159        if args.log_only:
160            PORT = PORT_LOG
161        else:
162            PORT = PORT_REQ
163
164log.info(f"REMOTE HOST: {HOST} PORT: {PORT}")
165
166if __name__ == "__main__":
167    main()
168
169    sys.exit(RET)
170