From patchwork Fri Jun 2 13:20:18 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Storm, Christian" X-Patchwork-Id: 1789619 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=googlegroups.com (client-ip=2a00:1450:4864:20::13b; helo=mail-lf1-x13b.google.com; envelope-from=swupdate+bncbdd6bwv65qpbb5gy46rqmgqeequhniy@googlegroups.com; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=googlegroups.com header.i=@googlegroups.com header.a=rsa-sha256 header.s=20221208 header.b=Yu3HFhfx; dkim-atps=neutral Received: from mail-lf1-x13b.google.com (mail-lf1-x13b.google.com [IPv6:2a00:1450:4864:20::13b]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4QXkBJ4cZWz20Tj for ; Fri, 2 Jun 2023 23:19:52 +1000 (AEST) Received: by mail-lf1-x13b.google.com with SMTP id 2adb3069b0e04-4f4c6244937sf1526702e87.2 for ; Fri, 02 Jun 2023 06:19:52 -0700 (PDT) ARC-Seal: i=3; a=rsa-sha256; t=1685711989; cv=pass; d=google.com; s=arc-20160816; b=qSpzJbh290U6U4t2J9LPm6ACSqLB1PpR6EhkvjUPot/qTVh+4s3qQ0i2pw8dkwKVOZ NPF8jE+A2LzXCpDF9CO5L39aPgLEaL1FcaNRreVbX+3Fa+shxodG5ctMPFPfWQvd4Zv+ IcYj5Ojs1+5nGWVag2Vr4WswTva+qlK9qcu/bbauSEZP8yMZc1JbZFwb5MWDLhdjCVBH mTMzkmDeF6xH7XTFm/jHqJ5rwG1hiNp4aN0id2qL74iyhTAX22KsjyFqe7Fp29SgCAbO zxyTbF0oBYZFOmuuMoK7ayvGyQ5OKNwPPu+4pWlO35l4+oPr2VjIwebCrYYP24HjjPyu 900Q== ARC-Message-Signature: i=3; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:reply-to:mime-version:message-id :date:subject:cc:to:from:dkim-signature; bh=FxeOo3ARVSuVh5vAkwH8Dk9StuCb1HbtSlWwrn75LH4=; b=Q6gtcJCIL5gbtQtf/sfLO4pSTRN2Ku4VxzPOSccUx6pfoa0TZPygxOw8wgvT3Ft+zj 6Qzz4VNpbdypLI8ejqbqu1DQ5g3Ve9KrfVJe8t/2S9WN9yhWysHDi8hOu2MO7Ki1T238 ipz5R2ENKPd9pMoihDu35mSdsAFjnNgy2dydJCrc3AVVqZWwZgzXxL+O340Fw78fX7+q ao/g/jnsNs5tk/4VgNHPskRR45W6fPxDJUipXvUgHC9Iivptc4h6sN5EBZ3o7+vYYaeI uTzesjou4heT0nK3iXFjkbACoLgiLnpiD7v5PUfWuI8DWz537hxiWPehseqgtWtaI1Wf wVGA== ARC-Authentication-Results: i=3; gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=mBnUT+gY; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::62f as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20221208; t=1685711989; x=1688303989; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:reply-to :x-original-authentication-results:x-original-sender:mime-version :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=FxeOo3ARVSuVh5vAkwH8Dk9StuCb1HbtSlWwrn75LH4=; b=Yu3HFhfx+bKX8TbJeRT1LmzzmBaJ1hWhZYcif/z2YJdQOopHM1hySKTcMTNwGd8i+w hMWuW2BRFrCdn3gyj8Py1D+RpxXqb6h7mclCYxSi0y+HhIiJjo3XAW4+w3nbU6IlElVt RVrc7gfTHfZ+KcTRDZQpWI1vs/od1w+k9RMzmtnWW6MiOnF8TII3e8FuIlvl+c4kOA68 FDzFrkXJ/dpdmQmPuytKrRrRoxGtnD2GjFl84d9P4TZjhHmC9DIDxPm/sSjYRxZxUbqq woPlCMzKCDMUN6Zt2OBZeGbvODR07MvJSt4ccQdjTMmSS9dQGhP+I/s9zixUWkN9qBsM ANPg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1685711989; x=1688303989; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :x-spam-checked-in-group:list-id:mailing-list:precedence:reply-to :x-original-authentication-results:x-original-sender:mime-version :message-id:date:subject:cc:to:from:x-beenthere:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=FxeOo3ARVSuVh5vAkwH8Dk9StuCb1HbtSlWwrn75LH4=; b=TfQ3aTEeR0pxj3U8NeggNV8e+/yMILbItHMOGk1x73qCVQpR7JIhWjDVCP5mi0TW4w c1aUwfJmEMvbzmr+anr71htLXrRIFBQJE7lbL1gEWtRzi86VPJXLF1/Is8Z4Mk21LiLe kkzo2yS+ds8x+tPNh5xDWGrcEdAuD5nXXmHQWyO23vZZg4x/MsVabuXOpmg28nssXJfQ V8Jgu0c+Bw7lHhwYv0zwVSHRr2VfJeTXBQbTSe4CmbMPE5NARJXwWAEZSBo/hIfTjtyv cNlDwuvTVbzqCwkWixqhb5R0/O0z0RboBaRFLKFQJxbcz50RU+nIbm0KvP2IhMvvR8L8 kS0g== X-Gm-Message-State: AC+VfDww7OaU9WKKDEGIVzNCJO7p1pWzdxA8EKiltR8Jm3DRBjgs4l59 fPzamG0MKypEN/sPwDDG1Rg= X-Google-Smtp-Source: ACHHUZ5xcKF9rjmzcilqCe52mHn3ylx6LCdk9Un6e0aiWUpJaOeV6G0D5c96x+gqzwID4BPP4ytvrw== X-Received: by 2002:a2e:9e05:0:b0:2a6:16b5:c658 with SMTP id e5-20020a2e9e05000000b002a616b5c658mr1726588ljk.31.1685711989021; Fri, 02 Jun 2023 06:19:49 -0700 (PDT) X-BeenThere: swupdate@googlegroups.com Received: by 2002:a2e:be8c:0:b0:2af:1a18:4819 with SMTP id a12-20020a2ebe8c000000b002af1a184819ls8870ljr.0.-pod-prod-05-eu; Fri, 02 Jun 2023 06:19:47 -0700 (PDT) X-Received: by 2002:ac2:5e9d:0:b0:4f3:895f:f3f8 with SMTP id b29-20020ac25e9d000000b004f3895ff3f8mr1989281lfq.16.1685711987460; Fri, 02 Jun 2023 06:19:47 -0700 (PDT) ARC-Seal: i=2; a=rsa-sha256; t=1685711987; cv=pass; d=google.com; s=arc-20160816; b=fkZJMAEbPQG84+eCnSjCjAB18F0Sc6p6arIPVPkYXH3z5vRfFB53Uk+TvcbHJSIuoI sgmoE1kYgQZGl7YYouehJKh3rXVG9uftCIF8juR4UZ+32ApEJ/zrVTAMN0Ex5OSfpyCJ WfU8TC088STobKRpjlmG9Plo7mWqUIXpqOFkxmaFO4pBD+RXkGT2ftYbug6zkijVFteQ zuVSXcwSDyocL5Ws/6NVzN7QcjDgypVyMWH4GGtw2JN9FLLdjmzwlI79jAWfEt8yRMdm YfH14Aiu1PRg3RXU162dLgnHRfLI4EJVjJ5BmFI3gCqUPtciPRJsY7R3xrU2FoKPgw4m iH/Q== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=mime-version:content-transfer-encoding:message-id:date:subject:cc :to:from:dkim-signature; bh=dvFvO9QHBNZVyQ3WZeDrH/q/mJcN8dazW35dLayHQ3I=; b=xPi7IanNKVlwItcuuwRzguyLUbl4Phs5pMmgbXBVVhV6fHbRZfYXYFobQmF2RzY6Ye F5BAET8VzOlDozc3kKdyQRlQsiRqsLsi12n3UV9HWdo+bAxIhSf0fAsuyUgCCezH7enY oBZU+XphvdtUxG+Z0xaQujNg7l3BtXq3CYaDrxrGL36N1enkxFBp3Ufi9+alFYuZCOD1 sNyNxhPZfAgNa8wYUvkIJJ0P0a1Ygu5ek3kHq0daj1sygRc+KMESE45Nf/u8YsDUbd3/ 8UBrQ86hMDc33pzFdFkhqJHXF7lAEIYoKVPL9jVxlyMpWJspYfbEX9gnD7v12KDU+/dT FXQg== ARC-Authentication-Results: i=2; gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=mBnUT+gY; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::62f as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com Received: from EUR03-DBA-obe.outbound.protection.outlook.com (mail-dbaeur03on2062f.outbound.protection.outlook.com. [2a01:111:f400:fe1a::62f]) by gmr-mx.google.com with ESMTPS id f8-20020a0565123b0800b004f194563eb7si92670lfv.10.2023.06.02.06.19.47 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 02 Jun 2023 06:19:47 -0700 (PDT) Received-SPF: pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::62f as permitted sender) client-ip=2a01:111:f400:fe1a::62f; ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=hWaiRJSYvPabVtWVidbcdxdkW2HeUprfQhnlNxZxFqKk/ciiT8dKcX56wz4JrOACdJets7cuL8m6avHxQf9xLnxpHuKpPAWvBbmWwUvbH+YSIWQCaeooLi7yi7jtbgvgM4TtwqO5K6FoNtBoMtlkCPHNAnMLO0+LbWntdXuzQ5KNuV081g5GLE7ny49C4lNcgUNIakgkkLqOGRvpA6sh2yvlZ5fi/Yv6SoJO51fWa6KUL23q6HLhXLhkNafMK+Iail7fcMyg7yYmkDMpE6GY1HbDm4+D/NKLCk5uHsMsBWvgzvK7Znlow+orshm+9YrJcDaNYcYJpA5VSSA0WCbxxA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=dvFvO9QHBNZVyQ3WZeDrH/q/mJcN8dazW35dLayHQ3I=; b=KK0PyKuBW1aAxfVCakETKx/fRMc8XejAzhpNIPeNlQgDP1zsCDcInjgvxPT6jSq2Vjl7YsXYuAyDhL9USw54LnVwvvdXZg6CiQ5ugrow9SVis+S4v4V0po930oBUqzvK9pyZgex24Ue1pW5X0M1WZ+f1hP/Sifhbjn87UPA9AKq015KkpTL0RtG5qTv+dyfNQStMkhJ6MeC1jexXjM1BRAaSP75MCIYTnn3BgnkjojBoJNJtJ7ck2C0npR+s595s7tjc/gO31+gk/nnXiWzLQMR29q7o7dP695YbTdKDYmzyoAhSFmY/2YdDVYZ6InHwTo8ifbWUbOLM2y2JySMfNg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=siemens.com; dmarc=pass action=none header.from=siemens.com; dkim=pass header.d=siemens.com; arc=none Received: from DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:10:333::17) by GVXPR10MB5887.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:150:6::15) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6455.23; Fri, 2 Jun 2023 13:19:45 +0000 Received: from DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM ([fe80::6bed:3b93:4756:c9f3]) by DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM ([fe80::6bed:3b93:4756:c9f3%6]) with mapi id 15.20.6433.025; Fri, 2 Jun 2023 13:19:45 +0000 X-Patchwork-Original-From: "'Christian Storm' via swupdate" From: "Storm, Christian" To: swupdate@googlegroups.com Cc: Christian Storm Subject: [swupdate] [PATCH 1/5] Lua/specs: Ignore missing returns diagnostics Date: Fri, 2 Jun 2023 15:20:18 +0200 Message-Id: <20230602132022.70931-1-christian.storm@siemens.com> X-Mailer: git-send-email 2.40.1 X-ClientProxiedBy: FR2P281CA0153.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:98::20) To DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:10:333::17) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DB9PR10MB5404:EE_|GVXPR10MB5887:EE_ X-MS-Office365-Filtering-Correlation-Id: 4f28c531-c724-4238-9ecc-08db636c0878 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: 6PovIzn62PkYgUJcvL/zBlBC9NqQt7msZVk3CBkvxNnE/yCycwndEhL8fyRRH9HYZwiN1bMJQ4wYTjxUtFhrLn1SYvCUD271FjB3BhNku88ow8kM02ZZ8BfukfyUJrnToKbLwSwbv6OruT/E3wnno/BeFgQZadPfHG609gI4ow1gnqiN1OXiOi+gnCDWZHAdCUpWAkOeoU2QAqYVfiDgPlVr4Weo1XweOzbBWJ6VoyFFigjhN6ecg+VwPHPKt6YVVt+5OvqJMcC2No7RZdRfgkETL+0JXBqTNrPV2pQTU0pZdIsu9qbpohv9j1h9gQ8Q1otyj0Qr6EGlV04dp/3VWTOrBysn9yfMM9jP0m7mpomxZJ1TsiEnZGfW0k/DuN8i/Zd/tyV7XXQ+it0Fyw8nWXVhTBVUVPL5G0sNFctosF5UNUoK1H91dsOeynD5fRuK7D9Ue83bRmLpQUgdtGqDsTqKIz4MLC8eq4OH7sf/k3TzswUtz17FJcQg1sUjdwx1Q7p9IqH3blpBjS5BLHvgO129fh6RXgV/O+QgXhbU/hg8RBSOQkiVZ1+bKujcNcLj X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(13230028)(4636009)(39860400002)(376002)(136003)(346002)(366004)(396003)(451199021)(6486002)(478600001)(6666004)(83380400001)(186003)(36756003)(6512007)(2616005)(38100700002)(6506007)(86362001)(82960400001)(107886003)(1076003)(316002)(4326008)(41300700001)(66476007)(6916009)(66556008)(66946007)(44832011)(5660300002)(8676002)(8936002)(2906002);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: ygTIjtXCI99qOeIF0I8EJwXTYSdu2zRSzZWwe/+rphdgQASNC8OIpH0xXs8ksTZDnQfDrEt8TAm1oW1gLtk7UERdieh8yuMg1Nw07QxFu0LzjDorQaIcm21ttajnfS5ihvpfr+aNvt5IOFPFcXUU3kFkLIjQ4tPon16xT9iOviP1SDV4w8zVQGGCKJWQGNzGdmexMkkbdrLGmBxlslqDWDj+dNRp2nVJtL6olEqyMepB1Jgm29G/X8EseSoHiTQqHab9NXa88q1bN1bVRykANxcbDvsvGiioxXzwWyyW5HVxe1zNe0JDAqiwvxjgJocnYCWD7AgN3Y9izY4Rh17EKSKvA6CDZuNUWWPG7kA8sWDFTwZoE9vguOwppRKKUTxMp9cRu59lTNG96cHfXGq8rsJWoKkh3C2/gijsrlaAFov9QZfFzgOjvunipta+wJpX9SsWZ1JbigYuk3ByzBOyCwQwVvX6c6XtjOqlNm6Omhiv7jvdNsSTOm9gfyqmqQgIVNZ4nSqzzfHjzEkEVosjgZ2JMznMumg59LYcNPUM8uHIwSIQHQB9KdVDxBaExTbFkkBPLGi+JUq8KMsTeBYcJ76gUrNzXIjTr6fZXUlA7SDDYAUTpikYs8jR0IDOhFhTkXRoPqBIDINkcgbx1ogRwUUIraoIDPeF0M0c3TTH5N0+KVFbl38+QXxJC8hXnkCGya4xraY3f7Vo63uTDMvMIOn8iIYpZEAIDidkX0oKkn23R7JJ5ZztUsGnWv2Qx4x5piXuT0/4tFCdubC3Dh9dC8D5PAqf2GDRBwK0/su2Fc4RQC9MZmQnK9pzmeL5w5R5YvKjyZ25Mv11L8dcFb2M/VI1pbF6JNyvhTmWxcuRxctzLn8pknlhc/cPHhBbW6BYIukfZ7jf68bJgIqpAf3ymXpJy/CJSNMOb2csbB/GgDaMR58Zbw8OmOXiYQYkPKTarDWFa9wceBEn3jstbY5p+7Wudp3DrxZ9uPNjYkBJ/Ca33YInIUooOvDeGULK9VzylibXaDIX2c0sMYPXvAYVmBHyuLl61+1rcXv0D6CEptzL7IQ6qEm/VwXQ39aRvdqVLkknhvQqAH2z5FHrIv64c63hSqyrPK8b9CinnveRNVoK73eH1wZhNWnmilQQYGmqgWr+fdBwzyqn1J0sttGYUO1qjoUAhj2Wcwlsll3IJbnu9me1B2EFRiRTa2MngN+cGAYy8lVIfsP6Qpx9QTX3Wy2OOM8fh6nAfxUXttMMyt/8HHfvttjgsGIO6VrLmO1PHXXBu/N6MJxiqEzcAW4CZxzxMJLKAS3YXhpjtKPvEn9n6bnKv60Acs1CQFGIUemD96Gx1ofK1JCA9pVaqgq2KSZiapI6kYOKCaXfILVJ0bNuVy6z5ucbNMx8tKegcpyuDcl6hYAe5kCd88KF0KC/KAHEu+3WQGzgbkvfIMFbxFI6mRcJZxxzsmHv82eXSrM7sbKlNtcGmrjeTYvl4GN0/7K4iaMkoaLNDw7vCrfLbOvSQ8DsJOm4s2DSG/fWm4A1gnnxKjkDd4nmWe60cG3Ewu7l7VnesHSoWI67hZSLBoYkObJCVDG3avOO8Sl6FweqHnpZi7FhOMHsefqIk+79048TYRQPjUumB0HaK8nPLNzY1cFNWOjsNPvg2myYzwreFzSqEMNUVPm04MSslP6KvQ== X-OriginatorOrg: siemens.com X-MS-Exchange-CrossTenant-Network-Message-Id: 4f28c531-c724-4238-9ecc-08db636c0878 X-MS-Exchange-CrossTenant-AuthSource: DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 02 Jun 2023 13:19:45.4709 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 38ae3bcd-9579-4fd4-adda-b42e1495d55a X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: ca0WO+pZvPJ95ow5h+tmC1g8OjDuhDV0HKUJ8CoYEyUF8ugPV3f+FVU19yvpfwM4rothZDqS3vliG0ybtC6SasH0mVZe3sw5FZatWylJ3yQ= X-MS-Exchange-Transport-CrossTenantHeadersStamped: GVXPR10MB5887 X-Original-Sender: christian.storm@siemens.com X-Original-Authentication-Results: gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=mBnUT+gY; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::62f as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com X-Original-From: Christian Storm Reply-To: Christian Storm Precedence: list Mailing-list: list swupdate@googlegroups.com; contact swupdate+owners@googlegroups.com List-ID: X-Spam-Checked-In-Group: swupdate@googlegroups.com X-Google-Group-Id: 605343134186 List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , Disable the `missing-return` diagnostics in specifications as it doesn't apply to the purpose of specifications. Signed-off-by: Christian Storm --- bindings/lua_swupdate.lua | 1 + handlers/swupdate.lua | 1 + suricatta/suricatta.lua | 1 + 3 files changed, 3 insertions(+) diff --git a/bindings/lua_swupdate.lua b/bindings/lua_swupdate.lua index e6a061f7..3ac1b042 100644 --- a/bindings/lua_swupdate.lua +++ b/bindings/lua_swupdate.lua @@ -12,6 +12,7 @@ --]] +---@diagnostic disable: missing-return ---@diagnostic disable: unused-local -- luacheck: no unused args diff --git a/handlers/swupdate.lua b/handlers/swupdate.lua index c83a9edd..465047e3 100644 --- a/handlers/swupdate.lua +++ b/handlers/swupdate.lua @@ -13,6 +13,7 @@ --]] +---@diagnostic disable: missing-return ---@diagnostic disable: unused-local -- luacheck: no unused args diff --git a/suricatta/suricatta.lua b/suricatta/suricatta.lua index 9dc1bcfe..1fa32180 100644 --- a/suricatta/suricatta.lua +++ b/suricatta/suricatta.lua @@ -12,6 +12,7 @@ --]] +---@diagnostic disable: missing-return ---@diagnostic disable: unused-local -- luacheck: no max line length -- luacheck: no unused args From patchwork Fri Jun 2 13:20:19 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: "Storm, Christian" X-Patchwork-Id: 1789620 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=googlegroups.com (client-ip=2a00:1450:4864:20::23d; helo=mail-lj1-x23d.google.com; envelope-from=swupdate+bncbdd6bwv65qpbb6wy46rqmgqeivxduly@googlegroups.com; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=googlegroups.com header.i=@googlegroups.com header.a=rsa-sha256 header.s=20221208 header.b=edEhAoik; dkim-atps=neutral Received: from mail-lj1-x23d.google.com (mail-lj1-x23d.google.com [IPv6:2a00:1450:4864:20::23d]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4QXkBS5lGkz20Tj for ; Fri, 2 Jun 2023 23:20:00 +1000 (AEST) Received: by mail-lj1-x23d.google.com with SMTP id 38308e7fff4ca-2af17f626e3sf17466141fa.0 for ; Fri, 02 Jun 2023 06:20:00 -0700 (PDT) ARC-Seal: i=3; a=rsa-sha256; t=1685711995; cv=pass; d=google.com; s=arc-20160816; b=FFplvLo/I7odcSxqPcJeXvF+JKmIUUYcDORjUx6g3sSJuSuO2BLuquu7wGuQCWqLZ8 PBWRM2y12RhirQT8fwPlF4cDvhhV/dhiFrd7PnbPZLAbYLYyRsjwakWWjRni7Hyyq5Uk Nd6vI6UiQKeBeFZUOWexjF7kpGCuqLXubw81/cWgFSOqL6Dya8C8lofRcCRhUJHclu21 nkUX/v9sc3XSFhbVePJpb/su6+zx3MBThSBRf8CyjAbJ5WRZ2NXoid3qGgWWF4mDSwIn lYpR5HF17zumRHcPEjuofhLCfpbLiWmSpwp2rzeV82kKLo/7Ijrb3AbcS1Um0CtOsAAr bhFw== ARC-Message-Signature: i=3; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:reply-to:mime-version :content-transfer-encoding:references:in-reply-to:message-id:date :subject:cc:to:from:dkim-signature; bh=5BylVeELJ6KZF0T3DA67HKGMSrbLwVyWCxcYIKzI2Fw=; b=cco2Iw6ty0AlYP0Cu40xG1IRUhY4Xj5s0Yna0uY1HNUOkenS1ax7QlRxYX7yR/UnYb vKV9x39gjfEhoriDqQjoWoaFXbUCis7NPKA9Zh0z8aS9NdKMr6fmJxe36o3U6SFpBo1V icEajqlz3NntiXuVe5EVRd5x2/LIenCqJ7H+p1BNF6AuBo2+RcgCgQHaCw8cc3ndQfIu QTGvvmBQ/wfUY5mf846kLOYiVTb9R0IfRgx6Xp1Ekllu6jxp5FS4wnhipERMr0wjzkTs zuHba3VgSA1T07f1gX5P6kKG7NgErtipUI5lXxog4lPTqBIbp7h/nLjnCHOVnbjpWKka 00AQ== ARC-Authentication-Results: i=3; gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=ATPyq2I5; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::628 as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20221208; t=1685711995; x=1688303995; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:reply-to :x-original-authentication-results:x-original-sender:mime-version :content-transfer-encoding:references:in-reply-to:message-id:date :subject:cc:to:from:from:to:cc:subject:date:message-id:reply-to; bh=5BylVeELJ6KZF0T3DA67HKGMSrbLwVyWCxcYIKzI2Fw=; b=edEhAoikTYhNQleWbUFsAlqJL4Ul2j9tLdA5JcnhDBOzOLMAy4Aw7uJ/s3enh6qLgN JyyAlv2MCF0y2Q/qPoInCskOf0oGQctBvK6n3HJhIOD6/VgQc7fqRu0hIV/cipzg94py ip9Aj/TPEyc6C2bPk7jI/2V/WlayuCZccdoR+I2KrnD2ClkhEb0prjRanu/YWYS0P1LX nuC37QkG6mjM48ieobFe93POSbf3v/fLQmAvB+ThjTw/sf8+LqOzinpd1u+jjre2DEnK KYlgV7o1DoCPEHNl0hdHCxviwGgnHBflNg7eZ6CO9r/DT9v830u5sUVXPkWeH0eP7ggv 36sA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1685711995; x=1688303995; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :x-spam-checked-in-group:list-id:mailing-list:precedence:reply-to :x-original-authentication-results:x-original-sender:mime-version :content-transfer-encoding:references:in-reply-to:message-id:date :subject:cc:to:from:x-beenthere:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=5BylVeELJ6KZF0T3DA67HKGMSrbLwVyWCxcYIKzI2Fw=; b=hr8+2hKJmayKgmUv8Vbp4zUVeGFctgXcX09TDMcxnjDNdDr4AJFZFYPcIZP19O1TcN TdBsIOUkJNmHz9e9S76pV4gtE2dmV+Z5rLyknrDM9QIIAVJLkll8rCk5akc26hxNYXAl hcHpmaNv3cSkqQGm1eW4JhLdPI6tXiAoKFHh6ac4pSvsXVgIZI0e8tCZPVKX+PpzYFJq d2tciJ0T8Hl/o5QV+Qb6voWimzUKgbFhuG/aUQevB0nEp5Ak+8cMwmZBXmSTUgtTMP1i LoYfrqbHin4b5CF5GIPPT6WVo6e6NlTgA6YUKvhZqbF08qQDmx7O2kH1Eni72kxG4Uze ZQRQ== X-Gm-Message-State: AC+VfDwOwCG9c45MnAOtboa9rpNZTeoi7MDrPNTL+bNZIi8cS0eeP5Uz tmHNOsoZ0KHNZrGXTFQsel0= X-Google-Smtp-Source: ACHHUZ7BCWoCKgGo8o0Tcf4Y+G1zZ2ZCxdQxdBHbtbjEFl32lQG6JUPiWwIg7ivQAu7K5EXWr2iWdQ== X-Received: by 2002:ac2:593b:0:b0:4d8:8ad1:a05f with SMTP id v27-20020ac2593b000000b004d88ad1a05fmr1646319lfi.48.1685711994831; Fri, 02 Jun 2023 06:19:54 -0700 (PDT) X-BeenThere: swupdate@googlegroups.com Received: by 2002:aa7:c2cb:0:b0:514:a425:62bd with SMTP id m11-20020aa7c2cb000000b00514a42562bdls532817edp.1.-pod-prod-02-eu; Fri, 02 Jun 2023 06:19:53 -0700 (PDT) X-Received: by 2002:a17:907:84a:b0:965:6075:d0e1 with SMTP id ww10-20020a170907084a00b009656075d0e1mr10724678ejb.72.1685711993318; Fri, 02 Jun 2023 06:19:53 -0700 (PDT) ARC-Seal: i=2; a=rsa-sha256; t=1685711993; cv=pass; d=google.com; s=arc-20160816; b=Ux6AnPpfTZQQwdSdQJ9tJ/+2Nf+7meqhtllU1aCtbbU6BeaXSVm5WYGscVTylVISAI /myOuI+HTEWfjB3bDdmXbR2VZA8SxtCm5JuRr/3WqGnKfTp5qI0mHOH27aw4Ky3QQBp+ uHQ9G0rqrttVs85kxL2f9EjlerYd9lZQAW4AOAkifVc/oPiKn0FkkjNByas4gmBUCpt8 pnBdvGfC0DRpfG4+uMW333+ZjoeCF/DKLdn/X3/exhah4v6HtsmobCJet9tUl/h3r0cx Cf6gznU7ij+eYmazwfykHFIrsLVxYKSafj5KmYLRtyK5ETUL90qj3enzo87EAWGKHDyH P1GA== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=mime-version:content-transfer-encoding:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature; bh=jv6kxTzrIb2wTb1DbzqCPEbXQsFmlyTZ5OBNB0VSVmM=; b=imhcg9w0qyf+zjrbq+mK+18kI46rHTU+xFq8x/bOPA4d1f0wnfn88TuXzZIbIsa+xi GFg/QOzeryBL3EexvFvYDB4DQ/+eZs15Ig+z7YyjTN+YOuDbEq6r7KRFVptMfgDt9boo 1gf0nuDKDu2q2CuClkc5p4nOxqVQ3Gli9xg3TmTD1c9O/fv4Xje7ewyKXBteuKUcyY99 QxR2LiVlwCbdF6DXedHsCRnKzP6bAwdkiDTG39hXIXu7Nt4ciAqqTZ6QQpNTbY0og/Eq Fb+osA/w7HxqaonfuGOY36kMUMloAVNSqNXxttaVI6VJxvsdWMswtnIHkyyEGzfUtgh/ Xgvg== ARC-Authentication-Results: i=2; gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=ATPyq2I5; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::628 as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com Received: from EUR03-DBA-obe.outbound.protection.outlook.com (mail-dbaeur03on20628.outbound.protection.outlook.com. [2a01:111:f400:fe1a::628]) by gmr-mx.google.com with ESMTPS id tc25-20020a1709078d1900b00974573ac6absi133255ejc.1.2023.06.02.06.19.53 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 02 Jun 2023 06:19:53 -0700 (PDT) Received-SPF: pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::628 as permitted sender) client-ip=2a01:111:f400:fe1a::628; ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=JbIjqwl05odx9v43fJ7Z2fVl2Udr61qyqJZqJPcAJTOv2t1xR6Q0WZS+ACWc08VtOP6sXM+AO2o/YCZeq2ZktDiQgbA6w6IzKL4mUx9+i3qNC+H6zgUCyqe6vdMHOfySMGLz2JEcfjXQOCKhueW/bydjs8Xv2NCIp5QJIjOq344rYL1bOw6R9Uj/tZtfGBfBmk18u3mG0NNZxVk3UpnJ9nFeMZ4exS1BvHdKRv3KFqKLnOrYHGQ4q03ErD7Z0yVsVNiLz7YHc2IBiWHgEfAgzBDJOtTTj1+S0uKKPZjuvxOk5p7lLLMSMs/F1XyZlFDlYyUVuAwMdXUffY0BRn5yyw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=jv6kxTzrIb2wTb1DbzqCPEbXQsFmlyTZ5OBNB0VSVmM=; b=iUewFATPGppJTih0h7+mh1RMsuHvnJf30LgRh60L5rL1GizBB3zJmNrEDYYRNStzY8Ua3xdGGxxjrf8L5c/0/w0r7MEHxy/e7ZSnt74qTrphjdB8xf0pqGNZFg42fRYtIGmfj50FvX4jrwrRE9yMGlQW4uaFZKCSbirgZj8nL58H+h9Eqrj3ofAIdrvir6ShUpmmhMvukaCTtBu3QG7R4fiw2NT/aY3e2JMNtCMVQsVoPI7iUqUONvSK6xDtlVLnU+RdwjR6Zoum6naFs0ZjIJZ6Qel/JvzysS4uNIvxZ8bEzVlkEqCRrzqW1Gv6H7AJqC/AXe2oE9LJ3DvRjpp+uw== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=siemens.com; dmarc=pass action=none header.from=siemens.com; dkim=pass header.d=siemens.com; arc=none Received: from DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:10:333::17) by GVXPR10MB5887.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:150:6::15) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6455.23; Fri, 2 Jun 2023 13:19:51 +0000 Received: from DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM ([fe80::6bed:3b93:4756:c9f3]) by DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM ([fe80::6bed:3b93:4756:c9f3%6]) with mapi id 15.20.6433.025; Fri, 2 Jun 2023 13:19:49 +0000 X-Patchwork-Original-From: "'Christian Storm' via swupdate" From: "Storm, Christian" To: swupdate@googlegroups.com Cc: Christian Storm Subject: [swupdate] [PATCH 2/5] suricatta/lua: Fix and update specification + example Date: Fri, 2 Jun 2023 15:20:19 +0200 Message-Id: <20230602132022.70931-2-christian.storm@siemens.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20230602132022.70931-1-christian.storm@siemens.com> References: <20230602132022.70931-1-christian.storm@siemens.com> X-ClientProxiedBy: FR3P281CA0002.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:1d::12) To DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:10:333::17) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DB9PR10MB5404:EE_|GVXPR10MB5887:EE_ X-MS-Office365-Filtering-Correlation-Id: 9fe8027a-7f90-4ab4-dae1-08db636c0ada X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: K1z97edOJ1fVIEGWwobkQZbJWOGAMKrOkQpLi9AFQDKdy7Qc8baKAl6nhE7y0c2CGLRkV1KpQzQ/b2rFozzYW9BMwO2nMAtDQQXNOd4lVs1hhbOSP6PM1Pa+DZCCstXec/WR1f1QaAUriDf0JGwP0tQ61X36yQ9pzFNM45O0UJQrrL9mJbxVng9l7PmbB5ouyuBejJhl9drAO7gb/KrGE4amAz415nXXcJXFa1RJqNWW8TPDMxKtnvgOpWv815K3YvTWMXNAjrQ61jKxKhsLfNA0lekH8KprNluWMl0Yhu2PY5C5BsPQyB+PDh9VPUYMEBdaP91y7j1Kq8zXzgjtghc/qzlatkvGkRI3eMEXkWWOwrsmU6JZR9yHcKy75aSP0G8VG3RDHZrGsvrp5M1cL9AqUp3Y6oQhyuwjlwPsRhasLjeT5fScIiJvSJTpZl9paG57VWZirGH37eUloerEgoR1AjGZuepwF3NIAiuyKW7REeldpQJAVmdUVLAFNXGhm0brewhDzrjUrZMHBDnLXWeqxGhl7ogZEWXI+VtkjayAJ9hj16pg74MZ0JnHOTHB X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(13230028)(4636009)(39860400002)(376002)(136003)(346002)(366004)(396003)(451199021)(966005)(6486002)(478600001)(6666004)(83380400001)(186003)(36756003)(6512007)(2616005)(38100700002)(6506007)(86362001)(82960400001)(107886003)(1076003)(316002)(4326008)(41300700001)(66476007)(6916009)(66556008)(66946007)(44832011)(5660300002)(30864003)(8676002)(8936002)(15650500001)(2906002);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: PSqQK46AX7i+MhgUqmYW4pJ2/1c9s/36nst5GtrOXeaxtUlbTQfLjyAd8HToL6BqHsWzlwgM7HMoKSQYS/oc9BCK8sTyCyGS21CwKWslfEtuSLRmodiS6pH9Xe5wfA3y628SNfg2KityFxluBE44sDK3IKK1IgG6OkOcqeSxDjIbragnwcmUeqZ8IZpd1eqaW1TA4Ym+FgzyIUCAVKMHNfGMBuWnNMjoYGAgM2NqsGgg2jmDmAUaJNCvlB/t2K1z3yLmg3+pz1a1j2KsJAL6lCQq7sE0TnOZxDjhYldiNp/Ctq2Ir40vqCGJ0/bE/8nCG1nryAyhpso5weUtZJ7ID1TtP3UxcEpmWUoMjZ3+ewmcNA11jYPME1QhjqwMn16vJ0c87e8I9UHVlnkSlLZGOuudD/shzW6X6kK9pVwRhW6YCsOvWlV1CaSddY2EFvV4rShbg3/yJcBZrZYzvycqU69nwP4ePK4mC3jyustV+WavnJH6MbXU1vgEaPuton2QakvmtapaEpb5OofPAVq+9i87QKDdf42OpdmRGU0RzM7zGGXuADisfepqMpwbt4nxI4HxKcTvnYpW7eoV38PbA+Y7Q++lFp6vtTOe/GWOBupy+h+jVtfox0DxYiKgo6tIvW7xRWpfVdeCD36LVgio5npxhGRbJ8m4NR+yKv7gzkHVnU7JduCnaMiSCudu6YAPsG1KKyVOfxqMkaPQ8MGJBo5pBVVBT9sVFLl/vYpKvv5RpjJT/By5w9vbjKMR2CAMoj1dtTIgYi8tbAD9uZJ80lMBlNvv7laoniyKX7ke0GbGVZD17Pv1i4DvuWEuASGTnfRxoaGGorP26Ym2BHIaABLm8kDhNiqQxnc+AyP1xsORsjwPiz6k5kwffn7c3yHAUcZudQwtu6vgBSOGH2xIiAdWsALwYyevN2ML0R3cWaavSs69kPTUwg6Dew1XvyjLbdYdPmwYDceGNZFFwe/9UGag7Il18LW6hHKPPsoFemlkY5OLqaMF+9U8kYsCkq27x1e3BmllMYV1yrqO2r7v2JyIwdKW17oOg8cFGo1hhd/tWZtYGLjgpw8nUQYIa/xw+ktXWDmPMtyMKJXw2a2hW7R+u4cMyIWtsiWP7YepMTI9OHZbyb36yVq01/X24IFvtlAoATH0EGEjQeFA75kOhHCKB5RgkbtaWJkY+Y5phxt9Dsoynqqi93RprCPbIkXzwnqvYhpWfLZZMnSZa1GDy9+CtAnV2iWEZ+GaFDl4PbCjk+eTBRqO6Wvy/nHm/VqjdfrzPPM/VfYThelMC8xYk7geKgHoBXf7SRN8WOvqhb7JElyEuoUboB0GMnRVgPmcknB4oRgDXRoRn3dKAd3BGUptC3urVjjv9areXEzgBkqUcK52qXEiL9oFriSUlCZWokHapSp6tX644AcBN4FBl6KejIzvXGWK1B5QdvGGgGKFJTeZau76WXdBr3uvXVkJQmc7yYAdyf5xxnWXY5Ot1i862BgCxYETZrx8P/JpN1AgqmaYiY/kegGZ2yS6GvNmuzPklY9TCZTiSNjqOzWCYV0ER37qeZziYkFgVT2d0x1d52XGzjD4H9RNtzb78dTvXCMzAmuPjF5vRlirerS3dUs0JO+lJvmmxxq+6+TtZ0qDV9645/PGXJtzoLQhJ7OsB1Fk5bjIg0seKjZuvKBhDw== X-OriginatorOrg: siemens.com X-MS-Exchange-CrossTenant-Network-Message-Id: 9fe8027a-7f90-4ab4-dae1-08db636c0ada X-MS-Exchange-CrossTenant-AuthSource: DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 02 Jun 2023 13:19:49.4963 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 38ae3bcd-9579-4fd4-adda-b42e1495d55a X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: yXt06N6fcQWUUFprAgYPb7+/nJh0L6g6kGgTQbEdlqI1Ks7cocaRYAKGl3AJNOsVOnCzwYt8T7wEfWuJOts8Bhh4tP9R0eb0oddJAu4jcPw= X-MS-Exchange-Transport-CrossTenantHeadersStamped: GVXPR10MB5887 X-Original-Sender: christian.storm@siemens.com X-Original-Authentication-Results: gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=ATPyq2I5; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::628 as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com X-Original-From: Christian Storm Reply-To: Christian Storm Precedence: list Mailing-list: list swupdate@googlegroups.com; contact swupdate+owners@googlegroups.com List-ID: X-Spam-Checked-In-Group: swupdate@googlegroups.com X-Google-Group-Id: 605343134186 List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , Fix General HTTP Server example type diagnostics. Correctly make `channel` a field of the `suricatta` Table. Factor out suricatta.channel.{put,get} return values. Fix some @enum annotations that are actually @class. Signed-off-by: Christian Storm --- examples/suricatta/swupdate_suricatta.lua | 28 ++-- suricatta/suricatta.lua | 189 ++++++++-------------- 2 files changed, 78 insertions(+), 139 deletions(-) diff --git a/examples/suricatta/swupdate_suricatta.lua b/examples/suricatta/swupdate_suricatta.lua index dffc2909..a74905c9 100644 --- a/examples/suricatta/swupdate_suricatta.lua +++ b/examples/suricatta/swupdate_suricatta.lua @@ -84,9 +84,9 @@ end -- The default substitutions table is suitable for escaping -- to proper JSON. -- ---- @param str string The JSON string to be escaped ---- @param substs? table Substitutions to apply ---- @return string # The escaped JSON string +--- @param str string The JSON string to be escaped +--- @param substs? table Substitutions to apply +--- @return string # The escaped JSON string function escape(str, substs) local escapes = '[%z\1-\31"\\]' if not substs then @@ -145,10 +145,10 @@ jobtype = { --- Configuration for the General Purpose HTTP Server. -- --- @class gs ---- @field channel table Channels used in this module ---- @field channel_config suricatta.channel.options Channel options (defaults, shadowed by config file, shadowed by command line arguments) ---- @field job job Current job information ---- @field polldelay table Default and temporary delay between two server poll operations in seconds +--- @field channel table Channels used in this module +--- @field channel_config suricatta.channel.options Channel options (defaults, shadowed by config file, shadowed by command line arguments) +--- @field job job Current job information +--- @field polldelay table Default and temporary delay between two server poll operations in seconds gs = { channel = {}, channel_config = {}, @@ -403,13 +403,14 @@ suricatta.server.register(install_update, suricatta.server.INSTALL_UPDATE) -- -- Lua counterpart of `void server_print_help(void)`. -- ---- @param defaults suricatta.channel.options Compile-time channel default options ∪ { polldelay = CHANNEL_DEFAULT_POLLING_INTERVAL } ---- @return suricatta.status # Suricatta return code +--- @param defaults suricatta.channel.options|table Default options +--- @return suricatta.status # Suricatta return code function print_help(defaults) defaults = defaults or {} - io.stdout:write(string.format("\t -u * URL to the server instance, e.g., http://localhost:8080\n")) - io.stdout:write(string.format("\t -i * The device ID.\n")) - io.stdout:write(string.format("\t -p Polling delay (default: %ds).\n", defaults.polldelay)) + local stdout = io.output() + stdout:write(string.format("\t -u * URL to the server instance, e.g., http://localhost:8080\n")) + stdout:write(string.format("\t -i * The device ID.\n")) + stdout:write(string.format("\t -p Polling delay (default: %ds).\n", defaults.polldelay)) return suricatta.status.OK end suricatta.server.register(print_help, suricatta.server.PRINT_HELP) @@ -441,7 +442,8 @@ function server_start(defaults, argv, fconfig) gs.polldelay.default = tonumber(arg) elseif opt == ":" then io.stderr:write("Missing argument.") - print_help(defaults) + -- Note: .polldelay is in fconfig + print_help(setmetatable((defaults or {}), { __index = fconfig })) return suricatta.status.EINIT end end diff --git a/suricatta/suricatta.lua b/suricatta/suricatta.lua index 1fa32180..419b58c2 100644 --- a/suricatta/suricatta.lua +++ b/suricatta/suricatta.lua @@ -17,12 +17,18 @@ -- luacheck: no max line length -- luacheck: no unused args -local suricatta = {} - +-- Note: Definitions prefixed with `---` are not part of the SWUpdate functionality +-- exposed to the Lua realm but instead are `typedef`-alikes returned by a function +-- of the SWUpdate Suricatta Lua Module. Nonetheless, they are in the suricatta +-- "namespace" for avoiding name clashes and, not least, convenience. ---- Lua equivalent of `server_op_res_t` enum as in `include/util.h`. +--- SWUpdate Suricatta Binding. -- +--- @class suricatta +local suricatta = {} + --- @enum suricatta.status +--- Lua equivalent of `server_op_res_t` enum as in `include/util.h`. suricatta.status = { OK = 0, EERR = 1, @@ -39,10 +45,10 @@ suricatta.status = { --- SWUpdate notify function bindings. -- --- Translates to `notify(string.format(message, ...))`, see --- `corelib/lua_interface.c`. +-- Translates to `notify(string.format(message, ...))`, +-- @see `corelib/lua_interface.c` -- ---- @type table +--- @class suricatta.notify suricatta.notify = { --- @type fun(message: string, ...: any) error = function(message, ...) end, @@ -63,15 +69,15 @@ suricatta.notify = { -- --- @class suricatta.bootloader suricatta.bootloader = { - --- Bootloaders supported by SWUpdate. - -- --- @enum suricatta.bootloader.bootloaders + --- Bootloaders supported by SWUpdate. bootloaders = { EBG = "ebg", NONE = "none", GRUB = "grub", UBOOT = "uboot", }, + --- Operations on the currently set bootloader's environment. -- --- @class suricatta.bootloader.env @@ -117,7 +123,7 @@ suricatta.bootloader.env.apply = function(filename) end --- SWUpdate's persistent state IDs as in `include/state.h` and reverse-lookup. -- ---- @enum suricatta.pstate +--- @class suricatta.pstate suricatta.pstate = { OK = string.byte('0'), [string.byte('0')] = "OK", INSTALLED = string.byte('1'), [string.byte('1')] = "INSTALLED", @@ -136,15 +142,15 @@ suricatta.pstate.get = function() end --- Save persistent state information. -- ---- @param state suricatta.pstate Persistent state ID number ---- @return boolean # Whether operation was successful or not +--- @param state number Persistent state ID number +--- @return boolean | nil # True on success, nil on error suricatta.pstate.save = function(state) end --- Function registry IDs for Lua suricatta functions. -- ---- @enum suricatta.server +--- @class suricatta.server suricatta.server = { HAS_PENDING_ACTION = 0, INSTALL_UPDATE = 1, @@ -160,25 +166,46 @@ suricatta.server = { --- Register a Lua function as Suricatta interface implementation. -- ---- @param function_p function Function to register for `purpose` ---- @param purpose suricatta.server Suricatta interface function implemented ---- @return boolean # Whether operation was successful or not +--- @param function_p function Function to register for `purpose` +--- @param purpose number Suricatta interface function implemented (one of suricatta.server's enums) +--- @return boolean # Whether operation was successful or not suricatta.server.register = function(function_p, purpose) end +--- Channel result table. +-- +-- Result in return of a channel operation. +-- +--- @class suricatta.channel_operation_result +--- @field http_response_code number +--- @field format suricatta.channel.content +--- @field json_reply table | nil Table if `format` was `suricatta.channel.content.JSON` +--- @field raw_reply string | nil Table if `format` was `suricatta.channel.content.RAW` +--- @field received_headers table | nil + + +--- Options and methods on the opened channel. +-- +-- Table as returned by `suricatta.channel.open()`. +-- +--- @class suricatta.open_channel +--- @field options suricatta.channel.options Channel creation-time set options as in `include/channel_curl.h`. +--- @field get fun(options: suricatta.channel.options): boolean, suricatta.status, suricatta.channel_operation_result Channel get operation +--- @field put fun(options: suricatta.channel.options): boolean, suricatta.status, suricatta.channel_operation_result Channel put operation +--- @field close fun() Channel close operation + +--- @class suricatta.channel suricatta.channel = { - --- Content type passed over the channel as in `include/channel_curl.h`. - -- --- @enum suricatta.channel.content + --- Content type passed over the channel as in `include/channel_curl.h`. content = { NONE = 0, JSON = 1, RAW = 2, }, - --- Transfer method to use over channel as in `include/channel_curl.h`. - -- --- @enum suricatta.channel.method + --- Transfer method to use over channel as in `include/channel_curl.h`. method = { GET = 0, POST = 1, @@ -216,118 +243,28 @@ suricatta.channel = { --- @field nofollow boolean `CURLOPT_FOLLOWLOCATION` - follow HTTP 3xx redirects --- @field max_download_speed string `CURLOPT_MAX_RECV_SPEED_LARGE` - rate limit data download speed --- @field headers_to_send table Header to send - options = { - url = "", - cached_file = "", - auth = "", - request_body = "", - iface = "", - dry_run = false, - cafile = "", - sslkey = "", - sslcert = "", - ciphers = "", - proxy = "", - info = "", - auth_token = "", - content_type = "", - retry_sleep = 5, - method = 0, - retries = 5, - low_speed_timeout = 300, - connection_timeout = 300, - format = 2, - debug = false, - usessl = false, - strictssl = false, - nocheckanswer = false, - nofollow = true, - max_download_speed = "", - headers_to_send = {}, - }, + options = {}, --- Open a new channel. -- --- @param options suricatta.channel.options Channel default options overridable per operation --- @return boolean # Whether operation was successful or not - --- @return table # Options of and operations on the opened channel - open = function(options) - --- Returned channel instance, on successful open. - -- - --- @type table - --- @class channel - --- @field options suricatta.channel.options Channel creation-time set options - --- @field get function Channel get operation - --- @field put function Channel put operation - --- @field close function Channel close operation - return true, { - - --- Channel creation-time set options as in `include/channel_curl.h`. - -- - --- @type suricatta.channel.options - options = {}, - - --- Execute get operation over channel. - -- - --- @param options_get suricatta.channel.options Channel options for get operation - --- @return boolean # Whether operation was successful or not - --- @return suricatta.status # Suricatta return code - --- @return table # Operation results - get = function(options_get) - return true, suricatta.status.OK, { - --- @type number - http_response_code = nil, - --- @type suricatta.channel.content - format = nil, - --- @type table | nil - json_reply = nil, -- if request method was `suricatta.channel.content.JSON` - --- @type string | nil - raw_reply = nil, -- if request method was `suricatta.channel.content.RAW` - --- @type table | nil - received_headers = nil, - } - end, - - --- Execute put operation over channel. - -- - --- @param options_put suricatta.channel.options Channel options for put operation - --- @return boolean # Whether operation was successful or not - --- @return suricatta.status # Suricatta return code - --- @return table # Operation results - put = function(options_put) - return true, suricatta.status.OK, { - --- @type number - http_response_code = nil, - --- @type suricatta.channel.content - format = nil, - --- @type table | nil - json_reply = nil, -- if request method was `suricatta.channel.content.JSON` - --- @type string | nil - raw_reply = nil, -- if request method was `suricatta.channel.content.RAW` - --- @type table | nil - received_headers = nil, - } - end, - - --- Close channel. - close = function() end, - } - end, + --- @return suricatta.open_channel # Options of and operations on the opened channel + open = function(options) end, } ---- @type table ---- @class suricatta.op_channel +--- Channel and Options Table to use for an operation. -- -- Channel to use for the download / installation operation as returned by `suricatta.channel.open()` -- plus channel options overriding the defaults per operation (@see suricatta.channel.options) -- and specific options to the download / installation operation, e.g., `drain_messages`. -- ---- @field channel channel Channel table as returned by `suricatta.channel.open()` +--- @class suricatta.operation_channel +--- @field channel suricatta.open_channel Channel table as returned by `suricatta.channel.open()` --- @field drain_messages? boolean Whether to flush all progress messages or only those while in-flight operation (default) --- @field ∈? suricatta.channel.options Channel options to override for this operation - --- Install an update artifact from remote server or local file. -- -- If the protocol specified in Table `install_channel`'s `url` field is `file://`, @@ -336,10 +273,10 @@ suricatta.channel = { -- Note that this file is to be deleted, if applicable, from the Lua realm. -- --- @see suricatta.download ---- @param install_channel suricatta.op_channel Channel to use for the download+installation operation ---- @return boolean # Whether operation was successful or not ---- @return suricatta.status # Suricatta return code ---- @return table # Error messages, if any +--- @param install_channel suricatta.operation_channel Channel to use for the download+installation operation +--- @return boolean # Whether operation was successful or not +--- @return suricatta.status # Suricatta return code +--- @return table # Error messages, if any suricatta.install = function(install_channel) end --- Download an update artifact from remote server. @@ -349,17 +286,17 @@ suricatta.install = function(install_channel) end -- an appropriate `install_channel` Table's `url` field. -- --- @see suricatta.install ---- @param download_channel suricatta.op_channel Channel to use for the download operation ---- @param localpath string Path where to store the downloaded artifact to ---- @return boolean # Whether operation was successful or not ---- @return suricatta.status # Suricatta return code ---- @return table # Error messages, if any +--- @param download_channel suricatta.operation_channel Channel to use for the download operation +--- @param localpath string Path where to store the downloaded artifact to +--- @return boolean # Whether operation was successful or not +--- @return suricatta.status # Suricatta return code +--- @return table # Error messages, if any suricatta.download = function(download_channel, localpath) end --- Sleep for a number of seconds. -- --- Call SLEEP(3) via C realm. +-- Call `SLEEP(3)` via C realm. -- --- @param seconds number # Number of seconds to sleep suricatta.sleep = function(seconds) end @@ -382,7 +319,7 @@ suricatta.get_tmpdir = function() end --- Get SWUpdate version. -- ---- @return suricatta.version # Table with 'version' and 'patchlevel' fields +--- @return suricatta.version # Table with `version` and `patchlevel` fields suricatta.getversion = function() end From patchwork Fri Jun 2 13:20:20 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: "Storm, Christian" X-Patchwork-Id: 1789621 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=googlegroups.com (client-ip=2a00:1450:4864:20::139; helo=mail-lf1-x139.google.com; envelope-from=swupdate+bncbdd6bwv65qpbb7wy46rqmgqehljajhq@googlegroups.com; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=googlegroups.com header.i=@googlegroups.com header.a=rsa-sha256 header.s=20221208 header.b=r0nMGyR+; dkim-atps=neutral Received: from mail-lf1-x139.google.com (mail-lf1-x139.google.com [IPv6:2a00:1450:4864:20::139]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4QXkBW0C7vz20Tj for ; Fri, 2 Jun 2023 23:20:02 +1000 (AEST) Received: by mail-lf1-x139.google.com with SMTP id 2adb3069b0e04-4f51dd0bf6asf1715034e87.3 for ; Fri, 02 Jun 2023 06:20:02 -0700 (PDT) ARC-Seal: i=3; a=rsa-sha256; t=1685711999; cv=pass; d=google.com; s=arc-20160816; b=cP8Eb1FiKP1tdO6bm+Z3taK2Pjntk8hJd2RIqnb8corLRA6gxIiP2GI7sDyst5WOD4 klla9uTv/6UgCKUAr3iu1IJvf4BTM3+dW7kmt2shpneEpsQsbs8UvfbxX3kVaqQ2tvW4 pmBqogg0b2PT1cuUPxZaXm5LmG/GUltP1rc9DxhIIuKwMUqdQys0+JST0SUZ04nzO0zl 6jSNswIOhQDg9D41CUM+HkpIqUJu2WKAxCgyDTfDex0yI1R1ewh1VaDuffszPNNw6gHD Z69nz1qco6c/DswMuSmI8Kuw29KOHo1syMVVD/YnVsWtD4CQe13NfjPcuEP7fYFvabX1 3ltg== ARC-Message-Signature: i=3; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:reply-to:mime-version :content-transfer-encoding:references:in-reply-to:message-id:date :subject:cc:to:from:dkim-signature; bh=BBaMdWV0zmeOr9wYjyMYbWx9FYrcSkwRnkBLh/19UKw=; b=cAB6uWjNj7X0gCfeMYJMxWdi/HBIrI7Qq2lZ+kgon8Iznfqy4xffspZyUgYqYntN5Q Jnu66Ok9Sk1+lbzbLT1wlPMzX7ilx7DKWhrxnx8sxei7k1WcPUMrG+L84TAnQTqlWMF2 XNksw0Qv9E2TnC7C99LBHsIFz2xQOfjIFxyTtcmGMGAzSMTWNf9ATgLTfz6F+kXjpRZB GaXBae1dFEmY0v1do7tcOdHHaSUprJInDO2VjwKiPxQUhOeZgpBCdG16pw49BDzICXFX Ohdjoml4lURJa+ssm5Fbx81fbi22xQuOAryy8KJ9GSRhNh70CdU9U1wkCl3LZB/MnDtC jXzA== ARC-Authentication-Results: i=3; gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=Dz+sD61A; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::606 as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20221208; t=1685711999; x=1688303999; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:reply-to :x-original-authentication-results:x-original-sender:mime-version :content-transfer-encoding:references:in-reply-to:message-id:date :subject:cc:to:from:from:to:cc:subject:date:message-id:reply-to; bh=BBaMdWV0zmeOr9wYjyMYbWx9FYrcSkwRnkBLh/19UKw=; b=r0nMGyR+lrd9wJ2mOLYeeSLCwAIwIWRO3dhNW3ICCwZbNbeoxE0rEFveclbltNYkX2 HYF53SGpprR24ZGpHJOQK1LplgFTCZI7kukkwmqlH4LH4CgNokuhn+aQLBU+vN2S2KQH XYijgmFnTWk+MXQ6T1s4ztzvj0KJz313xhZ2CYaRMWoyZi5gT+Y5yA2A7BCHgY+WcKN/ vm9VTbg+J0V8dfYJnfRcYpz4gPIwUbQVNd8XCdu5cx1Uf1w6LGWJRGspahqq2ZTJH8Jt Qz3xmw53kLNWeE5ABLgVtwq6F1m04BPuotV6GSWg4nmDtimlIk19ev8bftfGDzjEDSLq aSzg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1685711999; x=1688303999; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :x-spam-checked-in-group:list-id:mailing-list:precedence:reply-to :x-original-authentication-results:x-original-sender:mime-version :content-transfer-encoding:references:in-reply-to:message-id:date :subject:cc:to:from:x-beenthere:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=BBaMdWV0zmeOr9wYjyMYbWx9FYrcSkwRnkBLh/19UKw=; b=GXgGh6Yqf3j4BJHmAH61nEh0/htXiwBwBg8TulysBUZ9xpc1p9bQWryof+ZF7iQevt 7A5WHC30QZ1P/hzfH8qh7qXSXrw9VGcRt0nttypEEf39TaE4j6xv4gNiaq9Tqku4y1nr 1Le11zYH7nShGzRqqB58Jd0wD3uUSj/ZOpgkyu8Rl66c+iTX5MjZjPiX3EX2sNO4isBJ c0AmAbjmbTW3GQ9exMFGRf62uvBRs0ABp20/BGoDZDC13uHV2OhCUgOmcUHd6E8+F8BI IgAnYTjsVLfgwBuCYALYVIsCEWa7xG6am9Vbpsztxec1gBWCeH5kxTCocNWSlnpIe+Zh zy0w== X-Gm-Message-State: AC+VfDwtpFNKKtR0+Dzs03SjfXZqw0BH3uqUjpV3Anr7J1XLKWjaMTFQ TeERcpp58zpJe69yAG+npPk= X-Google-Smtp-Source: ACHHUZ7snr/jhwpUutGpbGZpanHQtNZNtMCmiOwqdhhdbS7+A+QqyDjCmNLbMzdR49F4RxL6GxOf0A== X-Received: by 2002:ac2:5143:0:b0:4f1:43b9:a600 with SMTP id q3-20020ac25143000000b004f143b9a600mr1685599lfd.60.1685711998811; Fri, 02 Jun 2023 06:19:58 -0700 (PDT) X-BeenThere: swupdate@googlegroups.com Received: by 2002:ac2:4dbb:0:b0:4f3:b80e:fe54 with SMTP id h27-20020ac24dbb000000b004f3b80efe54ls323969lfe.1.-pod-prod-07-eu; Fri, 02 Jun 2023 06:19:57 -0700 (PDT) X-Received: by 2002:ac2:5392:0:b0:4dd:9ddc:4463 with SMTP id g18-20020ac25392000000b004dd9ddc4463mr1638232lfh.5.1685711996914; Fri, 02 Jun 2023 06:19:56 -0700 (PDT) ARC-Seal: i=2; a=rsa-sha256; t=1685711996; cv=pass; d=google.com; s=arc-20160816; b=F7LPOD01J+TURZ1cXZgt7MQWwbkN0js4mBVbdJ9c7nBOjAgs62DdLGR/bNWr/qJ6EC iF/688ltm3AW+y1aqremptJUGFAjsROdi7VPN+6/2qSniMYG7vpdhfggO/PLuIOWC8Wm QUhqggWXLOlQ+q75ec0WZB+I7esOs/nEbeCPePJLgmYFFdPOQTGGmQQ+EsErYey1uOfA fkjPaX6Kffcbt1JvY7WvIw1dF3sCX38er5tfSzyZ8Ho+8kWw1UuR2tQfMG765/hzecRT eyOSOvTjY+V1IU9CT9doaQ/HhqkTvV8nXJ0M+4mAzdkyWYKB335fhmPDM9OtyfgVVVpL IaNA== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=mime-version:content-transfer-encoding:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature; bh=YglhNzs1nWXpwwmwRkXz/JRzz9JsQ2D6VVELWag1z7E=; b=XHmnaU++pR6x+DN9nW4GUmsKY0OF14lH7tAFykWD6A5DxBL8g3vcSpeVEq4bf2beVi qnr3z0V+5QTRw8vnKqvjhD9CaTAnzsdixVP3NKSD8VoOEtw5RCCM/qEtu+cRKrHJsywm 8Jsz8Ivsyap1eQE+LK3DrYkVMRuIlarx2e77i/GRiwAXzE1urEg22zwcREm0rE87Zo+3 0riANbCM48RNCcDW6O25I19QyIy9n4+X4DEUmP17hJltVZZIuOHUS7QKyXTUj6kDH8fK UIFSQJuoZ/MNcBtMpo8ssG5NS4Hrfe1lLB4eTMZJ8KQfikGni/g4mtC88Czyw+0l/3iq UXpQ== ARC-Authentication-Results: i=2; gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=Dz+sD61A; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::606 as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com Received: from EUR03-DBA-obe.outbound.protection.outlook.com (mail-dbaeur03on20606.outbound.protection.outlook.com. [2a01:111:f400:fe1a::606]) by gmr-mx.google.com with ESMTPS id p16-20020a056512235000b004f4d38a01a4si92159lfu.0.2023.06.02.06.19.56 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 02 Jun 2023 06:19:56 -0700 (PDT) Received-SPF: pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::606 as permitted sender) client-ip=2a01:111:f400:fe1a::606; ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=HzPrQG3d/TCzwZYIUwNlB8rBqiDFIr2eOSazZcHf5qU5E0w/TeREGyLI2+BL0LOvoAGi7fWn14b/KsmJkcKgBPpSFZuNklKE7B11ZNnyg7Cd0QyXOUTD89NolMv8T+xBAkyg4NxMwCk1+R5DbZVNT31uPGsmzs57hvnLgx1kAKaIeP/0CzX6lxSx+a+o2RCAQfzyUah650DsiVsJ2CIT2AGzevYd3EV2VclOnd4NG59rzwLvhh77utoFuFl+vDiAZNkfU6dR6eOWnVjCMC75lcioZa/4JSwkno8+CRjwQAM/GY2nXGhxbDEwRNpY2QICeKOWPdibsqetAD1y+pHeeg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=YglhNzs1nWXpwwmwRkXz/JRzz9JsQ2D6VVELWag1z7E=; b=MRRk1TX39ujGjcCW1KwvWGKHX2JGCWqBttvVLBPUyGxRjAyLcEpnDsu31snSABH/5bx/bbWl/rEk4EzF9oMONW5V5lWKJs/0WusfkLOGEwEFK134tYjX2UomP8wlBGhEQLfi6APdiJRELcZNhGgYk5x5Fb4Md43bmRAQvRbBZn02pSTIazwMdxuHYNxSr+kyAqCHm3UehXD4ycuZRjO2JCk3A6OLEsBWfnel5+EVNggfFoTwOd5Gh5XytBn42kB0s3Zi4r+5Y2prc/iQ/GsW1QMQIm/XyLFaLtD6++puoLHRVNz0S9MfXI+8N5QspNHNOuYiPgBBG3NIk17g/IlTRA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=siemens.com; dmarc=pass action=none header.from=siemens.com; dkim=pass header.d=siemens.com; arc=none Received: from DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:10:333::17) by GVXPR10MB5887.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:150:6::15) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6455.23; Fri, 2 Jun 2023 13:19:53 +0000 Received: from DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM ([fe80::6bed:3b93:4756:c9f3]) by DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM ([fe80::6bed:3b93:4756:c9f3%6]) with mapi id 15.20.6433.025; Fri, 2 Jun 2023 13:19:53 +0000 X-Patchwork-Original-From: "'Christian Storm' via swupdate" From: "Storm, Christian" To: swupdate@googlegroups.com Cc: Christian Storm Subject: [swupdate] [PATCH 3/5] suricatta/wfx: Add support for wfx server Date: Fri, 2 Jun 2023 15:20:20 +0200 Message-Id: <20230602132022.70931-3-christian.storm@siemens.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20230602132022.70931-1-christian.storm@siemens.com> References: <20230602132022.70931-1-christian.storm@siemens.com> X-ClientProxiedBy: FR0P281CA0057.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:49::14) To DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:10:333::17) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DB9PR10MB5404:EE_|GVXPR10MB5887:EE_ X-MS-Office365-Filtering-Correlation-Id: a0133405-f75b-4377-7047-08db636c0ccf X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: GQWLuNSBUrcsCiefGDcDINRshjnB+ug2VYYgMCVu+WDDkWlx0nfsYhhiQOQLjpL4ayjmbYpuUFjhzMVVs4CK+bB/S2t9JpAg9VAl8RENry4/8rLfSvkDO9d7BIByKmgnJFHTzIu1BYSizxmSrlTF2VnXAegNgngRhENHvBP3mBDZjmeVTP62NtkGC24zNcS72vg0rqZpfX3I9OCWFQqbcts5tKq67j/ZdjV4rbdF0VHrRP+pLU2bmqdMLwvcqjMm8g8XiSvxS6DHDHStvRDmxHaPxWIXkQzI0bwv/azSlMFKKSIQv8VaN0d6JF9tSPma26t2KMBwn6/w+m5CzAdK6X1x2S4LUrVmGZmFCMKMebQpUAjkRICPrNoIdZx0xPfkhQ8TN6WJNk00zsxjD0lpNY3YTGgqp9cBzzAEntzM3yaj+2Td2pgLBnYUdFp/nTcf+tWkwpxjaQNvRD7+hq06cg5UBBAIsbGfxX6ch8cP5hBFn4fKaumM7OFamqvpBoi+uvAZpIZS4yTYZdKOnVqGutPyEgM5O4l/ZCdkzcMCoCk= X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(13230028)(4636009)(39860400002)(376002)(136003)(346002)(366004)(396003)(451199021)(966005)(6486002)(478600001)(6666004)(83380400001)(186003)(36756003)(6512007)(2616005)(38100700002)(6506007)(86362001)(82960400001)(107886003)(1076003)(316002)(4326008)(41300700001)(66476007)(6916009)(66556008)(66946007)(44832011)(5660300002)(30864003)(8676002)(8936002)(2906002)(579004)(559001);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: CnQTp57ISeCnsgXL7KRky8eGMkPhMqsKzzTDVItqdHnCSYQt4HkT/dy+ct25myqcjrUyHeq8tW1g5CGxkauXL0rd5HLm42WLBYwsIH3yojEv1YstBqJ/A1k8OT/GjxfvKAKn1WDoBOOVS5rMfB6Xyyb7IBOfbj0hAT9l2RQfLHQ0gSlQvz3bP+fy1prApnPH/MVfLQO/ojlKjehdOAuumq0FPBpisEU6PLgA6NHpXFPwaqQQ0byjCxjcPRZD5tlz7abqMqX2i7hNRRZkVx8PdAkpP5/JrBPQgc2RAcGQfyT5DLXFHI4u9MNCBDHHIuvUDx6X1Szfr7nrtimWnxXMSyimJ6nzL6DiTU6wPsmCcNtT7hU7zWYYEQF/BhbLt31cyDdAxbc1P+CG4P/y9lkTv20pbRL0RxV4Fm0kJP06+rAOxCRnm4+/X+R0yFabBs0qowVTSboJ8ULSlYkpFC/GI2cmdrI04AbID3HZWzNt1hD8lqvPb40oav1M9cz11pbXdkuKkyb/5T9UKfE5CNJYeZ2TfwQCZ/5MHRxbN7P8e//NODJr6LRr+popYOqpzOpW8VpVT5sMHxVzWQJJ+loI/rrVx1PkgWlqJ291BwO7qRwqT+4xXs1W5UscUEaC9LiFnicejEF8r1lrWall9YjPs2m5OL/msUyEc+e+2Vrftzh8zV460xH5Rzv/xT1eSb8RcFWslsJwiF9i6HwqhECQ1XZeCzLssHrdN/qLAtzr4jLOgZLwyJPvwh5OnYZjfnCmhZWilCYl+EV86W85i1Gz9Xb/ggs6slD5nru7qm/4p0+Hig+FbFZF0c6g0fdA0QAGQ921EeDw7cW6t/5HHuISE0yHlbx+bmoTREljcTZaSxQkk1um3ccoysHtmGkcLhE0rSDyaxjkk5G2PyH0bDknAE4OsqT43yaUaoxSGtpQyBkcYbNBoAkCTak4gBq/qrYratp0WyNNKbHeXj+sPMbvi4f2kP+wMOJhk3JaZEpTKupF0LGSmmeXBJ0MieUJWgUQZdYulkWAFSkwRbuJ8d+Lqs6cPLNvlYvWRzt7vHsJi82pt6h6hlCkdOH8lGStmCRZKVj3p3ijQiVXS3Sv7uc5j6a2SFXOk4039izKkP3ud3GxKN1H8KUip1yBWxBp8XeW+e/4I9relZU9pPkaAV2svUlwJwPmvLWbPmorHT1KkMR9V2kfDLnJyHT83My+Ut+94N+wqgzqD++oA8WfO+rkhnO8+MKB7zvuz/OY09hFvM6B/Tdi6c2nWV82zk9L/TKsJ8zvjt7l4sHHa3X3JHokYGIx/25zJsrymwz59y3AWbwcaTr1ofnUWLcDl6dP29EJPzyMdSHguqyZLoQ0iLJ7+cY9CK2hrYDSITwOXYQ5ElKN8Mw3cbomprSvTYIb7o3qVWD/VLYK0L4HIHxeQ28LwjfsRkf3vu3cBhpCFmFVkxr9xMDwpLg9syurjOlgRZD7YTsaz4dMXygf3atujDb5qqIzZQV8zxcLjXS1Vek+cJgBLoe9MZjdGPRv98vUQ70K0l0laFD0yqGbek5xidvb3cpZak52ecTaxMG5hwPlTRFW4lt0FwNrSBoUbB101E0KRNuCUH3nx/aiL534IF9ftCWJ8xtIjNI18MZtGtNrJyeF6H4NmG1U6brEjxhGZ+WLc8bfOZGgZEVLX0iXE7cDuA== X-OriginatorOrg: siemens.com X-MS-Exchange-CrossTenant-Network-Message-Id: a0133405-f75b-4377-7047-08db636c0ccf X-MS-Exchange-CrossTenant-AuthSource: DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 02 Jun 2023 13:19:53.2636 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 38ae3bcd-9579-4fd4-adda-b42e1495d55a X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: CnwopCgkqRvq/Z6npXJ2EN56RJ7LjTCFM5EnBzI+B19ByYJizuc240Lu+tWUXIbspEiOtA9ZxRt+1OrYMU/Oa1l9j3BDNz22fyCA7dCfYz4= X-MS-Exchange-Transport-CrossTenantHeadersStamped: GVXPR10MB5887 X-Original-Sender: christian.storm@siemens.com X-Original-Authentication-Results: gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=Dz+sD61A; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::606 as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com X-Original-From: Christian Storm Reply-To: Christian Storm Precedence: list Mailing-list: list swupdate@googlegroups.com; contact swupdate+owners@googlegroups.com List-ID: X-Spam-Checked-In-Group: swupdate@googlegroups.com X-Google-Group-Id: 605343134186 List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , Add Support for wfx server (https://www.github.com/siemens/wfx) Signed-off-by: Christian Storm --- parser/Config.in | 2 +- suricatta/Config.in | 26 +- suricatta/server_wfx.lua | 2195 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 2215 insertions(+), 8 deletions(-) create mode 100644 suricatta/server_wfx.lua diff --git a/parser/Config.in b/parser/Config.in index d5444b8c..454e9cdb 100644 --- a/parser/Config.in +++ b/parser/Config.in @@ -29,7 +29,7 @@ config JSON depends on HAVE_JSON_C help Use json-c to parse the configuration file. - Also required for suricatta's hawkBit Server support. + Also required for suricatta server support. comment "JSON config parser support needs json-c" depends on !HAVE_JSON_C diff --git a/suricatta/Config.in b/suricatta/Config.in index 0fde4401..08362482 100644 --- a/suricatta/Config.in +++ b/suricatta/Config.in @@ -49,10 +49,11 @@ comment "hawkBit support needs json-c" config SURICATTA_LUA bool "Suricatta Lua module" - depends on HAVE_LUA depends on HAVE_JSON_C - select LUA + depends on HAVE_LUA + select CHANNEL_CURL select JSON + select LUA help Support for Suricatta modules in Lua. @@ -61,13 +62,17 @@ config SURICATTA_LUA enables channel result parsing to JSON per default. To enable, select 'libjson' in 'Parser Features'. -comment "Suricatta Lua module support needs Lua and json-c" - depends on !HAVE_LUA || !HAVE_JSON_C +config SURICATTA_WFX + bool "wfx support" + depends on SURICATTA_LUA + select EMBEDDED_SURICATTA_LUA + help + Support for wfx server. + https://www.github.com/siemens/wfx config EMBEDDED_SURICATTA_LUA bool "Embed Suricatta Lua module in SWUpdate binary" depends on SURICATTA_LUA - default n help Embed the Suricatta Lua module source code file into the SWUpdate binary. @@ -83,7 +88,8 @@ config EMBEDDED_SURICATTA_LUA config EMBEDDED_SURICATTA_LUA_SOURCE string "Suricatta Lua module file" depends on EMBEDDED_SURICATTA_LUA - default "swupdate_suricatta.lua" + default "swupdate_suricatta.lua" if !SURICATTA_WFX + default "suricatta/server_wfx.lua" if SURICATTA_WFX help Path to the Suricatta Lua module source code file to be embedded into the SWUpdate binary. @@ -102,7 +108,13 @@ comment "General HTTP support needs json-c" endmenu -endif +comment "hawkBit & Suricatta Lua module support needs json-c" + depends on !HAVE_JSON_C + +comment "Suricatta Lua module support needs Lua" + depends on !HAVE_LUA comment "Suricatta daemon support needs libcurl" depends on !HAVE_LIBCURL + +endif diff --git a/suricatta/server_wfx.lua b/suricatta/server_wfx.lua new file mode 100644 index 00000000..e34c3f6f --- /dev/null +++ b/suricatta/server_wfx.lua @@ -0,0 +1,2195 @@ +--[[ + + wfx SWUpdate Suricatta Lua Module. + + Author: Christian Storm + Copyright ©️ 2023, Siemens AG + + SPDX-License-Identifier: GPL-2.0-or-later + +--]] + +-- luacheck: no max line length +-- luacheck: no global + +suricatta = require("suricatta") + +-- Cater for table.unpack() on Lua 5.1 / LuaJIT +if not table.pack then + ---@diagnostic disable-next-line: duplicate-set-field + table.pack = function(...) + return { n = select("#", ...), ... } + end + ---@diagnostic disable-next-line: deprecated + table.unpack = unpack +end + +--- Exported wfx SWUpdate Suricatta Lua Module. +local M = { + --- Utility / Library Functions. + utils = { + --- Table Utility Functions. + table = {}, + --- String Utility Functions. + string = {}, + }, + --- DeviceArtifactUpdate Definitions and Functions. + dau = {}, + --- Suricatta Interface Function Implementations. + suricatta_funcs = {}, +} + +-- ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +-- ▏ +-- ▏ Utility / Library Functions +-- ▏ +-- ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +--- @enum GETOPT_ARG +--- getopt() argument specifier enum. +M.utils.GETOPT_ARG = { + NO = 0, + REQUIRED = 1, + [0] = "", + [1] = ":", +} + +--- Simplistic getopt(3)-alike. +-- +-- The following getopt(3) features are not supported: +-- * multiple identical short option arguments (e.g. `-v -v`), +-- * optional arguments, e.g., `-v [level]`, +-- * non-option arguments. +-- Unknown options given are (silently) ignored. +-- +-- The `opts` option table has the following format: +-- { +-- {"", GETOPT_ARG.{NO,REQUIRED}, "" }, +-- ... +-- } +-- `` may be `nil` in which case only `` is recognized. +-- +--- @param argv table Integer-keyed arguments Table, modified in-place. +--- @param opts table Option Table +--- @return function # Iterator, returning the next (option, optarg) pair +function M.utils.getopt(argv, opts) + local optmap = {} + local optstring = "" + for _, optdef in ipairs(opts) do + assert(optdef[3], "Mandatory short option char absent!") + assert(optdef[2], "Mandatory has_arg definition absent!") + optstring = ("%s%s%s"):format(optstring, optdef[3], M.utils.GETOPT_ARG[optdef[2]]) + optmap[optdef[1] or "_"] = optdef[3] + end + for index, value in ipairs(argv) do + if value:sub(1, 2) == "--" and optmap[value:sub(3)] then + local optvalue = "-" .. optmap[value:sub(3)] + argv[index] = optvalue + argv[optvalue] = index + elseif value:sub(1, 1) == "-" then + argv[value] = index + end + end + local f = optstring:gmatch("%a:?") + return function() + while true do + local option = f() + if not option then + return + end + local optchar = option:sub(1, 1) + local param = "-" .. optchar + if argv[param] then + if option:sub(2, 2) ~= ":" then + return optchar, nil + end + local value = argv[argv[param] + 1] + if not value then + return ":", optchar + end + if value:sub(1, 1) == "-" then + return ":", optchar + end + return optchar, value + end + end + end +end + +--- Reverse ipairs() iterator. +-- +-- Like Lua's internal `ipairs()`, just reverse. +-- +--- @param tab table Table to iterate over in reverse ipairs()-order +--- @return function # The iterator function +--- @return table # The table `tab` +--- @return number # Array index +function M.utils.ripairs(tab) + local function rev_iter_ipairs(t, i) + i = i - 1 + if i ~= 0 then + return i, t[i] + end + end + return rev_iter_ipairs, tab, #tab + 1 +end + +--- ipairs() + filter iterator. +-- +--- @param tab table Table to iterate over +--- @param fun fun(any):boolean Filter function; evaluates to `true` to filter out item +--- @return function # The iterator function +--- @return table # The table tab +--- @return number # Array index +function M.utils.ipairsf(tab, fun) + local function iter_ipairs(t, i) + while true do + i = i + 1 + local v = t[i] + if v == nil then + return + end + if not fun(v) then + return i, v + end + end + end + return iter_ipairs, tab, 0 +end + +--- Merge two Key=Value Tables, leftwards. +-- +--- @param tab table Destination Table, modified +--- @param ovr table Table to merge into `tab`, overruling `tab`'s data if existing in `ovr` +--- @return table # Merged Table, i.e., `tab` +function M.utils.table.merge(tab, ovr) + local function istable(t) + return type(t) == "table" + end + for k, v in pairs(ovr) do + if istable(v) and istable(tab[k]) then + M.utils.table.merge(tab[k], v) + else + tab[k] = v + end + end + return tab +end + +--- Check if a Table contains `value`. +-- +--- @param tab table Table to check for `value` +--- @param value any Value to check for presence in `tab` +--- @return boolean # `true` if `tab` contains `value`, `false` otherwise +function M.utils.table.contains(tab, value) + if tab[value] then + return true + end + for _, v in ipairs(tab) do + if v == value then + return true + end + end + return false +end + +--- Filter Array Table. +--- @param tbl table Table to filter +--- @param func fun(any):boolean Filter function; evaluates to `true` to filter out item +--- @return table # New table, filtered +function M.utils.table.ifilter(tbl, func) + local newtbl = {} + for _, v in ipairs(tbl or {}) do + if not func(v) then + newtbl[#newtbl + 1] = v + end + end + return newtbl +end + +--- Test if Table is empty. +-- +--- @param tbl table Table to test for emptiness +--- @return boolean # `true` if empty, `false` if not +function M.utils.table.is_empty(tbl) + local index, _ = next(tbl) + return not index and true or false +end + +--- Enquote a String array's elements. +-- +--- @param t string[] String array to quote elements of +--- @return string[] # Element-quoted string array +function M.utils.string.enquote(t) + local quoted = {} + for _, v in ipairs(t) do + quoted[#quoted + 1] = ('"%s"'):format(v) + end + return quoted +end + +--- Escape and trim a String. +-- +-- The default substitutions table is suitable for escaping +-- to proper JSON. +-- +--- @param str string The JSON string to be escaped +--- @param substs? table Substitutions to apply (optional) +--- @return string # The escaped JSON string +function M.utils.string.escape(str, substs) + local escapes = '[%z\1-\31"\\]' + if not substs then + substs = { + ['"'] = '"', + ["\\"] = "\\\\", + ["\b"] = "", + ["\f"] = "", + ["\n"] = "", + ["\r"] = "", + ["\t"] = "", + } + end + substs.__index = function(_, c) + return ("\\u00%02X"):format(c:byte()) + end + setmetatable(substs, substs) + return str:gsub(escapes, substs):match("^%s*(.-)%s*$"):gsub("%s+", " ") +end + +--- Execute a callback with retries and pause in between. +-- +-- The `callback` function must return `true` or, in case of error, +-- `false` to attempt the next try (if any left). +-- +--- @param times number Maximal number of tries +--- @param pause number Pause in seconds between tries (defaults to 1) +--- @param callback fun(...):boolean, ... The callback function to try +--- @param ... any The callback function's parameters +--- @return boolean # `true`, or, in case of error, `false` +--- @return ... # Return values of `callback(...)` +function M.utils.do_retry(times, pause, callback, ...) + local ret + times, pause = times or 1, pause or 1 + for try = 1, times do + ret = table.pack(callback(...)) + if ret[1] == true then + return table.unpack(ret) + end + suricatta.notify.warn("Retry attempt %d/%d failed, sleeping %s seconds.", try, times, pause) + suricatta.sleep(pause) + end + suricatta.notify.error("All %d retry attempts failed.", times) + return table.unpack(ret) +end + +--- Validate a JSON document against its schema. +-- +-- A schema for a JSON document is defined in terms of Lua code, +-- given in the `schema` parameter, and validated against this. +-- +-- The schema definition for a particular JSON key `jkey` is a Lua +-- Table specifying the `type` of `jkey`'s value, possibly extended +-- with `min` and `max` properties the value has to satisfy. +-- The latter properties are `type`-dependent: A property `max = 23` +-- for a number-typed `jkey` enforces its value to be less than or +-- equal to 23. For a string-typed `jkey`, a `max = 23` property +-- enforces the maximal string length to be less or equal to 23. +-- The `type` is simply given by an accordingly typed Lua value. +-- Per default, all keys (and their values) present in the schema +-- have to be present in the to be validated JSON document while +-- not necessarily vice versa. Optional schema JSON keys can be +-- flagged as such by the `optional = true` property. +-- *Note*: Any other extra fields (properties) in the JSON schema +-- are silently ignored. +-- +-- For example, +-- `jkey = { type = "string", min = 1 }` +-- specifies `jkey` to be a string with length > 1. +-- +-- As another example, +-- `jkey = { type = 100, max = 100, min = 0 }` +-- specifies `jkey` to be a number in [0,100]. +-- +-- This naturally extends to JSON arrays with its content schema +-- specified in the sole property `value` as follows +-- ``` +-- jkey = { +-- type = {}, +-- value = { +-- [1] = { +-- jkey = { type = "string", min = 1 } +-- }, +-- }, +-- } +-- ``` +-- specifying a non-empty JSON array `jkey` with its array contents +-- being strings of minimal length 1. +-- +--- @param data table Data to validate against a schema +--- @param schema table Schema table +--- @param path? string Initial path +--- @return boolean # `true` if `data` satisfies schema, `false` otherwise +function M.utils.validate_json_schema(data, schema, path) + local function check_types(def, k, v, p) + if not def[k] then + if v.optional == true then + if type(v.type) == "string" then + def[k] = "" + end + if type(v.type) == "number" then + def[k] = 0 + end + return true + end + suricatta.notify.error("Validation Error: JSON key '%s/%s' (%s) is missing.", p, k, type(v.type)) + return false + end + if type(def[k]) ~= type(v.type) then + suricatta.notify.error( + "Validation Error: JSON key '%s/%s' has wrong type (is %s != %s).", + p, + k, + type(def[k]), + type(v.type) + ) + return false + end + if type(def[k]) == "string" then + local slen = #def[k] + if v.min and slen < v.min then + suricatta.notify.error("Validation Error: JSON key '%s/%s' < %s", p, k, tostring(v.min)) + return false + end + if not v.min and slen == 0 then + suricatta.notify.error("Validation Error: JSON key '%s/%s' is empty.", p, k) + return false + end + if v.max and slen > v.max then + suricatta.notify.error("Validation Error: JSON key '%s/%s' > %s", p, k, tostring(v.max)) + return false + end + end + if type(def[k]) == "number" then + if v.min and def[k] < v.min then + suricatta.notify.error("Validation Error: JSON key '%s/%s' < %s", p, k, tostring(v.min)) + return false + end + if v.max and def[k] > v.max then + suricatta.notify.error("Validation Error: JSON key '%s/%s' > %s", p, k, tostring(v.max)) + return false + end + end + if type(v.type) == "table" then + if not M.utils.validate_json_schema(def[k], v.value, ("%s/%s"):format(p, k)) then + return false + end + end + return true + end + path = path or "" + if type(schema) ~= "table" then + suricatta.notify.error("Validation Error: Schema at '%s' is not Table.", path) + return false + end + for k, v in pairs(schema) do + if not check_types(data, k, v, path) then + return false + end + end + if schema[1] then + for i, _ in ipairs(data) do + if not check_types(data, i, schema[1], path) then + return false + end + end + end + return true +end + +-- ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +-- ▏ +-- ▏ wfx Job Definitions & Types +-- ▏ +-- ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +--- @class state +-- +-- Enum definitions for states. +-- +M.state = { + --- @enum state.types + --- Type of a state, used to classify state semantics. + types = { + --- A regular (i.e. none of the other) state of the workflow + REGULAR = "REGULAR", + --- A successful terminal state of the workflow + CLOSED = "CLOSED", + --- A failed terminal state of the workflow + FAILED = "FAILED", + }, +} + +--- @class transition +-- +-- Enum definitions for transitions. +-- +M.transition = { + --- @enum transition.eligibles + --- wfx's `EligibleEnum` enum values. + eligibles = { + --- CLIENT-eligible transition + CLIENT = "CLIENT", + --- WFX-eligible transition + WFX = "WFX", + }, + --- @enum transition.result + --- Transition execution function result enum values. + result = { + --- Transition execution function completed successfully, yield to wfx + COMPLETED = "COMPLETED", + --- Transition execution function failed, try next + FAIL_NEXT = "FAIL_NEXT", + --- Transition execution function failed, yield to wfx + FAIL_YIELD = "FAIL_YIELD", + }, +} + +--- Functionality-augmented wfx `Job` instance. +-- +--- @class job +--- @field id string Unique job ID +--- @field clientId string Device ID +--- @field definition job.definition DeviceArtifactUpdate definition +--- @field history any Job history (unused) +--- @field mtime string Date and time (ISO8601) when the job was last modified +--- @field stime string Date and time (ISO8601) when the job was created +--- @field status job.status Job Status Information +--- @field tags string[] Job tags +--- @field workflow job.workflow Job's Workflow +M.job = setmetatable({}, { + --- @param self job This job instance + --- @param wfx_job job wfx job + --- @return job? # This job instance with job data from wfx or `nil` on error + __call = function(self, wfx_job) + local function mt(tab, indextab) + local t = getmetatable(tab) or {} + t.__index = indextab + t.__newindex = function(_, key, value) + if not indextab[key] then + error( + ("Internal Error: Contaminating job Table with %s=%s"):format( + tostring(key or ""), + tostring(value or "") + ) + ) + end + rawset(indextab, key, value) + end + return setmetatable(tab, t) + end + + self.definition.meta = self + self.status.meta = self + self.workflow.meta = self + + M.job.status.normalize(wfx_job.status) + self = mt(self, wfx_job) + self.definition = mt(self.definition, wfx_job.definition) + self.status = mt(self.status, wfx_job.status) + self.workflow = mt(self.workflow, wfx_job.workflow) + if not self:validate() or not self:prepare() then + return + end + return self + end, +}) + +--- Validate the job. +-- +--- @return boolean # `true` if the job is valid, `false` otherwise +function M.job:validate() + local function validate_id() + if self.clientId ~= M.device.id then + suricatta.notify.error( + "Validation Error: Job's client ID (%s) doesn't match ours (%s).", + self.clientId, + M.device.id + ) + return false + end + return true + end + return validate_id() and self.definition:validate() and self.workflow:validate() +end + +--- Prepare the job. +-- +--- @return boolean # `true` if preparation succeeded, `false` otherwise +function M.job:prepare() + return self.definition:prepare() and self.workflow:prepare() +end + +--- Mark the job as invalid. +function M.job:invalidate() + if self.id then + self.id = "0xDEAD1D" + end +end + +--- Test whether the job is invalid. +-- +--- @return boolean # `true` if the job is invalid, `false` otherwise +function M.job:invalid() + return not self.id or self.id == "0xDEAD1D" +end + +-- ┌─────────────────────────────────────────────────────────────────────────── +-- │ Job Status +-- └─────────────────────────────────────────────────────────────────────────── + +--- Job status information. +-- +--- @class job.status +--- @field state string Name of the Workflow state +--- @field clientId string Client which sent the status update +--- @field progress number Current job progress percentage [0,100] +--- @field message string State update reason message +--- @field context string[] Additional context information. +--- @field definitionHash string `job.definition` hash +--- @field meta job This job status' `job` +M.job.status = { + --- Job status message JSON Schema. + -- + --- @class job.status.json_schema + -- + -- JSON schema to comply to when sending status updates to wfx. + -- + json_schema = { + clientId = { type = "string", min = 1 }, + state = { type = "string", min = 1 }, + progress = { type = 100, max = 100, min = 0 }, + message = { type = "string", max = 1024, min = 0 }, + context = { type = "string", max = 2000, delimiter = ", " }, + }, +} +setmetatable(M.job.status, { + __tostring = function(self) + local context = ([[{ "lines": [%s] }]]):format( + table.concat(M.utils.string.enquote(self.context or {}), self.json_schema.context.delimiter) + ) + if + not M.utils.validate_json_schema({ + state = self.state, + clientId = M.device.id, + progress = self.progress, + message = self.message, + context = context, + }, self.json_schema) + then + error("Internal Error: Status message is schema-invalid.") + end + local status = { + ([["state": "%s"]]):format(self.state), + ([["clientId": "%s"]]):format(M.device.id), + ([["progress": %.0f]]):format(self.progress), + ([["message": "%s"]]):format(self.message), + ([["context": %s]]):format(context), + } + return M.utils.string.escape(([[{%s}]]):format(table.concat(status, ", "))) + end, +}) + +--- Update job status JSON schema from its OpenAPI (Swagger) definition. +-- +--- @param swagger table Swagger definition +--- @return boolean # `true` if swagger was parsed, `false` otherwise +function M.job.status:update_json_schema(swagger) + if type(swagger) ~= "table" then + return false + end + local spec = (((swagger or {}).definitions or {}).JobStatus or {}).properties or {} + if M.utils.table.is_empty(spec) then + return false + end + for k, v in pairs(spec) do + if self.json_schema[k] then + local max = v.maxLength or v.maximum + self.json_schema[k].max = max and tonumber(max) or nil + end + end + return true +end + +--- Normalize job status information. +-- +--- @param status job.status Job status instance to normalize +--- @return job.status # Normalized `job.status` instance +function M.job.status.normalize(status) + status.state = status.state or "0xDEAD57A7E" + status.clientId = M.device.id + status.progress = status.progress or 0 + status.message = status.message or "" + status.context = status.context or {} + return status +end + +--- Set job status information. +-- +--- @param status job.status Data to set +--- @return job.status # `self` for chaining +function M.job.status:set(status) + if not status.state then + error("Internal Error: job.status:set() must be called with the .state field set.") + end + if self.definitionHash and status.definitionHash and self.definitionHash ~= status.definitionHash then + suricatta.notify.info("Job definition changed, getting new version from server...") + self.meta.definition:update() + end + return M.utils.table.merge(self.normalize(self), status) +end + +--- @class retry_settings +--- @field retries number Number of (re-)tries +--- @field retry_sleep number Number of seconds between retries + +--- Send job status information and update job with response. +-- +-- *Note*: Sends status information to wfx and SWUpdate's progress interface. +-- +--- @param chan? suricatta.open_channel The channel to send the status update over +--- @param retry? retry_settings Retry configuration +--- @return boolean # `true` if job status information sent, `false` on error +function M.job.status:send(chan, retry) + chan = chan or M.channel.main + local retries = (retry or {}).retries or chan.options.retries + local retry_sleep = (retry or {}).retry_sleep or chan.options.retry_sleep + local msg = tostring(self) + return M.utils.do_retry(retries, retry_sleep, function() + local res, _, data = chan.put { + url = ("%s/jobs/%s/status"):format(chan.options.url, self.meta.id), + content_type = "application/json", + method = suricatta.channel.method.PUT, + format = suricatta.channel.content.JSON, + headers_to_send = { + -- Filter out unneeded reply's context to save some bandwidth. + ["X-Response-Filter"] = "del(.context)", + }, + request_body = msg, + } + if not res then + suricatta.notify.error( + "Cannot transition to state '%s': HTTP Error %d.", + self.state, + data.http_response_code + ) + return false + end + suricatta.notify.debug(("[%3s%%%%] %s"):format(self.progress, self.message)) + suricatta.notify.progress(("[%3s%%] %s"):format(self.progress, self.message)) + if not M.utils.table.is_empty((data or {}).json_reply or {}) then + self:set(data.json_reply) + end + return true + end) +end + +-- ┌─────────────────────────────────────────────────────────────────────────── +-- │ Job Workflow +-- └─────────────────────────────────────────────────────────────────────────── + +--- Function prototype transition execution functions adhere to. +--- @alias dispatch_fn fun(job.workflow.transition, ...):transition.result, ... + +--- Job Workflow. +-- +-- The `states` Table is indexable by a state's name. +-- The `transitions` Table, indexed by a state name, holds all CLIENT-eligible transitions from that state. +-- +--- @class job.workflow +--- @field name string Unique Workflow name +--- @field states job.workflow.state[] | {[string]: job.workflow.state} States of the Workflow +--- @field transitions job.workflow.transition[] | {[string]: job.workflow.transition[]} Transitions of the Workflow +--- @field groups job.workflow.group[] | {[string]: job.workflow.group} State Groups of the Workflow +--- @field meta job This job.workflows' job +M.job.workflow = { + --- Workflow `Group` type definition. + -- + --- @class job.workflow.group + --- @field name string Group name + --- @field states string[] Name of States in this Group + + --- Workflow `state` type definition. + -- + --- @class job.workflow.state + --- @field name string State name + --- @field description string Description of the state + --- @field group string | nil Group the state belongs to + --- @field type state.types State's type ∈ { REGULAR, CLOSED, FAILED } + + --- Workflow `Transition` type definition. + -- + --- @class job.workflow.transition + --- @field from job.workflow.state Source state + --- @field to job.workflow.state Target state + --- @field description string Description of the transition + --- @field eligible transition.eligibles Actor that may execute the transition + --- @field execute dispatch_fn Transition execution function + + --- Transition execution function Dispatch. + -- + -- Table holding transition execution functions and their access functions. + -- + --- @class job.workflow.dispatch + dispatch = {}, +} + +--- Get a transition execution function. +-- +--- @param from string Source state name +--- @param to string Target state name +--- @return dispatch_fn? # Transition execution function +function M.job.workflow.dispatch:get(from, to) + return (self.__funcs or {})[from .. "→" .. to] +end + +--- Set a transition execution function. +-- +--- @param from string Source state name +--- @param to string Target state name +--- @param fun dispatch_fn Transition execution function +function M.job.workflow.dispatch:set(from, to, fun) + self.__funcs = self.__funcs or {} + self.__funcs[from .. "→" .. to] = fun +end + +--- Validate job Workflow. +-- +--- @return boolean # `true` if the job's workflow is valid, `false` otherwise +function M.job.workflow:validate() + for _, trans in ipairs(self.transitions) do + -- Note: :validate() is called prior to :prepare(), so not augmented yet. + --- @cast trans { [string]: string } + if trans.eligible == M.transition.eligibles.CLIENT and not self.dispatch:get(trans.from, trans.to) then + suricatta.notify.error( + "Validation Error: Transition execution function %s → %s not defined.", + trans.from, + trans.to + ) + return false + end + end + return true +end + +--- Augment the Workflow with meta functionality. +-- +-- *Note*: wfx makes sure a workflow is sound, i.e., if a state +-- is referenced in a transition, it's actually a valid state. +-- +--- @param self job.workflow This `job.workflow` instance +--- @return boolean # `true` if preparation succeeded, `false` otherwise +function M.job.workflow:prepare() + local group_type = {} + for _, n in ipairs(M.dau.wfx_group.failed) do + group_type[n] = M.state.types.FAILED + end + for _, n in ipairs(M.dau.wfx_group.closed) do + group_type[n] = M.state.types.CLOSED + end + + local state_group = {} + for _, g in ipairs(self.groups or {}) do + for _, s in ipairs(g.states) do + state_group[s] = g.name + end + self.groups[g.name] = g + end + for _, s in ipairs(self.states or {}) do + s.group = state_group[s.name] + s.type = group_type[s.group] or M.state.types.REGULAR + self.states[s.name] = s + end + + for i, t in ipairs(self.transitions or {}) do + local trans = { + from = self.states[t.from], + to = self.states[t.to], + eligible = t.eligible, + execute = self.dispatch:get(self.states[t.from].name, self.states[t.to].name), + } + self.transitions[i] = trans + if trans.eligible == M.transition.eligibles.CLIENT then + self.transitions[trans.from.name] = self.transitions[trans.from.name] or {} + self.transitions[trans.from.name][#self.transitions[trans.from.name] + 1] = trans + end + end + return true +end + +--- Execute a transition. +-- +--- @param self job.workflow This `job.workflow` instance +--- @param from string Source state name +--- @param to string Target state name +--- @param ... any Additional data passed to the transition execution function, if any +--- @return transition.result # Result of transition execution function or `nil` if none found +--- @return ...? # Additional return values of the transition execution function +function M.job.workflow:call(from, to, ...) + --- @type job.workflow.transition + local trans = M.utils.table.ifilter(self.transitions[from] or {}, function(t) + --- @cast t job.workflow.transition + return t.to.name ~= to + end)[1] + if not trans then + error(("Runtime Error: More than one transition %s → %s or not existent!"):format(from, to)) + end + suricatta.notify.info( + "Invoking transition %s:%s → %s:%s", + trans.from.type, + trans.from.name, + trans.to.type, + trans.to.name + ) + return trans:execute(...) +end + +--- Helper function actually executing transition execution function(s). +-- +--- @param self job.workflow This `job.workflow` instance +--- @param silent boolean Whether to be verbose or not +--- @param func fun(job.workflow.transition):boolean Filter function; evaluates to `true` to filter out item +--- @param ... any Additional data passed to the transition execution function, if any +--- @return transition.result # Result of transition execution function +--- @return ...? # Additional return values of the transition execution function +local function do_advance(self, silent, func, ...) + local message = { + [M.transition.result.COMPLETED] = nil, + [M.transition.result.FAIL_NEXT] = "Transition %s → %s execution failed, trying next.", + [M.transition.result.FAIL_YIELD] = "Transition %s → %s execution failed, yielding to wfx.", + } + for trans in M.dau.sequencer(M.utils.table.ifilter(self.transitions[self.meta.status.state] or {}, func)) do + if not silent then + local msg = (select(1, ...)) + --- @cast trans job.workflow.transition + suricatta.notify.info( + "Executing transition %s:%s → %s:%s%s", + trans.from.type, + trans.from.name, + trans.to.type, + trans.to.name, + type(msg) == "string" and (" (%s)"):format(msg) or "" + ) + end + local result = table.pack(trans:execute(...)) + if message[result] then + suricatta.notify.debug(string.format(message[result[1]], trans.from.name, trans.to.name)) + end + if M.utils.table.contains({ M.transition.result.COMPLETED, M.transition.result.FAIL_YIELD }, result[1]) then + return table.unpack(result) + end + end + suricatta.notify.debug("No (further) transition to execute found.") + return M.transition.result.FAIL_YIELD +end + +--- Advance Workflow executing client transitions. +-- +--- @param self job.workflow This `job.workflow` instance +--- @param ... any Additional data passed to the transition execution function, if any +--- @return transition.result # Result of transition execution function +--- @return ...? # Additional return values of the transition execution function +function M.job.workflow:advance(...) + return do_advance(self, false, function(t) + --- @cast t job.workflow.transition + return not M.utils.table.contains({ M.state.types.CLOSED, M.state.types.REGULAR }, t.to.type) + end, ...) +end + +--- Test if Workflow can be advanced by the client from its current state. +-- +--- @param self job.workflow This `job.workflow` instance +--- @return boolean # `true` if Workflow can be advanced from its current state, `false` otherwise +function M.job.workflow:can_advance() + return not M.utils.table.is_empty( + M.utils.table.ifilter(self.transitions[self.meta.status.state] or {}, function(t) + --- @cast t job.workflow.transition + return not M.utils.table.contains({ M.state.types.CLOSED, M.state.types.REGULAR }, t.to.type) + end) + ) +end + +--- Terminate Workflow processing with Failure. +-- +--- @param self job.workflow This `job.workflow` instance +--- @param ... any Additional data passed to the transition execution function, if any +--- @return transition.result # Result of transition execution function +--- @return ...? # Additional return values of the transition execution function +function M.job.workflow:fail(...) + return do_advance(self, false, function(t) + --- @cast t job.workflow.transition + return t.to.type ~= M.state.types.FAILED + end, ...) +end + +--- Report in-state progress to wfx. +-- +--- @param self job.workflow This `job.workflow` instance +--- @param ... any Additional data passed to the transition execution function, if any +--- @return transition.result # Result of transition execution function +--- @return ...? # Additional return values of the transition execution function +function M.job.workflow:report_progress(...) + return do_advance(self, true, function(t) + --- @cast t job.workflow.transition + return t.to.type ~= M.state.types.REGULAR or t.from.name ~= t.to.name + end, ...) +end + +--- Test if the current state is a terminal state. +-- +--- @param self job.workflow This `job.workflow` instance +--- @param status job.status Job status to test for terminal state +--- @return boolean? # `true` if current state is terminal, `false` otherwise +function M.job.workflow:terminated(status) + M.job.status.normalize(status) + return M.utils.table.contains( + { M.state.types.FAILED, M.state.types.CLOSED }, + (self.states[status.state] or {}).type + ) +end + +-- ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +-- ▏ +-- ▏ DeviceArtifactUpdate +-- ▏ +-- ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +--- DeviceArtifactUpdate ― Semantic Workflow Group Mapping. +-- +--- @class dau.wfx_group +--- @field query string[] Group name(s) used when querying wfx for (new) jobs +--- @field failed string[] Group name(s) of a failed update's terminal states +--- @field closed string[] Group name(s) of a successful update's terminal states +M.dau.wfx_group = { + query = { "OPEN" }, + failed = { "FAILED" }, + closed = { "CLOSED" }, +} + +--- @enum state.dau +--- State names in the DeviceArtifactUpdate Workflow Family +M.state.dau = { + DOWNLOAD = "DOWNLOAD", + DOWNLOADING = "DOWNLOADING", + DOWNLOADED = "DOWNLOADED", + INSTALL = "INSTALL", + INSTALLING = "INSTALLING", + INSTALLED = "INSTALLED", + ACTIVATE = "ACTIVATE", + ACTIVATING = "ACTIVATING", + ACTIVATED = "ACTIVATED", + TERMINATED = "TERMINATED", +} + +--- Transition Sequencer Iterator. +-- +-- The transition sequencer iterator returns the transition to be executed next +-- out of a set of eligible transitions, in a Workflow (Family)-specific order. +-- +-- This simple algorithm sorts the given transitions into 3 buckets: REGULAR, +-- CLOSED, and FAILED. The REGULAR transition bucket is sorted so that self- +-- looping transitions are prioritized, i.e., "non-greedy". Ordering the CLOSED +-- and FAILED transition bucket is redundant as each results in a terminal state. +-- This sorted list is returned as an iterator. +-- +--- @param transitions job.workflow.transition[] Eligible transitions +--- @return function # The iterator function +--- @return table # The modified `transitions` table +function M.dau.sequencer(transitions) + local trans = { + [M.state.types.REGULAR] = 1, + [M.state.types.CLOSED] = 2, + [M.state.types.FAILED] = 3, + [1] = {}, + [2] = {}, + [3] = {}, + } + for _, t in ipairs(transitions or {}) do + local bucket = trans[t.to.type] + trans[bucket][#trans[bucket] + 1] = t + end + table.sort(trans[1], function(a, _) + --- @cast a job.workflow.transition + if a.from.name == a.to.name then + -- Sort self-looping transitions first. + return true + end + return false + end) + local i = { bucket = 1, index = 0 } + local function get(tab) + i.index = i.index + 1 + if tab[i.bucket][i.index] ~= nil then + return tab[i.bucket][i.index] + end + i.bucket, i.index = i.bucket + 1, 0 + if not tab[i.bucket] then + return + end + return get(tab) + end + return get, trans +end + +-- ┌─────────────────────────────────────────────────────────────────────────── +-- │ DeviceArtifactUpdate :: Definition +-- └─────────────────────────────────────────────────────────────────────────── + +--- Default update artifact type (i.e., activation mode). +M.dau.DEFAULT_UPDATE_TYPE = "firmware" + +--- DeviceArtifactUpdate ― Job Definition. +-- +--- @class job.definition +--- @field version string Version of the Update +--- @field type string[] | {[string]: number} Type of the Update +--- @field artifacts job.definition.artifact[] List of Artifacts to install +--- @field meta job This job definitions' `job` +M.job.definition = {} + +--- DeviceArtifactUpdate ― Artifacts Definition. +-- +--- @class job.definition.artifact +--- @field name string (File) name of the artifact +--- @field version string Version of the Artifact +--- @field uri string URI where the artifact can be downloaded + +--- DeviceArtifactUpdate ― Definition JSON Schema +--- @class job.definition.json_schema +M.job.definition.json_schema = { + version = { type = "string" }, + type = { type = {}, value = { [1] = { type = "string" } } }, + artifacts = { + type = {}, + value = { + [1] = { + type = {}, + value = { + name = { type = "string", optional = true }, + version = { type = "string", optional = true }, + uri = { type = "string" }, + }, + }, + }, + }, +} + +--- Update job Definition from server. +-- +-- *Note*: This is a synchronous operation since the next transition execution +-- function may rely on a changed job definition's data. +-- +--- @param chan? suricatta.open_channel The channel to get the definition update over +function M.job.definition:update(chan) + chan = chan or M.channel.main + M.utils.do_retry(math.huge, chan.options.retry_sleep, function() + local res, _, data = chan.get { + url = ("%s/job/%s/definition"):format(chan.options.url, self.meta.id), + } + if not res then + suricatta.notify.warn("Got HTTP error code %d from server.", data.http_response_code) + return false + end + if M.utils.table.is_empty((data or {}).json_reply or {}) then + suricatta.notify.warn("Got bad JSON response from server: %q", tostring(data)) + return false + end + if not self:validate(data.json_reply) then + return false + end + local t = getmetatable(self) + t.__index = data.json_reply + self = setmetatable(self, t) + return true + end) +end + +--- Augment job Definition with meta functionality. +-- +--- @return boolean # `true` if preparation succeeded, `false` otherwise +function M.job.definition:prepare() + if #table.concat(self.type, ":") > 2048 then + suricatta.notify.error("Validation Error: definition's .type is > %d chars.", 2048 - (#self.type - 1)) + return false + end + for i, v in ipairs(self.type) do + self.type[v] = i + end + return true +end + +--- Validate job Definition. +-- +--- @param definition job.definition? Job definition other than `self`'s +--- @return boolean # `true` if the job's definition is valid, `false` otherwise +function M.job.definition:validate(definition) + return M.utils.validate_json_schema(definition or self, self.json_schema) and self:prepare() +end + +--- Test whether update artifact is of (default) type "firmware". +-- +-- If one of `.type`'s tags matches `DEFAULT_UPDATE_TYPE` == "firmware", only then the +-- update is to be firmware-activated, i.e., via rebooting into the new firmware. +-- +-- Other tags are passed verbatim to progress client(s), concatenated with ":", +-- for them to take according activation actions. +-- +--- @return boolean # `true` if activation method is "firmware" (default), `false` if not +function M.job.definition:is_firmware() + return self.type[M.dau.DEFAULT_UPDATE_TYPE] ~= nil +end + +-- ┌─────────────────────────────────────────────────────────────────────────── +-- │ DeviceArtifactUpdate :: Transitions +-- └─────────────────────────────────────────────────────────────────────────── + +--- Helper function to truncate installation logs to maximal length. +-- +--- @param log table Table with log lines +--- @param maxlength number Maximal String length +--- @param delimiter string JSON list delimiter +--- @return string[] # Table with log lines, `delimiter`-concatenated less than `maxlength` characters +local function prepare_logs(log, maxlength, delimiter) + local details = {} + -- Some headroom for JSON enclosure in status message. + local length = 50 + for index, line in M.utils.ripairs(log or {}) do + local message = ([[%d: %s]]):format(index, M.utils.string.escape(line)) + -- Add JSON list delimiter length plus \n to string length + length = length + #message + #delimiter + 1 + if length > maxlength then + break + end + table.insert(details, 1, message) + end + return details +end + +--- @class job_status +--- @field [1] job.workflow.transition This `job.workflow.transition` instance +--- @field [2] job This `job` instance +--- @field state? string Target state, defaults to this transition's `to.name` +--- @field message? string State update reason message +--- @field progress? number Current job progress percentage [0,100] +--- @field context? string[] Additional context information. +--- @field channel? suricatta.open_channel The channel to send the status update over +--- @field retry? retry_settings Retry settings, else channel defaults are used + +--- Helper function to send & sync Job Status with wfx. +-- +-- *Note*: `job.status.state` is set to this transition's `to` state. +-- +--- @param status job_status +--- @return boolean +local function sync_job_status(status) + if #status > 2 then + error("Internal Error: sync_job_status() called with > 2 positional parameters.") + end + local transition = status[1] + local job = status[2] + return job.status + :set({ + state = status.state or transition.to.name, + message = status.message or "", + progress = status.progress or 100, + context = status.context or {}, + }) + :send(status.channel, status.retry) +end + +--- Helper function to send update activation progress message. +-- +-- Send update activation message via SWUpdate's progress interface +-- to subscribed progress clients. +-- +--- @param job job +local function send_progress_activation_msg(job) + suricatta.notify.progress(M.utils.string + .escape([[ + { + "state": "ACTIVATING", + "progress": 100, + "message": "%s" + } + ]]) + :format(table.concat(job.definition.type, ":"))) +end + +--- Helper function to set persistent bootloader state. +-- +-- Storing the persistent state `pstate` is retried up to 3 times +-- with 3 seconds pause in between each try if it has failed. +-- +--- @param pstate number Any of `suricatta.pstate`'s enum values +--- @return boolean # `true` if persistent state is set, `false` otherwise +local function save_pstate(pstate) + suricatta.notify.debug("Setting persistent state to %s.", suricatta.pstate[pstate]) + return M.utils.do_retry(3, 3, function() + if suricatta.pstate.save(pstate) then + suricatta.notify.debug("Persistent state set to %s.", suricatta.pstate[pstate]) + return true + end + suricatta.notify.error("Error setting persistent state information, retrying in a few seconds.") + return false + end) +end + +--- Helper function to check if `pstate` allows starting a new job. +-- +--- @param self job.workflow.transition +--- @param job job +--- @return boolean +local function is_pstate_ok(self, job) + if M.device.pstate == suricatta.pstate.INSTALLED then + suricatta.notify.warn("Started new job while last one's activation is pending, notifying progress.") + send_progress_activation_msg(job) + return false + end + if M.utils.table.contains({ suricatta.pstate.FAILED, suricatta.pstate.TESTING }, M.device.pstate) then + local msg = ("Persistent state is %s at job start, spurious update test?"):format( + suricatta.pstate[M.device.pstate] or "N/A" + ) + suricatta.notify.warn(msg) + sync_job_status { self, job, message = msg, progress = 0 } + end + return true +end + +M.job.workflow.dispatch:set( + M.state.dau.DOWNLOAD, + M.state.dau.DOWNLOADING, + --- @param self job.workflow.transition + --- @param job job + --- @return transition.result + function(self, job) + local ok = is_pstate_ok(self, job) + if not ok then + return M.transition.result.FAIL_YIELD + end + + if not sync_job_status { self, job, message = ("Start %s."):format(self.from.name), progress = 0 } then + return M.transition.result.FAIL_YIELD + end + + M.channel.progress = M.channel(M.utils.table.merge({ noprogress = true }, M.channel.main.options)) + if not M.channel.progress then + suricatta.notify.warn("Cannot initialize progress reporting channel, won't send progress.") + end + + suricatta.notify.debug( + "%s Version '%s' (Type: %s).", + self.to.name, + job.definition.version, + table.concat(job.definition.type, ":") + ) + + for count, artifact in pairs(job.definition.artifacts) do + local source_uri = M.utils.string.escape(artifact.uri, { ['"'] = '"', ["\\"] = "" }) + local target_uri = ("%s%s_%d.swu"):format(suricatta.get_tmpdir(), job.id, count) + suricatta.notify.debug( + "[ 0%%] [%s] Artifact %d/%d: '%s' from %q.", + self.to.name, + count, + #job.definition.artifacts, + artifact.name, + source_uri + ) + local res, _, updatelog = suricatta.download({ channel = M.channel.main, url = source_uri }, target_uri) + if not res then + local msg = ("Error downloading artifact %d/%d."):format(count, #job.definition.artifacts) + suricatta.notify.error(msg) + return job.workflow:fail( + job, + msg, + prepare_logs( + updatelog, + job.status.json_schema.context.max, + job.status.json_schema.context.delimiter + ) + ) + end + sync_job_status { + self, + job, + message = ("Artifact %d/%d download finished."):format(count, #job.definition.artifacts), + progress = math.floor(100 / #job.definition.artifacts * count), + } + end + + if M.channel.progress then + M.channel.progress.close() + M.channel.progress = nil + end + + sync_job_status { self, job, message = "Finished." } + + -- Tail-call transition DOWNLOADING → DOWNLOADED without detour via wfx query. + return job.workflow:call(M.state.dau.DOWNLOADING, M.state.dau.DOWNLOADED, job) + end +) + +M.job.workflow.dispatch:set( + M.state.dau.DOWNLOAD, + M.state.dau.TERMINATED, + --- @param self job.workflow.transition + --- @param job job + --- @return transition.result + function(self, job) + if not sync_job_status { self, job } then + return M.transition.result.FAIL_YIELD + end + return M.transition.result.COMPLETED + end +) + +M.job.workflow.dispatch:set( + M.state.dau.DOWNLOADING, + M.state.dau.DOWNLOADING, + --- @param self job.workflow.transition + --- @param job job + --- @param message? suricatta.ipc.progress_msg + --- @return transition.result + function(self, job, message) + if not message then + -- Called as "regular" transition in response to wfx query. + -- This happens if: + -- * DOWNLOADING → TERMINATED has failed or + -- * DOWNLOADING → DOWNLOADED has failed. + -- In both cases, terminate the update. + return job.workflow:fail(job, "wfx sync has failed, terminating update.") + end + -- Called as progress reporting callback transition. + if not M.channel.progress or not message.source == suricatta.ipc.sourcetype.SOURCE_SURICATTA then + return M.transition.result.FAIL_YIELD + end + if message.status == suricatta.ipc.RECOVERY_STATUS.DOWNLOAD then + if M.server.progress_rate_limited(message.dwl_percent, job.status.progress) then + return M.transition.result.COMPLETED + end + -- Informational progress reports to wfx are not retried. + sync_job_status { + self, + job, + channel = M.channel.progress, + message = "Downloading artifact", + progress = message.dwl_percent, + retry = { retries = 1 }, + } + end + return M.transition.result.COMPLETED + end +) + +M.job.workflow.dispatch:set( + M.state.dau.DOWNLOADING, + M.state.dau.DOWNLOADED, + --- @param self job.workflow.transition + --- @param job job + --- @return transition.result + function(self, job) + if not sync_job_status { self, job, message = "Finished." } then + return M.transition.result.FAIL_YIELD + end + return M.transition.result.COMPLETED + end +) + +M.job.workflow.dispatch:set( + M.state.dau.DOWNLOADING, + M.state.dau.TERMINATED, + --- @param self job.workflow.transition + --- @param job job + --- @param message? string + --- @param context? string[] + --- @return transition.result + function(self, job, message, context) + if not sync_job_status { self, job, message = message, context = context } then + return M.transition.result.FAIL_YIELD + end + return M.transition.result.COMPLETED + end +) + +M.job.workflow.dispatch:set( + M.state.dau.INSTALL, + M.state.dau.INSTALLING, + --- @param self job.workflow.transition + --- @param job job + --- @return transition.result + function(self, job) + local ok = is_pstate_ok(self, job) + if not ok then + return M.transition.result.FAIL_YIELD + end + + if job.workflow.transitions[M.state.dau.DOWNLOAD] then + local function file_exists(name) + local f = io.open(name, "r") + return f ~= nil and io.close(f) + end + -- Check that all to be installed artifact files are downloaded. + for count, _ in pairs(job.definition.artifacts) do + local file = ("%s%s_%d.swu"):format(suricatta.get_tmpdir(), job.id, count) + if not file_exists(file) then + local msg = ("Artifact %d/%d not downloaded to '%s'.."):format( + count, + #job.definition.artifacts, + file + ) + suricatta.notify.error(msg) + return job.workflow:fail(job, msg) + end + end + end + + if not sync_job_status { self, job, message = ("Start %s."):format(self.from.name), progress = 0 } then + return M.transition.result.FAIL_YIELD + end + + M.channel.progress = M.channel(M.utils.table.merge({ noprogress = true }, M.channel.main.options)) + if not M.channel.progress then + suricatta.notify.warn("Cannot initialize progress reporting channel, won't send progress.") + end + + suricatta.notify.debug( + "%s Version '%s' (Type: %s).", + self.to.name, + job.definition.version, + table.concat(job.definition.type, ":") + ) + + for count, artifact in pairs(job.definition.artifacts) do + local source_uri = M.utils.string.escape(artifact.uri, { ['"'] = '"', ["\\"] = "" }) + if job.workflow.transitions[M.state.dau.DOWNLOAD] then + source_uri = ("file://%s%s_%d.swu"):format(suricatta.get_tmpdir(), job.id, count) + end + suricatta.notify.debug( + "[ 0%%] Artifact %d/%d: '%s' from %q", + count, + #job.definition.artifacts, + artifact.name, + source_uri + ) + local res, _, updatelog = suricatta.install { channel = M.channel.main, url = source_uri } + -- Unconditionally remove the artifact file ignoring errors, e.g., if run in streaming mode. + os.remove(("%s%s_%d.swu"):format(suricatta.get_tmpdir(), job.id, count)) + if not res then + local msg = ("Error installing artifact %d/%d."):format(count, #job.definition.artifacts) + suricatta.notify.error(msg) + return job.workflow:fail( + job, + msg, + prepare_logs( + updatelog, + job.status.json_schema.context.max, + job.status.json_schema.context.delimiter + ) + ) + end + sync_job_status { + self, + job, + message = ("Artifact %d/%d installation finished."):format(count, #job.definition.artifacts), + progress = math.floor(100 / #job.definition.artifacts * count), + } + end + + if M.channel.progress then + M.channel.progress.close() + M.channel.progress = nil + end + + sync_job_status { self, job, message = "Finished." } + + -- Tail-call transition INSTALLING → INSTALLED without detour via wfx query. + return job.workflow:call(M.state.dau.INSTALLING, M.state.dau.INSTALLED, job) + end +) + +M.job.workflow.dispatch:set( + M.state.dau.INSTALL, + M.state.dau.TERMINATED, + --- @param self job.workflow.transition + --- @param job job + --- @return transition.result + function(self, job) + if not sync_job_status { self, job } then + return M.transition.result.FAIL_YIELD + end + return M.transition.result.COMPLETED + end +) + +M.job.workflow.dispatch:set( + M.state.dau.INSTALLING, + M.state.dau.INSTALLING, + --- @param self job.workflow.transition + --- @param job job + --- @param message? suricatta.ipc.progress_msg + --- @return transition.result + function(self, job, message) + if not message then + -- Called as "regular" transition in response to wfx query. + -- This happens if: + -- * INSTALLING → TERMINATED has failed or + -- * INSTALLING → INSTALLED has failed. + -- In both cases, terminate the update. + return job.workflow:fail(job, "wfx sync has failed, terminating update.") + end + + if not M.channel.progress or not message.source == suricatta.ipc.sourcetype.SOURCE_SURICATTA then + return M.transition.result.FAIL_YIELD + end + + if message.status == suricatta.ipc.RECOVERY_STATUS.DOWNLOAD then + if M.server.progress_rate_limited(message.dwl_percent, job.status.progress) then + return M.transition.result.COMPLETED + end + local msg = "Downloading artifact" + if job.workflow.transitions[M.state.dau.DOWNLOAD] then + msg = ("Copying artifact to TMPDIR=%s"):format(suricatta.get_tmpdir()) + end + -- Informational progress reports to wfx are not retried. + sync_job_status { + self, + job, + channel = M.channel.progress, + message = msg, + progress = message.dwl_percent, + retry = { retries = 1 }, + } + return M.transition.result.COMPLETED + end + + if message.status == suricatta.ipc.RECOVERY_STATUS.PROGRESS then + -- Don't report installing message if the 'dummy' handler is used or no current image is set. + if #message.hnd_name == 0 or #message.cur_image == 0 or message.cur_step == 0 then + return M.transition.result.COMPLETED + end + if M.server.progress_rate_limited(message.cur_percent, job.status.progress) then + return M.transition.result.COMPLETED + end + -- Informational progress reports to wfx are not retried. + sync_job_status { + self, + job, + channel = M.channel.progress, + message = ("Installing artifact %d/%d: '%s' with '%s' ..."):format( + message.cur_step, + message.nsteps, + message.cur_image, + message.hnd_name + ), + progress = message.cur_percent, + retry = { retries = 1 }, + } + return M.transition.result.COMPLETED + end + + return M.transition.result.COMPLETED + end +) + +M.job.workflow.dispatch:set( + M.state.dau.INSTALLING, + M.state.dau.INSTALLED, + --- @param self job.workflow.transition + --- @param job job + --- @return transition.result + function(self, job) + if not sync_job_status { self, job, message = "Finished." } then + return M.transition.result.FAIL_YIELD + end + return M.transition.result.COMPLETED + end +) + +M.job.workflow.dispatch:set( + M.state.dau.INSTALLING, + M.state.dau.TERMINATED, + --- @param self job.workflow.transition + --- @param job job + --- @param message? string + --- @param context? string[] + --- @return transition.result + function(self, job, message, context) + if not sync_job_status { self, job, message = message, context = context } then + return M.transition.result.FAIL_YIELD + end + return M.transition.result.COMPLETED + end +) + +M.job.workflow.dispatch:set( + M.state.dau.ACTIVATE, + M.state.dau.ACTIVATING, + --- @param self job.workflow.transition + --- @param job job + --- @return transition.result + function(self, job) + if not job.definition:is_firmware() then + if M.device.pstate ~= suricatta.pstate.OK then + suricatta.notify.warn("Persistent state is not OK (is %s).", suricatta.pstate[M.device.pstate]) + if not save_pstate(suricatta.pstate.OK) then + return M.transition.result.FAIL_YIELD + end + M.device.pstate = suricatta.pstate.OK + end + if not sync_job_status { self, job, message = "Notifying Progress Client(s) to activate artifact." } then + return M.transition.result.FAIL_YIELD + end + send_progress_activation_msg(job) + -- Tail-call transition ACTIVATING → ACTIVATED without detour via wfx query. + return job.workflow:call(M.state.dau.ACTIVATING, M.state.dau.ACTIVATED, job) + end + + suricatta.notify.debug("Job activation mode is 'firmware'.") + if suricatta.pstate.get() == suricatta.pstate.INSTALLED then + suricatta.notify.warn("Persistent state is already set to INSTALLED.") + else + if not save_pstate(suricatta.pstate.IN_PROGRESS) or not save_pstate(suricatta.pstate.INSTALLED) then + return M.transition.result.FAIL_YIELD + end + end + M.device.pstate = suricatta.pstate.INSTALLED + + if not sync_job_status { self, job, message = "Set persistent state marker to INSTALLED." } then + return M.transition.result.FAIL_YIELD + end + -- Tail-call transition ACTIVATING → ACTIVATING without detour via wfx query. + return job.workflow:call(M.state.dau.ACTIVATING, M.state.dau.ACTIVATING, job, M.device.pstate) + end +) + +M.job.workflow.dispatch:set( + M.state.dau.ACTIVATE, + M.state.dau.TERMINATED, + --- @param self job.workflow.transition + --- @param job job + --- @return transition.result + function(self, job) + if not sync_job_status { self, job } then + return M.transition.result.FAIL_YIELD + end + return M.transition.result.COMPLETED + end +) + +M.job.workflow.dispatch:set( + M.state.dau.ACTIVATING, + M.state.dau.ACTIVATING, + --- @param self job.workflow.transition + --- @param job job + --- @param pstate? number + --- @return transition.result + function(self, job, pstate) + if not job.definition:is_firmware() then + -- Called as "regular" transition in response to wfx query. + -- This happens if the tail-call ACTIVATING → ACTIVATED has + -- failed. As progress clients have been notified, mark this + -- update as ACTIVATED. + return job.workflow:call(M.state.dau.ACTIVATING, M.state.dau.ACTIVATED, job) + end + + -- Refresh `pstate` if dispatched in response to wfx query. + M.device.pstate = pstate or suricatta.pstate.get() + + if M.device.pstate == suricatta.pstate.OK then + local msg = "Persistent state is already set to OK. Finished update." + suricatta.notify.info(msg) + return job.workflow:call(M.state.dau.ACTIVATING, M.state.dau.ACTIVATED, job, msg) + end + + if M.device.pstate == suricatta.pstate.INSTALLED then + sync_job_status { self, job, message = "Update installed. Notifying Progress Client(s) to reboot." } + send_progress_activation_msg(job) + return M.transition.result.COMPLETED + end + + if M.device.pstate == suricatta.pstate.TESTING then + local msg = ("Update activation SUCCESSFUL, finalizing job %s."):format(job.id) + suricatta.notify.info(msg) + if not save_pstate(suricatta.pstate.OK) then + return M.transition.result.FAIL_YIELD + end + M.device.pstate = suricatta.pstate.OK + return job.workflow:call(M.state.dau.ACTIVATING, M.state.dau.ACTIVATED, job, msg) + end + + if M.device.pstate == suricatta.pstate.FAILED then + local msg = ("Update activation FAILED, failing job %s ..."):format(job.id) + suricatta.notify.error(msg) + if not save_pstate(suricatta.pstate.OK) then + return M.transition.result.FAIL_YIELD + end + M.device.pstate = suricatta.pstate.OK + return job.workflow:fail(job, msg) + end + + local msg = ("%s → %s called with unhandled persistent state %s."):format( + self.from.name, + self.to.name, + suricatta.pstate[M.device.pstate] or "N/A" + ) + suricatta.notify.error(msg) + sync_job_status { self, job, msg } + return M.transition.result.FAIL_YIELD + end +) + +M.job.workflow.dispatch:set( + M.state.dau.ACTIVATING, + M.state.dau.ACTIVATED, + --- @param self job.workflow.transition + --- @param job job + --- @param message? string + --- @return transition.result + function(self, job, message) + if not sync_job_status { self, job, message = message or "Finished Update." } then + return M.transition.result.FAIL_YIELD + end + return M.transition.result.COMPLETED + end +) + +M.job.workflow.dispatch:set( + M.state.dau.ACTIVATING, + M.state.dau.TERMINATED, + --- @param self job.workflow.transition + --- @param job job + --- @param message? string + --- @return transition.result + function(self, job, message) + if not sync_job_status { self, job, message = message } then + return M.transition.result.FAIL_YIELD + end + return M.transition.result.COMPLETED + end +) + +-- ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +-- ▏ +-- ▏ Suricatta wfx-Binding Types & Definitions +-- ▏ +-- ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +--- Device state and information. +-- +--- @class device +--- @field pstate number Persistent state ID number ∈ suricatta.pstate +--- @field id string Device ID +--- @field version string Device client version information, sent to server in HTTP Header +--- @field reset function(device) Reset `device` Table +M.device = { + version = ("SWUpdate %s.%s"):format(table.unpack(suricatta.getversion() or { 2023, 5 })), + reset = function(self) + self.id = nil + self.pstate = nil + end, +} + +--- Device ↔ Server communication channel state and information. +-- +--- @type {[string]: suricatta.open_channel} +M.channel = setmetatable({}, { + __call = function(_, opts) + local res, chan = suricatta.channel.open(opts) + if not res then + return nil + end + return chan + end, +}) + +--- Server information. +-- +--- @class server +--- @field polldelay number Delay between two wfx poll operations in seconds +--- @field reset function(server) Reset `server` Table +M.server = { + reset = function(self) + self.polldelay = nil + end, +} + +--- Rate-limit the number of progress messages to wfx. +-- +--- @param percent number Progress in percent +--- @param last number Last Progress in percent +--- @return boolean # `true` if progress message shouldn't be sent +M.server.progress_rate_limited = function(percent, last) + local scale = 10 / 100 + return math.floor(scale * last) == math.floor(scale * percent) +end + +-- ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +-- ▏ +-- ▏ Suricatta Interface Implementation +-- ▏ +-- ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + +--- Progress thread callback handler reporting progress to wfx. +-- +--- @param message suricatta.ipc.progress_msg The progress message +--- @return suricatta.status # Suricatta return code +function M.suricatta_funcs.progress_callback(message) + if M.job.workflow:report_progress(M.job, message) == M.transition.result.COMPLETED then + return suricatta.status.OK + end + return suricatta.status.EERR +end +suricatta.server.register(M.suricatta_funcs.progress_callback, suricatta.server.CALLBACK_PROGRESS) + +--- Query and handle pending actions on the wfx. +-- +-- Lua counterpart of `server_op_res_t server_has_pending_action(int *action_id)`. +-- +-- The suricatta.status return values `UPDATE_AVAILABLE` and `ID_REQUESTED` are handled +-- in `suricatta/suricatta.c`, others result in SWUpdate sleeping. +-- +--- @return suricatta.status # Suricatta return code +function M.suricatta_funcs.has_pending_action() + --- Perform a GET request on the wfx. + -- + --- @param url string URL to query + --- @return boolean # `true` if wfx responded, `false` on network error + --- @return table? # Lua Table-ified JSON response or `nil` on error + local function query_wfx(url) + suricatta.notify.debug("Suricatta querying %q", url) + local res, _, data = M.channel.main.get { + url = url, + } + if not res then + suricatta.notify.debug("Got HTTP error code %d from server.", data.http_response_code) + return false + end + if M.utils.table.is_empty((data or {}).json_reply or {}) then + suricatta.notify.warn("Got bad JSON response from server: %q", tostring(data)) + return true + end + return true, data.json_reply + end + + --- Query the wfx for a new job to process. + -- + --- @return boolean # `true` if wfx responded, `false` on network error + --- @return job? # New job to process or `nil` on error + local function get_new_job() + local response, data = query_wfx( + ("%s/jobs?clientId=%s&group=%s"):format( + M.channel.main.options.url, + M.device.id, + table.concat(M.dau.wfx_group.query, ",") + ) + ) + for _, j in ipairs((data or {}).content or {}) do + --- @cast j job + if M.job(j) then + return response, j + end + suricatta.notify.error("Job validation failed, skipping job.") + end + return response + end + + --- Get the current job, if any, to process further from the wfx. + -- + --- @return boolean # `true` if wfx responded, `false` on network error + --- @return job? # Current job as wfx sees it or `nil` on error + local function get_current_job() + if M.job:invalid() then + return true + end + return query_wfx(("%s/jobs/%s"):format(M.channel.main.options.url, M.job.id)) + end + + -- ────┤ Handle & Dispatch ├────────────────────────────────────── + local response, j = get_current_job() + if not response then + suricatta.notify.debug("Got no response from server: sleeping ...") + return suricatta.status.NO_UPDATE_AVAILABLE + end + if not j or M.job.workflow:terminated(j.status) then + M.job:invalidate() + response, j = get_new_job() + if not response or not j then + suricatta.notify.debug("Got no response or no job: sleeping ...") + return suricatta.status.NO_UPDATE_AVAILABLE + end + end + + M.job.status:set(j.status) + if not M.job.workflow:can_advance() then + suricatta.notify.debug("No (further) transitions to take: sleeping ...") + return suricatta.status.EAGAIN + end + + suricatta.notify.info("Processing job ID %s in state %s ...", M.job.id, M.job.status.state) + _ = M.job.workflow:advance(M.job) + suricatta.notify.info("Yielding to wfx.") + return suricatta.status.OK +end +suricatta.server.register(M.suricatta_funcs.has_pending_action, suricatta.server.HAS_PENDING_ACTION) + +-- Lua counterpart of `server_op_res_t server_install_update(void)`. +-- +-- This function is intended to be called by `suricatta/suricatta.c`'s event loop +-- via `has_pending_action()` returning `UPDATE_AVAILABLE`. +-- Here, this functionality is called directly from within `job.workflow:advance()` +-- so that a dummy implementation satisfies the requirement of this function +-- being registered. +suricatta.server.register(function() + return suricatta.status.OK +end, suricatta.server.INSTALL_UPDATE) + +--- Print the help text. +-- +-- Lua counterpart of `void server_print_help(void)`. +-- +--- @param defaults suricatta.channel.options|table Default options +--- @return suricatta.status # Suricatta return code +function M.suricatta_funcs.print_help(defaults) + defaults = defaults or {} + local stdout = io.output() + stdout:write("\t -u, --url * Base URL to the wfx server, e.g.,http://localhost:8080/api/wfx/v1\n") + stdout:write("\t -i, --id * The device ID\n") + stdout:write("\t -y, --proxy Use proxy. Either proxy or ${http,all}_proxy is tried\n") + stdout:write( + ("\t -p, --polldelay Delay between two poll operations (default: %s sec)\n"):format( + tostring(defaults.polldelay or "?") + ) + ) + stdout:write( + ("\t -r, --retry Retry count prior to failing (default: %s)\n"):format( + tostring(defaults.retries or "?") + ) + ) + stdout:write( + ("\t -w, --retrywait Delay between retries, e.g., download resume (default: %s sec)\n"):format( + tostring(defaults.retry_sleep or "?") + ) + ) + stdout:write("\t -c, --confirm Confirm update test result\n") + stdout:write("\t -x, --nocheckcert Ignore invalid server certificates\n") + stdout:write("\t -v Enable verbose log messages\n") + return suricatta.status.OK +end +suricatta.server.register(M.suricatta_funcs.print_help, suricatta.server.PRINT_HELP) + +--- Start the Suricatta server. +-- +-- Lua counterpart of `server_op_res_t server_start(char *fname, int argc, char *argv[])`. +-- +--- @param defaults {[string]: any} Lua suricatta module channel default options +--- @param argv string[] C's `argv` as Lua Table +--- @param fconfig {[string]: any} SWUpdate configuration file's `[suricatta]` section as Lua Table ∪ { polldelay = CHANNEL_DEFAULT_POLLING_INTERVAL} +--- @return suricatta.status # Suricatta return code +function M.suricatta_funcs.server_start(defaults, argv, fconfig) + -- Use defaults, + -- ... shadowed by configuration file values, + -- ... shadowed by command line arguments. + local configuration = defaults or {} + M.utils.table.merge(configuration, fconfig or {}) + local opts = { + { "url", M.utils.GETOPT_ARG.REQUIRED, "u" }, + { "id", M.utils.GETOPT_ARG.REQUIRED, "i" }, + { "proxy", M.utils.GETOPT_ARG.REQUIRED, "y" }, + { "polldelay", M.utils.GETOPT_ARG.REQUIRED, "p" }, + { "retry", M.utils.GETOPT_ARG.REQUIRED, "r" }, + { "retrywait", M.utils.GETOPT_ARG.REQUIRED, "w" }, + { "confirm", M.utils.GETOPT_ARG.REQUIRED, "c" }, + { "nocheckcert", M.utils.GETOPT_ARG.NO, "x" }, + { nil, M.utils.GETOPT_ARG.NO, "v" }, + } + for opt, arg in M.utils.getopt(argv or {}, opts) do + if opt == "u" then + configuration.url = tostring(arg) + elseif opt == "i" then + configuration.id = tostring(arg) + elseif opt == "y" then + configuration.proxy = tostring(arg) + elseif opt == "p" then + configuration.polldelay = tonumber(arg) or configuration.polldelay + elseif opt == "r" then + configuration.retries = tonumber(arg) or configuration.retries + elseif opt == "w" then + configuration.retry_sleep = tonumber(arg) or configuration.retry_sleep + elseif opt == "c" then + -- Legacy support for suricatta hawkBit's -c encoding + if arg:lower() == "ok" or arg == suricatta.pstate.TESTING then + configuration.pstate = suricatta.pstate.TESTING + elseif arg:lower() == "failed" or arg == suricatta.pstate.FAILED then + configuration.pstate = suricatta.pstate.FAILED + else + io.stderr:write(("ERROR: Invalid argument: -%s %s\n"):format(opt, arg)) + return suricatta.status.EINIT + end + elseif opt == "x" then + configuration.strictssl = false + elseif opt == "v" then + configuration.debug = true + elseif opt == ":" then + io.stderr:write("ERROR: Missing argument.\n") + -- Note: .polldelay is in fconfig, make defaults __index it as well. + M.print_help(setmetatable((defaults or {}), { __index = fconfig })) + return suricatta.status.EINIT + end + end + + for k, v in pairs(configuration) do + if suricatta.channel.options[k] ~= nil and type(suricatta.channel.options[k]) ~= type(v) then + suricatta.notify.error( + "Configuration type mismatch: '%s' must be %s, is %s.", + k, + type(suricatta.channel.options[k]), + type(v) + ) + return suricatta.status.EINIT + end + end + + if not configuration.url or type(configuration.url) ~= "string" then + suricatta.notify.error("Mandatory parameter missing/mis-typed: URL") + return suricatta.status.EINIT + end + if not configuration.id or type(configuration.id) ~= "string" then + suricatta.notify.error("Mandatory parameter missing/mis-typed: Device ID") + return suricatta.status.EINIT + end + M.device.id = configuration.id + + if not configuration.pstate then + M.device.pstate = suricatta.pstate.get() + if M.device.pstate == suricatta.pstate.ERROR then + suricatta.notify.error("Error reading persistent state information.") + return suricatta.status.EINIT + end + suricatta.notify.info( + "Read persistent state %s = %s.", + string.char(M.device.pstate), + suricatta.pstate[M.device.pstate] or "N/A" + ) + else + M.device.pstate = configuration.pstate + suricatta.notify.info( + "Got supplied persistent state %s = %s.", + string.char(M.device.pstate), + suricatta.pstate[M.device.pstate] or "N/A" + ) + end + + M.server.polldelay = configuration.polldelay + + M.channel.main = M.channel(M.utils.table.merge({ + -- An API Gateway may use this information to do steering. + headers_to_send = { + ["X-Client-Version"] = M.device.version, + ["X-Client-Id"] = M.device.id, + }, + }, configuration)) + if not M.channel.main then + suricatta.notify.error("Cannot initialize channel.") + return suricatta.status.EINIT + end + + local url = ("%s/swagger.json"):format(M.channel.main.options.url) + suricatta.notify.debug("Suricatta querying %q", url) + repeat + res, _, data = M.channel.main.get { url = url } + if not res then + suricatta.notify.warn( + "Got HTTP error code %d, sleeping %d seconds ...", + data.http_response_code, + M.channel.main.options.retry_sleep + ) + suricatta.sleep(M.channel.main.options.retry_sleep) + end + until res and M.job.status:update_json_schema(((data or {}).json_reply or {})) + + suricatta.notify.info("Running with device ID %q on %q", M.device.id, configuration.url) + return suricatta.status.OK +end +suricatta.server.register(M.suricatta_funcs.server_start, suricatta.server.SERVER_START) + +--- Stop the Suricatta server. +-- +-- Lua counterpart of `server_op_res_t server_stop(void)`. +-- +--- @return suricatta.status # Suricatta return code +function M.suricatta_funcs.server_stop() + for name, chan in pairs(M.channel) do + chan.close() + M.channel[name] = nil + end + M.device:reset() + M.server:reset() + return suricatta.status.OK +end +suricatta.server.register(M.suricatta_funcs.server_stop, suricatta.server.SERVER_STOP) + +--- Get wfx polling interval. +-- +-- Lua counterpart of `unsigned int server_get_polling_interval(void)`. +-- +--- @return number # Polling interval in seconds +function M.suricatta_funcs.get_polling_interval() + -- Not implemented. + -- Remotely setting the polling interval is the original concern of + -- a device management solution that should set the polling interval + -- via SWUpdate IPC (or command line or configuration file). + return M.server.polldelay +end +suricatta.server.register(M.suricatta_funcs.get_polling_interval, suricatta.server.GET_POLLING_INTERVAL) + +--- Report device configuration/data to server. +-- +-- Lua counterpart of `server_op_res_t server_send_target_data(void)`. +-- +--- @return suricatta.status # Suricatta return code +function M.suricatta_funcs.send_target_data() + -- Not implemented. + -- This is the original concern of a device management solution that + -- should report device status including data gathered from SWUpdate + -- via IPC or the progress interface. + return suricatta.status.OK +end +suricatta.server.register(M.suricatta_funcs.send_target_data, suricatta.server.SEND_TARGET_DATA) + +--- Handle IPC messages sent to Suricatta Lua module. +-- +-- Lua counterpart of `server_op_res_t server_ipc(ipc_message *msg)`. +-- +--- @param message suricatta.ipc.ipc_message The IPC message sent +--- @return string # IPC JSON reply string +--- @return suricatta.status # Suricatta return code +function M.suricatta_funcs.ipc(message) + message = message or {} + if not message.json then + return M.utils.string.escape([[{ "request": "IPC requests must be JSON formatted" }]], { ['"'] = '"' }), + suricatta.status.EBADMSG + end + message.msg = message.msg or "" + if message.cmd == message.commands.CONFIG then + suricatta.notify.debug("Got IPC configuration message: %s", message.msg) + if message.json.polling then + M.server.polldelay = tonumber(message.json.polling) or M.server.polldelay + return M.utils.string.escape([[{ "reply": "applied" }]], { ['"'] = '"' }), suricatta.status.OK + end + elseif message.cmd == message.commands.ACTIVATION then + suricatta.notify.debug("Got IPC activation message: %s", message.msg) + return M.utils.string.escape([[{ "request": "inapplicable" }]], { ['"'] = '"' }), suricatta.status.OK + end + suricatta.notify.warn("Got unknown IPC message: %s", message.msg) + return M.utils.string.escape([[{ "request": "unknown" }]], { ['"'] = '"' }), suricatta.status.EBADMSG +end +suricatta.server.register(M.suricatta_funcs.ipc, suricatta.server.IPC) + +return M From patchwork Fri Jun 2 13:20:21 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Storm, Christian" X-Patchwork-Id: 1789622 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=googlegroups.com (client-ip=2a00:1450:4864:20::237; helo=mail-lj1-x237.google.com; envelope-from=swupdate+bncbdd6bwv65qpbbb6z46rqmgqeztu6jsy@googlegroups.com; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=googlegroups.com header.i=@googlegroups.com header.a=rsa-sha256 header.s=20221208 header.b=e3m9/GiH; dkim-atps=neutral Received: from mail-lj1-x237.google.com (mail-lj1-x237.google.com [IPv6:2a00:1450:4864:20::237]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4QXkBg3KKPz20Py for ; Fri, 2 Jun 2023 23:20:11 +1000 (AEST) Received: by mail-lj1-x237.google.com with SMTP id 38308e7fff4ca-2af31539394sf18464211fa.3 for ; Fri, 02 Jun 2023 06:20:11 -0700 (PDT) ARC-Seal: i=3; a=rsa-sha256; t=1685712008; cv=pass; d=google.com; s=arc-20160816; b=GWtx7KQlzridPHBmqDUyFkCNFnrbB7M+ouwbPglrWX8R6D9gjXJIvTGan6ZHlNxNpX NZyxZMKdkaBCfbbUY2BrO9abXiBNPSJz1tE1kt+gumrFe/v+r4rLE9DcGoK67Lo5nhPy uv3Khw5oTDBTUDBanjkMy5WAvUTVWGh1hxS8+4JLxN8BNCtldub/ZwWNW/8hNKA7N4O8 7evQxHoxh2SrymwXkkoPg8wlj7sfJaM9mydOJYz5Ep17Sx8rAPtTRjEJroauw+wI+6Wr bvICJXMG6fESquG0r86kt6X4dTphPeNKhy6E95WDROxQuWf/17Pgqlia2VIrgNVQ+/YW 9eng== ARC-Message-Signature: i=3; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:reply-to:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:dkim-signature; bh=rTn8jzUY9gLQ+Sb+AG7vtt5EFxlXzofQvXL2B7oJ5CY=; b=Jt193juUsAhxNX3hfdMpv6lMwCj69wm424gOb9OqfU0woGxh8EWsxEa0p/p1E/Ofd5 l4zVynlxQWc4+bWGXRx55y89Y0HAggy94bjuMPziFP+SvwgTkCJeSGovr3+XjppR7C6Y +JfMoRP5juYZ44vB8gcWhzCab/n0xsFZ2IoALhjk1o+dJW3WS0RxFk6PUYkV9L5rmBKs FZUqrunHePEbqXsFkftX2uAVLYuplig1k2A1cnzmfPVSXWF7EqbpiP3PN8LmAlalD30/ t0zExbwtRLEqskjJTbLFeDheWJJD9jHaJNjp3+j5tx2o+knfjMu6cneLnZdAi/qHlMMP cAtQ== ARC-Authentication-Results: i=3; gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=Q4pxw0GT; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::61f as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20221208; t=1685712008; x=1688304008; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:reply-to :x-original-authentication-results:x-original-sender:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from:from:to :cc:subject:date:message-id:reply-to; bh=rTn8jzUY9gLQ+Sb+AG7vtt5EFxlXzofQvXL2B7oJ5CY=; b=e3m9/GiHB52eFfBFBhvVC9WgNbY32IjBWLWYzKw9N9FfZHrcYiegewC4GuoHEx6xhC 1HZtAi81IPdVe3KFHBVeFFYDNRgrKBRCmVuOYFgcm0NIxx4KhOiK9A4kjjL8oV6s3NdN PEt1nnObiy9DUs3yStIrJ+510rFXj4OOjLP7yfV8doVVBb0Ib1I0EBPYfkL5pD7+juzR PM7elbaJr1xbCg6XyZ74HHySUQgQ/1AezOkF/EDRfpNF2zEFcOTOMBCLe/2jJMmfuw9p eYAW8Qu5gN7FxsgsCehbTti/GK8h82eKU3KVyrkQobjtgsVJBH1+AG8G6Js+R6bGKPvP n16w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1685712008; x=1688304008; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :x-spam-checked-in-group:list-id:mailing-list:precedence:reply-to :x-original-authentication-results:x-original-sender:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :x-beenthere:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=rTn8jzUY9gLQ+Sb+AG7vtt5EFxlXzofQvXL2B7oJ5CY=; b=StLUoA+Hg5Tfv9D11jCDWaqfEc0/WlrRSVljOWyPhbZkq+6w2xstFMmC4Q5LyQXWMo Imbe4bz7c1ZJPWak0oc7FFL3LjaWmEUCppLqcWiBvsCQYkZtguK40JkhID1KeTaioXqG ODTkq4luaKSCt7q9SOAiErdx5C6FyVGaluRWTJdcAaeV4qfbESYJPp2OKOc/cBaQJD/C dAjwi5Royf/xDkAk2VIv9GCuftIVkmaAMRUMPlEO/HmLK+wHyGW75gSxsIBgXOSBA9PN PIocSxLTAujPqX0r+PTdoEEFnbYnchJSaEwctkalzPEvyFpqOMh/ZfTiGFWCYhjfQCmn WemA== X-Gm-Message-State: AC+VfDw01socBzPatgtE+OvgrMUgTUXzI3QR3Ai2BUGxJLA9I+Odd1Ph quJbYflhWl8ORppmiw8fYCU= X-Google-Smtp-Source: ACHHUZ6tfstUHWlhISPl5K9BXP1UZ6o6ag89Pd5lPqKUvqfJKSzrR/u4n0/1s1aySMqSqoJFyL55IQ== X-Received: by 2002:a2e:a1c8:0:b0:2b1:a4b0:bd91 with SMTP id c8-20020a2ea1c8000000b002b1a4b0bd91mr1630554ljm.41.1685712007725; Fri, 02 Jun 2023 06:20:07 -0700 (PDT) X-BeenThere: swupdate@googlegroups.com Received: by 2002:a2e:b634:0:b0:2ac:8284:e51 with SMTP id s20-20020a2eb634000000b002ac82840e51ls376403ljn.1.-pod-prod-07-eu; Fri, 02 Jun 2023 06:20:06 -0700 (PDT) X-Received: by 2002:ac2:46cd:0:b0:4ee:d4bd:3475 with SMTP id p13-20020ac246cd000000b004eed4bd3475mr1469005lfo.32.1685712006285; Fri, 02 Jun 2023 06:20:06 -0700 (PDT) ARC-Seal: i=2; a=rsa-sha256; t=1685712006; cv=pass; d=google.com; s=arc-20160816; b=BhSvVAyBK+T62ylLasEk++eH9WSzdhevqBjMsTGJl4xuob5wYZdeCYWDZRZ6hrSINw zavjdGE4yRtlgxO9/KlcmT9hTpnBhcleY0Gj82hEWLa2XT6bOdwVHRJX4pervUA4HzGm wG927Rq/B86EmH3PtdffI9WQdwagW5PzXFUleO5Rd6K1RBBI8CiklI6x4Bw5aTGdEJ/h LDxgnhNieav9q8Z+TtoN4EwvESyCUcQ7aXu1G/0WbMiuq7CVA1xBQXl71GUr/K4Y3S47 LxfPXsDZdOanitT5jKiWfuIqcSLQGYK3Yj+XETmOMxMthbWCy6SAILJbgpnu+jGj1HPD Mdjg== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=mime-version:content-transfer-encoding:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature; bh=pnENxrYDVXgzd4n6ctz/RUW+Fd/hb9USGKTAiLvtvsc=; b=aBkITq6Wv63J1W95q3UXUiwy2ywDNREGzqMrNo2nluPB7csuALe4pzl7IE+AdFMX7O DM1Lo35Kv2grnn7juDbutHGNgESqs6wDPGmpZpiVwKQ2Rs8I31Km7eQmxSqtIPwsW1tU z92RBlZsgZeWnSNWEh5kKh2vhe9QpVic+9yZpA0zzijz2PUxKJXHAlbE+4doiBawH6fo ehuXDNVd1tPmyUftQfvceO7/wEPy/NMWhGBYOCliVkkx/oRWqDuBGzceyx4mXPLy6EFg ElV9Iy1Ix7ggxjmwXh8X6gvAiQ3Fq3Dn1xGkNGejPsR3IAo5wT2BY5H4ZhWKV2yAL8dD yX2Q== ARC-Authentication-Results: i=2; gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=Q4pxw0GT; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::61f as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com Received: from EUR03-DBA-obe.outbound.protection.outlook.com (mail-dbaeur03on2061f.outbound.protection.outlook.com. [2a01:111:f400:fe1a::61f]) by gmr-mx.google.com with ESMTPS id g32-20020a0565123ba000b004f3792ca9b8si93363lfv.6.2023.06.02.06.20.05 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 02 Jun 2023 06:20:06 -0700 (PDT) Received-SPF: pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::61f as permitted sender) client-ip=2a01:111:f400:fe1a::61f; ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=JAS6Qh+2S8xIz8hrfJ9CyOURboZ0SdbLidhLdH2E4jy1XyLID+TwcFdh3zvATZo358biP9+S+NNuPsKwGR9EOHh0/hGXLUEpfymfiEwKXj4Y9uXptRYM8yGbophaUvYwFYx+FGFuMn2L5ofGcmJ/IjzcjH0vf/ZiXedPenvWpqrDzLCUvbbuGYJkWvG8l8oPj45ugPF5R+SO2zvJpHuUtImguOcOKgDE/NvYm/G+YcWrWMYpWHbD2mAhuuI2lWudk8R1f0D7sjb2rzBoUR4Aoa3bTqzIFqWyMLkTtrckwNGFPuFT4sqRHbyZZr4eqNbS2vdIGXUvxX5bweNxaPEM7g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=pnENxrYDVXgzd4n6ctz/RUW+Fd/hb9USGKTAiLvtvsc=; b=Eg8c0f1FVxQE5Wc/6OWYy+sH4m9OrnpiZ9129TK1qhve/ynzPmeEU4q8KDMF/czPS016dnCxBMx3FB9RA3mON/v/c46C2jX7QhJCzQ23chmOaSx6y9cnUqI/nL1RJLT9K9PshlbI5ETXWgTnWp6143DeLXFIc12p0W1XIaXfUxivnDJV3at/xCh7sDrt/kjYmdzyHi4D4Q6YJOpgG1DOfbjgUG9ytZnaBZh6R+qekvMiKFpkbhPVJIyEku/Q1JH75Q7kQ/Jewj/EWdfzqZfoGBxXKfDON8Y3jCALYcp0v0biIs3siJofsHMPcWBe8ezAXadwCgd/gdvvOfWhCHARiQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=siemens.com; dmarc=pass action=none header.from=siemens.com; dkim=pass header.d=siemens.com; arc=none Received: from DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:10:333::17) by GVXPR10MB5887.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:150:6::15) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6455.23; Fri, 2 Jun 2023 13:20:04 +0000 Received: from DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM ([fe80::6bed:3b93:4756:c9f3]) by DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM ([fe80::6bed:3b93:4756:c9f3%6]) with mapi id 15.20.6433.025; Fri, 2 Jun 2023 13:20:04 +0000 X-Patchwork-Original-From: "'Christian Storm' via swupdate" From: "Storm, Christian" To: swupdate@googlegroups.com Cc: Christian Storm Subject: [swupdate] [PATCH 4/5] suricatta/wfx: Documentation Date: Fri, 2 Jun 2023 15:20:21 +0200 Message-Id: <20230602132022.70931-4-christian.storm@siemens.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20230602132022.70931-1-christian.storm@siemens.com> References: <20230602132022.70931-1-christian.storm@siemens.com> X-ClientProxiedBy: FR0P281CA0266.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:b5::11) To DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:10:333::17) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DB9PR10MB5404:EE_|GVXPR10MB5887:EE_ X-MS-Office365-Filtering-Correlation-Id: a3423357-d3c9-47f5-cb11-08db636c1396 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: 5ySzkRup2SFMpreqGw3FniPSBHEFVk//ZlWhpvQ2SzjUcO1owm1Rvm8bF+Jf6f3mvw3ZvjeQk/lyynrtb0KHgcqjEjCoKbIXu+02G8VAfDzj0f4oDtUO72gyrd8OXhYIILQ+Ns1O17K/PyWOqBWLLOIVSiHW3y0/JOasyBHP4nliEHklqZtn5n0IWNesmAFG8OeHlkyxkapCYeXCgoOx0nmGwsDoEcvr/mbVWDbjVV+MluQsDKsYLrMn5QRE54WEJwRFymXjiTF7ym2c0MwwDYYM37LLY3sn6FlnUhz8SpzHiCiqAfLmCbp5ZFy5rfoa1z0dFK8z5YFWr/nGyombz8nAmPsOsCFoboTbQpjX2m1EV9sCD0Q6dsyJTuiGCXgh4KFUJNM4/qduq1Tp3OqjkYxDmTBhSHFX8n25g4WRZxbqFgKON/shuAL8lvNn7I4ZNBokWeSSrbKK6i9sqkSfD/G6PEdWvtvZsUmjkxLLeZMK4cg0/nwlzlPsNuV0p4Wh/r+6IPBi/bdm/YbH3arXkL8uWMrdJYK1VP7u/NplN9JsDwjTZuewArV+BulndPUWWryyrGd6xH4vm4ehhLYYjg== X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(13230028)(4636009)(39860400002)(376002)(136003)(346002)(366004)(396003)(451199021)(966005)(6486002)(478600001)(6666004)(83380400001)(186003)(36756003)(6512007)(2616005)(38100700002)(6506007)(86362001)(82960400001)(107886003)(1076003)(316002)(4326008)(41300700001)(66476007)(6916009)(66556008)(66946007)(44832011)(5660300002)(8676002)(8936002)(2906002)(66899021);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: oEuJpwJ2cUZbt5r25JXhy8LjIVIwipMtUdIGM6SQ8pe8VMC8b0c3Q0zXIuCAviL/ErPmwOCTS23jY1D3b3apsOPHUcq+jPICfO3SoH/0tpT2JPGhAkFDZ6Uz4WwqZ0aJTLDKJU87FoQquBiQtuAjhHg12iMJGpP15zHrsR6xrP9Aq7jbUc3ftyjEM0ggZXVnYao0gFPUcY9JUjbPIW6JfIr1YkLCavooIWS36Z1j5NdQodc0ZYXwCOxB6iF9zBVidgJKJaDJxN82V+ZVI1US8cpjhhL3soyi3DC4pCYcmrkODDVQHAHQUHnOd/parrneCr7CMOHh3hwv8UUipCB9XviG+7jveFbxjYJuPptQZDH4GH2MoodkzQW2K2oIXRWpWsvLSvKFN/iNGY0j8658p9Q5iI0HCDxC5d8N6PuqHkqUq9j5wxWKrWqPQBHUqK61vkxY9aZUfdBzE200PihdKw+tmv7IKaWn7ZtKvpyAi/MUz7cACCmLvaDUkm4D+3b4svOF2ooHXKn1Gwqdj4iUUuYJLNnLitwCN2RMcwJ35r8qY9edW363z3cZxPIDKQgRGUqJJNhKqZxQ0WNsNnRoapg33yxUGtP6jHjc2RaVlO9e1J3Q8fVI4SSn56UFrd+8APvKZpdB5xeFJbbj8CF8XQZUvYZEhmbH0IkvPU1qVkLjMpzU+OB8SN0fS9gWBay0EnOqYu804uzVPdmG3ifCbiDCO0/YXNhQLj+d9m3KjdFVRp9+uLCTTQBukcREVRj5XiUr7sPrTW4b9AY38UCWqfB+wM7eb/xnZyc3pZga2kgAFbtngDP1n9GYDM/ewxQoXqyCEdCfLSBof2mC4xRMfgySeneGPWmPN/j/qWhvXwWXydlo9R75RkWc7cD9To8h2hhBxp5dXBW6JKnMox/h+3Rhk+Ec0ZKelvWcaVYH/6gzfTrymU5nZ2alDHwZCaKQupQXSe5snySJHVGPUnv3eUQBhHAJKu3TGGQkK8q9dygagVQUGGRxsHzY4ymYRu26nay7RnrR0D+XCUD/Eb1F5noNk0zTuSQewFzyitF3MJLe3A8HY1uZyCSgRGFOVDitTunb8Wrse1GPVXyCY4N4NE4CtlFPow+exYe807EQZcazfA2zeejTOI5pbdBC8o68oqKbS0C7ruKVdj1oghRivgz/g8NKnRW0y9jU+uCNGZybFDRAsjiedcjrglVX/XfhpCQYpDe7hGijajK/w2nF5n4sojz9FXy7f94w1LXO4S9XYZhamyqkoF9QaX0fupvPTyXdPLwKSmeu0tVfUGorDz9PLCvgOGeOrD1qg+bysBai80ET8e0DWgdGgxPgT/i7fjAt0DN/lwG6jJZduOed3ixBi3KhaCO4PGB2cSSRJKmh+0NxV8j+GCGbKkMFC9h8+pdKdy0JENQ9qpyKUe+81m1icnXYPigrUzraiaUg/AyCtvdZhLKsTmCkYfzqpR+Q1qhAJs/2OIfHZXsAAUMUtdjKuRE6ybVlzG8eTEO5EXDriqGIB3BlD7o9mMniToI30Bzm/nJDHWW6b1Xld/jKzJctRUcYMU/3y/TGUGzzkdHQVBlK8pFsNq3DlEbUN6z2Emd103mgaL+pWF1okKkRX2PgrEvcl0C8RfXGlTm+q1MgRM8VLpccvwA8vT4MPA7W9Yd4KNovlPiKOBZfFpqCkg== X-OriginatorOrg: siemens.com X-MS-Exchange-CrossTenant-Network-Message-Id: a3423357-d3c9-47f5-cb11-08db636c1396 X-MS-Exchange-CrossTenant-AuthSource: DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 02 Jun 2023 13:20:04.0990 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 38ae3bcd-9579-4fd4-adda-b42e1495d55a X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: u65L+fFTEr9XNp3zswSquD3V1QE0/30X16UVQ8tk3Jx86ImFWF3MvC8g7cBPgEYiI1sgudxIr9KK8meQvAtLaNejC5TUPhI2OaU0MEG4SAI= X-MS-Exchange-Transport-CrossTenantHeadersStamped: GVXPR10MB5887 X-Original-Sender: christian.storm@siemens.com X-Original-Authentication-Results: gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=Q4pxw0GT; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::61f as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com X-Original-From: Christian Storm Reply-To: Christian Storm Precedence: list Mailing-list: list swupdate@googlegroups.com; contact swupdate+owners@googlegroups.com List-ID: X-Spam-Checked-In-Group: swupdate@googlegroups.com X-Google-Group-Id: 605343134186 List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , Document support for wfx server (https://www.github.com/siemens/wfx) Signed-off-by: Christian Storm --- doc/source/suricatta.rst | 178 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/doc/source/suricatta.rst b/doc/source/suricatta.rst index 9782e591..0264f52d 100644 --- a/doc/source/suricatta.rst +++ b/doc/source/suricatta.rst @@ -167,6 +167,184 @@ if ``SURICATTA_HAWKBIT`` was selected while configuring SWUpdate. endif +Support for wfx +--------------- + +The `wfx`_ server is supported by the Lua Suricatta module +``suricatta/server_wfx.lua`` (cf. Section `Support for Suricatta Modules in Lua`_). +Specifically, it implements a binding to the `Device Artifact Update`_ (DAU) workflow +family. + +If enabled via ``CONFIG_SURICATTA_WFX``, the wfx Lua Suricatta module is embedded +into the SWUpdate binary so that no extra deployment steps are required. Note that +this is purely a convenience shortcut for the installation of a Lua Suricatta module +as described in `Support for Suricatta Modules in Lua`_. + +.. _wfx: https://github.com/siemens/wfx +.. _Device Artifact Update: https://github.com/siemens/wfx/workflow/dau/ + + +Job Definition +.............. + +As being a general purpose workflow executor, wfx doesn't impose a particular job +definition nor schema, except that it's in JSON format. Instead, the job definition +is a contract between the operator creating jobs, each possibly following a different +workflow, and the client(s) executing those jobs in lock-step with the wfx. + +The wfx Lua Suricatta module understands job definitions as in the following +example (see ``job.definition.json_schema`` in ``suricatta/server_wfx.lua``): + +.. code:: json + + { + "version": "1.0", + "type": ["firmware", "dummy"], + "artifacts": [ + { + "name": "Example Device Firmware Artifact", + "version": "1.1", + "uri": "http://localhost:8080/download/example_artifact.swu", + } + ] + } + +The ``type`` list field allows to label update jobs. Labels are sent ``:``-concatenated +to the progress interface on `Update Activation`_. The only predefined label ``firmware`` +instructs the wfx Lua Suricatta module to record an installed update to the bootloader +environment (see :doc:`bootloader_interface`). +Within the artifacts list, only the ``uri`` field is strictly required for each artifact +while the fields ``name`` and ``version`` are used for informational purposes, if provided. +Further fields, including top-level fields, are ignored on purpose and may be freely used, +e.g., to enrich the job definition with metadata for update management. + +Since wfx is not concerned with the job definition except for conveying it to the +client (i.e. SWUpdate), it can be adapted to specific needs by feeding a different +job definition into the wfx on job creation and adapting the verification and job +handling methods in the wfx Lua Suricatta module's ``job.definition = {...}`` Table. + + +Workflows +......... + +The two Device Artifact Update (DAU) workflows `wfx.workflow.dau.direct`_ and +`wfx.workflow.dau.phased`_ are supported by the wfx Lua Suricatta module. +Hint: Use wfx's ``wfx-viewer`` command line tool to visualize the YAML +workflows in SVG or PlantUML format. + + +For each transition in a workflow for which the ``eligible`` field contains +``CLIENT``, e.g., + +.. code:: yaml + + transitions: + - from: + to: + eligible: CLIENT + +there has to be a matching transition execution function defined in the wfx Lua +Suricatta module. It executes the client actions to go from state ```` +to state ```` and finally sends the new job status to the wfx, updating it: + +.. code:: lua + + job.workflow.dispatch:set( + "", + "", + --- @param self job.workflow.transition + --- @param job job + --- @return transition.result + function(self, job) + if not job.status + :set({ + state = self.to.name, -- resolves to `` + message = ("[%s] reached"):format(self.to.name), + progress = 100, + }) + :send() then + -- Do not try to execute further transitions, yield to wfx. + return transition.result.FAIL_YIELD + end + return transition.result.COMPLETED + end + ) + +See ``suricatta/server_wfx.lua`` for examples of such transition execution functions. + + +New or adapted workflows are supported by appropriately defining/modifying the +transition execution functions in ``suricatta/server_wfx.lua`` -- or taking it as +inspiration and creating a new wfx Lua Suricatta module as described in `Support +for Suricatta Modules in Lua`_. + +.. _wfx.workflow.dau.phased: https://github.com/siemens/wfx/workflow/dau/wfx.workflow.dau.phased.yml +.. _wfx.workflow.dau.direct: https://github.com/siemens/wfx/workflow/dau/wfx.workflow.dau.direct.yml + + +Update Activation +................. + +The Device Artifact Update (DAU) workflows offer a dedicated activation step in +the update steps sequence to decouple artifact installation and activation times +so to not, e.g., upon a power-cut, prematurely test-boot into the new firmware +after installation until the activation is actually due. + +When the activation step is executed, the wfx Lua Suricatta module sends a progress +message (see :doc:`progress`) upon which a progress client executes or schedules +activation measures. For example, the following JSON is sent as the progress +message's ``.info`` field on activation of the `Job Definition`_ example given above: + +.. code:: json + + { + "state": "ACTIVATING", + "progress": 100, + "message": "firmware:dummy" + } + +The progress message's ``.status`` is ``PROGRESS``, see ``tools/swupdate-progress.c`` +for details on how a progress client can be implemented. + +**Note:** The activation message may be sent multiple times if the update activation +is pending, namely on each wfx poll operation and if a new update job is enqueued +while the current one is not activated. + +Because of the (predefined) ``firmware`` label present, the progress client should +initiate or schedule a reboot of the device in order to test-boot the new firmware. +Also because of the ``firmware`` label present, the wfx Lua Suricatta module records +the installed update to the bootloader environment. If this label was missing, no such +recording would've been made which is suitable for, e.g., configuration or +application updates. + +In order for the this mechanism to work, SWUpdate must not record the update to the +bootloader environment after it has installed it or, in case of configuration or +application updates, must not touch the bootloader environment at all (see the +Sections `Update Transaction and Status Marker` and `bootloader` in +:doc:`sw-description`). + +Hence, for firmware updates requiring a test-boot into the new firmware, the +following properties should be set in the ``.swu`` file's ``sw-description``: + +.. code:: + + software = + { + bootloader_transaction_marker = true; + bootloader_state_marker = false; + ... + +For configuration or application updates, the following properties apply: + +.. code:: + + software = + { + bootloader_transaction_marker = false; + bootloader_state_marker = false; + ... + + Support for general purpose HTTP server --------------------------------------- From patchwork Fri Jun 2 13:20:22 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Storm, Christian" X-Patchwork-Id: 1789624 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=googlegroups.com (client-ip=2a00:1450:4864:20::13f; helo=mail-lf1-x13f.google.com; envelope-from=swupdate+bncbdd6bwv65qpbbdgz46rqmgqewf5uaiy@googlegroups.com; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=googlegroups.com header.i=@googlegroups.com header.a=rsa-sha256 header.s=20221208 header.b=KcvbEkmL; dkim-atps=neutral Received: from mail-lf1-x13f.google.com (mail-lf1-x13f.google.com [IPv6:2a00:1450:4864:20::13f]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4QXkBm454Bz20Py for ; Fri, 2 Jun 2023 23:20:16 +1000 (AEST) Received: by mail-lf1-x13f.google.com with SMTP id 2adb3069b0e04-4f256ddef3asf1715658e87.2 for ; Fri, 02 Jun 2023 06:20:16 -0700 (PDT) ARC-Seal: i=3; a=rsa-sha256; t=1685712013; cv=pass; d=google.com; s=arc-20160816; b=f8O0pgqH6OPabBeJizGRjlAFKxj5vC9qlzuIBCY3ql3w4TH+87RTQQjvmWR3pQMuGZ Hnr2r+MbcNz093yYxCG4o7wAwgVxtrVrWrdMAdZoSUmFLABBgch5vLBrSh5Qti3W3hLd inRVT/CEQLuNce9/LFAQUSi4qtTycCfPHFL4ivDGcESPRYUonnXhnv5cZrta04bJ5gPX n/ZapkCb16lJcbYgX2uDvmINQjOC9AAKX3rjB5CtFHh5OTNA0wbPX/YmsUhBWtXNn90k /Ik8Gik7tqyVtT4AvroiqrajuwdxjsZIBHDL/M03XNDshD72xYqr+NaJaDnHRWpf/c7P BBLw== ARC-Message-Signature: i=3; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:reply-to:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:dkim-signature; bh=pWQmjcLh0gDGidiJ2GWyzICyEuGnwJm6SP9OxcVHCQ4=; b=r+9rnPg0e7vE3QWetVUpvxeLqGrJBAkpTjLA/ODngc1AKNEYuC6rA6JgY6TkPyBVem oEaYfSCG7L28kwwKb7/vqqOP4uzSFJ//JM89lwg7ufuIQWEbHAI0iee21kwkEugLNhXZ Js/4Qda5r8aS4gFQYufse5uha6EevpyKRGO6A1fCdSPMKofcia0AgOxZPPHLCPWWFab/ A3rmco5juF6Q4dnQoeEjmX3NC81KfW5/dRDLa6CHhnUjdOh+2r4Np4vFSzm8A+xYWSWC F2wAsHnfrJu02VDlaNRSYGQt9U3NytvyaYEJXpKElrSL+eUZCFiA2+NJw2xnzAFSgmj7 75yw== ARC-Authentication-Results: i=3; gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=SYuDjkSH; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::626 as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20221208; t=1685712013; x=1688304013; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:reply-to :x-original-authentication-results:x-original-sender:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from:from:to :cc:subject:date:message-id:reply-to; bh=pWQmjcLh0gDGidiJ2GWyzICyEuGnwJm6SP9OxcVHCQ4=; b=KcvbEkmLSA2tEqJszSuYfDOHBjebWp63/7DdZgj3i6M4QdthcciINaqLhqh9dHhy2O OQ/PWfKFN7raMephkUK2KV1Dxu3kx/0osfAIj0YauCq1w6Rlfi9jIfyjLaqHPXGBiI68 Q7ax2RXpdqnTtckl2W6mCFbWK4G4fBtww8Dh42ba82xScUlWWw/fgD0xFqI+FlIwmaUT QK5vgl/RqzkgrweSOcayv2HL7k5YcM7nsvDi8WTMkgWBWt6ydj7nW5T8jiTRSx0F8Fp7 D8qBVmH6OvAPrm6fSPc5wbvcpHjAK0y3fhzW5JjRwbub4cHP1NK/np3ey0tWMaOByLdc kJ6g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1685712013; x=1688304013; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :x-spam-checked-in-group:list-id:mailing-list:precedence:reply-to :x-original-authentication-results:x-original-sender:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :x-beenthere:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=pWQmjcLh0gDGidiJ2GWyzICyEuGnwJm6SP9OxcVHCQ4=; b=COro5esHk9j6buH2/aJIrWM1FWNNVueBOEgO+8bcMA95QTP08E5euyBxaf8ayfzxT2 YWqXOuxPtD6O43NJGJXBR4zanIIe1XRcgKyIeirvVuSSvUuh9gxHh2jnxcQ/JD2e8zbi JXQOgsrGdlE8oUB6sVXLwsh7fY7C9C9Wh4DhZ3xBMakkmjyVdehvMat20x7luZ/BNi6V zOFU9i5KKqG3BkyXuI+ALFInNb6GNOEyT/n5yuxm/MG/vGciWCJtto7BqO3u1uq5aB51 cDxsiIrZ79fsEbsd4DzAIRPLQGD3izGroerQUSqKmK8rLWeY+BjVNkPXENvDOFtbetZ9 ActA== X-Gm-Message-State: AC+VfDz4YgbVCicyLMFuyfVZVW/9lXJxIR8I4YbEYreMj+y5RaZ5NmBu KDGTGI2G4E4C6EfacfIMbM0= X-Google-Smtp-Source: ACHHUZ6NEIrruy6GziyCfc+uhI08QLw0omPrIKW/s3MQCBB/yKdnhQp7vIF9GZMU5iV8wmo+NfbJyQ== X-Received: by 2002:ac2:485b:0:b0:4f2:7cb1:65fc with SMTP id 27-20020ac2485b000000b004f27cb165fcmr1931090lfy.64.1685712013258; Fri, 02 Jun 2023 06:20:13 -0700 (PDT) X-BeenThere: swupdate@googlegroups.com Received: by 2002:a19:8c11:0:b0:4f1:4c37:e1cc with SMTP id o17-20020a198c11000000b004f14c37e1ccls323681lfd.0.-pod-prod-07-eu; Fri, 02 Jun 2023 06:20:11 -0700 (PDT) X-Received: by 2002:ac2:447b:0:b0:4f0:124:b56b with SMTP id y27-20020ac2447b000000b004f00124b56bmr1863432lfl.7.1685712011603; Fri, 02 Jun 2023 06:20:11 -0700 (PDT) ARC-Seal: i=2; a=rsa-sha256; t=1685712011; cv=pass; d=google.com; s=arc-20160816; b=xH6Lcm8VQmBADvvl59/PTtDpDW6xdUn7tS5dMsjsoCPFMorkdJWRpob9Dp7+udGgq3 P+sIRnih+cnI114EbtFjsolusCIFlbqlzN1PnAMduaLSV8PVMrJjNoNmWAU/Yp5e48/x E7dxhENMgecsUkUqqPsErWkvH8oiGCpxFEE9imxlCeAGzOpVFWuKAqanyT0gUxfR3Yhv xT/uZgwaJjjGhHmb32UcOvb+e17DsrghbcsmAuovZGrdu2kaGJwu3lSBr0zCAIobI/wT 7g+6MCRy3dere/He/6X0D+zzGl0RNATANXL8JSH8XAZw7nonAR98XyYDF8MoSLjZBauB hV0Q== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=mime-version:content-transfer-encoding:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature; bh=i88hEps+cUEIxsCvHXjohbKNbr9k7e/6C7DWQaXhzuY=; b=vjInjGtpKW437BBneYNCHXm/BbVbhR9HHjjgiPu9ybbc0X7kaMC22Lg6BjocsF9rGe ValnG1DEIgyiSMOa5AXnO4mTPDBbULKH60NSIolFaglLN//g9UAoAnmifq5vAUE8XrkL EBV+nlZeho9rU0n1EPWGoLZrzNAHGOYCtj6Hc1LnQt2B4PRdEo5VoKTj+3u/VEl1moSg PqdrKo6j+uBvY0dxeiQGpX/96gWZHkl/7fGoiCwijSuDGTGeWuQjt/jHSR/bytRoGFIV x4tRTAQqCtqPP5+t9dDv/AiYQn7eTHSc1XGcXSvG24/WhyG1uBifo+RG2dBP3WU6x3WI Dixg== ARC-Authentication-Results: i=2; gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=SYuDjkSH; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::626 as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com Received: from EUR03-DBA-obe.outbound.protection.outlook.com (mail-dbaeur03on20626.outbound.protection.outlook.com. [2a01:111:f400:fe1a::626]) by gmr-mx.google.com with ESMTPS id e1-20020a195001000000b004f3a8f89761si160890lfb.11.2023.06.02.06.20.11 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 02 Jun 2023 06:20:11 -0700 (PDT) Received-SPF: pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::626 as permitted sender) client-ip=2a01:111:f400:fe1a::626; ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=krA+AoXkFC5pOnjcnmqVkeCIX2ezcAbYvQefOP1RwmLPvSOdkhfW0DRgqadgAwTHWzcfZ2ePNBOS6p/qTl5WUBMBdSXfPCMIfKTyXk+qj3UB4UIJMD9Ee/f0opVC6mhyyzU4Vu38SwJ0jCd2Um2c5N9hjHCF4dsAZ5XNm2KFWxF6/XKl717KFC0XDU4D+/2iiQgSHcmZ+IXOLEtSWqvHOo52IY59LPozdybrKonPXIWaKbTl7qqtYPV5EbREVam7xT9ULA5PMDvA3Q3HUQHS0Iy1YqMKo6UNVlbxGrn5emUz0uKYZ7HVxKC8tNnO/Bl5yRX+QTexRqsIRQ2pR4ndzA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=i88hEps+cUEIxsCvHXjohbKNbr9k7e/6C7DWQaXhzuY=; b=gADhUa1bbzDHjNt/+GJgCjtW/E3PFdENNOMEMyiOrSyCW5wPJu0u6tv36zHK1vHdRPlJoFES5HewCBV+bcwTdyaKKtcmz0gtMvHHtW2pffavTU4bSGIUK5Dfj78XMiDnvO0YxnyDkRok6qCYIft60tAcBsY1PWzTk2TqMKfG7MOI1KNIrVoMr3Wz9LtPv4qg1lUIc1jSw1lo5CwsZwwaqevL9Dsvvvmq2v1ToB2stM1Dp6qdunGsu+ZT/ShGo4koKIVBfMbEtpbyq9bM7BWw0sHeB8+5PO0qNGLVH9WfvG7YgEGSfRr7CpFpswyCnsj9GFPJGGyzJeB/KtJTgUhvFQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=siemens.com; dmarc=pass action=none header.from=siemens.com; dkim=pass header.d=siemens.com; arc=none Received: from DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:10:333::17) by GVXPR10MB5887.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:150:6::15) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6455.23; Fri, 2 Jun 2023 13:20:09 +0000 Received: from DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM ([fe80::6bed:3b93:4756:c9f3]) by DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM ([fe80::6bed:3b93:4756:c9f3%6]) with mapi id 15.20.6433.025; Fri, 2 Jun 2023 13:20:09 +0000 X-Patchwork-Original-From: "'Christian Storm' via swupdate" From: "Storm, Christian" To: swupdate@googlegroups.com Cc: Christian Storm Subject: [swupdate] [PATCH 5/5] tools/swupdate-progress: Adapt for wfx Date: Fri, 2 Jun 2023 15:20:22 +0200 Message-Id: <20230602132022.70931-5-christian.storm@siemens.com> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20230602132022.70931-1-christian.storm@siemens.com> References: <20230602132022.70931-1-christian.storm@siemens.com> X-ClientProxiedBy: FR3P281CA0145.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:95::8) To DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM (2603:10a6:10:333::17) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: DB9PR10MB5404:EE_|GVXPR10MB5887:EE_ X-MS-Office365-Filtering-Correlation-Id: e68050c0-5346-46f1-ef8b-08db636c16f0 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: OhMzN8zTx+fwEeK70ucsO060qeZgxewM0ot7+S+meV/dxGWSCDCIiWerdxBjdr6uip0IAAnjxtFEgz+y9o0wCVWw7OARzfRwmctK+yETcZpFZgE44MekhXkzAzhVWm1AfJXKk94XekO8WjeiHXaFMMpGUdX1U9B5eEz8kme0zrs3jriMZcJXnXlQ2Actes/yk54P6Q+zdDybf7x9Gtvg4eGvICnV9m2feOsrBP4PKIksj5OG8t8NgTCAkGLv64XNeBbU5gDdI+9S8Zc6PYwCRkEGwRZ1ThYkVHDgIan5ZS5H1vy0cJprLgfbNuLgCxEggPfLkRc1Q1yhuufUNjMyAChy2IR+n/TbIEJ8HN0WA383iYRLEp8xv/De8rURlvt/KuchnOGbQ9B64WYWN34QpyLmgCbK8l9EspGxN80uAwKicngmeRWVrEoW3GhoX9HOaLjIBU+xmoQSbuBjvIWuGna1F8G9l8WwbgC2WBYuZyrEtF/AXUJtxugk6bfjNoB9lLZG97z50RBlhCFHGB19yYqOL0JXMVykGMg6HfaSNwTR/LeimkeSW1n+9W/KTJ4h X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(13230028)(4636009)(39860400002)(376002)(136003)(346002)(366004)(396003)(451199021)(6486002)(478600001)(6666004)(83380400001)(186003)(36756003)(6512007)(2616005)(38100700002)(6506007)(86362001)(82960400001)(107886003)(1076003)(316002)(4326008)(41300700001)(66476007)(6916009)(66556008)(66946007)(44832011)(5660300002)(8676002)(8936002)(2906002);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: 7zvR63Uf9VwsUKivge7dScDFspq16NKveSbNiYigk4laT3CD3SJ1Hv2U6XFcX19Iz55Si9CGyNwBJie3H72MktXTuXYD4BwkA1cr16Ktr5zTTi+b0ce1nZc+SyJWD6DouqbrTpaWJB5B8VU3c/hhNNJAZG4/PbCf9XNU3BLjpCheRGgLFZN7MHHHlpTlhL1go/WQhOS/CDIvhfLJCb/7tBAfv7oN2RvYt/ihpOMh6Z1w+IDrVuDFXlQzkF5dKCVApJ9frmPT7ALOoH3jxRFnJ0WLStyZyeOv3VKqWuUBJ/7dqGF1Y+YnAuA+XLWZEB5IMooJCU6hnhJCheARt6BfXW5HRbCLCBA5208zyl/I9CuPVfOfc7jbGmt9Ylh4nRpG7Wbs2MoNRgou3AULyVWpGrI/FBZ+IFqJFgsg82iP50GzKKmFfbsE7YbItKgfZfymmHOcFyBHwP2JSHnm5wiVgUCuZgjv4q0RcrXDuPJwCNhLKaqR3b7fObNNFDFXB24odf0uSFUY+55X8+FcbKcXJEIZ/qXCfPzVIxPUTbl3klzg42BctYCvczIYPxh9pzzBggk3YfbRCQhLvwHNyLoLhXbiGApyBxHxptdnqj3x9R6Rv8TBNnP26z1bmEqhrEx+uTWdZumCpbuksIY78cSjTc7bdBpJdNJoW6k9dXXyODyh8c0Crnfz5OstaNktGMMWc5UumkfmamRLSKsy16+eVW4HFsoRFj3KtIBzDXRRi3VNCkosDFAczTsUx+bJYDmV2FzU21oMCUFDjfOEQcgI22WYwfsw5m05jlEqpnJkgf7CU7Eox347N0SYCpWbe+qcgvNqj/kVhh9A4T4XQRk6xU9Rlex0cnO0W7Ji6zQM/csoRxj4OCLFITZnUpfgIIxkn9uOTQL9rSVfiB5vQdi6+kdp1UmEMKSAPxFHDSjhc2lM9aOHONdhVYWRvOU59a3AiPeE6r6pl3IAiUd+L98IrjnnSlaZDnUGlTFNrryctPLpEncxHDIoC6dyMH0iabW3UNKXn1nRmji/nJLrk7/emR36HBVThZPuPTlFp9hHLHMT9AdF5q14vQ/Ue20kgUEbXae5gTiE9b70Qr4CYdSaUyuYmxtLMvmS+4BmNx8uHuC/4XyRiDIVClH3jm889o37JhE9ZPF++8DTAaMg0ouqW1uKrTLb58mSdee9nz/n0GvxOHgJoGQ2EChFm1mKyE57cUohauDoxY8sZ127EXcad2N5o1GXYqW3zikgiTYlieo+qWqlpvpDByNDAW8jxxobP96nDcIVNt7gH+HpNU+/JgTDyl4UbklJp5w1FtcL7GzNxN38De1BEJ+4MTYImfURlfGvC2Vq5OgXs2ZZ/TQtjOZVERjAhsJo+dNjROY60dlaTFX0vaQKpDsp4lSs1Smst8Qn4z/GDT+auXkTE26Gh/tQfAB4JuLLeIeeP+1R/ufsx2o25gNm7a69qQfbtdfubATf5jImIr3ghs8hseWgVUzvGAGql2OZYTplwfdJdTmrahmw25c9qNzBy4vis0zCF369ovqp18wNumTTuz9QT3qS8Jq+6Y8cOC3kKh75ZClATdQVQLeyTULYlxj+lsCwa+BeWReJ7DXN2QTB72GtICQsMA/T/+YjAHKrqtDczB9xHsLgUtUVlHI9W26BMzz+KKnom/igDLh8svTRJWH/Xw== X-OriginatorOrg: siemens.com X-MS-Exchange-CrossTenant-Network-Message-Id: e68050c0-5346-46f1-ef8b-08db636c16f0 X-MS-Exchange-CrossTenant-AuthSource: DB9PR10MB5404.EURPRD10.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 02 Jun 2023 13:20:09.7011 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 38ae3bcd-9579-4fd4-adda-b42e1495d55a X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: GndcKoeY/MrxAcnhCfmUoJUuy5pMFjPWQ8HO+GPSEoZ+VYDPqDVctcmpGviTAgig+LOW3XyRe2SKffUxKcueBb3ae4MUGl3P6X9GfFGiMes= X-MS-Exchange-Transport-CrossTenantHeadersStamped: GVXPR10MB5887 X-Original-Sender: christian.storm@siemens.com X-Original-Authentication-Results: gmr-mx.google.com; dkim=pass header.i=@siemens.com header.s=selector2 header.b=SYuDjkSH; arc=pass (i=1 spf=pass spfdomain=siemens.com dkim=pass dkdomain=siemens.com dmarc=pass fromdomain=siemens.com); spf=pass (google.com: domain of christian.storm@siemens.com designates 2a01:111:f400:fe1a::626 as permitted sender) smtp.mailfrom=christian.storm@siemens.com; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=siemens.com X-Original-From: Christian Storm Reply-To: Christian Storm Precedence: list Mailing-list: list swupdate@googlegroups.com; contact swupdate+owners@googlegroups.com List-ID: X-Spam-Checked-In-Group: swupdate@googlegroups.com X-Google-Group-Id: 605343134186 List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , wfx workflows do have an activation step after SWUpdate has installed the update, hence defer actions until activation is ordered by wfx. Signed-off-by: Christian Storm --- tools/swupdate-progress.c | 75 +++++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/tools/swupdate-progress.c b/tools/swupdate-progress.c index 72da5044..cba497a7 100644 --- a/tools/swupdate-progress.c +++ b/tools/swupdate-progress.c @@ -190,6 +190,31 @@ static void fill_progress_bar(char *bar, size_t size, unsigned int percent) memset(&bar[filled_len], '-', remain); } +static void reboot_device(void) +{ + sleep(5); + sync(); + if (reboot(LINUX_REBOOT_CMD_RESTART) < 0) { /* Should never happen. */ + fprintf(stdout, "Please reset the board.\n"); + } +} + +static void run_post_script(char *script, struct progress_msg *msg) +{ + char *cmd; + if (asprintf(&cmd, "%s %s", script, + msg->status == SUCCESS ? + "SUCCESS" : "FAILURE") == -1) { + fprintf(stderr, "OOM calling post-exec script\n"); + return; + } + int ret = system(cmd); + if (ret) { + fprintf(stdout, "Executed %s with error : %d\n", cmd, ret); + } + free(cmd); +} + int main(int argc, char **argv) { int connfd; @@ -206,7 +231,6 @@ int main(int argc, char **argv) int opt_r = 0; int opt_p = 0; int c; - int ret; char *script = NULL; bool wait_update = true; @@ -360,35 +384,50 @@ int main(int argc, char **argv) ? "SUCCESS" : "FAILURE"); if (script) { - char *cmd; - if (asprintf(&cmd, "%s %s", script, - msg.status == SUCCESS ? - "SUCCESS" : "FAILURE") == -1) { - fprintf(stderr, "OOM calling post-exec script\n"); - } else { - ret = system(cmd); - if (ret) { - fprintf(stdout, "Executed %s with error : %d\n", cmd, ret); - } - free(cmd); - } + run_post_script(script, &msg); } resetterm(); + #if !defined(CONFIG_SURICATTA_WFX) if (psplash_ok) psplash_progress(psplash_pipe_path, &msg); psplash_ok = 0; if ((msg.status == SUCCESS) && (msg.cur_step > 0) && opt_r) { - sleep(5); - sync(); - if (reboot(LINUX_REBOOT_CMD_RESTART) < 0) { /* It should never happen */ - fprintf(stdout, "Please reset the board.\n"); - } + reboot_device(); + } + #else + if (msg.status == SUCCESS) { + fprintf(stdout, "\nWaiting for activation.\n"); + char *buf = alloca(PSPLASH_MSG_SIZE); + snprintf(buf, PSPLASH_MSG_SIZE - 1, "MSG Waiting for activation."); + psplash_write_fifo(psplash_pipe_path, buf); + } + if (msg.status == FAILURE) { + if (psplash_ok) + psplash_progress(psplash_pipe_path, &msg); + psplash_ok = 0; } + #endif wait_update = true; break; case DONE: fprintf(stdout, "\nDONE.\n\n"); break; + case PROGRESS: + #if defined(CONFIG_SURICATTA_WFX) + if (strcasestr(msg.info, "\"state\": \"ACTIVATING\"") && + strcasestr(msg.info, "\"progress\": 100")) { + msg.status = SUCCESS; + if (psplash_ok) + psplash_progress(psplash_pipe_path, &msg); + psplash_ok = 0; + if (opt_r && strcasestr(msg.info, "firmware")) { + reboot_device(); + break; + } + fprintf(stdout, "Don't know how to activate this update, doing nothing.\n"); + } + #endif + break; default: break; }